feat: formula plugin (#1344)
* fix: transaction cannot be rolled back because it has been finished with state: rollback * fix(plugin-system-settings): convert array to json * fix(collection-manager): o2m is array type * fix: missing RefreshActionInitializer * fix(collection-manger): incorrect scope key parameter * fix: can't access pages without permission via url (#826) * feat(database): add sequence field type (#779) * feat(database): add serialString field type * feat(database): add serial string type field ui (skip ci) * test(feat/database): test field options * docs: demo * fix(database): fix array table field behavior * fix(database): fix serial type interface ui * fix(database): add match logic for patterns changes * fix(database): fix serial type query last bug in mysql * refactor(database): refactor last record logic * chore: revert modification on unnecessary file * refactor(database): rename serialString type to sequence Co-authored-by: chenos <chenlinxh@gmail.com> * added Russian translation (#840) * Russian translation * Add files via upload Add RU locale into index.ts Bugs fixed in the ru_RU.ts * Update index.ts Correct lines 4 and 8 * feat: update option must have filter or filterByTk (#847) * feat: update option must have filter or filterByTk * fix: typo * fix: typo * feat(core/cache): support cache (#876) * feat(core/cache): support cache * build(create-nocobase-app): remove --cache-store-package cli option * perf(core/cache): modify default cache config and remove unnecessary logic code * fix: slow join query issued by appends field in find method of repository (#845) * fix: slow join query issue by appends field in repository.find * feat: handle appending query in multiple relation repository * feat: handle appending query in single relation repository Co-authored-by: chenos <chenlinxh@gmail.com> * fix: sort parameter is missing (#849) * fix: 审计日志翻页sort丢失 * fix: 审计日志翻页sort丢失 * fix: 审计日志翻页sort丢失 Co-authored-by: 唐小爱 <tangxiaoai@192.168.0.103> * fix(formula): support integer and fix NaN error (#879) * fix(formula): support integer and fix NaN error * style(formula-input): remove debugger * fix(database): fix the index name too long error * feat(collection-manager): inverse fields can be configured (#883) * feat: inverse field * feat: improve code * feat: translations * fix: required * fix: run test by jest (#891) * fix: unable to submit form during file upload (#892) * fix(client/block-select-collection): fix select collection menu view error (#889) * fix(client/block-select-collection): fix too many collection menu view error * fix(client/relate-collection-field-menu): fix relate collection field menu view too long error * fix(client/record-picker): support record-picker show format DataPicker (#888) * fix(client/record-picker): support record-picker show format DataPicker * fix(client/record-picker): undefined judgment and when change field's label refresh format in time * feat: improve signin and signup page components * feat(plugin-workflow): add concat calculator (#894) * fix: single relation repository appends query issue (#901) * fix: appends merge includes (#905) * fix: build error * fix(client): tab pane initializers for create form block * fix: version judgment is not accurate * fix: sync collection field default value (#907) * feat: limit database identifier (#908) * fix: cannot read properties of undefined (reading 'target') * fix: appends merge now using primary key (#911) * fix: appends merge now using primary key * chore: console.log * fix: unbind on error throwing (#914) * feat: create with array of values (#912) * feat: create with array of values * chore: console.log * chore: debug * fix(client/route-switch): skip sub routes * Feat: plugin workflow collection field (#919) * feat(plugin-workflow): use Collectionfield component to render form * fix(plugin-workflow): fix association types value assigning in nodes * fix: missing menuItemGroupCss * fix: multiple = false * chore(versions): 😊 publish v0.7.5-alpha.1 (#920) * fix(plugin-workflow): temp disable validation of collection field in node (#928) * fix(plugin-workflow): fix schedule infinitely trigger when repeat not set (#926) * Feat/plugin workflow collection field (#934) * feat(plugin-workflow): support association constant simple input * fix(plugin-workflow): remove useless code * fix(plugin-workflow): add req context to processor (#936) * fix(plugin-workflow): avoid revision with ghost nodes (#941) * fix(plugin-workflow): fix collection fieldset component (#942) * feat(useSignup): customize success message * feat: changed with associations (#943) * feat: changed with associations * fix: test error * fix: test error * fix: test * fix(plugin-workflow): fix schedule trigger bug (#949) * fix(plugin-workflow): skip time based test (#951) * fix(plugin-workflow): test changedWithAssociations() (#950) * fix(plugin-workflow): test changedWithAssociations() * fix: toChangedWithAssociations * fix(plugin-workflow): add test cases Co-authored-by: chenos <chenlinxh@gmail.com> * feat: docker optimizing (#948) * chore: feat/docker-optimizing * feat: docker nginx gzip config * feat: ui schema cache (#877) * feat(core/cache): support cache * perf(plugins/ui-schema-storage): cache schema * refactor(plugins/ui-schema-storage): handle schema cache in repository level, not in action * fix(plugins/ui-schema-storage): jsonSchema use s_ + x-uid and properties use p_ + x-uid cache prevent jsonSchema and properties cache override each other * test(plugins/ui-schema-storage): add ui_schema repository with cache test * build(create-nocobase-app): remove create-nocobase cli's cache-store-package option * test(plugins/ui-schema-storage): add ui_schema repository with cache test with readFromCache false * fix(plugins/ui-schema-storage): repository insertAdjacent and patch method clear cache fix Co-authored-by: chenos <chenlinxh@gmail.com> * feat: add filter action to collection table (#953) * feat: 数据表支持筛选 * feat: 数据表支持筛选 * feat: 数据表支持筛选 * feat: 数据表支持筛选 * fix: format code Co-authored-by: chenos <chenlinxh@gmail.com> * fix: default value of VERDACCIO_URL * feat: add Dockerfile.official * refactor(plugin-file-manager): move client code into plugin folder and enable path config (#913) * Turkish language (#939) * Türkçe dil dosyası eklendi. Turkish language added * Türkçe dil düzenlemesi. Turkish language edit. * fix: cannot find name 'antdTrTR' * chore(versions): 😊 publish v0.7.6-alpha.2 (#954) * Turkish readme (#955) * Türkçe dil dosyası eklendi. Turkish language added * Türkçe Açıklama sayfası. Turkish readme file added * Turkish language link added * turkish language link added * fix: update readme * fix(plugin-workflow): fix constant schedule trigger time (#956) * feat: push ali docker registry (#957) * feat: push ali docker registry * fix: add VERDACCIO_URL * fix: env * fix: add VERDACCIO_URL to env * fix: update VERDACCIO_URL * fix: arg VERDACCIO_URL * fix: update Dockerfile.official * fix: build-args * fix: network: host * fix: ci * fix: verdaccio:10104 * Update branch-docker-ci.yml * Update branch-docker-ci.yml * Update branch-docker-ci.yml * Update branch-docker-ci.yml * Update branch-docker-ci.yml * Update branch-docker-ci.yml * Update branch-docker-ci.yml * feat: push acr Co-authored-by: ChengLei Shao <chareice@live.com> * feat: api service * fix: api services * feat(file-manager): support tencent cos (#958) * feat: support tencent cos * feat: normalize * chore: update env example * test: add cos * Revert "feat: normalize" This reverts commit 371880013c20b4cb4174ffaa15e0157acc5306c3. * feat: normalize * feat: tengxun to tencent * chore: fix missing deps * fix: locale * fix: locale cos * fix(database/formula-field): when formula's field caculate result is 0 it alse will be save (#962) * fix: lerna ERR! EUNCOMMIT M yarn.lock * fix: driver-opts: network=host * fix(ci): add verdaccio service * chore(versions): 😊 publish v0.7.7-alpha.1 (#963) * fix(plugin-workflow): fix workflow update action (#964) * fix(ci): yarn config set network-timeout 600000 * fix: empty logic operator filter (#961) * chore: fix incorrect deps (#970) * feat: support show lunar day (#972) * feat: support show lunar day * fix: update yarn.lock * fix: types error Co-authored-by: Dunqing <dengqing0821@gmail.com> * fix(ci): remove platforms * chore: improve ci (#976) * test: ci * fix: ci error * fix: on pull_request * fix: paths * fix: paths 'packages/**' * feat: support show lunar day in week and day (#977) Co-authored-by: qing <qing@imideMacBook-Air.local> * fix(client/formula): set cursor focus on input (#959) * fix(client/formula): set cursor focus on input * fix(client/formula): when formula field name has contains the other one will case FormulaError refactor partial implementation * fix(client/table-selector-provider): make data range config effective (#960) * fix(client/table-selector-provider): make data range config effective * fix(client/table-selector): fix when save data range config filter lose * fix(client/upload): fix upload mutiple files always uploading status (#974) * Update README.md * Update README.zh-CN.md * Update README.tr-TR.md * feat(calendar): support for add/remove repeats events (#988) * feat(calendar): support for adding repeats * feat: support delete events * fix: has many same x-action * feat: update better logic * fix: exclude is not an array * fix: handle parse cron error * feat: support every_week, every_month, every_year * fix(calendar): events cannot support moment (#1017) * fix(calendar): events cannot support moment * fix: avoid manipulating raw values * feat: add excel function field (#906) * feat: add excel function field * fix: translations * fix: documentation * chore: remove console log * chore: moved generation of parser to helper function; feat: added GET_DEEP function * chore: improve tokenisation for when there are similar names; * chore: move helper to utils * fix: import from @nocobase/utils/client * feat: integration as a plugin * fix: remove excel function interface * fix: locale * fix: error * refactor: formula plugin * fix: improve code * feat: update docs * fix: error * fix: update deps * fix: remove formula * fix: bigint & test * fix: result * fix: double data type * fix: maximum call stack size exceeded * fix: improve code * fix: improve code * feat(cli): quickstart * Revert "feat(cli): quickstart" This reverts commit 99ad54afb6785c9180c46dd3e9e01e25da368218. * fix: precision * fix: migration Co-authored-by: Michael DeGiovanni <m.degiovanni@gmail.com>
This commit is contained in:
parent
3091d25b5e
commit
452ecb5fc7
1
packages/app/client/src/plugins/excel-formula-field.ts
Normal file
1
packages/app/client/src/plugins/excel-formula-field.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from '@nocobase/plugin-excel-formula-field/client';
|
1
packages/app/client/src/plugins/math-formula-field.ts
Normal file
1
packages/app/client/src/plugins/math-formula-field.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from '@nocobase/plugin-math-formula-field/client';
|
@ -6,7 +6,6 @@ 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 './icon';
|
export * from './icon';
|
||||||
export * from './id';
|
export * from './id';
|
||||||
export * from './input';
|
export * from './input';
|
||||||
@ -31,3 +30,4 @@ export * from './textarea';
|
|||||||
export * from './time';
|
export * from './time';
|
||||||
export * from './updatedAt';
|
export * from './updatedAt';
|
||||||
export * from './updatedBy';
|
export * from './updatedBy';
|
||||||
|
|
||||||
|
220
packages/core/client/src/formula/Expression.tsx
Normal file
220
packages/core/client/src/formula/Expression.tsx
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
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.')],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
trigger={['click']}
|
||||||
|
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(' ');
|
||||||
|
}
|
||||||
|
onChange(text);
|
||||||
|
}}
|
||||||
|
// onClick={(e) => {
|
||||||
|
// const text = getValue(e.currentTarget);
|
||||||
|
// onChange(text);
|
||||||
|
// console.log('onChange', text);
|
||||||
|
// }}
|
||||||
|
onBlur={(e) => {
|
||||||
|
const text = getValue(e.currentTarget);
|
||||||
|
onChange(text);
|
||||||
|
}}
|
||||||
|
onInput={(e) => {
|
||||||
|
const text = getValue(e.currentTarget);
|
||||||
|
onChange(text);
|
||||||
|
}}
|
||||||
|
className={'ant-input'}
|
||||||
|
style={{ display: 'block' }}
|
||||||
|
ref={inputRef as any}
|
||||||
|
contentEditable
|
||||||
|
dangerouslySetInnerHTML={{ __html: html }}
|
||||||
|
/>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Expression;
|
53
packages/core/client/src/formula/Result.tsx
Normal file
53
packages/core/client/src/formula/Result.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
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;
|
51
packages/core/client/src/formula/demos/demo2.tsx
Normal file
51
packages/core/client/src/formula/demos/demo2.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
);
|
||||||
|
};
|
10
packages/core/client/src/formula/index.md
Normal file
10
packages/core/client/src/formula/index.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
nav:
|
||||||
|
path: /client
|
||||||
|
group:
|
||||||
|
path: /client
|
||||||
|
---
|
||||||
|
|
||||||
|
# Formula
|
||||||
|
|
||||||
|
<code src="./demos/demo2.tsx"/>
|
9
packages/core/client/src/formula/index.tsx
Normal file
9
packages/core/client/src/formula/index.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import Expression from './Expression';
|
||||||
|
import Result from './Result';
|
||||||
|
|
||||||
|
export const Formula = () => null;
|
||||||
|
|
||||||
|
Formula.Result = Result;
|
||||||
|
Formula.Expression = Expression;
|
||||||
|
|
||||||
|
export default Formula;
|
@ -11,6 +11,7 @@ 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';
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
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;
|
|
@ -1,120 +0,0 @@
|
|||||||
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 items = keys.map((key) => {
|
|
||||||
return { label: numColumns.get(key), key };
|
|
||||||
});
|
|
||||||
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();
|
|
||||||
}}
|
|
||||||
items={items}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
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;
|
|
@ -1,6 +0,0 @@
|
|||||||
import { Compute } from './Compute';
|
|
||||||
import { FormulaInput } from './FormulaInput';
|
|
||||||
|
|
||||||
export const Formula: any = () => null;
|
|
||||||
Formula.Expression = FormulaInput;
|
|
||||||
Formula.Result = Compute;
|
|
@ -4,14 +4,13 @@ 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 './cron';
|
|
||||||
export * from './color-select';
|
export * from './color-select';
|
||||||
|
export * from './cron';
|
||||||
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';
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
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()));
|
|
||||||
});
|
|
||||||
});
|
|
@ -6,12 +6,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 { ModelEventTypes } from '../types';
|
|
||||||
import { InheritedCollection } from '../inherited-collection';
|
import { InheritedCollection } from '../inherited-collection';
|
||||||
|
import { ModelEventTypes } from '../types';
|
||||||
|
|
||||||
export interface FieldContext {
|
export interface FieldContext {
|
||||||
database: Database;
|
database: Database;
|
||||||
|
@ -13,10 +13,11 @@ import {
|
|||||||
DoubleFieldOptions,
|
DoubleFieldOptions,
|
||||||
FloatFieldOptions,
|
FloatFieldOptions,
|
||||||
IntegerFieldOptions,
|
IntegerFieldOptions,
|
||||||
RealFieldOptions,
|
RealFieldOptions
|
||||||
} 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 { SetFieldOptions } from './set-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';
|
||||||
@ -24,11 +25,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 { SetFieldOptions } from './set-field';
|
|
||||||
|
|
||||||
export * from './array-field';
|
export * from './array-field';
|
||||||
export * from './set-field';
|
|
||||||
export * from './belongs-to-field';
|
export * from './belongs-to-field';
|
||||||
export * from './belongs-to-many-field';
|
export * from './belongs-to-many-field';
|
||||||
export * from './boolean-field';
|
export * from './boolean-field';
|
||||||
@ -42,6 +40,7 @@ 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 * from './set-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';
|
||||||
@ -49,7 +48,6 @@ 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 type FieldOptions =
|
export type FieldOptions =
|
||||||
| BaseFieldOptions
|
| BaseFieldOptions
|
||||||
@ -66,7 +64,6 @@ export type FieldOptions =
|
|||||||
| SortFieldOptions
|
| SortFieldOptions
|
||||||
| TextFieldOptions
|
| TextFieldOptions
|
||||||
| VirtualFieldOptions
|
| VirtualFieldOptions
|
||||||
| FormulaFieldOptions
|
|
||||||
| ArrayFieldOptions
|
| ArrayFieldOptions
|
||||||
| SetFieldOptions
|
| SetFieldOptions
|
||||||
| TimeFieldOptions
|
| TimeFieldOptions
|
||||||
|
4
packages/plugins/excel-formula-field/client.d.ts
vendored
Executable file
4
packages/plugins/excel-formula-field/client.d.ts
vendored
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
export * from './lib/client';
|
||||||
|
export { default } from './lib/client';
|
||||||
|
|
30
packages/plugins/excel-formula-field/client.js
Executable file
30
packages/plugins/excel-formula-field/client.js
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
"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];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
12
packages/plugins/excel-formula-field/package.json
Normal file
12
packages/plugins/excel-formula-field/package.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "@nocobase/plugin-excel-formula-field",
|
||||||
|
"version": "0.8.1-alpha.4",
|
||||||
|
"main": "lib/server/index.js",
|
||||||
|
"dependencies": {
|
||||||
|
"@formulajs/formulajs": "^3.1.7"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nocobase/server": "0.8.1-alpha.4",
|
||||||
|
"@nocobase/test": "0.8.1-alpha.4"
|
||||||
|
}
|
||||||
|
}
|
4
packages/plugins/excel-formula-field/server.d.ts
vendored
Executable file
4
packages/plugins/excel-formula-field/server.d.ts
vendored
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
export * from './lib/server';
|
||||||
|
export { default } from './lib/server';
|
||||||
|
|
30
packages/plugins/excel-formula-field/server.js
Executable file
30
packages/plugins/excel-formula-field/server.js
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
"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];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,20 @@
|
|||||||
|
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,
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,82 @@
|
|||||||
|
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 }}',
|
||||||
|
"x-reactions": [
|
||||||
|
{
|
||||||
|
target: 'uiSchema.x-component-props.step',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
display: '{{$self.value !== "string" ? "visible" : "none"}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
enum: [
|
||||||
|
{ value: 'string', label: '{{t("String")}}' },
|
||||||
|
{ value: 'number', label: '{{t("Number")}}' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'uiSchema.x-component-props.step': {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Precision")}}',
|
||||||
|
'x-component': 'Select',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
required: true,
|
||||||
|
default: '0',
|
||||||
|
enum: [
|
||||||
|
{ value: '0', label: '1' },
|
||||||
|
{ value: '0.1', label: '1.0' },
|
||||||
|
{ value: '0.01', label: '1.00' },
|
||||||
|
{ value: '0.001', label: '1.000' },
|
||||||
|
{ value: '0.0001', label: '1.0000' },
|
||||||
|
{ value: '0.00001', label: '1.00000' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
expression: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Expression")}}',
|
||||||
|
required: true,
|
||||||
|
description: '{{excelExpressionDescription}}',
|
||||||
|
'x-component': 'ExcelFormula.Expression',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component-props': {
|
||||||
|
supports: ['number', 'percent', 'integer', 'string'],
|
||||||
|
useCurrentFields: '{{ useCurrentFields }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filterable: {
|
||||||
|
operators: operators.string,
|
||||||
|
},
|
||||||
|
};
|
34
packages/plugins/excel-formula-field/src/client/index.tsx
Normal file
34
packages/plugins/excel-formula-field/src/client/index.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { CollectionManagerContext, registerField, SchemaComponentOptions } from '@nocobase/client';
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { excelFormula } from './excel-formula';
|
||||||
|
import { ExcelFormula } from './ExcelFormula';
|
||||||
|
|
||||||
|
registerField(excelFormula.group, 'excelFormula', excelFormula);
|
||||||
|
|
||||||
|
export default React.memo((props) => {
|
||||||
|
const ctx = useContext(CollectionManagerContext);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaComponentOptions
|
||||||
|
scope={{
|
||||||
|
excelExpressionDescription: (
|
||||||
|
<div>
|
||||||
|
{t('Syntax see', { ns: 'math-formula-field' })}{' '}
|
||||||
|
<a target={'_blank'} href={'https://formulajs.info/functions/'}>
|
||||||
|
formula.js
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
components={{
|
||||||
|
ExcelFormula,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CollectionManagerContext.Provider value={{ ...ctx, interfaces: { ...ctx.interfaces, excelFormula } }}>
|
||||||
|
{props.children}
|
||||||
|
</CollectionManagerContext.Provider>
|
||||||
|
</SchemaComponentOptions>
|
||||||
|
);
|
||||||
|
});
|
1
packages/plugins/excel-formula-field/src/index.ts
Normal file
1
packages/plugins/excel-formula-field/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './server';
|
@ -0,0 +1,108 @@
|
|||||||
|
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
packages/plugins/excel-formula-field/src/server/index.ts
Normal file
1
packages/plugins/excel-formula-field/src/server/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './plugin';
|
24
packages/plugins/excel-formula-field/src/server/plugin.ts
Normal file
24
packages/plugins/excel-formula-field/src/server/plugin.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
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;
|
@ -0,0 +1,9 @@
|
|||||||
|
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));
|
||||||
|
}
|
4
packages/plugins/math-formula-field/client.d.ts
vendored
Executable file
4
packages/plugins/math-formula-field/client.d.ts
vendored
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
export * from './lib/client';
|
||||||
|
export { default } from './lib/client';
|
||||||
|
|
30
packages/plugins/math-formula-field/client.js
Executable file
30
packages/plugins/math-formula-field/client.js
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
"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];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
12
packages/plugins/math-formula-field/package.json
Normal file
12
packages/plugins/math-formula-field/package.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "@nocobase/plugin-math-formula-field",
|
||||||
|
"version": "0.8.1-alpha.4",
|
||||||
|
"main": "lib/server/index.js",
|
||||||
|
"dependencies": {
|
||||||
|
"mathjs": "^10.6.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nocobase/server": "0.8.1-alpha.4",
|
||||||
|
"@nocobase/test": "0.8.1-alpha.4"
|
||||||
|
}
|
||||||
|
}
|
4
packages/plugins/math-formula-field/server.d.ts
vendored
Executable file
4
packages/plugins/math-formula-field/server.d.ts
vendored
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
export * from './lib/server';
|
||||||
|
export { default } from './lib/server';
|
||||||
|
|
30
packages/plugins/math-formula-field/server.js
Executable file
30
packages/plugins/math-formula-field/server.js
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
"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];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,20 @@
|
|||||||
|
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,
|
||||||
|
});
|
||||||
|
});
|
31
packages/plugins/math-formula-field/src/client/index.tsx
Normal file
31
packages/plugins/math-formula-field/src/client/index.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { CollectionManagerContext, registerField, SchemaComponentOptions } from '@nocobase/client';
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { mathFormula } from './math-formula';
|
||||||
|
import { MathFormula } from './MathFormula';
|
||||||
|
|
||||||
|
registerField(mathFormula.group, 'mathFormula', mathFormula);
|
||||||
|
|
||||||
|
export default React.memo((props) => {
|
||||||
|
const ctx = useContext(CollectionManagerContext);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<SchemaComponentOptions
|
||||||
|
scope={{
|
||||||
|
mathExpressionDescription: (
|
||||||
|
<div>
|
||||||
|
{t('Syntax see', { ns: 'math-formula-field' })}{' '}
|
||||||
|
<a target={'_blank'} href={'https://mathjs.org/docs/expressions/syntax.html'}>
|
||||||
|
math.js
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
components={{ MathFormula }}
|
||||||
|
>
|
||||||
|
<CollectionManagerContext.Provider value={{ ...ctx, interfaces: { ...ctx.interfaces, mathFormula } }}>
|
||||||
|
{props.children}
|
||||||
|
</CollectionManagerContext.Provider>
|
||||||
|
</SchemaComponentOptions>
|
||||||
|
);
|
||||||
|
});
|
@ -1,22 +1,22 @@
|
|||||||
import { defaultProps, operators } from './properties';
|
import { IField, interfacesProperties } from '@nocobase/client';
|
||||||
import { IField } from './types';
|
const { defaultProps, operators } = interfacesProperties;
|
||||||
|
|
||||||
export const formula: IField = {
|
export const mathFormula: IField = {
|
||||||
name: 'formula',
|
name: 'mathFormula',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
group: 'advanced',
|
group: 'advanced',
|
||||||
order: 1,
|
order: 1,
|
||||||
title: '{{t("Formula")}}',
|
title: '{{t("Math formula")}}',
|
||||||
description: '{{t("Formula description")}}',
|
description: '{{t("Compute a value based on the other fields using mathjs")}}',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
default: {
|
default: {
|
||||||
type: 'formula',
|
type: 'mathFormula',
|
||||||
// name,
|
// name,
|
||||||
uiSchema: {
|
uiSchema: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
// title,
|
// title,
|
||||||
"x-disabled": true,
|
'x-disabled': true,
|
||||||
'x-component': 'Formula.Result',
|
'x-component': 'MathFormula.Result',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
stringMode: true,
|
stringMode: true,
|
||||||
step: '1',
|
step: '1',
|
||||||
@ -25,16 +25,16 @@ export const formula: 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: '{{mathExpressionDescription}}',
|
||||||
'x-component': 'Formula.Expression',
|
'x-component': 'MathFormula.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': {
|
1
packages/plugins/math-formula-field/src/index.ts
Normal file
1
packages/plugins/math-formula-field/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './server';
|
@ -0,0 +1,90 @@
|
|||||||
|
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
packages/plugins/math-formula-field/src/server/index.ts
Normal file
1
packages/plugins/math-formula-field/src/server/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './plugin';
|
@ -1,16 +1,17 @@
|
|||||||
import { DataTypes } from 'sequelize';
|
import { BaseFieldOptions, Field } from '@nocobase/database';
|
||||||
import { BaseColumnFieldOptions, Field } from './field';
|
|
||||||
import * as math from 'mathjs';
|
import * as math from 'mathjs';
|
||||||
|
import { DataTypes } from 'sequelize';
|
||||||
|
import { evaluate } from '../utils/evaluate';
|
||||||
|
|
||||||
export class FormulaField extends Field {
|
export class MathFormulaField extends Field {
|
||||||
get dataType() {
|
get dataType() {
|
||||||
return DataTypes.FLOAT;
|
return DataTypes.DOUBLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
caculate(expression, scope) {
|
calculate(expression, scope) {
|
||||||
let result = null;
|
let result: any = null;
|
||||||
try {
|
try {
|
||||||
result = math.evaluate(expression, scope);
|
result = evaluate(expression, scope);
|
||||||
result = math.round(result, 9);
|
result = math.round(result, 9);
|
||||||
} catch {}
|
} catch {}
|
||||||
return result;
|
return result;
|
||||||
@ -26,7 +27,7 @@ export class FormulaField extends Field {
|
|||||||
|
|
||||||
for (const record of records) {
|
for (const record of records) {
|
||||||
const scope = record.toJSON();
|
const scope = record.toJSON();
|
||||||
const result = this.caculate(expression, scope);
|
const result = this.calculate(expression, scope);
|
||||||
if (result) {
|
if (result) {
|
||||||
await record.update(
|
await record.update(
|
||||||
{
|
{
|
||||||
@ -42,12 +43,12 @@ export class FormulaField extends Field {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
caculateField = async (instance) => {
|
calculateField = 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 = math.evaluate(expression, scope);
|
result = evaluate(expression, scope);
|
||||||
result = math.round(result, 9);
|
result = math.round(result, 9);
|
||||||
} catch {}
|
} catch {}
|
||||||
if (result === 0 || result) {
|
if (result === 0 || result) {
|
||||||
@ -67,7 +68,7 @@ export class FormulaField extends Field {
|
|||||||
|
|
||||||
for (const record of records) {
|
for (const record of records) {
|
||||||
const scope = record.toJSON();
|
const scope = record.toJSON();
|
||||||
const result = this.caculate(expression, scope);
|
const result = this.calculate(expression, scope);
|
||||||
await record.update(
|
await record.update(
|
||||||
{
|
{
|
||||||
[name]: result,
|
[name]: result,
|
||||||
@ -87,20 +88,19 @@ export class FormulaField 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.caculateField);
|
this.on('beforeSave', this.calculateField);
|
||||||
}
|
}
|
||||||
|
|
||||||
unbind() {
|
unbind() {
|
||||||
super.unbind();
|
super.unbind();
|
||||||
this.off('beforeSave', this.caculateField);
|
this.off('beforeSave', this.calculateField);
|
||||||
// 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 FormulaFieldOptions extends BaseColumnFieldOptions {
|
export interface MathFormulaFieldOptions extends BaseFieldOptions {
|
||||||
type: 'formula';
|
type: 'mathFormula';
|
||||||
|
|
||||||
expression: string;
|
expression: string;
|
||||||
}
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import { Migration } from '@nocobase/server';
|
||||||
|
|
||||||
|
export default class extends Migration {
|
||||||
|
async up() {
|
||||||
|
const result = await this.app.version.satisfies('<=0.8.1-alpha.4');
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const Field = this.context.db.getRepository('fields');
|
||||||
|
const fields = await Field.find();
|
||||||
|
await this.context.db.sequelize.transaction(async (transaction) => {
|
||||||
|
for (const field of fields) {
|
||||||
|
if (field.get('type') === 'formula') {
|
||||||
|
field.set('type', 'mathFormula');
|
||||||
|
field.set('interface', 'mathFormula');
|
||||||
|
await field.save({ transaction });
|
||||||
|
const schema = await field.getUiSchema({ transaction });
|
||||||
|
schema.set('x-component', 'MathFormula.Result');
|
||||||
|
await schema.save({ transaction });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
34
packages/plugins/math-formula-field/src/server/plugin.ts
Normal file
34
packages/plugins/math-formula-field/src/server/plugin.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { InstallOptions, Plugin } from '@nocobase/server';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import { MathFormulaField } from './math-formula-field';
|
||||||
|
|
||||||
|
export class MathFormulaFieldPlugin extends Plugin {
|
||||||
|
afterAdd() {}
|
||||||
|
|
||||||
|
beforeLoad() {
|
||||||
|
this.db.registerFieldTypes({
|
||||||
|
formula: MathFormulaField,
|
||||||
|
mathFormula: MathFormulaField,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.db.addMigrations({
|
||||||
|
namespace: this.name,
|
||||||
|
directory: resolve(__dirname, './migrations'),
|
||||||
|
context: {
|
||||||
|
plugin: this,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {}
|
||||||
|
|
||||||
|
async install(options?: InstallOptions) {}
|
||||||
|
|
||||||
|
async afterEnable() {}
|
||||||
|
|
||||||
|
async afterDisable() {}
|
||||||
|
|
||||||
|
async remove() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MathFormulaFieldPlugin;
|
@ -0,0 +1,8 @@
|
|||||||
|
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);
|
||||||
|
}
|
@ -21,6 +21,8 @@
|
|||||||
"@nocobase/plugin-export": "0.8.1-alpha.4",
|
"@nocobase/plugin-export": "0.8.1-alpha.4",
|
||||||
"@nocobase/plugin-file-manager": "0.8.1-alpha.4",
|
"@nocobase/plugin-file-manager": "0.8.1-alpha.4",
|
||||||
"@nocobase/plugin-iframe-block": "0.8.1-alpha.4",
|
"@nocobase/plugin-iframe-block": "0.8.1-alpha.4",
|
||||||
|
"@nocobase/plugin-math-formula-field": "0.8.1-alpha.4",
|
||||||
|
"@nocobase/plugin-excel-formula-field": "0.8.1-alpha.4",
|
||||||
"@nocobase/plugin-import": "0.8.1-alpha.4",
|
"@nocobase/plugin-import": "0.8.1-alpha.4",
|
||||||
"@nocobase/plugin-map": "0.8.1-alpha.4",
|
"@nocobase/plugin-map": "0.8.1-alpha.4",
|
||||||
"@nocobase/plugin-oidc": "0.8.1-alpha.4",
|
"@nocobase/plugin-oidc": "0.8.1-alpha.4",
|
||||||
|
@ -25,6 +25,8 @@ export class PresetNocoBase extends Plugin {
|
|||||||
'audit-logs',
|
'audit-logs',
|
||||||
'duplicator',
|
'duplicator',
|
||||||
'iframe-block',
|
'iframe-block',
|
||||||
|
'math-formula-field',
|
||||||
|
'excel-formula-field',
|
||||||
].concat(plugins),
|
].concat(plugins),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 016bec621e2576f4cda176cf3d42c9eb7e2eb857
|
Subproject commit 5a5fba41ae1cecaf3b073fcb610e9dc68dad0b85
|
214
yarn.lock
214
yarn.lock
@ -355,7 +355,7 @@
|
|||||||
resolved "https://registry.npmjs.org/@antv/x6-react-shape/-/x6-react-shape-1.6.3.tgz#053660bd555c4356e9719134e3e2a857b51301e2"
|
resolved "https://registry.npmjs.org/@antv/x6-react-shape/-/x6-react-shape-1.6.3.tgz#053660bd555c4356e9719134e3e2a857b51301e2"
|
||||||
integrity sha512-iCsRVkvKCVs7HUUblF/ALg5XxeeEPXDLHuYzRvbvNlCKKLbXLbaz6vJQSgTTrk2i4lrD+7xl4riA76FyWA3k/g==
|
integrity sha512-iCsRVkvKCVs7HUUblF/ALg5XxeeEPXDLHuYzRvbvNlCKKLbXLbaz6vJQSgTTrk2i4lrD+7xl4riA76FyWA3k/g==
|
||||||
|
|
||||||
"@antv/x6@^1.9.0":
|
"@antv/x6@^1.34.2":
|
||||||
version "1.34.6"
|
version "1.34.6"
|
||||||
resolved "https://registry.npmjs.org/@antv/x6/-/x6-1.34.6.tgz#b802b09b52c8620acf01734397714ea5e3f715b6"
|
resolved "https://registry.npmjs.org/@antv/x6/-/x6-1.34.6.tgz#b802b09b52c8620acf01734397714ea5e3f715b6"
|
||||||
integrity sha512-cl8ywN7CTNVT94/ZEbq2mnZQTQe6FUgIlGOAjNOPL8sljZTBrpE6OsuGw4BDA2/XllwELfUfvVpgBvbdwRKrxw==
|
integrity sha512-cl8ywN7CTNVT94/ZEbq2mnZQTQe6FUgIlGOAjNOPL8sljZTBrpE6OsuGw4BDA2/XllwELfUfvVpgBvbdwRKrxw==
|
||||||
@ -3274,6 +3274,14 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@formily/shared" "2.0.20"
|
"@formily/shared" "2.0.20"
|
||||||
|
|
||||||
|
"@formulajs/formulajs@^3.1.7":
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.npmjs.org/@formulajs/formulajs/-/formulajs-3.2.0.tgz#78425f4d49cee49d7d2169c2b9d0bccf2f92afa6"
|
||||||
|
integrity sha512-6rkarI3MTpOTWlvjG1AqRvF/M15Dga/Y6qDGOpxI5ekJGDZKpKRiItTAZ9VEZiDKvlZc42qkRy6tIV5FwHPuAw==
|
||||||
|
dependencies:
|
||||||
|
bessel "^1.0.2"
|
||||||
|
jstat "^1.9.6"
|
||||||
|
|
||||||
"@gar/promisify@^1.0.1":
|
"@gar/promisify@^1.0.1":
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210"
|
resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210"
|
||||||
@ -4411,6 +4419,153 @@
|
|||||||
call-me-maybe "^1.0.1"
|
call-me-maybe "^1.0.1"
|
||||||
glob-to-regexp "^0.3.0"
|
glob-to-regexp "^0.3.0"
|
||||||
|
|
||||||
|
"@nocobase/acl@0.8.0-alpha.9":
|
||||||
|
version "0.8.0-alpha.9"
|
||||||
|
resolved "https://registry.npmjs.org/@nocobase/acl/-/acl-0.8.0-alpha.9.tgz#7ffab4a367befc74daa681b1d5c2c907126cb280"
|
||||||
|
integrity sha512-jEgpjO6gGi4GwSyWxlmcwd368/AQgmrdsODDTLhhrJJI9+ovj2mxO664lQgh3FkDGtmduKlBiZ+qcgK+/RWa1w==
|
||||||
|
dependencies:
|
||||||
|
"@nocobase/resourcer" "0.8.0-alpha.9"
|
||||||
|
json-templates "^4.2.0"
|
||||||
|
|
||||||
|
"@nocobase/actions@0.8.0-alpha.9":
|
||||||
|
version "0.8.0-alpha.9"
|
||||||
|
resolved "https://registry.npmjs.org/@nocobase/actions/-/actions-0.8.0-alpha.9.tgz#3804bbf30f7c4e8aa205b9802ec4f56a58c18c72"
|
||||||
|
integrity sha512-EvdNjXa+TTMU2Xn+MnA7tSa5rooGFtzsvJsSYbkpODtsVQsmTDRIZYZsOYFk/XcezxjzTTsxxXMSA7sLq6wifQ==
|
||||||
|
dependencies:
|
||||||
|
"@nocobase/cache" "0.8.0-alpha.9"
|
||||||
|
"@nocobase/database" "0.8.0-alpha.9"
|
||||||
|
"@nocobase/resourcer" "0.8.0-alpha.9"
|
||||||
|
|
||||||
|
"@nocobase/cache@0.8.0-alpha.9":
|
||||||
|
version "0.8.0-alpha.9"
|
||||||
|
resolved "https://registry.npmjs.org/@nocobase/cache/-/cache-0.8.0-alpha.9.tgz#99f7ca196dacd41b69ed7b7f179fcc1c4c5baa27"
|
||||||
|
integrity sha512-GCi7OE9nLrXKq/Og6SPtPcjtreUxxAL9x7CwmD2x3Zb7gNTc2GvQ9NWoAlO8KTLlWuzuNcJXegBhxaxV8Lt5ew==
|
||||||
|
dependencies:
|
||||||
|
cache-manager "^4.1.0"
|
||||||
|
|
||||||
|
"@nocobase/client@0.8.0-alpha.9":
|
||||||
|
version "0.8.0-alpha.9"
|
||||||
|
resolved "https://registry.npmjs.org/@nocobase/client/-/client-0.8.0-alpha.9.tgz#03446b4c4160a7ebda9dfd8aa80e2203541eb72c"
|
||||||
|
integrity sha512-cvh01k8Ujhc9D7kDYQ7QKP5tnLF+wBOqixtisivti9aeklMBaOKj1oLmxKHZlB2IJzKpT+rUntpsxGxUS6co9A==
|
||||||
|
dependencies:
|
||||||
|
"@antv/g2plot" "^2.4.18"
|
||||||
|
"@dnd-kit/core" "^5.0.1"
|
||||||
|
"@dnd-kit/sortable" "^6.0.0"
|
||||||
|
"@emotion/css" "^11.7.1"
|
||||||
|
"@formily/antd" "2.0.20"
|
||||||
|
"@formily/core" "2.0.20"
|
||||||
|
"@formily/react" "2.0.20"
|
||||||
|
"@nocobase/sdk" "0.8.0-alpha.9"
|
||||||
|
"@nocobase/utils" "0.8.0-alpha.9"
|
||||||
|
ahooks "^3.0.5"
|
||||||
|
antd "~4.19.5"
|
||||||
|
axios "^0.26.1"
|
||||||
|
classnames "^2.3.1"
|
||||||
|
cron-parser "^4.6.0"
|
||||||
|
cronstrue "^2.11.0"
|
||||||
|
file-saver "^2.0.5"
|
||||||
|
i18next "^21.6.0"
|
||||||
|
json-templates "^4.2.0"
|
||||||
|
marked "^4.0.12"
|
||||||
|
mathjs "^10.6.0"
|
||||||
|
react-beautiful-dnd "^13.1.0"
|
||||||
|
react-big-calendar "^0.38.7"
|
||||||
|
react-contenteditable "^3.3.6"
|
||||||
|
react-drag-listview "^0.1.9"
|
||||||
|
react-helmet "^6.1.0"
|
||||||
|
react-hotkeys-hook "^3.4.7"
|
||||||
|
react-i18next "^11.15.1"
|
||||||
|
react-image-lightbox "^5.1.4"
|
||||||
|
react-js-cron "^1.4.0"
|
||||||
|
react-quill "^1.3.5"
|
||||||
|
react-router-dom "^5.2.0"
|
||||||
|
react-to-print "^2.14.7"
|
||||||
|
solarlunar-es "^1.0.9"
|
||||||
|
use-deep-compare-effect "^1.8.1"
|
||||||
|
|
||||||
|
"@nocobase/database@0.8.0-alpha.9":
|
||||||
|
version "0.8.0-alpha.9"
|
||||||
|
resolved "https://registry.npmjs.org/@nocobase/database/-/database-0.8.0-alpha.9.tgz#9684b4ddcfc5396df2ef3d4ae9cd2ce59142df81"
|
||||||
|
integrity sha512-zkLTW2iFg1tlbSTlW40oEmM7O2GxHXmIzErnDR4it0GWoLqjDgzNHP5QjODOxiiB/+/unNFudfGTNCoXxu2ZvA==
|
||||||
|
dependencies:
|
||||||
|
"@nocobase/utils" "0.8.0-alpha.9"
|
||||||
|
async-mutex "^0.3.2"
|
||||||
|
cron-parser "4.4.0"
|
||||||
|
deepmerge "^4.2.2"
|
||||||
|
flat "^5.0.2"
|
||||||
|
glob "^7.1.6"
|
||||||
|
mathjs "^10.6.1"
|
||||||
|
moment "2.x"
|
||||||
|
semver "^7.3.7"
|
||||||
|
sequelize "^6.9.0"
|
||||||
|
umzug "^3.1.1"
|
||||||
|
|
||||||
|
"@nocobase/resourcer@0.8.0-alpha.9":
|
||||||
|
version "0.8.0-alpha.9"
|
||||||
|
resolved "https://registry.npmjs.org/@nocobase/resourcer/-/resourcer-0.8.0-alpha.9.tgz#5415af5944b31293e5e2da73bf5c999866a90fab"
|
||||||
|
integrity sha512-6v9bfUex2RfT8C3xX+SUhj8/zECCY6pQA3ce8GV3xd9cu6ku8c7hIiNM3KwgPOD189aND3FxNwxqpECNaoWQ1A==
|
||||||
|
dependencies:
|
||||||
|
"@nocobase/utils" "0.8.0-alpha.9"
|
||||||
|
deepmerge "^4.2.2"
|
||||||
|
koa-compose "^4.1.0"
|
||||||
|
lodash "^4.17.21"
|
||||||
|
path-to-regexp "^6.1.0"
|
||||||
|
qs "^6.9.4"
|
||||||
|
|
||||||
|
"@nocobase/sdk@0.8.0-alpha.9":
|
||||||
|
version "0.8.0-alpha.9"
|
||||||
|
resolved "https://registry.npmjs.org/@nocobase/sdk/-/sdk-0.8.0-alpha.9.tgz#5306e7bf4aabf00894a65036c584508d93bda379"
|
||||||
|
integrity sha512-v+GF4N5ldYwmoqQIukGWzz/eOm2TgxcUZq3uE+YeP/aiDa4XAfkLrpBNsvCS24k3s/U+84FYrvTWRRStJGSzBA==
|
||||||
|
dependencies:
|
||||||
|
axios "^0.26.1"
|
||||||
|
qs "^6.10.1"
|
||||||
|
|
||||||
|
"@nocobase/server@0.8.0-alpha.9":
|
||||||
|
version "0.8.0-alpha.9"
|
||||||
|
resolved "https://registry.npmjs.org/@nocobase/server/-/server-0.8.0-alpha.9.tgz#bc1955e41b2ac9f6640fdf109516376604d4092c"
|
||||||
|
integrity sha512-/mHk2Uc8RS/3GcGvnMnFKlbf2aCufGuc+GOr5gEKuyWa50ibD4nZ7LKJx023uEO07rxWkFkhR6+ExuZ+BGRDGQ==
|
||||||
|
dependencies:
|
||||||
|
"@hapi/topo" "^6.0.0"
|
||||||
|
"@koa/cors" "^3.1.0"
|
||||||
|
"@koa/router" "^9.4.0"
|
||||||
|
"@nocobase/acl" "0.8.0-alpha.9"
|
||||||
|
"@nocobase/actions" "0.8.0-alpha.9"
|
||||||
|
"@nocobase/database" "0.8.0-alpha.9"
|
||||||
|
"@nocobase/resourcer" "0.8.0-alpha.9"
|
||||||
|
chalk "^4.1.1"
|
||||||
|
commander "^9.2.0"
|
||||||
|
find-package-json "^1.2.0"
|
||||||
|
i18next "^21.6.0"
|
||||||
|
koa "^2.13.4"
|
||||||
|
koa-bodyparser "^4.3.0"
|
||||||
|
koa-static "^5.0.0"
|
||||||
|
lodash "^4.17.21"
|
||||||
|
semver "^7.3.7"
|
||||||
|
xpipe "^1.0.5"
|
||||||
|
|
||||||
|
"@nocobase/test@0.8.0-alpha.9":
|
||||||
|
version "0.8.0-alpha.9"
|
||||||
|
resolved "https://registry.npmjs.org/@nocobase/test/-/test-0.8.0-alpha.9.tgz#40aaed50af9b8fd23c0666156c806273c75b0a3f"
|
||||||
|
integrity sha512-bpzcogPKzfq8mjIsLDy0xlsd0dNAUgmju9kk+RPV5wNZfhvofaA7YduexawbL+dLJQC+iyjmhAhARxl8rVhKuA==
|
||||||
|
dependencies:
|
||||||
|
"@nocobase/server" "0.8.0-alpha.9"
|
||||||
|
"@types/supertest" "^2.0.11"
|
||||||
|
mockjs "^1.1.0"
|
||||||
|
mysql2 "^2.3.3"
|
||||||
|
pg "^8.7.3"
|
||||||
|
pg-hstore "^2.3.4"
|
||||||
|
sqlite3 "^5.0.8"
|
||||||
|
supertest "^6.1.6"
|
||||||
|
|
||||||
|
"@nocobase/utils@0.8.0-alpha.9":
|
||||||
|
version "0.8.0-alpha.9"
|
||||||
|
resolved "https://registry.npmjs.org/@nocobase/utils/-/utils-0.8.0-alpha.9.tgz#fa7e203412e91ab7a6ebb12a6d53d83e09465591"
|
||||||
|
integrity sha512-oEqKs4+0sO4x0TFEtf+Tf1nIkb9q+9y9YmrqOn6MyMo/ejTSh6nlNt1rAYgs02gTP4uHkSOb2/4K4oDm0JXLdg==
|
||||||
|
dependencies:
|
||||||
|
"@hapi/topo" "^6.0.0"
|
||||||
|
deepmerge "^4.2.2"
|
||||||
|
flat-to-nested "^1.1.1"
|
||||||
|
|
||||||
"@node-saml/node-saml@^4.0.2":
|
"@node-saml/node-saml@^4.0.2":
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.npmjs.org/@node-saml/node-saml/-/node-saml-4.0.2.tgz#e12020ea635346f33fcef008fe0e6fa2f28713e5"
|
resolved "https://registry.npmjs.org/@node-saml/node-saml/-/node-saml-4.0.2.tgz#e12020ea635346f33fcef008fe0e6fa2f28713e5"
|
||||||
@ -6234,6 +6389,21 @@ ahooks-v3-count@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/ahooks-v3-count/-/ahooks-v3-count-1.0.0.tgz#ddeb392e009ad6e748905b3cbf63a9fd8262ca80"
|
resolved "https://registry.yarnpkg.com/ahooks-v3-count/-/ahooks-v3-count-1.0.0.tgz#ddeb392e009ad6e748905b3cbf63a9fd8262ca80"
|
||||||
integrity sha512-V7uUvAwnimu6eh/PED4mCDjE7tokeZQLKlxg9lCTMPhN+NjsSbtdacByVlR1oluXQzD3MOw55wylDmQo4+S9ZQ==
|
integrity sha512-V7uUvAwnimu6eh/PED4mCDjE7tokeZQLKlxg9lCTMPhN+NjsSbtdacByVlR1oluXQzD3MOw55wylDmQo4+S9ZQ==
|
||||||
|
|
||||||
|
ahooks@^3.0.5:
|
||||||
|
version "3.7.4"
|
||||||
|
resolved "https://registry.npmjs.org/ahooks/-/ahooks-3.7.4.tgz#25e76a989da324a855eb75758b789ab0644b7a59"
|
||||||
|
integrity sha512-hvgdqzPUKXn95mK3cGlDCi/ZZqv+FRibCUCFT8zW3hCwLGvixVfnHrIW2/2lgzPdLo8mLjp/XOdIJvcPvE2lgQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/js-cookie" "^2.x.x"
|
||||||
|
ahooks-v3-count "^1.0.0"
|
||||||
|
dayjs "^1.9.1"
|
||||||
|
intersection-observer "^0.12.0"
|
||||||
|
js-cookie "^2.x.x"
|
||||||
|
lodash "^4.17.21"
|
||||||
|
resize-observer-polyfill "^1.5.1"
|
||||||
|
screenfull "^5.0.0"
|
||||||
|
tslib "^2.4.1"
|
||||||
|
|
||||||
ahooks@^3.7.2:
|
ahooks@^3.7.2:
|
||||||
version "3.7.2"
|
version "3.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/ahooks/-/ahooks-3.7.2.tgz#0afa42625e77ae1cc4b60b19c45cf12a8cf29b56"
|
resolved "https://registry.yarnpkg.com/ahooks/-/ahooks-3.7.2.tgz#0afa42625e77ae1cc4b60b19c45cf12a8cf29b56"
|
||||||
@ -7380,6 +7550,11 @@ before-after-hook@^2.2.0:
|
|||||||
resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e"
|
resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e"
|
||||||
integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==
|
integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==
|
||||||
|
|
||||||
|
bessel@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.npmjs.org/bessel/-/bessel-1.0.2.tgz#828812291e0b62e94959cdea43fac186e8a7202d"
|
||||||
|
integrity sha512-Al3nHGQGqDYqqinXhQzmwmcRToe/3WyBv4N8aZc5Pef8xw2neZlR9VPi84Sa23JtgWcucu18HxVZrnI0fn2etw==
|
||||||
|
|
||||||
big.js@^5.2.2:
|
big.js@^5.2.2:
|
||||||
version "5.2.2"
|
version "5.2.2"
|
||||||
resolved "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
|
resolved "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
|
||||||
@ -14747,6 +14922,11 @@ jsprim@^1.2.2:
|
|||||||
json-schema "0.2.3"
|
json-schema "0.2.3"
|
||||||
verror "1.10.0"
|
verror "1.10.0"
|
||||||
|
|
||||||
|
jstat@^1.9.6:
|
||||||
|
version "1.9.6"
|
||||||
|
resolved "https://registry.npmjs.org/jstat/-/jstat-1.9.6.tgz#60e801b0d4c26e37aab0f375d1859fe9d60e10c0"
|
||||||
|
integrity sha512-rPBkJbK2TnA8pzs93QcDDPlKcrtZWuuCo2dVR0TFLOJSxhqfWOVCSp8aV3/oSbn+4uY4yw1URtLpHQedtmXfug==
|
||||||
|
|
||||||
jstoxml@^2.0.0:
|
jstoxml@^2.0.0:
|
||||||
version "2.2.9"
|
version "2.2.9"
|
||||||
resolved "https://registry.yarnpkg.com/jstoxml/-/jstoxml-2.2.9.tgz#2eebd5e55383fe66a375022ca0aa88f77bc4fb84"
|
resolved "https://registry.yarnpkg.com/jstoxml/-/jstoxml-2.2.9.tgz#2eebd5e55383fe66a375022ca0aa88f77bc4fb84"
|
||||||
@ -21020,6 +21200,11 @@ retry-as-promised@^6.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/retry-as-promised/-/retry-as-promised-6.1.0.tgz#11eca9a0f97804d552ec8e74bc4eb839bd226dc4"
|
resolved "https://registry.yarnpkg.com/retry-as-promised/-/retry-as-promised-6.1.0.tgz#11eca9a0f97804d552ec8e74bc4eb839bd226dc4"
|
||||||
integrity sha512-Hj/jY+wFC+SB9SDlIIFWiGOHnNG0swYbGYsOj2BJ8u2HKUaobNKab0OIC0zOLYzDy0mb7A4xA5BMo4LMz5YtEA==
|
integrity sha512-Hj/jY+wFC+SB9SDlIIFWiGOHnNG0swYbGYsOj2BJ8u2HKUaobNKab0OIC0zOLYzDy0mb7A4xA5BMo4LMz5YtEA==
|
||||||
|
|
||||||
|
retry-as-promised@^7.0.3:
|
||||||
|
version "7.0.3"
|
||||||
|
resolved "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.0.3.tgz#ca3c13b15525a7bfbf0f56d2996f0e75649d068b"
|
||||||
|
integrity sha512-SEvMa4khHvpU/o6zgh7sK24qm6rxVgKnrSyzb5POeDvZx5N9Bf0s5sQsQ4Fl+HjRp0X+w2UzACGfUnXtx6cJ9Q==
|
||||||
|
|
||||||
retry@^0.12.0:
|
retry@^0.12.0:
|
||||||
version "0.12.0"
|
version "0.12.0"
|
||||||
resolved "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
|
resolved "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
|
||||||
@ -21452,6 +21637,28 @@ sequelize@^6.26.0:
|
|||||||
validator "^13.7.0"
|
validator "^13.7.0"
|
||||||
wkx "^0.5.0"
|
wkx "^0.5.0"
|
||||||
|
|
||||||
|
sequelize@^6.9.0:
|
||||||
|
version "6.28.0"
|
||||||
|
resolved "https://registry.npmjs.org/sequelize/-/sequelize-6.28.0.tgz#d6bc4e36647e8501635467c0777c45a33f5d5ba8"
|
||||||
|
integrity sha512-+WHqvUQgTp19GLkt+gyQ+F6qg+FIEO2O5F9C0TOYV/PjZ2a/XwWvVkL1NCkS4VSIjVVvAUutiW6Wv9ofveGaVw==
|
||||||
|
dependencies:
|
||||||
|
"@types/debug" "^4.1.7"
|
||||||
|
"@types/validator" "^13.7.1"
|
||||||
|
debug "^4.3.3"
|
||||||
|
dottie "^2.0.2"
|
||||||
|
inflection "^1.13.2"
|
||||||
|
lodash "^4.17.21"
|
||||||
|
moment "^2.29.1"
|
||||||
|
moment-timezone "^0.5.34"
|
||||||
|
pg-connection-string "^2.5.0"
|
||||||
|
retry-as-promised "^7.0.3"
|
||||||
|
semver "^7.3.5"
|
||||||
|
sequelize-pool "^7.1.0"
|
||||||
|
toposort-class "^1.0.1"
|
||||||
|
uuid "^8.3.2"
|
||||||
|
validator "^13.7.0"
|
||||||
|
wkx "^0.5.0"
|
||||||
|
|
||||||
serialize-javascript@^4.0.0:
|
serialize-javascript@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
|
resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
|
||||||
@ -23072,6 +23279,11 @@ tslib@^2.1.0, tslib@^2.2.0:
|
|||||||
resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
|
resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
|
||||||
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
|
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
|
||||||
|
|
||||||
|
tslib@^2.4.1:
|
||||||
|
version "2.4.1"
|
||||||
|
resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"
|
||||||
|
integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==
|
||||||
|
|
||||||
tsscmp@1.0.6:
|
tsscmp@1.0.6:
|
||||||
version "1.0.6"
|
version "1.0.6"
|
||||||
resolved "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb"
|
resolved "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb"
|
||||||
|
Loading…
Reference in New Issue
Block a user