fix/show-count-traffic (#1070)
Reviewed-on: daoyoucloud/tachybase#1070 Co-authored-by: bai.zixv <bai.zixv@foxmail.com> Co-committed-by: bai.zixv <bai.zixv@foxmail.com>
This commit is contained in:
		
							parent
							
								
									1273e55e9b
								
							
						
					
					
						commit
						da87d7251b
					
				| @ -1,12 +1,16 @@ | ||||
| import { Plugin } from '@tachybase/client'; | ||||
| 
 | ||||
| import { SCFormula, ShowFieldFormulaInterface } from './show-formula/Formula.interface'; | ||||
| import { ShowFieldCodeInterface } from './show-code/Code.interface'; | ||||
| import { ViewCode } from './show-code/Code.view'; | ||||
| import { ShowFieldFormulaInterface } from './show-formula/Formula.interface'; | ||||
| import { ViewFormula } from './show-formula/Formula.view'; | ||||
| 
 | ||||
| export class PluginFieldAppends extends Plugin { | ||||
|   async afterAdd() { | ||||
|     this.app.pm.add(SCFormula); | ||||
|   } | ||||
|   async load() { | ||||
|     this.app.dataSourceManager.addFieldInterfaces([ShowFieldFormulaInterface]); | ||||
|     this.app.addComponents({ | ||||
|       Viewformula: ViewFormula, | ||||
|       ViewCode: ViewCode, | ||||
|     }); | ||||
|     this.app.dataSourceManager.addFieldInterfaces([ShowFieldFormulaInterface, ShowFieldCodeInterface]); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,75 @@ | ||||
| import { CollectionFieldInterface, interfacesProperties, Plugin } from '@tachybase/client'; | ||||
| 
 | ||||
| const { defaultProps } = interfacesProperties; | ||||
| 
 | ||||
| export interface CodeFieldProps { | ||||
|   // 代码字符串
 | ||||
|   jsCode: string; | ||||
|   // 前缀
 | ||||
|   prefix: string; | ||||
|   // 后缀
 | ||||
|   suffix: string; | ||||
|   // 精度
 | ||||
|   decimal: string; | ||||
| } | ||||
| 
 | ||||
| export class ShowFieldCodeInterface extends CollectionFieldInterface { | ||||
|   name = 'codeShow'; | ||||
|   type = 'object'; | ||||
|   group = 'advanced'; | ||||
|   title = 'jsCode(显示)'; | ||||
|   description = '通过jsCode, 用于定制化显示用户界面内容'; | ||||
|   sortable = true; | ||||
|   default = { | ||||
|     type: 'virtual', | ||||
|     uiSchema: { | ||||
|       type: 'string', | ||||
|       'x-component': 'ViewCode', | ||||
|       'x-component-props': { | ||||
|         jsCode: '', | ||||
|         prefix: '', | ||||
|         suffix: '', | ||||
|         decimal: '', | ||||
|       } as CodeFieldProps, | ||||
|       'x-read-pretty': true, | ||||
|     }, | ||||
|   }; | ||||
|   properties = { | ||||
|     ...defaultProps, | ||||
|     'uiSchema.x-component-props.prefix': { | ||||
|       type: 'string', | ||||
|       title: '前缀', | ||||
|       'x-decorator': 'FormItem', | ||||
|       'x-component': 'Input', | ||||
|     }, | ||||
|     'uiSchema.x-component-props.suffix': { | ||||
|       type: 'string', | ||||
|       title: '后缀', | ||||
|       'x-decorator': 'FormItem', | ||||
|       'x-component': 'Input', | ||||
|     }, | ||||
|     'uiSchema.x-component-props.decimal': { | ||||
|       type: 'string', | ||||
|       title: '{{t("Precision")}}', | ||||
|       'x-component': 'Select', | ||||
|       'x-decorator': 'FormItem', | ||||
|       default: '0', | ||||
|       enum: [ | ||||
|         { value: '0', label: '1' }, | ||||
|         { value: '1', label: '1.0' }, | ||||
|         { value: '2', label: '1.00' }, | ||||
|         { value: '3', label: '1.000' }, | ||||
|         { value: '4', label: '1.0000' }, | ||||
|         { value: '5', label: '1.00000' }, | ||||
|       ], | ||||
|     }, | ||||
|     'uiSchema.x-component-props.jsCode': { | ||||
|       type: 'string', | ||||
|       title: 'JSCode', | ||||
|       'x-decorator': 'FormItem', | ||||
|       'x-component': 'Input.TextArea', | ||||
|       default: '', | ||||
|       required: true, | ||||
|     }, | ||||
|   }; | ||||
| } | ||||
| @ -0,0 +1,109 @@ | ||||
| import React, { useEffect, useState } from 'react'; | ||||
| import { Input } from '@tachybase/client'; | ||||
| import { useField, useFieldSchema, useForm } from '@tachybase/schema'; | ||||
| 
 | ||||
| import { Descriptions } from 'antd'; | ||||
| import _ from 'lodash'; | ||||
| 
 | ||||
| import { CodeFieldProps } from './Code.interface'; | ||||
| 
 | ||||
| export const ViewCode = (props: CodeFieldProps) => { | ||||
|   const resultShowValue = useAction(props); | ||||
|   if (typeof resultShowValue === 'string') { | ||||
|     return <ShowValue value={resultShowValue} />; | ||||
|   } else { | ||||
|     return <>{resultShowValue}</>; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| const ShowValue = React.memo((props: { value: string }) => { | ||||
|   const { value } = props; | ||||
|   return <Input.ReadPretty value={value} />; | ||||
| }); | ||||
| 
 | ||||
| function useAction(props: CodeFieldProps): string | React.ReactNode { | ||||
|   const form = useForm(); | ||||
|   const fieldSchema = useFieldSchema(); | ||||
|   const field = useField(); | ||||
|   const { jsCode, prefix, suffix, decimal } = props; | ||||
| 
 | ||||
|   const path: any = field.path.entire; | ||||
|   const fieldPath = path?.replace(`.${fieldSchema.name}`, ''); | ||||
|   const recordData = _.chain(form.values).get(fieldPath).value(); | ||||
| 
 | ||||
|   const [result, setResult] = useState<any>({ | ||||
|     items: [], | ||||
|     childrenType: '', | ||||
|   }); | ||||
| 
 | ||||
|   const formatFunc = (value: string | number): string => { | ||||
|     let main = value; | ||||
|     if (typeof value === 'number') { | ||||
|       main = isNaN(value) ? value : Number(value).toFixed(+decimal || 0); | ||||
|     } | ||||
|     return `${prefix}${main}${suffix}`; | ||||
|   }; | ||||
|   useEffect(() => { | ||||
|     dynamicCode({ jsCode, form, path, recordData, result }, { setResult, formatFunc }); | ||||
|   }, []); | ||||
| 
 | ||||
|   const showItems = result.items.map((item) => { | ||||
|     return { | ||||
|       label: item.label, | ||||
|       children: <p>{item.children}</p>, | ||||
|     }; | ||||
|   }); | ||||
| 
 | ||||
|   if (result.childrenType === 'normal') { | ||||
|     return <Descriptions items={showItems} />; | ||||
|   } else if (result.childrenType === 'jsx') { | ||||
|     return <>{result?.items?.map((item) => item.children)}</>; | ||||
|   } else { | ||||
|     return result.items?.[0]?.children; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| async function dynamicCode({ jsCode, form, path, recordData, result }, { setResult, formatFunc }) { | ||||
|   try { | ||||
|     eval(jsCode); | ||||
|     // NOTE: 示例代码, 仿照此例配置即可
 | ||||
|     // {
 | ||||
|     //   import('dayjs')
 | ||||
|     //     .then((module) => {
 | ||||
|     //       // 使用加载的模块
 | ||||
|     //       const dayjs = module.default; // 假设模块默认导出了一个函数
 | ||||
|     //       const localeSetting = { invalidDate: '-' };
 | ||||
|     //       dayjs.updateLocale('en', localeSetting);
 | ||||
|     //       const date_pay = form.getValuesIn(path.replace('.date_fix', '.date_pay'));
 | ||||
| 
 | ||||
|     //       const date_receive = form.getValuesIn(path.replace('.date_fix', '.date_receive'));
 | ||||
|     //       const date_show = date_pay || date_receive;
 | ||||
| 
 | ||||
|     //       const formartedDate = dayjs(date_show ?? '-').format('YYYY-MM-DD');
 | ||||
|     //       setResult({
 | ||||
|     //         childrenType: 'jsx',
 | ||||
|     //         items: [
 | ||||
|     //           {
 | ||||
|     //             children: formartedDate,
 | ||||
|     //           },
 | ||||
|     //         ],
 | ||||
|     //       });
 | ||||
|     //     })
 | ||||
|     //     .catch((error) => {
 | ||||
|     //       // 处理加载模块时的错误
 | ||||
|     //       console.error('Failed to load module:', error);
 | ||||
|     //     });
 | ||||
|     // }
 | ||||
|   } catch (error) { | ||||
|     setResult({ | ||||
|       childrenType: '', | ||||
|       items: [ | ||||
|         { | ||||
|           key: '1', | ||||
|           label: '数据异常', | ||||
|           children: '请检查字段配置内容,error:' + error, | ||||
|         }, | ||||
|       ], | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| @ -1,6 +1,4 @@ | ||||
| import { CollectionFieldInterface, interfacesProperties, Plugin } from '@tachybase/client'; | ||||
| 
 | ||||
| import { ViewFormula } from './Formula.view'; | ||||
| import { CollectionFieldInterface, interfacesProperties } from '@tachybase/client'; | ||||
| 
 | ||||
| const { defaultProps } = interfacesProperties; | ||||
| 
 | ||||
| @ -15,14 +13,6 @@ export interface FormulaProps { | ||||
|   decimal: string; | ||||
| } | ||||
| 
 | ||||
| export class SCFormula extends Plugin { | ||||
|   async load() { | ||||
|     this.app.addComponents({ | ||||
|       Viewformula: ViewFormula, | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class ShowFieldFormulaInterface extends CollectionFieldInterface { | ||||
|   name = 'formulaShow'; | ||||
|   type = 'object'; | ||||
|  | ||||
| @ -3,100 +3,122 @@ import { Input } from '@tachybase/client'; | ||||
| import { evaluators } from '@tachybase/evaluators/client'; | ||||
| import { useField, useFieldSchema, useForm } from '@tachybase/schema'; | ||||
| 
 | ||||
| import _ from 'lodash'; | ||||
| 
 | ||||
| import { FormulaProps } from './Formula.interface'; | ||||
| 
 | ||||
| export const ViewFormula = (props: FormulaProps) => { | ||||
|   const { resultShowValue } = useAction(props); | ||||
|   return <Input.ReadPretty value={resultShowValue} />; | ||||
|   return <ShowValue value={resultShowValue} />; | ||||
| }; | ||||
| 
 | ||||
| const ShowValue = React.memo((props: { value: string }) => { | ||||
|   const { value } = props; | ||||
|   return <Input.ReadPretty value={value} />; | ||||
| }); | ||||
| 
 | ||||
| function useAction(props: FormulaProps) { | ||||
|   const form = useForm(); | ||||
|   const fieldSchema = useFieldSchema(); | ||||
|   const field = useField(); | ||||
| 
 | ||||
|   const { formulaString, prefix, suffix, decimal } = props; | ||||
| 
 | ||||
|   const path: any = field.path.entire; | ||||
|   const fieldPath = path?.replace(`.${fieldSchema.name}`, ''); | ||||
|   const recordData = _.chain(form.values).get(fieldPath).value(); | ||||
| 
 | ||||
|   const formulaArry = transformFormula(formulaString); | ||||
| 
 | ||||
|   const evaluateArray = getEvaluateArray(formulaArry, recordData); | ||||
|   if (!evaluateArray) { | ||||
|     return { resultShowValue: '' }; | ||||
|   } else { | ||||
|     const resultShowValue = getResultShowValue(evaluateArray, { prefix, suffix, decimal }); | ||||
|     return { resultShowValue }; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function transformFormula(formula: string) { | ||||
|   if (!formula) { | ||||
|     return []; | ||||
|   } | ||||
|   const formulaArray = formula.split(/([+\-*/?:()%])/).filter((item) => item); | ||||
|   return formulaArray; | ||||
| } | ||||
| 
 | ||||
| function getEvaluateArray(formulaArry, recordData): [string, object] { | ||||
|   if (formulaArry.length < 1) { | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   let varIndex = 0; | ||||
|   const calculateData = []; | ||||
|   const scopes = {}; | ||||
| 
 | ||||
|   for (let i = 0; i < formulaArry.length; i++) { | ||||
|     const item = formulaArry[i]; | ||||
| 
 | ||||
|     if (!item) continue; | ||||
| 
 | ||||
|     const isNumber = isNumberFunc(item); | ||||
|     const isSymbol = isSymbolFunc(item); | ||||
| 
 | ||||
|     if (!isNumber && !isSymbol) { | ||||
|       // NOTE: item 是字段的情况
 | ||||
|       let value; | ||||
|       // 举例: ${fieldObj}.fieldName
 | ||||
|       const pattern = /\${(.*?)}/g; | ||||
|       if (item.match(pattern)?.length) { | ||||
|         // NOTE: 关系字段
 | ||||
|         const target = item.match(pattern)[0].replace(/\${|}/g, ''); | ||||
|         // 以.分割字符串
 | ||||
|         const targetField = item.split('.')[1]; | ||||
|         const targetObj = _.chain(recordData).get(target).value(); | ||||
|         value = _.chain(targetObj).get(targetField, 0).value(); | ||||
|       } else { | ||||
|         // NOTE: 普通字段
 | ||||
|         value = _.chain(recordData).get(item, 0).value(); | ||||
|       } | ||||
| 
 | ||||
|       const varName = `var${varIndex++}`; | ||||
|       const varValue = value ?? 0; | ||||
| 
 | ||||
|       calculateData.push(`{{${varName}}}`); | ||||
|       scopes[varName] = varValue; | ||||
|     } else { | ||||
|       // NOTE: item 是数字或者符号的情况
 | ||||
|       calculateData.push(item); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return [calculateData.join(''), scopes]; | ||||
| } | ||||
| 
 | ||||
| function getResultShowValue(formulaArray, { prefix, suffix, decimal }): string { | ||||
|   const engine = evaluators.get('math.js'); | ||||
|   const evaluate = engine.evaluate.bind(engine); | ||||
|   const transformFormulaArray = transformFormula(formulaString); | ||||
| 
 | ||||
|   let calculateData = []; | ||||
| 
 | ||||
|   const newFormulaArray = (data): [string, object] => { | ||||
|     calculateData = []; | ||||
|     let count = 0; | ||||
|     const scopes = {}; | ||||
|     if (transformFormulaArray.length === 0) return; | ||||
|     for (let i = 0; i < transformFormulaArray.length; i++) { | ||||
|       const item = transformFormulaArray[i]; | ||||
|       if (!item) continue; | ||||
|       const isNumber = !isNaN(Number(item)); | ||||
|       const symbol = ['+', '-', '*', '/', '?', ':', '(', ')', '%'].includes(item); | ||||
|       if (!isNumber && !symbol) { | ||||
|         let value; | ||||
|         // 举例: ${fieldObj}.fieldName
 | ||||
|         const pattern = /\${(.*?)}/g; | ||||
|         if (item.match(pattern)?.length) { | ||||
|           const target = item.match(pattern)[0].replace(/\${|}/g, ''); | ||||
|           // 以.分割字符串
 | ||||
|           const targetField = item.split('.')[1]; | ||||
| 
 | ||||
|           if (path === fieldPath) { | ||||
|             // @ts-ignore
 | ||||
|             value = _.chain(data).get(target).get(targetField, 0).value(); | ||||
|           } else { | ||||
|             // @ts-ignore
 | ||||
|             value = _.chain(data).get(fieldPath).get(target).get(targetField, 0).value(); | ||||
|           } | ||||
|         } else { | ||||
|           if (path === fieldPath) { | ||||
|             // @ts-ignore
 | ||||
|             value = _.chain(data).get(item, 0).value(); | ||||
|           } else { | ||||
|             // @ts-ignore
 | ||||
|             value = _.chain(data).get(fieldPath).get(item, 0).value(); | ||||
|           } | ||||
|         } | ||||
|         if (!value) { | ||||
|           value = 0; | ||||
|         } | ||||
|         count += 1; | ||||
|         const varName = 'var' + count; | ||||
|         const varValue = value; | ||||
|         scopes[varName] = varValue; | ||||
|         calculateData.push('{{' + varName + '}}'); | ||||
|       } else { | ||||
|         calculateData.push(item); | ||||
|       } | ||||
|     } | ||||
|     return [calculateData.join(''), scopes]; | ||||
|   }; | ||||
| 
 | ||||
|   const formulaArray = newFormulaArray(form.values); | ||||
| 
 | ||||
|   if (!formulaArray) { | ||||
|     return {}; | ||||
|   } | ||||
|   const [code, scopes] = formulaArray; | ||||
| 
 | ||||
|   let resultShowValue; | ||||
| 
 | ||||
|   try { | ||||
|     const pre = prefix || ''; | ||||
|     const suf = suffix || ''; | ||||
|     const res = evaluate(code, scopes); | ||||
|     const main = isNaN(res) ? res : Number(res).toFixed(+decimal || 0); | ||||
|     resultShowValue = pre + main + suf; | ||||
|     const mainRes = isNaN(res) ? res : Number(res).toFixed(+decimal || 0); | ||||
|     resultShowValue = `${prefix}${mainRes}${suffix}`; | ||||
|   } catch (error) { | ||||
|     resultShowValue = `${code}`; | ||||
|     console.warn('code: ', code, ' scopes: ', scopes, 'error: ', resultShowValue, ' error message ', error.message); | ||||
|   } | ||||
| 
 | ||||
|   return { resultShowValue }; | ||||
|   return resultShowValue; | ||||
| } | ||||
| 
 | ||||
| function transformFormula(formula: string) { | ||||
|   if (!formula) return []; | ||||
|   const formulaArray = formula.split(/([+\-*/?:()%])/).filter((item) => item); | ||||
|   return formulaArray; | ||||
| // utils
 | ||||
| function isNumberFunc(value) { | ||||
|   return typeof value === 'number' && !isNaN(value); | ||||
| } | ||||
| 
 | ||||
| function isSymbolFunc(value) { | ||||
|   return ['+', '-', '*', '/', '?', ':', '(', ')', '%'].includes(value); | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user