fix: update formula field and percent field (#461)

* feat: update formula field and percent field

* fix: remove console

* fix: percent component & formula-field
This commit is contained in:
金昶 2022-06-03 22:14:34 +08:00 committed by GitHub
parent dd6f0a6d84
commit b9ba44f36e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 98 additions and 58 deletions

View File

@ -28,9 +28,12 @@ export const formula: IField = {
type: 'string', type: 'string',
title: '{{t("Expression")}}', title: '{{t("Expression")}}',
required: true, 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-component': 'Formula.Expression',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component-props': {
'supports': ['number', 'percent']
}
}, },
'uiSchema.x-component-props.step': { 'uiSchema.x-component-props.step': {
type: 'string', type: 'string',

View File

@ -14,7 +14,7 @@ export const percent: IField = {
uiSchema: { uiSchema: {
type: 'string', type: 'string',
// title, // title,
'x-component': 'InputNumber', 'x-component': 'Percent',
'x-component-props': { 'x-component-props': {
stringMode: true, stringMode: true,
step: '0', step: '0',

View File

@ -348,7 +348,8 @@ export default {
'Precision': '精确度', 'Precision': '精确度',
'Formula mode': '计算方式', 'Formula mode': '计算方式',
'Expression': '表达式', 'Expression': '表达式',
'Input +、-、*、/、( ) to calculate, input @ to open field variables.': '英文输入+、-、*、/、( ) 进行运算,输入@打开可用字段变量。', 'Input +, -, *, /, ( ) to calculate, input @ to open field variables.': '英文输入+、-、*、/、( ) 进行运算,输入@打开可用字段变量。',
'Formula error.': '公式验证错误。',
'Accept': '文件格式', 'Accept': '文件格式',
'Rich Text': '富文本', 'Rich Text': '富文本',
'Junction collection': '中间表', 'Junction collection': '中间表',

View File

@ -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 (
<InputNumber readOnly value={computeValue} stringMode={true} step={step} />
);
}
export const Compute = connect(
AntdCompute,
mapReadPretty(ReadPretty)
);
export default Compute;

View File

@ -1 +0,0 @@
export * from './Compute';

View File

@ -1,12 +1,20 @@
import { connect, mapProps } from '@formily/react'; import { CloseOutlined, LoadingOutlined } from '@ant-design/icons';
import { Dropdown, Menu } from 'antd'; 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 React, { useEffect, useRef, useState } from 'react';
import ContentEditable from 'react-contenteditable'; import ContentEditable from 'react-contenteditable';
import { useTranslation } from 'react-i18next';
import * as math from 'mathjs';
import { useCollectionManager } from '../../../collection-manager/hooks'; import { useCollectionManager } from '../../../collection-manager/hooks';
import { useRecord } from '../../../record-provider'; import { useRecord } from '../../../record-provider';
const AntdFormulaInput = (props) => { const AntdFormulaInput = (props) => {
const { value, onChange } = props; const { value, onChange, supports } = props;
const field = useField<Field>();
const { t } = useTranslation();
const record = useRecord(); const record = useRecord();
const { getCollectionFields } = useCollectionManager(); const { getCollectionFields } = useCollectionManager();
const fields = getCollectionFields(record.collectionName || record.name) as any[]; const fields = getCollectionFields(record.collectionName || record.name) as any[];
@ -17,8 +25,10 @@ const AntdFormulaInput = (props) => {
const [html, setHtml] = useState(null); const [html, setHtml] = useState(null);
const numColumns = new Map<string, string>(); const numColumns = new Map<string, string>();
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); numColumns.set(field.name, field.uiSchema.title);
scope[field.name] = 1;
}) })
const keys = Array.from(numColumns.keys()); const keys = Array.from(numColumns.keys());
@ -32,7 +42,7 @@ const AntdFormulaInput = (props) => {
useEffect(() => { useEffect(() => {
if (onChange && formula) { if (onChange && formula) {
let v = formula; let v = formula || '';
numColumns.forEach((value, key) => { numColumns.forEach((value, key) => {
v = v.replaceAll(value, key); v = v.replaceAll(value, key);
}) })
@ -60,6 +70,9 @@ const AntdFormulaInput = (props) => {
const current = inputRef.current as any; const current = inputRef.current as any;
setFormula(e.currentTarget.textContent); setFormula(e.currentTarget.textContent);
setHtml(current.innerHTML); setHtml(current.innerHTML);
if (e.currentTarget.textContent == '' && onChange) {
onChange(null);
}
} }
const handleKeyDown = (e) => { 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 ( return (
<Dropdown overlay={menu} visible={dropdownVisible}> <Dropdown overlay={menu} visible={dropdownVisible}>
<ContentEditable <ContentEditable

View File

@ -33,5 +33,5 @@ export * from './time-picker';
export * from './tree-select'; export * from './tree-select';
export * from './upload'; export * from './upload';
export * from './formula-input'; export * from './formula-input';
export * from './compute'; export * from './percent';
import './index.less'; import './index.less';

View File

@ -0,0 +1,26 @@
import { connect, mapReadPretty } from '@formily/react';
import { InputNumber } from 'antd';
import React from 'react';
import { ReadPretty } from '../input-number/ReadPretty';
export const Percent = connect(
(props) => {
const { value, onChange } = props;
return (
<InputNumber
{...props}
addonAfter="%"
value={value * 100}
onChange={(v: any) => {
if (onChange) {
onChange(v / 100);
}
}}
/>
);
},
mapReadPretty((props) => {
return (<ReadPretty {...props} value={props.value ? props.value * 100 : null} />);
}),
);

View File

@ -0,0 +1 @@
export * from './Percent';

View File

@ -8,7 +8,7 @@ export class FormulaField extends Field {
} }
caculate(expression, scope) { caculate(expression, scope) {
let result; let result = null;
try { try {
result = math.evaluate(expression, scope); result = math.evaluate(expression, scope);
result = math.round(result, 9); result = math.round(result, 9);
@ -18,7 +18,6 @@ export class FormulaField extends Field {
async initFieldData({ transaction }) { async initFieldData({ transaction }) {
const { expression, name } = this.options; const { expression, name } = this.options;
console.log('initFieldData', expression, name);
const records = await this.collection.repository.find({ const records = await this.collection.repository.find({
order: [this.collection.model.primaryKeyAttribute], 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() { bind() {
super.bind(); super.bind();
this.on('afterSync', this.initFieldData.bind(this)); 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('beforeCreate', this.caculateField.bind(this));
this.on('beforeUpdate', this.caculateField.bind(this)); this.on('beforeUpdate', this.caculateField.bind(this));
} }
@ -67,6 +94,7 @@ export class FormulaField extends Field {
super.unbind(); super.unbind();
this.off('beforeCreate', this.caculateField.bind(this)); this.off('beforeCreate', this.caculateField.bind(this));
this.off('beforeUpdate', 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)); this.off('afterSync', this.initFieldData.bind(this));
} }
} }