Commit ff08f027a45c39750d3f4eab3d3cba9d1f629fc4

Authored by zhuqin
1 parent 4fb0b766

feat: 函数编辑器

Too many changes to show.

To preserve performance only 26 of 33 files are displayed.

  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
  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;
... ...
  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 +});
... ...
  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);
... ...
  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 +}
... ...
  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
... ...
  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 +};
... ...
  1 +import { request } from '@qx/common';
  2 +
  3 +/**
  4 + *
  5 + * @param appCode
  6 + * @param funCode
  7 + */
  8 +export function getFunctionList(appCode: string, funCode: string) {
  9 + return request.get(
  10 + `/qx-apaas-lowcode/function/${appCode}/${funCode}/listFunctions`,
  11 + );
  12 +}
... ...
  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>
... ...
  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>
... ...
  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>
... ...
  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>
... ...
  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>
... ...
  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>
... ...
  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>
... ...
  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>
... ...