mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 21:49:25 +08:00
Merge branch 'develop' into next
# Conflicts: # packages/core/client/src/locale/it-IT.json # packages/plugins/@nocobase/plugin-action-import/src/client/ImportAction.tsx
This commit is contained in:
commit
07ac4717b3
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.7.0-beta.8",
|
"version": "1.7.0-alpha.4",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"npmClientArgs": ["--ignore-engines"],
|
"npmClientArgs": ["--ignore-engines"],
|
||||||
|
@ -52,7 +52,9 @@
|
|||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
"react-dom": "^18.0.0",
|
"react-dom": "^18.0.0",
|
||||||
"nwsapi": "2.2.7",
|
"nwsapi": "2.2.7",
|
||||||
"antd": "5.12.8",
|
"antd": "5.24.2",
|
||||||
|
"@formily/antd-v5": "1.2.3",
|
||||||
|
"dayjs": "1.11.13",
|
||||||
"@ant-design/icons": "^5.6.1"
|
"@ant-design/icons": "^5.6.1"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/acl",
|
"name": "@nocobase/acl",
|
||||||
"version": "1.7.0-beta.8",
|
"version": "1.7.0-alpha.4",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/resourcer": "1.7.0-beta.8",
|
"@nocobase/resourcer": "1.7.0-alpha.4",
|
||||||
"@nocobase/utils": "1.7.0-beta.8",
|
"@nocobase/utils": "1.7.0-alpha.4",
|
||||||
"minimatch": "^5.1.1"
|
"minimatch": "^5.1.1"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
579
packages/core/acl/src/__tests__/acl-role.test.ts
Normal file
579
packages/core/acl/src/__tests__/acl-role.test.ts
Normal file
@ -0,0 +1,579 @@
|
|||||||
|
/**
|
||||||
|
* 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 { ACL } from '..';
|
||||||
|
describe('multiple roles merge', () => {
|
||||||
|
let acl: ACL;
|
||||||
|
beforeEach(() => {
|
||||||
|
acl = new ACL();
|
||||||
|
});
|
||||||
|
describe('filter merge', () => {
|
||||||
|
test('should allow all(params:{}) when filter1 = undefined, filter2 is not exists', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should allow all(params:{}) when filter1 = undefined, filter2 = {}', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should allow all(params={}) when filter1 = {}, filter2 = {}', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should union filter(params.filter={$or:[{id:1}, {id:2}]}) when filter1 = {id: 1}, filter2 = {id: 2}', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: { id: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: { id: 2 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {
|
||||||
|
filter: {
|
||||||
|
$or: expect.arrayContaining([{ id: 1 }, { id: 2 }]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should union filter(filter={$or:[{id:1}, {name: zhangsan}]}) when filter1 = {id: 1}, filter2 = {name: zhangsan}', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: { id: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: { name: 'zhangsan' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {
|
||||||
|
filter: {
|
||||||
|
$or: expect.arrayContaining([{ id: 1 }, { name: 'zhangsan' }]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should union filter(filter={$or:[{id:1}, {name: zhangsan}]}) when filter1 = {id: 1}, filter2 = { $or: [{name: zhangsan}]', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: { id: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: { name: 'zhangsan' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {
|
||||||
|
filter: {
|
||||||
|
$or: expect.arrayContaining([{ id: 1 }, { name: 'zhangsan' }]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('feilds merge', () => {
|
||||||
|
test('should allow all(params={}) when fields1 = undefined, fields2 is not exists', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should allow all(params={}) when fields1 = undefined, fields2 is not exists', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should allow all(params={}) when fields1 = [], fields2 =[]', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should union fields(params={ fields: [a,b]}) when fields1 = [a], fields2 =[b]', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: ['a'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: ['b'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {
|
||||||
|
fields: expect.arrayContaining(['a', 'b']),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should union no repeat fields(params={ fields: [a,b,c]}) when fields1 = [a,b], fields2 =[b,c]', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: ['a', 'b'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: ['b', 'c'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {
|
||||||
|
fields: expect.arrayContaining(['a', 'b', 'c']),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(canResult.params.fields.length).toStrictEqual(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('whitelist', () => {
|
||||||
|
test('should union whitelist(params={ fields: [a,b,c]}) when fields1 = [a,b], fields2 =[c]', () => {
|
||||||
|
acl.setAvailableAction('update');
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:update': {
|
||||||
|
whitelist: ['a', 'b'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:update': {
|
||||||
|
whitelist: ['c'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ resource: 'posts', action: 'update', roles: ['role1', 'role2'] });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'update',
|
||||||
|
params: {
|
||||||
|
whitelist: expect.arrayContaining(['a', 'b', 'c']),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('appends', () => {
|
||||||
|
test('should union appends(params={ appends: [a,b,c]}) when appends = [a,b], appends =[c]', () => {
|
||||||
|
acl.setAvailableAction('update');
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:update': {
|
||||||
|
appends: ['a', 'b'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:update': {
|
||||||
|
appends: ['c'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ resource: 'posts', action: 'update', roles: ['role1', 'role2'] });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'update',
|
||||||
|
params: {
|
||||||
|
appends: expect.arrayContaining(['a', 'b', 'c']),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should union appends(params={ appends: [a,b]}) when appends = [a,b], appends =[]', () => {
|
||||||
|
acl.setAvailableAction('update');
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:update': {
|
||||||
|
appends: ['a', 'b'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:update': {
|
||||||
|
appends: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ resource: 'posts', action: 'update', roles: ['role1', 'role2'] });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'update',
|
||||||
|
params: {
|
||||||
|
appends: expect.arrayContaining(['a', 'b']),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('filter & fields merge', () => {
|
||||||
|
test('should allow all(params={}) when actions1 = {filter: {}}, actions2 = {fields: []}', () => {
|
||||||
|
acl.setAvailableAction('view', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should allow all(params={}) when actions1 = {filter: {}}, actions2 = {fields: [a]}', () => {
|
||||||
|
acl.setAvailableAction('view', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: ['a'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should allow all(params={}) when actions1 = {filter: {a:1}}, actions2 = {fields: []}', () => {
|
||||||
|
acl.setAvailableAction('view', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: { a: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should allow all(params={}) when actions1 = {filter: {a:1}}, actions2 = {fields: [a]}', () => {
|
||||||
|
acl.setAvailableAction('view', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: { a: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: ['a'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should union filter&fields(params={ filter:{ $or:[{a:1},{a:2}]}, fields:[a,b]}) when actions1={filter:{a:1}, fields:[a]}, actions2={filter: {a:1}},fields:[b]}', () => {
|
||||||
|
acl.setAvailableAction('view', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: { a: 1 },
|
||||||
|
fields: ['a'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: { a: 2 },
|
||||||
|
fields: ['b'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: expect.objectContaining({
|
||||||
|
filter: { $or: expect.arrayContaining([{ a: 1 }, { a: 2 }]) },
|
||||||
|
fields: expect.arrayContaining(['a', 'b']),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -7,11 +7,11 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { default as _, default as lodash } from 'lodash';
|
||||||
|
import minimatch from 'minimatch';
|
||||||
import { ACL, DefineOptions } from './acl';
|
import { ACL, DefineOptions } from './acl';
|
||||||
import { ACLAvailableStrategy, AvailableStrategyOptions } from './acl-available-strategy';
|
import { ACLAvailableStrategy, AvailableStrategyOptions } from './acl-available-strategy';
|
||||||
import { ACLResource } from './acl-resource';
|
import { ACLResource } from './acl-resource';
|
||||||
import lodash from 'lodash';
|
|
||||||
import minimatch from 'minimatch';
|
|
||||||
|
|
||||||
export interface RoleActionParams {
|
export interface RoleActionParams {
|
||||||
fields?: string[];
|
fields?: string[];
|
||||||
@ -185,12 +185,12 @@ export class ACLRole {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return _.cloneDeep({
|
||||||
role: this.name,
|
role: this.name,
|
||||||
strategy: this.strategy,
|
strategy: this.strategy,
|
||||||
actions,
|
actions,
|
||||||
snippets: Array.from(this.snippets),
|
snippets: Array.from(this.snippets),
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getResourceActionFromPath(path: string) {
|
protected getResourceActionFromPath(path: string) {
|
||||||
|
@ -19,6 +19,7 @@ import { AllowManager, ConditionFunc } from './allow-manager';
|
|||||||
import FixedParamsManager, { Merger } from './fixed-params-manager';
|
import FixedParamsManager, { Merger } from './fixed-params-manager';
|
||||||
import SnippetManager, { SnippetOptions } from './snippet-manager';
|
import SnippetManager, { SnippetOptions } from './snippet-manager';
|
||||||
import { NoPermissionError } from './errors/no-permission-error';
|
import { NoPermissionError } from './errors/no-permission-error';
|
||||||
|
import { mergeAclActionParams, removeEmptyParams } from './utils';
|
||||||
|
|
||||||
interface CanResult {
|
interface CanResult {
|
||||||
role: string;
|
role: string;
|
||||||
@ -54,11 +55,12 @@ export interface ListenerContext {
|
|||||||
type Listener = (ctx: ListenerContext) => void;
|
type Listener = (ctx: ListenerContext) => void;
|
||||||
|
|
||||||
interface CanArgs {
|
interface CanArgs {
|
||||||
role: string;
|
role?: string;
|
||||||
resource: string;
|
resource: string;
|
||||||
action: string;
|
action: string;
|
||||||
rawResourceName?: string;
|
rawResourceName?: string;
|
||||||
ctx?: any;
|
ctx?: any;
|
||||||
|
roles?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ACL extends EventEmitter {
|
export class ACL extends EventEmitter {
|
||||||
@ -169,6 +171,10 @@ export class ACL extends EventEmitter {
|
|||||||
return this.roles.get(name);
|
return this.roles.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRoles(names: string[]): ACLRole[] {
|
||||||
|
return names.map((name) => this.getRole(name)).filter((x) => Boolean(x));
|
||||||
|
}
|
||||||
|
|
||||||
removeRole(name: string) {
|
removeRole(name: string) {
|
||||||
return this.roles.delete(name);
|
return this.roles.delete(name);
|
||||||
}
|
}
|
||||||
@ -202,6 +208,36 @@ export class ACL extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
can(options: CanArgs): CanResult | null {
|
can(options: CanArgs): CanResult | null {
|
||||||
|
if (options.role) {
|
||||||
|
return lodash.cloneDeep(this.getCanByRole(options));
|
||||||
|
}
|
||||||
|
if (options.roles?.length) {
|
||||||
|
return lodash.cloneDeep(this.getCanByRoles(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCanByRoles(options: CanArgs) {
|
||||||
|
let canResult: CanResult | null = null;
|
||||||
|
|
||||||
|
for (const role of options.roles) {
|
||||||
|
const result = this.getCanByRole({
|
||||||
|
role,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
if (!canResult) {
|
||||||
|
canResult = result;
|
||||||
|
canResult && removeEmptyParams(canResult.params);
|
||||||
|
} else if (canResult && result) {
|
||||||
|
canResult.params = mergeAclActionParams(canResult.params, result.params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return canResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCanByRole(options: CanArgs) {
|
||||||
const { role, resource, action, rawResourceName } = options;
|
const { role, resource, action, rawResourceName } = options;
|
||||||
const aclRole = this.roles.get(role);
|
const aclRole = this.roles.get(role);
|
||||||
|
|
||||||
@ -351,9 +387,12 @@ export class ACL extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.can = (options: Omit<CanArgs, 'role'>) => {
|
ctx.can = (options: Omit<CanArgs, 'role'>) => {
|
||||||
const canResult = acl.can({ role: roleName, ...options });
|
const roles = ctx.state.currentRoles || [roleName];
|
||||||
|
const can = acl.can({ roles, ...options });
|
||||||
return canResult;
|
if (!can) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return can;
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.permission = {
|
ctx.permission = {
|
||||||
@ -370,7 +409,7 @@ export class ACL extends EventEmitter {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
async getActionParams(ctx) {
|
async getActionParams(ctx) {
|
||||||
const roleName = ctx.state.currentRole || 'anonymous';
|
const roleNames = ctx.state.currentRoles?.length ? ctx.state.currentRoles : 'anonymous';
|
||||||
const { resourceName: rawResourceName, actionName } = ctx.action;
|
const { resourceName: rawResourceName, actionName } = ctx.action;
|
||||||
|
|
||||||
let resourceName = rawResourceName;
|
let resourceName = rawResourceName;
|
||||||
@ -386,11 +425,11 @@ export class ACL extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.can = (options: Omit<CanArgs, 'role'>) => {
|
ctx.can = (options: Omit<CanArgs, 'role'>) => {
|
||||||
const can = this.can({ role: roleName, ...options });
|
const can = this.can({ roles: roleNames, ...options });
|
||||||
if (!can) {
|
if (can) {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return lodash.cloneDeep(can);
|
return lodash.cloneDeep(can);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.permission = {
|
ctx.permission = {
|
||||||
@ -421,6 +460,23 @@ export class ACL extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查 $or 条件中的 createdById
|
||||||
|
if (params?.filter?.$or?.length) {
|
||||||
|
const checkCreatedById = (items) => {
|
||||||
|
return items.some(
|
||||||
|
(x) =>
|
||||||
|
'createdById' in x || x.$or?.some((y) => 'createdById' in y) || x.$and?.some((y) => 'createdById' in y),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (checkCreatedById(params.filter.$or)) {
|
||||||
|
const collection = ctx.db.getCollection(resourceName);
|
||||||
|
if (!collection || !collection.getField('createdById')) {
|
||||||
|
throw new NoPermissionError('createdById field not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,3 +14,4 @@ export * from './acl-resource';
|
|||||||
export * from './acl-role';
|
export * from './acl-role';
|
||||||
export * from './skip-middleware';
|
export * from './skip-middleware';
|
||||||
export * from './errors';
|
export * from './errors';
|
||||||
|
export * from './utils';
|
||||||
|
213
packages/core/acl/src/utils/acl-role.ts
Normal file
213
packages/core/acl/src/utils/acl-role.ts
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
/**
|
||||||
|
* 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 { assign } from '@nocobase/utils';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { ACLRole } from '../acl-role';
|
||||||
|
|
||||||
|
export function mergeRole(roles: ACLRole[]) {
|
||||||
|
const result: Record<string, any> = {
|
||||||
|
roles: [],
|
||||||
|
strategy: {},
|
||||||
|
actions: null,
|
||||||
|
snippets: [],
|
||||||
|
resources: null,
|
||||||
|
};
|
||||||
|
const allSnippets: string[][] = [];
|
||||||
|
for (const role of roles) {
|
||||||
|
const jsonRole = role.toJSON();
|
||||||
|
result.roles = mergeRoleNames(result.roles, jsonRole.role);
|
||||||
|
result.strategy = mergeRoleStrategy(result.strategy, jsonRole.strategy);
|
||||||
|
result.actions = mergeRoleActions(result.actions, jsonRole.actions);
|
||||||
|
result.resources = mergeRoleResources(result.resources, [...role.resources.keys()]);
|
||||||
|
if (_.isArray(jsonRole.snippets)) {
|
||||||
|
allSnippets.push(jsonRole.snippets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.snippets = mergeRoleSnippets(allSnippets);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeRoleNames(sourceRoleNames, newRoleName) {
|
||||||
|
return newRoleName ? sourceRoleNames.concat(newRoleName) : sourceRoleNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeRoleStrategy(sourceStrategy, newStrategy) {
|
||||||
|
if (!newStrategy) {
|
||||||
|
return sourceStrategy;
|
||||||
|
}
|
||||||
|
if (_.isArray(newStrategy.actions)) {
|
||||||
|
if (!sourceStrategy.actions) {
|
||||||
|
sourceStrategy.actions = newStrategy.actions;
|
||||||
|
} else {
|
||||||
|
const actions = sourceStrategy.actions.concat(newStrategy.actions);
|
||||||
|
return {
|
||||||
|
...sourceStrategy,
|
||||||
|
actions: [...new Set(actions)],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sourceStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeRoleActions(sourceActions, newActions) {
|
||||||
|
if (_.isEmpty(sourceActions)) return newActions;
|
||||||
|
if (_.isEmpty(newActions)) return sourceActions;
|
||||||
|
|
||||||
|
const result = {};
|
||||||
|
[...new Set(Reflect.ownKeys(sourceActions).concat(Reflect.ownKeys(newActions)))].forEach((key) => {
|
||||||
|
if (_.has(sourceActions, key) && _.has(newActions, key)) {
|
||||||
|
result[key] = mergeAclActionParams(sourceActions[key], newActions[key]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result[key] = _.has(sourceActions, key) ? sourceActions[key] : newActions[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeRoleSnippets(allRoleSnippets: string[][]): string[] {
|
||||||
|
if (!allRoleSnippets.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const allSnippets = allRoleSnippets.flat();
|
||||||
|
const isExclusion = (value) => value.startsWith('!');
|
||||||
|
const includes = new Set(allSnippets.filter((x) => !isExclusion(x)));
|
||||||
|
const excludes = new Set(allSnippets.filter(isExclusion));
|
||||||
|
|
||||||
|
// 统计 xxx.* 在多少个角色中存在
|
||||||
|
const domainRoleMap = new Map<string, Set<number>>();
|
||||||
|
allRoleSnippets.forEach((roleSnippets, i) => {
|
||||||
|
roleSnippets
|
||||||
|
.filter((x) => x.endsWith('.*') && !isExclusion(x))
|
||||||
|
.forEach((include) => {
|
||||||
|
const domain = include.slice(0, -1);
|
||||||
|
if (!domainRoleMap.has(domain)) {
|
||||||
|
domainRoleMap.set(domain, new Set());
|
||||||
|
}
|
||||||
|
domainRoleMap.get(domain).add(i);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理黑名单交集(只有所有角色都有 `!xxx` 才保留)
|
||||||
|
const excludesSet = new Set<string>();
|
||||||
|
for (const snippet of excludes) {
|
||||||
|
if (allRoleSnippets.every((x) => x.includes(snippet))) {
|
||||||
|
excludesSet.add(snippet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [domain, indexes] of domainRoleMap.entries()) {
|
||||||
|
const fullDomain = `${domain}.*`;
|
||||||
|
|
||||||
|
// xxx.* 存在时,覆盖 !xxx.*
|
||||||
|
if (includes.has(fullDomain)) {
|
||||||
|
excludesSet.delete(`!${fullDomain}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算 !xxx.yyy,当所有 xxx.* 角色都包含 !xxx.yyy 时才保留
|
||||||
|
for (const roleIndex of indexes) {
|
||||||
|
for (const exclude of allRoleSnippets[roleIndex]) {
|
||||||
|
if (exclude.startsWith(`!${domain}`) && exclude !== `!${fullDomain}`) {
|
||||||
|
if ([...indexes].every((i) => allRoleSnippets[i].includes(exclude))) {
|
||||||
|
excludesSet.add(exclude);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保 !xxx.yyy 只有在 xxx.* 存在时才有效,同时解决 [xxx] 和 [!xxx] 冲突
|
||||||
|
if (includes.size > 0) {
|
||||||
|
for (const x of [...excludesSet]) {
|
||||||
|
const exactMatch = x.slice(1);
|
||||||
|
const segments = exactMatch.split('.');
|
||||||
|
if (segments.length > 1 && segments[1] !== '*') {
|
||||||
|
const parentDomain = segments[0] + '.*';
|
||||||
|
if (!includes.has(parentDomain)) {
|
||||||
|
excludesSet.delete(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...includes, ...excludesSet];
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeRoleResources(sourceResources, newResources) {
|
||||||
|
if (sourceResources === null) {
|
||||||
|
return newResources;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...new Set(sourceResources.concat(newResources))];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mergeAclActionParams(sourceParams, targetParams) {
|
||||||
|
if (_.isEmpty(sourceParams) || _.isEmpty(targetParams)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// source 和 target 其中之一没有 fields 字段时, 最终希望没有此字段
|
||||||
|
removeUnmatchedParams(sourceParams, targetParams, ['fields', 'whitelist', 'appends']);
|
||||||
|
|
||||||
|
const andMerge = (x, y) => {
|
||||||
|
if (_.isEmpty(x) || _.isEmpty(y)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return _.uniq(x.concat(y)).filter(Boolean);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mergedParams = assign(targetParams, sourceParams, {
|
||||||
|
own: (x, y) => x || y,
|
||||||
|
filter: (x, y) => {
|
||||||
|
if (_.isEmpty(x) || _.isEmpty(y)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const xHasOr = _.has(x, '$or'),
|
||||||
|
yHasOr = _.has(y, '$or');
|
||||||
|
let $or = [x, y];
|
||||||
|
if (xHasOr && !yHasOr) {
|
||||||
|
$or = [...x.$or, y];
|
||||||
|
} else if (!xHasOr && yHasOr) {
|
||||||
|
$or = [x, ...y.$or];
|
||||||
|
} else if (xHasOr && yHasOr) {
|
||||||
|
$or = [...x.$or, ...y.$or];
|
||||||
|
}
|
||||||
|
|
||||||
|
return { $or: _.uniqWith($or, _.isEqual) };
|
||||||
|
},
|
||||||
|
fields: andMerge,
|
||||||
|
whitelist: andMerge,
|
||||||
|
appends: 'union',
|
||||||
|
});
|
||||||
|
removeEmptyParams(mergedParams);
|
||||||
|
return mergedParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeEmptyParams(params) {
|
||||||
|
if (!_.isObject(params)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object.keys(params).forEach((key) => {
|
||||||
|
if (_.isEmpty(params[key])) {
|
||||||
|
delete params[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeUnmatchedParams(source, target, keys: string[]) {
|
||||||
|
for (const key of keys) {
|
||||||
|
if (_.has(source, key) && !_.has(target, key)) {
|
||||||
|
delete source[key];
|
||||||
|
}
|
||||||
|
if (!_.has(source, key) && _.has(target, key)) {
|
||||||
|
delete target[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
packages/core/acl/src/utils/index.ts
Normal file
10
packages/core/acl/src/utils/index.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './acl-role';
|
@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/actions",
|
"name": "@nocobase/actions",
|
||||||
"version": "1.7.0-beta.8",
|
"version": "1.7.0-alpha.4",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/cache": "1.7.0-beta.8",
|
"@nocobase/cache": "1.7.0-alpha.4",
|
||||||
"@nocobase/database": "1.7.0-beta.8",
|
"@nocobase/database": "1.7.0-alpha.4",
|
||||||
"@nocobase/resourcer": "1.7.0-beta.8"
|
"@nocobase/resourcer": "1.7.0-alpha.4"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/app",
|
"name": "@nocobase/app",
|
||||||
"version": "1.7.0-beta.8",
|
"version": "1.7.0-alpha.4",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/database": "1.7.0-beta.8",
|
"@nocobase/database": "1.7.0-alpha.4",
|
||||||
"@nocobase/preset-nocobase": "1.7.0-beta.8",
|
"@nocobase/preset-nocobase": "1.7.0-alpha.4",
|
||||||
"@nocobase/server": "1.7.0-beta.8"
|
"@nocobase/server": "1.7.0-alpha.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nocobase/client": "1.7.0-beta.8"
|
"@nocobase/client": "1.7.0-alpha.4"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/auth",
|
"name": "@nocobase/auth",
|
||||||
"version": "1.7.0-beta.8",
|
"version": "1.7.0-alpha.4",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/actions": "1.7.0-beta.8",
|
"@nocobase/actions": "1.7.0-alpha.4",
|
||||||
"@nocobase/cache": "1.7.0-beta.8",
|
"@nocobase/cache": "1.7.0-alpha.4",
|
||||||
"@nocobase/database": "1.7.0-beta.8",
|
"@nocobase/database": "1.7.0-alpha.4",
|
||||||
"@nocobase/resourcer": "1.7.0-beta.8",
|
"@nocobase/resourcer": "1.7.0-alpha.4",
|
||||||
"@nocobase/utils": "1.7.0-beta.8",
|
"@nocobase/utils": "1.7.0-alpha.4",
|
||||||
"@types/jsonwebtoken": "^8.5.8",
|
"@types/jsonwebtoken": "^8.5.8",
|
||||||
"jsonwebtoken": "^8.5.1"
|
"jsonwebtoken": "^8.5.1"
|
||||||
},
|
},
|
||||||
|
@ -22,7 +22,7 @@ describe('middleware', () => {
|
|||||||
app = await createMockServer({
|
app = await createMockServer({
|
||||||
registerActions: true,
|
registerActions: true,
|
||||||
acl: true,
|
acl: true,
|
||||||
plugins: ['users', 'auth', 'acl', 'field-sort', 'data-source-manager', 'error-handler'],
|
plugins: ['users', 'auth', 'acl', 'field-sort', 'data-source-manager', 'error-handler', 'system-settings'],
|
||||||
});
|
});
|
||||||
|
|
||||||
// app.plugin(ApiKeysPlugin);
|
// app.plugin(ApiKeysPlugin);
|
||||||
|
@ -267,6 +267,24 @@ export class BaseAuth extends Auth {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async signNewToken(userId: number) {
|
||||||
|
const tokenInfo = await this.tokenController.add({ userId });
|
||||||
|
const expiresIn = Math.floor((await this.tokenController.getConfig()).tokenExpirationTime / 1000);
|
||||||
|
const token = this.jwt.sign(
|
||||||
|
{
|
||||||
|
userId,
|
||||||
|
temp: true,
|
||||||
|
iat: Math.floor(tokenInfo.issuedTime / 1000),
|
||||||
|
signInTime: tokenInfo.signInTime,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jwtid: tokenInfo.jti,
|
||||||
|
expiresIn,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
async signIn() {
|
async signIn() {
|
||||||
let user: Model;
|
let user: Model;
|
||||||
try {
|
try {
|
||||||
@ -282,20 +300,7 @@ export class BaseAuth extends Auth {
|
|||||||
code: AuthErrorCode.NOT_EXIST_USER,
|
code: AuthErrorCode.NOT_EXIST_USER,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const tokenInfo = await this.tokenController.add({ userId: user.id });
|
const token = await this.signNewToken(user.id);
|
||||||
const expiresIn = Math.floor((await this.tokenController.getConfig()).tokenExpirationTime / 1000);
|
|
||||||
const token = this.jwt.sign(
|
|
||||||
{
|
|
||||||
userId: user.id,
|
|
||||||
temp: true,
|
|
||||||
iat: Math.floor(tokenInfo.issuedTime / 1000),
|
|
||||||
signInTime: tokenInfo.signInTime,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
jwtid: tokenInfo.jti,
|
|
||||||
expiresIn,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
token,
|
token,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/build",
|
"name": "@nocobase/build",
|
||||||
"version": "1.7.0-beta.8",
|
"version": "1.7.0-alpha.4",
|
||||||
"description": "Library build tool based on rollup.",
|
"description": "Library build tool based on rollup.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"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",
|
"name": "@nocobase/cache",
|
||||||
"version": "1.7.0-beta.8",
|
"version": "1.7.0-alpha.4",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/lock-manager": "1.6.0-alpha.6",
|
"@nocobase/lock-manager": "1.7.0-alpha.4",
|
||||||
"bloom-filters": "^3.0.1",
|
"bloom-filters": "^3.0.1",
|
||||||
"cache-manager": "^5.2.4",
|
"cache-manager": "^5.2.4",
|
||||||
"cache-manager-redis-yet": "^4.1.2"
|
"cache-manager-redis-yet": "^4.1.2"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/cli",
|
"name": "@nocobase/cli",
|
||||||
"version": "1.7.0-beta.8",
|
"version": "1.7.0-alpha.4",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./src/index.js",
|
"main": "./src/index.js",
|
||||||
@ -8,7 +8,7 @@
|
|||||||
"nocobase": "./bin/index.js"
|
"nocobase": "./bin/index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/app": "1.7.0-beta.8",
|
"@nocobase/app": "1.7.0-alpha.4",
|
||||||
"@types/fs-extra": "^11.0.1",
|
"@types/fs-extra": "^11.0.1",
|
||||||
"@umijs/utils": "3.5.20",
|
"@umijs/utils": "3.5.20",
|
||||||
"chalk": "^4.1.1",
|
"chalk": "^4.1.1",
|
||||||
@ -25,7 +25,7 @@
|
|||||||
"tsx": "^4.19.0"
|
"tsx": "^4.19.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nocobase/devtools": "1.7.0-beta.8"
|
"@nocobase/devtools": "1.7.0-alpha.4"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/client",
|
"name": "@nocobase/client",
|
||||||
"version": "1.7.0-beta.8",
|
"version": "1.7.0-alpha.4",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"module": "es/index.mjs",
|
"module": "es/index.mjs",
|
||||||
@ -17,7 +17,7 @@
|
|||||||
"@dnd-kit/modifiers": "^6.0.0",
|
"@dnd-kit/modifiers": "^6.0.0",
|
||||||
"@dnd-kit/sortable": "^6.0.0",
|
"@dnd-kit/sortable": "^6.0.0",
|
||||||
"@emotion/css": "^11.7.1",
|
"@emotion/css": "^11.7.1",
|
||||||
"@formily/antd-v5": "1.1.9",
|
"@formily/antd-v5": "1.2.3",
|
||||||
"@formily/core": "^2.2.27",
|
"@formily/core": "^2.2.27",
|
||||||
"@formily/grid": "^2.2.27",
|
"@formily/grid": "^2.2.27",
|
||||||
"@formily/json-schema": "^2.2.27",
|
"@formily/json-schema": "^2.2.27",
|
||||||
@ -27,11 +27,11 @@
|
|||||||
"@formily/reactive-react": "^2.2.27",
|
"@formily/reactive-react": "^2.2.27",
|
||||||
"@formily/shared": "^2.2.27",
|
"@formily/shared": "^2.2.27",
|
||||||
"@formily/validator": "^2.2.27",
|
"@formily/validator": "^2.2.27",
|
||||||
"@nocobase/evaluators": "1.7.0-beta.8",
|
"@nocobase/evaluators": "1.7.0-alpha.4",
|
||||||
"@nocobase/sdk": "1.7.0-beta.8",
|
"@nocobase/sdk": "1.7.0-alpha.4",
|
||||||
"@nocobase/utils": "1.7.0-beta.8",
|
"@nocobase/utils": "1.7.0-alpha.4",
|
||||||
"ahooks": "^3.7.2",
|
"ahooks": "^3.7.2",
|
||||||
"antd": "5.12.8",
|
"antd": "5.24.2",
|
||||||
"antd-style": "3.7.1",
|
"antd-style": "3.7.1",
|
||||||
"axios": "^1.7.0",
|
"axios": "^1.7.0",
|
||||||
"bignumber.js": "^9.1.2",
|
"bignumber.js": "^9.1.2",
|
||||||
|
@ -103,6 +103,11 @@ export const useRoleRecheck = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useCurrentRoleMode = () => {
|
||||||
|
const ctx = useContext(ACLContext);
|
||||||
|
return ctx?.data?.data?.roleMode;
|
||||||
|
};
|
||||||
|
|
||||||
export const useACLContext = () => {
|
export const useACLContext = () => {
|
||||||
return useContext(ACLContext);
|
return useContext(ACLContext);
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Checkbox, message, Table } from 'antd';
|
import { Checkbox, message, Table, TableProps } from 'antd';
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
import React, { createContext, useContext, useMemo, useState } from 'react';
|
import React, { createContext, useContext, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -102,7 +102,8 @@ export const SettingsCenterConfigure = () => {
|
|||||||
expandable={{
|
expandable={{
|
||||||
defaultExpandAllRows: true,
|
defaultExpandAllRows: true,
|
||||||
}}
|
}}
|
||||||
columns={[
|
columns={
|
||||||
|
[
|
||||||
{
|
{
|
||||||
dataIndex: 'title',
|
dataIndex: 'title',
|
||||||
title: t('Plugin name'),
|
title: t('Plugin name'),
|
||||||
@ -139,7 +140,8 @@ export const SettingsCenterConfigure = () => {
|
|||||||
return <Checkbox checked={checked} onChange={() => handleChange(checked, record)} />;
|
return <Checkbox checked={checked} onChange={() => handleChange(checked, record)} />;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]}
|
] as TableProps['columns']
|
||||||
|
}
|
||||||
dataSource={settings
|
dataSource={settings
|
||||||
.filter((v) => {
|
.filter((v) => {
|
||||||
return v.isTopLevel !== false;
|
return v.isTopLevel !== false;
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Checkbox, message, Table } from 'antd';
|
import { Checkbox, message, Table, TableProps } from 'antd';
|
||||||
import { uniq } from 'lodash';
|
import { uniq } from 'lodash';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -121,7 +121,8 @@ export const MenuConfigure = () => {
|
|||||||
expandable={{
|
expandable={{
|
||||||
defaultExpandAllRows: true,
|
defaultExpandAllRows: true,
|
||||||
}}
|
}}
|
||||||
columns={[
|
columns={
|
||||||
|
[
|
||||||
{
|
{
|
||||||
dataIndex: 'title',
|
dataIndex: 'title',
|
||||||
title: t('Menu item title'),
|
title: t('Menu item title'),
|
||||||
@ -149,12 +150,13 @@ export const MenuConfigure = () => {
|
|||||||
{t('Accessible')}
|
{t('Accessible')}
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
render: (_, schema) => {
|
render: (_, schema: { uid: string }) => {
|
||||||
const checked = uids.includes(schema.uid);
|
const checked = uids.includes(schema.uid);
|
||||||
return <Checkbox checked={checked} onChange={() => handleChange(checked, schema)} />;
|
return <Checkbox checked={checked} onChange={() => handleChange(checked, schema)} />;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]}
|
] as TableProps['columns']
|
||||||
|
}
|
||||||
dataSource={translateTitle(items)}
|
dataSource={translateTitle(items)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
import { FormItem, FormLayout } from '@formily/antd-v5';
|
import { FormItem, FormLayout } from '@formily/antd-v5';
|
||||||
import { ArrayField } from '@formily/core';
|
import { ArrayField } from '@formily/core';
|
||||||
import { connect, useField, useForm } from '@formily/react';
|
import { connect, useField, useForm } from '@formily/react';
|
||||||
import { Checkbox, Table, Tag } from 'antd';
|
import { Checkbox, Table, Tag, TableProps } from 'antd';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import React, { createContext } from 'react';
|
import React, { createContext } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -105,7 +105,8 @@ export const RolesResourcesActions = connect((props) => {
|
|||||||
className={antTableCell}
|
className={antTableCell}
|
||||||
size={'small'}
|
size={'small'}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
columns={[
|
columns={
|
||||||
|
[
|
||||||
{
|
{
|
||||||
dataIndex: 'displayName',
|
dataIndex: 'displayName',
|
||||||
title: t('Action display name'),
|
title: t('Action display name'),
|
||||||
@ -146,7 +147,8 @@ export const RolesResourcesActions = connect((props) => {
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
]}
|
] as TableProps['columns']
|
||||||
|
}
|
||||||
dataSource={availableActions?.map((item) => {
|
dataSource={availableActions?.map((item) => {
|
||||||
let enabled = false;
|
let enabled = false;
|
||||||
let scope = null;
|
let scope = null;
|
||||||
@ -169,7 +171,8 @@ export const RolesResourcesActions = connect((props) => {
|
|||||||
className={antTableCell}
|
className={antTableCell}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
dataSource={fieldPermissions}
|
dataSource={fieldPermissions}
|
||||||
columns={[
|
columns={
|
||||||
|
[
|
||||||
{
|
{
|
||||||
dataIndex: ['uiSchema', 'title'],
|
dataIndex: ['uiSchema', 'title'],
|
||||||
title: t('Field display name'),
|
title: t('Field display name'),
|
||||||
@ -222,7 +225,8 @@ export const RolesResourcesActions = connect((props) => {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
]}
|
] as TableProps['columns']
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</FormLayout>
|
</FormLayout>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import { ArrayField } from '@formily/core';
|
import { ArrayField } from '@formily/core';
|
||||||
import { connect, useField } from '@formily/react';
|
import { connect, useField } from '@formily/react';
|
||||||
import { Checkbox, Select, Table, Tag } from 'antd';
|
import { Checkbox, Select, Table, Tag, TableProps } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useCompile } from '../..';
|
import { useCompile } from '../..';
|
||||||
@ -55,7 +55,8 @@ export const StrategyActions = connect((props) => {
|
|||||||
size={'small'}
|
size={'small'}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
rowKey={'name'}
|
rowKey={'name'}
|
||||||
columns={[
|
columns={
|
||||||
|
[
|
||||||
{
|
{
|
||||||
dataIndex: 'displayName',
|
dataIndex: 'displayName',
|
||||||
title: t('Action display name'),
|
title: t('Action display name'),
|
||||||
@ -110,7 +111,8 @@ export const StrategyActions = connect((props) => {
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
]}
|
] as TableProps['columns']
|
||||||
|
}
|
||||||
dataSource={availableActions?.map((item) => {
|
dataSource={availableActions?.map((item) => {
|
||||||
let scope = 'all';
|
let scope = 'all';
|
||||||
let enabled = false;
|
let enabled = false;
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
import { get, set } from 'lodash';
|
import { get, set } from 'lodash';
|
||||||
import React, { ComponentType, createContext, useContext } from 'react';
|
import React, { ComponentType, createContext, useContext } from 'react';
|
||||||
|
import { matchRoutes } from 'react-router';
|
||||||
import {
|
import {
|
||||||
BrowserRouterProps,
|
BrowserRouterProps,
|
||||||
createBrowserRouter,
|
createBrowserRouter,
|
||||||
@ -42,6 +43,7 @@ export type RouterOptions = (HashRouterOptions | BrowserRouterOptions | MemoryRo
|
|||||||
export type ComponentTypeAndString<T = any> = ComponentType<T> | string;
|
export type ComponentTypeAndString<T = any> = ComponentType<T> | string;
|
||||||
export interface RouteType extends Omit<RouteObject, 'children' | 'Component'> {
|
export interface RouteType extends Omit<RouteObject, 'children' | 'Component'> {
|
||||||
Component?: ComponentTypeAndString;
|
Component?: ComponentTypeAndString;
|
||||||
|
skipAuthCheck?: boolean;
|
||||||
}
|
}
|
||||||
export type RenderComponentType = (Component: ComponentTypeAndString, props?: any) => React.ReactNode;
|
export type RenderComponentType = (Component: ComponentTypeAndString, props?: any) => React.ReactNode;
|
||||||
|
|
||||||
@ -134,6 +136,18 @@ export class RouterManager {
|
|||||||
this.options.basename = basename;
|
this.options.basename = basename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
matchRoutes(pathname: string) {
|
||||||
|
const routes = Object.values(this.routes);
|
||||||
|
// @ts-ignore
|
||||||
|
return matchRoutes<RouteType>(routes, pathname, this.basename);
|
||||||
|
}
|
||||||
|
|
||||||
|
isSkippedAuthCheckRoute(pathname: string) {
|
||||||
|
const matchedRoutes = this.matchRoutes(pathname);
|
||||||
|
return matchedRoutes.some((match) => {
|
||||||
|
return match?.route?.skipAuthCheck === true;
|
||||||
|
});
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
@ -30,7 +30,7 @@ describe('Router', () => {
|
|||||||
let router: RouterManager;
|
let router: RouterManager;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
router = new RouterManager({ type: 'memory', initialEntries: ['/'] }, app);
|
router = new RouterManager({ type: 'memory', initialEntries: ['/'], basename: '/nocobase/apps/test1' }, app);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('basic', () => {
|
it('basic', () => {
|
||||||
@ -132,6 +132,38 @@ describe('Router', () => {
|
|||||||
router.add('test', route);
|
router.add('test', route);
|
||||||
expect(router.getRoutesTree()).toEqual([{ path: '/', element: <Hello />, children: undefined }]);
|
expect(router.getRoutesTree()).toEqual([{ path: '/', element: <Hello />, children: undefined }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('add skipAuthCheck route', () => {
|
||||||
|
router.add('skip-auth-check', { path: '/skip-auth-check', Component: 'Hello', skipAuthCheck: true });
|
||||||
|
router.add('not-skip-auth-check', { path: '/not-skip-auth-check', Component: 'Hello' });
|
||||||
|
|
||||||
|
const RouterComponent = router.getRouterComponent();
|
||||||
|
const BaseLayout: FC = (props) => {
|
||||||
|
return <div>BaseLayout {props.children}</div>;
|
||||||
|
};
|
||||||
|
render(<RouterComponent BaseLayout={BaseLayout} />);
|
||||||
|
router.navigate('/skip-auth-check');
|
||||||
|
const state = router.state;
|
||||||
|
const { pathname, search } = state.location;
|
||||||
|
const isSkipedAuthCheck = router.isSkippedAuthCheckRoute(pathname);
|
||||||
|
expect(isSkipedAuthCheck).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('add not skipAuthCheck route', () => {
|
||||||
|
router.add('skip-auth-check', { path: '/skip-auth-check', Component: 'Hello', skipAuthCheck: true });
|
||||||
|
router.add('not-skip-auth-check', { path: '/not-skip-auth-check', Component: 'Hello' });
|
||||||
|
|
||||||
|
const RouterComponent = router.getRouterComponent();
|
||||||
|
const BaseLayout: FC = (props) => {
|
||||||
|
return <div>BaseLayout {props.children}</div>;
|
||||||
|
};
|
||||||
|
render(<RouterComponent BaseLayout={BaseLayout} />);
|
||||||
|
router.navigate('/not-skip-auth-check');
|
||||||
|
const state = router.state;
|
||||||
|
const { pathname, search } = state.location;
|
||||||
|
const isSkipedAuthCheck = router.isSkippedAuthCheckRoute(pathname);
|
||||||
|
expect(isSkipedAuthCheck).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('remove', () => {
|
describe('remove', () => {
|
||||||
|
@ -12,3 +12,4 @@ export * from './useAppSpin';
|
|||||||
export * from './usePlugin';
|
export * from './usePlugin';
|
||||||
export * from './useRouter';
|
export * from './useRouter';
|
||||||
export * from './useGlobalVariable';
|
export * from './useGlobalVariable';
|
||||||
|
export * from './useAclSnippets';
|
||||||
|
@ -12,7 +12,6 @@ import { useField, useFieldSchema } from '@formily/react';
|
|||||||
import { useUpdate } from 'ahooks';
|
import { useUpdate } from 'ahooks';
|
||||||
import { Spin } from 'antd';
|
import { Spin } from 'antd';
|
||||||
import React, { createContext, useContext, useEffect, useMemo, useRef } from 'react';
|
import React, { createContext, useContext, useEffect, useMemo, useRef } from 'react';
|
||||||
import { useCollectionManager_deprecated } from '../collection-manager';
|
|
||||||
import { useCollection, useCollectionRecordData } from '../data-source';
|
import { useCollection, useCollectionRecordData } from '../data-source';
|
||||||
import { useCollectionParentRecord } from '../data-source/collection-record/CollectionRecordProvider';
|
import { useCollectionParentRecord } from '../data-source/collection-record/CollectionRecordProvider';
|
||||||
import { withDynamicSchemaProps } from '../hoc/withDynamicSchemaProps';
|
import { withDynamicSchemaProps } from '../hoc/withDynamicSchemaProps';
|
||||||
@ -103,8 +102,7 @@ const useCompatDetailsBlockParams = (props) => {
|
|||||||
export const DetailsBlockProvider = withDynamicSchemaProps((props) => {
|
export const DetailsBlockProvider = withDynamicSchemaProps((props) => {
|
||||||
const { params, parseVariableLoading } = useCompatDetailsBlockParams(props);
|
const { params, parseVariableLoading } = useCompatDetailsBlockParams(props);
|
||||||
const record = useCollectionRecordData();
|
const record = useCollectionRecordData();
|
||||||
const { association, dataSource, action } = props;
|
const { association, action } = props;
|
||||||
const { getCollection } = useCollectionManager_deprecated(dataSource);
|
|
||||||
const { __collection } = record || {};
|
const { __collection } = record || {};
|
||||||
const { designable } = useDesignable();
|
const { designable } = useDesignable();
|
||||||
const collectionName = props.collection;
|
const collectionName = props.collection;
|
||||||
|
@ -1157,6 +1157,7 @@ export const useDetailsPaginationProps = () => {
|
|||||||
current: ctx.service?.data?.meta?.page || 1,
|
current: ctx.service?.data?.meta?.page || 1,
|
||||||
pageSize: 1,
|
pageSize: 1,
|
||||||
showSizeChanger: false,
|
showSizeChanger: false,
|
||||||
|
align: 'center',
|
||||||
async onChange(page) {
|
async onChange(page) {
|
||||||
const params = ctx.service?.params?.[0];
|
const params = ctx.service?.params?.[0];
|
||||||
ctx.service.run({ ...params, page });
|
ctx.service.run({ ...params, page });
|
||||||
@ -1182,6 +1183,7 @@ export const useDetailsPaginationProps = () => {
|
|||||||
total: count,
|
total: count,
|
||||||
pageSize: 1,
|
pageSize: 1,
|
||||||
showSizeChanger: false,
|
showSizeChanger: false,
|
||||||
|
align: 'center',
|
||||||
async onChange(page) {
|
async onChange(page) {
|
||||||
const params = ctx.service?.params?.[0];
|
const params = ctx.service?.params?.[0];
|
||||||
ctx.service.run({ ...params, page });
|
ctx.service.run({ ...params, page });
|
||||||
|
@ -25,6 +25,7 @@ import { useCollectionManager_deprecated } from '../hooks';
|
|||||||
import useDialect from '../hooks/useDialect';
|
import useDialect from '../hooks/useDialect';
|
||||||
import * as components from './components';
|
import * as components from './components';
|
||||||
import { useFieldInterfaceOptions } from './interfaces';
|
import { useFieldInterfaceOptions } from './interfaces';
|
||||||
|
import { ItemType, MenuItemType } from 'antd/es/menu/interface';
|
||||||
|
|
||||||
const getSchema = (schema: CollectionFieldInterface, record: any, compile) => {
|
const getSchema = (schema: CollectionFieldInterface, record: any, compile) => {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
@ -231,7 +232,7 @@ export const AddFieldAction = (props) => {
|
|||||||
}, [getTemplate, record]);
|
}, [getTemplate, record]);
|
||||||
const items = useMemo<MenuProps['items']>(() => {
|
const items = useMemo<MenuProps['items']>(() => {
|
||||||
return getFieldOptions()
|
return getFieldOptions()
|
||||||
.map((option) => {
|
.map((option): ItemType & { title: string; children?: ItemType[] } => {
|
||||||
if (option?.children?.length === 0) {
|
if (option?.children?.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ export const PresetFields = observer(
|
|||||||
rowSelection={{
|
rowSelection={{
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
selectedRowKeys,
|
selectedRowKeys,
|
||||||
getCheckboxProps: (record) => ({
|
getCheckboxProps: (record: { name: string }) => ({
|
||||||
name: record.name,
|
name: record.name,
|
||||||
disabled: props?.disabled || props?.presetFieldsDisabledIncludes?.includes?.(record.name),
|
disabled: props?.disabled || props?.presetFieldsDisabledIncludes?.includes?.(record.name),
|
||||||
}),
|
}),
|
||||||
|
@ -47,6 +47,7 @@ export const CSSVariableProvider = ({ children }) => {
|
|||||||
document.body.style.setProperty('--colorBgScrollBarActive', colorBgScrollBarActive);
|
document.body.style.setProperty('--colorBgScrollBarActive', colorBgScrollBarActive);
|
||||||
document.body.style.setProperty('--colorSettings', token.colorSettings || defaultTheme.token.colorSettings);
|
document.body.style.setProperty('--colorSettings', token.colorSettings || defaultTheme.token.colorSettings);
|
||||||
document.body.style.setProperty('--colorBgSettingsHover', token.colorBgSettingsHover);
|
document.body.style.setProperty('--colorBgSettingsHover', token.colorBgSettingsHover);
|
||||||
|
document.body.style.setProperty('--colorTemplateBgSettingsHover', token.colorTemplateBgSettingsHover);
|
||||||
document.body.style.setProperty('--colorBorderSettingsHover', token.colorBorderSettingsHover);
|
document.body.style.setProperty('--colorBorderSettingsHover', token.colorBorderSettingsHover);
|
||||||
document.body.style.setProperty('--colorBgMenuItemSelected', token.colorBgHeaderMenuActive);
|
document.body.style.setProperty('--colorBgMenuItemSelected', token.colorBgHeaderMenuActive);
|
||||||
|
|
||||||
@ -60,6 +61,7 @@ export const CSSVariableProvider = ({ children }) => {
|
|||||||
token.colorBgContainer,
|
token.colorBgContainer,
|
||||||
token.colorBgLayout,
|
token.colorBgLayout,
|
||||||
token.colorBgSettingsHover,
|
token.colorBgSettingsHover,
|
||||||
|
token.colorTemplateBgSettingsHover,
|
||||||
token.colorBorderSettingsHover,
|
token.colorBorderSettingsHover,
|
||||||
token.colorInfoBg,
|
token.colorInfoBg,
|
||||||
token.colorInfoBorder,
|
token.colorInfoBorder,
|
||||||
|
@ -24,11 +24,13 @@ const defaultTheme: ThemeConfig = {
|
|||||||
// UI 配置组件
|
// UI 配置组件
|
||||||
colorSettings: '#F18B62',
|
colorSettings: '#F18B62',
|
||||||
colorBgSettingsHover: 'rgba(241, 139, 98, 0.06)',
|
colorBgSettingsHover: 'rgba(241, 139, 98, 0.06)',
|
||||||
|
colorTemplateBgSettingsHover: 'rgba(98, 200, 241, 0.06)', // 默认为colorBgSettingsHover的互补色
|
||||||
colorBorderSettingsHover: 'rgba(241, 139, 98, 0.3)',
|
colorBorderSettingsHover: 'rgba(241, 139, 98, 0.3)',
|
||||||
|
|
||||||
// 动画相关
|
// 动画相关
|
||||||
motionUnit: 0.03,
|
motionUnit: 0.03,
|
||||||
motion: !process.env.__E2E__,
|
// ant design 升级到5.24.2后,Modal.confirm在E2E中如果关闭动画,会出现ant-modal-mask不销毁的问题
|
||||||
|
// motion: !process.env.__E2E__,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -30,6 +30,8 @@ export interface CustomToken extends AliasToken {
|
|||||||
colorSettings: string;
|
colorSettings: string;
|
||||||
/** 鼠标悬浮时显示的背景色 */
|
/** 鼠标悬浮时显示的背景色 */
|
||||||
colorBgSettingsHover: string;
|
colorBgSettingsHover: string;
|
||||||
|
/** 鼠标悬浮模板区块时显示的背景色 */
|
||||||
|
colorTemplateBgSettingsHover: string;
|
||||||
/** 鼠标悬浮时显示的边框色 */
|
/** 鼠标悬浮时显示的边框色 */
|
||||||
colorBorderSettingsHover: string;
|
colorBorderSettingsHover: string;
|
||||||
|
|
||||||
|
@ -164,6 +164,7 @@
|
|||||||
"Chart type": "Chart type",
|
"Chart type": "Chart type",
|
||||||
"Chart config": "Chart config",
|
"Chart config": "Chart config",
|
||||||
"Templates": "Templates",
|
"Templates": "Templates",
|
||||||
|
"Template": "Template",
|
||||||
"Select template": "Select template",
|
"Select template": "Select template",
|
||||||
"Action logs": "Action logs",
|
"Action logs": "Action logs",
|
||||||
"Create template": "Create template",
|
"Create template": "Create template",
|
||||||
@ -884,5 +885,8 @@
|
|||||||
"If selected, the page will display Tab pages.": "If selected, the page will display Tab pages.",
|
"If selected, the page will display Tab pages.": "If selected, the page will display Tab pages.",
|
||||||
"If selected, the route will be displayed in the menu.": "If selected, the route will be displayed in the menu.",
|
"If selected, the route will be displayed in the menu.": "If selected, the route will be displayed in the menu.",
|
||||||
"Are you sure you want to hide this tab?": "Are you sure you want to hide this tab?",
|
"Are you sure you want to hide this tab?": "Are you sure you want to hide this tab?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.",
|
||||||
|
"Deprecated": "Deprecated",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "The following old template features have been deprecated and will be removed in next version.",
|
||||||
|
"Full permissions": "Full permissions"
|
||||||
}
|
}
|
||||||
|
@ -148,6 +148,7 @@
|
|||||||
"Chart type": "Tipo del gráfico",
|
"Chart type": "Tipo del gráfico",
|
||||||
"Chart config": "Configuración del gráfico",
|
"Chart config": "Configuración del gráfico",
|
||||||
"Templates": "Plantillas",
|
"Templates": "Plantillas",
|
||||||
|
"Template": "Plantilla",
|
||||||
"Select template": "Seleccione plantilla",
|
"Select template": "Seleccione plantilla",
|
||||||
"Action logs": "Acción logs",
|
"Action logs": "Acción logs",
|
||||||
"Create template": "Crear plantilla",
|
"Create template": "Crear plantilla",
|
||||||
@ -801,5 +802,8 @@
|
|||||||
"If selected, the page will display Tab pages.": "Si se selecciona, la página mostrará páginas de pestañas.",
|
"If selected, the page will display Tab pages.": "Si se selecciona, la página mostrará páginas de pestañas.",
|
||||||
"If selected, the route will be displayed in the menu.": "Si se selecciona, la ruta se mostrará en el menú.",
|
"If selected, the route will be displayed in the menu.": "Si se selecciona, la ruta se mostrará en el menú.",
|
||||||
"Are you sure you want to hide this tab?": "¿Estás seguro de que quieres ocultar esta pestaña?",
|
"Are you sure you want to hide this tab?": "¿Estás seguro de que quieres ocultar esta pestaña?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Después de ocultar, esta pestaña ya no aparecerá en la barra de pestañas. Para mostrarla de nuevo, deberás ir a la página de gestión de rutas para configurarla."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Después de ocultar, esta pestaña ya no aparecerá en la barra de pestañas. Para mostrarla de nuevo, deberás ir a la página de gestión de rutas para configurarla.",
|
||||||
|
"Deprecated": "Obsoleto",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "Las siguientes características de plantilla antigua han quedado obsoletas y se eliminarán en la próxima versión.",
|
||||||
|
"Full permissions": "Todos los derechos"
|
||||||
}
|
}
|
||||||
|
@ -159,6 +159,7 @@
|
|||||||
"Chart type": "Type de graphique",
|
"Chart type": "Type de graphique",
|
||||||
"Chart config": "Configuration du graphique",
|
"Chart config": "Configuration du graphique",
|
||||||
"Templates": "Modèles",
|
"Templates": "Modèles",
|
||||||
|
"Template": "Modèle",
|
||||||
"Select template": "Sélectionner un modèle",
|
"Select template": "Sélectionner un modèle",
|
||||||
"Action logs": "Logs d'action",
|
"Action logs": "Logs d'action",
|
||||||
"Create template": "Créer un modèle",
|
"Create template": "Créer un modèle",
|
||||||
@ -821,5 +822,8 @@
|
|||||||
"If selected, the page will display Tab pages.": "Si sélectionné, la page affichera des onglets.",
|
"If selected, the page will display Tab pages.": "Si sélectionné, la page affichera des onglets.",
|
||||||
"If selected, the route will be displayed in the menu.": "Si sélectionné, la route sera affichée dans le menu.",
|
"If selected, the route will be displayed in the menu.": "Si sélectionné, la route sera affichée dans le menu.",
|
||||||
"Are you sure you want to hide this tab?": "Êtes-vous sûr de vouloir masquer cet onglet ?",
|
"Are you sure you want to hide this tab?": "Êtes-vous sûr de vouloir masquer cet onglet ?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Après avoir masqué, cette tab ne sera plus affichée dans la barre de tab. Pour la montrer à nouveau, vous devez vous rendre sur la page de gestion des routes pour la configurer."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Après avoir masqué, cette tab ne sera plus affichée dans la barre de tab. Pour la montrer à nouveau, vous devez vous rendre sur la page de gestion des routes pour la configurer.",
|
||||||
|
"Deprecated": "Déprécié",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "Les fonctionnalités des anciens modèles ont été dépréciées et seront supprimées dans la prochaine version.",
|
||||||
|
"Full permissions": "Tous les droits"
|
||||||
}
|
}
|
||||||
|
@ -145,6 +145,7 @@
|
|||||||
"Chart type": "チャートタイプ",
|
"Chart type": "チャートタイプ",
|
||||||
"Chart config": "チャート設定",
|
"Chart config": "チャート設定",
|
||||||
"Templates": "テンプレート",
|
"Templates": "テンプレート",
|
||||||
|
"Template": "テンプレート",
|
||||||
"Select template": "テンプレートを選択してください",
|
"Select template": "テンプレートを選択してください",
|
||||||
"Action logs": "操作履歴",
|
"Action logs": "操作履歴",
|
||||||
"Create template": "テンプレートを作成",
|
"Create template": "テンプレートを作成",
|
||||||
@ -1039,5 +1040,8 @@
|
|||||||
"If selected, the page will display Tab pages.": "選択されている場合、ページはタブページを表示します。",
|
"If selected, the page will display Tab pages.": "選択されている場合、ページはタブページを表示します。",
|
||||||
"If selected, the route will be displayed in the menu.": "選択されている場合、ルートはメニューに表示されます。",
|
"If selected, the route will be displayed in the menu.": "選択されている場合、ルートはメニューに表示されます。",
|
||||||
"Are you sure you want to hide this tab?": "このタブを非表示にしますか?",
|
"Are you sure you want to hide this tab?": "このタブを非表示にしますか?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "非表示にすると、このタブはタブバーに表示されなくなります。再表示するには、ルート管理ページで設定する必要があります。"
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "非表示にすると、このタブはタブバーに表示されなくなります。再表示するには、ルート管理ページで設定する必要があります。",
|
||||||
|
"Deprecated": "非推奨",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "次の古いテンプレート機能は非推奨になり、次のバージョンで削除されます。",
|
||||||
|
"Full permissions": "すべての権限"
|
||||||
}
|
}
|
||||||
|
@ -183,6 +183,7 @@
|
|||||||
"Chart type": "차트 유형",
|
"Chart type": "차트 유형",
|
||||||
"Chart config": "차트 구성",
|
"Chart config": "차트 구성",
|
||||||
"Templates": "템플릿",
|
"Templates": "템플릿",
|
||||||
|
"Template": "템플릿",
|
||||||
"Select template": "템플릿 선택",
|
"Select template": "템플릿 선택",
|
||||||
"Action logs": "작업 로그",
|
"Action logs": "작업 로그",
|
||||||
"Create template": "템플릿 생성",
|
"Create template": "템플릿 생성",
|
||||||
@ -912,5 +913,8 @@
|
|||||||
"If selected, the page will display Tab pages.": "선택되면 페이지는 탭 페이지를 표시합니다.",
|
"If selected, the page will display Tab pages.": "선택되면 페이지는 탭 페이지를 표시합니다.",
|
||||||
"If selected, the route will be displayed in the menu.": "선택되면 라우트는 메뉴에 표시됩니다.",
|
"If selected, the route will be displayed in the menu.": "선택되면 라우트는 메뉴에 표시됩니다.",
|
||||||
"Are you sure you want to hide this tab?": "이 탭을 숨기시겠습니까?",
|
"Are you sure you want to hide this tab?": "이 탭을 숨기시겠습니까?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "숨기면 이 탭은 탭 바에 더 이상 표시되지 않습니다. 다시 표시하려면 라우트 관리 페이지에서 설정해야 합니다."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "숨기면 이 탭은 탭 바에 더 이상 표시되지 않습니다. 다시 표시하려면 라우트 관리 페이지에서 설정해야 합니다.",
|
||||||
|
"Deprecated": "사용 중단됨",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "다음 오래된 템플릿 기능은 사용 중단되었으며 다음 버전에서 제거될 것입니다.",
|
||||||
|
"Full permissions": "모든 권한"
|
||||||
}
|
}
|
||||||
|
@ -112,6 +112,7 @@
|
|||||||
"Chart type": "Tipo de gráfico",
|
"Chart type": "Tipo de gráfico",
|
||||||
"Chart config": "Configuração do gráfico",
|
"Chart config": "Configuração do gráfico",
|
||||||
"Templates": "Modelos",
|
"Templates": "Modelos",
|
||||||
|
"Template": "Modelo",
|
||||||
"Select template": "Selecione um modelo",
|
"Select template": "Selecione um modelo",
|
||||||
"Action logs": "Registros de ação",
|
"Action logs": "Registros de ação",
|
||||||
"Create template": "Criar modelo",
|
"Create template": "Criar modelo",
|
||||||
@ -778,5 +779,8 @@
|
|||||||
"If selected, the page will display Tab pages.": "Se selecionado, a página exibirá páginas de abas.",
|
"If selected, the page will display Tab pages.": "Se selecionado, a página exibirá páginas de abas.",
|
||||||
"If selected, the route will be displayed in the menu.": "Se selecionado, a rota será exibida no menu.",
|
"If selected, the route will be displayed in the menu.": "Se selecionado, a rota será exibida no menu.",
|
||||||
"Are you sure you want to hide this tab?": "Tem certeza de que deseja ocultar esta guia?",
|
"Are you sure you want to hide this tab?": "Tem certeza de que deseja ocultar esta guia?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Depois de ocultar, esta guia não aparecerá mais na barra de guias. Para mostrá-la novamente, você precisa ir à página de gerenciamento de rotas para configurá-la."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Depois de ocultar, esta guia não aparecerá mais na barra de guias. Para mostrá-la novamente, você precisa ir à página de gerenciamento de rotas para configurá-la.",
|
||||||
|
"Deprecated": "Descontinuado",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "As seguintes funcionalidades de modelo antigo foram descontinuadas e serão removidas na próxima versão.",
|
||||||
|
"Full permissions": "Todas as permissões"
|
||||||
}
|
}
|
||||||
|
@ -102,6 +102,7 @@
|
|||||||
"Chart type": "Тип диаграммы",
|
"Chart type": "Тип диаграммы",
|
||||||
"Chart config": "Конфиг. диаграммы",
|
"Chart config": "Конфиг. диаграммы",
|
||||||
"Templates": "Шаблоны",
|
"Templates": "Шаблоны",
|
||||||
|
"Template": "Шаблон",
|
||||||
"Select template": "Выбрать шаблон",
|
"Select template": "Выбрать шаблон",
|
||||||
"Action logs": "Журналы действий",
|
"Action logs": "Журналы действий",
|
||||||
"Create template": "Создать шаблон",
|
"Create template": "Создать шаблон",
|
||||||
@ -607,5 +608,8 @@
|
|||||||
"If selected, the page will display Tab pages.": "Если выбран, страница будет отображать страницы с вкладками.",
|
"If selected, the page will display Tab pages.": "Если выбран, страница будет отображать страницы с вкладками.",
|
||||||
"If selected, the route will be displayed in the menu.": "Если выбран, маршрут будет отображаться в меню.",
|
"If selected, the route will be displayed in the menu.": "Если выбран, маршрут будет отображаться в меню.",
|
||||||
"Are you sure you want to hide this tab?": "Вы уверены, что хотите скрыть эту вкладку?",
|
"Are you sure you want to hide this tab?": "Вы уверены, что хотите скрыть эту вкладку?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "После скрытия этой вкладки она больше не будет отображаться во вкладке. Чтобы снова отобразить ее, вам нужно будет перейти на страницу управления маршрутами, чтобы установить ее."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "После скрытия этой вкладки она больше не будет отображаться во вкладке. Чтобы снова отобразить ее, вам нужно будет перейти на страницу управления маршрутами, чтобы установить ее.",
|
||||||
|
"Deprecated": "Устаревший",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "Следующие старые функции шаблонов устарели и будут удалены в следующей версии.",
|
||||||
|
"Full permissions": "Полные права"
|
||||||
}
|
}
|
||||||
|
@ -102,6 +102,7 @@
|
|||||||
"Chart type": "Grafik türü",
|
"Chart type": "Grafik türü",
|
||||||
"Chart config": "Grafik yapılandırması",
|
"Chart config": "Grafik yapılandırması",
|
||||||
"Templates": "Şablonlar",
|
"Templates": "Şablonlar",
|
||||||
|
"Template": "Şablon",
|
||||||
"Select template": "Şablon seç",
|
"Select template": "Şablon seç",
|
||||||
"Action logs": "Eylem günlükleri",
|
"Action logs": "Eylem günlükleri",
|
||||||
"Create template": "Şablon oluştur",
|
"Create template": "Şablon oluştur",
|
||||||
@ -605,5 +606,8 @@
|
|||||||
"If selected, the page will display Tab pages.": "Seçildiğinde, sayfa Tab sayfalarını görüntüleyecektir.",
|
"If selected, the page will display Tab pages.": "Seçildiğinde, sayfa Tab sayfalarını görüntüleyecektir.",
|
||||||
"If selected, the route will be displayed in the menu.": "Seçildiğinde, yol menüde görüntülenecektir.",
|
"If selected, the route will be displayed in the menu.": "Seçildiğinde, yol menüde görüntülenecektir.",
|
||||||
"Are you sure you want to hide this tab?": "Bu sekmeyi gizlemek istediğinizden emin misiniz?",
|
"Are you sure you want to hide this tab?": "Bu sekmeyi gizlemek istediğinizden emin misiniz?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Gizlendikten sonra, bu sekme artık sekme çubuğunda görünmeyecek. Onu tekrar göstermek için, rotayı yönetim sayfasına gidip ayarlamanız gerekiyor."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Gizlendikten sonra, bu sekme artık sekme çubuğunda görünmeyecek. Onu tekrar göstermek için, rotayı yönetim sayfasına gidip ayarlamanız gerekiyor.",
|
||||||
|
"Deprecated": "Kullanımdan kaldırıldı",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "Aşağıdaki eski şablon özellikleri kullanımdan kaldırıldı ve gelecek sürümlerde kaldırılacaktır.",
|
||||||
|
"Full permissions": "Tüm izinler"
|
||||||
}
|
}
|
||||||
|
@ -159,6 +159,7 @@
|
|||||||
"Chart type": "Тип діаграми",
|
"Chart type": "Тип діаграми",
|
||||||
"Chart config": "Налаштування діаграми",
|
"Chart config": "Налаштування діаграми",
|
||||||
"Templates": "Шаблони",
|
"Templates": "Шаблони",
|
||||||
|
"Template": "Шаблон",
|
||||||
"Select template": "Вибрати шаблон",
|
"Select template": "Вибрати шаблон",
|
||||||
"Action logs": "Журнал дій",
|
"Action logs": "Журнал дій",
|
||||||
"Create template": "Створити шаблон",
|
"Create template": "Створити шаблон",
|
||||||
@ -821,5 +822,8 @@
|
|||||||
"If selected, the page will display Tab pages.": "Якщо вибрано, сторінка відобразить сторінки з вкладками.",
|
"If selected, the page will display Tab pages.": "Якщо вибрано, сторінка відобразить сторінки з вкладками.",
|
||||||
"If selected, the route will be displayed in the menu.": "Якщо вибрано, маршрут буде відображений в меню.",
|
"If selected, the route will be displayed in the menu.": "Якщо вибрано, маршрут буде відображений в меню.",
|
||||||
"Are you sure you want to hide this tab?": "Ви впевнені, що хочете приховати цю вкладку?",
|
"Are you sure you want to hide this tab?": "Ви впевнені, що хочете приховати цю вкладку?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Після приховування цієї вкладки вона більше не з'явиться в панелі вкладок. Щоб знову показати її, вам потрібно перейти на сторінку керування маршрутами, щоб налаштувати її."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Після приховування цієї вкладки вона більше не з'явиться в панелі вкладок. Щоб знову показати її, вам потрібно перейти на сторінку керування маршрутами, щоб налаштувати її.",
|
||||||
|
"Deprecated": "Застаріло",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "Наступні старі функції шаблонів були застарілі і будуть видалені в наступній версії.",
|
||||||
|
"Full permissions": "Повні права"
|
||||||
}
|
}
|
@ -183,6 +183,7 @@
|
|||||||
"Chart type": "图表类型",
|
"Chart type": "图表类型",
|
||||||
"Chart config": "图表配置",
|
"Chart config": "图表配置",
|
||||||
"Templates": "模板",
|
"Templates": "模板",
|
||||||
|
"Template": "模板",
|
||||||
"Select template": "选择模板",
|
"Select template": "选择模板",
|
||||||
"Action logs": "操作日志",
|
"Action logs": "操作日志",
|
||||||
"Create template": "创建模板",
|
"Create template": "创建模板",
|
||||||
@ -1082,8 +1083,11 @@
|
|||||||
"If selected, the route will be displayed in the menu.": "如果选中,该路由将显示在菜单中。",
|
"If selected, the route will be displayed in the menu.": "如果选中,该路由将显示在菜单中。",
|
||||||
"Are you sure you want to hide this tab?": "你确定要隐藏该标签页吗?",
|
"Are you sure you want to hide this tab?": "你确定要隐藏该标签页吗?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "隐藏后,该标签将不再显示在标签栏中。要想再次显示它,你需要到路由管理页面进行设置。",
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "隐藏后,该标签将不再显示在标签栏中。要想再次显示它,你需要到路由管理页面进行设置。",
|
||||||
"Date scope": "日期范围",
|
"Deprecated": "已弃用",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "以下旧的模板功能已弃用,将在下个版本移除。",
|
||||||
|
"Full permissions": "全部权限",
|
||||||
"Enable index column": "启用序号列",
|
"Enable index column": "启用序号列",
|
||||||
|
"Date scope": "日期范围",
|
||||||
"Icon only": "仅显示图标",
|
"Icon only": "仅显示图标",
|
||||||
"Valid range: 100-900": "有效范围:100-900",
|
"Valid range: 100-900": "有效范围:100-900",
|
||||||
"Valid range: 10-40": "有效范围:10-40",
|
"Valid range: 10-40": "有效范围:10-40",
|
||||||
|
@ -183,6 +183,7 @@
|
|||||||
"Chart type": "圖表型別",
|
"Chart type": "圖表型別",
|
||||||
"Chart config": "圖表設定",
|
"Chart config": "圖表設定",
|
||||||
"Templates": "模板",
|
"Templates": "模板",
|
||||||
|
"Template": "模板",
|
||||||
"Select template": "選擇模板",
|
"Select template": "選擇模板",
|
||||||
"Action logs": "動作日誌",
|
"Action logs": "動作日誌",
|
||||||
"Create template": "建立模板",
|
"Create template": "建立模板",
|
||||||
@ -912,6 +913,8 @@
|
|||||||
"If selected, the page will display Tab pages.": "如果選中,該頁面將顯示標籤頁。",
|
"If selected, the page will display Tab pages.": "如果選中,該頁面將顯示標籤頁。",
|
||||||
"If selected, the route will be displayed in the menu.": "如果選中,該路由將顯示在菜單中。",
|
"If selected, the route will be displayed in the menu.": "如果選中,該路由將顯示在菜單中。",
|
||||||
"Are you sure you want to hide this tab?": "你確定要隱藏這個標籤嗎?",
|
"Are you sure you want to hide this tab?": "你確定要隱藏這個標籤嗎?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "隱藏後,這個標籤將不再出現在標籤欄中。要再次顯示它,你需要到路由管理頁面進行設置。"
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "隱藏後,這個標籤將不再出現在標籤欄中。要再次顯示它,你需要到路由管理頁面進行設置。",
|
||||||
|
"Deprecated": "已棄用",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "以下舊的模板功能已棄用,將在下個版本移除。",
|
||||||
|
"Full permissions": "完全權限"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ test.describe('bulk-destroy', () => {
|
|||||||
// 3. 点击批量删除按钮,Table 显示无数据
|
// 3. 点击批量删除按钮,Table 显示无数据
|
||||||
await page.getByLabel('action-Action-Delete-destroy-').click();
|
await page.getByLabel('action-Action-Delete-destroy-').click();
|
||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
await expect(page.getByLabel('block-item-CardItem-general-').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-general-').getByText('No data').last()).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Secondary confirmation', async ({ page, mockPage, mockRecords }) => {
|
test('Secondary confirmation', async ({ page, mockPage, mockRecords }) => {
|
||||||
@ -45,6 +45,7 @@ test.describe('bulk-destroy', () => {
|
|||||||
await page.getByLabel('designer-schema-settings-Action-actionSettings:bulkDelete-general').hover();
|
await page.getByLabel('designer-schema-settings-Action-actionSettings:bulkDelete-general').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Secondary confirmation' }).click();
|
await page.getByRole('menuitem', { name: 'Secondary confirmation' }).click();
|
||||||
await page.getByLabel('Enable secondary confirmation').uncheck();
|
await page.getByLabel('Enable secondary confirmation').uncheck();
|
||||||
|
await expect(page.getByRole('button', { name: 'OK' })).toHaveCount(1);
|
||||||
await page.getByRole('button', { name: 'OK' }).click();
|
await page.getByRole('button', { name: 'OK' }).click();
|
||||||
await page.mouse.move(500, 0);
|
await page.mouse.move(500, 0);
|
||||||
|
|
||||||
@ -53,6 +54,6 @@ test.describe('bulk-destroy', () => {
|
|||||||
|
|
||||||
// 3. 点击批量删除按钮,Table 显示无数据
|
// 3. 点击批量删除按钮,Table 显示无数据
|
||||||
await page.getByLabel('action-Action-Delete-destroy-').click();
|
await page.getByLabel('action-Action-Delete-destroy-').click();
|
||||||
await expect(page.getByLabel('block-item-CardItem-general-').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-general-').getByText('No data').last()).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -28,6 +28,9 @@ test.describe('Link', () => {
|
|||||||
|
|
||||||
// 2. config the Link button
|
// 2. config the Link button
|
||||||
await page.getByLabel('action-Action.Link-Link-customize:link-users-table-0').hover();
|
await page.getByLabel('action-Action.Link-Link-customize:link-users-table-0').hover();
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'designer-schema-settings-Action.Link-actionSettings:link-users' }),
|
||||||
|
).toHaveCount(1);
|
||||||
await page.getByRole('button', { name: 'designer-schema-settings-Action.Link-actionSettings:link-users' }).hover();
|
await page.getByRole('button', { name: 'designer-schema-settings-Action.Link-actionSettings:link-users' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Edit link' }).click();
|
await page.getByRole('menuitem', { name: 'Edit link' }).click();
|
||||||
await page
|
await page
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
} from './templates';
|
} from './templates';
|
||||||
|
|
||||||
test.describe('Submit: should refresh data after submit', () => {
|
test.describe('Submit: should refresh data after submit', () => {
|
||||||
test('submit in reference template block', async ({ page, mockPage, clearBlockTemplates, mockRecord }) => {
|
test.skip('submit in reference template block', async ({ page, mockPage, clearBlockTemplates, mockRecord }) => {
|
||||||
const nocoPage = await mockPage(submitInReferenceTemplateBlock).waitForInit();
|
const nocoPage = await mockPage(submitInReferenceTemplateBlock).waitForInit();
|
||||||
await mockRecord('collection', { nickname: 'abc' });
|
await mockRecord('collection', { nickname: 'abc' });
|
||||||
await nocoPage.goto();
|
await nocoPage.goto();
|
||||||
|
@ -29,6 +29,7 @@ test('basic', async ({ page, mockPage, mockRecord }) => {
|
|||||||
await expect(page.getByRole('tooltip').getByText('Disassociate')).toBeVisible();
|
await expect(page.getByRole('tooltip').getByText('Disassociate')).toBeVisible();
|
||||||
|
|
||||||
await page.getByLabel('block-item-CardItem-cc-table').hover();
|
await page.getByLabel('block-item-CardItem-cc-table').hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'Associate' }).waitFor({ state: 'detached' });
|
||||||
await page.getByLabel('schema-initializer-ActionBar-table:configureActions-cc').hover();
|
await page.getByLabel('schema-initializer-ActionBar-table:configureActions-cc').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Associate' }).click();
|
await page.getByRole('menuitem', { name: 'Associate' }).click();
|
||||||
//点击 associate 出现弹窗
|
//点击 associate 出现弹窗
|
||||||
|
@ -18,7 +18,7 @@ test('basic', async ({ page, mockPage, mockRecord }) => {
|
|||||||
await page.getByLabel('action-Action.Link-Edit record-update-collection1-table-0').click();
|
await page.getByLabel('action-Action.Link-Edit record-update-collection1-table-0').click();
|
||||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Table right' }).hover();
|
await page.getByRole('menuitem', { name: 'Table right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
|
await page.getByRole('menuitem', { name: 'Associated records' }).last().hover();
|
||||||
await page.getByRole('menuitem', { name: 'manyToMany' }).click();
|
await page.getByRole('menuitem', { name: 'manyToMany' }).click();
|
||||||
|
|
||||||
// 2. Table 中显示 Role UID 字段
|
// 2. Table 中显示 Role UID 字段
|
||||||
|
@ -14,6 +14,7 @@ import { useCollection } from '../../data-source/collection/CollectionProvider';
|
|||||||
import { useCompile } from '../../schema-component';
|
import { useCompile } from '../../schema-component';
|
||||||
import { SchemaToolbar } from '../../schema-settings/GeneralSchemaDesigner';
|
import { SchemaToolbar } from '../../schema-settings/GeneralSchemaDesigner';
|
||||||
import { useSchemaTemplate } from '../../schema-templates';
|
import { useSchemaTemplate } from '../../schema-templates';
|
||||||
|
import { useMobileLayout } from '../../route-switch/antd/admin-layout';
|
||||||
|
|
||||||
export const BlockSchemaToolbar = (props) => {
|
export const BlockSchemaToolbar = (props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -22,6 +23,7 @@ export const BlockSchemaToolbar = (props) => {
|
|||||||
const template = useSchemaTemplate();
|
const template = useSchemaTemplate();
|
||||||
const { association, collection } = useDataBlockProps() || {};
|
const { association, collection } = useDataBlockProps() || {};
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
|
const { isMobileLayout } = useMobileLayout();
|
||||||
|
|
||||||
if (association) {
|
if (association) {
|
||||||
const [collectionName] = association.split('.');
|
const [collectionName] = association.split('.');
|
||||||
@ -51,7 +53,7 @@ export const BlockSchemaToolbar = (props) => {
|
|||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
}, [currentCollectionTitle, currentCollectionName, associationField, associationCollection, compile, templateName]);
|
}, [currentCollectionTitle, currentCollectionName, associationField, associationCollection, compile, templateName]);
|
||||||
|
|
||||||
return <SchemaToolbar title={toolbarTitle} {...props} />;
|
return <SchemaToolbar title={toolbarTitle} {...props} draggable={!isMobileLayout} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getCollectionTitle(arg: {
|
export function getCollectionTitle(arg: {
|
||||||
|
@ -12,6 +12,7 @@ import { oneEmptyTableWithUsers } from './templatesOfBug';
|
|||||||
|
|
||||||
const deleteButton = async (page: Page, name: string) => {
|
const deleteButton = async (page: Page, name: string) => {
|
||||||
await page.getByRole('button', { name }).hover();
|
await page.getByRole('button', { name }).hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'Delete' }).waitFor({ state: 'detached' });
|
||||||
await page.getByRole('button', { name }).getByLabel('designer-schema-settings-').hover();
|
await page.getByRole('button', { name }).getByLabel('designer-schema-settings-').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
@ -31,6 +32,7 @@ test.describe('where multi data details block can be added', () => {
|
|||||||
// 1. 打开弹窗,通过 Associated records 添加一个详情区块
|
// 1. 打开弹窗,通过 Associated records 添加一个详情区块
|
||||||
await page.getByLabel('action-Action.Link-View').click();
|
await page.getByLabel('action-Action.Link-View').click();
|
||||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'Associated records right' }).waitFor({ state: 'detached' });
|
||||||
await page.getByRole('menuitem', { name: 'Details right' }).hover();
|
await page.getByRole('menuitem', { name: 'Details right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Associated records right' }).hover();
|
await page.getByRole('menuitem', { name: 'Associated records right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Roles' }).click();
|
await page.getByRole('menuitem', { name: 'Roles' }).click();
|
||||||
@ -41,6 +43,7 @@ test.describe('where multi data details block can be added', () => {
|
|||||||
await expect(page.getByLabel('block-item-CollectionField-').getByText('admin')).toBeVisible();
|
await expect(page.getByLabel('block-item-CollectionField-').getByText('admin')).toBeVisible();
|
||||||
|
|
||||||
// 2. 打开弹窗,通过 Other records 添加一个详情区块
|
// 2. 打开弹窗,通过 Other records 添加一个详情区块
|
||||||
|
await page.getByRole('menuitem', { name: 'Details right' }).waitFor({ state: 'detached' });
|
||||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Details right' }).hover();
|
await page.getByRole('menuitem', { name: 'Details right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Other records right' }).hover();
|
await page.getByRole('menuitem', { name: 'Other records right' }).hover();
|
||||||
@ -116,6 +119,7 @@ test.describe('configure actions', () => {
|
|||||||
await page.getByText('Delete').click();
|
await page.getByText('Delete').click();
|
||||||
|
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
|
await expect(page.getByRole('button', { name: 'Edit' })).toHaveCount(1);
|
||||||
await expect(page.getByRole('button', { name: 'Edit' })).toBeVisible();
|
await expect(page.getByRole('button', { name: 'Edit' })).toBeVisible();
|
||||||
await expect(page.getByRole('button', { name: 'Delete' })).toBeVisible();
|
await expect(page.getByRole('button', { name: 'Delete' })).toBeVisible();
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ test.describe('multi data details block schema settings', () => {
|
|||||||
'Linkage rules',
|
'Linkage rules',
|
||||||
'Set the data scope',
|
'Set the data scope',
|
||||||
'Set default sorting rules',
|
'Set default sorting rules',
|
||||||
'Save as template',
|
// 'Save as template',
|
||||||
'Delete',
|
'Delete',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -76,6 +76,7 @@ test.describe('actions schema settings', () => {
|
|||||||
await expectSettingsMenu({
|
await expectSettingsMenu({
|
||||||
page,
|
page,
|
||||||
showMenu: async () => {
|
showMenu: async () => {
|
||||||
|
await expect(page.getByRole('button', { name: 'Edit' })).toHaveCount(1);
|
||||||
await page.getByRole('button', { name: 'Edit' }).hover();
|
await page.getByRole('button', { name: 'Edit' }).hover();
|
||||||
await page.getByRole('button', { name: 'designer-schema-settings-Action' }).hover();
|
await page.getByRole('button', { name: 'designer-schema-settings-Action' }).hover();
|
||||||
},
|
},
|
||||||
|
@ -50,10 +50,10 @@ test.describe('setDataLoadingModeSettingsItem', () => {
|
|||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
|
|
||||||
// 所有区块应该显示 No data
|
// 所有区块应该显示 No data
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data').last()).toBeVisible();
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-details').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-details').getByText('No data').last()).toBeVisible();
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-list').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-list').getByText('No data').last()).toBeVisible();
|
||||||
await expect(page.getByLabel('block-item-BlockItem-users-').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-BlockItem-users-').getByText('No data').last()).toBeVisible();
|
||||||
|
|
||||||
// 3. 在筛选表单中数据一个筛选条件,点击筛选按钮,区块内应该显示数据
|
// 3. 在筛选表单中数据一个筛选条件,点击筛选按钮,区块内应该显示数据
|
||||||
await page.getByLabel('block-item-CollectionField-').getByRole('textbox').click();
|
await page.getByLabel('block-item-CollectionField-').getByRole('textbox').click();
|
||||||
@ -67,10 +67,10 @@ test.describe('setDataLoadingModeSettingsItem', () => {
|
|||||||
|
|
||||||
// 4. 点击筛选表单的 Reset 按钮,区块内应该显示 No data
|
// 4. 点击筛选表单的 Reset 按钮,区块内应该显示 No data
|
||||||
await page.getByLabel('action-Action-Reset to empty-users-').click();
|
await page.getByLabel('action-Action-Reset to empty-users-').click();
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data').last()).toBeVisible();
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-details').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-details').getByText('No data').last()).toBeVisible();
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-list').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-list').getByText('No data').last()).toBeVisible();
|
||||||
await expect(page.getByLabel('block-item-BlockItem-users-').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-BlockItem-users-').getByText('No data').last()).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('When the data block has data scope settings and dataLoadingMode is manual, data should not be displayed after the first page load', async ({
|
test('When the data block has data scope settings and dataLoadingMode is manual, data should not be displayed after the first page load', async ({
|
||||||
@ -78,7 +78,7 @@ test.describe('setDataLoadingModeSettingsItem', () => {
|
|||||||
mockPage,
|
mockPage,
|
||||||
}) => {
|
}) => {
|
||||||
await mockPage(TableBlockWithDataScope).goto();
|
await mockPage(TableBlockWithDataScope).goto();
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data').last()).toBeVisible();
|
||||||
|
|
||||||
// 此时点击 filter 按钮,应该还是没数据,因为表单没有值
|
// 此时点击 filter 按钮,应该还是没数据,因为表单没有值
|
||||||
await page.getByLabel('action-Action-Filter-submit-').click({
|
await page.getByLabel('action-Action-Filter-submit-').click({
|
||||||
@ -87,7 +87,7 @@ test.describe('setDataLoadingModeSettingsItem', () => {
|
|||||||
y: 10,
|
y: 10,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data').last()).toBeVisible();
|
||||||
|
|
||||||
// 点击 Reset 按钮,也是一样
|
// 点击 Reset 按钮,也是一样
|
||||||
await page.getByLabel('action-Action-Reset-users-').click({
|
await page.getByLabel('action-Action-Reset-users-').click({
|
||||||
@ -96,6 +96,6 @@ test.describe('setDataLoadingModeSettingsItem', () => {
|
|||||||
y: 10,
|
y: 10,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data').last()).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -26,7 +26,7 @@ test.describe('where single data details block can be added', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// https://nocobase.height.app/T-3848/description
|
// https://nocobase.height.app/T-3848/description
|
||||||
test('popup opened by clicking on the button for the relationship field', async ({
|
test.skip('popup opened by clicking on the button for the relationship field', async ({
|
||||||
page,
|
page,
|
||||||
mockPage,
|
mockPage,
|
||||||
mockRecord,
|
mockRecord,
|
||||||
@ -69,7 +69,7 @@ test.describe('where single data details block can be added', () => {
|
|||||||
// 3.通过 Associated records 创建一个详情区块
|
// 3.通过 Associated records 创建一个详情区块
|
||||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Details right' }).hover();
|
await page.getByRole('menuitem', { name: 'Details right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
|
await page.getByRole('menuitem', { name: 'Associated records' }).last().hover();
|
||||||
await page.getByRole('menuitem', { name: 'manyToOne' }).hover();
|
await page.getByRole('menuitem', { name: 'manyToOne' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Blank block' }).click();
|
await page.getByRole('menuitem', { name: 'Blank block' }).click();
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
@ -82,7 +82,7 @@ test.describe('where single data details block can be added', () => {
|
|||||||
// 4.通过 Associated records 创建一个详情区块,使用模板
|
// 4.通过 Associated records 创建一个详情区块,使用模板
|
||||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Details right' }).hover();
|
await page.getByRole('menuitem', { name: 'Details right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
|
await page.getByRole('menuitem', { name: 'Associated records' }).last().hover();
|
||||||
await page.getByRole('menuitem', { name: 'manyToOne' }).hover();
|
await page.getByRole('menuitem', { name: 'manyToOne' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Duplicate template' }).hover();
|
await page.getByRole('menuitem', { name: 'Duplicate template' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'example_Details (Fields only)' }).click();
|
await page.getByRole('menuitem', { name: 'example_Details (Fields only)' }).click();
|
||||||
|
@ -24,7 +24,7 @@ test.describe('single details block schema settings', () => {
|
|||||||
await page.getByLabel('block-item-CardItem-general-form').hover();
|
await page.getByLabel('block-item-CardItem-general-form').hover();
|
||||||
await page.getByLabel('designer-schema-settings-CardItem-FormV2.ReadPrettyDesigner-general').hover();
|
await page.getByLabel('designer-schema-settings-CardItem-FormV2.ReadPrettyDesigner-general').hover();
|
||||||
},
|
},
|
||||||
supportedOptions: ['Edit block title', 'Linkage rules', 'Save as block template', 'Delete'],
|
supportedOptions: ['Edit block title', 'Linkage rules', 'Delete'],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -60,10 +60,10 @@ test.describe('configure fields', () => {
|
|||||||
await expect(page.getByRole('menuitem', { name: 'ID', exact: true }).getByRole('switch')).toBeChecked();
|
await expect(page.getByRole('menuitem', { name: 'ID', exact: true }).getByRole('switch')).toBeChecked();
|
||||||
|
|
||||||
// add association fields
|
// add association fields
|
||||||
await page.getByRole('menuitem', { name: 'Many to one' }).nth(1).hover();
|
await page.getByRole('menuitem', { name: 'Many to one right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Nickname' }).click();
|
await page.getByRole('menuitem', { name: 'Nickname' }).click();
|
||||||
|
|
||||||
await page.getByRole('menuitem', { name: 'Many to one' }).nth(1).hover();
|
await page.getByRole('menuitem', { name: 'Many to one right' }).hover();
|
||||||
await expect(page.getByRole('menuitem', { name: 'Nickname' }).getByRole('switch')).toBeChecked();
|
await expect(page.getByRole('menuitem', { name: 'Nickname' }).getByRole('switch')).toBeChecked();
|
||||||
|
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
@ -72,13 +72,14 @@ test.describe('configure fields', () => {
|
|||||||
|
|
||||||
// delete fields
|
// delete fields
|
||||||
await page.getByLabel('schema-initializer-Grid-form:configureFields-general').hover();
|
await page.getByLabel('schema-initializer-Grid-form:configureFields-general').hover();
|
||||||
|
await expect(page.getByRole('menuitem', { name: 'ID', exact: true })).toHaveCount(1);
|
||||||
await page.getByRole('menuitem', { name: 'ID', exact: true }).click();
|
await page.getByRole('menuitem', { name: 'ID', exact: true }).click();
|
||||||
await expect(page.getByRole('menuitem', { name: 'ID', exact: true }).getByRole('switch')).not.toBeChecked();
|
await expect(page.getByRole('menuitem', { name: 'ID', exact: true }).getByRole('switch')).not.toBeChecked();
|
||||||
|
|
||||||
await page.getByRole('menuitem', { name: 'Many to one' }).nth(1).hover();
|
await page.getByRole('menuitem', { name: 'Many to one right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Nickname' }).click();
|
await page.getByRole('menuitem', { name: 'Nickname' }).click();
|
||||||
|
|
||||||
await page.getByRole('menuitem', { name: 'Many to one' }).nth(1).hover();
|
await page.getByRole('menuitem', { name: 'Many to one right' }).hover();
|
||||||
await expect(page.getByRole('menuitem', { name: 'Nickname' }).getByRole('switch')).not.toBeChecked();
|
await expect(page.getByRole('menuitem', { name: 'Nickname' }).getByRole('switch')).not.toBeChecked();
|
||||||
|
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
@ -119,6 +120,7 @@ test.describe('configure actions', () => {
|
|||||||
// add button
|
// add button
|
||||||
await page.getByRole('menuitem', { name: 'Submit' }).click();
|
await page.getByRole('menuitem', { name: 'Submit' }).click();
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
|
await expect(page.getByRole('button', { name: 'Submit' })).toHaveCount(1);
|
||||||
await expect(page.getByRole('button', { name: 'Submit' })).toBeVisible();
|
await expect(page.getByRole('button', { name: 'Submit' })).toBeVisible();
|
||||||
|
|
||||||
// delete button
|
// delete button
|
||||||
|
@ -279,7 +279,7 @@ test.describe('set default value', () => {
|
|||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
|
|
||||||
// 2. 设置的 ‘abcd’ 应该立即显示在 Nickname 字段的输入框中
|
// 2. 设置的 ‘abcd’ 应该立即显示在 Nickname 字段的输入框中
|
||||||
await expect(page.getByLabel('block-item-CollectionField-').getByRole('textbox')).toHaveValue('abcd');
|
await expect(page.getByLabel('block-item-CollectionField-').getByRole('textbox').last()).toHaveValue('abcd');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Current popup record', async ({ page, mockPage }) => {
|
test('Current popup record', async ({ page, mockPage }) => {
|
||||||
|
@ -51,7 +51,8 @@ test.describe('creation form block schema settings', () => {
|
|||||||
await runExpect();
|
await runExpect();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Save as block template & convert reference to duplicate', async ({ page, mockPage }) => {
|
// deprecated
|
||||||
|
test.skip('Save as block template & convert reference to duplicate', async ({ page, mockPage }) => {
|
||||||
await mockPage(oneTableBlockWithActionsAndFormBlocks).goto();
|
await mockPage(oneTableBlockWithActionsAndFormBlocks).goto();
|
||||||
await page.getByRole('button', { name: 'Add new' }).click();
|
await page.getByRole('button', { name: 'Add new' }).click();
|
||||||
|
|
||||||
@ -115,7 +116,7 @@ test.describe('creation form block schema settings', () => {
|
|||||||
await expect(page.getByLabel('block-item-CardItem-general-form')).not.toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-general-form')).not.toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('save as block Template', async ({ page, mockPage }) => {
|
test.skip('save as block Template', async ({ page, mockPage }) => {
|
||||||
await mockPage(oneEmptyForm).goto();
|
await mockPage(oneEmptyForm).goto();
|
||||||
|
|
||||||
// 先保存为模板 ------------------------------------------------------------------------
|
// 先保存为模板 ------------------------------------------------------------------------
|
||||||
@ -247,7 +248,7 @@ test.describe('creation form block schema settings', () => {
|
|||||||
|
|
||||||
// 重新选择一下数据,字段值才会被填充
|
// 重新选择一下数据,字段值才会被填充
|
||||||
// TODO: 保存后,数据应该直接被填充上
|
// TODO: 保存后,数据应该直接被填充上
|
||||||
await page.getByLabel('icon-close-select').click();
|
await page.getByLabel('icon-close-select').last().click();
|
||||||
await page.getByTestId('select-object-single').click();
|
await page.getByTestId('select-object-single').click();
|
||||||
await page.getByRole('option', { name: '2' }).click();
|
await page.getByRole('option', { name: '2' }).click();
|
||||||
|
|
||||||
@ -270,7 +271,7 @@ test.describe('creation form block schema settings', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('save block template & using block template', async ({ page, mockPage, clearBlockTemplates }) => {
|
test.skip('save block template & using block template', async ({ page, mockPage, clearBlockTemplates }) => {
|
||||||
// 确保测试结束后已保存的模板会被清空
|
// 确保测试结束后已保存的模板会被清空
|
||||||
await clearBlockTemplates();
|
await clearBlockTemplates();
|
||||||
const nocoPage = await mockPage({
|
const nocoPage = await mockPage({
|
||||||
|
@ -128,9 +128,14 @@ test.describe('linkage rules', () => {
|
|||||||
|
|
||||||
// 增加一条规则:当 number 字段的值等于 123 时
|
// 增加一条规则:当 number 字段的值等于 123 时
|
||||||
await page.getByRole('button', { name: 'plus Add linkage rule' }).click();
|
await page.getByRole('button', { name: 'plus Add linkage rule' }).click();
|
||||||
await page.locator('.ant-collapse-header').nth(1).getByRole('img', { name: 'right' }).click();
|
await page.locator('.ant-collapse-header .ant-collapse-expand-icon').nth(1).click();
|
||||||
|
|
||||||
await page.getByLabel('Linkage rules').getByRole('tabpanel').getByText('Add condition', { exact: true }).click();
|
await page
|
||||||
|
.getByLabel('Linkage rules')
|
||||||
|
.getByRole('tabpanel')
|
||||||
|
.getByText('Add condition', { exact: true })
|
||||||
|
.last()
|
||||||
|
.click();
|
||||||
await page.getByRole('button', { name: 'Select field' }).click();
|
await page.getByRole('button', { name: 'Select field' }).click();
|
||||||
await page.getByRole('menuitemcheckbox', { name: 'number' }).click();
|
await page.getByRole('menuitemcheckbox', { name: 'number' }).click();
|
||||||
await page.getByLabel('Linkage rules').getByRole('spinbutton').click();
|
await page.getByLabel('Linkage rules').getByRole('spinbutton').click();
|
||||||
@ -146,19 +151,19 @@ test.describe('linkage rules', () => {
|
|||||||
// action: 为 longText 字段赋上常量值
|
// action: 为 longText 字段赋上常量值
|
||||||
await page.getByLabel('Linkage rules').getByRole('tabpanel').getByText('Add property').click();
|
await page.getByLabel('Linkage rules').getByRole('tabpanel').getByText('Add property').click();
|
||||||
await page.getByRole('button', { name: 'Select field' }).click();
|
await page.getByRole('button', { name: 'Select field' }).click();
|
||||||
await page.getByRole('tree').getByText('longText').click();
|
await page.getByRole('tree').getByText('longText').last().click();
|
||||||
await page.getByRole('button', { name: 'action', exact: true }).click();
|
await page.getByRole('button', { name: 'action', exact: true }).click();
|
||||||
await page.getByRole('option', { name: 'Value', exact: true }).click();
|
await page.getByRole('option', { name: 'Value', exact: true }).last().click();
|
||||||
await page.getByLabel('dynamic-component-linkage-rules').getByRole('textbox').fill('456');
|
await page.getByLabel('dynamic-component-linkage-rules').getByRole('textbox').fill('456');
|
||||||
|
|
||||||
// action: 为 integer 字段附上一个表达式,使其值等于 number 字段的值
|
// action: 为 integer 字段附上一个表达式,使其值等于 number 字段的值
|
||||||
await page.getByLabel('Linkage rules').getByRole('tabpanel').getByText('Add property').click();
|
await page.getByLabel('Linkage rules').getByRole('tabpanel').getByText('Add property').click();
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Select field' }).click();
|
await page.getByRole('button', { name: 'Select field' }).click();
|
||||||
await page.getByRole('tree').getByText('integer').click();
|
await page.getByRole('tree').getByText('integer').last().click();
|
||||||
await page.getByRole('button', { name: 'action', exact: true }).click();
|
await page.getByRole('button', { name: 'action', exact: true }).click();
|
||||||
await page.getByRole('option', { name: 'Value', exact: true }).click();
|
await page.getByRole('option', { name: 'Value', exact: true }).last().click();
|
||||||
await page.getByTestId('select-linkage-value-type').nth(1).click();
|
await page.getByTestId('select-linkage-value-type').last().click();
|
||||||
await page.getByText('Expression').click();
|
await page.getByText('Expression').click();
|
||||||
|
|
||||||
await page.getByText('xSelect a variable').click();
|
await page.getByText('xSelect a variable').click();
|
||||||
@ -236,7 +241,7 @@ test.describe('linkage rules', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// https://nocobase.height.app/T-3806
|
// https://nocobase.height.app/T-3806
|
||||||
test('after save as block template', async ({ page, mockPage }) => {
|
test.skip('after save as block template', async ({ page, mockPage }) => {
|
||||||
await mockPage(T3806).goto();
|
await mockPage(T3806).goto();
|
||||||
|
|
||||||
// 1. 一开始联动规则应该正常
|
// 1. 一开始联动规则应该正常
|
||||||
|
@ -33,6 +33,7 @@ test.describe('bulk edit form', () => {
|
|||||||
await expect(page.getByLabel('block-item-BulkEditField-').getByText('*')).toBeVisible();
|
await expect(page.getByLabel('block-item-BulkEditField-').getByText('*')).toBeVisible();
|
||||||
|
|
||||||
// 3. 输入值,点击提交
|
// 3. 输入值,点击提交
|
||||||
|
await expect(page.getByLabel('block-item-BulkEditField-').getByRole('textbox')).toHaveCount(1);
|
||||||
await page.getByLabel('block-item-BulkEditField-').getByRole('textbox').fill('123');
|
await page.getByLabel('block-item-BulkEditField-').getByRole('textbox').fill('123');
|
||||||
await page.getByRole('button', { name: 'Submit' }).click();
|
await page.getByRole('button', { name: 'Submit' }).click();
|
||||||
|
|
||||||
@ -65,6 +66,7 @@ test.describe('bulk edit form', () => {
|
|||||||
await expect(page.getByLabel('block-item-BulkEditField-').getByText('*')).toBeVisible();
|
await expect(page.getByLabel('block-item-BulkEditField-').getByText('*')).toBeVisible();
|
||||||
|
|
||||||
// 4. 点击提交按钮,应该提示一个错误
|
// 4. 点击提交按钮,应该提示一个错误
|
||||||
|
await expect(page.getByRole('button', { name: 'Submit' })).toHaveCount(1);
|
||||||
await page.getByRole('button', { name: 'Submit' }).click();
|
await page.getByRole('button', { name: 'Submit' }).click();
|
||||||
await expect(page.getByLabel('block-item-BulkEditField-').getByText('The field value is required')).toBeVisible();
|
await expect(page.getByLabel('block-item-BulkEditField-').getByText('The field value is required')).toBeVisible();
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ test.describe('deprecated variables', () => {
|
|||||||
|
|
||||||
// 表达式输入框也是一样
|
// 表达式输入框也是一样
|
||||||
await page.getByText('xSelect a variable').click();
|
await page.getByText('xSelect a variable').click();
|
||||||
|
await expect(page.getByRole('menuitemcheckbox', { name: 'Current record right' })).toHaveCount(1);
|
||||||
await page.getByRole('menuitemcheckbox', { name: 'Current record right' }).hover({ position: { x: 40, y: 12 } });
|
await page.getByRole('menuitemcheckbox', { name: 'Current record right' }).hover({ position: { x: 40, y: 12 } });
|
||||||
await expect(page.getByRole('tooltip', { name: 'This variable has been deprecated' })).toBeVisible();
|
await expect(page.getByRole('tooltip', { name: 'This variable has been deprecated' })).toBeVisible();
|
||||||
await expect(page.getByRole('menuitemcheckbox', { name: 'Current record right' })).toHaveClass(
|
await expect(page.getByRole('menuitemcheckbox', { name: 'Current record right' })).toHaveClass(
|
||||||
@ -45,6 +46,7 @@ test.describe('deprecated variables', () => {
|
|||||||
|
|
||||||
// 3. 当设置为其它变量后,再次打开,变量列表中的弃用变量不再显示
|
// 3. 当设置为其它变量后,再次打开,变量列表中的弃用变量不再显示
|
||||||
await page.locator('button').filter({ hasText: /^x$/ }).click();
|
await page.locator('button').filter({ hasText: /^x$/ }).click();
|
||||||
|
await expect(page.getByRole('menuitemcheckbox', { name: 'Current form right' })).toHaveCount(1);
|
||||||
await page.getByRole('menuitemcheckbox', { name: 'Current form right' }).click();
|
await page.getByRole('menuitemcheckbox', { name: 'Current form right' }).click();
|
||||||
await page.getByRole('menuitemcheckbox', { name: 'Nickname' }).click();
|
await page.getByRole('menuitemcheckbox', { name: 'Nickname' }).click();
|
||||||
await expect(page.getByLabel('variable-tag').getByText('Current form / Nickname')).toBeVisible();
|
await expect(page.getByLabel('variable-tag').getByText('Current form / Nickname')).toBeVisible();
|
||||||
|
@ -26,7 +26,7 @@ test.describe('where edit form block can be added', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// https://nocobase.height.app/T-3848/description
|
// https://nocobase.height.app/T-3848/description
|
||||||
test('popup opened by clicking on the button for the relationship field', async ({
|
test.skip('popup opened by clicking on the button for the relationship field', async ({
|
||||||
page,
|
page,
|
||||||
mockPage,
|
mockPage,
|
||||||
mockRecord,
|
mockRecord,
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
import { T3825 } from './templatesOfBug';
|
import { T3825 } from './templatesOfBug';
|
||||||
const clickOption = async (page: Page, optionName: string) => {
|
const clickOption = async (page: Page, optionName: string) => {
|
||||||
await page.getByLabel('block-item-CardItem-general-form').hover();
|
await page.getByLabel('block-item-CardItem-general-form').hover();
|
||||||
|
await page.getByRole('menuitem', { name: optionName }).waitFor({ state: 'detached' });
|
||||||
await page.getByLabel('designer-schema-settings-CardItem-FormV2.Designer-general').hover();
|
await page.getByLabel('designer-schema-settings-CardItem-FormV2.Designer-general').hover();
|
||||||
await page.getByRole('menuitem', { name: optionName }).click();
|
await page.getByRole('menuitem', { name: optionName }).click();
|
||||||
};
|
};
|
||||||
@ -84,7 +85,7 @@ test.describe('edit form block schema settings', () => {
|
|||||||
await runExpect();
|
await runExpect();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Convert reference to duplicate & Save as block template', async ({ page, mockPage, mockRecord }) => {
|
test.skip('Convert reference to duplicate & Save as block template', async ({ page, mockPage, mockRecord }) => {
|
||||||
const nocoPage = await mockPage(oneTableBlockWithActionsAndFormBlocks).waitForInit();
|
const nocoPage = await mockPage(oneTableBlockWithActionsAndFormBlocks).waitForInit();
|
||||||
await mockRecord('general');
|
await mockRecord('general');
|
||||||
await nocoPage.goto();
|
await nocoPage.goto();
|
||||||
|
@ -12,9 +12,8 @@ import { useDetailsParentRecord } from '../../details-single/hooks/useDetailsDec
|
|||||||
import { useHiddenForInherit } from './useHiddenForInherit';
|
import { useHiddenForInherit } from './useHiddenForInherit';
|
||||||
|
|
||||||
export function useEditFormBlockDecoratorProps(props) {
|
export function useEditFormBlockDecoratorProps(props) {
|
||||||
const params = useFormBlockParams();
|
const params = useFormBlockParams(props);
|
||||||
let parentRecord;
|
let parentRecord;
|
||||||
|
|
||||||
const { hidden } = useHiddenForInherit(props);
|
const { hidden } = useHiddenForInherit(props);
|
||||||
|
|
||||||
// association 的值是固定不变的,所以这里可以使用 hooks
|
// association 的值是固定不变的,所以这里可以使用 hooks
|
||||||
@ -31,6 +30,6 @@ export function useEditFormBlockDecoratorProps(props) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function useFormBlockParams() {
|
function useFormBlockParams(props) {
|
||||||
return useParamsFromRecord();
|
return useParamsFromRecord(props);
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import { oneGridCardWithInheritFields } from './templatesOfBug';
|
|||||||
|
|
||||||
const deleteButton = async (page: Page, name: string) => {
|
const deleteButton = async (page: Page, name: string) => {
|
||||||
await page.getByRole('button', { name }).hover();
|
await page.getByRole('button', { name }).hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'Delete' }).waitFor({ state: 'detached' });
|
||||||
await page.getByRole('button', { name }).getByLabel('designer-schema-settings-').hover();
|
await page.getByRole('button', { name }).getByLabel('designer-schema-settings-').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
@ -47,6 +48,7 @@ test.describe('where grid card block can be added', () => {
|
|||||||
|
|
||||||
// 2. 通过 Other records 创建一个列表区块
|
// 2. 通过 Other records 创建一个列表区块
|
||||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'Other records right' }).waitFor({ state: 'detached' });
|
||||||
await page.getByRole('menuitem', { name: 'Grid Card right' }).hover();
|
await page.getByRole('menuitem', { name: 'Grid Card right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Other records right' }).hover();
|
await page.getByRole('menuitem', { name: 'Other records right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Users' }).click();
|
await page.getByRole('menuitem', { name: 'Users' }).click();
|
||||||
@ -151,10 +153,10 @@ test.describe('configure fields', () => {
|
|||||||
|
|
||||||
// add association fields
|
// add association fields
|
||||||
await page.mouse.wheel(0, 300);
|
await page.mouse.wheel(0, 300);
|
||||||
await page.getByRole('menuitem', { name: 'Many to one' }).nth(1).hover();
|
await page.getByRole('menuitem', { name: 'Many to one right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Nickname' }).click();
|
await page.getByRole('menuitem', { name: 'Nickname' }).click();
|
||||||
|
|
||||||
await page.getByRole('menuitem', { name: 'Many to one' }).nth(1).hover();
|
await page.getByRole('menuitem', { name: 'Many to one right' }).hover();
|
||||||
await expect(page.getByRole('menuitem', { name: 'Nickname' }).getByRole('switch')).toBeChecked();
|
await expect(page.getByRole('menuitem', { name: 'Nickname' }).getByRole('switch')).toBeChecked();
|
||||||
|
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
@ -165,14 +167,14 @@ test.describe('configure fields', () => {
|
|||||||
|
|
||||||
// delete fields
|
// delete fields
|
||||||
await formItemInitializer.hover();
|
await formItemInitializer.hover();
|
||||||
await page.getByRole('menuitem', { name: 'ID', exact: true }).click();
|
await page.getByRole('menuitem', { name: 'ID', exact: true }).first().click();
|
||||||
await expect(page.getByRole('menuitem', { name: 'ID', exact: true }).getByRole('switch')).not.toBeChecked();
|
await expect(page.getByRole('menuitem', { name: 'ID', exact: true }).getByRole('switch').first()).not.toBeChecked();
|
||||||
|
|
||||||
await page.mouse.wheel(0, 300);
|
await page.mouse.wheel(0, 300);
|
||||||
await page.getByRole('menuitem', { name: 'Many to one' }).nth(1).hover();
|
await page.getByRole('menuitem', { name: 'Many to one right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Nickname' }).click();
|
await page.getByRole('menuitem', { name: 'Nickname' }).click();
|
||||||
|
|
||||||
await page.getByRole('menuitem', { name: 'Many to one' }).nth(1).hover();
|
await page.getByRole('menuitem', { name: 'Many to one right' }).hover();
|
||||||
await expect(page.getByRole('menuitem', { name: 'Nickname' }).getByRole('switch')).not.toBeChecked();
|
await expect(page.getByRole('menuitem', { name: 'Nickname' }).getByRole('switch')).not.toBeChecked();
|
||||||
|
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
@ -185,7 +187,7 @@ test.describe('configure fields', () => {
|
|||||||
|
|
||||||
// add markdown
|
// add markdown
|
||||||
await formItemInitializer.hover();
|
await formItemInitializer.hover();
|
||||||
await page.getByRole('menuitem', { name: 'ID', exact: true }).hover();
|
await page.getByRole('menuitem', { name: 'ID', exact: true }).first().hover();
|
||||||
await page.mouse.wheel(0, 300);
|
await page.mouse.wheel(0, 300);
|
||||||
await page.getByRole('menuitem', { name: 'Add Markdown' }).click();
|
await page.getByRole('menuitem', { name: 'Add Markdown' }).click();
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ test.describe('grid card block schema settings', () => {
|
|||||||
page,
|
page,
|
||||||
showMenu: async () => {
|
showMenu: async () => {
|
||||||
await page.getByLabel('block-item-BlockItem-general-grid-card').hover();
|
await page.getByLabel('block-item-BlockItem-general-grid-card').hover();
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
await page.getByLabel('designer-schema-settings-BlockItem-GridCard.Designer-general').hover();
|
await page.getByLabel('designer-schema-settings-BlockItem-GridCard.Designer-general').hover();
|
||||||
},
|
},
|
||||||
supportedOptions: [
|
supportedOptions: [
|
||||||
@ -25,7 +26,7 @@ test.describe('grid card block schema settings', () => {
|
|||||||
'Set the data scope',
|
'Set the data scope',
|
||||||
'Set default sorting rules',
|
'Set default sorting rules',
|
||||||
'Records per page',
|
'Records per page',
|
||||||
'Save as template',
|
// 'Save as template',
|
||||||
'Delete',
|
'Delete',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
@ -12,6 +12,7 @@ import { oneEmptyTableWithUsers } from '../../details-multi/__e2e__/templatesOfB
|
|||||||
|
|
||||||
const deleteButton = async (page: Page, name: string) => {
|
const deleteButton = async (page: Page, name: string) => {
|
||||||
await page.getByRole('button', { name }).hover();
|
await page.getByRole('button', { name }).hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'Delete' }).waitFor({ state: 'detached' });
|
||||||
await page.getByRole('button', { name }).getByLabel('designer-schema-settings-').hover();
|
await page.getByRole('button', { name }).getByLabel('designer-schema-settings-').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
@ -71,6 +72,9 @@ test.describe('configure global actions', () => {
|
|||||||
await page.getByRole('menuitem', { name: 'Refresh' }).click();
|
await page.getByRole('menuitem', { name: 'Refresh' }).click();
|
||||||
|
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
|
await expect(page.getByRole('button', { name: 'Filter' })).toHaveCount(1);
|
||||||
|
await expect(page.getByRole('button', { name: 'Add new' })).toHaveCount(1);
|
||||||
|
await expect(page.getByRole('button', { name: 'Refresh' })).toHaveCount(1);
|
||||||
await expect(page.getByRole('button', { name: 'Filter' })).toBeVisible();
|
await expect(page.getByRole('button', { name: 'Filter' })).toBeVisible();
|
||||||
await expect(page.getByRole('button', { name: 'Add new' })).toBeVisible();
|
await expect(page.getByRole('button', { name: 'Add new' })).toBeVisible();
|
||||||
await expect(page.getByRole('button', { name: 'Refresh' })).toBeVisible();
|
await expect(page.getByRole('button', { name: 'Refresh' })).toBeVisible();
|
||||||
|
@ -24,7 +24,7 @@ test.describe('list block schema settings', () => {
|
|||||||
'Set the data scope',
|
'Set the data scope',
|
||||||
'Set default sorting rules',
|
'Set default sorting rules',
|
||||||
'Records per page',
|
'Records per page',
|
||||||
'Save as template',
|
// 'Save as template',
|
||||||
'Delete',
|
'Delete',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
@ -90,7 +90,7 @@ test.describe('configure actions column', () => {
|
|||||||
// 列宽度默认为 100
|
// 列宽度默认为 100
|
||||||
await expectActionsColumnWidth(100);
|
await expectActionsColumnWidth(100);
|
||||||
|
|
||||||
await page.getByText('Actions', { exact: true }).hover();
|
await page.getByText('Actions', { exact: true }).hover({ force: true });
|
||||||
await page.getByLabel('designer-schema-settings-TableV2.Column-fieldSettings:TableColumn-users').hover();
|
await page.getByLabel('designer-schema-settings-TableV2.Column-fieldSettings:TableColumn-users').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Column width' }).click();
|
await page.getByRole('menuitem', { name: 'Column width' }).click();
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
import { expect, test } from '@nocobase/test/e2e';
|
import { expect, test } from '@nocobase/test/e2e';
|
||||||
import { ordinaryBlockTemplatesCannotBeUsedToCreateAssociationBlocksAndViceVersa } from './templatesOfBug';
|
import { ordinaryBlockTemplatesCannotBeUsedToCreateAssociationBlocksAndViceVersa } from './templatesOfBug';
|
||||||
|
|
||||||
test.describe('block template', () => {
|
test.skip('block template', () => {
|
||||||
test('Ordinary block templates cannot be used to create association blocks, and vice versa', async ({
|
test('Ordinary block templates cannot be used to create association blocks, and vice versa', async ({
|
||||||
page,
|
page,
|
||||||
mockPage,
|
mockPage,
|
||||||
|
@ -25,6 +25,7 @@ test.describe('hide column', () => {
|
|||||||
|
|
||||||
// 2. Sub table: hide column
|
// 2. Sub table: hide column
|
||||||
await page.getByRole('button', { name: 'Role name' }).hover();
|
await page.getByRole('button', { name: 'Role name' }).hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'Hide column question-circle' }).waitFor({ state: 'detached' });
|
||||||
await page
|
await page
|
||||||
.getByRole('button', { name: 'designer-schema-settings-TableV2.Column-fieldSettings:TableColumn-roles' })
|
.getByRole('button', { name: 'designer-schema-settings-TableV2.Column-fieldSettings:TableColumn-roles' })
|
||||||
.hover();
|
.hover();
|
||||||
|
@ -295,19 +295,19 @@ test.describe('configure actions column', () => {
|
|||||||
await nocoPage.goto();
|
await nocoPage.goto();
|
||||||
|
|
||||||
// add view & Edit & Delete & Duplicate ------------------------------------------------------------
|
// add view & Edit & Delete & Duplicate ------------------------------------------------------------
|
||||||
await page.getByText('Actions', { exact: true }).hover();
|
await page.getByText('Actions', { exact: true }).hover({ force: true });
|
||||||
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
||||||
await page.getByRole('menuitem', { name: 'View' }).click();
|
await page.getByRole('menuitem', { name: 'View' }).click();
|
||||||
|
|
||||||
await page.getByText('Actions', { exact: true }).hover();
|
await page.getByText('Actions', { exact: true }).hover({ force: true });
|
||||||
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
||||||
|
|
||||||
await page.getByText('Actions', { exact: true }).hover();
|
await page.getByText('Actions', { exact: true }).hover({ force: true });
|
||||||
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||||
|
|
||||||
await page.getByText('Actions', { exact: true }).hover();
|
await page.getByText('Actions', { exact: true }).hover({ force: true });
|
||||||
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Duplicate' }).click();
|
await page.getByRole('menuitem', { name: 'Duplicate' }).click();
|
||||||
|
|
||||||
@ -330,11 +330,11 @@ test.describe('configure actions column', () => {
|
|||||||
await expect(page.getByLabel('action-Action.Link-Duplicate-duplicate-t_unp4scqamw9-table-0')).not.toBeVisible();
|
await expect(page.getByLabel('action-Action.Link-Duplicate-duplicate-t_unp4scqamw9-table-0')).not.toBeVisible();
|
||||||
|
|
||||||
// add custom action ------------------------------------------------------------
|
// add custom action ------------------------------------------------------------
|
||||||
await page.getByText('Actions', { exact: true }).hover();
|
await page.getByText('Actions', { exact: true }).hover({ force: true });
|
||||||
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Popup' }).click();
|
await page.getByRole('menuitem', { name: 'Popup' }).click();
|
||||||
|
|
||||||
await page.getByText('Actions', { exact: true }).hover();
|
await page.getByText('Actions', { exact: true }).hover({ force: true });
|
||||||
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Update record' }).click();
|
await page.getByRole('menuitem', { name: 'Update record' }).click();
|
||||||
|
|
||||||
@ -351,7 +351,7 @@ test.describe('configure actions column', () => {
|
|||||||
// 列宽度默认为 100
|
// 列宽度默认为 100
|
||||||
await expect(page.getByRole('columnheader', { name: 'Actions', exact: true })).toHaveJSProperty('offsetWidth', 100);
|
await expect(page.getByRole('columnheader', { name: 'Actions', exact: true })).toHaveJSProperty('offsetWidth', 100);
|
||||||
|
|
||||||
await page.getByText('Actions', { exact: true }).hover();
|
await page.getByText('Actions', { exact: true }).hover({ force: true });
|
||||||
await page.getByLabel('designer-schema-settings-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
await page.getByLabel('designer-schema-settings-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Column width' }).click();
|
await page.getByRole('menuitem', { name: 'Column width' }).click();
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import { T3686, T4005 } from './templatesOfBug';
|
|||||||
|
|
||||||
const deleteButton = async (page: Page, name: string) => {
|
const deleteButton = async (page: Page, name: string) => {
|
||||||
await page.getByRole('button', { name }).hover();
|
await page.getByRole('button', { name }).hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'Delete' }).waitFor({ state: 'detached' });
|
||||||
await page.getByRole('button', { name }).getByLabel('designer-schema-settings-').hover();
|
await page.getByRole('button', { name }).getByLabel('designer-schema-settings-').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
@ -37,7 +38,8 @@ test.describe('where table block can be added', () => {
|
|||||||
// 添加当前表关系区块
|
// 添加当前表关系区块
|
||||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Table right' }).hover();
|
await page.getByRole('menuitem', { name: 'Table right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
|
await page.getByRole('menuitem', { name: 'childAssociationField' }).waitFor({ state: 'detached' });
|
||||||
|
await page.getByRole('menuitem', { name: 'Associated records' }).last().hover();
|
||||||
await page.getByRole('menuitem', { name: 'childAssociationField' }).click();
|
await page.getByRole('menuitem', { name: 'childAssociationField' }).click();
|
||||||
await page
|
await page
|
||||||
.getByTestId('drawer-Action.Container-childCollection-View record')
|
.getByTestId('drawer-Action.Container-childCollection-View record')
|
||||||
@ -46,9 +48,10 @@ test.describe('where table block can be added', () => {
|
|||||||
await page.getByRole('menuitem', { name: 'childTargetText' }).click();
|
await page.getByRole('menuitem', { name: 'childTargetText' }).click();
|
||||||
|
|
||||||
// 添加父表关系区块
|
// 添加父表关系区块
|
||||||
|
await page.getByRole('menuitem', { name: 'Table right' }).waitFor({ state: 'detached' });
|
||||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Table right' }).hover();
|
await page.getByRole('menuitem', { name: 'Table right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
|
await page.getByRole('menuitem', { name: 'Associated records' }).last().hover();
|
||||||
await page.getByRole('menuitem', { name: 'parentAssociationField' }).click();
|
await page.getByRole('menuitem', { name: 'parentAssociationField' }).click();
|
||||||
await page.getByLabel('schema-initializer-TableV2-table:configureColumns-parentTargetCollection').hover();
|
await page.getByLabel('schema-initializer-TableV2-table:configureColumns-parentTargetCollection').hover();
|
||||||
await page.getByRole('menuitem', { name: 'parentTargetText' }).click();
|
await page.getByRole('menuitem', { name: 'parentTargetText' }).click();
|
||||||
|
@ -87,7 +87,7 @@ test.describe('actions schema settings', () => {
|
|||||||
|
|
||||||
// 切换为 dialog
|
// 切换为 dialog
|
||||||
await page.getByRole('menuitem', { name: 'Open mode' }).click();
|
await page.getByRole('menuitem', { name: 'Open mode' }).click();
|
||||||
await page.getByRole('option', { name: 'Dialog' }).click();
|
await page.getByRole('option', { name: 'Dialog' }).last().click();
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Add new' }).click();
|
await page.getByRole('button', { name: 'Add new' }).click();
|
||||||
await expect(page.getByTestId('modal-Action.Container-general-Add record')).toBeVisible();
|
await expect(page.getByTestId('modal-Action.Container-general-Add record')).toBeVisible();
|
||||||
@ -97,7 +97,7 @@ test.describe('actions schema settings', () => {
|
|||||||
await page.getByLabel('action-Action-Add new-create-').hover();
|
await page.getByLabel('action-Action-Add new-create-').hover();
|
||||||
await page.getByRole('button', { name: 'designer-schema-settings-Action-Action.Designer-general' }).hover();
|
await page.getByRole('button', { name: 'designer-schema-settings-Action-Action.Designer-general' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Open mode Dialog' }).click();
|
await page.getByRole('menuitem', { name: 'Open mode Dialog' }).click();
|
||||||
await page.getByRole('option', { name: 'Page' }).click();
|
await page.getByRole('option', { name: 'Page' }).last().click();
|
||||||
|
|
||||||
// 点击按钮后会跳转到一个页面
|
// 点击按钮后会跳转到一个页面
|
||||||
await page.getByLabel('action-Action-Add new-create-').click();
|
await page.getByLabel('action-Action-Add new-create-').click();
|
||||||
@ -116,7 +116,7 @@ test.describe('actions schema settings', () => {
|
|||||||
|
|
||||||
// 创建一条数据后返回,列表中应该有这条数据
|
// 创建一条数据后返回,列表中应该有这条数据
|
||||||
await page.getByTestId('select-single').click();
|
await page.getByTestId('select-single').click();
|
||||||
await page.getByRole('option', { name: 'option3' }).click();
|
await page.getByRole('option', { name: 'option3' }).last().click();
|
||||||
|
|
||||||
// 提交后会自动返回
|
// 提交后会自动返回
|
||||||
await page.getByLabel('action-Action-Submit-submit-').click();
|
await page.getByLabel('action-Action-Submit-submit-').click();
|
||||||
@ -136,7 +136,7 @@ test.describe('actions schema settings', () => {
|
|||||||
|
|
||||||
// 切换为 small
|
// 切换为 small
|
||||||
await page.getByRole('menuitem', { name: 'Popup size' }).click();
|
await page.getByRole('menuitem', { name: 'Popup size' }).click();
|
||||||
await page.getByRole('option', { name: 'Small' }).click();
|
await page.getByRole('option', { name: 'Small' }).last().click();
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Add new' }).click();
|
await page.getByRole('button', { name: 'Add new' }).click();
|
||||||
const drawerWidth =
|
const drawerWidth =
|
||||||
@ -148,7 +148,7 @@ test.describe('actions schema settings', () => {
|
|||||||
// 切换为 large
|
// 切换为 large
|
||||||
await showMenu(page);
|
await showMenu(page);
|
||||||
await page.getByRole('menuitem', { name: 'Popup size' }).click();
|
await page.getByRole('menuitem', { name: 'Popup size' }).click();
|
||||||
await page.getByRole('option', { name: 'Large' }).click();
|
await page.getByRole('option', { name: 'Large' }).last().click();
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Add new' }).click();
|
await page.getByRole('button', { name: 'Add new' }).click();
|
||||||
const drawerWidth2 =
|
const drawerWidth2 =
|
||||||
@ -325,7 +325,7 @@ test.describe('actions schema settings', () => {
|
|||||||
await page.getByText('Add property').click();
|
await page.getByText('Add property').click();
|
||||||
await page.getByLabel('block-item-ArrayCollapse-general').click();
|
await page.getByLabel('block-item-ArrayCollapse-general').click();
|
||||||
await page.getByTestId('select-linkage-properties').click();
|
await page.getByTestId('select-linkage-properties').click();
|
||||||
await page.getByRole('option', { name: 'Disabled' }).click();
|
await page.getByRole('option', { name: 'Disabled' }).last().click();
|
||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
|
|
||||||
await expect(page.getByLabel('action-Action.Link-View record-view-general-table-0')).toHaveAttribute(
|
await expect(page.getByLabel('action-Action.Link-View record-view-general-table-0')).toHaveAttribute(
|
||||||
@ -336,10 +336,10 @@ test.describe('actions schema settings', () => {
|
|||||||
// 设置第二组规则 --------------------------------------------------------------------------
|
// 设置第二组规则 --------------------------------------------------------------------------
|
||||||
await openLinkageRules();
|
await openLinkageRules();
|
||||||
await page.getByRole('button', { name: 'plus Add linkage rule' }).click();
|
await page.getByRole('button', { name: 'plus Add linkage rule' }).click();
|
||||||
await page.locator('.ant-collapse-header').nth(1).getByRole('img', { name: 'right' }).click();
|
await page.locator('.ant-collapse-header .ant-collapse-expand-icon').nth(1).click();
|
||||||
|
|
||||||
// 添加一个条件:ID 等于 1
|
// 添加一个条件:ID 等于 1
|
||||||
await page.getByRole('tabpanel').getByText('Add condition', { exact: true }).click();
|
await page.getByRole('tabpanel').getByText('Add condition', { exact: true }).last().click();
|
||||||
await page.getByRole('button', { name: 'Select field' }).click();
|
await page.getByRole('button', { name: 'Select field' }).click();
|
||||||
await page.getByRole('menuitemcheckbox', { name: 'ID', exact: true }).click();
|
await page.getByRole('menuitemcheckbox', { name: 'ID', exact: true }).click();
|
||||||
await page.getByRole('spinbutton').click();
|
await page.getByRole('spinbutton').click();
|
||||||
@ -348,7 +348,7 @@ test.describe('actions schema settings', () => {
|
|||||||
// action: 使按钮可用
|
// action: 使按钮可用
|
||||||
await page.getByRole('tabpanel').getByText('Add property').click();
|
await page.getByRole('tabpanel').getByText('Add property').click();
|
||||||
await page.locator('.ant-select', { hasText: 'action' }).click();
|
await page.locator('.ant-select', { hasText: 'action' }).click();
|
||||||
await page.getByRole('option', { name: 'Enabled' }).click();
|
await page.getByRole('option', { name: 'Enabled' }).last().click();
|
||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
|
|
||||||
// 后面的 action 会覆盖前面的
|
// 后面的 action 会覆盖前面的
|
||||||
@ -533,7 +533,7 @@ test.describe('actions schema settings', () => {
|
|||||||
await page.getByLabel('action-Action.Link-View').hover();
|
await page.getByLabel('action-Action.Link-View').hover();
|
||||||
await page.getByLabel('designer-schema-settings-Action.Link-actionSettings:view-users').hover();
|
await page.getByLabel('designer-schema-settings-Action.Link-actionSettings:view-users').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Open mode Drawer' }).click();
|
await page.getByRole('menuitem', { name: 'Open mode Drawer' }).click();
|
||||||
await page.getByRole('option', { name: 'Page' }).click();
|
await page.getByRole('option', { name: 'Page' }).last().click();
|
||||||
|
|
||||||
// 跳转到子页面后,其内容应该和弹窗中的内容一致
|
// 跳转到子页面后,其内容应该和弹窗中的内容一致
|
||||||
await page.getByLabel('action-Action.Link-View').click();
|
await page.getByLabel('action-Action.Link-View').click();
|
||||||
@ -706,7 +706,7 @@ test.describe('actions schema settings', () => {
|
|||||||
.getByRole('button', { name: 'designer-schema-settings-Action.Link-actionSettings:view-roles' })
|
.getByRole('button', { name: 'designer-schema-settings-Action.Link-actionSettings:view-roles' })
|
||||||
.hover();
|
.hover();
|
||||||
await page.getByRole('menuitem', { name: 'Open mode Drawer' }).click();
|
await page.getByRole('menuitem', { name: 'Open mode Drawer' }).click();
|
||||||
await page.getByRole('option', { name: 'Page' }).click();
|
await page.getByRole('option', { name: 'Page' }).last().click();
|
||||||
|
|
||||||
// 点击按钮跳转到子页面
|
// 点击按钮跳转到子页面
|
||||||
await page.getByLabel('action-Action.Link-View role-view-roles-table-admin').click();
|
await page.getByLabel('action-Action.Link-View role-view-roles-table-admin').click();
|
||||||
@ -955,7 +955,7 @@ test.describe('actions schema settings', () => {
|
|||||||
await page.getByLabel('action-Action.Link-Add child-').hover();
|
await page.getByLabel('action-Action.Link-Add child-').hover();
|
||||||
await page.getByLabel('designer-schema-settings-Action.Link-actionSettings:addChild-treeCollection').hover();
|
await page.getByLabel('designer-schema-settings-Action.Link-actionSettings:addChild-treeCollection').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Open mode Drawer' }).click();
|
await page.getByRole('menuitem', { name: 'Open mode Drawer' }).click();
|
||||||
await page.getByRole('option', { name: 'Page' }).click();
|
await page.getByRole('option', { name: 'Page' }).last().click();
|
||||||
|
|
||||||
// open popup with page mode
|
// open popup with page mode
|
||||||
await page.getByLabel('action-Action.Link-Add child-').click();
|
await page.getByLabel('action-Action.Link-Add child-').click();
|
||||||
@ -994,7 +994,7 @@ test.describe('table column schema settings', () => {
|
|||||||
// 1. 关系字段下拉框中应该有数据
|
// 1. 关系字段下拉框中应该有数据
|
||||||
await page.locator('.nb-sub-table-addNew').click();
|
await page.locator('.nb-sub-table-addNew').click();
|
||||||
await page.getByTestId('select-object-multiple').click();
|
await page.getByTestId('select-object-multiple').click();
|
||||||
await expect(page.getByRole('option', { name: record1.singleLineText, exact: true })).toBeVisible();
|
await expect(page.getByRole('option', { name: record1.singleLineText, exact: true }).last()).toBeVisible();
|
||||||
|
|
||||||
// 2. 为该关系字段设置一个数据范围后,下拉框中应该有一个匹配项
|
// 2. 为该关系字段设置一个数据范围后,下拉框中应该有一个匹配项
|
||||||
await page.getByRole('button', { name: 'manyToMany1', exact: true }).hover();
|
await page.getByRole('button', { name: 'manyToMany1', exact: true }).hover();
|
||||||
@ -1009,7 +1009,7 @@ test.describe('table column schema settings', () => {
|
|||||||
await page.reload();
|
await page.reload();
|
||||||
await page.locator('.nb-sub-table-addNew').click();
|
await page.locator('.nb-sub-table-addNew').click();
|
||||||
await page.getByTestId('select-object-multiple').click();
|
await page.getByTestId('select-object-multiple').click();
|
||||||
await expect(page.getByRole('option', { name: record1.singleLineText, exact: true })).toBeVisible();
|
await expect(page.getByRole('option', { name: record1.singleLineText, exact: true }).last()).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fixed column', async ({ page, mockPage }) => {
|
test('fixed column', async ({ page, mockPage }) => {
|
||||||
|
@ -34,7 +34,7 @@ test.describe('table block schema settings', () => {
|
|||||||
'Set the data scope',
|
'Set the data scope',
|
||||||
'Records per page',
|
'Records per page',
|
||||||
'Connect data blocks',
|
'Connect data blocks',
|
||||||
'Save as template',
|
// 'Save as template',
|
||||||
'Delete',
|
'Delete',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
@ -9,8 +9,8 @@
|
|||||||
|
|
||||||
import { expect, test } from '@nocobase/test/e2e';
|
import { expect, test } from '@nocobase/test/e2e';
|
||||||
|
|
||||||
test.describe('save as template', () => {
|
test.skip('save as template', () => {
|
||||||
test('save as template, then delete it', async ({ page, mockPage, clearBlockTemplates }) => {
|
test.skip('save as template, then delete it', async ({ page, mockPage, clearBlockTemplates }) => {
|
||||||
// 1. 创建一个区块,然后保存为模板
|
// 1. 创建一个区块,然后保存为模板
|
||||||
await mockPage().goto();
|
await mockPage().goto();
|
||||||
await page.getByLabel('schema-initializer-Grid-page:').hover();
|
await page.getByLabel('schema-initializer-Grid-page:').hover();
|
||||||
|
@ -25,7 +25,7 @@ test.describe('tree table block schema settings', () => {
|
|||||||
'Set default sorting rules',
|
'Set default sorting rules',
|
||||||
'Records per page',
|
'Records per page',
|
||||||
'Connect data blocks',
|
'Connect data blocks',
|
||||||
'Save as template',
|
// 'Save as template',
|
||||||
'Delete',
|
'Delete',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
@ -35,7 +35,11 @@ test.describe('where filter block can be added', () => {
|
|||||||
|
|
||||||
// 3. 与 Table、Details、List、GridCard 等区块建立连接
|
// 3. 与 Table、Details、List、GridCard 等区块建立连接
|
||||||
const connectByForm = async (name: string) => {
|
const connectByForm = async (name: string) => {
|
||||||
|
await page
|
||||||
|
.getByLabel('designer-schema-settings-CardItem-blockSettings:filterForm-users')
|
||||||
|
.waitFor({ state: 'hidden' });
|
||||||
await page.getByLabel('block-item-CardItem-users-filter-form').hover();
|
await page.getByLabel('block-item-CardItem-users-filter-form').hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'Connect data blocks right' }).waitFor({ state: 'detached' });
|
||||||
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:filterForm-users').hover();
|
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:filterForm-users').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Connect data blocks right' }).hover();
|
await page.getByRole('menuitem', { name: 'Connect data blocks right' }).hover();
|
||||||
await page.getByRole('menuitem', { name }).click();
|
await page.getByRole('menuitem', { name }).click();
|
||||||
@ -43,6 +47,7 @@ test.describe('where filter block can be added', () => {
|
|||||||
const connectByCollapse = async (name: string) => {
|
const connectByCollapse = async (name: string) => {
|
||||||
await page.mouse.move(-500, 0);
|
await page.mouse.move(-500, 0);
|
||||||
await page.getByLabel('block-item-CardItem-users-filter-collapse').hover();
|
await page.getByLabel('block-item-CardItem-users-filter-collapse').hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'Connect data blocks right' }).waitFor({ state: 'detached' });
|
||||||
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:filterCollapse-users').hover();
|
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:filterCollapse-users').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Connect data blocks right' }).hover();
|
await page.getByRole('menuitem', { name: 'Connect data blocks right' }).hover();
|
||||||
await page.getByRole('menuitem', { name }).click();
|
await page.getByRole('menuitem', { name }).click();
|
||||||
@ -150,7 +155,9 @@ test.describe('where filter block can be added', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 测试用表单筛选其它区块
|
// 2. 测试用表单筛选其它区块
|
||||||
|
await page.getByRole('menuitem', { name: 'Form right' }).waitFor({ state: 'detached' });
|
||||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'Users' }).waitFor({ state: 'detached' });
|
||||||
await page.getByRole('menuitem', { name: 'Form right' }).hover();
|
await page.getByRole('menuitem', { name: 'Form right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Users' }).click();
|
await page.getByRole('menuitem', { name: 'Users' }).click();
|
||||||
await page.getByLabel('schema-initializer-Grid-filterForm:configureFields-users').hover();
|
await page.getByLabel('schema-initializer-Grid-filterForm:configureFields-users').hover();
|
||||||
|
@ -25,7 +25,7 @@ test.describe('collapse schema settings', () => {
|
|||||||
await page.getByLabel('block-item-CardItem-general-filter-collapse').hover();
|
await page.getByLabel('block-item-CardItem-general-filter-collapse').hover();
|
||||||
await page.getByLabel('designer-schema-settings-CardItem-AssociationFilter.BlockDesigner-general').hover();
|
await page.getByLabel('designer-schema-settings-CardItem-AssociationFilter.BlockDesigner-general').hover();
|
||||||
},
|
},
|
||||||
supportedOptions: ['Edit block title', 'Save as template', 'Connect data blocks', 'Delete'],
|
supportedOptions: ['Edit block title', 'Connect data blocks', 'Delete'],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ test.describe('collapse schema settings', () => {
|
|||||||
await page.getByRole('menuitem', { name: 'General' }).click();
|
await page.getByRole('menuitem', { name: 'General' }).click();
|
||||||
|
|
||||||
// 点击一个选项,进行筛选
|
// 点击一个选项,进行筛选
|
||||||
await page.getByRole('button', { name: 'right singleSelect search' }).click();
|
await page.getByRole('button', { name: 'collapsed singleSelect search' }).click();
|
||||||
await page.getByLabel('block-item-CardItem-general-filter-collapse').getByText('Option1').click();
|
await page.getByLabel('block-item-CardItem-general-filter-collapse').getByText('Option1').click();
|
||||||
|
|
||||||
// 注意:在本地运行时,由于运行结束后不会清空之前创建的数据,所以在第一次运行之后,下面会报错。如果遇到这种情况,可以先不管
|
// 注意:在本地运行时,由于运行结束后不会清空之前创建的数据,所以在第一次运行之后,下面会报错。如果遇到这种情况,可以先不管
|
||||||
|
@ -126,7 +126,7 @@ test.describe('filter form', () => {
|
|||||||
y: 10,
|
y: 10,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data').last()).toBeVisible();
|
||||||
|
|
||||||
// 4. 此时点击 Reset 按钮,应该只显示一条数据,因为会把 nickname 的值重置为 {{$user.nickname}}
|
// 4. 此时点击 Reset 按钮,应该只显示一条数据,因为会把 nickname 的值重置为 {{$user.nickname}}
|
||||||
await page.getByLabel('action-Action-Reset-users-').click({
|
await page.getByLabel('action-Action-Reset-users-').click({
|
||||||
@ -152,6 +152,6 @@ test.describe('filter form', () => {
|
|||||||
y: 10,
|
y: 10,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data').last()).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -28,7 +28,7 @@ test.describe('filter block schema settings', () => {
|
|||||||
},
|
},
|
||||||
supportedOptions: [
|
supportedOptions: [
|
||||||
'Edit block title',
|
'Edit block title',
|
||||||
'Save as block template',
|
// 'Save as block template',
|
||||||
'Linkage rules',
|
'Linkage rules',
|
||||||
'Connect data blocks',
|
'Connect data blocks',
|
||||||
'Delete',
|
'Delete',
|
||||||
@ -37,7 +37,7 @@ test.describe('filter block schema settings', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.describe('connect data blocks', () => {
|
test.describe('connect data blocks', () => {
|
||||||
test('connecting two blocks of the same collection', async ({
|
test.skip('connecting two blocks of the same collection', async ({
|
||||||
page,
|
page,
|
||||||
mockPage,
|
mockPage,
|
||||||
mockRecords,
|
mockRecords,
|
||||||
|
@ -53,7 +53,7 @@ test.describe('AssociationSelect ', () => {
|
|||||||
.getByLabel('block-item-CollectionField-test-form-test.b-b')
|
.getByLabel('block-item-CollectionField-test-form-test.b-b')
|
||||||
.getByTestId('select-object-multiple')
|
.getByTestId('select-object-multiple')
|
||||||
.click();
|
.click();
|
||||||
await expect(page.getByText('No data')).toBeVisible();
|
await expect(page.getByText('No data').last()).toBeVisible();
|
||||||
|
|
||||||
// 2. 当给字段 a 选择一个值后,字段 b 的下拉列表中会显示符合条件的值
|
// 2. 当给字段 a 选择一个值后,字段 b 的下拉列表中会显示符合条件的值
|
||||||
await page
|
await page
|
||||||
|
@ -34,6 +34,6 @@ test.describe('data scope of component Select', () => {
|
|||||||
await page.getByTestId('select-object-multiple').click();
|
await page.getByTestId('select-object-multiple').click();
|
||||||
await expect(page.getByRole('option', { name: 'admin' })).toBeHidden();
|
await expect(page.getByRole('option', { name: 'admin' })).toBeHidden();
|
||||||
await expect(page.getByRole('option', { name: 'member' })).toBeHidden();
|
await expect(page.getByRole('option', { name: 'member' })).toBeHidden();
|
||||||
await expect(page.getByText('No data')).toBeVisible();
|
await expect(page.getByText('No data').last()).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -63,9 +63,11 @@ test.describe('page schema settings', () => {
|
|||||||
await page.getByLabel('block-item-Input-Tab name').getByRole('textbox').fill('new tab');
|
await page.getByLabel('block-item-Input-Tab name').getByRole('textbox').fill('new tab');
|
||||||
// 选择一个图标
|
// 选择一个图标
|
||||||
await page.getByRole('button', { name: 'Select icon' }).click();
|
await page.getByRole('button', { name: 'Select icon' }).click();
|
||||||
|
await expect(page.getByLabel('account-book').locator('svg')).toHaveCount(1);
|
||||||
await page.getByLabel('account-book').locator('svg').click();
|
await page.getByLabel('account-book').locator('svg').click();
|
||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
await expect(page.getByText('new tab')).toBeVisible();
|
await expect(page.getByText('new tab')).toBeVisible();
|
||||||
|
await expect(page.getByLabel('account-book').locator('svg')).toHaveCount(1);
|
||||||
await expect(page.getByLabel('account-book').locator('svg')).toBeVisible();
|
await expect(page.getByLabel('account-book').locator('svg')).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -92,10 +94,12 @@ test.describe('tabs schema settings', () => {
|
|||||||
await page.getByLabel('block-item-Input-Tab name').getByRole('textbox').click();
|
await page.getByLabel('block-item-Input-Tab name').getByRole('textbox').click();
|
||||||
await page.getByLabel('block-item-Input-Tab name').getByRole('textbox').fill('new name of page tab');
|
await page.getByLabel('block-item-Input-Tab name').getByRole('textbox').fill('new name of page tab');
|
||||||
await page.getByRole('button', { name: 'Select icon' }).click();
|
await page.getByRole('button', { name: 'Select icon' }).click();
|
||||||
|
await expect(page.getByLabel('account-book').locator('svg')).toHaveCount(1);
|
||||||
await page.getByLabel('account-book').locator('svg').click();
|
await page.getByLabel('account-book').locator('svg').click();
|
||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
|
|
||||||
await expect(page.getByText('new name of page tab')).toBeVisible();
|
await expect(page.getByText('new name of page tab')).toBeVisible();
|
||||||
|
await expect(page.getByLabel('account-book').locator('svg')).toHaveCount(1);
|
||||||
await expect(page.getByLabel('account-book').locator('svg')).toBeVisible();
|
await expect(page.getByLabel('account-book').locator('svg')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ test.describe('add blocks to the popup', () => {
|
|||||||
// 通过 Association records 创建一个关系区块
|
// 通过 Association records 创建一个关系区块
|
||||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Details right' }).hover();
|
await page.getByRole('menuitem', { name: 'Details right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
|
await page.getByRole('menuitem', { name: 'Associated records' }).last().hover();
|
||||||
await page.getByRole('menuitem', { name: 'Roles' }).click();
|
await page.getByRole('menuitem', { name: 'Roles' }).click();
|
||||||
await page.getByLabel('schema-initializer-Grid-details:configureFields-roles').hover();
|
await page.getByLabel('schema-initializer-Grid-details:configureFields-roles').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Role UID' }).click();
|
await page.getByRole('menuitem', { name: 'Role UID' }).click();
|
||||||
@ -87,7 +87,7 @@ test.describe('add blocks to the popup', () => {
|
|||||||
// 通过 Association records 创建关系区块
|
// 通过 Association records 创建关系区块
|
||||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Table right' }).hover();
|
await page.getByRole('menuitem', { name: 'Table right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
|
await page.getByRole('menuitem', { name: 'Associated records' }).last().hover();
|
||||||
await page.getByRole('menuitem', { name: 'manyToMany' }).click();
|
await page.getByRole('menuitem', { name: 'manyToMany' }).click();
|
||||||
await page.mouse.move(-300, 0);
|
await page.mouse.move(-300, 0);
|
||||||
await page
|
await page
|
||||||
@ -135,7 +135,7 @@ test.describe('add blocks to the popup', () => {
|
|||||||
// 通过 Association records 创建一个关系区块
|
// 通过 Association records 创建一个关系区块
|
||||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Table right' }).hover();
|
await page.getByRole('menuitem', { name: 'Table right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
|
await page.getByRole('menuitem', { name: 'Associated records' }).last().hover();
|
||||||
await page.getByRole('menuitem', { name: 'Roles' }).click();
|
await page.getByRole('menuitem', { name: 'Roles' }).click();
|
||||||
await page
|
await page
|
||||||
.getByTestId('drawer-Action.Container-users-View record')
|
.getByTestId('drawer-Action.Container-users-View record')
|
||||||
|
@ -124,10 +124,13 @@ test.describe('where to open a popup and what can be added to it', () => {
|
|||||||
|
|
||||||
async function addBlock(names: string[]) {
|
async function addBlock(names: string[]) {
|
||||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||||
|
await page.waitForTimeout(500);
|
||||||
for (let i = 0; i < names.length - 1; i++) {
|
for (let i = 0; i < names.length - 1; i++) {
|
||||||
const name = names[i];
|
const name = names[i];
|
||||||
await page.getByRole('menuitem', { name }).hover();
|
await page.getByRole('menuitem', { name }).hover();
|
||||||
|
await page.waitForTimeout(500);
|
||||||
}
|
}
|
||||||
|
await expect(page.getByRole('menuitem', { name: names[names.length - 1] })).toHaveCount(1);
|
||||||
await page.getByRole('menuitem', { name: names[names.length - 1] }).click();
|
await page.getByRole('menuitem', { name: names[names.length - 1] }).click();
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
}
|
}
|
||||||
@ -206,13 +209,14 @@ test.describe('where to open a popup and what can be added to it', () => {
|
|||||||
|
|
||||||
await page.getByLabel('schema-initializer-Grid-popup:common:addBlock-general').hover();
|
await page.getByLabel('schema-initializer-Grid-popup:common:addBlock-general').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Details' }).hover();
|
await page.getByRole('menuitem', { name: 'Details' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
|
await page.getByRole('menuitem', { name: 'Associated records' }).last().hover();
|
||||||
await page.getByRole('menuitem', { name: 'Many to one' }).click();
|
await page.getByRole('menuitem', { name: 'Many to one' }).click();
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-')).toBeVisible();
|
||||||
|
|
||||||
await page.getByLabel('schema-initializer-Grid-popup:common:addBlock-general').hover();
|
await page.getByLabel('schema-initializer-Grid-popup:common:addBlock-general').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Table right' }).hover();
|
await page.getByRole('menuitem', { name: 'Table right' }).hover();
|
||||||
|
await expect(page.getByRole('menuitem', { name: 'Associated records' })).toHaveCount(1);
|
||||||
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
|
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'One to many' }).click();
|
await page.getByRole('menuitem', { name: 'One to many' }).click();
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
@ -272,13 +276,14 @@ test.describe('where to open a popup and what can be added to it', () => {
|
|||||||
|
|
||||||
await page.getByLabel('schema-initializer-Grid-popup:common:addBlock-general').hover();
|
await page.getByLabel('schema-initializer-Grid-popup:common:addBlock-general').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Details' }).hover();
|
await page.getByRole('menuitem', { name: 'Details' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
|
await page.getByRole('menuitem', { name: 'Associated records' }).last().hover();
|
||||||
await page.getByRole('menuitem', { name: 'Many to one' }).click();
|
await page.getByRole('menuitem', { name: 'Many to one' }).click();
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-')).toBeVisible();
|
||||||
|
|
||||||
await page.getByLabel('schema-initializer-Grid-popup:common:addBlock-general').hover();
|
await page.getByLabel('schema-initializer-Grid-popup:common:addBlock-general').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Table right' }).hover();
|
await page.getByRole('menuitem', { name: 'Table right' }).hover();
|
||||||
|
await expect(page.getByRole('menuitem', { name: 'Associated records' })).toHaveCount(1);
|
||||||
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
|
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'One to many' }).click();
|
await page.getByRole('menuitem', { name: 'One to many' }).click();
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
|
@ -44,6 +44,7 @@ test.describe('tabs schema settings', () => {
|
|||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
|
|
||||||
await expect(page.getByText('Add new with new name')).toBeVisible();
|
await expect(page.getByText('Add new with new name')).toBeVisible();
|
||||||
|
await expect(page.getByLabel('account-book').locator('svg')).toHaveCount(1);
|
||||||
await expect(page.getByLabel('account-book').locator('svg')).toBeVisible();
|
await expect(page.getByLabel('account-book').locator('svg')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ test.describe('variable: parent object', () => {
|
|||||||
|
|
||||||
// 1. Use "Current form" and "Parent object" variables in nested subforms and subtables
|
// 1. Use "Current form" and "Parent object" variables in nested subforms and subtables
|
||||||
await page.getByLabel('block-item-CollectionField-collection1-form-collection1.m2m1-m2m1').hover();
|
await page.getByLabel('block-item-CollectionField-collection1-form-collection1.m2m1-m2m1').hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'Linkage rules' }).waitFor({ state: 'detached' });
|
||||||
await page
|
await page
|
||||||
.getByLabel('designer-schema-settings-CollectionField-fieldSettings:FormItem-collection1-collection1.m2m1', {
|
.getByLabel('designer-schema-settings-CollectionField-fieldSettings:FormItem-collection1-collection1.m2m1', {
|
||||||
exact: true,
|
exact: true,
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
export * from './PluginManagerLink';
|
export * from './PluginManagerLink';
|
||||||
import { PageHeader } from '@ant-design/pro-layout';
|
import { PageHeader } from '@ant-design/pro-layout';
|
||||||
import { useDebounce } from 'ahooks';
|
import { useDebounce } from 'ahooks';
|
||||||
import { Button, Col, Divider, Input, List, Modal, Result, Row, Space, Spin, Table, Tabs } from 'antd';
|
import { Button, Col, Divider, Input, List, Modal, Result, Row, Space, Spin, Table, Tabs, TableProps } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -127,7 +127,8 @@ function BulkEnableButton({ plugins = [] }) {
|
|||||||
}}
|
}}
|
||||||
size={'small'}
|
size={'small'}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
columns={[
|
columns={
|
||||||
|
[
|
||||||
{
|
{
|
||||||
title: t('Plugin'),
|
title: t('Plugin'),
|
||||||
dataIndex: 'displayName',
|
dataIndex: 'displayName',
|
||||||
@ -145,7 +146,8 @@ function BulkEnableButton({ plugins = [] }) {
|
|||||||
width: 300,
|
width: 300,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
},
|
},
|
||||||
]}
|
] as TableProps['columns']
|
||||||
|
}
|
||||||
dataSource={items}
|
dataSource={items}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -13,6 +13,7 @@ import React, { useEffect, useMemo } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useApp, useNavigateNoUpdate } from '../application';
|
import { useApp, useNavigateNoUpdate } from '../application';
|
||||||
|
import { useMobileLayout } from '../route-switch/antd/admin-layout';
|
||||||
import { useCompile } from '../schema-component';
|
import { useCompile } from '../schema-component';
|
||||||
import { useToken } from '../style';
|
import { useToken } from '../style';
|
||||||
|
|
||||||
@ -20,6 +21,12 @@ export const PluginManagerLink = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigateNoUpdate();
|
const navigate = useNavigateNoUpdate();
|
||||||
const { token } = useToken();
|
const { token } = useToken();
|
||||||
|
const { isMobileLayout } = useMobileLayout();
|
||||||
|
|
||||||
|
if (isMobileLayout) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={t('Plugin manager')}>
|
<Tooltip title={t('Plugin manager')}>
|
||||||
<Button
|
<Button
|
||||||
@ -62,6 +69,12 @@ export const SettingsCenterDropdown = () => {
|
|||||||
};
|
};
|
||||||
}, [app.pluginSettingsManager]);
|
}, [app.pluginSettingsManager]);
|
||||||
|
|
||||||
|
const { isMobileLayout } = useMobileLayout();
|
||||||
|
|
||||||
|
if (isMobileLayout) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
menu={{
|
menu={{
|
||||||
|
@ -29,12 +29,13 @@ export class PMPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addSettings() {
|
addSettings() {
|
||||||
this.app.pluginSettingsManager.add('ui-schema-storage', {
|
// hide old block template settings page
|
||||||
title: '{{t("Block templates")}}',
|
// this.app.pluginSettingsManager.add('ui-schema-storage', {
|
||||||
icon: 'LayoutOutlined',
|
// title: '{{t("Block templates")}}',
|
||||||
Component: BlockTemplatesPane,
|
// icon: 'LayoutOutlined',
|
||||||
aclSnippet: 'pm.ui-schema-storage.block-templates',
|
// Component: BlockTemplatesPane,
|
||||||
});
|
// aclSnippet: 'pm.ui-schema-storage.block-templates',
|
||||||
|
// });
|
||||||
this.app.pluginSettingsManager.add('system-settings', {
|
this.app.pluginSettingsManager.add('system-settings', {
|
||||||
icon: 'SettingOutlined',
|
icon: 'SettingOutlined',
|
||||||
title: '{{t("System settings")}}',
|
title: '{{t("System settings")}}',
|
||||||
|
@ -11,7 +11,7 @@ import { EllipsisOutlined } from '@ant-design/icons';
|
|||||||
import ProLayout, { RouteContext, RouteContextType } from '@ant-design/pro-layout';
|
import ProLayout, { RouteContext, RouteContextType } from '@ant-design/pro-layout';
|
||||||
import { HeaderViewProps } from '@ant-design/pro-layout/es/components/Header';
|
import { HeaderViewProps } from '@ant-design/pro-layout/es/components/Header';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { Popover, Tooltip } from 'antd';
|
import { theme as antdTheme, ConfigProvider, Popover, Tooltip } from 'antd';
|
||||||
import React, { createContext, FC, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { createContext, FC, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Link, Navigate, Outlet, useLocation, useNavigate } from 'react-router-dom';
|
import { Link, Navigate, Outlet, useLocation, useNavigate } from 'react-router-dom';
|
||||||
@ -29,6 +29,7 @@ import {
|
|||||||
RemoteSchemaTemplateManagerProvider,
|
RemoteSchemaTemplateManagerProvider,
|
||||||
SortableItem,
|
SortableItem,
|
||||||
useDesignable,
|
useDesignable,
|
||||||
|
useGlobalTheme,
|
||||||
useMenuDragEnd,
|
useMenuDragEnd,
|
||||||
useParseURLAndParams,
|
useParseURLAndParams,
|
||||||
useRequest,
|
useRequest,
|
||||||
@ -153,7 +154,10 @@ const layoutContentClass = css`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
/* 基础高度(所有浏览器支持) */
|
||||||
height: calc(100vh - var(--nb-header-height));
|
height: calc(100vh - var(--nb-header-height));
|
||||||
|
/* 动态视口高度(现代浏览器支持) */
|
||||||
|
height: calc(100dvh - var(--nb-header-height));
|
||||||
> div {
|
> div {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@ -496,22 +500,44 @@ const headerRender = (props: HeaderViewProps, defaultDom: React.ReactNode) => {
|
|||||||
return <headerContext.Provider value={headerContextValue}>{defaultDom}</headerContext.Provider>;
|
return <headerContext.Provider value={headerContextValue}>{defaultDom}</headerContext.Provider>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const IsMobileLayoutContext = React.createContext<{
|
||||||
|
isMobileLayout: boolean;
|
||||||
|
setIsMobileLayout: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
}>({
|
||||||
|
isMobileLayout: false,
|
||||||
|
setIsMobileLayout: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const MobileLayoutProvider: FC = (props) => {
|
||||||
|
const [isMobileLayout, setIsMobileLayout] = useState(false);
|
||||||
|
const value = useMemo(() => ({ isMobileLayout, setIsMobileLayout }), [isMobileLayout]);
|
||||||
|
|
||||||
|
return <IsMobileLayoutContext.Provider value={value}>{props.children}</IsMobileLayoutContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useMobileLayout = () => {
|
||||||
|
const { isMobileLayout, setIsMobileLayout } = useContext(IsMobileLayoutContext);
|
||||||
|
return { isMobileLayout, setIsMobileLayout };
|
||||||
|
};
|
||||||
|
|
||||||
export const InternalAdminLayout = () => {
|
export const InternalAdminLayout = () => {
|
||||||
const { allAccessRoutes } = useAllAccessDesktopRoutes();
|
const { allAccessRoutes } = useAllAccessDesktopRoutes();
|
||||||
const { designable } = useDesignable();
|
const { designable: _designable } = useDesignable();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { onDragEnd } = useMenuDragEnd();
|
const { onDragEnd } = useMenuDragEnd();
|
||||||
const { token } = useToken();
|
const { token } = useToken();
|
||||||
const [isMobile, setIsMobile] = useState(false);
|
const { isMobileLayout, setIsMobileLayout } = useMobileLayout();
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(isMobileLayout);
|
||||||
const doNotChangeCollapsedRef = useRef(false);
|
const doNotChangeCollapsedRef = useRef(false);
|
||||||
const { t } = useMenuTranslation();
|
const { t } = useMenuTranslation();
|
||||||
|
const designable = isMobileLayout ? false : _designable;
|
||||||
|
|
||||||
const route = useMemo(() => {
|
const route = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
path: '/',
|
path: '/',
|
||||||
children: convertRoutesToLayout(allAccessRoutes, { designable, isMobile, t }),
|
children: convertRoutesToLayout(allAccessRoutes, { designable, isMobile: isMobileLayout, t }),
|
||||||
};
|
};
|
||||||
}, [allAccessRoutes, designable, isMobile, t]);
|
}, [allAccessRoutes, designable, isMobileLayout, t]);
|
||||||
const layoutToken = useMemo(() => {
|
const layoutToken = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
header: {
|
header: {
|
||||||
@ -535,6 +561,21 @@ export const InternalAdminLayout = () => {
|
|||||||
bgLayout: token.colorBgLayout,
|
bgLayout: token.colorBgLayout,
|
||||||
};
|
};
|
||||||
}, [token]);
|
}, [token]);
|
||||||
|
const { theme, isDarkTheme } = useGlobalTheme();
|
||||||
|
const mobileTheme = useMemo(() => {
|
||||||
|
return {
|
||||||
|
...theme,
|
||||||
|
token: {
|
||||||
|
...theme.token,
|
||||||
|
paddingPageHorizontal: 8, // Horizontal page padding
|
||||||
|
paddingPageVertical: 8, // Vertical page padding
|
||||||
|
marginBlock: 12, // Spacing between blocks
|
||||||
|
borderRadiusBlock: 8, // Block border radius
|
||||||
|
fontSize: 14, // Font size
|
||||||
|
},
|
||||||
|
algorithm: isDarkTheme ? [antdTheme.compactAlgorithm, antdTheme.darkAlgorithm] : antdTheme.compactAlgorithm, // Set mobile mode to always use compact algorithm
|
||||||
|
};
|
||||||
|
}, [theme, isDarkTheme]);
|
||||||
|
|
||||||
const onCollapse = useCallback((collapsed: boolean) => {
|
const onCollapse = useCallback((collapsed: boolean) => {
|
||||||
if (doNotChangeCollapsedRef.current) {
|
if (doNotChangeCollapsedRef.current) {
|
||||||
@ -576,11 +617,15 @@ export const InternalAdminLayout = () => {
|
|||||||
{(value: RouteContextType) => {
|
{(value: RouteContextType) => {
|
||||||
const { isMobile: _isMobile } = value;
|
const { isMobile: _isMobile } = value;
|
||||||
|
|
||||||
if (_isMobile !== isMobile) {
|
if (_isMobile !== isMobileLayout) {
|
||||||
setIsMobile(_isMobile);
|
setIsMobileLayout(_isMobile);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <LayoutContent />;
|
return (
|
||||||
|
<ConfigProvider theme={_isMobile ? mobileTheme : theme}>
|
||||||
|
<LayoutContent />
|
||||||
|
</ConfigProvider>
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
</RouteContext.Consumer>
|
</RouteContext.Consumer>
|
||||||
</ProLayout>
|
</ProLayout>
|
||||||
@ -697,6 +742,7 @@ export class AdminLayoutPlugin extends Plugin {
|
|||||||
async load() {
|
async load() {
|
||||||
this.app.schemaSettingsManager.add(userCenterSettings);
|
this.app.schemaSettingsManager.add(userCenterSettings);
|
||||||
this.app.addComponents({ AdminLayout, AdminDynamicPage });
|
this.app.addComponents({ AdminLayout, AdminDynamicPage });
|
||||||
|
this.app.use(MobileLayoutProvider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -718,36 +764,6 @@ export function findRouteBySchemaUid(schemaUid: string, treeArray: any[]) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MenuItemIcon: FC<{ icon: string; title: string }> = (props) => {
|
|
||||||
const { inHeader } = useContext(headerContext);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RouteContext.Consumer>
|
|
||||||
{(value: RouteContextType) => {
|
|
||||||
const { collapsed } = value;
|
|
||||||
|
|
||||||
if (collapsed && !inHeader) {
|
|
||||||
return props.icon ? (
|
|
||||||
<Icon type={props.icon} />
|
|
||||||
) : (
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
display: 'inline-block',
|
|
||||||
width: '100%',
|
|
||||||
textAlign: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.title.charAt(0)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return props.icon ? <Icon type={props.icon} /> : null;
|
|
||||||
}}
|
|
||||||
</RouteContext.Consumer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const MenuDesignerButton: FC<{ testId: string }> = (props) => {
|
const MenuDesignerButton: FC<{ testId: string }> = (props) => {
|
||||||
const { render: renderInitializer } = useSchemaInitializerRender(menuItemInitializer);
|
const { render: renderInitializer } = useSchemaInitializerRender(menuItemInitializer);
|
||||||
|
|
||||||
@ -767,7 +783,7 @@ const MenuTitleWithIcon: FC<{ icon: any; title: string }> = (props) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return props.title;
|
return <>{props.title}</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function convertRoutesToLayout(
|
function convertRoutesToLayout(
|
||||||
|
@ -12,15 +12,15 @@ import React from 'react';
|
|||||||
import { useActionContext } from '.';
|
import { useActionContext } from '.';
|
||||||
import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField';
|
import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField';
|
||||||
import { useOpenModeContext } from '../../../modules/popup/OpenModeProvider';
|
import { useOpenModeContext } from '../../../modules/popup/OpenModeProvider';
|
||||||
import { ComposedActionDrawer } from './types';
|
|
||||||
import { ActionDrawer } from './Action.Drawer';
|
import { ActionDrawer } from './Action.Drawer';
|
||||||
|
import { ComposedActionDrawer } from './types';
|
||||||
|
|
||||||
const PopupLevelContext = React.createContext(0);
|
const PopupLevelContext = React.createContext(0);
|
||||||
|
|
||||||
export const ActionContainer: ComposedActionDrawer = observer(
|
export const ActionContainer: ComposedActionDrawer = observer(
|
||||||
(props: any) => {
|
(props: any) => {
|
||||||
const { getComponentByOpenMode, defaultOpenMode } = useOpenModeContext() || {};
|
const { getComponentByOpenMode, defaultOpenMode } = useOpenModeContext() || {};
|
||||||
const { openMode = defaultOpenMode } = useActionContext();
|
const { openMode = props.openMode || defaultOpenMode } = useActionContext();
|
||||||
const popupLevel = React.useContext(PopupLevelContext);
|
const popupLevel = React.useContext(PopupLevelContext);
|
||||||
const currentLevel = popupLevel + 1;
|
const currentLevel = popupLevel + 1;
|
||||||
|
|
||||||
|
@ -187,6 +187,13 @@ export function AssignedFieldValues() {
|
|||||||
'x-component': 'Grid',
|
'x-component': 'Grid',
|
||||||
'x-initializer': 'assignFieldValuesForm:configureFields',
|
'x-initializer': 'assignFieldValuesForm:configureFields',
|
||||||
};
|
};
|
||||||
|
if (fieldSchema['x-template-uid']) {
|
||||||
|
initialSchema['x-template-root-ref'] = {
|
||||||
|
'x-template-uid': fieldSchema['x-template-uid'],
|
||||||
|
'x-path': 'x-action-settings.schemaUid',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const tips = {
|
const tips = {
|
||||||
'customize:update': t(
|
'customize:update': t(
|
||||||
'After clicking the custom button, the following fields of the current record will be saved according to the following form.',
|
'After clicking the custom button, the following fields of the current record will be saved according to the following form.',
|
||||||
|
@ -25,7 +25,7 @@ export const useStyles = genStyleHook('nb-action-drawer', (token) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
'&.nb-record-picker-selector': {
|
'&.nb-record-picker-selector': {
|
||||||
'.ant-drawer-wrapper-body': {
|
'.ant-drawer-content': {
|
||||||
backgroundColor: token.colorBgLayout,
|
backgroundColor: token.colorBgLayout,
|
||||||
},
|
},
|
||||||
'.nb-block-item': {
|
'.nb-block-item': {
|
||||||
|
@ -179,4 +179,17 @@ ActionModal.Footer = observer(
|
|||||||
{ displayName: 'ActionModal.Footer' },
|
{ displayName: 'ActionModal.Footer' },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ActionModal.FootBar = observer(
|
||||||
|
() => {
|
||||||
|
const field = useField();
|
||||||
|
const schema = useFieldSchema();
|
||||||
|
return (
|
||||||
|
<div className="ant-modal-footer">
|
||||||
|
<NocoBaseRecursionField basePath={field.address} schema={schema} onlyRenderProperties />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{ displayName: 'ActionModal.FootBar' },
|
||||||
|
);
|
||||||
|
|
||||||
export default ActionModal;
|
export default ActionModal;
|
||||||
|
@ -20,7 +20,8 @@ export const useActionPageStyle = genStyleHook('nb-action-page', (token) => {
|
|||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
backgroundColor: token.colorBgLayout,
|
backgroundColor: token.colorBgLayout,
|
||||||
overflow: 'auto',
|
overflowX: 'hidden',
|
||||||
|
overflowY: 'auto',
|
||||||
|
|
||||||
'.ant-tabs-nav': {
|
'.ant-tabs-nav': {
|
||||||
background: token.colorBgContainer,
|
background: token.colorBgContainer,
|
||||||
|
@ -34,6 +34,9 @@ const useStyles = genStyleHook('nb-action', (token) => {
|
|||||||
background: 'var(--colorBgSettingsHover)',
|
background: 'var(--colorBgSettingsHover)',
|
||||||
border: '0',
|
border: '0',
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
|
'&.nb-in-template': {
|
||||||
|
background: 'var(--colorTemplateBgSettingsHover)',
|
||||||
|
},
|
||||||
'> .general-schema-designer-icons': {
|
'> .general-schema-designer-icons': {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
right: '2px',
|
right: '2px',
|
||||||
|
@ -18,7 +18,7 @@ export function useSetAriaLabelForDrawer(visible: boolean) {
|
|||||||
if (visible) {
|
if (visible) {
|
||||||
// 因为 Action 是点击后渲染内容,所以需要延迟一下
|
// 因为 Action 是点击后渲染内容,所以需要延迟一下
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const wrappers = [...document.querySelectorAll('.ant-drawer-wrapper-body')];
|
const wrappers = [...document.querySelectorAll('.ant-drawer-body')];
|
||||||
const masks = [...document.querySelectorAll('.ant-drawer-mask')];
|
const masks = [...document.querySelectorAll('.ant-drawer-mask')];
|
||||||
// 如果存在多个 mask,最后一个 mask 为当前打开的 mask;wrapper 也是同理
|
// 如果存在多个 mask,最后一个 mask 为当前打开的 mask;wrapper 也是同理
|
||||||
const currentMask = masks[masks.length - 1];
|
const currentMask = masks[masks.length - 1];
|
||||||
|
@ -7,12 +7,13 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CloseCircleFilled } from '@ant-design/icons';
|
import { CloseCircleFilled, DownOutlined } from '@ant-design/icons';
|
||||||
import { Tag, TreeSelect } from 'antd';
|
import { Tag, TreeSelect } from 'antd';
|
||||||
import type { DefaultOptionType, TreeSelectProps } from 'rc-tree-select/es/TreeSelect';
|
import type { TreeSelectProps } from 'rc-tree-select/es/TreeSelect';
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { CollectionFieldOptions_deprecated, parseCollectionName, useApp, useCompile } from '../../..';
|
import { CollectionFieldOptions_deprecated, parseCollectionName, useApp, useCompile } from '../../..';
|
||||||
|
import { DefaultOptionType } from 'antd/es/select';
|
||||||
|
|
||||||
export type AppendsTreeSelectProps = {
|
export type AppendsTreeSelectProps = {
|
||||||
value: string[] | string;
|
value: string[] | string;
|
||||||
@ -261,6 +262,7 @@ export const AppendsTreeSelect: React.FC<TreeSelectProps & AppendsTreeSelectProp
|
|||||||
treeData={treeData}
|
treeData={treeData}
|
||||||
loadData={loadData}
|
loadData={loadData}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
|
suffixIcon={<DownOutlined />}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
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