20260827173205
This commit is contained in:
1 parent
60cacfa8fb
commit
3824af0f12
38 files changed
+2398
-3552
No files matched your search
@@ -1,20 +1,20 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { LayoutDashboard } from '@lucide/vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import Button from '@/components/ui/button/button.vue'
|
||||
import Input from '@/components/ui/input/input.vue'
|
||||
import { ref } from 'vue'
|
||||
import {
|
||||
ChevronDown,
|
||||
Download,
|
||||
Plus,
|
||||
RefreshCw,
|
||||
Search,
|
||||
SlidersHorizontal,
|
||||
Trash2,
|
||||
Upload,
|
||||
} from '@lucide/vue'
|
||||
import { Button, Dropdown, Input, Pagination, RangePicker, Select } from '@/components/ui'
|
||||
import FmsTable from '@/components/fms-table/FmsTable.vue'
|
||||
|
||||
defineOptions({ name: 'Dashboard' })
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// 跳转到详情页(不同 id = 不同路由缓存),用于验证「多参数独立缓存」
|
||||
function goDetail(id) {
|
||||
router.push(`/detail/${id}`)
|
||||
}
|
||||
|
||||
/* ---------- fms-table 大数据量 + 单行编辑 demo ---------- */
|
||||
const TYPE_OPTIONS = [
|
||||
{ value: 'task', label: '任务' },
|
||||
@@ -57,9 +57,16 @@ function makeColumns() {
|
||||
editor: { type: 'input' },
|
||||
})
|
||||
}
|
||||
// 纯展示列(无 editor、无 customCell),走库原生文本渲染:
|
||||
// money 千分位 / datetime 日期时间 / date 日期 / percent 百分比
|
||||
columns.push(
|
||||
{ title: '金额', dataIndex: 'amount', width: 120 },
|
||||
{ title: '创建时间', dataIndex: 'createdAt', width: 170 },
|
||||
{ title: '到期日', dataIndex: 'expireDate', width: 110 },
|
||||
{ title: '完成率', dataIndex: 'progress', width: 90 },
|
||||
)
|
||||
return columns
|
||||
}
|
||||
|
||||
function makeRows(count) {
|
||||
const rows = []
|
||||
for (let i = 0; i < count; i++) {
|
||||
@@ -70,6 +77,10 @@ function makeRows(count) {
|
||||
status: STATUS_OPTIONS[i % STATUS_OPTIONS.length].value,
|
||||
enabled: i % 2,
|
||||
score: Math.round(Math.random() * 1000) / 10,
|
||||
amount: Math.round(Math.random() * 10000000) / 100,
|
||||
createdAt: new Date(Date.now() - (count - i) * 3600_000).toISOString(),
|
||||
expireDate: new Date(Date.now() + ((i % 365) + 1) * 86400_000).toISOString(),
|
||||
progress: Math.round(Math.random() * 100) / 100,
|
||||
}
|
||||
for (let j = 1; j <= EXTRA_FIELD_COUNT; j++) row[`f${j}`] = `${i + 1}-${j}`
|
||||
rows.push(row)
|
||||
@@ -77,68 +88,117 @@ function makeRows(count) {
|
||||
return rows
|
||||
}
|
||||
|
||||
const DEFAULT_ROW_COUNT = 5000
|
||||
// 行级特殊样式:根据业务字段返回行 class(经 $attrs 透传给 stk-table 的 rowClassName)。
|
||||
// 返回的 class 在下方 <style scoped> 中通过 :deep() 定义字体/背景颜色。
|
||||
function rowClassName(row) {
|
||||
if (row.type === 'alert') return 'fms-row-alert'
|
||||
if (row.status === 'pending') return 'fms-row-pending'
|
||||
if (row.status === 'disabled') return 'fms-row-disabled'
|
||||
return ''
|
||||
}
|
||||
|
||||
/* ---------- 表格数据:默认 1w 行(虚拟滚动) ---------- */
|
||||
const DEFAULT_ROW_COUNT = 10000
|
||||
const columns = ref(makeColumns())
|
||||
const rowCount = ref(DEFAULT_ROW_COUNT)
|
||||
const dataSource = ref(makeRows(DEFAULT_ROW_COUNT))
|
||||
const editCount = ref(0)
|
||||
|
||||
// 修改行数后重新生成全新数据(行对象全部重建,fms-table 编辑状态随之自然重置)
|
||||
function regenerate() {
|
||||
const count = Math.max(
|
||||
1,
|
||||
Math.min(100000, Math.floor(Number(rowCount.value)) || DEFAULT_ROW_COUNT),
|
||||
)
|
||||
rowCount.value = count
|
||||
dataSource.value = makeRows(count)
|
||||
editCount.value = 0
|
||||
/* ---------- 工具栏 UI(仅界面骨架,搜索/查询逻辑后续接入) ---------- */
|
||||
const keyword = ref('')
|
||||
const advancedOpen = ref(false)
|
||||
const advancedForm = ref({
|
||||
name: '',
|
||||
type: undefined,
|
||||
status: undefined,
|
||||
range: null,
|
||||
})
|
||||
|
||||
const moreItems = [
|
||||
{ key: 'export', label: '导出数据', icon: Download },
|
||||
{ key: 'import', label: '导入数据', icon: Upload },
|
||||
{ key: 'refresh', label: '刷新', icon: RefreshCw },
|
||||
{ key: 'divider-1', divider: true },
|
||||
{ key: 'batch-delete', label: '批量删除', icon: Trash2, danger: true },
|
||||
]
|
||||
|
||||
// TODO: 接入真实搜索 / 高级搜索 / 更多操作 / 新增逻辑
|
||||
function onMoreAction() {}
|
||||
function onAdvancedSearch() {}
|
||||
function onAdvancedReset() {
|
||||
advancedForm.value = { name: '', type: undefined, status: undefined, range: null }
|
||||
}
|
||||
|
||||
// fms-table 编辑提交时回传 (row, col, newValue),这里统计编辑次数
|
||||
function onCellChange() {
|
||||
editCount.value++
|
||||
}
|
||||
|
||||
const totalCells = computed(() => columns.value.length * dataSource.value.length)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dashboard-page">
|
||||
<header class="dashboard-header">
|
||||
<div class="dashboard-heading">
|
||||
<div class="dashboard-icon" aria-hidden="true">
|
||||
<LayoutDashboard />
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="dashboard-title">工作台 · fms-table Demo</h1>
|
||||
<p class="dashboard-desc">
|
||||
20 列单行编辑示例:单击单元格进入编辑,Enter 提交,Esc 取消,方向键在单元格间移动。默认
|
||||
5000 行(虚拟滚动),可修改行数后「重新生成」。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-actions">
|
||||
<Button type="primary" @click="goDetail(1)">打开详情 #1</Button>
|
||||
<Button type="primary" @click="goDetail(2)">打开详情 #2</Button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="demo-card">
|
||||
<!-- 工具栏:搜索 + 高级搜索 | 更多操作 + 新增 -->
|
||||
<div class="demo-toolbar">
|
||||
<label class="toolbar-field">
|
||||
<span class="toolbar-label">行数</span>
|
||||
<Input v-model="rowCount" type="number" class="toolbar-input" />
|
||||
</label>
|
||||
<Button type="primary" @click="regenerate">重新生成</Button>
|
||||
<span class="toolbar-meta">
|
||||
{{ dataSource.length.toLocaleString() }} 行 × {{ columns.length }} 列(内置序号列不计入),
|
||||
共 {{ totalCells.toLocaleString() }} 个单元格 · 已编辑 {{ editCount }} 次
|
||||
</span>
|
||||
<div class="toolbar-left">
|
||||
<Input
|
||||
v-model="keyword"
|
||||
class="toolbar-search"
|
||||
placeholder="搜索名称 / ID"
|
||||
allow-clear
|
||||
>
|
||||
<template #prefix><Search :size="14" aria-hidden="true" /></template>
|
||||
</Input>
|
||||
<Button
|
||||
type="outline"
|
||||
:class="{ 'toolbar-toggle-active': advancedOpen }"
|
||||
@click="advancedOpen = !advancedOpen"
|
||||
>
|
||||
<SlidersHorizontal :size="14" aria-hidden="true" />
|
||||
高级搜索
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="toolbar-right">
|
||||
<Dropdown :items="moreItems" @click="onMoreAction">
|
||||
<template #trigger>
|
||||
<Button type="outline">
|
||||
更多操作
|
||||
<ChevronDown :size="14" aria-hidden="true" />
|
||||
</Button>
|
||||
</template>
|
||||
</Dropdown>
|
||||
<Button type="primary">
|
||||
<Plus :size="14" aria-hidden="true" />
|
||||
新增
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 高级搜索面板:label 在上、控件等宽网格(适配多语言标签长度),仅 UI 骨架 -->
|
||||
<div v-show="advancedOpen" class="demo-advanced">
|
||||
<div class="advanced-field">
|
||||
<label class="advanced-label">名称 / ID</label>
|
||||
<Input v-model="advancedForm.name" placeholder="模糊搜索" allow-clear />
|
||||
</div>
|
||||
<div class="advanced-field">
|
||||
<label class="advanced-label">类型</label>
|
||||
<Select v-model="advancedForm.type" :options="TYPE_OPTIONS" clearable />
|
||||
</div>
|
||||
<div class="advanced-field">
|
||||
<label class="advanced-label">状态</label>
|
||||
<Select v-model="advancedForm.status" :options="STATUS_OPTIONS" clearable />
|
||||
</div>
|
||||
<div class="advanced-field">
|
||||
<label class="advanced-label">创建时间</label>
|
||||
<RangePicker v-model="advancedForm.range" />
|
||||
</div>
|
||||
<div class="advanced-actions">
|
||||
<Button type="primary" @click="onAdvancedSearch">查询</Button>
|
||||
<Button type="secondary" @click="onAdvancedReset">重置</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-table">
|
||||
<FmsTable :columns="columns" :data-source="dataSource" row-key="id" @change="onCellChange" />
|
||||
<FmsTable :columns="columns" :data-source="dataSource" row-key="id" />
|
||||
</div>
|
||||
|
||||
<!-- 底部分页:仅 UI 骨架,分页逻辑后续接入 -->
|
||||
<div class="demo-pagination">
|
||||
<Pagination :total="dataSource.length" show-total show-size-changer />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -155,56 +215,7 @@ const totalCells = computed(() => columns.value.length * dataSource.value.length
|
||||
color: var(--fms-text);
|
||||
}
|
||||
|
||||
.dashboard-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.dashboard-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.dashboard-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
flex: none;
|
||||
border-radius: 10px;
|
||||
background: color-mix(in srgb, var(--fms-primary) 10%, var(--fms-card));
|
||||
color: var(--fms-primary);
|
||||
|
||||
:deep(svg) {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dashboard-desc {
|
||||
margin: 2px 0 0;
|
||||
font-size: 13px;
|
||||
color: var(--fms-text-secondary);
|
||||
}
|
||||
|
||||
.dashboard-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* ---- demo 卡片:工具栏 + 表格(灰底画布上的白卡,靠阴影分层,无边框线) ---- */
|
||||
/* ---- demo 卡片:工具栏 + 高级搜索 + 表格 + 分页(灰底画布上的白卡,靠阴影分层,无边框线) ---- */
|
||||
.demo-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -216,38 +227,121 @@ const totalCells = computed(() => columns.value.length * dataSource.value.length
|
||||
box-shadow: var(--fms-shadow);
|
||||
}
|
||||
|
||||
/* ---- 工具栏:左侧搜索 + 高级搜索,右侧更多操作 + 新增 ---- */
|
||||
.demo-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid var(--fms-border);
|
||||
}
|
||||
|
||||
.toolbar-field {
|
||||
display: inline-flex;
|
||||
.toolbar-left,
|
||||
.toolbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.toolbar-label {
|
||||
font-size: 13px;
|
||||
color: var(--fms-text-secondary);
|
||||
.toolbar-search {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.toolbar-input {
|
||||
width: 120px;
|
||||
/* 高级搜索按钮:展开面板时高亮 */
|
||||
.toolbar-toggle-active {
|
||||
color: var(--fms-primary);
|
||||
background: color-mix(in srgb, var(--fms-primary) 10%, var(--fms-card));
|
||||
}
|
||||
|
||||
.toolbar-meta {
|
||||
margin-left: auto;
|
||||
/* ---- 高级搜索面板:label 在上、控件等宽网格。
|
||||
* 多语言下标签长度不一,label 置顶 + 控件铺满网格单元即可保证各列对齐;
|
||||
* 查询/重置独立一行、靠右。 ---- */
|
||||
.demo-advanced {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 12px 16px;
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid var(--fms-border);
|
||||
background: color-mix(in srgb, var(--fms-background) 30%, var(--fms-card));
|
||||
}
|
||||
|
||||
.advanced-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.advanced-label {
|
||||
font-size: 12px;
|
||||
color: var(--fms-text-secondary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* 控件统一铺满所在网格单元(Input / Select / RangePicker 根节点) */
|
||||
.advanced-field :deep(.input-wrapper),
|
||||
.advanced-field :deep(.select),
|
||||
.advanced-field :deep(.fms-range-picker) {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.advanced-actions {
|
||||
grid-column: 1 / -1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.demo-table {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
|
||||
/* 去掉表格外框:卡片本身靠阴影分层,表格直接铺满白卡(仅此处覆盖,
|
||||
不影响其他页面使用 FmsTable 时保留外框) */
|
||||
:deep(.fms-table) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 行级特殊样式:rowClassName 返回的 class 落在 stk-table 的 tr 上,
|
||||
这里通过 :deep() 跨过组件边界设置字体颜色与背景色 */
|
||||
:deep(.fms-row-alert) {
|
||||
color: var(--fms-danger);
|
||||
|
||||
td {
|
||||
background: color-mix(in srgb, var(--fms-danger) 8%, var(--fms-card));
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.fms-row-pending) {
|
||||
color: var(--fms-warning);
|
||||
|
||||
td {
|
||||
background: color-mix(in srgb, var(--fms-warning) 8%, var(--fms-card));
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.fms-row-disabled) {
|
||||
color: var(--fms-disabled-text);
|
||||
|
||||
td {
|
||||
background: var(--fms-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 底部分页条:总数靠左,分页靠右 ---- */
|
||||
.demo-pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user