20260903173038
This commit is contained in:
1 parent
c8940f6e03
commit
0faf44cffc
55 files changed
+3278
-208
No files matched your search
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user