diff --git a/packages/plugins/formula-field/src/client/field.tsx b/packages/plugins/formula-field/src/client/field.tsx index 7a9618251..15526dbce 100644 --- a/packages/plugins/formula-field/src/client/field.tsx +++ b/packages/plugins/formula-field/src/client/field.tsx @@ -69,12 +69,12 @@ export default { uiSchema: { type: 'number', // title, - 'x-disabled': true, 'x-component': 'Formula.Result', 'x-component-props': { stringMode: true, step: '1', }, + 'x-read-pretty': true }, }, properties: { @@ -162,7 +162,6 @@ export default { 'select', 'multipleSelect', - 'snapshot' // 'json' ], useCurrentFields: '{{ useCurrentFields }}', diff --git a/packages/plugins/formula-field/src/client/formula/Result.tsx b/packages/plugins/formula-field/src/client/formula/Result.tsx index 177129418..4dadd4c09 100644 --- a/packages/plugins/formula-field/src/client/formula/Result.tsx +++ b/packages/plugins/formula-field/src/client/formula/Result.tsx @@ -1,11 +1,14 @@ +import React from 'react'; import { onFormValuesChange } from '@formily/core'; -import { useField, useFieldSchema, useForm, useFormEffects } from '@formily/react'; +import { useFieldSchema, useFormEffects } from '@formily/react'; +import cloneDeep from 'lodash/cloneDeep'; + import evaluators, { Evaluator } from '@nocobase/evaluators/client'; import { Registry, toFixedByStep } from '@nocobase/utils/client'; -import cloneDeep from 'lodash/cloneDeep'; -import React, { useState } from 'react'; import { Checkbox, DatePicker, Input as InputString, InputNumber, useCollection } from '@nocobase/client'; +import { toDbType } from '../../utils'; + const TypedComponents = { boolean: Checkbox, integer: InputNumber, @@ -16,45 +19,32 @@ const TypedComponents = { string: InputString, }; -const ReadPretty = (props) => { - const { dataType } = props?.options?? {}; - const Component = TypedComponents[dataType] ?? InputString; - return ; -}; - -const Input = (props) => { - const { options } = props; - const { dataType, expression, engine = 'math.js' } = options||{}; - const { evaluate } = (evaluators as Registry).get(engine); - const form = useForm(); - const val = () => { - const scope = cloneDeep(form.values); - try { - return evaluate(expression, scope); - } catch (error) { - return null; - } - }; - const [value, setVal] = useState(() => { - return val(); - }); - useFormEffects(() => { - onFormValuesChange(() => { - setVal(val()); - }); - }); - if (dataType === 'double') { - return
{toFixedByStep(value, props.step)}
; - } - return
{value}
; -}; - -export const Result = (props: any) => { - const field = useField(); +export const Result = (props) => { + const { value, ...others } = props; const { getField } = useCollection(); const fieldSchema = useFieldSchema(); const options = getField(fieldSchema.name as string); - return field.readPretty ? : ; + const { dataType, expression, engine = 'math.js' } = options || {}; + const { evaluate } = (evaluators as Registry).get(engine); + useFormEffects(() => { + onFormValuesChange((form) => { + const scope = cloneDeep(form.values); + let v; + try { + v = evaluate(expression, scope); + v = toDbType(v, dataType); + } catch (error) { + v = null; + } + if ((v == null && value == null) || JSON.stringify(v) === JSON.stringify(value)) { + return; + } + // console.log(options.name, v, value, props.defaultValue); + form.setValuesIn(options.name, v); + }); + }); + const Component = TypedComponents[dataType] ?? InputString; + return ; }; export default Result; diff --git a/packages/plugins/formula-field/src/client/formula/index.tsx b/packages/plugins/formula-field/src/client/formula/index.tsx index 9a59d66df..ddecd1b3d 100644 --- a/packages/plugins/formula-field/src/client/formula/index.tsx +++ b/packages/plugins/formula-field/src/client/formula/index.tsx @@ -1,9 +1,11 @@ +import { connect, mapReadPretty } from '@formily/react'; + import Expression from './Expression'; import Result from './Result'; export const Formula = () => null; -Formula.Result = Result; Formula.Expression = Expression; +Formula.Result = connect(Result); export default Formula; diff --git a/packages/plugins/formula-field/src/server/formula-field.ts b/packages/plugins/formula-field/src/server/formula-field.ts index 62c982316..519ccc618 100644 --- a/packages/plugins/formula-field/src/server/formula-field.ts +++ b/packages/plugins/formula-field/src/server/formula-field.ts @@ -3,6 +3,8 @@ import { DataTypes } from 'sequelize'; import { BaseFieldOptions, Field } from '@nocobase/database'; import evaluators from '@nocobase/evaluators'; +import { DataTypeTransformers, toDbType } from '../utils'; + export interface FormulaFieldOptions extends BaseFieldOptions { type: 'formula', engine: string; @@ -10,199 +12,21 @@ export interface FormulaFieldOptions extends BaseFieldOptions { } const DataTypeMap = { - boolean: { - type: DataTypes.BOOLEAN, - transformers: Boolean - }, - integer: { - type: DataTypes.INTEGER, - transformers: { - boolean(value: boolean) { - return Number(value); - }, - number(value: number) { - return value >= 0 ? Math.floor(value) : Math.ceil(value); - }, - bigint(value: bigint) { - return Number(value); - }, - string(value: string) { - const result = Number.parseInt(value, 10); - if (Number.isNaN(result) || !Number.isFinite(result)) { - return null; - } - return result; - }, - date(value: Date) { - const result = value.valueOf(); - if (Number.isNaN(result)) { - return null; - } - return result; - } - } - }, - bigInt: { - type: DataTypes.BIGINT, - transformers: { - boolean(value: boolean) { - return Number(value); - }, - number(value: number) { - return Math.floor(value >= 0 ? Math.floor(value) : Math.ceil(value)); - }, - bigint(value: bigint) { - return Number(value); - }, - string(value: string) { - const result = Number.parseInt(value, 10); - if (Number.isNaN(result) || !Number.isFinite(result)) { - return null; - } - return result; - }, - date(value: Date) { - const result = value.valueOf(); - if (Number.isNaN(result)) { - return null; - } - return result; - } - } - }, - double: { - type: DataTypes.DOUBLE, - transformers: { - boolean(value: boolean) { - return Number(value); - }, - number(value: number) { - return value; - }, - bigint(value: bigint) { - return Number(value); - }, - string(value: string) { - const result = Number.parseFloat(value); - if (Number.isNaN(result) || !Number.isFinite(result)) { - return null; - } - return result; - }, - date(value: Date) { - const result = value.valueOf(); - if (Number.isNaN(result)) { - return null; - } - return result; - } - } - }, - decimal: { - type: DataTypes.DECIMAL, - transformers: { - boolean(value: boolean) { - return Number(value); - }, - number(value: number) { - return value; - }, - bigint(value: bigint) { - return value; - }, - date(value: Date) { - const result = value.valueOf(); - if (Number.isNaN(result)) { - return null; - } - return result; - } - } - }, - string: { - type: DataTypes.STRING, - transformers: { - boolean(value: boolean) { - return value.toString(); - }, - number(value: number) { - return value.toString(); - }, - bigint(value: bigint) { - return value.toString(); - }, - string(value: string) { - return value; - }, - date(value: Date) { - return value.toISOString(); - } - } - }, - date: { - type: DataTypes.DATE(3), - transformers: { - boolean(value: boolean) { - return null; - }, - number(value: number) { - const result = new Date(value); - if (Number.isNaN(result.valueOf())) { - return null; - } - return result; - }, - bigint(value: bigint) { - const result = new Date(Number(value)); - if (Number.isNaN(result.valueOf())) { - return null; - } - return result; - }, - string(value: string) { - const ts = Date.parse(value); - if (Number.isNaN(ts)) { - return null; - } - return new Date(ts); - }, - date(value: Date) { - return new Date(value); - } - } - }, + boolean: DataTypes.BOOLEAN, + integer: DataTypes.INTEGER, + bigInt: DataTypes.BIGINT, + double: DataTypes.DOUBLE, + decimal: DataTypes.DECIMAL, + string: DataTypes.STRING, + date: DataTypes.DATE(3), } -function toDbType(value, type) { - if (value == null) { - return null; - } - - let jsType: string = typeof value; - if (jsType == 'object' && value instanceof Date) { - jsType = 'date'; - } - - if (!DataTypeMap[type]) { - return null; - } - - const { transformers } = DataTypeMap[type]; - - if (typeof transformers === 'function') { - return transformers(value); - } - - const transformer = transformers[jsType]; - return transformer ? transformer(value) : null; -} - export class FormulaField extends Field { get dataType() { const { dataType } = this.options; - return DataTypeMap[dataType]?.type ?? DataTypes.DOUBLE; + return DataTypeMap[dataType] ?? DataTypes.DOUBLE; } calculate(scope) { diff --git a/packages/plugins/formula-field/src/utils/index.ts b/packages/plugins/formula-field/src/utils/index.ts new file mode 100644 index 000000000..aa4beb167 --- /dev/null +++ b/packages/plugins/formula-field/src/utils/index.ts @@ -0,0 +1,168 @@ +export const DataTypeTransformers = { + boolean: Boolean, + integer: { + boolean(value: boolean) { + return Number(value); + }, + number(value: number) { + return value >= 0 ? Math.floor(value) : Math.ceil(value); + }, + bigint(value: bigint) { + return Number(value); + }, + string(value: string) { + const result = Number.parseInt(value, 10); + if (Number.isNaN(result) || !Number.isFinite(result)) { + return null; + } + return result; + }, + date(value: Date) { + const result = value.valueOf(); + if (Number.isNaN(result)) { + return null; + } + return result; + } + }, + bigInt: { + boolean(value: boolean) { + return Number(value); + }, + number(value: number) { + return Math.floor(value >= 0 ? Math.floor(value) : Math.ceil(value)); + }, + bigint(value: bigint) { + return Number(value); + }, + string(value: string) { + const result = Number.parseInt(value, 10); + if (Number.isNaN(result) || !Number.isFinite(result)) { + return null; + } + return result; + }, + date(value: Date) { + const result = value.valueOf(); + if (Number.isNaN(result)) { + return null; + } + return result; + } + }, + double: { + boolean(value: boolean) { + return Number(value); + }, + number(value: number) { + return value; + }, + bigint(value: bigint) { + return Number(value); + }, + string(value: string) { + const result = Number.parseFloat(value); + if (Number.isNaN(result) || !Number.isFinite(result)) { + return null; + } + return result; + }, + date(value: Date) { + const result = value.valueOf(); + if (Number.isNaN(result)) { + return null; + } + return result; + } + }, + decimal: { + boolean(value: boolean) { + return Number(value); + }, + number(value: number) { + return value; + }, + bigint(value: bigint) { + return value; + }, + date(value: Date) { + const result = value.valueOf(); + if (Number.isNaN(result)) { + return null; + } + return result; + } + }, + string: { + boolean(value: boolean) { + return value.toString(); + }, + number(value: number) { + return value.toString(); + }, + bigint(value: bigint) { + return value.toString(); + }, + string(value: string) { + return value; + }, + date(value: Date) { + return value.toISOString(); + } + }, + date: { + boolean(value: boolean) { + return null; + }, + number(value: number) { + const result = new Date(value); + if (Number.isNaN(result.valueOf())) { + return null; + } + return result; + }, + bigint(value: bigint) { + const result = new Date(Number(value)); + if (Number.isNaN(result.valueOf())) { + return null; + } + return result; + }, + string(value: string) { + const ts = Date.parse(value); + if (Number.isNaN(ts)) { + return null; + } + return new Date(ts); + }, + date(value: Date) { + return new Date(value); + } + } +}; + + + +export function toDbType(value: any, type: string) { + if (value == null) { + return null; + } + + let jsType: string = typeof value; + if (jsType == 'object' && value instanceof Date) { + jsType = 'date'; + } + + const transformers = DataTypeTransformers[type]; + + if (!transformers) { + return null; + } + + if (typeof transformers === 'function') { + return transformers(value); + } + + const transformer = transformers[jsType]; + return transformer ? transformer(value) : null; +}