From 666c92d524da14db934f87f393dd0bc5ff4853ac Mon Sep 17 00:00:00 2001 From: Katherine Date: Thu, 7 Nov 2024 15:21:13 +0800 Subject: [PATCH 1/5] fix: issue with fuzzy search support for association fields with string-type title field (#5611) --- .../schema-component/antd/remote-select/RemoteSelect.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/core/client/src/schema-component/antd/remote-select/RemoteSelect.tsx b/packages/core/client/src/schema-component/antd/remote-select/RemoteSelect.tsx index 86f521c188..8597efcd33 100644 --- a/packages/core/client/src/schema-component/antd/remote-select/RemoteSelect.tsx +++ b/packages/core/client/src/schema-component/antd/remote-select/RemoteSelect.tsx @@ -82,8 +82,12 @@ const InternalRemoteSelect = connect( const operator = useMemo(() => { if (targetField?.interface) { - const initialOperator = getInterface(targetField.interface)?.filterable?.operators[0].value || '$includes'; - return initialOperator !== '$eq' ? initialOperator : '$includes'; + const targetInterface = getInterface(targetField.interface); + const initialOperator = targetInterface?.filterable?.operators[0].value || '$includes'; + if (targetField.type === 'string') { + return '$includes'; + } + return initialOperator; } return '$includes'; }, [targetField]); From 89269ec7a4705bd1534466bd7bd6598b7c19e5ba Mon Sep 17 00:00:00 2001 From: Katherine Date: Thu, 7 Nov 2024 17:30:53 +0800 Subject: [PATCH 2/5] fix: table scrollbar issue in non-config mode (#5599) * fix: unnecessary scrollbar in non-config mode with few columns * fix: bug * refactor: table column --- lerna.json | 4 +--- .../src/schema-component/antd/table-v2/Table.tsx | 4 ++-- .../src/schema-component/antd/table/Table.Array.tsx | 11 ++++++++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lerna.json b/lerna.json index b06f5984ca..9f82f187b1 100644 --- a/lerna.json +++ b/lerna.json @@ -2,9 +2,7 @@ "version": "1.4.0-alpha.2", "npmClient": "yarn", "useWorkspaces": true, - "npmClientArgs": [ - "--ignore-engines" - ], + "npmClientArgs": ["--ignore-engines"], "command": { "version": { "forcePublish": true, diff --git a/packages/core/client/src/schema-component/antd/table-v2/Table.tsx b/packages/core/client/src/schema-component/antd/table-v2/Table.tsx index 0b6cf319d7..db7b12b8cc 100644 --- a/packages/core/client/src/schema-component/antd/table-v2/Table.tsx +++ b/packages/core/client/src/schema-component/antd/table-v2/Table.tsx @@ -164,7 +164,7 @@ const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginat return columns; } const res = [ - ...columns, + ...adjustColumnOrder(columns), { title: render(), dataIndex: 'TABLE_COLUMN_INITIALIZER', @@ -210,7 +210,7 @@ const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginat }); } - return adjustColumnOrder(res); + return res; }, [columns, exists, field, render, props.showDel, designable]); return tableColumns; diff --git a/packages/core/client/src/schema-component/antd/table/Table.Array.tsx b/packages/core/client/src/schema-component/antd/table/Table.Array.tsx index b506c64bbb..272d5ce59e 100644 --- a/packages/core/client/src/schema-component/antd/table/Table.Array.tsx +++ b/packages/core/client/src/schema-component/antd/table/Table.Array.tsx @@ -65,7 +65,16 @@ const useTableColumns = () => { {/* fix https://nocobase.height.app/T-3232/description */} {/* 如果作为关系表格区块,则 parentRecordData 应该有值;如果作为普通表格使用(如数据源管理页面的表格)则应该使用 recordData,且 parentRecordData 为空 */} - + + + ); From e70d2d1c9e7f49ae1dd27f5b40845dc29e4de429 Mon Sep 17 00:00:00 2001 From: YANG QIA <2013xile@gmail.com> Date: Thu, 7 Nov 2024 20:41:03 +0800 Subject: [PATCH 3/5] chore(auth): optimize error message (#5612) --- .../plugin-auth/src/locale/en-US.json | 3 ++- .../plugin-auth/src/locale/zh-CN.json | 3 ++- .../src/server/__tests__/actions.test.ts | 4 ++-- .../plugin-auth/src/server/basic-auth.ts | 4 ++-- .../plugin-auth/src/server/locale/en-US.ts | 19 ------------------- .../plugin-auth/src/server/locale/fr-FR.ts | 19 ------------------- .../plugin-auth/src/server/locale/index.ts | 12 ------------ .../plugin-auth/src/server/locale/ja-JP.ts | 13 ------------- .../plugin-auth/src/server/locale/pt-BR.ts | 19 ------------------- .../plugin-auth/src/server/locale/zh-CN.ts | 19 ------------------- .../src/locale/en_US.json | 4 ++-- .../src/locale/zh_CN.json | 2 +- .../plugin-error-handler/src/server/server.ts | 2 +- 13 files changed, 12 insertions(+), 111 deletions(-) delete mode 100644 packages/plugins/@nocobase/plugin-auth/src/server/locale/en-US.ts delete mode 100644 packages/plugins/@nocobase/plugin-auth/src/server/locale/fr-FR.ts delete mode 100644 packages/plugins/@nocobase/plugin-auth/src/server/locale/index.ts delete mode 100644 packages/plugins/@nocobase/plugin-auth/src/server/locale/ja-JP.ts delete mode 100644 packages/plugins/@nocobase/plugin-auth/src/server/locale/pt-BR.ts delete mode 100644 packages/plugins/@nocobase/plugin-auth/src/server/locale/zh-CN.ts diff --git a/packages/plugins/@nocobase/plugin-auth/src/locale/en-US.json b/packages/plugins/@nocobase/plugin-auth/src/locale/en-US.json index f6f77a628c..02d59fbea1 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/locale/en-US.json +++ b/packages/plugins/@nocobase/plugin-auth/src/locale/en-US.json @@ -23,5 +23,6 @@ "No authentication methods available.": "No authentication methods available.", "The password is inconsistent, please re-enter": "The password is inconsistent, please re-enter", "Sign-in": "Sign-in", - "Password": "Password" + "Password": "Password", + "The username/email or password is incorrect, please re-enter": "The username/email or password is incorrect, please re-enter" } diff --git a/packages/plugins/@nocobase/plugin-auth/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-auth/src/locale/zh-CN.json index 1e244da8a7..c8760b1bc4 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/locale/zh-CN.json +++ b/packages/plugins/@nocobase/plugin-auth/src/locale/zh-CN.json @@ -23,5 +23,6 @@ "No authentication methods available.": "没有可用的认证方式。", "The password is inconsistent, please re-enter": "密码不一致,请重新输入", "Sign-in": "登录", - "Password": "密码" + "Password": "密码", + "The username/email or password is incorrect, please re-enter": "用户名/邮箱或密码有误,请重新输入" } diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/__tests__/actions.test.ts b/packages/plugins/@nocobase/plugin-auth/src/server/__tests__/actions.test.ts index c5f87ca261..ed023169aa 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/server/__tests__/actions.test.ts +++ b/packages/plugins/@nocobase/plugin-auth/src/server/__tests__/actions.test.ts @@ -116,7 +116,7 @@ describe('actions', () => { email: 'no-exists@nocobase.com', }); expect(res.statusCode).toEqual(401); - expect(res.error.text).toBe('The username or email is incorrect, please re-enter'); + expect(res.error.text).toBe('The username/email or password is incorrect, please re-enter'); }); it('should check password when signing in', async () => { @@ -125,7 +125,7 @@ describe('actions', () => { password: 'incorrect', }); expect(res.statusCode).toEqual(401); - expect(res.error.text).toBe('The password is incorrect, please re-enter'); + expect(res.error.text).toBe('The username/email or password is incorrect, please re-enter'); }); it('should sign in with password', async () => { diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/basic-auth.ts b/packages/plugins/@nocobase/plugin-auth/src/server/basic-auth.ts index 13c80bada1..281b823ef2 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/server/basic-auth.ts +++ b/packages/plugins/@nocobase/plugin-auth/src/server/basic-auth.ts @@ -39,13 +39,13 @@ export class BasicAuth extends BaseAuth { }); if (!user) { - ctx.throw(401, ctx.t('The username or email is incorrect, please re-enter', { ns: namespace })); + ctx.throw(401, ctx.t('The username/email or password is incorrect, please re-enter', { ns: namespace })); } const field = this.userCollection.getField('password'); const valid = await field.verify(password, user.password); if (!valid) { - ctx.throw(401, ctx.t('The password is incorrect, please re-enter', { ns: namespace })); + ctx.throw(401, ctx.t('The username/email or password is incorrect, please re-enter', { ns: namespace })); } return user; } diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/locale/en-US.ts b/packages/plugins/@nocobase/plugin-auth/src/server/locale/en-US.ts deleted file mode 100644 index a5780ac09c..0000000000 --- a/packages/plugins/@nocobase/plugin-auth/src/server/locale/en-US.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'The email is incorrect, please re-enter': 'The email is incorrect, please re-enter', - 'Please fill in your email address': 'Please fill in your email address', - 'The password is incorrect, please re-enter': 'The password is incorrect, please re-enter', - 'Not a valid cellphone number, please re-enter': 'Not a valid cellphone number, please re-enter', - 'The phone number has been registered, please login directly': - 'The phone number has been registered, please login directly', - 'The phone number is not registered, please register first': - 'The phone number is not registered, please register first', -}; diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/locale/fr-FR.ts b/packages/plugins/@nocobase/plugin-auth/src/server/locale/fr-FR.ts deleted file mode 100644 index 58967f0cff..0000000000 --- a/packages/plugins/@nocobase/plugin-auth/src/server/locale/fr-FR.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'The email is incorrect, please re-enter': 'L\'email est incorrect, veuillez le saisir à nouveau', - 'Please fill in your email address': 'Veuillez remplir votre adresse e-mail', - 'The password is incorrect, please re-enter': 'Le mot de passe est incorrect, veuillez le saisir à nouveau', - 'Not a valid cellphone number, please re-enter': 'Numéro de téléphone portable non valide, veuillez le saisir à nouveau', - 'The phone number has been registered, please login directly': - 'Le numéro de téléphone a été enregistré, veuillez vous connecter directement', - 'The phone number is not registered, please register first': - 'Le numéro de téléphone n\'est pas enregistré, veuillez vous inscrire d\'abord', -}; diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/locale/index.ts b/packages/plugins/@nocobase/plugin-auth/src/server/locale/index.ts deleted file mode 100644 index 29e1be6c80..0000000000 --- a/packages/plugins/@nocobase/plugin-auth/src/server/locale/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export { default as enUS } from './en-US'; -export { default as zhCN } from './zh-CN'; -export { default as ptBR } from './pt-BR'; diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/locale/ja-JP.ts b/packages/plugins/@nocobase/plugin-auth/src/server/locale/ja-JP.ts deleted file mode 100644 index 63406d55ce..0000000000 --- a/packages/plugins/@nocobase/plugin-auth/src/server/locale/ja-JP.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'Please fill in your email address': 'メールアドレスを入力してください', - 'The password is incorrect, please re-enter': 'パスワードが正しくありません。再度入力してください。', -}; diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/locale/pt-BR.ts b/packages/plugins/@nocobase/plugin-auth/src/server/locale/pt-BR.ts deleted file mode 100644 index 6da802d81d..0000000000 --- a/packages/plugins/@nocobase/plugin-auth/src/server/locale/pt-BR.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'The email is incorrect, please re-enter': 'O e-mail está incorreto, por favor, digite novamente', - 'Please fill in your email address': 'Por favor, preencha o seu endereço de e-mail', - 'The password is incorrect, please re-enter': 'A senha está incorreta, por favor, digite novamente', - 'Not a valid cellphone number, please re-enter': 'Número de celular inválido, por favor, digite novamente', - 'The phone number has been registered, please login directly': - 'O número de celular já está registrado, por favor, faça login diretamente', - 'The phone number is not registered, please register first': - 'O número de celular não está registrado, por favor, registre-se primeiro', -}; diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/locale/zh-CN.ts b/packages/plugins/@nocobase/plugin-auth/src/server/locale/zh-CN.ts deleted file mode 100644 index de99c16e07..0000000000 --- a/packages/plugins/@nocobase/plugin-auth/src/server/locale/zh-CN.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'The username or email is incorrect, please re-enter': '用户名或邮箱有误,请重新输入', - 'The password is incorrect, please re-enter': '密码有误,请重新输入', - 'Not a valid cellphone number, please re-enter': '不是有效的手机号,请重新输入', - 'The phone number has been registered, please login directly': '手机号已注册,请直接登录', - 'The phone number is not registered, please register first': '手机号未注册,请先注册', - 'Please keep and enable at least one authenticator': '请至少保留并启用一个认证器', - 'Please enter your username or email': '请输入用户名或邮箱', - 'Please enter a valid username': '请输入有效的用户名', -}; diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/locale/en_US.json b/packages/plugins/@nocobase/plugin-error-handler/src/locale/en_US.json index 1d03ff8807..3601fa9dd1 100644 --- a/packages/plugins/@nocobase/plugin-error-handler/src/locale/en_US.json +++ b/packages/plugins/@nocobase/plugin-error-handler/src/locale/en_US.json @@ -1,6 +1,6 @@ { - "unique violation": "{{field}} must be unique", - "notNull violation": "notNull violation", + "unique violation": "{{field}} already exists", + "notNull violation": "{{field}} cannot be null", "Validation error": "{{field}} validation error", "notNull Violation": "{{field}} cannot be null" } diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/locale/zh_CN.json b/packages/plugins/@nocobase/plugin-error-handler/src/locale/zh_CN.json index 057a84fbca..7bb4234aca 100644 --- a/packages/plugins/@nocobase/plugin-error-handler/src/locale/zh_CN.json +++ b/packages/plugins/@nocobase/plugin-error-handler/src/locale/zh_CN.json @@ -1,5 +1,5 @@ { - "unique violation": "{{field}} 字段值是唯一的", + "unique violation": "{{field}} 字段值已存在", "notNull violation": "{{field}} 字段不能为空", "Validation error": "{{field}} 字段规则验证失败" } diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/server/server.ts b/packages/plugins/@nocobase/plugin-error-handler/src/server/server.ts index 2c6e38c1ce..af20a45f16 100644 --- a/packages/plugins/@nocobase/plugin-error-handler/src/server/server.ts +++ b/packages/plugins/@nocobase/plugin-error-handler/src/server/server.ts @@ -56,7 +56,7 @@ export class PluginErrorHandlerServer extends Plugin { return { message: t(err.type, { ns: this.i18nNs, - field: t(title, { ns: 'lm-collections' }), + field: t(title, { ns: ['lm-collections', 'client'] }), }), }; }), From 3d512adadee9a3735dba5ec0ab0a188431cd7b07 Mon Sep 17 00:00:00 2001 From: Zeke Zhang <958414905@qq.com> Date: Thu, 7 Nov 2024 20:47:14 +0800 Subject: [PATCH 4/5] feat(Table): add 'Hide column' configuration option (#5597) * chore(deps): bump antd from 5.12.8 to 5.13.3 * chore: update yarn.lock * chore: fix unit tests * chore: make e2e tests pass * feat(Table): add 'Hide column' configuration option * test: add e2e test * chore: fix build * Revert "chore: make e2e tests pass" This reverts commit a84a22c140c393c055136f90140ff74c657d8b28. * Revert "chore: fix unit tests" This reverts commit 436b001c9b3cf75ee2ddcc00915199b12e1d37d4. * Revert "chore: update yarn.lock" This reverts commit 0b960251c414a66d8a0a1274bd1cd3330093ccc0. * Revert "chore(deps): bump antd from 5.12.8 to 5.13.3" This reverts commit b165d557f68e3cb7dab4172e8f22f631decea277. * chore: make e2e more stable --- packages/core/client/src/locale/en-US.json | 4 +- packages/core/client/src/locale/es-ES.json | 4 +- packages/core/client/src/locale/fr-FR.json | 4 +- packages/core/client/src/locale/ja-JP.json | 4 +- packages/core/client/src/locale/ko-KR.json | 4 +- packages/core/client/src/locale/pt-BR.json | 4 +- packages/core/client/src/locale/ru-RU.json | 4 +- packages/core/client/src/locale/tr-TR.json | 4 +- packages/core/client/src/locale/uk-UA.json | 4 +- packages/core/client/src/locale/zh-CN.json | 4 +- packages/core/client/src/locale/zh-TW.json | 4 +- .../table/__e2e__/hideColumn.test.ts | 42 ++ .../table/__e2e__/templatesOfBug.ts | 441 ++++++++++++++++++ .../data-blocks/table/tableColumnSettings.tsx | 43 ++ .../menu/__e2e__/schemaInitializer.test.ts | 2 +- .../schema-component/antd/table-v2/Table.tsx | 54 ++- .../src/schema-settings/SchemaSettings.tsx | 8 +- 17 files changed, 613 insertions(+), 21 deletions(-) create mode 100644 packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/hideColumn.test.ts diff --git a/packages/core/client/src/locale/en-US.json b/packages/core/client/src/locale/en-US.json index e7b5995120..83f0759ff9 100644 --- a/packages/core/client/src/locale/en-US.json +++ b/packages/core/client/src/locale/en-US.json @@ -847,5 +847,7 @@ "Skip getting the total number of table records during paging to speed up loading. It is recommended to enable this option for data tables with a large amount of data": "Skip getting the total number of table records during paging to speed up loading. It is recommended to enable this option for data tables with a large amount of data", "Enable secondary confirmation": "Enable secondary confirmation", "Notification": "Notification", - "Ellipsis overflow content": "Ellipsis overflow content" + "Ellipsis overflow content": "Ellipsis overflow content", + "Hide column": "Hide column", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect." } diff --git a/packages/core/client/src/locale/es-ES.json b/packages/core/client/src/locale/es-ES.json index 614c6f2c12..d8488c2d29 100644 --- a/packages/core/client/src/locale/es-ES.json +++ b/packages/core/client/src/locale/es-ES.json @@ -768,5 +768,7 @@ "Sorry, the page you visited does not exist.": "Lo siento, la página que visitaste no existe.", "Allow multiple selection": "Permitir selección múltiple", "Parent object": "Objeto padre", - "Ellipsis overflow content": "Contenido de desbordamiento de elipsis" + "Ellipsis overflow content": "Contenido de desbordamiento de elipsis", + "Hide column": "Ocultar columna", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "En modo de configuración, toda la columna se vuelve transparente. En modo de no configuración, toda la columna se ocultará. Incluso si toda la columna está oculta, sus valores predeterminados configurados y otras configuraciones seguirán tomando efecto." } diff --git a/packages/core/client/src/locale/fr-FR.json b/packages/core/client/src/locale/fr-FR.json index 781ca69efc..60320c9598 100644 --- a/packages/core/client/src/locale/fr-FR.json +++ b/packages/core/client/src/locale/fr-FR.json @@ -788,5 +788,7 @@ "Sorry, the page you visited does not exist.": "Désolé, la page que vous avez visitée n'existe pas.", "Allow multiple selection": "Permettre la sélection multiple", "Parent object": "Objet parent", - "Ellipsis overflow content": "Contenu de débordement avec ellipse" + "Ellipsis overflow content": "Contenu de débordement avec ellipse", + "Hide column": "Masquer la colonne", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "En mode de configuration, toute la colonne devient transparente. En mode de non-configuration, toute la colonne sera masquée. Même si toute la colonne est masquée, ses valeurs par défaut configurées et les autres paramètres resteront toujours en vigueur." } diff --git a/packages/core/client/src/locale/ja-JP.json b/packages/core/client/src/locale/ja-JP.json index 08a6df9a88..b311b11e34 100644 --- a/packages/core/client/src/locale/ja-JP.json +++ b/packages/core/client/src/locale/ja-JP.json @@ -1006,5 +1006,7 @@ "Skip getting the total number of table records during paging to speed up loading. It is recommended to enable this option for data tables with a large amount of data": "ページング時にテーブルレコードの総数取得をスキップして、読み込み速度を向上させます。データ量が多い場合にこのオプションの使用をお勧めします。", "The current user only has the UI configuration permission, but don't have view permission for collection \"{{name}}\"": "現在のユーザーにはUI設定の権限しかなく、コレクション「{{name}}」を閲覧する権限はありません。", "Allow multiple selection": "複数選択を許可", - "Parent object": "親オブジェクト" + "Parent object": "親オブジェクト", + "Hide column": "列を非表示", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "設定モードでは、列全体が透明になります。非設定モードでは、列全体が非表示になります。列全体が非表示になっても、設定されたデフォルト値やその他の設定は依然として有効です。" } diff --git a/packages/core/client/src/locale/ko-KR.json b/packages/core/client/src/locale/ko-KR.json index cf8fa45a7d..f3fcf24d73 100644 --- a/packages/core/client/src/locale/ko-KR.json +++ b/packages/core/client/src/locale/ko-KR.json @@ -879,5 +879,7 @@ "Sorry, the page you visited does not exist.": "죄송합니다. 방문한 페이지가 존재하지 않습니다.", "Allow multiple selection": "다중 선택 허용", "Parent object": "부모 객체", -"Ellipsis overflow content": "생략 부호로 내용 줄임" + "Ellipsis overflow content": "생략 부호로 내용 줄임", + "Hide column": "열 숨기기", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "구성 모드에서는 전체 열이 투명해집니다. 비구성 모드에서는 전체 열이 숨겨집니다. 전체 열이 숨겨져도 구성된 기본값 및 기타 설정은 여전히 적용됩니다." } diff --git a/packages/core/client/src/locale/pt-BR.json b/packages/core/client/src/locale/pt-BR.json index 3353373ff4..ba1bd69b45 100644 --- a/packages/core/client/src/locale/pt-BR.json +++ b/packages/core/client/src/locale/pt-BR.json @@ -745,5 +745,7 @@ "Sorry, the page you visited does not exist.": "Desculpe, a página que você visitou não existe.", "Allow multiple selection": "Permitir seleção múltipla", "Parent object": "Objeto pai", - "Ellipsis overflow content": "Conteúdo de transbordamento com reticências" + "Ellipsis overflow content": "Conteúdo de transbordamento com reticências", + "Hide column": "Ocultar coluna", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "Em modo de configuração, a coluna inteira se torna transparente. Em modo de não configuração, a coluna inteira será ocultada. Mesmo se a coluna inteira estiver oculta, seus valores padrão configurados e outras configurações ainda terão efeito." } diff --git a/packages/core/client/src/locale/ru-RU.json b/packages/core/client/src/locale/ru-RU.json index 6b0c2cd7c5..82138f9c5b 100644 --- a/packages/core/client/src/locale/ru-RU.json +++ b/packages/core/client/src/locale/ru-RU.json @@ -582,5 +582,7 @@ "Sorry, the page you visited does not exist.": "Извините, посещенной вами страницы не существует.", "Allow multiple selection": "Разрешить множественный выбор", "Parent object": "Родительский объект", - "Ellipsis overflow content": "Содержимое с многоточием при переполнении" + "Ellipsis overflow content": "Содержимое с многоточием при переполнении", + "Hide column": "Скрыть столбец", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "В режиме конфигурации вся колонка становится прозрачной. В режиме не конфигурации вся колонка будет скрыта. Даже если вся колонка будет скрыта, её настроенные значения по умолчанию и другие настройки все равно будут действовать." } diff --git a/packages/core/client/src/locale/tr-TR.json b/packages/core/client/src/locale/tr-TR.json index 83f6f2f9d0..b6bcc27250 100644 --- a/packages/core/client/src/locale/tr-TR.json +++ b/packages/core/client/src/locale/tr-TR.json @@ -580,5 +580,7 @@ "Sorry, the page you visited does not exist.": "Üzgünüz, ziyaret ettiğiniz sayfa mevcut değil.", "Allow multiple selection": "Çoklu seçim izni", "Parent object": "Üst nesne", - "Ellipsis overflow content": "Üç nokta ile taşan içerik" + "Ellipsis overflow content": "Üç nokta ile taşan içerik", + "Hide column": "Sütunu gizle", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "Yapılandırma modunda, tüm sütun tamamen saydamlık alır. Yapılandırma modu olmayan durumda, tüm sütun gizlenir. Tamamen sütun gizlendiğinde bile, yapılandırılmış varsayılan değerleri ve diğer ayarları hâlâ etkin olur." } diff --git a/packages/core/client/src/locale/uk-UA.json b/packages/core/client/src/locale/uk-UA.json index 3fa1500a9d..c53f9816e7 100644 --- a/packages/core/client/src/locale/uk-UA.json +++ b/packages/core/client/src/locale/uk-UA.json @@ -788,5 +788,7 @@ "Sorry, the page you visited does not exist.": "Вибачте, сторінка, яку ви відвідали, не існує.", "Allow multiple selection": "Дозволити множинний вибір", "Parent object": "Батьківський об'єкт", - "Ellipsis overflow content": "Вміст з багатокрапкою при переповненні" + "Ellipsis overflow content": "Вміст з багатокрапкою при переповненні", + "Hide column": "Сховати стовпець", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "В режимі конфігурації вся колонка стає прозорою. В режимі не конфігурації вся колонка буде прихована. Якщо вся колонка буде прихована, її налаштовані значення за замовчуванням і інші налаштування все одно будуть діяти." } diff --git a/packages/core/client/src/locale/zh-CN.json b/packages/core/client/src/locale/zh-CN.json index b135ff986f..d6172ea7d5 100644 --- a/packages/core/client/src/locale/zh-CN.json +++ b/packages/core/client/src/locale/zh-CN.json @@ -1025,5 +1025,7 @@ "Ellipsis": "省略", "Set block layout": "设置区块布局", "Add & Update": "添加 & 更新", - "Table size":"表格大小" + "Table size":"表格大小", + "Hide column": "隐藏列", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "在配置模式下,整个列会变为透明色。在非配置模式下,整个列将被隐藏。即使整个列被隐藏了,其配置的默认值和其他设置仍然有效。" } diff --git a/packages/core/client/src/locale/zh-TW.json b/packages/core/client/src/locale/zh-TW.json index 391a04ec50..a48d2c408e 100644 --- a/packages/core/client/src/locale/zh-TW.json +++ b/packages/core/client/src/locale/zh-TW.json @@ -878,5 +878,7 @@ "Sorry, the page you visited does not exist.": "抱歉,你訪問的頁面不存在。", "Allow multiple selection": "允許多選", "Parent object": "上級物件", - "Ellipsis overflow content": "省略超出長度的內容" + "Ellipsis overflow content": "省略超出長度的內容", + "Hide column": "隱藏列", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "在配置模式下,整個列會變為透明色。在非配置模式下,整個列將被隱藏。即使整個列被隱藏了,其配置的默認值和其他設置仍然有效。" } diff --git a/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/hideColumn.test.ts b/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/hideColumn.test.ts new file mode 100644 index 0000000000..4d5a3190f1 --- /dev/null +++ b/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/hideColumn.test.ts @@ -0,0 +1,42 @@ +/** + * 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 { expect, test } from '@nocobase/test/e2e'; +import { hideColumnBasic } from './templatesOfBug'; + +test.describe('hide column', () => { + test('basic', async ({ page, mockPage }) => { + await mockPage(hideColumnBasic).goto(); + + // 1. Normal table: hide column + await page.getByRole('button', { name: 'Nickname' }).hover(); + await page + .getByRole('button', { name: 'designer-schema-settings-TableV2.Column-fieldSettings:TableColumn-users' }) + .hover(); + await page.getByRole('menuitem', { name: 'Hide column question-circle' }).click(); + await page.mouse.move(500, 0); + + // 2. Sub table: hide column + await page.getByRole('button', { name: 'Role name' }).hover(); + await page + .getByRole('button', { name: 'designer-schema-settings-TableV2.Column-fieldSettings:TableColumn-roles' }) + .hover(); + await page.getByRole('menuitem', { name: 'Hide column question-circle' }).click(); + await page.mouse.move(500, 0); + + // Assert: In configuration mode, the entire column becomes transparent + await expect(page.locator('th', { hasText: 'Nickname' })).toHaveCSS('opacity', '0.3'); + await expect(page.locator('th', { hasText: 'Role name' })).toHaveCSS('opacity', '0.3'); + + // Assert: In non-configuration mode, the entire column will be hidden + await page.getByTestId('ui-editor-button').click(); + await expect(page.locator('th', { hasText: 'Nickname' })).not.toBeVisible(); + await expect(page.locator('th', { hasText: 'Role name' })).not.toBeVisible(); + }); +}); diff --git a/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/templatesOfBug.ts b/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/templatesOfBug.ts index 72ba397b28..ff12ba7efd 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/templatesOfBug.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/templatesOfBug.ts @@ -7926,3 +7926,444 @@ export const differentURL_DifferentPopupContent = { 'x-index': 1, }, }; +export const hideColumnBasic = { + pageSchema: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + 'x-app-version': '1.4.0-alpha.1', + properties: { + bfc254m95nt: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'page:addBlock', + 'x-app-version': '1.4.0-alpha.1', + properties: { + '4e61hstsu6e': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.4.0-alpha.1', + properties: { + j9xovyw5nce: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.4.0-alpha.1', + properties: { + '9ivpaiunf9y': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableBlockProvider', + 'x-acl-action': 'users:list', + 'x-use-decorator-props': 'useTableBlockDecoratorProps', + 'x-decorator-props': { + collection: 'users', + dataSource: 'main', + action: 'list', + params: { + pageSize: 20, + }, + rowKey: 'id', + showIndex: true, + dragSort: false, + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:table', + 'x-component': 'CardItem', + 'x-filter-targets': [], + 'x-app-version': '1.4.0-alpha.1', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'table:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 'var(--nb-spacing)', + }, + }, + 'x-app-version': '1.4.0-alpha.1', + 'x-uid': '51wc0e6u31j', + 'x-async': false, + 'x-index': 1, + }, + bs7p8uu830m: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'array', + 'x-initializer': 'table:configureColumns', + 'x-component': 'TableV2', + 'x-use-component-props': 'useTableBlockProps', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + }, + 'x-app-version': '1.4.0-alpha.1', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Actions") }}', + 'x-action-column': 'actions', + 'x-decorator': 'TableV2.Column.ActionBar', + 'x-component': 'TableV2.Column', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-initializer': 'table:configureItemActions', + 'x-settings': 'fieldSettings:TableColumn', + 'x-toolbar-props': { + initializer: 'table:configureItemActions', + }, + 'x-app-version': '1.4.0-alpha.1', + properties: { + jw80rplhox4: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'DndContext', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, + 'x-app-version': '1.4.0-alpha.1', + 'x-uid': 'u06c9tleqou', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '9s1pfakz0om', + 'x-async': false, + 'x-index': 1, + }, + aph2w5uiev9: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-settings': 'fieldSettings:TableColumn', + 'x-component': 'TableV2.Column', + 'x-app-version': '1.4.0-alpha.1', + properties: { + nickname: { + _isJSONSchemaObject: true, + version: '2.0', + 'x-collection-field': 'users.nickname', + 'x-component': 'CollectionField', + 'x-component-props': { + ellipsis: true, + }, + 'x-read-pretty': true, + 'x-decorator': null, + 'x-decorator-props': { + labelStyle: { + display: 'none', + }, + }, + 'x-app-version': '1.4.0-alpha.1', + 'x-uid': '3w3zou3ceb3', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '4m9sepacwvm', + 'x-async': false, + 'x-index': 2, + }, + wpmysa656gq: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-settings': 'fieldSettings:TableColumn', + 'x-component': 'TableV2.Column', + 'x-app-version': '1.4.0-alpha.1', + properties: { + username: { + _isJSONSchemaObject: true, + version: '2.0', + 'x-collection-field': 'users.username', + 'x-component': 'CollectionField', + 'x-component-props': { + ellipsis: true, + }, + 'x-read-pretty': true, + 'x-decorator': null, + 'x-decorator-props': { + labelStyle: { + display: 'none', + }, + }, + 'x-app-version': '1.4.0-alpha.1', + 'x-uid': '38dn4mm3hr5', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '9ed6nyhlgzi', + 'x-async': false, + 'x-index': 3, + }, + }, + 'x-uid': 'sc4a347zrvr', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'z8gp6wsukkv', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '60rzu0t7mm9', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'po4tzdbip1j', + 'x-async': false, + 'x-index': 1, + }, + h9rg0fz8izl: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.4.0-alpha.1', + properties: { + acu1j5bgeeh: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.4.0-alpha.1', + properties: { + azoaet9funq: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-acl-action-props': { + skipScopeCheck: true, + }, + 'x-acl-action': 'users:create', + 'x-decorator': 'FormBlockProvider', + 'x-use-decorator-props': 'useCreateFormBlockDecoratorProps', + 'x-decorator-props': { + dataSource: 'main', + collection: 'users', + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:createForm', + 'x-component': 'CardItem', + 'x-app-version': '1.4.0-alpha.1', + properties: { + pfltqojjolr: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'FormV2', + 'x-use-component-props': 'useCreateFormBlockProps', + 'x-app-version': '1.4.0-alpha.1', + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'form:configureFields', + 'x-app-version': '1.4.0-alpha.1', + properties: { + y4g2q6tyb0a: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.4.0-alpha.1', + properties: { + thgbnha840e: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.4.0-alpha.1', + properties: { + roles: { + 'x-uid': 'ivyzmc9lc21', + _isJSONSchemaObject: true, + version: '2.0', + type: 'string', + 'x-toolbar': 'FormItemSchemaToolbar', + 'x-settings': 'fieldSettings:FormItem', + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + 'x-collection-field': 'users.roles', + 'x-component-props': { + fieldNames: { + label: 'name', + value: 'name', + }, + mode: 'SubTable', + }, + 'x-app-version': '1.4.0-alpha.1', + default: null, + properties: { + eoizayhit92: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'AssociationField.SubTable', + 'x-initializer': 'table:configureColumns', + 'x-initializer-props': { + action: false, + }, + 'x-index': 1, + 'x-app-version': '1.4.0-alpha.1', + properties: { + nhe3d7uuyur: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-settings': 'fieldSettings:TableColumn', + 'x-component': 'TableV2.Column', + 'x-app-version': '1.4.0-alpha.1', + properties: { + name: { + _isJSONSchemaObject: true, + version: '2.0', + 'x-collection-field': 'roles.name', + 'x-component': 'CollectionField', + 'x-component-props': { + ellipsis: true, + }, + 'x-decorator': 'FormItem', + 'x-decorator-props': { + labelStyle: { + display: 'none', + }, + }, + 'x-app-version': '1.4.0-alpha.1', + 'x-uid': 'e89fxf1mg7i', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'kxbrpz5jmc7', + 'x-async': false, + 'x-index': 1, + }, + jgnjc800er5: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-settings': 'fieldSettings:TableColumn', + 'x-component': 'TableV2.Column', + 'x-app-version': '1.4.0-alpha.1', + properties: { + title: { + _isJSONSchemaObject: true, + version: '2.0', + 'x-collection-field': 'roles.title', + 'x-component': 'CollectionField', + 'x-component-props': { + ellipsis: true, + }, + 'x-decorator': 'FormItem', + 'x-decorator-props': { + labelStyle: { + display: 'none', + }, + }, + 'x-app-version': '1.4.0-alpha.1', + 'x-uid': 'bankqu2es91', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'uwyyk7ix2lb', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'ff16rxs8lbo', + 'x-async': false, + }, + }, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '4nagabglobc', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'noqp2jcfp2j', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'a330n90aoyf', + 'x-async': false, + 'x-index': 1, + }, + vpwq72gj933: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'createForm:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + layout: 'one-column', + }, + 'x-app-version': '1.4.0-alpha.1', + 'x-uid': '1nauytag1dh', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': '342ktwn88zo', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '84tmhiuph4u', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'j70955c17aq', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'vtz6tq8n0mx', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': '1f4xbt4prbg', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'ifb5nqtgnng', + 'x-async': true, + 'x-index': 1, + }, +}; diff --git a/packages/core/client/src/modules/blocks/data-blocks/table/tableColumnSettings.tsx b/packages/core/client/src/modules/blocks/data-blocks/table/tableColumnSettings.tsx index 6e08363070..b5cdca020c 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/table/tableColumnSettings.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/table/tableColumnSettings.tsx @@ -7,8 +7,10 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ +import { QuestionCircleOutlined } from '@ant-design/icons'; import { ISchema } from '@formily/json-schema'; import { useField, useFieldSchema } from '@formily/react'; +import { Tooltip } from 'antd'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { useApp, useSchemaToolbar } from '../../../../application'; @@ -380,6 +382,47 @@ export const tableColumnSettings = new SchemaSettings({ }; }, }, + { + name: 'hidden', + type: 'switch', + useComponentProps() { + const field: any = useField(); + const { t } = useTranslation(); + const columnSchema = useFieldSchema(); + const { dn } = useDesignable(); + + return { + title: ( + + {t('Hide column')} + + + + + ), + checked: field.componentProps.columnHidden, + onChange: (v) => { + const schema: ISchema = { + ['x-uid']: columnSchema['x-uid'], + }; + columnSchema['x-component-props'] = { + ...columnSchema['x-component-props'], + columnHidden: v, + }; + schema['x-component-props'] = columnSchema['x-component-props']; + field.componentProps.columnHidden = v; + dn.emit('patch', { + schema, + }); + dn.refresh(); + }, + }; + }, + }, fieldComponentSettingsItem, ], }, diff --git a/packages/core/client/src/modules/menu/__e2e__/schemaInitializer.test.ts b/packages/core/client/src/modules/menu/__e2e__/schemaInitializer.test.ts index 8da21efcf6..ceb401c7ae 100644 --- a/packages/core/client/src/modules/menu/__e2e__/schemaInitializer.test.ts +++ b/packages/core/client/src/modules/menu/__e2e__/schemaInitializer.test.ts @@ -102,7 +102,7 @@ test.describe('add menu item', () => { // open link page await page.getByLabel(pageLink).click(); - await page.waitForTimeout(1000); + await page.waitForTimeout(2000); // After clicking, it will redirect to another page, so we need to get the instance of the new page const newPage = page.context().pages()[1]; diff --git a/packages/core/client/src/schema-component/antd/table-v2/Table.tsx b/packages/core/client/src/schema-component/antd/table-v2/Table.tsx index db7b12b8cc..c96b8dc507 100644 --- a/packages/core/client/src/schema-component/antd/table-v2/Table.tsx +++ b/packages/core/client/src/schema-component/antd/table-v2/Table.tsx @@ -121,13 +121,15 @@ const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginat } }, []); const dataIndex = collectionFields?.length > 0 ? collectionFields[0].name : s.name; + const columnHidden = !!s['x-component-props']?.['columnHidden']; return { title: , dataIndex, key: s.name, sorter: s['x-component-props']?.['sorter'], - width: 200, + columnHidden, ...s['x-component-props'], + width: columnHidden && !designable ? 0 : s['x-component-props']?.width || 200, render: (v, record) => { // 这行代码会导致这里的测试不通过:packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/schemaInitializer.test.ts:189 // if (collectionFields?.length === 1 && collectionFields[0]['x-read-pretty'] && v == undefined) return null; @@ -149,14 +151,25 @@ const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginat ); }, onCell: (record, rowIndex) => { - return { record, schema: s, rowIndex, isSubTable: props.isSubTable }; + return { + record, + schema: s, + rowIndex, + isSubTable: props.isSubTable, + columnHidden, + }; + }, + onHeaderCell: () => { + return { + columnHidden, + }; }, } as TableColumnProps; }), // 这里不能把 columnsSchema 作为依赖,因为其每次都会变化,这里使用 hasChangedColumns 作为依赖 // eslint-disable-next-line react-hooks/exhaustive-deps - [hasChangedColumns, field.value, field.address, collection, parentRecordData, schemaToolbarBigger], + [hasChangedColumns, field.value, field.address, collection, parentRecordData, schemaToolbarBigger, designable], ); const tableColumns = useMemo(() => { @@ -440,7 +453,25 @@ const HeaderWrapperComponent = (props) => { ); }; +// Style when Hidden is enabled in table column configuration +const columnHiddenStyle = { + borderRight: 'none', + paddingLeft: 0, + paddingRight: 0, +}; + +// Style when Hidden is enabled in configuration mode +const columnOpacityStyle = { + opacity: 0.3, +}; + const HeaderCellComponent = (props) => { + const { designable } = useDesignable(); + + if (props.columnHidden) { + return {designable ? props.children : null}; + } + return ; }; @@ -453,7 +484,7 @@ const BodyRowComponent = (props: { return ; }; -const BodyCellComponent = (props) => { +const InternalBodyCellComponent = (props) => { const { token } = useToken(); const inView = useContext(InViewContext); const isIndex = props.className?.includes('selection-column'); @@ -474,6 +505,21 @@ const BodyCellComponent = (props) => { ); }; +const displayNone = { display: 'none' }; +const BodyCellComponent = (props) => { + const { designable } = useDesignable(); + + if (props.columnHidden) { + return ( + + {designable ? props.children : {props.children}} + + ); + } + + return ; +}; + interface TableProps { /** @deprecated */ useProps?: () => any; diff --git a/packages/core/client/src/schema-settings/SchemaSettings.tsx b/packages/core/client/src/schema-settings/SchemaSettings.tsx index 7a74e28999..aa6d723fda 100644 --- a/packages/core/client/src/schema-settings/SchemaSettings.tsx +++ b/packages/core/client/src/schema-settings/SchemaSettings.tsx @@ -363,7 +363,7 @@ export const SchemaSettingsFormItemTemplate = function FormItemTemplate(props) { }; export interface SchemaSettingsItemProps extends Omit { - title: string; + title: string | ReactNode; } export const SchemaSettingsItem: FC = (props) => { const { pushMenuItem } = useCollectMenuItems(); @@ -544,7 +544,7 @@ export const SchemaSettingsCascaderItem: FC = ( }; export interface SchemaSettingsSwitchItemProps extends Omit { - title: string; + title: string | ReactNode; checked?: boolean; onChange?: (v: boolean) => void; } @@ -598,9 +598,7 @@ export const SchemaSettingsPopupItem: FC = (props) => ); }; -export interface SchemaSettingsActionModalItemProps - extends SchemaSettingsModalItemProps, - Omit { +export interface SchemaSettingsActionModalItemProps extends SchemaSettingsModalItemProps { uid?: string; initialSchema?: ISchema; schema?: ISchema; From ef1ded8ff2d1839cc2fd0965fa8e534305f1911d Mon Sep 17 00:00:00 2001 From: ChengLei Shao Date: Thu, 7 Nov 2024 21:05:58 +0800 Subject: [PATCH 5/5] fix: import with date field (#5606) * fix: import with dateOnly and datetimeNoTz field * fix: import with date field * fix: export datetime filed * fix: test * fix: test * fix: test * fix: unixtimestamp import * chore: test --- .../antd/date-picker/ReadPretty.tsx | 5 +- .../interfaces/datetime-interface.test.ts | 17 +- .../database/src/interfaces/date-interface.ts | 7 + .../src/interfaces/datetime-interface.ts | 8 +- .../interfaces/datetime-no-tz-interface.ts | 49 ++++++ .../core/database/src/interfaces/index.ts | 2 + .../core/database/src/interfaces/utils.ts | 5 + packages/core/utils/src/date.ts | 2 +- .../server/__tests__/export-to-xlsx.test.ts | 126 ++++++++++++- .../server/__tests__/xlsx-importer.test.ts | 166 +++++++++++++++++- 10 files changed, 348 insertions(+), 39 deletions(-) create mode 100644 packages/core/database/src/interfaces/date-interface.ts create mode 100644 packages/core/database/src/interfaces/datetime-no-tz-interface.ts diff --git a/packages/core/client/src/schema-component/antd/date-picker/ReadPretty.tsx b/packages/core/client/src/schema-component/antd/date-picker/ReadPretty.tsx index 58e1e60e22..bd91927d11 100644 --- a/packages/core/client/src/schema-component/antd/date-picker/ReadPretty.tsx +++ b/packages/core/client/src/schema-component/antd/date-picker/ReadPretty.tsx @@ -10,11 +10,11 @@ import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__'; import { isArr } from '@formily/shared'; import { + getDefaultFormat, GetDefaultFormatProps, + str2moment, Str2momentOptions, Str2momentValue, - getDefaultFormat, - str2moment, } from '@nocobase/utils/client'; import cls from 'classnames'; import dayjs from 'dayjs'; @@ -67,6 +67,7 @@ ReadPretty.DateRangePicker = function DateRangePicker(props: DateRangePickerRead const labels = m.map((m) => m.format(format)); return isArr(labels) ? labels.join('~') : labels; }; + return (
{getLabels()} diff --git a/packages/core/database/src/__tests__/interfaces/datetime-interface.test.ts b/packages/core/database/src/__tests__/interfaces/datetime-interface.test.ts index e5a0deab12..621c090e40 100644 --- a/packages/core/database/src/__tests__/interfaces/datetime-interface.test.ts +++ b/packages/core/database/src/__tests__/interfaces/datetime-interface.test.ts @@ -39,7 +39,7 @@ describe('Date time interface', () => { }, { name: 'dateTime', - type: 'date', + type: 'datetime', uiSchema: { ['x-component-props']: { showTime: true, @@ -74,20 +74,5 @@ describe('Date time interface', () => { expect(await interfaceInstance.toValue(42510)).toBe('2016-05-20T00:00:00.000Z'); expect(await interfaceInstance.toValue('42510')).toBe('2016-05-20T00:00:00.000Z'); expect(await interfaceInstance.toValue('2016-05-20T00:00:00.000Z')).toBe('2016-05-20T00:00:00.000Z'); - expect( - await interfaceInstance.toValue('2016-05-20 04:22:22', { - field: testCollection.getField('dateOnly'), - }), - ).toBe('2016-05-20T00:00:00.000Z'); - expect( - await interfaceInstance.toValue('2016-05-20 01:00:00', { - field: testCollection.getField('dateTime'), - }), - ).toBe(dayjs('2016-05-20 01:00:00').toISOString()); - expect( - await interfaceInstance.toValue('2016-05-20 01:00:00', { - field: testCollection.getField('dateTimeGmt'), - }), - ).toBe('2016-05-20T01:00:00.000Z'); }); }); diff --git a/packages/core/database/src/interfaces/date-interface.ts b/packages/core/database/src/interfaces/date-interface.ts new file mode 100644 index 0000000000..6b8aedcdb6 --- /dev/null +++ b/packages/core/database/src/interfaces/date-interface.ts @@ -0,0 +1,7 @@ +import { DatetimeInterface } from './datetime-interface'; + +export class DateInterface extends DatetimeInterface { + toString(value: any, ctx?: any): any { + return value; + } +} diff --git a/packages/core/database/src/interfaces/datetime-interface.ts b/packages/core/database/src/interfaces/datetime-interface.ts index fed3a38753..85661d5933 100644 --- a/packages/core/database/src/interfaces/datetime-interface.ts +++ b/packages/core/database/src/interfaces/datetime-interface.ts @@ -8,7 +8,7 @@ */ import { BaseInterface } from './base-interface'; -import { getDefaultFormat, moment2str, str2moment } from '@nocobase/utils'; +import { getDefaultFormat, str2moment } from '@nocobase/utils'; import dayjs from 'dayjs'; import { getJsDateFromExcel } from 'excel-date-to-js'; @@ -51,11 +51,7 @@ export class DatetimeInterface extends BaseInterface { } else if (isNumeric(value)) { return getJsDateFromExcel(value).toISOString(); } else if (typeof value === 'string') { - const props = ctx.field?.options?.uiSchema?.['x-component-props'] || {}; - const m = dayjs(value); - if (m.isValid()) { - return moment2str(m, props); - } + return value; } throw new Error(`Invalid date - ${value}`); diff --git a/packages/core/database/src/interfaces/datetime-no-tz-interface.ts b/packages/core/database/src/interfaces/datetime-no-tz-interface.ts new file mode 100644 index 0000000000..d2d4a7d9d0 --- /dev/null +++ b/packages/core/database/src/interfaces/datetime-no-tz-interface.ts @@ -0,0 +1,49 @@ +import { DatetimeInterface } from './datetime-interface'; +import dayjs from 'dayjs'; +import { getJsDateFromExcel } from 'excel-date-to-js'; +import { getDefaultFormat, str2moment } from '@nocobase/utils'; + +function isDate(v) { + return v instanceof Date; +} + +function isNumeric(str: any) { + if (typeof str === 'number') return true; + if (typeof str != 'string') return false; + return !isNaN(str as any) && !isNaN(parseFloat(str)); +} + +export class DatetimeNoTzInterface extends DatetimeInterface { + async toValue(value: any, ctx: any = {}): Promise { + if (!value) { + return null; + } + + if (typeof value === 'string') { + const match = /^(\d{4})[-/]?(\d{2})[-/]?(\d{2})$/.exec(value); + if (match) { + return `${match[1]}-${match[2]}-${match[3]}`; + } + } + + if (dayjs.isDayjs(value)) { + return value; + } else if (isDate(value)) { + return value; + } else if (isNumeric(value)) { + const date = getJsDateFromExcel(value); + return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; + } else if (typeof value === 'string') { + return value; + } + + throw new Error(`Invalid date - ${value}`); + } + + toString(value: any, ctx?: any) { + const props = this.options?.uiSchema?.['x-component-props'] ?? {}; + const format = getDefaultFormat(props); + const m = str2moment(value, { ...props }); + return m ? m.format(format) : ''; + } +} diff --git a/packages/core/database/src/interfaces/index.ts b/packages/core/database/src/interfaces/index.ts index 00eba5d9d8..038ada52cc 100644 --- a/packages/core/database/src/interfaces/index.ts +++ b/packages/core/database/src/interfaces/index.ts @@ -12,4 +12,6 @@ export * from './percent-interface'; export * from './multiple-select-interface'; export * from './select-interface'; export * from './datetime-interface'; +export * from './datetime-no-tz-interface'; export * from './boolean-interface'; +export * from './date-interface'; diff --git a/packages/core/database/src/interfaces/utils.ts b/packages/core/database/src/interfaces/utils.ts index e23ac2e49f..170687f6e5 100644 --- a/packages/core/database/src/interfaces/utils.ts +++ b/packages/core/database/src/interfaces/utils.ts @@ -10,7 +10,9 @@ import Database from '../database'; import { BooleanInterface, + DateInterface, DatetimeInterface, + DatetimeNoTzInterface, MultipleSelectInterface, PercentInterface, SelectInterface, @@ -36,6 +38,9 @@ const interfaces = { radioGroup: SelectInterface, percent: PercentInterface, datetime: DatetimeInterface, + datetimeNoTz: DatetimeNoTzInterface, + unixTimestamp: DatetimeInterface, + date: DateInterface, createdAt: DatetimeInterface, updatedAt: DatetimeInterface, boolean: BooleanInterface, diff --git a/packages/core/utils/src/date.ts b/packages/core/utils/src/date.ts index 731c23a1f9..d35fe611e3 100644 --- a/packages/core/utils/src/date.ts +++ b/packages/core/utils/src/date.ts @@ -78,7 +78,7 @@ const toMoment = (val: any, options?: Str2momentOptions) => { if (!val) { return; } - const offset = options.utcOffset || -1 * new Date().getTimezoneOffset(); + const offset = options.utcOffset !== undefined ? options.utcOffset : -1 * new Date().getTimezoneOffset(); const { gmt, picker, utc = true } = options; if (dayjs(val).isValid()) { if (!utc) { diff --git a/packages/plugins/@nocobase/plugin-action-export/src/server/__tests__/export-to-xlsx.test.ts b/packages/plugins/@nocobase/plugin-action-export/src/server/__tests__/export-to-xlsx.test.ts index 01276726e6..78eb6f3808 100644 --- a/packages/plugins/@nocobase/plugin-action-export/src/server/__tests__/export-to-xlsx.test.ts +++ b/packages/plugins/@nocobase/plugin-action-export/src/server/__tests__/export-to-xlsx.test.ts @@ -31,6 +31,122 @@ describe('export to xlsx with preset', () => { await app.destroy(); }); + describe('export with date field', () => { + let Post; + + beforeEach(async () => { + Post = app.db.collection({ + name: 'posts', + fields: [ + { type: 'string', name: 'title' }, + { + name: 'datetime', + type: 'datetime', + interface: 'datetime', + uiSchema: { + 'x-component-props': { picker: 'date', dateFormat: 'YYYY-MM-DD', gmt: false, showTime: false, utc: true }, + type: 'string', + 'x-component': 'DatePicker', + title: 'dateTz', + }, + }, + { + name: 'dateOnly', + type: 'dateOnly', + interface: 'date', + defaultToCurrentTime: false, + onUpdateToCurrentTime: false, + timezone: true, + }, + { + name: 'datetimeNoTz', + type: 'datetimeNoTz', + interface: 'datetimeNoTz', + uiSchema: { + 'x-component-props': { picker: 'date', dateFormat: 'YYYY-MM-DD', gmt: false, showTime: false, utc: true }, + type: 'string', + 'x-component': 'DatePicker', + title: 'dateTz', + }, + }, + { + name: 'unixTimestamp', + type: 'unixTimestamp', + interface: 'unixTimestamp', + uiSchema: { + 'x-component-props': { + picker: 'date', + dateFormat: 'YYYY-MM-DD', + showTime: true, + timeFormat: 'HH:mm:ss', + }, + }, + }, + ], + }); + + await app.db.sync(); + }); + + it('should export with datetime field', async () => { + await Post.repository.create({ + values: { + title: 'p1', + datetime: '2024-05-10T01:42:35.000Z', + dateOnly: '2024-05-10', + datetimeNoTz: '2024-01-01 00:00:00', + unixTimestamp: '2024-05-10T01:42:35.000Z', + }, + }); + + const exporter = new XlsxExporter({ + collectionManager: app.mainDataSource.collectionManager, + collection: Post, + chunkSize: 10, + columns: [ + { dataIndex: ['title'], defaultTitle: 'Title' }, + { + dataIndex: ['datetime'], + defaultTitle: 'datetime', + }, + { + dataIndex: ['dateOnly'], + defaultTitle: 'dateOnly', + }, + { + dataIndex: ['datetimeNoTz'], + defaultTitle: 'datetimeNoTz', + }, + { + dataIndex: ['unixTimestamp'], + defaultTitle: 'unixTimestamp', + }, + ], + }); + + const wb = await exporter.run(); + + const xlsxFilePath = path.resolve(__dirname, `t_${uid()}.xlsx`); + + try { + XLSX.writeFile(wb, xlsxFilePath); + + // read xlsx file + const workbook = XLSX.readFile(xlsxFilePath); + const firstSheet = workbook.Sheets[workbook.SheetNames[0]]; + const sheetData = XLSX.utils.sheet_to_json(firstSheet, { header: 1 }); + + const firstUser = sheetData[1]; + expect(firstUser[1]).toEqual('2024-05-10'); + expect(firstUser[2]).toEqual('2024-05-10'); + expect(firstUser[3]).toEqual('2024-01-01'); + expect(firstUser[4]).toEqual('2024-05-10 01:42:35'); + } finally { + fs.unlinkSync(xlsxFilePath); + } + }); + }); + it('should export with checkbox field', async () => { const Post = app.db.collection({ name: 'posts', @@ -520,7 +636,7 @@ describe('export to xlsx', () => { title: 'test_date', }, name: 'test_date', - type: 'date', + type: 'datetime', interface: 'datetime', }, ], @@ -548,11 +664,7 @@ describe('export to xlsx', () => { ], }); - const wb = await exporter.run({ - get() { - return '+08:00'; - }, - }); + const wb = await exporter.run(); const xlsxFilePath = path.resolve(__dirname, `t_${uid()}.xlsx`); try { @@ -564,7 +676,7 @@ describe('export to xlsx', () => { const sheetData = XLSX.utils.sheet_to_json(firstSheet, { header: 1 }); const firstUser = sheetData[1]; - expect(firstUser).toEqual(['some_title', '2024-05-10 09:42:35']); + expect(firstUser).toEqual(['some_title', '2024-05-10 01:42:35']); } finally { fs.unlinkSync(xlsxFilePath); } diff --git a/packages/plugins/@nocobase/plugin-action-import/src/server/__tests__/xlsx-importer.test.ts b/packages/plugins/@nocobase/plugin-action-import/src/server/__tests__/xlsx-importer.test.ts index f8f0d0beff..031c1f493f 100644 --- a/packages/plugins/@nocobase/plugin-action-import/src/server/__tests__/xlsx-importer.test.ts +++ b/packages/plugins/@nocobase/plugin-action-import/src/server/__tests__/xlsx-importer.test.ts @@ -38,24 +38,143 @@ describe('xlsx importer', () => { name: 'name', }, { - type: 'date', - name: 'date', + type: 'datetime', + name: 'datetime', interface: 'datetime', }, + { + type: 'datetimeNoTz', + name: 'datetimeNoTz', + interface: 'datetimeNoTz', + uiSchema: { + 'x-component-props': { + picker: 'date', + dateFormat: 'YYYY-MM-DD', + showTime: true, + timeFormat: 'HH:mm:ss', + }, + }, + }, + { + type: 'dateOnly', + name: 'dateOnly', + interface: 'date', + }, + { + type: 'unixTimestamp', + name: 'unixTimestamp', + interface: 'unixTimestamp', + uiSchema: { + 'x-component-props': { + picker: 'date', + dateFormat: 'YYYY-MM-DD', + showTime: true, + timeFormat: 'HH:mm:ss', + }, + }, + }, ], }); await app.db.sync(); }); - it('should import with date', async () => { + it('should import with dateOnly', async () => { const columns = [ { dataIndex: ['name'], defaultTitle: '姓名', }, { - dataIndex: ['date'], + dataIndex: ['dateOnly'], + defaultTitle: '日期', + }, + ]; + + const templateCreator = new TemplateCreator({ + collection: User, + columns, + }); + + const template = await templateCreator.run(); + + const worksheet = template.Sheets[template.SheetNames[0]]; + + XLSX.utils.sheet_add_aoa( + worksheet, + [ + ['test', 77383], + ['test2', '2021-10-18'], + ], + { origin: 'A2' }, + ); + + const importer = new XlsxImporter({ + collectionManager: app.mainDataSource.collectionManager, + collection: User, + columns, + workbook: template, + }); + + await importer.run(); + + const users = (await User.repository.find()).map((user) => user.toJSON()); + expect(users[0]['dateOnly']).toBe('2111-11-12'); + expect(users[1]['dateOnly']).toBe('2021-10-18'); + }); + + it.skipIf(process.env['DB_DIALECT'] === 'sqlite')('should import with datetimeNoTz', async () => { + const columns = [ + { + dataIndex: ['name'], + defaultTitle: '姓名', + }, + { + dataIndex: ['datetimeNoTz'], + defaultTitle: '日期', + }, + ]; + + const templateCreator = new TemplateCreator({ + collection: User, + columns, + }); + + const template = await templateCreator.run(); + + const worksheet = template.Sheets[template.SheetNames[0]]; + + XLSX.utils.sheet_add_aoa( + worksheet, + [ + ['test', 77383], + ['test2', '2021-10-18'], + ], + { origin: 'A2' }, + ); + + const importer = new XlsxImporter({ + collectionManager: app.mainDataSource.collectionManager, + collection: User, + columns, + workbook: template, + }); + + await importer.run(); + + const users = (await User.repository.find()).map((user) => user.toJSON()); + expect(users[0]['datetimeNoTz']).toBe('2111-11-12 00:00:00'); + expect(users[1]['datetimeNoTz']).toBe('2021-10-18 00:00:00'); + }); + + it('should import with unixTimestamp', async () => { + const columns = [ + { + dataIndex: ['name'], + defaultTitle: '姓名', + }, + { + dataIndex: ['unixTimestamp'], defaultTitle: '日期', }, ]; @@ -80,11 +199,44 @@ describe('xlsx importer', () => { await importer.run(); - expect(await User.repository.count()).toBe(1); + const users = (await User.repository.find()).map((user) => user.toJSON()); + expect(moment(users[0]['unixTimestamp']).toISOString()).toEqual('2111-11-12T00:00:00.000Z'); + }); - const user = await User.repository.findOne(); + it('should import with datetimeTz', async () => { + const columns = [ + { + dataIndex: ['name'], + defaultTitle: '姓名', + }, + { + dataIndex: ['datetime'], + defaultTitle: '日期', + }, + ]; - expect(moment(user.get('date')).format('YYYY-MM-DD')).toBe('2111-11-12'); + const templateCreator = new TemplateCreator({ + collection: User, + columns, + }); + + const template = await templateCreator.run(); + + const worksheet = template.Sheets[template.SheetNames[0]]; + + XLSX.utils.sheet_add_aoa(worksheet, [['test', 77383]], { origin: 'A2' }); + + const importer = new XlsxImporter({ + collectionManager: app.mainDataSource.collectionManager, + collection: User, + columns, + workbook: template, + }); + + await importer.run(); + + const users = (await User.repository.find()).map((user) => user.toJSON()); + expect(moment(users[0]['datetime']).toISOString()).toEqual('2111-11-12T00:00:00.000Z'); }); });