Files
workspace/code/fms/fms-vue/src/components/fms-table/utils/createCellRenderer.js
T
2026-08-11 16:51:11 +08:00

225 lines
6.9 KiB
JavaScript

import { h, ref } from "vue";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { createEditableCell } from "./createEditableCellRenderer";
const stop = (event) => event.stopPropagation();
const actionButtonClass = cn(buttonVariants({ variant: "ghost", size: "xs" }), "fms-action-btn");
const dangerActionButtonClass = cn(
actionButtonClass,
"text-destructive hover:bg-destructive/10 hover:text-destructive",
);
const moreActionButtonClass = cn(
buttonVariants({ variant: "ghost", size: "xs" }),
"fms-action-btn fms-action-more",
);
function resolveActionState(state, row) {
return typeof state === "function" ? Boolean(state(row)) : Boolean(state);
}
function isActionVisible(action, row) {
return action.visible === undefined || resolveActionState(action.visible, row);
}
function actionButton(action, row, triggerAction) {
const disabled = resolveActionState(action.disabled, row);
const danger = action.danger || action.type === "danger" || action.type === "delete";
const content = [];
if (action.icon) content.push(h(action.icon, { "data-icon": "inline-start" }));
if (action.label || action.type) content.push(action.label || action.type);
return h(
"button",
{
type: "button",
class: danger ? dangerActionButtonClass : actionButtonClass,
title: action.title || action.label,
disabled,
onMousedown: stop,
onClick: (event) => {
event.stopPropagation();
triggerAction(action, row);
},
},
content,
);
}
function createConfirmDialog(state, close, confirm) {
const config = typeof confirm === "string" ? { title: confirm } : confirm || {};
const title = config.title || "确认操作";
const description = config.description ?? config.content ?? "确定要执行此操作吗?";
return h(
AlertDialog,
{
open: Boolean(state.value),
"onUpdate:open": (open) => {
if (!open) close();
},
},
{
default: () =>
h(AlertDialogContent, {}, () => [
h(AlertDialogHeader, {}, () => [
h(AlertDialogTitle, {}, () => title),
h(AlertDialogDescription, {}, () => description),
]),
h(AlertDialogFooter, {}, () => [
h(AlertDialogCancel, { onClick: close }, () => config.cancelText || "取消"),
h(
AlertDialogAction,
{
class:
config.okType === "danger"
? buttonVariants({ variant: "destructive" })
: undefined,
onClick: () => {
const pending = state.value;
close();
pending?.action?.onClick?.(pending.row);
},
},
() => config.okText || "确定",
),
]),
]),
},
);
}
function createActionsCell(actions) {
if (!actions?.length) return undefined;
const inlineActions = actions.filter((action) => action.type !== "more");
const moreAction = actions.find((action) => action.type === "more");
const openRow = ref(null);
const confirmState = ref(null);
function closeMenu() {
openRow.value = null;
}
function closeConfirm() {
confirmState.value = null;
}
function triggerAction(action, row) {
if (resolveActionState(action.disabled, row)) return;
if (!action.confirm) {
action.onClick?.(row);
return;
}
confirmState.value = { action, row };
}
return ({ row }) => {
const children = inlineActions
.filter((action) => isActionVisible(action, row))
.map((action) => actionButton(action, row, triggerAction));
const visibleMoreItems = (moreAction?.items || []).filter((item) => isActionVisible(item, row));
if (visibleMoreItems.length) {
const moreButton = h(
"button",
{
type: "button",
class: [
moreActionButtonClass,
openRow.value === row && "bg-accent text-accent-foreground",
],
title: moreAction.title || "更多操作",
"aria-label": moreAction.title || "更多操作",
onMousedown: stop,
onClick: (event) => {
event.stopPropagation();
openRow.value = row;
},
},
moreAction.label || "⋯",
);
if (openRow.value !== row) {
children.push(moreButton);
} else {
children.push(
h(
DropdownMenu,
{
open: true,
"onUpdate:open": (open) => {
if (!open) closeMenu();
},
},
{
default: () => [
h(DropdownMenuTrigger, { asChild: true }, () => moreButton),
h(DropdownMenuContent, { align: "end", onMousedown: stop }, () =>
h(DropdownMenuGroup, {}, () =>
visibleMoreItems.map((item, index) =>
h(
DropdownMenuItem,
{
key: String(item.key ?? item.type ?? index),
disabled: resolveActionState(item.disabled, row),
variant:
item.danger || item.type === "danger" || item.type === "delete"
? "destructive"
: "default",
onSelect: (event) => {
event.stopPropagation();
closeMenu();
triggerAction(item, row);
},
},
() => [item.icon ? h(item.icon) : null, item.label],
),
),
),
),
],
},
),
);
}
}
if (!children.length) return h("span", { class: "fms-actions-cell" });
const dialog = confirmState.value
? createConfirmDialog(confirmState, closeConfirm, confirmState.value.action.confirm)
: null;
if (dialog) children.push(dialog);
return h("span", { class: "fms-actions-cell" }, children);
};
}
export function resolveCustomCell(col, editingContext) {
if (col.customCell) return col;
if (col.editor) return { ...col, customCell: createEditableCell(col, editingContext) };
if (col.actions?.length) {
const customCell = createActionsCell(col.actions);
if (customCell) return { ...col, customCell };
}
// 纯展示列使用 StkTable 原生渲染,避免虚拟滚动时重复创建函数组件。
return col;
}