首先,列表页面结构如下:
<script setup lang="ts">
import type {
PaginationQuery,
ProductInfoEntity,
SearchBasicOption,
UserActivityInfo,
} from '@vben/types';
import type { ContextMenu, VxeGridProps } from '#/adapter/vxe-table';
import { onMounted, reactive, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { ElCard, ElTag } from 'element-plus';
import { OneButton, OneNotification } from '#/adapter';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import {
columnHelperStore,
EntityType,
ModuleIdentifierType,
OrderStatusPool,
productCenterStore,
stateMachinesStore,
UserBehavior,
} from '#/store';
import {
BaseDeleteModal,
BaseProductListBtn,
BaseUserFieldSettings,
ProductInfoEdit,
SearchOperator,
} from '#/views';
//...
// 右键菜单
const productContextMenus: ContextMenu[] = [
{
key: 'add',
text: 'base.base-info-button.add',
emit: 'context-add',
},
{
key: 'edit',
text: 'common.edit',
disabled: (data) => !data.row,
emit: 'context-edit',
},
{
key: 'onSale',
text: 'base.base-info-button.onSale',
disabled: (data) => !data.row,
emit: 'context-on-sale',
},
];
/**
* 列表配置
*/
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: productState.columnList,
sortConfig: {
multiple: true,
},
//...
},
gridEvents,
//此处将菜单内容传进去
contextMenuDefs: productContextMenus,
});
const handleAddBtn = () => {};
//...
</script>
<template>
<Page>
<-- ... -->
<div class="main-container">
<ElCard shadow="never" style="border: none">
<div class="table-container">
<Grid
<-- 此处加上触发的对应函数 -->
@context-add="handleAddBtn"
@context-edit="handleEditBtn"
@context-on-sale="handleOnSaleBtn"
>
<template #image></template>
</Grid>
</div>
</ElCard>
</div>
</Page>
</template>
项目中使用的vxe-table是已经封装过的,在vxe-table组件中,需要加入内容菜单组件
<script lang="ts" setup>
import type {
VxeGridDefines,
VxeGridInstance,
VxeGridListeners,
VxeGridPropTypes,
VxeGridProps as VxeTableGridProps,
VxeToolbarPropTypes,
} from 'vxe-table';
//...
// 记录最近一次右键目标行,用于确保菜单作用于右键行
const lastContextRow = ref<any>();
const state = props.api?.useStore?.();
//...
// 右键整行
function onNativeContextMenu(ev: MouseEvent) {
const target = ev.target as HTMLElement | null;
if (!target) return;
const tr = target.closest('tr');
if (!tr) return;
const node = gridRef.value?.getRowNode?.(tr as HTMLElement);
const row = (node as any)?.item;
if (row) {
lastContextRow.value = row;
gridRef.value?.setCurrentRow?.(row);
(
gridEvents.value?.currentRowChange as VxeGridListeners['currentRowChange']
)?.({
$table: gridRef.value as any,
row: row as any,
rowIndex: -1,
} as any);
}
}
//...
const contextHandlerData = computed(() => {
const currentRow =
lastContextRow.value ?? gridRef.value?.getCurrentRecord?.();
return {
row: currentRow,
api: props.api,
emit,
};
});
//这里的contextMenuDefs就是传进来的菜单配置
const menusFactory = computed(() => {
const defs = contextMenuDefs?.value ?? [];
return buildContextMenus(defs);
});
</script>
<template>
<div :class="cn('bg-card h-full rounded-md', className)">
<VbenContextMenu
:menus="menusFactory as any"
:handler-data="contextHandlerData"
:modal="false"
content-class="z-popup"
>
<VxeGrid
ref="gridRef"
:class="
cn(
'p-2',
{
'pt-0': showToolbar && !formOptions,
},
gridClass,
)
"
v-bind="options"
v-on="events"
@contextmenu.capture="onNativeContextMenu"
>
<-- ... -->
</VxeGrid>
</VbenContextMenu>
</div>
</template>
其中VbenContextMenu为封装的上下文菜单,内容如下:
<script setup lang="ts">
import type {
ContextMenuContentProps,
ContextMenuRootEmits,
ContextMenuRootProps,
} from 'radix-vue';
import type { ClassType } from '@vben-core/typings';
import type { IContextMenuItem } from './interface';
import { computed } from 'vue';
import { useForwardPropsEmits } from 'radix-vue';
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuTrigger,
} from '../../ui/context-menu';
const props = defineProps<
ContextMenuRootProps & {
class?: ClassType;
contentClass?: ClassType;
contentProps?: ContextMenuContentProps;
handlerData?: Record<string, any>;
itemClass?: ClassType;
menus: (data: any) => IContextMenuItem[];
}
>();
const emits = defineEmits<ContextMenuRootEmits>();
const delegatedProps = computed(() => {
const {
class: _cls,
contentClass: _,
contentProps: _cProps,
itemClass: _iCls,
...delegated
} = props;
return delegated;
});
const forwarded = useForwardPropsEmits(delegatedProps, emits);
const menusView = computed(() => {
return props.menus?.(props.handlerData);
});
function handleClick(menu: IContextMenuItem) {
if (menu.disabled) {
return;
}
menu?.handler?.(props.handlerData);
}
</script>
<template>
<ContextMenu v-bind="forwarded">
<ContextMenuTrigger as-child>
<slot></slot>
</ContextMenuTrigger>
<ContextMenuContent
:class="contentClass"
v-bind="contentProps"
class="side-content z-popup"
>
<template v-for="menu in menusView" :key="menu.key">
<ContextMenuItem
:class="itemClass"
:disabled="menu.disabled"
:inset="menu.inset || !menu.icon"
class="cursor-pointer"
@click="handleClick(menu)"
>
<component
:is="menu.icon"
v-if="menu.icon"
class="mr-2 size-4 text-lg"
/>
{{ menu.text }}
<ContextMenuShortcut v-if="menu.shortcut">
{{ menu.shortcut }}
</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuSeparator v-if="menu.separator" />
</template>
</ContextMenuContent>
</ContextMenu>
</template>
最后是buildContextMenus的定义:
import type { Component } from 'vue';
import type { IContextMenuItem } from '@vben-core/shadcn-ui';
import { h } from 'vue';
import { $t } from '@vben/locales';
// 供上下文菜单生成时使用的数据
export interface GridContextMenuData {
row?: any;
api?: any;
emit?: (event: string, payload: any) => void;
}
// 列表页传入的菜单定义接口
export interface ContextMenu {
/** 唯一键 */
key: string;
/** 文本的 i18n key(必填),作为参数传入 $t() */
text: string;
/** 图标 class(可选) */
icon?: string;
/** 是否可见(布尔或根据行/上下文计算) */
visible?: ((data: GridContextMenuData) => boolean) | boolean;
/** 是否禁用(布尔或根据行/上下文计算) */
disabled?: ((data: GridContextMenuData) => boolean) | boolean;
/** 触发事件名(通过 emit 触发,默认带 row 作为 payload) */
emit?: string;
/** 自定义处理函数(拿到 GridContextMenuData) */
handler?: (data: GridContextMenuData) => void;
/** 事件 payload 解析器(默认使用 row) */
payloadResolver?: (data: GridContextMenuData) => any;
}
// 由 ContextMenu[] 生成 VbenContextMenu 需要的 IContextMenuItem[]
export function buildContextMenus(defs: ContextMenu[]) {
return (data: GridContextMenuData): IContextMenuItem[] => {
const items: IContextMenuItem[] = [];
for (const def of defs ?? []) {
const isVisible =
typeof def.visible === 'function' ? def.visible(data) : def.visible;
if (isVisible === false) continue;
const disabled =
typeof def.disabled === 'function' ? def.disabled(data) : def.disabled;
const payload = def.payloadResolver
? def.payloadResolver(data)
: data?.row;
const text = $t(def.text);
const iconComp: Component | undefined = def.icon
? { render: () => h('span', { class: def.icon }) }
: undefined;
items.push({
key: def.key,
text,
icon: iconComp,
disabled,
handler: () => {
if (def.handler) {
def.handler(data);
return;
}
if (def.emit && typeof data?.emit === 'function') {
(data.emit as any)(def.emit, payload);
}
},
});
}
return items;
};
}
现在就可以在列表中使用右键菜单,并进行自定义的配置
1251

被折叠的 条评论
为什么被折叠?



