+
@@ -97,7 +107,11 @@ export const QuickEdit = observer(
if (!collectionField) {
return null;
}
- return field.editable ?
:
;
+ return field.editable || field.disabled ? (
+
+ ) : (
+
+ );
},
{ displayName: 'QuickEdit' },
);
diff --git a/packages/core/client/src/schema-component/antd/quick-edit/__tests__/QuickEdit.test.tsx b/packages/core/client/src/schema-component/antd/quick-edit/__tests__/QuickEdit.test.tsx
index 7b43ebe1dc..2e3cf559ac 100644
--- a/packages/core/client/src/schema-component/antd/quick-edit/__tests__/QuickEdit.test.tsx
+++ b/packages/core/client/src/schema-component/antd/quick-edit/__tests__/QuickEdit.test.tsx
@@ -8,7 +8,7 @@
*/
import { BlockSchemaComponentPlugin } from '@nocobase/client';
-import { screen, renderAppOptions, sleep, userEvent, waitFor } from '@nocobase/test/client';
+import { renderAppOptions, screen, sleep, userEvent, waitFor } from '@nocobase/test/client';
describe('QuickEdit', () => {
function getRenderOptions(readPretty = false) {
diff --git a/packages/core/client/src/schema-component/antd/table-v2/Table.tsx b/packages/core/client/src/schema-component/antd/table-v2/Table.tsx
index 569c3001b6..a06ba65b6d 100644
--- a/packages/core/client/src/schema-component/antd/table-v2/Table.tsx
+++ b/packages/core/client/src/schema-component/antd/table-v2/Table.tsx
@@ -132,7 +132,7 @@ const useTableColumns = (props: { showDel?: boolean; isSubTable?: boolean }) =>
const index = field.value?.indexOf(record);
const basePath = field.address.concat(record.__index || index);
return (
-
+
@@ -261,6 +261,8 @@ const TableIndex = (props) => {
);
};
+const pageSizeOptions = [5, 10, 20, 50, 100, 200];
+
const usePaginationProps = (pagination1, pagination2) => {
const { t } = useTranslation();
const field: any = useField();
@@ -279,12 +281,14 @@ const usePaginationProps = (pagination1, pagination2) => {
const result = useMemo(() => {
if (totalCount) {
return {
+ pageSizeOptions,
showTotal,
showSizeChanger: true,
...pagination,
};
} else {
return {
+ pageSizeOptions,
showTotal: false,
simple: true,
showTitle: false,
diff --git a/packages/core/client/src/schema-component/antd/table-v2/TableBlockDesigner.tsx b/packages/core/client/src/schema-component/antd/table-v2/TableBlockDesigner.tsx
index 83a75537e8..9d04b52971 100644
--- a/packages/core/client/src/schema-component/antd/table-v2/TableBlockDesigner.tsx
+++ b/packages/core/client/src/schema-component/antd/table-v2/TableBlockDesigner.tsx
@@ -282,6 +282,7 @@ export const TableBlockDesigner = () => {
title={t('Records per page')}
value={field.decoratorProps?.params?.pageSize || 20}
options={[
+ { label: '5', value: 5 },
{ label: '10', value: 10 },
{ label: '20', value: 20 },
{ label: '50', value: 50 },
diff --git a/packages/core/client/src/schema-component/antd/table-v2/TableSelectorDesigner.tsx b/packages/core/client/src/schema-component/antd/table-v2/TableSelectorDesigner.tsx
index cb8562f354..951e3d92ed 100644
--- a/packages/core/client/src/schema-component/antd/table-v2/TableSelectorDesigner.tsx
+++ b/packages/core/client/src/schema-component/antd/table-v2/TableSelectorDesigner.tsx
@@ -237,6 +237,7 @@ export const TableSelectorDesigner = () => {
title={t('Records per page')}
value={field.decoratorProps?.params?.pageSize || 20}
options={[
+ { label: '5', value: 5 },
{ label: '10', value: 10 },
{ label: '20', value: 20 },
{ label: '50', value: 50 },
diff --git a/packages/core/client/src/schema-component/antd/table/Table.Void.Designer.tsx b/packages/core/client/src/schema-component/antd/table/Table.Void.Designer.tsx
index 22adb1b741..45d32c3597 100644
--- a/packages/core/client/src/schema-component/antd/table/Table.Void.Designer.tsx
+++ b/packages/core/client/src/schema-component/antd/table/Table.Void.Designer.tsx
@@ -194,6 +194,7 @@ export const TableVoidDesigner = () => {
title={'每页显示'}
value={field.decoratorProps.request.params?.pageSize || 20}
options={[
+ { label: '5', value: 5 },
{ label: '10', value: 10 },
{ label: '20', value: 20 },
{ label: '50', value: 50 },
diff --git a/packages/core/client/src/schema-component/antd/table/Table.Void.tsx b/packages/core/client/src/schema-component/antd/table/Table.Void.tsx
index b61d56f17e..cd4fa12c4c 100644
--- a/packages/core/client/src/schema-component/antd/table/Table.Void.tsx
+++ b/packages/core/client/src/schema-component/antd/table/Table.Void.tsx
@@ -26,6 +26,8 @@ type TableVoidProps = TableProps & {
) => Result & { state?: any; setState?: any };
};
+const pageSizeOptions = [5, 10, 20, 50, 100, 200];
+
const usePaginationProps = (props: TableProps & { request?: any }, service): TablePaginationConfig | false => {
if (props.pagination === false) {
return false;
@@ -41,6 +43,7 @@ const usePaginationProps = (props: TableProps & { request?: any }, service)
pagination.pageSize = pageSize;
}
return {
+ pageSizeOptions,
showSizeChanger: true,
...pagination,
onChange(page, pageSize) {
diff --git a/packages/core/client/src/schema-component/antd/variable/Input.tsx b/packages/core/client/src/schema-component/antd/variable/Input.tsx
index b75aa81e59..47e2dbc6ff 100644
--- a/packages/core/client/src/schema-component/antd/variable/Input.tsx
+++ b/packages/core/client/src/schema-component/antd/variable/Input.tsx
@@ -132,14 +132,14 @@ function getTypedConstantOption(type: string, types: UseTypeConstantType, fieldN
)
: allTypes
).map((item) =>
- Object.keys(item).reduce(
+ Object.keys(fieldNames).reduce(
(result, key) =>
- fieldNames[key] in item
+ key in item
? Object.assign(result, {
[fieldNames[key]]: item[key],
})
: result,
- item,
+ { ...item },
),
);
return {
diff --git a/packages/core/client/src/schema-component/common/utils/logic.js b/packages/core/client/src/schema-component/common/utils/logic.js
index df8bb51fab..2306419801 100644
--- a/packages/core/client/src/schema-component/common/utils/logic.js
+++ b/packages/core/client/src/schema-component/common/utils/logic.js
@@ -120,9 +120,11 @@ export function getJsonLogic() {
return a.indexOf(b) !== -1;
},
$notIncludes: function (a, b) {
- if (!a || typeof a.indexOf === 'undefined') return false;
- if (Array.isArray(a)) return !a.some((element) => element.includes(b));
- return !(a.indexOf(b) !== -1);
+ if (Array.isArray(a)) return !a.some((element) => (element || '').includes(b));
+
+ a = a || '';
+
+ return !a.includes(b);
},
$anyOf: function (a, b) {
if (a.length === 0) {
diff --git a/packages/core/client/src/schema-component/common/utils/uitls.tsx b/packages/core/client/src/schema-component/common/utils/uitls.tsx
index f02e5e175f..abacb5720f 100644
--- a/packages/core/client/src/schema-component/common/utils/uitls.tsx
+++ b/packages/core/client/src/schema-component/common/utils/uitls.tsx
@@ -7,13 +7,13 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-import _, { every, findIndex, some } from 'lodash';
import Handlebars from 'handlebars';
+import _, { every, findIndex, some } from 'lodash';
+import { replaceVariableValue } from '../../../block-provider/hooks';
import { VariableOption, VariablesContextType } from '../../../variables/types';
import { isVariable } from '../../../variables/utils/isVariable';
import { transformVariableValue } from '../../../variables/utils/transformVariableValue';
import { getJsonLogic } from '../../common/utils/logic';
-import { replaceVariableValue } from '../../../block-provider/hooks';
type VariablesCtx = {
/** 当前登录的用户 */
@@ -80,10 +80,16 @@ export const conditionAnalyses = async ({
ruleGroup,
variables,
localVariables,
+ variableNameOfLeftCondition,
}: {
ruleGroup;
variables: VariablesContextType;
localVariables: VariableOption[];
+ /**
+ * used to parse the variable name of the left condition value
+ * @default '$nForm'
+ */
+ variableNameOfLeftCondition?: string;
}) => {
const type = Object.keys(ruleGroup)[0] || '$and';
const conditions = ruleGroup[type];
@@ -101,13 +107,15 @@ export const conditionAnalyses = async ({
return true;
}
- const targetVariableName = targetFieldToVariableString(getTargetField(condition));
- const targetValue = variables.parseVariable(targetVariableName, localVariables, {
- doNotRequest: true,
- });
+ const targetVariableName = targetFieldToVariableString(getTargetField(condition), variableNameOfLeftCondition);
+ const targetValue = variables
+ .parseVariable(targetVariableName, localVariables, {
+ doNotRequest: true,
+ })
+ .then(({ value }) => value);
const parsingResult = isVariable(jsonlogic?.value)
- ? [variables.parseVariable(jsonlogic?.value, localVariables), targetValue]
+ ? [variables.parseVariable(jsonlogic?.value, localVariables).then(({ value }) => value), targetValue]
: [jsonlogic?.value, targetValue];
try {
@@ -138,9 +146,9 @@ export const conditionAnalyses = async ({
* @param targetField
* @returns
*/
-export function targetFieldToVariableString(targetField: string[]) {
+export function targetFieldToVariableString(targetField: string[], variableName = '$nForm') {
// Action 中的联动规则虽然没有 form 上下文但是在这里也使用的是 `$nForm` 变量,这样实现更简单
- return `{{ $nForm.${targetField.join('.')} }}`;
+ return `{{ ${variableName}.${targetField.join('.')} }}`;
}
const getVariablesData = (localVariables) => {
diff --git a/packages/core/client/src/schema-component/hooks/useDesignable.tsx b/packages/core/client/src/schema-component/hooks/useDesignable.tsx
index 1b8b952926..c8e3e2efc2 100644
--- a/packages/core/client/src/schema-component/hooks/useDesignable.tsx
+++ b/packages/core/client/src/schema-component/hooks/useDesignable.tsx
@@ -217,6 +217,19 @@ export class Designable {
});
message.success(t('Saved successfully'), 0.2);
});
+ this.on('initializeActionContext', async ({ schema }) => {
+ this.refresh();
+ if (!schema?.['x-uid']) {
+ return;
+ }
+ await api.request({
+ url: `/uiSchemas:initializeActionContext`,
+ method: 'post',
+ data: {
+ ...schema,
+ },
+ });
+ });
this.on('batchPatch', async ({ schemas }) => {
this.refresh();
await api.request({
@@ -267,14 +280,17 @@ export class Designable {
generateUid(schema);
}
- on(name: 'insertAdjacent' | 'remove' | 'error' | 'patch' | 'batchPatch', listener: any) {
+ on(name: 'insertAdjacent' | 'remove' | 'error' | 'patch' | 'batchPatch' | 'initializeActionContext', listener: any) {
if (!this.events[name]) {
this.events[name] = [];
}
this.events[name].push(listener);
}
- async emit(name: 'insertAdjacent' | 'remove' | 'error' | 'patch' | 'batchPatch', ...args) {
+ async emit(
+ name: 'insertAdjacent' | 'remove' | 'error' | 'patch' | 'batchPatch' | 'initializeActionContext',
+ ...args
+ ) {
if (!this.events[name]) {
return;
}
diff --git a/packages/core/client/src/schema-initializer/utils.ts b/packages/core/client/src/schema-initializer/utils.ts
index bd0e03ef3d..cba8c5d018 100644
--- a/packages/core/client/src/schema-initializer/utils.ts
+++ b/packages/core/client/src/schema-initializer/utils.ts
@@ -492,25 +492,27 @@ export const useAssociatedFormItemInitializerFields = (options?: any) => {
return groups;
};
-const getItem = (
+const associationFieldToMenu = (
field: FieldOptions,
schemaName: string,
collectionName: string,
getCollectionFields,
processedCollections: string[],
) => {
- if (field.interface === 'm2o') {
- if (processedCollections.includes(field.target)) return null;
+ if (field.target && field.uiSchema) {
+ if (processedCollections.includes(field.target) || processedCollections.length >= 1) return;
const subFields = getCollectionFields(field.target);
+ if (!subFields?.length) return;
+
return {
type: 'subMenu',
name: schemaName,
title: field.uiSchema?.title,
children: subFields
.map((subField) =>
- getItem(subField, `${schemaName}.${subField.name}`, collectionName, getCollectionFields, [
+ associationFieldToMenu(subField, `${schemaName}.${subField.name}`, collectionName, getCollectionFields, [
...processedCollections,
field.target,
]),
@@ -519,12 +521,11 @@ const getItem = (
} as SchemaInitializerItemType;
}
- if (isAssocField(field)) return null;
+ if (!field.uiSchema) return;
const schema = {
type: 'string',
name: schemaName,
- // 'x-designer': 'FormItem.FilterFormDesigner',
'x-toolbar': 'FormItemSchemaToolbar',
'x-settings': 'fieldSettings:FilterFormItem',
'x-designer-props': {
@@ -551,13 +552,10 @@ const getItem = (
export const useFilterAssociatedFormItemInitializerFields = () => {
const { name, fields } = useCollection_deprecated();
const { getCollectionFields } = useCollectionManager_deprecated();
- const interfaces = ['m2o'];
- const groups = fields
- ?.filter((field) => {
- return interfaces.includes(field.interface);
- })
- ?.map((field) => getItem(field, field.name, name, getCollectionFields, []));
- return groups;
+ return fields
+ ?.filter((field) => field.target && field.uiSchema)
+ .map((field) => associationFieldToMenu(field, field.name, name, getCollectionFields, []))
+ .filter(Boolean);
};
export const useInheritsFormItemInitializerFields = (options?) => {
diff --git a/packages/core/client/src/schema-items/GeneralSchemaItems.tsx b/packages/core/client/src/schema-items/GeneralSchemaItems.tsx
index 89d96e42fa..926a5e8802 100644
--- a/packages/core/client/src/schema-items/GeneralSchemaItems.tsx
+++ b/packages/core/client/src/schema-items/GeneralSchemaItems.tsx
@@ -14,8 +14,8 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { useCollection_deprecated, useCollectionManager_deprecated } from '../collection-manager';
import { useDesignable } from '../schema-component';
-import { getTempFieldState } from '../schema-component/antd/form-v2/utils';
import { SchemaSettingsModalItem, SchemaSettingsSwitchItem } from '../schema-settings';
+import { getTempFieldState } from '../schema-settings/LinkageRules/bindLinkageRulesToFiled';
export const GeneralSchemaItems: React.FC<{
required?: boolean;
diff --git a/packages/core/client/src/schema-settings/LinkageRules/LinkageRuleActionGroup.tsx b/packages/core/client/src/schema-settings/LinkageRules/LinkageRuleActionGroup.tsx
index ba9aba450e..aee60dbb8d 100644
--- a/packages/core/client/src/schema-settings/LinkageRules/LinkageRuleActionGroup.tsx
+++ b/packages/core/client/src/schema-settings/LinkageRules/LinkageRuleActionGroup.tsx
@@ -68,6 +68,7 @@ export const LinkageRuleActionGroup = withDynamicSchemaProps(
const items = f.value || [];
items.push({});
f.value = items;
+ f.initialValue = items;
}, [field]);
return (
diff --git a/packages/core/client/src/schema-settings/LinkageRules/ValueDynamicComponent.tsx b/packages/core/client/src/schema-settings/LinkageRules/ValueDynamicComponent.tsx
index 5fd7647838..f7bf01bf99 100644
--- a/packages/core/client/src/schema-settings/LinkageRules/ValueDynamicComponent.tsx
+++ b/packages/core/client/src/schema-settings/LinkageRules/ValueDynamicComponent.tsx
@@ -9,7 +9,7 @@
import { Input, Select } from 'antd';
import { css } from '@emotion/css';
-import React, { useCallback, useMemo, useState } from 'react';
+import React, { useCallback, useMemo, useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useFormBlockContext } from '../../block-provider/FormBlockProvider';
import { useRecord } from '../../record-provider';
@@ -50,6 +50,11 @@ export const ValueDynamicComponent = (props: ValueDynamicComponentProps) => {
const constantStyle = useMemo(() => {
return { minWidth: 150, maxWidth: 430 };
}, []);
+
+ useEffect(() => {
+ setMode(fieldValue?.mode || 'constant');
+ }, [fieldValue?.mode]);
+
const handleChangeOfConstant = useCallback(
(value) => {
setValue({
@@ -160,7 +165,7 @@ export const ValueDynamicComponent = (props: ValueDynamicComponentProps) => {
))}
- {modeMap[fieldValue?.mode || mode]}
+ {modeMap[mode]}
);
};
diff --git a/packages/core/client/src/schema-settings/LinkageRules/bindLinkageRulesToFiled.ts b/packages/core/client/src/schema-settings/LinkageRules/bindLinkageRulesToFiled.ts
new file mode 100644
index 0000000000..fefb4e3e8f
--- /dev/null
+++ b/packages/core/client/src/schema-settings/LinkageRules/bindLinkageRulesToFiled.ts
@@ -0,0 +1,417 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+import { Field } from '@formily/core';
+import { reaction } from '@formily/reactive';
+import evaluators from '@nocobase/evaluators/client';
+import { getValuesByPath, uid } from '@nocobase/utils/client';
+import _ from 'lodash';
+import { conditionAnalyses, getInnermostKeyAndValue, getTargetField } from '../../schema-component/common/utils/uitls';
+import { VariableOption, VariablesContextType } from '../../variables/types';
+import { getPath } from '../../variables/utils/getPath';
+import { getVariableName } from '../../variables/utils/getVariableName';
+import {
+ getVariablesFromExpression,
+ isVariable,
+ REGEX_OF_VARIABLE_IN_EXPRESSION,
+} from '../../variables/utils/isVariable';
+import { ActionType } from './type';
+
+interface Props {
+ operator;
+ value;
+ field: Field & {
+ [key: string]: any;
+ };
+ condition;
+ variables: VariablesContextType;
+ localVariables: VariableOption[];
+ /**
+ * used to parse the variable name of the left condition value
+ * @default '$nForm'
+ */
+ variableNameOfLeftCondition?: string;
+}
+
+export function bindLinkageRulesToFiled({
+ field,
+ linkageRules,
+ formValues,
+ localVariables,
+ action,
+ rule,
+ variables,
+ variableNameOfLeftCondition,
+}: {
+ field: any;
+ linkageRules: any[];
+ formValues: any;
+ localVariables: VariableOption[];
+ action: any;
+ rule: any;
+ variables: VariablesContextType;
+ /**
+ * used to parse the variable name of the left condition value
+ * @default '$nForm'
+ */
+ variableNameOfLeftCondition?: string;
+}) {
+ field['initStateOfLinkageRules'] = {
+ display: field.initStateOfLinkageRules?.display || getTempFieldState(true, field.display),
+ required: field.initStateOfLinkageRules?.required || getTempFieldState(true, field.required || false),
+ pattern: field.initStateOfLinkageRules?.pattern || getTempFieldState(true, field.pattern),
+ value: field.initStateOfLinkageRules?.value || getTempFieldState(true, field.value || field.initialValue),
+ };
+
+ return reaction(
+ // 这里共依赖 3 部分,当这 3 部分中的任意一部分发生变更后,需要触发联动规则:
+ // 1. 条件中的字段值;
+ // 2. 条件中的变量值;
+ // 3. value 表达式中的变量值;
+ () => {
+ // 获取条件中的字段值
+ const fieldValuesInCondition = getFieldValuesInCondition({ linkageRules, formValues });
+
+ // 获取条件中的变量值
+ const variableValuesInCondition = getVariableValuesInCondition({ linkageRules, localVariables });
+
+ // 获取 value 表达式中的变量值
+ const variableValuesInExpression = getVariableValuesInExpression({ action, localVariables });
+
+ const result = [fieldValuesInCondition, variableValuesInCondition, variableValuesInExpression]
+ .map((item) => JSON.stringify(item))
+ .join(',');
+ return result;
+ },
+ getSubscriber({ action, field, rule, variables, localVariables, variableNameOfLeftCondition }),
+ { fireImmediately: true, equals: _.isEqual },
+ );
+}
+
+function getFieldValuesInCondition({ linkageRules, formValues }) {
+ return linkageRules.map((rule) => {
+ const run = (condition) => {
+ const type = Object.keys(condition)[0] || '$and';
+ const conditions = condition[type];
+
+ return conditions
+ .map((condition) => {
+ // fix https://nocobase.height.app/T-3251
+ if ('$and' in condition || '$or' in condition) {
+ return run(condition);
+ }
+
+ const path = getTargetField(condition).join('.');
+ return getValuesByPath(formValues, path);
+ })
+ .filter(Boolean);
+ };
+
+ return run(rule.condition);
+ });
+}
+
+function getVariableValuesInCondition({
+ linkageRules,
+ localVariables,
+}: {
+ linkageRules: any[];
+ localVariables: VariableOption[];
+}) {
+ return linkageRules.map((rule) => {
+ const type = Object.keys(rule.condition)[0] || '$and';
+ const conditions = rule.condition[type];
+
+ return conditions
+ .map((condition) => {
+ const jsonlogic = getInnermostKeyAndValue(condition);
+ if (!jsonlogic) {
+ return null;
+ }
+ if (isVariable(jsonlogic.value)) {
+ return getVariableValue(jsonlogic.value, localVariables);
+ }
+
+ return jsonlogic.value;
+ })
+ .filter(Boolean);
+ });
+}
+
+function getVariableValuesInExpression({ action, localVariables }) {
+ const actionValue = action.value;
+ const mode = actionValue?.mode;
+ const value = actionValue?.value || actionValue?.result;
+
+ if (mode !== 'express') {
+ return;
+ }
+
+ if (value == null) {
+ return;
+ }
+
+ return getVariablesFromExpression(value)
+ ?.map((variableString: string) => {
+ return getVariableValue(variableString, localVariables);
+ })
+ .filter(Boolean);
+}
+
+function getVariableValue(variableString: string, localVariables: VariableOption[]) {
+ if (!isVariable(variableString)) {
+ return;
+ }
+
+ const variableName = getVariableName(variableString);
+ const ctx = {
+ [variableName]: localVariables.find((item) => item.name === variableName)?.ctx,
+ };
+
+ return getValuesByPath(ctx, getPath(variableString));
+}
+
+function getSubscriber({
+ action,
+ field,
+ rule,
+ variables,
+ localVariables,
+ variableNameOfLeftCondition,
+}: {
+ action: any;
+ field: any;
+ rule: any;
+ variables: VariablesContextType;
+ localVariables: VariableOption[];
+ /**
+ * used to parse the variable name of the left condition value
+ * @default '$nForm'
+ */
+ variableNameOfLeftCondition?: string;
+}): (value: string, oldValue: string) => void {
+ return () => {
+ // 当条件改变触发 reaction 时,会同步收集字段状态,并保存到 field.stateOfLinkageRules 中
+ collectFieldStateOfLinkageRules({
+ operator: action.operator,
+ value: action.value,
+ field,
+ condition: rule.condition,
+ variables,
+ localVariables,
+ variableNameOfLeftCondition,
+ });
+
+ // 当条件改变时,有可能会触发多个 reaction,所以这里需要延迟一下,确保所有的 reaction 都执行完毕后,
+ // 再从 field.stateOfLinkageRules 中取值,因为此时 field.stateOfLinkageRules 中的值才是全的。
+ setTimeout(async () => {
+ const fieldName = getFieldNameByOperator(action.operator);
+
+ // 防止重复赋值
+ if (!field.stateOfLinkageRules?.[fieldName]) {
+ return;
+ }
+
+ let stateList = field.stateOfLinkageRules[fieldName];
+
+ stateList = await Promise.all(stateList);
+ stateList = stateList.filter((v) => v.condition);
+
+ const lastState = stateList[stateList.length - 1];
+
+ if (fieldName === 'value') {
+ // value 比较特殊,它只有在匹配条件时才需要赋值,当条件不匹配时,维持现在的值;
+ // stateList 中肯定会有一个初始值,所以当 stateList.length > 1 时,就说明有匹配条件的情况;
+ if (stateList.length > 1) {
+ field.value = lastState.value;
+ }
+ } else {
+ field[fieldName] = lastState?.value;
+ requestAnimationFrame(() => {
+ field.setState((state) => {
+ state[fieldName] = lastState?.value;
+ });
+ });
+ //字段隐藏时清空数据
+ if (fieldName === 'display' && lastState?.value === 'none') {
+ field.value = null;
+ }
+ }
+ // 在这里清空 field.stateOfLinkageRules,就可以保证:当条件再次改变时,如果该字段没有和任何条件匹配,则需要把对应的值恢复到初始值;
+ field.stateOfLinkageRules[fieldName] = null;
+ });
+ };
+}
+
+function getFieldNameByOperator(operator: ActionType) {
+ switch (operator) {
+ case ActionType.Required:
+ case ActionType.InRequired:
+ return 'required';
+ case ActionType.Visible:
+ case ActionType.None:
+ case ActionType.Hidden:
+ return 'display';
+ case ActionType.Editable:
+ case ActionType.ReadOnly:
+ case ActionType.ReadPretty:
+ return 'pattern';
+ case ActionType.Value:
+ return 'value';
+ default:
+ return null;
+ }
+}
+
+export const collectFieldStateOfLinkageRules = ({
+ operator,
+ value,
+ field,
+ condition,
+ variables,
+ localVariables,
+ variableNameOfLeftCondition,
+}: Props) => {
+ const requiredResult = field?.stateOfLinkageRules?.required || [field?.initStateOfLinkageRules?.required];
+ const displayResult = field?.stateOfLinkageRules?.display || [field?.initStateOfLinkageRules?.display];
+ const patternResult = field?.stateOfLinkageRules?.pattern || [field?.initStateOfLinkageRules?.pattern];
+ const valueResult = field?.stateOfLinkageRules?.value || [field?.initStateOfLinkageRules?.value];
+ const { evaluate } = evaluators.get('formula.js');
+ const paramsToGetConditionResult = { ruleGroup: condition, variables, localVariables, variableNameOfLeftCondition };
+
+ switch (operator) {
+ case ActionType.Required:
+ requiredResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult), true));
+ field.stateOfLinkageRules = {
+ ...field.stateOfLinkageRules,
+ required: requiredResult,
+ };
+ break;
+ case ActionType.InRequired:
+ requiredResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult), false));
+ field.stateOfLinkageRules = {
+ ...field.stateOfLinkageRules,
+ required: requiredResult,
+ };
+ break;
+ case ActionType.Visible:
+ case ActionType.None:
+ case ActionType.Hidden:
+ displayResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult), operator));
+ field.stateOfLinkageRules = {
+ ...field.stateOfLinkageRules,
+ display: displayResult,
+ };
+ break;
+ case ActionType.Editable:
+ case ActionType.ReadOnly:
+ case ActionType.ReadPretty:
+ patternResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult), operator));
+ field.stateOfLinkageRules = {
+ ...field.stateOfLinkageRules,
+ pattern: patternResult,
+ };
+ break;
+ case ActionType.Value:
+ {
+ const getValue = async () => {
+ if (value?.mode === 'express') {
+ if ((value.value || value.result) == null) {
+ return;
+ }
+
+ // 解析如 `{{$user.name}}` 之类的变量
+ const { exp, scope: expScope } = await replaceVariables(value.value || value.result, {
+ variables,
+ localVariables,
+ });
+
+ try {
+ const result = evaluate(exp, { now: () => new Date().toString(), ...expScope });
+ return result;
+ } catch (error) {
+ console.error(error);
+ }
+ } else if (value?.mode === 'constant') {
+ return value?.value ?? value;
+ } else {
+ return null;
+ }
+ };
+ if (isConditionEmpty(condition)) {
+ valueResult.push(getTempFieldState(true, getValue()));
+ } else {
+ valueResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult), getValue()));
+ }
+ field.stateOfLinkageRules = {
+ ...field.stateOfLinkageRules,
+ value: valueResult,
+ };
+ }
+ break;
+ default:
+ return null;
+ }
+}; /**
+ * 获取字段临时状态对象
+ */
+
+export async function getTempFieldState(condition: boolean | Promise, value: any) {
+ [condition, value] = await Promise.all([condition, value]);
+
+ return {
+ condition,
+ value,
+ };
+}
+
+function isConditionEmpty(rules: { $and?: any; $or?: any }) {
+ const type = Object.keys(rules)[0] || '$and';
+ const conditions = rules[type];
+
+ return _.isEmpty(conditions);
+}
+export async function replaceVariables(
+ value: string,
+ {
+ variables,
+ localVariables,
+ }: {
+ variables: VariablesContextType;
+ localVariables: VariableOption[];
+ },
+) {
+ const store = {};
+ const scope = {};
+
+ if (value == null) {
+ return;
+ }
+
+ const waitForParsing = value.match(REGEX_OF_VARIABLE_IN_EXPRESSION)?.map(async (item) => {
+ const { value: parsedValue } = await variables.parseVariable(item, localVariables);
+
+ // 在开头加 `_` 是为了保证 id 不能以数字开头,否则在解析表达式的时候(不是解析变量)会报错
+ const id = `_${uid()}`;
+
+ scope[id] = parsedValue;
+ store[item] = id;
+ return parsedValue;
+ });
+
+ if (waitForParsing) {
+ await Promise.all(waitForParsing);
+ }
+ return {
+ exp: value.replace(REGEX_OF_VARIABLE_IN_EXPRESSION, (match) => {
+ return `{{${store[match] || match}}}`;
+ }),
+ scope,
+ };
+}
diff --git a/packages/core/client/src/schema-settings/LinkageRules/forEachLinkageRule.ts b/packages/core/client/src/schema-settings/LinkageRules/forEachLinkageRule.ts
new file mode 100644
index 0000000000..abb96ef553
--- /dev/null
+++ b/packages/core/client/src/schema-settings/LinkageRules/forEachLinkageRule.ts
@@ -0,0 +1,16 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+export function forEachLinkageRule(linkageRules: any[], callback: (action: any, rule: any) => void) {
+ linkageRules.forEach((rule) => {
+ rule.actions?.forEach((action) => {
+ callback(action, rule);
+ });
+ });
+}
diff --git a/packages/core/client/src/schema-settings/LinkageRules/index.tsx b/packages/core/client/src/schema-settings/LinkageRules/index.tsx
index 4b29f7569e..ce8cbc0935 100644
--- a/packages/core/client/src/schema-settings/LinkageRules/index.tsx
+++ b/packages/core/client/src/schema-settings/LinkageRules/index.tsx
@@ -13,6 +13,7 @@ import React, { useMemo } from 'react';
import { FormBlockContext } from '../../block-provider/FormBlockProvider';
import { useCollectionManager_deprecated } from '../../collection-manager';
import { useCollectionParentRecordData } from '../../data-source/collection-record/CollectionRecordProvider';
+import { CollectionProvider } from '../../data-source/collection/CollectionProvider';
import { withDynamicSchemaProps } from '../../hoc/withDynamicSchemaProps';
import { RecordProvider } from '../../record-provider';
import { SchemaComponent, useProps } from '../../schema-component';
@@ -23,7 +24,7 @@ import { LinkageRuleActionGroup } from './LinkageRuleActionGroup';
import { EnableLinkage } from './components/EnableLinkage';
import { ArrayCollapse } from './components/LinkageHeader';
-interface Props {
+export interface Props {
dynamicComponent: any;
}
@@ -178,7 +179,9 @@ export const FormLinkageRules = withDynamicSchemaProps(
-
+
+
+
diff --git a/packages/core/client/src/schema-settings/SchemaSettings.tsx b/packages/core/client/src/schema-settings/SchemaSettings.tsx
index c8afc6a68b..117d2eda1f 100644
--- a/packages/core/client/src/schema-settings/SchemaSettings.tsx
+++ b/packages/core/client/src/schema-settings/SchemaSettings.tsx
@@ -1007,7 +1007,7 @@ export const SchemaSettingsDefaultSortingRules = function DefaultSortingRules(pr
};
export const SchemaSettingsLinkageRules = function LinkageRules(props) {
- const { collectionName, readPretty } = props;
+ const { collectionName, readPretty, Component, afterSubmit } = props;
const fieldSchema = useFieldSchema();
const { form } = useFormBlockContext();
const { dn } = useDesignable();
@@ -1040,7 +1040,7 @@ export const SchemaSettingsLinkageRules = function LinkageRules(props) {
title,
properties: {
fieldReaction: {
- 'x-component': FormLinkageRules,
+ 'x-component': Component || FormLinkageRules,
'x-use-component-props': () => {
return {
options,
@@ -1059,7 +1059,7 @@ export const SchemaSettingsLinkageRules = function LinkageRules(props) {
},
},
}),
- [collectionName, fieldSchema, form, gridSchema, localVariables, record, t, variables, getRules],
+ [collectionName, fieldSchema, form, gridSchema, localVariables, record, t, variables, getRules, Component],
);
const components = useMemo(() => ({ ArrayCollapse, FormLayout }), []);
const onSubmit = useCallback(
@@ -1079,8 +1079,9 @@ export const SchemaSettingsLinkageRules = function LinkageRules(props) {
schema,
});
dn.refresh();
+ afterSubmit?.();
},
- [dn, getTemplateById, gridSchema, dataKey],
+ [dn, getTemplateById, gridSchema, dataKey, afterSubmit],
);
return (
diff --git a/packages/core/client/src/schema-settings/SchemaSettingsRenderEngine.tsx b/packages/core/client/src/schema-settings/SchemaSettingsRenderEngine.tsx
index 602f6c54ae..baa38e9f0e 100644
--- a/packages/core/client/src/schema-settings/SchemaSettingsRenderEngine.tsx
+++ b/packages/core/client/src/schema-settings/SchemaSettingsRenderEngine.tsx
@@ -38,6 +38,7 @@ export function SchemaSettingsRenderEngine() {
options={options}
value={field.decoratorProps.engine || 'string'}
onChange={(engine) => {
+ fieldSchema['x-decorator-props'] = fieldSchema['x-decorator-props'] || {};
fieldSchema['x-decorator-props'].engine = engine;
field.decoratorProps.engine = engine;
dn.emit('patch', {
diff --git a/packages/core/client/src/schema-settings/hooks/useParseDataScopeFilter.ts b/packages/core/client/src/schema-settings/hooks/useParseDataScopeFilter.ts
index 3699841566..dfc29cae03 100644
--- a/packages/core/client/src/schema-settings/hooks/useParseDataScopeFilter.ts
+++ b/packages/core/client/src/schema-settings/hooks/useParseDataScopeFilter.ts
@@ -55,7 +55,7 @@ const useParseDataScopeFilter = ({ exclude = defaultExclude }: Props = {}) => {
if (exclude.includes(getVariableName(value))) {
return value;
}
- const result = variables?.parseVariable(value, localVariables);
+ const result = variables?.parseVariable(value, localVariables).then(({ value }) => value);
return result;
},
});
diff --git a/packages/core/client/src/system-settings/SystemSettingsShortcut.tsx b/packages/core/client/src/system-settings/SystemSettingsShortcut.tsx
index d9751d38af..c97703007e 100644
--- a/packages/core/client/src/system-settings/SystemSettingsShortcut.tsx
+++ b/packages/core/client/src/system-settings/SystemSettingsShortcut.tsx
@@ -92,7 +92,7 @@ const schema: ISchema = {
type: 'string',
title: "{{t('System title')}}",
'x-decorator': 'FormItem',
- 'x-component': 'Input',
+ 'x-component': 'Input.TextArea',
required: true,
},
logo: {
diff --git a/packages/core/client/src/variables/VariablesProvider.tsx b/packages/core/client/src/variables/VariablesProvider.tsx
index 93cd3be3fa..7cc56b0d11 100644
--- a/packages/core/client/src/variables/VariablesProvider.tsx
+++ b/packages/core/client/src/variables/VariablesProvider.tsx
@@ -69,7 +69,7 @@ const VariablesProvider = ({ children }) => {
* 2. 如果某个 `key` 不存在,且 `key` 是一个关联字段,则从 api 中获取数据,并缓存到 `ctx` 中
* 3. 如果某个 `key` 不存在,且 `key` 不是一个关联字段,则返回当前值
*/
- const getValue = useCallback(
+ const getResult = useCallback(
async (
variablePath: string,
localVariables?: VariableOption[],
@@ -87,13 +87,23 @@ const VariablesProvider = ({ children }) => {
const { fieldPath, dataSource, variableOption } = getFieldPath(variableName, _variableToCollectionName);
let collectionName = fieldPath;
+ const { fieldPath: fieldPathOfVariable } = getFieldPath(variablePath, _variableToCollectionName);
+ const collectionNameOfVariable =
+ list.length === 1
+ ? variableOption.collectionName
+ : getCollectionJoinField(fieldPathOfVariable, dataSource)?.target;
+
if (!(variableName in current)) {
throw new Error(`VariablesProvider: ${variableName} is not found`);
}
for (let index = 0; index < list.length; index++) {
if (current == null) {
- return current === undefined ? variableOption.defaultValue : current;
+ return {
+ value: current === undefined ? variableOption.defaultValue : current,
+ dataSource,
+ collectionName: collectionNameOfVariable,
+ };
}
const key = list[index];
@@ -171,8 +181,12 @@ const VariablesProvider = ({ children }) => {
}
}
- const result = compile(_.isFunction(current) ? current() : current);
- return result === undefined ? variableOption.defaultValue : result;
+ const _value = compile(_.isFunction(current) ? current() : current);
+ return {
+ value: _value === undefined ? variableOption.defaultValue : _value,
+ dataSource,
+ collectionName: collectionNameOfVariable,
+ };
},
[getCollectionJoinField],
);
@@ -248,11 +262,14 @@ const VariablesProvider = ({ children }) => {
}
const path = getPath(str);
- const value = await getValue(path, localVariables as VariableOption[], options);
+ const result = await getResult(path, localVariables as VariableOption[], options);
- return uniq(filterEmptyValues(value));
+ return {
+ ...result,
+ value: uniq(filterEmptyValues(result.value)),
+ };
},
- [getValue],
+ [getResult],
);
const getCollectionField = useCallback(
diff --git a/packages/core/client/src/variables/__tests__/useVariables.test.tsx b/packages/core/client/src/variables/__tests__/useVariables.test.tsx
index 69c016e740..459da4ba7a 100644
--- a/packages/core/client/src/variables/__tests__/useVariables.test.tsx
+++ b/packages/core/client/src/variables/__tests__/useVariables.test.tsx
@@ -276,7 +276,7 @@ describe('useVariables', () => {
});
await waitFor(async () => {
- expect(await result.current.parseVariable('{{ $user.nickname }}')).toBe('test');
+ expect(await result.current.parseVariable('{{ $user.nickname }}').then(({ value }) => value)).toBe('test');
});
});
@@ -286,7 +286,9 @@ describe('useVariables', () => {
});
await waitFor(async () => {
- expect(await result.current.parseVariable('{{ $user.nickname }}')).toBe('from request');
+ expect(await result.current.parseVariable('{{ $user.nickname }}').then(({ value }) => value)).toBe(
+ 'from request',
+ );
});
});
@@ -296,7 +298,7 @@ describe('useVariables', () => {
});
await waitFor(async () => {
- expect(await result.current.parseVariable('{{ $user.belongsToField }}')).toEqual({
+ expect(await result.current.parseVariable('{{ $user.belongsToField }}').then(({ value }) => value)).toEqual({
id: 0,
name: '$user.belongsToField',
});
@@ -309,9 +311,11 @@ describe('useVariables', () => {
});
await waitFor(async () => {
- expect(await result.current.parseVariable('{{ $user.belongsToField }}', undefined, { doNotRequest: true })).toBe(
- null,
- );
+ expect(
+ await result.current
+ .parseVariable('{{ $user.belongsToField }}', undefined, { doNotRequest: true })
+ .then(({ value }) => value),
+ ).toBe(null);
});
});
@@ -321,7 +325,9 @@ describe('useVariables', () => {
});
await waitFor(async () => {
- expect(await result.current.parseVariable('{{ $user.belongsToField.name }}')).toBe('$user.belongsToField');
+ expect(await result.current.parseVariable('{{ $user.belongsToField.name }}').then(({ value }) => value)).toBe(
+ '$user.belongsToField',
+ );
});
});
@@ -331,7 +337,7 @@ describe('useVariables', () => {
});
await waitFor(async () => {
- expect(await result.current.parseVariable('{{ $user.hasManyField }}')).toEqual([
+ expect(await result.current.parseVariable('{{ $user.hasManyField }}').then(({ value }) => value)).toEqual([
{
id: 0,
name: '$user.hasManyField',
@@ -340,7 +346,9 @@ describe('useVariables', () => {
});
await waitFor(async () => {
- expect(await result.current.parseVariable('{{ $user.hasManyField.name }}')).toEqual(['$user.hasManyField']);
+ expect(await result.current.parseVariable('{{ $user.hasManyField.name }}').then(({ value }) => value)).toEqual([
+ '$user.hasManyField',
+ ]);
});
});
@@ -350,7 +358,9 @@ describe('useVariables', () => {
});
await waitFor(async () => {
- expect(await result.current.parseVariable('{{ $user.hasManyField.hasManyField }}')).toEqual([
+ expect(
+ await result.current.parseVariable('{{ $user.hasManyField.hasManyField }}').then(({ value }) => value),
+ ).toEqual([
{
id: 0,
name: '$user.hasManyField.hasManyField',
@@ -359,15 +369,34 @@ describe('useVariables', () => {
});
});
+ it('$user.hasManyField', async () => {
+ const { result } = renderHook(() => useVariables(), {
+ wrapper: Providers,
+ });
+
+ await waitFor(async () => {
+ expect(await result.current.parseVariable('{{ $user.hasManyField }}')).toEqual({
+ collectionName: 'test',
+ dataSource: 'main',
+ value: [
+ {
+ id: 0,
+ name: '$user.hasManyField',
+ },
+ ],
+ });
+ });
+ });
+
it('$user.hasManyField.hasManyField.name', async () => {
const { result } = renderHook(() => useVariables(), {
wrapper: Providers,
});
await waitFor(async () => {
- expect(await result.current.parseVariable('{{ $user.hasManyField.hasManyField.name }}')).toEqual([
- '$user.hasManyField.hasManyField',
- ]);
+ expect(
+ await result.current.parseVariable('{{ $user.hasManyField.hasManyField.name }}').then(({ value }) => value),
+ ).toEqual(['$user.hasManyField.hasManyField']);
});
});
@@ -393,7 +422,7 @@ describe('useVariables', () => {
});
await waitFor(async () => {
- expect(await result.current.parseVariable('{{ $user.hasManyField }}')).toEqual([
+ expect(await result.current.parseVariable('{{ $user.hasManyField }}').then(({ value }) => value)).toEqual([
{
id: 0,
name: '$user.hasManyField',
@@ -402,7 +431,9 @@ describe('useVariables', () => {
});
await waitFor(async () => {
- expect(await result.current.parseVariable('{{ $user.hasManyField.hasManyField }}')).toEqual([
+ expect(
+ await result.current.parseVariable('{{ $user.hasManyField.hasManyField }}').then(({ value }) => value),
+ ).toEqual([
{
id: 0,
name: '$user.hasManyField.hasManyField',
@@ -417,7 +448,7 @@ describe('useVariables', () => {
});
await waitFor(async () => {
- expect(await result.current.parseVariable('{{ $user.belongsToManyField }}')).toEqual([
+ expect(await result.current.parseVariable('{{ $user.belongsToManyField }}').then(({ value }) => value)).toEqual([
{
id: 0,
name: '$user.belongsToManyField',
@@ -608,7 +639,7 @@ describe('useVariables', () => {
});
await waitFor(async () => {
- expect(await result.current.parseVariable('{{ $new.name }}')).toBe('new variable');
+ expect(await result.current.parseVariable('{{ $new.name }}').then(({ value }) => value)).toBe('new variable');
});
});
@@ -627,7 +658,7 @@ describe('useVariables', () => {
});
await waitFor(async () => {
- expect(await result.current.parseVariable('{{ $new.noExist }}')).toBe(null);
+ expect(await result.current.parseVariable('{{ $new.noExist }}').then(({ value }) => value)).toBe(null);
});
});
@@ -647,7 +678,7 @@ describe('useVariables', () => {
});
await waitFor(async () => {
- expect(await result.current.parseVariable('{{ $new.noExist }}')).toBe('default value');
+ expect(await result.current.parseVariable('{{ $new.noExist }}').then(({ value }) => value)).toBe('default value');
});
});
@@ -667,7 +698,7 @@ describe('useVariables', () => {
});
await waitFor(async () => {
- expect(await result.current.parseVariable('{{ $new.noExist }}')).toBe(undefined);
+ expect(await result.current.parseVariable('{{ $new.noExist }}').then(({ value }) => value)).toBe(undefined);
});
});
@@ -686,8 +717,30 @@ describe('useVariables', () => {
ctx: {
name: 'local variable',
},
+ collectionName: 'local',
+ dataSource: 'local',
}),
- ).toBe('local variable');
+ ).toEqual({
+ value: 'local variable',
+ dataSource: 'local',
+ });
+
+ expect(
+ await result.current.parseVariable('{{ $local }}', {
+ name: '$local',
+ ctx: {
+ name: 'local variable',
+ },
+ collectionName: 'local',
+ dataSource: 'local',
+ }),
+ ).toEqual({
+ value: {
+ name: 'local variable',
+ },
+ collectionName: 'local',
+ dataSource: 'local',
+ });
// 由于 $local 是一个局部变量,所以不会被缓存到 ctx 中
expect(result.current.getVariable('$local')).toBe(null);
@@ -703,14 +756,16 @@ describe('useVariables', () => {
});
expect(
- await result.current.parseVariable('{{ $local.name }}', [
- {
- name: '$local',
- ctx: {
- name: 'local variable',
+ await result.current
+ .parseVariable('{{ $local.name }}', [
+ {
+ name: '$local',
+ ctx: {
+ name: 'local variable',
+ },
},
- },
- ]),
+ ])
+ .then(({ value }) => value),
).toBe('local variable');
// 由于 $local 是一个局部变量,所以不会被缓存到 ctx 中
@@ -764,7 +819,9 @@ describe('useVariables', () => {
});
await waitFor(async () => {
- expect(await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}')).toEqual({
+ expect(
+ await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}').then(({ value }) => value),
+ ).toEqual({
id: 0,
name: '$some.belongsToField.belongsToField',
});
@@ -783,7 +840,9 @@ describe('useVariables', () => {
await waitFor(async () => {
// 只有解析后的值是 undefined 才会使用默认值
- expect(await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}')).toBe(null);
+ expect(
+ await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}').then(({ value }) => value),
+ ).toBe(null);
});
// 会覆盖之前的 $some
@@ -798,7 +857,9 @@ describe('useVariables', () => {
await waitFor(async () => {
// 解析后的值是 undefined 所以会返回上面设置的默认值
- expect(await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}')).toBe('default value');
+ expect(
+ await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}').then(({ value }) => value),
+ ).toBe('default value');
});
});
diff --git a/packages/core/client/src/variables/types.ts b/packages/core/client/src/variables/types.ts
index 7387a2806b..b202ec5916 100644
--- a/packages/core/client/src/variables/types.ts
+++ b/packages/core/client/src/variables/types.ts
@@ -34,7 +34,7 @@ export interface VariablesContextType {
* @returns 变量解析后的值
*
* ```ts
- * const value = await parseVariable('{{ $user.name }}');
+ * const { value } = await parseVariable('{{ $user.name }}');
* console.log(value); // test
* ```
*/
@@ -47,7 +47,14 @@ export interface VariablesContextType {
/** do not request when the association field is empty */
doNotRequest?: boolean;
},
- ) => Promise;
+ ) => Promise<{
+ value: any;
+ /**
+ * 当前变量所对应的数据表的名称,如果为空,则表示当前变量是一个普通类型的变量(字符串、数字等)
+ */
+ collectionName?: string;
+ dataSource?: string;
+ }>;
/**
* 注册变量
* @param variableOption 新变量的配置
diff --git a/packages/core/create-nocobase-app/package.json b/packages/core/create-nocobase-app/package.json
index 08daa82746..14b0c0a3e6 100755
--- a/packages/core/create-nocobase-app/package.json
+++ b/packages/core/create-nocobase-app/package.json
@@ -1,6 +1,6 @@
{
"name": "create-nocobase-app",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"main": "src/index.js",
"license": "AGPL-3.0",
"dependencies": {
diff --git a/packages/core/data-source-manager/package.json b/packages/core/data-source-manager/package.json
index bf06b066ba..dd0d7be77c 100644
--- a/packages/core/data-source-manager/package.json
+++ b/packages/core/data-source-manager/package.json
@@ -1,16 +1,16 @@
{
"name": "@nocobase/data-source-manager",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"description": "",
"license": "AGPL-3.0",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"dependencies": {
- "@nocobase/actions": "1.3.0-beta",
- "@nocobase/cache": "1.3.0-beta",
- "@nocobase/database": "1.3.0-beta",
- "@nocobase/resourcer": "1.3.0-beta",
- "@nocobase/utils": "1.3.0-beta",
+ "@nocobase/actions": "1.3.19-beta",
+ "@nocobase/cache": "1.3.19-beta",
+ "@nocobase/database": "1.3.19-beta",
+ "@nocobase/resourcer": "1.3.19-beta",
+ "@nocobase/utils": "1.3.19-beta",
"@types/jsonwebtoken": "^8.5.8",
"jsonwebtoken": "^8.5.1"
},
diff --git a/packages/core/database/package.json b/packages/core/database/package.json
index b2453e1e64..792fc7cec0 100644
--- a/packages/core/database/package.json
+++ b/packages/core/database/package.json
@@ -1,13 +1,13 @@
{
"name": "@nocobase/database",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"description": "",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"license": "AGPL-3.0",
"dependencies": {
- "@nocobase/logger": "1.3.0-beta",
- "@nocobase/utils": "1.3.0-beta",
+ "@nocobase/logger": "1.3.19-beta",
+ "@nocobase/utils": "1.3.19-beta",
"async-mutex": "^0.3.2",
"chalk": "^4.1.1",
"cron-parser": "4.4.0",
diff --git a/packages/core/database/src/relation-repository/relation-repository.ts b/packages/core/database/src/relation-repository/relation-repository.ts
index 3f1059521a..cf7dee06f5 100644
--- a/packages/core/database/src/relation-repository/relation-repository.ts
+++ b/packages/core/database/src/relation-repository/relation-repository.ts
@@ -55,6 +55,44 @@ export abstract class RelationRepository {
return this.db.getCollection(this.targetModel.name);
}
+ abstract find(options?: FindOptions): Promise;
+
+ async chunk(
+ options: FindOptions & { chunkSize: number; callback: (rows: Model[], options: FindOptions) => Promise },
+ ) {
+ const { chunkSize, callback, limit: overallLimit } = options;
+ const transaction = await this.getTransaction(options);
+
+ let offset = 0;
+ let totalProcessed = 0;
+
+ // eslint-disable-next-line no-constant-condition
+ while (true) {
+ // Calculate the limit for the current chunk
+ const currentLimit = overallLimit !== undefined ? Math.min(chunkSize, overallLimit - totalProcessed) : chunkSize;
+
+ const rows = await this.find({
+ ...options,
+ limit: currentLimit,
+ offset,
+ transaction,
+ });
+
+ if (rows.length === 0) {
+ break;
+ }
+
+ await callback(rows, options);
+
+ offset += currentLimit;
+ totalProcessed += rows.length;
+
+ if (overallLimit !== undefined && totalProcessed >= overallLimit) {
+ break;
+ }
+ }
+ }
+
convertTk(options: any) {
let tk = options;
if (typeof options === 'object' && options['tk']) {
diff --git a/packages/core/database/src/view/field-type-map.ts b/packages/core/database/src/view/field-type-map.ts
index 8a31465a65..190a3304c8 100644
--- a/packages/core/database/src/view/field-type-map.ts
+++ b/packages/core/database/src/view/field-type-map.ts
@@ -70,7 +70,7 @@ const mysql = {
double: 'float',
boolean: 'boolean',
decimal: 'decimal',
-
+ year: ['string', 'integer'],
datetime: 'date',
timestamp: 'date',
json: ['json', 'array'],
diff --git a/packages/core/devtools/package.json b/packages/core/devtools/package.json
index d2ba648143..c04e50ce6b 100644
--- a/packages/core/devtools/package.json
+++ b/packages/core/devtools/package.json
@@ -1,13 +1,13 @@
{
"name": "@nocobase/devtools",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"description": "",
"license": "AGPL-3.0",
"main": "./src/index.js",
"dependencies": {
- "@nocobase/build": "1.3.0-beta",
- "@nocobase/client": "1.3.0-beta",
- "@nocobase/test": "1.3.0-beta",
+ "@nocobase/build": "1.3.19-beta",
+ "@nocobase/client": "1.3.19-beta",
+ "@nocobase/test": "1.3.19-beta",
"@types/koa": "^2.13.4",
"@types/koa-bodyparser": "^4.3.4",
"@types/lodash": "^4.14.177",
diff --git a/packages/core/evaluators/package.json b/packages/core/evaluators/package.json
index ec97d62e54..70cf52579b 100644
--- a/packages/core/evaluators/package.json
+++ b/packages/core/evaluators/package.json
@@ -1,13 +1,13 @@
{
"name": "@nocobase/evaluators",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"description": "",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"license": "AGPL-3.0",
"dependencies": {
"@formulajs/formulajs": "4.2.0",
- "@nocobase/utils": "1.3.0-beta",
+ "@nocobase/utils": "1.3.19-beta",
"mathjs": "^10.6.0"
},
"repository": {
diff --git a/packages/core/logger/package.json b/packages/core/logger/package.json
index 44840788a3..a293e52166 100644
--- a/packages/core/logger/package.json
+++ b/packages/core/logger/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/logger",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"description": "nocobase logging library",
"license": "AGPL-3.0",
"main": "./lib/index.js",
diff --git a/packages/core/resourcer/package.json b/packages/core/resourcer/package.json
index c004e2a3f7..813ef1bf3e 100644
--- a/packages/core/resourcer/package.json
+++ b/packages/core/resourcer/package.json
@@ -1,12 +1,12 @@
{
"name": "@nocobase/resourcer",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"description": "",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"license": "AGPL-3.0",
"dependencies": {
- "@nocobase/utils": "1.3.0-beta",
+ "@nocobase/utils": "1.3.19-beta",
"deepmerge": "^4.2.2",
"koa-compose": "^4.1.0",
"lodash": "^4.17.21",
diff --git a/packages/core/sdk/package.json b/packages/core/sdk/package.json
index 34fe30c7de..d0f70f6bb8 100644
--- a/packages/core/sdk/package.json
+++ b/packages/core/sdk/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/sdk",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"license": "AGPL-3.0",
"main": "lib/index.js",
"types": "lib/index.d.ts",
diff --git a/packages/core/server/package.json b/packages/core/server/package.json
index 9391393204..b659348841 100644
--- a/packages/core/server/package.json
+++ b/packages/core/server/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/server",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"main": "lib/index.js",
"types": "./lib/index.d.ts",
"license": "AGPL-3.0",
@@ -10,18 +10,18 @@
"@koa/cors": "^3.1.0",
"@koa/multer": "^3.0.2",
"@koa/router": "^9.4.0",
- "@nocobase/acl": "1.3.0-beta",
- "@nocobase/actions": "1.3.0-beta",
- "@nocobase/auth": "1.3.0-beta",
- "@nocobase/cache": "1.3.0-beta",
- "@nocobase/data-source-manager": "1.3.0-beta",
- "@nocobase/database": "1.3.0-beta",
- "@nocobase/evaluators": "1.3.0-beta",
- "@nocobase/logger": "1.3.0-beta",
- "@nocobase/resourcer": "1.3.0-beta",
- "@nocobase/sdk": "1.3.0-beta",
- "@nocobase/telemetry": "1.3.0-beta",
- "@nocobase/utils": "1.3.0-beta",
+ "@nocobase/acl": "1.3.19-beta",
+ "@nocobase/actions": "1.3.19-beta",
+ "@nocobase/auth": "1.3.19-beta",
+ "@nocobase/cache": "1.3.19-beta",
+ "@nocobase/data-source-manager": "1.3.19-beta",
+ "@nocobase/database": "1.3.19-beta",
+ "@nocobase/evaluators": "1.3.19-beta",
+ "@nocobase/logger": "1.3.19-beta",
+ "@nocobase/resourcer": "1.3.19-beta",
+ "@nocobase/sdk": "1.3.19-beta",
+ "@nocobase/telemetry": "1.3.19-beta",
+ "@nocobase/utils": "1.3.19-beta",
"@types/decompress": "4.2.4",
"@types/ini": "^1.3.31",
"@types/koa-send": "^4.1.3",
diff --git a/packages/core/server/src/plugin-manager/options/resource.ts b/packages/core/server/src/plugin-manager/options/resource.ts
index 210512b079..5e758f1be7 100644
--- a/packages/core/server/src/plugin-manager/options/resource.ts
+++ b/packages/core/server/src/plugin-manager/options/resource.ts
@@ -9,11 +9,10 @@
import { uid } from '@nocobase/utils';
import fs from 'fs';
+import fse from 'fs-extra';
import path from 'path';
import Application from '../../application';
-import { getExposeUrl } from '../clientStaticUtils';
import PluginManager from '../plugin-manager';
-//@ts-ignore
export default {
name: 'pm',
@@ -135,21 +134,19 @@ export default {
enabled: true,
},
});
- ctx.body = items
- .map((item) => {
- try {
- return {
- ...item.toJSON(),
- url: `${process.env.APP_SERVER_BASE_URL}${getExposeUrl(
- item.packageName,
- PLUGIN_CLIENT_ENTRY_FILE,
- )}?version=${item.version}`,
- };
- } catch {
- return false;
- }
- })
- .filter(Boolean);
+ const arr = [];
+ for (const item of items) {
+ const pkgPath = path.resolve(process.env.NODE_MODULES_PATH, item.packageName);
+ const r = await fse.exists(pkgPath);
+ if (r) {
+ const url = `${process.env.APP_SERVER_BASE_URL}${process.env.PLUGIN_STATICS_PATH}${item.packageName}/${PLUGIN_CLIENT_ENTRY_FILE}?version=${item.version}`;
+ arr.push({
+ ...item.toJSON(),
+ url,
+ });
+ }
+ }
+ ctx.body = arr;
await next();
},
async get(ctx, next) {
diff --git a/packages/core/telemetry/package.json b/packages/core/telemetry/package.json
index 25e206e5fb..a11941f93f 100644
--- a/packages/core/telemetry/package.json
+++ b/packages/core/telemetry/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/telemetry",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"description": "nocobase telemetry library",
"license": "AGPL-3.0",
"main": "./lib/index.js",
@@ -11,7 +11,7 @@
"directory": "packages/telemetry"
},
"dependencies": {
- "@nocobase/utils": "1.3.0-beta",
+ "@nocobase/utils": "1.3.19-beta",
"@opentelemetry/api": "^1.7.0",
"@opentelemetry/instrumentation": "^0.46.0",
"@opentelemetry/resources": "^1.19.0",
diff --git a/packages/core/test/package.json b/packages/core/test/package.json
index 6956382a5c..1934256c7e 100644
--- a/packages/core/test/package.json
+++ b/packages/core/test/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/test",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"main": "lib/index.js",
"module": "./src/index.ts",
"types": "./lib/index.d.ts",
@@ -51,7 +51,7 @@
},
"dependencies": {
"@faker-js/faker": "8.1.0",
- "@nocobase/server": "1.3.0-beta",
+ "@nocobase/server": "1.3.19-beta",
"@playwright/test": "^1.45.3",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.0.0",
diff --git a/packages/core/utils/package.json b/packages/core/utils/package.json
index e173441215..6483ef035a 100644
--- a/packages/core/utils/package.json
+++ b/packages/core/utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/utils",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"main": "lib/index.js",
"types": "./lib/index.d.ts",
"license": "AGPL-3.0",
diff --git a/packages/plugins/@nocobase/plugin-acl/package.json b/packages/plugins/@nocobase/plugin-acl/package.json
index f7c3cff5a9..1228e6df47 100644
--- a/packages/plugins/@nocobase/plugin-acl/package.json
+++ b/packages/plugins/@nocobase/plugin-acl/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "权限控制",
"description": "Based on roles, resources, and actions, access control can precisely manage interface configuration permissions, data operation permissions, menu access permissions, and plugin permissions.",
"description.zh-CN": "基于角色、资源和操作的权限控制,可以精确控制界面配置权限、数据操作权限、菜单访问权限、插件权限。",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/acl",
diff --git a/packages/plugins/@nocobase/plugin-acl/src/client/ACLSettingsUI.tsx b/packages/plugins/@nocobase/plugin-acl/src/client/ACLSettingsUI.tsx
new file mode 100644
index 0000000000..ec6cc22b23
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-acl/src/client/ACLSettingsUI.tsx
@@ -0,0 +1,79 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+import { TabsProps } from 'antd/es/tabs/index';
+import React from 'react';
+import { GeneralPermissions } from './permissions/GeneralPermissions';
+import { MenuItemsProvider } from './permissions/MenuItemsProvider';
+import { MenuPermissions } from './permissions/MenuPermissions';
+import { Role } from './RolesManagerProvider';
+
+interface PermissionsTabsProps {
+ /**
+ * the key of the currently active tab panel
+ */
+ activeKey: string;
+ /**
+ * the currently selected role
+ */
+ role: Role;
+ /**
+ * translation function
+ */
+ t: (key: string) => string;
+ /**
+ * used to constrain the size of the container in the Tab
+ */
+ TabLayout: React.FC;
+}
+
+type Tab = TabsProps['items'][0];
+
+type TabCallback = (props: PermissionsTabsProps) => Tab;
+
+/**
+ * the extension API for ACL settings page
+ */
+export class ACLSettingsUI {
+ private permissionsTabs: (Tab | TabCallback)[] = [
+ ({ t, TabLayout }) => ({
+ key: 'general',
+ label: t('System'),
+ children: (
+
+
+
+ ),
+ }),
+ ({ activeKey, t, TabLayout }) => ({
+ key: 'menu',
+ label: t('Menu'),
+ children: (
+
+
+
+
+
+ ),
+ }),
+ ];
+
+ addPermissionsTab(tab: Tab | TabCallback): void {
+ this.permissionsTabs.push(tab);
+ }
+
+ getPermissionsTabs(props: PermissionsTabsProps): Tab[] {
+ return this.permissionsTabs.map((tab) => {
+ if (typeof tab === 'function') {
+ return tab(props);
+ }
+ return tab;
+ });
+ }
+}
diff --git a/packages/plugins/@nocobase/plugin-acl/src/client/RolesManagerProvider.tsx b/packages/plugins/@nocobase/plugin-acl/src/client/RolesManagerProvider.tsx
index e32c968fdb..abd635ebfd 100644
--- a/packages/plugins/@nocobase/plugin-acl/src/client/RolesManagerProvider.tsx
+++ b/packages/plugins/@nocobase/plugin-acl/src/client/RolesManagerProvider.tsx
@@ -9,9 +9,25 @@
import { createContext } from 'react';
+export interface Role {
+ createdAt: string;
+ updatedAt: string;
+ name: string;
+ title: string;
+ description: string;
+ strategy: {
+ actions: string[];
+ };
+ default: boolean;
+ hidden: boolean;
+ allowConfigure: boolean;
+ allowNewMenu: boolean;
+ snippets: string[];
+}
+
export const RolesManagerContext = createContext<{
- role: any;
- setRole: (role: any) => void;
+ role: Role;
+ setRole: (role: Role) => void;
}>({
role: null,
} as any);
diff --git a/packages/plugins/@nocobase/plugin-acl/src/client/index.ts b/packages/plugins/@nocobase/plugin-acl/src/client/index.ts
index 9df8d9cfc9..129c613363 100644
--- a/packages/plugins/@nocobase/plugin-acl/src/client/index.ts
+++ b/packages/plugins/@nocobase/plugin-acl/src/client/index.ts
@@ -8,11 +8,13 @@
*/
import { Plugin } from '@nocobase/client';
+import { ACLSettingsUI } from './ACLSettingsUI';
import { RolesManagement } from './RolesManagement';
import { RolesManager } from './roles-manager';
export class PluginACLClient extends Plugin {
rolesManager = new RolesManager();
+ settingsUI = new ACLSettingsUI();
async load() {
this.pluginSettingsManager.add('users-permissions.roles', {
diff --git a/packages/plugins/@nocobase/plugin-acl/src/client/permissions/GeneralPermissions.tsx b/packages/plugins/@nocobase/plugin-acl/src/client/permissions/GeneralPermissions.tsx
index 7fec43175c..0cc34c6c8a 100644
--- a/packages/plugins/@nocobase/plugin-acl/src/client/permissions/GeneralPermissions.tsx
+++ b/packages/plugins/@nocobase/plugin-acl/src/client/permissions/GeneralPermissions.tsx
@@ -7,19 +7,19 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-import { onFormValuesChange, createForm, Form, onFieldChange } from '@formily/core';
+import { createForm, Form, onFormValuesChange } from '@formily/core';
import { connect } from '@formily/react';
-import { SchemaComponent, useAPIClient, useRequest } from '@nocobase/client';
+import { uid } from '@formily/shared';
+import { SchemaComponent, useAPIClient } from '@nocobase/client';
+import { useMemoizedFn } from 'ahooks';
import { Checkbox, message } from 'antd';
import uniq from 'lodash/uniq';
import React, { useContext, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
-import { uid } from '@formily/shared';
-import { useMemoizedFn } from 'ahooks';
-import { RolesManagerContext } from '../RolesManagerProvider';
-import { StrategyActions } from './StrategyActions';
import { useACLTranslation } from '../locale';
+import { RolesManagerContext } from '../RolesManagerProvider';
import { PluginPermissions } from './PluginPermissions';
+import { StrategyActions } from './StrategyActions';
const SnippetCheckboxGroup = connect((props) => {
const { t } = useTranslation();
@@ -65,9 +65,7 @@ const SnippetCheckboxGroup = connect((props) => {
);
});
-export const GeneralPermissions: React.FC<{
- active: boolean;
-}> = ({ active }) => {
+export const GeneralPermissions: React.FC = () => {
const { role, setRole } = useContext(RolesManagerContext);
const { t } = useACLTranslation();
const api = useAPIClient();
diff --git a/packages/plugins/@nocobase/plugin-acl/src/client/permissions/MenuPermissions.tsx b/packages/plugins/@nocobase/plugin-acl/src/client/permissions/MenuPermissions.tsx
index f22e39de48..644e29aadd 100644
--- a/packages/plugins/@nocobase/plugin-acl/src/client/permissions/MenuPermissions.tsx
+++ b/packages/plugins/@nocobase/plugin-acl/src/client/permissions/MenuPermissions.tsx
@@ -7,17 +7,17 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
+import { createForm, Form, onFormValuesChange } from '@formily/core';
+import { uid } from '@formily/shared';
+import { SchemaComponent, useAPIClient, useRequest } from '@nocobase/client';
+import { useMemoizedFn } from 'ahooks';
import { Checkbox, message, Table } from 'antd';
-import { onFormValuesChange, createForm, Form } from '@formily/core';
import { uniq } from 'lodash';
-import React, { useContext, useState, useMemo } from 'react';
+import React, { useContext, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { uid } from '@formily/shared';
-import { useAPIClient, SchemaComponent, useRequest } from '@nocobase/client';
-import { useStyles } from './style';
-import { useMemoizedFn } from 'ahooks';
import { RolesManagerContext } from '../RolesManagerProvider';
import { useMenuItems } from './MenuItemsProvider';
+import { useStyles } from './style';
const findUids = (items) => {
if (!Array.isArray(items)) {
diff --git a/packages/plugins/@nocobase/plugin-acl/src/client/permissions/Permissions.tsx b/packages/plugins/@nocobase/plugin-acl/src/client/permissions/Permissions.tsx
index 23f820fb26..d286a68006 100644
--- a/packages/plugins/@nocobase/plugin-acl/src/client/permissions/Permissions.tsx
+++ b/packages/plugins/@nocobase/plugin-acl/src/client/permissions/Permissions.tsx
@@ -7,15 +7,13 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-import { useApp, useRequest, useAPIClient } from '@nocobase/client';
+import { useAPIClient, usePlugin, useRequest } from '@nocobase/client';
import { Tabs } from 'antd';
import React, { useContext, useEffect, useMemo } from 'react';
-import { RolesManagerContext } from '../RolesManagerProvider';
+import PluginACLClient from '..';
+import { Role, RolesManagerContext } from '../RolesManagerProvider';
import { useACLTranslation } from '../locale';
import { AvailableActionsProvider } from './AvailableActions';
-import { GeneralPermissions } from './GeneralPermissions';
-import { MenuItemsProvider } from './MenuItemsProvider';
-import { MenuPermissions } from './MenuPermissions';
const TabLayout: React.FC = (props) => {
return {props.children}
;
@@ -25,52 +23,15 @@ export const Permissions: React.FC<{ active: boolean }> = ({ active }) => {
const { t } = useACLTranslation();
const [activeKey, setActiveKey] = React.useState('general');
const { role, setRole } = useContext(RolesManagerContext);
- const pm = role?.snippets?.includes('pm.*');
- const app = useApp();
- const DataSourcePermissionManager = app.getComponent('DataSourcePermissionManager');
+ const pluginACLClient = usePlugin(PluginACLClient);
const items = useMemo(
- () => [
- {
- key: 'general',
- label: t('System'),
- children: (
-
-
-
- ),
- },
- {
- key: 'menu',
- label: t('Menu'),
- children: (
-
-
-
-
-
- ),
- },
- ...(DataSourcePermissionManager
- ? [
- {
- key: 'dataSource',
- label: t('Data sources'),
- children: (
-
-
-
-
-
- ),
- },
- ]
- : []),
- ],
- [pm, activeKey, active, t],
+ () => pluginACLClient.settingsUI.getPermissionsTabs({ t, activeKey, TabLayout, role }),
+ [activeKey, pluginACLClient.settingsUI, role, t],
);
+
const api = useAPIClient();
- const { data } = useRequest(
+ const { data } = useRequest(
() =>
api
.resource('roles')
@@ -89,13 +50,15 @@ export const Permissions: React.FC<{ active: boolean }> = ({ active }) => {
refreshDeps: [role?.name],
},
);
+
useEffect(() => {
setActiveKey('general');
}, [role?.name]);
useEffect(() => {
setRole(data);
- }, [data]);
+ }, [data, setRole]);
+
return (
setActiveKey(key)} items={items} />
diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/package.json b/packages/plugins/@nocobase/plugin-action-bulk-edit/package.json
index 069aaf4365..8a4d6e2cdb 100644
--- a/packages/plugins/@nocobase/plugin-action-bulk-edit/package.json
+++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-action-bulk-edit",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-bulk-edit",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-bulk-edit",
diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/BulkEditAction.Settings.tsx b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/BulkEditAction.Settings.tsx
index bdc89e6785..7b2ae0069e 100644
--- a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/BulkEditAction.Settings.tsx
+++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/BulkEditAction.Settings.tsx
@@ -33,7 +33,7 @@ function UpdateMode() {
title={t('Data will be updated')}
options={[
{ label: t('Selected'), value: 'selected' },
- { label: t('All'), value: 'all' },
+ { label: t('Entire collection', { ns: 'action-bulk-edit' }), value: 'all' },
]}
value={fieldSchema?.['x-action-settings']?.['updateMode']}
onChange={(value) => {
diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/BulkEditActionDecorator.tsx b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/BulkEditActionDecorator.tsx
new file mode 100644
index 0000000000..354fa1cfb2
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/BulkEditActionDecorator.tsx
@@ -0,0 +1,19 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+import { ACLActionProvider, PopupSettingsProvider } from '@nocobase/client';
+import React, { FC } from 'react';
+
+export const BulkEditActionDecorator: FC = (props) => {
+ return (
+
+ {props.children}
+
+ );
+};
diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/__e2e__/schemaSettings.test.ts b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/__e2e__/schemaSettings.test.ts
index 2b39281840..7b1816f287 100644
--- a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/__e2e__/schemaSettings.test.ts
+++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/__e2e__/schemaSettings.test.ts
@@ -21,7 +21,7 @@ test.describe('bulk edit action setting', () => {
await expect(page.getByTitle('Data will be updated').getByText('Selected')).toBeVisible();
await page.getByRole('menuitem', { name: 'Data will be updated' }).click();
//切换为全部数据
- await page.getByRole('option', { name: 'All' }).click();
+ await page.getByRole('option', { name: 'Entire collection' }).click();
//配置更新规则
await page.getByLabel('Bulk edit').click();
await page.getByLabel('schema-initializer-Grid-popup:bulkEdit:addBlock-general').hover();
diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/component/BulkEditField.tsx b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/component/BulkEditField.tsx
index 35f0fbfeaf..1d4c4cdfad 100644
--- a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/component/BulkEditField.tsx
+++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/component/BulkEditField.tsx
@@ -95,7 +95,7 @@ export const BulkEditField = (props: any) => {
const { t } = useTranslation();
const fieldSchema = useFieldSchema();
const field = useField();
- const [type, setType] = useState(BulkEditFormItemValueType.ChangedTo);
+ const [type, setType] = useState(BulkEditFormItemValueType.RemainsTheSame);
const [value, setValue] = useState(null);
const { getField } = useCollection_deprecated();
const collectionField = getField(fieldSchema.name) || {};
diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/index.tsx b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/index.tsx
index c1f52a22bb..83d2149023 100644
--- a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/index.tsx
+++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/index.tsx
@@ -9,6 +9,7 @@
import { Plugin, useActionAvailable } from '@nocobase/client';
import { bulkEditActionSettings, deprecatedBulkEditActionSettings } from './BulkEditAction.Settings';
+import { BulkEditActionDecorator } from './BulkEditActionDecorator';
import { BulkEditActionInitializer } from './BulkEditActionInitializer';
import {
BulkEditBlockInitializers_deprecated,
@@ -25,7 +26,7 @@ import { BulkEditField } from './component/BulkEditField';
import { useCustomizeBulkEditActionProps } from './utils';
export class PluginActionBulkEditClient extends Plugin {
async load() {
- this.app.addComponents({ BulkEditField });
+ this.app.addComponents({ BulkEditField, BulkEditActionDecorator });
this.app.addScopes({ useCustomizeBulkEditActionProps });
this.app.schemaSettingsManager.add(deprecatedBulkEditActionSettings);
this.app.schemaSettingsManager.add(bulkEditActionSettings);
@@ -45,7 +46,7 @@ export class PluginActionBulkEditClient extends Plugin {
Component: BulkEditActionInitializer,
schema: {
'x-align': 'right',
- 'x-decorator': 'ACLActionProvider',
+ 'x-decorator': 'BulkEditActionDecorator',
'x-action': 'customize:bulkEdit',
'x-toolbar': 'ActionSchemaToolbar',
'x-settings': 'actionSettings:bulkEdit',
diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/locale/zh-CN.json
index f9e8775f39..05ba2d2124 100644
--- a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/locale/zh-CN.json
+++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/locale/zh-CN.json
@@ -1,4 +1,5 @@
{
"Bulk edit": "批量编辑",
- "Data will be updated": "更新的数据"
+ "Data will be updated": "更新的数据",
+ "Entire collection":"全表"
}
diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/package.json b/packages/plugins/@nocobase/plugin-action-bulk-update/package.json
index 562f1b20d2..d3d0c42c5f 100644
--- a/packages/plugins/@nocobase/plugin-action-bulk-update/package.json
+++ b/packages/plugins/@nocobase/plugin-action-bulk-update/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-action-bulk-update",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-bulk-update",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-bulk-update",
diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/BulkUpdateAction.Settings.tsx b/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/BulkUpdateAction.Settings.tsx
index 5be24f6b66..6521d54bf9 100644
--- a/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/BulkUpdateAction.Settings.tsx
+++ b/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/BulkUpdateAction.Settings.tsx
@@ -33,7 +33,7 @@ function UpdateMode() {
title={t('Data will be updated')}
options={[
{ label: t('Selected'), value: 'selected' },
- { label: t('All'), value: 'all' },
+ { label: t('Entire collection', { ns: 'action-bulk-edit' }), value: 'all' },
]}
value={fieldSchema?.['x-action-settings']?.['updateMode']}
onChange={(value) => {
diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/__e2e__/schemaSettings.test.ts b/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/__e2e__/schemaSettings.test.ts
index fb37fbaed5..73bd22caac 100644
--- a/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/__e2e__/schemaSettings.test.ts
+++ b/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/__e2e__/schemaSettings.test.ts
@@ -24,7 +24,7 @@ test.describe('data will be updated && Assign field values && after successful s
await expect(page.getByTitle('Data will be updated').getByText('Selected')).toBeVisible();
await page.getByRole('menuitem', { name: 'Data will be updated' }).click();
//切换为全部数据
- await page.getByRole('option', { name: 'All' }).click();
+ await page.getByRole('option', { name: 'Entire collection' }).click();
//字段赋值
await page.getByRole('menuitem', { name: 'Assign field values' }).click();
await page.getByLabel('schema-initializer-Grid-assignFieldValuesForm:configureFields-general').click();
diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/utils.tsx b/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/utils.tsx
index 9ca8917bf0..181ef1d2b3 100644
--- a/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/utils.tsx
+++ b/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/utils.tsx
@@ -64,7 +64,7 @@ export const useCustomizeBulkUpdateActionProps = () => {
}
if (isVariable(value)) {
- const result = await variables?.parseVariable(value, localVariables);
+ const result = await variables?.parseVariable(value, localVariables).then(({ value }) => value);
if (result) {
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
}
diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/zh-CN.ts b/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/zh-CN.ts
index f1ca06e0ea..71fdb1988c 100644
--- a/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/zh-CN.ts
+++ b/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/zh-CN.ts
@@ -11,4 +11,5 @@ export default {
'Bulk update': '批量更新',
'After successful bulk update': '批量成功更新后',
'Please select the records to be updated': '请选择要更新的记录',
+ "Entire collection":"全表"
};
diff --git a/packages/plugins/@nocobase/plugin-action-custom-request/package.json b/packages/plugins/@nocobase/plugin-action-custom-request/package.json
index cd7678a7a4..dc2d4e525b 100644
--- a/packages/plugins/@nocobase/plugin-action-custom-request/package.json
+++ b/packages/plugins/@nocobase/plugin-action-custom-request/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-action-custom-request",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-custom-request",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-custom-request",
diff --git a/packages/plugins/@nocobase/plugin-action-custom-request/src/client/schemaSettings.ts b/packages/plugins/@nocobase/plugin-action-custom-request/src/client/schemaSettings.ts
index 88e18b298c..4f6686ea0e 100644
--- a/packages/plugins/@nocobase/plugin-action-custom-request/src/client/schemaSettings.ts
+++ b/packages/plugins/@nocobase/plugin-action-custom-request/src/client/schemaSettings.ts
@@ -17,6 +17,7 @@ import {
useCollection,
useSchemaToolbar,
RefreshDataBlockRequest,
+ useCollectionRecord,
} from '@nocobase/client';
import { CustomRequestACL, CustomRequestSettingsItem } from './components/CustomRequestActionDesigner';
import { useFieldSchema } from '@formily/react';
@@ -45,6 +46,10 @@ export const customizeCustomRequestActionSettings = new SchemaSettings({
collectionName: name,
};
},
+ useVisible() {
+ const record = useCollectionRecord();
+ return !record?.isNew;
+ },
},
{
name: 'secondConFirm',
diff --git a/packages/plugins/@nocobase/plugin-action-duplicate/package.json b/packages/plugins/@nocobase/plugin-action-duplicate/package.json
index 489754b2b9..2f0f245f28 100644
--- a/packages/plugins/@nocobase/plugin-action-duplicate/package.json
+++ b/packages/plugins/@nocobase/plugin-action-duplicate/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-action-duplicate",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-duplicate",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-duplicate",
diff --git a/packages/plugins/@nocobase/plugin-action-duplicate/src/client/DuplicateAction.tsx b/packages/plugins/@nocobase/plugin-action-duplicate/src/client/DuplicateAction.tsx
index 361ac3f4e3..95907e04f7 100644
--- a/packages/plugins/@nocobase/plugin-action-duplicate/src/client/DuplicateAction.tsx
+++ b/packages/plugins/@nocobase/plugin-action-duplicate/src/client/DuplicateAction.tsx
@@ -13,6 +13,7 @@ import {
ActionContextProvider,
CollectionProvider_deprecated,
FormBlockContext,
+ PopupSettingsProvider,
RecordProvider,
fetchTemplateData,
useACLActionParamsContext,
@@ -203,7 +204,9 @@ export const DuplicateAction = observer(
{/* 这里的 record 就是弹窗中创建表单的 sourceRecord */}
-
+
+
+
diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/providers/index.ts b/packages/plugins/@nocobase/plugin-action-duplicate/src/client/DuplicateActionDecorator.tsx
similarity index 57%
rename from packages/plugins/@nocobase/plugin-mobile/src/client/providers/index.ts
rename to packages/plugins/@nocobase/plugin-action-duplicate/src/client/DuplicateActionDecorator.tsx
index afe67bb560..dfe9a85390 100644
--- a/packages/plugins/@nocobase/plugin-mobile/src/client/providers/index.ts
+++ b/packages/plugins/@nocobase/plugin-action-duplicate/src/client/DuplicateActionDecorator.tsx
@@ -7,4 +7,9 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-export * from './MobileCheckerProvider';
+import { ACLActionProvider } from '@nocobase/client';
+import React, { FC } from 'react';
+
+export const DuplicateActionDecorator: FC = (props) => {
+ return {props.children};
+};
diff --git a/packages/plugins/@nocobase/plugin-action-duplicate/src/client/DuplicateActionInitializer.tsx b/packages/plugins/@nocobase/plugin-action-duplicate/src/client/DuplicateActionInitializer.tsx
index fd18e8a8af..a145dc72dd 100644
--- a/packages/plugins/@nocobase/plugin-action-duplicate/src/client/DuplicateActionInitializer.tsx
+++ b/packages/plugins/@nocobase/plugin-action-duplicate/src/client/DuplicateActionInitializer.tsx
@@ -19,7 +19,7 @@ export const DuplicateActionInitializer = (props) => {
'x-acl-action': 'create',
title: '{{ t("Duplicate") }}',
'x-component': 'Action.Link',
- 'x-decorator': 'ACLActionProvider',
+ 'x-decorator': 'DuplicateActionDecorator',
'x-component-props': {
openMode: defaultOpenMode,
component: 'DuplicateAction',
diff --git a/packages/plugins/@nocobase/plugin-action-duplicate/src/client/index.ts b/packages/plugins/@nocobase/plugin-action-duplicate/src/client/index.ts
index 882b100e64..af78cfde3e 100644
--- a/packages/plugins/@nocobase/plugin-action-duplicate/src/client/index.ts
+++ b/packages/plugins/@nocobase/plugin-action-duplicate/src/client/index.ts
@@ -10,6 +10,7 @@
import { Plugin, useActionAvailable } from '@nocobase/client';
import { DuplicateAction } from './DuplicateAction';
import { deprecatedDuplicateActionSettings, duplicateActionSettings } from './DuplicateAction.Settings';
+import { DuplicateActionDecorator } from './DuplicateActionDecorator';
import { DuplicateActionInitializer } from './DuplicateActionInitializer';
import { DuplicatePluginProvider } from './DuplicatePluginProvider';
@@ -19,6 +20,7 @@ export class PluginActionDuplicateClient extends Plugin {
this.app.addComponents({
DuplicateActionInitializer,
DuplicateAction,
+ DuplicateActionDecorator,
});
this.app.schemaSettingsManager.add(deprecatedDuplicateActionSettings);
this.app.schemaSettingsManager.add(duplicateActionSettings);
@@ -31,7 +33,7 @@ export class PluginActionDuplicateClient extends Plugin {
'x-action': 'duplicate',
'x-toolbar': 'ActionSchemaToolbar',
'x-settings': 'actionSettings:duplicate',
- 'x-decorator': 'ACLActionProvider',
+ 'x-decorator': 'DuplicateActionDecorator',
'x-component-props': {
type: 'primary',
},
diff --git a/packages/plugins/@nocobase/plugin-action-export/package.json b/packages/plugins/@nocobase/plugin-action-export/package.json
index e2f6a8c3f2..621636758c 100644
--- a/packages/plugins/@nocobase/plugin-action-export/package.json
+++ b/packages/plugins/@nocobase/plugin-action-export/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "操作:导出记录",
"description": "Export filtered records to excel, you can configure which fields to export.",
"description.zh-CN": "导出筛选后的记录到 Excel 中,可以配置导出哪些字段。",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-export",
diff --git a/packages/plugins/@nocobase/plugin-action-export/src/server/__tests__/export-action.test.ts b/packages/plugins/@nocobase/plugin-action-export/src/server/__tests__/export-action.test.ts
new file mode 100644
index 0000000000..0501ae1451
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-action-export/src/server/__tests__/export-action.test.ts
@@ -0,0 +1,89 @@
+import { createMockServer, MockServer } from '@nocobase/test';
+import fs from 'fs';
+import os from 'os';
+import path from 'path';
+import XLSX from 'xlsx';
+
+describe('export action', () => {
+ let app: MockServer;
+
+ beforeEach(async () => {
+ app = await createMockServer({
+ plugins: ['nocobase'],
+ acl: false,
+ });
+ });
+
+ it('should export with association repository', async () => {
+ await app.db.getRepository('collections').create({
+ values: {
+ name: 'posts',
+ fields: [
+ {
+ name: 'title',
+ type: 'string',
+ },
+ {
+ name: 'comments',
+ type: 'hasMany',
+ target: 'comments',
+ },
+ ],
+ },
+ context: {},
+ });
+
+ await app.db.getRepository('collections').create({
+ values: {
+ name: 'comments',
+ fields: [
+ {
+ name: 'content',
+ type: 'string',
+ },
+ ],
+ },
+ context: {},
+ });
+
+ await app.db.getRepository('posts').create({
+ values: [
+ {
+ title: 'post1',
+ comments: [
+ {
+ content: 'comment1',
+ },
+ ],
+ },
+ {
+ title: 'post2',
+ comments: [
+ {
+ content: 'comment2',
+ },
+ ],
+ },
+ ],
+ });
+
+ const res = await app
+ .agent()
+ .resource('posts.comments', 1)
+ .export({
+ values: {
+ columns: [{ dataIndex: ['content'], defaultTitle: 'content' }],
+ },
+ });
+
+ expect(res.status).toBe(200);
+
+ const buffer = res.body;
+
+ const workbook = XLSX.read(buffer, { type: 'buffer' });
+ const sheetName = workbook.SheetNames[0];
+ const sheet = workbook.Sheets[sheetName];
+ const rows = XLSX.utils.sheet_to_json(sheet);
+ expect(rows.length).toBe(1);
+ });
+});
diff --git a/packages/plugins/@nocobase/plugin-action-export/src/server/actions/export-xlsx.ts b/packages/plugins/@nocobase/plugin-action-export/src/server/actions/export-xlsx.ts
index f37f94ea8a..69b6eba309 100644
--- a/packages/plugins/@nocobase/plugin-action-export/src/server/actions/export-xlsx.ts
+++ b/packages/plugins/@nocobase/plugin-action-export/src/server/actions/export-xlsx.ts
@@ -33,6 +33,7 @@ async function exportXlsxAction(ctx: Context, next: Next) {
const xlsxExporter = new XlsxExporter({
collectionManager: dataSource.collectionManager,
collection,
+ repository,
columns,
findOptions: {
filter,
diff --git a/packages/plugins/@nocobase/plugin-action-export/src/server/xlsx-exporter.ts b/packages/plugins/@nocobase/plugin-action-export/src/server/xlsx-exporter.ts
index 9b92b05d39..71201bb952 100644
--- a/packages/plugins/@nocobase/plugin-action-export/src/server/xlsx-exporter.ts
+++ b/packages/plugins/@nocobase/plugin-action-export/src/server/xlsx-exporter.ts
@@ -29,6 +29,7 @@ type ExportColumn = {
type ExportOptions = {
collectionManager: ICollectionManager;
collection: ICollection;
+ repository?: any;
columns: Array;
findOptions?: FindOptions;
chunkSize?: number;
@@ -51,7 +52,7 @@ class XlsxExporter {
constructor(private options: ExportOptions) {}
async run(ctx?): Promise {
- const { collection, columns, chunkSize } = this.options;
+ const { collection, columns, chunkSize, repository } = this.options;
const workbook = XLSX.utils.book_new();
const worksheet = XLSX.utils.sheet_new();
@@ -63,7 +64,7 @@ class XlsxExporter {
let startRowNumber = 2;
- await collection.repository.chunk({
+ await (repository || collection.repository).chunk({
...this.getFindOptions(),
chunkSize: chunkSize || 200,
callback: async (rows, options) => {
diff --git a/packages/plugins/@nocobase/plugin-action-import/package.json b/packages/plugins/@nocobase/plugin-action-import/package.json
index 0af786a5ef..7358a934b6 100644
--- a/packages/plugins/@nocobase/plugin-action-import/package.json
+++ b/packages/plugins/@nocobase/plugin-action-import/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "操作:导入记录",
"description": "Import records using excel templates. You can configure which fields to import and templates will be generated automatically.",
"description.zh-CN": "使用 Excel 模板导入数据,可以配置导入哪些字段,自动生成模板。",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-import",
diff --git a/packages/plugins/@nocobase/plugin-action-print/package.json b/packages/plugins/@nocobase/plugin-action-print/package.json
index adfab22555..89f5545588 100644
--- a/packages/plugins/@nocobase/plugin-action-print/package.json
+++ b/packages/plugins/@nocobase/plugin-action-print/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-action-print",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-print",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-print",
diff --git a/packages/plugins/@nocobase/plugin-api-doc/package.json b/packages/plugins/@nocobase/plugin-api-doc/package.json
index 06a1f0ed2f..ec8e9989ca 100644
--- a/packages/plugins/@nocobase/plugin-api-doc/package.json
+++ b/packages/plugins/@nocobase/plugin-api-doc/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-api-doc",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"displayName": "API documentation",
"displayName.zh-CN": "API 文档",
"description": "An OpenAPI documentation generator for NocoBase HTTP API.",
diff --git a/packages/plugins/@nocobase/plugin-api-keys/package.json b/packages/plugins/@nocobase/plugin-api-keys/package.json
index e59e79c99b..a2e299efb6 100644
--- a/packages/plugins/@nocobase/plugin-api-keys/package.json
+++ b/packages/plugins/@nocobase/plugin-api-keys/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "认证:API 密钥",
"description": "Allows users to use API key to access application's HTTP API",
"description.zh-CN": "允许用户使用 API 密钥访问应用的 HTTP API",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/api-keys",
diff --git a/packages/plugins/@nocobase/plugin-audit-logs/package.json b/packages/plugins/@nocobase/plugin-audit-logs/package.json
index b7ec07bb87..840b4afa52 100644
--- a/packages/plugins/@nocobase/plugin-audit-logs/package.json
+++ b/packages/plugins/@nocobase/plugin-audit-logs/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-audit-logs",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"displayName": "Audit logs (deprecated)",
"displayName.zh-CN": "审计日志(废弃)",
"description": "This plugin is deprecated. There will be a new audit log plugin in the future.",
diff --git a/packages/plugins/@nocobase/plugin-audit-logs/src/client/deplicated/AuditLogsDesigner.tsx b/packages/plugins/@nocobase/plugin-audit-logs/src/client/deplicated/AuditLogsDesigner.tsx
index 5d1e4a0162..6f87350ae8 100644
--- a/packages/plugins/@nocobase/plugin-audit-logs/src/client/deplicated/AuditLogsDesigner.tsx
+++ b/packages/plugins/@nocobase/plugin-audit-logs/src/client/deplicated/AuditLogsDesigner.tsx
@@ -54,6 +54,7 @@ export const AuditLogsDesigner = () => {
title={t('Records per page')}
value={field.decoratorProps?.params?.pageSize || 20}
options={[
+ { label: '5', value: 5 },
{ label: '10', value: 10 },
{ label: '20', value: 20 },
{ label: '50', value: 50 },
diff --git a/packages/plugins/@nocobase/plugin-auth-sms/package.json b/packages/plugins/@nocobase/plugin-auth-sms/package.json
index 04cf3202b0..8cea7f62b2 100644
--- a/packages/plugins/@nocobase/plugin-auth-sms/package.json
+++ b/packages/plugins/@nocobase/plugin-auth-sms/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "认证:短信",
"description": "SMS authentication.",
"description.zh-CN": "通过短信验证码认证身份。",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/auth-sms",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/auth-sms",
diff --git a/packages/plugins/@nocobase/plugin-auth/package.json b/packages/plugins/@nocobase/plugin-auth/package.json
index ac931a000d..3411fb6479 100644
--- a/packages/plugins/@nocobase/plugin-auth/package.json
+++ b/packages/plugins/@nocobase/plugin-auth/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-auth",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/auth",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/auth",
diff --git a/packages/plugins/@nocobase/plugin-auth/src/client/pages/AuthLayout.tsx b/packages/plugins/@nocobase/plugin-auth/src/client/pages/AuthLayout.tsx
index 969430e0ae..d6594bc752 100644
--- a/packages/plugins/@nocobase/plugin-auth/src/client/pages/AuthLayout.tsx
+++ b/packages/plugins/@nocobase/plugin-auth/src/client/pages/AuthLayout.tsx
@@ -8,11 +8,11 @@
*/
import { css } from '@emotion/css';
+import { PoweredBy, ReadPretty, useAPIClient, useRequest, useSystemSettings } from '@nocobase/client';
+import { Spin } from 'antd';
import React, { FC } from 'react';
import { Outlet } from 'react-router-dom';
-import { useSystemSettings, PoweredBy, useRequest, useAPIClient } from '@nocobase/client';
import { AuthenticatorsContext } from '../authenticator';
-import { Spin } from 'antd';
export const AuthenticatorsContextProvider: FC<{ children: React.ReactNode }> = ({ children }) => {
const api = useAPIClient();
@@ -55,7 +55,9 @@ export function AuthLayout() {
paddingTop: '20vh',
}}
>
- {data?.data?.title}
+
+
+
diff --git a/packages/plugins/@nocobase/plugin-backup-restore/package.json b/packages/plugins/@nocobase/plugin-backup-restore/package.json
index c42222b806..48e91b567b 100644
--- a/packages/plugins/@nocobase/plugin-backup-restore/package.json
+++ b/packages/plugins/@nocobase/plugin-backup-restore/package.json
@@ -1,10 +1,10 @@
{
"name": "@nocobase/plugin-backup-restore",
- "displayName": "App backup & restore",
- "displayName.zh-CN": "应用的备份与还原",
+ "displayName": "App backup & restore (deprecated)",
+ "displayName.zh-CN": "应用的备份与还原(废弃)",
"description": "Backup and restore applications for scenarios such as application replication, migration, and upgrades.",
"description.zh-CN": "备份和还原应用,可用于应用的复制、迁移、升级等场景。",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/backup-restore",
diff --git a/packages/plugins/@nocobase/plugin-backup-restore/src/server/__tests__/cli.test.ts b/packages/plugins/@nocobase/plugin-backup-restore/src/server/__tests__/cli.test.ts
index 1ea0cfd08e..c2a67f1c7b 100644
--- a/packages/plugins/@nocobase/plugin-backup-restore/src/server/__tests__/cli.test.ts
+++ b/packages/plugins/@nocobase/plugin-backup-restore/src/server/__tests__/cli.test.ts
@@ -14,7 +14,7 @@ describe('cli', () => {
beforeEach(async () => {
app = await createMockServer({
- plugins: ['nocobase'],
+ plugins: ['nocobase', 'backup-restore'],
});
});
diff --git a/packages/plugins/@nocobase/plugin-backup-restore/src/server/__tests__/dumper.test.ts b/packages/plugins/@nocobase/plugin-backup-restore/src/server/__tests__/dumper.test.ts
index 6b4b1dc2db..9e85ae37f8 100644
--- a/packages/plugins/@nocobase/plugin-backup-restore/src/server/__tests__/dumper.test.ts
+++ b/packages/plugins/@nocobase/plugin-backup-restore/src/server/__tests__/dumper.test.ts
@@ -10,11 +10,11 @@
import { Database } from '@nocobase/database';
import { MockServer } from '@nocobase/test';
import fs from 'fs';
+import * as process from 'node:process';
import path from 'path';
import { Dumper } from '../dumper';
import { Restorer } from '../restorer';
import createApp from './index';
-import * as process from 'node:process';
describe('dumper', () => {
let app: MockServer;
diff --git a/packages/plugins/@nocobase/plugin-backup-restore/src/server/__tests__/index.ts b/packages/plugins/@nocobase/plugin-backup-restore/src/server/__tests__/index.ts
index 34990d8c7c..5422f4b207 100644
--- a/packages/plugins/@nocobase/plugin-backup-restore/src/server/__tests__/index.ts
+++ b/packages/plugins/@nocobase/plugin-backup-restore/src/server/__tests__/index.ts
@@ -11,7 +11,7 @@ import { createMockServer } from '@nocobase/test';
export default async function createApp() {
const app = await createMockServer({
- plugins: ['nocobase', 'collection-sql'],
+ plugins: ['nocobase', 'collection-sql', 'backup-restore'],
});
return app;
}
diff --git a/packages/plugins/@nocobase/plugin-block-iframe/package.json b/packages/plugins/@nocobase/plugin-block-iframe/package.json
index b9e2a0e5dc..30d366f224 100644
--- a/packages/plugins/@nocobase/plugin-block-iframe/package.json
+++ b/packages/plugins/@nocobase/plugin-block-iframe/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "区块:iframe",
"description": "Create an iframe block on the page to embed and display external web pages or content.",
"description.zh-CN": "在页面上创建和管理iframe,用于嵌入和展示外部网页或内容。",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/block-iframe",
diff --git a/packages/plugins/@nocobase/plugin-block-workbench/package.json b/packages/plugins/@nocobase/plugin-block-workbench/package.json
index 05921ae1aa..1a11887698 100644
--- a/packages/plugins/@nocobase/plugin-block-workbench/package.json
+++ b/packages/plugins/@nocobase/plugin-block-workbench/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-block-workbench",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"displayName": "Block: Workbench",
"displayName.zh-CN": "区块:工作台",
"description": "Add buttons for actions, links, etc. in the workbench block to quickly initiate actions and jump pages.",
diff --git a/packages/plugins/@nocobase/plugin-calendar/package.json b/packages/plugins/@nocobase/plugin-calendar/package.json
index 4315dec8d9..4212dd3e44 100644
--- a/packages/plugins/@nocobase/plugin-calendar/package.json
+++ b/packages/plugins/@nocobase/plugin-calendar/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-calendar",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"displayName": "Calendar",
"displayName.zh-CN": "日历",
"description": "Provides callendar collection template and block for managing date data, typically for date/time related information such as events, appointments, tasks, and so on.",
diff --git a/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/Calendar.tsx b/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/Calendar.tsx
index 7f8a13407c..1e485890c2 100644
--- a/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/Calendar.tsx
+++ b/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/Calendar.tsx
@@ -10,12 +10,12 @@
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
import { RecursionField, Schema, observer, useFieldSchema } from '@formily/react';
import {
- ActionContextProvider,
+ PopupContextProvider,
RecordProvider,
- VariablePopupRecordProvider,
getLabelFormatValue,
useCollection,
useCollectionParentRecordData,
+ usePopupUtils,
useProps,
withDynamicSchemaProps,
} from '@nocobase/client';
@@ -23,10 +23,11 @@ import { parseExpression } from 'cron-parser';
import type { Dayjs } from 'dayjs';
import dayjs from 'dayjs';
import get from 'lodash/get';
-import React, { useCallback, useMemo, useState } from 'react';
+import React, { useMemo, useState } from 'react';
import { Calendar as BigCalendar, View, dayjsLocalizer } from 'react-big-calendar';
import * as dates from 'react-big-calendar/lib/utils/dates';
import { i18nt, useTranslation } from '../../locale';
+import { CalendarRecordViewer, findEventSchema } from './CalendarRecordViewer';
import Header from './components/Header';
import { CalendarToolbarContext } from './context';
import GlobalStyle from './global.style';
@@ -160,54 +161,24 @@ const useEvents = (dataSource: any, fieldNames: any, date: Date, view: (typeof W
}, [dataSource, fieldNames.start, fieldNames.end, fieldNames.id, fieldNames.title, date, view, t]);
};
-const CalendarRecordViewer = (props) => {
- const { visible, setVisible, record } = props;
- const { t } = useTranslation();
- const collection = useCollection();
- const parentRecordData = useCollectionParentRecordData();
- const fieldSchema = useFieldSchema();
- const eventSchema: Schema = useMemo(
- () =>
- fieldSchema.reduceProperties((buf, current) => {
- if (current['x-component'].endsWith('.Event')) {
- return current;
- }
- return buf;
- }, null),
- [],
- );
-
- const close = useCallback(() => {
- setVisible(false);
- }, []);
-
- return (
- eventSchema && (
-
-
-
-
-
-
-
-
-
- )
- );
-};
-
export const Calendar: any = withDynamicSchemaProps(
observer(
(props: any) => {
+ const [visible, setVisible] = useState(false);
+ const { openPopup } = usePopupUtils({
+ setVisible,
+ });
+
// 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
const { dataSource, fieldNames, showLunar } = useProps(props);
const height = useCalenderHeight();
const [date, setDate] = useState(new Date());
const [view, setView] = useState('month');
const events = useEvents(dataSource, fieldNames, date, view);
- const [visible, setVisible] = useState(false);
const [record, setRecord] = useState({});
const { wrapSSR, hashId, componentCls: containerClassName } = useStyle();
+ const parentRecordData = useCollectionParentRecordData();
+ const fieldSchema = useFieldSchema();
const components = useMemo(() => {
return {
@@ -247,50 +218,57 @@ export const Calendar: any = withDynamicSchemaProps(
};
return wrapSSR(
-
-
-
{
- console.log('onSelectSlot', slotInfo);
- }}
- onDoubleClickEvent={() => {
- console.log('onDoubleClickEvent');
- }}
- onSelectEvent={(event) => {
- const record = dataSource?.find((item) => item[fieldNames.id] === event.id);
- if (!record) {
- return;
- }
- record.__event = { ...event, start: formatDate(dayjs(event.start)), end: formatDate(dayjs(event.end)) };
-
- setRecord(record);
- setVisible(true);
- }}
- formats={{
- monthHeaderFormat: 'YYYY-M',
- agendaDateFormat: 'M-DD',
- dayHeaderFormat: 'YYYY-M-DD',
- dayRangeHeaderFormat: ({ start, end }, culture, local) => {
- if (dates.eq(start, end, 'month')) {
- return local.format(start, 'YYYY-M', culture);
+
+
+
+
+
+ {
+ console.log('onSelectSlot', slotInfo);
+ }}
+ onDoubleClickEvent={() => {
+ console.log('onDoubleClickEvent');
+ }}
+ onSelectEvent={(event) => {
+ const record = dataSource?.find((item) => item[fieldNames.id] === event.id);
+ if (!record) {
+ return;
}
- return `${local.format(start, 'YYYY-M', culture)} - ${local.format(end, 'YYYY-M', culture)}`;
- },
- }}
- components={components}
- localizer={localizer}
- />
+ record.__event = { ...event, start: formatDate(dayjs(event.start)), end: formatDate(dayjs(event.end)) };
+
+ setRecord(record);
+ openPopup({
+ recordData: record,
+ customActionSchema: findEventSchema(fieldSchema),
+ });
+ }}
+ formats={{
+ monthHeaderFormat: 'YYYY-M',
+ agendaDateFormat: 'M-DD',
+ dayHeaderFormat: 'YYYY-M-DD',
+ dayRangeHeaderFormat: ({ start, end }, culture, local) => {
+ if (dates.eq(start, end, 'month')) {
+ return local.format(start, 'YYYY-M', culture);
+ }
+ return `${local.format(start, 'YYYY-M', culture)} - ${local.format(end, 'YYYY-M', culture)}`;
+ },
+ }}
+ components={components}
+ localizer={localizer}
+ />
+
,
);
},
diff --git a/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/CalendarRecordViewer.tsx b/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/CalendarRecordViewer.tsx
new file mode 100644
index 0000000000..ebace0bdbd
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/CalendarRecordViewer.tsx
@@ -0,0 +1,35 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+import { RecursionField, Schema, useFieldSchema } from '@formily/react';
+import React, { FC, useMemo } from 'react';
+
+export const CalendarRecordViewer: FC = (props) => {
+ const fieldSchema = useFieldSchema();
+ const eventSchema: Schema = useMemo(() => findEventSchema(fieldSchema), [fieldSchema]);
+
+ if (!eventSchema) {
+ return null;
+ }
+
+ return ;
+};
+
+export function findEventSchema(schema: Schema) {
+ if (schema['x-component'].endsWith('.Event')) {
+ return schema;
+ }
+
+ return schema.reduceProperties((buf, current) => {
+ if (current['x-component'].endsWith('.Event')) {
+ return current;
+ }
+ return buf;
+ }, null);
+}
diff --git a/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/Event.tsx b/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/Event.tsx
index 5e08c41e85..e9fbc44955 100644
--- a/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/Event.tsx
+++ b/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/Event.tsx
@@ -8,11 +8,35 @@
*/
import { observer } from '@formily/react';
-import React from 'react';
+import {
+ PopupContextProvider,
+ useActionContext,
+ useCollection,
+ useCollectionRecordData,
+ VariablePopupRecordProvider,
+} from '@nocobase/client';
+import React, { useCallback } from 'react';
+import { DeleteEventContext } from './Calendar';
export const Event = observer(
(props) => {
- return <>{props.children}>;
+ const { visible, setVisible } = useActionContext();
+ const recordData = useCollectionRecordData();
+ const collection = useCollection();
+
+ const close = useCallback(() => {
+ setVisible(false);
+ }, [setVisible]);
+
+ return (
+
+
+
+ {props.children}
+
+
+
+ );
},
{ displayName: 'Event' },
);
diff --git a/packages/plugins/@nocobase/plugin-charts/package.json b/packages/plugins/@nocobase/plugin-charts/package.json
index 8784601af4..585555b9cd 100644
--- a/packages/plugins/@nocobase/plugin-charts/package.json
+++ b/packages/plugins/@nocobase/plugin-charts/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "图表(废弃)",
"description": "The plugin has been deprecated, please use the data visualization plugin instead.",
"description.zh-CN": "已废弃插件,请使用数据可视化插件代替。",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"main": "./dist/server/index.js",
"license": "AGPL-3.0",
"devDependencies": {
diff --git a/packages/plugins/@nocobase/plugin-client/package.json b/packages/plugins/@nocobase/plugin-client/package.json
index b1d0bbbfd8..959e7dc5fc 100644
--- a/packages/plugins/@nocobase/plugin-client/package.json
+++ b/packages/plugins/@nocobase/plugin-client/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "WEB 客户端",
"description": "Provides a client interface for the NocoBase server",
"description.zh-CN": "为 NocoBase 服务端提供客户端界面",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"main": "./dist/server/index.js",
"license": "AGPL-3.0",
"devDependencies": {
diff --git a/packages/plugins/@nocobase/plugin-collection-sql/package.json b/packages/plugins/@nocobase/plugin-collection-sql/package.json
index 8cf1f91ea9..f9febb6d54 100644
--- a/packages/plugins/@nocobase/plugin-collection-sql/package.json
+++ b/packages/plugins/@nocobase/plugin-collection-sql/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "数据表: SQL",
"description": "Provides SQL collection template",
"description.zh-CN": "提供 SQL 数据表模板",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"homepage": "https://docs-cn.nocobase.com/handbook/collection-sql",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/collection-sql",
"main": "dist/server/index.js",
diff --git a/packages/plugins/@nocobase/plugin-collection-tree/package.json b/packages/plugins/@nocobase/plugin-collection-tree/package.json
index d948bc0a82..4f9082fc38 100644
--- a/packages/plugins/@nocobase/plugin-collection-tree/package.json
+++ b/packages/plugins/@nocobase/plugin-collection-tree/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-collection-tree",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"displayName": "Collection: Tree",
"displayName.zh-CN": "数据表:树",
"description": "Provides tree collection template",
diff --git a/packages/plugins/@nocobase/plugin-collection-tree/src/client/index.tsx b/packages/plugins/@nocobase/plugin-collection-tree/src/client/index.tsx
index 3d230b6763..17f499410b 100644
--- a/packages/plugins/@nocobase/plugin-collection-tree/src/client/index.tsx
+++ b/packages/plugins/@nocobase/plugin-collection-tree/src/client/index.tsx
@@ -18,7 +18,6 @@ export class PluginCollectionTreeClient extends Plugin {
// You can get and modify the app instance here
async load() {
- console.log(this.app);
// this.app.addComponents({})
// this.app.addScopes({})
// this.app.addProvider()
diff --git a/packages/plugins/@nocobase/plugin-collection-tree/src/server/__tests__/sync.test.ts b/packages/plugins/@nocobase/plugin-collection-tree/src/server/__tests__/sync.test.ts
index 2a1aae5043..2291fc041c 100644
--- a/packages/plugins/@nocobase/plugin-collection-tree/src/server/__tests__/sync.test.ts
+++ b/packages/plugins/@nocobase/plugin-collection-tree/src/server/__tests__/sync.test.ts
@@ -7,9 +7,9 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
+import { Repository } from '@nocobase/database';
import { MockDatabase, MockServer, createMockServer } from '@nocobase/test';
import Migration from '../migrations/20240802141435-collection-tree';
-import { Repository } from '@nocobase/database';
describe('tree collection sync', async () => {
let app: MockServer;
@@ -77,38 +77,25 @@ describe('collection tree migrate test', () => {
{
type: 'belongsTo',
name: 'parent',
+ foreignKey: 'parentId',
+ target: 'test_tree',
treeParent: true,
},
{
type: 'hasMany',
name: 'children',
+ foreignKey: 'parentId',
+ target: 'test_tree',
treeChildren: true,
},
],
},
+ context: {},
});
- const collection = db.collection({
- name: 'test_tree',
- tree: 'adjacency-list',
- fields: [
- {
- type: 'string',
- name: 'name',
- },
- {
- type: 'belongsTo',
- name: 'parent',
- treeParent: true,
- },
- {
- type: 'hasMany',
- name: 'children',
- treeChildren: true,
- },
- ],
- });
- await collection.sync();
- await collection.repository.create({
+ await app.db.getCollection('test_tree').model.truncate();
+ await app.db.getCollection('main_test_tree_path').model.truncate();
+ const repository = app.db.getRepository('test_tree');
+ await repository.create({
values: [
{
name: 'c1',
@@ -131,7 +118,6 @@ describe('collection tree migrate test', () => {
});
afterEach(async () => {
- await app.db.clean({ drop: true });
await app.destroy();
});
@@ -164,26 +150,7 @@ describe('collection tree migrate test', () => {
},
});
expect(p.name).toBe('collection-tree');
- const collection1 = db.collection({
- name: 'test_tree',
- tree: 'adjacency-list',
- fields: [
- {
- type: 'string',
- name: 'name',
- },
- {
- type: 'belongsTo',
- name: 'parent',
- treeParent: true,
- },
- {
- type: 'hasMany',
- name: 'children',
- treeChildren: true,
- },
- ],
- });
+ const collection1 = db.getCollection('test_tree');
const pathCollection1 = db.getCollection(name);
expect(pathCollection1).toBeTruthy();
expect(await pathCollection1.existsInDb()).toBeTruthy();
diff --git a/packages/plugins/@nocobase/plugin-collection-tree/src/server/migrations/20240802141435-collection-tree.ts b/packages/plugins/@nocobase/plugin-collection-tree/src/server/migrations/20240802141435-collection-tree.ts
index e3e233b430..480ed9651c 100644
--- a/packages/plugins/@nocobase/plugin-collection-tree/src/server/migrations/20240802141435-collection-tree.ts
+++ b/packages/plugins/@nocobase/plugin-collection-tree/src/server/migrations/20240802141435-collection-tree.ts
@@ -7,10 +7,10 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-import { Migration } from '@nocobase/server';
import { Model, SyncOptions } from '@nocobase/database';
-import { Transaction } from 'sequelize';
+import { Migration } from '@nocobase/server';
import lodash from 'lodash';
+import { Transaction } from 'sequelize';
export default class extends Migration {
on = 'afterLoad'; // 'beforeLoad' or 'afterLoad'
@@ -28,7 +28,7 @@ export default class extends Migration {
for (const treeCollection of treeCollections) {
const name = `main_${treeCollection.name}_path`;
- this.app.db.collection({
+ const collectionOptions = {
name,
autoGenId: false,
timestamps: false,
@@ -42,11 +42,15 @@ export default class extends Migration {
fields: [{ name: 'path', length: 191 }],
},
],
- });
+ };
+ if (treeCollection.options.schema) {
+ collectionOptions['schema'] = treeCollection.options.schema;
+ }
+ this.app.db.collection(collectionOptions);
const treeExistsInDb = await this.app.db.getCollection(name).existsInDb({ transaction });
if (!treeExistsInDb) {
await this.app.db.getCollection(name).sync({ transaction } as SyncOptions);
- this.app.db.collection({
+ const opts = {
name: treeCollection.name,
autoGenId: false,
timestamps: false,
@@ -54,7 +58,11 @@ export default class extends Migration {
{ type: 'integer', name: 'id' },
{ type: 'integer', name: 'parentId' },
],
- });
+ };
+ if (treeCollection.options.schema) {
+ opts['schema'] = treeCollection.options.schema;
+ }
+ this.app.db.collection(opts);
const chunkSize = 1000;
await this.app.db.getRepository(treeCollection.name).chunk({
chunkSize: chunkSize,
diff --git a/packages/plugins/@nocobase/plugin-collection-tree/src/server/plugin.ts b/packages/plugins/@nocobase/plugin-collection-tree/src/server/plugin.ts
index 93faf0f44d..7fce8610e4 100644
--- a/packages/plugins/@nocobase/plugin-collection-tree/src/server/plugin.ts
+++ b/packages/plugins/@nocobase/plugin-collection-tree/src/server/plugin.ts
@@ -7,11 +7,11 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-import { Plugin } from '@nocobase/server';
-import { Collection, Model, SyncOptions, DestroyOptions } from '@nocobase/database';
import { DataSource, SequelizeCollectionManager } from '@nocobase/data-source-manager';
-import { Transaction } from 'sequelize';
+import { Collection, DestroyOptions, Model, SyncOptions } from '@nocobase/database';
+import { Plugin } from '@nocobase/server';
import lodash from 'lodash';
+import { Transaction } from 'sequelize';
import { TreeCollection } from './tree-collection';
const getFilterTargetKey = (model: Model) => {
@@ -40,7 +40,11 @@ class PluginCollectionTreeServer extends Plugin {
const parentForeignKey = collection.treeParentField?.foreignKey || 'parentId';
//always define tree path collection
- this.defineTreePathCollection(name);
+ const options = {};
+ if (collection.options.schema) {
+ options['schema'] = collection.options.schema;
+ }
+ this.defineTreePathCollection(name, options);
//afterSync
collectionManager.db.on(`${collection.name}.afterSync`, async ({ transaction }) => {
@@ -134,7 +138,7 @@ class PluginCollectionTreeServer extends Plugin {
});
}
- private async defineTreePathCollection(name: string) {
+ private async defineTreePathCollection(name: string, options: { schema?: string }) {
this.db.collection({
name,
autoGenId: false,
@@ -149,6 +153,7 @@ class PluginCollectionTreeServer extends Plugin {
fields: [{ name: 'path', length: 191 }],
},
],
+ ...options,
});
}
diff --git a/packages/plugins/@nocobase/plugin-data-source-main/package.json b/packages/plugins/@nocobase/plugin-data-source-main/package.json
index 8f52ebb40d..32afe694bb 100644
--- a/packages/plugins/@nocobase/plugin-data-source-main/package.json
+++ b/packages/plugins/@nocobase/plugin-data-source-main/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "数据源:主数据库",
"description": "NocoBase main database, supports relational databases such as MySQL, PostgreSQL, SQLite and so on.",
"description.zh-CN": "NocoBase 主数据库,支持 MySQL、PostgreSQL、SQLite 等关系型数据库。",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/data-source-main",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/data-source-main",
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/package.json b/packages/plugins/@nocobase/plugin-data-source-manager/package.json
index 3271228b7c..661397457d 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/package.json
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-data-source-manager",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"main": "dist/server/index.js",
"displayName": "Data source manager",
"displayName.zh-CN": "数据源管理",
@@ -10,6 +10,7 @@
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/data-source-manager",
"peerDependencies": {
"@nocobase/client": "1.x",
+ "@nocobase/plugin-acl": "1.x",
"@nocobase/server": "1.x",
"@nocobase/test": "1.x"
},
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/PermissionManager/index.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/PermissionManager/index.tsx
index b5ec3ddfbe..83f4291545 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/PermissionManager/index.tsx
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/PermissionManager/index.tsx
@@ -9,20 +9,20 @@
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
-import { Card } from 'antd';
-import React, { createContext } from 'react';
import {
- SchemaComponent,
MenuConfigure,
- SettingsCenterConfigure,
- SettingCenterProvider,
ResourceActionProvider,
+ SchemaComponent,
+ SettingCenterProvider,
+ SettingsCenterConfigure,
} from '@nocobase/client';
+import { Card } from 'antd';
+import React, { createContext } from 'react';
import { DataSourceTable } from './DataSourceTable';
+import { RoleRecordProvider } from './PermisionProvider';
import { RoleConfigure } from './RoleConfigure';
-import { StrategyActions } from './StrategyActions';
import { RolesResourcesActions } from './RolesResourcesActions';
-import { RoleRecordProvider } from './PermisionProvider';
+import { StrategyActions } from './StrategyActions';
const schema2: ISchema = {
type: 'object',
@@ -36,7 +36,7 @@ const schema2: ISchema = {
export const CurrentRolesContext = createContext({} as any);
CurrentRolesContext.displayName = 'CurrentRolesContext';
-export const DataSourcePermissionManager = ({ role }: any) => {
+export const DataSourcePermissionManager = ({ role }) => {
return (
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/PermissionManager/schemas/dataSourceTable.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/PermissionManager/schemas/dataSourceTable.ts
index c6536496ed..ab5576da06 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/PermissionManager/schemas/dataSourceTable.ts
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/PermissionManager/schemas/dataSourceTable.ts
@@ -79,7 +79,7 @@ export const dataSourceSchema: ISchema = {
'x-uid': 'input',
'x-component': 'Table.Void',
'x-component-props': {
- rowKey: 'name',
+ rowKey: 'key',
rowSelection: {
type: 'checkbox',
},
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/index.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/index.tsx
index 3d5e7ecc47..473ffb8c17 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/index.tsx
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/index.tsx
@@ -8,6 +8,7 @@
*/
import { Plugin } from '@nocobase/client';
+import PluginACLClient from '@nocobase/plugin-acl/client';
import React from 'react';
import { DatabaseConnectionProvider } from './DatabaseConnectionProvider';
import { ThirdDataSource } from './ThridDataSource';
@@ -21,10 +22,17 @@ import { NAMESPACE } from './locale';
export class PluginDataSourceManagerClient extends Plugin {
types = new Map();
async load() {
- // 注册组件
- this.app.addComponents({
- DataSourcePermissionManager,
- });
+ // register a configuration item in the Users & Permissions management page
+ this.app.pm.get(PluginACLClient).settingsUI.addPermissionsTab(({ t, TabLayout, role }) => ({
+ key: 'dataSource',
+ label: t('Data sources'),
+ children: (
+
+
+
+ ),
+ }));
+
this.app.use(DatabaseConnectionProvider);
this.app.pluginSettingsManager.add(NAMESPACE, {
title: `{{t("Data sources", { ns: "${NAMESPACE}" })}}`,
diff --git a/packages/plugins/@nocobase/plugin-data-visualization/package.json b/packages/plugins/@nocobase/plugin-data-visualization/package.json
index be41e3a3e5..0e99d58c90 100644
--- a/packages/plugins/@nocobase/plugin-data-visualization/package.json
+++ b/packages/plugins/@nocobase/plugin-data-visualization/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-data-visualization",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"displayName": "Data visualization",
"displayName.zh-CN": "数据可视化",
"description": "Provides data visualization feature, including chart block and chart filter block, support line charts, area charts, bar charts and more than a dozen kinds of charts, you can also extend more chart types.",
diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/__tests__/utils.test.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/__tests__/utils.test.ts
index b862bedd63..f05e5d4169 100644
--- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/__tests__/utils.test.ts
+++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/__tests__/utils.test.ts
@@ -10,35 +10,72 @@
import { processData } from '../utils';
describe('utils', () => {
- it('processFields', () => {
- expect(
- processData(
- [
- {
- name: 'tag',
- type: 'bigInt',
- interface: 'select',
- uiSchema: {
- type: 'string',
- enum: [
- {
- value: '1',
- label: 'Yes',
- },
- {
- value: '2',
- label: 'No',
- },
- ],
+ describe('process data', () => {
+ it('should process select field', () => {
+ expect(
+ processData(
+ [
+ {
+ name: 'tag',
+ type: 'bigInt',
+ interface: 'select',
+ uiSchema: {
+ type: 'string',
+ enum: [
+ {
+ value: '1',
+ label: 'Yes',
+ },
+ {
+ value: '2',
+ label: 'No',
+ },
+ ],
+ },
+ label: 'Tag',
+ value: 'tag',
+ key: 'tag',
},
- label: 'Tag',
- value: 'tag',
- key: 'tag',
- },
- ],
- [{ tag: 1 }],
- {},
- ),
- ).toEqual([{ tag: 'Yes' }]);
+ ],
+ [{ tag: 1 }],
+ {},
+ ),
+ ).toEqual([{ tag: 'Yes' }]);
+ });
+ it('should not process when aggregating', () => {
+ expect(
+ processData(
+ [
+ {
+ name: 'tag',
+ type: 'bigInt',
+ interface: 'select',
+ uiSchema: {
+ type: 'string',
+ enum: [
+ {
+ value: '1',
+ label: 'Yes',
+ },
+ {
+ value: '2',
+ label: 'No',
+ },
+ ],
+ },
+ label: 'Tag',
+ value: 'tag',
+ key: 'tag',
+ query: {
+ field: 'tag',
+ aggregation: 'count',
+ },
+ },
+ ],
+ [{ tag: 1 }],
+ {},
+ ),
+ ).toEqual([{ tag: 1 }]);
+ });
});
});
diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/filter/utils.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/filter/utils.ts
index 5321bffb2a..6a56930980 100644
--- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/filter/utils.ts
+++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/filter/utils.ts
@@ -179,7 +179,7 @@ export const setDefaultValue = async (field: any, variables: any) => {
field.setInitialValue(defaultValue);
} else {
field.loading = true;
- const value = await variables.parseVariable(defaultValue);
+ const { value } = await variables.parseVariable(defaultValue);
const transformedValue = transformValue(value, field.componentProps);
field.setValue(transformedValue);
field.setInitialValue(transformedValue);
diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/hooks/filter.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/hooks/filter.ts
index 846291c0e0..4439919b7e 100644
--- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/hooks/filter.ts
+++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/hooks/filter.ts
@@ -7,10 +7,10 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
+import { Schema } from '@formily/react';
import {
Collection,
CollectionFieldInterfaceManager,
- CollectionFieldOptions,
CollectionManager,
SchemaInitializerItemType,
i18n,
@@ -19,18 +19,16 @@ import {
useDataSourceManager,
useVariables,
} from '@nocobase/client';
+import { flatten, parse, unflatten } from '@nocobase/utils/client';
+import { useMemoizedFn } from 'ahooks';
+import deepmerge from 'deepmerge';
+import { default as _, default as lodash } from 'lodash';
import { useCallback, useContext, useMemo } from 'react';
import { ChartDataContext } from '../block/ChartDataProvider';
-import { Schema } from '@formily/react';
-import { useChartsTranslation } from '../locale';
import { ChartFilterContext } from '../filter/FilterProvider';
-import { useMemoizedFn } from 'ahooks';
-import { flatten, parse, unflatten } from '@nocobase/utils/client';
-import lodash from 'lodash';
-import { getFormulaComponent, getValuesByPath } from '../utils';
-import deepmerge from 'deepmerge';
import { findSchema, getFilterFieldPrefix, parseFilterFieldName } from '../filter/utils';
-import _ from 'lodash';
+import { useChartsTranslation } from '../locale';
+import { getFormulaComponent, getValuesByPath } from '../utils';
export const useCustomFieldInterface = () => {
const { getInterface } = useCollectionManager_deprecated();
@@ -420,7 +418,7 @@ export const useChartFilter = () => {
if (['$user', '$date', '$nDate', '$nRole', '$nFilter'].some((n) => value.includes(n))) {
return value;
}
- const result = variables?.parseVariable(value);
+ const result = variables?.parseVariable(value).then(({ value }) => value);
return result;
},
});
diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/utils.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/utils.ts
index a823640fa5..71a7ade470 100644
--- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/utils.ts
+++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/utils.ts
@@ -73,6 +73,7 @@ export const getSelectedFields = (fields: FieldOption[], query: QueryProps) => {
key: selectedField.alias || fieldProps?.key,
label: selectedField.alias || fieldProps?.label,
value: selectedField.alias || fieldProps?.value,
+ query: selectedField,
};
});
};
@@ -84,7 +85,7 @@ export const getSelectedFields = (fields: FieldOption[], query: QueryProps) => {
return selectedFields;
};
-export const processData = (selectedFields: FieldOption[], data: any[], scope: any) => {
+export const processData = (selectedFields: (FieldOption & { query?: any })[], data: any[], scope: any) => {
const parseEnum = (field: FieldOption, value: any) => {
const options = field.uiSchema?.enum as { value: string; label: string }[];
if (!options || !Array.isArray(options)) {
@@ -99,7 +100,7 @@ export const processData = (selectedFields: FieldOption[], data: any[], scope: a
return data.map((record) => {
const processed = {};
Object.entries(record).forEach(([key, value]) => {
- const field = selectedFields.find((field) => field.value === key);
+ const field = selectedFields.find((field) => field.value === key && !field?.query?.aggregation);
if (!field) {
processed[key] = value;
return;
diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/server/actions/query.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/server/actions/query.ts
index 7472136648..4fecc9cfbb 100644
--- a/packages/plugins/@nocobase/plugin-data-visualization/src/server/actions/query.ts
+++ b/packages/plugins/@nocobase/plugin-data-visualization/src/server/actions/query.ts
@@ -79,6 +79,7 @@ export const postProcess = async (ctx: Context, next: Next) => {
case 'integer':
case 'float':
case 'double':
+ case 'decimal':
record[key] = Number(value);
break;
}
diff --git a/packages/plugins/@nocobase/plugin-disable-pm-add/package.json b/packages/plugins/@nocobase/plugin-disable-pm-add/package.json
index 9aee732a06..42de27b5f9 100644
--- a/packages/plugins/@nocobase/plugin-disable-pm-add/package.json
+++ b/packages/plugins/@nocobase/plugin-disable-pm-add/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-disable-pm-add",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"main": "./dist/server/index.js",
"peerDependencies": {
"@nocobase/client": "1.x",
diff --git a/packages/plugins/@nocobase/plugin-error-handler/package.json b/packages/plugins/@nocobase/plugin-error-handler/package.json
index b2d5899b2f..75e55669f5 100644
--- a/packages/plugins/@nocobase/plugin-error-handler/package.json
+++ b/packages/plugins/@nocobase/plugin-error-handler/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "错误处理器",
"description": "Handling application errors and exceptions.",
"description.zh-CN": "处理应用程序中的错误和异常。",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"devDependencies": {
diff --git a/packages/plugins/@nocobase/plugin-field-china-region/package.json b/packages/plugins/@nocobase/plugin-field-china-region/package.json
index ddf8a8d452..b73c894486 100644
--- a/packages/plugins/@nocobase/plugin-field-china-region/package.json
+++ b/packages/plugins/@nocobase/plugin-field-china-region/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-field-china-region",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"displayName": "Administrative divisions of China",
"displayName.zh-CN": "中国行政区划",
"description": "Provides data and field type for administrative divisions of China.",
diff --git a/packages/plugins/@nocobase/plugin-field-formula/package.json b/packages/plugins/@nocobase/plugin-field-formula/package.json
index 4e46461758..0c3aaa307c 100644
--- a/packages/plugins/@nocobase/plugin-field-formula/package.json
+++ b/packages/plugins/@nocobase/plugin-field-formula/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "数据表字段:公式",
"description": "Configure and store the results of calculations between multiple field values in the same record, supporting both Math.js and Excel formula functions.",
"description.zh-CN": "可以配置并存储同一条记录的多字段值之间的计算结果,支持 Math.js 和 Excel formula functions 两种引擎",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/field-formula",
diff --git a/packages/plugins/@nocobase/plugin-field-formula/src/client/components/Formula/Result.tsx b/packages/plugins/@nocobase/plugin-field-formula/src/client/components/Formula/Result.tsx
index d5d9938f85..52a88dc8da 100644
--- a/packages/plugins/@nocobase/plugin-field-formula/src/client/components/Formula/Result.tsx
+++ b/packages/plugins/@nocobase/plugin-field-formula/src/client/components/Formula/Result.tsx
@@ -18,8 +18,8 @@ import {
useCollection_deprecated,
useCollectionManager_deprecated,
useFormBlockContext,
- ActionContext,
} from '@nocobase/client';
+import _ from 'lodash';
import { Evaluator, evaluators } from '@nocobase/evaluators/client';
import { Registry, toFixedByStep } from '@nocobase/utils/client';
import React, { useEffect, useState, useContext } from 'react';
@@ -61,6 +61,22 @@ function getValuesByPath(values, key, index?) {
}
}
+function areValuesEqual(value1, value2) {
+ if (_.isString(value1) && !isNaN(Date.parse(value1))) {
+ value1 = new Date(value1);
+ }
+
+ if (_.isString(value2) && !isNaN(Date.parse(value2))) {
+ value2 = new Date(value2);
+ }
+
+ if (_.isDate(value1) && _.isDate(value2)) {
+ return value1.getTime() === value2.getTime();
+ }
+
+ return _.isEqual(value1, value2);
+}
+
export function Result(props) {
const { value, ...others } = props;
const fieldSchema = useFieldSchema();
@@ -91,7 +107,7 @@ export function Result(props) {
let v;
try {
v = evaluate(expression, scope);
- v = toDbType(v, dataType);
+ v = v && toDbType(v, dataType);
} catch (error) {
v = null;
}
@@ -99,11 +115,16 @@ export function Result(props) {
setEditingValue(v);
}
setEditingValue(v);
- if (v !== field.value) {
- field.value = v;
- }
});
});
+
+ useEffect(() => {
+ if (!areValuesEqual(field.value, editingValue)) {
+ setTimeout(() => {
+ field.value = editingValue;
+ });
+ }
+ }, [editingValue]);
const Component = TypedComponents[dataType] ?? InputString;
return (
diff --git a/packages/plugins/@nocobase/plugin-field-m2m-array/package.json b/packages/plugins/@nocobase/plugin-field-m2m-array/package.json
index 503be69a1f..ad130897ec 100644
--- a/packages/plugins/@nocobase/plugin-field-m2m-array/package.json
+++ b/packages/plugins/@nocobase/plugin-field-m2m-array/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "数据表字段:多对多 (数组)",
"description": "Allows to create many to many relationships between two models by storing an array of unique keys of the target model.",
"description.zh-CN": "支持通过在数组中存储目标表唯一键的方式建立多对多关系。",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"main": "dist/server/index.js",
"peerDependencies": {
"@nocobase/client": "1.x",
diff --git a/packages/plugins/@nocobase/plugin-field-m2m-array/src/server/belongs-to-array-field.ts b/packages/plugins/@nocobase/plugin-field-m2m-array/src/server/belongs-to-array-field.ts
index 104cda0c03..6b4b30ccad 100644
--- a/packages/plugins/@nocobase/plugin-field-m2m-array/src/server/belongs-to-array-field.ts
+++ b/packages/plugins/@nocobase/plugin-field-m2m-array/src/server/belongs-to-array-field.ts
@@ -12,6 +12,7 @@ import { BaseColumnFieldOptions, BelongsToArrayAssociation, Model, RelationField
export const elementTypeMap = {
nanoid: 'string',
sequence: 'string',
+ uid: 'string',
};
export class BelongsToArrayField extends RelationField {
diff --git a/packages/plugins/@nocobase/plugin-field-m2m-array/src/server/hooks/before-destroy-foreign-key.ts b/packages/plugins/@nocobase/plugin-field-m2m-array/src/server/hooks/before-destroy-foreign-key.ts
index 995da63501..d459cad9cd 100644
--- a/packages/plugins/@nocobase/plugin-field-m2m-array/src/server/hooks/before-destroy-foreign-key.ts
+++ b/packages/plugins/@nocobase/plugin-field-m2m-array/src/server/hooks/before-destroy-foreign-key.ts
@@ -19,6 +19,9 @@ export function beforeDestroyForeignKey(db: Database) {
const fieldKeys = [];
const collection = db.getCollection(collectionName);
+ if (!collection) {
+ return;
+ }
for (const [, field] of collection.fields) {
const fieldKey = field.options?.key;
diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/package.json b/packages/plugins/@nocobase/plugin-field-markdown-vditor/package.json
index ad8085cf95..056a672feb 100644
--- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/package.json
+++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "数据表字段:Markdown(Vditor)",
"description": "Used to store Markdown and render it using Vditor editor, supports common Markdown syntax such as list, code, quote, etc., and supports uploading images, recordings, etc.It also allows for instant rendering, where what you see is what you get.",
"description.zh-CN": "用于存储 Markdown,并使用 Vditor 编辑器渲染,支持常见 Markdown 语法,如列表,代码,引用等,并支持上传图片,录音等。同时可以做到即时渲染,所见即所得。",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"license": "AGPL-3.0",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/field-markdown-vditor",
diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/components/Display.tsx b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/components/Display.tsx
index 474c15d0d7..9c2ee41061 100644
--- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/components/Display.tsx
+++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/components/Display.tsx
@@ -35,13 +35,12 @@ const getContentWidth = (element) => {
};
function DisplayInner(props: { value: string; style?: CSSProperties }) {
- const containerRef = useRef();
+ const containerRef = useRef(null);
const { wrapSSR, componentCls, hashId } = useStyle();
const cdn = useCDN();
useEffect(() => {
- if (!props.value) return;
- Vditor.preview(containerRef.current, props.value, {
+ Vditor.preview(containerRef.current, props.value ?? '', {
mode: 'light',
cdn,
});
@@ -67,7 +66,6 @@ export const Display = withDynamicSchemaProps((props) => {
const [text, setText] = useState('');
const elRef = useRef();
-
useEffect(() => {
if (!props.value || !field.value) return;
if (props.ellipsis) {
@@ -125,6 +123,5 @@ export const Display = withDynamicSchemaProps((props) => {
);
}
-
return ;
});
diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/components/Edit.tsx b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/components/Edit.tsx
index fa828d063a..475bab4232 100644
--- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/components/Edit.tsx
+++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/components/Edit.tsx
@@ -8,7 +8,7 @@
*/
import { useAPIClient, useApp, withDynamicSchemaProps } from '@nocobase/client';
-import React, { useEffect, useLayoutEffect, useMemo, useRef } from 'react';
+import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import Vditor from 'vditor';
import { defaultToolbar } from '../interfaces/markdown-vditor';
import { useCDN } from './const';
@@ -19,10 +19,11 @@ const locales = ['en_US', 'fr_FR', 'pt_BR', 'ja_JP', 'ko_KR', 'ru_RU', 'sv_SE',
export const Edit = withDynamicSchemaProps((props) => {
const { disabled, onChange, value, fileCollection, toolbar } = props;
+ const [editorReady, setEditorReady] = useState(false);
const vdRef = useRef();
const vdFullscreen = useRef(false);
- const containerRef = useRef();
- const containerParentRef = useRef();
+ const containerRef = useRef(null);
+ const containerParentRef = useRef(null);
const app = useApp();
const apiClient = useAPIClient();
const cdn = useCDN();
@@ -38,28 +39,24 @@ export const Edit = withDynamicSchemaProps((props) => {
}, [locale]);
useEffect(() => {
+ if (!containerRef.current) return;
+
const uploadFileCollection = fileCollection || 'attachments';
const toolbarConfig = toolbar ?? defaultToolbar;
+
const vditor = new Vditor(containerRef.current, {
value: value ?? '',
lang,
- cache: {
- enable: false,
- },
+ cache: { enable: false },
undoDelay: 0,
- preview: {
- math: {
- engine: 'KaTeX',
- },
- },
+ preview: { math: { engine: 'KaTeX' } },
toolbar: toolbarConfig,
- fullscreen: {
- index: 1200,
- },
+ fullscreen: { index: 1200 },
cdn,
minHeight: 200,
after: () => {
vdRef.current = vditor;
+ setEditorReady(true); // Notify that the editor is ready
vditor.setValue(value ?? '');
if (disabled) {
vditor.disabled();
@@ -92,6 +89,7 @@ export const Edit = withDynamicSchemaProps((props) => {
},
},
});
+
return () => {
vdRef.current?.destroy();
vdRef.current = undefined;
@@ -99,34 +97,42 @@ export const Edit = withDynamicSchemaProps((props) => {
}, [fileCollection, toolbar?.join(',')]);
useEffect(() => {
- if (value === vdRef?.current?.getValue()) {
- return;
- }
- vdRef.current?.setValue(value);
- vdRef.current?.focus();
- // 移动光标到末尾
- const preArea = containerRef.current.querySelector('div.vditor-content > div.vditor-ir > pre') as HTMLPreElement;
- if (preArea) {
- const range = document.createRange();
- const selection = window.getSelection();
- if (selection) {
- range.selectNodeContents(preArea);
- range.collapse(false); // 将光标移动到内容末尾
- selection.removeAllRanges();
- selection.addRange(range);
+ if (editorReady && vdRef.current) {
+ const editor = vdRef.current;
+ if (value !== editor.getValue()) {
+ editor.setValue(value ?? '');
+ // editor.focus();
+
+ const preArea = containerRef.current?.querySelector(
+ 'div.vditor-content > div.vditor-ir > pre',
+ ) as HTMLPreElement;
+ if (preArea) {
+ const range = document.createRange();
+ const selection = window.getSelection();
+ if (selection) {
+ range.selectNodeContents(preArea);
+ range.collapse(false); // Move cursor to the end
+ selection.removeAllRanges();
+ selection.addRange(range);
+ }
+ }
}
}
- }, [value]);
+ }, [value, editorReady]);
useEffect(() => {
- if (disabled) {
- vdRef.current?.disabled();
- } else {
- vdRef.current?.enable();
+ if (editorReady && vdRef.current) {
+ if (disabled) {
+ vdRef.current.disabled();
+ } else {
+ vdRef.current.enable();
+ }
}
- }, [disabled]);
+ }, [disabled, editorReady]);
useLayoutEffect(() => {
+ if (!containerRef.current) return;
+
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const target = entry.target;
diff --git a/packages/plugins/@nocobase/plugin-field-sequence/package.json b/packages/plugins/@nocobase/plugin-field-sequence/package.json
index 3581932e08..da63bb1daf 100644
--- a/packages/plugins/@nocobase/plugin-field-sequence/package.json
+++ b/packages/plugins/@nocobase/plugin-field-sequence/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "数据表字段:自动编码",
"description": "Automatically generate codes based on configured rules, supporting combinations of dates, numbers, and text.",
"description.zh-CN": "根据配置的规则自动生成编码,支持日期、数字、文本的组合。",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/field-sequence",
diff --git a/packages/plugins/@nocobase/plugin-file-manager/package.json b/packages/plugins/@nocobase/plugin-file-manager/package.json
index 439627ffc0..701d16ffc2 100644
--- a/packages/plugins/@nocobase/plugin-file-manager/package.json
+++ b/packages/plugins/@nocobase/plugin-file-manager/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-file-manager",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"displayName": "File manager",
"displayName.zh-CN": "文件管理器",
"description": "Provides files storage services with files collection template and attachment field.",
diff --git a/packages/plugins/@nocobase/plugin-gantt/package.json b/packages/plugins/@nocobase/plugin-gantt/package.json
index b50dced1b0..0555e33d55 100644
--- a/packages/plugins/@nocobase/plugin-gantt/package.json
+++ b/packages/plugins/@nocobase/plugin-gantt/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-gantt",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"displayName": "Block: Gantt",
"displayName.zh-CN": "区块:甘特图",
"description": "Provides Gantt block.",
diff --git a/packages/plugins/@nocobase/plugin-gantt/src/client/components/gantt/Event.tsx b/packages/plugins/@nocobase/plugin-gantt/src/client/components/gantt/Event.tsx
index 5e08c41e85..c41d86b4cd 100644
--- a/packages/plugins/@nocobase/plugin-gantt/src/client/components/gantt/Event.tsx
+++ b/packages/plugins/@nocobase/plugin-gantt/src/client/components/gantt/Event.tsx
@@ -8,11 +8,28 @@
*/
import { observer } from '@formily/react';
+import {
+ PopupContextProvider,
+ useActionContext,
+ useCollection,
+ useCollectionRecordData,
+ VariablePopupRecordProvider,
+} from '@nocobase/client';
import React from 'react';
export const Event = observer(
(props) => {
- return <>{props.children}>;
+ const { visible, setVisible } = useActionContext();
+ const recordData = useCollectionRecordData();
+ const collection = useCollection();
+
+ return (
+
+
+ {props.children}
+
+
+ );
},
{ displayName: 'Event' },
);
diff --git a/packages/plugins/@nocobase/plugin-gantt/src/client/components/gantt/GanttRecordViewer.tsx b/packages/plugins/@nocobase/plugin-gantt/src/client/components/gantt/GanttRecordViewer.tsx
new file mode 100644
index 0000000000..65b33a519d
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-gantt/src/client/components/gantt/GanttRecordViewer.tsx
@@ -0,0 +1,23 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+import { RecursionField, useFieldSchema } from '@formily/react';
+import { Schema } from '@nocobase/utils';
+import React, { FC } from 'react';
+
+export const GanttRecordViewer: FC = (props) => {
+ const fieldSchema = useFieldSchema();
+ const eventSchema: Schema = fieldSchema.properties.detail;
+
+ if (!eventSchema) {
+ return null;
+ }
+
+ return ;
+};
diff --git a/packages/plugins/@nocobase/plugin-gantt/src/client/components/gantt/gantt.tsx b/packages/plugins/@nocobase/plugin-gantt/src/client/components/gantt/gantt.tsx
index a656a6d081..3e495a47e6 100644
--- a/packages/plugins/@nocobase/plugin-gantt/src/client/components/gantt/gantt.tsx
+++ b/packages/plugins/@nocobase/plugin-gantt/src/client/components/gantt/gantt.tsx
@@ -8,17 +8,16 @@
*/
import { css, cx } from '@emotion/css';
-import { RecursionField, Schema, useFieldSchema } from '@formily/react';
+import { RecursionField, useFieldSchema } from '@formily/react';
import {
- ActionContextProvider,
+ PopupContextProvider,
RecordProvider,
- VariablePopupRecordProvider,
useAPIClient,
useBlockRequestContext,
- useCollection,
useCollectionParentRecordData,
useCurrentAppInfo,
useDesignable,
+ usePopupUtils,
useProps,
useTableBlockContext,
useToken,
@@ -26,7 +25,7 @@ import {
} from '@nocobase/client';
import { Spin, message } from 'antd';
import { debounce } from 'lodash';
-import React, { SyntheticEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import React, { SyntheticEvent, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useGanttBlockContext } from '../../GanttBlockProvider';
import { convertToBarTasks } from '../../helpers/bar-helper';
@@ -41,6 +40,7 @@ import { GridProps } from '../grid/grid';
import { HorizontalScroll } from '../other/horizontal-scroll';
import { StandardTooltipContent, Tooltip } from '../other/tooltip';
import { VerticalScroll } from '../other/vertical-scroll';
+import { GanttRecordViewer } from './GanttRecordViewer';
import useStyles from './style';
import { TaskGantt } from './task-gantt';
import { TaskGanttContentProps } from './task-gantt-content';
@@ -49,34 +49,7 @@ const getColumnWidth = (dataSetLength: any, clientWidth: any) => {
const columnWidth = clientWidth / dataSetLength > 50 ? Math.floor(clientWidth / dataSetLength) + 20 : 50;
return columnWidth;
};
-export const DeleteEventContext = React.createContext({
- close: () => {},
-});
-const GanttRecordViewer = (props) => {
- const { visible, setVisible, record } = props;
- const { t } = useTranslation();
- const collection = useCollection();
- const parentRecordData = useCollectionParentRecordData();
- const fieldSchema = useFieldSchema();
- const eventSchema: Schema = fieldSchema.properties.detail;
- const close = useCallback(() => {
- setVisible(false);
- }, []);
- return (
- eventSchema && (
-
-
-
-
-
-
-
-
-
- )
- );
-};
const debounceHandleTaskChange = debounce(async (task: Task, resource, fieldNames, service, t) => {
await resource.update({
filterByTk: task.id,
@@ -160,7 +133,11 @@ export const Gantt: any = withDynamicSchemaProps((props: any) => {
return { viewMode, dates: seedDates(startDate, endDate, viewMode) };
});
const [visible, setVisible] = useState(false);
+ const { openPopup } = usePopupUtils({
+ setVisible,
+ });
const [record, setRecord] = useState({});
+ const parentRecordData = useCollectionParentRecordData();
const [currentViewDate, setCurrentViewDate] = useState(undefined);
const [taskListWidth, setTaskListWidth] = useState(0);
const [svgContainerWidth, setSvgContainerWidth] = useState(0);
@@ -473,7 +450,10 @@ export const Gantt: any = withDynamicSchemaProps((props: any) => {
return;
}
setRecord(recordData);
- setVisible(true);
+ openPopup({
+ recordData,
+ customActionSchema: fieldSchema.properties.detail,
+ });
};
const gridProps: GridProps = {
columnWidth,
@@ -536,55 +516,59 @@ export const Gantt: any = withDynamicSchemaProps((props: any) => {
`)}
ref={ganttRef}
>
-
-
-
-
-
- {ganttEvent.changedTask && (
-
+
+
+
+
+
+
+
- )}
-
-
-
+ )}
+
-
-
+
+
+
+
+
);
});
diff --git a/packages/plugins/@nocobase/plugin-graph-collection-manager/package.json b/packages/plugins/@nocobase/plugin-graph-collection-manager/package.json
index 20763f7d44..51f13538d0 100644
--- a/packages/plugins/@nocobase/plugin-graph-collection-manager/package.json
+++ b/packages/plugins/@nocobase/plugin-graph-collection-manager/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "可视化数据表管理",
"description": "An ER diagram-like tool. Currently only the Master database is supported.",
"description.zh-CN": "类似 ER 图的工具,目前只支持主数据库。",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/graph-collection-manager",
diff --git a/packages/plugins/@nocobase/plugin-kanban/package.json b/packages/plugins/@nocobase/plugin-kanban/package.json
index 717e59d152..995781685c 100644
--- a/packages/plugins/@nocobase/plugin-kanban/package.json
+++ b/packages/plugins/@nocobase/plugin-kanban/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-kanban",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/block-kanban",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/block-kanban",
diff --git a/packages/plugins/@nocobase/plugin-localization/package.json b/packages/plugins/@nocobase/plugin-localization/package.json
index 76f9659aa9..812e750ed5 100644
--- a/packages/plugins/@nocobase/plugin-localization/package.json
+++ b/packages/plugins/@nocobase/plugin-localization/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-localization",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/localization-management",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/localization-management",
diff --git a/packages/plugins/@nocobase/plugin-logger/package.json b/packages/plugins/@nocobase/plugin-logger/package.json
index 23646ffbdf..e279bb9bd8 100644
--- a/packages/plugins/@nocobase/plugin-logger/package.json
+++ b/packages/plugins/@nocobase/plugin-logger/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "日志",
"description": "Server-side logs, mainly including API request logs and system runtime logs, and allows to package and download log files.",
"description.zh-CN": "服务端日志,主要包括接口请求日志和系统运行日志,并支持打包和下载日志文件。",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"license": "AGPL-3.0",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/logger",
diff --git a/packages/plugins/@nocobase/plugin-map/package.json b/packages/plugins/@nocobase/plugin-map/package.json
index 13a33fb533..e11da760bc 100644
--- a/packages/plugins/@nocobase/plugin-map/package.json
+++ b/packages/plugins/@nocobase/plugin-map/package.json
@@ -2,7 +2,7 @@
"name": "@nocobase/plugin-map",
"displayName": "Block: Map",
"displayName.zh-CN": "区块:地图",
- "version": "1.3.0-beta",
+ "version": "1.3.19-beta",
"description": "Map block, support Gaode map and Google map, you can also extend more map types.",
"description.zh-CN": "地图区块,支持高德地图和 Google 地图,你也可以扩展更多地图类型。",
"license": "AGPL-3.0",
diff --git a/packages/plugins/@nocobase/plugin-map/src/client/block/MapBlock.tsx b/packages/plugins/@nocobase/plugin-map/src/client/block/MapBlock.tsx
index 1915fa44a4..52157ee421 100644
--- a/packages/plugins/@nocobase/plugin-map/src/client/block/MapBlock.tsx
+++ b/packages/plugins/@nocobase/plugin-map/src/client/block/MapBlock.tsx
@@ -8,15 +8,29 @@
*/
import {
+ PopupContextProvider,
useCollection_deprecated,
useCollectionManager_deprecated,
+ usePopupUtils,
useProps,
withDynamicSchemaProps,
} from '@nocobase/client';
import React, { useMemo } from 'react';
import { MapBlockComponent } from '../components';
+import { MapBlockDrawer } from '../components/MapBlockDrawer';
export const MapBlock = withDynamicSchemaProps((props) => {
+ const { context } = usePopupUtils();
+
+ // only render the popup
+ if (context.currentLevel) {
+ return (
+
+
+
+ );
+ }
+
// 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
const { fieldNames } = useProps(props);
diff --git a/packages/plugins/@nocobase/plugin-map/src/client/components/AMap/Block.tsx b/packages/plugins/@nocobase/plugin-map/src/client/components/AMap/Block.tsx
index 50e5d8ed70..7238c7e88b 100644
--- a/packages/plugins/@nocobase/plugin-map/src/client/components/AMap/Block.tsx
+++ b/packages/plugins/@nocobase/plugin-map/src/client/components/AMap/Block.tsx
@@ -8,11 +8,8 @@
*/
import { CheckOutlined, EnvironmentOutlined, ExpandOutlined } from '@ant-design/icons';
-import { RecursionField, useFieldSchema } from '@formily/react';
import {
- ActionContextProvider,
RecordProvider,
- VariablePopupRecordProvider,
css,
getLabelFormatValue,
useCollection,
@@ -21,14 +18,16 @@ import {
useCollection_deprecated,
useCompile,
useFilterAPI,
+ usePopupUtils,
useProps,
} from '@nocobase/client';
import { useMemoizedFn } from 'ahooks';
import { Button, Space } from 'antd';
-import React, { useEffect, useMemo, useRef, useState } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
import { defaultImage, selectedImage } from '../../constants';
import { useMapTranslation } from '../../locale';
import { getSource } from '../../utils';
+import { MapBlockDrawer } from '../MapBlockDrawer';
import { AMapComponent, AMapForwardedRefProps } from './Map';
export const AMapBlock = (props) => {
@@ -50,6 +49,9 @@ export const AMapBlock = (props) => {
const selectingModeRef = useRef(selectingMode);
selectingModeRef.current = selectingMode;
const { fields } = useCollection();
+ const parentRecordData = useCollectionParentRecordData();
+ const { openPopup } = usePopupUtils();
+
const labelUiSchema = fields.find((v) => v.name === fieldNames?.marker)?.uiSchema;
const setOverlayOptions = (overlay: AMap.Polygon | AMap.Marker, state?: boolean) => {
const extData = overlay.getExtData();
@@ -199,6 +201,9 @@ export const AMapBlock = (props) => {
if (data) {
setRecord(data);
+ openPopup({
+ recordData: data,
+ });
}
};
o.on('click', onClick);
@@ -244,7 +249,17 @@ export const AMapBlock = (props) => {
});
events.forEach((e) => e());
};
- }, [dataSource, isMapInitialization, fieldNames, name, primaryKey, collectionField.type, isConnected, lineSort]);
+ }, [
+ dataSource,
+ isMapInitialization,
+ fieldNames,
+ name,
+ primaryKey,
+ collectionField.type,
+ isConnected,
+ lineSort,
+ openPopup,
+ ]);
useEffect(() => {
setTimeout(() => {
@@ -307,7 +322,9 @@ export const AMapBlock = (props) => {
) : null}
-