feat: config management page

参数配置页:纯键值 CRUD,复用 ty-crud + JSON Schema 零新组件。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
liuyun 2026-10-11 10:06:47 +08:00
parent 1df44fd57e
commit 16b512a3f1
3 changed files with 126 additions and 1 deletions

View File

@ -0,0 +1,112 @@
<template>
<ty-crud
entity-name="配置"
:columns="columns"
:search-schema="searchSchema"
:form-schema="formSchema"
:list="list"
:create="create"
:update="update"
:remove="remove"
id-key="id"
name-key="key"
/>
</template>
<script setup lang="ts">
import type { JsonSchema, PageQuery, PageResult, TableColumn } from '@/types/crud'
const columns: TableColumn[] = [
{ prop: 'key', label: '键名', minWidth: 160 },
{ prop: 'value', label: '值', minWidth: 220 },
{ prop: 'remark', label: '备注', minWidth: 160 },
{ prop: 'createdAt', label: '创建时间', width: 160, type: 'datetime' },
]
const searchSchema: JsonSchema = {
type: 'object',
properties: {
key: { type: 'string', title: '键名', placeholder: '请输入键名' },
value: { type: 'string', title: '值', placeholder: '请输入值' },
},
}
const formSchema: JsonSchema = {
type: 'object',
properties: {
key: { type: 'string', title: '键名', placeholder: '请输入键名', minLength: 1 },
value: { type: 'string', title: '值', format: 'textarea', placeholder: '请输入值' },
remark: { type: 'string', title: '备注', placeholder: '请输入备注' },
},
required: ['key'],
}
// ---- 占位数据(后续对接后端时替换为 http 调用)----
interface ConfigRow {
id: string
key: string
value: string
remark: string
createdAt: number
}
const now = Date.now()
const day = 24 * 60 * 60 * 1000
const configs: ConfigRow[] = [
{ id: '1', key: 'sys.name', value: '投云系统管理平台', remark: '系统名称', createdAt: now - 30 * day },
{ id: '2', key: 'sys.theme', value: '#2E7FA3', remark: '默认主题色', createdAt: now - 20 * day },
{ id: '3', key: 'sys.copyright', value: '云南投云科技有限公司', remark: '版权信息', createdAt: now - 10 * day },
{ id: '4', key: 'sys.pageSize', value: '10', remark: '默认分页大小', createdAt: now - 5 * day },
{ id: '5', key: 'sys.captcha', value: 'true', remark: '是否开启验证码', createdAt: now - 1 * day },
]
let seq = 0
const nextId = () => `c-${Date.now()}-${++seq}`
function delay<T>(fn: () => T, ms = 300): Promise<T> {
return new Promise((resolve) => setTimeout(() => resolve(fn()), ms))
}
const list = (query: PageQuery): Promise<PageResult<Record<string, any>>> =>
delay(() => {
const { page, pageSize, key, value } = query
const filtered = configs.filter((c) => {
if (key && !c.key.includes(String(key))) return false
if (value && !c.value.includes(String(value))) return false
return true
})
const start = (page - 1) * pageSize
return {
list: filtered.slice(start, start + pageSize) as unknown as Record<string, any>[],
total: filtered.length,
page,
pageSize,
}
})
const create = (data: Record<string, any>): Promise<unknown> =>
delay(() => {
configs.unshift({
id: nextId(),
key: data.key,
value: data.value ?? '',
remark: data.remark ?? '',
createdAt: Date.now(),
})
return null
})
const update = (id: string, data: Record<string, any>): Promise<unknown> =>
delay(() => {
const c = configs.find((x) => x.id === id)
if (c) Object.assign(c, data)
return null
})
const remove = (id: string): Promise<unknown> =>
delay(() => {
const i = configs.findIndex((x) => x.id === id)
if (i >= 0) configs.splice(i, 1)
return null
})
</script>

File diff suppressed because one or more lines are too long

13
typed-router.d.ts vendored
View File

@ -44,6 +44,13 @@ declare module 'vue-router/auto-routes' {
Record<never, never>, Record<never, never>,
| never | never
>, >,
'/system/config/': RouteRecordInfo<
'/system/config/',
'/system/config',
Record<never, never>,
Record<never, never>,
| never
>,
'/system/user/': RouteRecordInfo< '/system/user/': RouteRecordInfo<
'/system/user/', '/system/user/',
'/system/user', '/system/user',
@ -82,6 +89,12 @@ declare module 'vue-router/auto-routes' {
views: views:
| never | never
} }
'src/pages/system/config/index.vue': {
routes:
| '/system/config/'
views:
| never
}
'src/pages/system/user/index.vue': { 'src/pages/system/user/index.vue': {
routes: routes:
| '/system/user/' | '/system/user/'