mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 21:49:25 +08:00
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:
parent
a9000d16c1
commit
c2928d38cf
37
packages/core/client/src/hoc/withTooltipComponent.tsx
Normal file
37
packages/core/client/src/hoc/withTooltipComponent.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
@ -88,6 +88,5 @@ export {
|
|||||||
IsInNocoBaseRecursionFieldContext,
|
IsInNocoBaseRecursionFieldContext,
|
||||||
NocoBaseRecursionField,
|
NocoBaseRecursionField,
|
||||||
RefreshComponentProvider,
|
RefreshComponentProvider,
|
||||||
useRefreshFieldSchema
|
useRefreshFieldSchema,
|
||||||
} from './formily/NocoBaseRecursionField';
|
} from './formily/NocoBaseRecursionField';
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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) => {
|
||||||
|
@ -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'],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
|
|
||||||
// 设置一个新名称
|
// 设置一个新名称
|
||||||
|
@ -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;
|
||||||
|
@ -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" />,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -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,
|
||||||
|
@ -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'],
|
||||||
|
@ -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}
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
),
|
),
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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'];
|
||||||
|
|
||||||
|
@ -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]) {
|
||||||
|
@ -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',
|
||||||
|
@ -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';
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user