feat: add tooltip configuration to menu item and table headers (#6346)

* feat: add tooltip configuration to menu item and table headers

* feat: add tooltip to link and group menus

* feat: menu tooltip icon style adjustment

* feat: menu tooltip icon style adjustment

* fix: e2e

* refactor: optimize tooltip component usage in table columns

* feat: add tooltip editing functionality and enhance tooltip display in menu items

---------

Co-authored-by: Zeke Zhang <958414905@qq.com>
This commit is contained in:
chenyongxin 2025-03-06 20:09:14 +08:00 committed by GitHub
parent a9000d16c1
commit c2928d38cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 297 additions and 78 deletions

View File

@ -0,0 +1,37 @@
/**
* 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 React from 'react';
import { Tooltip } from 'antd';
import { QuestionCircleOutlined } from '@ant-design/icons';
const titleWrapperStyle = {
display: 'flex',
alignItems: 'center',
gap: 4,
};
export const withTooltipComponent = (Component: React.FC) => {
return (props) => {
const { tooltip } = props;
if (!tooltip) {
return <Component {...props} />;
}
return (
<div style={titleWrapperStyle}>
<Component {...props} />
<Tooltip title={tooltip}>
<QuestionCircleOutlined style={{ zIndex: 1 }} />
</Tooltip>
</div>
);
};
};

View File

@ -88,6 +88,5 @@ export {
IsInNocoBaseRecursionFieldContext, IsInNocoBaseRecursionFieldContext,
NocoBaseRecursionField, NocoBaseRecursionField,
RefreshComponentProvider, RefreshComponentProvider,
useRefreshFieldSchema useRefreshFieldSchema,
} from './formily/NocoBaseRecursionField'; } from './formily/NocoBaseRecursionField';

View File

@ -306,6 +306,9 @@ test.describe('configure actions column', () => {
await page.getByText('Actions', { exact: true }).hover(); await page.getByText('Actions', { exact: true }).hover();
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover(); await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
await page.getByRole('menuitem', { name: 'Delete' }).click(); await page.getByRole('menuitem', { name: 'Delete' }).click();
await page.getByText('Actions', { exact: true }).hover();
await page.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);

View File

@ -82,6 +82,43 @@ export const tableColumnSettings = new SchemaSettings({
}; };
}, },
}, },
{
name: 'editTooltip',
type: 'modal',
useComponentProps() {
const { t } = useTranslation();
const { dn } = useDesignable();
const field = useField();
const columnSchema = useFieldSchema();
return {
title: t('Edit tooltip'),
schema: {
type: 'object',
title: t('Edit tooltip'),
properties: {
tooltip: {
default: columnSchema?.['x-component-props']?.tooltip || '',
'x-decorator': 'FormItem',
'x-component': 'Input.TextArea',
'x-component-props': {},
},
},
} as ISchema,
onSubmit({ tooltip }) {
field.componentProps.tooltip = tooltip;
columnSchema['x-component-props'] = columnSchema['x-component-props'] || {};
columnSchema['x-component-props']['tooltip'] = tooltip;
dn.emit('patch', {
schema: {
'x-uid': columnSchema['x-uid'],
'x-component-props': columnSchema['x-component-props'],
},
});
},
};
},
},
{ {
name: 'style', name: 'style',
Component: (props) => { Component: (props) => {

View File

@ -17,7 +17,7 @@ test.describe('group page side menus schema settings', () => {
await expectSettingsMenu({ await expectSettingsMenu({
page, page,
showMenu: () => showSettingsInSide(page, 'group page in side'), showMenu: () => showSettingsInSide(page, 'group page in side'),
supportedOptions: ['Edit', 'Move to', 'Insert before', 'Insert after', 'Insert inner', 'Delete'], supportedOptions: ['Edit', 'Edit tooltip', 'Move to', 'Insert before', 'Insert after', 'Insert inner', 'Delete'],
}); });
}); });
@ -28,7 +28,7 @@ test.describe('group page side menus schema settings', () => {
await expectSettingsMenu({ await expectSettingsMenu({
page, page,
showMenu: () => showSettingsInSide(page, 'link page in side'), showMenu: () => showSettingsInSide(page, 'link page in side'),
supportedOptions: ['Edit', 'Move to', 'Insert before', 'Insert after', 'Delete'], supportedOptions: ['Edit', 'Edit tooltip', 'Move to', 'Insert before', 'Insert after', 'Delete'],
}); });
}); });
@ -39,7 +39,7 @@ test.describe('group page side menus schema settings', () => {
await expectSettingsMenu({ await expectSettingsMenu({
page, page,
showMenu: () => showSettingsInSide(page, 'single page in side'), showMenu: () => showSettingsInSide(page, 'single page in side'),
supportedOptions: ['Edit', 'Move to', 'Insert before', 'Insert after', 'Delete'], supportedOptions: ['Edit', 'Edit tooltip', 'Move to', 'Insert before', 'Insert after', 'Delete'],
}); });
}); });
}); });

View File

@ -13,7 +13,7 @@ test.describe('group page menus schema settings', () => {
test('edit', async ({ page, mockPage }) => { test('edit', async ({ page, mockPage }) => {
await mockPage({ type: 'group', name: 'group page' }).goto(); await mockPage({ type: 'group', name: 'group page' }).goto();
await showSettings(page, 'group page'); await showSettings(page, 'group page');
await page.getByRole('menuitem', { name: 'Edit' }).click(); await page.getByRole('menuitem', { name: 'Edit', exact: true }).click();
await page.mouse.move(300, 0); await page.mouse.move(300, 0);
// 设置一个新名称 // 设置一个新名称

View File

@ -23,6 +23,7 @@ export interface NocoBaseDesktopRoute {
children?: NocoBaseDesktopRoute[]; children?: NocoBaseDesktopRoute[];
title?: string; title?: string;
tooltip?: string;
icon?: string; icon?: string;
schemaUid?: string; schemaUid?: string;
menuSchemaUid?: string; menuSchemaUid?: string;

View File

@ -50,6 +50,7 @@ import { KeepAlive } from './KeepAlive';
import { NocoBaseDesktopRoute, NocoBaseDesktopRouteType } from './convertRoutesToSchema'; import { NocoBaseDesktopRoute, NocoBaseDesktopRouteType } from './convertRoutesToSchema';
import { MenuSchemaToolbar, ResetThemeTokenAndKeepAlgorithm } from './menuItemSettings'; import { MenuSchemaToolbar, ResetThemeTokenAndKeepAlgorithm } from './menuItemSettings';
import { userCenterSettings } from './userCenterSettings'; import { userCenterSettings } from './userCenterSettings';
import { withTooltipComponent } from '../../../hoc/withTooltipComponent';
export { KeepAlive, NocoBaseDesktopRouteType }; export { KeepAlive, NocoBaseDesktopRouteType };
@ -434,16 +435,26 @@ const actionsRender: any = (props) => {
return <PinnedPluginList />; return <PinnedPluginList />;
}; };
const MenuItemTitle: React.FC = (props) => {
return <>{props.children}</>;
};
const MenuItemTitleWithTooltip = withTooltipComponent(MenuItemTitle);
const menuItemRender = (item, dom, options) => { const menuItemRender = (item, dom, options) => {
return ( return (
<MenuItem item={item} options={options}> <MenuItem item={item} options={options}>
{dom} <MenuItemTitleWithTooltip tooltip={item._route?.tooltip}>{dom}</MenuItemTitleWithTooltip>
</MenuItem> </MenuItem>
); );
}; };
const subMenuItemRender = (item, dom) => { const subMenuItemRender = (item, dom) => {
return <GroupItem item={item}>{dom}</GroupItem>; return (
<GroupItem item={item}>
<MenuItemTitleWithTooltip tooltip={item._route?.tooltip}>{dom}</MenuItemTitleWithTooltip>
</GroupItem>
);
}; };
const CollapsedButton: FC<{ collapsed: boolean }> = (props) => { const CollapsedButton: FC<{ collapsed: boolean }> = (props) => {
@ -753,6 +764,8 @@ function convertRoutesToLayout(routes: NocoBaseDesktopRoute[], { designable, par
name: <MenuDesignerButton testId={testId} />, name: <MenuDesignerButton testId={testId} />,
path: '/', path: '/',
disabled: true, disabled: true,
_route: {},
_parentRoute: {},
icon: <Icon type="setting" />, icon: <Icon type="setting" />,
}; };
}; };

View File

@ -525,6 +525,53 @@ const MoveToMenuItem = () => {
); );
}; };
const EditTooltip = () => {
const { t } = useTranslation();
const currentRoute = useCurrentRoute();
const { updateRoute } = useNocoBaseRoutes();
const editTooltipSchema = useMemo(() => {
return {
type: 'object',
title: t('Edit tooltip'),
properties: {
tooltip: {
'x-decorator': 'FormItem',
'x-component': 'Input.TextArea',
'x-component-props': {},
},
},
};
}, [t]);
const initialTooltipValues = useMemo(() => {
return {
tooltip: currentRoute.tooltip,
};
}, [currentRoute.tooltip]);
const onEditTooltipSubmit: (values: any) => void = useCallback(
({ tooltip }) => {
// 更新菜单对应的路由
if (currentRoute.id !== undefined) {
updateRoute(currentRoute.id, {
tooltip,
});
}
},
[currentRoute.id, updateRoute],
);
return (
<SchemaSettingsModalItem
title={t('Edit tooltip')}
eventKey="edit-tooltip"
schema={editTooltipSchema as ISchema}
initialValues={initialTooltipValues}
onSubmit={onEditTooltipSubmit}
/>
);
};
export const menuItemSettings = new SchemaSettings({ export const menuItemSettings = new SchemaSettings({
name: 'menuSettings:menuItem', name: 'menuSettings:menuItem',
items: [ items: [
@ -532,6 +579,10 @@ export const menuItemSettings = new SchemaSettings({
name: 'edit', name: 'edit',
Component: EditMenuItem, Component: EditMenuItem,
}, },
{
name: 'editTooltip',
Component: EditTooltip,
},
{ {
name: 'hidden', name: 'hidden',
Component: HiddenMenuItem, Component: HiddenMenuItem,

View File

@ -45,6 +45,7 @@ import { useToken } from '../__builtins__';
import { SubFormProvider, useAssociationFieldContext } from '../association-field/hooks'; import { SubFormProvider, useAssociationFieldContext } from '../association-field/hooks';
import { ColumnFieldProvider } from '../table-v2/components/ColumnFieldProvider'; import { ColumnFieldProvider } from '../table-v2/components/ColumnFieldProvider';
import { extractIndex, isCollectionFieldComponent, isColumnComponent } from '../table-v2/utils'; import { extractIndex, isCollectionFieldComponent, isColumnComponent } from '../table-v2/utils';
import { withTooltipComponent } from '../../../hoc/withTooltipComponent';
const InViewContext = React.createContext(false); const InViewContext = React.createContext(false);
@ -85,6 +86,8 @@ export const useColumnsDeepMemoized = (columns: any[]) => {
return oldObj.value; return oldObj.value;
}; };
const TableColumnTitle = withTooltipComponent(RecursionField);
const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginationProps) => { const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginationProps) => {
const { token } = useToken(); const { token } = useToken();
const field = useArrayField(props); const field = useArrayField(props);
@ -112,7 +115,6 @@ const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginat
}, [token.paddingContentVerticalLG, token.marginSM]); }, [token.paddingContentVerticalLG, token.marginSM]);
const collection = useCollection(); const collection = useCollection();
const columns = useMemo( const columns = useMemo(
() => () =>
columnsSchema?.map((s: Schema) => { columnsSchema?.map((s: Schema) => {
@ -124,7 +126,7 @@ 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']; const columnHidden = !!s['x-component-props']?.['columnHidden'];
return { return {
title: <RecursionField name={s.name} schema={s} onlyRenderSelf />, title: <TableColumnTitle name={s.name} schema={s} onlyRenderSelf tooltip={s['x-component-props']?.tooltip} />,
dataIndex, dataIndex,
key: s.name, key: s.name,
sorter: s['x-component-props']?.['sorter'], sorter: s['x-component-props']?.['sorter'],

View File

@ -286,14 +286,14 @@ export const MenuDesigner = () => {
f.dataSource = f.dataSource =
component === 'Menu.SubMenu' component === 'Menu.SubMenu'
? [ ? [
{ label: t('Before'), value: 'beforeBegin' }, { label: t('Before'), value: 'beforeBegin' },
{ label: t('After'), value: 'afterEnd' }, { label: t('After'), value: 'afterEnd' },
{ label: t('Inner'), value: 'beforeEnd' }, { label: t('Inner'), value: 'beforeEnd' },
] ]
: [ : [
{ label: t('Before'), value: 'beforeBegin' }, { label: t('Before'), value: 'beforeBegin' },
{ label: t('After'), value: 'afterEnd' }, { label: t('After'), value: 'afterEnd' },
]; ];
}); });
}); });
}, },
@ -325,6 +325,24 @@ export const MenuDesigner = () => {
icon: field.componentProps.icon, icon: field.componentProps.icon,
}; };
}, [field.title, field.componentProps.icon]); }, [field.title, field.componentProps.icon]);
const editTooltipSchema = useMemo(() => {
return {
type: 'object',
title: t('Edit tooltip'),
properties: {
tooltip: {
'x-decorator': 'FormItem',
'x-component': 'Input.TextArea',
'x-component-props': {},
},
},
};
}, [t]);
const initialTooltipValues = useMemo(() => {
return {
tooltip: field.componentProps.tooltip,
};
}, [field.componentProps.tooltip]);
if (fieldSchema['x-component'] === 'Menu.URL') { if (fieldSchema['x-component'] === 'Menu.URL') {
schema.properties['href'] = urlSchema; schema.properties['href'] = urlSchema;
schema.properties['params'] = paramsSchema; schema.properties['params'] = paramsSchema;
@ -369,15 +387,26 @@ export const MenuDesigner = () => {
options: options:
href || params href || params
? { ? {
href, href,
params, params,
} }
: undefined, : undefined,
}); });
} }
}, },
[fieldSchema, field, dn, refresh, onSelect], [fieldSchema, field, dn, refresh, onSelect],
); );
const onEditTooltipSubmit: (values: any) => void = useCallback(
({ tooltip }) => {
// 更新菜单对应的路由
if (fieldSchema['__route__']?.id) {
updateRoute(fieldSchema['__route__'].id, {
tooltip,
});
}
},
[fieldSchema, field, dn, refresh, onSelect],
);
const modalSchema = useMemo(() => { const modalSchema = useMemo(() => {
return { return {
@ -471,6 +500,13 @@ export const MenuDesigner = () => {
initialValues={initialValues} initialValues={initialValues}
onSubmit={onEditSubmit} onSubmit={onEditSubmit}
/> />
<SchemaSettingsModalItem
title={t('Edit tooltip')}
eventKey="edit-tooltip"
schema={editTooltipSchema as ISchema}
initialValues={initialTooltipValues}
onSubmit={onEditTooltipSubmit}
/>
<SchemaSettingsSwitchItem <SchemaSettingsSwitchItem
title={t('Hidden')} title={t('Hidden')}
checked={fieldSchema['x-component-props']?.hidden} checked={fieldSchema['x-component-props']?.hidden}

View File

@ -40,6 +40,7 @@ import { useUpdate } from 'ahooks';
import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { useRefreshComponent, useRefreshFieldSchema } from '../../../formily/NocoBaseRecursionField'; import { useRefreshComponent, useRefreshFieldSchema } from '../../../formily/NocoBaseRecursionField';
import { NocoBaseDesktopRoute } from '../../../route-switch/antd/admin-layout/convertRoutesToSchema'; import { NocoBaseDesktopRoute } from '../../../route-switch/antd/admin-layout/convertRoutesToSchema';
import { withTooltipComponent } from '../../../hoc/withTooltipComponent';
const subMenuDesignerCss = css` const subMenuDesignerCss = css`
position: relative; position: relative;
@ -96,7 +97,7 @@ const subMenuDesignerCss = css`
const designerCss = css` const designerCss = css`
position: relative; position: relative;
display: flex; display: flex;
justify-content: center; // justify-content: center;
align-items: center; align-items: center;
margin-left: -20px; margin-left: -20px;
margin-right: -20px; margin-right: -20px;
@ -203,6 +204,16 @@ type ComposedMenu = React.FC<any> & {
Designer?: React.FC<any>; Designer?: React.FC<any>;
}; };
const MenuItemTitle: React.FC<{
schema: any;
style?: React.CSSProperties;
}> = ({ schema, style }) => {
const { t } = useMenuTranslation();
return <span style={style}>{t(schema.title)}</span>;
};
const MenuItemTitleWithTooltip = withTooltipComponent(MenuItemTitle);
export const ParentRouteContext = createContext<NocoBaseDesktopRoute>(null); export const ParentRouteContext = createContext<NocoBaseDesktopRoute>(null);
ParentRouteContext.displayName = 'ParentRouteContext'; ParentRouteContext.displayName = 'ParentRouteContext';
@ -668,8 +679,9 @@ const menuItemTitleStyle = {
overflow: 'hidden', overflow: 'hidden',
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
display: 'inline-block', display: 'inline-block',
width: '100%', // width: '100%',
verticalAlign: 'middle', verticalAlign: 'middle',
// marginInlineEnd: '4px',
}; };
Menu.Item = observer( Menu.Item = observer(
@ -698,7 +710,11 @@ Menu.Item = observer(
removeParentsIfNoChildren={false} removeParentsIfNoChildren={false}
> >
<Icon type={icon} /> <Icon type={icon} />
<span style={menuItemTitleStyle}>{t(schema.title)}</span> <MenuItemTitleWithTooltip
schema={schema}
style={menuItemTitleStyle}
tooltip={schema?.['x-component-props']?.tooltip}
/>
<Designer /> <Designer />
</SortableItem> </SortableItem>
</FieldContext.Provider> </FieldContext.Provider>
@ -720,6 +736,7 @@ Menu.Item = observer(
const MenuURLButton = ({ href, params, icon }) => { const MenuURLButton = ({ href, params, icon }) => {
const field = useField(); const field = useField();
const schema = useFieldSchema();
const { t } = useMenuTranslation(); const { t } = useMenuTranslation();
const Designer = useContext(MenuItemDesignerContext); const Designer = useContext(MenuItemDesignerContext);
const { parseURLAndParams } = useParseURLAndParams(); const { parseURLAndParams } = useParseURLAndParams();
@ -749,17 +766,11 @@ const MenuURLButton = ({ href, params, icon }) => {
aria-label={t(field.title)} aria-label={t(field.title)}
> >
<Icon type={icon} /> <Icon type={icon} />
<span <MenuItemTitleWithTooltip
style={{ schema={schema}
overflow: 'hidden', style={menuItemTitleStyle}
textOverflow: 'ellipsis', tooltip={schema?.['x-component-props']?.tooltip}
display: 'inline-block', />
width: '100%',
verticalAlign: 'middle',
}}
>
{t(field.title)}
</span>
<Designer /> <Designer />
</SortableItem> </SortableItem>
); );
@ -814,6 +825,7 @@ Menu.SubMenu = observer(
const field = useField(); const field = useField();
const mode = useContext(MenuModeContext); const mode = useContext(MenuModeContext);
const Designer = useContext(MenuItemDesignerContext); const Designer = useContext(MenuItemDesignerContext);
const submenu = useMemo(() => { const submenu = useMemo(() => {
return { return {
...others, ...others,
@ -830,7 +842,11 @@ Menu.SubMenu = observer(
aria-label={t(field.title)} aria-label={t(field.title)}
> >
<Icon type={icon} /> <Icon type={icon} />
{t(field.title)} <MenuItemTitleWithTooltip
schema={schema}
style={{ marginInlineStart: 0 }}
tooltip={schema?.['x-component-props']?.tooltip}
/>
<Designer /> <Designer />
</SortableItem> </SortableItem>
</FieldContext.Provider> </FieldContext.Provider>

View File

@ -66,6 +66,7 @@ import { useToken } from '../__builtins__';
import { useAssociationFieldContext } from '../association-field/hooks'; 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';
type BodyRowComponentProps = { type BodyRowComponentProps = {
rowIndex?: number; rowIndex?: number;
@ -198,6 +199,9 @@ const useTableColumns = (
const collection = useCollection(); const collection = useCollection();
// 不能提取到外部,否则 NocoBaseRecursionField 的值在一开始会是 undefined。原因未知
const TableColumnTitle = useMemo(() => withTooltipComponent(NocoBaseRecursionField), []);
const columns = useMemo( const columns = useMemo(
() => () =>
columnsSchemas?.map((columnSchema: Schema) => { columnsSchemas?.map((columnSchema: Schema) => {
@ -217,11 +221,12 @@ const useTableColumns = (
return { return {
title: ( title: (
<RefreshComponentProvider refresh={refresh}> <RefreshComponentProvider refresh={refresh}>
<NocoBaseRecursionField <TableColumnTitle
name={columnSchema.name} name={columnSchema.name}
schema={columnSchema} schema={columnSchema}
onlyRenderSelf onlyRenderSelf
isUseFormilyField={false} isUseFormilyField={false}
tooltip={columnSchema?.['x-component-props']?.tooltip}
/> />
</RefreshComponentProvider> </RefreshComponentProvider>
), ),

View File

@ -1396,11 +1396,11 @@ export async function expectSettingsMenu({
await page.waitForTimeout(100); await page.waitForTimeout(100);
await showMenu(); await showMenu();
for (const option of supportedOptions) { for (const option of supportedOptions) {
await expect(page.getByRole('menuitem', { name: option })).toBeVisible(); await expect(page.getByRole('menuitem', { name: option, exact: option === 'Edit' })).toBeVisible();
} }
if (unsupportedOptions) { if (unsupportedOptions) {
for (const option of unsupportedOptions) { for (const option of unsupportedOptions) {
await expect(page.getByRole('menuitem', { name: option })).not.toBeVisible(); await expect(page.getByRole('menuitem', { name: option, exact: option === 'Edit' })).not.toBeVisible();
} }
} }
} }

View File

@ -9,7 +9,14 @@
import { createForm, Form, onFormValuesChange } from '@formily/core'; import { createForm, Form, onFormValuesChange } from '@formily/core';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import { css, SchemaComponent, useAllAccessDesktopRoutes, useAPIClient, useCompile, useRequest } from '@nocobase/client'; import {
css,
SchemaComponent,
useAllAccessDesktopRoutes,
useAPIClient,
useCompile,
useRequest,
} from '@nocobase/client';
import { useMemoizedFn } from 'ahooks'; import { useMemoizedFn } from 'ahooks';
import { Checkbox, message, Table } from 'antd'; import { Checkbox, message, Table } from 'antd';
import { uniq } from 'lodash'; import { uniq } from 'lodash';
@ -68,7 +75,8 @@ const style = css`
const translateTitle = (menus: any[], t, compile) => { const translateTitle = (menus: any[], t, compile) => {
return menus.map((menu) => { return menus.map((menu) => {
const title = (menu.title?.match(/^\s*\{\{\s*.+?\s*\}\}\s*$/) ? compile(menu.title) : t(menu.title)) || t('Unnamed'); const title =
(menu.title?.match(/^\s*\{\{\s*.+?\s*\}\}\s*$/) ? compile(menu.title) : t(menu.title)) || t('Unnamed');
if (menu.children) { if (menu.children) {
return { return {
...menu, ...menu,
@ -123,7 +131,7 @@ const DesktopRoutesProvider: FC<{
}; };
export const DesktopAllRoutesProvider: React.FC<{ active: boolean }> = ({ children, active }) => { export const DesktopAllRoutesProvider: React.FC<{ active: boolean }> = ({ children, active }) => {
const refreshRef = React.useRef(() => { }); const refreshRef = React.useRef(() => {});
useEffect(() => { useEffect(() => {
if (active) { if (active) {

View File

@ -29,7 +29,7 @@ import {
usePopupUtils, usePopupUtils,
useProps, useProps,
withDynamicSchemaProps, withDynamicSchemaProps,
withSkeletonComponent withSkeletonComponent,
} from '@nocobase/client'; } from '@nocobase/client';
import type { Dayjs } from 'dayjs'; import type { Dayjs } from 'dayjs';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@ -75,7 +75,7 @@ const getColorString = (
}; };
export const DeleteEventContext = React.createContext({ export const DeleteEventContext = React.createContext({
close: () => { }, close: () => {},
allowDeleteEvent: false, allowDeleteEvent: false,
}); });

View File

@ -70,9 +70,9 @@ export class PluginCalendarClient extends Plugin {
colorFieldInterfaces: { colorFieldInterfaces: {
[T: string]: { useGetColor: (field: any) => ColorFunctions }; [T: string]: { useGetColor: (field: any) => ColorFunctions };
} = { } = {
select: { useGetColor }, select: { useGetColor },
radioGroup: { useGetColor }, radioGroup: { useGetColor },
}; };
dateTimeFieldInterfaces = ['date', 'datetime', 'dateOnly', 'datetimeNoTz', 'unixTimestamp', 'createdAt', 'updatedAt']; dateTimeFieldInterfaces = ['date', 'datetime', 'dateOnly', 'datetimeNoTz', 'unixTimestamp', 'createdAt', 'updatedAt'];

View File

@ -236,13 +236,13 @@ export const createRoutesTableSchema = (collectionName: string, basename: string
'x-component': 'IconPicker', 'x-component': 'IconPicker',
'x-reactions': isMobile 'x-reactions': isMobile
? { ? {
dependencies: ['type'], dependencies: ['type'],
fulfill: { fulfill: {
state: { state: {
required: '{{$deps[0] !== "tabs"}}', required: '{{$deps[0] !== "tabs"}}',
},
}, },
}, }
}
: undefined, : undefined,
}, },
// 由于历史原因,桌面端使用的是 'href' 作为 key移动端使用的是 'url' 作为 key // 由于历史原因,桌面端使用的是 'href' 作为 key移动端使用的是 'url' 作为 key
@ -574,8 +574,9 @@ export const createRoutesTableSchema = (collectionName: string, basename: string
} }
if (recordData.type === NocoBaseDesktopRouteType.page) { if (recordData.type === NocoBaseDesktopRouteType.page) {
const path = `${basenameOfCurrentRouter.slice(0, -1)}${basename}/${isMobile ? recordData.schemaUid : recordData.menuSchemaUid const path = `${basenameOfCurrentRouter.slice(0, -1)}${basename}/${
}`; isMobile ? recordData.schemaUid : recordData.menuSchemaUid
}`;
// 在点击 Access 按钮时,会用到 // 在点击 Access 按钮时,会用到
recordData._path = path; recordData._path = path;
@ -695,13 +696,13 @@ export const createRoutesTableSchema = (collectionName: string, basename: string
'x-component': 'IconPicker', 'x-component': 'IconPicker',
'x-reactions': isMobile 'x-reactions': isMobile
? { ? {
dependencies: ['type'], dependencies: ['type'],
fulfill: { fulfill: {
state: { state: {
required: '{{$deps[0] !== "tabs"}}', required: '{{$deps[0] !== "tabs"}}',
},
}, },
}, }
}
: undefined, : undefined,
}, },
// 由于历史原因,桌面端使用的是 'href' 作为 key移动端使用的是 'url' 作为 key // 由于历史原因,桌面端使用的是 'href' 作为 key移动端使用的是 'url' 作为 key
@ -984,13 +985,13 @@ export const createRoutesTableSchema = (collectionName: string, basename: string
'x-component': 'IconPicker', 'x-component': 'IconPicker',
'x-reactions': isMobile 'x-reactions': isMobile
? { ? {
dependencies: ['type'], dependencies: ['type'],
fulfill: { fulfill: {
state: { state: {
required: '{{$deps[0] !== "tabs"}}', required: '{{$deps[0] !== "tabs"}}',
},
}, },
}, }
}
: undefined, : undefined,
}, },
// 由于历史原因,桌面端使用的是 'href' 作为 key移动端使用的是 'url' 作为 key // 由于历史原因,桌面端使用的是 'href' 作为 key移动端使用的是 'url' 作为 key
@ -1245,11 +1246,7 @@ function useCreateRouteSchema(isMobile: boolean) {
const insertPageSchema = useInsertPageSchema(); const insertPageSchema = useInsertPageSchema();
const createRouteSchema = useCallback( const createRouteSchema = useCallback(
async ({ async ({ type }: { type: NocoBaseDesktopRouteType }) => {
type,
}: {
type: NocoBaseDesktopRouteType;
}) => {
const menuSchemaUid = isMobile ? undefined : uid(); const menuSchemaUid = isMobile ? undefined : uid();
const pageSchemaUid = uid(); const pageSchemaUid = uid();
const tabSchemaName = uid(); const tabSchemaName = uid();
@ -1259,10 +1256,10 @@ function useCreateRouteSchema(isMobile: boolean) {
[NocoBaseDesktopRouteType.page]: isMobile [NocoBaseDesktopRouteType.page]: isMobile
? getMobilePageSchema(pageSchemaUid, tabSchemaUid).schema ? getMobilePageSchema(pageSchemaUid, tabSchemaUid).schema
: getPageMenuSchema({ : getPageMenuSchema({
pageSchemaUid, pageSchemaUid,
tabSchemaUid, tabSchemaUid,
tabSchemaName, tabSchemaName,
}), }),
}; };
if (!typeToSchema[type]) { if (!typeToSchema[type]) {

View File

@ -205,6 +205,21 @@ export default {
title: '{{t("Title")}}', title: '{{t("Title")}}',
}, },
}, },
{
key: 'brt6tz4hgin',
name: 'tooltip',
type: 'string',
interface: 'textarea',
description: null,
collectionName: 'desktopRoutes',
parentKey: null,
reverseKey: null,
uiSchema: {
type: 'string',
'x-component': 'Input.TextArea',
title: '{{t("Tooltip")}}',
},
},
{ {
key: 'ozl5d8t2d5e', key: 'ozl5d8t2d5e',
name: 'icon', name: 'icon',

View File

@ -76,7 +76,7 @@ export default class PluginWorkflowClient extends Plugin {
return this.triggers.get(workflow.type)?.sync ?? workflow.sync; return this.triggers.get(workflow.type)?.sync ?? workflow.sync;
} }
registerTrigger(type: string, trigger: Trigger | { new(): Trigger }) { registerTrigger(type: string, trigger: Trigger | { new (): Trigger }) {
if (typeof trigger === 'function') { if (typeof trigger === 'function') {
this.triggers.register(type, new trigger()); this.triggers.register(type, new trigger());
} else if (trigger) { } else if (trigger) {
@ -86,7 +86,7 @@ export default class PluginWorkflowClient extends Plugin {
} }
} }
registerInstruction(type: string, instruction: Instruction | { new(): Instruction }) { registerInstruction(type: string, instruction: Instruction | { new (): Instruction }) {
if (typeof instruction === 'function') { if (typeof instruction === 'function') {
this.instructions.register(type, new instruction()); this.instructions.register(type, new instruction());
} else if (instruction instanceof Instruction) { } else if (instruction instanceof Instruction) {
@ -193,4 +193,3 @@ export { default as useStyles } from './style';
export { Trigger, useTrigger } from './triggers'; export { Trigger, useTrigger } from './triggers';
export * from './utils'; export * from './utils';
export * from './variable'; export * from './variable';