Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 201 additions & 5 deletions packages/vtable-plugins/demo/context-menu/context-menu.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as VTable from '@visactor/vtable';
import { ContextMenuPlugin } from '../../src/context-menu';
import { TableSeriesNumber } from '../../src/table-series-number';
import { DEFAULT_HEADER_MENU_ITEMS, MenuKey } from '../../src';
import { DEFAULT_HEADER_MENU_ITEMS } from '../../src';
import type { MenuItem } from '../../src';

const CONTAINER_ID = 'vTable';

Expand All @@ -20,6 +21,12 @@ const generateTestData = (count: number) => {
}));
};

const COPY_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`;

const DELETE_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>`;

const SETTINGS_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>`;

/**
* 创建示例表格
*/
Expand Down Expand Up @@ -72,7 +79,7 @@ export function createTableInstance() {
...DEFAULT_HEADER_MENU_ITEMS,
{
text: '设置筛选器',

customIcon: { svg: SETTINGS_SVG, width: 16, height: 16 },
menuKey: 'set_filter'
}
],
Expand Down Expand Up @@ -155,10 +162,181 @@ export function createTableInstance() {
// { text: '合并单元格', menuKey: 'merge_cells', iconName: 'merge' },
// { text: '设置保护范围', menuKey: 'set_protection', iconName: 'protect' }
// ],
bodyCellMenuItems: [
{
text: '复制',
customClassName: {
item: 'copy-item',
icon: 'copy-icon',
text: 'copy-text',
shortcut: 'copy-shortcut',
leftContainer: 'left',
rightContainer: 'right'
},
menuKey: 'copy',
customIcon: { svg: COPY_SVG, width: 16, height: 16 },
shortcut: 'Ctrl+C'
},
{ text: '剪切', menuKey: 'cut', iconName: 'cut', shortcut: 'Ctrl+X' },
{ text: '粘贴', menuKey: 'paste', iconName: 'paste', shortcut: 'Ctrl+V' },
'---',
{
text: '删除选中',
customIcon: (_menuItem: MenuItem) => {
const el = document.createElement('span');
el.style.display = 'inline-block';
el.style.width = '8px';
el.style.height = '8px';
el.style.borderRadius = '50%';
el.style.backgroundColor = '#ff4d4f';
return el;
},
menuKey: 'delete_row'
},
{
text: '自定义操作',
customIcon: (_menuItem: MenuItem) => {
const img = document.createElement('img');
img.src = 'data:image/svg+xml,' + encodeURIComponent(DELETE_SVG);
img.width = 16;
img.height = 16;
img.style.opacity = '0.6';
return img;
},
menuKey: 'custom_action'
},
'---',
{
text: '插入',
menuKey: 'insert',
iconName: 'insert',
children: [
{
text: '向上插入行数:',
menuKey: 'insert_row_above',
iconName: 'up-arrow',
inputDefaultValue: 1,
customClassName: { input: 'input' }
},
{ text: '向下插入行数:', menuKey: 'insert_row_below', iconName: 'down-arrow', inputDefaultValue: 1 },
'---',
{ text: '向左插入列数:', menuKey: 'insert_column_left', iconName: 'left-arrow', inputDefaultValue: 1 },
{ text: '向右插入列数:', menuKey: 'insert_column_right', iconName: 'right-arrow', inputDefaultValue: 1 }
],
customClassName: {
arrow: 'arrow'
}
},
'---',
{ text: '合并单元格', menuKey: 'merge_cells' },
'---',
{
text: '不可用操作',
menuKey: 'disabled_action',
disabled: true,
iconName: 'protect',
customClassName: { itemDisabled: 'custom-disabled' }
},
{ text: '不可用粘贴', menuKey: 'paste_disabled', disabled: true, shortcut: 'Ctrl+V' }
],
menuClickCallback: {
// [MenuKey.COPY]: (args: MenuClickEventArgs, table: VTable.ListTable) => {
// console.log('复制', args, table);
// }
custom_action: (args, _table) => {
alert(`自定义操作被点击: 行${args.rowIndex} 列${args.colIndex}`);
},
set_filter: (args, table) => {
alert(`设置筛选器: 列${args.colIndex}`);
}
},
CustomMenuAttributions: {
style: {
menuContainer: {
backgroundColor: 'rgba(255, 255, 255)',
border: '1px solid #ccc',
borderRadius: '6px',
boxShadow: '0 3px 12px rgba(0, 0, 0, 0.15)',
padding: '4px',
maxHeight: '400px'
},
submenuContainer: {
borderRadius: '6px',
boxShadow: '0 3px 12px rgba(0, 0, 0, 0.15)'
},
menuItem: {
padding: '6px 12px',
borderRadius: '4px',
cursor: 'pointer'
},
menuItemHover: {
backgroundColor: 'rgba(22, 119, 255, 0.1)'
},
menuItemDisabled: {
opacity: '0.35',
cursor: 'not-allowed'
},
menuItemSeparator: {
height: '2px',
backgroundColor: 'rgba(0, 0, 0, 0.12)',
margin: '4px 0'
},
menuItemIcon: {
marginRight: '10px',
width: '18px',
height: '18px'
},
menuItemText: {
flex: '1',
fontSize: '13px'
},
menuItemShortcut: {
marginLeft: '24px',
color: '#aaa',
fontSize: '11px'
},
submenuArrow: {
marginLeft: '8px',
fontSize: '10px',
color: '#999'
},
inputContainer: {
padding: '6px 12px',
display: 'flex',
alignItems: 'center'
},
inputLabel: {
marginRight: '6px',
whiteSpace: 'nowrap',
fontSize: '12px'
},
inputField: {
width: '50px',
padding: '3px 4px',
border: '1px solid #d9d9d9',
borderRadius: '4px',
fontSize: '12px',
outline: 'none'
},
buttonContainer: {
display: 'flex',
justifyContent: 'flex-end',
padding: '4px 12px'
},
button: {
padding: '4px 12px',
backgroundColor: '#1677ff',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '12px'
}
},
class: {
menuContainer: 'custom-context-menu-container',
menuItem: 'custom-context-menu-item',
menuItemSeparator: ['custom1-context-menu-item-separator', 'custom2-context-menu-item-separator'],
submenuContainer: 'custom-context-menu-submenu',
menuItemDisabled: 'custom-context-menu-item-disabled'
}
}
});
const tableSeriesNumberPlugin = new TableSeriesNumber({
Expand Down Expand Up @@ -238,6 +416,24 @@ export function createTable() {
<li>支持输入框数量</li>
<li>长菜单可滚动</li>
</ul>

<h3>菜单自定义图标和样式演示</h3>
<ul>
<li><b>表体单元格</b>:展示了 SVG 图标、渲染函数图标、内置 emoji 图标的混合使用</li>
<li><b>表头单元格</b>:展示了 SVG 图标(设置图标)</li>
<li>菜单样式和类名通过 <code>CustomMenuAttributions</code> 统一配置</li>
<li><b>图标类型说明</b></li>
<ul>
<li><code>iconName: 'copy'</code> — 内置 emoji 图标</li>
<li><code>customIcon: { svg: '...', width: 16, height: 16 }</code> — SVG 图标</li>
<li><code>customIcon: (menuItem) => HTMLElement</code> — 渲染函数</li>
</ul>
<li><b>自定义类名说明</b></li>
<ul>
<li><code>CustomMenuAttributions.class</code> — 统一追加类名</li>
<li><code>MenuItem.customClassName</code> — 单项精细化类名</li>
</ul>
</ul>
`;

