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:
bai.zixv 2024-05-29 20:45:58 +08:00 committed by sealday
parent 1273e55e9b
commit da87d7251b
5 changed files with 285 additions and 85 deletions

View File

@ -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]);
} }
} }

View File

@ -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,
},
};
}

View File

@ -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,
},
],
});
}
}

View File

@ -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';

View File

@ -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 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 engine = evaluators.get('math.js');
const evaluate = engine.evaluate.bind(engine); 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; 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);
} }