mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-07 22:49:26 +08:00
Merge branch 'develop' of github.com:nocobase/nocobase into feat-main-datasource-mssql
This commit is contained in:
commit
b93d3bc01d
19
CHANGELOG.md
19
CHANGELOG.md
@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [v1.6.10](https://github.com/nocobase/nocobase/compare/v1.6.9...v1.6.10) - 2025-03-25
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[client]**
|
||||
- Unable to use 'Current User' variable when adding a link page ([#6536](https://github.com/nocobase/nocobase/pull/6536)) by @zhangzhonghe
|
||||
|
||||
- field assignment with null value is ineffective ([#6549](https://github.com/nocobase/nocobase/pull/6549)) by @katherinehhh
|
||||
|
||||
- `yarn doc` command error ([#6540](https://github.com/nocobase/nocobase/pull/6540)) by @gchust
|
||||
|
||||
- Remove the 'Allow multiple selection' option from dropdown single-select fields in filter forms ([#6515](https://github.com/nocobase/nocobase/pull/6515)) by @zhangzhonghe
|
||||
|
||||
- Relational field's data range linkage is not effective ([#6530](https://github.com/nocobase/nocobase/pull/6530)) by @zhangzhonghe
|
||||
|
||||
- **[Collection: Tree]** Migration issue for plugin-collection-tree ([#6537](https://github.com/nocobase/nocobase/pull/6537)) by @2013xile
|
||||
|
||||
- **[Action: Custom request]** Unable to download UTF-8 encoded files ([#6541](https://github.com/nocobase/nocobase/pull/6541)) by @2013xile
|
||||
|
||||
## [v1.6.9](https://github.com/nocobase/nocobase/compare/v1.6.8...v1.6.9) - 2025-03-23
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
@ -5,6 +5,25 @@
|
||||
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
|
||||
并且本项目遵循 [语义化版本](https://semver.org/spec/v2.0.0.html)。
|
||||
|
||||
## [v1.6.10](https://github.com/nocobase/nocobase/compare/v1.6.9...v1.6.10) - 2025-03-25
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[client]**
|
||||
- 添加链接页面时,无法使用“当前用户”变量 ([#6536](https://github.com/nocobase/nocobase/pull/6536)) by @zhangzhonghe
|
||||
|
||||
- 字段赋值对字段进行“空值”赋值无效 ([#6549](https://github.com/nocobase/nocobase/pull/6549)) by @katherinehhh
|
||||
|
||||
- `yarn doc` 命令报错 ([#6540](https://github.com/nocobase/nocobase/pull/6540)) by @gchust
|
||||
|
||||
- 筛选表单中,移除下拉单选字段的“允许多选”选项 ([#6515](https://github.com/nocobase/nocobase/pull/6515)) by @zhangzhonghe
|
||||
|
||||
- 关系字段的数据范围联动不生效 ([#6530](https://github.com/nocobase/nocobase/pull/6530)) by @zhangzhonghe
|
||||
|
||||
- **[数据表:树]** 树表插件的迁移脚本问题 ([#6537](https://github.com/nocobase/nocobase/pull/6537)) by @2013xile
|
||||
|
||||
- **[操作:自定义请求]** 无法下载utf8编码的文件 ([#6541](https://github.com/nocobase/nocobase/pull/6541)) by @2013xile
|
||||
|
||||
## [v1.6.9](https://github.com/nocobase/nocobase/compare/v1.6.8...v1.6.9) - 2025-03-23
|
||||
|
||||
### 🐛 修复
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"npmClientArgs": ["--ignore-engines"],
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@nocobase/acl",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/resourcer": "1.7.0-alpha.4",
|
||||
"@nocobase/utils": "1.7.0-alpha.4",
|
||||
"@nocobase/resourcer": "1.7.0-beta.9",
|
||||
"@nocobase/utils": "1.7.0-beta.9",
|
||||
"minimatch": "^5.1.1"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "@nocobase/actions",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/cache": "1.7.0-alpha.4",
|
||||
"@nocobase/database": "1.7.0-alpha.4",
|
||||
"@nocobase/resourcer": "1.7.0-alpha.4"
|
||||
"@nocobase/cache": "1.7.0-beta.9",
|
||||
"@nocobase/database": "1.7.0-beta.9",
|
||||
"@nocobase/resourcer": "1.7.0-beta.9"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -1,17 +1,17 @@
|
||||
{
|
||||
"name": "@nocobase/app",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/database": "1.7.0-alpha.4",
|
||||
"@nocobase/preset-nocobase": "1.7.0-alpha.4",
|
||||
"@nocobase/server": "1.7.0-alpha.4"
|
||||
"@nocobase/database": "1.7.0-beta.9",
|
||||
"@nocobase/preset-nocobase": "1.7.0-beta.9",
|
||||
"@nocobase/server": "1.7.0-beta.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nocobase/client": "1.7.0-alpha.4"
|
||||
"@nocobase/client": "1.7.0-beta.9"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "@nocobase/auth",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/actions": "1.7.0-alpha.4",
|
||||
"@nocobase/cache": "1.7.0-alpha.4",
|
||||
"@nocobase/database": "1.7.0-alpha.4",
|
||||
"@nocobase/resourcer": "1.7.0-alpha.4",
|
||||
"@nocobase/utils": "1.7.0-alpha.4",
|
||||
"@nocobase/actions": "1.7.0-beta.9",
|
||||
"@nocobase/cache": "1.7.0-beta.9",
|
||||
"@nocobase/database": "1.7.0-beta.9",
|
||||
"@nocobase/resourcer": "1.7.0-beta.9",
|
||||
"@nocobase/utils": "1.7.0-beta.9",
|
||||
"@types/jsonwebtoken": "^8.5.8",
|
||||
"jsonwebtoken": "^8.5.1"
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/build",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"description": "Library build tool based on rollup.",
|
||||
"main": "lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
|
4
packages/core/cache/package.json
vendored
4
packages/core/cache/package.json
vendored
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@nocobase/cache",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/lock-manager": "1.7.0-alpha.4",
|
||||
"@nocobase/lock-manager": "1.7.0-beta.9",
|
||||
"bloom-filters": "^3.0.1",
|
||||
"cache-manager": "^5.2.4",
|
||||
"cache-manager-redis-yet": "^4.1.2"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/cli",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./src/index.js",
|
||||
@ -8,7 +8,7 @@
|
||||
"nocobase": "./bin/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nocobase/app": "1.7.0-alpha.4",
|
||||
"@nocobase/app": "1.7.0-beta.9",
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"@umijs/utils": "3.5.20",
|
||||
"chalk": "^4.1.1",
|
||||
@ -25,7 +25,7 @@
|
||||
"tsx": "^4.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nocobase/devtools": "1.7.0-alpha.4"
|
||||
"@nocobase/devtools": "1.7.0-beta.9"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/client",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "lib/index.js",
|
||||
"module": "es/index.mjs",
|
||||
@ -27,9 +27,9 @@
|
||||
"@formily/reactive-react": "^2.2.27",
|
||||
"@formily/shared": "^2.2.27",
|
||||
"@formily/validator": "^2.2.27",
|
||||
"@nocobase/evaluators": "1.7.0-alpha.4",
|
||||
"@nocobase/sdk": "1.7.0-alpha.4",
|
||||
"@nocobase/utils": "1.7.0-alpha.4",
|
||||
"@nocobase/evaluators": "1.7.0-beta.9",
|
||||
"@nocobase/sdk": "1.7.0-beta.9",
|
||||
"@nocobase/utils": "1.7.0-beta.9",
|
||||
"ahooks": "^3.7.2",
|
||||
"antd": "5.24.2",
|
||||
"antd-style": "3.7.1",
|
||||
|
@ -102,44 +102,46 @@ export const SettingsCenterConfigure = () => {
|
||||
expandable={{
|
||||
defaultExpandAllRows: true,
|
||||
}}
|
||||
columns={[
|
||||
{
|
||||
dataIndex: 'title',
|
||||
title: t('Plugin name'),
|
||||
render: (value) => {
|
||||
return compile(value);
|
||||
columns={
|
||||
[
|
||||
{
|
||||
dataIndex: 'title',
|
||||
title: t('Plugin name'),
|
||||
render: (value) => {
|
||||
return compile(value);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'accessible',
|
||||
title: (
|
||||
<>
|
||||
<Checkbox
|
||||
checked={allChecked}
|
||||
onChange={async () => {
|
||||
const values = allAclSnippets.map((v) => '!' + v);
|
||||
if (!allChecked) {
|
||||
await resource.remove({
|
||||
values,
|
||||
});
|
||||
} else {
|
||||
await resource.add({
|
||||
values,
|
||||
});
|
||||
}
|
||||
refresh();
|
||||
message.success(t('Saved successfully'));
|
||||
}}
|
||||
/>{' '}
|
||||
{t('Accessible')}
|
||||
</>
|
||||
),
|
||||
render: (_, record) => {
|
||||
const checked = !snippets.includes('!' + record.aclSnippet);
|
||||
return <Checkbox checked={checked} onChange={() => handleChange(checked, record)} />;
|
||||
{
|
||||
dataIndex: 'accessible',
|
||||
title: (
|
||||
<>
|
||||
<Checkbox
|
||||
checked={allChecked}
|
||||
onChange={async () => {
|
||||
const values = allAclSnippets.map((v) => '!' + v);
|
||||
if (!allChecked) {
|
||||
await resource.remove({
|
||||
values,
|
||||
});
|
||||
} else {
|
||||
await resource.add({
|
||||
values,
|
||||
});
|
||||
}
|
||||
refresh();
|
||||
message.success(t('Saved successfully'));
|
||||
}}
|
||||
/>{' '}
|
||||
{t('Accessible')}
|
||||
</>
|
||||
),
|
||||
render: (_, record) => {
|
||||
const checked = !snippets.includes('!' + record.aclSnippet);
|
||||
return <Checkbox checked={checked} onChange={() => handleChange(checked, record)} />;
|
||||
},
|
||||
},
|
||||
},
|
||||
] as TableProps['columns']}
|
||||
] as TableProps['columns']
|
||||
}
|
||||
dataSource={settings
|
||||
.filter((v) => {
|
||||
return v.isTopLevel !== false;
|
||||
|
@ -121,40 +121,42 @@ export const MenuConfigure = () => {
|
||||
expandable={{
|
||||
defaultExpandAllRows: true,
|
||||
}}
|
||||
columns={[
|
||||
{
|
||||
dataIndex: 'title',
|
||||
title: t('Menu item title'),
|
||||
},
|
||||
{
|
||||
dataIndex: 'accessible',
|
||||
title: (
|
||||
<>
|
||||
<Checkbox
|
||||
checked={allChecked}
|
||||
onChange={async (value) => {
|
||||
if (allChecked) {
|
||||
await resource.set({
|
||||
values: [],
|
||||
});
|
||||
} else {
|
||||
await resource.set({
|
||||
values: allUids,
|
||||
});
|
||||
}
|
||||
refresh();
|
||||
message.success(t('Saved successfully'));
|
||||
}}
|
||||
/>{' '}
|
||||
{t('Accessible')}
|
||||
</>
|
||||
),
|
||||
render: (_, schema: { uid: string }) => {
|
||||
const checked = uids.includes(schema.uid);
|
||||
return <Checkbox checked={checked} onChange={() => handleChange(checked, schema)} />;
|
||||
columns={
|
||||
[
|
||||
{
|
||||
dataIndex: 'title',
|
||||
title: t('Menu item title'),
|
||||
},
|
||||
},
|
||||
] as TableProps['columns']}
|
||||
{
|
||||
dataIndex: 'accessible',
|
||||
title: (
|
||||
<>
|
||||
<Checkbox
|
||||
checked={allChecked}
|
||||
onChange={async (value) => {
|
||||
if (allChecked) {
|
||||
await resource.set({
|
||||
values: [],
|
||||
});
|
||||
} else {
|
||||
await resource.set({
|
||||
values: allUids,
|
||||
});
|
||||
}
|
||||
refresh();
|
||||
message.success(t('Saved successfully'));
|
||||
}}
|
||||
/>{' '}
|
||||
{t('Accessible')}
|
||||
</>
|
||||
),
|
||||
render: (_, schema: { uid: string }) => {
|
||||
const checked = uids.includes(schema.uid);
|
||||
return <Checkbox checked={checked} onChange={() => handleChange(checked, schema)} />;
|
||||
},
|
||||
},
|
||||
] as TableProps['columns']
|
||||
}
|
||||
dataSource={translateTitle(items)}
|
||||
/>
|
||||
);
|
||||
|
@ -105,48 +105,50 @@ export const RolesResourcesActions = connect((props) => {
|
||||
className={antTableCell}
|
||||
size={'small'}
|
||||
pagination={false}
|
||||
columns={[
|
||||
{
|
||||
dataIndex: 'displayName',
|
||||
title: t('Action display name'),
|
||||
render: (value) => compile(value),
|
||||
},
|
||||
{
|
||||
dataIndex: 'onNewRecord',
|
||||
title: t('Action type'),
|
||||
render: (onNewRecord) =>
|
||||
onNewRecord ? (
|
||||
<Tag color={'green'}>{t('Action on new records')}</Tag>
|
||||
) : (
|
||||
<Tag color={'geekblue'}>{t('Action on existing records')}</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
dataIndex: 'enabled',
|
||||
title: t('Allow'),
|
||||
render: (enabled, action) => (
|
||||
<Checkbox
|
||||
checked={enabled}
|
||||
onChange={() => {
|
||||
toggleAction(action.name);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
dataIndex: 'scope',
|
||||
title: t('Data scope'),
|
||||
render: (value, action) =>
|
||||
!action.onNewRecord && (
|
||||
<ScopeSelect
|
||||
value={value}
|
||||
onChange={(scope) => {
|
||||
setScope(action.name, scope);
|
||||
columns={
|
||||
[
|
||||
{
|
||||
dataIndex: 'displayName',
|
||||
title: t('Action display name'),
|
||||
render: (value) => compile(value),
|
||||
},
|
||||
{
|
||||
dataIndex: 'onNewRecord',
|
||||
title: t('Action type'),
|
||||
render: (onNewRecord) =>
|
||||
onNewRecord ? (
|
||||
<Tag color={'green'}>{t('Action on new records')}</Tag>
|
||||
) : (
|
||||
<Tag color={'geekblue'}>{t('Action on existing records')}</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
dataIndex: 'enabled',
|
||||
title: t('Allow'),
|
||||
render: (enabled, action) => (
|
||||
<Checkbox
|
||||
checked={enabled}
|
||||
onChange={() => {
|
||||
toggleAction(action.name);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
] as TableProps['columns']}
|
||||
},
|
||||
{
|
||||
dataIndex: 'scope',
|
||||
title: t('Data scope'),
|
||||
render: (value, action) =>
|
||||
!action.onNewRecord && (
|
||||
<ScopeSelect
|
||||
value={value}
|
||||
onChange={(scope) => {
|
||||
setScope(action.name, scope);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
] as TableProps['columns']
|
||||
}
|
||||
dataSource={availableActions?.map((item) => {
|
||||
let enabled = false;
|
||||
let scope = null;
|
||||
@ -169,60 +171,62 @@ export const RolesResourcesActions = connect((props) => {
|
||||
className={antTableCell}
|
||||
pagination={false}
|
||||
dataSource={fieldPermissions}
|
||||
columns={[
|
||||
{
|
||||
dataIndex: ['uiSchema', 'title'],
|
||||
title: t('Field display name'),
|
||||
render: (value) => compile(value),
|
||||
},
|
||||
...availableActionsWithFields.map((action) => {
|
||||
const checked = allChecked?.[action.name];
|
||||
return {
|
||||
dataIndex: action.name,
|
||||
title: (
|
||||
<>
|
||||
columns={
|
||||
[
|
||||
{
|
||||
dataIndex: ['uiSchema', 'title'],
|
||||
title: t('Field display name'),
|
||||
render: (value) => compile(value),
|
||||
},
|
||||
...availableActionsWithFields.map((action) => {
|
||||
const checked = allChecked?.[action.name];
|
||||
return {
|
||||
dataIndex: action.name,
|
||||
title: (
|
||||
<>
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
onChange={() => {
|
||||
const item = actionMap[action.name] || {
|
||||
name: action.name,
|
||||
};
|
||||
if (checked) {
|
||||
item.fields = [];
|
||||
} else {
|
||||
item.fields = collectionFields?.map?.((item) => item.name);
|
||||
}
|
||||
actionMap[action.name] = item;
|
||||
onChange(Object.values(actionMap));
|
||||
}}
|
||||
/>{' '}
|
||||
{compile(action.displayName)}
|
||||
</>
|
||||
),
|
||||
render: (checked, field) => (
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
aria-label={`${action.name}_checkbox`}
|
||||
onChange={() => {
|
||||
const item = actionMap[action.name] || {
|
||||
name: action.name,
|
||||
};
|
||||
const fields: string[] = item.fields || [];
|
||||
if (checked) {
|
||||
item.fields = [];
|
||||
const index = fields.indexOf(field.name);
|
||||
fields.splice(index, 1);
|
||||
} else {
|
||||
item.fields = collectionFields?.map?.((item) => item.name);
|
||||
fields.push(field.name);
|
||||
}
|
||||
item.fields = fields;
|
||||
actionMap[action.name] = item;
|
||||
onChange(Object.values(actionMap));
|
||||
}}
|
||||
/>{' '}
|
||||
{compile(action.displayName)}
|
||||
</>
|
||||
),
|
||||
render: (checked, field) => (
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
aria-label={`${action.name}_checkbox`}
|
||||
onChange={() => {
|
||||
const item = actionMap[action.name] || {
|
||||
name: action.name,
|
||||
};
|
||||
const fields: string[] = item.fields || [];
|
||||
if (checked) {
|
||||
const index = fields.indexOf(field.name);
|
||||
fields.splice(index, 1);
|
||||
} else {
|
||||
fields.push(field.name);
|
||||
}
|
||||
item.fields = fields;
|
||||
actionMap[action.name] = item;
|
||||
onChange(Object.values(actionMap));
|
||||
}}
|
||||
/>
|
||||
),
|
||||
};
|
||||
}),
|
||||
] as TableProps['columns']}
|
||||
/>
|
||||
),
|
||||
};
|
||||
}),
|
||||
] as TableProps['columns']
|
||||
}
|
||||
/>
|
||||
</FormItem>
|
||||
</FormLayout>
|
||||
|
@ -55,62 +55,64 @@ export const StrategyActions = connect((props) => {
|
||||
size={'small'}
|
||||
pagination={false}
|
||||
rowKey={'name'}
|
||||
columns={[
|
||||
{
|
||||
dataIndex: 'displayName',
|
||||
title: t('Action display name'),
|
||||
render: (value) => compile(value),
|
||||
},
|
||||
{
|
||||
dataIndex: 'onNewRecord',
|
||||
title: t('Action type'),
|
||||
render: (onNewRecord) =>
|
||||
onNewRecord ? (
|
||||
<Tag color={'green'}>{t('Action on new records')}</Tag>
|
||||
) : (
|
||||
<Tag color={'geekblue'}>{t('Action on existing records')}</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
dataIndex: 'enabled',
|
||||
title: t('Allow'),
|
||||
render: (enabled, action) => (
|
||||
<Checkbox
|
||||
checked={enabled}
|
||||
aria-label={`${action.name}_checkbox`}
|
||||
onChange={(e) => {
|
||||
if (enabled) {
|
||||
delete scopes[action.name];
|
||||
} else {
|
||||
scopes[action.name] = 'all';
|
||||
}
|
||||
onChange(toFieldValue(scopes));
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
dataIndex: 'scope',
|
||||
title: t('Data scope'),
|
||||
render: (scope, action) =>
|
||||
!action.onNewRecord && (
|
||||
<Select
|
||||
data-testid="select-data-scope"
|
||||
popupMatchSelectWidth={false}
|
||||
size={'small'}
|
||||
value={scope}
|
||||
options={[
|
||||
{ label: t('All records'), value: 'all' },
|
||||
{ label: t('Own records'), value: 'own' },
|
||||
]}
|
||||
onChange={(value) => {
|
||||
scopes[action.name] = value;
|
||||
columns={
|
||||
[
|
||||
{
|
||||
dataIndex: 'displayName',
|
||||
title: t('Action display name'),
|
||||
render: (value) => compile(value),
|
||||
},
|
||||
{
|
||||
dataIndex: 'onNewRecord',
|
||||
title: t('Action type'),
|
||||
render: (onNewRecord) =>
|
||||
onNewRecord ? (
|
||||
<Tag color={'green'}>{t('Action on new records')}</Tag>
|
||||
) : (
|
||||
<Tag color={'geekblue'}>{t('Action on existing records')}</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
dataIndex: 'enabled',
|
||||
title: t('Allow'),
|
||||
render: (enabled, action) => (
|
||||
<Checkbox
|
||||
checked={enabled}
|
||||
aria-label={`${action.name}_checkbox`}
|
||||
onChange={(e) => {
|
||||
if (enabled) {
|
||||
delete scopes[action.name];
|
||||
} else {
|
||||
scopes[action.name] = 'all';
|
||||
}
|
||||
onChange(toFieldValue(scopes));
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
] as TableProps['columns']}
|
||||
},
|
||||
{
|
||||
dataIndex: 'scope',
|
||||
title: t('Data scope'),
|
||||
render: (scope, action) =>
|
||||
!action.onNewRecord && (
|
||||
<Select
|
||||
data-testid="select-data-scope"
|
||||
popupMatchSelectWidth={false}
|
||||
size={'small'}
|
||||
value={scope}
|
||||
options={[
|
||||
{ label: t('All records'), value: 'all' },
|
||||
{ label: t('Own records'), value: 'own' },
|
||||
]}
|
||||
onChange={(value) => {
|
||||
scopes[action.name] = value;
|
||||
onChange(toFieldValue(scopes));
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
] as TableProps['columns']
|
||||
}
|
||||
dataSource={availableActions?.map((item) => {
|
||||
let scope = 'all';
|
||||
let enabled = false;
|
||||
|
@ -167,7 +167,7 @@ export function useCollectValuesToSubmit() {
|
||||
if (parsedValue !== null && parsedValue !== undefined) {
|
||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||
}
|
||||
} else if (value != null && value !== '') {
|
||||
} else if (value !== '') {
|
||||
assignedValues[key] = value;
|
||||
}
|
||||
});
|
||||
@ -203,6 +203,12 @@ export function useCollectValuesToSubmit() {
|
||||
]);
|
||||
}
|
||||
|
||||
function interpolateVariables(str: string, scope: Record<string, any>): string {
|
||||
return str.replace(/\{\{\s*([a-zA-Z0-9_$-.]+?)\s*\}\}/g, (_, key) => {
|
||||
return scope[key] !== undefined ? String(scope[key]) : '';
|
||||
});
|
||||
}
|
||||
|
||||
export const useCreateActionProps = () => {
|
||||
const filterByTk = useFilterByTk();
|
||||
const record = useCollectionRecord();
|
||||
@ -219,11 +225,20 @@ export const useCreateActionProps = () => {
|
||||
const collectValues = useCollectValuesToSubmit();
|
||||
const action = record.isNew ? actionField.componentProps.saveMode || 'create' : 'update';
|
||||
const filterKeys = actionField.componentProps.filterKeys?.checked || [];
|
||||
const localVariables = useLocalVariables();
|
||||
const variables = useVariables();
|
||||
|
||||
return {
|
||||
async onClick() {
|
||||
const { onSuccess, skipValidator, triggerWorkflows } = actionSchema?.['x-action-settings'] ?? {};
|
||||
const { manualClose, redirecting, redirectTo, successMessage, actionAfterSuccess } = onSuccess || {};
|
||||
const {
|
||||
manualClose,
|
||||
redirecting,
|
||||
redirectTo: rawRedirectTo,
|
||||
successMessage,
|
||||
actionAfterSuccess,
|
||||
} = onSuccess || {};
|
||||
|
||||
if (!skipValidator) {
|
||||
await form.submit();
|
||||
}
|
||||
@ -241,6 +256,15 @@ export const useCreateActionProps = () => {
|
||||
: undefined,
|
||||
updateAssociationValues,
|
||||
});
|
||||
let redirectTo = rawRedirectTo;
|
||||
if (rawRedirectTo) {
|
||||
const { exp, scope: expScope } = await replaceVariables(rawRedirectTo, {
|
||||
variables,
|
||||
localVariables: [...localVariables, { name: '$record', ctx: new Proxy(data?.data?.data, {}) }],
|
||||
});
|
||||
redirectTo = interpolateVariables(exp, expScope);
|
||||
}
|
||||
|
||||
if (actionAfterSuccess === 'previous' || (!actionAfterSuccess && redirecting !== true)) {
|
||||
setVisible?.(false);
|
||||
}
|
||||
@ -338,7 +362,7 @@ export const useAssociationCreateActionProps = () => {
|
||||
if (parsedValue) {
|
||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||
}
|
||||
} else if (value != null && value !== '') {
|
||||
} else if (value !== '') {
|
||||
assignedValues[key] = value;
|
||||
}
|
||||
});
|
||||
@ -588,7 +612,13 @@ export const useCustomizeUpdateActionProps = () => {
|
||||
skipValidator,
|
||||
triggerWorkflows,
|
||||
} = actionSchema?.['x-action-settings'] ?? {};
|
||||
const { manualClose, redirecting, redirectTo, successMessage, actionAfterSuccess } = onSuccess || {};
|
||||
const {
|
||||
manualClose,
|
||||
redirecting,
|
||||
redirectTo: rawRedirectTo,
|
||||
successMessage,
|
||||
actionAfterSuccess,
|
||||
} = onSuccess || {};
|
||||
const assignedValues = {};
|
||||
const waitList = Object.keys(originalAssignedValues).map(async (key) => {
|
||||
const value = originalAssignedValues[key];
|
||||
@ -605,7 +635,7 @@ export const useCustomizeUpdateActionProps = () => {
|
||||
if (parsedValue) {
|
||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||
}
|
||||
} else if (value != null && value !== '') {
|
||||
} else if (value !== '') {
|
||||
assignedValues[key] = value;
|
||||
}
|
||||
});
|
||||
@ -614,7 +644,7 @@ export const useCustomizeUpdateActionProps = () => {
|
||||
if (skipValidator === false) {
|
||||
await form.submit();
|
||||
}
|
||||
await resource.update({
|
||||
const result = await resource.update({
|
||||
filterByTk,
|
||||
values: { ...assignedValues },
|
||||
// TODO(refactor): should change to inject by plugin
|
||||
@ -622,6 +652,16 @@ export const useCustomizeUpdateActionProps = () => {
|
||||
? triggerWorkflows.map((row) => [row.workflowKey, row.context].filter(Boolean).join('!')).join(',')
|
||||
: undefined,
|
||||
});
|
||||
|
||||
let redirectTo = rawRedirectTo;
|
||||
if (rawRedirectTo) {
|
||||
const { exp, scope: expScope } = await replaceVariables(rawRedirectTo, {
|
||||
variables,
|
||||
localVariables: [...localVariables, { name: '$record', ctx: new Proxy(result?.data?.data?.[0], {}) }],
|
||||
});
|
||||
redirectTo = interpolateVariables(exp, expScope);
|
||||
}
|
||||
|
||||
if (actionAfterSuccess === 'previous' || (!actionAfterSuccess && redirecting !== true)) {
|
||||
setVisible?.(false);
|
||||
}
|
||||
@ -708,7 +748,7 @@ export const useCustomizeBulkUpdateActionProps = () => {
|
||||
if (parsedValue) {
|
||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||
}
|
||||
} else if (value != null && value !== '') {
|
||||
} else if (value !== '') {
|
||||
assignedValues[key] = value;
|
||||
}
|
||||
});
|
||||
@ -913,7 +953,13 @@ export const useUpdateActionProps = () => {
|
||||
skipValidator,
|
||||
triggerWorkflows,
|
||||
} = actionSchema?.['x-action-settings'] ?? {};
|
||||
const { manualClose, redirecting, redirectTo, successMessage, actionAfterSuccess } = onSuccess || {};
|
||||
const {
|
||||
manualClose,
|
||||
redirecting,
|
||||
redirectTo: rawRedirectTo,
|
||||
successMessage,
|
||||
actionAfterSuccess,
|
||||
} = onSuccess || {};
|
||||
const assignedValues = {};
|
||||
const waitList = Object.keys(originalAssignedValues).map(async (key) => {
|
||||
const value = originalAssignedValues[key];
|
||||
@ -930,7 +976,7 @@ export const useUpdateActionProps = () => {
|
||||
if (parsedValue) {
|
||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||
}
|
||||
} else if (value != null && value !== '') {
|
||||
} else if (value !== '') {
|
||||
assignedValues[key] = value;
|
||||
}
|
||||
});
|
||||
@ -952,7 +998,7 @@ export const useUpdateActionProps = () => {
|
||||
actionField.data = field.data || {};
|
||||
actionField.data.loading = true;
|
||||
try {
|
||||
await resource.update({
|
||||
const result = await resource.update({
|
||||
filterByTk,
|
||||
values: {
|
||||
...values,
|
||||
@ -971,6 +1017,15 @@ export const useUpdateActionProps = () => {
|
||||
if (callBack) {
|
||||
callBack?.();
|
||||
}
|
||||
let redirectTo = rawRedirectTo;
|
||||
if (rawRedirectTo) {
|
||||
const { exp, scope: expScope } = await replaceVariables(rawRedirectTo, {
|
||||
variables,
|
||||
localVariables: [...localVariables, { name: '$record', ctx: new Proxy(result?.data?.data?.[0], {}) }],
|
||||
});
|
||||
redirectTo = interpolateVariables(exp, expScope);
|
||||
}
|
||||
|
||||
if (actionAfterSuccess === 'previous' || (!actionAfterSuccess && redirecting !== true)) {
|
||||
setVisible?.(false);
|
||||
}
|
||||
|
@ -157,6 +157,8 @@ export const useCollectionFilterOptions = (collection: any, dataSource?: string)
|
||||
const option = {
|
||||
name: field.name,
|
||||
title: field?.uiSchema?.title || field.name,
|
||||
label: field?.uiSchema?.title || field.name,
|
||||
value: field.name,
|
||||
schema: field?.uiSchema,
|
||||
operators:
|
||||
operators?.filter?.((operator) => {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1094,5 +1094,6 @@
|
||||
"Font Size(px)": "字体大小(像素)",
|
||||
"Font Weight": "字体粗细",
|
||||
"Font Style": "字体样式",
|
||||
"Italic": "斜体"
|
||||
"Italic": "斜体",
|
||||
"Response record":"响应结果记录"
|
||||
}
|
||||
|
@ -126,6 +126,10 @@ export const getAllowMultiple = (params?: { title: string }) => {
|
||||
return {
|
||||
name: 'allowMultiple',
|
||||
type: 'switch',
|
||||
useVisible() {
|
||||
const isAssociationField = useIsAssociationField();
|
||||
return isAssociationField;
|
||||
},
|
||||
useComponentProps() {
|
||||
const { t } = useTranslation();
|
||||
const field = useField<Field>();
|
||||
|
@ -14,6 +14,14 @@ import React, { useCallback, useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { SchemaInitializerItem } from '../../application';
|
||||
import {
|
||||
CollectionManagerProvider,
|
||||
useCollectionManager,
|
||||
} from '../../data-source/collection/CollectionManagerProvider';
|
||||
import {
|
||||
DataSourceManagerProvider,
|
||||
useDataSourceManager,
|
||||
} from '../../data-source/data-source/DataSourceManagerProvider';
|
||||
import { useGlobalTheme } from '../../global-theme';
|
||||
import { NocoBaseDesktopRouteType } from '../../route-switch/antd/admin-layout/convertRoutesToSchema';
|
||||
import {
|
||||
@ -34,6 +42,8 @@ export const LinkMenuItem = () => {
|
||||
const { urlSchema, paramsSchema } = useURLAndHTMLSchema();
|
||||
const parentRoute = useParentRoute();
|
||||
const { createRoute } = useNocoBaseRoutes();
|
||||
const dm = useDataSourceManager();
|
||||
const cm = useCollectionManager();
|
||||
|
||||
const handleClick = useCallback(async () => {
|
||||
const values = await FormDialog(
|
||||
@ -41,31 +51,35 @@ export const LinkMenuItem = () => {
|
||||
() => {
|
||||
const history = createMemoryHistory();
|
||||
return (
|
||||
<Router location={history.location} navigator={history}>
|
||||
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
|
||||
<FormLayout layout={'vertical'}>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
properties: {
|
||||
title: {
|
||||
title: t('Menu item title'),
|
||||
required: true,
|
||||
'x-component': 'Input',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
icon: {
|
||||
title: t('Icon'),
|
||||
'x-component': 'IconPicker',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
href: urlSchema,
|
||||
params: paramsSchema,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</FormLayout>
|
||||
</SchemaComponentOptions>
|
||||
</Router>
|
||||
<DataSourceManagerProvider dataSourceManager={dm}>
|
||||
<CollectionManagerProvider instance={cm} dataSource={cm?.dataSource?.key}>
|
||||
<Router location={history.location} navigator={history}>
|
||||
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
|
||||
<FormLayout layout={'vertical'}>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
properties: {
|
||||
title: {
|
||||
title: t('Menu item title'),
|
||||
required: true,
|
||||
'x-component': 'Input',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
icon: {
|
||||
title: t('Icon'),
|
||||
'x-component': 'IconPicker',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
href: urlSchema,
|
||||
params: paramsSchema,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</FormLayout>
|
||||
</SchemaComponentOptions>
|
||||
</Router>
|
||||
</CollectionManagerProvider>
|
||||
</DataSourceManagerProvider>
|
||||
);
|
||||
},
|
||||
theme,
|
||||
|
@ -127,25 +127,27 @@ function BulkEnableButton({ plugins = [] }) {
|
||||
}}
|
||||
size={'small'}
|
||||
pagination={false}
|
||||
columns={[
|
||||
{
|
||||
title: t('Plugin'),
|
||||
dataIndex: 'displayName',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: t('Description'),
|
||||
dataIndex: 'description',
|
||||
ellipsis: true,
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
title: t('Package name'),
|
||||
dataIndex: 'packageName',
|
||||
width: 300,
|
||||
ellipsis: true,
|
||||
},
|
||||
] as TableProps['columns']}
|
||||
columns={
|
||||
[
|
||||
{
|
||||
title: t('Plugin'),
|
||||
dataIndex: 'displayName',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: t('Description'),
|
||||
dataIndex: 'description',
|
||||
ellipsis: true,
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
title: t('Package name'),
|
||||
dataIndex: 'packageName',
|
||||
width: 300,
|
||||
ellipsis: true,
|
||||
},
|
||||
] as TableProps['columns']
|
||||
}
|
||||
dataSource={items}
|
||||
/>
|
||||
</Modal>
|
||||
|
@ -88,7 +88,7 @@ export const SettingsCenterDropdown = () => {
|
||||
<Button
|
||||
data-testid="plugin-settings-button"
|
||||
icon={<SettingOutlined style={{ color: token.colorTextHeaderMenu }} />}
|
||||
// title={t('All plugin settings')}
|
||||
// title={t('All plugin settings')}
|
||||
/>
|
||||
</Dropdown>
|
||||
);
|
||||
|
@ -52,6 +52,7 @@ import { KeepAlive } from './KeepAlive';
|
||||
import { NocoBaseDesktopRoute, NocoBaseDesktopRouteType } from './convertRoutesToSchema';
|
||||
import { MenuSchemaToolbar, ResetThemeTokenAndKeepAlgorithm } from './menuItemSettings';
|
||||
import { userCenterSettings } from './userCenterSettings';
|
||||
import { createGlobalStyle, createStyles } from 'antd-style';
|
||||
|
||||
export { KeepAlive, NocoBaseDesktopRouteType };
|
||||
|
||||
@ -268,7 +269,7 @@ const GroupItem: FC<{ item: any }> = (props) => {
|
||||
};
|
||||
|
||||
const WithTooltip: FC<{ title: string; hidden: boolean }> = (props) => {
|
||||
const { inHeader } = useContext(headerContext);
|
||||
const { inHeader } = useContext(HeaderContext);
|
||||
|
||||
return (
|
||||
<RouteContext.Consumer>
|
||||
@ -406,7 +407,7 @@ const contentStyle = {
|
||||
paddingInline: 0,
|
||||
};
|
||||
|
||||
const headerContext = React.createContext<{ inHeader: boolean }>({ inHeader: false });
|
||||
const HeaderContext = React.createContext<{ inHeader: boolean }>({ inHeader: false });
|
||||
|
||||
const popoverStyle = css`
|
||||
.ant-popover-inner {
|
||||
@ -495,9 +496,38 @@ const collapsedButtonRender = (collapsed, dom) => {
|
||||
return <CollapsedButton collapsed={collapsed}>{dom}</CollapsedButton>;
|
||||
};
|
||||
|
||||
/**
|
||||
* 这个问题源自 antd 的一个 bug,等 antd 修复了这个问题后,可以删除这个样式。
|
||||
* - issue: https://github.com/ant-design/pro-components/issues/8593
|
||||
* - issue: https://github.com/ant-design/pro-components/issues/8522
|
||||
* - issue: https://github.com/ant-design/pro-components/issues/8432
|
||||
*/
|
||||
const useHeaderStyle = createStyles(({ token }: any) => {
|
||||
return {
|
||||
headerPopup: {
|
||||
'&.ant-menu-submenu-popup>.ant-menu': {
|
||||
backgroundColor: `${token.colorBgHeader}`,
|
||||
},
|
||||
},
|
||||
headerMenuActive: {
|
||||
'& .ant-menu-submenu-selected>.ant-menu-submenu-title': {
|
||||
color: token.colorTextHeaderMenuActive,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
const headerContextValue = { inHeader: true };
|
||||
const HeaderWrapper: FC = (props) => {
|
||||
const { styles } = useHeaderStyle();
|
||||
|
||||
return (
|
||||
<span className={styles.headerMenuActive}>
|
||||
<HeaderContext.Provider value={headerContextValue}>{props.children}</HeaderContext.Provider>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
const headerRender = (props: HeaderViewProps, defaultDom: React.ReactNode) => {
|
||||
return <headerContext.Provider value={headerContextValue}>{defaultDom}</headerContext.Provider>;
|
||||
return <HeaderWrapper>{defaultDom}</HeaderWrapper>;
|
||||
};
|
||||
|
||||
const IsMobileLayoutContext = React.createContext<{
|
||||
@ -531,6 +561,7 @@ export const InternalAdminLayout = () => {
|
||||
const doNotChangeCollapsedRef = useRef(false);
|
||||
const { t } = useMenuTranslation();
|
||||
const designable = isMobileLayout ? false : _designable;
|
||||
const { styles } = useHeaderStyle();
|
||||
|
||||
const route = useMemo(() => {
|
||||
return {
|
||||
@ -544,7 +575,7 @@ export const InternalAdminLayout = () => {
|
||||
colorBgHeader: token.colorBgHeader,
|
||||
colorTextMenu: token.colorTextHeaderMenu,
|
||||
colorTextMenuSelected: token.colorTextHeaderMenuActive,
|
||||
colorTextMenuActive: token.colorTextHeaderMenuActive,
|
||||
colorTextMenuActive: token.colorTextHeaderMenuHover,
|
||||
colorBgMenuItemHover: token.colorBgHeaderMenuHover,
|
||||
colorBgMenuItemSelected: token.colorBgHeaderMenuActive,
|
||||
heightLayoutHeader: 46,
|
||||
@ -591,6 +622,12 @@ export const InternalAdminLayout = () => {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const menuProps = useMemo(() => {
|
||||
return {
|
||||
overflowedIndicatorPopupClassName: styles.headerPopup,
|
||||
};
|
||||
}, [styles.headerPopup]);
|
||||
|
||||
return (
|
||||
<DndContext onDragEnd={onDragEnd}>
|
||||
<ProLayout
|
||||
@ -612,6 +649,7 @@ export const InternalAdminLayout = () => {
|
||||
onCollapse={onCollapse}
|
||||
collapsed={collapsed}
|
||||
onPageChange={onPageChange}
|
||||
menuProps={menuProps}
|
||||
>
|
||||
<RouteContext.Consumer>
|
||||
{(value: RouteContextType) => {
|
||||
|
@ -34,6 +34,8 @@ import {
|
||||
import { DefaultValueProvider } from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
|
||||
import { useLinkageAction } from './hooks';
|
||||
import { requestSettingsSchema } from './utils';
|
||||
import { useAfterSuccessOptions } from './hooks/useGetAfterSuccessVariablesOptions';
|
||||
import { useGlobalVariable } from '../../../application/hooks/useGlobalVariable';
|
||||
|
||||
const MenuGroup = (props) => {
|
||||
return props.children;
|
||||
@ -280,11 +282,24 @@ export function SkipValidation() {
|
||||
);
|
||||
}
|
||||
|
||||
const fieldNames = {
|
||||
value: 'value',
|
||||
label: 'label',
|
||||
};
|
||||
const useVariableProps = (environmentVariables) => {
|
||||
const scope = useAfterSuccessOptions();
|
||||
return {
|
||||
scope: [environmentVariables, ...scope].filter(Boolean),
|
||||
fieldNames,
|
||||
};
|
||||
};
|
||||
|
||||
export function AfterSuccess() {
|
||||
const { dn } = useDesignable();
|
||||
const { t } = useTranslation();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { onSuccess } = fieldSchema?.['x-action-settings'] || {};
|
||||
const environmentVariables = useGlobalVariable('$env');
|
||||
return (
|
||||
<SchemaSettingsModalItem
|
||||
title={t('After successful submission')}
|
||||
@ -363,8 +378,9 @@ export function AfterSuccess() {
|
||||
redirectTo: {
|
||||
title: t('Link'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {},
|
||||
'x-component': 'Variable.TextArea',
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
'x-use-component-props': () => useVariableProps(environmentVariables),
|
||||
},
|
||||
},
|
||||
} as ISchema
|
||||
|
@ -16,16 +16,19 @@ import Action from './Action';
|
||||
import { ComposedAction } from './types';
|
||||
import { Icon } from '../../../icon';
|
||||
|
||||
const WrapperComponent = ({ component: Component = 'a', icon, onlyIcon, children, ...restProps }: any) => {
|
||||
return (
|
||||
<Component {...restProps}>
|
||||
<Tooltip title={restProps.title}>
|
||||
<span style={{ marginRight: 3 }}>{icon && typeof icon === 'string' ? <Icon type={icon} /> : icon}</span>
|
||||
</Tooltip>
|
||||
{onlyIcon ? children[1] : children}
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
const WrapperComponent = React.forwardRef(
|
||||
({ component: Component = 'a', icon, onlyIcon, children, ...restProps }: any, ref) => {
|
||||
return (
|
||||
<Component ref={ref} {...restProps}>
|
||||
<Tooltip title={restProps.title}>
|
||||
<span style={{ marginRight: 3 }}>{icon && typeof icon === 'string' ? <Icon type={icon} /> : icon}</span>
|
||||
</Tooltip>
|
||||
{onlyIcon ? children[1] : children}
|
||||
</Component>
|
||||
);
|
||||
},
|
||||
);
|
||||
WrapperComponent.displayName = 'WrapperComponentLink';
|
||||
|
||||
export const ActionLink: ComposedAction = withDynamicSchemaProps(
|
||||
observer((props: any) => {
|
||||
@ -34,6 +37,7 @@ export const ActionLink: ComposedAction = withDynamicSchemaProps(
|
||||
{...props}
|
||||
component={props.component || WrapperComponent}
|
||||
className={classnames('nb-action-link', props.className)}
|
||||
isLink
|
||||
/>
|
||||
);
|
||||
}),
|
||||
|
@ -247,7 +247,6 @@ const InternalAction: React.FC<InternalActionProps> = observer(function Com(prop
|
||||
const aclCtx = useACLActionParamsContext();
|
||||
const { run, element, disabled: disableAction } = useAction?.(actionCallback) || ({} as any);
|
||||
const disabled = form.disabled || field.disabled || field.data?.disabled || propsDisabled || disableAction;
|
||||
|
||||
const buttonStyle = useMemo(() => {
|
||||
return {
|
||||
...style,
|
||||
@ -538,6 +537,7 @@ const RenderButtonInner = observer(
|
||||
Designer: React.ElementType;
|
||||
designerProps: any;
|
||||
title: string;
|
||||
isLink?: boolean;
|
||||
}) => {
|
||||
const {
|
||||
designable,
|
||||
@ -558,6 +558,7 @@ const RenderButtonInner = observer(
|
||||
Designer,
|
||||
designerProps,
|
||||
title,
|
||||
isLink,
|
||||
...others
|
||||
} = props;
|
||||
const debouncedClick = useCallback(
|
||||
@ -583,7 +584,7 @@ const RenderButtonInner = observer(
|
||||
|
||||
const actionTitle = title || field?.title;
|
||||
const { opacity, ...restButtonStyle } = buttonStyle;
|
||||
|
||||
const linkStyle = isLink && opacity ? { opacity } : undefined;
|
||||
return (
|
||||
<SortableItem
|
||||
role="button"
|
||||
@ -592,9 +593,9 @@ const RenderButtonInner = observer(
|
||||
onMouseEnter={handleMouseEnter}
|
||||
// @ts-ignore
|
||||
loading={field?.data?.loading || loading}
|
||||
icon={typeof icon === 'string' ? <Icon style={{ opacity }} type={icon} /> : icon}
|
||||
icon={typeof icon === 'string' ? <Icon type={icon} style={linkStyle} /> : icon}
|
||||
disabled={disabled}
|
||||
style={restButtonStyle}
|
||||
style={isLink ? restButtonStyle : buttonStyle}
|
||||
onClick={process.env.__E2E__ ? handleButtonClick : debouncedClick} // E2E 中的点击操作都是很快的,如果加上 debounce 会导致 E2E 测试失败
|
||||
component={tarComponent || Button}
|
||||
className={classnames(componentCls, hashId, className, 'nb-action')}
|
||||
@ -602,7 +603,7 @@ const RenderButtonInner = observer(
|
||||
title={actionTitle}
|
||||
>
|
||||
{actionTitle && (
|
||||
<span className={icon ? 'nb-action-title' : null} style={{ opacity }}>
|
||||
<span className={icon ? 'nb-action-title' : null} style={linkStyle}>
|
||||
{actionTitle}
|
||||
</span>
|
||||
)}
|
||||
|
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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 { useMemo } from 'react';
|
||||
import { useCollection_deprecated, useCollectionFilterOptions } from '../../../../collection-manager';
|
||||
import { useCollectionRecordData } from '../../../../data-source';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCompile } from '../../../';
|
||||
import { useBlockContext } from '../../../../block-provider/BlockProvider';
|
||||
import { usePopupVariable } from '../../../../schema-settings/VariableInput/hooks';
|
||||
import { useCurrentRoleVariable } from '../../../../schema-settings/VariableInput/hooks';
|
||||
|
||||
export const useAfterSuccessOptions = () => {
|
||||
const collection = useCollection_deprecated();
|
||||
const { t } = useTranslation();
|
||||
const fieldsOptions = useCollectionFilterOptions(collection);
|
||||
const userFieldOptions = useCollectionFilterOptions('users', 'main');
|
||||
const compile = useCompile();
|
||||
const recordData = useCollectionRecordData();
|
||||
const { name: blockType } = useBlockContext() || {};
|
||||
const [fields, userFields] = useMemo(() => {
|
||||
return [compile(fieldsOptions), compile(userFieldOptions)];
|
||||
}, [fieldsOptions, userFieldOptions]);
|
||||
const { settings: popupRecordSettings, shouldDisplayPopupRecord } = usePopupVariable();
|
||||
const { currentRoleSettings } = useCurrentRoleVariable();
|
||||
const record = useCollectionRecordData();
|
||||
return useMemo(() => {
|
||||
return [
|
||||
(record || blockType === 'form') && {
|
||||
value: '$record',
|
||||
label: t('Response record', { ns: 'client' }),
|
||||
children: [...fields],
|
||||
},
|
||||
recordData && {
|
||||
value: 'currentRecord',
|
||||
label: t('Current record', { ns: 'client' }),
|
||||
children: [...fields],
|
||||
},
|
||||
shouldDisplayPopupRecord && {
|
||||
...popupRecordSettings,
|
||||
},
|
||||
{
|
||||
value: 'currentUser',
|
||||
label: t('Current user', { ns: 'client' }),
|
||||
children: userFields,
|
||||
},
|
||||
currentRoleSettings,
|
||||
{
|
||||
value: 'currentTime',
|
||||
label: t('Current time', { ns: 'client' }),
|
||||
children: null,
|
||||
},
|
||||
{
|
||||
value: '$nToken',
|
||||
label: 'API token',
|
||||
children: null,
|
||||
},
|
||||
].filter(Boolean);
|
||||
}, [recordData, t, fields, blockType, userFields]);
|
||||
};
|
@ -16,5 +16,6 @@ export * from './hooks/useGetAriaLabelOfAction';
|
||||
export * from './hooks/useGetAriaLabelOfDrawer';
|
||||
export * from './hooks/useGetAriaLabelOfModal';
|
||||
export * from './hooks/useGetAriaLabelOfPopover';
|
||||
export * from './hooks/useGetAfterSuccessVariablesOptions';
|
||||
export * from './types';
|
||||
export * from './zIndexContext';
|
||||
|
@ -14,22 +14,22 @@ import { uid } from '@formily/shared';
|
||||
import { Space, message } from 'antd';
|
||||
import { isEqual } from 'lodash';
|
||||
import { isFunction } from 'mathjs';
|
||||
import React, { useEffect, useState, useContext } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ClearCollectionFieldContext,
|
||||
NocoBaseRecursionField,
|
||||
RecordProvider,
|
||||
SchemaComponentContext,
|
||||
useAPIClient,
|
||||
useCollectionRecordData,
|
||||
SchemaComponentContext,
|
||||
} from '../../../';
|
||||
import { Action } from '../action';
|
||||
import { VariablePopupRecordProvider } from '../../../modules/variable/variablesProvider/VariablePopupRecordProvider';
|
||||
import { isVariable } from '../../../variables/utils/isVariable';
|
||||
import { getInnermostKeyAndValue } from '../../common/utils/uitls';
|
||||
import { Action } from '../action';
|
||||
import { RemoteSelect, RemoteSelectProps } from '../remote-select';
|
||||
import useServiceOptions, { useAssociationFieldContext } from './hooks';
|
||||
import { VariablePopupRecordProvider } from '../../../modules/variable/variablesProvider/VariablePopupRecordProvider';
|
||||
|
||||
export const AssociationFieldAddNewer = (props) => {
|
||||
const schemaComponentCtxValue = useContext(SchemaComponentContext);
|
||||
|
@ -158,7 +158,10 @@ export const InternalPicker = observer(
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const newSchema = useMemo(() => isMobileLayout ? transformMultiColumnToSingleColumn(fieldSchema) : fieldSchema, [isMobileLayout, fieldSchema]);
|
||||
const newSchema = useMemo(
|
||||
() => (isMobileLayout ? transformMultiColumnToSingleColumn(fieldSchema) : fieldSchema),
|
||||
[isMobileLayout, fieldSchema],
|
||||
);
|
||||
|
||||
return (
|
||||
<PopupSettingsProvider enableURL={false}>
|
||||
|
@ -144,8 +144,14 @@ const ToManyNester = observer(
|
||||
const update = useUpdate();
|
||||
const { isMobileLayout } = useMobileLayout();
|
||||
|
||||
const newSchema = useMemo(() => isMobileLayout ? transformMultiColumnToSingleColumn(fieldSchema) : fieldSchema, [isMobileLayout, fieldSchema]);
|
||||
const newParentSchema = useMemo(() => isMobileLayout ? transformMultiColumnToSingleColumn(fieldSchema.parent) : fieldSchema.parent, [isMobileLayout, fieldSchema.parent]);
|
||||
const newSchema = useMemo(
|
||||
() => (isMobileLayout ? transformMultiColumnToSingleColumn(fieldSchema) : fieldSchema),
|
||||
[isMobileLayout, fieldSchema],
|
||||
);
|
||||
const newParentSchema = useMemo(
|
||||
() => (isMobileLayout ? transformMultiColumnToSingleColumn(fieldSchema.parent) : fieldSchema.parent),
|
||||
[isMobileLayout, fieldSchema.parent],
|
||||
);
|
||||
|
||||
const refreshComponent = useRefreshComponent();
|
||||
const refresh = useCallback(() => {
|
||||
|
@ -60,6 +60,7 @@ export function useAssociationFieldContext<F extends GeneralField>() {
|
||||
};
|
||||
}
|
||||
|
||||
// 用于获取关系字段请求数据时所需的一些参数
|
||||
export default function useServiceOptions(props) {
|
||||
const { action = 'list', service, useOriginalFilter } = props;
|
||||
const fieldSchema = useFieldSchema();
|
||||
@ -86,24 +87,24 @@ export default function useServiceOptions(props) {
|
||||
mergeFilter([
|
||||
isOToAny && !isInFilterFormBlock(fieldSchema) && collectionField?.foreignKey && !useOriginalFilter
|
||||
? {
|
||||
[collectionField.foreignKey]: {
|
||||
$is: null,
|
||||
},
|
||||
}
|
||||
[collectionField.foreignKey]: {
|
||||
$is: null,
|
||||
},
|
||||
}
|
||||
: null,
|
||||
parsedFilterParams,
|
||||
]),
|
||||
isOToAny &&
|
||||
sourceValue !== undefined &&
|
||||
sourceValue !== null &&
|
||||
!isInFilterFormBlock(fieldSchema) &&
|
||||
collectionField?.foreignKey &&
|
||||
!useOriginalFilter
|
||||
sourceValue !== undefined &&
|
||||
sourceValue !== null &&
|
||||
!isInFilterFormBlock(fieldSchema) &&
|
||||
collectionField?.foreignKey &&
|
||||
!useOriginalFilter
|
||||
? {
|
||||
[collectionField.foreignKey]: {
|
||||
$eq: sourceValue,
|
||||
},
|
||||
}
|
||||
[collectionField.foreignKey]: {
|
||||
$eq: sourceValue,
|
||||
},
|
||||
}
|
||||
: null,
|
||||
// params?.filter && value?.length
|
||||
// ? {
|
||||
|
@ -50,7 +50,7 @@ describe('CollectionSelect', () => {
|
||||
>
|
||||
<div
|
||||
aria-label="block-item-demo title"
|
||||
class="nb-block-item nb-form-item css-9qorhu ant-nb-block-item css-dev-only-do-not-override-11aiz3o"
|
||||
class="nb-block-item nb-form-item css-9qorhu ant-nb-block-item css-dev-only-do-not-override-1rquknz"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
@ -191,7 +191,7 @@ describe('CollectionSelect', () => {
|
||||
>
|
||||
<div
|
||||
aria-label="block-item-demo title"
|
||||
class="nb-block-item nb-form-item css-9qorhu ant-nb-block-item css-dev-only-do-not-override-11aiz3o"
|
||||
class="nb-block-item nb-form-item css-9qorhu ant-nb-block-item css-dev-only-do-not-override-1rquknz"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
|
@ -92,7 +92,7 @@ export const useGetFilterFieldOptions = () => {
|
||||
|
||||
const getOptions = (fields, depth, usedInVariable?: boolean) => {
|
||||
const options = [];
|
||||
fields.forEach((field) => {
|
||||
fields?.forEach((field) => {
|
||||
const option = field2option(field, depth, usedInVariable);
|
||||
if (option) {
|
||||
options.push(option);
|
||||
|
@ -52,7 +52,10 @@ const FormComponent: React.FC<FormProps> = (props) => {
|
||||
labelWrap = true,
|
||||
} = cardItemSchema?.['x-component-props'] || {};
|
||||
const { isMobileLayout } = useMobileLayout();
|
||||
const newSchema = useMemo(() => isMobileLayout ? transformMultiColumnToSingleColumn(fieldSchema) : fieldSchema, [fieldSchema, isMobileLayout]);
|
||||
const newSchema = useMemo(
|
||||
() => (isMobileLayout ? transformMultiColumnToSingleColumn(fieldSchema) : fieldSchema),
|
||||
[fieldSchema, isMobileLayout],
|
||||
);
|
||||
|
||||
return (
|
||||
<FieldContext.Provider value={undefined}>
|
||||
@ -81,12 +84,7 @@ const FormComponent: React.FC<FormProps> = (props) => {
|
||||
}
|
||||
`}
|
||||
>
|
||||
<NocoBaseRecursionField
|
||||
basePath={f.address}
|
||||
schema={newSchema}
|
||||
onlyRenderProperties
|
||||
isUseFormilyField
|
||||
/>
|
||||
<NocoBaseRecursionField basePath={f.address} schema={newSchema} onlyRenderProperties isUseFormilyField />
|
||||
</div>
|
||||
</FormLayout>
|
||||
</FormContext.Provider>
|
||||
@ -104,19 +102,17 @@ const FormDecorator: React.FC<FormProps> = (props) => {
|
||||
const f = useAttach(form.createVoidField({ ...field.props, basePath: '' }));
|
||||
const Component = useComponent(fieldSchema['x-component'], Def);
|
||||
const { isMobileLayout } = useMobileLayout();
|
||||
const newSchema = useMemo(() => isMobileLayout ? transformMultiColumnToSingleColumn(fieldSchema) : fieldSchema, [fieldSchema, isMobileLayout]);
|
||||
const newSchema = useMemo(
|
||||
() => (isMobileLayout ? transformMultiColumnToSingleColumn(fieldSchema) : fieldSchema),
|
||||
[fieldSchema, isMobileLayout],
|
||||
);
|
||||
return (
|
||||
<FieldContext.Provider value={undefined}>
|
||||
<FormContext.Provider value={form}>
|
||||
<FormLayout layout={'vertical'} {...others}>
|
||||
<FieldContext.Provider value={f}>
|
||||
<Component {...field.componentProps}>
|
||||
<NocoBaseRecursionField
|
||||
basePath={f.address}
|
||||
schema={newSchema}
|
||||
onlyRenderProperties
|
||||
isUseFormilyField
|
||||
/>
|
||||
<NocoBaseRecursionField basePath={f.address} schema={newSchema} onlyRenderProperties isUseFormilyField />
|
||||
</Component>
|
||||
</FieldContext.Provider>
|
||||
{/* <FieldContext.Provider value={f}>{children}</FieldContext.Provider> */}
|
||||
|
@ -1,5 +0,0 @@
|
||||
# Page
|
||||
|
||||
Can be used in conjunction with DocumentTitleProvider to display the page title on document.title.
|
||||
|
||||
<code src="./demos/demo1.tsx"></code>
|
@ -1,5 +0,0 @@
|
||||
# Page
|
||||
|
||||
可以与 DocumentTitleProvider 搭配使用,将 page title 显示在 document.title 上。
|
||||
|
||||
<code src="./demos/demo1.tsx"></code>
|
@ -343,14 +343,14 @@ export const TableBlockDesigner = () => {
|
||||
}}
|
||||
/>
|
||||
<SchemaSettingsConnectDataBlocks type={FilterBlockType.TABLE} emptyDescription={t('No blocks to connect')} />
|
||||
{supportTemplate && <SchemaSettingsDivider />}
|
||||
{/* {supportTemplate && <SchemaSettingsDivider />}
|
||||
{supportTemplate && (
|
||||
<SchemaSettingsTemplate
|
||||
componentName={`${componentNamePrefix}Table`}
|
||||
collectionName={name}
|
||||
resourceName={defaultResource}
|
||||
/>
|
||||
)}
|
||||
)} */}
|
||||
<SchemaSettingsDivider />
|
||||
<SchemaSettingsRemove
|
||||
removeParentsIfNoChildren
|
||||
|
@ -233,10 +233,10 @@ describe('Table.settings', () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Save as template',
|
||||
type: 'modal',
|
||||
},
|
||||
// {
|
||||
// title: 'Save as template',
|
||||
// type: 'modal',
|
||||
// },
|
||||
{
|
||||
title: 'Delete',
|
||||
type: 'delete',
|
||||
@ -299,7 +299,12 @@ describe('Table.settings', () => {
|
||||
|
||||
test('menu list', async () => {
|
||||
await renderSettings(getRenderSettingsOptions());
|
||||
await checkTableSettings();
|
||||
await checkTableSettings([
|
||||
{
|
||||
title: 'Save as template',
|
||||
type: 'modal',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('old schema', async () => {
|
||||
|
@ -45,9 +45,9 @@ export const Sortable = (props: any) => {
|
||||
const { draggable, droppable } = useContext(SortableContext);
|
||||
const { isOver, setNodeRef } = droppable;
|
||||
const droppableStyle = { ...style };
|
||||
|
||||
const isLinkComponent = component === 'a' || component?.displayName === 'WrapperComponentLink';
|
||||
if (isOver && draggable?.active?.id !== droppable?.over?.id) {
|
||||
droppableStyle[component === 'a' ? 'color' : 'background'] = getComputedColor(token.colorSettings);
|
||||
droppableStyle[isLinkComponent ? 'color' : 'background'] = getComputedColor(token.colorSettings);
|
||||
Object.assign(droppableStyle, overStyle);
|
||||
}
|
||||
|
||||
@ -118,7 +118,6 @@ export const SortableItem: React.FC<SortableItemProps> = React.memo((props) => {
|
||||
if (designable) {
|
||||
return <InternalSortableItem {...props} />;
|
||||
}
|
||||
|
||||
return React.createElement(
|
||||
component || 'div',
|
||||
_.omit(others, ['children', 'schema', 'overStyle', 'openMode', 'id', 'eid', 'removeParentsIfNoChildren']),
|
||||
|
@ -267,6 +267,13 @@ function FinallyButton({
|
||||
}) {
|
||||
const { getCollection } = useCollectionManager_deprecated();
|
||||
const aclCtx = useACLActionParamsContext();
|
||||
const buttonStyle = useMemo(() => {
|
||||
const shouldApplyOpacity = designable && (field?.data?.hidden || !aclCtx);
|
||||
const opacityValue = componentType !== 'link' ? (shouldApplyOpacity ? 0.1 : 1) : 1;
|
||||
return {
|
||||
opacity: opacityValue,
|
||||
};
|
||||
}, [designable, field?.data?.hidden, aclCtx, componentType]);
|
||||
|
||||
if (inheritsCollections?.length > 0) {
|
||||
if (!linkageFromForm) {
|
||||
@ -276,6 +283,7 @@ function FinallyButton({
|
||||
danger={props.danger}
|
||||
type={componentType}
|
||||
icon={<DownOutlined />}
|
||||
style={{ ...props?.style, ...buttonStyle }}
|
||||
buttonsRender={([leftButton, rightButton]) => [
|
||||
React.cloneElement(leftButton as React.ReactElement<any, string>, {
|
||||
style: props?.style,
|
||||
@ -296,7 +304,13 @@ function FinallyButton({
|
||||
) : (
|
||||
<Dropdown menu={menu}>
|
||||
{
|
||||
<Button aria-label={props['aria-label']} icon={icon} type={componentType} danger={props.danger}>
|
||||
<Button
|
||||
aria-label={props['aria-label']}
|
||||
icon={icon}
|
||||
type={componentType}
|
||||
danger={props.danger}
|
||||
style={{ ...props?.style, ...buttonStyle }}
|
||||
>
|
||||
{props.children} <DownOutlined />
|
||||
</Button>
|
||||
}
|
||||
@ -321,6 +335,7 @@ function FinallyButton({
|
||||
style={{
|
||||
display: !designable && field?.data?.hidden && 'none',
|
||||
opacity: designable && field?.data?.hidden && 0.1,
|
||||
...buttonStyle,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
@ -342,7 +357,7 @@ function FinallyButton({
|
||||
...props?.style,
|
||||
display: !designable && field?.data?.hidden && 'none',
|
||||
opacity: designable && field?.data?.hidden && 0.1,
|
||||
height: '100%',
|
||||
...buttonStyle,
|
||||
}}
|
||||
>
|
||||
{props.onlyIcon ? props?.children?.[1] : props?.children}
|
||||
|
@ -57,16 +57,6 @@ export const useLinkageCollectionFieldOptions = (collectionName: string, readPre
|
||||
if (nested || children || ['formula', 'richText', 'sequence'].includes(fieldInterface.name)) {
|
||||
return operator?.value !== ActionType.Value && operator?.value !== ActionType.Options;
|
||||
}
|
||||
if (!['select', 'radioGroup', 'multipleSelect', 'checkboxGroup'].includes(fieldInterface.name)) {
|
||||
return operator?.value !== ActionType.Options;
|
||||
}
|
||||
if (
|
||||
!['date', 'datetime', 'dateOnly', 'datetimeNoTz', 'unixTimestamp', 'createdAt', 'updatedAt'].includes(
|
||||
fieldInterface.name,
|
||||
)
|
||||
) {
|
||||
return operator?.value !== ActionType.DateScope;
|
||||
}
|
||||
return true;
|
||||
}) || [],
|
||||
};
|
||||
|
@ -10,6 +10,7 @@
|
||||
import { useField } from '@formily/react';
|
||||
import { useEffect, useContext } from 'react';
|
||||
import { LinkageLogicContext } from './context';
|
||||
import { ActionType } from './type';
|
||||
|
||||
const findOption = (dataIndex = [], options) => {
|
||||
let items = options;
|
||||
@ -45,9 +46,34 @@ export const useValues = (options) => {
|
||||
field.data = field.data || {};
|
||||
const operators = option?.operators;
|
||||
field.data.operators = operators?.filter((v) => {
|
||||
if (dataIndex.length > 1) {
|
||||
return !['value', 'dateScope', 'options'].includes(v.value);
|
||||
const isOptionField = ['select', 'radioGroup', 'multipleSelect', 'checkboxGroup'].includes(
|
||||
option?.interface || '',
|
||||
);
|
||||
const isDateField = [
|
||||
'date',
|
||||
'datetime',
|
||||
'dateOnly',
|
||||
'datetimeNoTz',
|
||||
'unixTimestamp',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
].includes(option?.interface || '');
|
||||
|
||||
// 如果 多个字段,则排除 Value、DateScope、Options
|
||||
if (dataIndex.length > 1 && [ActionType.Value, ActionType.DateScope, ActionType.Options].includes(v.value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 非选项字段,去掉 Options
|
||||
if (!isOptionField && v.value === ActionType.Options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 非时间字段,去掉 DateScope
|
||||
if (!isDateField && v.value === ActionType.DateScope) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
field.data.schema = option?.schema;
|
||||
|
@ -18,7 +18,6 @@ import { getDataSourceHeaders } from '../data-source/utils';
|
||||
import { useCompile } from '../schema-component';
|
||||
import useBuiltInVariables from './hooks/useBuiltinVariables';
|
||||
import { VariableOption, VariablesContextType } from './types';
|
||||
import { cacheLazyLoadedValues, getCachedLazyLoadedValues } from './utils/cacheLazyLoadedValues';
|
||||
import { filterEmptyValues } from './utils/filterEmptyValues';
|
||||
import { getAction } from './utils/getAction';
|
||||
import { getPath } from './utils/getPath';
|
||||
@ -144,14 +143,13 @@ const VariablesProvider = ({ children, filterVariables }: any) => {
|
||||
.then((data) => {
|
||||
clearRequested(url);
|
||||
const value = data.data.data;
|
||||
cacheLazyLoadedValues(item, currentVariablePath, value);
|
||||
return value;
|
||||
});
|
||||
stashRequested(url, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return getCachedLazyLoadedValues(item, currentVariablePath) || item?.[key];
|
||||
return item?.[key];
|
||||
});
|
||||
current = removeThroughCollectionFields(_.flatten(await Promise.all(result)), associationField);
|
||||
} else if (
|
||||
@ -180,17 +178,9 @@ const VariablesProvider = ({ children, filterVariables }: any) => {
|
||||
}
|
||||
|
||||
const value = data.data.data;
|
||||
if (!getCachedLazyLoadedValues(current, currentVariablePath)) {
|
||||
// Cache the API response data to avoid repeated requests
|
||||
cacheLazyLoadedValues(current, currentVariablePath, value);
|
||||
}
|
||||
|
||||
current = removeThroughCollectionFields(value, associationField);
|
||||
} else {
|
||||
current = removeThroughCollectionFields(
|
||||
getCachedLazyLoadedValues(current, currentVariablePath) || getValuesByPath(current, key),
|
||||
associationField,
|
||||
);
|
||||
current = removeThroughCollectionFields(getValuesByPath(current, key), associationField);
|
||||
}
|
||||
|
||||
if (associationField?.target) {
|
||||
@ -359,13 +349,8 @@ export default VariablesProvider;
|
||||
function shouldToRequest(value, variableCtx: Record<string, any>, variablePath: string) {
|
||||
let result = false;
|
||||
|
||||
if (getCachedLazyLoadedValues(variableCtx, variablePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// value may be a reactive object, using untracked to avoid unexpected autorun
|
||||
untracked(() => {
|
||||
// fix https://nocobase.height.app/T-2502
|
||||
// Compatible with `xxx to many` and `xxx to one` subform fields and subtable fields
|
||||
if (JSON.stringify(value) === '[{}]' || JSON.stringify(value) === '{}') {
|
||||
result = true;
|
||||
|
@ -1,25 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const cache = new Map<Record<string, any>, any>();
|
||||
|
||||
export const cacheLazyLoadedValues = (variableCtx: Record<string, any>, variablePath: string, value: any) => {
|
||||
const cachedValue = cache.get(variableCtx);
|
||||
|
||||
if (cachedValue) {
|
||||
cachedValue[variablePath] = value;
|
||||
} else {
|
||||
cache.set(variableCtx, { [variablePath]: value });
|
||||
}
|
||||
};
|
||||
|
||||
export const getCachedLazyLoadedValues = (variableCtx: Record<string, any>, variablePath: string) => {
|
||||
const cachedValue = cache.get(variableCtx);
|
||||
return cachedValue?.[variablePath];
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-nocobase-app",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"main": "src/index.js",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
|
@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "@nocobase/data-source-manager",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/actions": "1.7.0-alpha.4",
|
||||
"@nocobase/cache": "1.7.0-alpha.4",
|
||||
"@nocobase/database": "1.7.0-alpha.4",
|
||||
"@nocobase/resourcer": "1.7.0-alpha.4",
|
||||
"@nocobase/utils": "1.7.0-alpha.4",
|
||||
"@nocobase/actions": "1.7.0-beta.9",
|
||||
"@nocobase/cache": "1.7.0-beta.9",
|
||||
"@nocobase/database": "1.7.0-beta.9",
|
||||
"@nocobase/resourcer": "1.7.0-beta.9",
|
||||
"@nocobase/utils": "1.7.0-beta.9",
|
||||
"@types/jsonwebtoken": "^8.5.8",
|
||||
"jsonwebtoken": "^8.5.1"
|
||||
},
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@nocobase/database",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"description": "",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@nocobase/logger": "1.7.0-alpha.4",
|
||||
"@nocobase/utils": "1.7.0-alpha.4",
|
||||
"@nocobase/logger": "1.7.0-beta.9",
|
||||
"@nocobase/utils": "1.7.0-beta.9",
|
||||
"async-mutex": "^0.3.2",
|
||||
"chalk": "^4.1.1",
|
||||
"cron-parser": "4.4.0",
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@nocobase/devtools",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./src/index.js",
|
||||
"dependencies": {
|
||||
"@nocobase/build": "1.7.0-alpha.4",
|
||||
"@nocobase/client": "1.7.0-alpha.4",
|
||||
"@nocobase/test": "1.7.0-alpha.4",
|
||||
"@nocobase/build": "1.7.0-beta.9",
|
||||
"@nocobase/client": "1.7.0-beta.9",
|
||||
"@nocobase/test": "1.7.0-beta.9",
|
||||
"@types/koa": "^2.15.0",
|
||||
"@types/koa-bodyparser": "^4.3.4",
|
||||
"@types/lodash": "^4.14.177",
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@nocobase/evaluators",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"description": "",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@formulajs/formulajs": "4.4.9",
|
||||
"@nocobase/utils": "1.7.0-alpha.4",
|
||||
"@nocobase/utils": "1.7.0-beta.9",
|
||||
"mathjs": "^10.6.0"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "@nocobase/lock-manager",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"main": "lib/index.js",
|
||||
"license": "AGPL-3.0",
|
||||
"devDependencies": {
|
||||
"@nocobase/utils": "1.7.0-alpha.4",
|
||||
"@nocobase/utils": "1.7.0-beta.9",
|
||||
"async-mutex": "^0.5.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/logger",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"description": "nocobase logging library",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
|
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@nocobase/resourcer",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"description": "",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@nocobase/utils": "1.7.0-alpha.4",
|
||||
"@nocobase/utils": "1.7.0-beta.9",
|
||||
"deepmerge": "^4.2.2",
|
||||
"koa-compose": "^4.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/sdk",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/server",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"main": "lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"license": "AGPL-3.0",
|
||||
@ -10,19 +10,19 @@
|
||||
"@koa/cors": "^5.0.0",
|
||||
"@koa/multer": "^3.0.2",
|
||||
"@koa/router": "^9.4.0",
|
||||
"@nocobase/acl": "1.7.0-alpha.4",
|
||||
"@nocobase/actions": "1.7.0-alpha.4",
|
||||
"@nocobase/auth": "1.7.0-alpha.4",
|
||||
"@nocobase/cache": "1.7.0-alpha.4",
|
||||
"@nocobase/data-source-manager": "1.7.0-alpha.4",
|
||||
"@nocobase/database": "1.7.0-alpha.4",
|
||||
"@nocobase/evaluators": "1.7.0-alpha.4",
|
||||
"@nocobase/lock-manager": "1.7.0-alpha.4",
|
||||
"@nocobase/logger": "1.7.0-alpha.4",
|
||||
"@nocobase/resourcer": "1.7.0-alpha.4",
|
||||
"@nocobase/sdk": "1.7.0-alpha.4",
|
||||
"@nocobase/telemetry": "1.7.0-alpha.4",
|
||||
"@nocobase/utils": "1.7.0-alpha.4",
|
||||
"@nocobase/acl": "1.7.0-beta.9",
|
||||
"@nocobase/actions": "1.7.0-beta.9",
|
||||
"@nocobase/auth": "1.7.0-beta.9",
|
||||
"@nocobase/cache": "1.7.0-beta.9",
|
||||
"@nocobase/data-source-manager": "1.7.0-beta.9",
|
||||
"@nocobase/database": "1.7.0-beta.9",
|
||||
"@nocobase/evaluators": "1.7.0-beta.9",
|
||||
"@nocobase/lock-manager": "1.7.0-beta.9",
|
||||
"@nocobase/logger": "1.7.0-beta.9",
|
||||
"@nocobase/resourcer": "1.7.0-beta.9",
|
||||
"@nocobase/sdk": "1.7.0-beta.9",
|
||||
"@nocobase/telemetry": "1.7.0-beta.9",
|
||||
"@nocobase/utils": "1.7.0-beta.9",
|
||||
"@types/decompress": "4.2.7",
|
||||
"@types/ini": "^1.3.31",
|
||||
"@types/koa-send": "^4.1.3",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/telemetry",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"description": "nocobase telemetry library",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
@ -11,7 +11,7 @@
|
||||
"directory": "packages/telemetry"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nocobase/utils": "1.7.0-alpha.4",
|
||||
"@nocobase/utils": "1.7.0-beta.9",
|
||||
"@opentelemetry/api": "^1.7.0",
|
||||
"@opentelemetry/instrumentation": "^0.46.0",
|
||||
"@opentelemetry/resources": "^1.19.0",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/test",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"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.7.0-alpha.4",
|
||||
"@nocobase/server": "1.7.0-beta.9",
|
||||
"@playwright/test": "^1.45.3",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/utils",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"main": "lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"license": "AGPL-3.0",
|
||||
|
@ -37,7 +37,7 @@ vi.mock('@formily/json-schema', () => {
|
||||
const result = { ...schema, parent };
|
||||
result._isJSONSchemaObject = true;
|
||||
return result;
|
||||
})
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
@ -261,26 +261,26 @@ describe('transformMultiColumnToSingleColumn', () => {
|
||||
name: 'grid1',
|
||||
'x-component': 'Grid',
|
||||
properties: {
|
||||
row1: {
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col1: { 'x-component': 'Input' },
|
||||
col2: { 'x-component': 'Select' },
|
||||
row1: {
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col1: { 'x-component': 'Input' },
|
||||
col2: { 'x-component': 'Select' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
parent: {
|
||||
properties: {
|
||||
grid1: {} // Will be replaced by result
|
||||
}
|
||||
properties: {
|
||||
grid1: {}, // Will be replaced by result
|
||||
},
|
||||
},
|
||||
toJSON: vi.fn().mockImplementation(function() {
|
||||
return {
|
||||
name: this.name,
|
||||
'x-component': this['x-component'],
|
||||
properties: this.properties
|
||||
};
|
||||
})
|
||||
toJSON: vi.fn().mockImplementation(function () {
|
||||
return {
|
||||
name: this.name,
|
||||
'x-component': this['x-component'],
|
||||
properties: this.properties,
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
const result = transformMultiColumnToSingleColumn(mockSchema);
|
||||
|
@ -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.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/acl",
|
||||
|
@ -276,41 +276,43 @@ export const MenuPermissions: React.FC<{
|
||||
expandable={{
|
||||
defaultExpandAllRows: false,
|
||||
}}
|
||||
columns={[
|
||||
{
|
||||
dataIndex: 'title',
|
||||
title: t('Route name'),
|
||||
},
|
||||
{
|
||||
dataIndex: 'accessible',
|
||||
title: (
|
||||
<>
|
||||
<Checkbox
|
||||
checked={allChecked}
|
||||
onChange={async (value) => {
|
||||
if (allChecked) {
|
||||
await resource.set({
|
||||
values: [],
|
||||
});
|
||||
} else {
|
||||
await resource.set({
|
||||
values: allIDList,
|
||||
});
|
||||
}
|
||||
refresh();
|
||||
refreshDesktopRoutes();
|
||||
message.success(t('Saved successfully'));
|
||||
}}
|
||||
/>{' '}
|
||||
{t('Accessible')}
|
||||
</>
|
||||
),
|
||||
render: (_, schema) => {
|
||||
const checked = IDList.includes(schema.id);
|
||||
return <Checkbox checked={checked} onChange={() => handleChange(checked, schema)} />;
|
||||
columns={
|
||||
[
|
||||
{
|
||||
dataIndex: 'title',
|
||||
title: t('Route name'),
|
||||
},
|
||||
},
|
||||
] as TableProps['columns']}
|
||||
{
|
||||
dataIndex: 'accessible',
|
||||
title: (
|
||||
<>
|
||||
<Checkbox
|
||||
checked={allChecked}
|
||||
onChange={async (value) => {
|
||||
if (allChecked) {
|
||||
await resource.set({
|
||||
values: [],
|
||||
});
|
||||
} else {
|
||||
await resource.set({
|
||||
values: allIDList,
|
||||
});
|
||||
}
|
||||
refresh();
|
||||
refreshDesktopRoutes();
|
||||
message.success(t('Saved successfully'));
|
||||
}}
|
||||
/>{' '}
|
||||
{t('Accessible')}
|
||||
</>
|
||||
),
|
||||
render: (_, schema) => {
|
||||
const checked = IDList.includes(schema.id);
|
||||
return <Checkbox checked={checked} onChange={() => handleChange(checked, schema)} />;
|
||||
},
|
||||
},
|
||||
] as TableProps['columns']
|
||||
}
|
||||
dataSource={translateTitle(items, t, compile)}
|
||||
/>
|
||||
</>
|
||||
|
@ -113,44 +113,46 @@ export const PluginPermissions: React.FC<{
|
||||
expandable={{
|
||||
defaultExpandAllRows: true,
|
||||
}}
|
||||
columns={[
|
||||
{
|
||||
dataIndex: 'title',
|
||||
title: t('Plugin name'),
|
||||
render: (value) => {
|
||||
return compile(value);
|
||||
columns={
|
||||
[
|
||||
{
|
||||
dataIndex: 'title',
|
||||
title: t('Plugin name'),
|
||||
render: (value) => {
|
||||
return compile(value);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'accessible',
|
||||
title: (
|
||||
<>
|
||||
<Checkbox
|
||||
checked={allChecked}
|
||||
onChange={async () => {
|
||||
const values = allAclSnippets.map((v) => '!' + v);
|
||||
if (!allChecked) {
|
||||
await resource.remove({
|
||||
values,
|
||||
});
|
||||
} else {
|
||||
await resource.add({
|
||||
values,
|
||||
});
|
||||
}
|
||||
refresh();
|
||||
message.success(t('Saved successfully'));
|
||||
}}
|
||||
/>
|
||||
{t('Accessible')}
|
||||
</>
|
||||
),
|
||||
render: (_, record) => {
|
||||
const checked = !snippets.includes('!' + record.aclSnippet);
|
||||
return <Checkbox checked={checked} onChange={() => handleChange(checked, record)} />;
|
||||
{
|
||||
dataIndex: 'accessible',
|
||||
title: (
|
||||
<>
|
||||
<Checkbox
|
||||
checked={allChecked}
|
||||
onChange={async () => {
|
||||
const values = allAclSnippets.map((v) => '!' + v);
|
||||
if (!allChecked) {
|
||||
await resource.remove({
|
||||
values,
|
||||
});
|
||||
} else {
|
||||
await resource.add({
|
||||
values,
|
||||
});
|
||||
}
|
||||
refresh();
|
||||
message.success(t('Saved successfully'));
|
||||
}}
|
||||
/>
|
||||
{t('Accessible')}
|
||||
</>
|
||||
),
|
||||
render: (_, record) => {
|
||||
const checked = !snippets.includes('!' + record.aclSnippet);
|
||||
return <Checkbox checked={checked} onChange={() => handleChange(checked, record)} />;
|
||||
},
|
||||
},
|
||||
},
|
||||
] as TableProps['columns']}
|
||||
] as TableProps['columns']
|
||||
}
|
||||
dataSource={settings
|
||||
.filter((v) => {
|
||||
return v.isTopLevel !== false;
|
||||
|
@ -106,48 +106,50 @@ export const RolesResourcesActions = connect((props) => {
|
||||
className={styles}
|
||||
size={'small'}
|
||||
pagination={false}
|
||||
columns={[
|
||||
{
|
||||
dataIndex: 'displayName',
|
||||
title: t('Action display name'),
|
||||
render: (value) => compile(value),
|
||||
},
|
||||
{
|
||||
dataIndex: 'onNewRecord',
|
||||
title: t('Action type'),
|
||||
render: (onNewRecord) =>
|
||||
onNewRecord ? (
|
||||
<Tag color={'green'}>{t('Action on new records')}</Tag>
|
||||
) : (
|
||||
<Tag color={'geekblue'}>{t('Action on existing records')}</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
dataIndex: 'enabled',
|
||||
title: t('Allow'),
|
||||
render: (enabled, action) => (
|
||||
<Checkbox
|
||||
checked={enabled}
|
||||
onChange={() => {
|
||||
toggleAction(action.name);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
dataIndex: 'scope',
|
||||
title: t('Data scope'),
|
||||
render: (value, action) =>
|
||||
!action.onNewRecord && (
|
||||
<ScopeSelect
|
||||
value={value}
|
||||
onChange={(scope) => {
|
||||
setScope(action.name, scope);
|
||||
columns={
|
||||
[
|
||||
{
|
||||
dataIndex: 'displayName',
|
||||
title: t('Action display name'),
|
||||
render: (value) => compile(value),
|
||||
},
|
||||
{
|
||||
dataIndex: 'onNewRecord',
|
||||
title: t('Action type'),
|
||||
render: (onNewRecord) =>
|
||||
onNewRecord ? (
|
||||
<Tag color={'green'}>{t('Action on new records')}</Tag>
|
||||
) : (
|
||||
<Tag color={'geekblue'}>{t('Action on existing records')}</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
dataIndex: 'enabled',
|
||||
title: t('Allow'),
|
||||
render: (enabled, action) => (
|
||||
<Checkbox
|
||||
checked={enabled}
|
||||
onChange={() => {
|
||||
toggleAction(action.name);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
] as TableProps['columns']}
|
||||
},
|
||||
{
|
||||
dataIndex: 'scope',
|
||||
title: t('Data scope'),
|
||||
render: (value, action) =>
|
||||
!action.onNewRecord && (
|
||||
<ScopeSelect
|
||||
value={value}
|
||||
onChange={(scope) => {
|
||||
setScope(action.name, scope);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
] as TableProps['columns']
|
||||
}
|
||||
dataSource={availableActions?.map((item) => {
|
||||
let enabled = false;
|
||||
let scope = null;
|
||||
@ -170,60 +172,62 @@ export const RolesResourcesActions = connect((props) => {
|
||||
className={styles}
|
||||
pagination={false}
|
||||
dataSource={fieldPermissions}
|
||||
columns={[
|
||||
{
|
||||
dataIndex: ['uiSchema', 'title'],
|
||||
title: t('Field display name'),
|
||||
render: (value) => compile(value),
|
||||
},
|
||||
...availableActionsWithFields.map((action) => {
|
||||
const checked = allChecked?.[action.name];
|
||||
return {
|
||||
dataIndex: action.name,
|
||||
title: (
|
||||
<>
|
||||
columns={
|
||||
[
|
||||
{
|
||||
dataIndex: ['uiSchema', 'title'],
|
||||
title: t('Field display name'),
|
||||
render: (value) => compile(value),
|
||||
},
|
||||
...availableActionsWithFields.map((action) => {
|
||||
const checked = allChecked?.[action.name];
|
||||
return {
|
||||
dataIndex: action.name,
|
||||
title: (
|
||||
<>
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
onChange={() => {
|
||||
const item = actionMap[action.name] || {
|
||||
name: action.name,
|
||||
};
|
||||
if (checked) {
|
||||
item.fields = [];
|
||||
} else {
|
||||
item.fields = collectionFields?.map?.((item) => item.name);
|
||||
}
|
||||
actionMap[action.name] = item;
|
||||
onChange(Object.values(actionMap));
|
||||
}}
|
||||
/>{' '}
|
||||
{compile(action.displayName)}
|
||||
</>
|
||||
),
|
||||
render: (checked, field) => (
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
aria-label={`${action.name}_checkbox`}
|
||||
onChange={() => {
|
||||
const item = actionMap[action.name] || {
|
||||
name: action.name,
|
||||
};
|
||||
const fields: string[] = item.fields || [];
|
||||
if (checked) {
|
||||
item.fields = [];
|
||||
const index = fields.indexOf(field.name);
|
||||
fields.splice(index, 1);
|
||||
} else {
|
||||
item.fields = collectionFields?.map?.((item) => item.name);
|
||||
fields.push(field.name);
|
||||
}
|
||||
item.fields = fields;
|
||||
actionMap[action.name] = item;
|
||||
onChange(Object.values(actionMap));
|
||||
}}
|
||||
/>{' '}
|
||||
{compile(action.displayName)}
|
||||
</>
|
||||
),
|
||||
render: (checked, field) => (
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
aria-label={`${action.name}_checkbox`}
|
||||
onChange={() => {
|
||||
const item = actionMap[action.name] || {
|
||||
name: action.name,
|
||||
};
|
||||
const fields: string[] = item.fields || [];
|
||||
if (checked) {
|
||||
const index = fields.indexOf(field.name);
|
||||
fields.splice(index, 1);
|
||||
} else {
|
||||
fields.push(field.name);
|
||||
}
|
||||
item.fields = fields;
|
||||
actionMap[action.name] = item;
|
||||
onChange(Object.values(actionMap));
|
||||
}}
|
||||
/>
|
||||
),
|
||||
};
|
||||
}),
|
||||
] as TableProps['columns']}
|
||||
/>
|
||||
),
|
||||
};
|
||||
}),
|
||||
] as TableProps['columns']
|
||||
}
|
||||
/>
|
||||
</FormItem>
|
||||
</FormLayout>
|
||||
|
@ -55,62 +55,64 @@ export const StrategyActions = connect((props) => {
|
||||
rowKey={'name'}
|
||||
size={'small'}
|
||||
pagination={false}
|
||||
columns={[
|
||||
{
|
||||
dataIndex: 'displayName',
|
||||
title: t('Action display name'),
|
||||
render: (value) => compile(value),
|
||||
},
|
||||
{
|
||||
dataIndex: 'onNewRecord',
|
||||
title: t('Action type'),
|
||||
render: (onNewRecord) =>
|
||||
onNewRecord ? (
|
||||
<Tag color={'green'}>{t('Action on new records')}</Tag>
|
||||
) : (
|
||||
<Tag color={'geekblue'}>{t('Action on existing records')}</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
dataIndex: 'enabled',
|
||||
title: t('Allow'),
|
||||
render: (enabled, action) => (
|
||||
<Checkbox
|
||||
checked={enabled}
|
||||
aria-label={`${action.name}_checkbox`}
|
||||
onChange={(e) => {
|
||||
if (enabled) {
|
||||
delete scopes[action.name];
|
||||
} else {
|
||||
scopes[action.name] = 'all';
|
||||
}
|
||||
onChange(toFieldValue(scopes));
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
dataIndex: 'scope',
|
||||
title: t('Data scope'),
|
||||
render: (scope, action) =>
|
||||
!action.onNewRecord && (
|
||||
<Select
|
||||
data-testid="select-data-scope"
|
||||
popupMatchSelectWidth={false}
|
||||
size={'small'}
|
||||
value={scope}
|
||||
options={[
|
||||
{ label: t('All records'), value: 'all' },
|
||||
{ label: t('Own records'), value: 'own' },
|
||||
]}
|
||||
onChange={(value) => {
|
||||
scopes[action.name] = value;
|
||||
columns={
|
||||
[
|
||||
{
|
||||
dataIndex: 'displayName',
|
||||
title: t('Action display name'),
|
||||
render: (value) => compile(value),
|
||||
},
|
||||
{
|
||||
dataIndex: 'onNewRecord',
|
||||
title: t('Action type'),
|
||||
render: (onNewRecord) =>
|
||||
onNewRecord ? (
|
||||
<Tag color={'green'}>{t('Action on new records')}</Tag>
|
||||
) : (
|
||||
<Tag color={'geekblue'}>{t('Action on existing records')}</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
dataIndex: 'enabled',
|
||||
title: t('Allow'),
|
||||
render: (enabled, action) => (
|
||||
<Checkbox
|
||||
checked={enabled}
|
||||
aria-label={`${action.name}_checkbox`}
|
||||
onChange={(e) => {
|
||||
if (enabled) {
|
||||
delete scopes[action.name];
|
||||
} else {
|
||||
scopes[action.name] = 'all';
|
||||
}
|
||||
onChange(toFieldValue(scopes));
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
] as TableProps['columns']}
|
||||
},
|
||||
{
|
||||
dataIndex: 'scope',
|
||||
title: t('Data scope'),
|
||||
render: (scope, action) =>
|
||||
!action.onNewRecord && (
|
||||
<Select
|
||||
data-testid="select-data-scope"
|
||||
popupMatchSelectWidth={false}
|
||||
size={'small'}
|
||||
value={scope}
|
||||
options={[
|
||||
{ label: t('All records'), value: 'all' },
|
||||
{ label: t('Own records'), value: 'own' },
|
||||
]}
|
||||
onChange={(value) => {
|
||||
scopes[action.name] = value;
|
||||
onChange(toFieldValue(scopes));
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
] as TableProps['columns']
|
||||
}
|
||||
dataSource={availableActions?.map((item) => {
|
||||
let scope = 'all';
|
||||
let enabled = false;
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-action-bulk-edit",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"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",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-action-bulk-update",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"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",
|
||||
|
@ -8,7 +8,6 @@
|
||||
*/
|
||||
|
||||
import { ISchema, useFieldSchema } from '@formily/react';
|
||||
import { isValid } from '@formily/shared';
|
||||
import {
|
||||
ActionDesigner,
|
||||
SchemaSettings,
|
||||
@ -19,6 +18,8 @@ import {
|
||||
useDesignable,
|
||||
useSchemaToolbar,
|
||||
RefreshDataBlockRequest,
|
||||
useAfterSuccessOptions,
|
||||
useGlobalVariable,
|
||||
} from '@nocobase/client';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React from 'react';
|
||||
@ -49,11 +50,22 @@ function UpdateMode() {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const fieldNames = {
|
||||
value: 'value',
|
||||
label: 'label',
|
||||
};
|
||||
const useVariableProps = (environmentVariables) => {
|
||||
const scope = useAfterSuccessOptions();
|
||||
return {
|
||||
scope: [environmentVariables, ...scope].filter(Boolean),
|
||||
fieldNames,
|
||||
};
|
||||
};
|
||||
function AfterSuccess() {
|
||||
const { dn } = useDesignable();
|
||||
const { t } = useTranslation();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const environmentVariables = useGlobalVariable('$env');
|
||||
return (
|
||||
<SchemaSettingsModalItem
|
||||
title={t('After successful submission')}
|
||||
@ -100,8 +112,9 @@ function AfterSuccess() {
|
||||
redirectTo: {
|
||||
title: t('Link'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {},
|
||||
'x-component': 'Variable.TextArea',
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
'x-use-component-props': () => useVariableProps(environmentVariables),
|
||||
},
|
||||
},
|
||||
} as ISchema
|
||||
|
@ -68,7 +68,7 @@ export const useCustomizeBulkUpdateActionProps = () => {
|
||||
if (result) {
|
||||
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
|
||||
}
|
||||
} else if (value != null && value !== '') {
|
||||
} else if (value !== '') {
|
||||
assignedValues[key] = value;
|
||||
}
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-action-custom-request",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"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",
|
||||
|
@ -68,10 +68,13 @@ export const useCustomizeRequestActionProps = () => {
|
||||
responseType: fieldSchema['x-response-type'] === 'stream' ? 'blob' : 'json',
|
||||
});
|
||||
if (res.headers['content-disposition']) {
|
||||
const regex = /attachment;\s*filename="([^"]+)"/;
|
||||
const match = res.headers['content-disposition'].match(regex);
|
||||
if (match?.[1]) {
|
||||
saveAs(res.data, match[1]);
|
||||
const contentDisposition = res.headers['content-disposition'];
|
||||
const utf8Match = contentDisposition.match(/filename\*=utf-8''([^;]+)/i);
|
||||
const asciiMatch = contentDisposition.match(/filename="([^"]+)"/i);
|
||||
if (utf8Match) {
|
||||
saveAs(res.data, decodeURIComponent(utf8Match[1]));
|
||||
} else if (asciiMatch) {
|
||||
saveAs(res.data, asciiMatch[1]);
|
||||
}
|
||||
}
|
||||
actionField.data.loading = false;
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-action-duplicate",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"main": "dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/action-duplicate",
|
||||
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-duplicate",
|
||||
|
@ -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.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/action-export",
|
||||
|
@ -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.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/action-import",
|
||||
|
@ -12,6 +12,7 @@ import { ISchema, useFieldSchema } from '@formily/react';
|
||||
import { Action, ActionContextProvider, PopupSettingsProvider, SchemaComponent, useCompile } from '@nocobase/client';
|
||||
import React, { useState } from 'react';
|
||||
import { NAMESPACE } from './constants';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { UploadOutlined } from '@ant-design/icons';
|
||||
|
||||
const importFormSchema: ISchema = {
|
||||
@ -128,7 +129,7 @@ export const ImportAction = (props) => {
|
||||
return (
|
||||
<ActionContextProvider value={{ visible, setVisible, fieldSchema }}>
|
||||
<Action
|
||||
icon={props.icon || <UploadOutlined />}
|
||||
icon={props.icon || 'uploadoutlined'}
|
||||
title={compile(fieldSchema?.title || "t('Import')")}
|
||||
{...props}
|
||||
onClick={() => setVisible(true)}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-action-print",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"main": "dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/action-print",
|
||||
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-print",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"displayName.zh-CN": "AI 集成",
|
||||
"description": "Support integration with AI services, providing AI-related workflow nodes to enhance business processing capabilities.",
|
||||
"description.zh-CN": "支持接入 AI 服务,提供 AI 相关的工作流节点,增强业务处理能力。",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"main": "dist/server/index.js",
|
||||
"peerDependencies": {
|
||||
"@nocobase/client": "1.x",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-api-doc",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"displayName": "API documentation",
|
||||
"displayName.zh-CN": "API 文档",
|
||||
"description": "An OpenAPI documentation generator for NocoBase HTTP API.",
|
||||
|
@ -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.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/api-keys",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"displayName.zh-CN": "异步任务管理器",
|
||||
"description": "Manage and monitor asynchronous tasks such as data import/export. Support task progress tracking and notification.",
|
||||
"description.zh-CN": "管理和监控数据导入导出等异步任务。支持任务进度跟踪和通知。",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"main": "dist/server/index.js",
|
||||
"peerDependencies": {
|
||||
"@nocobase/client": "1.x",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-audit-logs",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"displayName": "Audit logs (deprecated)",
|
||||
"displayName.zh-CN": "审计日志(废弃)",
|
||||
"description": "This plugin is deprecated. There will be a new audit log plugin in the future.",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"displayName.zh-CN": "认证:短信",
|
||||
"description": "SMS authentication.",
|
||||
"description.zh-CN": "通过短信验证码认证身份。",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"main": "./dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/auth-sms",
|
||||
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/auth-sms",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-auth",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"main": "./dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/auth",
|
||||
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/auth",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"displayName.zh-CN": "应用的备份与还原(废弃)",
|
||||
"description": "Backup and restore applications for scenarios such as application replication, migration, and upgrades.",
|
||||
"description.zh-CN": "备份和还原应用,可用于应用的复制、迁移、升级等场景。",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/backup-restore",
|
||||
|
@ -111,11 +111,11 @@ const LearnMore: any = (props: { collectionsData?: any; isBackup?: boolean }) =>
|
||||
render: (_, data) => {
|
||||
const title = compile(data.title);
|
||||
const name = data.name;
|
||||
return name === title ? title : (
|
||||
return name === title ? (
|
||||
title
|
||||
) : (
|
||||
<div>
|
||||
{data.name}
|
||||
{' '}
|
||||
<span style={{ color: 'rgba(0, 0, 0, 0.3)', fontSize: '0.9em' }}>({compile(data.title)})</span>
|
||||
{data.name} <span style={{ color: 'rgba(0, 0, 0, 0.3)', fontSize: '0.9em' }}>({compile(data.title)})</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@ -418,73 +418,75 @@ export const BackupAndRestoreList = () => {
|
||||
<Table
|
||||
dataSource={dataSource}
|
||||
loading={loading}
|
||||
columns={[
|
||||
{
|
||||
title: t('Backup file'),
|
||||
dataIndex: 'name',
|
||||
width: 400,
|
||||
onCell: (data) => {
|
||||
return data.inProgress
|
||||
? {
|
||||
colSpan: 4,
|
||||
}
|
||||
: {};
|
||||
columns={
|
||||
[
|
||||
{
|
||||
title: t('Backup file'),
|
||||
dataIndex: 'name',
|
||||
width: 400,
|
||||
onCell: (data) => {
|
||||
return data.inProgress
|
||||
? {
|
||||
colSpan: 4,
|
||||
}
|
||||
: {};
|
||||
},
|
||||
render: (name, data) =>
|
||||
data.inProgress ? (
|
||||
<div style={{ color: 'rgba(0, 0, 0, 0.88)' }}>
|
||||
{name}({t('Backing up')}...)
|
||||
</div>
|
||||
) : (
|
||||
<div>{name}</div>
|
||||
),
|
||||
},
|
||||
render: (name, data) =>
|
||||
data.inProgress ? (
|
||||
<div style={{ color: 'rgba(0, 0, 0, 0.88)' }}>
|
||||
{name}({t('Backing up')}...)
|
||||
</div>
|
||||
) : (
|
||||
<div>{name}</div>
|
||||
{
|
||||
title: t('File size'),
|
||||
dataIndex: 'fileSize',
|
||||
onCell: (data) => {
|
||||
return data.inProgress
|
||||
? {
|
||||
colSpan: 0,
|
||||
}
|
||||
: {};
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Created at', { ns: 'client' }),
|
||||
dataIndex: 'createdAt',
|
||||
onCell: (data) => {
|
||||
return data.inProgress
|
||||
? {
|
||||
colSpan: 0,
|
||||
}
|
||||
: {};
|
||||
},
|
||||
render: (value) => {
|
||||
return <DatePicker.ReadPretty value={value} showTime />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Actions', { ns: 'client' }),
|
||||
dataIndex: 'actions',
|
||||
onCell: (data) => {
|
||||
return data.inProgress
|
||||
? {
|
||||
colSpan: 0,
|
||||
}
|
||||
: {};
|
||||
},
|
||||
render: (_, record) => (
|
||||
<Space split={<Divider type="vertical" />}>
|
||||
<Restore ButtonComponent={'a'} title={t('Restore')} fileData={record} />
|
||||
<a type="link" onClick={() => handleDownload(record)}>
|
||||
{t('Download')}
|
||||
</a>
|
||||
<a onClick={() => handleDestory(record)}>{t('Delete')}</a>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('File size'),
|
||||
dataIndex: 'fileSize',
|
||||
onCell: (data) => {
|
||||
return data.inProgress
|
||||
? {
|
||||
colSpan: 0,
|
||||
}
|
||||
: {};
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Created at', { ns: 'client' }),
|
||||
dataIndex: 'createdAt',
|
||||
onCell: (data) => {
|
||||
return data.inProgress
|
||||
? {
|
||||
colSpan: 0,
|
||||
}
|
||||
: {};
|
||||
},
|
||||
render: (value) => {
|
||||
return <DatePicker.ReadPretty value={value} showTime />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Actions', { ns: 'client' }),
|
||||
dataIndex: 'actions',
|
||||
onCell: (data) => {
|
||||
return data.inProgress
|
||||
? {
|
||||
colSpan: 0,
|
||||
}
|
||||
: {};
|
||||
},
|
||||
render: (_, record) => (
|
||||
<Space split={<Divider type="vertical" />}>
|
||||
<Restore ButtonComponent={'a'} title={t('Restore')} fileData={record} />
|
||||
<a type="link" onClick={() => handleDownload(record)}>
|
||||
{t('Download')}
|
||||
</a>
|
||||
<a onClick={() => handleDestory(record)}>{t('Delete')}</a>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
] as TableProps['columns']}
|
||||
] as TableProps['columns']
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
|
@ -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.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/block-iframe",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"displayName.zh-CN": "区块:模板",
|
||||
"description": "Create and manage block templates for reuse on pages.",
|
||||
"description.zh-CN": "创建和管理区块模板,用于在页面中重复使用。",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/block-template",
|
||||
|
@ -23,7 +23,7 @@ import { useLocation } from 'react-router-dom';
|
||||
|
||||
const blockDecoratorMenuMaps = {
|
||||
TableBlockProvider: ['Table', 'table'],
|
||||
FormBlockProvider: ['Form', 'form'],
|
||||
FormBlockProvider: ['FormItem', 'form'],
|
||||
DetailsBlockProvider: ['Details', 'details'],
|
||||
'List.Decorator': ['List', 'list'],
|
||||
'GridCard.Decorator': ['GridCard', 'gridCard'],
|
||||
@ -281,6 +281,7 @@ function getTemplateSchemaFromPage(schema: ISchema) {
|
||||
if (s['x-template-root-uid']) {
|
||||
return;
|
||||
}
|
||||
t = t || {};
|
||||
_.merge(t, _.omit(s, ['x-uid', 'properties']));
|
||||
t['x-uid'] = uid();
|
||||
if (s.properties) {
|
||||
@ -288,7 +289,7 @@ function getTemplateSchemaFromPage(schema: ISchema) {
|
||||
if (s.properties[key]['x-template-root-uid']) {
|
||||
continue;
|
||||
}
|
||||
_.set(t, `properties.${key}`, {});
|
||||
_.set(t, `properties.['${key}']`, {});
|
||||
traverseSchema(s.properties[key], t.properties[key]);
|
||||
}
|
||||
}
|
||||
|
@ -169,6 +169,9 @@ export class PluginBlockTemplateClient extends Plugin {
|
||||
'blockSettings:createForm',
|
||||
'blockSettings:details',
|
||||
'blockSettings:detailsWithPagination',
|
||||
'blockSettings:multiDataDetails',
|
||||
'blockSettings:singleDataDetails',
|
||||
'blockSettings:stepsForm',
|
||||
'blockSettings:filterCollapse',
|
||||
'blockSettings:filterForm',
|
||||
'blockSettings:gantt',
|
||||
@ -176,6 +179,13 @@ export class PluginBlockTemplateClient extends Plugin {
|
||||
'blockSettings:kanban',
|
||||
'blockSettings:list',
|
||||
'blockSettings:table',
|
||||
'blockSettings:tree',
|
||||
'ReadPrettyFormSettings',
|
||||
'GanttBlockSettings',
|
||||
'FormV1Settings',
|
||||
'FormSettings',
|
||||
'FormItemSettings',
|
||||
'FormDetailsSettings',
|
||||
];
|
||||
if (blockSettings.includes(key)) {
|
||||
// schemaSetting.add('template-saveAsTemplateItem', saveAsTemplateSetting);
|
||||
|
@ -43,6 +43,10 @@ export function convertTplBlock(
|
||||
if (newSchema['x-decorator'] === 'TemplateGridDecorator') {
|
||||
delete newSchema['x-decorator'];
|
||||
}
|
||||
if (newSchema['x-linkage-rules']) {
|
||||
// linkage rules 有可能保存在Grid组件中
|
||||
delete newSchema['x-linkage-rules'];
|
||||
}
|
||||
for (const key in tpl.properties) {
|
||||
const t = convertTplBlock(tpl.properties[key], virtual, isRoot, newRootId, templateKey, options);
|
||||
if (isRoot) {
|
||||
@ -154,8 +158,12 @@ export function formSchemaPatch(currentSchema: ISchema, options?: any) {
|
||||
return key !== 'grid';
|
||||
});
|
||||
if (actionKey) {
|
||||
_.set(currentSchema, `properties.${comKey}.x-use-component-props`, 'useEditFormBlockProps');
|
||||
_.set(currentSchema, `properties.${comKey}.properties.${actionKey}.x-initializer`, 'editForm:configureActions');
|
||||
_.set(currentSchema, `properties.['${comKey}'].x-use-component-props`, 'useEditFormBlockProps');
|
||||
_.set(
|
||||
currentSchema,
|
||||
`properties.['${comKey}'].properties.['${actionKey}'].x-initializer`,
|
||||
'editForm:configureActions',
|
||||
);
|
||||
|
||||
const actionBarSchema = _.get(currentSchema, `properties.${comKey}.properties.${actionKey}.properties`, {});
|
||||
for (const key in actionBarSchema) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-block-workbench",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"displayName": "Block: Action panel",
|
||||
"displayName.zh-CN": "区块:操作面板",
|
||||
"description": "Centrally manages and displays various actions, allowing users to efficiently perform tasks. It supports extensibility, with current action types including pop-ups, links, scanning, and custom requests.",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-calendar",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"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.",
|
||||
|
@ -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.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"main": "./dist/server/index.js",
|
||||
"license": "AGPL-3.0",
|
||||
"devDependencies": {
|
||||
|
@ -4,7 +4,7 @@
|
||||
"displayName.zh-CN": "WEB 客户端",
|
||||
"description": "Provides a client interface for the NocoBase server",
|
||||
"description.zh-CN": "为 NocoBase 服务端提供客户端界面",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"main": "./dist/server/index.js",
|
||||
"license": "AGPL-3.0",
|
||||
"devDependencies": {
|
||||
|
@ -4,7 +4,7 @@
|
||||
"displayName.zh-CN": "数据表: SQL",
|
||||
"description": "Provides SQL collection template",
|
||||
"description.zh-CN": "提供 SQL 数据表模板",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"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",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-collection-tree",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"displayName": "Collection: Tree",
|
||||
"displayName.zh-CN": "数据表:树",
|
||||
"description": "Provides tree collection template",
|
||||
|
@ -31,6 +31,8 @@ export default class extends Migration {
|
||||
await this.db.sequelize.transaction(async (transaction) => {
|
||||
const treeCollections = await this.getTreeCollections({ transaction });
|
||||
for (const treeCollection of treeCollections) {
|
||||
await treeCollection.load({ transaction });
|
||||
|
||||
const name = `main_${treeCollection.name}_path`;
|
||||
const collectionOptions = {
|
||||
name,
|
||||
@ -47,35 +49,16 @@ export default class extends Migration {
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const collectionInstance = this.db.getCollection(treeCollection.name);
|
||||
const treeCollectionSchema = collectionInstance.collectionSchema();
|
||||
|
||||
if (this.app.db.inDialect('postgres') && treeCollectionSchema != this.app.db.options.schema) {
|
||||
collectionOptions['schema'] = treeCollectionSchema;
|
||||
}
|
||||
|
||||
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);
|
||||
const opts = {
|
||||
name: treeCollection.name,
|
||||
autoGenId: false,
|
||||
timestamps: false,
|
||||
fields: [
|
||||
{ type: 'integer', name: 'id' },
|
||||
{ type: 'integer', name: 'parentId' },
|
||||
],
|
||||
};
|
||||
|
||||
if (treeCollectionSchema != this.app.db.options.schema) {
|
||||
opts['schema'] = treeCollectionSchema;
|
||||
}
|
||||
|
||||
this.app.db.collection(opts);
|
||||
const chunkSize = 1000;
|
||||
await this.app.db.getRepository(treeCollection.name).chunk({
|
||||
chunkSize: chunkSize,
|
||||
|
@ -4,7 +4,7 @@
|
||||
"displayName.zh-CN": "数据源:主数据库",
|
||||
"description": "NocoBase main database, supports relational databases such as PostgreSQL, MySQL, MariaDB and so on.",
|
||||
"description.zh-CN": "NocoBase 主数据库,支持 PostgreSQL、MySQL、MariaDB 等关系型数据库。",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"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",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-data-source-manager",
|
||||
"version": "1.7.0-alpha.4",
|
||||
"version": "1.7.0-beta.9",
|
||||
"main": "dist/server/index.js",
|
||||
"displayName": "Data source manager",
|
||||
"displayName.zh-CN": "数据源管理",
|
||||
|
@ -101,48 +101,50 @@ export const RolesResourcesActions = connect((props) => {
|
||||
className={styles}
|
||||
size={'small'}
|
||||
pagination={false}
|
||||
columns={[
|
||||
{
|
||||
dataIndex: 'displayName',
|
||||
title: t('Action display name'),
|
||||
render: (value) => compile(value),
|
||||
},
|
||||
{
|
||||
dataIndex: 'onNewRecord',
|
||||
title: t('Action type'),
|
||||
render: (onNewRecord) =>
|
||||
onNewRecord ? (
|
||||
<Tag color={'green'}>{t('Action on new records')}</Tag>
|
||||
) : (
|
||||
<Tag color={'geekblue'}>{t('Action on existing records')}</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
dataIndex: 'enabled',
|
||||
title: t('Allow'),
|
||||
render: (enabled, action) => (
|
||||
<Checkbox
|
||||
checked={enabled}
|
||||
onChange={() => {
|
||||
toggleAction(action.name);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
dataIndex: 'scope',
|
||||
title: t('Data scope'),
|
||||
render: (value, action) =>
|
||||
!action.onNewRecord && (
|
||||
<ScopeSelect
|
||||
value={value}
|
||||
onChange={(scope) => {
|
||||
setScope(action.name, scope);
|
||||
columns={
|
||||
[
|
||||
{
|
||||
dataIndex: 'displayName',
|
||||
title: t('Action display name'),
|
||||
render: (value) => compile(value),
|
||||
},
|
||||
{
|
||||
dataIndex: 'onNewRecord',
|
||||
title: t('Action type'),
|
||||
render: (onNewRecord) =>
|
||||
onNewRecord ? (
|
||||
<Tag color={'green'}>{t('Action on new records')}</Tag>
|
||||
) : (
|
||||
<Tag color={'geekblue'}>{t('Action on existing records')}</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
dataIndex: 'enabled',
|
||||
title: t('Allow'),
|
||||
render: (enabled, action) => (
|
||||
<Checkbox
|
||||
checked={enabled}
|
||||
onChange={() => {
|
||||
toggleAction(action.name);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
] as TableProps['columns']}
|
||||
},
|
||||
{
|
||||
dataIndex: 'scope',
|
||||
title: t('Data scope'),
|
||||
render: (value, action) =>
|
||||
!action.onNewRecord && (
|
||||
<ScopeSelect
|
||||
value={value}
|
||||
onChange={(scope) => {
|
||||
setScope(action.name, scope);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
] as TableProps['columns']
|
||||
}
|
||||
dataSource={availableActions?.map((item) => {
|
||||
let enabled = false;
|
||||
let scope = null;
|
||||
@ -165,59 +167,61 @@ export const RolesResourcesActions = connect((props) => {
|
||||
className={styles}
|
||||
pagination={false}
|
||||
dataSource={fieldPermissions}
|
||||
columns={[
|
||||
{
|
||||
dataIndex: ['uiSchema', 'title'],
|
||||
title: t('Field display name'),
|
||||
render: (value, record) => compile(value) || record.name,
|
||||
},
|
||||
...availableActionsWithFields.map((action) => {
|
||||
const checked = allChecked?.[action.name];
|
||||
return {
|
||||
dataIndex: action.name,
|
||||
title: (
|
||||
<>
|
||||
columns={
|
||||
[
|
||||
{
|
||||
dataIndex: ['uiSchema', 'title'],
|
||||
title: t('Field display name'),
|
||||
render: (value, record) => compile(value) || record.name,
|
||||
},
|
||||
...availableActionsWithFields.map((action) => {
|
||||
const checked = allChecked?.[action.name];
|
||||
return {
|
||||
dataIndex: action.name,
|
||||
title: (
|
||||
<>
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
onChange={() => {
|
||||
const item = actionMap[action.name] || {
|
||||
name: action.name,
|
||||
};
|
||||
if (checked) {
|
||||
item.fields = [];
|
||||
} else {
|
||||
item.fields = collectionFields?.map?.((item) => item.name);
|
||||
}
|
||||
actionMap[action.name] = item;
|
||||
onChange(Object.values(actionMap));
|
||||
}}
|
||||
/>
|
||||
{compile(action.displayName)}
|
||||
</>
|
||||
),
|
||||
render: (checked, field) => (
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
onChange={() => {
|
||||
const item = actionMap[action.name] || {
|
||||
name: action.name,
|
||||
};
|
||||
const fields: string[] = item.fields || [];
|
||||
if (checked) {
|
||||
item.fields = [];
|
||||
const index = fields.indexOf(field.name);
|
||||
fields.splice(index, 1);
|
||||
} else {
|
||||
item.fields = collectionFields?.map?.((item) => item.name);
|
||||
fields.push(field.name);
|
||||
}
|
||||
item.fields = fields;
|
||||
actionMap[action.name] = item;
|
||||
onChange(Object.values(actionMap));
|
||||
}}
|
||||
/>
|
||||
{compile(action.displayName)}
|
||||
</>
|
||||
),
|
||||
render: (checked, field) => (
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
onChange={() => {
|
||||
const item = actionMap[action.name] || {
|
||||
name: action.name,
|
||||
};
|
||||
const fields: string[] = item.fields || [];
|
||||
if (checked) {
|
||||
const index = fields.indexOf(field.name);
|
||||
fields.splice(index, 1);
|
||||
} else {
|
||||
fields.push(field.name);
|
||||
}
|
||||
item.fields = fields;
|
||||
actionMap[action.name] = item;
|
||||
onChange(Object.values(actionMap));
|
||||
}}
|
||||
/>
|
||||
),
|
||||
};
|
||||
}),
|
||||
] as TableProps['columns']}
|
||||
),
|
||||
};
|
||||
}),
|
||||
] as TableProps['columns']
|
||||
}
|
||||
/>
|
||||
</FormItem>
|
||||
</FormLayout>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user