mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 06:15:11 +00:00
Merge branch 'next' into F-827
This commit is contained in:
commit
0d778eb00f
@ -2,9 +2,7 @@
|
|||||||
"version": "1.4.0-alpha.2",
|
"version": "1.4.0-alpha.2",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"npmClientArgs": [
|
"npmClientArgs": ["--ignore-engines"],
|
||||||
"--ignore-engines"
|
|
||||||
],
|
|
||||||
"command": {
|
"command": {
|
||||||
"version": {
|
"version": {
|
||||||
"forcePublish": true,
|
"forcePublish": true,
|
||||||
|
@ -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",
|
"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",
|
"Enable secondary confirmation": "Enable secondary confirmation",
|
||||||
"Notification": "Notification",
|
"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."
|
||||||
}
|
}
|
||||||
|
@ -768,5 +768,7 @@
|
|||||||
"Sorry, the page you visited does not exist.": "Lo siento, la página que visitaste no existe.",
|
"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",
|
"Allow multiple selection": "Permitir selección múltiple",
|
||||||
"Parent object": "Objeto padre",
|
"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."
|
||||||
}
|
}
|
||||||
|
@ -788,5 +788,7 @@
|
|||||||
"Sorry, the page you visited does not exist.": "Désolé, la page que vous avez visitée n'existe pas.",
|
"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",
|
"Allow multiple selection": "Permettre la sélection multiple",
|
||||||
"Parent object": "Objet parent",
|
"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."
|
||||||
}
|
}
|
||||||
|
@ -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": "ページング時にテーブルレコードの総数取得をスキップして、読み込み速度を向上させます。データ量が多い場合にこのオプションの使用をお勧めします。",
|
"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}}」を閲覧する権限はありません。",
|
"The current user only has the UI configuration permission, but don't have view permission for collection \"{{name}}\"": "現在のユーザーにはUI設定の権限しかなく、コレクション「{{name}}」を閲覧する権限はありません。",
|
||||||
"Allow multiple selection": "複数選択を許可",
|
"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.": "設定モードでは、列全体が透明になります。非設定モードでは、列全体が非表示になります。列全体が非表示になっても、設定されたデフォルト値やその他の設定は依然として有効です。"
|
||||||
}
|
}
|
||||||
|
@ -879,5 +879,7 @@
|
|||||||
"Sorry, the page you visited does not exist.": "죄송합니다. 방문한 페이지가 존재하지 않습니다.",
|
"Sorry, the page you visited does not exist.": "죄송합니다. 방문한 페이지가 존재하지 않습니다.",
|
||||||
"Allow multiple selection": "다중 선택 허용",
|
"Allow multiple selection": "다중 선택 허용",
|
||||||
"Parent object": "부모 객체",
|
"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.": "구성 모드에서는 전체 열이 투명해집니다. 비구성 모드에서는 전체 열이 숨겨집니다. 전체 열이 숨겨져도 구성된 기본값 및 기타 설정은 여전히 적용됩니다."
|
||||||
}
|
}
|
||||||
|
@ -745,5 +745,7 @@
|
|||||||
"Sorry, the page you visited does not exist.": "Desculpe, a página que você visitou não existe.",
|
"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",
|
"Allow multiple selection": "Permitir seleção múltipla",
|
||||||
"Parent object": "Objeto pai",
|
"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."
|
||||||
}
|
}
|
||||||
|
@ -582,5 +582,7 @@
|
|||||||
"Sorry, the page you visited does not exist.": "Извините, посещенной вами страницы не существует.",
|
"Sorry, the page you visited does not exist.": "Извините, посещенной вами страницы не существует.",
|
||||||
"Allow multiple selection": "Разрешить множественный выбор",
|
"Allow multiple selection": "Разрешить множественный выбор",
|
||||||
"Parent object": "Родительский объект",
|
"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.": "В режиме конфигурации вся колонка становится прозрачной. В режиме не конфигурации вся колонка будет скрыта. Даже если вся колонка будет скрыта, её настроенные значения по умолчанию и другие настройки все равно будут действовать."
|
||||||
}
|
}
|
||||||
|
@ -580,5 +580,7 @@
|
|||||||
"Sorry, the page you visited does not exist.": "Üzgünüz, ziyaret ettiğiniz sayfa mevcut değil.",
|
"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",
|
"Allow multiple selection": "Çoklu seçim izni",
|
||||||
"Parent object": "Üst nesne",
|
"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."
|
||||||
}
|
}
|
||||||
|
@ -788,5 +788,7 @@
|
|||||||
"Sorry, the page you visited does not exist.": "Вибачте, сторінка, яку ви відвідали, не існує.",
|
"Sorry, the page you visited does not exist.": "Вибачте, сторінка, яку ви відвідали, не існує.",
|
||||||
"Allow multiple selection": "Дозволити множинний вибір",
|
"Allow multiple selection": "Дозволити множинний вибір",
|
||||||
"Parent object": "Батьківський об'єкт",
|
"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.": "В режимі конфігурації вся колонка стає прозорою. В режимі не конфігурації вся колонка буде прихована. Якщо вся колонка буде прихована, її налаштовані значення за замовчуванням і інші налаштування все одно будуть діяти."
|
||||||
}
|
}
|
||||||
|
@ -1025,5 +1025,7 @@
|
|||||||
"Ellipsis": "省略",
|
"Ellipsis": "省略",
|
||||||
"Set block layout": "设置区块布局",
|
"Set block layout": "设置区块布局",
|
||||||
"Add & Update": "添加 & 更新",
|
"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.": "在配置模式下,整个列会变为透明色。在非配置模式下,整个列将被隐藏。即使整个列被隐藏了,其配置的默认值和其他设置仍然有效。"
|
||||||
}
|
}
|
||||||
|
@ -878,5 +878,7 @@
|
|||||||
"Sorry, the page you visited does not exist.": "抱歉,你訪問的頁面不存在。",
|
"Sorry, the page you visited does not exist.": "抱歉,你訪問的頁面不存在。",
|
||||||
"Allow multiple selection": "允許多選",
|
"Allow multiple selection": "允許多選",
|
||||||
"Parent object": "上級物件",
|
"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.": "在配置模式下,整個列會變為透明色。在非配置模式下,整個列將被隱藏。即使整個列被隱藏了,其配置的默認值和其他設置仍然有效。"
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
@ -7926,6 +7926,447 @@ export const differentURL_DifferentPopupContent = {
|
|||||||
'x-index': 1,
|
'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,
|
||||||
|
},
|
||||||
|
};
|
||||||
export const subTableLinkageRules = {
|
export const subTableLinkageRules = {
|
||||||
collections: [
|
collections: [
|
||||||
{
|
{
|
||||||
|
@ -7,8 +7,10 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||||
import { ISchema } from '@formily/json-schema';
|
import { ISchema } from '@formily/json-schema';
|
||||||
import { useField, useFieldSchema } from '@formily/react';
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
|
import { Tooltip } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useApp, useSchemaToolbar } from '../../../../application';
|
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: (
|
||||||
|
<span>
|
||||||
|
{t('Hide column')}
|
||||||
|
<Tooltip
|
||||||
|
title={t(
|
||||||
|
'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.',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<QuestionCircleOutlined style={{ marginLeft: 4, opacity: 0.65 }} />
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
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,
|
fieldComponentSettingsItem,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -102,7 +102,7 @@ test.describe('add menu item', () => {
|
|||||||
|
|
||||||
// open link page
|
// open link page
|
||||||
await page.getByLabel(pageLink).click();
|
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
|
// 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];
|
const newPage = page.context().pages()[1];
|
||||||
|
@ -10,11 +10,11 @@
|
|||||||
import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__';
|
import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__';
|
||||||
import { isArr } from '@formily/shared';
|
import { isArr } from '@formily/shared';
|
||||||
import {
|
import {
|
||||||
|
getDefaultFormat,
|
||||||
GetDefaultFormatProps,
|
GetDefaultFormatProps,
|
||||||
|
str2moment,
|
||||||
Str2momentOptions,
|
Str2momentOptions,
|
||||||
Str2momentValue,
|
Str2momentValue,
|
||||||
getDefaultFormat,
|
|
||||||
str2moment,
|
|
||||||
} from '@nocobase/utils/client';
|
} from '@nocobase/utils/client';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
@ -67,6 +67,7 @@ ReadPretty.DateRangePicker = function DateRangePicker(props: DateRangePickerRead
|
|||||||
const labels = m.map((m) => m.format(format));
|
const labels = m.map((m) => m.format(format));
|
||||||
return isArr(labels) ? labels.join('~') : labels;
|
return isArr(labels) ? labels.join('~') : labels;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cls(prefixCls, props.className)} style={props.style}>
|
<div className={cls(prefixCls, props.className)} style={props.style}>
|
||||||
{getLabels()}
|
{getLabels()}
|
||||||
|
@ -86,8 +86,12 @@ const InternalRemoteSelect = withDynamicSchemaProps(
|
|||||||
|
|
||||||
const operator = useMemo(() => {
|
const operator = useMemo(() => {
|
||||||
if (targetField?.interface) {
|
if (targetField?.interface) {
|
||||||
const initialOperator = getInterface(targetField.interface)?.filterable?.operators[0].value || '$includes';
|
const targetInterface = getInterface(targetField.interface);
|
||||||
return initialOperator !== '$eq' ? initialOperator : '$includes';
|
const initialOperator = targetInterface?.filterable?.operators[0].value || '$includes';
|
||||||
|
if (targetField.type === 'string') {
|
||||||
|
return '$includes';
|
||||||
|
}
|
||||||
|
return initialOperator;
|
||||||
}
|
}
|
||||||
return '$includes';
|
return '$includes';
|
||||||
}, [targetField]);
|
}, [targetField]);
|
||||||
|
@ -121,13 +121,15 @@ const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginat
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
const dataIndex = collectionFields?.length > 0 ? collectionFields[0].name : s.name;
|
const dataIndex = collectionFields?.length > 0 ? collectionFields[0].name : s.name;
|
||||||
|
const columnHidden = !!s['x-component-props']?.['columnHidden'];
|
||||||
return {
|
return {
|
||||||
title: <RecursionField name={s.name} schema={s} onlyRenderSelf />,
|
title: <RecursionField name={s.name} schema={s} onlyRenderSelf />,
|
||||||
dataIndex,
|
dataIndex,
|
||||||
key: s.name,
|
key: s.name,
|
||||||
sorter: s['x-component-props']?.['sorter'],
|
sorter: s['x-component-props']?.['sorter'],
|
||||||
width: 200,
|
columnHidden,
|
||||||
...s['x-component-props'],
|
...s['x-component-props'],
|
||||||
|
width: columnHidden && !designable ? 0 : s['x-component-props']?.width || 200,
|
||||||
render: (v, record) => {
|
render: (v, record) => {
|
||||||
// 这行代码会导致这里的测试不通过:packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/schemaInitializer.test.ts:189
|
// 这行代码会导致这里的测试不通过: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;
|
// 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) => {
|
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<any>;
|
} as TableColumnProps<any>;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// 这里不能把 columnsSchema 作为依赖,因为其每次都会变化,这里使用 hasChangedColumns 作为依赖
|
// 这里不能把 columnsSchema 作为依赖,因为其每次都会变化,这里使用 hasChangedColumns 作为依赖
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// 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(() => {
|
const tableColumns = useMemo(() => {
|
||||||
@ -164,7 +177,7 @@ const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginat
|
|||||||
return columns;
|
return columns;
|
||||||
}
|
}
|
||||||
const res = [
|
const res = [
|
||||||
...columns,
|
...adjustColumnOrder(columns),
|
||||||
{
|
{
|
||||||
title: render(),
|
title: render(),
|
||||||
dataIndex: 'TABLE_COLUMN_INITIALIZER',
|
dataIndex: 'TABLE_COLUMN_INITIALIZER',
|
||||||
@ -210,7 +223,7 @@ const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginat
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return adjustColumnOrder(res);
|
return res;
|
||||||
}, [columns, exists, field, render, props.showDel, designable]);
|
}, [columns, exists, field, render, props.showDel, designable]);
|
||||||
|
|
||||||
return tableColumns;
|
return tableColumns;
|
||||||
@ -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 HeaderCellComponent = (props) => {
|
||||||
|
const { designable } = useDesignable();
|
||||||
|
|
||||||
|
if (props.columnHidden) {
|
||||||
|
return <th style={designable ? columnOpacityStyle : columnHiddenStyle}>{designable ? props.children : null}</th>;
|
||||||
|
}
|
||||||
|
|
||||||
return <th {...props} className={cls(props.className, headerClass)} />;
|
return <th {...props} className={cls(props.className, headerClass)} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -453,7 +484,7 @@ const BodyRowComponent = (props: {
|
|||||||
return <SortableRow {...props} />;
|
return <SortableRow {...props} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BodyCellComponent = (props) => {
|
const InternalBodyCellComponent = (props) => {
|
||||||
const { token } = useToken();
|
const { token } = useToken();
|
||||||
const inView = useContext(InViewContext);
|
const inView = useContext(InViewContext);
|
||||||
const isIndex = props.className?.includes('selection-column');
|
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 (
|
||||||
|
<td style={designable ? columnOpacityStyle : columnHiddenStyle}>
|
||||||
|
{designable ? props.children : <span style={displayNone}>{props.children}</span>}
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <InternalBodyCellComponent {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
interface TableProps {
|
interface TableProps {
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
useProps?: () => any;
|
useProps?: () => any;
|
||||||
|
@ -65,7 +65,16 @@ const useTableColumns = () => {
|
|||||||
{/* fix https://nocobase.height.app/T-3232/description */}
|
{/* fix https://nocobase.height.app/T-3232/description */}
|
||||||
{/* 如果作为关系表格区块,则 parentRecordData 应该有值;如果作为普通表格使用(如数据源管理页面的表格)则应该使用 recordData,且 parentRecordData 为空 */}
|
{/* 如果作为关系表格区块,则 parentRecordData 应该有值;如果作为普通表格使用(如数据源管理页面的表格)则应该使用 recordData,且 parentRecordData 为空 */}
|
||||||
<RecordProvider record={record} parent={parentRecordData || recordData}>
|
<RecordProvider record={record} parent={parentRecordData || recordData}>
|
||||||
<RecursionField schema={s} name={record.__index || index} onlyRenderProperties />
|
<span
|
||||||
|
role="button"
|
||||||
|
className={css`
|
||||||
|
.ant-space-gap-col-small {
|
||||||
|
column-gap: 10px;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<RecursionField schema={s} name={record.__index || index} onlyRenderProperties />
|
||||||
|
</span>
|
||||||
</RecordProvider>
|
</RecordProvider>
|
||||||
</RecordIndexProvider>
|
</RecordIndexProvider>
|
||||||
);
|
);
|
||||||
|
@ -363,7 +363,7 @@ export const SchemaSettingsFormItemTemplate = function FormItemTemplate(props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface SchemaSettingsItemProps extends Omit<MenuItemProps, 'title'> {
|
export interface SchemaSettingsItemProps extends Omit<MenuItemProps, 'title'> {
|
||||||
title: string;
|
title: string | ReactNode;
|
||||||
}
|
}
|
||||||
export const SchemaSettingsItem: FC<SchemaSettingsItemProps> = (props) => {
|
export const SchemaSettingsItem: FC<SchemaSettingsItemProps> = (props) => {
|
||||||
const { pushMenuItem } = useCollectMenuItems();
|
const { pushMenuItem } = useCollectMenuItems();
|
||||||
@ -544,7 +544,7 @@ export const SchemaSettingsCascaderItem: FC<SchemaSettingsCascaderItemProps> = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface SchemaSettingsSwitchItemProps extends Omit<MenuItemProps, 'onChange'> {
|
export interface SchemaSettingsSwitchItemProps extends Omit<MenuItemProps, 'onChange'> {
|
||||||
title: string;
|
title: string | ReactNode;
|
||||||
checked?: boolean;
|
checked?: boolean;
|
||||||
onChange?: (v: boolean) => void;
|
onChange?: (v: boolean) => void;
|
||||||
}
|
}
|
||||||
@ -598,9 +598,7 @@ export const SchemaSettingsPopupItem: FC<SchemaSettingsPopupProps> = (props) =>
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface SchemaSettingsActionModalItemProps
|
export interface SchemaSettingsActionModalItemProps extends SchemaSettingsModalItemProps {
|
||||||
extends SchemaSettingsModalItemProps,
|
|
||||||
Omit<SchemaSettingsItemProps, 'onSubmit' | 'onClick'> {
|
|
||||||
uid?: string;
|
uid?: string;
|
||||||
initialSchema?: ISchema;
|
initialSchema?: ISchema;
|
||||||
schema?: ISchema;
|
schema?: ISchema;
|
||||||
|
@ -39,7 +39,7 @@ describe('Date time interface', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'dateTime',
|
name: 'dateTime',
|
||||||
type: 'date',
|
type: 'datetime',
|
||||||
uiSchema: {
|
uiSchema: {
|
||||||
['x-component-props']: {
|
['x-component-props']: {
|
||||||
showTime: true,
|
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('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-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');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
7
packages/core/database/src/interfaces/date-interface.ts
Normal file
7
packages/core/database/src/interfaces/date-interface.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { DatetimeInterface } from './datetime-interface';
|
||||||
|
|
||||||
|
export class DateInterface extends DatetimeInterface {
|
||||||
|
toString(value: any, ctx?: any): any {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { BaseInterface } from './base-interface';
|
import { BaseInterface } from './base-interface';
|
||||||
import { getDefaultFormat, moment2str, str2moment } from '@nocobase/utils';
|
import { getDefaultFormat, str2moment } from '@nocobase/utils';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { getJsDateFromExcel } from 'excel-date-to-js';
|
import { getJsDateFromExcel } from 'excel-date-to-js';
|
||||||
|
|
||||||
@ -51,11 +51,7 @@ export class DatetimeInterface extends BaseInterface {
|
|||||||
} else if (isNumeric(value)) {
|
} else if (isNumeric(value)) {
|
||||||
return getJsDateFromExcel(value).toISOString();
|
return getJsDateFromExcel(value).toISOString();
|
||||||
} else if (typeof value === 'string') {
|
} else if (typeof value === 'string') {
|
||||||
const props = ctx.field?.options?.uiSchema?.['x-component-props'] || {};
|
return value;
|
||||||
const m = dayjs(value);
|
|
||||||
if (m.isValid()) {
|
|
||||||
return moment2str(m, props);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Invalid date - ${value}`);
|
throw new Error(`Invalid date - ${value}`);
|
||||||
|
@ -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<any> {
|
||||||
|
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) : '';
|
||||||
|
}
|
||||||
|
}
|
@ -12,4 +12,6 @@ export * from './percent-interface';
|
|||||||
export * from './multiple-select-interface';
|
export * from './multiple-select-interface';
|
||||||
export * from './select-interface';
|
export * from './select-interface';
|
||||||
export * from './datetime-interface';
|
export * from './datetime-interface';
|
||||||
|
export * from './datetime-no-tz-interface';
|
||||||
export * from './boolean-interface';
|
export * from './boolean-interface';
|
||||||
|
export * from './date-interface';
|
||||||
|
@ -10,7 +10,9 @@
|
|||||||
import Database from '../database';
|
import Database from '../database';
|
||||||
import {
|
import {
|
||||||
BooleanInterface,
|
BooleanInterface,
|
||||||
|
DateInterface,
|
||||||
DatetimeInterface,
|
DatetimeInterface,
|
||||||
|
DatetimeNoTzInterface,
|
||||||
MultipleSelectInterface,
|
MultipleSelectInterface,
|
||||||
PercentInterface,
|
PercentInterface,
|
||||||
SelectInterface,
|
SelectInterface,
|
||||||
@ -36,6 +38,9 @@ const interfaces = {
|
|||||||
radioGroup: SelectInterface,
|
radioGroup: SelectInterface,
|
||||||
percent: PercentInterface,
|
percent: PercentInterface,
|
||||||
datetime: DatetimeInterface,
|
datetime: DatetimeInterface,
|
||||||
|
datetimeNoTz: DatetimeNoTzInterface,
|
||||||
|
unixTimestamp: DatetimeInterface,
|
||||||
|
date: DateInterface,
|
||||||
createdAt: DatetimeInterface,
|
createdAt: DatetimeInterface,
|
||||||
updatedAt: DatetimeInterface,
|
updatedAt: DatetimeInterface,
|
||||||
boolean: BooleanInterface,
|
boolean: BooleanInterface,
|
||||||
|
@ -78,7 +78,7 @@ const toMoment = (val: any, options?: Str2momentOptions) => {
|
|||||||
if (!val) {
|
if (!val) {
|
||||||
return;
|
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;
|
const { gmt, picker, utc = true } = options;
|
||||||
if (dayjs(val).isValid()) {
|
if (dayjs(val).isValid()) {
|
||||||
if (!utc) {
|
if (!utc) {
|
||||||
|
@ -31,6 +31,122 @@ describe('export to xlsx with preset', () => {
|
|||||||
await app.destroy();
|
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 () => {
|
it('should export with checkbox field', async () => {
|
||||||
const Post = app.db.collection({
|
const Post = app.db.collection({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
@ -520,7 +636,7 @@ describe('export to xlsx', () => {
|
|||||||
title: 'test_date',
|
title: 'test_date',
|
||||||
},
|
},
|
||||||
name: 'test_date',
|
name: 'test_date',
|
||||||
type: 'date',
|
type: 'datetime',
|
||||||
interface: 'datetime',
|
interface: 'datetime',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -548,11 +664,7 @@ describe('export to xlsx', () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const wb = await exporter.run({
|
const wb = await exporter.run();
|
||||||
get() {
|
|
||||||
return '+08:00';
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const xlsxFilePath = path.resolve(__dirname, `t_${uid()}.xlsx`);
|
const xlsxFilePath = path.resolve(__dirname, `t_${uid()}.xlsx`);
|
||||||
try {
|
try {
|
||||||
@ -564,7 +676,7 @@ describe('export to xlsx', () => {
|
|||||||
const sheetData = XLSX.utils.sheet_to_json(firstSheet, { header: 1 });
|
const sheetData = XLSX.utils.sheet_to_json(firstSheet, { header: 1 });
|
||||||
|
|
||||||
const firstUser = sheetData[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 {
|
} finally {
|
||||||
fs.unlinkSync(xlsxFilePath);
|
fs.unlinkSync(xlsxFilePath);
|
||||||
}
|
}
|
||||||
|
@ -38,24 +38,143 @@ describe('xlsx importer', () => {
|
|||||||
name: 'name',
|
name: 'name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'date',
|
type: 'datetime',
|
||||||
name: 'date',
|
name: 'datetime',
|
||||||
interface: '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();
|
await app.db.sync();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should import with date', async () => {
|
it('should import with dateOnly', async () => {
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
dataIndex: ['name'],
|
dataIndex: ['name'],
|
||||||
defaultTitle: '姓名',
|
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: '日期',
|
defaultTitle: '日期',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -80,11 +199,44 @@ describe('xlsx importer', () => {
|
|||||||
|
|
||||||
await importer.run();
|
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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -23,5 +23,6 @@
|
|||||||
"No authentication methods available.": "No authentication methods available.",
|
"No authentication methods available.": "No authentication methods available.",
|
||||||
"The password is inconsistent, please re-enter": "The password is inconsistent, please re-enter",
|
"The password is inconsistent, please re-enter": "The password is inconsistent, please re-enter",
|
||||||
"Sign-in": "Sign-in",
|
"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"
|
||||||
}
|
}
|
||||||
|
@ -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": "密码",
|
||||||
|
"The username/email or password is incorrect, please re-enter": "用户名/邮箱或密码有误,请重新输入"
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ describe('actions', () => {
|
|||||||
email: 'no-exists@nocobase.com',
|
email: 'no-exists@nocobase.com',
|
||||||
});
|
});
|
||||||
expect(res.statusCode).toEqual(401);
|
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 () => {
|
it('should check password when signing in', async () => {
|
||||||
@ -126,7 +126,7 @@ describe('actions', () => {
|
|||||||
password: 'incorrect',
|
password: 'incorrect',
|
||||||
});
|
});
|
||||||
expect(res.statusCode).toEqual(401);
|
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 () => {
|
it('should sign in with password', async () => {
|
||||||
|
@ -39,13 +39,13 @@ export class BasicAuth extends BaseAuth {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
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<PasswordField>('password');
|
const field = this.userCollection.getField<PasswordField>('password');
|
||||||
const valid = await field.verify(password, user.password);
|
const valid = await field.verify(password, user.password);
|
||||||
if (!valid) {
|
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;
|
return user;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"unique violation": "{{field}} must be unique",
|
"unique violation": "{{field}} already exists",
|
||||||
"notNull violation": "notNull violation",
|
"notNull violation": "{{field}} cannot be null",
|
||||||
"Validation error": "{{field}} validation error",
|
"Validation error": "{{field}} validation error",
|
||||||
"notNull Violation": "{{field}} cannot be null"
|
"notNull Violation": "{{field}} cannot be null"
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"unique violation": "{{field}} 字段值是唯一的",
|
"unique violation": "{{field}} 字段值已存在",
|
||||||
"notNull violation": "{{field}} 字段不能为空",
|
"notNull violation": "{{field}} 字段不能为空",
|
||||||
"Validation error": "{{field}} 字段规则验证失败"
|
"Validation error": "{{field}} 字段规则验证失败"
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ export class PluginErrorHandlerServer extends Plugin {
|
|||||||
return {
|
return {
|
||||||
message: t(err.type, {
|
message: t(err.type, {
|
||||||
ns: this.i18nNs,
|
ns: this.i18nNs,
|
||||||
field: t(title, { ns: 'lm-collections' }),
|
field: t(title, { ns: ['lm-collections', 'client'] }),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
Loading…
Reference in New Issue
Block a user