diff --git a/packages/core/client/src/collection-manager/interfaces/formula.ts b/packages/core/client/src/collection-manager/interfaces/formula.ts index 6f99daac1..b9776e81d 100644 --- a/packages/core/client/src/collection-manager/interfaces/formula.ts +++ b/packages/core/client/src/collection-manager/interfaces/formula.ts @@ -28,9 +28,12 @@ export const formula: IField = { type: 'string', title: '{{t("Expression")}}', required: true, - description: '{{t("Input +、-、*、/、( ) to calculate, input @ to open field variables.")}}', + description: '{{t("Input +, -, *, /, ( ) to calculate, input @ to open field variables.")}}', 'x-component': 'Formula.Expression', 'x-decorator': 'FormItem', + 'x-component-props': { + 'supports': ['number', 'percent'] + } }, 'uiSchema.x-component-props.step': { type: 'string', diff --git a/packages/core/client/src/collection-manager/interfaces/percent.ts b/packages/core/client/src/collection-manager/interfaces/percent.ts index 7ecee77be..7ec198775 100644 --- a/packages/core/client/src/collection-manager/interfaces/percent.ts +++ b/packages/core/client/src/collection-manager/interfaces/percent.ts @@ -14,7 +14,7 @@ export const percent: IField = { uiSchema: { type: 'string', // title, - 'x-component': 'InputNumber', + 'x-component': 'Percent', 'x-component-props': { stringMode: true, step: '0', diff --git a/packages/core/client/src/locale/zh_CN.ts b/packages/core/client/src/locale/zh_CN.ts index fe09725a2..fe126b55d 100644 --- a/packages/core/client/src/locale/zh_CN.ts +++ b/packages/core/client/src/locale/zh_CN.ts @@ -348,7 +348,8 @@ export default { 'Precision': '精确度', 'Formula mode': '计算方式', 'Expression': '表达式', - 'Input +、-、*、/、( ) to calculate, input @ to open field variables.': '英文输入+、-、*、/、( ) 进行运算,输入@打开可用字段变量。', + 'Input +, -, *, /, ( ) to calculate, input @ to open field variables.': '英文输入+、-、*、/、( ) 进行运算,输入@打开可用字段变量。', + 'Formula error.': '公式验证错误。', 'Accept': '文件格式', 'Rich Text': '富文本', 'Junction collection': '中间表', diff --git a/packages/core/client/src/schema-component/antd/compute/Compute.tsx b/packages/core/client/src/schema-component/antd/compute/Compute.tsx deleted file mode 100644 index 8db6db9e1..000000000 --- a/packages/core/client/src/schema-component/antd/compute/Compute.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { onFormValuesChange } from '@formily/core'; -import { connect, mapReadPretty, useFieldSchema, useFormEffects } from '@formily/react'; -import { InputNumber } from 'antd'; -import React, { useState } from 'react'; -import * as math from 'mathjs'; -import _ from 'lodash'; -import { ReadPretty } from '../input-number/ReadPretty'; -import { useCollection, useCollectionField } from '../../../collection-manager/hooks'; - -const AntdCompute = (props) => { - const { value, onChange, step } = props; - // const { expression } = useCollectionField(); - const { getField } = useCollection(); - const fieldSchema = useFieldSchema(); - const options = getField(fieldSchema.name); - const { expression } = options; - const [computeValue, setComputeValue] = useState(value); - - useFormEffects(() => { - onFormValuesChange((form) => { - const scope = _.cloneDeep(form.values); - let result; - try { - result = math.evaluate(expression, scope); - result = math.round(result, 9); - } catch {} - if (result) { - setComputeValue(result); - if (onChange) { - onChange(result); - } - } - }) - }) - - return ( - - ); -} - -export const Compute = connect( - AntdCompute, - mapReadPretty(ReadPretty) -); - -export default Compute; \ No newline at end of file diff --git a/packages/core/client/src/schema-component/antd/compute/index.ts b/packages/core/client/src/schema-component/antd/compute/index.ts deleted file mode 100644 index b0482c837..000000000 --- a/packages/core/client/src/schema-component/antd/compute/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Compute'; \ No newline at end of file diff --git a/packages/core/client/src/schema-component/antd/formula-input/FormulaInput.tsx b/packages/core/client/src/schema-component/antd/formula-input/FormulaInput.tsx index 8e206aabf..4ae630a20 100644 --- a/packages/core/client/src/schema-component/antd/formula-input/FormulaInput.tsx +++ b/packages/core/client/src/schema-component/antd/formula-input/FormulaInput.tsx @@ -1,12 +1,20 @@ -import { connect, mapProps } from '@formily/react'; -import { Dropdown, Menu } from 'antd'; +import { CloseOutlined, LoadingOutlined } from '@ant-design/icons'; +import { useFormLayout } from '@formily/antd'; +import { Field, onFormSubmitValidateStart } from '@formily/core'; +import { connect, mapProps, mapReadPretty, useField, useFieldSchema, useFormEffects } from '@formily/react'; +import { isValid } from '@formily/shared'; +import { Button, Input, Popover, Tag, Menu, Dropdown } from 'antd'; import React, { useEffect, useRef, useState } from 'react'; import ContentEditable from 'react-contenteditable'; +import { useTranslation } from 'react-i18next'; +import * as math from 'mathjs'; import { useCollectionManager } from '../../../collection-manager/hooks'; import { useRecord } from '../../../record-provider'; const AntdFormulaInput = (props) => { - const { value, onChange } = props; + const { value, onChange, supports } = props; + const field = useField(); + const { t } = useTranslation(); const record = useRecord(); const { getCollectionFields } = useCollectionManager(); const fields = getCollectionFields(record.collectionName || record.name) as any[]; @@ -17,8 +25,10 @@ const AntdFormulaInput = (props) => { const [html, setHtml] = useState(null); const numColumns = new Map(); - fields.filter(field => field.interface === 'number').forEach(field => { + const scope = {}; + fields.filter(field => supports.includes(field.interface)).forEach(field => { numColumns.set(field.name, field.uiSchema.title); + scope[field.name] = 1; }) const keys = Array.from(numColumns.keys()); @@ -32,7 +42,7 @@ const AntdFormulaInput = (props) => { useEffect(() => { if (onChange && formula) { - let v = formula; + let v = formula || ''; numColumns.forEach((value, key) => { v = v.replaceAll(value, key); }) @@ -60,6 +70,9 @@ const AntdFormulaInput = (props) => { const current = inputRef.current as any; setFormula(e.currentTarget.textContent); setHtml(current.innerHTML); + if (e.currentTarget.textContent == '' && onChange) { + onChange(null); + } } const handleKeyDown = (e) => { @@ -78,6 +91,21 @@ const AntdFormulaInput = (props) => { } } + useFormEffects(() => { + onFormSubmitValidateStart(() => { + try { + math.evaluate(field.value, scope); + field.feedbacks = []; + } catch { + field.setFeedback({ + type: 'error', + code: 'FormulaError', + messages: [t('Formula error.')], + }); + } + }) + }) + return ( { + const { value, onChange } = props; + + return ( + { + if (onChange) { + onChange(v / 100); + } + }} + /> + ); + }, + mapReadPretty((props) => { + return (); + }), +); diff --git a/packages/core/client/src/schema-component/antd/percent/index.ts b/packages/core/client/src/schema-component/antd/percent/index.ts new file mode 100644 index 000000000..c3d9f84ac --- /dev/null +++ b/packages/core/client/src/schema-component/antd/percent/index.ts @@ -0,0 +1 @@ +export * from './Percent'; \ No newline at end of file diff --git a/packages/core/database/src/fields/formula-field.ts b/packages/core/database/src/fields/formula-field.ts index 87c26f226..a5bbff295 100644 --- a/packages/core/database/src/fields/formula-field.ts +++ b/packages/core/database/src/fields/formula-field.ts @@ -8,7 +8,7 @@ export class FormulaField extends Field { } caculate(expression, scope) { - let result; + let result = null; try { result = math.evaluate(expression, scope); result = math.round(result, 9); @@ -18,7 +18,6 @@ export class FormulaField extends Field { async initFieldData({ transaction }) { const { expression, name } = this.options; - console.log('initFieldData', expression, name); const records = await this.collection.repository.find({ order: [this.collection.model.primaryKeyAttribute], @@ -56,9 +55,37 @@ export class FormulaField extends Field { } } + async updateFieldData(instance, { transaction }) { + if (this.collection.name === instance.collectionName && instance.name === this.options.name) { + this.options = Object.assign(this.options, instance.options); + const { name, expression } = this.options; + + const records = await this.collection.repository.find({ + order: [this.collection.model.primaryKeyAttribute], + transaction, + }); + + for (const record of records) { + const scope = record.toJSON(); + const result = this.caculate(expression, scope); + await record.update( + { + [name]: result, + }, + { + transaction, + silent: true, + hooks: false, + }, + ); + } + } + } + bind() { super.bind(); this.on('afterSync', this.initFieldData.bind(this)); + this.database.on('fields.afterUpdate', this.updateFieldData.bind(this)); this.on('beforeCreate', this.caculateField.bind(this)); this.on('beforeUpdate', this.caculateField.bind(this)); } @@ -67,6 +94,7 @@ export class FormulaField extends Field { super.unbind(); this.off('beforeCreate', this.caculateField.bind(this)); this.off('beforeUpdate', this.caculateField.bind(this)); + this.database.off('fields.afterUpdate', this.updateFieldData.bind(this)); this.off('afterSync', this.initFieldData.bind(this)); } }