20260903173038

This commit is contained in:
oneao committed 2026-09-03 17:30:39 +08:00
1 parent c8940f6e03
commit 0faf44cffc
55 files changed
+3278 -208

No files matched your search

+196 -108
View File
@@ -1,15 +1,6 @@
<script setup>
import { ref } from 'vue'
import {
ChevronDown,
Download,
Plus,
RefreshCw,
Search,
SlidersHorizontal,
Trash2,
Upload,
} from '@lucide/vue'
import { computed, ref } from 'vue'
import { ChevronDown, ChevronUp, Download, Plus, RefreshCw, Search, Trash2, Upload } from '@lucide/vue'
import { Button, Dropdown, Input, Pagination, RangePicker, Select } from '@/components/ui'
import FmsTable from '@/components/fms-table/FmsTable.vue'
@@ -27,6 +18,21 @@ const STATUS_OPTIONS = [
{ value: 'disabled', label: '禁用' },
{ value: 'pending', label: '待审' },
]
// 低频筛选字段选项(模拟多字段场景)
const PRIORITY_OPTIONS = [
{ value: 'high', label: '高' },
{ value: 'mid', label: '中' },
{ value: 'low', label: '低' },
]
const SOURCE_OPTIONS = [
{ value: 'manual', label: '手工录入' },
{ value: 'import', label: '导入' },
{ value: 'api', label: 'API' },
]
const ENABLED_OPTIONS = [
{ value: '1', label: '是' },
{ value: '0', label: '否' },
]
// 额外可编辑文本列数量,与下方业务列合计 20 列
const EXTRA_FIELD_COUNT = 14
@@ -102,16 +108,48 @@ const DEFAULT_ROW_COUNT = 10000
const columns = ref(makeColumns())
const dataSource = ref(makeRows(DEFAULT_ROW_COUNT))
/* ---------- 工具栏 UI(仅界面骨架,搜索/查询逻辑后续接入) ---------- */
const keyword = ref('')
const advancedOpen = ref(false)
const advancedForm = ref({
/* ---------- 搜索表单 + 操作工具栏(仅界面骨架,查询/操作逻辑后续接入) ---------- */
const searchForm = ref({
name: '',
type: undefined,
status: undefined,
range: null,
owner: undefined,
priority: undefined,
source: undefined,
enabled: undefined,
minScore: null,
})
// 搜索字段描述:顺序即表单字段顺序(前 FIELDS_PER_ROW 个为常驻行)
const searchFields = [
{ key: 'name', label: '名称 / ID', type: 'input', placeholder: '模糊搜索', submitIcon: true },
{ key: 'type', label: '类型', type: 'select', options: TYPE_OPTIONS },
{ key: 'status', label: '状态', type: 'select', options: STATUS_OPTIONS },
{ key: 'range', label: '创建时间', type: 'range' },
{ key: 'owner', label: '负责人', type: 'input' },
{ key: 'priority', label: '优先级', type: 'select', options: PRIORITY_OPTIONS },
{ key: 'source', label: '来源', type: 'select', options: SOURCE_OPTIONS },
{ key: 'enabled', label: '启用', type: 'select', options: ENABLED_OPTIONS },
{ key: 'minScore', label: '最低评分', type: 'input-number' },
]
/* 每行固定 4 个字段格;字段更多时低频字段另起整行(展开/收起) */
const FIELDS_PER_ROW = 4
const searchExpanded = ref(false)
const fieldsCollapsible = computed(() => searchFields.length > FIELDS_PER_ROW)
const firstRowFields = computed(() => searchFields.slice(0, FIELDS_PER_ROW))
const moreRowFields = computed(() => searchFields.slice(FIELDS_PER_ROW))
const moreCount = computed(() => moreRowFields.value.length)
function toggleSearchExpanded() {
searchExpanded.value = !searchExpanded.value
if (!searchExpanded.value) {
// 收起时清空被隐藏的低频字段,保证「看到的条件 = 生效的条件」
for (const field of moreRowFields.value) searchForm.value[field.key] = null
}
}
const moreItems = [
{ key: 'export', label: '导出数据', icon: Download },
{ key: 'import', label: '导入数据', icon: Upload },
@@ -120,35 +158,94 @@ const moreItems = [
{ key: 'batch-delete', label: '批量删除', icon: Trash2, danger: true },
]
// TODO: 接入真实搜索 / 高级搜索 / 更多操作 / 新增逻辑
// TODO: 接入真实搜索 / 更多操作 / 新增逻辑
function onMoreAction() {}
function onAdvancedSearch() {}
function onAdvancedReset() {
advancedForm.value = { name: '', type: undefined, status: undefined, range: null }
function onSearch() {}
function onReset() {
searchForm.value = {
name: '',
type: undefined,
status: undefined,
range: null,
owner: undefined,
priority: undefined,
source: undefined,
enabled: undefined,
minScore: null,
}
}
</script>
<template>
<div class="dashboard-page">
<section class="demo-card">
<!-- 工具栏:搜索 + 高级搜索 | 更多操作 + 新增 -->
<div class="demo-toolbar">
<div class="toolbar-left">
<!-- 搜索卡(独立白卡):每行固定 4 个字段格;字段更多时低频字段另起整行(展开/收起),
操作区固定末行。「看到的条件 = 生效的条件」,收起时清空隐藏字段 -->
<form class="search-card" @submit.prevent="onSearch">
<div class="search-row">
<div v-for="field in firstRowFields" :key="field.key" class="search-field">
<label class="search-label">{{ field.label }}</label>
<Input
v-model="keyword"
class="toolbar-search"
placeholder="搜索名称 / ID"
v-if="field.type === 'input' || field.type === 'input-number'"
v-model="searchForm[field.key]"
:input-type="field.type === 'input-number' ? 'number' : 'text'"
:placeholder="field.placeholder"
allow-clear
>
<template #prefix><Search :size="14" aria-hidden="true" /></template>
<template v-if="field.submitIcon" #suffix>
<button type="submit" class="search-submit" aria-label="查询">
<Search :size="14" aria-hidden="true" />
</button>
</template>
</Input>
<Button
type="outline"
:class="{ 'toolbar-toggle-active': advancedOpen }"
@click="advancedOpen = !advancedOpen"
>
<SlidersHorizontal :size="14" aria-hidden="true" />
高级搜索
<Select
v-else-if="field.type === 'select'"
v-model="searchForm[field.key]"
:options="field.options"
clearable
/>
<RangePicker v-else-if="field.type === 'range'" v-model="searchForm[field.key]" />
</div>
</div>
<!-- 低频字段行:字段总数超过每行格数时按开关显隐 -->
<div v-if="fieldsCollapsible && searchExpanded" class="search-row">
<div v-for="field in moreRowFields" :key="field.key" class="search-field">
<label class="search-label">{{ field.label }}</label>
<Input
v-if="field.type === 'input' || field.type === 'input-number'"
v-model="searchForm[field.key]"
:input-type="field.type === 'input-number' ? 'number' : 'text'"
:placeholder="field.placeholder"
allow-clear
/>
<Select
v-else-if="field.type === 'select'"
v-model="searchForm[field.key]"
:options="field.options"
clearable
/>
<RangePicker v-else-if="field.type === 'range'" v-model="searchForm[field.key]" />
</div>
</div>
<div class="search-actions">
<Button type="primary" @click="onSearch">查询</Button>
<Button type="secondary" @click="onReset">重置</Button>
<Button v-if="fieldsCollapsible" type="text" @click="toggleSearchExpanded">
{{ searchExpanded ? '收起' : `展开 ${moreCount} 项` }}
<component :is="searchExpanded ? ChevronUp : ChevronDown" :size="14" aria-hidden="true" />
</Button>
</div>
</form>
<!-- 列表卡:操作工具栏 + 表格 + 分页 -->
<section class="demo-card">
<!-- 操作工具栏:主操作靠左,批量/更多操作靠右 -->
<div class="demo-toolbar">
<div class="toolbar-left">
<Button type="primary">
<Plus :size="14" aria-hidden="true" />
新增
</Button>
</div>
@@ -161,38 +258,6 @@ function onAdvancedReset() {
</Button>
</template>
</Dropdown>
<Button type="primary">
<Plus :size="14" aria-hidden="true" />
新增
</Button>
<Button type="primary">
<Plus :size="14" aria-hidden="true" />
Add New
</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>
@@ -220,7 +285,7 @@ function onAdvancedReset() {
color: var(--fms-text);
}
/* ---- demo 卡片:工具栏 + 高级搜索 + 表格 + 分页(灰底画布上的白卡,靠阴影分层,无边框线) ---- */
/* ---- 列表卡:操作工具栏 + 表格 + 分页(灰底画布上的白卡,靠阴影分层,无边框线) ---- */
.demo-card {
display: flex;
flex-direction: column;
@@ -232,80 +297,102 @@ function onAdvancedReset() {
box-shadow: var(--fms-shadow);
}
/* ---- 工具栏:左侧搜索 + 高级搜索,右侧更多操作 + 新增 ---- */
/* ---- 操作工具栏:主操作靠左,更多操作靠右(白底,靠 padding 与上下分区) ---- */
.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-left,
.toolbar-right {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.toolbar-search {
width: 240px;
}
/* 高级搜索按钮:展开面板时高亮 */
.toolbar-toggle-active {
color: var(--fms-primary);
background: color-mix(in srgb, var(--fms-primary) 10%, var(--fms-card));
}
/* ---- 高级搜索面板: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 {
/* ---- 搜索卡(独立白卡):行堆叠,每行固定 4 个等宽字段格。
* 格内 label 行内居左、控件占满剩余宽度。
* 回车提交:表单 submit + 首字段尾部的内嵌查询按钮。 ---- */
.search-card {
display: flex;
flex-direction: column;
gap: 6px;
gap: 10px;
padding: 12px;
border-radius: 8px;
background: var(--fms-card);
box-shadow: var(--fms-shadow);
}
.search-row {
display: flex;
flex-wrap: wrap;
align-items: center;
column-gap: 16px;
row-gap: 10px;
}
.search-field {
display: flex;
flex-direction: column;
gap: 4px;
/* 控件自适应内容宽度,不撑满整行 */
flex: 0 1 auto;
min-width: 0;
}
.advanced-label {
.search-label {
font-size: 12px;
color: var(--fms-text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
/* 多语言下 label 可能很长:不强制单行,允许换行,防溢出 */
white-space: normal;
word-break: break-word;
line-height: 1.4;
}
/* 控件统一铺满所在网格单元(Input / Select / RangePicker 根节点) */
.advanced-field :deep(.input-wrapper),
.advanced-field :deep(.select),
.advanced-field :deep(.fms-range-picker) {
width: 100%;
min-width: 0;
/* 控件按内容自适应宽度(Input / Select / RangePicker 根节点),带保底宽度 */
.search-field :deep(.input-wrapper),
.search-field :deep(.select),
.search-field :deep(.fms-range-picker) {
width: auto;
min-width: 160px;
max-width: 260px;
}
.advanced-actions {
grid-column: 1 / -1;
/* 内嵌在首字段尾部的小查询按钮(原生 button,避免触发表单内嵌套问题) */
.search-submit {
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
background: none;
padding: 2px;
color: var(--fms-text-secondary);
cursor: pointer;
}
.search-submit:hover {
color: var(--fms-primary);
}
.search-actions {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 8px;
/* 右对齐:按钮落在表单末行右下角,与字段的阅读终点衔接 */
margin-left: auto;
}
.demo-table {
flex: 1;
min-height: 0;
/* 表头文字对齐 12px 轴线:4px 容器 padding + 表格单元格自带 8px 水平 padding */
padding: 0 4px;
/* 行级特殊样式:rowClassName 返回的 class 落在 stk-table 的 tr 上,
这里通过 :deep() 跨过组件边界设置字体颜色与背景色 */
@@ -334,7 +421,8 @@ function onAdvancedReset() {
}
}
/* ---- 底部分页条:总数靠左,分页靠右 ---- */
/* ---- 底部分页条:白底,靠 padding 与表格分区(列表卡整体纯白);
* 总数靠左,分页靠右 ---- */
.demo-pagination {
display: flex;
align-items: center;