225 lines
6.9 KiB
JavaScript
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;
|
|
}
|