Showing
26 changed files
with
3669 additions
and
478 deletions
Too many changes to show.
To preserve performance only 26 of 33 files are displayed.
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 | 1 | export * from './qx-app-selector'; |
12 | -export * from './utils'; | |
13 | -export * from './qx-field'; | |
14 | -export * from './qx-field-setter'; | |
15 | 2 | export * from './qx-base-condition'; |
3 | +export * from './qx-base-icon'; | |
16 | 4 | export * from './qx-btn'; |
17 | -export * from './qx-progress'; | |
18 | -export * from './qx-search-input'; | |
5 | +export * from './qx-condition'; | |
19 | 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 | 10 | export * from './qx-flow-node-selector'; |
11 | +export * from './qx-form-select'; | |
12 | +export * from './qx-function-operation'; | |
22 | 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 | 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 | 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 | 37 | type PositionProps = { |
25 | 38 | s: number; |
... | ... | @@ -36,7 +49,7 @@ export type VariableProps = { |
36 | 49 | * @param code |
37 | 50 | */ |
38 | 51 | export const getAllVariable = (code: string) => { |
39 | - let codeLocal: string = cloneDeep(code); | |
52 | + let codeLocal: string = _.clone(code); | |
40 | 53 | if (!codeLocal) { |
41 | 54 | return []; |
42 | 55 | } |
... | ... | @@ -61,307 +74,655 @@ export const getAllVariable = (code: string) => { |
61 | 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 | 101 | // eg: 'hello ${v1} ${v2}!' |
83 | 102 | value: string; |
84 | 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 | 110 | className?: any; |
88 | - newVariable?: VariableMappingProps; | |
89 | 111 | style?: any; |
90 | - autofocus?: boolean; | |
91 | - focusFunHandler?: (str: string) => void; | |
92 | 112 | readOnly?: boolean; |
93 | - // 是否使用函数(使用,则执行针对函数关键词的高亮匹配处理) | |
94 | - isUseFun?: boolean; | |
95 | 113 | from?: string; |
96 | 114 | resetValue?: string; |
97 | 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 | 129 | value, |
110 | - variableObj, | |
111 | 130 | newVariable, |
131 | + from, | |
112 | 132 | className, |
113 | 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 | 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 | 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 | 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 | 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 | 299 | editor.focus(); |
175 | 300 | }, |
176 | 301 | |
177 | 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 | 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 | 319 | line: line, |
200 | 320 | ch: sIndex, |
201 | 321 | }, |
202 | 322 | { |
203 | 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 | 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 | 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 | 397 | useEffect(() => { |
267 | 398 | // 消息提醒 新增模板时 触发方式变为"定时触发"时 需要将消息内容中的表单字段值清除 |
268 | 399 | if (resetValue) { |
269 | 400 | setValueLocal(resetValue); |
270 | 401 | } |
271 | - // console.log('--resetValue--',resetValue) | |
272 | 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 | 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 | 674 | return ( |
333 | - //这里className中的qx-copy-send-cm 是抄送节点--消息内容专用的样式,如果有必要,可将className回滚设置为'qx-formula-cm ' + className | |
334 | 675 | <div |
335 | 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 | 683 | <CodeMirror |
684 | + editorDidMount={(editor) => { | |
685 | + setEditor(editor); | |
686 | + editor.addKeyMap({ | |
687 | + 'Ctrl-f': autoFormatSelection, | |
688 | + }); | |
689 | + }} | |
341 | 690 | value={(valueLocal || '').toString()} |
342 | - editorDidMount={(editor) => setCodeEditor(editor)} | |
343 | - // onCursorActivity={(e) => console.log('e', e)} | |
344 | - // onCursorActivity={(e) => e?.showHint()} //没有会报错 | |
345 | 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 | 707 | readOnly: Boolean(readOnly), |
350 | 708 | cursorHeight: Boolean(readOnly) ? 0 : 'auto', |
351 | 709 | // 滚动(false,默认)或自动换行 |
352 | 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 | 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 | 725 | </div> |
364 | 726 | ); |
365 | 727 | }; |
366 | - | |
367 | 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 | 20 | import type { ReactElement } from 'react'; |
2 | 21 | import React, { |
22 | + useCallback, | |
3 | 23 | useEffect, |
4 | 24 | useImperativeHandle, |
25 | + useMemo, | |
5 | 26 | useRef, |
6 | 27 | useState, |
7 | - useMemo, | |
8 | - useCallback, | |
9 | 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 | 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 | 41 | import { AddressSetter } from './components/address-setter'; |
20 | -import {QxFieldPopover} from '@qx/common'; | |
21 | 42 | import { InputSetter } from './components/input-setter'; |
22 | 43 | import { OrgSetter } from './components/org-setter'; |
23 | 44 | import { RelSetter } from './components/rel-setter'; |
45 | +import { RelTreeSetter } from './components/rel-tree-setter'; | |
24 | 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 | 48 | import './index.less'; |
49 | +import { getPreviewSelect, getSelect } from './service'; | |
29 | 50 | |
30 | 51 | const { Option } = Select; |
31 | 52 | const dateFormat = 'YYYY-MM-DD'; |
32 | 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 | 56 | const timeSelectedFormatEnum: Record<string, string> = { |
47 | 57 | YEAR_HOUR: 'YYYY-MM-DD HH:00', |
... | ... | @@ -156,7 +166,7 @@ export interface paramColSelectProps extends ColSelectProps { |
156 | 166 | iconText?: string; // Popover-icon 自定义 后面跟随文本 |
157 | 167 | allowClear?: boolean; |
158 | 168 | popupOnBody?: boolean; // 下拉 跟随 body 还是自身 |
159 | - getName?: (val: any) => void | |
169 | + getName?: (val: any) => void; | |
160 | 170 | } |
161 | 171 | |
162 | 172 | export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
... | ... | @@ -201,7 +211,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
201 | 211 | |
202 | 212 | // @ts-ignore |
203 | 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 | 218 | ['qxSelect', 'qxMultiSelect'].includes(props?.widget || ''); |
206 | 219 | //单选多选 选项数据 |
207 | 220 | const [options, setOptions] = useState<any[]>([]); |
... | ... | @@ -232,7 +245,11 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
232 | 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 | 253 | if (!!res?.length) { |
237 | 254 | setOptions( |
238 | 255 | res.filter((item: any) => { |
... | ... | @@ -305,7 +322,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
305 | 322 | valueFin = valCp; |
306 | 323 | } else if (Boolean(isMixValue) && valueType === ParamValueType.FIELD) { |
307 | 324 | // 多选时,如果是`TreeSelect`传入值,val为当前选择项。这里处理追加上已选择的项 |
308 | - valueFin = [...(getMappingValues(valueLocal || [], ParamValueType.FIELD) || []), ...valueFin]; | |
325 | + valueFin = [ | |
326 | + ...(getMappingValues(valueLocal || [], ParamValueType.FIELD) || []), | |
327 | + ...valueFin, | |
328 | + ]; | |
309 | 329 | } else if (Boolean(isMultiple)) { |
310 | 330 | valueFin = valCp; |
311 | 331 | } else if (valueFin && valueFin.length > 0) { |
... | ... | @@ -356,92 +376,97 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
356 | 376 | * @param str 字符串形式 |
357 | 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 | 385 | (val || []).map((valItem: any) => { |
376 | - if (treeChild.key === valItem) { | |
386 | + if (tree.key === valItem) { | |
377 | 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 | 411 | } else { |
381 | 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 | 442 | } else { |
409 | 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 | 474 | * @param str 字符串形式 |
450 | 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 | 482 | const name: any[] = []; |
454 | 483 | let flag: boolean = false; |
455 | 484 | |
... | ... | @@ -564,7 +593,11 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
564 | 593 | type = FIELD_TYPE_PROPS.ORG; |
565 | 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 | 601 | if (fieldGroupType === FIELD_TYPE_PROPS.NUM) { |
569 | 602 | type = COMP_TYPES.INPUT_NUMBER; |
570 | 603 | } |
... | ... | @@ -579,7 +612,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
579 | 612 | * @param mValues |
580 | 613 | * @param type |
581 | 614 | */ |
582 | - function getMappingValues(mValues: MappingValueProps[], type?: ParamValueType) { | |
615 | + function getMappingValues( | |
616 | + mValues: MappingValueProps[], | |
617 | + type?: ParamValueType, | |
618 | + ) { | |
583 | 619 | if (!mValues || size(mValues) === 0) { |
584 | 620 | return []; |
585 | 621 | } |
... | ... | @@ -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 | 634 | if (!mValues || size(mValues) === 0) { |
596 | 635 | return []; |
597 | 636 | } |
... | ... | @@ -661,15 +700,21 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
661 | 700 | //手机/邮箱/地址 只能选自己类型的 |
662 | 701 | if ( |
663 | 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 | 712 | return co.extract.widget === props.widget; |
669 | 713 | } |
670 | 714 | |
671 | 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 | 718 | co.fieldGroupType === FIELD_TYPE_PROPS.FLOW_WF_RECORD |
674 | 719 | ) { |
675 | 720 | return true; |
... | ... | @@ -690,7 +735,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
690 | 735 | return true; |
691 | 736 | } |
692 | 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 | 742 | } else { |
695 | 743 | return false; |
696 | 744 | } |
... | ... | @@ -740,7 +788,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
740 | 788 | ((co.extract && co.extract.fieldKey === 'id') || |
741 | 789 | co.key.endsWith('.id') || |
742 | 790 | co.key.endsWith('|id'))) || |
743 | - (co.fieldGroupType === 'TIME' && fieldGroupType === 'DATE' && !!props?.timeSelected) || | |
791 | + (co.fieldGroupType === 'TIME' && | |
792 | + fieldGroupType === 'DATE' && | |
793 | + !!props?.timeSelected) || | |
744 | 794 | (co.fieldGroupType === 'DATE' && |
745 | 795 | fieldGroupType === 'TIME' && |
746 | 796 | !!props?.dateSelected && |
... | ... | @@ -752,7 +802,8 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
752 | 802 | }; |
753 | 803 | |
754 | 804 | // @ts-ignore |
755 | - const loopCheckCol = useCallback((nodeChildren: ColsTreeProps[]) => { | |
805 | + const loopCheckCol = useCallback( | |
806 | + (nodeChildren: ColsTreeProps[]) => { | |
756 | 807 | return nodeChildren.filter((co: ColsTreeProps) => { |
757 | 808 | if (co.children && co.children.length > 0) { |
758 | 809 | co.children = loopCheckCol(co.children); |
... | ... | @@ -777,9 +828,13 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
777 | 828 | |
778 | 829 | // 禁用选项 |
779 | 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 | 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 | 846 | const formula = valueLocal?.[0]?.value; |
792 | 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 | 852 | formulaOptions.map((item) => |
796 | 853 | Object.assign(variableObjNew, { |
797 | 854 | [`\${${item.key}\}`]: item.key, |
... | ... | @@ -863,7 +920,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
863 | 920 | fieldGroupType === FIELD_TYPE_PROPS.COMBINED_TEXT ? ( |
864 | 921 | <CodeEditor |
865 | 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 | 927 | variableObj={variableObj} |
868 | 928 | resetValue={props?.resetValue || ''} |
869 | 929 | allowClear={!!props?.allowClear} |
... | ... | @@ -877,25 +937,39 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
877 | 937 | value={valueLocal} |
878 | 938 | disabled={disabled} |
879 | 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 | 946 | <InputNumber |
884 | 947 | className={'select ' + (colsTree ? '' : 'full')} |
885 | 948 | placeholder="请填写数字" |
886 | 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 | 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 | 967 | disabled={disabled} |
897 | 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 | 975 | ) : getFieldSimpleComType() === COMP_TYPES.USER ? ( |
... | ... | @@ -907,7 +981,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
907 | 981 | getName={getName} |
908 | 982 | // @ts-ignore |
909 | 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 | 988 | ) : getFieldSimpleComType() === COMP_TYPES.ORG ? ( |
913 | 989 | <OrgSetter |
... | ... | @@ -918,7 +994,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
918 | 994 | // @ts-ignore |
919 | 995 | getName={getName} |
920 | 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 | 1001 | ) : getFieldSimpleComType() === COMP_TYPES.REL ? ( |
924 | 1002 | <RelSetter |
... | ... | @@ -954,30 +1032,36 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
954 | 1032 | ) : getFieldSimpleComType() === COMP_TYPES.INPUT ? ( |
955 | 1033 | <Input |
956 | 1034 | className={'select ' + (colsTree ? '' : 'full')} |
957 | - defaultValue={getMappingValues(valueLocal || [], ParamValueType.MANUAL).toString()} | |
1035 | + defaultValue={getMappingValues( | |
1036 | + valueLocal || [], | |
1037 | + ParamValueType.MANUAL, | |
1038 | + ).toString()} | |
958 | 1039 | // value={getMappingValues(valueLocal || [], ParamValueType.MANUAL).toString()} |
959 | 1040 | placeholder="请输入" |
960 | 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 | 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 | 1065 | getMappingValues(valueLocal || [], ParamValueType.FIELD) |
982 | 1066 | ? undefined |
983 | 1067 | : moment( |
... | ... | @@ -1008,7 +1092,12 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1008 | 1092 | ) : getFieldSimpleComType() === COMP_TYPE_FORMULA ? ( |
1009 | 1093 | <CodeEditor |
1010 | 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 | 1101 | variableObj={variableObj} |
1013 | 1102 | newVariable={newVariable} |
1014 | 1103 | onChange={(val: string) => { |
... | ... | @@ -1028,14 +1117,24 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1028 | 1117 | style={{ flex: 1 }} |
1029 | 1118 | placeholder="请填写数字" |
1030 | 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 | 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 | 1139 | disabled={disabled} |
1041 | 1140 | onChange={(val) => |
... | ... | @@ -1060,14 +1159,24 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1060 | 1159 | style={{ flex: 1 }} |
1061 | 1160 | placeholder="请填写数字" |
1062 | 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 | 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 | 1181 | disabled={disabled} |
1073 | 1182 | onChange={(val) => |
... | ... | @@ -1088,7 +1197,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1088 | 1197 | value={ |
1089 | 1198 | getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[0] |
1090 | 1199 | ? moment( |
1091 | - getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[0], | |
1200 | + getMappingValues( | |
1201 | + valueLocal || [], | |
1202 | + ParamValueType.MANUAL, | |
1203 | + )?.[0], | |
1092 | 1204 | formatEnum[ |
1093 | 1205 | !!extract?.type |
1094 | 1206 | ? extract.type === 'REL_FIELD' |
... | ... | @@ -1139,7 +1251,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1139 | 1251 | value={ |
1140 | 1252 | getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[1] |
1141 | 1253 | ? moment( |
1142 | - getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[1], | |
1254 | + getMappingValues( | |
1255 | + valueLocal || [], | |
1256 | + ParamValueType.MANUAL, | |
1257 | + )?.[1], | |
1143 | 1258 | formatEnum[ |
1144 | 1259 | !!extract?.type |
1145 | 1260 | ? extract.type === 'REL_FIELD' |
... | ... | @@ -1185,7 +1300,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1185 | 1300 | getCompleteName={getCompleteName} |
1186 | 1301 | disabled={disabled} |
1187 | 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 | 1307 | ) : getFieldSimpleComType() === COMP_TYPES.RANGE_TIME ? ( |
1191 | 1308 | <div className={'select full'} style={{ width: '100%' }}> |
... | ... | @@ -1195,7 +1312,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1195 | 1312 | value={ |
1196 | 1313 | getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[0] |
1197 | 1314 | ? moment( |
1198 | - getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[0], | |
1315 | + getMappingValues( | |
1316 | + valueLocal || [], | |
1317 | + ParamValueType.MANUAL, | |
1318 | + )?.[0], | |
1199 | 1319 | formatEnum[ |
1200 | 1320 | !!extract?.type |
1201 | 1321 | ? extract.type === 'REL_FIELD' |
... | ... | @@ -1235,7 +1355,10 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1235 | 1355 | value={ |
1236 | 1356 | getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[1] |
1237 | 1357 | ? moment( |
1238 | - getMappingValues(valueLocal || [], ParamValueType.MANUAL)?.[1], | |
1358 | + getMappingValues( | |
1359 | + valueLocal || [], | |
1360 | + ParamValueType.MANUAL, | |
1361 | + )?.[1], | |
1239 | 1362 | formatEnum[ |
1240 | 1363 | !!extract?.type |
1241 | 1364 | ? extract.type === 'REL_FIELD' |
... | ... | @@ -1279,7 +1402,8 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1279 | 1402 | className={'select qx-filter-select' + (colsTree ? '' : 'full')} |
1280 | 1403 | mode={ |
1281 | 1404 | !Boolean(isMultiple) || |
1282 | - (Boolean(isMultiple) && fieldGroupType === FIELD_TYPE_PROPS.TEXT) | |
1405 | + (Boolean(isMultiple) && | |
1406 | + fieldGroupType === FIELD_TYPE_PROPS.TEXT) | |
1283 | 1407 | ? 'tags' |
1284 | 1408 | : 'multiple' |
1285 | 1409 | } |
... | ... | @@ -1302,7 +1426,8 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1302 | 1426 | closable={closable} |
1303 | 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 | 1431 | String(label).startsWith('${') ? ( |
1307 | 1432 | <span style={{ color: 'red' }}>已缺失</span> |
1308 | 1433 | ) : ( |
... | ... | @@ -1325,7 +1450,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1325 | 1450 | ); |
1326 | 1451 | }} |
1327 | 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 | 1457 | let valueType: any; |
1331 | 1458 | // 查询原值(如果存在于`valueLocal`下)类型 |
... | ... | @@ -1343,9 +1470,17 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1343 | 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 | 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 | 1484 | } else { |
1350 | 1485 | onFilterValueChange(val, valueType, -1, true); |
1351 | 1486 | } |
... | ... | @@ -1355,17 +1490,26 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1355 | 1490 | }} |
1356 | 1491 | > |
1357 | 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 | 1497 | {valItem?.value || valItem?.name} |
1360 | 1498 | </Option> |
1361 | 1499 | ))} |
1362 | 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 | 1506 | </Option> |
1366 | 1507 | )} |
1367 | 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 | 1514 | </Option> |
1371 | 1515 | )} |
... | ... | @@ -1391,10 +1535,14 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1391 | 1535 | : disabled |
1392 | 1536 | : disabled |
1393 | 1537 | } |
1394 | - allowClear={!isMixValue || valueLocal?.[0]?.type === ParamValueType.FIELD} | |
1538 | + allowClear={ | |
1539 | + !isMixValue || valueLocal?.[0]?.type === ParamValueType.FIELD | |
1540 | + } | |
1395 | 1541 | onChange={(val) => { |
1396 | 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 | 1546 | let valueType = (valOptionsArr || []).includes(valLocal?.[0]) |
1399 | 1547 | ? ParamValueType.OPERATOR |
1400 | 1548 | : ParamValueType.MANUAL; |
... | ... | @@ -1405,27 +1553,42 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1405 | 1553 | }} |
1406 | 1554 | > |
1407 | 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 | 1561 | </Option> |
1411 | 1562 | )} |
1412 | 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 | 1569 | </Option> |
1416 | 1570 | )} |
1417 | 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 | 1577 | </Option> |
1421 | 1578 | )} |
1422 | 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 | 1585 | </Option> |
1426 | 1586 | )} |
1427 | 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 | 1592 | {valItem?.value || valItem?.name} |
1430 | 1593 | </Option> |
1431 | 1594 | ))} |
... | ... | @@ -1438,7 +1601,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1438 | 1601 | // @ts-ignore |
1439 | 1602 | width={ |
1440 | 1603 | props.popWidth || |
1441 | - (currentElem && currentElem.current && currentElem.current.clientWidth + 'px') | |
1604 | + (currentElem && | |
1605 | + currentElem.current && | |
1606 | + currentElem.current.clientWidth + 'px') | |
1442 | 1607 | } |
1443 | 1608 | data={colsTreeProps} |
1444 | 1609 | popFooter={props.extraFooter} |
... | ... | @@ -1471,7 +1636,8 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1471 | 1636 | //TODO 临时方案,可多选字段 |
1472 | 1637 | if (FIELD_TYPE.FORMULA === fieldType) { |
1473 | 1638 | const _historyValue = |
1474 | - getMappingValues(valueLocal || [], ParamValueType.FIELD) || []; | |
1639 | + getMappingValues(valueLocal || [], ParamValueType.FIELD) || | |
1640 | + []; | |
1475 | 1641 | if (!_historyValue.includes(val.toString())) { |
1476 | 1642 | const newVal = [..._historyValue, ...[val.toString()]]; |
1477 | 1643 | onFilterValueChange(newVal, ParamValueType.FIELD, 0, true); |
... | ... | @@ -1488,7 +1654,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1488 | 1654 | > |
1489 | 1655 | <ControlOutlined /> |
1490 | 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 | 1660 | ) : null} |
1493 | 1661 | </span> |
1494 | 1662 | </QxFieldPopover> |
... | ... | @@ -1520,7 +1688,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1520 | 1688 | } |
1521 | 1689 | placeholder="请输入日期(时间)" |
1522 | 1690 | defaultValue={ |
1523 | - !isEmpty(getMappingExtValue(valueLocal || [], ParamValueType.MANUAL)) | |
1691 | + !isEmpty( | |
1692 | + getMappingExtValue(valueLocal || [], ParamValueType.MANUAL), | |
1693 | + ) | |
1524 | 1694 | ? moment( |
1525 | 1695 | getMappingExtValue(valueLocal || [], ParamValueType.MANUAL), |
1526 | 1696 | formatEnum[ |
... | ... | @@ -1534,7 +1704,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1534 | 1704 | : undefined |
1535 | 1705 | } |
1536 | 1706 | value={ |
1537 | - !isEmpty(getMappingExtValue(valueLocal || [], ParamValueType.MANUAL)) | |
1707 | + !isEmpty( | |
1708 | + getMappingExtValue(valueLocal || [], ParamValueType.MANUAL), | |
1709 | + ) | |
1538 | 1710 | ? moment( |
1539 | 1711 | getMappingExtValue(valueLocal || [], ParamValueType.MANUAL), |
1540 | 1712 | formatEnum[ |
... | ... | @@ -1556,7 +1728,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1556 | 1728 | style={{ width: '100%', marginTop: '6px' }} |
1557 | 1729 | placeholder="请输入时间" |
1558 | 1730 | defaultValue={ |
1559 | - !isEmpty(getMappingExtValue(valueLocal || [], ParamValueType.MANUAL)) | |
1731 | + !isEmpty( | |
1732 | + getMappingExtValue(valueLocal || [], ParamValueType.MANUAL), | |
1733 | + ) | |
1560 | 1734 | ? moment( |
1561 | 1735 | getMappingExtValue(valueLocal || [], ParamValueType.MANUAL), |
1562 | 1736 | formatEnum[ |
... | ... | @@ -1570,7 +1744,9 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1570 | 1744 | : undefined |
1571 | 1745 | } |
1572 | 1746 | value={ |
1573 | - !isEmpty(getMappingExtValue(valueLocal || [], ParamValueType.MANUAL)) | |
1747 | + !isEmpty( | |
1748 | + getMappingExtValue(valueLocal || [], ParamValueType.MANUAL), | |
1749 | + ) | |
1574 | 1750 | ? moment( |
1575 | 1751 | getMappingExtValue(valueLocal || [], ParamValueType.MANUAL), |
1576 | 1752 | formatEnum[ |
... | ... | @@ -1616,4 +1792,4 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ |
1616 | 1792 | )} |
1617 | 1793 | </div> |
1618 | 1794 | ); |
1619 | -} | |
1795 | +}; | ... | ... |
1 | 1 | import type { DataNode } from 'antd/lib/tree'; |
2 | +import { FIELD_TYPE_PROPS } from '../constant'; | |
3 | + | |
4 | +export { FIELD_TYPE_PROPS }; | |
2 | 5 | |
3 | 6 | export type OperatorProps = { |
4 | 7 | text: string; |
... | ... | @@ -691,71 +694,6 @@ export interface ColsTreeProps extends DataNode { |
691 | 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 | 697 | export interface QxQueryProps { |
760 | 698 | paramMappings?: ParamMappingProps[]; |
761 | 699 | sqlType?: SqlTypeProps; |
... | ... | @@ -855,7 +793,11 @@ export const getOperationsType = (data: any) => { |
855 | 793 | fileTypeTem = fieldType; |
856 | 794 | } else if (fieldType === FIELD_TYPE_PROPS.REL_FIELD) { |
857 | 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 | 801 | fileTypeTem = FIELD_TYPE_PROPS.TIME; |
860 | 802 | } else { |
861 | 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 { handleHighlight } from './var-picker'; | |
14 | +const emptyImg = require('../svg/custom_chart_null.png'); | |
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 { widgetMapping } from '../util'; | |
15 | +const emptyImg = require('../svg/custom_chart_null.png'); | |
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> | |
\ No newline at end of file | ... | ... |
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 | + const funcList = originData.map( | |
149 | + (item: { name: string; code: string; details?: any[] }) => { | |
150 | + return { | |
151 | + key: item.code, | |
152 | + title: item.name, | |
153 | + selectable: false, | |
154 | + children: (item.details || []).map((it: any) => ({ | |
155 | + ...it, | |
156 | + title: it.funcNameEg, | |
157 | + key: it.id, | |
158 | + titleDesc: it.funcName, | |
159 | + formula: it.methodView, | |
160 | + desc: it.description, | |
161 | + returnType: it.dataType, | |
162 | + demon: it.executeDemo, | |
163 | + })), | |
164 | + }; | |
165 | + }, | |
166 | + ); | |
167 | + setFuncDataList(funcList); | |
168 | + }; | |
169 | + | |
170 | + useEffect(() => { | |
171 | + let _colsTree; | |
172 | + let curLevel = colsTree.filter((o) => !o.titleStr?.startsWith('【主表】')); | |
173 | + const mainLevel = colsTree | |
174 | + .filter((o) => o.titleStr?.startsWith('【主表】')) | |
175 | + .map((o) => ({ | |
176 | + ...o, | |
177 | + titleStr: o.titleStr?.substring(4), | |
178 | + })); | |
179 | + if (isInSubForm) { | |
180 | + if (_.isEmpty(curLevel)) { | |
181 | + _colsTree = mainLevel; | |
182 | + } else { | |
183 | + _colsTree = mainLevel.concat({ | |
184 | + key: 'SUB', | |
185 | + titleStr: '子表字段', | |
186 | + children: curLevel, | |
187 | + }); | |
188 | + } | |
189 | + } else { | |
190 | + _colsTree = curLevel; | |
191 | + } | |
192 | + setVarDataList([ | |
193 | + { | |
194 | + key: 'FORM', | |
195 | + titleStr: '表单变量', | |
196 | + children: _colsTree, | |
197 | + }, | |
198 | + { | |
199 | + key: 'SYS', | |
200 | + titleStr: '系统变量', | |
201 | + children: SYSVariable, | |
202 | + }, | |
203 | + ]); | |
204 | + }, [JSON.stringify(colsTree), SYSVariable, isInSubForm]); | |
205 | + useEffect(() => { | |
206 | + getFunctionList(appCode, funCode).then((res: any) => { | |
207 | + if (res) { | |
208 | + dealFunctionList(res); | |
209 | + } | |
210 | + }); | |
211 | + }, []); | |
212 | + | |
213 | + const flattenVarList = useMemo(() => { | |
214 | + const _list: any[] = []; | |
215 | + handleFlattenList(varDataList || [], _list); | |
216 | + return _list; | |
217 | + }, [varDataList]); | |
218 | + | |
219 | + // 编辑器光标移动 | |
220 | + const onHandleHoverFxChange = useCallback((data: any) => { | |
221 | + if (data) { | |
222 | + setFunDescription(data); | |
223 | + } else if (pickedFunc.current) { | |
224 | + setFunDescription(pickedFunc.current); | |
225 | + } else { | |
226 | + setFunDescription(null); | |
227 | + } | |
228 | + }, []); | |
229 | + | |
230 | + // 选中函数公式 | |
231 | + const onHandlerPickFunc = useCallback((data: any) => { | |
232 | + setNewVariable({ | |
233 | + key: data.funcNameEg, | |
234 | + name: data.title, | |
235 | + type: 'fun', | |
236 | + }); | |
237 | + }, []); | |
238 | + | |
239 | + // 选中变量 | |
240 | + const onHandlerPickVar = useCallback((data: any) => { | |
241 | + setNewVariable({ | |
242 | + key: data.key, | |
243 | + name: data.titleStr, | |
244 | + }); | |
245 | + }, []); | |
246 | + | |
247 | + useEffect(() => { | |
248 | + if (!funcDataList.length) return; | |
249 | + const { errMsg, errCode } = checkFormulaExpress( | |
250 | + value, | |
251 | + funcDataList, | |
252 | + varDataList, | |
253 | + codeEditorRef?.current?.getUsedFuncList(), | |
254 | + fieldType, | |
255 | + flattenVarList, | |
256 | + ); | |
257 | + setErrorMap({ | |
258 | + show: errCode ? true : false, | |
259 | + msg: errMsg, | |
260 | + }); | |
261 | + }, [funcDataList]); | |
262 | + | |
263 | + // 捕获编辑器内容改变 | |
264 | + const onHandlerEditorChange = (code: any) => { | |
265 | + console.log('code: ', code); | |
266 | + onChange(code); | |
267 | + if (!funcDataList.length) return; | |
268 | + const { errMsg, errCode } = checkFormulaExpress( | |
269 | + code, | |
270 | + funcDataList, | |
271 | + varDataList, | |
272 | + codeEditorRef?.current?.getUsedFuncList(), | |
273 | + fieldType, | |
274 | + flattenVarList, | |
275 | + ); | |
276 | + setErrorMap({ | |
277 | + show: errCode ? true : false, | |
278 | + msg: errMsg, | |
279 | + }); | |
280 | + }; | |
281 | + | |
282 | + useImperativeHandle(cRef, () => ({ | |
283 | + scriptEdit, | |
284 | + getUsedFuncList() { | |
285 | + return codeEditorRef?.current?.getUsedFuncList(); | |
286 | + }, | |
287 | + })); | |
288 | + | |
289 | + return ( | |
290 | + <div className="qx-operation"> | |
291 | + <div className="toolbar"> | |
292 | + <div className="field-name">{fieldName} =</div> | |
293 | + <div className="tools"> | |
294 | + <div | |
295 | + className="tool-item" | |
296 | + onClick={() => { | |
297 | + codeEditorRef?.current?.autoFormatSelection(); | |
298 | + }} | |
299 | + > | |
300 | + <span className="qx-f-icon-beautify" /> | |
301 | + {/* <UngroupOutlined /> */} | |
302 | + <span className="text ">美化</span> | |
303 | + </div> | |
304 | + <div | |
305 | + className="tool-item" | |
306 | + onClick={() => { | |
307 | + const editor = codeEditorRef?.current?.getEditor(); | |
308 | + const s_len = editor.getValue().length; | |
309 | + const startPos = { line: 0, ch: 0, sticky: null }; | |
310 | + const endPos = editor.doc.posFromIndex(s_len); | |
311 | + editor.setSelection(startPos, endPos); | |
312 | + copyText(editor.getValue()); | |
313 | + }} | |
314 | + > | |
315 | + <span className="qx-f-icon-copy" /> | |
316 | + <span className="text ">复制</span> | |
317 | + </div> | |
318 | + <div className="tool-item"> | |
319 | + <Switch | |
320 | + checked={scriptEdit} | |
321 | + size="small" | |
322 | + onChange={(checked) => { | |
323 | + setScriptEdit(checked); | |
324 | + }} | |
325 | + /> | |
326 | + <span className="text">脚本编辑</span> | |
327 | + </div> | |
328 | + </div> | |
329 | + </div> | |
330 | + <div | |
331 | + ref={editorBoxRef} | |
332 | + className="editor-box" | |
333 | + onClick={() => { | |
334 | + const editor = codeEditorRef?.current?.getEditor(); | |
335 | + editor.focus(); | |
336 | + }} | |
337 | + > | |
338 | + <CodeEditor | |
339 | + cRef={codeEditorRef} | |
340 | + key={uniKey} | |
341 | + value={value} | |
342 | + isUseFun={true} | |
343 | + autofocus={autofocus} | |
344 | + funcDataList={funcDataList} | |
345 | + varDataList={varDataList} | |
346 | + newVariable={newVariable} | |
347 | + onChange={onHandlerEditorChange} | |
348 | + onFocusFunc={(data: any) => { | |
349 | + // @ts-ignore | |
350 | + pickedFunc.current = data; | |
351 | + setFunDescription(data); | |
352 | + }} | |
353 | + /> | |
354 | + <div className={`error-zone ${errorMap.show ? 'show' : ''}`}> | |
355 | + {errorMap.msg} | |
356 | + </div> | |
357 | + </div> | |
358 | + <div className="custom"> | |
359 | + <FxPicker | |
360 | + dataSource={funcDataList} | |
361 | + onHover={onHandleHoverFxChange} | |
362 | + onPick={onHandlerPickFunc} | |
363 | + pickFunc={pickFunc} | |
364 | + /> | |
365 | + <VarPicker dataSource={varDataList} onPick={onHandlerPickVar} /> | |
366 | + <DescBox scriptEdit={scriptEdit} funcData={funDescription} /> | |
367 | + </div> | |
368 | + </div> | |
369 | + ); | |
370 | +}; | |
371 | + | |
372 | +interface QxFunOperationModalProps extends QxFunOperationProps { | |
373 | + modalParams: ModalProps; | |
374 | + defaultSetting?: any; // 函数公式 一个字段校验 所需参数 放置处 | |
375 | + isInSubForm?: boolean; | |
376 | + appCode: string; | |
377 | + funCode: string; | |
378 | +} | |
379 | + | |
380 | +/** | |
381 | + * "函数运算器"Modal弹窗版 | |
382 | + * | |
383 | + * @param value | |
384 | + * @param onChange | |
385 | + * @param style | |
386 | + * @param colsTree | |
387 | + * @param autofocus | |
388 | + * @param modalParams | |
389 | + * @param fieldName | |
390 | + * @param fieldType | |
391 | + * @param uniKey | |
392 | + * @constructor | |
393 | + */ | |
394 | +export const QxFunctionOperationModal: React.FC<QxFunOperationModalProps> = ({ | |
395 | + value, | |
396 | + onChange, | |
397 | + style, | |
398 | + colsTree, | |
399 | + autofocus, | |
400 | + modalParams, | |
401 | + fieldName, | |
402 | + fieldType, | |
403 | + uniKey, | |
404 | + isScriptEditMode, | |
405 | + isInSubForm, | |
406 | + appCode, | |
407 | + funCode, | |
408 | +}) => { | |
409 | + const qxFuncOperaionRef = useRef<any>(); | |
410 | + const [funValues, setFunValues] = useState<any>(); | |
411 | + | |
412 | + useEffect(() => { | |
413 | + setFunValues(value); | |
414 | + }, [value]); | |
415 | + | |
416 | + const [isFullScreen, setIsFullScreen] = useState(false); | |
417 | + /**实现F11全屏效果*/ | |
418 | + const fullScreen = () => { | |
419 | + // var docElm = document.documentElement; | |
420 | + // /*W3C*/ | |
421 | + // if (docElm.requestFullscreen) { | |
422 | + // docElm.requestFullscreen(); | |
423 | + // } /*FireFox */ else if (docElm.mozRequestFullScreen) { | |
424 | + // docElm.mozRequestFullScreen(); | |
425 | + // } /*Chrome等 */ else if (docElm.webkitRequestFullScreen) { | |
426 | + // docElm.webkitRequestFullScreen(); | |
427 | + // } /*IE11*/ else if (docElm.msRequestFullscreen) { | |
428 | + // docElm.msRequestFullscreen(); | |
429 | + // } | |
430 | + setIsFullScreen(true); | |
431 | + }; | |
432 | + /**退出F11全屏*/ | |
433 | + const exitFullScreen = () => { | |
434 | + // if (document.exitFullscreen) { | |
435 | + // document.exitFullscreen(); | |
436 | + // } else if (document.mozCancelFullScreen) { | |
437 | + // document.mozCancelFullScreen(); | |
438 | + // } else if (document.webkitCancelFullScreen) { | |
439 | + // document.webkitCancelFullScreen(); | |
440 | + // } else if (document.msExitFullscreen) { | |
441 | + // document.msExitFullscreen(); | |
442 | + // } | |
443 | + setIsFullScreen(false); | |
444 | + }; | |
445 | + | |
446 | + // 点击确定 调用方法 | |
447 | + const onHandleCancel = () => { | |
448 | + setFunValues(value || ''); | |
449 | + | |
450 | + const e: any = new MouseEvent('click', { | |
451 | + bubbles: true, | |
452 | + cancelable: true, | |
453 | + }); | |
454 | + setTimeout(() => modalParams?.onCancel && modalParams?.onCancel(e), 200); | |
455 | + }; | |
456 | + | |
457 | + // 点击确定 调用方法 | |
458 | + const onHandleOk = () => { | |
459 | + const { getUsedFuncList, scriptEdit } = qxFuncOperaionRef.current; | |
460 | + if (!_.isEqual(funValues, value) || scriptEdit !== isScriptEditMode) { | |
461 | + const usedFuncList = getUsedFuncList(); | |
462 | + console.log( | |
463 | + 'code: ', | |
464 | + funValues, | |
465 | + ' useFuncList: ', | |
466 | + usedFuncList, | |
467 | + ' scriptEdit: ', | |
468 | + scriptEdit, | |
469 | + ); | |
470 | + onChange(funValues, usedFuncList, scriptEdit); | |
471 | + } | |
472 | + | |
473 | + const e: any = new MouseEvent('click', { | |
474 | + bubbles: true, | |
475 | + cancelable: true, | |
476 | + }); | |
477 | + setTimeout(() => modalParams?.onOk && modalParams?.onOk(e), 200); | |
478 | + }; | |
479 | + | |
480 | + const newColsTree = useMemo(() => { | |
481 | + (colsTree || []).forEach((item) => { | |
482 | + if (item.widget === 'qxSelect') { | |
483 | + const code = item.key.slice(2, -1); | |
484 | + item.attrs = [ | |
485 | + { | |
486 | + key: '${' + code + '.code' + '}', | |
487 | + titleStr: '编号', | |
488 | + fieldGroupType: FIELD_TYPE_PROPS.TEXT, | |
489 | + }, | |
490 | + { | |
491 | + key: '${' + code + '.name' + '}', | |
492 | + titleStr: '文本', | |
493 | + fieldGroupType: FIELD_TYPE_PROPS.TEXT, | |
494 | + }, | |
495 | + { | |
496 | + key: '${' + code + '.score' + '}', | |
497 | + titleStr: '赋值', | |
498 | + fieldGroupType: FIELD_TYPE_PROPS.NUM, | |
499 | + }, | |
500 | + ]; | |
501 | + } | |
502 | + }); | |
503 | + return colsTree; | |
504 | + }, [colsTree]); | |
505 | + | |
506 | + // const handleTree = (colsTree: ColsTreeSelectProps) => { | |
507 | + // console.log(colsTree); | |
508 | + // return colsTree; | |
509 | + // }; | |
510 | + | |
511 | + return ( | |
512 | + <Modal | |
513 | + key={uniKey} | |
514 | + visible={modalParams.visible} | |
515 | + wrapClassName={`fx-wrapper`} | |
516 | + width={880} | |
517 | + centered={true} | |
518 | + maskClosable={false} | |
519 | + destroyOnClose={true} | |
520 | + footer={null} | |
521 | + {...modalParams} | |
522 | + className={`rel-more_modal ${isFullScreen ? 'rel-more_modal_full' : ''}`} | |
523 | + > | |
524 | + <div className="header"> | |
525 | + <div className="header-item"> | |
526 | + <span className="title">函数编辑</span> | |
527 | + <span | |
528 | + className="manual" | |
529 | + onClick={() => { | |
530 | + handleWindowOpen( | |
531 | + 'https://qgutech.yuque.com/g/team-qx/helper/mlh9yzyewec7hb81/collaborator' + | |
532 | + '/join?token=mjqmsZwJo9iTTkTb&source=doc_collaborator#', | |
533 | + ); | |
534 | + }} | |
535 | + > | |
536 | + <svg | |
537 | + width="16" | |
538 | + height="16" | |
539 | + viewBox="0 0 16 16" | |
540 | + fill="none" | |
541 | + className="qx-f-icon-help" | |
542 | + > | |
543 | + <g> | |
544 | + <path | |
545 | + fillRule="evenodd" | |
546 | + clipRule="evenodd" | |
547 | + 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" | |
548 | + /> | |
549 | + </g> | |
550 | + </svg> | |
551 | + 公式使用帮助手册 | |
552 | + </span> | |
553 | + </div> | |
554 | + {/* <Tooltip placement="right" title={'公式使用帮助手册'}> | |
555 | + </Tooltip> */} | |
556 | + <div className="header-item"> | |
557 | + {isFullScreen ? ( | |
558 | + <CompressOutlined | |
559 | + className="icon-right expand" | |
560 | + onClick={exitFullScreen} | |
561 | + /> | |
562 | + ) : ( | |
563 | + <ExpandOutlined | |
564 | + className="icon-right expand" | |
565 | + onClick={fullScreen} | |
566 | + /> | |
567 | + )} | |
568 | + <CloseOutlined | |
569 | + className="icon-right close" | |
570 | + onClick={onHandleCancel} | |
571 | + /> | |
572 | + </div> | |
573 | + </div> | |
574 | + <QxFunctionOperation | |
575 | + cRef={qxFuncOperaionRef} | |
576 | + value={funValues} | |
577 | + onChange={(code: string) => { | |
578 | + setFunValues(code); | |
579 | + }} | |
580 | + appCode={appCode} | |
581 | + funCode={funCode} | |
582 | + isInSubForm={isInSubForm} | |
583 | + style={style} | |
584 | + colsTree={newColsTree} | |
585 | + isScriptEditMode={isScriptEditMode} | |
586 | + autofocus={autofocus} | |
587 | + fieldName={fieldName} | |
588 | + fieldType={fieldType} | |
589 | + uniKey={uniKey} | |
590 | + isFullScreen={isFullScreen} | |
591 | + /> | |
592 | + <div className="footer"> | |
593 | + <Button onClick={onHandleCancel}>取消</Button> | |
594 | + <Button type="primary" onClick={onHandleOk}> | |
595 | + 确定 | |
596 | + </Button> | |
597 | + </div> | |
598 | + </Modal> | |
599 | + ); | |
600 | +}; | ... | ... |
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> | ... | ... |