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:
parent
dd6f0a6d84
commit
b9ba44f36e
@ -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',
|
||||||
|
@ -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',
|
||||||
|
@ -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': '中间表',
|
||||||
|
@ -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;
|
|
@ -1 +0,0 @@
|
|||||||
export * from './Compute';
|
|
@ -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
|
||||||
|
@ -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';
|
||||||
|
@ -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} />);
|
||||||
|
}),
|
||||||
|
);
|
@ -0,0 +1 @@
|
|||||||
|
export * from './Percent';
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user