document.body.insertBefore(info, container);
Expand Down
9 changes: 8 additions & 1 deletion packages/vtable-plugins/src/context-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { TABLE_EVENT_TYPE } from '@visactor/vtable';
import type { pluginsDefinition } from '@visactor/vtable';
import { MenuManager } from './contextmenu/menu-manager';
import { MenuHandler } from './contextmenu/handle-menu-helper';
import { mergeClasses, mergeStyles } from './contextmenu/styles';
import type { CustomMenuAttributions } from './contextmenu/styles';
import type { MenuItemOrSeparator, MenuClickEventArgs } from './contextmenu/types';
import {
DEFAULT_BODY_MENU_ITEMS,
Expand All @@ -27,6 +29,8 @@ export interface ContextMenuOptions {
headerCellMenuItems?: MenuItemOrSeparator[];
/** 表体菜单项 */
bodyCellMenuItems?: MenuItemOrSeparator[];
/** 自定义菜单样式 */
CustomMenuAttributions?: CustomMenuAttributions;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

命名使用小驼峰

/** 菜单点击回调。如果设置是函数,则忽略内部默认的菜单项处理逻辑。如果这里配置的是个对象(对象的key为menuKey),则有匹配的menuKey时忽略内部默认的菜单项处理逻辑,
* 以这里配置的为准 ,没有匹配的menuKey时,则使用内部默认的菜单项处理逻辑。*/
menuClickCallback?:
Expand Down Expand Up @@ -63,7 +67,10 @@ export class ContextMenuPlugin implements pluginsDefinition.IVTablePlugin {
constructor(pluginOptions: ContextMenuOptions = {}) {
this.id = pluginOptions.id ?? this.id;
this.pluginOptions = pluginOptions;
this.menuManager = new MenuManager();
this.menuManager = new MenuManager(
mergeStyles(pluginOptions.CustomMenuAttributions?.style),
mergeClasses(pluginOptions.CustomMenuAttributions?.class)
);
this.menuHandler = new MenuHandler();
this.initDefaultMenuItems();
}
Expand Down
Loading
Loading