feat(device-center): 帮助中心内容管理落地 + 适用范围通用/指定设备切换
- 新增设备中心 / 帮助中心 / 内容管理菜单与页面(列表 + 新增/编辑弹框 + 详情抽屉) - 适用范围改为 Radio:通用(默认) / 指定设备;指定设备时才出现设备分类多选 - 列表通用记录显示「通用」Tag,指定设备记录显示设备分类文字 - 详情抽屉按 scope 分支展示;mock 数据补充 scope 字段并演示两种形态 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
0e884b884a
commit
e28baced32
@@ -0,0 +1,220 @@
|
||||
import { registerMocks } from '@axios/mockBus'
|
||||
import type { ApiResponse, MockContext, RequestParameter } from '@axios'
|
||||
|
||||
/**
|
||||
* 帮助中心 - 内容管理 mock 数据
|
||||
* - 维护系统以及设备的操作说明、常见问题等帮助内容
|
||||
*/
|
||||
|
||||
type HelpCategory = '操作说明' | '常见问题' | '新手指引' | '系统版本' | '其他'
|
||||
|
||||
type ApplicableScope = 'general' | 'specified'
|
||||
|
||||
interface HelpContentRecord {
|
||||
id: string
|
||||
code: string
|
||||
category: HelpCategory
|
||||
title: string
|
||||
clientTag: string
|
||||
scope: ApplicableScope
|
||||
deviceTypes: string[]
|
||||
content: string
|
||||
createTime: string
|
||||
}
|
||||
|
||||
interface HelpContentParams {
|
||||
pageNum?: number
|
||||
pageSize?: number
|
||||
column?: string
|
||||
order?: 'ascend' | 'descend' | null
|
||||
keyword?: string
|
||||
category?: HelpCategory
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
const getParams = (params: RequestParameter): HelpContentParams => (params ?? {}) as HelpContentParams
|
||||
|
||||
const includes = (value: unknown, keyword: unknown): boolean => {
|
||||
if (!keyword) return true
|
||||
return String(value ?? '').toLowerCase().includes(String(keyword).toLowerCase())
|
||||
}
|
||||
|
||||
const paginate = <T extends HelpContentRecord>(list: T[], params: HelpContentParams): ApiResponse<T[]> => {
|
||||
const pageNum = Number(params.pageNum ?? 1)
|
||||
const pageSize = Number(params.pageSize ?? 30)
|
||||
const start = (pageNum - 1) * pageSize
|
||||
return { code: '00000', msg: '查询成功', data: list.slice(start, start + pageSize), total: list.length }
|
||||
}
|
||||
|
||||
const ok = <T = null>(msg: string, data: T = null as T): ApiResponse<T> => ({ code: '00000', msg, data })
|
||||
|
||||
const helpContentList: HelpContentRecord[] = [
|
||||
{
|
||||
id: 'hlp-001',
|
||||
code: 'HLP001',
|
||||
category: '操作说明',
|
||||
title: '智能货柜操作说明',
|
||||
clientTag: 'APP-001',
|
||||
scope: 'specified',
|
||||
deviceTypes: ['智能货柜', '档口机'],
|
||||
content: '智能货柜操作说明:\n1. 开机前检查电源连接是否正常;\n2. 按照屏幕提示完成初始化配置;\n3. 放置菜品后确认称重数据;\n4. 定期清理货柜内部卫生。',
|
||||
createTime: '2026-05-10 09:30:00',
|
||||
},
|
||||
{
|
||||
id: 'hlp-002',
|
||||
code: 'HLP002',
|
||||
category: '常见问题',
|
||||
title: '设备离线如何处理',
|
||||
clientTag: 'APP-003',
|
||||
scope: 'specified',
|
||||
deviceTypes: ['智能货柜'],
|
||||
content: '设备离线常见问题及处理方法:\n1. 检查设备网络连接是否正常;\n2. 确认路由器工作状态;\n3. 重启设备后观察是否恢复在线;\n4. 如仍离线,请联系运维人员上门处理。',
|
||||
createTime: '2026-05-12 14:20:00',
|
||||
},
|
||||
{
|
||||
id: 'hlp-003',
|
||||
code: 'HLP003',
|
||||
category: '系统版本',
|
||||
title: '系统升级通知(2026年7月)',
|
||||
clientTag: 'APP-001',
|
||||
scope: 'general',
|
||||
deviceTypes: [],
|
||||
content: '各位用户:\n系统将于2026年7月20日凌晨2:00-6:00进行例行升级维护,期间部分服务可能短暂中断,请提前做好业务安排。\n升级内容:\n1. 优化设备心跳检测机制;\n2. 修复数据分发异常问题;\n3. 提升系统整体稳定性。',
|
||||
createTime: '2026-05-15 10:00:00',
|
||||
},
|
||||
{
|
||||
id: 'hlp-004',
|
||||
code: 'HLP004',
|
||||
category: '操作说明',
|
||||
title: '入库秤使用指南',
|
||||
clientTag: 'APP-003',
|
||||
scope: 'specified',
|
||||
deviceTypes: ['入库秤'],
|
||||
content: '入库秤使用指南:\n1. 确认秤面平稳放置;\n2. 开机后等待归零提示;\n3. 将待称物品轻放秤面中央;\n4. 待读数稳定后记录数据;\n5. 称量完毕后清理秤面。',
|
||||
createTime: '2026-05-18 16:45:00',
|
||||
},
|
||||
{
|
||||
id: 'hlp-005',
|
||||
code: 'HLP005',
|
||||
category: '常见问题',
|
||||
title: '客户端登录异常通用排查',
|
||||
clientTag: 'APP-001',
|
||||
scope: 'general',
|
||||
deviceTypes: [],
|
||||
content: '客户端登录异常通用排查步骤:\n1. 确认账号密码输入是否正确;\n2. 检查客户端版本是否需要升级;\n3. 切换网络环境(WiFi/4G)后重试;\n4. 清理客户端本地缓存;\n5. 如仍无法登录,请联系管理员重置账号。',
|
||||
createTime: '2026-05-20 11:10:00',
|
||||
},
|
||||
{
|
||||
id: 'hlp-006',
|
||||
code: 'HLP006',
|
||||
category: '其他',
|
||||
title: '炒菜机器人维护保养注意事项',
|
||||
clientTag: 'APP-003',
|
||||
scope: 'specified',
|
||||
deviceTypes: ['炒菜机器人'],
|
||||
content: '炒菜机器人维护保养注意事项:\n1. 每日使用后清洁锅体表面油污;\n2. 每周检查搅拌臂转动是否顺畅;\n3. 每月对传动部件进行润滑保养;\n4. 定期检查电源线路绝缘状况;\n5. 发现异常噪音或异味立即停机报修。',
|
||||
createTime: '2026-05-25 08:20:00',
|
||||
},
|
||||
{
|
||||
id: 'hlp-007',
|
||||
code: 'HLP007',
|
||||
category: '新手指引',
|
||||
title: '营养秤校准流程',
|
||||
clientTag: 'APP-001',
|
||||
scope: 'specified',
|
||||
deviceTypes: ['营养秤'],
|
||||
content: '营养秤校准流程:\n1. 确认秤面无物品且干净;\n2. 进入设备设置菜单;\n3. 选择"校准"功能;\n4. 按照提示放置标准砝码;\n5. 等待校准完成提示;\n6. 校准完成后进行验证称量。',
|
||||
createTime: '2026-06-01 13:50:00',
|
||||
},
|
||||
{
|
||||
id: 'hlp-008',
|
||||
code: 'HLP008',
|
||||
category: '常见问题',
|
||||
title: '统计大屏数据显示异常',
|
||||
clientTag: 'APP-001',
|
||||
scope: 'specified',
|
||||
deviceTypes: ['统计大屏'],
|
||||
content: '统计大屏数据显示异常排查:\n1. 检查大屏与数据服务器的网络连接;\n2. 确认数据分发配置是否正确;\n3. 查看大屏本地缓存是否需要刷新;\n4. 重启大屏设备;\n5. 联系技术支持人员协助排查。',
|
||||
createTime: '2026-06-05 15:30:00',
|
||||
},
|
||||
]
|
||||
|
||||
/** 关键词搜索:编号 / 标题 */
|
||||
const matchKeyword = (item: HelpContentRecord, keyword?: string): boolean => {
|
||||
if (!keyword) return true
|
||||
return includes(item.code, keyword) || includes(item.title, keyword)
|
||||
}
|
||||
|
||||
const listHelpContent = (ctx: MockContext): ApiResponse<HelpContentRecord[]> => {
|
||||
const params = getParams(ctx.params)
|
||||
const filtered = helpContentList.filter((item) => {
|
||||
if (!matchKeyword(item, params.keyword)) return false
|
||||
if (params.category && item.category !== params.category) return false
|
||||
return true
|
||||
})
|
||||
if (params.column === 'createTime' && params.order) {
|
||||
filtered.sort((a, b) => {
|
||||
const cmp = a.createTime.localeCompare(b.createTime)
|
||||
return params.order === 'ascend' ? cmp : -cmp
|
||||
})
|
||||
}
|
||||
return paginate(filtered, params)
|
||||
}
|
||||
|
||||
const infoHelpContent = (ctx: MockContext): ApiResponse<HelpContentRecord | null> => {
|
||||
const params = getParams(ctx.params)
|
||||
const id = String(params.id ?? '')
|
||||
const target = helpContentList.find((item) => item.id === id)
|
||||
return ok('查询成功', target ?? null)
|
||||
}
|
||||
|
||||
const addHelpContent = (ctx: MockContext): ApiResponse<null> => {
|
||||
const params = getParams(ctx.params)
|
||||
const now = new Date()
|
||||
const pad = (n: number): string => String(n).padStart(2, '0')
|
||||
const createTime = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
|
||||
const record: HelpContentRecord = {
|
||||
id: `hlp-${Date.now().toString(36)}`,
|
||||
code: String(params.code ?? ''),
|
||||
category: (params.category as HelpCategory) ?? '其他',
|
||||
title: String(params.title ?? ''),
|
||||
clientTag: String(params.clientTag ?? ''),
|
||||
scope: (params.scope as ApplicableScope) ?? 'general',
|
||||
deviceTypes: Array.isArray(params.deviceTypes) ? (params.deviceTypes as string[]) : [],
|
||||
content: String(params.content ?? ''),
|
||||
createTime,
|
||||
}
|
||||
helpContentList.unshift(record)
|
||||
return ok('新增成功')
|
||||
}
|
||||
|
||||
const updateHelpContent = (ctx: MockContext): ApiResponse<null> => {
|
||||
const params = getParams(ctx.params)
|
||||
const id = String(params.id ?? '')
|
||||
const target = helpContentList.find((item) => item.id === id)
|
||||
if (!target) return ok('记录不存在', null)
|
||||
if (params.code !== undefined) target.code = String(params.code)
|
||||
if (params.category !== undefined) target.category = params.category as HelpCategory
|
||||
if (params.title !== undefined) target.title = String(params.title)
|
||||
if (params.clientTag !== undefined) target.clientTag = String(params.clientTag)
|
||||
if (params.scope !== undefined) target.scope = (params.scope as ApplicableScope) ?? 'general'
|
||||
if (params.deviceTypes !== undefined) target.deviceTypes = Array.isArray(params.deviceTypes) ? (params.deviceTypes as string[]) : []
|
||||
if (params.content !== undefined) target.content = String(params.content)
|
||||
return ok('更新成功')
|
||||
}
|
||||
|
||||
const deleteHelpContent = (ctx: MockContext): ApiResponse<null> => {
|
||||
const params = getParams(ctx.params)
|
||||
const id = String(params.id ?? '')
|
||||
const idx = helpContentList.findIndex((item) => item.id === id)
|
||||
if (idx >= 0) helpContentList.splice(idx, 1)
|
||||
return ok('删除成功')
|
||||
}
|
||||
|
||||
registerMocks([
|
||||
{ method: 'POST', url: '/admin/device/help/content/page', handler: listHelpContent },
|
||||
{ method: 'POST', url: '/admin/device/help/content/info', handler: infoHelpContent },
|
||||
{ method: 'POST', url: '/admin/device/help/content/add', handler: addHelpContent },
|
||||
{ method: 'POST', url: '/admin/device/help/content/update', handler: updateHelpContent },
|
||||
{ method: 'POST', url: '/admin/device/help/content/delete', handler: deleteHelpContent },
|
||||
])
|
||||
@@ -86,5 +86,7 @@ import './miniprogram/ops/workOrder'
|
||||
import './miniprogram/ops/scan'
|
||||
// 实施运维小程序 - 巡检(计划列表 4 状态 + 任务详情逐项填报 + 异常转工单)
|
||||
import './miniprogram/ops/inspect'
|
||||
// 设备中心 - 帮助中心 / 内容管理(CRUD)
|
||||
import './device-center/helpContent'
|
||||
// 实施运维小程序 - 我的(个人统计 + 消息中心 + 离线缓存)
|
||||
import './miniprogram/ops/me'
|
||||
|
||||
@@ -553,6 +553,21 @@ export const adminSections: NavSection[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
// 8. 帮助中心
|
||||
{
|
||||
key: 'admin-device-help-center',
|
||||
label: '帮助中心',
|
||||
icon: 'QuestionCircleOutlined',
|
||||
children: [
|
||||
{
|
||||
key: 'admin-device-help-content',
|
||||
label: '内容管理',
|
||||
path: 'help/content',
|
||||
status: 'ready',
|
||||
component: () => import('@pages/admin-portal/device-center/help/content/content.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { ApiResponse } from '@axios'
|
||||
import { postRequest } from '@axios'
|
||||
import type { HelpContentItem, HelpContentPayload, SearchForm } from '../types'
|
||||
|
||||
/** 查询帮助内容列表 */
|
||||
export const list = (params: SearchForm): Promise<ApiResponse<HelpContentItem[]>> =>
|
||||
postRequest('axiosRequest', '/admin/device/help/content/page', params)
|
||||
|
||||
/** 查询帮助内容详情 */
|
||||
export const info = (id: string): Promise<ApiResponse<HelpContentItem>> =>
|
||||
postRequest('axiosRequest', '/admin/device/help/content/info', { id })
|
||||
|
||||
/** 新增帮助内容 */
|
||||
export const add = (payload: HelpContentPayload): Promise<ApiResponse<null>> =>
|
||||
postRequest('axiosRequest', '/admin/device/help/content/add', payload)
|
||||
|
||||
/** 更新帮助内容 */
|
||||
export const update = (payload: HelpContentPayload): Promise<ApiResponse<null>> =>
|
||||
postRequest('axiosRequest', '/admin/device/help/content/update', payload)
|
||||
|
||||
/** 删除帮助内容 */
|
||||
export const remove = (id: string): Promise<ApiResponse<null>> =>
|
||||
postRequest('axiosRequest', '/admin/device/help/content/delete', { id })
|
||||
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
v-model:open="visible"
|
||||
title="内容详情"
|
||||
:width="600"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
destroy-on-close
|
||||
>
|
||||
<a-spin :spinning="spinning">
|
||||
<a-descriptions v-if="detail" :column="2" bordered size="small">
|
||||
<a-descriptions-item label="编号">{{ detail.code ?? '—' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="所属类别">
|
||||
<a-tag :color="categoryColorMap[detail.category] ?? 'default'">{{ detail.category }}</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="标题" :span="2">{{ detail.title ?? '—' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="客户端标识">{{ detail.clientTag ?? '—' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{ detail.createTime ?? '—' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="适用范围" :span="2">
|
||||
<a-space wrap>
|
||||
<a-tag :color="detail.scope === 'specified' ? 'blue' : 'green'">
|
||||
{{ detail.scope === 'specified' ? '指定设备' : '通用' }}
|
||||
</a-tag>
|
||||
<template v-if="detail.scope === 'specified'">
|
||||
<a-tag v-for="d in detail.deviceTypes" :key="d" color="blue">{{ d }}</a-tag>
|
||||
<span v-if="!detail.deviceTypes?.length">—</span>
|
||||
</template>
|
||||
</a-space>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="内容描述" :span="2">
|
||||
<div class="detail-content">{{ detail.content ?? '—' }}</div>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-spin>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 帮助内容 - 详情抽屉
|
||||
* - 只读展示,所有字段 ?? '—'
|
||||
*/
|
||||
import { ref } from 'vue'
|
||||
import { postRequest } from '@axios'
|
||||
import type { ApiResponse } from '@axios'
|
||||
import type { ApplicableScope, HelpCategory } from '../../../../types'
|
||||
|
||||
/** 帮助内容列表项 */
|
||||
interface HelpContentItem {
|
||||
id: string
|
||||
code: string
|
||||
category: HelpCategory
|
||||
title: string
|
||||
clientTag: string
|
||||
scope: ApplicableScope
|
||||
deviceTypes: string[]
|
||||
content: string
|
||||
createTime: string
|
||||
}
|
||||
|
||||
/** 类别 → Tag 颜色 */
|
||||
const categoryColorMap: Record<string, string> = {
|
||||
'操作说明': 'blue',
|
||||
'常见问题': 'orange',
|
||||
'新手指引': 'green',
|
||||
'系统版本': 'purple',
|
||||
'其他': 'default',
|
||||
}
|
||||
|
||||
const visible = ref(false)
|
||||
const spinning = ref(false)
|
||||
const detail = ref<HelpContentItem | null>(null)
|
||||
|
||||
/** 打开详情抽屉 */
|
||||
const openModal = (record: HelpContentItem): void => {
|
||||
visible.value = true
|
||||
spinning.value = true
|
||||
postRequest('axiosRequest', '/admin/device/help/content/info', { id: record.id })
|
||||
.then((res: ApiResponse<HelpContentItem>) => {
|
||||
if (res.code === '00000') {
|
||||
detail.value = res.data ?? null
|
||||
}
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
console.error('查询帮助内容详情失败:', err)
|
||||
})
|
||||
.finally(() => {
|
||||
spinning.value = false
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ openModal })
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.detail-content {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
line-height: 1.6;
|
||||
padding: 8px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:open="pageInfo.visible"
|
||||
:title="pageInfo.title"
|
||||
:width="pageInfo.width"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
>
|
||||
<a-spin :spinning="pageInfo.spin">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="{ style: { width: '110px', minWidth: '110px' } }"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col v-if="pageInfo.type === 'edit'" :span="12">
|
||||
<a-form-item label="编号" name="code">
|
||||
<a-input
|
||||
v-model:value="form.code"
|
||||
v-only-alphanumeric
|
||||
disabled
|
||||
allow-clear
|
||||
:maxlength="40"
|
||||
placeholder="系统自动生成"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="pageInfo.type === 'edit' ? 12 : 24">
|
||||
<a-form-item label="所属类别" name="category">
|
||||
<a-select v-model:value="form.category" :options="CATEGORY_OPTIONS" placeholder="请选择" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="标题" name="title">
|
||||
<a-input
|
||||
v-model:value="form.title"
|
||||
v-no-space
|
||||
allow-clear
|
||||
:maxlength="100"
|
||||
placeholder="请输入标题"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="客户端标识" name="clientTag">
|
||||
<a-input
|
||||
v-model:value="form.clientTag"
|
||||
allow-clear
|
||||
:maxlength="40"
|
||||
placeholder="如 APP-001"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="适用范围" name="scope">
|
||||
<a-radio-group v-model:value="form.scope">
|
||||
<a-radio
|
||||
v-for="opt in SCOPE_OPTIONS"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
>{{ opt.label }}</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
v-if="form.scope === 'specified'"
|
||||
label="适用设备分类"
|
||||
name="deviceTypes"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="form.deviceTypes"
|
||||
:options="DEVICE_TYPE_OPTIONS"
|
||||
mode="multiple"
|
||||
show-search
|
||||
allow-clear
|
||||
placeholder="请选择适用设备分类"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="内容描述" name="content">
|
||||
<a-textarea
|
||||
v-model:value="form.content"
|
||||
:rows="8"
|
||||
:maxlength="2000"
|
||||
show-count
|
||||
placeholder="请输入内容描述(富文本编辑器,原型以 textarea 模拟)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
|
||||
<template #footer>
|
||||
<a-button @click="closeModal">取消</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="pageInfo.spin"
|
||||
:loading="pageInfo.spin"
|
||||
@click="submit"
|
||||
>确定</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 帮助内容 - 新增/编辑弹框
|
||||
* - 字段:编号 / 所属类别 / 标题 / 客户端标识 / 适用范围 / 适用设备分类(指定设备时) / 内容描述
|
||||
*/
|
||||
|
||||
import { usePage } from './init/usePage'
|
||||
import {
|
||||
CATEGORY_OPTIONS,
|
||||
DEVICE_TYPE_OPTIONS,
|
||||
SCOPE_OPTIONS,
|
||||
} from '@pages/admin-portal/device-center/help/content/types'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'load'): void
|
||||
}>()
|
||||
|
||||
const {
|
||||
pageInfo,
|
||||
formRef,
|
||||
form,
|
||||
rules,
|
||||
openModal,
|
||||
closeModal,
|
||||
submit,
|
||||
} = usePage(emit)
|
||||
|
||||
defineExpose({ openModal })
|
||||
</script>
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 帮助内容 - 新增/编辑弹框 - 接口层
|
||||
* - 复用主页的 info / add / update
|
||||
*/
|
||||
|
||||
import type { ApiResponse } from '@axios'
|
||||
import { postRequest } from '@axios'
|
||||
import type { Form } from '../types'
|
||||
|
||||
/** 详情查询(编辑回填) */
|
||||
export const info = (id: string): Promise<ApiResponse<Form>> =>
|
||||
postRequest('axiosRequest', '/admin/device/help/content/info', { id })
|
||||
|
||||
/** 新增 */
|
||||
export const add = (params: Record<string, unknown>): Promise<ApiResponse<null>> =>
|
||||
postRequest('axiosRequest', '/admin/device/help/content/add', params)
|
||||
|
||||
/** 编辑 */
|
||||
export const edit = (params: Record<string, unknown>): Promise<ApiResponse<null>> =>
|
||||
postRequest('axiosRequest', '/admin/device/help/content/update', params)
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* 帮助内容 - 新增/编辑弹框 - 主逻辑
|
||||
*
|
||||
* 严格遵循 modal-spec.md:
|
||||
* - createInitForm 工厂函数
|
||||
* - openModal 用 Promise.all 拉取详情
|
||||
* - addRequest 必须 delete params.id
|
||||
* - submit 开头立即 pageInfo.spin = true
|
||||
* - 用 params.id 判断新增/编辑
|
||||
*/
|
||||
|
||||
import type { FormInstance, Rule } from 'ant-design-vue/es/form'
|
||||
import { useAntdStaticMethods } from '@utils/antDesign/popUp'
|
||||
import { add, edit, info } from '../api'
|
||||
import type { Form, ModalType, OpenContext, PageInfo } from '../types'
|
||||
|
||||
/** 初始表单工厂:适用范围默认通用 */
|
||||
const createInitForm = (): Form => ({
|
||||
id: undefined,
|
||||
code: '',
|
||||
category: '操作说明',
|
||||
title: '',
|
||||
clientTag: '',
|
||||
scope: 'general',
|
||||
deviceTypes: [],
|
||||
content: '',
|
||||
})
|
||||
|
||||
export const usePage = (emit: (e: 'load') => void) => {
|
||||
const { message } = useAntdStaticMethods()
|
||||
|
||||
const pageInfo = reactive<PageInfo>({
|
||||
visible: false,
|
||||
type: 'add',
|
||||
title: '',
|
||||
width: 720,
|
||||
spin: false,
|
||||
})
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const form = ref<Form>(createInitForm())
|
||||
|
||||
/** 适用范围切换回通用时清空已选设备分类 */
|
||||
watch(
|
||||
() => form.value.scope,
|
||||
(next) => {
|
||||
if (next === 'general') form.value.deviceTypes = []
|
||||
},
|
||||
)
|
||||
|
||||
/** 指定设备时校验至少选择一个设备分类 */
|
||||
const validateDeviceTypes = async (): Promise<void> => {
|
||||
if (form.value.scope === 'specified' && (!form.value.deviceTypes || form.value.deviceTypes.length === 0)) {
|
||||
return Promise.reject(new Error('请选择至少一个设备分类'))
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
const rules: Record<string, Rule[]> = {
|
||||
category: [{ required: true, message: '请选择所属类别', trigger: 'change' }],
|
||||
title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
|
||||
clientTag: [{ required: true, message: '请输入客户端标识', trigger: 'blur' }],
|
||||
scope: [{ required: true, message: '请选择适用范围', trigger: 'change' }],
|
||||
deviceTypes: [{ required: true, validator: validateDeviceTypes, trigger: 'change' }],
|
||||
content: [{ required: true, message: '请输入内容描述', trigger: 'blur' }],
|
||||
}
|
||||
|
||||
const resetForm = (): void => {
|
||||
formRef.value?.clearValidate()
|
||||
form.value = createInitForm()
|
||||
}
|
||||
|
||||
const closeModal = (): void => {
|
||||
resetForm()
|
||||
pageInfo.visible = false
|
||||
}
|
||||
|
||||
const infoRequest = (id: string) => info(id)
|
||||
|
||||
/** 打开弹框 */
|
||||
const openModal = (type: ModalType, ctx?: OpenContext): void => {
|
||||
pageInfo.visible = true
|
||||
pageInfo.spin = true
|
||||
pageInfo.type = type
|
||||
resetForm()
|
||||
|
||||
pageInfo.title = type === 'add' ? '新增帮助内容' : '编辑帮助内容'
|
||||
|
||||
const requests: Promise<unknown>[] = []
|
||||
if (type === 'edit' && ctx?.id) requests.push(infoRequest(ctx.id))
|
||||
|
||||
Promise.all(requests)
|
||||
.then((results) => {
|
||||
if (type === 'edit' && ctx?.id) {
|
||||
const infoRes = results[0] as { code: string; data: Form }
|
||||
if (infoRes?.code === '00000' && infoRes.data) {
|
||||
form.value = { ...createInitForm(), ...infoRes.data }
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
console.error('打开弹框初始化失败:', err)
|
||||
})
|
||||
.finally(() => {
|
||||
pageInfo.spin = false
|
||||
})
|
||||
}
|
||||
|
||||
const successRequest = (msg: string): void => {
|
||||
void message.success(msg)
|
||||
emit('load')
|
||||
closeModal()
|
||||
}
|
||||
|
||||
const addRequest = (params: Record<string, unknown>): void => {
|
||||
delete params.id
|
||||
add(params)
|
||||
.then((res) => {
|
||||
if (res.code === '00000') successRequest(res.msg)
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
console.error('新增失败:', err)
|
||||
})
|
||||
.finally(() => {
|
||||
pageInfo.spin = false
|
||||
})
|
||||
}
|
||||
|
||||
const editRequest = (params: Record<string, unknown>): void => {
|
||||
edit(params)
|
||||
.then((res) => {
|
||||
if (res.code === '00000') successRequest(res.msg)
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
console.error('编辑失败:', err)
|
||||
})
|
||||
.finally(() => {
|
||||
pageInfo.spin = false
|
||||
})
|
||||
}
|
||||
|
||||
const submit = (): void => {
|
||||
pageInfo.spin = true
|
||||
formRef.value
|
||||
?.validate()
|
||||
.then(() => {
|
||||
const params = JSON.parse(JSON.stringify(form.value)) as Record<string, unknown>
|
||||
if (params.id) editRequest(params)
|
||||
else addRequest(params)
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
pageInfo.spin = false
|
||||
console.error('表单验证失败:', err)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
pageInfo,
|
||||
formRef,
|
||||
form,
|
||||
rules,
|
||||
openModal,
|
||||
closeModal,
|
||||
submit,
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import type { FormInstance } from 'ant-design-vue/es/form'
|
||||
import type { ApplicableScope, HelpCategory } from '../../../../types'
|
||||
|
||||
/** 弹框类型 */
|
||||
export type ModalType = 'add' | 'edit'
|
||||
|
||||
/** 弹框页面状态 */
|
||||
export interface PageInfo {
|
||||
visible: boolean
|
||||
type: ModalType
|
||||
title: string
|
||||
width: number
|
||||
spin: boolean
|
||||
}
|
||||
|
||||
/** 打开弹框上下文 */
|
||||
export interface OpenContext {
|
||||
/** 编辑模式必传 */
|
||||
id?: string
|
||||
}
|
||||
|
||||
/** 表单字段 */
|
||||
export interface Form {
|
||||
id: string | undefined
|
||||
/** 编号 */
|
||||
code: string
|
||||
/** 所属类别 */
|
||||
category: HelpCategory
|
||||
/** 标题 */
|
||||
title: string
|
||||
/** 客户端标识 */
|
||||
clientTag: string
|
||||
/** 适用范围 */
|
||||
scope: ApplicableScope
|
||||
/** 适用设备分类(scope='specified' 时使用) */
|
||||
deviceTypes: string[]
|
||||
/** 内容描述 */
|
||||
content: string
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div class="admin-list-page">
|
||||
<FilterBar v-model="search" @search="searchQuery" @reset="resetQuery">
|
||||
<a-form-item label="关键字">
|
||||
<a-input
|
||||
v-model:value="search.keyword"
|
||||
v-no-space
|
||||
allow-clear
|
||||
placeholder="编号 / 标题"
|
||||
style="width: 220px"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="所属类别">
|
||||
<a-select
|
||||
v-model:value="search.category"
|
||||
:options="CATEGORY_OPTIONS"
|
||||
allow-clear
|
||||
placeholder="全部类别"
|
||||
style="width: 160px"
|
||||
/>
|
||||
</a-form-item>
|
||||
</FilterBar>
|
||||
|
||||
<TableCard :table="table" :loading="pageLoading" row-key="id" @change="dataSourceChange">
|
||||
<template #toolbar>
|
||||
<a-button type="primary" @click="openAddModal">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新增内容
|
||||
</a-button>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<a-button type="link" size="small" @click="handleAction('edit', record)">编辑</a-button>
|
||||
<a-divider type="vertical" />
|
||||
<a-button type="link" size="small" @click="handleAction('detail', record)">详情</a-button>
|
||||
<a-divider type="vertical" />
|
||||
<a-button type="link" size="small" danger @click="handleAction('delete', record)">删除</a-button>
|
||||
</template>
|
||||
</template>
|
||||
</TableCard>
|
||||
|
||||
<AddOrEdit ref="addOrEditRef" @load="listRequest" />
|
||||
<Detail ref="detailRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PlusOutlined } from '@ant-design/icons-vue'
|
||||
import AddOrEdit from './component/modal/addOrEdit/addOrEdit.vue'
|
||||
import Detail from './component/drawer/detail/detail.vue'
|
||||
import { usePage } from './init/usePage'
|
||||
import { CATEGORY_OPTIONS } from './types'
|
||||
|
||||
const {
|
||||
pageLoading,
|
||||
search,
|
||||
table,
|
||||
addOrEditRef,
|
||||
detailRef,
|
||||
searchQuery,
|
||||
resetQuery,
|
||||
dataSourceChange,
|
||||
openAddModal,
|
||||
handleAction,
|
||||
listRequest,
|
||||
} = usePage()
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import "@assets/styles/listPage.less";
|
||||
|
||||
.admin-list-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,147 @@
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import type { TablePaginationConfig } from 'ant-design-vue'
|
||||
import { useAntdStaticMethods } from '@utils/antDesign/popUp'
|
||||
import { createPaginationConfig } from '@utils/antDesign/table'
|
||||
import type { TableState } from '@utils/antDesign/table'
|
||||
import AddOrEdit from '../component/modal/addOrEdit/addOrEdit.vue'
|
||||
import Detail from '../component/drawer/detail/detail.vue'
|
||||
import { list, remove } from '../api'
|
||||
import type { HelpContentItem, SearchForm } from '../types'
|
||||
import { createSearchKey } from './useSearch'
|
||||
import { setActionClickHandler, tableColumns } from './useTable'
|
||||
|
||||
/**
|
||||
* 帮助中心 - 内容管理页主编排:
|
||||
* - 标准 CRUD + 详情抽屉
|
||||
* - 操作列:编辑 / 详情 / 删除
|
||||
*/
|
||||
export const usePage = () => {
|
||||
const { message, Modal } = useAntdStaticMethods()
|
||||
|
||||
const search = reactive<SearchForm>(createSearchKey())
|
||||
|
||||
const table = reactive<TableState<HelpContentItem>>({
|
||||
columns: tableColumns,
|
||||
dataSource: [],
|
||||
sort: { field: '', order: null },
|
||||
pagination: createPaginationConfig() as TablePaginationConfig,
|
||||
})
|
||||
|
||||
const pageLoading = ref<boolean>(false)
|
||||
const selectedRowKeys = ref<(string | number)[]>([])
|
||||
|
||||
/** 新增/编辑弹框 ref */
|
||||
const addOrEditRef = ref<InstanceType<typeof AddOrEdit> | null>(null)
|
||||
/** 详情弹窗 ref */
|
||||
const detailRef = ref<InstanceType<typeof Detail> | null>(null)
|
||||
|
||||
/** 操作列回调:编辑 / 详情 / 删除 */
|
||||
const onAction = (type: 'edit' | 'detail' | 'delete', record: HelpContentItem): void => {
|
||||
if (type === 'edit') {
|
||||
addOrEditRef.value?.openModal('edit', { id: record.id })
|
||||
return
|
||||
}
|
||||
if (type === 'detail') {
|
||||
detailRef.value?.openModal(record)
|
||||
return
|
||||
}
|
||||
if (type === 'delete') {
|
||||
deleteRecord(record)
|
||||
}
|
||||
}
|
||||
|
||||
// 注入列回调(tableColumns 模块级常量,通过 setter 注入)
|
||||
setActionClickHandler(onAction)
|
||||
|
||||
const listRequest = (): void => {
|
||||
pageLoading.value = true
|
||||
list(search)
|
||||
.then((res) => {
|
||||
if (res.code === '00000') {
|
||||
table.dataSource = res.data ?? []
|
||||
table.pagination.total = table.dataSource.length
|
||||
}
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
console.error('帮助内容列表请求失败:', err)
|
||||
})
|
||||
.finally(() => {
|
||||
pageLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const searchQuery = (): void => {
|
||||
table.pagination.current = 1
|
||||
listRequest()
|
||||
}
|
||||
|
||||
const resetQuery = (): void => {
|
||||
Object.assign(search, createSearchKey())
|
||||
table.pagination.current = 1
|
||||
listRequest()
|
||||
}
|
||||
|
||||
const dataSourceChange = (pagination: TablePaginationConfig): void => {
|
||||
table.pagination.current = pagination.current ?? 1
|
||||
table.pagination.pageSize = pagination.pageSize ?? 30
|
||||
}
|
||||
|
||||
const rowSelection = {
|
||||
selectedRowKeys,
|
||||
onChange: (keys: (string | number)[]): void => {
|
||||
selectedRowKeys.value = keys
|
||||
},
|
||||
}
|
||||
|
||||
/** 打开新增弹框 */
|
||||
const openAddModal = (): void => {
|
||||
addOrEditRef.value?.openModal('add')
|
||||
}
|
||||
|
||||
/** 删除记录(二次确认) */
|
||||
const deleteRecord = (record: HelpContentItem): void => {
|
||||
Modal.confirm({
|
||||
title: '确认删除?',
|
||||
content: `帮助内容「${record.title}」删除后不可恢复,请谨慎操作。`,
|
||||
okType: 'danger',
|
||||
okText: '确认删除',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
remove(record.id)
|
||||
.then((res) => {
|
||||
if (res.code === '00000') {
|
||||
void message.success(res.msg)
|
||||
listRequest()
|
||||
}
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
console.error('帮助内容删除失败:', err)
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
listRequest()
|
||||
})
|
||||
|
||||
const handleAction = (type: 'edit' | 'detail' | 'delete', record: HelpContentItem): void => {
|
||||
onAction(type, record)
|
||||
}
|
||||
|
||||
return {
|
||||
pageLoading,
|
||||
search,
|
||||
table,
|
||||
selectedRowKeys,
|
||||
rowSelection,
|
||||
addOrEditRef,
|
||||
detailRef,
|
||||
searchQuery,
|
||||
resetQuery,
|
||||
dataSourceChange,
|
||||
openAddModal,
|
||||
handleAction,
|
||||
listRequest,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { SearchForm } from '../types'
|
||||
|
||||
/** 搜索表单工厂:统一重置入口 */
|
||||
export const createSearchKey = (): SearchForm => ({
|
||||
keyword: undefined,
|
||||
category: undefined,
|
||||
})
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { TableColumnsType } from 'ant-design-vue'
|
||||
import { h } from 'vue'
|
||||
import { Button, Tag } from 'ant-design-vue'
|
||||
import type { ApplicableScope, HelpContentItem, HelpCategory } from '../types'
|
||||
import { categoryColorMap, scopeColorMap, scopeLabelMap } from '../types'
|
||||
|
||||
/** 帮助内容列表列定义(含操作列:编辑 / 详情 / 删除) */
|
||||
export const tableColumns: TableColumnsType = [
|
||||
{ title: '序号', dataIndex: 'index', align: 'center', fixed: 'left', width: 70, customRender: ({ index }: { index: number }) => index + 1 },
|
||||
{ title: '编号', dataIndex: 'code', align: 'left', width: 120, resizable: true, ellipsis: true },
|
||||
{
|
||||
title: '所属类别',
|
||||
dataIndex: 'category',
|
||||
align: 'center',
|
||||
width: 110,
|
||||
customRender: ({ value }: { value: HelpCategory }) =>
|
||||
h(Tag, { color: categoryColorMap[value] ?? 'default' }, () => value),
|
||||
},
|
||||
{ title: '标题', dataIndex: 'title', align: 'left', width: 240, resizable: true, ellipsis: true },
|
||||
{ title: '客户端标识', dataIndex: 'clientTag', align: 'center', width: 130, resizable: true, ellipsis: true },
|
||||
{
|
||||
title: '适用范围',
|
||||
dataIndex: 'scope',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
resizable: true,
|
||||
ellipsis: true,
|
||||
customRender: ({ record }: { record: HelpContentItem }) => {
|
||||
const scope: ApplicableScope = record.scope ?? 'general'
|
||||
if (scope === 'general') {
|
||||
return h(Tag, { color: scopeColorMap.general }, () => scopeLabelMap.general)
|
||||
}
|
||||
return (record.deviceTypes ?? []).join('、') || '—'
|
||||
},
|
||||
},
|
||||
{ title: '创建时间', dataIndex: 'createTime', align: 'center', width: 170, sorter: true, resizable: true },
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
customRender: ({ record }: { record: HelpContentItem }) =>
|
||||
h('div', {}, [
|
||||
h(Button, { type: 'link', size: 'small', onClick: () => onActionClick?.('edit', record) }, () => '编辑'),
|
||||
h(Button, { type: 'link', size: 'small', onClick: () => onActionClick?.('detail', record) }, () => '详情'),
|
||||
h(Button, { type: 'link', size: 'small', danger: true, onClick: () => onActionClick?.('delete', record) }, () => '删除'),
|
||||
]),
|
||||
},
|
||||
]
|
||||
|
||||
/** 操作点击回调(由 usePage 注入) */
|
||||
let onActionClick: ((type: 'edit' | 'detail' | 'delete', record: HelpContentItem) => void) | null = null
|
||||
|
||||
/** 注入操作点击回调(供 usePage 调用) */
|
||||
export const setActionClickHandler = (handler: (type: 'edit' | 'detail' | 'delete', record: HelpContentItem) => void): void => {
|
||||
onActionClick = handler
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import type { BaseRecord, BaseSearchForm } from '@pages/admin-portal/shared/types'
|
||||
|
||||
/** 所属类别 */
|
||||
export type HelpCategory = '操作说明' | '常见问题' | '新手指引' | '系统版本' | '其他'
|
||||
|
||||
/** 适用范围:通用 / 指定设备 */
|
||||
export type ApplicableScope = 'general' | 'specified'
|
||||
|
||||
/** 帮助内容列表项 */
|
||||
export interface HelpContentItem extends BaseRecord {
|
||||
/** 编号 */
|
||||
code: string
|
||||
/** 所属类别 */
|
||||
category: HelpCategory
|
||||
/** 标题 */
|
||||
title: string
|
||||
/** 客户端标识 */
|
||||
clientTag: string
|
||||
/** 适用范围 */
|
||||
scope: ApplicableScope
|
||||
/** 适用设备分类(scope='specified' 时生效) */
|
||||
deviceTypes: string[]
|
||||
/** 内容描述 */
|
||||
content: string
|
||||
/** 创建时间 */
|
||||
createTime: string
|
||||
}
|
||||
|
||||
/** 搜索表单 */
|
||||
export interface SearchForm extends BaseSearchForm {
|
||||
keyword?: string
|
||||
category?: HelpCategory
|
||||
}
|
||||
|
||||
/** 新增/编辑提交载荷 */
|
||||
export interface HelpContentPayload {
|
||||
id?: string
|
||||
code: string
|
||||
category: HelpCategory
|
||||
title: string
|
||||
clientTag: string
|
||||
scope: ApplicableScope
|
||||
deviceTypes: string[]
|
||||
content: string
|
||||
}
|
||||
|
||||
/** 类别下拉选项 */
|
||||
export const CATEGORY_OPTIONS = [
|
||||
{ label: '操作说明', value: '操作说明' },
|
||||
{ label: '常见问题', value: '常见问题' },
|
||||
{ label: '新手指引', value: '新手指引' },
|
||||
{ label: '系统版本', value: '系统版本' },
|
||||
{ label: '其他', value: '其他' },
|
||||
]
|
||||
|
||||
/** 客户端标识下拉选项 */
|
||||
export const CLIENT_TAG_OPTIONS = [
|
||||
{ label: '综合管理后台', value: '综合管理后台' },
|
||||
{ label: '租户运营后台', value: '租户运营后台' },
|
||||
{ label: '微信小程序', value: '微信小程序' },
|
||||
{ label: '硬件终端', value: '硬件终端' },
|
||||
]
|
||||
|
||||
/** 适用范围下拉/Radio 选项 */
|
||||
export const SCOPE_OPTIONS = [
|
||||
{ label: '通用', value: 'general' as ApplicableScope },
|
||||
{ label: '指定设备', value: 'specified' as ApplicableScope },
|
||||
]
|
||||
|
||||
/** 适用范围 → 文本映射 */
|
||||
export const scopeLabelMap: Record<ApplicableScope, string> = {
|
||||
general: '通用',
|
||||
specified: '指定设备',
|
||||
}
|
||||
|
||||
/** 适用范围 → Tag 颜色 */
|
||||
export const scopeColorMap: Record<ApplicableScope, string> = {
|
||||
general: 'green',
|
||||
specified: 'blue',
|
||||
}
|
||||
|
||||
/** 设备类型下拉选项 */
|
||||
export const DEVICE_TYPE_OPTIONS = [
|
||||
{ label: '智能货柜', value: '智能货柜' },
|
||||
{ label: '档口机', value: '档口机' },
|
||||
{ label: '入库秤', value: '入库秤' },
|
||||
{ label: '营养秤', value: '营养秤' },
|
||||
{ label: '吐盘机', value: '吐盘机' },
|
||||
{ label: '结算台', value: '结算台' },
|
||||
{ label: '配比秤', value: '配比秤' },
|
||||
{ label: '带鱼屏', value: '带鱼屏' },
|
||||
{ label: '统计大屏', value: '统计大屏' },
|
||||
{ label: '炒菜机器人', value: '炒菜机器人' },
|
||||
]
|
||||
|
||||
/** 类别 → Tag 颜色 */
|
||||
export const categoryColorMap: Record<HelpCategory, string> = {
|
||||
'操作说明': 'blue',
|
||||
'常见问题': 'orange',
|
||||
'新手指引': 'green',
|
||||
'系统版本': 'purple',
|
||||
'其他': 'default',
|
||||
}
|
||||
Reference in New Issue
Block a user