Commit 570e82110c9916b21555a32094eaed769b84354f

Authored by 邱嘉伟
2 parents 377df153 2a6613bb

Merge branch 'feature/dataflow' of http://gitlab.qgutech.com/tianqiang/qx-common…

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