Revert "refactor: formula plugin (#1082)"
This reverts commit 0469b8c44d
.
This commit is contained in:
parent
0469b8c44d
commit
0cbfa0a521
@ -1 +0,0 @@
|
|||||||
export { default } from '@nocobase/plugin-excel-formula-field/client';
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from '@nocobase/plugin-math-formula-field/client';
|
|
@ -1,5 +1,5 @@
|
|||||||
import { Spin } from 'antd';
|
import { Spin } from 'antd';
|
||||||
import React, { useContext, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useAPIClient, useRequest } from '../api-client';
|
import { useAPIClient, useRequest } from '../api-client';
|
||||||
import { CollectionManagerSchemaComponentProvider } from './CollectionManagerSchemaComponentProvider';
|
import { CollectionManagerSchemaComponentProvider } from './CollectionManagerSchemaComponentProvider';
|
||||||
import { CollectionManagerContext } from './context';
|
import { CollectionManagerContext } from './context';
|
||||||
@ -8,13 +8,11 @@ import { CollectionManagerOptions } from './types';
|
|||||||
|
|
||||||
export const CollectionManagerProvider: React.FC<CollectionManagerOptions> = (props) => {
|
export const CollectionManagerProvider: React.FC<CollectionManagerOptions> = (props) => {
|
||||||
const { service, interfaces, collections = [], refreshCM } = props;
|
const { service, interfaces, collections = [], refreshCM } = props;
|
||||||
const ctx = useContext(CollectionManagerContext);
|
|
||||||
return (
|
return (
|
||||||
<CollectionManagerContext.Provider
|
<CollectionManagerContext.Provider
|
||||||
value={{
|
value={{
|
||||||
...ctx,
|
|
||||||
service,
|
service,
|
||||||
interfaces: { ...defaultInterfaces, ...interfaces, ...ctx.interfaces },
|
interfaces: { ...defaultInterfaces, ...interfaces },
|
||||||
collections,
|
collections,
|
||||||
refreshCM,
|
refreshCM,
|
||||||
}}
|
}}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { ArrayTable } from '@formily/antd';
|
import { ArrayTable } from '@formily/antd';
|
||||||
import { useForm } from '@formily/react';
|
import { ISchema, useForm } from '@formily/react';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { Button, Dropdown, Menu } from 'antd';
|
import { Button, Dropdown, Menu } from 'antd';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
@ -14,9 +14,9 @@ import { useCollectionManager } from '../hooks';
|
|||||||
import { IField } from '../interfaces/types';
|
import { IField } from '../interfaces/types';
|
||||||
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
|
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
|
||||||
import * as components from './components';
|
import * as components from './components';
|
||||||
import { getOptions } from './interfaces';
|
import { options } from './interfaces';
|
||||||
|
|
||||||
const getSchema = (schema: IField, record: any, compile) => {
|
const getSchema = (schema: IField, record: any, compile): ISchema => {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -24,7 +24,7 @@ const getSchema = (schema: IField, record: any, compile) => {
|
|||||||
const properties = cloneDeep(schema.properties) as any;
|
const properties = cloneDeep(schema.properties) as any;
|
||||||
|
|
||||||
if (schema.hasDefaultValue === true) {
|
if (schema.hasDefaultValue === true) {
|
||||||
properties['defaultValue'] = cloneDeep(schema?.default?.uiSchema);
|
properties['defaultValue'] = cloneDeep(schema.default.uiSchema);
|
||||||
properties['defaultValue']['title'] = compile('{{ t("Default value") }}');
|
properties['defaultValue']['title'] = compile('{{ t("Default value") }}');
|
||||||
properties['defaultValue']['x-decorator'] = 'FormItem';
|
properties['defaultValue']['x-decorator'] = 'FormItem';
|
||||||
}
|
}
|
||||||
@ -108,9 +108,9 @@ export const useCollectionFieldFormValues = () => {
|
|||||||
}
|
}
|
||||||
delete values.autoCreateReverseField;
|
delete values.autoCreateReverseField;
|
||||||
return values;
|
return values;
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const useCreateCollectionField = () => {
|
const useCreateCollectionField = () => {
|
||||||
const form = useForm();
|
const form = useForm();
|
||||||
@ -164,13 +164,11 @@ export const AddFieldAction = (props) => {
|
|||||||
}}
|
}}
|
||||||
onClick={(info) => {
|
onClick={(info) => {
|
||||||
const schema = getSchema(getInterface(info.key), record, compile);
|
const schema = getSchema(getInterface(info.key), record, compile);
|
||||||
if (schema) {
|
|
||||||
setSchema(schema);
|
setSchema(schema);
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{getOptions().map((option) => {
|
{options.map((option) => {
|
||||||
return (
|
return (
|
||||||
option.children.length > 0 && (
|
option.children.length > 0 && (
|
||||||
<Menu.ItemGroup key={option.label} title={compile(option.label)}>
|
<Menu.ItemGroup key={option.label} title={compile(option.label)}>
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
import { observer } from '@formily/react';
|
|
||||||
import { Tag } from 'antd';
|
|
||||||
import React from 'react';
|
|
||||||
import { useCompile } from '../../../schema-component';
|
|
||||||
import { useCollectionManager } from '../../hooks';
|
|
||||||
|
|
||||||
export const CollectionFieldInterface = observer((props: any) => {
|
|
||||||
const { value } = props;
|
|
||||||
const { getInterface } = useCollectionManager();
|
|
||||||
const compile = useCompile();
|
|
||||||
const schema = getInterface(value);
|
|
||||||
|
|
||||||
if (!schema) return null;
|
|
||||||
|
|
||||||
return <Tag>{compile(schema.title)}</Tag>;
|
|
||||||
});
|
|
@ -31,8 +31,7 @@ registerGroupLabel('advanced', '{{t("Advanced type")}}');
|
|||||||
registerGroupLabel('systemInfo', '{{t("System info")}}');
|
registerGroupLabel('systemInfo', '{{t("System info")}}');
|
||||||
registerGroupLabel('others', '{{t("Others")}}');
|
registerGroupLabel('others', '{{t("Others")}}');
|
||||||
|
|
||||||
export const getOptions = () => {
|
export const options = Object.keys(groupLabels).map((groupName) => {
|
||||||
return Object.keys(groupLabels).map((groupName) => {
|
|
||||||
return {
|
return {
|
||||||
label: groupLabels[groupName],
|
label: groupLabels[groupName],
|
||||||
children: Object.keys(fields[groupName] || {})
|
children: Object.keys(fields[groupName] || {})
|
||||||
@ -48,6 +47,3 @@ export const getOptions = () => {
|
|||||||
.sort((a, b) => a.order - b.order),
|
.sort((a, b) => a.order - b.order),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
export const options = getOptions();
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { ISchema } from '@formily/react';
|
import { ISchema } from '@formily/react';
|
||||||
import { CollectionOptions } from '../../types';
|
import { CollectionOptions } from '../../types';
|
||||||
import { CollectionFieldInterface } from '../components/CollectionFieldInterface';
|
|
||||||
import { options } from '../interfaces';
|
import { options } from '../interfaces';
|
||||||
|
|
||||||
const collection: CollectionOptions = {
|
const collection: CollectionOptions = {
|
||||||
@ -156,10 +155,9 @@ export const collectionFieldSchema: ISchema = {
|
|||||||
type: 'void',
|
type: 'void',
|
||||||
'x-decorator': 'Table.Column.Decorator',
|
'x-decorator': 'Table.Column.Decorator',
|
||||||
'x-component': 'Table.Column',
|
'x-component': 'Table.Column',
|
||||||
title: '{{t("Field interface")}}',
|
|
||||||
properties: {
|
properties: {
|
||||||
interface: {
|
interface: {
|
||||||
'x-component': CollectionFieldInterface,
|
'x-component': 'CollectionField',
|
||||||
'x-read-pretty': true,
|
'x-read-pretty': true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -106,7 +106,7 @@ export const useCollectionManager = () => {
|
|||||||
if (!fieldNames?.length) {
|
if (!fieldNames?.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let cName: any = collectionName;
|
let cName = collectionName;
|
||||||
let collectionField;
|
let collectionField;
|
||||||
while (cName && fieldNames.length > 0) {
|
while (cName && fieldNames.length > 0) {
|
||||||
const fileName = fieldNames.shift();
|
const fileName = fieldNames.shift();
|
||||||
|
@ -5,12 +5,10 @@ export * from './CollectionManagerProvider';
|
|||||||
export * from './CollectionManagerSchemaComponentProvider';
|
export * from './CollectionManagerSchemaComponentProvider';
|
||||||
export * from './CollectionManagerShortcut';
|
export * from './CollectionManagerShortcut';
|
||||||
export * from './CollectionProvider';
|
export * from './CollectionProvider';
|
||||||
export * from './Configuration';
|
|
||||||
export { registerField, registerGroupLabel } from './Configuration/interfaces';
|
|
||||||
export * from './context';
|
export * from './context';
|
||||||
export * from './hooks';
|
export * from './hooks';
|
||||||
export * as interfacesProperties from './interfaces/properties';
|
|
||||||
export * from './interfaces/types';
|
|
||||||
export * from './ResourceActionProvider';
|
export * from './ResourceActionProvider';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
export * from './Configuration';
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
import { IField, interfacesProperties } from '@nocobase/client';
|
import { defaultProps, operators } from './properties';
|
||||||
const { defaultProps, operators } = interfacesProperties;
|
import { IField } from './types';
|
||||||
|
|
||||||
export const mathFormula: IField = {
|
export const formula: IField = {
|
||||||
name: 'mathFormula',
|
name: 'formula',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
group: 'advanced',
|
group: 'advanced',
|
||||||
order: 1,
|
order: 1,
|
||||||
title: '{{t("Math formula")}}',
|
title: '{{t("Formula")}}',
|
||||||
description: '{{t("Compute a value based on the other fields using mathjs")}}',
|
description: '{{t("Formula description")}}',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
default: {
|
default: {
|
||||||
type: 'mathFormula',
|
type: 'formula',
|
||||||
// name,
|
// name,
|
||||||
uiSchema: {
|
uiSchema: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
// title,
|
// title,
|
||||||
'x-disabled': true,
|
"x-disabled": true,
|
||||||
'x-component': 'MathFormula.Result',
|
'x-component': 'Formula.Result',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
stringMode: true,
|
stringMode: true,
|
||||||
step: '1',
|
step: '1',
|
||||||
@ -25,16 +25,16 @@ export const mathFormula: IField = {
|
|||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
expression: {
|
'expression': {
|
||||||
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': 'MathFormula.Expression',
|
'x-component': 'Formula.Expression',
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
supports: ['number', 'percent', 'integer'],
|
'supports': ['number', 'percent', 'integer'],
|
||||||
useCurrentFields: '{{ useCurrentFields }}',
|
'useCurrentFields': '{{ useCurrentFields }}'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'uiSchema.x-component-props.step': {
|
'uiSchema.x-component-props.step': {
|
@ -6,6 +6,8 @@ export * from './createdAt';
|
|||||||
export * from './createdBy';
|
export * from './createdBy';
|
||||||
export * from './datetime';
|
export * from './datetime';
|
||||||
export * from './email';
|
export * from './email';
|
||||||
|
export * from './formula';
|
||||||
|
export * from './sequence';
|
||||||
export * from './icon';
|
export * from './icon';
|
||||||
export * from './id';
|
export * from './id';
|
||||||
export * from './input';
|
export * from './input';
|
||||||
@ -25,10 +27,8 @@ export * from './phone';
|
|||||||
export * from './radioGroup';
|
export * from './radioGroup';
|
||||||
export * from './richText';
|
export * from './richText';
|
||||||
export * from './select';
|
export * from './select';
|
||||||
export * from './sequence';
|
|
||||||
export * from './subTable';
|
export * from './subTable';
|
||||||
export * from './textarea';
|
export * from './textarea';
|
||||||
export * from './time';
|
export * from './time';
|
||||||
export * from './updatedAt';
|
export * from './updatedAt';
|
||||||
export * from './updatedBy';
|
export * from './updatedBy';
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { registerValidateFormats } from '@formily/core';
|
|
||||||
import { i18n } from '../../i18n';
|
|
||||||
import { defaultProps, operators, unique } from './properties';
|
import { defaultProps, operators, unique } from './properties';
|
||||||
import { IField } from './types';
|
import { IField } from './types';
|
||||||
|
import { i18n } from '../../i18n';
|
||||||
|
import { registerValidateFormats } from '@formily/core';
|
||||||
|
import { ISchema } from '@formily/react';
|
||||||
|
|
||||||
registerValidateFormats({
|
registerValidateFormats({
|
||||||
odd: /^-?\d*[13579]$/,
|
odd: /^-?\d*[13579]$/,
|
||||||
@ -16,7 +17,7 @@ export const integer: IField = {
|
|||||||
title: '{{t("Integer")}}',
|
title: '{{t("Integer")}}',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
default: {
|
default: {
|
||||||
type: 'bigInt',
|
type: 'integer',
|
||||||
// name,
|
// name,
|
||||||
uiSchema: {
|
uiSchema: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { registerValidateRules } from '@formily/core';
|
import { defaultProps, operators, unique } from './properties';
|
||||||
import { defaultProps } from './properties';
|
|
||||||
import { IField } from './types';
|
import { IField } from './types';
|
||||||
|
import { registerValidateRules } from '@formily/core';
|
||||||
|
|
||||||
registerValidateRules({
|
registerValidateRules({
|
||||||
json(value) {
|
json(value) {
|
||||||
@ -20,7 +20,7 @@ export const json: IField = {
|
|||||||
name: 'json',
|
name: 'json',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
group: 'advanced',
|
group: 'advanced',
|
||||||
order: 4,
|
order: 3,
|
||||||
title: '{{t("JSON")}}',
|
title: '{{t("JSON")}}',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
default: {
|
default: {
|
||||||
|
@ -15,7 +15,6 @@ export const type: ISchema = {
|
|||||||
{ label: 'String', value: 'string' },
|
{ label: 'String', value: 'string' },
|
||||||
{ label: 'Text', value: 'text' },
|
{ label: 'Text', value: 'text' },
|
||||||
{ label: 'Integer', value: 'integer' },
|
{ label: 'Integer', value: 'integer' },
|
||||||
{ label: 'BigInteger', value: 'bigInt' },
|
|
||||||
{ label: 'Float', value: 'float' },
|
{ label: 'Float', value: 'float' },
|
||||||
{ label: 'Double', value: 'double' },
|
{ label: 'Double', value: 'double' },
|
||||||
{ label: 'Decimal', value: 'decimal' },
|
{ label: 'Decimal', value: 'decimal' },
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { css } from '@emotion/css';
|
import React, { useContext } from 'react';
|
||||||
import { ArrayTable, FormButtonGroup, FormDrawer, FormLayout, Submit } from '@formily/antd';
|
import { Button, Select } from 'antd';
|
||||||
import { onFieldValueChange } from '@formily/core';
|
import { onFieldValueChange } from '@formily/core';
|
||||||
import { SchemaOptionsContext, useForm, useFormEffects } from '@formily/react';
|
import { SchemaOptionsContext, useForm, useFormEffects } from '@formily/react';
|
||||||
import { Button, Select } from 'antd';
|
import { ArrayTable, FormButtonGroup, FormDrawer, FormLayout, Submit } from '@formily/antd';
|
||||||
import React, { useContext } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
|
||||||
import { Cron, SchemaComponent, SchemaComponentOptions, useCompile } from '../../schema-component';
|
import { Cron, SchemaComponent, SchemaComponentOptions, useCompile } from '../../schema-component';
|
||||||
import { defaultProps, operators, unique } from './properties';
|
|
||||||
import { IField } from './types';
|
import { IField } from './types';
|
||||||
|
import { defaultProps, operators, unique } from './properties';
|
||||||
|
|
||||||
function RuleTypeSelect(props) {
|
function RuleTypeSelect(props) {
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
@ -252,7 +252,7 @@ export const sequence: IField = {
|
|||||||
name: 'sequence',
|
name: 'sequence',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
group: 'advanced',
|
group: 'advanced',
|
||||||
order: 3,
|
order: 2,
|
||||||
title: '{{t("Sequence")}}',
|
title: '{{t("Sequence")}}',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
default: {
|
default: {
|
||||||
|
@ -1,224 +0,0 @@
|
|||||||
import { css } from '@emotion/css';
|
|
||||||
import { Field, onFormSubmitValidateStart } from '@formily/core';
|
|
||||||
import { useField, useFormEffects } from '@formily/react';
|
|
||||||
import { Dropdown, Menu } from 'antd';
|
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
function pasteHtml(html, selectPastedContent = false) {
|
|
||||||
var sel, range;
|
|
||||||
if (window.getSelection) {
|
|
||||||
// IE9 and non-IE
|
|
||||||
sel = window.getSelection();
|
|
||||||
if (sel.getRangeAt && sel.rangeCount) {
|
|
||||||
range = sel.getRangeAt(0);
|
|
||||||
range.deleteContents();
|
|
||||||
|
|
||||||
// Range.createContextualFragment() would be useful here but is
|
|
||||||
// only relatively recently standardized and is not supported in
|
|
||||||
// some browsers (IE9, for one)
|
|
||||||
var el = document.createElement('div');
|
|
||||||
el.innerHTML = html;
|
|
||||||
var frag = document.createDocumentFragment(),
|
|
||||||
node,
|
|
||||||
lastNode;
|
|
||||||
while ((node = el.firstChild)) {
|
|
||||||
lastNode = frag.appendChild(node);
|
|
||||||
}
|
|
||||||
var firstNode = frag.firstChild;
|
|
||||||
range.insertNode(frag);
|
|
||||||
|
|
||||||
// Preserve the selection
|
|
||||||
if (lastNode) {
|
|
||||||
range = range.cloneRange();
|
|
||||||
range.setStartAfter(lastNode);
|
|
||||||
if (selectPastedContent) {
|
|
||||||
range.setStartBefore(firstNode);
|
|
||||||
} else {
|
|
||||||
range.collapse(true);
|
|
||||||
}
|
|
||||||
sel.removeAllRanges();
|
|
||||||
sel.addRange(range);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getValue = (el) => {
|
|
||||||
const values: any[] = [];
|
|
||||||
for (const node of el.childNodes) {
|
|
||||||
if (node.nodeName === 'SPAN') {
|
|
||||||
values.push(`{{${node['dataset']['key']}}}`);
|
|
||||||
} else {
|
|
||||||
values.push(node.textContent?.trim?.());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const text = values.join(' ')?.replace(/\s+/g, ' ').trim();
|
|
||||||
return text;
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderExp = (exp: string, scope = {}) => {
|
|
||||||
return exp.replace(/{{([^}]+)}}/g, (_, i) => {
|
|
||||||
return scope[i.trim()] || '';
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Expression = (props) => {
|
|
||||||
const { evaluate, value, supports, useCurrentFields } = props;
|
|
||||||
const field = useField<Field>();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const fields = useCurrentFields();
|
|
||||||
const inputRef = useRef<any>();
|
|
||||||
const [changed, setChanged] = useState(false);
|
|
||||||
|
|
||||||
const onChange = (value) => {
|
|
||||||
setChanged(true);
|
|
||||||
props.onChange(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const numColumns = new Map<string, string>();
|
|
||||||
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());
|
|
||||||
const [html, setHtml] = useState(() => {
|
|
||||||
const scope = {};
|
|
||||||
for (const key of keys) {
|
|
||||||
const val = numColumns.get(key);
|
|
||||||
scope[
|
|
||||||
key
|
|
||||||
] = ` <span class="ant-tag" style="margin: 0 3px;" contentEditable="false" data-key="${key}">${val}</span> `;
|
|
||||||
}
|
|
||||||
return renderExp(value || '', scope);
|
|
||||||
});
|
|
||||||
useEffect(() => {
|
|
||||||
if (changed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const scope = {};
|
|
||||||
for (const key of keys) {
|
|
||||||
const val = numColumns.get(key);
|
|
||||||
scope[
|
|
||||||
key
|
|
||||||
] = ` <span class="ant-tag" style="margin: 0 3px;" contentEditable="false" data-key="${key}">${val}</span> `;
|
|
||||||
}
|
|
||||||
const val = renderExp(value || '', scope);
|
|
||||||
setHtml(val);
|
|
||||||
}, [value]);
|
|
||||||
const menu = (
|
|
||||||
<Menu>
|
|
||||||
{keys.length > 0 ? (
|
|
||||||
keys.map((key) => (
|
|
||||||
<Menu.Item disabled key={key}>
|
|
||||||
<button
|
|
||||||
onClick={async (args) => {
|
|
||||||
(inputRef.current as any).focus();
|
|
||||||
const val = numColumns.get(key);
|
|
||||||
pasteHtml(
|
|
||||||
` <span class="ant-tag" style="margin: 0 3px;" contentEditable="false" data-key="${key}">${val}</span> `,
|
|
||||||
);
|
|
||||||
const text = getValue(inputRef.current);
|
|
||||||
onChange(text);
|
|
||||||
console.log('onChange', text);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{numColumns.get(key)}
|
|
||||||
</button>
|
|
||||||
</Menu.Item>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<Menu.Item disabled key={0}>
|
|
||||||
{t('No available fields')}
|
|
||||||
</Menu.Item>
|
|
||||||
)}
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
|
|
||||||
useFormEffects(() => {
|
|
||||||
onFormSubmitValidateStart(() => {
|
|
||||||
try {
|
|
||||||
evaluate(field.value, scope);
|
|
||||||
field.feedbacks = [];
|
|
||||||
} catch (e) {
|
|
||||||
console.error(field.value, scope, (e as Error).message);
|
|
||||||
field.setFeedback({
|
|
||||||
type: 'error',
|
|
||||||
code: 'FormulaError',
|
|
||||||
messages: [t('Formula error.')],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('value, html', value, html);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dropdown
|
|
||||||
overlay={menu}
|
|
||||||
overlayClassName={css`
|
|
||||||
.ant-dropdown-menu-item {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 5px 12px;
|
|
||||||
text-align: left;
|
|
||||||
color: rgba(0, 0, 0, 0.85);
|
|
||||||
width: 100%;
|
|
||||||
line-height: inherit;
|
|
||||||
height: auto;
|
|
||||||
border: 0px;
|
|
||||||
background-color: transparent;
|
|
||||||
&:hover {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
const text = getValue(e.currentTarget);
|
|
||||||
if (e.key === 'Backspace') {
|
|
||||||
if (text && keys.map((k) => `{{${k}}}`).includes(text)) {
|
|
||||||
inputRef.current.innerHTML = ' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onKeyUp={(e) => {
|
|
||||||
const text = getValue(e.currentTarget);
|
|
||||||
if (e.key === 'Backspace') {
|
|
||||||
// pasteHtml(' ');
|
|
||||||
}
|
|
||||||
console.log('onChange', text);
|
|
||||||
onChange(text);
|
|
||||||
}}
|
|
||||||
onClick={(e) => {
|
|
||||||
const text = getValue(e.currentTarget);
|
|
||||||
onChange(text);
|
|
||||||
console.log('onChange', text);
|
|
||||||
}}
|
|
||||||
onBlur={(e) => {
|
|
||||||
const text = getValue(e.currentTarget);
|
|
||||||
console.log('onChange', text);
|
|
||||||
onChange(text);
|
|
||||||
}}
|
|
||||||
onInput={(e) => {
|
|
||||||
const text = getValue(e.currentTarget);
|
|
||||||
console.log('onChange', text);
|
|
||||||
onChange(text);
|
|
||||||
}}
|
|
||||||
className={'ant-input'}
|
|
||||||
style={{ display: 'block' }}
|
|
||||||
ref={inputRef as any}
|
|
||||||
contentEditable
|
|
||||||
dangerouslySetInnerHTML={{ __html: html }}
|
|
||||||
/>
|
|
||||||
</Dropdown>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Expression;
|
|
@ -1,53 +0,0 @@
|
|||||||
import { onFormValuesChange } from '@formily/core';
|
|
||||||
import { useField, useFieldSchema, useForm, useFormEffects } from '@formily/react';
|
|
||||||
import { toFixedByStep } from '@nocobase/utils/client';
|
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
|
||||||
import * as math from 'mathjs';
|
|
||||||
import { isNumber } from 'mathjs';
|
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { useCollection } from '../collection-manager';
|
|
||||||
|
|
||||||
const ReadPretty = (props) => {
|
|
||||||
if (props?.options?.dataType !== 'string') {
|
|
||||||
return <div>{toFixedByStep(props.value, props.step)}</div>;
|
|
||||||
}
|
|
||||||
return <div>{props.value}</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Input = (props) => {
|
|
||||||
const { evaluate, options } = props;
|
|
||||||
const { dataType, expression } = options;
|
|
||||||
const form = useForm();
|
|
||||||
const val = () => {
|
|
||||||
const scope = cloneDeep(form.values);
|
|
||||||
try {
|
|
||||||
let result = evaluate(expression, scope);
|
|
||||||
result = isNumber(result) && Number.isFinite(result) ? math.round(result, 9) : result;
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const [value, setVal] = useState(() => {
|
|
||||||
return val();
|
|
||||||
});
|
|
||||||
useFormEffects(() => {
|
|
||||||
onFormValuesChange(() => {
|
|
||||||
setVal(val());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (dataType !== 'string') {
|
|
||||||
return <div>{toFixedByStep(value, props.step)}</div>;
|
|
||||||
}
|
|
||||||
return <div>{value}</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Result = (props: any) => {
|
|
||||||
const field = useField();
|
|
||||||
const { getField } = useCollection();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
const options = getField(fieldSchema.name as string);
|
|
||||||
return field.readPretty ? <ReadPretty {...props} options={options} /> : <Input {...props} options={options} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Result;
|
|
@ -1,51 +0,0 @@
|
|||||||
/**
|
|
||||||
* title: Formula
|
|
||||||
*/
|
|
||||||
import { connect } from '@formily/react';
|
|
||||||
import { Formula, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
const Expression = connect(Formula.Expression);
|
|
||||||
|
|
||||||
const schema = {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
input: {
|
|
||||||
type: 'string',
|
|
||||||
default: '{{f1 }} + {{ f2}}',
|
|
||||||
'x-component': 'Expression',
|
|
||||||
'x-component-props': {
|
|
||||||
evaluate: (exp, scope) => {
|
|
||||||
return 1;
|
|
||||||
},
|
|
||||||
supports: ['number'],
|
|
||||||
useCurrentFields: () => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
name: 'f1',
|
|
||||||
interface: 'number',
|
|
||||||
uiSchema: {
|
|
||||||
title: 'F1',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'f2',
|
|
||||||
interface: 'number',
|
|
||||||
uiSchema: {
|
|
||||||
title: 'F2',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
return (
|
|
||||||
<SchemaComponentProvider components={{ Expression }}>
|
|
||||||
<SchemaComponent schema={schema} />
|
|
||||||
</SchemaComponentProvider>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
nav:
|
|
||||||
path: /client
|
|
||||||
group:
|
|
||||||
path: /client
|
|
||||||
---
|
|
||||||
|
|
||||||
# Formula
|
|
||||||
|
|
||||||
<code src="./demos/demo2.tsx"/>
|
|
@ -1,9 +0,0 @@
|
|||||||
import Expression from './Expression';
|
|
||||||
import Result from './Result';
|
|
||||||
|
|
||||||
export const Formula = () => null;
|
|
||||||
|
|
||||||
Formula.Result = Result;
|
|
||||||
Formula.Expression = Expression;
|
|
||||||
|
|
||||||
export default Formula;
|
|
@ -11,7 +11,6 @@ export * from './board';
|
|||||||
export * from './china-region';
|
export * from './china-region';
|
||||||
export * from './collection-manager';
|
export * from './collection-manager';
|
||||||
export * from './document-title';
|
export * from './document-title';
|
||||||
export * from './formula';
|
|
||||||
export * from './i18n';
|
export * from './i18n';
|
||||||
export * from './icon';
|
export * from './icon';
|
||||||
export * from './plugin-manager';
|
export * from './plugin-manager';
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
import { onFormValuesChange } from '@formily/core';
|
||||||
|
import { connect, mapReadPretty, useFieldSchema, useFormEffects } from '@formily/react';
|
||||||
|
import { InputNumber } from 'antd';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import * as math from 'mathjs';
|
||||||
|
import React from 'react';
|
||||||
|
import { useCollection } from '../../../collection-manager/hooks';
|
||||||
|
import { ReadPretty } from '../input-number/ReadPretty';
|
||||||
|
|
||||||
|
const AntdCompute = (props) => {
|
||||||
|
const { onChange, ...others } = props;
|
||||||
|
const { getField } = useCollection();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const options = getField(fieldSchema.name);
|
||||||
|
const { expression } = options;
|
||||||
|
|
||||||
|
useFormEffects(() => {
|
||||||
|
onFormValuesChange((form) => {
|
||||||
|
const scope = _.cloneDeep(form.values);
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = math.evaluate(expression, scope);
|
||||||
|
result = Number.isFinite(result) ? math.round(result, 9) : null;
|
||||||
|
} catch{}
|
||||||
|
if (onChange) {
|
||||||
|
onChange(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return <InputNumber {...others} readOnly stringMode={true} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Compute = connect(AntdCompute, mapReadPretty(ReadPretty));
|
||||||
|
|
||||||
|
export default Compute;
|
@ -0,0 +1,121 @@
|
|||||||
|
import { Field, onFormSubmitValidateStart } from '@formily/core';
|
||||||
|
import { connect, mapProps, useField, useFormEffects } from '@formily/react';
|
||||||
|
import { 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';
|
||||||
|
|
||||||
|
const AntdFormulaInput = (props) => {
|
||||||
|
const { value, onChange, supports, useCurrentFields } = props;
|
||||||
|
const field = useField<Field>();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const fields = useCurrentFields();
|
||||||
|
|
||||||
|
const inputRef = useRef();
|
||||||
|
const [dropdownVisible, setDropdownVisible] = useState(false);
|
||||||
|
const [html, setHtml] = useState(null);
|
||||||
|
|
||||||
|
const numColumns = new Map<string, string>();
|
||||||
|
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());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (value) {
|
||||||
|
let newHtml = value;
|
||||||
|
numColumns.forEach((value, key) => {
|
||||||
|
newHtml = newHtml.replaceAll(
|
||||||
|
key,
|
||||||
|
`<span contentEditable="false" ><input disabled="disabled" style="width:${
|
||||||
|
18 * value.length
|
||||||
|
}px;max-width: 120px" value="${value}"/><span hidden>${key}</span></span>`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
newHtml = `${newHtml}<span style="padding-left: 5px"></span>`; // set extra span for cursor focus on last position
|
||||||
|
setHtml(newHtml);
|
||||||
|
} else {
|
||||||
|
setHtml('');
|
||||||
|
}
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
const menu = (
|
||||||
|
<Menu
|
||||||
|
onClick={async (args) => {
|
||||||
|
const replaceFormula = field.value.replace('@', args.key);
|
||||||
|
if (onChange && replaceFormula != field.value) {
|
||||||
|
onChange(replaceFormula);
|
||||||
|
}
|
||||||
|
setDropdownVisible(false);
|
||||||
|
(inputRef.current as any).focus();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{keys.map((key) => (
|
||||||
|
<Menu.Item key={key}>{numColumns.get(key)}</Menu.Item>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
if (onChange) {
|
||||||
|
if (e.currentTarget.textContent == '') {
|
||||||
|
onChange(null);
|
||||||
|
} else {
|
||||||
|
onChange(e.currentTarget.textContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (e) => {
|
||||||
|
const { key } = e;
|
||||||
|
switch (key) {
|
||||||
|
case 'Enter':
|
||||||
|
e.preventDefault();
|
||||||
|
break;
|
||||||
|
case '@':
|
||||||
|
case 'Process':
|
||||||
|
setDropdownVisible(true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setDropdownVisible(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useFormEffects(() => {
|
||||||
|
onFormSubmitValidateStart(() => {
|
||||||
|
try {
|
||||||
|
math.evaluate(field.value, scope);
|
||||||
|
field.feedbacks = [];
|
||||||
|
} catch (e) {
|
||||||
|
console.error(field.value, scope, (e as Error).message);
|
||||||
|
field.setFeedback({
|
||||||
|
type: 'error',
|
||||||
|
code: 'FormulaError',
|
||||||
|
messages: [t('Formula error.')],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown overlay={menu} visible={dropdownVisible}>
|
||||||
|
<ContentEditable
|
||||||
|
innerRef={inputRef}
|
||||||
|
className="ant-input"
|
||||||
|
onChange={handleChange}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
html={html || ''}
|
||||||
|
/>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormulaInput = connect(AntdFormulaInput, mapProps({}));
|
||||||
|
|
||||||
|
export default FormulaInput;
|
@ -0,0 +1,6 @@
|
|||||||
|
import { Compute } from './Compute';
|
||||||
|
import { FormulaInput } from './FormulaInput';
|
||||||
|
|
||||||
|
export const Formula: any = () => null;
|
||||||
|
Formula.Expression = FormulaInput;
|
||||||
|
Formula.Result = Compute;
|
@ -4,13 +4,14 @@ export * from './calendar';
|
|||||||
export * from './card-item';
|
export * from './card-item';
|
||||||
export * from './cascader';
|
export * from './cascader';
|
||||||
export * from './checkbox';
|
export * from './checkbox';
|
||||||
export * from './color-select';
|
|
||||||
export * from './cron';
|
export * from './cron';
|
||||||
|
export * from './color-select';
|
||||||
export * from './date-picker';
|
export * from './date-picker';
|
||||||
export * from './filter';
|
export * from './filter';
|
||||||
export * from './form';
|
export * from './form';
|
||||||
export * from './form-item';
|
export * from './form-item';
|
||||||
export * from './form-v2';
|
export * from './form-v2';
|
||||||
|
export * from './formula-input';
|
||||||
export * from './g2plot';
|
export * from './g2plot';
|
||||||
export * from './grid';
|
export * from './grid';
|
||||||
export * from './icon-picker';
|
export * from './icon-picker';
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
import { mockDatabase } from '..';
|
||||||
|
import { Database } from '../../database';
|
||||||
|
import { FormulaField } from '../../fields';
|
||||||
|
|
||||||
|
describe('formula field', () => {
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
db = mockDatabase();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('add formula field with old table, already has data.', async () => {
|
||||||
|
const Test = db.collection({
|
||||||
|
name: 'tests',
|
||||||
|
fields: [
|
||||||
|
{ type: 'float', name: 'price' },
|
||||||
|
{ type: 'float', name: 'count' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.sync();
|
||||||
|
|
||||||
|
const test = await Test.model.create<any>({
|
||||||
|
price: '1.2',
|
||||||
|
count: '2',
|
||||||
|
});
|
||||||
|
|
||||||
|
const expression = 'price*count';
|
||||||
|
const field = Test.addField('sum', { type: 'formula', expression });
|
||||||
|
|
||||||
|
await field.sync({});
|
||||||
|
|
||||||
|
const updatedTest = await Test.model.findByPk(test.id);
|
||||||
|
const sum = updatedTest.get('sum');
|
||||||
|
|
||||||
|
const sumField = Test.getField<FormulaField>('sum');
|
||||||
|
expect(sum).toEqual(sumField.caculate(expression, updatedTest.toJSON()));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('auto set formula field with create or update data', async () => {
|
||||||
|
const expression = 'price*count';
|
||||||
|
const Test = db.collection({
|
||||||
|
name: 'tests',
|
||||||
|
fields: [
|
||||||
|
{ type: 'float', name: 'price' },
|
||||||
|
{ type: 'float', name: 'count' },
|
||||||
|
{ name: 'sum', type: 'formula', expression },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.sync();
|
||||||
|
|
||||||
|
const test = await Test.model.create<any>({
|
||||||
|
price: '1.2',
|
||||||
|
count: '2',
|
||||||
|
});
|
||||||
|
|
||||||
|
const sumField = Test.getField<FormulaField>('sum');
|
||||||
|
expect(test.get('sum')).toEqual(sumField.caculate(expression, test.toJSON()));
|
||||||
|
|
||||||
|
test.set('count', '6');
|
||||||
|
await test.save();
|
||||||
|
expect(test.get('sum')).toEqual(sumField.caculate(expression, test.toJSON()));
|
||||||
|
});
|
||||||
|
});
|
@ -5,12 +5,12 @@ import {
|
|||||||
ModelIndexesOptions,
|
ModelIndexesOptions,
|
||||||
QueryInterfaceOptions,
|
QueryInterfaceOptions,
|
||||||
SyncOptions,
|
SyncOptions,
|
||||||
Transactionable
|
Transactionable,
|
||||||
} from 'sequelize';
|
} from 'sequelize';
|
||||||
import { Collection } from '../collection';
|
import { Collection } from '../collection';
|
||||||
import { Database } from '../database';
|
import { Database } from '../database';
|
||||||
import { InheritedCollection } from '../inherited-collection';
|
|
||||||
import { ModelEventTypes } from '../types';
|
import { ModelEventTypes } from '../types';
|
||||||
|
import { InheritedCollection } from '../inherited-collection';
|
||||||
|
|
||||||
export interface FieldContext {
|
export interface FieldContext {
|
||||||
database: Database;
|
database: Database;
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
import { BaseFieldOptions, Field } from '@nocobase/database';
|
|
||||||
import * as math from 'mathjs';
|
|
||||||
import { DataTypes } from 'sequelize';
|
import { DataTypes } from 'sequelize';
|
||||||
import { evaluate } from '../utils/evaluate';
|
import { BaseColumnFieldOptions, Field } from './field';
|
||||||
|
import * as math from 'mathjs';
|
||||||
|
|
||||||
export class MathFormulaField extends Field {
|
export class FormulaField extends Field {
|
||||||
get dataType() {
|
get dataType() {
|
||||||
return DataTypes.DOUBLE;
|
return DataTypes.FLOAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
calculate(expression, scope) {
|
caculate(expression, scope) {
|
||||||
let result: any = null;
|
let result = null;
|
||||||
try {
|
try {
|
||||||
result = evaluate(expression, scope);
|
result = math.evaluate(expression, scope);
|
||||||
result = math.round(result, 9);
|
result = math.round(result, 9);
|
||||||
} catch {}
|
} catch {}
|
||||||
return result;
|
return result;
|
||||||
@ -27,7 +26,7 @@ export class MathFormulaField extends Field {
|
|||||||
|
|
||||||
for (const record of records) {
|
for (const record of records) {
|
||||||
const scope = record.toJSON();
|
const scope = record.toJSON();
|
||||||
const result = this.calculate(expression, scope);
|
const result = this.caculate(expression, scope);
|
||||||
if (result) {
|
if (result) {
|
||||||
await record.update(
|
await record.update(
|
||||||
{
|
{
|
||||||
@ -43,12 +42,12 @@ export class MathFormulaField extends Field {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
calculateField = async (instance) => {
|
caculateField = async (instance) => {
|
||||||
const { expression, name } = this.options;
|
const { expression, name } = this.options;
|
||||||
const scope = instance.toJSON();
|
const scope = instance.toJSON();
|
||||||
let result;
|
let result;
|
||||||
try {
|
try {
|
||||||
result = evaluate(expression, scope);
|
result = math.evaluate(expression, scope);
|
||||||
result = math.round(result, 9);
|
result = math.round(result, 9);
|
||||||
} catch {}
|
} catch {}
|
||||||
if (result === 0 || result) {
|
if (result === 0 || result) {
|
||||||
@ -68,7 +67,7 @@ export class MathFormulaField extends Field {
|
|||||||
|
|
||||||
for (const record of records) {
|
for (const record of records) {
|
||||||
const scope = record.toJSON();
|
const scope = record.toJSON();
|
||||||
const result = this.calculate(expression, scope);
|
const result = this.caculate(expression, scope);
|
||||||
await record.update(
|
await record.update(
|
||||||
{
|
{
|
||||||
[name]: result,
|
[name]: result,
|
||||||
@ -88,19 +87,20 @@ export class MathFormulaField extends Field {
|
|||||||
this.on('afterSync', this.initFieldData);
|
this.on('afterSync', this.initFieldData);
|
||||||
// TODO: should not depends on fields table (which is defined by other plugin)
|
// TODO: should not depends on fields table (which is defined by other plugin)
|
||||||
this.database.on('fields.afterUpdate', this.updateFieldData);
|
this.database.on('fields.afterUpdate', this.updateFieldData);
|
||||||
this.on('beforeSave', this.calculateField);
|
this.on('beforeSave', this.caculateField);
|
||||||
}
|
}
|
||||||
|
|
||||||
unbind() {
|
unbind() {
|
||||||
super.unbind();
|
super.unbind();
|
||||||
this.off('beforeSave', this.calculateField);
|
this.off('beforeSave', this.caculateField);
|
||||||
// TODO: should not depends on fields table
|
// TODO: should not depends on fields table
|
||||||
this.database.off('fields.afterUpdate', this.updateFieldData);
|
this.database.off('fields.afterUpdate', this.updateFieldData);
|
||||||
this.off('afterSync', this.initFieldData);
|
this.off('afterSync', this.initFieldData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MathFormulaFieldOptions extends BaseFieldOptions {
|
export interface FormulaFieldOptions extends BaseColumnFieldOptions {
|
||||||
type: 'mathFormula';
|
type: 'formula';
|
||||||
|
|
||||||
expression: string;
|
expression: string;
|
||||||
}
|
}
|
@ -17,7 +17,6 @@ import {
|
|||||||
} from './number-field';
|
} from './number-field';
|
||||||
import { PasswordFieldOptions } from './password-field';
|
import { PasswordFieldOptions } from './password-field';
|
||||||
import { RadioFieldOptions } from './radio-field';
|
import { RadioFieldOptions } from './radio-field';
|
||||||
import { SequenceFieldOptions } from './sequence-field';
|
|
||||||
import { SortFieldOptions } from './sort-field';
|
import { SortFieldOptions } from './sort-field';
|
||||||
import { StringFieldOptions } from './string-field';
|
import { StringFieldOptions } from './string-field';
|
||||||
import { TextFieldOptions } from './text-field';
|
import { TextFieldOptions } from './text-field';
|
||||||
@ -25,6 +24,8 @@ import { TimeFieldOptions } from './time-field';
|
|||||||
import { UidFieldOptions } from './uid-field';
|
import { UidFieldOptions } from './uid-field';
|
||||||
import { UUIDFieldOptions } from './uuid-field';
|
import { UUIDFieldOptions } from './uuid-field';
|
||||||
import { VirtualFieldOptions } from './virtual-field';
|
import { VirtualFieldOptions } from './virtual-field';
|
||||||
|
import { FormulaFieldOptions } from './formula-field';
|
||||||
|
import { SequenceFieldOptions } from './sequence-field';
|
||||||
|
|
||||||
export * from './array-field';
|
export * from './array-field';
|
||||||
export * from './belongs-to-field';
|
export * from './belongs-to-field';
|
||||||
@ -40,7 +41,6 @@ export * from './number-field';
|
|||||||
export * from './password-field';
|
export * from './password-field';
|
||||||
export * from './radio-field';
|
export * from './radio-field';
|
||||||
export * from './relation-field';
|
export * from './relation-field';
|
||||||
export { SequenceField } from './sequence-field';
|
|
||||||
export * from './sort-field';
|
export * from './sort-field';
|
||||||
export * from './string-field';
|
export * from './string-field';
|
||||||
export * from './text-field';
|
export * from './text-field';
|
||||||
@ -48,6 +48,8 @@ export * from './time-field';
|
|||||||
export * from './uid-field';
|
export * from './uid-field';
|
||||||
export * from './uuid-field';
|
export * from './uuid-field';
|
||||||
export * from './virtual-field';
|
export * from './virtual-field';
|
||||||
|
export * from './formula-field';
|
||||||
|
export { SequenceField } from './sequence-field';
|
||||||
|
|
||||||
export type FieldOptions =
|
export type FieldOptions =
|
||||||
| BaseFieldOptions
|
| BaseFieldOptions
|
||||||
@ -64,6 +66,7 @@ export type FieldOptions =
|
|||||||
| SortFieldOptions
|
| SortFieldOptions
|
||||||
| TextFieldOptions
|
| TextFieldOptions
|
||||||
| VirtualFieldOptions
|
| VirtualFieldOptions
|
||||||
|
| FormulaFieldOptions
|
||||||
| ArrayFieldOptions
|
| ArrayFieldOptions
|
||||||
| TimeFieldOptions
|
| TimeFieldOptions
|
||||||
| DateFieldOptions
|
| DateFieldOptions
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
export * from './lib/client';
|
|
||||||
export { default } from './lib/client';
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
||||||
|
|
||||||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
||||||
|
|
||||||
var _index = _interopRequireWildcard(require("./lib/client"));
|
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
|
||||||
value: true
|
|
||||||
});
|
|
||||||
var _exportNames = {};
|
|
||||||
Object.defineProperty(exports, "default", {
|
|
||||||
enumerable: true,
|
|
||||||
get: function get() {
|
|
||||||
return _index.default;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(_index).forEach(function (key) {
|
|
||||||
if (key === "default" || key === "__esModule") return;
|
|
||||||
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
||||||
if (key in exports && exports[key] === _index[key]) return;
|
|
||||||
Object.defineProperty(exports, key, {
|
|
||||||
enumerable: true,
|
|
||||||
get: function get() {
|
|
||||||
return _index[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@nocobase/plugin-excel-formula-field",
|
|
||||||
"version": "0.8.0-alpha.13",
|
|
||||||
"main": "lib/server/index.js",
|
|
||||||
"dependencies": {
|
|
||||||
"@formulajs/formulajs": "^3.1.7"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@nocobase/server": "0.8.0-alpha.13",
|
|
||||||
"@nocobase/test": "0.8.0-alpha.13"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
export * from './lib/server';
|
|
||||||
export { default } from './lib/server';
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
||||||
|
|
||||||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
||||||
|
|
||||||
var _index = _interopRequireWildcard(require("./lib/server"));
|
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
|
||||||
value: true
|
|
||||||
});
|
|
||||||
var _exportNames = {};
|
|
||||||
Object.defineProperty(exports, "default", {
|
|
||||||
enumerable: true,
|
|
||||||
get: function get() {
|
|
||||||
return _index.default;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(_index).forEach(function (key) {
|
|
||||||
if (key === "default" || key === "__esModule") return;
|
|
||||||
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
||||||
if (key in exports && exports[key] === _index[key]) return;
|
|
||||||
Object.defineProperty(exports, key, {
|
|
||||||
enumerable: true,
|
|
||||||
get: function get() {
|
|
||||||
return _index[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,20 +0,0 @@
|
|||||||
import { connect, observer } from '@formily/react';
|
|
||||||
import { Formula } from '@nocobase/client';
|
|
||||||
import React from 'react';
|
|
||||||
import { evaluate } from '../../utils/evaluate';
|
|
||||||
|
|
||||||
export const ExcelFormula: any = () => null;
|
|
||||||
|
|
||||||
ExcelFormula.Result = observer((props) =>
|
|
||||||
React.createElement(Formula.Result, {
|
|
||||||
...props,
|
|
||||||
evaluate,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
ExcelFormula.Expression = connect((props) => {
|
|
||||||
return React.createElement(Formula.Expression, {
|
|
||||||
...props,
|
|
||||||
evaluate,
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,56 +0,0 @@
|
|||||||
import { IField, interfacesProperties } from '@nocobase/client';
|
|
||||||
const { defaultProps, operators } = interfacesProperties;
|
|
||||||
|
|
||||||
export const excelFormula: IField = {
|
|
||||||
name: 'excelFormula',
|
|
||||||
type: 'object',
|
|
||||||
group: 'advanced',
|
|
||||||
order: 2,
|
|
||||||
title: '{{t("Excel formula")}}',
|
|
||||||
description: '{{t("Compute a value based on the other fields using excel formula functions")}}',
|
|
||||||
sortable: true,
|
|
||||||
default: {
|
|
||||||
type: 'excelFormula',
|
|
||||||
// name,
|
|
||||||
uiSchema: {
|
|
||||||
type: 'string',
|
|
||||||
// title,
|
|
||||||
'x-disabled': true,
|
|
||||||
'x-component': 'ExcelFormula.Result',
|
|
||||||
'x-component-props': {
|
|
||||||
stringMode: true,
|
|
||||||
step: '1',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
...defaultProps,
|
|
||||||
dataType: {
|
|
||||||
type: 'string',
|
|
||||||
title: '{{t("Data type")}}',
|
|
||||||
'x-component': 'Select',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
default: 'number',
|
|
||||||
'x-disabled': '{{ !createOnly }}',
|
|
||||||
enum: [
|
|
||||||
{ value: 'string', label: '{{t("String")}}' },
|
|
||||||
{ value: 'number', label: '{{t("Number")}}' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
expression: {
|
|
||||||
type: 'string',
|
|
||||||
title: '{{t("Expression")}}',
|
|
||||||
required: true,
|
|
||||||
description: '{{t("Input @ to open field variables.")}}',
|
|
||||||
'x-component': 'ExcelFormula.Expression',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component-props': {
|
|
||||||
supports: ['number', 'percent', 'integer', 'string'],
|
|
||||||
useCurrentFields: '{{ useCurrentFields }}',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
filterable: {
|
|
||||||
operators: operators.string,
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,17 +0,0 @@
|
|||||||
import { CollectionManagerContext, registerField, SchemaComponentOptions } from '@nocobase/client';
|
|
||||||
import React, { useContext } from 'react';
|
|
||||||
import { excelFormula } from './excel-formula';
|
|
||||||
import { ExcelFormula } from './ExcelFormula';
|
|
||||||
|
|
||||||
registerField(excelFormula.group, 'excelFormula', excelFormula);
|
|
||||||
|
|
||||||
export default React.memo((props) => {
|
|
||||||
const ctx = useContext(CollectionManagerContext);
|
|
||||||
return (
|
|
||||||
<SchemaComponentOptions components={{ ExcelFormula }}>
|
|
||||||
<CollectionManagerContext.Provider value={{ ...ctx, interfaces: { ...ctx.interfaces, excelFormula } }}>
|
|
||||||
{props.children}
|
|
||||||
</CollectionManagerContext.Provider>
|
|
||||||
</SchemaComponentOptions>
|
|
||||||
);
|
|
||||||
});
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './server';
|
|
@ -1,108 +0,0 @@
|
|||||||
import { BaseFieldOptions, Field } from '@nocobase/database';
|
|
||||||
import { DataTypes } from 'sequelize';
|
|
||||||
import { evaluate } from '../utils/evaluate';
|
|
||||||
|
|
||||||
export class ExcelFormulaField extends Field {
|
|
||||||
get dataType() {
|
|
||||||
const { dataType } = this.options;
|
|
||||||
return dataType === 'string' ? DataTypes.STRING : DataTypes.DOUBLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
calculate(expression, scope) {
|
|
||||||
try {
|
|
||||||
return evaluate(expression, scope);
|
|
||||||
} catch {}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
initFieldData = async ({ transaction }) => {
|
|
||||||
const { expression, name } = 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.calculate(expression, scope);
|
|
||||||
if (result) {
|
|
||||||
await record.update(
|
|
||||||
{
|
|
||||||
[name]: result,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
transaction,
|
|
||||||
silent: true,
|
|
||||||
hooks: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
calculateField = async (instance) => {
|
|
||||||
const { expression, name } = this.options;
|
|
||||||
const scope = instance.toJSON();
|
|
||||||
let result = this.calculate(expression, scope);
|
|
||||||
if (result) {
|
|
||||||
instance.set(name, result);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
updateFieldData = async (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.calculate(expression, scope);
|
|
||||||
await record.update(
|
|
||||||
{
|
|
||||||
[name]: result,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
transaction,
|
|
||||||
silent: true,
|
|
||||||
hooks: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
bind() {
|
|
||||||
super.bind();
|
|
||||||
this.on('afterSync', this.initFieldData);
|
|
||||||
// TODO: should not depends on fields table (which is defined by other plugin)
|
|
||||||
this.database.on('fields.afterUpdate', this.updateFieldData);
|
|
||||||
this.on('beforeSave', this.calculateField);
|
|
||||||
}
|
|
||||||
|
|
||||||
unbind() {
|
|
||||||
super.unbind();
|
|
||||||
this.off('beforeSave', this.calculateField);
|
|
||||||
// TODO: should not depends on fields table
|
|
||||||
this.database.off('fields.afterUpdate', this.updateFieldData);
|
|
||||||
this.off('afterSync', this.initFieldData);
|
|
||||||
}
|
|
||||||
|
|
||||||
toSequelize() {
|
|
||||||
const opts = super.toSequelize();
|
|
||||||
delete opts.dataType;
|
|
||||||
return opts;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ExcelFormulaFieldOptions extends BaseFieldOptions {
|
|
||||||
type: 'excelFormula';
|
|
||||||
dataType?: 'number' | 'string';
|
|
||||||
expression: string;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './plugin';
|
|
@ -1,24 +0,0 @@
|
|||||||
import { InstallOptions, Plugin } from '@nocobase/server';
|
|
||||||
import { ExcelFormulaField } from './excel-formula-field';
|
|
||||||
|
|
||||||
export class ExcelFormulaFieldPlugin extends Plugin {
|
|
||||||
afterAdd() {}
|
|
||||||
|
|
||||||
beforeLoad() {
|
|
||||||
this.db.registerFieldTypes({
|
|
||||||
excelFormula: ExcelFormulaField,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async load() {}
|
|
||||||
|
|
||||||
async install(options?: InstallOptions) {}
|
|
||||||
|
|
||||||
async afterEnable() {}
|
|
||||||
|
|
||||||
async afterDisable() {}
|
|
||||||
|
|
||||||
async remove() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ExcelFormulaFieldPlugin;
|
|
@ -1,9 +0,0 @@
|
|||||||
import * as formulajs from '@formulajs/formulajs';
|
|
||||||
|
|
||||||
export function evaluate(exp: string, scope = {}) {
|
|
||||||
const expression = exp.replace(/{{([^}]+)}}/g, (match, i) => {
|
|
||||||
return scope[i.trim()] || '';
|
|
||||||
});
|
|
||||||
const fn = new Function(...Object.keys(formulajs), ...Object.keys(scope), `return ${expression}`);
|
|
||||||
return fn(...Object.values(formulajs), ...Object.values(scope));
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
export * from './lib/client';
|
|
||||||
export { default } from './lib/client';
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
||||||
|
|
||||||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
||||||
|
|
||||||
var _index = _interopRequireWildcard(require("./lib/client"));
|
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
|
||||||
value: true
|
|
||||||
});
|
|
||||||
var _exportNames = {};
|
|
||||||
Object.defineProperty(exports, "default", {
|
|
||||||
enumerable: true,
|
|
||||||
get: function get() {
|
|
||||||
return _index.default;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(_index).forEach(function (key) {
|
|
||||||
if (key === "default" || key === "__esModule") return;
|
|
||||||
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
||||||
if (key in exports && exports[key] === _index[key]) return;
|
|
||||||
Object.defineProperty(exports, key, {
|
|
||||||
enumerable: true,
|
|
||||||
get: function get() {
|
|
||||||
return _index[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@nocobase/plugin-math-formula-field",
|
|
||||||
"version": "0.8.0-alpha.13",
|
|
||||||
"main": "lib/server/index.js",
|
|
||||||
"dependencies": {
|
|
||||||
"mathjs": "^10.6.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@nocobase/server": "0.8.0-alpha.13",
|
|
||||||
"@nocobase/test": "0.8.0-alpha.13"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
export * from './lib/server';
|
|
||||||
export { default } from './lib/server';
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
||||||
|
|
||||||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
||||||
|
|
||||||
var _index = _interopRequireWildcard(require("./lib/server"));
|
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", {
|
|
||||||
value: true
|
|
||||||
});
|
|
||||||
var _exportNames = {};
|
|
||||||
Object.defineProperty(exports, "default", {
|
|
||||||
enumerable: true,
|
|
||||||
get: function get() {
|
|
||||||
return _index.default;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(_index).forEach(function (key) {
|
|
||||||
if (key === "default" || key === "__esModule") return;
|
|
||||||
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
||||||
if (key in exports && exports[key] === _index[key]) return;
|
|
||||||
Object.defineProperty(exports, key, {
|
|
||||||
enumerable: true,
|
|
||||||
get: function get() {
|
|
||||||
return _index[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,20 +0,0 @@
|
|||||||
import { connect, observer } from '@formily/react';
|
|
||||||
import { Formula } from '@nocobase/client';
|
|
||||||
import React from 'react';
|
|
||||||
import { evaluate } from '../../utils/evaluate';
|
|
||||||
|
|
||||||
export const MathFormula: any = () => null;
|
|
||||||
|
|
||||||
MathFormula.Result = observer((props) =>
|
|
||||||
React.createElement(Formula.Result, {
|
|
||||||
...props,
|
|
||||||
evaluate,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
MathFormula.Expression = connect((props) => {
|
|
||||||
return React.createElement(Formula.Expression, {
|
|
||||||
...props,
|
|
||||||
evaluate,
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,17 +0,0 @@
|
|||||||
import { CollectionManagerContext, registerField, SchemaComponentOptions } from '@nocobase/client';
|
|
||||||
import React, { useContext } from 'react';
|
|
||||||
import { mathFormula } from './math-formula';
|
|
||||||
import { MathFormula } from './MathFormula';
|
|
||||||
|
|
||||||
registerField(mathFormula.group, 'mathFormula', mathFormula);
|
|
||||||
|
|
||||||
export default React.memo((props) => {
|
|
||||||
const ctx = useContext(CollectionManagerContext);
|
|
||||||
return (
|
|
||||||
<SchemaComponentOptions components={{ MathFormula }}>
|
|
||||||
<CollectionManagerContext.Provider value={{ ...ctx, interfaces: { ...ctx.interfaces, mathFormula } }}>
|
|
||||||
{props.children}
|
|
||||||
</CollectionManagerContext.Provider>
|
|
||||||
</SchemaComponentOptions>
|
|
||||||
);
|
|
||||||
});
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './server';
|
|
@ -1,90 +0,0 @@
|
|||||||
import { Database, mockDatabase } from '@nocobase/database';
|
|
||||||
import { MathFormulaField } from '../math-formula-field';
|
|
||||||
|
|
||||||
describe('formula field', () => {
|
|
||||||
let db: Database;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
db = mockDatabase();
|
|
||||||
db.registerFieldTypes({
|
|
||||||
mathFormula: MathFormulaField,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
await db.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('auto set formula field with create or update data', async () => {
|
|
||||||
const expression = 'price*count';
|
|
||||||
const Test = db.collection({
|
|
||||||
name: 'tests',
|
|
||||||
fields: [
|
|
||||||
{ type: 'float', name: 'price' },
|
|
||||||
{ type: 'float', name: 'count' },
|
|
||||||
{ name: 'sum', type: 'mathFormula', expression },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.sync();
|
|
||||||
|
|
||||||
const test = await Test.model.create<any>({
|
|
||||||
price: '1.2',
|
|
||||||
count: '2',
|
|
||||||
});
|
|
||||||
|
|
||||||
const sumField = Test.getField('sum');
|
|
||||||
expect(test.get('sum')).toEqual(2.4);
|
|
||||||
|
|
||||||
test.set('count', '6');
|
|
||||||
await test.save();
|
|
||||||
expect(test.get('sum')).toEqual(7.2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('auto set formula field with create or update data', async () => {
|
|
||||||
const expression = '{{price}}*{{count}}';
|
|
||||||
const Test = db.collection({
|
|
||||||
name: 'tests',
|
|
||||||
fields: [
|
|
||||||
{ type: 'float', name: 'price' },
|
|
||||||
{ type: 'float', name: 'count' },
|
|
||||||
{ name: 'sum', type: 'mathFormula', expression },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.sync();
|
|
||||||
|
|
||||||
const test = await Test.model.create<any>({
|
|
||||||
price: '1.2',
|
|
||||||
count: '2',
|
|
||||||
});
|
|
||||||
|
|
||||||
const sumField = Test.getField('sum');
|
|
||||||
expect(test.get('sum')).toEqual(2.4);
|
|
||||||
|
|
||||||
test.set('count', '6');
|
|
||||||
await test.save();
|
|
||||||
expect(test.get('sum')).toEqual(7.2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('1.22+2=3.22', async () => {
|
|
||||||
const expression = '{{a}}+{{b}}';
|
|
||||||
const Test = db.collection({
|
|
||||||
name: 'tests',
|
|
||||||
fields: [
|
|
||||||
{ type: 'float', name: 'a' },
|
|
||||||
{ type: 'float', name: 'b' },
|
|
||||||
{ name: 'sum', type: 'mathFormula', expression },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.sync();
|
|
||||||
|
|
||||||
const test = await Test.model.create<any>({
|
|
||||||
a: '2',
|
|
||||||
b: '1.22',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(test.get('sum')).toEqual(3.22);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './plugin';
|
|
@ -1,24 +0,0 @@
|
|||||||
import { InstallOptions, Plugin } from '@nocobase/server';
|
|
||||||
import { MathFormulaField } from './math-formula-field';
|
|
||||||
|
|
||||||
export class MathFormulaFieldPlugin extends Plugin {
|
|
||||||
afterAdd() {}
|
|
||||||
|
|
||||||
beforeLoad() {
|
|
||||||
this.db.registerFieldTypes({
|
|
||||||
mathFormula: MathFormulaField,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async load() {}
|
|
||||||
|
|
||||||
async install(options?: InstallOptions) {}
|
|
||||||
|
|
||||||
async afterEnable() {}
|
|
||||||
|
|
||||||
async afterDisable() {}
|
|
||||||
|
|
||||||
async remove() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MathFormulaFieldPlugin;
|
|
@ -1,8 +0,0 @@
|
|||||||
import * as math from 'mathjs';
|
|
||||||
|
|
||||||
export function evaluate(exp: string, scope = {}) {
|
|
||||||
const expression = exp.replace(/{{([^}]+)}}/g, (_, i) => {
|
|
||||||
return scope[i.trim()] || '';
|
|
||||||
});
|
|
||||||
return math.evaluate(expression, scope);
|
|
||||||
}
|
|
@ -22,8 +22,6 @@
|
|||||||
"@nocobase/plugin-import": "0.8.0-alpha.13",
|
"@nocobase/plugin-import": "0.8.0-alpha.13",
|
||||||
"@nocobase/plugin-sample-hello": "0.8.0-alpha.13",
|
"@nocobase/plugin-sample-hello": "0.8.0-alpha.13",
|
||||||
"@nocobase/plugin-system-settings": "0.8.0-alpha.13",
|
"@nocobase/plugin-system-settings": "0.8.0-alpha.13",
|
||||||
"@nocobase/plugin-math-formula-field": "0.8.0-alpha.13",
|
|
||||||
"@nocobase/plugin-excel-formula-field": "0.8.0-alpha.13",
|
|
||||||
"@nocobase/plugin-ui-routes-storage": "0.8.0-alpha.13",
|
"@nocobase/plugin-ui-routes-storage": "0.8.0-alpha.13",
|
||||||
"@nocobase/plugin-ui-schema-storage": "0.8.0-alpha.13",
|
"@nocobase/plugin-ui-schema-storage": "0.8.0-alpha.13",
|
||||||
"@nocobase/plugin-users": "0.8.0-alpha.13",
|
"@nocobase/plugin-users": "0.8.0-alpha.13",
|
||||||
|
@ -19,8 +19,6 @@ export class PresetNocoBase extends Plugin {
|
|||||||
'export',
|
'export',
|
||||||
'import',
|
'import',
|
||||||
'audit-logs',
|
'audit-logs',
|
||||||
'math-formula-field',
|
|
||||||
'excel-formula-field',
|
|
||||||
];
|
];
|
||||||
await this.app.pm.add(plugins, {
|
await this.app.pm.add(plugins, {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user