20260726225220
This commit is contained in:
1 parent
e3c7c6f8da
commit
a8478d60ed
170 files changed
+6786
-1678
No files matched your search
@@ -1,198 +1,162 @@
|
||||
import { h, ref } from "vue";
|
||||
import { Dropdown, Menu, MenuItem, Modal } from "antdv-next";
|
||||
import { createInputCell } from "./createEditableCellRenderer";
|
||||
import { Dropdown, Modal } from "antdv-next";
|
||||
import { createEditableCell } from "./createEditableCellRenderer";
|
||||
|
||||
const stop = (e) => e.stopPropagation();
|
||||
const stop = (event) => event.stopPropagation();
|
||||
|
||||
// ──────────────────────────────────────────
|
||||
// 1. 通用操作触发:支持 confirm 弹窗
|
||||
// ──────────────────────────────────────────
|
||||
function triggerAction(action, row) {
|
||||
if (action.confirm) {
|
||||
Modal.confirm({
|
||||
title: action.confirm,
|
||||
okText: "确定",
|
||||
okType: "danger",
|
||||
cancelText: "取消",
|
||||
onOk: () => action.onClick?.(row),
|
||||
});
|
||||
} else {
|
||||
if (resolveActionState(action.disabled, row)) return;
|
||||
if (!action.confirm) {
|
||||
action.onClick?.(row);
|
||||
return;
|
||||
}
|
||||
|
||||
const confirm = typeof action.confirm === "string" ? { title: action.confirm } : action.confirm;
|
||||
Modal.confirm({
|
||||
okText: "确定",
|
||||
okType: "danger",
|
||||
cancelText: "取消",
|
||||
...confirm,
|
||||
onOk: () => action.onClick?.(row),
|
||||
});
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────
|
||||
// 2. checkbox 类型渲染器
|
||||
// ──────────────────────────────────────────
|
||||
function createCheckboxCell(col) {
|
||||
return ({ row, cellValue }) => {
|
||||
const checked = cellValue === 1;
|
||||
return h(
|
||||
"label",
|
||||
{
|
||||
class: checked ? "fms-checkbox checked" : "fms-checkbox",
|
||||
onClick: stop,
|
||||
onMousedown: stop,
|
||||
},
|
||||
[
|
||||
h("input", {
|
||||
type: "checkbox",
|
||||
checked,
|
||||
onChange: (e) => {
|
||||
e.stopPropagation();
|
||||
row[col.dataIndex] = e.target.checked ? 1 : 0;
|
||||
col.onChange?.();
|
||||
},
|
||||
onClick: stop,
|
||||
onMousedown: stop,
|
||||
}),
|
||||
h("span", { class: "fms-checkbox-box" }),
|
||||
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, className = "fms-action-btn") {
|
||||
const disabled = resolveActionState(action.disabled, row);
|
||||
const content = [];
|
||||
if (action.icon) content.push(h(action.icon, { size: 14 }));
|
||||
if (action.label || action.type) content.push(action.label || action.type);
|
||||
return h(
|
||||
"button",
|
||||
{
|
||||
type: "button",
|
||||
class: [
|
||||
className,
|
||||
{
|
||||
"fms-action-btn--danger":
|
||||
action.danger || action.type === "danger" || action.type === "delete",
|
||||
},
|
||||
],
|
||||
);
|
||||
};
|
||||
title: action.title || action.label,
|
||||
disabled,
|
||||
onMousedown: stop,
|
||||
onClick: (event) => {
|
||||
event.stopPropagation();
|
||||
triggerAction(action, row);
|
||||
},
|
||||
},
|
||||
content,
|
||||
);
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────
|
||||
// 3. actions 类型渲染器
|
||||
// ──────────────────────────────────────────
|
||||
function createActionsCell(actions) {
|
||||
if (!actions?.length) return undefined;
|
||||
|
||||
const inlineActions = actions.filter((a) => a.type !== "more");
|
||||
const moreAction = actions.find((a) => a.type === "more");
|
||||
const moreOpen = ref(false);
|
||||
const moreRow = ref(null);
|
||||
const inlineActions = actions.filter((action) => action.type !== "more");
|
||||
const moreAction = actions.find((action) => action.type === "more");
|
||||
const openRow = ref(null);
|
||||
const itemByKey = new Map(
|
||||
moreAction?.items?.map((item, index) => [String(item.key ?? item.type ?? index), item]) ?? [],
|
||||
);
|
||||
|
||||
function makeButton(action, row) {
|
||||
return h(
|
||||
"button",
|
||||
{
|
||||
type: "button",
|
||||
class: "fms-action-btn",
|
||||
onMousedown: stop,
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
triggerAction(action, row);
|
||||
},
|
||||
},
|
||||
action.label || action.type,
|
||||
);
|
||||
function closeMenu() {
|
||||
openRow.value = null;
|
||||
}
|
||||
|
||||
return ({ row }) => {
|
||||
const children = [];
|
||||
const children = inlineActions
|
||||
.filter((action) => isActionVisible(action, row))
|
||||
.map((action) => actionButton(action, row));
|
||||
const visibleMoreItems = (moreAction?.items || []).filter((item) => isActionVisible(item, row));
|
||||
if (!visibleMoreItems.length) return h("span", { class: "fms-actions-cell" }, children);
|
||||
|
||||
for (const action of inlineActions) {
|
||||
children.push(makeButton(action, row));
|
||||
const menuItems = visibleMoreItems.map((item, index) => ({
|
||||
key: String(item.key ?? item.type ?? index),
|
||||
label: item.label,
|
||||
icon: item.icon ? h(item.icon, { size: 14 }) : undefined,
|
||||
disabled: resolveActionState(item.disabled, row),
|
||||
danger: item.danger || item.type === "danger" || item.type === "delete",
|
||||
}));
|
||||
const visibleItemByKey = new Map(
|
||||
visibleMoreItems.map((item, index) => [String(item.key ?? item.type ?? index), item]),
|
||||
);
|
||||
|
||||
if (openRow.value !== row) {
|
||||
children.push(
|
||||
h(
|
||||
"button",
|
||||
{
|
||||
type: "button",
|
||||
class: "fms-action-btn fms-action-more",
|
||||
title: moreAction.title || "更多操作",
|
||||
"aria-label": moreAction.title || "更多操作",
|
||||
onMousedown: stop,
|
||||
onClick: (event) => {
|
||||
event.stopPropagation();
|
||||
openRow.value = row;
|
||||
},
|
||||
},
|
||||
moreAction.label || "⋯",
|
||||
),
|
||||
);
|
||||
return h("span", { class: "fms-actions-cell" }, children);
|
||||
}
|
||||
|
||||
if (moreAction?.items?.length) {
|
||||
if (!moreOpen.value) {
|
||||
children.push(
|
||||
h(
|
||||
"button",
|
||||
{
|
||||
type: "button",
|
||||
class: "fms-action-btn fms-action-more",
|
||||
onMousedown: stop,
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
moreOpen.value = true;
|
||||
moreRow.value = row;
|
||||
children.push(
|
||||
h(
|
||||
Dropdown,
|
||||
{
|
||||
open: true,
|
||||
trigger: ["click"],
|
||||
placement: "bottomRight",
|
||||
menu: {
|
||||
items: menuItems,
|
||||
onClick: ({ key }) => {
|
||||
const item = visibleItemByKey.get(String(key)) || itemByKey.get(String(key));
|
||||
closeMenu();
|
||||
if (item) triggerAction(item, row);
|
||||
},
|
||||
},
|
||||
onOpenChange: (open) => {
|
||||
if (!open) closeMenu();
|
||||
},
|
||||
},
|
||||
{
|
||||
default: () =>
|
||||
h(
|
||||
"button",
|
||||
{
|
||||
type: "button",
|
||||
class: "fms-action-btn fms-action-more fms-action-more-open",
|
||||
title: moreAction.title || "更多操作",
|
||||
"aria-label": moreAction.title || "更多操作",
|
||||
onMousedown: stop,
|
||||
onClick: stop,
|
||||
},
|
||||
},
|
||||
"更多",
|
||||
),
|
||||
);
|
||||
} else {
|
||||
children.push(
|
||||
h(
|
||||
Dropdown,
|
||||
{
|
||||
visible: true,
|
||||
trigger: "click",
|
||||
placement: "bottomRight",
|
||||
onVisibleChange: (v) => {
|
||||
if (!v) {
|
||||
moreOpen.value = false;
|
||||
moreRow.value = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
default: () =>
|
||||
h(
|
||||
"button",
|
||||
{
|
||||
type: "button",
|
||||
class: "fms-action-btn fms-action-more fms-action-more-open",
|
||||
onMousedown: stop,
|
||||
},
|
||||
"更多",
|
||||
),
|
||||
overlay: () =>
|
||||
h(Menu, null, {
|
||||
default: () =>
|
||||
moreAction.items.map((item) =>
|
||||
h(MenuItem, { key: item.label }, {
|
||||
default: () =>
|
||||
h(
|
||||
"span",
|
||||
{
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
moreOpen.value = false;
|
||||
triggerAction(item, moreRow.value);
|
||||
},
|
||||
},
|
||||
item.label,
|
||||
),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
moreAction.label || "⋯",
|
||||
),
|
||||
},
|
||||
),
|
||||
);
|
||||
return h("span", { class: "fms-actions-cell" }, children);
|
||||
};
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────
|
||||
// 4. 统一入口:根据 col.type 注入 customCell
|
||||
// ──────────────────────────────────────────
|
||||
const cellCache = new WeakMap();
|
||||
|
||||
/**
|
||||
* 根据列定义的 type 属性自动注入 customCell 渲染器。
|
||||
* - `type: "input"` → 原生 input(编辑态通过 row._editing 控制,非编辑态零 VNode 纯文本)
|
||||
* - `type: "checkbox"` → 原生 checkbox
|
||||
* - `type: "actions"` → 操作按钮列(从 col.actions 读取按钮配置)
|
||||
* - 无 type 或有自定义 customCell → 保持原样
|
||||
*
|
||||
* input 类型不做缓存:因为 customCell 读的是 row._editing 运行时状态,
|
||||
* 不依赖闭包捕获的外部值,每次创建相同函数亦可安全返回。
|
||||
* checkbox/actions 使用 WeakMap 缓存,避免重复创建。
|
||||
*/
|
||||
export function resolveCustomCell(col) {
|
||||
export function resolveCustomCell(col, editingContext) {
|
||||
if (col.customCell) return col;
|
||||
if (col.editor) return { ...col, customCell: createEditableCell(col, editingContext) };
|
||||
|
||||
const cached = cellCache.get(col);
|
||||
if (cached) return cached;
|
||||
|
||||
let result = col;
|
||||
if (col.type === "input") {
|
||||
// 不缓存:row._editing 在渲染时动态读取
|
||||
return { ...col, customCell: createInputCell(col) };
|
||||
} else if (col.type === "checkbox") {
|
||||
result = { ...col, customCell: createCheckboxCell(col) };
|
||||
} else if (col.type === "actions") {
|
||||
const fn = createActionsCell(col.actions);
|
||||
if (fn) result = { ...col, customCell: fn };
|
||||
if (col.actions?.length) {
|
||||
const customCell = createActionsCell(col.actions);
|
||||
if (customCell) return { ...col, customCell };
|
||||
}
|
||||
|
||||
cellCache.set(col, result);
|
||||
return result;
|
||||
return col;
|
||||
}
|
||||
Reference in new issue
Block a user