mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 21:49:25 +08:00
Merge branch 'next' into develop
This commit is contained in:
commit
8d89174f31
12
packages/core/client/src/i18n/constant.ts
Normal file
12
packages/core/client/src/i18n/constant.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const NAMESPACE_UI_SCHEMA = 'ui-schema-storage';
|
||||||
|
|
||||||
|
export { NAMESPACE_UI_SCHEMA };
|
@ -8,3 +8,4 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './i18n';
|
export * from './i18n';
|
||||||
|
export * from './constant';
|
||||||
|
@ -37,8 +37,8 @@ test.describe('linkage rules', () => {
|
|||||||
await page.getByText('Add condition', { exact: true }).click();
|
await page.getByText('Add condition', { exact: true }).click();
|
||||||
await page.getByTestId('select-filter-field').click();
|
await page.getByTestId('select-filter-field').click();
|
||||||
await page.getByRole('menuitemcheckbox', { name: 'singleLineText' }).click();
|
await page.getByRole('menuitemcheckbox', { name: 'singleLineText' }).click();
|
||||||
await page.getByLabel('Linkage rules').locator('input[type="text"]').click();
|
await page.getByLabel('Linkage rules').getByRole('tabpanel').getByRole('textbox').click();
|
||||||
await page.getByLabel('Linkage rules').locator('input[type="text"]').fill('123');
|
await page.getByLabel('Linkage rules').getByRole('tabpanel').getByRole('textbox').fill('123');
|
||||||
|
|
||||||
// action:禁用 longText 字段
|
// action:禁用 longText 字段
|
||||||
await page.getByText('Add property').click();
|
await page.getByText('Add property').click();
|
||||||
|
@ -85,6 +85,7 @@ test.describe('configure fields', () => {
|
|||||||
await page.getByRole('menuitem', { name: 'manyToOne2 right' }).hover();
|
await page.getByRole('menuitem', { name: 'manyToOne2 right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'manyToOne3' }).click();
|
await page.getByRole('menuitem', { name: 'manyToOne3' }).click();
|
||||||
await page.mouse.move(600, 0);
|
await page.mouse.move(600, 0);
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
await expect(page.getByLabel('block-item-CollectionField-general-form-general.manyToOne1-manyToOne1')).toHaveText(
|
await expect(page.getByLabel('block-item-CollectionField-general-form-general.manyToOne1-manyToOne1')).toHaveText(
|
||||||
`manyToOne1:${record.manyToOne1.id}`,
|
`manyToOne1:${record.manyToOne1.id}`,
|
||||||
|
@ -41,6 +41,7 @@ test.describe('where list block can be added', () => {
|
|||||||
await page.getByLabel('schema-initializer-Grid-').nth(1).hover();
|
await page.getByLabel('schema-initializer-Grid-').nth(1).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Role name' }).click();
|
await page.getByRole('menuitem', { name: 'Role name' }).click();
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
|
await page.reload();
|
||||||
await expect(page.getByLabel('block-item-CollectionField-').getByText('Root')).toBeVisible();
|
await expect(page.getByLabel('block-item-CollectionField-').getByText('Root')).toBeVisible();
|
||||||
await expect(page.getByLabel('block-item-CollectionField-').getByText('Admin')).toBeVisible();
|
await expect(page.getByLabel('block-item-CollectionField-').getByText('Admin')).toBeVisible();
|
||||||
await expect(page.getByLabel('block-item-CollectionField-').getByText('Member')).toBeVisible();
|
await expect(page.getByLabel('block-item-CollectionField-').getByText('Member')).toBeVisible();
|
||||||
|
@ -13,14 +13,16 @@ import { T4334 } from '../templatesOfBug';
|
|||||||
// fix https://nocobase.height.app/T-2187
|
// fix https://nocobase.height.app/T-2187
|
||||||
test('action linkage by row data', async ({ page, mockPage }) => {
|
test('action linkage by row data', async ({ page, mockPage }) => {
|
||||||
await mockPage(T4334).goto();
|
await mockPage(T4334).goto();
|
||||||
const adminEditAction = page.getByLabel('action-Action.Link-Edit-update-roles-table-admin');
|
const adminEditAction = page
|
||||||
|
.getByLabel('action-Action.Link-Edit-update-roles-table-admin')
|
||||||
|
.locator('.nb-action-title');
|
||||||
const adminEditActionStyle = await adminEditAction.evaluate((element) => {
|
const adminEditActionStyle = await adminEditAction.evaluate((element) => {
|
||||||
const computedStyle = window.getComputedStyle(element);
|
const computedStyle = window.getComputedStyle(element);
|
||||||
return {
|
return {
|
||||||
opacity: computedStyle.opacity,
|
opacity: computedStyle.opacity,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const rootEditAction = page.getByLabel('action-Action.Link-Edit-update-roles-table-root');
|
const rootEditAction = page.getByLabel('action-Action.Link-Edit-update-roles-table-root').locator('.nb-action-title');
|
||||||
const rootEditActionStyle = await rootEditAction.evaluate((element) => {
|
const rootEditActionStyle = await rootEditAction.evaluate((element) => {
|
||||||
const computedStyle = window.getComputedStyle(element);
|
const computedStyle = window.getComputedStyle(element);
|
||||||
return {
|
return {
|
||||||
|
@ -156,6 +156,7 @@ test.describe('configure columns', () => {
|
|||||||
await page.getByRole('menuitem', { name: 'manyToOne2 right' }).hover();
|
await page.getByRole('menuitem', { name: 'manyToOne2 right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'manyToOne3' }).click();
|
await page.getByRole('menuitem', { name: 'manyToOne3' }).click();
|
||||||
await page.mouse.move(600, 0);
|
await page.mouse.move(600, 0);
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
// 2. Click on the association field, create a details block in the popup, display the ID field, and assert if it's correct
|
// 2. Click on the association field, create a details block in the popup, display the ID field, and assert if it's correct
|
||||||
await page
|
await page
|
||||||
@ -194,6 +195,7 @@ test.describe('configure columns', () => {
|
|||||||
await page.getByLabel('schema-initializer-Grid-details:configureFields-emptyCollection').hover();
|
await page.getByLabel('schema-initializer-Grid-details:configureFields-emptyCollection').hover();
|
||||||
await page.getByRole('menuitem', { name: 'ID', exact: true }).click();
|
await page.getByRole('menuitem', { name: 'ID', exact: true }).click();
|
||||||
await page.mouse.move(600, 0);
|
await page.mouse.move(600, 0);
|
||||||
|
|
||||||
await expect(page.getByLabel('block-item-CollectionField-')).toHaveText(
|
await expect(page.getByLabel('block-item-CollectionField-')).toHaveText(
|
||||||
`ID:${record.manyToOne1.manyToOne2.manyToOne3.id}`,
|
`ID:${record.manyToOne1.manyToOne2.manyToOne3.id}`,
|
||||||
);
|
);
|
||||||
@ -212,6 +214,7 @@ test.describe('configure columns', () => {
|
|||||||
await page.getByRole('menuitem', { name: 'manyToOne2 right' }).hover();
|
await page.getByRole('menuitem', { name: 'manyToOne2 right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'manyToOne3' }).click();
|
await page.getByRole('menuitem', { name: 'manyToOne3' }).click();
|
||||||
await page.mouse.move(600, 0);
|
await page.mouse.move(600, 0);
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
// 2. 点击每一个关系字段,创建一个详情区块,显示 ID 字段,断言 ID 是否正确
|
// 2. 点击每一个关系字段,创建一个详情区块,显示 ID 字段,断言 ID 是否正确
|
||||||
await page
|
await page
|
||||||
@ -307,8 +310,8 @@ test.describe('configure actions column', () => {
|
|||||||
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||||
|
|
||||||
await page.getByText('Actions', { exact: true }).hover({ force: true });
|
// await page.getByText('Actions', { exact: true }).hover({ force: true });
|
||||||
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
// await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Duplicate' }).click();
|
await page.getByRole('menuitem', { name: 'Duplicate' }).click();
|
||||||
|
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
|
@ -36,7 +36,8 @@ test.describe('popup router', () => {
|
|||||||
await page.locator('.ant-drawer-mask').click();
|
await page.locator('.ant-drawer-mask').click();
|
||||||
|
|
||||||
// expect to be back to the first page
|
// expect to be back to the first page
|
||||||
await page.getByText('Users单层子页面Configure').hover();
|
await page.getByLabel('block-item-CardItem-users-').getByText('Users 单层子页面Configure').hover();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'designer-schema-settings-CardItem-blockSettings:table-users' }),
|
page.getByRole('button', { name: 'designer-schema-settings-CardItem-blockSettings:table-users' }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
@ -60,7 +61,7 @@ test.describe('popup router', () => {
|
|||||||
await page.locator('.ant-drawer-mask').click();
|
await page.locator('.ant-drawer-mask').click();
|
||||||
|
|
||||||
// expect to be back to the first page
|
// expect to be back to the first page
|
||||||
await page.getByText('Users单层子页面Configure').hover();
|
await page.getByLabel('block-item-CardItem-users-').getByText('Users 单层子页面Configure').hover();
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'designer-schema-settings-CardItem-blockSettings:table-users' }),
|
page.getByRole('button', { name: 'designer-schema-settings-CardItem-blockSettings:table-users' }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
import { observer, RecursionField, useField, useFieldSchema } from '@formily/react';
|
import { observer, RecursionField, useField, useFieldSchema } from '@formily/react';
|
||||||
import { Drawer } from 'antd';
|
import { Drawer } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { FC, startTransition, useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { FC, startTransition, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
|
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
|
||||||
import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField';
|
import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField';
|
||||||
@ -22,6 +23,7 @@ import { useActionContext } from './hooks';
|
|||||||
import { useSetAriaLabelForDrawer } from './hooks/useSetAriaLabelForDrawer';
|
import { useSetAriaLabelForDrawer } from './hooks/useSetAriaLabelForDrawer';
|
||||||
import { ActionDrawerProps, ComposedActionDrawer, OpenSize } from './types';
|
import { ActionDrawerProps, ComposedActionDrawer, OpenSize } from './types';
|
||||||
import { getZIndex, useZIndexContext, zIndexContext } from './zIndexContext';
|
import { getZIndex, useZIndexContext, zIndexContext } from './zIndexContext';
|
||||||
|
import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
||||||
|
|
||||||
const MemoizeRecursionField = React.memo(RecursionField);
|
const MemoizeRecursionField = React.memo(RecursionField);
|
||||||
MemoizeRecursionField.displayName = 'MemoizeRecursionField';
|
MemoizeRecursionField.displayName = 'MemoizeRecursionField';
|
||||||
@ -81,6 +83,7 @@ export const InternalActionDrawer: React.FC<ActionDrawerProps> = observer(
|
|||||||
const { visible, setVisible, openSize = 'middle', drawerProps } = useActionContext();
|
const { visible, setVisible, openSize = 'middle', drawerProps } = useActionContext();
|
||||||
const schema = useFieldSchema();
|
const schema = useFieldSchema();
|
||||||
const field = useField();
|
const field = useField();
|
||||||
|
const { t } = useTranslation();
|
||||||
const { componentCls, hashId } = useStyles();
|
const { componentCls, hashId } = useStyles();
|
||||||
const tabContext = useTabsContext();
|
const tabContext = useTabsContext();
|
||||||
const parentZIndex = useZIndexContext();
|
const parentZIndex = useZIndexContext();
|
||||||
@ -126,7 +129,7 @@ export const InternalActionDrawer: React.FC<ActionDrawerProps> = observer(
|
|||||||
<Drawer
|
<Drawer
|
||||||
zIndex={zIndex}
|
zIndex={zIndex}
|
||||||
width={openSizeWidthMap.get(openSize)}
|
width={openSizeWidthMap.get(openSize)}
|
||||||
title={field.title}
|
title={t(field.title, { ns: NAMESPACE_UI_SCHEMA })}
|
||||||
{...others}
|
{...others}
|
||||||
{...drawerProps}
|
{...drawerProps}
|
||||||
rootStyle={rootStyle}
|
rootStyle={rootStyle}
|
||||||
|
@ -48,6 +48,7 @@ import { ActionContextProvider } from './context';
|
|||||||
import { useGetAriaLabelOfAction } from './hooks/useGetAriaLabelOfAction';
|
import { useGetAriaLabelOfAction } from './hooks/useGetAriaLabelOfAction';
|
||||||
import { ActionContextProps, ActionProps, ComposedAction } from './types';
|
import { ActionContextProps, ActionProps, ComposedAction } from './types';
|
||||||
import { linkageAction, setInitialActionState } from './utils';
|
import { linkageAction, setInitialActionState } from './utils';
|
||||||
|
import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
||||||
|
|
||||||
// 这个要放到最下面,否则会导致前端单测失败
|
// 这个要放到最下面,否则会导致前端单测失败
|
||||||
import { useApp } from '../../../application';
|
import { useApp } from '../../../application';
|
||||||
@ -563,6 +564,7 @@ const RenderButtonInner = observer(
|
|||||||
onlyIcon,
|
onlyIcon,
|
||||||
...others
|
...others
|
||||||
} = props;
|
} = props;
|
||||||
|
const { t } = useTranslation();
|
||||||
const debouncedClick = useCallback(
|
const debouncedClick = useCallback(
|
||||||
debounce(
|
debounce(
|
||||||
(e: React.MouseEvent, checkPortal = true) => {
|
(e: React.MouseEvent, checkPortal = true) => {
|
||||||
@ -584,7 +586,8 @@ const RenderButtonInner = observer(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const actionTitle = title || field?.title;
|
const rawTitle = title ?? field?.title;
|
||||||
|
const actionTitle = typeof rawTitle === 'string' ? t(rawTitle, { ns: NAMESPACE_UI_SCHEMA }) : rawTitle;
|
||||||
const { opacity, ...restButtonStyle } = buttonStyle;
|
const { opacity, ...restButtonStyle } = buttonStyle;
|
||||||
const linkStyle = isLink && opacity ? { opacity } : undefined;
|
const linkStyle = isLink && opacity ? { opacity } : undefined;
|
||||||
return (
|
return (
|
||||||
|
@ -9,8 +9,10 @@
|
|||||||
|
|
||||||
import { Card, CardProps } from 'antd';
|
import { Card, CardProps } from 'antd';
|
||||||
import React, { useMemo, useRef, useEffect, createContext, useState } from 'react';
|
import React, { useMemo, useRef, useEffect, createContext, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useToken } from '../../../style';
|
import { useToken } from '../../../style';
|
||||||
import { MarkdownReadPretty } from '../markdown';
|
import { MarkdownReadPretty } from '../markdown';
|
||||||
|
import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
||||||
|
|
||||||
export const BlockItemCardContext = createContext({});
|
export const BlockItemCardContext = createContext({});
|
||||||
|
|
||||||
@ -22,6 +24,7 @@ export const BlockItemCard = React.forwardRef<HTMLDivElement, CardProps | any>((
|
|||||||
}, [token.marginBlock]);
|
}, [token.marginBlock]);
|
||||||
const [titleHeight, setTitleHeight] = useState(0);
|
const [titleHeight, setTitleHeight] = useState(0);
|
||||||
const titleRef = useRef<HTMLDivElement | null>(null);
|
const titleRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const { t } = useTranslation();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
if (titleRef.current) {
|
if (titleRef.current) {
|
||||||
@ -38,10 +41,10 @@ export const BlockItemCard = React.forwardRef<HTMLDivElement, CardProps | any>((
|
|||||||
}, [blockTitle, description]);
|
}, [blockTitle, description]);
|
||||||
const title = (blockTitle || description) && (
|
const title = (blockTitle || description) && (
|
||||||
<div ref={titleRef} style={{ padding: '8px 0px 8px' }}>
|
<div ref={titleRef} style={{ padding: '8px 0px 8px' }}>
|
||||||
<span>{blockTitle}</span>
|
<span> {t(blockTitle, { ns: NAMESPACE_UI_SCHEMA })}</span>
|
||||||
{description && (
|
{description && (
|
||||||
<MarkdownReadPretty
|
<MarkdownReadPretty
|
||||||
value={props.description}
|
value={t(description, { ns: NAMESPACE_UI_SCHEMA })}
|
||||||
style={{
|
style={{
|
||||||
overflowWrap: 'break-word',
|
overflowWrap: 'break-word',
|
||||||
whiteSpace: 'normal',
|
whiteSpace: 'normal',
|
||||||
|
@ -27,6 +27,8 @@ import { useEnsureOperatorsValid } from './SchemaSettingOptions';
|
|||||||
import useLazyLoadDisplayAssociationFieldsOfForm from './hooks/useLazyLoadDisplayAssociationFieldsOfForm';
|
import useLazyLoadDisplayAssociationFieldsOfForm from './hooks/useLazyLoadDisplayAssociationFieldsOfForm';
|
||||||
import { useLinkageRulesForSubTableOrSubForm } from './hooks/useLinkageRulesForSubTableOrSubForm';
|
import { useLinkageRulesForSubTableOrSubForm } from './hooks/useLinkageRulesForSubTableOrSubForm';
|
||||||
import useParseDefaultValue from './hooks/useParseDefaultValue';
|
import useParseDefaultValue from './hooks/useParseDefaultValue';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
||||||
|
|
||||||
Item.displayName = 'FormilyFormItem';
|
Item.displayName = 'FormilyFormItem';
|
||||||
|
|
||||||
@ -64,7 +66,7 @@ export const FormItem: any = withDynamicSchemaProps(
|
|||||||
const schema = useFieldSchema();
|
const schema = useFieldSchema();
|
||||||
const { addActiveFieldName } = useFormActiveFields() || {};
|
const { addActiveFieldName } = useFormActiveFields() || {};
|
||||||
const { wrapperStyle }: { wrapperStyle: any } = useDataFormItemProps();
|
const { wrapperStyle }: { wrapperStyle: any } = useDataFormItemProps();
|
||||||
|
const { t } = useTranslation();
|
||||||
useParseDefaultValue();
|
useParseDefaultValue();
|
||||||
useLazyLoadDisplayAssociationFieldsOfForm();
|
useLazyLoadDisplayAssociationFieldsOfForm();
|
||||||
useLinkageRulesForSubTableOrSubForm();
|
useLinkageRulesForSubTableOrSubForm();
|
||||||
@ -72,18 +74,20 @@ export const FormItem: any = withDynamicSchemaProps(
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
addActiveFieldName?.(schema.name as string);
|
addActiveFieldName?.(schema.name as string);
|
||||||
}, [addActiveFieldName, schema.name]);
|
}, [addActiveFieldName, schema.name]);
|
||||||
|
field.title = t(field.title, { ns: NAMESPACE_UI_SCHEMA });
|
||||||
const showTitle = schema['x-decorator-props']?.showTitle ?? true;
|
const showTitle = schema['x-decorator-props']?.showTitle ?? true;
|
||||||
const extra = useMemo(() => {
|
const extra = useMemo(() => {
|
||||||
if (field.description && field.description !== '') {
|
if (field.description && field.description !== '') {
|
||||||
return typeof field.description === 'string' ? (
|
return typeof field.description === 'string' ? (
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: HTMLEncode(field.description).split('\n').join('<br/>'),
|
__html: HTMLEncode(t(field.description, { ns: NAMESPACE_UI_SCHEMA }))
|
||||||
|
.split('\n')
|
||||||
|
.join('<br/>'),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
field.description
|
t(field.description, { ns: NAMESPACE_UI_SCHEMA })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [field.description]);
|
}, [field.description]);
|
||||||
|
@ -52,6 +52,7 @@ import { useStyles } from './Page.style';
|
|||||||
import { PageDesigner, PageTabDesigner } from './PageTabDesigner';
|
import { PageDesigner, PageTabDesigner } from './PageTabDesigner';
|
||||||
import { PopupRouteContextResetter } from './PopupRouteContextResetter';
|
import { PopupRouteContextResetter } from './PopupRouteContextResetter';
|
||||||
import { transformMultiColumnToSingleColumn } from '@nocobase/utils/client';
|
import { transformMultiColumnToSingleColumn } from '@nocobase/utils/client';
|
||||||
|
import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
currentTabUid: string;
|
currentTabUid: string;
|
||||||
@ -431,7 +432,8 @@ const NocoBasePageHeader = React.memo(({ activeKey, className }: { activeKey: st
|
|||||||
const { token } = useToken();
|
const { token } = useToken();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const title = t(fieldSchema.title) || t(currentRoute?.title);
|
const title =
|
||||||
|
t(fieldSchema.title, { ns: NAMESPACE_UI_SCHEMA }) || t(currentRoute?.title, { ns: NAMESPACE_UI_SCHEMA });
|
||||||
if (title) {
|
if (title) {
|
||||||
setDocumentTitle(title);
|
setDocumentTitle(title);
|
||||||
setPageTitle(title);
|
setPageTitle(title);
|
||||||
|
@ -9,8 +9,11 @@
|
|||||||
|
|
||||||
import { useField } from '@formily/react';
|
import { useField } from '@formily/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
||||||
|
|
||||||
export const TableColumn = (props) => {
|
export const TableColumn = (props) => {
|
||||||
const field = useField();
|
const field = useField();
|
||||||
return <div role="button">{field.title}</div>;
|
const { t } = useTranslation();
|
||||||
|
return <div role="button">{t(field.title, { ns: NAMESPACE_UI_SCHEMA })}</div>;
|
||||||
};
|
};
|
||||||
|
@ -67,6 +67,7 @@ import { useAssociationFieldContext } from '../association-field/hooks';
|
|||||||
import { TableSkeleton } from './TableSkeleton';
|
import { TableSkeleton } from './TableSkeleton';
|
||||||
import { extractIndex, isCollectionFieldComponent, isColumnComponent } from './utils';
|
import { extractIndex, isCollectionFieldComponent, isColumnComponent } from './utils';
|
||||||
import { withTooltipComponent } from '../../../hoc/withTooltipComponent';
|
import { withTooltipComponent } from '../../../hoc/withTooltipComponent';
|
||||||
|
import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
||||||
|
|
||||||
type BodyRowComponentProps = {
|
type BodyRowComponentProps = {
|
||||||
rowIndex?: number;
|
rowIndex?: number;
|
||||||
@ -164,6 +165,7 @@ const useTableColumns = (
|
|||||||
props: { showDel?: any; isSubTable?: boolean; optimizeTextCellRender: boolean },
|
props: { showDel?: any; isSubTable?: boolean; optimizeTextCellRender: boolean },
|
||||||
paginationProps,
|
paginationProps,
|
||||||
) => {
|
) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { token } = useToken();
|
const { token } = useToken();
|
||||||
const field = useArrayField(props);
|
const field = useArrayField(props);
|
||||||
const schema = useFieldSchema();
|
const schema = useFieldSchema();
|
||||||
@ -213,11 +215,10 @@ const useTableColumns = (
|
|||||||
const dataIndex = collectionFields?.length > 0 ? collectionFields[0].name : columnSchema.name;
|
const dataIndex = collectionFields?.length > 0 ? collectionFields[0].name : columnSchema.name;
|
||||||
const columnHidden = !!columnSchema['x-component-props']?.['columnHidden'];
|
const columnHidden = !!columnSchema['x-component-props']?.['columnHidden'];
|
||||||
const { uiSchema, defaultValue, interface: _interface } = collection?.getField(dataIndex) || {};
|
const { uiSchema, defaultValue, interface: _interface } = collection?.getField(dataIndex) || {};
|
||||||
|
columnSchema.title = t(columnSchema?.title, { ns: NAMESPACE_UI_SCHEMA });
|
||||||
if (uiSchema) {
|
if (uiSchema) {
|
||||||
uiSchema.default = defaultValue;
|
uiSchema.default = defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: (
|
title: (
|
||||||
<RefreshComponentProvider refresh={refresh}>
|
<RefreshComponentProvider refresh={refresh}>
|
||||||
|
@ -12,6 +12,7 @@ import { observer, RecursionField, Schema, useField, useFieldSchema } from '@for
|
|||||||
import { Tabs as AntdTabs, TabPaneProps, TabsProps } from 'antd';
|
import { Tabs as AntdTabs, TabPaneProps, TabsProps } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useSchemaInitializerRender } from '../../../application';
|
import { useSchemaInitializerRender } from '../../../application';
|
||||||
import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps';
|
import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps';
|
||||||
import { Icon } from '../../../icon';
|
import { Icon } from '../../../icon';
|
||||||
@ -22,6 +23,7 @@ import { useTabsContext } from './context';
|
|||||||
import { TabsDesigner } from './Tabs.Designer';
|
import { TabsDesigner } from './Tabs.Designer';
|
||||||
import { useMobileLayout } from '../../../route-switch/antd/admin-layout';
|
import { useMobileLayout } from '../../../route-switch/antd/admin-layout';
|
||||||
import { transformMultiColumnToSingleColumn } from '@nocobase/utils/client';
|
import { transformMultiColumnToSingleColumn } from '@nocobase/utils/client';
|
||||||
|
import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
||||||
|
|
||||||
const MemoizeRecursionField = React.memo(RecursionField);
|
const MemoizeRecursionField = React.memo(RecursionField);
|
||||||
MemoizeRecursionField.displayName = 'MemoizeRecursionField';
|
MemoizeRecursionField.displayName = 'MemoizeRecursionField';
|
||||||
@ -136,14 +138,15 @@ Tabs.TabPane = withDynamicSchemaProps(
|
|||||||
(props: TabPaneProps & { icon?: any; hidden?: boolean }) => {
|
(props: TabPaneProps & { icon?: any; hidden?: boolean }) => {
|
||||||
const Designer = useDesigner();
|
const Designer = useDesigner();
|
||||||
const field = useField();
|
const field = useField();
|
||||||
|
const { t } = useTranslation();
|
||||||
if (props.hidden) {
|
if (props.hidden) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SortableItem className={classNames('nb-action-link', designerCss, props.className)}>
|
<SortableItem className={classNames('nb-action-link', designerCss, props.className)}>
|
||||||
{props.icon && <Icon style={{ marginRight: 2 }} type={props.icon} />} {props.tab || field.title}
|
{props.icon && <Icon style={{ marginRight: 2 }} type={props.icon} />}{' '}
|
||||||
|
{props.tab || t(field.title, { ns: NAMESPACE_UI_SCHEMA })}
|
||||||
<Designer />
|
<Designer />
|
||||||
</SortableItem>
|
</SortableItem>
|
||||||
);
|
);
|
||||||
|
@ -37,8 +37,8 @@ test.describe('data will be updated && Assign field values && after successful s
|
|||||||
await page.getByRole('menuitem', { name: 'After successful submission' }).click();
|
await page.getByRole('menuitem', { name: 'After successful submission' }).click();
|
||||||
await page.getByLabel('Manually close').check();
|
await page.getByLabel('Manually close').check();
|
||||||
await page.getByLabel('Redirect to').check();
|
await page.getByLabel('Redirect to').check();
|
||||||
await page.locator('input[type="text"]').click();
|
await page.getByLabel('textbox').click();
|
||||||
await page.locator('input[type="text"]').fill('/admin/pm/list/local/');
|
await page.getByLabel('textbox').fill('/admin/pm/list/local/');
|
||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
await page.getByLabel('action-Action-Bulk update-customize:bulkUpdate-general-table').click();
|
await page.getByLabel('action-Action-Bulk update-customize:bulkUpdate-general-table').click();
|
||||||
const [request] = await Promise.all([
|
const [request] = await Promise.all([
|
||||||
|
@ -25,9 +25,7 @@ test.describe('custom request action', () => {
|
|||||||
await page.getByLabel('designer-schema-settings-CustomRequestAction-actionSettings:customRequest-').hover();
|
await page.getByLabel('designer-schema-settings-CustomRequestAction-actionSettings:customRequest-').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Edit button' }).click();
|
await page.getByRole('menuitem', { name: 'Edit button' }).click();
|
||||||
|
|
||||||
// 应该只显示标题输入框
|
|
||||||
await expect(page.getByText('Button title')).toBeVisible();
|
await expect(page.getByText('Button title')).toBeVisible();
|
||||||
await expect(page.getByText('Button icon')).not.toBeVisible();
|
|
||||||
await expect(page.getByText('Button background color')).not.toBeVisible();
|
await expect(page.getByText('Button background color')).not.toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -8,10 +8,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useFieldSchema } from '@formily/react';
|
import { useFieldSchema } from '@formily/react';
|
||||||
import { Action, Icon, useCompile, useComponent, withDynamicSchemaProps, ACLActionProvider } from '@nocobase/client';
|
import {
|
||||||
|
Action,
|
||||||
|
Icon,
|
||||||
|
useComponent,
|
||||||
|
withDynamicSchemaProps,
|
||||||
|
ACLActionProvider,
|
||||||
|
NAMESPACE_UI_SCHEMA,
|
||||||
|
} from '@nocobase/client';
|
||||||
import { Avatar } from 'antd';
|
import { Avatar } from 'antd';
|
||||||
import { createStyles } from 'antd-style';
|
import { createStyles } from 'antd-style';
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { WorkbenchBlockContext } from './WorkbenchBlock';
|
import { WorkbenchBlockContext } from './WorkbenchBlock';
|
||||||
import { WorkbenchLayout } from './workbenchBlockSettings';
|
import { WorkbenchLayout } from './workbenchBlockSettings';
|
||||||
|
|
||||||
@ -40,8 +48,8 @@ function Button() {
|
|||||||
const backgroundColor = fieldSchema['x-component-props']?.['iconColor'];
|
const backgroundColor = fieldSchema['x-component-props']?.['iconColor'];
|
||||||
const { layout, ellipsis = true } = useContext(WorkbenchBlockContext);
|
const { layout, ellipsis = true } = useContext(WorkbenchBlockContext);
|
||||||
const { styles, cx } = useStyles();
|
const { styles, cx } = useStyles();
|
||||||
const compile = useCompile();
|
const { t } = useTranslation();
|
||||||
const title = compile(fieldSchema.title);
|
const title = t(fieldSchema.title, { ns: NAMESPACE_UI_SCHEMA });
|
||||||
return layout === WorkbenchLayout.Grid ? (
|
return layout === WorkbenchLayout.Grid ? (
|
||||||
<div title={title} className={cx(styles.avatar)}>
|
<div title={title} className={cx(styles.avatar)}>
|
||||||
<Avatar style={{ backgroundColor }} size={48} icon={<Icon type={icon} />} />
|
<Avatar style={{ backgroundColor }} size={48} icon={<Icon type={icon} />} />
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
import { Cache } from '@nocobase/cache';
|
import { Cache } from '@nocobase/cache';
|
||||||
import { Repository, Transaction, Transactionable } from '@nocobase/database';
|
import { Repository, Transaction, Transactionable } from '@nocobase/database';
|
||||||
import { uid } from '@nocobase/utils';
|
import { uid } from '@nocobase/utils';
|
||||||
import lodash from 'lodash';
|
import { default as _, default as lodash } from 'lodash';
|
||||||
import { ChildOptions, SchemaNode, TargetPosition } from './dao/ui_schema_node_dao';
|
import { ChildOptions, SchemaNode, TargetPosition } from './dao/ui_schema_node_dao';
|
||||||
|
|
||||||
export interface GetJsonSchemaOptions {
|
export interface GetJsonSchemaOptions {
|
||||||
@ -297,6 +297,23 @@ export class UiSchemaRepository extends Repository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async emitAfterSaveEvent(s, options) {
|
||||||
|
if (!s?.schema) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const keys = ['title', 'description', 'x-component-props.title', 'x-decorator-props.title'];
|
||||||
|
let r = false;
|
||||||
|
for (const key of keys) {
|
||||||
|
if (_.get(s?.schema, key)) {
|
||||||
|
r = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (r) {
|
||||||
|
await this.database.emitAsync(`${this.collection.name}.afterSave`, s, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@transaction()
|
@transaction()
|
||||||
async patch(newSchema: any, options?) {
|
async patch(newSchema: any, options?) {
|
||||||
const { transaction } = options;
|
const { transaction } = options;
|
||||||
@ -305,8 +322,8 @@ export class UiSchemaRepository extends Repository {
|
|||||||
if (!newSchema['properties']) {
|
if (!newSchema['properties']) {
|
||||||
const s = await this.model.findByPk(rootUid, { transaction });
|
const s = await this.model.findByPk(rootUid, { transaction });
|
||||||
s.set('schema', { ...s.toJSON(), ...newSchema });
|
s.set('schema', { ...s.toJSON(), ...newSchema });
|
||||||
// console.log(s.toJSON());
|
|
||||||
await s.save({ transaction, hooks: false });
|
await s.save({ transaction, hooks: false });
|
||||||
|
await this.emitAfterSaveEvent(s, options);
|
||||||
if (newSchema['x-server-hooks']) {
|
if (newSchema['x-server-hooks']) {
|
||||||
await this.database.emitAsync(`${this.collection.name}.afterSave`, s, options);
|
await this.database.emitAsync(`${this.collection.name}.afterSave`, s, options);
|
||||||
}
|
}
|
||||||
@ -488,8 +505,14 @@ export class UiSchemaRepository extends Repository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await this[`insert${lodash.upperFirst(position)}`](target, schema, options);
|
const result = await this[`insert${lodash.upperFirst(position)}`](target, schema, options);
|
||||||
|
|
||||||
|
const s = await this.model.findByPk(schema, { transaction });
|
||||||
|
|
||||||
|
await this.emitAfterSaveEvent(s, options);
|
||||||
|
|
||||||
// clear target schema path cache
|
// clear target schema path cache
|
||||||
await this.clearXUidPathCache(result['x-uid'], transaction);
|
await this.clearXUidPathCache(result['x-uid'], transaction);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -869,6 +892,8 @@ WHERE TreeTable.depth = 1 AND TreeTable.ancestor = :ancestor and TreeTable.sort
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await this.emitAfterSaveEvent(nodeModel, { transaction });
|
||||||
|
|
||||||
if (schema['x-server-hooks']) {
|
if (schema['x-server-hooks']) {
|
||||||
await this.database.emitAsync(`${this.collection.name}.afterSave`, nodeModel, { transaction });
|
await this.database.emitAsync(`${this.collection.name}.afterSave`, nodeModel, { transaction });
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
import { MagicAttributeModel } from '@nocobase/database';
|
import { MagicAttributeModel } from '@nocobase/database';
|
||||||
import { Plugin } from '@nocobase/server';
|
import { Plugin } from '@nocobase/server';
|
||||||
|
import PluginLocalizationServer from '@nocobase/plugin-localization';
|
||||||
|
import { tval } from '@nocobase/utils';
|
||||||
import { uid } from '@nocobase/utils';
|
import { uid } from '@nocobase/utils';
|
||||||
import path, { resolve } from 'path';
|
import path, { resolve } from 'path';
|
||||||
import { uiSchemaActions } from './actions/ui-schema-action';
|
import { uiSchemaActions } from './actions/ui-schema-action';
|
||||||
@ -17,6 +19,19 @@ import UiSchemaRepository from './repository';
|
|||||||
import { ServerHooks } from './server-hooks';
|
import { ServerHooks } from './server-hooks';
|
||||||
import { ServerHookModel } from './server-hooks/model';
|
import { ServerHookModel } from './server-hooks/model';
|
||||||
|
|
||||||
|
export const compile = (title: string) => (title || '').replace(/{{\s*t\(["|'|`](.*)["|'|`]\)\s*}}/g, '$1');
|
||||||
|
|
||||||
|
function extractFields(obj) {
|
||||||
|
return [
|
||||||
|
obj.title,
|
||||||
|
obj.description,
|
||||||
|
obj['x-component-props']?.title,
|
||||||
|
obj['x-component-props']?.description,
|
||||||
|
obj['x-decorator-props']?.title,
|
||||||
|
obj['x-decorator-props']?.description,
|
||||||
|
].filter((value) => value !== undefined && value !== '');
|
||||||
|
}
|
||||||
|
|
||||||
export class PluginUISchemaStorageServer extends Plugin {
|
export class PluginUISchemaStorageServer extends Plugin {
|
||||||
serverHooks: ServerHooks;
|
serverHooks: ServerHooks;
|
||||||
|
|
||||||
@ -28,7 +43,7 @@ export class PluginUISchemaStorageServer extends Plugin {
|
|||||||
|
|
||||||
async beforeLoad() {
|
async beforeLoad() {
|
||||||
const db = this.app.db;
|
const db = this.app.db;
|
||||||
|
const pm = this.app.pm;
|
||||||
this.serverHooks = new ServerHooks(db);
|
this.serverHooks = new ServerHooks(db);
|
||||||
|
|
||||||
this.app.db.registerModels({ MagicAttributeModel, UiSchemaModel, ServerHookModel });
|
this.app.db.registerModels({ MagicAttributeModel, UiSchemaModel, ServerHookModel });
|
||||||
@ -51,6 +66,19 @@ export class PluginUISchemaStorageServer extends Plugin {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
db.on('uiSchemas.afterSave', async function setUid(model, options) {
|
||||||
|
const localizationPlugin = pm.get('localization') as PluginLocalizationServer;
|
||||||
|
const texts = [];
|
||||||
|
const changedFields = extractFields(model.toJSON());
|
||||||
|
if (!changedFields.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
changedFields.forEach((field) => {
|
||||||
|
field && texts.push({ text: compile(field), module: `resources.ui-schema-storage` });
|
||||||
|
});
|
||||||
|
await localizationPlugin?.addNewTexts?.(texts, options);
|
||||||
|
});
|
||||||
|
|
||||||
db.on('uiSchemas.afterCreate', async function insertSchema(model, options) {
|
db.on('uiSchemas.afterCreate', async function insertSchema(model, options) {
|
||||||
const { transaction } = options;
|
const { transaction } = options;
|
||||||
const uiSchemaRepository = db.getCollection('uiSchemas').repository as UiSchemaRepository;
|
const uiSchemaRepository = db.getCollection('uiSchemas').repository as UiSchemaRepository;
|
||||||
@ -125,6 +153,34 @@ export class PluginUISchemaStorageServer extends Plugin {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
await this.importCollections(resolve(__dirname, 'collections'));
|
await this.importCollections(resolve(__dirname, 'collections'));
|
||||||
|
// this.registerLocalizationSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
registerLocalizationSource() {
|
||||||
|
const localizationPlugin = this.app.pm.get('localization') as PluginLocalizationServer;
|
||||||
|
if (!localizationPlugin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
localizationPlugin.sourceManager.registerSource('ui-schema-storage', {
|
||||||
|
title: tval('UiSchema'),
|
||||||
|
sync: async (ctx) => {
|
||||||
|
const uiSchemas = await ctx.db.getRepository('uiSchemas').find({
|
||||||
|
raw: true,
|
||||||
|
});
|
||||||
|
const resources = {};
|
||||||
|
uiSchemas.forEach((route: { schema?: any }) => {
|
||||||
|
const changedFields = extractFields(route.schema);
|
||||||
|
if (changedFields.length) {
|
||||||
|
changedFields.forEach((field) => {
|
||||||
|
resources[field] = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
'ui-schema-storage': resources,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user