feat: add interactive component examples
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
|
||||
- 后端:Go 1.23.0、Wails v3.0.0-alpha.9、SQLite
|
||||
- 前端:Vue 3、TypeScript、Vite、Tailwind CSS、Ant Design Vue
|
||||
- 当前功能:多语言、主题切换、统一设置 schema、主窗口状态恢复、本地配置、快捷键、本地登录 provider、商品图片 AI 优化页面、本地模拟图片优化任务、审计日志、运行日志、P0 数据统计服务、OCR 示例
|
||||
- 当前功能:多语言、主题切换、统一设置 schema、主窗口状态恢复、本地配置、快捷键、本地登录 provider、商品图片 AI 优化页面、本地模拟图片优化任务、审计日志、运行日志、P0 数据统计服务、常用组件示例页、OCR 示例
|
||||
- 目标模板模块:登录、设置、主业务、审计日志、运行日志、数据统计、常用组件示例
|
||||
|
||||
## 环境准备
|
||||
|
||||
@@ -66,7 +66,7 @@ frontend/src
|
||||
- `frontend/src/modules/image-optimizer/`:放已接入的商品图片 AI 优化样例页面。
|
||||
- `frontend/src/modules/logs/`:目标目录,放审计日志和运行日志页面。
|
||||
- `frontend/src/modules/statistics/`:放统计卡片和趋势图。
|
||||
- `frontend/src/modules/components-demo/`:目标目录,放常用组件示例。
|
||||
- `frontend/src/modules/components-demo/`:放已接入的常用组件示例页。
|
||||
- `frontend/src/components/template/`:放当前模块占位页共享壳。
|
||||
- `frontend/src/components/`:通用展示组件和组合控件。
|
||||
- `frontend/src/utils/`:国际化、主题、OS、热键等工具函数。
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@
|
||||
|
||||
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| T-030 | 建立常用组件示例页 | T-012 | 输入框、多选框、下拉框、多行文本、表格、筛选、分页、状态标签有可运行示例 | TODO |
|
||||
| T-030 | 建立常用组件示例页 | T-012 | 输入框、多选框、下拉框、多行文本、表格、筛选、分页、状态标签有可运行示例 | DONE |
|
||||
| T-031 | 文档化 Wails bindings 生成流程 | T-012 | README 或技术栈文档说明 `wails3 generate bindings` 的使用、失败处理和版本要求 | TODO |
|
||||
| T-032 | 补充 Windows 打包验证文档 | T-031 | `wails3 package` 前置条件、输出目录和常见失败处理写入 README 或 docs | TODO |
|
||||
| T-033 | 完成一次模板 smoke 清单 | T-023 | 登录、设置、业务样例、日志、统计、组件示例、托盘、热键都按清单验证并记录结果 | TODO |
|
||||
|
||||
+10
-2
@@ -6,7 +6,7 @@
|
||||
|
||||
项目处于可复用桌面应用模板的 Phase 2 业务样例与日志统计收尾阶段。目标已经从迁移后的基础工程调整为“Windows-first、可扩展到其他平台的桌面端应用模板”。
|
||||
|
||||
当前代码已从迁移后的 Wails/Vue/SQLite 基线推进到主应用路由骨架、登录 provider 边界、统一设置 schema、主窗口状态恢复、商品图片 AI 优化页面、日志服务边界和 P0 数据统计阶段,已具备设置、多语言、主题、本地配置、热键、托盘、OCR 示例、模板级模块入口、本地登录能力、本地模拟图片优化任务能力、业务样例页面、审计日志、运行日志和 P0 统计查询能力。统计页面仍是骨架,尚未展示真实统计数据。
|
||||
当前代码已从迁移后的 Wails/Vue/SQLite 基线推进到主应用路由骨架、登录 provider 边界、统一设置 schema、主窗口状态恢复、商品图片 AI 优化页面、日志服务边界、P0 数据统计和常用组件示例阶段,已具备设置、多语言、主题、本地配置、热键、托盘、OCR 示例、模板级模块入口、本地登录能力、本地模拟图片优化任务能力、业务样例页面、组件示例页、审计日志、运行日志和 P0 统计查询能力。统计页面仍是骨架,尚未展示真实统计数据。
|
||||
|
||||
## 已确认事实
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
- 已实现 AuditLogService:`Record`、`List`,登录、设置变更和图片任务创建会写入 `audit_logs`。
|
||||
- 已实现 AppLogService:`Record`、`List`,图片任务创建失败和服务内部错误会写入 `app_logs`。
|
||||
- 已实现 MetricsService:`GetDashboardMetrics`、`GetProcessingTrend`,可查询今日处理量、成功率、平均耗时、失败数、今日审计日志数和最近 N 天趋势。
|
||||
- 已实现常用组件示例页:输入框、多选框、下拉框、多行文本、表格、筛选、分页和状态标签都有可运行示例。
|
||||
|
||||
## 当前命令
|
||||
|
||||
@@ -203,9 +204,16 @@ wails3 build
|
||||
- WebView 文件选择器不返回绝对路径时,页面会提示用户粘贴绝对路径。
|
||||
- `go test ./...`、`npm run build`、`go build -o bin\cmbone.exe .` 和 `cmd /c build.bat` 通过。
|
||||
|
||||
同日完成常用组件示例页:
|
||||
|
||||
- `frontend/src/modules/components-demo/ComponentsDemoPage.vue` 从静态骨架改为可交互示例。
|
||||
- 表单区支持输入框、多选框、下拉框和多行文本,并实时展示当前值。
|
||||
- 表格区支持关键字、分类、状态筛选,展示分页和状态标签。
|
||||
- `go test ./...`、`npm run build`、`go build -o bin\cmbone.exe .` 和 `cmd /c build.bat` 通过。
|
||||
|
||||
## 下一步
|
||||
|
||||
从 [`06-tasks.md`](06-tasks.md) 领取第一个 `TODO` 任务。目前建议先做 `T-030`:建立常用组件示例页。
|
||||
从 [`06-tasks.md`](06-tasks.md) 领取第一个 `TODO` 任务。目前建议先做 `T-031`:文档化 Wails bindings 生成流程。
|
||||
|
||||
## 当前 blocker
|
||||
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@
|
||||
| `frontend/src/modules/logs/LogsPage.vue` | 日志页骨架 | 审计日志和运行日志入口 |
|
||||
| `frontend/src/modules/statistics/StatisticsPage.vue` | 统计页骨架 | P0 指标和 7 天趋势入口 |
|
||||
| `frontend/src/modules/settings/SettingsPage.vue` | 设置页 | 复用存量设置组件,并展示后端统一设置 schema |
|
||||
| `frontend/src/modules/components-demo/ComponentsDemoPage.vue` | 组件示例骨架 | 常用表单、筛选、表格和状态标签 |
|
||||
| `frontend/src/modules/components-demo/ComponentsDemoPage.vue` | 组件示例页 | 输入框、多选框、下拉框、多行文本、表格、筛选、分页和状态标签 |
|
||||
| `frontend/src/pages/Home/Index.vue` | 第二窗口入口 | OCR 示例页面 |
|
||||
| `frontend/src/utils/ThemeManager.ts` | 主题工具 | 主题切换和持久化协作 |
|
||||
| `frontend/src/utils/i18n.ts` | 国际化工具 | 语言资源和切换 |
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@
|
||||
| `#/logs` | 操作日志 | 后端审计日志和运行日志服务已接入;页面列表待后续扩展 |
|
||||
| `#/statistics` | 数据统计 | 后端 `MetricsService` 已接入趋势、成功率、耗时和失败任务;页面展示待后续扩展 |
|
||||
| `#/settings` | 设置中心 | 已接入统一设置 schema,窗口配置已用于主窗口尺寸恢复 |
|
||||
| `#/components` | 组件示例 | `T-030` 扩充输入框、多选框、下拉框、多行文本、表格、筛选、分页 |
|
||||
| `#/components` | 组件示例 | 已接入输入框、多选框、下拉框、多行文本、表格、筛选、分页和状态标签 |
|
||||
|
||||
## 主窗口布局
|
||||
|
||||
|
||||
@@ -1,21 +1,141 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import {
|
||||
LeftOutlined,
|
||||
ReloadOutlined,
|
||||
RightOutlined,
|
||||
SearchOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
import ModulePageShell from '../../components/template/ModulePageShell.vue'
|
||||
|
||||
const checked = ref(true)
|
||||
type ComponentStatus = 'Ready' | 'Draft' | 'Blocked'
|
||||
|
||||
type ComponentRow = {
|
||||
id: number
|
||||
name: string
|
||||
category: string
|
||||
owner: string
|
||||
status: ComponentStatus
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
const productName = ref('商品主图优化')
|
||||
const selectedChannels = ref(['desktop', 'audit'])
|
||||
const category = ref('image')
|
||||
const rows = [
|
||||
{ name: '输入框', category: 'Form', status: 'Ready' },
|
||||
{ name: '多选框', category: 'Form', status: 'Ready' },
|
||||
{ name: '表格', category: 'Data', status: 'Ready' },
|
||||
const note = ref('用于记录任务备注、失败原因或人工审核意见。')
|
||||
const keyword = ref('')
|
||||
const categoryFilter = ref('')
|
||||
const statusFilter = ref('')
|
||||
const currentPage = ref(1)
|
||||
const pageSize = 5
|
||||
|
||||
const channelOptions = [
|
||||
{ value: 'desktop', label: '桌面端' },
|
||||
{ value: 'audit', label: '审计日志' },
|
||||
{ value: 'statistics', label: '数据统计' },
|
||||
]
|
||||
|
||||
const summary = [
|
||||
{ label: '表单控件', value: '4', tone: 'blue' as const },
|
||||
{ label: '表格', value: '1', tone: 'green' as const },
|
||||
{ label: '筛选', value: '1', tone: 'amber' as const },
|
||||
{ label: '状态标签', value: '3', tone: 'violet' as const },
|
||||
const categoryOptions = [
|
||||
{ value: 'image', label: '图片任务' },
|
||||
{ value: 'logs', label: '日志查询' },
|
||||
{ value: 'settings', label: '设置项' },
|
||||
]
|
||||
|
||||
const rows: ComponentRow[] = [
|
||||
{ id: 1, name: '文本输入', category: 'Form', owner: 'Settings', status: 'Ready', updatedAt: '2026-07-09' },
|
||||
{ id: 2, name: '多选框组', category: 'Form', owner: 'Business', status: 'Ready', updatedAt: '2026-07-09' },
|
||||
{ id: 3, name: '下拉选择', category: 'Form', owner: 'Settings', status: 'Ready', updatedAt: '2026-07-09' },
|
||||
{ id: 4, name: '多行文本', category: 'Form', owner: 'Logs', status: 'Ready', updatedAt: '2026-07-09' },
|
||||
{ id: 5, name: '任务表格', category: 'Data', owner: 'Business', status: 'Ready', updatedAt: '2026-07-09' },
|
||||
{ id: 6, name: '筛选栏', category: 'Data', owner: 'Logs', status: 'Ready', updatedAt: '2026-07-09' },
|
||||
{ id: 7, name: '分页控件', category: 'Data', owner: 'Dashboard', status: 'Ready', updatedAt: '2026-07-09' },
|
||||
{ id: 8, name: '成功状态标签', category: 'Status', owner: 'Business', status: 'Ready', updatedAt: '2026-07-09' },
|
||||
{ id: 9, name: '草稿状态标签', category: 'Status', owner: 'Settings', status: 'Draft', updatedAt: '2026-07-09' },
|
||||
{ id: 10, name: '阻塞状态标签', category: 'Status', owner: 'Logs', status: 'Blocked', updatedAt: '2026-07-09' },
|
||||
{ id: 11, name: '只读字段', category: 'Form', owner: 'Settings', status: 'Draft', updatedAt: '2026-07-09' },
|
||||
{ id: 12, name: '表格空状态', category: 'Data', owner: 'Dashboard', status: 'Blocked', updatedAt: '2026-07-09' },
|
||||
]
|
||||
|
||||
const summary = computed(() => [
|
||||
{ label: '表单控件', value: String(rows.filter((row) => row.category === 'Form').length), tone: 'blue' as const },
|
||||
{ label: '表格行数', value: String(filteredRows.value.length), tone: 'green' as const },
|
||||
{ label: '当前页', value: `${visiblePage.value}/${totalPages.value}`, tone: 'amber' as const },
|
||||
{ label: '状态标签', value: String(rows.filter((row) => row.category === 'Status').length), tone: 'violet' as const },
|
||||
])
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
const term = keyword.value.trim().toLowerCase()
|
||||
return rows.filter((row) => {
|
||||
const matchesKeyword = !term
|
||||
|| row.name.toLowerCase().includes(term)
|
||||
|| row.owner.toLowerCase().includes(term)
|
||||
|| row.category.toLowerCase().includes(term)
|
||||
const matchesCategory = !categoryFilter.value || row.category === categoryFilter.value
|
||||
const matchesStatus = !statusFilter.value || row.status === statusFilter.value
|
||||
return matchesKeyword && matchesCategory && matchesStatus
|
||||
})
|
||||
})
|
||||
|
||||
const totalPages = computed(() => Math.max(1, Math.ceil(filteredRows.value.length / pageSize)))
|
||||
const visiblePage = computed(() => Math.min(currentPage.value, totalPages.value))
|
||||
|
||||
const pagedRows = computed(() => {
|
||||
const start = (visiblePage.value - 1) * pageSize
|
||||
return filteredRows.value.slice(start, start + pageSize)
|
||||
})
|
||||
|
||||
const formPreview = computed(() => {
|
||||
const channelLabels = channelOptions
|
||||
.filter((option) => selectedChannels.value.includes(option.value))
|
||||
.map((option) => option.label)
|
||||
.join('、')
|
||||
const selectedCategory = categoryOptions.find((option) => option.value === category.value)?.label || '-'
|
||||
return [
|
||||
{ label: '名称', value: productName.value || '-' },
|
||||
{ label: '模块', value: selectedCategory },
|
||||
{ label: '场景', value: channelLabels || '-' },
|
||||
{ label: '备注', value: note.value || '-' },
|
||||
]
|
||||
})
|
||||
|
||||
function applyFilters() {
|
||||
currentPage.value = 1
|
||||
}
|
||||
|
||||
function resetFilters() {
|
||||
keyword.value = ''
|
||||
categoryFilter.value = ''
|
||||
statusFilter.value = ''
|
||||
currentPage.value = 1
|
||||
}
|
||||
|
||||
function previousPage() {
|
||||
currentPage.value = Math.max(1, currentPage.value - 1)
|
||||
}
|
||||
|
||||
function nextPage() {
|
||||
currentPage.value = Math.min(totalPages.value, currentPage.value + 1)
|
||||
}
|
||||
|
||||
function statusClass(status: ComponentStatus) {
|
||||
if (status === 'Ready') {
|
||||
return 'border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-900/60 dark:bg-emerald-950/40 dark:text-emerald-200'
|
||||
}
|
||||
if (status === 'Blocked') {
|
||||
return 'border-rose-200 bg-rose-50 text-rose-700 dark:border-rose-900/60 dark:bg-rose-950/40 dark:text-rose-200'
|
||||
}
|
||||
return 'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-900/60 dark:bg-amber-950/40 dark:text-amber-200'
|
||||
}
|
||||
|
||||
function statusLabel(status: ComponentStatus) {
|
||||
if (status === 'Ready') {
|
||||
return '可用'
|
||||
}
|
||||
if (status === 'Blocked') {
|
||||
return '阻塞'
|
||||
}
|
||||
return '草稿'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -23,72 +143,200 @@ const summary = [
|
||||
eyebrow="Components"
|
||||
title="组件示例"
|
||||
description="常用业务控件的运行示例,后续按真实模块沉淀组合组件。"
|
||||
status="路由可访问"
|
||||
status="示例可运行"
|
||||
:items="summary"
|
||||
>
|
||||
<div class="grid grid-cols-1 gap-4 xl:grid-cols-[360px_1fr]">
|
||||
<section class="rounded-lg border border-slate-200 bg-white p-4 dark:border-slate-800 dark:bg-slate-900">
|
||||
<h2 class="mb-4 text-base font-semibold">
|
||||
表单
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
<label class="block">
|
||||
<span class="mb-1 block text-sm font-medium">输入框</span>
|
||||
<input class="h-10 w-full rounded-md border border-slate-300 bg-white px-3 text-sm dark:border-slate-700 dark:bg-slate-950" value="商品主图" />
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-sm">
|
||||
<input v-model="checked" class="h-4 w-4 rounded border-slate-300" type="checkbox" />
|
||||
<span>多选框</span>
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="mb-1 block text-sm font-medium">下拉框</span>
|
||||
<select v-model="category" class="h-10 w-full rounded-md border border-slate-300 bg-white px-3 text-sm dark:border-slate-700 dark:bg-slate-950">
|
||||
<option value="image">图片任务</option>
|
||||
<option value="logs">日志查询</option>
|
||||
<option value="settings">设置项</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="mb-1 block text-sm font-medium">多行文本</span>
|
||||
<textarea class="min-h-[96px] w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-950">用于记录任务备注。</textarea>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
<div class="grid grid-cols-1 gap-4 xl:grid-cols-[360px_minmax(0,1fr)]">
|
||||
<aside class="space-y-4">
|
||||
<section class="rounded-lg border border-slate-200 bg-white p-4 dark:border-slate-800 dark:bg-slate-900">
|
||||
<h2 class="mb-4 text-base font-semibold text-slate-950 dark:text-white">
|
||||
表单控件
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
<label class="block">
|
||||
<span class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-200">输入框</span>
|
||||
<input
|
||||
v-model="productName"
|
||||
class="h-10 w-full rounded-md border border-slate-300 bg-white px-3 text-sm dark:border-slate-700 dark:bg-slate-950"
|
||||
>
|
||||
</label>
|
||||
|
||||
<section class="rounded-lg border border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900">
|
||||
<div class="grid grid-cols-1 gap-3 border-b border-slate-200 p-4 dark:border-slate-800 md:grid-cols-[1fr_180px_auto]">
|
||||
<input class="h-10 rounded-md border border-slate-300 bg-white px-3 text-sm dark:border-slate-700 dark:bg-slate-950" placeholder="筛选关键字" />
|
||||
<select class="h-10 rounded-md border border-slate-300 bg-white px-3 text-sm dark:border-slate-700 dark:bg-slate-950">
|
||||
<option>全部分类</option>
|
||||
<option>Form</option>
|
||||
<option>Data</option>
|
||||
<fieldset class="space-y-2">
|
||||
<legend class="text-sm font-medium text-slate-700 dark:text-slate-200">
|
||||
多选框
|
||||
</legend>
|
||||
<label
|
||||
v-for="option in channelOptions"
|
||||
:key="option.value"
|
||||
class="flex min-h-[28px] items-center gap-2 text-sm text-slate-700 dark:text-slate-200"
|
||||
>
|
||||
<input
|
||||
v-model="selectedChannels"
|
||||
class="h-4 w-4 rounded border-slate-300"
|
||||
type="checkbox"
|
||||
:value="option.value"
|
||||
>
|
||||
<span>{{ option.label }}</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<label class="block">
|
||||
<span class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-200">下拉框</span>
|
||||
<select
|
||||
v-model="category"
|
||||
class="h-10 w-full rounded-md border border-slate-300 bg-white px-3 text-sm dark:border-slate-700 dark:bg-slate-950"
|
||||
>
|
||||
<option v-for="option in categoryOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="block">
|
||||
<span class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-200">多行文本</span>
|
||||
<textarea
|
||||
v-model="note"
|
||||
class="min-h-[108px] w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-950"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="rounded-lg border border-slate-200 bg-white p-4 dark:border-slate-800 dark:bg-slate-900">
|
||||
<h2 class="mb-4 text-base font-semibold text-slate-950 dark:text-white">
|
||||
当前值
|
||||
</h2>
|
||||
<dl class="space-y-3 text-sm">
|
||||
<div v-for="item in formPreview" :key="item.label">
|
||||
<dt class="text-xs font-medium text-slate-500 dark:text-slate-400">
|
||||
{{ item.label }}
|
||||
</dt>
|
||||
<dd class="mt-1 break-all text-slate-900 dark:text-white">
|
||||
{{ item.value }}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
</aside>
|
||||
|
||||
<section class="min-w-0 rounded-lg border border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900">
|
||||
<div class="grid grid-cols-1 gap-3 border-b border-slate-200 p-4 dark:border-slate-800 lg:grid-cols-[minmax(180px,1fr)_160px_160px_auto_auto]">
|
||||
<input
|
||||
v-model="keyword"
|
||||
class="h-10 rounded-md border border-slate-300 bg-white px-3 text-sm dark:border-slate-700 dark:bg-slate-950"
|
||||
placeholder="筛选关键字"
|
||||
@keyup.enter="applyFilters"
|
||||
>
|
||||
<select
|
||||
v-model="categoryFilter"
|
||||
class="h-10 rounded-md border border-slate-300 bg-white px-3 text-sm dark:border-slate-700 dark:bg-slate-950"
|
||||
@change="applyFilters"
|
||||
>
|
||||
<option value="">全部分类</option>
|
||||
<option value="Form">Form</option>
|
||||
<option value="Data">Data</option>
|
||||
<option value="Status">Status</option>
|
||||
</select>
|
||||
<button class="h-10 rounded-md bg-slate-950 px-4 text-sm font-medium text-white dark:bg-white dark:text-slate-950" type="button">
|
||||
筛选
|
||||
<select
|
||||
v-model="statusFilter"
|
||||
class="h-10 rounded-md border border-slate-300 bg-white px-3 text-sm dark:border-slate-700 dark:bg-slate-950"
|
||||
@change="applyFilters"
|
||||
>
|
||||
<option value="">全部状态</option>
|
||||
<option value="Ready">可用</option>
|
||||
<option value="Draft">草稿</option>
|
||||
<option value="Blocked">阻塞</option>
|
||||
</select>
|
||||
<button
|
||||
class="inline-flex h-10 items-center justify-center gap-2 rounded-md bg-slate-950 px-4 text-sm font-medium text-white dark:bg-white dark:text-slate-950"
|
||||
type="button"
|
||||
@click="applyFilters"
|
||||
>
|
||||
<SearchOutlined />
|
||||
<span>筛选</span>
|
||||
</button>
|
||||
<button
|
||||
class="inline-flex h-10 items-center justify-center gap-2 rounded-md border border-slate-300 bg-white px-4 text-sm font-medium text-slate-700 hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-950 dark:text-slate-200 dark:hover:bg-slate-900"
|
||||
type="button"
|
||||
@click="resetFilters"
|
||||
>
|
||||
<ReloadOutlined />
|
||||
<span>重置</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full text-left text-sm">
|
||||
<thead class="bg-slate-50 text-xs uppercase text-slate-500 dark:bg-slate-950 dark:text-slate-400">
|
||||
<tr>
|
||||
<th class="px-4 py-3 font-medium">名称</th>
|
||||
<th class="px-4 py-3 font-medium">分类</th>
|
||||
<th class="px-4 py-3 font-medium">状态</th>
|
||||
<th class="w-[90px] px-4 py-3 font-medium">编号</th>
|
||||
<th class="min-w-[180px] px-4 py-3 font-medium">名称</th>
|
||||
<th class="w-[130px] px-4 py-3 font-medium">分类</th>
|
||||
<th class="w-[140px] px-4 py-3 font-medium">归属</th>
|
||||
<th class="w-[120px] px-4 py-3 font-medium">状态</th>
|
||||
<th class="w-[140px] px-4 py-3 font-medium">更新时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-200 dark:divide-slate-800">
|
||||
<tr v-for="row in rows" :key="row.name">
|
||||
<td class="px-4 py-3">{{ row.name }}</td>
|
||||
<td class="px-4 py-3">{{ row.category }}</td>
|
||||
<tr v-if="pagedRows.length === 0">
|
||||
<td class="px-4 py-8 text-center text-slate-500 dark:text-slate-400" colspan="6">
|
||||
暂无数据
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-for="row in pagedRows" :key="row.id" class="hover:bg-slate-50 dark:hover:bg-slate-950">
|
||||
<td class="px-4 py-3 font-medium text-slate-900 dark:text-white">
|
||||
#{{ row.id }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-slate-900 dark:text-white">
|
||||
{{ row.name }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-slate-600 dark:text-slate-300">
|
||||
{{ row.category }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-slate-600 dark:text-slate-300">
|
||||
{{ row.owner }}
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<span class="rounded-md bg-emerald-50 px-2 py-1 text-xs font-medium text-emerald-700 dark:bg-emerald-950/40 dark:text-emerald-200">
|
||||
{{ row.status }}
|
||||
<span class="inline-flex rounded-md border px-2 py-1 text-xs font-medium" :class="statusClass(row.status)">
|
||||
{{ statusLabel(row.status) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-slate-500 dark:text-slate-400">
|
||||
{{ row.updatedAt }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 border-t border-slate-200 p-4 dark:border-slate-800 md:flex-row md:items-center md:justify-between">
|
||||
<div class="text-sm text-slate-500 dark:text-slate-400">
|
||||
{{ filteredRows.length }} 条结果
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="inline-flex h-9 w-9 items-center justify-center rounded-md border border-slate-300 text-slate-700 disabled:cursor-not-allowed disabled:opacity-40 dark:border-slate-700 dark:text-slate-200"
|
||||
type="button"
|
||||
:disabled="currentPage === 1"
|
||||
title="上一页"
|
||||
@click="previousPage"
|
||||
>
|
||||
<LeftOutlined />
|
||||
</button>
|
||||
<span class="min-w-[72px] text-center text-sm text-slate-700 dark:text-slate-200">
|
||||
{{ visiblePage }} / {{ totalPages }}
|
||||
</span>
|
||||
<button
|
||||
class="inline-flex h-9 w-9 items-center justify-center rounded-md border border-slate-300 text-slate-700 disabled:cursor-not-allowed disabled:opacity-40 dark:border-slate-700 dark:text-slate-200"
|
||||
type="button"
|
||||
:disabled="currentPage === totalPages"
|
||||
title="下一页"
|
||||
@click="nextPage"
|
||||
>
|
||||
<RightOutlined />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</ModulePageShell>
|
||||
|
||||
+17
@@ -324,3 +324,20 @@
|
||||
- 非阻塞警告:
|
||||
- `cmd /c build.bat` 仍提示 `wails3 build` 是 `wails3 task` 的 alias。
|
||||
- 前端构建仍提示 Browserslist 数据过期和 chunk 大小警告。
|
||||
|
||||
### 建立常用组件示例页
|
||||
|
||||
- 任务:完成 `T-030`,输入框、多选框、下拉框、多行文本、表格、筛选、分页、状态标签有可运行示例。
|
||||
- 结果:
|
||||
- `frontend/src/modules/components-demo/ComponentsDemoPage.vue` 从静态骨架改为可交互示例。
|
||||
- 表单区支持输入框、多选框、下拉框、多行文本,并实时展示当前值。
|
||||
- 表格区支持关键字、分类、状态筛选。
|
||||
- 表格底部支持分页,状态列支持可用、草稿、阻塞三类状态标签。
|
||||
- 验证:
|
||||
- `cd frontend && npm run build` 通过。
|
||||
- `go test ./...` 通过。
|
||||
- `go build -o bin\cmbone.exe .` 通过。
|
||||
- `cmd /c build.bat` 通过。
|
||||
- 非阻塞警告:
|
||||
- `cmd /c build.bat` 仍提示 `wails3 build` 是 `wails3 task` 的 alias。
|
||||
- 前端构建仍提示 Browserslist 数据过期和 chunk 大小警告。
|
||||
|
||||
Reference in New Issue
Block a user