Commit 570e82110c9916b21555a32094eaed769b84354f
Merge branch 'feature/dataflow' of http://gitlab.qgutech.com/tianqiang/qx-common…
… into feature/dataflow
Showing
35 changed files
with
4961 additions
and
517 deletions
1 | { | 1 | { |
2 | "name": "@qx/common", | 2 | "name": "@qx/common", |
3 | - "version": "3.0.0-alpha.45", | 3 | + "version": "3.0.0-alpha.47", |
4 | "description": "A react library developed with dumi", | 4 | "description": "A react library developed with dumi", |
5 | "license": "MIT", | 5 | "license": "MIT", |
6 | "module": "dist/index.js", | 6 | "module": "dist/index.js", |
@@ -51,6 +51,9 @@ | @@ -51,6 +51,9 @@ | ||
51 | "classnames": "^2.3.2", | 51 | "classnames": "^2.3.2", |
52 | "codemirror": "5.65.8", | 52 | "codemirror": "5.65.8", |
53 | "dayjs": "^1.11.9", | 53 | "dayjs": "^1.11.9", |
54 | + "hot-formula-parser": "^4.0.0", | ||
55 | + "js-beautify": "^1.14.9", | ||
56 | + "jshint": "^2.13.6", | ||
54 | "lodash-es": "^4.17.21", | 57 | "lodash-es": "^4.17.21", |
55 | "rc-virtual-list": "^3.4.13", | 58 | "rc-virtual-list": "^3.4.13", |
56 | "react-codemirror2": "^7.2.1" | 59 | "react-codemirror2": "^7.2.1" |
@@ -59,6 +62,10 @@ | @@ -59,6 +62,10 @@ | ||
59 | "@commitlint/cli": "^17.1.2", | 62 | "@commitlint/cli": "^17.1.2", |
60 | "@commitlint/config-conventional": "^17.1.0", | 63 | "@commitlint/config-conventional": "^17.1.0", |
61 | "@qx/utils": "0.0.58", | 64 | "@qx/utils": "0.0.58", |
65 | + "@types/codemirror": "^5.60.10", | ||
66 | + "@types/hot-formula-parser": "^4.0.1", | ||
67 | + "@types/js-beautify": "^1.14.1", | ||
68 | + "@types/jshint": "^2.12.2", | ||
62 | "@types/lodash-es": "^4.17.8", | 69 | "@types/lodash-es": "^4.17.8", |
63 | "@types/react": "^18.0.0", | 70 | "@types/react": "^18.0.0", |
64 | "@types/react-dom": "^18.0.0", | 71 | "@types/react-dom": "^18.0.0", |
src/constant/index.ts
0 → 100644
1 | +export enum FIELD_TYPE_PROPS { | ||
2 | + EMPTY = '', | ||
3 | + // 文本 | ||
4 | + TEXT = 'TEXT', | ||
5 | + STRING = 'STRING', | ||
6 | + | ||
7 | + // 日期 | ||
8 | + DATE = 'DATE', | ||
9 | + DATE_TIME = 'DATE_TIME', | ||
10 | + // 人员 | ||
11 | + USER = 'USER', | ||
12 | + USER_MULTI = 'USER_MULTI', | ||
13 | + // 部门 | ||
14 | + ORG = 'ORG', | ||
15 | + ORG_MULTI = 'ORG_MULTI', | ||
16 | + // 数字 | ||
17 | + NUM = 'NUM', | ||
18 | + // 布尔 | ||
19 | + BOOL = 'BOOL', | ||
20 | + // 枚举 | ||
21 | + ENUM = 'ENUM', | ||
22 | + ENUM_MULTI = 'ENUM_MULTI', | ||
23 | + // 文件 | ||
24 | + FILE = 'FILE', | ||
25 | + | ||
26 | + // 公式:数值类 | ||
27 | + FORMULA = 'FORMULA', | ||
28 | + | ||
29 | + DOUBLE = 'DOUBLE', | ||
30 | + INTEGER = 'INTEGER', | ||
31 | + DECIMAL = 'DECIMAL', | ||
32 | + PERCENT = 'PERCENT', | ||
33 | + | ||
34 | + YEAR = 'YEAR', | ||
35 | + YEAR_MONTH = 'YEAR_MONTH', | ||
36 | + YEAR_HOUR = 'YEAR_HOUR', | ||
37 | + YEAR_DATE = 'YEAR_DATE', | ||
38 | + YEAR_MIN = 'YEAR_MIN', | ||
39 | + YEAR_SEC = 'YEAR_SEC', | ||
40 | + HOUR = 'HOUR', | ||
41 | + HOUR_MIN = 'HOUR_MIN', | ||
42 | + HOUR_SEC = 'HOUR_SEC', | ||
43 | + | ||
44 | + TREE = 'TREE', | ||
45 | + REL = 'REL', | ||
46 | + REL_MULTI = 'REL_MULTI', | ||
47 | + REL_FIELD = 'REL_FIELD', | ||
48 | + | ||
49 | + TABLE = 'TABLE', | ||
50 | + | ||
51 | + // 流程专用 | ||
52 | + FLOW_WF_APRV_USR = 'FLOW_WF_APRV_USR', | ||
53 | + FLOW_WF_DQ_MODEL = 'FLOW_WF_DQ_MODEL', | ||
54 | + FLOW_WF_RECORD = 'FLOW_WF_RECORD', | ||
55 | + | ||
56 | + //参数专用 | ||
57 | + OBJECT = 'OBJECT', | ||
58 | + ARRAY = 'ARRAY', | ||
59 | + | ||
60 | + // 组合文本,用于文本和变量组合 | ||
61 | + COMBINED_TEXT = 'COMBINED_TEXT', | ||
62 | + // 时间 | ||
63 | + TIME = 'TIME', | ||
64 | +} | ||
65 | + | ||
66 | +export const formatEnum: Record<string, string> = { | ||
67 | + YEAR: 'YYYY', | ||
68 | + YEAR_MONTH: 'YYYY-MM', | ||
69 | + YEAR_DATE: 'YYYY-MM-DD', | ||
70 | + YEAR_HOUR: 'YYYY-MM-DD HH:00', | ||
71 | + YEAR_MIN: 'YYYY-MM-DD HH:mm', | ||
72 | + YEAR_SEC: 'YYYY-MM-DD HH:mm:ss', | ||
73 | + HOUR_MIN: 'HH:mm', | ||
74 | + HOUR_SEC: 'HH:mm:ss', | ||
75 | +}; |
1 | -export * from './qx-filter-condition'; | ||
2 | -export * from './qx-parameter-setting'; | ||
3 | -export * from './qx-sort-condition'; | ||
4 | -export * from './qx-base-icon'; | ||
5 | -export * from './qx-tags-input'; | ||
6 | -export * from './qx-user-selector'; | ||
7 | -export * from './qx-org-selector'; | ||
8 | -export * from './qx-form-select'; | ||
9 | -export * from './qx-pos-selector'; | ||
10 | -export * from './qx-role-selector'; | ||
11 | export * from './qx-app-selector'; | 1 | export * from './qx-app-selector'; |
12 | -export * from './utils'; | ||
13 | -export * from './qx-field'; | ||
14 | -export * from './qx-field-setter'; | ||
15 | export * from './qx-base-condition'; | 2 | export * from './qx-base-condition'; |
3 | +export * from './qx-base-icon'; | ||
16 | export * from './qx-btn'; | 4 | export * from './qx-btn'; |
17 | -export * from './qx-progress'; | ||
18 | -export * from './qx-search-input'; | 5 | +export * from './qx-condition'; |
19 | export * from './qx-dynamic-component'; | 6 | export * from './qx-dynamic-component'; |
20 | -export * from './qx-widget-icon'; | 7 | +export * from './qx-field'; |
8 | +export * from './qx-field-setter'; | ||
9 | +export * from './qx-filter-condition'; | ||
21 | export * from './qx-flow-node-selector'; | 10 | export * from './qx-flow-node-selector'; |
11 | +export * from './qx-form-select'; | ||
12 | +export * from './qx-function-operation'; | ||
22 | export * from './qx-icon-selector'; | 13 | export * from './qx-icon-selector'; |
23 | -export * from './qx-condition'; | ||
24 | - | ||
25 | - | 14 | +export * from './qx-org-selector'; |
15 | +export * from './qx-parameter-setting'; | ||
16 | +export * from './qx-pos-selector'; | ||
17 | +export * from './qx-progress'; | ||
18 | +export * from './qx-role-selector'; | ||
19 | +export * from './qx-search-input'; | ||
20 | +export * from './qx-sort-condition'; | ||
21 | +export * from './qx-tags-input'; | ||
22 | +export * from './qx-user-selector'; | ||
23 | +export * from './qx-widget-icon'; | ||
24 | +export * from './utils'; |
src/qx-code-editor-old/functions.ts
renamed from
src/qx-code-editor/functions.ts
src/qx-code-editor-old/index.less
renamed from
src/qx-code-editor/index.less
src/qx-code-editor-old/index.tsx
0 → 100644
1 | +import React, { useEffect, useState } from 'react'; | ||
2 | +import { UnControlled as CodeMirror } from 'react-codemirror2'; | ||
3 | +import { cloneDeep, flatten, sortBy, size, isEqual, forEach, debounce } from 'lodash-es'; | ||
4 | +import type { VariableMappingProps } from '../qx-field-setter'; | ||
5 | +import funObjects from './functions'; | ||
6 | +import 'codemirror/lib/codemirror.css'; | ||
7 | +import 'codemirror/addon/hint/show-hint'; | ||
8 | +import 'codemirror/addon/hint/show-hint.css'; | ||
9 | +import { CloseCircleFilled } from '@ant-design/icons'; | ||
10 | +import './index.less'; | ||
11 | + | ||
12 | +/** | ||
13 | + * 填充到指定位数 | ||
14 | + * eg:000000000 | ||
15 | + * | ||
16 | + * @param val 原始值 | ||
17 | + * @param length 填充后长度 | ||
18 | + * @param fill 填充值 | ||
19 | + */ | ||
20 | +const strFill = (val: string | number, length: number, fill: string | number) => { | ||
21 | + return val.toString().padStart(length, fill.toString()); | ||
22 | +}; | ||
23 | + | ||
24 | +type PositionProps = { | ||
25 | + s: number; | ||
26 | + e: number; | ||
27 | +}; | ||
28 | + | ||
29 | +export type VariableProps = { | ||
30 | + variable: string; | ||
31 | + pos: PositionProps; | ||
32 | +}; | ||
33 | + | ||
34 | +/** | ||
35 | + * 字符串中提取变量(${xxx}) | ||
36 | + * @param code | ||
37 | + */ | ||
38 | +export const getAllVariable = (code: string) => { | ||
39 | + let codeLocal: string = cloneDeep(code); | ||
40 | + if (!codeLocal) { | ||
41 | + return []; | ||
42 | + } | ||
43 | + const variables: VariableProps[] = []; | ||
44 | + | ||
45 | + function loopGet() { | ||
46 | + const pos: PositionProps = { | ||
47 | + s: codeLocal.indexOf('${'), | ||
48 | + e: codeLocal.indexOf('}'), | ||
49 | + }; | ||
50 | + if (pos.s === -1 && pos.e === -1) { | ||
51 | + return; | ||
52 | + } | ||
53 | + const variable = codeLocal.slice(pos.s, pos.e + 1); | ||
54 | + variables.push({ variable, pos }); | ||
55 | + codeLocal = codeLocal.replace(variable, strFill(0, variable.length, 0)); | ||
56 | + | ||
57 | + loopGet(); | ||
58 | + } | ||
59 | + | ||
60 | + loopGet(); | ||
61 | + return variables; | ||
62 | +}; | ||
63 | + | ||
64 | +const domTagGen = (variable: string, text: string, color?: string) => { | ||
65 | + // tag html | ||
66 | + const dom = document.createElement('span'); | ||
67 | + // dom.className = 'ant-tag tag'; | ||
68 | + dom.style.background = '#c9dffc'; | ||
69 | + dom.style.borderRadius = '2px'; | ||
70 | + dom.style.margin = '0 1px'; | ||
71 | + dom.style.padding = '1px 3px'; | ||
72 | + dom.style.fontSize = '96%'; | ||
73 | + if (color) { | ||
74 | + dom.style.color = color; | ||
75 | + } | ||
76 | + dom.setAttribute('data-widget', variable); | ||
77 | + dom.innerHTML = text; | ||
78 | + return dom; | ||
79 | +}; | ||
80 | + | ||
81 | +type CodeHighLightProps = { | ||
82 | + // eg: 'hello ${v1} ${v2}!' | ||
83 | + value: string; | ||
84 | + // eg: {'${v1}': 'hehe', '${v2}': 'enen'} | ||
85 | + variableObj: Record<string, string> | undefined; | ||
86 | + onChange: (val: any) => void; | ||
87 | + className?: any; | ||
88 | + newVariable?: VariableMappingProps; | ||
89 | + style?: any; | ||
90 | + autofocus?: boolean; | ||
91 | + focusFunHandler?: (str: string) => void; | ||
92 | + readOnly?: boolean; | ||
93 | + // 是否使用函数(使用,则执行针对函数关键词的高亮匹配处理) | ||
94 | + isUseFun?: boolean; | ||
95 | + from?: string; | ||
96 | + resetValue?: string; | ||
97 | + allowClear?: boolean | undefined; | ||
98 | +}; | ||
99 | + | ||
100 | +/** | ||
101 | + * 变量高亮 | ||
102 | + * const inputRef = React.useRef<any>(null); | ||
103 | + * `const cm = inputRef.current.editor;` | ||
104 | + * | ||
105 | + * @param props | ||
106 | + * @constructor | ||
107 | + */ | ||
108 | +const CodeEditor: React.FC<CodeHighLightProps> = ({ | ||
109 | + value, | ||
110 | + variableObj, | ||
111 | + newVariable, | ||
112 | + className, | ||
113 | + onChange, | ||
114 | + autofocus, | ||
115 | + focusFunHandler, | ||
116 | + readOnly, | ||
117 | + isUseFun, | ||
118 | + from, | ||
119 | + resetValue, | ||
120 | + allowClear, | ||
121 | +}) => { | ||
122 | + const [valueLocal, setValueLocal] = useState<string>(); | ||
123 | + // 变量值、名称映射关系 | ||
124 | + const [variableObjLocal, setVariableObjLocal] = useState<Record<string, string>>({}); | ||
125 | + const [codeEditor, setCodeEditor] = useState<any>(); | ||
126 | + const [isInitDone, setIsInitDone] = useState<boolean>(false); | ||
127 | + const [focusFun, setFocusFun] = useState<string>(); | ||
128 | + | ||
129 | + const funObjs = flatten(funObjects.map((item: any) => item.children)); | ||
130 | + const funObjSimple: any = {}; | ||
131 | + funObjs.map((item) => Object.assign(funObjSimple, { [item.title]: item.title })); | ||
132 | + // 以变量字符长度排序,确保`DATAIF`比`IF`优先匹配 | ||
133 | + const funObjsSort = sortBy(funObjs, function (o) { | ||
134 | + return 0 - o.title.length; | ||
135 | + }); | ||
136 | + | ||
137 | + useEffect(() => { | ||
138 | + if (value && variableObj) { | ||
139 | + // 限制初始值只设置一次 | ||
140 | + if (!isInitDone) { | ||
141 | + setValueLocal(value); | ||
142 | + } | ||
143 | + | ||
144 | + if (size(variableObj) > 0) { | ||
145 | + setVariableObjLocal(variableObj); | ||
146 | + } | ||
147 | + } | ||
148 | + }, [value, variableObj]); | ||
149 | + | ||
150 | + useEffect(() => { | ||
151 | + if (typeof focusFunHandler === 'function') { | ||
152 | + focusFunHandler(focusFun || ''); | ||
153 | + } | ||
154 | + }, [focusFun]); | ||
155 | + | ||
156 | + const cmUtils = { | ||
157 | + // 插入变量 | ||
158 | + insert: (editor: any, variable: string, isFormula: boolean = false) => { | ||
159 | + if (!editor) { | ||
160 | + return; | ||
161 | + } | ||
162 | + // 光标位置插入新值(变量) | ||
163 | + editor.replaceSelection(variable); | ||
164 | + cmUtils.variableRender(editor); | ||
165 | + | ||
166 | + // "公式"类型变量时 | ||
167 | + if (isFormula) { | ||
168 | + // 光标位置固定插入内容 | ||
169 | + editor.replaceSelection('()'); | ||
170 | + // 光标左移 | ||
171 | + codeEditor.execCommand('goCharLeft'); | ||
172 | + } | ||
173 | + // 让编辑器聚集 | ||
174 | + editor.focus(); | ||
175 | + }, | ||
176 | + | ||
177 | + // 替换变量(变量示例:${xxx}) | ||
178 | + variableRender: (editor: any) => { | ||
179 | + if (!editor) { | ||
180 | + return; | ||
181 | + } | ||
182 | + | ||
183 | + const code = cloneDeep(editor.getValue()); | ||
184 | + // 换行分隔 | ||
185 | + const codeArr = code.split('\n'); | ||
186 | + const variReplace = (cm: any, line: number) => { | ||
187 | + const sIndex = codeArr[line].indexOf('${'); | ||
188 | + const eIndex = codeArr[line].indexOf('}'); | ||
189 | + if (sIndex === -1 && eIndex === -1) { | ||
190 | + return; | ||
191 | + } | ||
192 | + | ||
193 | + const variable = codeArr[line].slice(sIndex, eIndex + 1); | ||
194 | + const text: string = variableObjLocal[variable] || '(已缺失)'; | ||
195 | + const hasError: boolean = variableObjLocal[variable] === undefined; | ||
196 | + | ||
197 | + cm?.markText( | ||
198 | + { | ||
199 | + line: line, | ||
200 | + ch: sIndex, | ||
201 | + }, | ||
202 | + { | ||
203 | + line: line, | ||
204 | + ch: eIndex + 1, | ||
205 | + }, | ||
206 | + { | ||
207 | + replacedWith: domTagGen(variable, text, hasError ? 'red' : '#026be1'), | ||
208 | + }, | ||
209 | + ); | ||
210 | + codeArr[line] = codeArr[line].replace(variable, strFill(0, variable.length, 0)); | ||
211 | + | ||
212 | + variReplace(cm, line); | ||
213 | + }; | ||
214 | + | ||
215 | + const funReplace = (cm: any, line: number, funStr: string) => { | ||
216 | + const sIndex = codeArr[line].indexOf(funStr); | ||
217 | + const eIndex = sIndex + funStr.length; | ||
218 | + if (sIndex === -1) { | ||
219 | + return; | ||
220 | + } | ||
221 | + const variable = codeArr[line].slice(sIndex, eIndex); | ||
222 | + const text: string = funObjSimple[variable] || '(已缺失)'; | ||
223 | + const hasError: boolean = funObjSimple[variable] === undefined; | ||
224 | + | ||
225 | + cm?.markText( | ||
226 | + { | ||
227 | + line: line, | ||
228 | + ch: sIndex, | ||
229 | + }, | ||
230 | + { | ||
231 | + line: line, | ||
232 | + ch: eIndex, | ||
233 | + }, | ||
234 | + { | ||
235 | + replacedWith: domTagGen(variable, text, hasError ? 'red' : '#026be1'), | ||
236 | + }, | ||
237 | + ); | ||
238 | + codeArr[line] = codeArr[line].replace(variable, strFill(0, variable.length, 0)); | ||
239 | + | ||
240 | + funReplace(cm, line, funStr); | ||
241 | + }; | ||
242 | + | ||
243 | + for (let i = 0; i < editor.doc.size; i++) { | ||
244 | + variReplace(editor, i); | ||
245 | + if (Boolean(isUseFun)) { | ||
246 | + funObjsSort.map((v) => funReplace(editor, i, v.title)); | ||
247 | + } | ||
248 | + } | ||
249 | + }, | ||
250 | + }; | ||
251 | + | ||
252 | + useEffect(() => { | ||
253 | + if (!codeEditor || !newVariable) { | ||
254 | + return; | ||
255 | + } | ||
256 | + | ||
257 | + setVariableObjLocal(() => { | ||
258 | + return Object.assign(variableObjLocal, { | ||
259 | + [newVariable.key]: newVariable.name, | ||
260 | + }); | ||
261 | + }); | ||
262 | + | ||
263 | + cmUtils.insert(codeEditor, newVariable.key, newVariable.type === 'fun'); | ||
264 | + }, [newVariable, codeEditor]); | ||
265 | + | ||
266 | + useEffect(() => { | ||
267 | + // 消息提醒 新增模板时 触发方式变为"定时触发"时 需要将消息内容中的表单字段值清除 | ||
268 | + if (resetValue) { | ||
269 | + setValueLocal(resetValue); | ||
270 | + } | ||
271 | + // console.log('--resetValue--',resetValue) | ||
272 | + }, [resetValue]); | ||
273 | + | ||
274 | + const onValueChange = (editor: any, data: any, val: string) => { | ||
275 | + setCodeEditor(editor); | ||
276 | + cmUtils.variableRender(editor); | ||
277 | + | ||
278 | + // TODO 默认值bug修复 暂时注释 | ||
279 | + // const isSame = isEqual(valueLocal, val); | ||
280 | + // if (isSame) { | ||
281 | + // return; | ||
282 | + // } | ||
283 | + | ||
284 | + setIsInitDone(true); | ||
285 | + onChange(val); | ||
286 | + }; | ||
287 | + | ||
288 | + useEffect(() => { | ||
289 | + if (variableObjLocal) { | ||
290 | + cmUtils.variableRender(codeEditor); | ||
291 | + } | ||
292 | + }, [variableObjLocal]); | ||
293 | + | ||
294 | + // 字符串反转 | ||
295 | + const strReverse = (str: string) => { | ||
296 | + return str.split('').reverse().join(''); | ||
297 | + }; | ||
298 | + | ||
299 | + /** | ||
300 | + * 查找编辑器光标所对应函数,设置其函数说明信息 | ||
301 | + * todo 待优化,规则不够完善 | ||
302 | + * @param editor | ||
303 | + */ | ||
304 | + const findFocusFun = (editor: any) => { | ||
305 | + const cursor = editor.getCursor(); | ||
306 | + const lineStr = editor.getLine(cursor.line); | ||
307 | + const lineStrPart = lineStr.substring(0, cursor.ch); | ||
308 | + const lineStrPartRev = strReverse(lineStrPart); | ||
309 | + | ||
310 | + let flag: boolean = false; | ||
311 | + let funName: string = ''; | ||
312 | + | ||
313 | + forEach(funObjsSort, (v) => { | ||
314 | + if (flag) { | ||
315 | + return; | ||
316 | + } | ||
317 | + const i = (lineStrPartRev || '').indexOf(strReverse(v.title)); | ||
318 | + if (i > -1) { | ||
319 | + flag = true; | ||
320 | + funName = v.title; | ||
321 | + return; | ||
322 | + } | ||
323 | + }); | ||
324 | + return funName; | ||
325 | + }; | ||
326 | + | ||
327 | + const handleClear = () => { | ||
328 | + // 清除组件内容 | ||
329 | + codeEditor.doc.setValue(''); | ||
330 | + }; | ||
331 | + | ||
332 | + return ( | ||
333 | + //这里className中的qx-copy-send-cm 是抄送节点--消息内容专用的样式,如果有必要,可将className回滚设置为'qx-formula-cm ' + className | ||
334 | + <div | ||
335 | + className={ | ||
336 | + from === 'copySend' ? 'qx-copy-send-cm ' + className : 'qx-formula-cm ' + className | ||
337 | + } | ||
338 | + style={{ height: '100%', padding: 0 }} | ||
339 | + > | ||
340 | + <CodeMirror | ||
341 | + value={(valueLocal || '').toString()} | ||
342 | + editorDidMount={(editor) => setCodeEditor(editor)} | ||
343 | + // onCursorActivity={(e) => console.log('e', e)} | ||
344 | + // onCursorActivity={(e) => e?.showHint()} //没有会报错 | ||
345 | + options={{ | ||
346 | + // mode: 'text/html', | ||
347 | + lineNumbers: false, | ||
348 | + autofocus, | ||
349 | + readOnly: Boolean(readOnly), | ||
350 | + cursorHeight: Boolean(readOnly) ? 0 : 'auto', | ||
351 | + // 滚动(false,默认)或自动换行 | ||
352 | + lineWrapping: true, | ||
353 | + }} | ||
354 | + onChange={debounce(onValueChange, 300)} | ||
355 | + onCursor={(editor) => { | ||
356 | + const funName = findFocusFun(editor); | ||
357 | + setFocusFun(funName); | ||
358 | + }} | ||
359 | + /> | ||
360 | + {allowClear && value && ( | ||
361 | + <CloseCircleFilled className={'qx-field-setter__clear'} onClick={handleClear} /> | ||
362 | + )} | ||
363 | + </div> | ||
364 | + ); | ||
365 | +}; | ||
366 | + | ||
367 | +export default CodeEditor; |
1 | -import React, { useEffect, useState } from 'react'; | ||
2 | -import { UnControlled as CodeMirror } from 'react-codemirror2'; | ||
3 | -import { cloneDeep, flatten, sortBy, size, isEqual, forEach, debounce } from 'lodash-es'; | ||
4 | -import type { VariableMappingProps } from '../qx-field-setter'; | ||
5 | -import funObjects from './functions'; | 1 | +import { CloseCircleFilled } from '@ant-design/icons'; |
2 | +import _ from 'lodash-es'; | ||
3 | +import React, { useEffect, useImperativeHandle, useRef, useState } from 'react'; | ||
4 | +import ReactDOM from 'react-dom'; | ||
5 | +import { handleHighlight } from '../qx-function-operation/components/var-picker'; | ||
6 | +import { | ||
7 | + domTagGen, | ||
8 | + flatten, | ||
9 | + reverseStr, | ||
10 | + strFill, | ||
11 | + widgetMapping, | ||
12 | +} from '../qx-function-operation/util'; | ||
13 | +import './style.less'; | ||
14 | + | ||
15 | +import Cm from 'codemirror'; | ||
6 | import 'codemirror/lib/codemirror.css'; | 16 | import 'codemirror/lib/codemirror.css'; |
7 | -import 'codemirror/addon/hint/show-hint'; | 17 | +import 'codemirror/lib/codemirror.js'; |
18 | +import { UnControlled as CodeMirror } from 'react-codemirror2'; | ||
19 | +import './plugins/cm-extensions'; | ||
20 | + | ||
21 | +import 'codemirror/mode/javascript/javascript.js'; | ||
22 | +import 'codemirror/theme/idea.css'; | ||
23 | + | ||
8 | import 'codemirror/addon/hint/show-hint.css'; | 24 | import 'codemirror/addon/hint/show-hint.css'; |
9 | -import { CloseCircleFilled } from '@ant-design/icons'; | ||
10 | -import './index.less'; | 25 | +import 'codemirror/addon/hint/show-hint.js'; // ctrl代码提示补全 |
11 | 26 | ||
12 | -/** | ||
13 | - * 填充到指定位数 | ||
14 | - * eg:000000000 | ||
15 | - * | ||
16 | - * @param val 原始值 | ||
17 | - * @param length 填充后长度 | ||
18 | - * @param fill 填充值 | ||
19 | - */ | ||
20 | -const strFill = (val: string | number, length: number, fill: string | number) => { | ||
21 | - return val.toString().padStart(length, fill.toString()); | ||
22 | -}; | 27 | +import 'codemirror/addon/selection/active-line.js'; // 光标代码高亮 |
28 | + | ||
29 | +import 'codemirror/addon/edit/matchbrackets.js'; | ||
30 | +import 'codemirror/addon/lint/javascript-lint.js'; | ||
31 | +import 'codemirror/addon/lint/lint.css'; | ||
32 | +import 'codemirror/addon/lint/lint.js'; // 错误提示 | ||
33 | +import beautify from 'js-beautify'; | ||
34 | +import { JSHINT } from 'jshint'; | ||
35 | +window.JSHINT = JSHINT; | ||
23 | 36 | ||
24 | type PositionProps = { | 37 | type PositionProps = { |
25 | s: number; | 38 | s: number; |
@@ -36,7 +49,7 @@ export type VariableProps = { | @@ -36,7 +49,7 @@ export type VariableProps = { | ||
36 | * @param code | 49 | * @param code |
37 | */ | 50 | */ |
38 | export const getAllVariable = (code: string) => { | 51 | export const getAllVariable = (code: string) => { |
39 | - let codeLocal: string = cloneDeep(code); | 52 | + let codeLocal: string = _.clone(code); |
40 | if (!codeLocal) { | 53 | if (!codeLocal) { |
41 | return []; | 54 | return []; |
42 | } | 55 | } |
@@ -61,307 +74,655 @@ export const getAllVariable = (code: string) => { | @@ -61,307 +74,655 @@ export const getAllVariable = (code: string) => { | ||
61 | return variables; | 74 | return variables; |
62 | }; | 75 | }; |
63 | 76 | ||
64 | -const domTagGen = (variable: string, text: string, color?: string) => { | ||
65 | - // tag html | ||
66 | - const dom = document.createElement('span'); | ||
67 | - // dom.className = 'ant-tag tag'; | ||
68 | - dom.style.background = '#c9dffc'; | ||
69 | - dom.style.borderRadius = '2px'; | ||
70 | - dom.style.margin = '0 1px'; | ||
71 | - dom.style.padding = '1px 3px'; | ||
72 | - dom.style.fontSize = '96%'; | ||
73 | - if (color) { | ||
74 | - dom.style.color = color; | 77 | +const getWidgetByType = (type?: string) => { |
78 | + let res: string; | ||
79 | + switch (type) { | ||
80 | + case 'TEXT': | ||
81 | + res = 'qxInput'; | ||
82 | + break; | ||
83 | + case 'NUM': | ||
84 | + res = 'qxNumber'; | ||
85 | + break; | ||
86 | + default: | ||
87 | + res = ''; | ||
75 | } | 88 | } |
76 | - dom.setAttribute('data-widget', variable); | ||
77 | - dom.innerHTML = text; | ||
78 | - return dom; | 89 | + // @ts-ignore |
90 | + return res; | ||
91 | +}; | ||
92 | + | ||
93 | +type VarProps = { | ||
94 | + key: string; | ||
95 | + name?: string; | ||
96 | + type?: string; | ||
79 | }; | 97 | }; |
80 | 98 | ||
81 | -type CodeHighLightProps = { | 99 | +type EditorProps = { |
100 | + cRef: any; | ||
82 | // eg: 'hello ${v1} ${v2}!' | 101 | // eg: 'hello ${v1} ${v2}!' |
83 | value: string; | 102 | value: string; |
84 | // eg: {'${v1}': 'hehe', '${v2}': 'enen'} | 103 | // eg: {'${v1}': 'hehe', '${v2}': 'enen'} |
85 | - variableObj: Record<string, string> | undefined; | ||
86 | - onChange: (val: any) => void; | 104 | + // variableObj: Record<string, string> | undefined; |
105 | + isUseFun: boolean; | ||
106 | + autofocus?: boolean; | ||
107 | + newVariable: VarProps; | ||
108 | + funcDataList: any[]; | ||
109 | + varDataList: any[]; | ||
87 | className?: any; | 110 | className?: any; |
88 | - newVariable?: VariableMappingProps; | ||
89 | style?: any; | 111 | style?: any; |
90 | - autofocus?: boolean; | ||
91 | - focusFunHandler?: (str: string) => void; | ||
92 | readOnly?: boolean; | 112 | readOnly?: boolean; |
93 | - // 是否使用函数(使用,则执行针对函数关键词的高亮匹配处理) | ||
94 | - isUseFun?: boolean; | ||
95 | from?: string; | 113 | from?: string; |
96 | resetValue?: string; | 114 | resetValue?: string; |
97 | allowClear?: boolean | undefined; | 115 | allowClear?: boolean | undefined; |
116 | + onChange: (code: string) => void; | ||
117 | + onFocusFunc: (funcName: string | null) => void; | ||
98 | }; | 118 | }; |
99 | 119 | ||
100 | -/** | ||
101 | - * 变量高亮 | ||
102 | - * const inputRef = React.useRef<any>(null); | ||
103 | - * `const cm = inputRef.current.editor;` | ||
104 | - * | ||
105 | - * @param props | ||
106 | - * @constructor | ||
107 | - */ | ||
108 | -const CodeEditor: React.FC<CodeHighLightProps> = ({ | 120 | +const CodeEditor: React.FC<EditorProps> = ({ |
121 | + cRef, | ||
122 | + funcDataList, | ||
123 | + varDataList, | ||
124 | + isUseFun, | ||
125 | + autofocus, | ||
126 | + readOnly, | ||
127 | + allowClear, | ||
128 | + resetValue, | ||
109 | value, | 129 | value, |
110 | - variableObj, | ||
111 | newVariable, | 130 | newVariable, |
131 | + from, | ||
112 | className, | 132 | className, |
113 | onChange, | 133 | onChange, |
114 | - autofocus, | ||
115 | - focusFunHandler, | ||
116 | - readOnly, | ||
117 | - isUseFun, | ||
118 | - from, | ||
119 | - resetValue, | ||
120 | - allowClear, | 134 | + onFocusFunc, |
121 | }) => { | 135 | }) => { |
122 | - const [valueLocal, setValueLocal] = useState<string>(); | ||
123 | - // 变量值、名称映射关系 | ||
124 | - const [variableObjLocal, setVariableObjLocal] = useState<Record<string, string>>({}); | ||
125 | - const [codeEditor, setCodeEditor] = useState<any>(); | ||
126 | const [isInitDone, setIsInitDone] = useState<boolean>(false); | 136 | const [isInitDone, setIsInitDone] = useState<boolean>(false); |
127 | - const [focusFun, setFocusFun] = useState<string>(); | ||
128 | - | ||
129 | - const funObjs = flatten(funObjects.map((item: any) => item.children)); | ||
130 | - const funObjSimple: any = {}; | ||
131 | - funObjs.map((item) => Object.assign(funObjSimple, { [item.title]: item.title })); | ||
132 | - // 以变量字符长度排序,确保`DATAIF`比`IF`优先匹配 | ||
133 | - const funObjsSort = sortBy(funObjs, function (o) { | ||
134 | - return 0 - o.title.length; | ||
135 | - }); | 137 | + const [valueLocal, setValueLocal] = useState<string>(); |
138 | + const [editor, setEditor] = useState<any>(null); | ||
139 | + const formatRef = useRef(false); // 代码格式化引起的变化 | ||
140 | + // 定义匹配括号的正则表达式 | ||
141 | + const bracketRegex = /[(){}\[\]]/g; | ||
136 | 142 | ||
137 | useEffect(() => { | 143 | useEffect(() => { |
138 | - if (value && variableObj) { | ||
139 | - // 限制初始值只设置一次 | ||
140 | - if (!isInitDone) { | ||
141 | - setValueLocal(value); | 144 | + if (!editor) return; |
145 | + editor.on('mousedown', (_cm: any, event: any) => { | ||
146 | + // 1 = 鼠标左键,2 = 鼠标右键,4 = 鼠标中键 | ||
147 | + if (event.buttons === 1) { | ||
148 | + const element = event.target as Element; | ||
149 | + if (element.classList.contains('funcName')) { | ||
150 | + const funcName = element.getAttribute('data-widget'); | ||
151 | + const funcData = funcDataListFlatten.current.find( | ||
152 | + (data: any) => data.funcNameEg === funcName, | ||
153 | + ); | ||
154 | + if (funcData && typeof onFocusFunc === 'function') { | ||
155 | + onFocusFunc(funcData); | ||
156 | + } | ||
157 | + } | ||
158 | + } | ||
159 | + }); | ||
160 | + // 添加占位符功能 | ||
161 | + const placeholderText = editor.getOption('placeholder'); | ||
162 | + const placeholderElement = document.createElement('div'); | ||
163 | + placeholderElement.className = 'CodeMirror-placeholder'; | ||
164 | + placeholderElement.textContent = placeholderText; | ||
165 | + editor.getWrapperElement().appendChild(placeholderElement); | ||
166 | + | ||
167 | + const handleChange = () => { | ||
168 | + const value = editor.getValue(); | ||
169 | + if (value?.length) { | ||
170 | + placeholderElement.style.display = 'none'; | ||
171 | + } else { | ||
172 | + placeholderElement.style.display = 'block'; | ||
142 | } | 173 | } |
174 | + const lastLine = editor.lastLine(); | ||
175 | + editor.scrollIntoView( | ||
176 | + { line: lastLine, ch: 0, margin: { bottom: 50 } }, | ||
177 | + 100, | ||
178 | + ); | ||
179 | + | ||
180 | + setTimeout(() => { | ||
181 | + // 获取编辑器的包装元素 | ||
182 | + const wrapperElements = | ||
183 | + document.getElementsByClassName('CodeMirror-widget'); | ||
184 | + // 将 cm-ignore-events 的值设为 false | ||
185 | + for (let i = 0; i < (wrapperElements || []).length; i++) { | ||
186 | + wrapperElements[i].setAttribute('cm-ignore-events', 'false'); | ||
187 | + } | ||
188 | + }, 100); | ||
189 | + | ||
190 | + // 在内容中查找匹配的括号 | ||
191 | + let match: any; | ||
192 | + while ((match = bracketRegex.exec(value))) { | ||
193 | + const from = { line: 0, ch: match.index }; | ||
194 | + const to = { line: 0, ch: match.index + 1 }; | ||
143 | 195 | ||
144 | - if (size(variableObj) > 0) { | ||
145 | - setVariableObjLocal(variableObj); | 196 | + // 标记匹配的括号并应用 CSS 类名 |
197 | + editor.markText(from, to, { className: 'bracket-mark' }); | ||
146 | } | 198 | } |
147 | - } | ||
148 | - }, [value, variableObj]); | 199 | + |
200 | + // const processedContent = value.replace(/(\(|\)|\[|\]|\{|\})/g, '<span class="custom-paren">$1</span>'); | ||
201 | + // editor.setValue(processedContent); | ||
202 | + }; | ||
203 | + handleChange(); | ||
204 | + | ||
205 | + // 监听编辑器的输入事件 | ||
206 | + editor.on('change', handleChange); | ||
207 | + }, [editor]); | ||
208 | + | ||
209 | + // 扁平化dataSource | ||
210 | + const funcDataListFlatten: any = useRef([]); | ||
211 | + if (!funcDataListFlatten.current.length && funcDataList.length) { | ||
212 | + flatten(funcDataList, funcDataListFlatten.current); | ||
213 | + } | ||
214 | + const funcDataListSimple: any = {}; | ||
215 | + funcDataListFlatten.current.forEach((item: any) => { | ||
216 | + Object.assign(funcDataListSimple, { [item.title]: item.title }); | ||
217 | + }); | ||
218 | + // 以变量字符长度排序,确保`DATAIF`比`IF`优先匹配 | ||
219 | + const funcDataListSort = funcDataListFlatten.current; | ||
149 | 220 | ||
150 | useEffect(() => { | 221 | useEffect(() => { |
151 | - if (typeof focusFunHandler === 'function') { | ||
152 | - focusFunHandler(focusFun || ''); | 222 | + if (funcDataListSort.length) { |
223 | + setTimeout(() => { | ||
224 | + cmUtils.variableRender(); | ||
225 | + }, 100); | ||
153 | } | 226 | } |
154 | - }, [focusFun]); | 227 | + }, [JSON.stringify(funcDataListSort)]); |
155 | 228 | ||
156 | - const cmUtils = { | ||
157 | - // 插入变量 | ||
158 | - insert: (editor: any, variable: string, isFormula: boolean = false) => { | ||
159 | - if (!editor) { | ||
160 | - return; | 229 | + // const varDataListFlatten: any[] = []; |
230 | + const varDataListFlatten = useRef<any[]>([]); | ||
231 | + if (!varDataListFlatten.current.length && varDataList.length) { | ||
232 | + flatten(varDataList, varDataListFlatten.current); | ||
233 | + } | ||
234 | + // flatten(varDataList, varDataListFlatten); | ||
235 | + const varDataListSimple: any = {}; | ||
236 | + varDataListFlatten.current.forEach((item) => { | ||
237 | + Object.assign(varDataListSimple, { [item.key]: item.titleStr }); | ||
238 | + if (item.attrs) { | ||
239 | + item.attrs.forEach((attrItem: any) => | ||
240 | + Object.assign(varDataListSimple, { | ||
241 | + [attrItem.key]: `${item.titleStr}.${attrItem.titleStr}`, | ||
242 | + }), | ||
243 | + ); | ||
244 | + } | ||
245 | + }); | ||
246 | + | ||
247 | + // 插入的变量在函数内,并且前面也是变量 | ||
248 | + const isInFunc = (id: number, code: string) => { | ||
249 | + let left1: number = 0; | ||
250 | + let left2: number = 0; | ||
251 | + let right1: number = 0; | ||
252 | + let right2: number = 0; | ||
253 | + // 前面是否是变量 | ||
254 | + const beforeVar = code.split('')[id - 1] === '}'; | ||
255 | + code.split('').forEach((item, index) => { | ||
256 | + if (index < id) { | ||
257 | + if (item === '(') { | ||
258 | + left1++; | ||
259 | + } else if (item === ')') { | ||
260 | + left2++; | ||
261 | + } | ||
262 | + } else { | ||
263 | + if (item === '(') { | ||
264 | + right1++; | ||
265 | + } else if (item === ')') { | ||
266 | + right2++; | ||
267 | + } | ||
161 | } | 268 | } |
269 | + }); | ||
270 | + return left1 > left2 && right1 < right2 && beforeVar; | ||
271 | + }; | ||
272 | + | ||
273 | + const cmUtils: { | ||
274 | + insert: (variable: string, isFormula: boolean) => void; | ||
275 | + variableRender: () => void; | ||
276 | + } = { | ||
277 | + // 插入变量 | ||
278 | + insert: (variable: string, isFormula: boolean = false) => { | ||
162 | // 光标位置插入新值(变量) | 279 | // 光标位置插入新值(变量) |
163 | - editor.replaceSelection(variable); | ||
164 | - cmUtils.variableRender(editor); | 280 | + // editor.replaceSelection(variable); |
165 | 281 | ||
166 | // "公式"类型变量时 | 282 | // "公式"类型变量时 |
167 | if (isFormula) { | 283 | if (isFormula) { |
168 | // 光标位置固定插入内容 | 284 | // 光标位置固定插入内容 |
169 | - editor.replaceSelection('()'); | 285 | + editor.replaceSelection(variable + '()'); |
170 | // 光标左移 | 286 | // 光标左移 |
171 | - codeEditor.execCommand('goCharLeft'); | 287 | + editor.execCommand('goCharLeft'); |
288 | + } else { | ||
289 | + const pos = editor.getCursor()?.ch; | ||
290 | + const inFunc = isInFunc(pos, editor.getValue()); | ||
291 | + if (inFunc) { | ||
292 | + editor.replaceSelection(',' + variable); | ||
293 | + } else { | ||
294 | + editor.replaceSelection(variable); | ||
295 | + } | ||
172 | } | 296 | } |
297 | + cmUtils.variableRender(); | ||
173 | // 让编辑器聚集 | 298 | // 让编辑器聚集 |
174 | editor.focus(); | 299 | editor.focus(); |
175 | }, | 300 | }, |
176 | 301 | ||
177 | // 替换变量(变量示例:${xxx}) | 302 | // 替换变量(变量示例:${xxx}) |
178 | - variableRender: (editor: any) => { | ||
179 | - if (!editor) { | ||
180 | - return; | ||
181 | - } | ||
182 | - | ||
183 | - const code = cloneDeep(editor.getValue()); | 303 | + variableRender: () => { |
304 | + const code = _.cloneDeep(editor.getValue()); | ||
184 | // 换行分隔 | 305 | // 换行分隔 |
185 | const codeArr = code.split('\n'); | 306 | const codeArr = code.split('\n'); |
186 | - const variReplace = (cm: any, line: number) => { | ||
187 | - const sIndex = codeArr[line].indexOf('${'); | ||
188 | - const eIndex = codeArr[line].indexOf('}'); | ||
189 | - if (sIndex === -1 && eIndex === -1) { | ||
190 | - return; | ||
191 | - } | ||
192 | 307 | ||
193 | - const variable = codeArr[line].slice(sIndex, eIndex + 1); | ||
194 | - const text: string = variableObjLocal[variable] || '(已缺失)'; | ||
195 | - const hasError: boolean = variableObjLocal[variable] === undefined; | ||
196 | - | ||
197 | - cm?.markText( | 308 | + const makeText = ( |
309 | + line: number, | ||
310 | + sIndex: number, | ||
311 | + eIndex: number, | ||
312 | + variable: string, | ||
313 | + text: string, | ||
314 | + hasError: boolean, | ||
315 | + type?: string, | ||
316 | + ) => { | ||
317 | + editor.markText( | ||
198 | { | 318 | { |
199 | line: line, | 319 | line: line, |
200 | ch: sIndex, | 320 | ch: sIndex, |
201 | }, | 321 | }, |
202 | { | 322 | { |
203 | line: line, | 323 | line: line, |
204 | - ch: eIndex + 1, | 324 | + ch: eIndex, |
205 | }, | 325 | }, |
206 | { | 326 | { |
207 | - replacedWith: domTagGen(variable, text, hasError ? 'red' : '#026be1'), | 327 | + replacedWith: domTagGen( |
328 | + variable, | ||
329 | + text, | ||
330 | + hasError ? 'red' : '#026be1', | ||
331 | + type, | ||
332 | + ), | ||
333 | + inclusiveRight: false, | ||
334 | + readOnly: false, | ||
208 | }, | 335 | }, |
209 | ); | 336 | ); |
210 | - codeArr[line] = codeArr[line].replace(variable, strFill(0, variable.length, 0)); | ||
211 | - | ||
212 | - variReplace(cm, line); | ||
213 | }; | 337 | }; |
214 | 338 | ||
215 | - const funReplace = (cm: any, line: number, funStr: string) => { | ||
216 | - const sIndex = codeArr[line].indexOf(funStr); | ||
217 | - const eIndex = sIndex + funStr.length; | ||
218 | - if (sIndex === -1) { | ||
219 | - return; | 339 | + const replacement = ( |
340 | + line: number, | ||
341 | + regexp: any, | ||
342 | + refObj: any, | ||
343 | + type?: string, | ||
344 | + ) => { | ||
345 | + const lineStr = codeArr[line]; | ||
346 | + let match: any; | ||
347 | + while ((match = regexp.exec(lineStr)) !== null) { | ||
348 | + const variable = match[0]; | ||
349 | + const sIndex = match.index; | ||
350 | + const eIndex = sIndex + variable.length; | ||
351 | + const text: string = refObj[variable] || '(已缺失)'; | ||
352 | + if (!type || (type === 'function' && lineStr[eIndex] === '(')) { | ||
353 | + const hasError: boolean = refObj[variable] === undefined; | ||
354 | + makeText(line, sIndex, eIndex, variable, text, hasError, type); | ||
355 | + codeArr[line] = codeArr[line].replace( | ||
356 | + variable, | ||
357 | + strFill(0, variable.length, 0), | ||
358 | + ); | ||
359 | + } | ||
220 | } | 360 | } |
221 | - const variable = codeArr[line].slice(sIndex, eIndex); | ||
222 | - const text: string = funObjSimple[variable] || '(已缺失)'; | ||
223 | - const hasError: boolean = funObjSimple[variable] === undefined; | ||
224 | - | ||
225 | - cm?.markText( | ||
226 | - { | ||
227 | - line: line, | ||
228 | - ch: sIndex, | ||
229 | - }, | ||
230 | - { | ||
231 | - line: line, | ||
232 | - ch: eIndex, | ||
233 | - }, | ||
234 | - { | ||
235 | - replacedWith: domTagGen(variable, text, hasError ? 'red' : '#026be1'), | ||
236 | - }, | ||
237 | - ); | ||
238 | - codeArr[line] = codeArr[line].replace(variable, strFill(0, variable.length, 0)); | ||
239 | - | ||
240 | - funReplace(cm, line, funStr); | ||
241 | }; | 361 | }; |
242 | 362 | ||
363 | + const funcRegexp = new RegExp( | ||
364 | + funcDataListSort | ||
365 | + .map((data: any) => { | ||
366 | + return '\\b' + data.title + '\\b'; | ||
367 | + }) | ||
368 | + .join('|'), | ||
369 | + 'g', | ||
370 | + ); | ||
371 | + | ||
243 | for (let i = 0; i < editor.doc.size; i++) { | 372 | for (let i = 0; i < editor.doc.size; i++) { |
244 | - variReplace(editor, i); | ||
245 | - if (Boolean(isUseFun)) { | ||
246 | - funObjsSort.map((v) => funReplace(editor, i, v.title)); | 373 | + // 替换变量名称 |
374 | + const regexp = /\$\{.*?\}/g; | ||
375 | + replacement(i, regexp, varDataListSimple); | ||
376 | + if (Boolean(isUseFun) && funcDataListSort.length > 0) { | ||
377 | + // 替换函数公式名称 | ||
378 | + replacement(i, funcRegexp, funcDataListSimple, 'function'); | ||
247 | } | 379 | } |
248 | } | 380 | } |
249 | }, | 381 | }, |
250 | }; | 382 | }; |
251 | 383 | ||
384 | + const getFuncId = (name: string) => { | ||
385 | + return funcDataListFlatten.current.find( | ||
386 | + (item: any) => item.funcNameEg === name, | ||
387 | + )?.id; | ||
388 | + }; | ||
389 | + | ||
252 | useEffect(() => { | 390 | useEffect(() => { |
253 | - if (!codeEditor || !newVariable) { | ||
254 | - return; | 391 | + if (value && !isInitDone) { |
392 | + // 限制初始值只设置一次 | ||
393 | + setValueLocal(value); | ||
255 | } | 394 | } |
256 | - | ||
257 | - setVariableObjLocal(() => { | ||
258 | - return Object.assign(variableObjLocal, { | ||
259 | - [newVariable.key]: newVariable.name, | ||
260 | - }); | ||
261 | - }); | ||
262 | - | ||
263 | - cmUtils.insert(codeEditor, newVariable.key, newVariable.type === 'fun'); | ||
264 | - }, [newVariable, codeEditor]); | 395 | + }, [value]); |
265 | 396 | ||
266 | useEffect(() => { | 397 | useEffect(() => { |
267 | // 消息提醒 新增模板时 触发方式变为"定时触发"时 需要将消息内容中的表单字段值清除 | 398 | // 消息提醒 新增模板时 触发方式变为"定时触发"时 需要将消息内容中的表单字段值清除 |
268 | if (resetValue) { | 399 | if (resetValue) { |
269 | setValueLocal(resetValue); | 400 | setValueLocal(resetValue); |
270 | } | 401 | } |
271 | - // console.log('--resetValue--',resetValue) | ||
272 | }, [resetValue]); | 402 | }, [resetValue]); |
273 | 403 | ||
274 | - const onValueChange = (editor: any, data: any, val: string) => { | ||
275 | - setCodeEditor(editor); | ||
276 | - cmUtils.variableRender(editor); | ||
277 | - | ||
278 | - // TODO 默认值bug修复 暂时注释 | ||
279 | - // const isSame = isEqual(valueLocal, val); | ||
280 | - // if (isSame) { | ||
281 | - // return; | ||
282 | - // } | 404 | + useEffect(() => { |
405 | + if (newVariable) { | ||
406 | + cmUtils.insert(newVariable.key, newVariable.type === 'fun'); | ||
407 | + } | ||
408 | + }, [newVariable]); | ||
283 | 409 | ||
410 | + // 编辑器内容变化响应 | ||
411 | + const onHandleChange = (editor: any, data: any, value: string) => { | ||
412 | + cmUtils.variableRender(); | ||
413 | + onChange(value); | ||
284 | setIsInitDone(true); | 414 | setIsInitDone(true); |
285 | - onChange(val); | ||
286 | - }; | ||
287 | - | ||
288 | - useEffect(() => { | ||
289 | - if (variableObjLocal) { | ||
290 | - cmUtils.variableRender(codeEditor); | 415 | + if (!formatRef.current) { |
416 | + editor.showHint(); | ||
417 | + } | ||
418 | + formatRef.current = false; | ||
419 | + // 函数补全时,光标左移 | ||
420 | + if (data.origin === 'complete' && data.text?.[0]?.endsWith('()')) { | ||
421 | + editor.execCommand('goCharLeft'); | ||
422 | + const funcName = data.text?.[0].slice(0, -2); | ||
423 | + const funcData = funcDataListFlatten.current.find( | ||
424 | + (data: any) => data.funcNameEg === funcName, | ||
425 | + ); | ||
426 | + if (funcData && typeof onFocusFunc === 'function') { | ||
427 | + onFocusFunc(funcData); | ||
428 | + } | ||
291 | } | 429 | } |
292 | - }, [variableObjLocal]); | 430 | + if (value === '') { |
431 | + onFocusFunc(null); | ||
432 | + } | ||
433 | + }; | ||
293 | 434 | ||
294 | - // 字符串反转 | ||
295 | - const strReverse = (str: string) => { | ||
296 | - return str.split('').reverse().join(''); | 435 | + // 光标移动响应 |
436 | + // const onHandleCursor = (editor: any, cursor: any) => { | ||
437 | + // const lineStr = editor.getLine(cursor.line); | ||
438 | + // const funcName = getLegalVaribleNameFromIndex(lineStr, cursor.ch); | ||
439 | + // const funcData = funcDataListFlatten.find( | ||
440 | + // (data) => data.title === funcName, | ||
441 | + // ); | ||
442 | + // if (funcData && typeof onFocusFunc === 'function') { | ||
443 | + // onFocusFunc(funcData); | ||
444 | + // } | ||
445 | + // }; | ||
446 | + | ||
447 | + // 格式化code文本 | ||
448 | + const autoFormatSelection = () => { | ||
449 | + const code = editor.getValue(); // 获取编辑器中的代码 | ||
450 | + let beautifiedCode = beautify(code); // 使用代码美化工具(如 js-beautify)美化代码 | ||
451 | + beautifiedCode = beautifiedCode.replace( | ||
452 | + /\$\s*\{\s*(.*?)\s*\}/g, | ||
453 | + function (a, b) { | ||
454 | + return '${' + b + '}'; | ||
455 | + }, | ||
456 | + ); | ||
457 | + formatRef.current = true; | ||
458 | + editor.setValue(beautifiedCode); // 将美化后的代码设置回编辑器 | ||
297 | }; | 459 | }; |
298 | 460 | ||
299 | - /** | ||
300 | - * 查找编辑器光标所对应函数,设置其函数说明信息 | ||
301 | - * todo 待优化,规则不够完善 | ||
302 | - * @param editor | ||
303 | - */ | ||
304 | - const findFocusFun = (editor: any) => { | ||
305 | - const cursor = editor.getCursor(); | ||
306 | - const lineStr = editor.getLine(cursor.line); | ||
307 | - const lineStrPart = lineStr.substring(0, cursor.ch); | ||
308 | - const lineStrPartRev = strReverse(lineStrPart); | ||
309 | - | ||
310 | - let flag: boolean = false; | ||
311 | - let funName: string = ''; | ||
312 | - | ||
313 | - forEach(funObjsSort, (v) => { | ||
314 | - if (flag) { | ||
315 | - return; | ||
316 | - } | ||
317 | - const i = (lineStrPartRev || '').indexOf(strReverse(v.title)); | ||
318 | - if (i > -1) { | ||
319 | - flag = true; | ||
320 | - funName = v.title; | ||
321 | - return; | ||
322 | - } | 461 | + // 代码提示 |
462 | + const hintCompletion = (cm: any, options: any) => { | ||
463 | + const funRegex = /[a-zA-Z0-9_\u4e00-\u9fff]/; | ||
464 | + const dotRegex = /\./; | ||
465 | + const varRegex = /^\.(\}.*?\{\$)/; | ||
466 | + const comp = funcDataListSort.map((data: any) => data.title); | ||
467 | + return new Promise((accept) => { | ||
468 | + setTimeout(() => { | ||
469 | + const cursor = cm.getCursor(); | ||
470 | + const line = cm.getLine(cursor.line); | ||
471 | + let start = cursor.ch; | ||
472 | + let end = cursor.ch; | ||
473 | + let completions = []; | ||
474 | + while (start && funRegex.test(line.charAt(start - 1))) --start; | ||
475 | + while (end < line.length && funRegex.test(line.charAt(end))) ++end; | ||
476 | + const word = line.slice(start, end); | ||
477 | + if (start && dotRegex.test(line.charAt(start - 1))) { | ||
478 | + // 提示对象.属性 | ||
479 | + if (word === '') { | ||
480 | + const lineReverse = reverseStr(line.slice(0, start)); | ||
481 | + const matched = varRegex.exec(lineReverse); | ||
482 | + const word = matched ? reverseStr(matched[1]) : ''; | ||
483 | + const findVar = varDataListFlatten.current.find( | ||
484 | + (item: any) => item.key === word, | ||
485 | + ); | ||
486 | + if (findVar && findVar.attrs) { | ||
487 | + completions.push( | ||
488 | + ...findVar.attrs.map((attrItem: any) => ({ | ||
489 | + text: attrItem.key, | ||
490 | + displayText: attrItem.titleStr, | ||
491 | + from: Cm.Pos(cursor.line, start - word.length - 1), | ||
492 | + to: Cm.Pos(cursor.line, end), | ||
493 | + customInfo: { | ||
494 | + widget: getWidgetByType(attrItem.fieldGroupType), | ||
495 | + }, | ||
496 | + })), | ||
497 | + ); | ||
498 | + } | ||
499 | + } | ||
500 | + } else { | ||
501 | + // 提示公式函数名、变量 | ||
502 | + if (word) { | ||
503 | + for (let i = 0; i < comp.length; i++) { | ||
504 | + if (comp[i].toLowerCase().indexOf(word.toLowerCase()) > -1) { | ||
505 | + completions.push(comp[i]); | ||
506 | + } | ||
507 | + } | ||
508 | + for (let i = 0; i < varDataListFlatten.current.length; i++) { | ||
509 | + const it = varDataListFlatten.current[i]; | ||
510 | + if (it.titleStr.indexOf(word) > -1) { | ||
511 | + completions.push({ | ||
512 | + text: it.key, | ||
513 | + displayText: it.titleStr, | ||
514 | + }); | ||
515 | + } | ||
516 | + } | ||
517 | + } | ||
518 | + } | ||
519 | + | ||
520 | + // const firstFieldIndex = completions.findIndex(item => typeof item !== 'string'); | ||
521 | + completions = completions.map((item: any, index) => { | ||
522 | + if (typeof item === 'string') { | ||
523 | + return { | ||
524 | + text: item + '()', | ||
525 | + displayText: item, | ||
526 | + render: (element: any, self: any, data: any) => { | ||
527 | + const container = document.createElement('div'); | ||
528 | + container.className = 'hint-line'; | ||
529 | + const top = document.createElement('div'); | ||
530 | + // @ts-ignore | ||
531 | + const jsxEl: React.ReactElement = handleHighlight( | ||
532 | + item, | ||
533 | + word, | ||
534 | + 'fx', | ||
535 | + ); | ||
536 | + ReactDOM?.render(jsxEl, top); | ||
537 | + // top.innerHTML = <span>122</span>; | ||
538 | + top.className = 'hint-line_left'; | ||
539 | + const bottom = document.createElement('div'); | ||
540 | + bottom.className = 'hint-line_right'; | ||
541 | + // @ts-ignore | ||
542 | + const desc = | ||
543 | + funcDataListFlatten.current.find( | ||
544 | + (it: any) => it.funcNameEg === item, | ||
545 | + )?.funcName || ''; | ||
546 | + bottom.innerHTML = desc; | ||
547 | + container.appendChild(top); | ||
548 | + container.appendChild(bottom); | ||
549 | + element.appendChild(container); | ||
550 | + element.addEventListener('click', function () { | ||
551 | + self.complete(); // 选择当前提示项并插入到编辑器中 | ||
552 | + self.close(); // 关闭代码提示列表 | ||
553 | + }); | ||
554 | + }, | ||
555 | + }; | ||
556 | + } else { | ||
557 | + return { | ||
558 | + text: item.text, | ||
559 | + displayText: item.displayText, | ||
560 | + ...(item.from && { | ||
561 | + from: item.from, | ||
562 | + to: item.to, | ||
563 | + }), | ||
564 | + render: (element: any, self: any) => { | ||
565 | + element.style.padding = '5px'; | ||
566 | + const DIV = document.createElement('div'); | ||
567 | + DIV.style.display = 'flex'; | ||
568 | + DIV.style.justifyContent = 'space-between'; | ||
569 | + const LEFT = document.createElement('span'); | ||
570 | + // @ts-ignore | ||
571 | + const jsxEl: React.ReactElement = handleHighlight( | ||
572 | + item.displayText, | ||
573 | + word, | ||
574 | + ); | ||
575 | + ReactDOM?.render(jsxEl, LEFT); | ||
576 | + // LEFT.innerHTML = item.displayText; | ||
577 | + const variable = varDataListFlatten.current.find( | ||
578 | + (it) => it.key === item.text, | ||
579 | + ); | ||
580 | + let RIGHT; | ||
581 | + if (variable?.widget) { | ||
582 | + const { name, color, bgColor } = | ||
583 | + widgetMapping[variable.widget] || {}; | ||
584 | + RIGHT = document.createElement('span'); | ||
585 | + RIGHT.className = 'tag'; | ||
586 | + RIGHT.innerHTML = name; | ||
587 | + RIGHT.style.color = color; | ||
588 | + RIGHT.style.backgroundColor = bgColor; | ||
589 | + } else if (item.customInfo?.widget) { | ||
590 | + const { name, color, bgColor } = | ||
591 | + widgetMapping[item.customInfo?.widget] || {}; | ||
592 | + RIGHT = document.createElement('span'); | ||
593 | + RIGHT.className = 'tag'; | ||
594 | + RIGHT.innerHTML = name; | ||
595 | + RIGHT.style.color = color; | ||
596 | + RIGHT.style.backgroundColor = bgColor; | ||
597 | + } | ||
598 | + DIV.appendChild(LEFT); | ||
599 | + if (RIGHT) DIV.appendChild(RIGHT); | ||
600 | + // if (firstFieldIndex === index) { | ||
601 | + // const TITLE = document.createElement('div'); | ||
602 | + // TITLE.innerHTML = '当前表单字段'; | ||
603 | + // TITLE.style.padding = '5px'; | ||
604 | + // element.appendChild(TITLE) | ||
605 | + // } | ||
606 | + element.appendChild(DIV); | ||
607 | + element.addEventListener('click', function () { | ||
608 | + self.complete(); // 选择当前提示项并插入到编辑器中 | ||
609 | + self.close(); // 关闭代码提示列表 | ||
610 | + }); | ||
611 | + }, | ||
612 | + }; | ||
613 | + } | ||
614 | + }); | ||
615 | + | ||
616 | + if (completions.length) { | ||
617 | + return accept({ | ||
618 | + list: completions, | ||
619 | + from: Cm.Pos(cursor.line, start), | ||
620 | + to: Cm.Pos(cursor.line, end), | ||
621 | + }); | ||
622 | + } else { | ||
623 | + return accept(null); | ||
624 | + } | ||
625 | + }, 50); | ||
323 | }); | 626 | }); |
324 | - return funName; | ||
325 | }; | 627 | }; |
326 | 628 | ||
327 | - const handleClear = () => { | ||
328 | - // 清除组件内容 | ||
329 | - codeEditor.doc.setValue(''); | 629 | + // 清除组件内容 |
630 | + const onHandleClear = () => { | ||
631 | + editor.doc.setValue(''); | ||
330 | }; | 632 | }; |
331 | 633 | ||
634 | + useImperativeHandle(cRef, () => ({ | ||
635 | + autoFormatSelection, | ||
636 | + // 获取编辑器使用到的函数公式名称, eg: [IF, SUM, CONCAT] | ||
637 | + getUsedFuncList() { | ||
638 | + const usedFuncList = []; | ||
639 | + const codeArr = _.cloneDeep(editor.getValue()).split('\n'); | ||
640 | + if (Boolean(isUseFun) && funcDataListSort.length > 0) { | ||
641 | + const funcRegexp = new RegExp( | ||
642 | + funcDataListSort | ||
643 | + .map((data: any) => { | ||
644 | + return '\\b' + data.title + '\\b'; | ||
645 | + }) | ||
646 | + .join('|'), | ||
647 | + 'g', | ||
648 | + ); | ||
649 | + for (let i = 0; i < editor.doc.size; i++) { | ||
650 | + const lineStr = codeArr[i]; | ||
651 | + let match: any; | ||
652 | + while ((match = funcRegexp.exec(lineStr)) !== null) { | ||
653 | + const variable = match[0]; | ||
654 | + if (lineStr[match['index'] + variable.length] === '(') { | ||
655 | + const funcId = getFuncId(variable); | ||
656 | + if (funcDataListSimple[variable] && funcId) { | ||
657 | + usedFuncList.push(funcId); | ||
658 | + } | ||
659 | + } | ||
660 | + } | ||
661 | + } | ||
662 | + } | ||
663 | + return usedFuncList; | ||
664 | + }, | ||
665 | + getEditor() { | ||
666 | + return editor; | ||
667 | + }, | ||
668 | + })); | ||
669 | + | ||
670 | + function customLint() { | ||
671 | + return []; | ||
672 | + } | ||
673 | + | ||
332 | return ( | 674 | return ( |
333 | - //这里className中的qx-copy-send-cm 是抄送节点--消息内容专用的样式,如果有必要,可将className回滚设置为'qx-formula-cm ' + className | ||
334 | <div | 675 | <div |
335 | className={ | 676 | className={ |
336 | - from === 'copySend' ? 'qx-copy-send-cm ' + className : 'qx-formula-cm ' + className | 677 | + from === 'copySend' |
678 | + ? 'qx-copy-send-cm ' + className | ||
679 | + : 'qx-formula-cm ' + className | ||
337 | } | 680 | } |
338 | - style={{ height: '100%', padding: 0 }} | ||
339 | > | 681 | > |
682 | + {/* @ts-ignore */} | ||
340 | <CodeMirror | 683 | <CodeMirror |
684 | + editorDidMount={(editor) => { | ||
685 | + setEditor(editor); | ||
686 | + editor.addKeyMap({ | ||
687 | + 'Ctrl-f': autoFormatSelection, | ||
688 | + }); | ||
689 | + }} | ||
341 | value={(valueLocal || '').toString()} | 690 | value={(valueLocal || '').toString()} |
342 | - editorDidMount={(editor) => setCodeEditor(editor)} | ||
343 | - // onCursorActivity={(e) => console.log('e', e)} | ||
344 | - // onCursorActivity={(e) => e?.showHint()} //没有会报错 | ||
345 | options={{ | 691 | options={{ |
346 | - // mode: 'text/html', | ||
347 | - lineNumbers: false, | ||
348 | - autofocus, | 692 | + mode: 'javascript', // 语言 |
693 | + theme: 'idea', | ||
694 | + placeholder: '请输入函数', | ||
695 | + extraKeys: { Ctrl: 'autocomplete' }, //ctrl+空格自动提示配置 | ||
696 | + hintOptions: { | ||
697 | + completeSingle: false, | ||
698 | + hint: hintCompletion, | ||
699 | + className: 'myCodeMirrorHint', | ||
700 | + }, | ||
701 | + autofocus, //自动获取焦点 | ||
702 | + matchBrackets: true, // 匹配括号 | ||
703 | + autoCloseBrackets: true, | ||
704 | + lint: { | ||
705 | + getAnnotations: customLint, | ||
706 | + }, | ||
349 | readOnly: Boolean(readOnly), | 707 | readOnly: Boolean(readOnly), |
350 | cursorHeight: Boolean(readOnly) ? 0 : 'auto', | 708 | cursorHeight: Boolean(readOnly) ? 0 : 'auto', |
351 | // 滚动(false,默认)或自动换行 | 709 | // 滚动(false,默认)或自动换行 |
352 | lineWrapping: true, | 710 | lineWrapping: true, |
711 | + styleActiveLine: true, // 光标行代码高亮 | ||
353 | }} | 712 | }} |
354 | - onChange={debounce(onValueChange, 300)} | ||
355 | - onCursor={(editor) => { | ||
356 | - const funName = findFocusFun(editor); | ||
357 | - setFocusFun(funName); | ||
358 | - }} | 713 | + // onKeyEvent={handleKeyEvent} |
714 | + // onBeforeChange={handleBeforeChange} | ||
715 | + // onCursorActivity={handleCursorActivity} | ||
716 | + // onCursor={onHandleCursor} | ||
717 | + onChange={onHandleChange} | ||
359 | /> | 718 | /> |
360 | {allowClear && value && ( | 719 | {allowClear && value && ( |
361 | - <CloseCircleFilled className={'qx-field-setter__clear'} onClick={handleClear} /> | 720 | + <CloseCircleFilled |
721 | + className={'qx-field-setter__clear'} | ||
722 | + onClick={onHandleClear} | ||
723 | + /> | ||
362 | )} | 724 | )} |
363 | </div> | 725 | </div> |
364 | ); | 726 | ); |
365 | }; | 727 | }; |
366 | - | ||
367 | export default CodeEditor; | 728 | export default CodeEditor; |
src/qx-code-editor/plugins/cm-extensions.ts
0 → 100644
1 | +import CodeMirror from 'codemirror'; | ||
2 | + | ||
3 | +// Comment/uncomment the specified range | ||
4 | +CodeMirror.defineExtension('commentRange', function (isComment, from, to) { | ||
5 | + let cm = this, | ||
6 | + curMode = CodeMirror.innerMode( | ||
7 | + cm.getMode(), | ||
8 | + cm.getTokenAt(from).state, | ||
9 | + ).mode; | ||
10 | + cm.operation(function () { | ||
11 | + if (isComment) { | ||
12 | + // Comment range | ||
13 | + cm.replaceRange(curMode.commentEnd, to); | ||
14 | + cm.replaceRange(curMode.commentStart, from); | ||
15 | + if (from.line == to.line && from.ch == to.ch) | ||
16 | + // An empty comment inserted - put cursor inside | ||
17 | + cm.setCursor(from.line, from.ch + curMode.commentStart.length); | ||
18 | + } else { | ||
19 | + // Uncomment range | ||
20 | + let selText = cm.getRange(from, to); | ||
21 | + let startIndex = selText.indexOf(curMode.commentStart); | ||
22 | + let endIndex = selText.lastIndexOf(curMode.commentEnd); | ||
23 | + if (startIndex > -1 && endIndex > -1 && endIndex > startIndex) { | ||
24 | + // Take string till comment start | ||
25 | + selText = | ||
26 | + selText.substr(0, startIndex) + | ||
27 | + // From comment start till comment end | ||
28 | + selText.substring( | ||
29 | + startIndex + curMode.commentStart.length, | ||
30 | + endIndex, | ||
31 | + ) + | ||
32 | + // From comment end till string end | ||
33 | + selText.substr(endIndex + curMode.commentEnd.length); | ||
34 | + } | ||
35 | + cm.replaceRange(selText, from, to); | ||
36 | + } | ||
37 | + }); | ||
38 | +}); | ||
39 | + | ||
40 | +// Applies automatic formatting to the specified range | ||
41 | +CodeMirror.defineExtension('autoFormatRange', function (from, to) { | ||
42 | + let cm = this; | ||
43 | + let outer = cm.getMode(), | ||
44 | + text = cm.getRange(from, to).split('\n'); | ||
45 | + let state = CodeMirror.copyState(outer, cm.getTokenAt(from).state); | ||
46 | + let tabSize = cm.getOption('tabSize'); | ||
47 | + | ||
48 | + let out = '', | ||
49 | + lines = 0, | ||
50 | + atSol = from.ch == 0; | ||
51 | + function newline() { | ||
52 | + out += '\n'; | ||
53 | + atSol = true; | ||
54 | + ++lines; | ||
55 | + } | ||
56 | + | ||
57 | + for (let i = 0; i < text.length; ++i) { | ||
58 | + let stream = new CodeMirror.StringStream(text[i], tabSize); | ||
59 | + while (!stream.eol()) { | ||
60 | + let inner = CodeMirror.innerMode(outer, state); | ||
61 | + let style = outer.token(stream, state), | ||
62 | + cur = stream.current(); | ||
63 | + stream.start = stream.pos; | ||
64 | + if (!atSol || /\S/.test(cur)) { | ||
65 | + out += cur; | ||
66 | + atSol = false; | ||
67 | + } | ||
68 | + if ( | ||
69 | + !atSol && | ||
70 | + inner.mode.newlineAfterToken && | ||
71 | + inner.mode.newlineAfterToken( | ||
72 | + style, | ||
73 | + cur, | ||
74 | + stream.string.slice(stream.pos) || text[i + 1] || '', | ||
75 | + inner.state, | ||
76 | + ) | ||
77 | + ) | ||
78 | + newline(); | ||
79 | + } | ||
80 | + if (!stream.pos && outer.blankLine) outer.blankLine(state); | ||
81 | + if (!atSol) newline(); | ||
82 | + } | ||
83 | + | ||
84 | + cm.operation(function () { | ||
85 | + cm.replaceRange(out, from, to); | ||
86 | + for (let cur = from.line + 1, end = from.line + lines; cur <= end; ++cur) | ||
87 | + cm.indentLine(cur, 'smart'); | ||
88 | + cm.setSelection(from, cm.getCursor(false)); | ||
89 | + }); | ||
90 | +}); | ||
91 | + | ||
92 | +// Applies automatic mode-aware indentation to the specified range | ||
93 | +CodeMirror.defineExtension('autoIndentRange', function (from, to) { | ||
94 | + let cmInstance = this; | ||
95 | + this.operation(function () { | ||
96 | + for (let i = from.line; i <= to.line; i++) { | ||
97 | + cmInstance.indentLine(i, 'smart'); | ||
98 | + } | ||
99 | + }); | ||
100 | +}); |
src/qx-code-editor/style.less
0 → 100644
1 | +@import '~@qx/ui/src/style/variable.less'; | ||
2 | + | ||
3 | +.qx-formula-cm { | ||
4 | + .CodeMirror { | ||
5 | + height: auto; | ||
6 | + line-height: 24px; | ||
7 | + } | ||
8 | +} | ||
9 | + | ||
10 | +.cm-bg .CodeMirror { | ||
11 | + border: 1px solid #e7e6e6; | ||
12 | + background-color: #f9f9f9 !important; | ||
13 | +} | ||
14 | + | ||
15 | +.qx-copy-send-cm { | ||
16 | + height: 100%; | ||
17 | + | ||
18 | + .react-codemirror2 { | ||
19 | + height: 100%; | ||
20 | + | ||
21 | + .CodeMirror { | ||
22 | + height: auto; | ||
23 | + min-height: 80px; | ||
24 | + line-height: 24px; | ||
25 | + } | ||
26 | + } | ||
27 | +} | ||
28 | + | ||
29 | +.qx-field-setter__clear { | ||
30 | + position: absolute; | ||
31 | + bottom: 8px; | ||
32 | + right: 8px; | ||
33 | + color: #bcb9b9; | ||
34 | + font-size: 14px; | ||
35 | +} | ||
36 | + | ||
37 | +.CodeMirror-line { | ||
38 | + .CodeMirror-matchingbracket { | ||
39 | + outline: none !important; | ||
40 | + background: #ccc; | ||
41 | + border-radius: 2px; | ||
42 | + //padding: 0 1px 2px; | ||
43 | + } | ||
44 | +} | ||
45 | + | ||
46 | +body { | ||
47 | + .CodeMirror-hints.idea { | ||
48 | + background: #fff !important; | ||
49 | + border: 1px solid @N4; | ||
50 | + border-radius: 5px; | ||
51 | + width: 240px; | ||
52 | + .tree-node-high { | ||
53 | + color: @B8; | ||
54 | + } | ||
55 | + .tag { | ||
56 | + padding: 0 8px; | ||
57 | + border-radius: 4px; | ||
58 | + background-color: #e7efff; | ||
59 | + font-size: 14px; | ||
60 | + color: #1764ff; | ||
61 | + white-space: nowrap; | ||
62 | + margin-left: 10px; | ||
63 | + } | ||
64 | + .hint-line { | ||
65 | + padding: 5px 0; | ||
66 | + display: flex; | ||
67 | + justify-content: space-between; | ||
68 | + font-size: 13px; | ||
69 | + &_left { | ||
70 | + //overflow: hidden; | ||
71 | + //white-space: nowrap; | ||
72 | + //text-overflow: ellipsis; | ||
73 | + width: 50%; | ||
74 | + margin-right: 10px; | ||
75 | + } | ||
76 | + &_right { | ||
77 | + color: #7c7e86; | ||
78 | + width: 50%; | ||
79 | + overflow: hidden; | ||
80 | + text-align: right; | ||
81 | + white-space: nowrap; | ||
82 | + text-overflow: ellipsis; | ||
83 | + } | ||
84 | + } | ||
85 | + } | ||
86 | + .CodeMirror-hints.idea .CodeMirror-hint-active { | ||
87 | + background: #f5f5f5 !important; | ||
88 | + } | ||
89 | + .CodeMirror-hints::-webkit-scrollbar { | ||
90 | + width: 10px; /* 设置滚动条宽度 */ | ||
91 | + background-color: #fff; | ||
92 | + border-radius: 5px; | ||
93 | + } | ||
94 | + | ||
95 | + .CodeMirror-hints::-webkit-scrollbar-track { | ||
96 | + background-color: #f1f1f1; /* 设置滚动条轨道背景色 */ | ||
97 | + border-radius: 10px; /* 设置滚动条轨道的圆角 */ | ||
98 | + } | ||
99 | + | ||
100 | + .CodeMirror-hints::-webkit-scrollbar-thumb { | ||
101 | + background-color: #ccc; /* 设置滚动条滑块颜色 */ | ||
102 | + border-radius: 10px; /* 设置滚动条滑块的圆角 */ | ||
103 | + } | ||
104 | + | ||
105 | + .CodeMirror-hints::-webkit-scrollbar-thumb:hover { | ||
106 | + background-color: #555; /* 设置滚动条滑块的悬停颜色 */ | ||
107 | + } | ||
108 | + .CodeMirror-placeholder { | ||
109 | + position: absolute; | ||
110 | + top: 5px; | ||
111 | + left: 5px; | ||
112 | + color: #bfbfbf; | ||
113 | + } | ||
114 | +} | ||
115 | + | ||
116 | +.CodeMirror-hint:hover { | ||
117 | + background: #f5f5f5 !important; | ||
118 | +} | ||
119 | + | ||
120 | +.CodeMirror-line { | ||
121 | + .bracket-mark { | ||
122 | + padding: 0 1px 2px; | ||
123 | + margin: 0 1px 0 0; | ||
124 | + } | ||
125 | +} |
1 | +import { | ||
2 | + CloseOutlined, | ||
3 | + ControlOutlined, | ||
4 | + RightOutlined, | ||
5 | +} from '@ant-design/icons'; | ||
6 | +import { QxFieldPopover } from '@qx/common'; | ||
7 | +import { | ||
8 | + DatePicker, | ||
9 | + Input, | ||
10 | + InputNumber, | ||
11 | + Menu, | ||
12 | + Select, | ||
13 | + Tag, | ||
14 | + TimePicker, | ||
15 | + message, | ||
16 | +} from 'antd'; | ||
17 | +import type { DataNode } from 'antd/lib/tree'; | ||
18 | +import moment from 'dayjs'; | ||
19 | +import { cloneDeep, findIndex, isEmpty, isEqual, size } from 'lodash-es'; | ||
1 | import type { ReactElement } from 'react'; | 20 | import type { ReactElement } from 'react'; |
2 | import React, { | 21 | import React, { |
22 | + useCallback, | ||
3 | useEffect, | 23 | useEffect, |
4 | useImperativeHandle, | 24 | useImperativeHandle, |
25 | + useMemo, | ||
5 | useRef, | 26 | useRef, |
6 | useState, | 27 | useState, |
7 | - useMemo, | ||
8 | - useCallback, | ||
9 | } from 'react'; | 28 | } from 'react'; |
10 | -import { DatePicker, Input, InputNumber, Menu, Select, Tag, message, TimePicker } from 'antd'; | ||
11 | -import { NumFormulaEnum, NumFormulaOptions } from './constant'; | ||
12 | -import type { DataNode } from 'antd/lib/tree'; | ||
13 | -import moment from 'dayjs'; | 29 | +import { formatEnum } from '../constant'; |
30 | +import CodeEditor, { | ||
31 | + VariableProps, | ||
32 | + getAllVariable, | ||
33 | +} from '../qx-code-editor-old'; | ||
14 | import type { MappingValueProps } from '../qx-filter-condition/filter'; | 34 | import type { MappingValueProps } from '../qx-filter-condition/filter'; |
15 | -import CodeEditor, { getAllVariable, VariableProps } from '../qx-code-editor'; | ||
16 | -import { CloseOutlined, ControlOutlined, RightOutlined } from '@ant-design/icons'; | ||
17 | -import { cloneDeep, isEqual, isEmpty, findIndex, size } from 'lodash-es'; | ||
18 | -import { RelTreeSetter } from './components/rel-tree-setter'; | 35 | +import { |
36 | + ConditionCol, | ||
37 | + FIELD_TYPE, | ||
38 | + FIELD_TYPE_PROPS, | ||
39 | + ParamValueType, | ||
40 | +} from '../qx-filter-condition/filter'; | ||
19 | import { AddressSetter } from './components/address-setter'; | 41 | import { AddressSetter } from './components/address-setter'; |
20 | -import {QxFieldPopover} from '@qx/common'; | ||
21 | import { InputSetter } from './components/input-setter'; | 42 | import { InputSetter } from './components/input-setter'; |
22 | import { OrgSetter } from './components/org-setter'; | 43 | import { OrgSetter } from './components/org-setter'; |
23 | import { RelSetter } from './components/rel-setter'; | 44 | import { RelSetter } from './components/rel-setter'; |
45 | +import { RelTreeSetter } from './components/rel-tree-setter'; | ||
24 | import { UserSetter } from './components/user-setter'; | 46 | import { UserSetter } from './components/user-setter'; |
25 | -import { getSelect, getPreviewSelect } from './service'; | ||
26 | -import { QxBaseIcon } from '@qx/common'; | ||
27 | -import { ConditionCol, FIELD_TYPE_PROPS, FIELD_TYPE, ParamValueType } from '../qx-filter-condition/filter'; | 47 | +import { NumFormulaEnum, NumFormulaOptions } from './constant'; |
28 | import './index.less'; | 48 | import './index.less'; |
49 | +import { getPreviewSelect, getSelect } from './service'; | ||
29 | 50 | ||
30 | const { Option } = Select; | 51 | const { Option } = Select; |
31 | const dateFormat = 'YYYY-MM-DD'; | 52 | const dateFormat = 'YYYY-MM-DD'; |
32 | const dateTimeFormat = 'YYYY-MM-DD HH:mm:ss'; | 53 | const dateTimeFormat = 'YYYY-MM-DD HH:mm:ss'; |
33 | 54 | ||
34 | -export const formatEnum: Record<string, string> = { | ||
35 | - YEAR: 'YYYY', | ||
36 | - YEAR_MONTH: 'YYYY-MM', | ||
37 | - YEAR_DATE: 'YYYY-MM-DD', | ||
38 | - YEAR_HOUR: 'YYYY-MM-DD HH:00', | ||
39 | - YEAR_MIN: 'YYYY-MM-DD HH:mm', | ||
40 | - YEAR_SEC: 'YYYY-MM-DD HH:mm:ss', | ||
41 | - HOUR_MIN: 'HH:mm', | ||
42 | - HOUR_SEC: 'HH:mm:ss', | ||
43 | -}; | ||
44 | - | ||
45 | // 时间可选字段过滤 | 55 | // 时间可选字段过滤 |
46 | const timeSelectedFormatEnum: Record<string, string> = { | 56 | const timeSelectedFormatEnum: Record<string, string> = { |
47 | YEAR_HOUR: 'YYYY-MM-DD HH:00', | 57 | YEAR_HOUR: 'YYYY-MM-DD HH:00', |
@@ -156,7 +166,7 @@ export interface paramColSelectProps extends ColSelectProps { | @@ -156,7 +166,7 @@ export interface paramColSelectProps extends ColSelectProps { | ||
156 | iconText?: string; // Popover-icon 自定义 后面跟随文本 | 166 | iconText?: string; // Popover-icon 自定义 后面跟随文本 |
157 | allowClear?: boolean; | 167 | allowClear?: boolean; |
158 | popupOnBody?: boolean; // 下拉 跟随 body 还是自身 | 168 | popupOnBody?: boolean; // 下拉 跟随 body 还是自身 |
159 | - getName?: (val: any) => void | 169 | + getName?: (val: any) => void; |
160 | } | 170 | } |
161 | 171 | ||
162 | export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | 172 | export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
@@ -201,7 +211,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -201,7 +211,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
201 | 211 | ||
202 | // @ts-ignore | 212 | // @ts-ignore |
203 | const isEnum = | 213 | const isEnum = |
204 | - (!!fieldGroupType && [FIELD_TYPE_PROPS.ENUM_MULTI, FIELD_TYPE_PROPS.ENUM].includes(fieldGroupType)) || | 214 | + (!!fieldGroupType && |
215 | + [FIELD_TYPE_PROPS.ENUM_MULTI, FIELD_TYPE_PROPS.ENUM].includes( | ||
216 | + fieldGroupType, | ||
217 | + )) || | ||
205 | ['qxSelect', 'qxMultiSelect'].includes(props?.widget || ''); | 218 | ['qxSelect', 'qxMultiSelect'].includes(props?.widget || ''); |
206 | //单选多选 选项数据 | 219 | //单选多选 选项数据 |
207 | const [options, setOptions] = useState<any[]>([]); | 220 | const [options, setOptions] = useState<any[]>([]); |
@@ -232,7 +245,11 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -232,7 +245,11 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
232 | request = getSelect; | 245 | request = getSelect; |
233 | } | 246 | } |
234 | 247 | ||
235 | - request(params.appCode ? params.appCode : 'id', params.funCode, _data).then((res: any) => { | 248 | + request( |
249 | + params.appCode ? params.appCode : 'id', | ||
250 | + params.funCode, | ||
251 | + _data, | ||
252 | + ).then((res: any) => { | ||
236 | if (!!res?.length) { | 253 | if (!!res?.length) { |
237 | setOptions( | 254 | setOptions( |
238 | res.filter((item: any) => { | 255 | res.filter((item: any) => { |
@@ -305,7 +322,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -305,7 +322,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
305 | valueFin = valCp; | 322 | valueFin = valCp; |
306 | } else if (Boolean(isMixValue) && valueType === ParamValueType.FIELD) { | 323 | } else if (Boolean(isMixValue) && valueType === ParamValueType.FIELD) { |
307 | // 多选时,如果是`TreeSelect`传入值,val为当前选择项。这里处理追加上已选择的项 | 324 | // 多选时,如果是`TreeSelect`传入值,val为当前选择项。这里处理追加上已选择的项 |
308 | - valueFin = [...(getMappingValues(valueLocal || [], ParamValueType.FIELD) || []), ...valueFin]; | 325 | + valueFin = [ |
326 | + ...(getMappingValues(valueLocal || [], ParamValueType.FIELD) || []), | ||
327 | + ...valueFin, | ||
328 | + ]; | ||
309 | } else if (Boolean(isMultiple)) { | 329 | } else if (Boolean(isMultiple)) { |
310 | valueFin = valCp; | 330 | valueFin = valCp; |
311 | } else if (valueFin && valueFin.length > 0) { | 331 | } else if (valueFin && valueFin.length > 0) { |
@@ -356,92 +376,97 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -356,92 +376,97 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
356 | * @param str 字符串形式 | 376 | * @param str 字符串形式 |
357 | * @param joinParent 拼接父节点 | 377 | * @param joinParent 拼接父节点 |
358 | */ | 378 | */ |
359 | - const getName = props?.getName || ((val: string[], str?: boolean, joinParent?: boolean) => { | ||
360 | - const name: any[] = []; | ||
361 | - let flag: boolean = false; | ||
362 | - (colsTree || []).map((tree) => { | ||
363 | - (val || []).map((valItem: any) => { | ||
364 | - if (tree.key === valItem) { | ||
365 | - if (str) { | ||
366 | - name.push(tree?.titleStr); | ||
367 | - } else { | ||
368 | - name.push(tree?.title || tree?.titleStr); | ||
369 | - } | ||
370 | - flag = true; | ||
371 | - return; | ||
372 | - } | ||
373 | - }); | ||
374 | - (tree?.children || []).map((treeChild: any) => { | 379 | + const getName = |
380 | + props?.getName || | ||
381 | + ((val: string[], str?: boolean, joinParent?: boolean) => { | ||
382 | + const name: any[] = []; | ||
383 | + let flag: boolean = false; | ||
384 | + (colsTree || []).map((tree) => { | ||
375 | (val || []).map((valItem: any) => { | 385 | (val || []).map((valItem: any) => { |
376 | - if (treeChild.key === valItem) { | 386 | + if (tree.key === valItem) { |
377 | if (str) { | 387 | if (str) { |
378 | - if (typeof joinParent === 'boolean' && !joinParent) { | ||
379 | - name.push(`${treeChild?.titleStr}`); | 388 | + name.push(tree?.titleStr); |
389 | + } else { | ||
390 | + name.push(tree?.title || tree?.titleStr); | ||
391 | + } | ||
392 | + flag = true; | ||
393 | + return; | ||
394 | + } | ||
395 | + }); | ||
396 | + (tree?.children || []).map((treeChild: any) => { | ||
397 | + (val || []).map((valItem: any) => { | ||
398 | + if (treeChild.key === valItem) { | ||
399 | + if (str) { | ||
400 | + if (typeof joinParent === 'boolean' && !joinParent) { | ||
401 | + name.push(`${treeChild?.titleStr}`); | ||
402 | + } else { | ||
403 | + name.push( | ||
404 | + <> | ||
405 | + ${tree?.titleStr} | ||
406 | + <RightOutlined className={'qx-field-setter__icon'} />$ | ||
407 | + {treeChild?.titleStr} | ||
408 | + </>, | ||
409 | + ); | ||
410 | + } | ||
380 | } else { | 411 | } else { |
381 | name.push( | 412 | name.push( |
382 | <> | 413 | <> |
383 | - ${tree?.titleStr} | ||
384 | - <RightOutlined className={'qx-field-setter__icon'} />${treeChild?.titleStr} | 414 | + {tree?.title || tree?.titleStr} |
415 | + <RightOutlined className={'qx-field-setter__icon'} /> | ||
416 | + {treeChild?.title || treeChild?.titleStr} | ||
385 | </>, | 417 | </>, |
386 | ); | 418 | ); |
387 | } | 419 | } |
388 | - } else { | ||
389 | - name.push( | ||
390 | - <> | ||
391 | - {tree?.title || tree?.titleStr} | ||
392 | - <RightOutlined className={'qx-field-setter__icon'} /> | ||
393 | - {treeChild?.title || treeChild?.titleStr} | ||
394 | - </>, | ||
395 | - ); | 420 | + flag = true; |
421 | + return; | ||
396 | } | 422 | } |
397 | - flag = true; | ||
398 | - return; | ||
399 | - } | ||
400 | - }); | ||
401 | - if (treeChild.children) { | ||
402 | - treeChild.children.map((_it: any) => { | ||
403 | - (val || []).map((valItem: any) => { | ||
404 | - if (_it.key === valItem) { | ||
405 | - if (str) { | ||
406 | - if (typeof joinParent === 'boolean' && !joinParent) { | ||
407 | - name.push(`${_it?.titleStr}`); | 423 | + }); |
424 | + if (treeChild.children) { | ||
425 | + treeChild.children.map((_it: any) => { | ||
426 | + (val || []).map((valItem: any) => { | ||
427 | + if (_it.key === valItem) { | ||
428 | + if (str) { | ||
429 | + if (typeof joinParent === 'boolean' && !joinParent) { | ||
430 | + name.push(`${_it?.titleStr}`); | ||
431 | + } else { | ||
432 | + name.push( | ||
433 | + <> | ||
434 | + ${tree?.titleStr} | ||
435 | + <RightOutlined className={'qx-field-setter__icon'} />$ | ||
436 | + {treeChild?.titleStr} | ||
437 | + <RightOutlined className={'qx-field-setter__icon'} />$ | ||
438 | + {_it?.titleStr} | ||
439 | + </>, | ||
440 | + ); | ||
441 | + } | ||
408 | } else { | 442 | } else { |
409 | name.push( | 443 | name.push( |
410 | <> | 444 | <> |
411 | - ${tree?.titleStr} | ||
412 | - <RightOutlined className={'qx-field-setter__icon'} />${treeChild?.titleStr} | ||
413 | - <RightOutlined className={'qx-field-setter__icon'} />${_it?.titleStr} | 445 | + {tree?.title || tree?.titleStr} |
446 | + <RightOutlined className={'qx-field-setter__icon'} /> | ||
447 | + {treeChild?.title || treeChild?.titleStr} | ||
448 | + <RightOutlined className={'qx-field-setter__icon'} /> | ||
449 | + {_it?.title || _it?.titleStr} | ||
414 | </>, | 450 | </>, |
415 | ); | 451 | ); |
416 | } | 452 | } |
417 | - } else { | ||
418 | - name.push( | ||
419 | - <> | ||
420 | - {tree?.title || tree?.titleStr} | ||
421 | - <RightOutlined className={'qx-field-setter__icon'} /> | ||
422 | - {treeChild?.title || treeChild?.titleStr} | ||
423 | - <RightOutlined className={'qx-field-setter__icon'} /> | ||
424 | - {_it?.title || _it?.titleStr} | ||
425 | - </>, | ||
426 | - ); | 453 | + flag = true; |
454 | + return; | ||
427 | } | 455 | } |
428 | - flag = true; | ||
429 | - return; | ||
430 | - } | 456 | + }); |
431 | }); | 457 | }); |
432 | - }); | ||
433 | - } | 458 | + } |
459 | + }); | ||
434 | }); | 460 | }); |
461 | + if (colsTree === undefined && val?.[0]?.startsWith('${')) { | ||
462 | + return; | ||
463 | + } | ||
464 | + if (str) { | ||
465 | + return flag ? name : undefined; | ||
466 | + } else { | ||
467 | + return flag ? name : val; | ||
468 | + } | ||
435 | }); | 469 | }); |
436 | - if (colsTree === undefined && val?.[0]?.startsWith('${')) { | ||
437 | - return; | ||
438 | - } | ||
439 | - if (str) { | ||
440 | - return flag ? name : undefined; | ||
441 | - } else { | ||
442 | - return flag ? name : val; | ||
443 | - } | ||
444 | - }); | ||
445 | 470 | ||
446 | /** | 471 | /** |
447 | * 名称转换 | 472 | * 名称转换 |
@@ -449,7 +474,11 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -449,7 +474,11 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
449 | * @param str 字符串形式 | 474 | * @param str 字符串形式 |
450 | * @param joinParent 拼接父节点 | 475 | * @param joinParent 拼接父节点 |
451 | */ | 476 | */ |
452 | - const getCompleteName = (val: string[], str?: boolean, joinParent?: boolean) => { | 477 | + const getCompleteName = ( |
478 | + val: string[], | ||
479 | + str?: boolean, | ||
480 | + joinParent?: boolean, | ||
481 | + ) => { | ||
453 | const name: any[] = []; | 482 | const name: any[] = []; |
454 | let flag: boolean = false; | 483 | let flag: boolean = false; |
455 | 484 | ||
@@ -564,7 +593,11 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -564,7 +593,11 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
564 | type = FIELD_TYPE_PROPS.ORG; | 593 | type = FIELD_TYPE_PROPS.ORG; |
565 | } else { | 594 | } else { |
566 | // 无值时,或手动设置值时,组件类型使用传入类型判断 | 595 | // 无值时,或手动设置值时,组件类型使用传入类型判断 |
567 | - if (!valueLocal || valueLocal.length === 0 || valueLocal[0]?.type === ParamValueType.MANUAL) { | 596 | + if ( |
597 | + !valueLocal || | ||
598 | + valueLocal.length === 0 || | ||
599 | + valueLocal[0]?.type === ParamValueType.MANUAL | ||
600 | + ) { | ||
568 | if (fieldGroupType === FIELD_TYPE_PROPS.NUM) { | 601 | if (fieldGroupType === FIELD_TYPE_PROPS.NUM) { |
569 | type = COMP_TYPES.INPUT_NUMBER; | 602 | type = COMP_TYPES.INPUT_NUMBER; |
570 | } | 603 | } |
@@ -579,7 +612,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -579,7 +612,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
579 | * @param mValues | 612 | * @param mValues |
580 | * @param type | 613 | * @param type |
581 | */ | 614 | */ |
582 | - function getMappingValues(mValues: MappingValueProps[], type?: ParamValueType) { | 615 | + function getMappingValues( |
616 | + mValues: MappingValueProps[], | ||
617 | + type?: ParamValueType, | ||
618 | + ) { | ||
583 | if (!mValues || size(mValues) === 0) { | 619 | if (!mValues || size(mValues) === 0) { |
584 | return []; | 620 | return []; |
585 | } | 621 | } |
@@ -591,7 +627,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -591,7 +627,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
591 | } | 627 | } |
592 | } | 628 | } |
593 | 629 | ||
594 | - function getMappingExtValue(mValues: MappingValueProps[], type?: ParamValueType) { | 630 | + function getMappingExtValue( |
631 | + mValues: MappingValueProps[], | ||
632 | + type?: ParamValueType, | ||
633 | + ) { | ||
595 | if (!mValues || size(mValues) === 0) { | 634 | if (!mValues || size(mValues) === 0) { |
596 | return []; | 635 | return []; |
597 | } | 636 | } |
@@ -661,15 +700,21 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -661,15 +700,21 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
661 | //手机/邮箱/地址 只能选自己类型的 | 700 | //手机/邮箱/地址 只能选自己类型的 |
662 | if ( | 701 | if ( |
663 | co.extract && | 702 | co.extract && |
664 | - ['qxMobile', 'qxEmail', 'qxSwitch', 'qxBizNo', 'qxAddress', 'qxUploadImage'].indexOf( | ||
665 | - props.widget || '', | ||
666 | - ) > -1 | 703 | + [ |
704 | + 'qxMobile', | ||
705 | + 'qxEmail', | ||
706 | + 'qxSwitch', | ||
707 | + 'qxBizNo', | ||
708 | + 'qxAddress', | ||
709 | + 'qxUploadImage', | ||
710 | + ].indexOf(props.widget || '') > -1 | ||
667 | ) { | 711 | ) { |
668 | return co.extract.widget === props.widget; | 712 | return co.extract.widget === props.widget; |
669 | } | 713 | } |
670 | 714 | ||
671 | if ( | 715 | if ( |
672 | - (fieldGroupType === FIELD_TYPE_PROPS.REL || fieldGroupType === FIELD_TYPE_PROPS.REL_MULTI) && | 716 | + (fieldGroupType === FIELD_TYPE_PROPS.REL || |
717 | + fieldGroupType === FIELD_TYPE_PROPS.REL_MULTI) && | ||
673 | co.fieldGroupType === FIELD_TYPE_PROPS.FLOW_WF_RECORD | 718 | co.fieldGroupType === FIELD_TYPE_PROPS.FLOW_WF_RECORD |
674 | ) { | 719 | ) { |
675 | return true; | 720 | return true; |
@@ -690,7 +735,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -690,7 +735,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
690 | return true; | 735 | return true; |
691 | } | 736 | } |
692 | if (ex && co.extract && ex.relId) { | 737 | if (ex && co.extract && ex.relId) { |
693 | - return ex.relId === co.extract.relId || ex.relId === co.extract.qxProps?.relId; | 738 | + return ( |
739 | + ex.relId === co.extract.relId || | ||
740 | + ex.relId === co.extract.qxProps?.relId | ||
741 | + ); | ||
694 | } else { | 742 | } else { |
695 | return false; | 743 | return false; |
696 | } | 744 | } |
@@ -740,7 +788,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -740,7 +788,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
740 | ((co.extract && co.extract.fieldKey === 'id') || | 788 | ((co.extract && co.extract.fieldKey === 'id') || |
741 | co.key.endsWith('.id') || | 789 | co.key.endsWith('.id') || |
742 | co.key.endsWith('|id'))) || | 790 | co.key.endsWith('|id'))) || |
743 | - (co.fieldGroupType === 'TIME' && fieldGroupType === 'DATE' && !!props?.timeSelected) || | 791 | + (co.fieldGroupType === 'TIME' && |
792 | + fieldGroupType === 'DATE' && | ||
793 | + !!props?.timeSelected) || | ||
744 | (co.fieldGroupType === 'DATE' && | 794 | (co.fieldGroupType === 'DATE' && |
745 | fieldGroupType === 'TIME' && | 795 | fieldGroupType === 'TIME' && |
746 | !!props?.dateSelected && | 796 | !!props?.dateSelected && |
@@ -752,7 +802,8 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -752,7 +802,8 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
752 | }; | 802 | }; |
753 | 803 | ||
754 | // @ts-ignore | 804 | // @ts-ignore |
755 | - const loopCheckCol = useCallback((nodeChildren: ColsTreeProps[]) => { | 805 | + const loopCheckCol = useCallback( |
806 | + (nodeChildren: ColsTreeProps[]) => { | ||
756 | return nodeChildren.filter((co: ColsTreeProps) => { | 807 | return nodeChildren.filter((co: ColsTreeProps) => { |
757 | if (co.children && co.children.length > 0) { | 808 | if (co.children && co.children.length > 0) { |
758 | co.children = loopCheckCol(co.children); | 809 | co.children = loopCheckCol(co.children); |
@@ -777,9 +828,13 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -777,9 +828,13 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
777 | 828 | ||
778 | // 禁用选项 | 829 | // 禁用选项 |
779 | if (excludeKeys && size(excludeKeys) > 0) { | 830 | if (excludeKeys && size(excludeKeys) > 0) { |
780 | - colsTreeProps = colsTreeProps?.filter((co: ColsTreeProps) => excludeKeys.includes(co.key)); | 831 | + colsTreeProps = colsTreeProps?.filter((co: ColsTreeProps) => |
832 | + excludeKeys.includes(co.key), | ||
833 | + ); | ||
781 | colsTreeProps.map((col) => { | 834 | colsTreeProps.map((col) => { |
782 | - col.children = col?.children?.filter((co: ColsTreeProps) => excludeKeys.includes(co.key)); | 835 | + col.children = col?.children?.filter((co: ColsTreeProps) => |
836 | + excludeKeys.includes(co.key), | ||
837 | + ); | ||
783 | }); | 838 | }); |
784 | } | 839 | } |
785 | // 公式相关 | 840 | // 公式相关 |
@@ -791,7 +846,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -791,7 +846,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
791 | const formula = valueLocal?.[0]?.value; | 846 | const formula = valueLocal?.[0]?.value; |
792 | const variableObjNew: Record<string, string> = {}; | 847 | const variableObjNew: Record<string, string> = {}; |
793 | // 追加公式(简版)变量预定义 | 848 | // 追加公式(简版)变量预定义 |
794 | - const formulaOptions = NumFormulaOptions.filter((item) => item.key !== NumFormulaEnum.CUSTOM); | 849 | + const formulaOptions = NumFormulaOptions.filter( |
850 | + (item) => item.key !== NumFormulaEnum.CUSTOM, | ||
851 | + ); | ||
795 | formulaOptions.map((item) => | 852 | formulaOptions.map((item) => |
796 | Object.assign(variableObjNew, { | 853 | Object.assign(variableObjNew, { |
797 | [`\${${item.key}\}`]: item.key, | 854 | [`\${${item.key}\}`]: item.key, |
@@ -863,7 +920,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -863,7 +920,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
863 | fieldGroupType === FIELD_TYPE_PROPS.COMBINED_TEXT ? ( | 920 | fieldGroupType === FIELD_TYPE_PROPS.COMBINED_TEXT ? ( |
864 | <CodeEditor | 921 | <CodeEditor |
865 | className={'select ant-input btn-text ' + (colsTree ? '' : 'full')} | 922 | className={'select ant-input btn-text ' + (colsTree ? '' : 'full')} |
866 | - value={getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[0] || ''} | 923 | + value={ |
924 | + getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[0] || | ||
925 | + '' | ||
926 | + } | ||
867 | variableObj={variableObj} | 927 | variableObj={variableObj} |
868 | resetValue={props?.resetValue || ''} | 928 | resetValue={props?.resetValue || ''} |
869 | allowClear={!!props?.allowClear} | 929 | allowClear={!!props?.allowClear} |
@@ -877,25 +937,39 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -877,25 +937,39 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
877 | value={valueLocal} | 937 | value={valueLocal} |
878 | disabled={disabled} | 938 | disabled={disabled} |
879 | getName={getName} | 939 | getName={getName} |
880 | - onChange={(val) => onFilterValueChange(val || [], ParamValueType.MANUAL)} | 940 | + onChange={(val) => |
941 | + onFilterValueChange(val || [], ParamValueType.MANUAL) | ||
942 | + } | ||
881 | /> | 943 | /> |
882 | - ) : getFieldSimpleComType() === COMP_TYPES.INPUT_NUMBER && !isMultiple ? ( | 944 | + ) : getFieldSimpleComType() === COMP_TYPES.INPUT_NUMBER && |
945 | + !isMultiple ? ( | ||
883 | <InputNumber | 946 | <InputNumber |
884 | className={'select ' + (colsTree ? '' : 'full')} | 947 | className={'select ' + (colsTree ? '' : 'full')} |
885 | placeholder="请填写数字" | 948 | placeholder="请填写数字" |
886 | defaultValue={ | 949 | defaultValue={ |
887 | - valueLocal == undefined || valueLocal?.length == 0 || valueLocal[0]?.value == '' | 950 | + valueLocal == undefined || |
951 | + valueLocal?.length == 0 || | ||
952 | + valueLocal[0]?.value == '' | ||
888 | ? '' | 953 | ? '' |
889 | - : Number(getMappingValues(valueLocal || [], ParamValueType.MANUAL)) | 954 | + : Number( |
955 | + getMappingValues(valueLocal || [], ParamValueType.MANUAL), | ||
956 | + ) | ||
890 | } | 957 | } |
891 | value={ | 958 | value={ |
892 | - valueLocal == undefined || valueLocal?.length == 0 || valueLocal[0]?.value == '' | 959 | + valueLocal == undefined || |
960 | + valueLocal?.length == 0 || | ||
961 | + valueLocal[0]?.value == '' | ||
893 | ? '' | 962 | ? '' |
894 | - : Number(getMappingValues(valueLocal || [], ParamValueType.MANUAL)) | 963 | + : Number( |
964 | + getMappingValues(valueLocal || [], ParamValueType.MANUAL), | ||
965 | + ) | ||
895 | } | 966 | } |
896 | disabled={disabled} | 967 | disabled={disabled} |
897 | onChange={(val) => | 968 | onChange={(val) => |
898 | - onFilterValueChange([val == 0 || val ? val.toString() : ''], ParamValueType.MANUAL) | 969 | + onFilterValueChange( |
970 | + [val == 0 || val ? val.toString() : ''], | ||
971 | + ParamValueType.MANUAL, | ||
972 | + ) | ||
899 | } | 973 | } |
900 | /> | 974 | /> |
901 | ) : getFieldSimpleComType() === COMP_TYPES.USER ? ( | 975 | ) : getFieldSimpleComType() === COMP_TYPES.USER ? ( |
@@ -907,7 +981,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -907,7 +981,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
907 | getName={getName} | 981 | getName={getName} |
908 | // @ts-ignore | 982 | // @ts-ignore |
909 | params={{ ...props.params, field: props.field }} | 983 | params={{ ...props.params, field: props.field }} |
910 | - onChange={(val, type, isDelete) => onFilterValueChange(val || [], type, 0, isDelete)} | 984 | + onChange={(val, type, isDelete) => |
985 | + onFilterValueChange(val || [], type, 0, isDelete) | ||
986 | + } | ||
911 | /> | 987 | /> |
912 | ) : getFieldSimpleComType() === COMP_TYPES.ORG ? ( | 988 | ) : getFieldSimpleComType() === COMP_TYPES.ORG ? ( |
913 | <OrgSetter | 989 | <OrgSetter |
@@ -918,7 +994,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -918,7 +994,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
918 | // @ts-ignore | 994 | // @ts-ignore |
919 | getName={getName} | 995 | getName={getName} |
920 | params={{ ...props.params, field: props.field }} | 996 | params={{ ...props.params, field: props.field }} |
921 | - onChange={(val, type, isDelete) => onFilterValueChange(val || [], type, 0, isDelete)} | 997 | + onChange={(val, type, isDelete) => |
998 | + onFilterValueChange(val || [], type, 0, isDelete) | ||
999 | + } | ||
922 | /> | 1000 | /> |
923 | ) : getFieldSimpleComType() === COMP_TYPES.REL ? ( | 1001 | ) : getFieldSimpleComType() === COMP_TYPES.REL ? ( |
924 | <RelSetter | 1002 | <RelSetter |
@@ -954,30 +1032,36 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -954,30 +1032,36 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
954 | ) : getFieldSimpleComType() === COMP_TYPES.INPUT ? ( | 1032 | ) : getFieldSimpleComType() === COMP_TYPES.INPUT ? ( |
955 | <Input | 1033 | <Input |
956 | className={'select ' + (colsTree ? '' : 'full')} | 1034 | className={'select ' + (colsTree ? '' : 'full')} |
957 | - defaultValue={getMappingValues(valueLocal || [], ParamValueType.MANUAL).toString()} | 1035 | + defaultValue={getMappingValues( |
1036 | + valueLocal || [], | ||
1037 | + ParamValueType.MANUAL, | ||
1038 | + ).toString()} | ||
958 | // value={getMappingValues(valueLocal || [], ParamValueType.MANUAL).toString()} | 1039 | // value={getMappingValues(valueLocal || [], ParamValueType.MANUAL).toString()} |
959 | placeholder="请输入" | 1040 | placeholder="请输入" |
960 | disabled={disabled} | 1041 | disabled={disabled} |
961 | - onChange={(e) => onFilterValueChange([e.target.value], ParamValueType.MANUAL)} | 1042 | + onChange={(e) => |
1043 | + onFilterValueChange([e.target.value], ParamValueType.MANUAL) | ||
1044 | + } | ||
962 | /> | 1045 | /> |
963 | ) : getFieldSimpleComType() === COMP_TYPES.DATE_PICKER ? ( | 1046 | ) : getFieldSimpleComType() === COMP_TYPES.DATE_PICKER ? ( |
964 | - <DatePicker | ||
965 | - className={'select ' + (colsTree ? '' : 'full')} | ||
966 | - placeholder="请输入日期(时间)" | ||
967 | - defaultValue={ | ||
968 | - getMappingValues(valueLocal || [], ParamValueType.FIELD) ? | ||
969 | - undefined : moment( | ||
970 | - getMappingValues(valueLocal || [], ParamValueType.FIELD), | ||
971 | - formatEnum[ | ||
972 | - !!extract?.type | ||
973 | - ? extract.type === 'REL_FIELD' | ||
974 | - ? extract.renderData.type | ||
975 | - : extract.type | ||
976 | - : fieldType || 'YEAR_SEC' | ||
977 | - ] || dateFormat, | ||
978 | - ) | ||
979 | - } | ||
980 | - value={ | 1047 | + <DatePicker |
1048 | + className={'select ' + (colsTree ? '' : 'full')} | ||
1049 | + placeholder="请输入日期(时间)" | ||
1050 | + defaultValue={ | ||
1051 | + getMappingValues(valueLocal || [], ParamValueType.FIELD) | ||
1052 | + ? undefined | ||
1053 | + : moment( | ||
1054 | + getMappingValues(valueLocal || [], ParamValueType.FIELD), | ||
1055 | + formatEnum[ | ||
1056 | + !!extract?.type | ||
1057 | + ? extract.type === 'REL_FIELD' | ||
1058 | + ? extract.renderData.type | ||
1059 | + : extract.type | ||
1060 | + : fieldType || 'YEAR_SEC' | ||
1061 | + ] || dateFormat, | ||
1062 | + ) | ||
1063 | + } | ||
1064 | + value={ | ||
981 | getMappingValues(valueLocal || [], ParamValueType.FIELD) | 1065 | getMappingValues(valueLocal || [], ParamValueType.FIELD) |
982 | ? undefined | 1066 | ? undefined |
983 | : moment( | 1067 | : moment( |
@@ -1008,7 +1092,12 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1008,7 +1092,12 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1008 | ) : getFieldSimpleComType() === COMP_TYPE_FORMULA ? ( | 1092 | ) : getFieldSimpleComType() === COMP_TYPE_FORMULA ? ( |
1009 | <CodeEditor | 1093 | <CodeEditor |
1010 | className={'select ant-input btn-text ' + (colsTree ? '' : 'full')} | 1094 | className={'select ant-input btn-text ' + (colsTree ? '' : 'full')} |
1011 | - value={getMappingValues(valueLocal || [], ParamValueType.OPERATOR)?.[0] || ''} | 1095 | + value={ |
1096 | + getMappingValues( | ||
1097 | + valueLocal || [], | ||
1098 | + ParamValueType.OPERATOR, | ||
1099 | + )?.[0] || '' | ||
1100 | + } | ||
1012 | variableObj={variableObj} | 1101 | variableObj={variableObj} |
1013 | newVariable={newVariable} | 1102 | newVariable={newVariable} |
1014 | onChange={(val: string) => { | 1103 | onChange={(val: string) => { |
@@ -1028,14 +1117,24 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1028,14 +1117,24 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1028 | style={{ flex: 1 }} | 1117 | style={{ flex: 1 }} |
1029 | placeholder="请填写数字" | 1118 | placeholder="请填写数字" |
1030 | defaultValue={ | 1119 | defaultValue={ |
1031 | - valueLocal == undefined || valueLocal?.length == 0 || valueLocal[0]?.value == '' | 1120 | + valueLocal == undefined || |
1121 | + valueLocal?.length == 0 || | ||
1122 | + valueLocal[0]?.value == '' | ||
1032 | ? '' | 1123 | ? '' |
1033 | - : getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[0] | 1124 | + : getMappingValues( |
1125 | + valueLocal || [], | ||
1126 | + ParamValueType.MANUAL, | ||
1127 | + )?.[0] | ||
1034 | } | 1128 | } |
1035 | value={ | 1129 | value={ |
1036 | - valueLocal == undefined || valueLocal?.length == 0 || valueLocal[0]?.value == '' | 1130 | + valueLocal == undefined || |
1131 | + valueLocal?.length == 0 || | ||
1132 | + valueLocal[0]?.value == '' | ||
1037 | ? '' | 1133 | ? '' |
1038 | - : getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[0] | 1134 | + : getMappingValues( |
1135 | + valueLocal || [], | ||
1136 | + ParamValueType.MANUAL, | ||
1137 | + )?.[0] | ||
1039 | } | 1138 | } |
1040 | disabled={disabled} | 1139 | disabled={disabled} |
1041 | onChange={(val) => | 1140 | onChange={(val) => |
@@ -1060,14 +1159,24 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1060,14 +1159,24 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1060 | style={{ flex: 1 }} | 1159 | style={{ flex: 1 }} |
1061 | placeholder="请填写数字" | 1160 | placeholder="请填写数字" |
1062 | defaultValue={ | 1161 | defaultValue={ |
1063 | - valueLocal == undefined || valueLocal?.length == 0 || valueLocal[1]?.value == '' | 1162 | + valueLocal == undefined || |
1163 | + valueLocal?.length == 0 || | ||
1164 | + valueLocal[1]?.value == '' | ||
1064 | ? '' | 1165 | ? '' |
1065 | - : getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[1] | 1166 | + : getMappingValues( |
1167 | + valueLocal || [], | ||
1168 | + ParamValueType.MANUAL, | ||
1169 | + )?.[1] | ||
1066 | } | 1170 | } |
1067 | value={ | 1171 | value={ |
1068 | - valueLocal == undefined || valueLocal?.length == 0 || valueLocal[1]?.value == '' | 1172 | + valueLocal == undefined || |
1173 | + valueLocal?.length == 0 || | ||
1174 | + valueLocal[1]?.value == '' | ||
1069 | ? '' | 1175 | ? '' |
1070 | - : getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[1] | 1176 | + : getMappingValues( |
1177 | + valueLocal || [], | ||
1178 | + ParamValueType.MANUAL, | ||
1179 | + )?.[1] | ||
1071 | } | 1180 | } |
1072 | disabled={disabled} | 1181 | disabled={disabled} |
1073 | onChange={(val) => | 1182 | onChange={(val) => |
@@ -1088,7 +1197,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1088,7 +1197,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1088 | value={ | 1197 | value={ |
1089 | getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[0] | 1198 | getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[0] |
1090 | ? moment( | 1199 | ? moment( |
1091 | - getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[0], | 1200 | + getMappingValues( |
1201 | + valueLocal || [], | ||
1202 | + ParamValueType.MANUAL, | ||
1203 | + )?.[0], | ||
1092 | formatEnum[ | 1204 | formatEnum[ |
1093 | !!extract?.type | 1205 | !!extract?.type |
1094 | ? extract.type === 'REL_FIELD' | 1206 | ? extract.type === 'REL_FIELD' |
@@ -1139,7 +1251,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1139,7 +1251,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1139 | value={ | 1251 | value={ |
1140 | getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[1] | 1252 | getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[1] |
1141 | ? moment( | 1253 | ? moment( |
1142 | - getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[1], | 1254 | + getMappingValues( |
1255 | + valueLocal || [], | ||
1256 | + ParamValueType.MANUAL, | ||
1257 | + )?.[1], | ||
1143 | formatEnum[ | 1258 | formatEnum[ |
1144 | !!extract?.type | 1259 | !!extract?.type |
1145 | ? extract.type === 'REL_FIELD' | 1260 | ? extract.type === 'REL_FIELD' |
@@ -1185,7 +1300,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1185,7 +1300,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1185 | getCompleteName={getCompleteName} | 1300 | getCompleteName={getCompleteName} |
1186 | disabled={disabled} | 1301 | disabled={disabled} |
1187 | params={{ ...props.params, field: props.field }} | 1302 | params={{ ...props.params, field: props.field }} |
1188 | - onChange={(val) => onFilterValueChange(val || [], ParamValueType.MANUAL)} | 1303 | + onChange={(val) => |
1304 | + onFilterValueChange(val || [], ParamValueType.MANUAL) | ||
1305 | + } | ||
1189 | /> | 1306 | /> |
1190 | ) : getFieldSimpleComType() === COMP_TYPES.RANGE_TIME ? ( | 1307 | ) : getFieldSimpleComType() === COMP_TYPES.RANGE_TIME ? ( |
1191 | <div className={'select full'} style={{ width: '100%' }}> | 1308 | <div className={'select full'} style={{ width: '100%' }}> |
@@ -1195,7 +1312,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1195,7 +1312,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1195 | value={ | 1312 | value={ |
1196 | getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[0] | 1313 | getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[0] |
1197 | ? moment( | 1314 | ? moment( |
1198 | - getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[0], | 1315 | + getMappingValues( |
1316 | + valueLocal || [], | ||
1317 | + ParamValueType.MANUAL, | ||
1318 | + )?.[0], | ||
1199 | formatEnum[ | 1319 | formatEnum[ |
1200 | !!extract?.type | 1320 | !!extract?.type |
1201 | ? extract.type === 'REL_FIELD' | 1321 | ? extract.type === 'REL_FIELD' |
@@ -1235,7 +1355,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1235,7 +1355,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1235 | value={ | 1355 | value={ |
1236 | getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[1] | 1356 | getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[1] |
1237 | ? moment( | 1357 | ? moment( |
1238 | - getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[1], | 1358 | + getMappingValues( |
1359 | + valueLocal || [], | ||
1360 | + ParamValueType.MANUAL, | ||
1361 | + )?.[1], | ||
1239 | formatEnum[ | 1362 | formatEnum[ |
1240 | !!extract?.type | 1363 | !!extract?.type |
1241 | ? extract.type === 'REL_FIELD' | 1364 | ? extract.type === 'REL_FIELD' |
@@ -1279,7 +1402,8 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1279,7 +1402,8 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1279 | className={'select qx-filter-select' + (colsTree ? '' : 'full')} | 1402 | className={'select qx-filter-select' + (colsTree ? '' : 'full')} |
1280 | mode={ | 1403 | mode={ |
1281 | !Boolean(isMultiple) || | 1404 | !Boolean(isMultiple) || |
1282 | - (Boolean(isMultiple) && fieldGroupType === FIELD_TYPE_PROPS.TEXT) | 1405 | + (Boolean(isMultiple) && |
1406 | + fieldGroupType === FIELD_TYPE_PROPS.TEXT) | ||
1283 | ? 'tags' | 1407 | ? 'tags' |
1284 | : 'multiple' | 1408 | : 'multiple' |
1285 | } | 1409 | } |
@@ -1302,7 +1426,8 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1302,7 +1426,8 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1302 | closable={closable} | 1426 | closable={closable} |
1303 | onClose={onClose} | 1427 | onClose={onClose} |
1304 | > | 1428 | > |
1305 | - {_val === undefined ? null : String(_val) === String([String(label)]) && | 1429 | + {_val === undefined ? null : String(_val) === |
1430 | + String([String(label)]) && | ||
1306 | String(label).startsWith('${') ? ( | 1431 | String(label).startsWith('${') ? ( |
1307 | <span style={{ color: 'red' }}>已缺失</span> | 1432 | <span style={{ color: 'red' }}>已缺失</span> |
1308 | ) : ( | 1433 | ) : ( |
@@ -1325,7 +1450,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1325,7 +1450,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1325 | ); | 1450 | ); |
1326 | }} | 1451 | }} |
1327 | onChange={(val) => { | 1452 | onChange={(val) => { |
1328 | - const valOptionsArr = getValueOptions().map((valItem: any) => valItem?.key); | 1453 | + const valOptionsArr = getValueOptions().map( |
1454 | + (valItem: any) => valItem?.key, | ||
1455 | + ); | ||
1329 | // 原值类型 | 1456 | // 原值类型 |
1330 | let valueType: any; | 1457 | let valueType: any; |
1331 | // 查询原值(如果存在于`valueLocal`下)类型 | 1458 | // 查询原值(如果存在于`valueLocal`下)类型 |
@@ -1343,9 +1470,17 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1343,9 +1470,17 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1343 | valueType = ParamValueType.MANUAL; | 1470 | valueType = ParamValueType.MANUAL; |
1344 | } | 1471 | } |
1345 | } | 1472 | } |
1346 | - if (fieldGroupType === FIELD_TYPE_PROPS.BOOL && !Boolean(isMultiple)) { | 1473 | + if ( |
1474 | + fieldGroupType === FIELD_TYPE_PROPS.BOOL && | ||
1475 | + !Boolean(isMultiple) | ||
1476 | + ) { | ||
1347 | if (val.length > 0) { | 1477 | if (val.length > 0) { |
1348 | - onFilterValueChange([val[val.length - 1]], valueType, -1, true); | 1478 | + onFilterValueChange( |
1479 | + [val[val.length - 1]], | ||
1480 | + valueType, | ||
1481 | + -1, | ||
1482 | + true, | ||
1483 | + ); | ||
1349 | } else { | 1484 | } else { |
1350 | onFilterValueChange(val, valueType, -1, true); | 1485 | onFilterValueChange(val, valueType, -1, true); |
1351 | } | 1486 | } |
@@ -1355,17 +1490,26 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1355,17 +1490,26 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1355 | }} | 1490 | }} |
1356 | > | 1491 | > |
1357 | {getValueOptions().map((valItem: any) => ( | 1492 | {getValueOptions().map((valItem: any) => ( |
1358 | - <Option key={valItem?.key || valItem?.code} value={valItem?.key || valItem?.code}> | 1493 | + <Option |
1494 | + key={valItem?.key || valItem?.code} | ||
1495 | + value={valItem?.key || valItem?.code} | ||
1496 | + > | ||
1359 | {valItem?.value || valItem?.name} | 1497 | {valItem?.value || valItem?.name} |
1360 | </Option> | 1498 | </Option> |
1361 | ))} | 1499 | ))} |
1362 | {fieldGroupType && fieldGroupType === FIELD_TYPE_PROPS.DATE && ( | 1500 | {fieldGroupType && fieldGroupType === FIELD_TYPE_PROPS.DATE && ( |
1363 | - <Option value={'__action_date'} style={{ borderTop: '1px solid #f0f0f0' }}> | 1501 | + <Option |
1502 | + value={'__action_date'} | ||
1503 | + style={{ borderTop: '1px solid #f0f0f0' }} | ||
1504 | + > | ||
1364 | 指定日期 | 1505 | 指定日期 |
1365 | </Option> | 1506 | </Option> |
1366 | )} | 1507 | )} |
1367 | {fieldGroupType && fieldGroupType === FIELD_TYPE_PROPS.TIME && ( | 1508 | {fieldGroupType && fieldGroupType === FIELD_TYPE_PROPS.TIME && ( |
1368 | - <Option value={'__action_time'} style={{ borderTop: '1px solid #f0f0f0' }}> | 1509 | + <Option |
1510 | + value={'__action_time'} | ||
1511 | + style={{ borderTop: '1px solid #f0f0f0' }} | ||
1512 | + > | ||
1369 | 指定时间 | 1513 | 指定时间 |
1370 | </Option> | 1514 | </Option> |
1371 | )} | 1515 | )} |
@@ -1391,10 +1535,14 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1391,10 +1535,14 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1391 | : disabled | 1535 | : disabled |
1392 | : disabled | 1536 | : disabled |
1393 | } | 1537 | } |
1394 | - allowClear={!isMixValue || valueLocal?.[0]?.type === ParamValueType.FIELD} | 1538 | + allowClear={ |
1539 | + !isMixValue || valueLocal?.[0]?.type === ParamValueType.FIELD | ||
1540 | + } | ||
1395 | onChange={(val) => { | 1541 | onChange={(val) => { |
1396 | const valLocal = typeof val === 'string' ? [val] : val; | 1542 | const valLocal = typeof val === 'string' ? [val] : val; |
1397 | - const valOptionsArr = getValueOptions().map((valItem: any) => valItem?.key); | 1543 | + const valOptionsArr = getValueOptions().map( |
1544 | + (valItem: any) => valItem?.key, | ||
1545 | + ); | ||
1398 | let valueType = (valOptionsArr || []).includes(valLocal?.[0]) | 1546 | let valueType = (valOptionsArr || []).includes(valLocal?.[0]) |
1399 | ? ParamValueType.OPERATOR | 1547 | ? ParamValueType.OPERATOR |
1400 | : ParamValueType.MANUAL; | 1548 | : ParamValueType.MANUAL; |
@@ -1405,27 +1553,42 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1405,27 +1553,42 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1405 | }} | 1553 | }} |
1406 | > | 1554 | > |
1407 | {fieldGroupType && fieldGroupType === FIELD_TYPE_PROPS.DATE && ( | 1555 | {fieldGroupType && fieldGroupType === FIELD_TYPE_PROPS.DATE && ( |
1408 | - <Option value={'__action_date'} style={{ borderBottom: '1px solid #f0f0f0' }}> | 1556 | + <Option |
1557 | + value={'__action_date'} | ||
1558 | + style={{ borderBottom: '1px solid #f0f0f0' }} | ||
1559 | + > | ||
1409 | 指定日期 | 1560 | 指定日期 |
1410 | </Option> | 1561 | </Option> |
1411 | )} | 1562 | )} |
1412 | {fieldGroupType && fieldGroupType === FIELD_TYPE_PROPS.USER && ( | 1563 | {fieldGroupType && fieldGroupType === FIELD_TYPE_PROPS.USER && ( |
1413 | - <Option value={'__action_user'} style={{ borderBottom: '1px solid #f0f0f0' }}> | 1564 | + <Option |
1565 | + value={'__action_user'} | ||
1566 | + style={{ borderBottom: '1px solid #f0f0f0' }} | ||
1567 | + > | ||
1414 | 选择人员 | 1568 | 选择人员 |
1415 | </Option> | 1569 | </Option> |
1416 | )} | 1570 | )} |
1417 | {fieldGroupType && fieldGroupType === FIELD_TYPE_PROPS.ORG && ( | 1571 | {fieldGroupType && fieldGroupType === FIELD_TYPE_PROPS.ORG && ( |
1418 | - <Option value={'__action_org'} style={{ borderBottom: '1px solid #f0f0f0' }}> | 1572 | + <Option |
1573 | + value={'__action_org'} | ||
1574 | + style={{ borderBottom: '1px solid #f0f0f0' }} | ||
1575 | + > | ||
1419 | 选择部门 | 1576 | 选择部门 |
1420 | </Option> | 1577 | </Option> |
1421 | )} | 1578 | )} |
1422 | {fieldGroupType && fieldGroupType === FIELD_TYPE_PROPS.TIME && ( | 1579 | {fieldGroupType && fieldGroupType === FIELD_TYPE_PROPS.TIME && ( |
1423 | - <Option value={'__action_time'} style={{ borderBottom: '1px solid #f0f0f0' }}> | 1580 | + <Option |
1581 | + value={'__action_time'} | ||
1582 | + style={{ borderBottom: '1px solid #f0f0f0' }} | ||
1583 | + > | ||
1424 | 指定时间 | 1584 | 指定时间 |
1425 | </Option> | 1585 | </Option> |
1426 | )} | 1586 | )} |
1427 | {getValueOptions().map((valItem: any) => ( | 1587 | {getValueOptions().map((valItem: any) => ( |
1428 | - <Option key={valItem?.key || valItem?.code} value={valItem?.key || valItem?.code}> | 1588 | + <Option |
1589 | + key={valItem?.key || valItem?.code} | ||
1590 | + value={valItem?.key || valItem?.code} | ||
1591 | + > | ||
1429 | {valItem?.value || valItem?.name} | 1592 | {valItem?.value || valItem?.name} |
1430 | </Option> | 1593 | </Option> |
1431 | ))} | 1594 | ))} |
@@ -1438,7 +1601,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1438,7 +1601,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1438 | // @ts-ignore | 1601 | // @ts-ignore |
1439 | width={ | 1602 | width={ |
1440 | props.popWidth || | 1603 | props.popWidth || |
1441 | - (currentElem && currentElem.current && currentElem.current.clientWidth + 'px') | 1604 | + (currentElem && |
1605 | + currentElem.current && | ||
1606 | + currentElem.current.clientWidth + 'px') | ||
1442 | } | 1607 | } |
1443 | data={colsTreeProps} | 1608 | data={colsTreeProps} |
1444 | popFooter={props.extraFooter} | 1609 | popFooter={props.extraFooter} |
@@ -1471,7 +1636,8 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1471,7 +1636,8 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1471 | //TODO 临时方案,可多选字段 | 1636 | //TODO 临时方案,可多选字段 |
1472 | if (FIELD_TYPE.FORMULA === fieldType) { | 1637 | if (FIELD_TYPE.FORMULA === fieldType) { |
1473 | const _historyValue = | 1638 | const _historyValue = |
1474 | - getMappingValues(valueLocal || [], ParamValueType.FIELD) || []; | 1639 | + getMappingValues(valueLocal || [], ParamValueType.FIELD) || |
1640 | + []; | ||
1475 | if (!_historyValue.includes(val.toString())) { | 1641 | if (!_historyValue.includes(val.toString())) { |
1476 | const newVal = [..._historyValue, ...[val.toString()]]; | 1642 | const newVal = [..._historyValue, ...[val.toString()]]; |
1477 | onFilterValueChange(newVal, ParamValueType.FIELD, 0, true); | 1643 | onFilterValueChange(newVal, ParamValueType.FIELD, 0, true); |
@@ -1488,7 +1654,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1488,7 +1654,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1488 | > | 1654 | > |
1489 | <ControlOutlined /> | 1655 | <ControlOutlined /> |
1490 | {props?.iconText ? ( | 1656 | {props?.iconText ? ( |
1491 | - <span className={'qx-field-setter__icon-text'}>{props.iconText}</span> | 1657 | + <span className={'qx-field-setter__icon-text'}> |
1658 | + {props.iconText} | ||
1659 | + </span> | ||
1492 | ) : null} | 1660 | ) : null} |
1493 | </span> | 1661 | </span> |
1494 | </QxFieldPopover> | 1662 | </QxFieldPopover> |
@@ -1520,7 +1688,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1520,7 +1688,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1520 | } | 1688 | } |
1521 | placeholder="请输入日期(时间)" | 1689 | placeholder="请输入日期(时间)" |
1522 | defaultValue={ | 1690 | defaultValue={ |
1523 | - !isEmpty(getMappingExtValue(valueLocal || [], ParamValueType.MANUAL)) | 1691 | + !isEmpty( |
1692 | + getMappingExtValue(valueLocal || [], ParamValueType.MANUAL), | ||
1693 | + ) | ||
1524 | ? moment( | 1694 | ? moment( |
1525 | getMappingExtValue(valueLocal || [], ParamValueType.MANUAL), | 1695 | getMappingExtValue(valueLocal || [], ParamValueType.MANUAL), |
1526 | formatEnum[ | 1696 | formatEnum[ |
@@ -1534,7 +1704,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1534,7 +1704,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1534 | : undefined | 1704 | : undefined |
1535 | } | 1705 | } |
1536 | value={ | 1706 | value={ |
1537 | - !isEmpty(getMappingExtValue(valueLocal || [], ParamValueType.MANUAL)) | 1707 | + !isEmpty( |
1708 | + getMappingExtValue(valueLocal || [], ParamValueType.MANUAL), | ||
1709 | + ) | ||
1538 | ? moment( | 1710 | ? moment( |
1539 | getMappingExtValue(valueLocal || [], ParamValueType.MANUAL), | 1711 | getMappingExtValue(valueLocal || [], ParamValueType.MANUAL), |
1540 | formatEnum[ | 1712 | formatEnum[ |
@@ -1556,7 +1728,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1556,7 +1728,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1556 | style={{ width: '100%', marginTop: '6px' }} | 1728 | style={{ width: '100%', marginTop: '6px' }} |
1557 | placeholder="请输入时间" | 1729 | placeholder="请输入时间" |
1558 | defaultValue={ | 1730 | defaultValue={ |
1559 | - !isEmpty(getMappingExtValue(valueLocal || [], ParamValueType.MANUAL)) | 1731 | + !isEmpty( |
1732 | + getMappingExtValue(valueLocal || [], ParamValueType.MANUAL), | ||
1733 | + ) | ||
1560 | ? moment( | 1734 | ? moment( |
1561 | getMappingExtValue(valueLocal || [], ParamValueType.MANUAL), | 1735 | getMappingExtValue(valueLocal || [], ParamValueType.MANUAL), |
1562 | formatEnum[ | 1736 | formatEnum[ |
@@ -1570,7 +1744,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1570,7 +1744,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1570 | : undefined | 1744 | : undefined |
1571 | } | 1745 | } |
1572 | value={ | 1746 | value={ |
1573 | - !isEmpty(getMappingExtValue(valueLocal || [], ParamValueType.MANUAL)) | 1747 | + !isEmpty( |
1748 | + getMappingExtValue(valueLocal || [], ParamValueType.MANUAL), | ||
1749 | + ) | ||
1574 | ? moment( | 1750 | ? moment( |
1575 | getMappingExtValue(valueLocal || [], ParamValueType.MANUAL), | 1751 | getMappingExtValue(valueLocal || [], ParamValueType.MANUAL), |
1576 | formatEnum[ | 1752 | formatEnum[ |
@@ -1616,4 +1792,4 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | @@ -1616,4 +1792,4 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ | ||
1616 | )} | 1792 | )} |
1617 | </div> | 1793 | </div> |
1618 | ); | 1794 | ); |
1619 | -} | 1795 | +}; |
1 | import type { DataNode } from 'antd/lib/tree'; | 1 | import type { DataNode } from 'antd/lib/tree'; |
2 | +import { FIELD_TYPE_PROPS } from '../constant'; | ||
3 | + | ||
4 | +export { FIELD_TYPE_PROPS }; | ||
2 | 5 | ||
3 | export type OperatorProps = { | 6 | export type OperatorProps = { |
4 | text: string; | 7 | text: string; |
@@ -691,71 +694,6 @@ export interface ColsTreeProps extends DataNode { | @@ -691,71 +694,6 @@ export interface ColsTreeProps extends DataNode { | ||
691 | type?: any; | 694 | type?: any; |
692 | } | 695 | } |
693 | 696 | ||
694 | -export enum FIELD_TYPE_PROPS { | ||
695 | - EMPTY = '', | ||
696 | - // 文本 | ||
697 | - TEXT = 'TEXT', | ||
698 | - STRING = 'STRING', | ||
699 | - | ||
700 | - // 日期 | ||
701 | - DATE = 'DATE', | ||
702 | - DATE_TIME = 'DATE_TIME', | ||
703 | - // 人员 | ||
704 | - USER = 'USER', | ||
705 | - USER_MULTI = 'USER_MULTI', | ||
706 | - // 部门 | ||
707 | - ORG = 'ORG', | ||
708 | - ORG_MULTI = 'ORG_MULTI', | ||
709 | - // 数字 | ||
710 | - NUM = 'NUM', | ||
711 | - // 布尔 | ||
712 | - BOOL = 'BOOL', | ||
713 | - // 枚举 | ||
714 | - ENUM = 'ENUM', | ||
715 | - ENUM_MULTI = 'ENUM_MULTI', | ||
716 | - // 文件 | ||
717 | - FILE = 'FILE', | ||
718 | - | ||
719 | - // 公式:数值类 | ||
720 | - FORMULA = 'FORMULA', | ||
721 | - | ||
722 | - DOUBLE = 'DOUBLE', | ||
723 | - INTEGER = 'INTEGER', | ||
724 | - DECIMAL = 'DECIMAL', | ||
725 | - PERCENT = 'PERCENT', | ||
726 | - | ||
727 | - YEAR = 'YEAR', | ||
728 | - YEAR_MONTH = 'YEAR_MONTH', | ||
729 | - YEAR_HOUR = 'YEAR_HOUR', | ||
730 | - YEAR_DATE = 'YEAR_DATE', | ||
731 | - YEAR_MIN = 'YEAR_MIN', | ||
732 | - YEAR_SEC = 'YEAR_SEC', | ||
733 | - HOUR = 'HOUR', | ||
734 | - HOUR_MIN = 'HOUR_MIN', | ||
735 | - HOUR_SEC = 'HOUR_SEC', | ||
736 | - | ||
737 | - TREE = 'TREE', | ||
738 | - REL = 'REL', | ||
739 | - REL_MULTI = 'REL_MULTI', | ||
740 | - REL_FIELD = 'REL_FIELD', | ||
741 | - | ||
742 | - TABLE = 'TABLE', | ||
743 | - | ||
744 | - // 流程专用 | ||
745 | - FLOW_WF_APRV_USR = 'FLOW_WF_APRV_USR', | ||
746 | - FLOW_WF_DQ_MODEL = 'FLOW_WF_DQ_MODEL', | ||
747 | - FLOW_WF_RECORD = 'FLOW_WF_RECORD', | ||
748 | - | ||
749 | - //参数专用 | ||
750 | - OBJECT = 'OBJECT', | ||
751 | - ARRAY = 'ARRAY', | ||
752 | - | ||
753 | - // 组合文本,用于文本和变量组合 | ||
754 | - COMBINED_TEXT = 'COMBINED_TEXT', | ||
755 | - // 时间 | ||
756 | - TIME = 'TIME', | ||
757 | -} | ||
758 | - | ||
759 | export interface QxQueryProps { | 697 | export interface QxQueryProps { |
760 | paramMappings?: ParamMappingProps[]; | 698 | paramMappings?: ParamMappingProps[]; |
761 | sqlType?: SqlTypeProps; | 699 | sqlType?: SqlTypeProps; |
@@ -855,7 +793,11 @@ export const getOperationsType = (data: any) => { | @@ -855,7 +793,11 @@ export const getOperationsType = (data: any) => { | ||
855 | fileTypeTem = fieldType; | 793 | fileTypeTem = fieldType; |
856 | } else if (fieldType === FIELD_TYPE_PROPS.REL_FIELD) { | 794 | } else if (fieldType === FIELD_TYPE_PROPS.REL_FIELD) { |
857 | fileTypeTem = data.refType; | 795 | fileTypeTem = data.refType; |
858 | - } else if (fieldType === 'HOUR_MIN' || fieldType === 'HOUR_SEC' || fieldType === 'TIME') { | 796 | + } else if ( |
797 | + fieldType === 'HOUR_MIN' || | ||
798 | + fieldType === 'HOUR_SEC' || | ||
799 | + fieldType === 'TIME' | ||
800 | + ) { | ||
859 | fileTypeTem = FIELD_TYPE_PROPS.TIME; | 801 | fileTypeTem = FIELD_TYPE_PROPS.TIME; |
860 | } else { | 802 | } else { |
861 | fileTypeTem = FIELD_TYPE_PROPS.TEXT; | 803 | fileTypeTem = FIELD_TYPE_PROPS.TEXT; |
1 | +import React, { useEffect, useState } from 'react'; | ||
2 | + | ||
3 | +import { FunctionProps } from '../index'; | ||
4 | + | ||
5 | +type DescProps = { | ||
6 | + scriptEdit: boolean; | ||
7 | + funcData: FunctionProps | undefined | null; | ||
8 | +}; | ||
9 | + | ||
10 | +const highLightFunc = (str: string | undefined, func: string) => { | ||
11 | + if (!str) return ''; | ||
12 | + const startIndex = str.indexOf(func); | ||
13 | + const endIndex = startIndex + func.length; | ||
14 | + if (startIndex > -1) { | ||
15 | + return ( | ||
16 | + <> | ||
17 | + <span>{str.slice(0, startIndex)}</span> | ||
18 | + <span className="highlight">{func}</span> | ||
19 | + <span>{str.slice(endIndex)}</span> | ||
20 | + </> | ||
21 | + ); | ||
22 | + } else { | ||
23 | + return str; | ||
24 | + } | ||
25 | +}; | ||
26 | + | ||
27 | +const DescBox: React.FC<DescProps> = ({ scriptEdit, funcData }) => { | ||
28 | + const defaulTips = () => { | ||
29 | + return ( | ||
30 | + <ul className={'fx-ul'}> | ||
31 | + <li className="line">从左侧面板选择函数和变量,或输入函数</li> | ||
32 | + <li className="line">公式编辑示例:PRODUCT(单价, 数量)</li> | ||
33 | + <li | ||
34 | + className="line" | ||
35 | + dangerouslySetInnerHTML={{ | ||
36 | + __html: | ||
37 | + '支持运算符:加(+)、减(-)、乘(*)、除(/)、大于(>)、小于(<)、等于(==)、不等于(!=)、大于等于(>=)、小于等于(<=)', | ||
38 | + }} | ||
39 | + ></li> | ||
40 | + <li className="line">支持用"."来获取对象/对象数组的属性</li> | ||
41 | + </ul> | ||
42 | + ); | ||
43 | + }; | ||
44 | + | ||
45 | + const scriptTips = () => { | ||
46 | + return ( | ||
47 | + <ul className={'fx-ul'}> | ||
48 | + <li className="line"> | ||
49 | + 支持JavaScript ES5标准语法,请在函数头部定义变量接受字段动态值 | ||
50 | + </li> | ||
51 | + <li className="line">脚本需要有‘return’ 关键字显式的返回数据类型</li> | ||
52 | + <li className="line">每行最大支持1000字符,最多支持2000行</li> | ||
53 | + </ul> | ||
54 | + ); | ||
55 | + }; | ||
56 | + | ||
57 | + const fxTips = () => { | ||
58 | + const { title, desc, formula, returnType, demon, funcNameEg } = | ||
59 | + funcData || {}; | ||
60 | + return ( | ||
61 | + <> | ||
62 | + <div className="line"> | ||
63 | + <span style={{ fontWeight: 600 }}>描述</span>: | ||
64 | + <span className="highlight">{title}</span> | ||
65 | + {desc} | ||
66 | + </div> | ||
67 | + <div className="line"> | ||
68 | + <span style={{ fontWeight: 600 }}>表达式</span>: | ||
69 | + {highLightFunc(formula, funcNameEg)} | ||
70 | + </div> | ||
71 | + <div className="line"> | ||
72 | + <span style={{ fontWeight: 600 }}>返回值类型</span>:{returnType} | ||
73 | + </div> | ||
74 | + <div className="line"> | ||
75 | + <span style={{ fontWeight: 600 }}>示例</span>: | ||
76 | + {highLightFunc(demon, funcNameEg)} | ||
77 | + </div> | ||
78 | + </> | ||
79 | + ); | ||
80 | + }; | ||
81 | + | ||
82 | + const [tips, setTips] = useState<any>(null); | ||
83 | + useEffect(() => { | ||
84 | + let tips = !!funcData ? fxTips() : scriptEdit ? scriptTips() : defaulTips(); | ||
85 | + setTips(tips); | ||
86 | + }, [scriptEdit, funcData]); | ||
87 | + | ||
88 | + return ( | ||
89 | + <div className="describle-box"> | ||
90 | + <div className="title-bar">{funcData?.title}</div> | ||
91 | + <div className="desc-content">{tips}</div> | ||
92 | + </div> | ||
93 | + ); | ||
94 | +}; | ||
95 | + | ||
96 | +export default DescBox; |
1 | +import { DownOutlined, SearchOutlined } from '@ant-design/icons'; | ||
2 | +import { Empty, Input, Tree } from 'antd'; | ||
3 | +import _ from 'lodash-es'; | ||
4 | +import React, { | ||
5 | + memo, | ||
6 | + useEffect, | ||
7 | + useLayoutEffect, | ||
8 | + useMemo, | ||
9 | + useRef, | ||
10 | + useState, | ||
11 | +} from 'react'; | ||
12 | +import { FunctionProps } from '../index'; | ||
13 | +import emptyImg from '../svg/custom_chart_null.png'; | ||
14 | +import { handleHighlight } from './var-picker'; | ||
15 | +type PickProps = { | ||
16 | + dataSource: FunctionProps[]; | ||
17 | + onHover: (nodeData: FunctionProps | null) => void; | ||
18 | + onPick: (funcData: FunctionProps) => void; | ||
19 | + pickFunc: (funcData: FunctionProps) => void; | ||
20 | +}; | ||
21 | +const FxPicker: React.FC<PickProps> = ({ | ||
22 | + onHover, | ||
23 | + onPick, | ||
24 | + pickFunc, | ||
25 | + ...props | ||
26 | +}): JSX.Element => { | ||
27 | + const [searchVal, setSearchVal] = useState(''); | ||
28 | + const [selectIndex, setSelectIndex] = useState(0); | ||
29 | + const searchInputRef = useRef(null); | ||
30 | + const [filterData, setFilterData] = useState<FunctionProps[]>([]); | ||
31 | + // const selectFunc = useRef<string>(''); | ||
32 | + // const change | ||
33 | + | ||
34 | + const dataSource: FunctionProps[] = useMemo(() => { | ||
35 | + let cloneData = _.cloneDeep(props.dataSource || []); | ||
36 | + cloneData.forEach((item) => { | ||
37 | + (item.children || []).forEach((it: any) => { | ||
38 | + // @ts-ignore | ||
39 | + if (!it.titleCopy && typeof it.title === 'string') { | ||
40 | + it.titleCopy = it.title; | ||
41 | + } | ||
42 | + // @ts-ignore | ||
43 | + it.title = <span style={{ display: 'inline-block' }}>{it.title}</span>; | ||
44 | + }); | ||
45 | + }); | ||
46 | + return cloneData; | ||
47 | + }, [props.dataSource]); | ||
48 | + | ||
49 | + useEffect(() => { | ||
50 | + let cloneData = _.cloneDeep(dataSource); | ||
51 | + cloneData.forEach((item: any) => { | ||
52 | + if (item.title?.includes(searchVal)) { | ||
53 | + item.title = handleHighlight(item.title, searchVal); | ||
54 | + item._hidden = false; | ||
55 | + } else { | ||
56 | + if ((item.children || []).length === 0) { | ||
57 | + item._hidden = true; | ||
58 | + } | ||
59 | + (item.children || []).forEach((it: any) => { | ||
60 | + if (!it.funcNameEg.toLowerCase()?.includes(searchVal.toLowerCase())) { | ||
61 | + it._hidden = true; | ||
62 | + item.hiddenLen = (item.hiddenLen || 0) + 1; | ||
63 | + if (item.hiddenLen === item.children?.length) { | ||
64 | + item._hidden = true; | ||
65 | + } | ||
66 | + } else { | ||
67 | + const title = handleHighlight(it.titleCopy, searchVal, 'fx'); | ||
68 | + it.title = <span style={{ display: 'inline-block' }}>{title}</span>; | ||
69 | + } | ||
70 | + }); | ||
71 | + } | ||
72 | + }); | ||
73 | + cloneData = cloneData.filter((item: any) => item._hidden !== true); | ||
74 | + cloneData.forEach((item) => { | ||
75 | + item.children = (item.children || []).filter((it: any) => !it._hidden); | ||
76 | + }); | ||
77 | + setFilterData(cloneData); | ||
78 | + }, [searchVal]); | ||
79 | + | ||
80 | + // 根据查询value过滤公式列表数据 | ||
81 | + // const dataSourceFilter: FunctionProps[] = useMemo(() => { | ||
82 | + // // 扁平化dataSource | ||
83 | + // const dataSourceFlatten: FunctionProps[] = []; | ||
84 | + // flatten(dataSource, dataSourceFlatten); | ||
85 | + // return dataSourceFlatten.filter((ds) => ds.title.includes(searchVal)); | ||
86 | + // }, [searchVal, dataSource]); | ||
87 | + | ||
88 | + const handleSearch = _.debounce((val: string) => { | ||
89 | + setSelectIndex(0); | ||
90 | + setSearchVal(val); | ||
91 | + }, 200); | ||
92 | + | ||
93 | + // 搜索框值变化时触发 | ||
94 | + const onHandleSearchChange = (e: any) => { | ||
95 | + const val = e.target.value; | ||
96 | + handleSearch(val); | ||
97 | + }; | ||
98 | + | ||
99 | + // 设置当前选中的函数公式 | ||
100 | + // const onHandlePickFunc = (funcData: FunctionProps) => { | ||
101 | + // _.isFunction(onPick) && onPick(funcData); | ||
102 | + // searchInputRef?.current?.blur(); | ||
103 | + // }; | ||
104 | + /** | ||
105 | + * 设置键盘事件逻辑 | ||
106 | + * keyCode 38:ArrowUp 40:ArrowDown 13: Enter | ||
107 | + */ | ||
108 | + // const onHandleKeydown = (e: any) => { | ||
109 | + // if (e.keyCode == 38 && selectIndex > 0) { | ||
110 | + // setSelectIndex(selectIndex - 1); | ||
111 | + // } else if (e.keyCode == 40 && selectIndex < dataSourceFilter.length - 1) { | ||
112 | + // setSelectIndex(selectIndex + 1); | ||
113 | + // } else if (e.keyCode == 13) { | ||
114 | + // onHandlePickFunc(dataSourceFilter[selectIndex]); | ||
115 | + // } | ||
116 | + // _.isFunction(onHover) && onHover(dataSourceFilter[selectIndex]); | ||
117 | + // }; | ||
118 | + | ||
119 | + // 设置popover-content内滚动条位置。DOM更新之后立即执行 | ||
120 | + useLayoutEffect(() => { | ||
121 | + const dom = document.getElementsByClassName('fun-selected')[0]; | ||
122 | + if (dom) { | ||
123 | + dom.scrollIntoView(Object.assign({ block: 'center', inline: 'nearest' })); | ||
124 | + } | ||
125 | + }, [selectIndex]); | ||
126 | + | ||
127 | + // const onHandleUpload = () => { | ||
128 | + // message.info('功能暂未开发'); | ||
129 | + // }; | ||
130 | + | ||
131 | + // const onHandleCustomFunc = () => { | ||
132 | + // message.info('功能暂未开发'); | ||
133 | + // }; | ||
134 | + | ||
135 | + const onHandleMouseEnter = (event: any, nodeData: FunctionProps) => { | ||
136 | + if (nodeData.children && nodeData.children.length > 0) return; | ||
137 | + if (_.isFunction(onHover)) { | ||
138 | + onHover(nodeData); | ||
139 | + } | ||
140 | + }; | ||
141 | + | ||
142 | + const onHandleSelectTNode = (key: any, event: any) => { | ||
143 | + if (event.node.children && event.node.children.length > 0) return; | ||
144 | + if (_.isFunction(onPick)) { | ||
145 | + onPick(event.node); | ||
146 | + } | ||
147 | + if (_.isFunction(onHover)) { | ||
148 | + onHover(event.node); | ||
149 | + } | ||
150 | + pickFunc(event.node); | ||
151 | + // selectFunc.current = event.node.funcNameEg; | ||
152 | + }; | ||
153 | + | ||
154 | + // 设置popover-content | ||
155 | + // const searchContent = useMemo(() => { | ||
156 | + // return ( | ||
157 | + // <div className="search-content"> | ||
158 | + // {dataSourceFilter.map((ds, index) => { | ||
159 | + // return ( | ||
160 | + // <div | ||
161 | + // className={index === selectIndex ? 'fun-selected' : ''} | ||
162 | + // key={ds.key} | ||
163 | + // onClick={() => onHandlePickFunc(ds)} | ||
164 | + // > | ||
165 | + // {ds.title} | ||
166 | + // </div> | ||
167 | + // ); | ||
168 | + // })} | ||
169 | + // </div> | ||
170 | + // ); | ||
171 | + // }, [dataSourceFilter, selectIndex]); | ||
172 | + | ||
173 | + const treeRender = useMemo(() => { | ||
174 | + return searchVal.length && !filterData?.length ? ( | ||
175 | + <Empty | ||
176 | + image={emptyImg} | ||
177 | + style={{ | ||
178 | + height: '100%', | ||
179 | + width: '100%', | ||
180 | + display: 'flex', | ||
181 | + flexDirection: 'column', | ||
182 | + justifyContent: 'center', | ||
183 | + color: '#999', | ||
184 | + }} | ||
185 | + /> | ||
186 | + ) : ( | ||
187 | + <Tree | ||
188 | + key={Math.random()} | ||
189 | + rootClassName={'custom-tree'} | ||
190 | + blockNode={true} | ||
191 | + selectable={true} | ||
192 | + defaultExpandAll={!!searchVal.length} | ||
193 | + switcherIcon={<DownOutlined />} | ||
194 | + titleRender={(nodeData) => { | ||
195 | + const { title, titleDesc } = nodeData; | ||
196 | + return ( | ||
197 | + <div | ||
198 | + className="custom-title" | ||
199 | + onMouseEnter={(event) => { | ||
200 | + onHandleMouseEnter(event, nodeData); | ||
201 | + }} | ||
202 | + onMouseLeave={() => { | ||
203 | + // if (nodeData.funcNameEg === selectFunc.current) { | ||
204 | + // selectFunc.current = ''; | ||
205 | + // return; | ||
206 | + // } | ||
207 | + if (_.isFunction(onHover)) { | ||
208 | + onHover(null); | ||
209 | + } | ||
210 | + }} | ||
211 | + > | ||
212 | + <span>{title}</span> | ||
213 | + <span>{titleDesc}</span> | ||
214 | + </div> | ||
215 | + ); | ||
216 | + }} | ||
217 | + treeData={searchVal.length ? filterData : dataSource} | ||
218 | + defaultExpandedKeys={['COMMON']} | ||
219 | + onSelect={onHandleSelectTNode} | ||
220 | + /> | ||
221 | + ); | ||
222 | + }, [dataSource, filterData]); | ||
223 | + | ||
224 | + return ( | ||
225 | + <div className="function-box"> | ||
226 | + <div className="search-bar"> | ||
227 | + {/*<Popover*/} | ||
228 | + {/* overlayClassName={'custom-search-popover'}*/} | ||
229 | + {/* placement="bottom"*/} | ||
230 | + {/* content={searchContent}*/} | ||
231 | + {/* trigger={'focus'}*/} | ||
232 | + {/* autoAdjustOverflow={false}*/} | ||
233 | + {/* getPopupContainer={(HTMLElement) => HTMLElement.parentNode}*/} | ||
234 | + {/*>*/} | ||
235 | + <Input | ||
236 | + bordered={false} | ||
237 | + ref={searchInputRef} | ||
238 | + onChange={onHandleSearchChange} | ||
239 | + prefix={<SearchOutlined className="search" />} | ||
240 | + // addonAfter={ | ||
241 | + // <> | ||
242 | + // <UploadOutlined className="upload" onClick={onHandleUpload} /> | ||
243 | + // <PlusOutlined className="plus" onClick={onHandleCustomFunc} /> | ||
244 | + // </> | ||
245 | + // } | ||
246 | + placeholder="搜索函数" | ||
247 | + /> | ||
248 | + {/*</Popover>*/} | ||
249 | + </div> | ||
250 | + {dataSource?.length ? ( | ||
251 | + treeRender | ||
252 | + ) : ( | ||
253 | + <div className={'qx-empty-center'}>(暂无数据)</div> | ||
254 | + )} | ||
255 | + {/*{_.size(dataSource) === 0 && (*/} | ||
256 | + {/* <div className={'qx-empty-center'}>(暂无数据)</div>*/} | ||
257 | + {/*)}*/} | ||
258 | + </div> | ||
259 | + ); | ||
260 | +}; | ||
261 | + | ||
262 | +export default memo(FxPicker); |
1 | +import { DownOutlined, SearchOutlined } from '@ant-design/icons'; | ||
2 | +import { getWidgetsIcon } from '@qx/utils'; | ||
3 | +import { Empty, Input, Tooltip, Tree } from 'antd'; | ||
4 | +import _ from 'lodash-es'; | ||
5 | +import React, { | ||
6 | + memo, | ||
7 | + useEffect, | ||
8 | + useLayoutEffect, | ||
9 | + useMemo, | ||
10 | + useRef, | ||
11 | + useState, | ||
12 | +} from 'react'; | ||
13 | +import { ColsTreeSelectProps } from '../index'; | ||
14 | +import emptyImg from '../svg/custom_chart_null.png'; | ||
15 | +import { widgetMapping } from '../util'; | ||
16 | + | ||
17 | +type VarProps = { | ||
18 | + dataSource: ColsTreeSelectProps[]; | ||
19 | + onPick: (varData: ColsTreeSelectProps) => void; | ||
20 | +}; | ||
21 | + | ||
22 | +const IconMap: any = { | ||
23 | + CUR_USER: 'qx-f-icon-user', | ||
24 | + CUR_ORG: 'qx-f-icon-org', | ||
25 | + CUR_CORP: 'qx-f-icon-corp', | ||
26 | + LOGIN_INFO: 'qx-f-icon-loginInfo', | ||
27 | + CUR_SERVER: 'qx-f-icon-server', | ||
28 | +}; | ||
29 | + | ||
30 | +export const handleHighlight = (title: string, val: string, type?: string) => { | ||
31 | + if (!title) return null; | ||
32 | + let index: number; | ||
33 | + if (type === 'fx') { | ||
34 | + index = title.toLowerCase()?.indexOf(val.toLowerCase()); | ||
35 | + } else { | ||
36 | + index = title.indexOf(val); | ||
37 | + } | ||
38 | + return ( | ||
39 | + <span> | ||
40 | + {title.slice(0, index)} | ||
41 | + <span className={'tree-node-high'}> | ||
42 | + {title.slice(index, index + val.length)} | ||
43 | + </span> | ||
44 | + {title.slice(index + (val?.length || 0))} | ||
45 | + </span> | ||
46 | + ); | ||
47 | +}; | ||
48 | + | ||
49 | +const VarPicker: React.FC<VarProps> = ({ onPick, ...props }) => { | ||
50 | + const [searchVal, setSearchVal] = useState(''); | ||
51 | + const [selectIndex, setSelectIndex] = useState(0); | ||
52 | + const searchInputRef = useRef(null); | ||
53 | + const [filterData, setFilterData] = useState<ColsTreeSelectProps[]>([]); | ||
54 | + | ||
55 | + const dataSource: ColsTreeSelectProps[] = useMemo(() => { | ||
56 | + let cloneData = _.cloneDeep(props.dataSource || []); | ||
57 | + cloneData.forEach((item) => { | ||
58 | + (item.children || []).forEach((it) => { | ||
59 | + // @ts-ignore | ||
60 | + it.title = <span style={{ display: 'inline-block' }}>{it.title}</span>; | ||
61 | + }); | ||
62 | + }); | ||
63 | + return cloneData; | ||
64 | + }, [props.dataSource]); | ||
65 | + | ||
66 | + useEffect(() => { | ||
67 | + let cloneData = _.cloneDeep(dataSource); | ||
68 | + cloneData.forEach((item: any) => { | ||
69 | + if (item.titleStr?.includes(searchVal)) { | ||
70 | + item.titleStr = handleHighlight(item.titleStr, searchVal); | ||
71 | + item._hidden = false; | ||
72 | + } else { | ||
73 | + (item.children || []).forEach((it: any) => { | ||
74 | + if (!it.titleStr?.includes(searchVal)) { | ||
75 | + it._hidden = true; | ||
76 | + item.hiddenLen = (item.hiddenLen || 0) + 1; | ||
77 | + if (item.hiddenLen === item.children?.length) { | ||
78 | + item._hidden = true; | ||
79 | + } | ||
80 | + } else { | ||
81 | + it.titleStr = handleHighlight(it.titleStr, searchVal); | ||
82 | + } | ||
83 | + }); | ||
84 | + } | ||
85 | + }); | ||
86 | + cloneData = cloneData.filter((item: any) => item._hidden !== true); | ||
87 | + cloneData.forEach((item) => { | ||
88 | + item.children = (item.children || []).filter((it: any) => !it._hidden); | ||
89 | + }); | ||
90 | + setFilterData(cloneData); | ||
91 | + }, [searchVal]); | ||
92 | + | ||
93 | + // 根据查询value过滤公式列表数据 | ||
94 | + // const dataSourceFilter: ColsTreeSelectProps[] = useMemo(() => { | ||
95 | + // // 扁平化dataSource | ||
96 | + // const dataSourceFlatten: ColsTreeSelectProps[] = []; | ||
97 | + // flatten(dataSource, dataSourceFlatten); | ||
98 | + // return dataSourceFlatten.filter((ds) => ds.titleStr?.includes(searchVal)); | ||
99 | + // }, [searchVal, dataSource]); | ||
100 | + | ||
101 | + const handleSearch = _.debounce((val: string) => { | ||
102 | + setSelectIndex(0); | ||
103 | + setSearchVal(val.trim()); | ||
104 | + }, 200); | ||
105 | + | ||
106 | + // 搜索框值变化时触发 | ||
107 | + const onHandleSearchChange = (e: any) => { | ||
108 | + const val = e.target.value; | ||
109 | + handleSearch(val); | ||
110 | + }; | ||
111 | + | ||
112 | + // 设置当前选中的变量 | ||
113 | + const onHandlePickVar = (varData: ColsTreeSelectProps) => { | ||
114 | + _.isFunction(onPick) && onPick(varData); | ||
115 | + searchInputRef?.current?.blur(); | ||
116 | + }; | ||
117 | + /** | ||
118 | + * 设置键盘事件逻辑 | ||
119 | + * keyCode 38:ArrowUp 40:ArrowDown 13: Enter | ||
120 | + */ | ||
121 | + // const onHandleKeydown = (e: any) => { | ||
122 | + // if (e.keyCode == 38 && selectIndex > 0) { | ||
123 | + // setSelectIndex(selectIndex - 1); | ||
124 | + // } else if (e.keyCode == 40 && selectIndex < dataSourceFilter.length - 1) { | ||
125 | + // setSelectIndex(selectIndex + 1); | ||
126 | + // } else if (e.keyCode == 13) { | ||
127 | + // onHandlePickVar(dataSourceFilter[selectIndex]); | ||
128 | + // } | ||
129 | + // }; | ||
130 | + | ||
131 | + // 设置popover-content内滚动条位置。DOM更新之后立即执行 | ||
132 | + useLayoutEffect(() => { | ||
133 | + const dom = document.getElementsByClassName('var-selected')[0]; | ||
134 | + dom && | ||
135 | + dom.scrollIntoView(Object.assign({ block: 'center', inline: 'nearest' })); | ||
136 | + }, [selectIndex]); | ||
137 | + | ||
138 | + const onHandleSelectTNode: any = (key: string, event: any) => { | ||
139 | + if (event.node.children && event.node.children.length > 0) return; | ||
140 | + _.isFunction(onPick) && onPick(event.node); | ||
141 | + }; | ||
142 | + | ||
143 | + // 设置popover-content | ||
144 | + // const searchContent = useMemo(() => { | ||
145 | + // return ( | ||
146 | + // <div className="search-content"> | ||
147 | + // {dataSourceFilter.map((ds, index) => { | ||
148 | + // return ( | ||
149 | + // <div | ||
150 | + // className={index === selectIndex ? 'var-selected' : ''} | ||
151 | + // key={ds.key} | ||
152 | + // onClick={() => onHandlePickVar(ds)} | ||
153 | + // > | ||
154 | + // {ds.titleStr} | ||
155 | + // </div> | ||
156 | + // ); | ||
157 | + // })} | ||
158 | + // </div> | ||
159 | + // ); | ||
160 | + // }, [dataSourceFilter, selectIndex]); | ||
161 | + | ||
162 | + const treeRender = useMemo(() => { | ||
163 | + return searchVal.length && !filterData?.length ? ( | ||
164 | + <Empty | ||
165 | + image={emptyImg} | ||
166 | + style={{ | ||
167 | + height: '100%', | ||
168 | + width: '100%', | ||
169 | + display: 'flex', | ||
170 | + flexDirection: 'column', | ||
171 | + justifyContent: 'center', | ||
172 | + color: '#999', | ||
173 | + }} | ||
174 | + /> | ||
175 | + ) : ( | ||
176 | + <Tree | ||
177 | + key={Math.random()} | ||
178 | + rootClassName={'custom-tree'} | ||
179 | + blockNode={true} | ||
180 | + selectable={true} | ||
181 | + switcherIcon={<DownOutlined />} | ||
182 | + defaultExpandAll={!!searchVal.length} | ||
183 | + titleRender={(nodeData: any) => { | ||
184 | + const { titleStr, widget } = nodeData; | ||
185 | + const key = nodeData.key.slice(6, -1); | ||
186 | + const { name, color, bgColor } = widgetMapping[widget] || {}; | ||
187 | + const icon = getWidgetsIcon(widget); | ||
188 | + return ( | ||
189 | + <div className="custom-title"> | ||
190 | + <Tooltip title={titleStr} placement={'topRight'}> | ||
191 | + <div | ||
192 | + style={ | ||
193 | + IconMap[key] | ||
194 | + ? { display: 'flex', alignItems: 'center' } | ||
195 | + : {} | ||
196 | + } | ||
197 | + > | ||
198 | + {IconMap[key] ? ( | ||
199 | + <span className={`qx-f-icon-common ${IconMap[key]}`} /> | ||
200 | + ) : widget ? ( | ||
201 | + <span className="icon">{icon}</span> | ||
202 | + ) : null} | ||
203 | + <span>{titleStr}</span> | ||
204 | + </div> | ||
205 | + </Tooltip> | ||
206 | + {widget && ( | ||
207 | + <span | ||
208 | + className="tag" | ||
209 | + style={{ backgroundColor: bgColor, color: color }} | ||
210 | + > | ||
211 | + {name} | ||
212 | + </span> | ||
213 | + )} | ||
214 | + </div> | ||
215 | + ); | ||
216 | + }} | ||
217 | + treeData={searchVal.length ? filterData : dataSource} | ||
218 | + defaultExpandedKeys={['FORM']} | ||
219 | + onSelect={onHandleSelectTNode} | ||
220 | + /> | ||
221 | + ); | ||
222 | + }, [dataSource, filterData]); | ||
223 | + | ||
224 | + return ( | ||
225 | + <div className="variable-box"> | ||
226 | + <div className="search-bar"> | ||
227 | + {/*<Popover*/} | ||
228 | + {/* overlayClassName={'custom-search-popover'}*/} | ||
229 | + {/* placement="bottom"*/} | ||
230 | + {/* content={searchContent}*/} | ||
231 | + {/* trigger={'focus'}*/} | ||
232 | + {/* autoAdjustOverflow={false}*/} | ||
233 | + {/* getPopupContainer={(HTMLElement) => HTMLElement.parentNode}*/} | ||
234 | + {/*>*/} | ||
235 | + <Input | ||
236 | + bordered={false} | ||
237 | + ref={searchInputRef} | ||
238 | + onChange={onHandleSearchChange} | ||
239 | + prefix={<SearchOutlined className="search" />} | ||
240 | + placeholder="搜索变量" | ||
241 | + /> | ||
242 | + {/*</Popover>*/} | ||
243 | + </div> | ||
244 | + {dataSource?.length ? ( | ||
245 | + treeRender | ||
246 | + ) : ( | ||
247 | + <div className={'qx-empty-center'}>(暂无数据)</div> | ||
248 | + )} | ||
249 | + {/*{_.size(dataSource) === 0 && (*/} | ||
250 | + {/* <div className={'qx-empty-center'}>(暂无数据)</div>*/} | ||
251 | + {/*)}*/} | ||
252 | + </div> | ||
253 | + ); | ||
254 | +}; | ||
255 | + | ||
256 | +export default memo(VarPicker); |
src/qx-function-operation/index.less
0 → 100644
1 | +@import '~@qx/ui/src/style/variable.less'; | ||
2 | + | ||
3 | +.fx-wrapper { | ||
4 | + .ant-modal-content { | ||
5 | + border-radius: 8px; | ||
6 | + background-color: #ffffff; | ||
7 | + padding: 0 24px; | ||
8 | + | ||
9 | + .ant-modal-close, | ||
10 | + .ant-modal-header, | ||
11 | + .ant-modal-footer { | ||
12 | + display: none; | ||
13 | + } | ||
14 | + .ant-modal-body { | ||
15 | + position: relative; | ||
16 | + height: 600px; | ||
17 | + padding: 0 0 48px; | ||
18 | + | ||
19 | + .header { | ||
20 | + padding: 24px 0 12px; | ||
21 | + height: 60px; | ||
22 | + line-height: 24px; | ||
23 | + display: flex; | ||
24 | + align-items: center; | ||
25 | + justify-content: space-between; | ||
26 | + | ||
27 | + > .header-item { | ||
28 | + height: 100%; | ||
29 | + display: flex; | ||
30 | + align-items: center; | ||
31 | + } | ||
32 | + // 左侧 | ||
33 | + .title { | ||
34 | + font-size: 16px; | ||
35 | + font-weight: 700; | ||
36 | + color: #242835; | ||
37 | + padding-right: 8px; | ||
38 | + } | ||
39 | + .manual { | ||
40 | + display: flex; | ||
41 | + align-items: center; | ||
42 | + color: @N8; | ||
43 | + cursor: pointer; | ||
44 | + font-size: 12px; | ||
45 | + .qx-f-icon-help { | ||
46 | + margin-right: 4px; | ||
47 | + cursor: pointer; | ||
48 | + height: 16px; | ||
49 | + width: 16px; | ||
50 | + path { | ||
51 | + fill: @N6; | ||
52 | + } | ||
53 | + } | ||
54 | + | ||
55 | + &:hover { | ||
56 | + color: @B8; | ||
57 | + .qx-f-icon-help { | ||
58 | + path { | ||
59 | + fill: @B8; | ||
60 | + } | ||
61 | + } | ||
62 | + } | ||
63 | + } | ||
64 | + // 右侧 | ||
65 | + .icon-right { | ||
66 | + cursor: pointer; | ||
67 | + width: 16px; | ||
68 | + height: 16px; | ||
69 | + color: @N7; | ||
70 | + &.expand { | ||
71 | + margin-right: 12px; | ||
72 | + } | ||
73 | + } | ||
74 | + } | ||
75 | + .qx-operation { | ||
76 | + height: calc(100% - 60px); | ||
77 | + overflow: auto; | ||
78 | + border: 1px solid #e9e9ea; | ||
79 | + border-radius: 4px; | ||
80 | + //display: flex; | ||
81 | + //flex-direction: column; | ||
82 | + | ||
83 | + .toolbar { | ||
84 | + display: flex; | ||
85 | + height: 40px; | ||
86 | + border-radius: 4px 4px 0px 0px; | ||
87 | + background-color: #f4f4f5; | ||
88 | + justify-content: space-between; | ||
89 | + padding: 8px 12px; | ||
90 | + line-height: 24px; | ||
91 | + | ||
92 | + .field-name { | ||
93 | + font-size: 14px; | ||
94 | + font-weight: 700; | ||
95 | + color: #242835; | ||
96 | + } | ||
97 | + .tools { | ||
98 | + display: flex; | ||
99 | + font-size: 14px; | ||
100 | + font-weight: 400; | ||
101 | + color: #242835; | ||
102 | + | ||
103 | + .tool-item { | ||
104 | + position: relative; | ||
105 | + cursor: pointer; | ||
106 | + display: flex; | ||
107 | + height: 24px; | ||
108 | + align-items: center; | ||
109 | + margin-left: 16px; | ||
110 | + &:not(:last-child) { | ||
111 | + width: 56px; | ||
112 | + display: flex; | ||
113 | + align-items: center; | ||
114 | + justify-content: center; | ||
115 | + &:hover { | ||
116 | + border-radius: 2px; | ||
117 | + background-color: @N4; | ||
118 | + } | ||
119 | + .qx-f-icon-beautify, | ||
120 | + .qx-f-icon-copy { | ||
121 | + width: 16px; | ||
122 | + height: 16px; | ||
123 | + margin-right: 4px; | ||
124 | + } | ||
125 | + .qx-f-icon-beautify { | ||
126 | + background: url('./svg/beautify.svg') no-repeat; | ||
127 | + } | ||
128 | + .qx-f-icon-copy { | ||
129 | + background: url('./svg/copy.svg') no-repeat; | ||
130 | + } | ||
131 | + } | ||
132 | + &:last-child { | ||
133 | + button.ant-switch { | ||
134 | + bottom: 0; | ||
135 | + position: relative; | ||
136 | + line-height: 24px; | ||
137 | + margin-right: 4px; | ||
138 | + &:after { | ||
139 | + position: absolute; | ||
140 | + width: 60px; | ||
141 | + height: 16px; | ||
142 | + left: 28px; | ||
143 | + top: 0; | ||
144 | + content: ''; | ||
145 | + z-index: 99; | ||
146 | + } | ||
147 | + } | ||
148 | + } | ||
149 | + } | ||
150 | + } | ||
151 | + } | ||
152 | + .editor-box { | ||
153 | + position: relative; | ||
154 | + height: 200px; | ||
155 | + min-height: 200px; | ||
156 | + max-height: 452px; | ||
157 | + padding-top: 8px; | ||
158 | + overflow: hidden; | ||
159 | + | ||
160 | + .drag-bar { | ||
161 | + position: absolute; | ||
162 | + right: 0; | ||
163 | + bottom: 0; | ||
164 | + height: 12px; | ||
165 | + width: 12px; | ||
166 | + cursor: ns-resize; | ||
167 | + | ||
168 | + &::before { | ||
169 | + content: ''; | ||
170 | + position: absolute; | ||
171 | + left: 50%; | ||
172 | + top: 0; | ||
173 | + bottom: 0; | ||
174 | + width: 1px; | ||
175 | + background-color: #86909c; | ||
176 | + transform: rotateZ(45deg); | ||
177 | + } | ||
178 | + &::after { | ||
179 | + content: ''; | ||
180 | + position: absolute; | ||
181 | + left: 75%; | ||
182 | + top: 50%; | ||
183 | + bottom: 0; | ||
184 | + width: 1px; | ||
185 | + background-color: #86909c; | ||
186 | + transform: rotateZ(45deg); | ||
187 | + } | ||
188 | + } | ||
189 | + .qx-formula-cm { | ||
190 | + max-height: 100%; | ||
191 | + padding: 0 8px 36px 8px; | ||
192 | + overflow: auto; | ||
193 | + } | ||
194 | + .error-zone { | ||
195 | + position: absolute; | ||
196 | + bottom: -36px; | ||
197 | + left: 0; | ||
198 | + right: 0; | ||
199 | + height: 36px; | ||
200 | + line-height: 36px; | ||
201 | + text-align: center; | ||
202 | + font-size: 14px; | ||
203 | + font-weight: 400; | ||
204 | + color: #e34d59; | ||
205 | + background-color: #fdf2f3; | ||
206 | + transition: bottom 0.2s linear; | ||
207 | + | ||
208 | + &.show { | ||
209 | + bottom: 0; | ||
210 | + } | ||
211 | + } | ||
212 | + .cm-s-idea .CodeMirror-activeline-background { | ||
213 | + background-color: #fff; | ||
214 | + } | ||
215 | + } | ||
216 | + .custom { | ||
217 | + display: flex; | ||
218 | + height: calc(100% - 240px); | ||
219 | + | ||
220 | + .function-box, | ||
221 | + .variable-box, | ||
222 | + .describle-box { | ||
223 | + position: relative; | ||
224 | + width: 30%; | ||
225 | + padding-top: 32px; | ||
226 | + border-top: 1px solid #e9e9ea; | ||
227 | + border-right: 1px solid #e9e9ea; | ||
228 | + | ||
229 | + &::before { | ||
230 | + content: ''; | ||
231 | + position: absolute; | ||
232 | + top: 32px; | ||
233 | + left: 0; | ||
234 | + right: 0; | ||
235 | + height: 1px; | ||
236 | + background-color: #e9e9ea; | ||
237 | + } | ||
238 | + | ||
239 | + .search-bar { | ||
240 | + position: absolute; | ||
241 | + top: 0; | ||
242 | + left: 0; | ||
243 | + height: 32px; | ||
244 | + width: 100%; | ||
245 | + padding: 0 12px; | ||
246 | + .ant-input-prefix { | ||
247 | + margin-right: 8px; | ||
248 | + } | ||
249 | + | ||
250 | + .search { | ||
251 | + color: #7c7e86; | ||
252 | + width: 14px; | ||
253 | + height: 14px; | ||
254 | + } | ||
255 | + | ||
256 | + .custom-search-popover { | ||
257 | + width: 100%; | ||
258 | + padding-top: 0; | ||
259 | + | ||
260 | + .ant-popover-arrow { | ||
261 | + display: none; | ||
262 | + } | ||
263 | + .ant-popover-inner-content { | ||
264 | + padding: 6px 0; | ||
265 | + height: 215px; | ||
266 | + overflow: auto; | ||
267 | + } | ||
268 | + .search-content { | ||
269 | + height: 100%; | ||
270 | + div { | ||
271 | + height: 32px; | ||
272 | + padding: 0 12px; | ||
273 | + line-height: 32px; | ||
274 | + cursor: pointer; | ||
275 | + | ||
276 | + &:hover, | ||
277 | + &.fun-selected, | ||
278 | + &.var-selected { | ||
279 | + background-color: #f4f4f5; | ||
280 | + } | ||
281 | + } | ||
282 | + } | ||
283 | + } | ||
284 | + } | ||
285 | + .custom-tree { | ||
286 | + height: 100%; | ||
287 | + overflow: auto; | ||
288 | + | ||
289 | + .tree-node-high { | ||
290 | + color: @B8; | ||
291 | + } | ||
292 | + | ||
293 | + .ant-tree-treenode { | ||
294 | + padding: 0 4px; | ||
295 | + | ||
296 | + &:hover { | ||
297 | + background-color: #f4f4f5; | ||
298 | + } | ||
299 | + | ||
300 | + .ant-tree-node-selected { | ||
301 | + background-color: transparent; | ||
302 | + } | ||
303 | + } | ||
304 | + .ant-tree-switcher { | ||
305 | + line-height: 36px; | ||
306 | + position: relative; | ||
307 | + .anticon { | ||
308 | + color: @N9 !important; | ||
309 | + } | ||
310 | + &:after { | ||
311 | + content: ''; | ||
312 | + position: absolute; | ||
313 | + max-width: 200px; | ||
314 | + min-width: 170px; | ||
315 | + height: 36px; | ||
316 | + background: transparent; | ||
317 | + top: 0; | ||
318 | + left: 24px; | ||
319 | + z-index: 999; | ||
320 | + } | ||
321 | + } | ||
322 | + .ant-tree-switcher-noop { | ||
323 | + display: none; | ||
324 | + } | ||
325 | + .custom-title { | ||
326 | + display: flex; | ||
327 | + height: 36px; | ||
328 | + padding: 7px 0; | ||
329 | + line-height: 22px; | ||
330 | + justify-content: space-between; | ||
331 | + } | ||
332 | + } | ||
333 | + } | ||
334 | + .function-box { | ||
335 | + .search-bar { | ||
336 | + padding: 0; | ||
337 | + .upload { | ||
338 | + height: 16px; | ||
339 | + width: 16px; | ||
340 | + margin-right: 12px; | ||
341 | + cursor: pointer; | ||
342 | + } | ||
343 | + .plus { | ||
344 | + height: 16px; | ||
345 | + width: 16px; | ||
346 | + color: #1764ff; | ||
347 | + cursor: pointer; | ||
348 | + } | ||
349 | + .ant-input-group-addon { | ||
350 | + background-color: transparent; | ||
351 | + border: none; | ||
352 | + } | ||
353 | + } | ||
354 | + .custom-tree { | ||
355 | + .ant-tree-node-content-wrapper { | ||
356 | + &:hover { | ||
357 | + background-color: unset; | ||
358 | + } | ||
359 | + } | ||
360 | + } | ||
361 | + .custom-title { | ||
362 | + span { | ||
363 | + &:not(:first-child) { | ||
364 | + color: @N7; | ||
365 | + } | ||
366 | + } | ||
367 | + } | ||
368 | + } | ||
369 | + .variable-box { | ||
370 | + .search-bar { | ||
371 | + padding: 0; | ||
372 | + } | ||
373 | + .custom-tree { | ||
374 | + .ant-tree-node-content-wrapper { | ||
375 | + flex: 1; | ||
376 | + overflow: hidden; | ||
377 | + &:hover { | ||
378 | + background-color: unset; | ||
379 | + } | ||
380 | + } | ||
381 | + .custom-title { | ||
382 | + div { | ||
383 | + flex: 1; | ||
384 | + max-width: 129px; | ||
385 | + overflow: hidden; | ||
386 | + white-space: nowrap; | ||
387 | + text-overflow: ellipsis; | ||
388 | + //.custom-title_left { | ||
389 | + // width: 100%; | ||
390 | + // overflow: hidden; | ||
391 | + // white-space: nowrap; | ||
392 | + // text-overflow: ellipsis; | ||
393 | + //} | ||
394 | + } | ||
395 | + .icon { | ||
396 | + padding-right: 4px; | ||
397 | + } | ||
398 | + .tag { | ||
399 | + padding: 0 8px; | ||
400 | + border-radius: 4px; | ||
401 | + background-color: #e7efff; | ||
402 | + font-size: 14px; | ||
403 | + color: #1764ff; | ||
404 | + white-space: nowrap; | ||
405 | + margin-left: 10px; | ||
406 | + } | ||
407 | + } | ||
408 | + } | ||
409 | + } | ||
410 | + .describle-box { | ||
411 | + width: 40%; | ||
412 | + border-right: none; | ||
413 | + | ||
414 | + .title-bar { | ||
415 | + position: absolute; | ||
416 | + top: 0; | ||
417 | + left: 0; | ||
418 | + height: 32px; | ||
419 | + width: 100%; | ||
420 | + padding: 0 12px; | ||
421 | + font-size: 14px; | ||
422 | + font-weight: 400; | ||
423 | + line-height: 32px; | ||
424 | + color: #242835; | ||
425 | + } | ||
426 | + .desc-content { | ||
427 | + height: 100%; | ||
428 | + overflow: auto; | ||
429 | + padding: 8px 12px; | ||
430 | + .fx-ul { | ||
431 | + list-style: disc !important; | ||
432 | + padding-inline-start: 20px; | ||
433 | + } | ||
434 | + | ||
435 | + .line { | ||
436 | + font-size: 14px; | ||
437 | + font-weight: 400; | ||
438 | + line-height: 22px; | ||
439 | + color: #50535d; | ||
440 | + | ||
441 | + .highlight { | ||
442 | + color: #00a870; | ||
443 | + } | ||
444 | + } | ||
445 | + } | ||
446 | + } | ||
447 | + } | ||
448 | + } | ||
449 | + .footer { | ||
450 | + position: absolute; | ||
451 | + bottom: 0; | ||
452 | + left: 0; | ||
453 | + right: 0; | ||
454 | + height: 48px; | ||
455 | + padding: 8px 0; | ||
456 | + text-align: right; | ||
457 | + | ||
458 | + .ant-btn { | ||
459 | + border-radius: 4px; | ||
460 | + | ||
461 | + &:not(:last-child) { | ||
462 | + margin-right: 8px; | ||
463 | + } | ||
464 | + } | ||
465 | + } | ||
466 | + } | ||
467 | + } | ||
468 | + | ||
469 | + &.fx-fullscreen { | ||
470 | + .ant-modal-content { | ||
471 | + position: fixed; | ||
472 | + top: 0; | ||
473 | + left: 0; | ||
474 | + right: 0; | ||
475 | + bottom: 0; | ||
476 | + border-radius: 0 !important; | ||
477 | + | ||
478 | + .ant-modal-body { | ||
479 | + position: relative; | ||
480 | + height: 100%; | ||
481 | + width: 100%; | ||
482 | + padding: 60px 0 48px; | ||
483 | + | ||
484 | + .header { | ||
485 | + position: absolute; | ||
486 | + top: 0; | ||
487 | + left: 0; | ||
488 | + right: 0; | ||
489 | + height: 60px; | ||
490 | + } | ||
491 | + .qx-operation { | ||
492 | + position: relative; | ||
493 | + height: 100%; | ||
494 | + width: 100%; | ||
495 | + padding: 40px 0 250px; | ||
496 | + | ||
497 | + .toolbar { | ||
498 | + position: absolute; | ||
499 | + top: 0; | ||
500 | + left: 0; | ||
501 | + right: 0; | ||
502 | + height: 40px; | ||
503 | + } | ||
504 | + // 全屏 | ||
505 | + .editor-box { | ||
506 | + height: 100% !important; | ||
507 | + | ||
508 | + .drag-bar { | ||
509 | + display: none !important; | ||
510 | + } | ||
511 | + .qx-formula-cm { | ||
512 | + height: 100%; | ||
513 | + } | ||
514 | + } | ||
515 | + .custom { | ||
516 | + position: absolute; | ||
517 | + bottom: 0; | ||
518 | + left: 0; | ||
519 | + right: 0; | ||
520 | + height: 250px !important; | ||
521 | + } | ||
522 | + } | ||
523 | + .footer { | ||
524 | + position: absolute; | ||
525 | + bottom: 0; | ||
526 | + left: 0; | ||
527 | + right: 0; | ||
528 | + height: 48px; | ||
529 | + } | ||
530 | + } | ||
531 | + } | ||
532 | + } | ||
533 | +} | ||
534 | + | ||
535 | +.CodeMirror-lint-tooltip { | ||
536 | + z-index: 9999 !important; | ||
537 | +} | ||
538 | +.CodeMirror-hints { | ||
539 | + display: block !important; | ||
540 | + z-index: 9999 !important; | ||
541 | + | ||
542 | + .CodeMirror-hint { | ||
543 | + display: block !important; | ||
544 | + | ||
545 | + &.CodeMirror-hint-active { | ||
546 | + } | ||
547 | + } | ||
548 | +} | ||
549 | +.rel-more_modal_full.ant-modal { | ||
550 | + .ant-modal-body { | ||
551 | + height: 98% !important; | ||
552 | + padding: 0 0 48px !important; | ||
553 | + .qx-operation .editor-box { | ||
554 | + height: 55% !important; | ||
555 | + max-height: unset !important; | ||
556 | + } | ||
557 | + .qx-operation .custom { | ||
558 | + height: calc(45% - 40px) !important; | ||
559 | + } | ||
560 | + } | ||
561 | +} | ||
562 | + | ||
563 | +.qx-empty-center { | ||
564 | + color: #999; | ||
565 | + display: flex; | ||
566 | + justify-content: center; | ||
567 | + align-items: center; | ||
568 | +} | ||
569 | + | ||
570 | +.qx-f-icon-common { | ||
571 | + width: 24px; | ||
572 | + height: 24px; | ||
573 | + padding: 4px; | ||
574 | + display: inline-block; | ||
575 | + color: #7c7e86; | ||
576 | +} | ||
577 | + | ||
578 | +.qx-f-icon-user { | ||
579 | + background: url('./svg/people.svg') no-repeat; | ||
580 | + background-size: 16px 16px; | ||
581 | + background-position: 3px 3px; | ||
582 | +} | ||
583 | + | ||
584 | +.qx-f-icon-corp { | ||
585 | + background: url('./svg/company.svg') no-repeat; | ||
586 | + background-size: 16px 16px; | ||
587 | + background-position: 3px 3px; | ||
588 | +} | ||
589 | + | ||
590 | +.qx-f-icon-loginInfo { | ||
591 | + background: url('./svg/idcard.svg') no-repeat; | ||
592 | + background-size: 16px 16px; | ||
593 | + background-position: 3px 3px; | ||
594 | +} | ||
595 | + | ||
596 | +.qx-f-icon-server { | ||
597 | + background: url('./svg/storage.svg') no-repeat; | ||
598 | + background-size: 16px 16px; | ||
599 | + background-position: 3px 3px; | ||
600 | +} | ||
601 | + | ||
602 | +.qx-f-icon-org { | ||
603 | + background: url('./svg/department.svg') no-repeat; | ||
604 | + background-size: 16px 16px; | ||
605 | + background-position: 3px 3px; | ||
606 | +} | ||
607 | + | ||
608 | +.hint-className { | ||
609 | + padding: 5px 0; | ||
610 | + font-size: 14px; | ||
611 | +} |
src/qx-function-operation/index.md
0 → 100644
1 | +### 函数编辑器 | ||
2 | + | ||
3 | +### | ||
4 | + | ||
5 | +```tsx | ||
6 | +import { request, QxFunctionOperationModal } from '@qx/common'; | ||
7 | +import React, { useState, useMemo } from 'react'; | ||
8 | +import { FIELD_TYPE_PROPS } from '../constant'; | ||
9 | + | ||
10 | +const numWidgets = ['qxNumber', 'qxMoney', 'qxPercent', 'qxFormula']; | ||
11 | +const dateWidgets = ['qxDatetime']; | ||
12 | +const boolWidgets = ['qxSwitch']; | ||
13 | + | ||
14 | + | ||
15 | +/** | ||
16 | + * 归类字段类型 | ||
17 | + */ | ||
18 | +const getOperationsType = (data: any) => { | ||
19 | + if (!data) { | ||
20 | + return undefined; | ||
21 | + } | ||
22 | + const fieldType = data.fieldType; | ||
23 | + if (!fieldType) { | ||
24 | + return FIELD_TYPE_PROPS.EMPTY; | ||
25 | + } | ||
26 | + let fileTypeTem: FIELD_TYPE_PROPS; | ||
27 | + if (fieldType.startsWith(FIELD_TYPE_PROPS.USER)) { | ||
28 | + fileTypeTem = FIELD_TYPE_PROPS.USER; | ||
29 | + } else if (fieldType.indexOf(FIELD_TYPE_PROPS.ORG) !== -1) { | ||
30 | + fileTypeTem = FIELD_TYPE_PROPS.ORG; | ||
31 | + } else if ( | ||
32 | + fieldType === 'DATETIME' || | ||
33 | + fieldType === FIELD_TYPE_PROPS.DATE || | ||
34 | + fieldType === 'TIME' || | ||
35 | + // fieldType.startsWith(FIELD_TYPE_PROPS.HOUR) || | ||
36 | + fieldType.startsWith(FIELD_TYPE_PROPS.YEAR) | ||
37 | + ) { | ||
38 | + fileTypeTem = FIELD_TYPE_PROPS.DATE; | ||
39 | + } else if ( | ||
40 | + fieldType === FIELD_TYPE_PROPS.DOUBLE || | ||
41 | + fieldType === FIELD_TYPE_PROPS.DECIMAL || | ||
42 | + fieldType === FIELD_TYPE_PROPS.INTEGER || | ||
43 | + fieldType === FIELD_TYPE_PROPS.FORMULA || | ||
44 | + fieldType === FIELD_TYPE_PROPS.PERCENT | ||
45 | + ) { | ||
46 | + fileTypeTem = FIELD_TYPE_PROPS.NUM; | ||
47 | + } else if ( | ||
48 | + fieldType === FIELD_TYPE_PROPS.ENUM || | ||
49 | + fieldType === FIELD_TYPE_PROPS.ENUM_MULTI || | ||
50 | + fieldType === FIELD_TYPE_PROPS.TREE || | ||
51 | + fieldType === FIELD_TYPE_PROPS.REL || | ||
52 | + fieldType === FIELD_TYPE_PROPS.REL_MULTI || | ||
53 | + fieldType === FIELD_TYPE_PROPS.TABLE || | ||
54 | + fieldType === FIELD_TYPE_PROPS.FILE || | ||
55 | + fieldType === FIELD_TYPE_PROPS.BOOL | ||
56 | + ) { | ||
57 | + fileTypeTem = fieldType; | ||
58 | + } else if (fieldType === FIELD_TYPE_PROPS.REL_FIELD) { | ||
59 | + fileTypeTem = data.refType; | ||
60 | + } else if (fieldType === 'HOUR_MIN' || fieldType === 'HOUR_SEC') { | ||
61 | + fileTypeTem = FIELD_TYPE_PROPS.TIME; | ||
62 | + } else if (fieldType === 'LOCATION') { | ||
63 | + fileTypeTem = FIELD_TYPE_PROPS.LOCATION; | ||
64 | + } else { | ||
65 | + fileTypeTem = FIELD_TYPE_PROPS.TEXT; | ||
66 | + } | ||
67 | + return fileTypeTem; | ||
68 | +}; | ||
69 | + | ||
70 | +const getOperationsTypeByWidget = (widget: string, qxProps?: any, max?: number) => { | ||
71 | + if (!widget) { | ||
72 | + return FIELD_TYPE_PROPS.EMPTY; | ||
73 | + } | ||
74 | + let fileTypeTem: FIELD_TYPE_PROPS | undefined; | ||
75 | + if (widget === 'userSelector') { | ||
76 | + fileTypeTem = FIELD_TYPE_PROPS.USER; | ||
77 | + } else if (widget === 'orgSelector') { | ||
78 | + fileTypeTem = FIELD_TYPE_PROPS.ORG; | ||
79 | + } else if ( | ||
80 | + dateWidgets.includes(widget) || | ||
81 | + (widget === 'qxFormula' && | ||
82 | + qxProps?.calculateMode === 'DATE' && | ||
83 | + qxProps?.calculate?.formula === 'INC_DEC') | ||
84 | + ) { | ||
85 | + fileTypeTem = FIELD_TYPE_PROPS.DATE; | ||
86 | + } else if (numWidgets.includes(widget)) { | ||
87 | + fileTypeTem = FIELD_TYPE_PROPS.NUM; | ||
88 | + } else if (boolWidgets.includes(widget)) { | ||
89 | + fileTypeTem = FIELD_TYPE_PROPS.BOOL; | ||
90 | + } else if (widget === 'relField') { | ||
91 | + fileTypeTem = getOperationsType({ fieldType: qxProps?.refFieldType || '' }); | ||
92 | + } else if (widget === 'qxTime') { | ||
93 | + fileTypeTem = FIELD_TYPE_PROPS.TIME; | ||
94 | + } else if (widget === 'qxLocation') { | ||
95 | + fileTypeTem = FIELD_TYPE_PROPS.LOCATION; | ||
96 | + } else if (widget === 'qxSelect') { | ||
97 | + fileTypeTem = FIELD_TYPE_PROPS.ENUM; | ||
98 | + } else if (widget === 'qxMultiSelect') { | ||
99 | + fileTypeTem = FIELD_TYPE_PROPS.ENUM_MULTI; | ||
100 | + } else if (widget === 'relSelector') { | ||
101 | + if (max === 1) { | ||
102 | + fileTypeTem = FIELD_TYPE_PROPS.REL; | ||
103 | + } else { | ||
104 | + fileTypeTem = FIELD_TYPE_PROPS.REL_MULTI; | ||
105 | + } | ||
106 | + } else if (widget === 'qxTree') { | ||
107 | + fileTypeTem = FIELD_TYPE_PROPS.TREE; | ||
108 | + } else if (widget === 'qxUpload' || widget === 'qxUploadImage') { | ||
109 | + fileTypeTem = FIELD_TYPE_PROPS.FILE; | ||
110 | + } else { | ||
111 | + fileTypeTem = FIELD_TYPE_PROPS.TEXT; | ||
112 | + } | ||
113 | + // if (textWidgets.includes(widget)) { | ||
114 | + // fileTypeTem = | ||
115 | + // }) | ||
116 | + return fileTypeTem; | ||
117 | +}; | ||
118 | + | ||
119 | + | ||
120 | +export default () => { | ||
121 | + const [modalVisible, setModalVisible] = useState(true); | ||
122 | + | ||
123 | + const [props, setProps] = useState({ | ||
124 | + value: { | ||
125 | + type: 'FORMULA', | ||
126 | + expression: undefined, | ||
127 | + values: undefined, | ||
128 | + customScript: false, | ||
129 | + }, | ||
130 | + schema: { | ||
131 | + qxProps: { | ||
132 | + widget: 'qxInput', | ||
133 | + fieldName: { | ||
134 | + title: '文本11', | ||
135 | + }, | ||
136 | + calculateMode: '', | ||
137 | + calculate: { | ||
138 | + formula: '', | ||
139 | + }, | ||
140 | + refFieldType: '', // 关联属性需要 | ||
141 | + relId: '' | ||
142 | + } | ||
143 | + } | ||
144 | + }); | ||
145 | + | ||
146 | + const formulaColsTree = useMemo(() => { | ||
147 | + return []; | ||
148 | + }, []); | ||
149 | + | ||
150 | + | ||
151 | + const uniKey = useMemo(() => { | ||
152 | + return ''; | ||
153 | + }, []); | ||
154 | + | ||
155 | + const handleFxChange = (val: string, usedFuncList?: string, scriptEdit?: boolean) => { | ||
156 | + setProps((prev) => ({ | ||
157 | + ...prev, | ||
158 | + value: { | ||
159 | + type: 'FORMULA', | ||
160 | + expression: val, | ||
161 | + functionList: usedFuncList, | ||
162 | + customScript: scriptEdit, | ||
163 | + } | ||
164 | + })); | ||
165 | + }; | ||
166 | + | ||
167 | + return ( | ||
168 | + <QxFunctionOperationModal | ||
169 | + modalParams={{ | ||
170 | + visible: modalVisible, | ||
171 | + onCancel:() => setModalVisible(false), | ||
172 | + onOk:() => setModalVisible(false), | ||
173 | + }} | ||
174 | + autofocus={true} | ||
175 | + colsTree={formulaColsTree} | ||
176 | + value={props?.value?.type === 'FORMULA' ? (props?.value?.values || props?.value?.expression || '') : ''} | ||
177 | + onChange={handleFxChange} | ||
178 | + isScriptEditMode={props.value?.customScript} | ||
179 | + fieldName={props.schema?.fieldName?.title || props?.addons?.formData?.title || ''} | ||
180 | + uniKey={uniKey} | ||
181 | + isInSubForm={false} | ||
182 | + defaultSetting={{ | ||
183 | + widget: props?.schema?.qxProps?.widget, | ||
184 | + fieldName: props.schema?.fieldName?.title || props?.addons?.formData?.title || '', | ||
185 | + fieldGroupType: getOperationsTypeByWidget(props?.schema?.qxProps?.widget, props?.schema?.qxProps), | ||
186 | + currentRelId: props?.schema?.qxProps?.relId, | ||
187 | + }} | ||
188 | + /> | ||
189 | + ); | ||
190 | +}; | ||
191 | +``` | ||
192 | + | ||
193 | +<API id="QxFunctionOperationModal"></API> |
src/qx-function-operation/index.tsx
0 → 100644
1 | +import React, { | ||
2 | + useCallback, | ||
3 | + useEffect, | ||
4 | + useImperativeHandle, | ||
5 | + useMemo, | ||
6 | + useRef, | ||
7 | + useState, | ||
8 | +} from 'react'; | ||
9 | +import './index.less'; | ||
10 | + | ||
11 | +import { | ||
12 | + CloseOutlined, | ||
13 | + CompressOutlined, | ||
14 | + ExpandOutlined, | ||
15 | +} from '@ant-design/icons'; | ||
16 | +import { handleWindowOpen } from '@qx/utils'; | ||
17 | +import { Button, Modal, Switch } from 'antd'; | ||
18 | +import { ModalProps, TreeDataNode } from 'antd/lib'; | ||
19 | +import _ from 'lodash-es'; | ||
20 | +import { FIELD_TYPE_PROPS } from '../constant'; | ||
21 | +import CodeEditor from '../qx-code-editor'; | ||
22 | +import DescBox from './components/desc-box'; | ||
23 | +import FxPicker from './components/fx-picker'; | ||
24 | +import VarPicker from './components/var-picker'; | ||
25 | +import { getFunctionList } from './service'; | ||
26 | +import { | ||
27 | + SYSVariable, | ||
28 | + checkFormulaExpress, | ||
29 | + copyText, | ||
30 | + handleFlattenList, | ||
31 | +} from './util'; | ||
32 | + | ||
33 | +export interface FunctionProps { | ||
34 | + title: string; | ||
35 | + titleDesc?: string; | ||
36 | + key: string; | ||
37 | + selectable?: boolean; | ||
38 | + icon?: any; | ||
39 | + children?: FunctionProps[]; | ||
40 | + desc?: string; | ||
41 | + formula?: string; | ||
42 | + params?: any; | ||
43 | + returnType?: string; | ||
44 | + demon?: string; | ||
45 | + funcNameEg: string; | ||
46 | +} | ||
47 | +export interface ColsTreeSelectProps extends TreeDataNode { | ||
48 | + attrs?: { titleStr: string; fieldGroupType: FIELD_TYPE_PROPS; key: string }[]; | ||
49 | + key: string; | ||
50 | + titleStr?: string; | ||
51 | + iconName?: string; | ||
52 | + widget?: string; | ||
53 | + fieldGroupType?: FIELD_TYPE_PROPS; | ||
54 | + children?: ColsTreeSelectProps[]; | ||
55 | +} | ||
56 | + | ||
57 | +interface QxFunOperationProps { | ||
58 | + appCode: string; | ||
59 | + funCode: string; | ||
60 | + cRef?: any; | ||
61 | + value: any; | ||
62 | + onChange: (val: string, val2?: string[], val3?: boolean) => void; | ||
63 | + colsTree: ColsTreeSelectProps[]; | ||
64 | + style?: any; | ||
65 | + autofocus?: boolean; | ||
66 | + fieldName: string; | ||
67 | + fieldType: FIELD_TYPE_PROPS; | ||
68 | + uniKey: string; | ||
69 | + isScriptEditMode?: boolean; | ||
70 | + isInSubForm?: boolean; | ||
71 | + isFullScreen: boolean; | ||
72 | +} | ||
73 | + | ||
74 | +/** | ||
75 | + * 启效函数运算器 | ||
76 | + * | ||
77 | + * @param props | ||
78 | + * @constructor | ||
79 | + */ | ||
80 | +export const QxFunctionOperation: React.FC<QxFunOperationProps> = ({ | ||
81 | + cRef, | ||
82 | + value, | ||
83 | + appCode, | ||
84 | + funCode, | ||
85 | + onChange, | ||
86 | + colsTree, | ||
87 | + autofocus, | ||
88 | + fieldName = '', | ||
89 | + fieldType, | ||
90 | + isScriptEditMode = false, | ||
91 | + uniKey, | ||
92 | + isInSubForm, | ||
93 | +}) => { | ||
94 | + const editorBoxRef = useRef<any>(); | ||
95 | + const codeEditorRef = useRef<any>(); | ||
96 | + | ||
97 | + // 变量列表 | ||
98 | + const [varDataList, setVarDataList] = useState<ColsTreeSelectProps[]>([]); | ||
99 | + // 公式函数列表 | ||
100 | + const [funcDataList, setFuncDataList] = useState<FunctionProps[]>([]); | ||
101 | + // 动态设置函数释义下的"当前函数" | ||
102 | + const [funDescription, setFunDescription] = useState<FunctionProps | null>(); | ||
103 | + // 新变量(插入) | ||
104 | + const [newVariable, setNewVariable] = useState<any>(); | ||
105 | + // 标识脚本编辑 | ||
106 | + const [scriptEdit, setScriptEdit] = useState(false); | ||
107 | + // 错误悬浮提示 | ||
108 | + const [errorMap, setErrorMap] = useState({ | ||
109 | + show: false, | ||
110 | + msg: '', | ||
111 | + }); | ||
112 | + const pickedFunc = useRef<FunctionProps>(null); | ||
113 | + const pickFunc = (data: FunctionProps) => { | ||
114 | + // @ts-ignore | ||
115 | + pickedFunc.current = data; | ||
116 | + setFunDescription(data); | ||
117 | + }; | ||
118 | + | ||
119 | + // const onMouseDown = (event: any) => { | ||
120 | + // let startPy = event.clientY; | ||
121 | + // let offsetHeight = editorBoxRef?.current.offsetHeight; | ||
122 | + // document.body.onmousemove = function (e: any) { | ||
123 | + // const offsetY = e.clientY - startPy; | ||
124 | + // const height = offsetHeight + offsetY; | ||
125 | + // if (!startPy || height > 400 || height < 200) return; | ||
126 | + // editorBoxRef?.current?.setAttribute('style', `height: ${height}px`); | ||
127 | + // }; | ||
128 | + // document.body.onmouseup = function () { | ||
129 | + // document.body.onmousemove = null; | ||
130 | + // }; | ||
131 | + // }; | ||
132 | + | ||
133 | + useEffect(() => { | ||
134 | + if (isScriptEditMode) setScriptEdit(isScriptEditMode); | ||
135 | + }, [isScriptEditMode]); | ||
136 | + | ||
137 | + const dealFunctionList = (originData: any) => { | ||
138 | + // const titleEnum: any = { | ||
139 | + // TEXT: '文本函数', | ||
140 | + // MATH: '数学函数', | ||
141 | + // DATE: '日期函数', | ||
142 | + // LOGICAL: '逻辑函数', | ||
143 | + // REGULAR: '正则函数', | ||
144 | + // ADVANCED: '高级函数', | ||
145 | + // CUSTOM: '自定义', | ||
146 | + // COMMON: '常用函数', | ||
147 | + // }; | ||
148 | + if (!_.isArray(originData)) { | ||
149 | + return; | ||
150 | + } | ||
151 | + const funcList = originData?.map( | ||
152 | + (item: { name: string; code: string; details?: any[] }) => { | ||
153 | + return { | ||
154 | + key: item.code, | ||
155 | + title: item.name, | ||
156 | + selectable: false, | ||
157 | + children: (item.details || []).map((it: any) => ({ | ||
158 | + ...it, | ||
159 | + title: it.funcNameEg, | ||
160 | + key: it.id, | ||
161 | + titleDesc: it.funcName, | ||
162 | + formula: it.methodView, | ||
163 | + desc: it.description, | ||
164 | + returnType: it.dataType, | ||
165 | + demon: it.executeDemo, | ||
166 | + })), | ||
167 | + }; | ||
168 | + }, | ||
169 | + ); | ||
170 | + setFuncDataList(funcList); | ||
171 | + }; | ||
172 | + | ||
173 | + useEffect(() => { | ||
174 | + let _colsTree; | ||
175 | + let curLevel = colsTree.filter((o) => !o.titleStr?.startsWith('【主表】')); | ||
176 | + const mainLevel = colsTree | ||
177 | + .filter((o) => o.titleStr?.startsWith('【主表】')) | ||
178 | + .map((o) => ({ | ||
179 | + ...o, | ||
180 | + titleStr: o.titleStr?.substring(4), | ||
181 | + })); | ||
182 | + if (isInSubForm) { | ||
183 | + if (_.isEmpty(curLevel)) { | ||
184 | + _colsTree = mainLevel; | ||
185 | + } else { | ||
186 | + _colsTree = mainLevel.concat({ | ||
187 | + key: 'SUB', | ||
188 | + titleStr: '子表字段', | ||
189 | + children: curLevel, | ||
190 | + }); | ||
191 | + } | ||
192 | + } else { | ||
193 | + _colsTree = curLevel; | ||
194 | + } | ||
195 | + setVarDataList([ | ||
196 | + { | ||
197 | + key: 'FORM', | ||
198 | + titleStr: '表单变量', | ||
199 | + children: _colsTree, | ||
200 | + }, | ||
201 | + { | ||
202 | + key: 'SYS', | ||
203 | + titleStr: '系统变量', | ||
204 | + children: SYSVariable, | ||
205 | + }, | ||
206 | + ]); | ||
207 | + }, [JSON.stringify(colsTree), SYSVariable, isInSubForm]); | ||
208 | + useEffect(() => { | ||
209 | + getFunctionList(appCode, funCode).then((res: any) => { | ||
210 | + if (res) { | ||
211 | + dealFunctionList(res); | ||
212 | + } | ||
213 | + }); | ||
214 | + }, []); | ||
215 | + | ||
216 | + const flattenVarList = useMemo(() => { | ||
217 | + const _list: any[] = []; | ||
218 | + handleFlattenList(varDataList || [], _list); | ||
219 | + return _list; | ||
220 | + }, [varDataList]); | ||
221 | + | ||
222 | + // 编辑器光标移动 | ||
223 | + const onHandleHoverFxChange = useCallback((data: any) => { | ||
224 | + if (data) { | ||
225 | + setFunDescription(data); | ||
226 | + } else if (pickedFunc.current) { | ||
227 | + setFunDescription(pickedFunc.current); | ||
228 | + } else { | ||
229 | + setFunDescription(null); | ||
230 | + } | ||
231 | + }, []); | ||
232 | + | ||
233 | + // 选中函数公式 | ||
234 | + const onHandlerPickFunc = useCallback((data: any) => { | ||
235 | + setNewVariable({ | ||
236 | + key: data.funcNameEg, | ||
237 | + name: data.title, | ||
238 | + type: 'fun', | ||
239 | + }); | ||
240 | + }, []); | ||
241 | + | ||
242 | + // 选中变量 | ||
243 | + const onHandlerPickVar = useCallback((data: any) => { | ||
244 | + setNewVariable({ | ||
245 | + key: data.key, | ||
246 | + name: data.titleStr, | ||
247 | + }); | ||
248 | + }, []); | ||
249 | + | ||
250 | + useEffect(() => { | ||
251 | + if (!funcDataList.length) return; | ||
252 | + const { errMsg, errCode } = checkFormulaExpress( | ||
253 | + value, | ||
254 | + funcDataList, | ||
255 | + varDataList, | ||
256 | + codeEditorRef?.current?.getUsedFuncList(), | ||
257 | + fieldType, | ||
258 | + flattenVarList, | ||
259 | + ); | ||
260 | + setErrorMap({ | ||
261 | + show: errCode ? true : false, | ||
262 | + msg: errMsg, | ||
263 | + }); | ||
264 | + }, [funcDataList]); | ||
265 | + | ||
266 | + // 捕获编辑器内容改变 | ||
267 | + const onHandlerEditorChange = (code: any) => { | ||
268 | + console.log('code: ', code); | ||
269 | + onChange(code); | ||
270 | + if (!funcDataList.length) return; | ||
271 | + const { errMsg, errCode } = checkFormulaExpress( | ||
272 | + code, | ||
273 | + funcDataList, | ||
274 | + varDataList, | ||
275 | + codeEditorRef?.current?.getUsedFuncList(), | ||
276 | + fieldType, | ||
277 | + flattenVarList, | ||
278 | + ); | ||
279 | + setErrorMap({ | ||
280 | + show: errCode ? true : false, | ||
281 | + msg: errMsg, | ||
282 | + }); | ||
283 | + }; | ||
284 | + | ||
285 | + useImperativeHandle(cRef, () => ({ | ||
286 | + scriptEdit, | ||
287 | + getUsedFuncList() { | ||
288 | + return codeEditorRef?.current?.getUsedFuncList(); | ||
289 | + }, | ||
290 | + })); | ||
291 | + | ||
292 | + return ( | ||
293 | + <div className="qx-operation"> | ||
294 | + <div className="toolbar"> | ||
295 | + <div className="field-name">{fieldName} =</div> | ||
296 | + <div className="tools"> | ||
297 | + <div | ||
298 | + className="tool-item" | ||
299 | + onClick={() => { | ||
300 | + codeEditorRef?.current?.autoFormatSelection(); | ||
301 | + }} | ||
302 | + > | ||
303 | + <span className="qx-f-icon-beautify" /> | ||
304 | + {/* <UngroupOutlined /> */} | ||
305 | + <span className="text ">美化</span> | ||
306 | + </div> | ||
307 | + <div | ||
308 | + className="tool-item" | ||
309 | + onClick={() => { | ||
310 | + const editor = codeEditorRef?.current?.getEditor(); | ||
311 | + const s_len = editor.getValue().length; | ||
312 | + const startPos = { line: 0, ch: 0, sticky: null }; | ||
313 | + const endPos = editor.doc.posFromIndex(s_len); | ||
314 | + editor.setSelection(startPos, endPos); | ||
315 | + copyText(editor.getValue()); | ||
316 | + }} | ||
317 | + > | ||
318 | + <span className="qx-f-icon-copy" /> | ||
319 | + <span className="text ">复制</span> | ||
320 | + </div> | ||
321 | + <div className="tool-item"> | ||
322 | + <Switch | ||
323 | + checked={scriptEdit} | ||
324 | + size="small" | ||
325 | + onChange={(checked) => { | ||
326 | + setScriptEdit(checked); | ||
327 | + }} | ||
328 | + /> | ||
329 | + <span className="text">脚本编辑</span> | ||
330 | + </div> | ||
331 | + </div> | ||
332 | + </div> | ||
333 | + <div | ||
334 | + ref={editorBoxRef} | ||
335 | + className="editor-box" | ||
336 | + onClick={() => { | ||
337 | + const editor = codeEditorRef?.current?.getEditor(); | ||
338 | + editor.focus(); | ||
339 | + }} | ||
340 | + > | ||
341 | + <CodeEditor | ||
342 | + cRef={codeEditorRef} | ||
343 | + key={uniKey} | ||
344 | + value={value} | ||
345 | + isUseFun={true} | ||
346 | + autofocus={autofocus} | ||
347 | + funcDataList={funcDataList} | ||
348 | + varDataList={varDataList} | ||
349 | + newVariable={newVariable} | ||
350 | + onChange={onHandlerEditorChange} | ||
351 | + onFocusFunc={(data: any) => { | ||
352 | + // @ts-ignore | ||
353 | + pickedFunc.current = data; | ||
354 | + setFunDescription(data); | ||
355 | + }} | ||
356 | + /> | ||
357 | + <div className={`error-zone ${errorMap.show ? 'show' : ''}`}> | ||
358 | + {errorMap.msg} | ||
359 | + </div> | ||
360 | + </div> | ||
361 | + <div className="custom"> | ||
362 | + <FxPicker | ||
363 | + dataSource={funcDataList} | ||
364 | + onHover={onHandleHoverFxChange} | ||
365 | + onPick={onHandlerPickFunc} | ||
366 | + pickFunc={pickFunc} | ||
367 | + /> | ||
368 | + <VarPicker dataSource={varDataList} onPick={onHandlerPickVar} /> | ||
369 | + <DescBox scriptEdit={scriptEdit} funcData={funDescription} /> | ||
370 | + </div> | ||
371 | + </div> | ||
372 | + ); | ||
373 | +}; | ||
374 | + | ||
375 | +interface QxFunOperationModalProps extends QxFunOperationProps { | ||
376 | + modalParams: ModalProps; | ||
377 | + defaultSetting?: any; // 函数公式 一个字段校验 所需参数 放置处 | ||
378 | + isInSubForm?: boolean; | ||
379 | + appCode: string; | ||
380 | + funCode: string; | ||
381 | +} | ||
382 | + | ||
383 | +/** | ||
384 | + * "函数运算器"Modal弹窗版 | ||
385 | + * | ||
386 | + * @param value | ||
387 | + * @param onChange | ||
388 | + * @param style | ||
389 | + * @param colsTree | ||
390 | + * @param autofocus | ||
391 | + * @param modalParams | ||
392 | + * @param fieldName | ||
393 | + * @param fieldType | ||
394 | + * @param uniKey | ||
395 | + * @constructor | ||
396 | + */ | ||
397 | +export const QxFunctionOperationModal: React.FC<QxFunOperationModalProps> = ({ | ||
398 | + value, | ||
399 | + onChange, | ||
400 | + style, | ||
401 | + colsTree, | ||
402 | + autofocus, | ||
403 | + modalParams, | ||
404 | + fieldName, | ||
405 | + fieldType, | ||
406 | + uniKey, | ||
407 | + isScriptEditMode, | ||
408 | + isInSubForm, | ||
409 | + appCode, | ||
410 | + funCode, | ||
411 | +}) => { | ||
412 | + const qxFuncOperaionRef = useRef<any>(); | ||
413 | + const [funValues, setFunValues] = useState<any>(); | ||
414 | + | ||
415 | + useEffect(() => { | ||
416 | + setFunValues(value); | ||
417 | + }, [value]); | ||
418 | + | ||
419 | + const [isFullScreen, setIsFullScreen] = useState(false); | ||
420 | + /**实现F11全屏效果*/ | ||
421 | + const fullScreen = () => { | ||
422 | + // var docElm = document.documentElement; | ||
423 | + // /*W3C*/ | ||
424 | + // if (docElm.requestFullscreen) { | ||
425 | + // docElm.requestFullscreen(); | ||
426 | + // } /*FireFox */ else if (docElm.mozRequestFullScreen) { | ||
427 | + // docElm.mozRequestFullScreen(); | ||
428 | + // } /*Chrome等 */ else if (docElm.webkitRequestFullScreen) { | ||
429 | + // docElm.webkitRequestFullScreen(); | ||
430 | + // } /*IE11*/ else if (docElm.msRequestFullscreen) { | ||
431 | + // docElm.msRequestFullscreen(); | ||
432 | + // } | ||
433 | + setIsFullScreen(true); | ||
434 | + }; | ||
435 | + /**退出F11全屏*/ | ||
436 | + const exitFullScreen = () => { | ||
437 | + // if (document.exitFullscreen) { | ||
438 | + // document.exitFullscreen(); | ||
439 | + // } else if (document.mozCancelFullScreen) { | ||
440 | + // document.mozCancelFullScreen(); | ||
441 | + // } else if (document.webkitCancelFullScreen) { | ||
442 | + // document.webkitCancelFullScreen(); | ||
443 | + // } else if (document.msExitFullscreen) { | ||
444 | + // document.msExitFullscreen(); | ||
445 | + // } | ||
446 | + setIsFullScreen(false); | ||
447 | + }; | ||
448 | + | ||
449 | + // 点击确定 调用方法 | ||
450 | + const onHandleCancel = () => { | ||
451 | + setFunValues(value || ''); | ||
452 | + | ||
453 | + const e: any = new MouseEvent('click', { | ||
454 | + bubbles: true, | ||
455 | + cancelable: true, | ||
456 | + }); | ||
457 | + setTimeout(() => modalParams?.onCancel && modalParams?.onCancel(e), 200); | ||
458 | + }; | ||
459 | + | ||
460 | + // 点击确定 调用方法 | ||
461 | + const onHandleOk = () => { | ||
462 | + const { getUsedFuncList, scriptEdit } = qxFuncOperaionRef.current; | ||
463 | + if (!_.isEqual(funValues, value) || scriptEdit !== isScriptEditMode) { | ||
464 | + const usedFuncList = getUsedFuncList(); | ||
465 | + console.log( | ||
466 | + 'code: ', | ||
467 | + funValues, | ||
468 | + ' useFuncList: ', | ||
469 | + usedFuncList, | ||
470 | + ' scriptEdit: ', | ||
471 | + scriptEdit, | ||
472 | + ); | ||
473 | + onChange(funValues, usedFuncList, scriptEdit); | ||
474 | + } | ||
475 | + | ||
476 | + const e: any = new MouseEvent('click', { | ||
477 | + bubbles: true, | ||
478 | + cancelable: true, | ||
479 | + }); | ||
480 | + setTimeout(() => modalParams?.onOk && modalParams?.onOk(e), 200); | ||
481 | + }; | ||
482 | + | ||
483 | + const newColsTree = useMemo(() => { | ||
484 | + (colsTree || []).forEach((item) => { | ||
485 | + if (item.widget === 'qxSelect') { | ||
486 | + const code = item.key.slice(2, -1); | ||
487 | + item.attrs = [ | ||
488 | + { | ||
489 | + key: '${' + code + '.code' + '}', | ||
490 | + titleStr: '编号', | ||
491 | + fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
492 | + }, | ||
493 | + { | ||
494 | + key: '${' + code + '.name' + '}', | ||
495 | + titleStr: '文本', | ||
496 | + fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
497 | + }, | ||
498 | + { | ||
499 | + key: '${' + code + '.score' + '}', | ||
500 | + titleStr: '赋值', | ||
501 | + fieldGroupType: FIELD_TYPE_PROPS.NUM, | ||
502 | + }, | ||
503 | + ]; | ||
504 | + } | ||
505 | + }); | ||
506 | + return colsTree; | ||
507 | + }, [colsTree]); | ||
508 | + | ||
509 | + // const handleTree = (colsTree: ColsTreeSelectProps) => { | ||
510 | + // console.log(colsTree); | ||
511 | + // return colsTree; | ||
512 | + // }; | ||
513 | + | ||
514 | + return ( | ||
515 | + <Modal | ||
516 | + key={uniKey} | ||
517 | + visible={modalParams.visible} | ||
518 | + wrapClassName={`fx-wrapper`} | ||
519 | + width={880} | ||
520 | + centered={true} | ||
521 | + maskClosable={false} | ||
522 | + destroyOnClose={true} | ||
523 | + footer={null} | ||
524 | + {...modalParams} | ||
525 | + className={`rel-more_modal ${isFullScreen ? 'rel-more_modal_full' : ''}`} | ||
526 | + > | ||
527 | + <div className="header"> | ||
528 | + <div className="header-item"> | ||
529 | + <span className="title">函数编辑</span> | ||
530 | + <span | ||
531 | + className="manual" | ||
532 | + onClick={() => { | ||
533 | + handleWindowOpen( | ||
534 | + 'https://qgutech.yuque.com/g/team-qx/helper/mlh9yzyewec7hb81/collaborator' + | ||
535 | + '/join?token=mjqmsZwJo9iTTkTb&source=doc_collaborator#', | ||
536 | + ); | ||
537 | + }} | ||
538 | + > | ||
539 | + <svg | ||
540 | + width="16" | ||
541 | + height="16" | ||
542 | + viewBox="0 0 16 16" | ||
543 | + fill="none" | ||
544 | + className="qx-f-icon-help" | ||
545 | + > | ||
546 | + <g> | ||
547 | + <path | ||
548 | + fillRule="evenodd" | ||
549 | + clipRule="evenodd" | ||
550 | + d="M1.33331 7.99992C1.33331 4.31802 4.31808 1.33325 7.99998 1.33325C11.6819 1.33325 14.6666 4.31802 14.6666 7.99992C14.6666 11.6818 11.6819 14.6666 7.99998 14.6666C4.31808 14.6666 1.33331 11.6818 1.33331 7.99992ZM6.86652 6.65945V6.50573C6.86732 6.32282 6.93273 6.11993 7.07868 5.96739C7.21347 5.82651 7.4762 5.66659 7.99602 5.66659C8.48416 5.66659 8.8236 5.89964 8.99671 6.16424C9.18158 6.4468 9.14515 6.68389 9.05741 6.80234C8.94799 6.95007 8.81212 7.08651 8.64793 7.23433C8.59797 7.2793 8.5364 7.33288 8.47038 7.39032L8.47037 7.39034C8.35231 7.49307 8.22004 7.60817 8.11457 7.70852C7.74008 8.06486 7.3333 8.5622 7.3333 9.33325L7.33547 9.67087L8.66877 9.6623L8.66665 9.33143C8.66714 9.10341 8.76061 8.93428 9.03368 8.67446C9.12546 8.58712 9.20797 8.51574 9.30238 8.43406L9.30242 8.43402C9.37224 8.37362 9.44858 8.30758 9.54 8.22528C9.73012 8.05413 9.94168 7.84862 10.1288 7.59597C10.6342 6.91365 10.516 6.05112 10.1125 5.43426C9.69713 4.79943 8.94146 4.33325 7.99602 4.33325C7.17786 4.33325 6.54355 4.59802 6.11528 5.04563C5.69841 5.48133 5.5347 6.0303 5.53318 6.50235V6.65945H6.86652ZM7.33545 10.3333V11.6692H8.66879V10.3333H7.33545Z" | ||
551 | + /> | ||
552 | + </g> | ||
553 | + </svg> | ||
554 | + 公式使用帮助手册 | ||
555 | + </span> | ||
556 | + </div> | ||
557 | + {/* <Tooltip placement="right" title={'公式使用帮助手册'}> | ||
558 | + </Tooltip> */} | ||
559 | + <div className="header-item"> | ||
560 | + {isFullScreen ? ( | ||
561 | + <CompressOutlined | ||
562 | + className="icon-right expand" | ||
563 | + onClick={exitFullScreen} | ||
564 | + /> | ||
565 | + ) : ( | ||
566 | + <ExpandOutlined | ||
567 | + className="icon-right expand" | ||
568 | + onClick={fullScreen} | ||
569 | + /> | ||
570 | + )} | ||
571 | + <CloseOutlined | ||
572 | + className="icon-right close" | ||
573 | + onClick={onHandleCancel} | ||
574 | + /> | ||
575 | + </div> | ||
576 | + </div> | ||
577 | + <QxFunctionOperation | ||
578 | + cRef={qxFuncOperaionRef} | ||
579 | + value={funValues} | ||
580 | + onChange={(code: string) => { | ||
581 | + setFunValues(code); | ||
582 | + }} | ||
583 | + appCode={appCode} | ||
584 | + funCode={funCode} | ||
585 | + isInSubForm={isInSubForm} | ||
586 | + style={style} | ||
587 | + colsTree={newColsTree} | ||
588 | + isScriptEditMode={isScriptEditMode} | ||
589 | + autofocus={autofocus} | ||
590 | + fieldName={fieldName} | ||
591 | + fieldType={fieldType} | ||
592 | + uniKey={uniKey} | ||
593 | + isFullScreen={isFullScreen} | ||
594 | + /> | ||
595 | + <div className="footer"> | ||
596 | + <Button onClick={onHandleCancel}>取消</Button> | ||
597 | + <Button type="primary" onClick={onHandleOk}> | ||
598 | + 确定 | ||
599 | + </Button> | ||
600 | + </div> | ||
601 | + </Modal> | ||
602 | + ); | ||
603 | +}; |
src/qx-function-operation/service.ts
0 → 100644
src/qx-function-operation/svg/beautify.svg
0 → 100644
1 | +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
2 | +<g id="general/beautify"> | ||
3 | +<path id="Union" fill-rule="evenodd" clip-rule="evenodd" d="M8.08163 1.33325C7.62049 1.33325 7.24695 1.70679 7.24695 2.16793V4.37048C7.24695 4.59185 7.33489 4.80416 7.49142 4.96069C7.64795 5.11722 7.86026 5.20516 8.08163 5.20516C8.303 5.20516 8.5153 5.11722 8.67183 4.96069C8.82836 4.80416 8.9163 4.59185 8.9163 4.37048V2.16793C8.9163 1.70658 8.54206 1.33325 8.08163 1.33325ZM4.01636 3.17374C3.90543 3.1727 3.79541 3.19377 3.69272 3.23574C3.59003 3.27771 3.49674 3.33973 3.4183 3.41818C3.33985 3.49662 3.27784 3.58991 3.23587 3.6926C3.1939 3.79529 3.17282 3.90531 3.17386 4.01624C3.17491 4.12717 3.19806 4.23678 3.24195 4.33866C3.28539 4.43947 3.34828 4.53071 3.42701 4.60717L4.98284 6.16251C5.13936 6.31898 5.35162 6.40688 5.57294 6.40688C5.79426 6.40688 6.00656 6.31894 6.16308 6.16247C6.48896 5.83659 6.48896 5.30856 6.16308 4.98268L4.60727 3.42686C4.53082 3.34814 4.43958 3.28526 4.33878 3.24183C4.2369 3.19793 4.12729 3.17479 4.01636 3.17374ZM12.1493 3.1871C11.9306 3.18504 11.7199 3.26885 11.5624 3.42052L11.5602 3.4227L9.99996 4.98288C9.84375 5.13937 9.75601 5.35146 9.75601 5.57257C9.75601 5.74239 9.80776 5.90688 9.90254 6.04496V6.06536L10.0002 6.163C10.1567 6.31947 10.369 6.40737 10.5903 6.40737C10.8116 6.40737 11.0239 6.31943 11.1805 6.16296L12.7404 4.60298L12.7426 4.60072C12.8943 4.44323 12.9781 4.23251 12.976 4.01387C12.974 3.79524 12.8862 3.58614 12.7316 3.43153C12.577 3.27692 12.3679 3.18916 12.1493 3.1871ZM8.08281 7.24019C7.97188 7.23914 7.86186 7.26022 7.75917 7.30219C7.65648 7.34416 7.56318 7.40618 7.48474 7.48462C7.4063 7.56306 7.34428 7.65635 7.30231 7.75904C7.26034 7.86173 7.23926 7.97176 7.24031 8.08269C7.24135 8.19362 7.2645 8.30322 7.3084 8.40511C7.35182 8.50589 7.41469 8.59711 7.49339 8.67355L13.2457 14.4259L13.2497 14.4296C13.4079 14.5778 13.6175 14.6586 13.8341 14.655C14.0508 14.6514 14.2575 14.5637 14.4107 14.4104C14.5639 14.2572 14.6514 14.0503 14.6549 13.8337C14.6583 13.617 14.5773 13.4075 14.4291 13.2494L14.4255 13.2456L8.67368 7.49328C8.59724 7.41457 8.50601 7.3517 8.40523 7.30828C8.30335 7.26438 8.19374 7.24123 8.08281 7.24019ZM2.16805 7.24683C1.70762 7.24683 1.33337 7.62015 1.33337 8.0815C1.33337 8.30287 1.42131 8.51518 1.57785 8.67171C1.73438 8.82824 1.94668 8.91618 2.16805 8.91618H4.37061C4.59198 8.91618 4.80428 8.82824 4.96081 8.67171C5.11734 8.51518 5.20528 8.30287 5.20528 8.0815C5.20528 7.86013 5.11734 7.64783 4.96081 7.4913C4.80428 7.33477 4.59198 7.24683 4.37061 7.24683H2.16805ZM11.6295 7.24683C11.4081 7.24683 11.1958 7.33477 11.0393 7.4913C10.8827 7.64783 10.7948 7.86013 10.7948 8.0815C10.7948 8.30287 10.8827 8.51518 11.0393 8.67171C11.1958 8.82824 11.4081 8.91618 11.6295 8.91618H13.832C14.0534 8.91618 14.2657 8.82824 14.4222 8.67171C14.5788 8.51518 14.6667 8.30287 14.6667 8.0815C14.6667 7.86013 14.5788 7.64783 14.4222 7.4913C14.2657 7.33477 14.0534 7.24683 13.832 7.24683H11.6295ZM5.45959 9.87733C5.24095 9.87512 5.03018 9.95879 4.87258 10.1104L4.87024 10.1126L3.31014 11.6727C3.15367 11.8292 3.06577 12.0415 3.06577 12.2628C3.06577 12.4328 3.11761 12.5974 3.21255 12.7356V12.756L3.31038 12.8537C3.46687 13.0099 3.67896 13.0976 3.90007 13.0976C4.12119 13.0976 4.33348 13.0097 4.48997 12.8534L6.04987 11.2935L6.05196 11.2914C6.20374 11.134 6.28771 10.9233 6.2858 10.7047C6.28389 10.486 6.19627 10.2769 6.04177 10.1222C5.88727 9.96746 5.67823 9.87954 5.45959 9.87733ZM8.08163 10.7952C7.6207 10.7952 7.24695 11.1685 7.24695 11.6294V13.8319C7.24695 14.0533 7.33489 14.2656 7.49142 14.4221C7.64795 14.5786 7.86026 14.6666 8.08163 14.6666C8.303 14.6666 8.5153 14.5786 8.67183 14.4221C8.82836 14.2656 8.9163 14.0533 8.9163 13.8319V11.6294C8.9163 11.1683 8.54185 10.7952 8.08163 10.7952Z" fill="#50535D"/> | ||
4 | +</g> | ||
5 | +</svg> |
src/qx-function-operation/svg/calendar.svg
0 → 100644
1 | +<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" ><path fill-rule="evenodd" fill="#7C7E86" d="M16 3a2 2 0 0 1 2 2v1h12V5a2 2 0 1 1 4 0v1h6a4 4 0 0 1 4 4v30a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4V10a4 4 0 0 1 4-4h6V5a2 2 0 0 1 2-2Zm14 7v1a2 2 0 1 0 4 0v-1h6v7H8v-7h6v1a2 2 0 1 0 4 0v-1h12ZM8 40V21h32v19H8Zm5-11h8v-4h-8v4Zm8 7h-8v-4h8v4Zm6-7h8v-4h-8v4Z" clip-rule="evenodd"/></svg> |
src/qx-function-operation/svg/company.svg
0 → 100644
1 | +<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" ><path fill="#7C7E86" fill-rule="evenodd" d="M8 8h14v32H8V8Zm14 36H4V8a4 4 0 0 1 4-4h14a4 4 0 0 1 4 4v4h14a4 4 0 0 1 4 4v28H22Zm4-28v24h14V16H26Zm-12-2h-4v-3h4v3Zm-4 5h4v-3h-4v3Zm22 3h-4v-3h4v3Zm-16-8h4v-3h-4v3Zm4 5h-4v-3h4v3Zm14 3h4v-3h-4v3Z" clip-rule="evenodd"/></svg> |
src/qx-function-operation/svg/copy.svg
0 → 100644
1 | +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
2 | +<g id="edit/copy"> | ||
3 | +<path id="Vector" fill-rule="evenodd" clip-rule="evenodd" d="M12.6667 2.66659H6.66667V1.33325H12.6667C13.403 1.33325 14 1.93021 14 2.66659V9.99992H12.6667V2.66659ZM2 5.33325C2 4.59687 2.59695 3.99992 3.33333 3.99992H10C10.735 3.99992 11.3333 4.5944 11.3333 5.33218V13.3361C11.3333 14.075 10.7347 14.6666 10.0009 14.6666H3.33184C2.59351 14.6666 2 14.0677 2 13.3333V5.33325ZM10 5.33325H3.33333V13.3333H10V5.33325Z" fill="#50535D"/> | ||
4 | +</g> | ||
5 | +</svg> |
20.3 KB
src/qx-function-operation/svg/department.svg
0 → 100644
1 | +<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path fill="#7C7E86" fill-rule="evenodd" d="M15 7a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3h-4v1h9a3 3 0 0 1 3 3v2h3a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H31a3 3 0 0 1-3-3V31a3 3 0 0 1 3-3h3v-1H14v1h3a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H7a3 3 0 0 1-3-3V31a3 3 0 0 1 3-3h3v-2a3 3 0 0 1 3-3h9v-1h-4a3 3 0 0 1-3-3V7Zm4 1v10h10V8H19ZM8 32v8h8v-8H8Zm24 0v8h8v-8h-8Z" clip-rule="evenodd"/></svg> |
src/qx-function-operation/svg/idcard.svg
0 → 100644
1 | +<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" ><path fill="#7C7E86" fill-rule="evenodd" d="M4 10a4 4 0 0 1 4-4h32a4 4 0 0 1 4 4v28a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4V10Zm36 0H8v28h32V10Zm-20 9h-9v-4h9v4Zm0 7h-9v-4h9v4Zm-4 7h-5v-4h5v4Zm13-5a5 5 0 0 0-5 5h-4a9.002 9.002 0 0 1 5.357-8.232 6 6 0 1 1 7.286 0A9.002 9.002 0 0 1 38 33h-4a5 5 0 0 0-5-5Zm-2-8a2 2 0 1 1 4 0 2 2 0 0 1-4 0Z" clip-rule="evenodd"/></svg> |
src/qx-function-operation/svg/people.svg
0 → 100644
1 | +<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" ><path fill="#7C7E86" fill-rule="evenodd" d="M21 13a3 3 0 1 1 6 0 3 3 0 0 1-6 0Zm3-7a7 7 0 1 0 0 14C12.954 20 4 28.954 4 40a2 2 0 1 0 4 0c0-8.837 7.163-16 16-16s16 7.163 16 16a2 2 0 1 0 4 0c0-11.046-8.954-20-20-20a7 7 0 1 0 0-14Z" clip-rule="evenodd"/></svg> |
src/qx-function-operation/svg/storage.svg
0 → 100644
1 | +<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path fill="#7C7E86" fill-rule="evenodd" d="M8 4a3 3 0 0 0-3 3v34a3 3 0 0 0 3 3h32a3 3 0 0 0 3-3V7a3 3 0 0 0-3-3H8Zm31 24H9v-8h30v8ZM9 40v-8h30v8H9Zm30-24V8H9v8h30ZM15.02 34H11v4.02h4.02V34ZM11 10h4.02v4.02H11V10Zm4.02 12H11v4.02h4.02V22Z" clip-rule="evenodd"/></svg> |
src/qx-function-operation/util.tsx
0 → 100644
1 | +import { message } from 'antd'; | ||
2 | +import _ from 'lodash-es'; | ||
3 | + | ||
4 | +// hot-formula-parser 处理公式 | ||
5 | +import { Parser } from 'hot-formula-parser'; | ||
6 | +import { FIELD_TYPE_PROPS, formatEnum } from '../constant'; | ||
7 | +const parser = new Parser(); | ||
8 | + | ||
9 | +// 数值类型字段 不可选函数 | ||
10 | +const unNumSelectArr = [ | ||
11 | + 'CONCAT', | ||
12 | + 'NOW', | ||
13 | + 'TODAY', | ||
14 | + 'CONCATENATE', | ||
15 | + 'REPLACE', | ||
16 | + 'MID', | ||
17 | + 'LEFT', | ||
18 | + 'RIGHT', | ||
19 | + 'TRIM', | ||
20 | + 'CLEAN', | ||
21 | + 'REPT', | ||
22 | + 'LOWER', | ||
23 | + 'UPPER', | ||
24 | + 'FIND', | ||
25 | + 'SPLIT', | ||
26 | + 'IF', | ||
27 | + 'OR', | ||
28 | + 'AND', | ||
29 | + 'NOT', | ||
30 | + 'FALSE', | ||
31 | + 'TRUE', | ||
32 | +]; | ||
33 | + | ||
34 | +const mathCodeReg = | ||
35 | + /^(((\$\{((?![}+]).)+(\}))|(-?\d+(\.\d+)?))[+\-*/])+((\$\{((?![}+]).)+(\}))|(-?\d+(\.\d+)?))$/; | ||
36 | +const boolCodeReg = | ||
37 | + /^(((\$\{((?![}+]).)+(\}))|(-?\d+(\.\d+)?))(==|===|!=|!==|=|<|>|<=|>=|&&|\|\|))+((\$\{((?![}+]).)+(\}))|(-?\d+(\.\d+)?))$/; | ||
38 | +const singleField = /^\$\{((?![}+]).)+(\})$/; // 匹配单字段的正则 | ||
39 | + | ||
40 | +// 根据 fieldGroupType 赋 虚拟值 | ||
41 | +const getPseudoStr = (type: string) => { | ||
42 | + let _data: string | number = '测试'; | ||
43 | + switch (type) { | ||
44 | + case FIELD_TYPE_PROPS.NUM: | ||
45 | + case FIELD_TYPE_PROPS.DOUBLE: | ||
46 | + case FIELD_TYPE_PROPS.INTEGER: | ||
47 | + case FIELD_TYPE_PROPS.DECIMAL: | ||
48 | + case FIELD_TYPE_PROPS.PERCENT: | ||
49 | + case FIELD_TYPE_PROPS.FORMULA: | ||
50 | + _data = 100; | ||
51 | + break; | ||
52 | + case FIELD_TYPE_PROPS.DATE: | ||
53 | + case FIELD_TYPE_PROPS.YEAR: | ||
54 | + case FIELD_TYPE_PROPS.YEAR_MONTH: | ||
55 | + case FIELD_TYPE_PROPS.YEAR_DATE: | ||
56 | + case FIELD_TYPE_PROPS.YEAR_MIN: | ||
57 | + case FIELD_TYPE_PROPS.YEAR_SEC: | ||
58 | + _data = '2022-12-23 11:22:33'; | ||
59 | + break; | ||
60 | + case FIELD_TYPE_PROPS.TIME: | ||
61 | + case FIELD_TYPE_PROPS.HOUR_MIN: | ||
62 | + case FIELD_TYPE_PROPS.HOUR_SEC: | ||
63 | + _data = '11:22:33'; | ||
64 | + break; | ||
65 | + } | ||
66 | + return _data; | ||
67 | +}; | ||
68 | +// 获取公式函数数组 | ||
69 | +const getFunList = (_data: any, type?: string) => { | ||
70 | + let _target: any = []; | ||
71 | + (_data || []).forEach((_v: any) => { | ||
72 | + if (_v?.children && Array.isArray(_v?.children) && !!_v?.children.length) { | ||
73 | + _target = _target.concat(getFunList(_v?.children, _v.key)); | ||
74 | + } else { | ||
75 | + if (type !== 'normal') { | ||
76 | + _target.push({ | ||
77 | + type: type || '', | ||
78 | + value: _v.title, | ||
79 | + }); | ||
80 | + } | ||
81 | + } | ||
82 | + }); | ||
83 | + return _target; | ||
84 | +}; | ||
85 | + | ||
86 | +// 公式 格式化 | ||
87 | +const getFormulaObj = (val: string, treeList: any[]) => { | ||
88 | + let _res: string = val; | ||
89 | + let _pseudoData: string = val; | ||
90 | + | ||
91 | + const _formula: any = []; | ||
92 | + if (val.indexOf('${') === -1) { | ||
93 | + return { | ||
94 | + codeList: [], | ||
95 | + formulaStr: val, | ||
96 | + }; | ||
97 | + } else { | ||
98 | + const _val = val.replaceAll('${', ' ' + '${'); | ||
99 | + const _matchs: string[] = []; | ||
100 | + const _strs: string[] = []; | ||
101 | + const _pseudoDataStrs: any[] = []; | ||
102 | + // @ts-ignore | ||
103 | + _val.replace(/(\$\{[^\s]*\})/g, function (match, p1) { | ||
104 | + _matchs.push(match); | ||
105 | + const _filter = treeList.filter( | ||
106 | + (item: { key: string }) => item.key === p1, | ||
107 | + ); | ||
108 | + if (!_.isEmpty(_filter)) { | ||
109 | + _formula.push(_filter[0]); | ||
110 | + _strs.push(_filter[0]?.titleStr || ''); | ||
111 | + _pseudoDataStrs.push(getPseudoStr(_filter[0]?.fieldGroupType || '')); | ||
112 | + } | ||
113 | + }); | ||
114 | + _strs.forEach((str, index) => { | ||
115 | + _res = _res.replace(_matchs[index], str); | ||
116 | + }); | ||
117 | + _pseudoDataStrs.forEach((str, index) => { | ||
118 | + _pseudoData = _pseudoData.replace( | ||
119 | + _matchs[index], | ||
120 | + str && typeof str === 'number' ? str.toString() : "'" + str + "'", | ||
121 | + ); | ||
122 | + }); | ||
123 | + | ||
124 | + return { | ||
125 | + codeList: _formula, | ||
126 | + formulaStr: _res, | ||
127 | + pseudoData: _pseudoData, | ||
128 | + }; | ||
129 | + } | ||
130 | +}; | ||
131 | +// 重置函数公式字符串 与hot-formula-parser 内部函数匹配 | ||
132 | +const replaceValue = (_v: string) => { | ||
133 | + let _currentVal = _v; | ||
134 | + _currentVal = _currentVal | ||
135 | + .replace(/CONCAT/g, 'CONCATENATE') | ||
136 | + .replace(/NETWORKDAY/g, 'NETWORKDAYS'); | ||
137 | + _currentVal = _currentVal | ||
138 | + .replace(/CONCATENATEENATE/g, 'CONCATENATE') | ||
139 | + .replace(/NETWORKDAYSS/g, 'NETWORKDAYS'); | ||
140 | + return _currentVal.replace(/,(?=\))/g, ''); | ||
141 | +}; | ||
142 | + | ||
143 | +/* | ||
144 | + * 检验函数公式校验合法性 | ||
145 | + * _val: 当前值 | ||
146 | + * _obj: 公式格式化后的值 | ||
147 | + * funArr: 公式函数数组 | ||
148 | + */ | ||
149 | +const checkFormulaValue = (val: string) => { | ||
150 | + let _flag = false; | ||
151 | + const res = parser.parse(replaceValue(val)); | ||
152 | + //for sass:追加 DATEDELTA 报错:'#NAME?'兼容 | ||
153 | + if (res.error) { | ||
154 | + if (res.error === '#NAME?' && val.indexOf('DATEDELTA') === 0) { | ||
155 | + _flag = true; | ||
156 | + } else if (res.error === '#ERROR!') { | ||
157 | + try { | ||
158 | + eval(replaceValue(val)); | ||
159 | + _flag = true; | ||
160 | + } catch (e) { | ||
161 | + console.log(e); | ||
162 | + _flag = false; | ||
163 | + message.warning('函数公式有语法或参数类型错误'); | ||
164 | + } | ||
165 | + } else { | ||
166 | + _flag = false; | ||
167 | + message.warning('函数公式有语法或参数类型错误'); | ||
168 | + } | ||
169 | + } else { | ||
170 | + _flag = true; | ||
171 | + } | ||
172 | + return _flag; | ||
173 | +}; | ||
174 | +/* | ||
175 | + * 多字段 单独校验 | ||
176 | + * _val: 当前值 | ||
177 | + * _obj: 公式格式化后的值 | ||
178 | + * funArr: 公式函数数组 | ||
179 | + */ | ||
180 | +const checkFormulaList = (_val: string, _obj: any, funArr: any[]) => { | ||
181 | + let _flag = false; | ||
182 | + if (_obj.codeList.length > 1) { | ||
183 | + const _keyList = _obj.codeList.map((item: any) => { | ||
184 | + return item.key; | ||
185 | + }); | ||
186 | + if (_keyList.join('') === _val && _val.indexOf(',') === -1) { | ||
187 | + _flag = false; | ||
188 | + message.warning('多个字段请用英文逗号分隔'); | ||
189 | + } else { | ||
190 | + const _filterArr = funArr.filter( | ||
191 | + (_v: any) => _val.indexOf(_v.value) > -1, | ||
192 | + ); | ||
193 | + if (_filterArr && !!_filterArr?.length) { | ||
194 | + _flag = checkFormulaValue(_obj.pseudoData); | ||
195 | + } else { | ||
196 | + _flag = false; | ||
197 | + message.warning('多个字段请使用函数运算'); | ||
198 | + } | ||
199 | + } | ||
200 | + } else { | ||
201 | + _flag = checkFormulaValue(_obj.pseudoData); | ||
202 | + } | ||
203 | + return _flag; | ||
204 | +}; | ||
205 | + | ||
206 | +/* | ||
207 | + * 默认值 当前字段 可匹配类型 判断 用于获取符合条件的字段 返回 true、false | ||
208 | + * a: 当前数据字段类型 b: 关联字段类型 c: 当前关联记录字段关联表单id | ||
209 | + */ | ||
210 | +export const getFiledSelectedFlag = (_item: any, b: string, c?: string) => { | ||
211 | + const allowWidget: Record<string, string[]> = { | ||
212 | + qxInput: [ | ||
213 | + 'qxInput', | ||
214 | + 'qxMobile', | ||
215 | + 'qxEmail', | ||
216 | + 'qxNumber', | ||
217 | + 'qxMoney', | ||
218 | + 'qxPercent', | ||
219 | + 'qxDatetime', | ||
220 | + ], | ||
221 | + qxNumber: ['qxNumber', 'qxMoney', 'qxPercent'], | ||
222 | + qxMoney: ['qxNumber', 'qxMoney', 'qxPercent'], | ||
223 | + qxPercent: ['qxNumber', 'qxMoney', 'qxPercent'], | ||
224 | + qxRichText: [ | ||
225 | + 'qxInput', | ||
226 | + 'qxMobile', | ||
227 | + 'qxEmail', | ||
228 | + 'qxNumber', | ||
229 | + 'qxMoney', | ||
230 | + 'qxPercent', | ||
231 | + 'qxDatetime', | ||
232 | + ], | ||
233 | + }; | ||
234 | + const formatEnumList = Object.keys(formatEnum).map((key) => key); | ||
235 | + | ||
236 | + const _widget = _item?.extract?.widget || _item?.widget || ''; | ||
237 | + const _relId = _item?.extract?.relId || _item?.relId || ''; | ||
238 | + | ||
239 | + let _flag = false; | ||
240 | + if (allowWidget[b] && allowWidget[b]?.indexOf(_widget) > -1) { | ||
241 | + _flag = true; | ||
242 | + } else if (b === 'relSelector' && _widget === b && c === _relId) { | ||
243 | + _flag = true; | ||
244 | + } else if (b !== 'relSelector' && _widget === b) { | ||
245 | + _flag = true; | ||
246 | + } else if ( | ||
247 | + ['qxNumber', 'qxMoney', 'qxPercent'].indexOf(b) > -1 && | ||
248 | + _widget === 'qxFormula' && | ||
249 | + _item?.extract?.fieldType === 'FORMULA' | ||
250 | + ) { | ||
251 | + _flag = true; | ||
252 | + } else if ( | ||
253 | + 'qxDatetime' === b && | ||
254 | + _widget === 'qxFormula' && | ||
255 | + formatEnumList.includes(_item?.extract?.fieldType || '') | ||
256 | + ) { | ||
257 | + // 日期格式 匹配 公式 日期增减类型 | ||
258 | + _flag = true; | ||
259 | + } else { | ||
260 | + _flag = false; | ||
261 | + } | ||
262 | + return _flag; | ||
263 | +}; | ||
264 | + | ||
265 | +/* | ||
266 | + * 校验 函数公式 | ||
267 | + * targetValue: 当前值 | ||
268 | + * treeList: 可选字段list | ||
269 | + * defaultSetting: 默认值 过滤字段 传参 | ||
270 | + */ | ||
271 | +export const checkFormulaFn = ( | ||
272 | + targetValue: string, | ||
273 | + treeList: any[], | ||
274 | + defaultSetting?: any, | ||
275 | + funObjects?: any, | ||
276 | +) => { | ||
277 | + let _flag = false; | ||
278 | + const _targetValue = targetValue.trim(); | ||
279 | + if (!_targetValue) { | ||
280 | + message.warning('内容不能为空!'); | ||
281 | + return false; | ||
282 | + } | ||
283 | + if (_.isEmpty(defaultSetting)) { | ||
284 | + return true; | ||
285 | + } | ||
286 | + const formulaObj = getFormulaObj(_targetValue, treeList); // 公式内容 字段处理 | ||
287 | + const functionArr = getFunList(funObjects); // 函数list | ||
288 | + functionArr.push( | ||
289 | + ...[ | ||
290 | + { | ||
291 | + type: 'text', | ||
292 | + value: 'CONCAT', | ||
293 | + }, | ||
294 | + { | ||
295 | + type: 'date', | ||
296 | + value: 'NETWORKDAY', | ||
297 | + }, | ||
298 | + ], | ||
299 | + ); | ||
300 | + | ||
301 | + if (!formulaObj.codeList?.length) { | ||
302 | + // 校验 没有字段时 | ||
303 | + _flag = checkFormulaValue(_targetValue); | ||
304 | + } else if ( | ||
305 | + formulaObj.codeList?.length === 1 && | ||
306 | + formulaObj.codeList[0].key === _targetValue | ||
307 | + ) { | ||
308 | + // 校验 有且只有一个字段 选择的字段类型匹配 | ||
309 | + const _typeList = ['NUM', 'DATE']; | ||
310 | + if ( | ||
311 | + defaultSetting?.fieldGroupType && | ||
312 | + _typeList.includes(defaultSetting.fieldGroupType) | ||
313 | + ) { | ||
314 | + if ( | ||
315 | + _typeList.includes(formulaObj.codeList[0].fieldGroupType) && | ||
316 | + defaultSetting.fieldGroupType === formulaObj.codeList[0].fieldGroupType | ||
317 | + ) { | ||
318 | + _flag = true; | ||
319 | + } else { | ||
320 | + _flag = false; | ||
321 | + message.warning( | ||
322 | + `${formulaObj.codeList[0].titleStr}和${defaultSetting.fieldName}不匹配!`, | ||
323 | + ); | ||
324 | + } | ||
325 | + } else { | ||
326 | + if ( | ||
327 | + getFiledSelectedFlag( | ||
328 | + formulaObj.codeList[0], | ||
329 | + defaultSetting.widget, | ||
330 | + defaultSetting.currentRelId || '', | ||
331 | + ) | ||
332 | + ) { | ||
333 | + _flag = true; | ||
334 | + } else { | ||
335 | + _flag = false; | ||
336 | + message.warning( | ||
337 | + `${formulaObj.codeList[0].titleStr}和${defaultSetting.fieldName}不匹配!`, | ||
338 | + ); | ||
339 | + } | ||
340 | + } | ||
341 | + } else { | ||
342 | + _flag = checkFormulaList(_targetValue, formulaObj, functionArr); | ||
343 | + // _flag = checkFormulaValue(formulaObj.pseudoData || ''); | ||
344 | + } | ||
345 | + if (_flag && defaultSetting?.fieldGroupType === 'NUM') { | ||
346 | + const _targetArr = _targetValue.split('('); | ||
347 | + const _filterArr = unNumSelectArr.filter( | ||
348 | + (_v: string) => _targetArr[0] === _v, | ||
349 | + ); | ||
350 | + if (_filterArr && !!_filterArr?.length) { | ||
351 | + _flag = false; | ||
352 | + message.warning('函数公式返回值与当前字段类型不匹配'); | ||
353 | + } else { | ||
354 | + _flag = true; | ||
355 | + } | ||
356 | + } | ||
357 | + return _flag; | ||
358 | +}; | ||
359 | + | ||
360 | +const getAttrItem = (list: any[], param: string) => { | ||
361 | + let res: any; | ||
362 | + for (let i = 0; i < list.length; i++) { | ||
363 | + if (list[i].attrs?.length) { | ||
364 | + res = list[i].attrs?.find((item: { key: string }) => item.key === param); | ||
365 | + if (res) { | ||
366 | + break; | ||
367 | + } | ||
368 | + } | ||
369 | + } | ||
370 | + return res; | ||
371 | +}; | ||
372 | + | ||
373 | +export enum ErrorCodeEnum { | ||
374 | + 'CHAR_ILLEGA' = 'CHAR_ILLEGA', // ${char}字符错误 | ||
375 | + 'SYNTAX_ILLEGA' = 'SYNTAX_ILLEGA', // 语法错误,缺少标识符 | ||
376 | + 'LINES_OVERFLOW' = 'LINES_OVERFLOW', // 公式最多2000行 | ||
377 | + 'LINE_CHARS_OVERFLOW' = 'LINE_CHARS_OVERFLOW', // 单行最多10000个字符 | ||
378 | + 'PARAMS_NUM_ILLEGA' = 'PARAMS_NUM_ILLEGA', // ${name}函数参数的个数不符合要求 | ||
379 | + 'PARAM_TYPE_ILLEGA' = 'PARAM_TYPE_ILLEGA', // ${name}函数的第${n}个参数不符合类型要求 | ||
380 | + 'RT_TYPE_NEQ_FIELD_TYPE' = 'RT_TYPE_NEQ_FIELD_TYPE', // 返回值数据类型与当前字段类型不匹配 . 文本字段、开关字段对公式返回值的数据类型不做校验 | ||
381 | + 'FUNCTION_UNDEFINED' = 'FUNCTION_UNDEFINED', // 函数未定义 | ||
382 | + 'FUNC_PARAM_UNDEFINED' = 'FUNC_PARAM_UNDEFINED', // 函数公式参数未定义 | ||
383 | + 'FUNC_FORMAT_ILLEGA' = 'FUNC_FORMAT_ILLEGA', // 函数公式格式不正确(编辑时的异常状态) | ||
384 | +} | ||
385 | + | ||
386 | +interface StackProps { | ||
387 | + stack: string[]; | ||
388 | +} | ||
389 | +class Stack implements StackProps { | ||
390 | + stack: string[]; | ||
391 | + | ||
392 | + constructor() { | ||
393 | + this.stack = []; | ||
394 | + } | ||
395 | + push(char: string) { | ||
396 | + return this.stack.push(char); | ||
397 | + } | ||
398 | + pop() { | ||
399 | + return this.stack.pop(); | ||
400 | + } | ||
401 | + getSize() { | ||
402 | + return this.stack.length; | ||
403 | + } | ||
404 | + peek() { | ||
405 | + return this.stack[this.getSize() - 1]; | ||
406 | + } | ||
407 | + isEmpty() { | ||
408 | + return this.getSize() === 0; | ||
409 | + } | ||
410 | +} | ||
411 | + | ||
412 | +const matchBrackets = (code: string): boolean => { | ||
413 | + const Bracket: any = { | ||
414 | + '{': '}', | ||
415 | + '(': ')', | ||
416 | + '[': ']', | ||
417 | + }; | ||
418 | + const bracketValues = Object.values(Bracket); | ||
419 | + const stack = new Stack(); | ||
420 | + for (let char of code) { | ||
421 | + if (Bracket[char]) { | ||
422 | + stack.push(char); // 左括号,入栈 | ||
423 | + } else if (bracketValues.includes(char)) { | ||
424 | + // 右括号,将当前的元素和栈顶的第一个元素进行匹配 | ||
425 | + const lastChar = stack.pop() || ''; | ||
426 | + if (char !== Bracket[lastChar]) return false; | ||
427 | + } else { | ||
428 | + // 这里排除的是空字符的情况,如果不是左右括号而是其他的空字符串或者非法字符的话,将终止本次循环,执行下一次循环 | ||
429 | + continue; | ||
430 | + } | ||
431 | + } | ||
432 | + //遍历完成之后要保证栈内要为空 | ||
433 | + return stack.getSize() === 0; | ||
434 | +}; | ||
435 | + | ||
436 | +const jsTypeToQxType: any = { | ||
437 | + '[object String]': FIELD_TYPE_PROPS.TEXT, | ||
438 | + '[object Number]': FIELD_TYPE_PROPS.NUM, | ||
439 | + '[object Boolean]': FIELD_TYPE_PROPS.BOOL, | ||
440 | + '[object Array]': FIELD_TYPE_PROPS.ARRAY, | ||
441 | + '[object Object]': FIELD_TYPE_PROPS.OBJECT, | ||
442 | + '[object Date]': FIELD_TYPE_PROPS.DATE, | ||
443 | + '[object Null]': 'NULL', | ||
444 | + '[object Undefined]': 'UNDEFINED', | ||
445 | +}; | ||
446 | + | ||
447 | +export const handleFlattenList = (varList: any[], list: any[]) => { | ||
448 | + varList.forEach((item) => { | ||
449 | + if (item.fieldGroupType) { | ||
450 | + list.push({ | ||
451 | + key: item.key, | ||
452 | + titleStr: item.titleStr, | ||
453 | + fieldGroupType: item.fieldGroupType, | ||
454 | + }); | ||
455 | + } | ||
456 | + const children = item.children || item.attrs || []; | ||
457 | + children.forEach((it: any) => { | ||
458 | + const variable = { | ||
459 | + key: it.key, | ||
460 | + titleStr: it.titleStr, | ||
461 | + fieldGroupType: it.fieldGroupType, | ||
462 | + }; | ||
463 | + list.push(variable); | ||
464 | + if (it.children || it.attrs) { | ||
465 | + handleFlattenList(it.children || it.attrs, list); | ||
466 | + } | ||
467 | + }); | ||
468 | + }); | ||
469 | +}; | ||
470 | + | ||
471 | +const getTypeFromVar = (flattenVarList: any[], field: string) => { | ||
472 | + let type: string; | ||
473 | + if (field === '${SYS.CUR_USER}') { | ||
474 | + type = 'USER'; | ||
475 | + } else if (field === '${SYS.CUR_ORG}') { | ||
476 | + type = 'ORG'; | ||
477 | + } else { | ||
478 | + type = | ||
479 | + flattenVarList.find((item) => item.key === field)?.fieldGroupType || ''; | ||
480 | + } | ||
481 | + return type; | ||
482 | +}; | ||
483 | + | ||
484 | +/* | ||
485 | + * 函数参数校验 | ||
486 | + * @param returnType 参数是函数时的返回类型 | ||
487 | + * @param varItem 参数是变量时的值 | ||
488 | + * @param param 用户输入的参数 | ||
489 | + * @param funcParamItemType 当前参数需要的类型 | ||
490 | + * @param errMap 错误 | ||
491 | + * @param funcName 函数名称 | ||
492 | + * @param i 当前参数index | ||
493 | + */ | ||
494 | + | ||
495 | +const handleParamCheck = ( | ||
496 | + returnType: string, | ||
497 | + varItem: any, | ||
498 | + param: string, | ||
499 | + funcParamItemType: string, | ||
500 | + errMap: { errCode: string; errMsg: string }, | ||
501 | + funcName: string, | ||
502 | + i: number, | ||
503 | + flattenVarList: any[], | ||
504 | +) => { | ||
505 | + let _param = param; | ||
506 | + let mathCodeType: string = ''; | ||
507 | + let isBool: boolean = false; | ||
508 | + if (mathCodeReg.test(_param)) { | ||
509 | + const matchs = _param.match(/\$\{((?![}]).)+(\})/g); | ||
510 | + if (matchs && matchs.length) { | ||
511 | + for (let i = 0; i < matchs.length; i++) { | ||
512 | + const type = flattenVarList.find( | ||
513 | + (item) => item.key === matchs[i], | ||
514 | + )?.fieldGroupType; | ||
515 | + if (type !== 'NUM') { | ||
516 | + Object.assign(errMap, { | ||
517 | + errCode: ErrorCodeEnum.PARAM_TYPE_ILLEGA, | ||
518 | + errMsg: '数学运算中包含非数字类型', | ||
519 | + }); | ||
520 | + break; | ||
521 | + } | ||
522 | + } | ||
523 | + } | ||
524 | + if (errMap.errCode) { | ||
525 | + return errMap; | ||
526 | + } | ||
527 | + mathCodeType = 'NUM'; | ||
528 | + } else if (boolCodeReg.test(_param)) { | ||
529 | + isBool = true; | ||
530 | + } else if (!returnType && !varItem?.fieldGroupType && param) { | ||
531 | + try { | ||
532 | + if (/^'|".'|"$/.test(_param)) { | ||
533 | + _param = _param.slice(1, -1); | ||
534 | + } else { | ||
535 | + _param = JSON.parse(_param); | ||
536 | + } | ||
537 | + } catch (e) { | ||
538 | + console.log(e); | ||
539 | + } | ||
540 | + } | ||
541 | + let varType = | ||
542 | + varItem?.fieldGroupType || // qx定义变量类型 | ||
543 | + mathCodeType || | ||
544 | + returnType || // 参数为函数返回值类型 | ||
545 | + jsTypeToQxType[Object.prototype.toString.call(_param)]; // js变量类型 | ||
546 | + if (isBool) { | ||
547 | + varType = 'BOOLEAN'; | ||
548 | + } | ||
549 | + let requireType = funcParamItemType === 'STRING' ? 'TEXT' : funcParamItemType; | ||
550 | + varType = varType === 'STRING' ? 'TEXT' : varType; | ||
551 | + | ||
552 | + // 支持时间戳赋值动作数值类型 | ||
553 | + // if (requireType === 'NUM' && varType === 'DATE') { | ||
554 | + // varType = 'NUM'; | ||
555 | + // } | ||
556 | + | ||
557 | + if ( | ||
558 | + requireType !== varType && | ||
559 | + requireType !== 'BOOLEAN' && | ||
560 | + requireType !== 'OBJECT' | ||
561 | + ) { | ||
562 | + Object.assign(errMap, { | ||
563 | + errCode: ErrorCodeEnum.PARAM_TYPE_ILLEGA, | ||
564 | + errMsg: `${funcName}函数的第${i + 1}个参数不符合类型要求`, | ||
565 | + }); | ||
566 | + } | ||
567 | + return errMap; | ||
568 | +}; | ||
569 | + | ||
570 | +/* | ||
571 | + * 校验 函数公式 | ||
572 | + */ | ||
573 | +export const checkFormulaExpress = ( | ||
574 | + code: string, | ||
575 | + funcDataList: any[] = [], | ||
576 | + varDataList: any[] = [], | ||
577 | + usedFuncList: any[] = [], | ||
578 | + fieldType: FIELD_TYPE_PROPS, | ||
579 | + flattenVarList: any[], | ||
580 | +): any => { | ||
581 | + const funcDataListFlatten: any[] = []; | ||
582 | + const varDataListFlatten: any[] = []; | ||
583 | + flatten(funcDataList, funcDataListFlatten); | ||
584 | + flatten(varDataList, varDataListFlatten); | ||
585 | + const errMap = { | ||
586 | + errCode: '', | ||
587 | + errMsg: '', | ||
588 | + }; | ||
589 | + | ||
590 | + const codeArr = code?.split('\n') || []; | ||
591 | + const charErr = | ||
592 | + /[\u3002|\uff1f|\uff01|\uff0c|\u3001|\uff1b|\uff1a|\u201c|\u201d|\u2018|\u2019|\uff08|\uff09|\u300a|\u300b|\u3010|\u3011|\u007e]/.exec( | ||
593 | + code, | ||
594 | + ); // 匹配中文符号 | ||
595 | + // \b\d+(\.\d+)?\b | ||
596 | + // 匹配直接使用算数运算符的情况, 如数值1 + 数值2、数值 + 4 // TODO | ||
597 | + | ||
598 | + if (code.trim() === '') { | ||
599 | + } else if (singleField.test(code.trim())) { | ||
600 | + const type = getTypeFromVar(flattenVarList, code.trim()); | ||
601 | + // 支持时间戳赋值给数值 | ||
602 | + // const timeStamp = type === 'DATE' && fieldType === 'NUM'; | ||
603 | + if ( | ||
604 | + type !== fieldType && | ||
605 | + fieldType !== FIELD_TYPE_PROPS.TEXT && | ||
606 | + fieldType !== FIELD_TYPE_PROPS.BOOL | ||
607 | + // && !timeStamp | ||
608 | + ) { | ||
609 | + Object.assign(errMap, { | ||
610 | + errCode: ErrorCodeEnum.RT_TYPE_NEQ_FIELD_TYPE, | ||
611 | + errMsg: `所选字段类型与当前字段类型不匹配`, | ||
612 | + }); | ||
613 | + } | ||
614 | + } else if (fieldType === 'NUM' && mathCodeReg.test(code.replace(/\s/g, ''))) { | ||
615 | + const matchs = code.match(/\$\{((?![}]).)+(\})/g); | ||
616 | + if (matchs && matchs.length) { | ||
617 | + for (let i = 0; i < matchs.length; i++) { | ||
618 | + const type = flattenVarList.find( | ||
619 | + (item) => item.key === matchs[i], | ||
620 | + )?.fieldGroupType; | ||
621 | + // DATE类型转为时间戳,视为数字 | ||
622 | + if ( | ||
623 | + type !== 'NUM' | ||
624 | + // && type !== 'DATE' | ||
625 | + ) { | ||
626 | + Object.assign(errMap, { | ||
627 | + errCode: ErrorCodeEnum.PARAM_TYPE_ILLEGA, | ||
628 | + errMsg: `第${i + 1}个参数不符合类型要求`, | ||
629 | + }); | ||
630 | + break; | ||
631 | + } | ||
632 | + } | ||
633 | + } | ||
634 | + } else if (charErr) { | ||
635 | + Object.assign(errMap, { | ||
636 | + errCode: ErrorCodeEnum.CHAR_ILLEGA, | ||
637 | + errMsg: `${charErr[0]}字符错误`, | ||
638 | + }); | ||
639 | + } else if (!matchBrackets(code)) { | ||
640 | + Object.assign(errMap, { | ||
641 | + errCode: ErrorCodeEnum.SYNTAX_ILLEGA, | ||
642 | + errMsg: `语法错误,缺少标识符`, | ||
643 | + }); | ||
644 | + } else if (codeArr.length > 2000) { | ||
645 | + Object.assign(errMap, { | ||
646 | + errCode: ErrorCodeEnum.LINES_OVERFLOW, | ||
647 | + errMsg: `公式最多2000行`, | ||
648 | + }); | ||
649 | + } else if (codeArr.find((line: string) => line.length > 10000)) { | ||
650 | + Object.assign(errMap, { | ||
651 | + errCode: ErrorCodeEnum.LINE_CHARS_OVERFLOW, | ||
652 | + errMsg: `单行最多10000个字符`, | ||
653 | + }); | ||
654 | + } else { | ||
655 | + let lineStr = code.replace(/\s/g, ''); | ||
656 | + let reverseLineStr = reverseStr(code).replace(/\s/g, ''); | ||
657 | + const usedFuncName: string[] = usedFuncList.map((item) => { | ||
658 | + return funcDataListFlatten.find((it) => it.id === item)?.funcNameEg; | ||
659 | + }); | ||
660 | + const regexp = new RegExp( | ||
661 | + `\\(\\s*(${reverseStr(usedFuncName.join('|'))})`, | ||
662 | + 'g', | ||
663 | + ); | ||
664 | + let match: any; | ||
665 | + const funcReturnType: any = []; | ||
666 | + while ((match = regexp.exec(reverseLineStr)) !== null) { | ||
667 | + console.log(match, '-------'); | ||
668 | + const funcName = reverseStr(match[1]); | ||
669 | + const funcItem = funcDataListFlatten.find( | ||
670 | + (item) => item.funcNameEg === funcName, | ||
671 | + ); | ||
672 | + if (!funcItem) return errMap; | ||
673 | + const startIndex = lineStr.length - match.index; | ||
674 | + const endIndex = lineStr.indexOf(')', startIndex); | ||
675 | + const paramStr = lineStr.substring(startIndex, endIndex); | ||
676 | + let paramList: string[] = []; | ||
677 | + if (paramStr?.length) { | ||
678 | + paramList = paramStr.split(','); | ||
679 | + } | ||
680 | + | ||
681 | + // ---------------------------------------------------- | ||
682 | + // const returnType = funcReturnType.pop(); | ||
683 | + if (funcItem.paramsList) { | ||
684 | + // 校验可变参数的情况,如SUM(1,2,3,4),参数个数不固定,只校验参数类型 | ||
685 | + if ( | ||
686 | + funcItem.paramsList.length === 1 && | ||
687 | + funcItem.paramsList[0]?.varargs | ||
688 | + ) { | ||
689 | + if (!paramList.length) { | ||
690 | + Object.assign(errMap, { | ||
691 | + errCode: ErrorCodeEnum.PARAM_TYPE_ILLEGA, | ||
692 | + errMsg: `${funcName}函数的参数个数不符合类型要求`, | ||
693 | + }); | ||
694 | + } | ||
695 | + const type = funcItem.paramsList[0]?.type; | ||
696 | + for (let i = 0; i < paramList.length; i++) { | ||
697 | + let _param = paramList[i]; | ||
698 | + let varItem = varDataListFlatten.find( | ||
699 | + (item) => item.key === _param, | ||
700 | + ); | ||
701 | + if (!varItem && _param.split('.').length > 2) { | ||
702 | + varItem = getAttrItem(varDataListFlatten, _param); | ||
703 | + } | ||
704 | + const isFunc = | ||
705 | + _param.split('').filter((it) => it === '0')?.length === | ||
706 | + _param.length; | ||
707 | + const err = handleParamCheck( | ||
708 | + isFunc ? funcReturnType.pop() : undefined, | ||
709 | + varItem, | ||
710 | + _param, | ||
711 | + type, | ||
712 | + errMap, | ||
713 | + funcName, | ||
714 | + i, | ||
715 | + flattenVarList, | ||
716 | + ); | ||
717 | + if (err.errCode) { | ||
718 | + break; | ||
719 | + } | ||
720 | + } | ||
721 | + } else if (funcItem.paramsList.length !== paramList.length) { | ||
722 | + // 过滤出必填参数 | ||
723 | + const requiredLength = funcItem.paramsList.filter( | ||
724 | + (item: { required?: boolean }) => item.required, | ||
725 | + )?.length; | ||
726 | + if (requiredLength !== paramList.length) { | ||
727 | + Object.assign(errMap, { | ||
728 | + errCode: ErrorCodeEnum.PARAMS_NUM_ILLEGA, | ||
729 | + errMsg: `${funcName}函数参数的个数不符合要求`, | ||
730 | + }); | ||
731 | + } | ||
732 | + } else { | ||
733 | + for (let i = 0; i < funcItem.paramsList.length; i++) { | ||
734 | + let funcParamItemType = funcItem.paramsList[i]?.type; | ||
735 | + if (funcParamItemType === 'STRING') { | ||
736 | + funcParamItemType = 'TEXT'; | ||
737 | + } | ||
738 | + let _param = paramList[i]; | ||
739 | + let varItem = varDataListFlatten.find( | ||
740 | + (item) => item.key === paramList[i], | ||
741 | + ); | ||
742 | + if (!varItem && _param.split('.').length > 2) { | ||
743 | + varItem = getAttrItem(varDataListFlatten, _param); | ||
744 | + } | ||
745 | + const isFunc = | ||
746 | + _param.split('').filter((it) => it === '0')?.length === | ||
747 | + _param.length; | ||
748 | + const err = handleParamCheck( | ||
749 | + isFunc ? funcReturnType.pop() : undefined, | ||
750 | + varItem, | ||
751 | + _param, | ||
752 | + funcParamItemType, | ||
753 | + errMap, | ||
754 | + funcName, | ||
755 | + i, | ||
756 | + flattenVarList, | ||
757 | + ); | ||
758 | + if (err.errCode) { | ||
759 | + break; | ||
760 | + } | ||
761 | + } | ||
762 | + } | ||
763 | + } | ||
764 | + funcReturnType.push(funcItem.returnType); | ||
765 | + // ---------------------------------------------------- | ||
766 | + const funcExpress = `${funcName}(${paramStr})`; | ||
767 | + lineStr = reverseStr(lineStr).replace( | ||
768 | + reverseStr(funcExpress), | ||
769 | + strFill(0, funcExpress.length, 0), | ||
770 | + ); | ||
771 | + lineStr = reverseStr(lineStr); | ||
772 | + console.log(paramStr, '----++++++---', funcExpress); | ||
773 | + console.log(lineStr, '----=======---'); | ||
774 | + } | ||
775 | + const _funcReturnType = funcReturnType.pop(); | ||
776 | + if ( | ||
777 | + _funcReturnType !== fieldType && | ||
778 | + _funcReturnType !== 'OBJECT' && | ||
779 | + fieldType !== FIELD_TYPE_PROPS.TEXT && | ||
780 | + fieldType !== FIELD_TYPE_PROPS.BOOL | ||
781 | + ) { | ||
782 | + Object.assign(errMap, { | ||
783 | + errCode: ErrorCodeEnum.RT_TYPE_NEQ_FIELD_TYPE, | ||
784 | + errMsg: `公式返回值数据类型与当前字段类型不匹配`, | ||
785 | + }); | ||
786 | + } | ||
787 | + } | ||
788 | + return errMap; | ||
789 | +}; | ||
790 | + | ||
791 | +// 字符串取反 | ||
792 | +export const reverseStr = (str: string) => { | ||
793 | + if (typeof str !== 'string') return ''; | ||
794 | + return str.split('').reverse().join(''); | ||
795 | +}; | ||
796 | + | ||
797 | +// 平铺树结构数据,只取的叶子结点 | ||
798 | +export const flatten = (dataList: any[] = [], flattenList: any[]) => { | ||
799 | + for (let i = 0; i < dataList.length; i++) { | ||
800 | + const data = dataList[i]; | ||
801 | + if (data.children) { | ||
802 | + flatten(data.children, flattenList); | ||
803 | + } else { | ||
804 | + flattenList.push(data); | ||
805 | + } | ||
806 | + } | ||
807 | +}; | ||
808 | + | ||
809 | +// 根据索引值,匹配光标所在位置周围符合语法语义规则的变量名 | ||
810 | +export const getLegalVaribleNameFromIndex = ( | ||
811 | + lineStr: string, | ||
812 | + index: number, | ||
813 | +) => { | ||
814 | + if (typeof lineStr !== 'string') return ''; | ||
815 | + const regExp = /(.*?)[\;\:\=\?\~\,\.\<\>\{\}\(\)\s]|(.*)$/; | ||
816 | + const leftStr = lineStr.substring(0, index); | ||
817 | + const rightStr = lineStr.substring(index, lineStr.length); | ||
818 | + const matchLeftStr = reverseStr(leftStr).match(regExp); | ||
819 | + const matchRightStr = rightStr.match(regExp); | ||
820 | + const leftPart = reverseStr( | ||
821 | + matchLeftStr ? matchLeftStr[1] || matchLeftStr[2] || '' : '', | ||
822 | + ); | ||
823 | + const rightPart = matchRightStr | ||
824 | + ? matchRightStr[1] || matchRightStr[2] || '' | ||
825 | + : ''; | ||
826 | + return leftPart + rightPart; | ||
827 | +}; | ||
828 | + | ||
829 | +export const domTagGen = ( | ||
830 | + variable: string, | ||
831 | + text: string, | ||
832 | + color?: string, | ||
833 | + type?: string, | ||
834 | +) => { | ||
835 | + const dom = document.createElement('span'); | ||
836 | + dom.style.margin = '0 1px'; | ||
837 | + if (!type) { | ||
838 | + dom.style.background = '#c9dffc'; | ||
839 | + dom.style.borderRadius = '2px'; | ||
840 | + dom.style.padding = '1px 3px'; | ||
841 | + dom.style.fontSize = '96%'; | ||
842 | + if (color) { | ||
843 | + dom.style.color = color; | ||
844 | + } | ||
845 | + } else if (type === 'function') { | ||
846 | + dom.style.color = '#00A870'; | ||
847 | + dom.style.cursor = 'default'; | ||
848 | + dom.className = 'funcName'; | ||
849 | + } | ||
850 | + dom.setAttribute('data-widget', variable); | ||
851 | + dom.innerHTML = text; | ||
852 | + return dom; | ||
853 | +}; | ||
854 | + | ||
855 | +/** | ||
856 | + * 填充到指定位数 | ||
857 | + * eg:000000000 | ||
858 | + * | ||
859 | + * @param val 原始值 | ||
860 | + * @param length 填充后长度 | ||
861 | + * @param fill 填充值 | ||
862 | + */ | ||
863 | +export const strFill = ( | ||
864 | + val: string | number, | ||
865 | + length: number, | ||
866 | + fill: string | number, | ||
867 | +) => { | ||
868 | + return val.toString().padStart(length, fill.toString()); | ||
869 | +}; | ||
870 | + | ||
871 | +//复制文本 | ||
872 | +export const copyText = (text: string) => { | ||
873 | + var element = createElement(text); | ||
874 | + element.select(); | ||
875 | + element.setSelectionRange(0, element.value.length); | ||
876 | + document.execCommand('copy'); | ||
877 | + element.remove(); | ||
878 | + message.success('复制成功'); | ||
879 | +}; | ||
880 | + | ||
881 | +//创建临时的输入框元素 | ||
882 | +export const createElement = (text: string) => { | ||
883 | + var isRTL = document.documentElement.getAttribute('dir') === 'rtl'; | ||
884 | + var element = document.createElement('textarea'); | ||
885 | + // 防止在ios中产生缩放效果 | ||
886 | + element.style.fontSize = '12pt'; | ||
887 | + // 重置盒模型 | ||
888 | + element.style.border = '0'; | ||
889 | + element.style.padding = '0'; | ||
890 | + element.style.margin = '0'; | ||
891 | + // 将元素移到屏幕外 | ||
892 | + element.style.position = 'absolute'; | ||
893 | + element.style[isRTL ? 'right' : 'left'] = '-9999px'; | ||
894 | + // 移动元素到页面底部 | ||
895 | + let yPosition = window.pageYOffset || document.documentElement.scrollTop; | ||
896 | + element.style.top = `${yPosition}px`; | ||
897 | + //设置元素只读 | ||
898 | + element.setAttribute('readonly', ''); | ||
899 | + element.value = text; | ||
900 | + document.body.appendChild(element); | ||
901 | + return element; | ||
902 | +}; | ||
903 | + | ||
904 | +const TEXT_CONFIG = { | ||
905 | + name: '文本', | ||
906 | + color: '#1764FF', | ||
907 | + bgColor: '#E7EFFF', | ||
908 | +}; | ||
909 | + | ||
910 | +const NUM_CONFIG = { | ||
911 | + name: '数值', | ||
912 | + color: '#FF7D00', | ||
913 | + bgColor: '#FFF7E8', | ||
914 | +}; | ||
915 | + | ||
916 | +const DATE_CONFIG = { | ||
917 | + name: '日期', | ||
918 | + color: '#722ED1', | ||
919 | + bgColor: '#F5E8FF', | ||
920 | +}; | ||
921 | + | ||
922 | +export const widgetMapping: any = { | ||
923 | + qxInput: TEXT_CONFIG, | ||
924 | + qxNumber: NUM_CONFIG, | ||
925 | + dateTime: DATE_CONFIG, | ||
926 | + qxDatetime: DATE_CONFIG, | ||
927 | + qxTime: { | ||
928 | + name: '时间', | ||
929 | + color: '#722ED1', | ||
930 | + bgColor: '#F5E8FF', | ||
931 | + }, | ||
932 | + qxSwitch: { | ||
933 | + name: '布尔', | ||
934 | + color: '#3491FA', | ||
935 | + bgColor: '#E8F7FF', | ||
936 | + }, | ||
937 | + qxSelect: { | ||
938 | + name: '单选', | ||
939 | + color: '#3491FA', | ||
940 | + bgColor: '#E8F7FF', | ||
941 | + }, | ||
942 | + qxMultiSelect: { | ||
943 | + name: '多选', | ||
944 | + color: '#3491FA', | ||
945 | + bgColor: '#E8F7FF', | ||
946 | + }, | ||
947 | + qxMobile: TEXT_CONFIG, | ||
948 | + qxMoney: NUM_CONFIG, | ||
949 | + qxEmail: TEXT_CONFIG, | ||
950 | + qxPercent: NUM_CONFIG, | ||
951 | + qxUpload: { | ||
952 | + name: '附件', | ||
953 | + color: '#3491FA', | ||
954 | + bgColor: '#E8F7FF', | ||
955 | + }, | ||
956 | + qxUploadImage: { | ||
957 | + name: '图片', | ||
958 | + color: '#3491FA', | ||
959 | + bgColor: '#E8F7FF', | ||
960 | + }, | ||
961 | + qxAddress: TEXT_CONFIG, | ||
962 | + qxRichText: { | ||
963 | + name: '富文本', | ||
964 | + color: '#F7BA1E', | ||
965 | + bgColor: '#FFFCE8', | ||
966 | + }, | ||
967 | + qxLocation: { | ||
968 | + name: '对象', | ||
969 | + color: '#3491FA', | ||
970 | + bgColor: '#E8F7FF', | ||
971 | + }, | ||
972 | + | ||
973 | + orgSelector: { | ||
974 | + name: '部门', | ||
975 | + color: '#3491FA', | ||
976 | + bgColor: '#E8F7FF', | ||
977 | + }, | ||
978 | + userSelector: { | ||
979 | + name: '人员', | ||
980 | + color: '#3491FA', | ||
981 | + bgColor: '#E8F7FF', | ||
982 | + }, | ||
983 | + createdBy: { | ||
984 | + name: '人员', | ||
985 | + color: '#3491FA', | ||
986 | + bgColor: '#E8F7FF', | ||
987 | + }, | ||
988 | + created_by: { | ||
989 | + name: '人员', | ||
990 | + color: '#3491FA', | ||
991 | + bgColor: '#E8F7FF', | ||
992 | + }, | ||
993 | + createdAt: DATE_CONFIG, | ||
994 | + created_at: DATE_CONFIG, | ||
995 | + updatedBy: { | ||
996 | + name: '人员', | ||
997 | + color: '#3491FA', | ||
998 | + bgColor: '#E8F7FF', | ||
999 | + }, | ||
1000 | + updated_by: { | ||
1001 | + name: '人员', | ||
1002 | + color: '#3491FA', | ||
1003 | + bgColor: '#E8F7FF', | ||
1004 | + }, | ||
1005 | + updatedAt: DATE_CONFIG, | ||
1006 | + updated_at: DATE_CONFIG, | ||
1007 | + qxBizNo: TEXT_CONFIG, | ||
1008 | + | ||
1009 | + relSelector: { | ||
1010 | + name: '关联记录', | ||
1011 | + color: '#3491FA', | ||
1012 | + bgColor: '#E8F7FF', | ||
1013 | + }, | ||
1014 | + relField: { | ||
1015 | + name: '关联属性', | ||
1016 | + color: '#3491FA', | ||
1017 | + bgColor: '#E8F7FF', | ||
1018 | + }, | ||
1019 | + subform: { | ||
1020 | + name: '子表', | ||
1021 | + color: '#3491FA', | ||
1022 | + bgColor: '#E8F7FF', | ||
1023 | + }, | ||
1024 | + table: { | ||
1025 | + name: '子表', | ||
1026 | + color: '#3491FA', | ||
1027 | + bgColor: '#E8F7FF', | ||
1028 | + }, | ||
1029 | + qxTree: { | ||
1030 | + name: '树形关联', | ||
1031 | + color: '#3491FA', | ||
1032 | + bgColor: '#E8F7FF', | ||
1033 | + }, | ||
1034 | + qxFormula: { | ||
1035 | + name: '公式', | ||
1036 | + color: '#3491FA', | ||
1037 | + bgColor: '#E8F7FF', | ||
1038 | + }, | ||
1039 | + qxDivider: { | ||
1040 | + name: '分割线', | ||
1041 | + color: '#3491FA', | ||
1042 | + bgColor: '#E8F7FF', | ||
1043 | + }, | ||
1044 | + qxRemark: { | ||
1045 | + name: '标题备注', | ||
1046 | + color: '#3491FA', | ||
1047 | + bgColor: '#E8F7FF', | ||
1048 | + }, | ||
1049 | + qxEmbed: { | ||
1050 | + name: '嵌入', | ||
1051 | + color: '#3491FA', | ||
1052 | + bgColor: '#E8F7FF', | ||
1053 | + }, | ||
1054 | + | ||
1055 | + qxTabs: { | ||
1056 | + name: '页签', | ||
1057 | + color: '#3491FA', | ||
1058 | + bgColor: '#E8F7FF', | ||
1059 | + }, | ||
1060 | + qxLayout: { | ||
1061 | + name: '布局', | ||
1062 | + color: '#3491FA', | ||
1063 | + bgColor: '#E8F7FF', | ||
1064 | + }, | ||
1065 | + qxObject: { | ||
1066 | + name: '对象', | ||
1067 | + color: '#F5319D', | ||
1068 | + bgColor: '#FFE8F1', | ||
1069 | + }, | ||
1070 | +}; | ||
1071 | + | ||
1072 | +export const SYSVariable = [ | ||
1073 | + { | ||
1074 | + key: '${SYS.CUR_USER}', | ||
1075 | + titleStr: '当前人员', | ||
1076 | + widget: 'qxObject', | ||
1077 | + fieldGroupType: FIELD_TYPE_PROPS.OBJECT, | ||
1078 | + attrs: [ | ||
1079 | + { | ||
1080 | + key: '${SYS.CUR_USER.id}', | ||
1081 | + titleStr: '用户ID', | ||
1082 | + fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
1083 | + }, | ||
1084 | + { | ||
1085 | + key: '${SYS.CUR_USER.name}', | ||
1086 | + titleStr: '用户名', | ||
1087 | + fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
1088 | + }, | ||
1089 | + { | ||
1090 | + key: '${SYS.CUR_USER.code}', | ||
1091 | + titleStr: '工号', | ||
1092 | + fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
1093 | + }, | ||
1094 | + { | ||
1095 | + key: '${SYS.CUR_USER.mobile}', | ||
1096 | + titleStr: '手机号', | ||
1097 | + fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
1098 | + }, | ||
1099 | + { | ||
1100 | + key: '${SYS.CUR_USER.email}', | ||
1101 | + titleStr: '邮箱', | ||
1102 | + fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
1103 | + }, | ||
1104 | + // { | ||
1105 | + // key: '${SYS.CUR_USER.role}', | ||
1106 | + // titleStr: '角色', | ||
1107 | + // fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
1108 | + // }, | ||
1109 | + ], | ||
1110 | + }, | ||
1111 | + { | ||
1112 | + key: '${SYS.CUR_ORG}', | ||
1113 | + titleStr: '当前部门', | ||
1114 | + widget: 'qxObject', | ||
1115 | + fieldGroupType: FIELD_TYPE_PROPS.OBJECT, | ||
1116 | + attrs: [ | ||
1117 | + { | ||
1118 | + key: '${SYS.CUR_ORG.id}', | ||
1119 | + titleStr: '部门ID', | ||
1120 | + fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
1121 | + }, | ||
1122 | + { | ||
1123 | + key: '${SYS.CUR_ORG.code}', | ||
1124 | + titleStr: '部门编号', | ||
1125 | + fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
1126 | + }, | ||
1127 | + { | ||
1128 | + key: '${SYS.CUR_ORG.name}', | ||
1129 | + titleStr: '部门名称', | ||
1130 | + fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
1131 | + }, | ||
1132 | + { | ||
1133 | + key: '${SYS.CUR_ORG.orgLevel}', | ||
1134 | + titleStr: '部门层级', | ||
1135 | + fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
1136 | + }, | ||
1137 | + ], | ||
1138 | + }, | ||
1139 | + { | ||
1140 | + key: '${SYS.CUR_CORP}', | ||
1141 | + titleStr: '当前公司', | ||
1142 | + widget: 'qxObject', | ||
1143 | + fieldGroupType: FIELD_TYPE_PROPS.OBJECT, | ||
1144 | + attrs: [ | ||
1145 | + { | ||
1146 | + key: '${SYS.CUR_CORP.corpCode}', | ||
1147 | + titleStr: '公司编号', | ||
1148 | + fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
1149 | + }, | ||
1150 | + { | ||
1151 | + key: '${SYS.CUR_CORP.corpName}', | ||
1152 | + titleStr: '公司名称', | ||
1153 | + fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
1154 | + }, | ||
1155 | + ], | ||
1156 | + }, | ||
1157 | + { | ||
1158 | + key: '${SYS.CUR_DATE}', | ||
1159 | + titleStr: '当前时间', | ||
1160 | + widget: 'qxDatetime', | ||
1161 | + fieldGroupType: FIELD_TYPE_PROPS.DATE, | ||
1162 | + }, | ||
1163 | + { | ||
1164 | + key: '${SYS.LOGIN_INFO}', | ||
1165 | + titleStr: '登录信息', | ||
1166 | + widget: 'qxObject', | ||
1167 | + fieldGroupType: FIELD_TYPE_PROPS.OBJECT, | ||
1168 | + attrs: [ | ||
1169 | + { | ||
1170 | + key: '${SYS.LOGIN_INFO.ipAddr}', | ||
1171 | + titleStr: 'IP地址', | ||
1172 | + fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
1173 | + }, | ||
1174 | + { | ||
1175 | + key: '${SYS.LOGIN_INFO.terminal}', | ||
1176 | + titleStr: '终端类型', | ||
1177 | + fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
1178 | + }, | ||
1179 | + { | ||
1180 | + key: '${SYS.LOGIN_INFO.browser}', | ||
1181 | + titleStr: '浏览器信息', | ||
1182 | + fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
1183 | + }, | ||
1184 | + { | ||
1185 | + key: '${SYS.LOGIN_INFO.browserRoute}', | ||
1186 | + titleStr: '当前浏览器路由信息', | ||
1187 | + fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
1188 | + }, | ||
1189 | + { | ||
1190 | + key: '${SYS.LOGIN_INFO.session}', | ||
1191 | + titleStr: 'session信息', | ||
1192 | + fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
1193 | + }, | ||
1194 | + ], | ||
1195 | + }, | ||
1196 | + // { | ||
1197 | + // key: '${SYS.CUR_SERVER}', | ||
1198 | + // titleStr: '服务器信息', | ||
1199 | + // widget: 'qxObject', | ||
1200 | + // fieldGroupType: FIELD_TYPE_PROPS.OBJECT, | ||
1201 | + // attrs: [ | ||
1202 | + // { | ||
1203 | + // key: '${SYS.CUR_SERVER.ipAddr}', | ||
1204 | + // titleStr: 'IP地址', | ||
1205 | + // fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
1206 | + // }, | ||
1207 | + // { | ||
1208 | + // key: '${SYS.CUR_SERVER.macAddr}', | ||
1209 | + // titleStr: 'MAC地址', | ||
1210 | + // fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
1211 | + // }, | ||
1212 | + // { | ||
1213 | + // key: '${SYS.CUR_SERVER.serverName}', | ||
1214 | + // titleStr: '服务器名称', | ||
1215 | + // fieldGroupType: FIELD_TYPE_PROPS.TEXT, | ||
1216 | + // }, | ||
1217 | + // ], | ||
1218 | + // }, | ||
1219 | +]; | ||
1220 | + | ||
1221 | +export const flattenVariable = (_list: any[], parent: any, newList: any[]) => { | ||
1222 | + _list.forEach((o) => { | ||
1223 | + const item = _.cloneDeep(o); | ||
1224 | + const parentTitle = parent?.titleStr || ''; | ||
1225 | + item.titleStr = `${parentTitle ? parentTitle + '.' : ''}${item.titleStr}`; | ||
1226 | + newList.push(item); | ||
1227 | + if (item.attrs) { | ||
1228 | + flattenVariable(item.attrs, item, newList); | ||
1229 | + } | ||
1230 | + }); | ||
1231 | +}; |
@@ -20,7 +20,6 @@ import { | @@ -20,7 +20,6 @@ import { | ||
20 | import React, { useCallback, useEffect, useState } from 'react'; | 20 | import React, { useCallback, useEffect, useState } from 'react'; |
21 | import JSONEditor from './codeMirror'; | 21 | import JSONEditor from './codeMirror'; |
22 | import { | 22 | import { |
23 | - formatEnum, | ||
24 | typeTranslateFieIdtype, | 23 | typeTranslateFieIdtype, |
25 | typeTranslateGrouptype, | 24 | typeTranslateGrouptype, |
26 | typeTranslateWidget, | 25 | typeTranslateWidget, |
@@ -33,6 +32,7 @@ import moment from 'dayjs'; | @@ -33,6 +32,7 @@ import moment from 'dayjs'; | ||
33 | import { cloneDeep } from 'lodash-es'; | 32 | import { cloneDeep } from 'lodash-es'; |
34 | import { SubDropIcon, SubNodeIcon } from './sub-node-icon'; | 33 | import { SubDropIcon, SubNodeIcon } from './sub-node-icon'; |
35 | 34 | ||
35 | +import { formatEnum } from '../constant'; | ||
36 | import '../style/reset.less'; | 36 | import '../style/reset.less'; |
37 | 37 | ||
38 | const valueOptions = [ | 38 | const valueOptions = [ |
1 | import type { DataNode } from 'antd/lib/tree'; | 1 | import type { DataNode } from 'antd/lib/tree'; |
2 | - | ||
3 | -export const formatEnum = { | ||
4 | - YEAR: 'YYYY', | ||
5 | - YEAR_MONTH: 'YYYY-MM', | ||
6 | - YEAR_DATE: 'YYYY-MM-DD', | ||
7 | - YEAR_HOUR: 'YYYY-MM-DD HH:00', | ||
8 | - YEAR_MIN: 'YYYY-MM-DD HH:mm', | ||
9 | - YEAR_SEC: 'YYYY-MM-DD HH:mm:ss', | ||
10 | - // "HOUR_MIN": "HH:mm", | ||
11 | - // "HOUR_SEC": "HH:mm:ss" | ||
12 | -}; | 2 | +import { formatEnum } from '../constant'; |
13 | 3 | ||
14 | export const typeText = (type: string) => { | 4 | export const typeText = (type: string) => { |
15 | switch (type) { | 5 | switch (type) { |
@@ -185,11 +185,11 @@ export default () => { | @@ -185,11 +185,11 @@ export default () => { | ||
185 | // nodeItem={nodeItem} | 185 | // nodeItem={nodeItem} |
186 | // componentItem={componentItem} | 186 | // componentItem={componentItem} |
187 | onChange={handleChange} | 187 | onChange={handleChange} |
188 | - // isHideSearch={isHide} | 188 | + isHideSearch={isHide} |
189 | isShowField={true} | 189 | isShowField={true} |
190 | - node={node} | ||
191 | - nodes={nodes} | ||
192 | - // title={titleDom} | 190 | + // node={node} |
191 | + // nodes={nodes} | ||
192 | + title={titleDom} | ||
193 | // type={'preview'} | 193 | // type={'preview'} |
194 | // component={QxTagsInput} | 194 | // component={QxTagsInput} |
195 | request={request} | 195 | request={request} |
@@ -14,7 +14,6 @@ import { | @@ -14,7 +14,6 @@ import { | ||
14 | import type { TreeProps } from 'antd/lib/tree'; | 14 | import type { TreeProps } from 'antd/lib/tree'; |
15 | import React, { useCallback, useEffect, useRef, useState } from 'react'; | 15 | import React, { useCallback, useEffect, useRef, useState } from 'react'; |
16 | import ParameterModal from './ParameterModal'; | 16 | import ParameterModal from './ParameterModal'; |
17 | -import { formatEnum } from './constant'; | ||
18 | // import type { InputRef } from 'antd'; | 17 | // import type { InputRef } from 'antd'; |
19 | import { | 18 | import { |
20 | typeTranslateFieIdtype, | 19 | typeTranslateFieIdtype, |
@@ -31,6 +30,7 @@ import { uidGen } from './stringUtil'; | @@ -31,6 +30,7 @@ import { uidGen } from './stringUtil'; | ||
31 | import './style.less'; | 30 | import './style.less'; |
32 | 31 | ||
33 | import { SubDropIcon, SubNodeIcon } from './sub-node-icon'; | 32 | import { SubDropIcon, SubNodeIcon } from './sub-node-icon'; |
33 | +import { formatEnum } from '../constant'; | ||
34 | const valueOptions = [ | 34 | const valueOptions = [ |
35 | { key: 'STRING', label: '文本' }, | 35 | { key: 'STRING', label: '文本' }, |
36 | { key: 'NUMBER', label: '数字' }, | 36 | { key: 'NUMBER', label: '数字' }, |
1 | -import * as React from 'react'; | ||
2 | -import { useCallback, useEffect, useImperativeHandle, useState } from 'react'; | 1 | +import { SearchOutlined } from '@ant-design/icons'; |
3 | import { Checkbox, Empty, Input, Radio, Spin } from 'antd'; | 2 | import { Checkbox, Empty, Input, Radio, Spin } from 'antd'; |
4 | import Menu from 'antd/es/menu'; | 3 | import Menu from 'antd/es/menu'; |
5 | -import { SearchOutlined } from '@ant-design/icons'; | 4 | +import { cloneDeep } from 'lodash-es'; |
5 | +import * as React from 'react'; | ||
6 | +import { useCallback, useEffect, useImperativeHandle, useState } from 'react'; | ||
6 | import { getRoles, getRolesByAppCode } from './service'; | 7 | import { getRoles, getRolesByAppCode } from './service'; |
7 | -import { cloneDeep } from "lodash-es"; | ||
8 | -// import _ from 'lodash'; | 8 | +// import _ from 'lodash-es'; |
9 | 9 | ||
10 | type PosCoreProps = { | 10 | type PosCoreProps = { |
11 | appId?: string; | 11 | appId?: string; |
@@ -74,7 +74,7 @@ const RoleSelCore: React.FC<PosCoreProps> = ({ | @@ -74,7 +74,7 @@ const RoleSelCore: React.FC<PosCoreProps> = ({ | ||
74 | }, [requestData]); | 74 | }, [requestData]); |
75 | 75 | ||
76 | useEffect(() => { | 76 | useEffect(() => { |
77 | - const isOnSelect = onSelect ? onSelect : () => { }; | 77 | + const isOnSelect = onSelect ? onSelect : () => {}; |
78 | if (selectedKeys) { | 78 | if (selectedKeys) { |
79 | isOnSelect(selectedKeys, selectedData); | 79 | isOnSelect(selectedKeys, selectedData); |
80 | } | 80 | } |
@@ -217,11 +217,15 @@ const RoleSelCore: React.FC<PosCoreProps> = ({ | @@ -217,11 +217,15 @@ const RoleSelCore: React.FC<PosCoreProps> = ({ | ||
217 | return ( | 217 | return ( |
218 | <Menu.SubMenu key={item.id} title={item.name}> | 218 | <Menu.SubMenu key={item.id} title={item.name}> |
219 | {item.child.map((child) => { | 219 | {item.child.map((child) => { |
220 | - return typeof child.visible === 'boolean' && !child.visible ? null : ( | 220 | + return typeof child.visible === 'boolean' && |
221 | + !child.visible ? null : ( | ||
221 | <Menu.Item key={child.id} disabled={child.disabled}> | 222 | <Menu.Item key={child.id} disabled={child.disabled}> |
222 | {multiple ? ( | 223 | {multiple ? ( |
223 | <Checkbox | 224 | <Checkbox |
224 | - checked={selectedKeys && selectedKeys.indexOf(child.id) > -1} | 225 | + checked={ |
226 | + selectedKeys && | ||
227 | + selectedKeys.indexOf(child.id) > -1 | ||
228 | + } | ||
225 | disabled={child.disabled} | 229 | disabled={child.disabled} |
226 | onChange={(e) => { | 230 | onChange={(e) => { |
227 | handleMultiSelect(e.target.checked, child); | 231 | handleMultiSelect(e.target.checked, child); |
@@ -232,7 +236,8 @@ const RoleSelCore: React.FC<PosCoreProps> = ({ | @@ -232,7 +236,8 @@ const RoleSelCore: React.FC<PosCoreProps> = ({ | ||
232 | ) : isRole ? ( | 236 | ) : isRole ? ( |
233 | <Radio | 237 | <Radio |
234 | checked={ | 238 | checked={ |
235 | - selectedKeys?.length && selectedKeys[0].indexOf(child.id) > -1 | 239 | + selectedKeys?.length && |
240 | + selectedKeys[0].indexOf(child.id) > -1 | ||
236 | } | 241 | } |
237 | disabled={child.disabled} | 242 | disabled={child.disabled} |
238 | > | 243 | > |
@@ -255,9 +260,11 @@ const RoleSelCore: React.FC<PosCoreProps> = ({ | @@ -255,9 +260,11 @@ const RoleSelCore: React.FC<PosCoreProps> = ({ | ||
255 | <Spin | 260 | <Spin |
256 | spinning={loading} | 261 | spinning={loading} |
257 | style={{ width: '100%', marginTop: '40px' }} | 262 | style={{ width: '100%', marginTop: '40px' }} |
258 | - // indicator={<LoadingOutlined style={{ fontSize: 24, marginTop: '40px' }} spin />} | 263 | + // indicator={<LoadingOutlined style={{ fontSize: 24, marginTop: '40px' }} spin />} |
259 | /> | 264 | /> |
260 | - {!loading && data.length === 0 ? <Empty style={{ paddingTop: '30px' }} /> : null} | 265 | + {!loading && data.length === 0 ? ( |
266 | + <Empty style={{ paddingTop: '30px' }} /> | ||
267 | + ) : null} | ||
261 | </div> | 268 | </div> |
262 | </div> | 269 | </div> |
263 | ); | 270 | ); |
1 | -import { QxUploadCore, deleteUploadFile as deleteFile } from '@qx/utils'; // 晴晴抽出 | ||
2 | -import { createRequest } from '@qx/utils'; | ||
3 | -const request = createRequest() | 1 | +import { |
2 | + QxUploadCore, | ||
3 | + createRequest, | ||
4 | + deleteUploadFile as deleteFile, | ||
5 | +} from '@qx/utils'; // 晴晴抽出 | ||
6 | +const request = createRequest(); | ||
4 | 7 | ||
5 | const UploadFile = ( | 8 | const UploadFile = ( |
6 | file: any, | 9 | file: any, |
@@ -8,15 +8,27 @@ | @@ -8,15 +8,27 @@ | ||
8 | "skipLibCheck": true, | 8 | "skipLibCheck": true, |
9 | "noEmit": true, | 9 | "noEmit": true, |
10 | "esModuleInterop": true, | 10 | "esModuleInterop": true, |
11 | - "noEmit": true, | ||
12 | "jsx": "react", | 11 | "jsx": "react", |
13 | "baseUrl": "./", | 12 | "baseUrl": "./", |
14 | "paths": { | 13 | "paths": { |
15 | - "@@/*": [".dumi/tmp/*"], | ||
16 | - "@qx/common": ["src"], | ||
17 | - "@qx/common/*": ["src/*", "*"], | ||
18 | - "@qx/*": ["./*/"] | 14 | + "@@/*": [ |
15 | + ".dumi/tmp/*" | ||
16 | + ], | ||
17 | + "@qx/common": [ | ||
18 | + "src" | ||
19 | + ], | ||
20 | + "@qx/common/*": [ | ||
21 | + "src/*", | ||
22 | + "*" | ||
23 | + ], | ||
24 | + "@qx/*": [ | ||
25 | + "./*/" | ||
26 | + ] | ||
19 | } | 27 | } |
20 | }, | 28 | }, |
21 | - "include": [".dumi/**/*", ".dumirc.ts", "src/**/*", "typings.d.ts"] | ||
22 | -} | 29 | + "include": [ |
30 | + ".dumirc.ts", | ||
31 | + "src/**/*", | ||
32 | + "typings.d.ts" | ||
33 | + ] | ||
34 | +} |