20260813222656
This commit is contained in:
1 parent
ab30604fe4
commit
68de86aee9
3668 files changed
+3921
-216192
No files matched your search
@@ -1,341 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref, shallowRef } from "vue";
|
||||
import { toast } from "vue-sonner";
|
||||
import { Copy, Pencil, Plus, RefreshCw, Search, Trash2, X } from "@lucide/vue";
|
||||
import FmsTable from "@/components/fms-table/FmsTable.vue";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
|
||||
type DemoRow = {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
owner: string;
|
||||
status: number;
|
||||
enabled: number;
|
||||
updatedAt: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
const ROW_COUNT = 20;
|
||||
const DEMO_NAMES = ["客户资料", "订单状态", "库存预警", "合同归档", "发票登记"];
|
||||
const OWNER_OPTIONS = [
|
||||
{ label: "林晓", value: "lin-xiao" },
|
||||
{ label: "陈默", value: "chen-mo" },
|
||||
{ label: "周宁", value: "zhou-ning" },
|
||||
{ label: "方言", value: "fang-yan" },
|
||||
];
|
||||
const STATUS_OPTIONS = [
|
||||
{ label: "启用", value: 1 },
|
||||
{ label: "禁用", value: 0 },
|
||||
];
|
||||
|
||||
function buildRows(): DemoRow[] {
|
||||
return Array.from({ length: ROW_COUNT }, (_, index) => ({
|
||||
id: `R${String(index + 1).padStart(4, "0")}`,
|
||||
code: `FMS-${String(index + 1).padStart(4, "0")}`,
|
||||
name: `${DEMO_NAMES[index % DEMO_NAMES.length]} ${index + 1}`,
|
||||
owner: OWNER_OPTIONS[index % OWNER_OPTIONS.length]!.value,
|
||||
status: index % 7 === 0 ? 0 : 1,
|
||||
enabled: index % 11 === 0 ? 0 : 1,
|
||||
updatedAt: `2026-08-${String((index % 28) + 1).padStart(2, "0")} ${String((index % 10) + 8).padStart(2, "0")}:30`,
|
||||
}));
|
||||
}
|
||||
|
||||
const tableRef = ref<InstanceType<typeof FmsTable> | null>(null);
|
||||
const dataSource = shallowRef<DemoRow[]>(buildRows());
|
||||
const searchQuery = ref("");
|
||||
const statusFilter = ref("all");
|
||||
const selectedRowKeys = ref<string[]>([]);
|
||||
const activeRowKey = ref<string | null>(null);
|
||||
const nextRowNumber = ref(ROW_COUNT + 1);
|
||||
const nextColumnNumber = ref(1);
|
||||
const extraColumns = shallowRef<
|
||||
{ dataIndex: string; title: string; width: number; editor: { type: string } }[]
|
||||
>([]);
|
||||
|
||||
const baseColumns = [
|
||||
{ dataIndex: "code", title: "记录编号", width: 145, fixed: "left" },
|
||||
{ dataIndex: "name", title: "项目名称", width: 220, editor: { type: "input" } },
|
||||
{
|
||||
dataIndex: "owner",
|
||||
title: "负责人",
|
||||
width: 130,
|
||||
editor: { type: "select", options: OWNER_OPTIONS },
|
||||
},
|
||||
{
|
||||
dataIndex: "status",
|
||||
title: "状态",
|
||||
width: 105,
|
||||
editor: { type: "select", options: STATUS_OPTIONS },
|
||||
},
|
||||
{ dataIndex: "enabled", title: "启用", width: 82, editor: { type: "checkbox" } },
|
||||
{ dataIndex: "updatedAt", title: "最近更新", width: 175 },
|
||||
{
|
||||
dataIndex: "actions",
|
||||
key: "actions",
|
||||
title: "操作",
|
||||
width: 190,
|
||||
actions: [
|
||||
{ type: "edit", label: "编辑", icon: Pencil, onClick: (row: DemoRow) => editRow(row) },
|
||||
{
|
||||
type: "delete",
|
||||
label: "删除",
|
||||
icon: Trash2,
|
||||
danger: true,
|
||||
confirm: {
|
||||
title: "删除这条记录?",
|
||||
content: "删除后无法恢复,请确认操作。",
|
||||
okType: "danger",
|
||||
},
|
||||
onClick: (row: DemoRow) => removeRow(row),
|
||||
},
|
||||
{
|
||||
type: "more",
|
||||
label: "更多",
|
||||
items: [
|
||||
{
|
||||
key: "duplicate",
|
||||
label: "复制记录",
|
||||
icon: Copy,
|
||||
onClick: (row: DemoRow) => duplicateRow(row),
|
||||
},
|
||||
{ key: "disable", label: "切换启用状态", onClick: (row: DemoRow) => toggleRow(row) },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const columns = shallowRef([...baseColumns]);
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
const keyword = searchQuery.value.trim().toLowerCase();
|
||||
return dataSource.value.filter((row) => {
|
||||
const matchesKeyword =
|
||||
!keyword || `${row.code} ${row.name} ${row.owner}`.toLowerCase().includes(keyword);
|
||||
const matchesStatus = statusFilter.value === "all" || String(row.status) === statusFilter.value;
|
||||
return matchesKeyword && matchesStatus;
|
||||
});
|
||||
});
|
||||
|
||||
const rowSelection = computed(() => ({
|
||||
selectedRowKeys: selectedRowKeys.value,
|
||||
onChange: (keys: (string | number)[]) => {
|
||||
selectedRowKeys.value = keys.map(String);
|
||||
},
|
||||
}));
|
||||
|
||||
const selectedCount = computed(() => selectedRowKeys.value.length);
|
||||
|
||||
function syncRows() {
|
||||
dataSource.value = [...dataSource.value];
|
||||
}
|
||||
|
||||
function syncColumns() {
|
||||
columns.value = [...baseColumns, ...extraColumns.value];
|
||||
}
|
||||
|
||||
function addColumn() {
|
||||
const colNum = nextColumnNumber.value++;
|
||||
const dataIndex = `field_${colNum}`;
|
||||
const title = `自定义${colNum}`;
|
||||
// 为已有行补上该字段的默认值
|
||||
dataSource.value = dataSource.value.map((row) => ({
|
||||
...row,
|
||||
[dataIndex]: row[dataIndex] ?? "",
|
||||
}));
|
||||
extraColumns.value = [
|
||||
...extraColumns.value,
|
||||
{ dataIndex, title, width: 160, editor: { type: "input" } },
|
||||
];
|
||||
syncColumns();
|
||||
toast.success(`已新增列: ${title}`);
|
||||
}
|
||||
|
||||
function removeColumn(dataIndex: string) {
|
||||
extraColumns.value = extraColumns.value.filter((col) => col.dataIndex !== dataIndex);
|
||||
dataSource.value = dataSource.value.map((row) => {
|
||||
const { [dataIndex]: _, ...rest } = row;
|
||||
return rest as DemoRow;
|
||||
});
|
||||
syncColumns();
|
||||
toast.success(`已移除列`);
|
||||
}
|
||||
|
||||
function onTableChange(
|
||||
row: DemoRow,
|
||||
column: { title?: string; dataIndex?: string },
|
||||
value: unknown,
|
||||
) {
|
||||
activeRowKey.value = row.id;
|
||||
syncRows();
|
||||
toast.success(`${column.title || column.dataIndex} 已更新`, {
|
||||
description: `${row.code} · ${String(value)}`,
|
||||
});
|
||||
}
|
||||
|
||||
function onRowOrderChange(rows: DemoRow[]) {
|
||||
dataSource.value = rows;
|
||||
toast.success("行顺序已更新");
|
||||
}
|
||||
|
||||
function regenerate() {
|
||||
dataSource.value = buildRows();
|
||||
extraColumns.value = [];
|
||||
syncColumns();
|
||||
selectedRowKeys.value = [];
|
||||
activeRowKey.value = null;
|
||||
nextRowNumber.value = ROW_COUNT + 1;
|
||||
nextColumnNumber.value = 1;
|
||||
toast.success(`已生成 ${ROW_COUNT.toLocaleString()} 条演示数据`);
|
||||
}
|
||||
|
||||
async function addRow() {
|
||||
const rowNumber = nextRowNumber.value++;
|
||||
const row: DemoRow = {
|
||||
id: `R${String(rowNumber).padStart(4, "0")}`,
|
||||
code: `FMS-${String(rowNumber).padStart(4, "0")}`,
|
||||
name: `新建记录 ${rowNumber}`,
|
||||
owner: OWNER_OPTIONS[0]!.value,
|
||||
status: 1,
|
||||
enabled: 1,
|
||||
updatedAt: "2026-08-10 20:00",
|
||||
};
|
||||
for (const col of extraColumns.value) {
|
||||
row[col.dataIndex] = "";
|
||||
}
|
||||
|
||||
dataSource.value = [...dataSource.value, row];
|
||||
searchQuery.value = "";
|
||||
statusFilter.value = "all";
|
||||
activeRowKey.value = row.id;
|
||||
await nextTick();
|
||||
await tableRef.value?.editRow(row.id, "name");
|
||||
toast.success(`已新增 ${row.code}`);
|
||||
}
|
||||
|
||||
function editRow(row: DemoRow) {
|
||||
activeRowKey.value = row.id;
|
||||
tableRef.value?.editRow(row.id, "name", { scroll: false });
|
||||
}
|
||||
|
||||
function removeRow(row: DemoRow) {
|
||||
dataSource.value = dataSource.value.filter((item) => item.id !== row.id);
|
||||
selectedRowKeys.value = selectedRowKeys.value.filter((key) => key !== row.id);
|
||||
toast.success(`已删除 ${row.code}`);
|
||||
}
|
||||
|
||||
function duplicateRow(row: DemoRow) {
|
||||
const copyRow = {
|
||||
...row,
|
||||
id: `${row.id}-COPY`,
|
||||
code: `${row.code}-COPY`,
|
||||
name: `${row.name} 副本`,
|
||||
};
|
||||
dataSource.value = [copyRow, ...dataSource.value];
|
||||
toast.success(`已复制 ${row.code}`);
|
||||
}
|
||||
|
||||
function toggleRow(row: DemoRow) {
|
||||
row.enabled = row.enabled === 1 ? 0 : 1;
|
||||
syncRows();
|
||||
toast.success(row.enabled === 1 ? "记录已启用" : "记录已停用");
|
||||
}
|
||||
|
||||
function editFirstSelected() {
|
||||
const firstKey = selectedRowKeys.value[0];
|
||||
if (!firstKey) {
|
||||
toast.info("请先选择一条记录");
|
||||
return;
|
||||
}
|
||||
editRow(dataSource.value.find((row) => row.id === firstKey)!);
|
||||
}
|
||||
|
||||
function clearSelection() {
|
||||
selectedRowKeys.value = [];
|
||||
tableRef.value?.clearSelectedArea();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="flex h-full min-h-[460px] flex-col gap-3">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<Button size="sm" @click="addRow">
|
||||
<Plus data-icon="inline-start" />
|
||||
新增行
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" @click="regenerate">
|
||||
<RefreshCw data-icon="inline-start" />
|
||||
重新生成
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" @click="addColumn">
|
||||
<Plus data-icon="inline-start" />
|
||||
新增列
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" :disabled="!selectedCount" @click="editFirstSelected">
|
||||
<Pencil data-icon="inline-start" />
|
||||
编辑选中
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" :disabled="!selectedCount" @click="clearSelection">
|
||||
<X data-icon="inline-start" />
|
||||
清空选择
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="text-xs tabular-nums text-muted-foreground">
|
||||
{{ filteredRows.length.toLocaleString() }} / {{ dataSource.length.toLocaleString() }} 条
|
||||
</span>
|
||||
<div class="relative">
|
||||
<Search
|
||||
class="pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<Input
|
||||
v-model="searchQuery"
|
||||
class="h-8 w-56 pl-8 text-xs"
|
||||
aria-label="搜索编号、名称或负责人"
|
||||
/>
|
||||
</div>
|
||||
<Select v-model="statusFilter">
|
||||
<SelectTrigger size="sm" class="h-8 w-28" aria-label="筛选状态">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value="all">全部状态</SelectItem>
|
||||
<SelectItem value="1">启用</SelectItem>
|
||||
<SelectItem value="0">禁用</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="min-h-0 flex-1">
|
||||
<FmsTable
|
||||
ref="tableRef"
|
||||
v-model:columns="columns"
|
||||
:data-source="filteredRows"
|
||||
:row-selection="rowSelection"
|
||||
:active-row-key="activeRowKey"
|
||||
row-key="id"
|
||||
row-draggable
|
||||
scroll-row-by-row="scrollbar"
|
||||
show-overflow
|
||||
@change="onTableChange"
|
||||
@row-order-change="onRowOrderChange"
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
Reference in new issue
Block a user