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 { 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 { | export class PluginFieldAppends extends Plugin { | ||||||
|   async afterAdd() { |  | ||||||
|     this.app.pm.add(SCFormula); |  | ||||||
|   } |  | ||||||
|   async load() { |   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 { CollectionFieldInterface, interfacesProperties } from '@tachybase/client'; | ||||||
| 
 |  | ||||||
| import { ViewFormula } from './Formula.view'; |  | ||||||
| 
 | 
 | ||||||
| const { defaultProps } = interfacesProperties; | const { defaultProps } = interfacesProperties; | ||||||
| 
 | 
 | ||||||
| @ -15,14 +13,6 @@ export interface FormulaProps { | |||||||
|   decimal: string; |   decimal: string; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class SCFormula extends Plugin { |  | ||||||
|   async load() { |  | ||||||
|     this.app.addComponents({ |  | ||||||
|       Viewformula: ViewFormula, |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export class ShowFieldFormulaInterface extends CollectionFieldInterface { | export class ShowFieldFormulaInterface extends CollectionFieldInterface { | ||||||
|   name = 'formulaShow'; |   name = 'formulaShow'; | ||||||
|   type = 'object'; |   type = 'object'; | ||||||
|  | |||||||
| @ -3,100 +3,122 @@ import { Input } from '@tachybase/client'; | |||||||
| import { evaluators } from '@tachybase/evaluators/client'; | import { evaluators } from '@tachybase/evaluators/client'; | ||||||
| import { useField, useFieldSchema, useForm } from '@tachybase/schema'; | import { useField, useFieldSchema, useForm } from '@tachybase/schema'; | ||||||
| 
 | 
 | ||||||
|  | import _ from 'lodash'; | ||||||
|  | 
 | ||||||
| import { FormulaProps } from './Formula.interface'; | import { FormulaProps } from './Formula.interface'; | ||||||
| 
 | 
 | ||||||
| export const ViewFormula = (props: FormulaProps) => { | export const ViewFormula = (props: FormulaProps) => { | ||||||
|   const { resultShowValue } = useAction(props); |   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) { | function useAction(props: FormulaProps) { | ||||||
|   const form = useForm(); |   const form = useForm(); | ||||||
|   const fieldSchema = useFieldSchema(); |   const fieldSchema = useFieldSchema(); | ||||||
|   const field = useField(); |   const field = useField(); | ||||||
|  | 
 | ||||||
|   const { formulaString, prefix, suffix, decimal } = props; |   const { formulaString, prefix, suffix, decimal } = props; | ||||||
| 
 | 
 | ||||||
|   const path: any = field.path.entire; |   const path: any = field.path.entire; | ||||||
|   const fieldPath = path?.replace(`.${fieldSchema.name}`, ''); |   const fieldPath = path?.replace(`.${fieldSchema.name}`, ''); | ||||||
|   const engine = evaluators.get('math.js'); |   const recordData = _.chain(form.values).get(fieldPath).value(); | ||||||
|   const evaluate = engine.evaluate.bind(engine); |  | ||||||
|   const transformFormulaArray = transformFormula(formulaString); |  | ||||||
| 
 | 
 | ||||||
|   let calculateData = []; |   const formulaArry = transformFormula(formulaString); | ||||||
| 
 | 
 | ||||||
|   const newFormulaArray = (data): [string, object] => { |   const evaluateArray = getEvaluateArray(formulaArry, recordData); | ||||||
|     calculateData = []; |   if (!evaluateArray) { | ||||||
|     let count = 0; |     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 = {}; |   const scopes = {}; | ||||||
|     if (transformFormulaArray.length === 0) return; | 
 | ||||||
|     for (let i = 0; i < transformFormulaArray.length; i++) { |   for (let i = 0; i < formulaArry.length; i++) { | ||||||
|       const item = transformFormulaArray[i]; |     const item = formulaArry[i]; | ||||||
|  | 
 | ||||||
|     if (!item) continue; |     if (!item) continue; | ||||||
|       const isNumber = !isNaN(Number(item)); | 
 | ||||||
|       const symbol = ['+', '-', '*', '/', '?', ':', '(', ')', '%'].includes(item); |     const isNumber = isNumberFunc(item); | ||||||
|       if (!isNumber && !symbol) { |     const isSymbol = isSymbolFunc(item); | ||||||
|  | 
 | ||||||
|  |     if (!isNumber && !isSymbol) { | ||||||
|  |       // NOTE: item 是字段的情况
 | ||||||
|       let value; |       let value; | ||||||
|       // 举例: ${fieldObj}.fieldName
 |       // 举例: ${fieldObj}.fieldName
 | ||||||
|       const pattern = /\${(.*?)}/g; |       const pattern = /\${(.*?)}/g; | ||||||
|       if (item.match(pattern)?.length) { |       if (item.match(pattern)?.length) { | ||||||
|  |         // NOTE: 关系字段
 | ||||||
|         const target = item.match(pattern)[0].replace(/\${|}/g, ''); |         const target = item.match(pattern)[0].replace(/\${|}/g, ''); | ||||||
|         // 以.分割字符串
 |         // 以.分割字符串
 | ||||||
|         const targetField = item.split('.')[1]; |         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(); | ||||||
|  |       } | ||||||
| 
 | 
 | ||||||
|           if (path === fieldPath) { |       const varName = `var${varIndex++}`; | ||||||
|             // @ts-ignore
 |       const varValue = value ?? 0; | ||||||
|             value = _.chain(data).get(target).get(targetField, 0).value(); | 
 | ||||||
|           } else { |       calculateData.push(`{{${varName}}}`); | ||||||
|             // @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; |       scopes[varName] = varValue; | ||||||
|         calculateData.push('{{' + varName + '}}'); |  | ||||||
|     } else { |     } else { | ||||||
|  |       // NOTE: item 是数字或者符号的情况
 | ||||||
|       calculateData.push(item); |       calculateData.push(item); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|   return [calculateData.join(''), scopes]; |   return [calculateData.join(''), scopes]; | ||||||
|   }; | } | ||||||
| 
 | 
 | ||||||
|   const formulaArray = newFormulaArray(form.values); | function getResultShowValue(formulaArray, { prefix, suffix, decimal }): string { | ||||||
| 
 |   const engine = evaluators.get('math.js'); | ||||||
|   if (!formulaArray) { |   const evaluate = engine.evaluate.bind(engine); | ||||||
|     return {}; |  | ||||||
|   } |  | ||||||
|   const [code, scopes] = formulaArray; |   const [code, scopes] = formulaArray; | ||||||
|  | 
 | ||||||
|   let resultShowValue; |   let resultShowValue; | ||||||
|  | 
 | ||||||
|   try { |   try { | ||||||
|     const pre = prefix || ''; |  | ||||||
|     const suf = suffix || ''; |  | ||||||
|     const res = evaluate(code, scopes); |     const res = evaluate(code, scopes); | ||||||
|     const main = isNaN(res) ? res : Number(res).toFixed(+decimal || 0); |     const mainRes = isNaN(res) ? res : Number(res).toFixed(+decimal || 0); | ||||||
|     resultShowValue = pre + main + suf; |     resultShowValue = `${prefix}${mainRes}${suffix}`; | ||||||
|   } catch (error) { |   } catch (error) { | ||||||
|     resultShowValue = `${code}`; |     resultShowValue = `${code}`; | ||||||
|     console.warn('code: ', code, ' scopes: ', scopes, 'error: ', resultShowValue, ' error message ', error.message); |     console.warn('code: ', code, ' scopes: ', scopes, 'error: ', resultShowValue, ' error message ', error.message); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return { resultShowValue }; |   return resultShowValue; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function transformFormula(formula: string) { | // utils
 | ||||||
|   if (!formula) return []; | function isNumberFunc(value) { | ||||||
|   const formulaArray = formula.split(/([+\-*/?:()%])/).filter((item) => item); |   return typeof value === 'number' && !isNaN(value); | ||||||
|   return formulaArray; | } | ||||||
|  | 
 | ||||||
|  | function isSymbolFunc(value) { | ||||||
|  |   return ['+', '-', '*', '/', '?', ':', '(', ')', '%'].includes(value); | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user