Files
workspace/code/fms/fms-vue/src/views/dashboard/demo.vue
T
2026-07-31 17:19:11 +08:00

318 lines
7.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { h, ref, shallowRef } from "vue";
import { message } from "antdv-next";
import { Input, InputNumber, Select } from "antdv-next";
import FmsSelect from "@/components/FmsSelect.vue";
import FmsTable from "@/components/fms-table/FmsTable.vue";
const COLUMN_COUNT = 20;
const ROW_COUNT = 4000;
const STATUS_OPTIONS = [
{ label: "启用", value: 1 },
{ label: "禁用", value: 0 },
];
function randInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function randText(len) {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let s = "";
for (let i = 0; i < len; i++) s += chars[randInt(0, chars.length - 1)];
return s;
}
function generateValue(c, i) {
switch (c % 5) {
case 0:
return randText(randInt(4, 10));
case 1:
return randInt(0, 99999);
case 2:
return STATUS_OPTIONS[randInt(0, 1)].value;
case 3:
return `名称-${i}-${c}`;
default:
return `${randInt(1000, 9999)}-${randText(4)}`;
}
}
function buildData() {
const rows = [];
for (let i = 0; i < ROW_COUNT; i++) {
const row = { id: `R${i}` };
for (let c = 1; c <= COLUMN_COUNT; c++) row[`col${c}`] = generateValue(c, i);
rows.push(row);
}
return rows;
}
const dataSource = shallowRef(buildData());
const selectedLocale = ref("");
const selectedLocaleLabel = ref("");
const selectedLocaleRecord = shallowRef(null);
// 当前编辑行 key;进入编辑时留存快照,取消可还原
const editingKey = shallowRef(null);
const editSnapshot = shallowRef(null);
function editConfigFor(c) {
switch (c % 5) {
case 1:
return { type: "number" };
case 2:
return { type: "select", options: STATUS_OPTIONS };
default:
return { type: "text", maxlength: 64 };
}
}
// 单元格渲染:编辑行显示控件,其他行直接返回文本值(让 stk-table 用原生 textContent 渲染,零 VNode 开销)
function makeCellEditor(dataIndex, edit) {
const selectOptions = edit.options || [];
return (props) => {
const { row, cellValue } = props;
if (row.id !== editingKey.value) {
// 非编辑态:直接返回文本字符串,不创建 VNode 包装
if (edit.type === "select") {
const opt = selectOptions.find((o) => o.value === cellValue);
return opt ? opt.label : (cellValue ?? "");
}
return cellValue ?? "";
}
// 编辑态:返回编辑组件 VNode
const update = (v) => {
row[dataIndex] = v;
};
const common = {
size: "small",
style: { width: "100%" },
onClick: (e) => e.stopPropagation(),
};
if (edit.type === "number") {
return h(InputNumber, {
...common,
value: cellValue,
min: edit.min,
max: edit.max,
precision: edit.precision,
"onUpdate:value": update,
});
}
if (edit.type === "select") {
return h(Select, {
...common,
value: cellValue,
options: selectOptions,
"onUpdate:value": update,
});
}
return h(Input, {
...common,
value: cellValue,
maxlength: edit.maxlength,
"onUpdate:value": update,
});
};
}
function buildColumns() {
const cols = [
{
dataIndex: "id",
title: "编码",
width: 120,
fixed: "left",
customCell: makeCellEditor("id", { type: "text", maxlength: 32 }),
},
];
for (let c = 1; c <= COLUMN_COUNT; c++) {
const edit = editConfigFor(c);
cols.push({
dataIndex: `col${c}`,
title: `列 ${c}`,
width: 140,
sorter: edit.type === "number" ? true : undefined,
customCell: makeCellEditor(`col${c}`, edit),
});
}
return cols;
}
const columns = shallowRef(buildColumns());
function onRowClick(ev, row) {
if (row.id === editingKey.value) return;
editingKey.value = row.id;
editSnapshot.value = JSON.parse(JSON.stringify(row));
}
function saveEdit() {
if (editingKey.value == null) return;
editingKey.value = null;
editSnapshot.value = null;
message.success("已保存当前行修改");
}
function cancelEdit() {
if (editingKey.value != null && editSnapshot.value) {
const idx = dataSource.value.findIndex((r) => r.id === editingKey.value);
if (idx >= 0) dataSource.value[idx] = editSnapshot.value;
}
editingKey.value = null;
editSnapshot.value = null;
message.info("已取消编辑");
}
function regenerate() {
editingKey.value = null;
editSnapshot.value = null;
dataSource.value = buildData();
message.success("已重新生成随机数据");
}
function rowClassName(row) {
return row.id === editingKey.value ? "fms-row-editing" : "";
}
function onLocaleChange(record) {
selectedLocaleRecord.value = record;
}
</script>
<template>
<main class="table-demo">
<header class="table-demo__toolbar">
<div class="table-demo__title">
<strong>大数据表格 Demo</strong>
<span class="table-demo__meta">
共 {{ dataSource.length }} 行 · {{ columns.length }} 列 ·
<template v-if="editingKey">正在编辑:{{ editingKey }}</template>
<template v-else>点击任意行进入编辑</template>
</span>
</div>
<div class="table-demo__actions">
<a-button :disabled="editingKey == null" type="primary" @click="saveEdit">保存</a-button>
<a-button :disabled="editingKey == null" @click="cancelEdit">取消</a-button>
<a-button @click="regenerate">重新生成</a-button>
</div>
</header>
<section class="select-demo">
<div class="select-demo__field">
<label class="select-demo__label">FmsSelect 远程选择</label>
<FmsSelect
v-model="selectedLocale"
v-model:label="selectedLocaleLabel"
view-name="b_i18n_type"
label-key="b_name"
value-key="b_code"
order-by="b_code ASC"
:page-size="10"
@change="onLocaleChange"
/>
</div>
<div class="select-demo__value">
<span>当前值:{{ selectedLocale || "-" }}</span>
<span>显示文本:{{ selectedLocaleLabel || "-" }}</span>
<span>记录编码:{{ selectedLocaleRecord?.b_code || "-" }}</span>
</div>
</section>
<section class="table-demo__body">
<FmsTable
v-model:columns="columns"
:data-source="dataSource"
row-key="id"
:row-class-name="rowClassName"
show-overflow
@row-click="onRowClick"
/>
</section>
</main>
</template>
<style scoped lang="scss">
.table-demo {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
min-height: 0;
color: var(--fms-text);
background: var(--fms-surface);
}
.table-demo__toolbar {
display: flex;
flex: none;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 12px 16px;
border-bottom: 1px solid var(--fms-border);
}
.table-demo__title {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.table-demo__title strong {
font-size: 15px;
font-weight: 600;
}
.table-demo__meta {
color: var(--fms-text-secondary);
font-size: 12px;
}
.table-demo__actions {
display: flex;
flex: none;
gap: 8px;
}
.table-demo__body {
position: relative;
flex: 1;
min-height: 0;
padding: 12px 16px 16px;
}
.select-demo {
display: flex;
flex: none;
align-items: flex-end;
gap: 16px;
padding: 12px 16px 0;
}
.select-demo__field {
width: 320px;
}
.select-demo__label {
display: block;
margin-bottom: 6px;
color: var(--fms-text-secondary);
font-size: 12px;
}
.select-demo__value {
display: flex;
min-width: 0;
gap: 16px;
color: var(--fms-text-secondary);
font-size: 12px;
line-height: 32px;
}
:deep(.fms-row-editing) {
background: rgba(22, 119, 255, 0.08) !important;
}
</style>