diff --git a/src/axios/handlers/device-center/helpContent.ts b/src/axios/handlers/device-center/helpContent.ts new file mode 100644 index 0000000..6363a75 --- /dev/null +++ b/src/axios/handlers/device-center/helpContent.ts @@ -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 = (list: T[], params: HelpContentParams): ApiResponse => { + 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 = (msg: string, data: T = null as T): ApiResponse => ({ 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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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 }, +]) diff --git a/src/axios/handlers/index.ts b/src/axios/handlers/index.ts index 9b60c20..468042d 100644 --- a/src/axios/handlers/index.ts +++ b/src/axios/handlers/index.ts @@ -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' diff --git a/src/meta/nav.ts b/src/meta/nav.ts index ac14754..4aabc82 100644 --- a/src/meta/nav.ts +++ b/src/meta/nav.ts @@ -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'), + }, + ], + }, ], }, ] diff --git a/src/pages/admin-portal/device-center/help/content/api/index.ts b/src/pages/admin-portal/device-center/help/content/api/index.ts new file mode 100644 index 0000000..06b9403 --- /dev/null +++ b/src/pages/admin-portal/device-center/help/content/api/index.ts @@ -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> => + postRequest('axiosRequest', '/admin/device/help/content/page', params) + +/** 查询帮助内容详情 */ +export const info = (id: string): Promise> => + postRequest('axiosRequest', '/admin/device/help/content/info', { id }) + +/** 新增帮助内容 */ +export const add = (payload: HelpContentPayload): Promise> => + postRequest('axiosRequest', '/admin/device/help/content/add', payload) + +/** 更新帮助内容 */ +export const update = (payload: HelpContentPayload): Promise> => + postRequest('axiosRequest', '/admin/device/help/content/update', payload) + +/** 删除帮助内容 */ +export const remove = (id: string): Promise> => + postRequest('axiosRequest', '/admin/device/help/content/delete', { id }) diff --git a/src/pages/admin-portal/device-center/help/content/component/drawer/detail/detail.vue b/src/pages/admin-portal/device-center/help/content/component/drawer/detail/detail.vue new file mode 100644 index 0000000..dbc1a0c --- /dev/null +++ b/src/pages/admin-portal/device-center/help/content/component/drawer/detail/detail.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/src/pages/admin-portal/device-center/help/content/component/modal/addOrEdit/addOrEdit.vue b/src/pages/admin-portal/device-center/help/content/component/modal/addOrEdit/addOrEdit.vue new file mode 100644 index 0000000..bc19214 --- /dev/null +++ b/src/pages/admin-portal/device-center/help/content/component/modal/addOrEdit/addOrEdit.vue @@ -0,0 +1,138 @@ + + + diff --git a/src/pages/admin-portal/device-center/help/content/component/modal/addOrEdit/api/index.ts b/src/pages/admin-portal/device-center/help/content/component/modal/addOrEdit/api/index.ts new file mode 100644 index 0000000..681e998 --- /dev/null +++ b/src/pages/admin-portal/device-center/help/content/component/modal/addOrEdit/api/index.ts @@ -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> => + postRequest('axiosRequest', '/admin/device/help/content/info', { id }) + +/** 新增 */ +export const add = (params: Record): Promise> => + postRequest('axiosRequest', '/admin/device/help/content/add', params) + +/** 编辑 */ +export const edit = (params: Record): Promise> => + postRequest('axiosRequest', '/admin/device/help/content/update', params) diff --git a/src/pages/admin-portal/device-center/help/content/component/modal/addOrEdit/init/usePage.ts b/src/pages/admin-portal/device-center/help/content/component/modal/addOrEdit/init/usePage.ts new file mode 100644 index 0000000..0023ebf --- /dev/null +++ b/src/pages/admin-portal/device-center/help/content/component/modal/addOrEdit/init/usePage.ts @@ -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({ + visible: false, + type: 'add', + title: '', + width: 720, + spin: false, + }) + + const formRef = ref() + const form = ref
(createInitForm()) + + /** 适用范围切换回通用时清空已选设备分类 */ + watch( + () => form.value.scope, + (next) => { + if (next === 'general') form.value.deviceTypes = [] + }, + ) + + /** 指定设备时校验至少选择一个设备分类 */ + const validateDeviceTypes = async (): Promise => { + if (form.value.scope === 'specified' && (!form.value.deviceTypes || form.value.deviceTypes.length === 0)) { + return Promise.reject(new Error('请选择至少一个设备分类')) + } + return Promise.resolve() + } + + const rules: Record = { + 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[] = [] + 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): 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): 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 + 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, + } +} diff --git a/src/pages/admin-portal/device-center/help/content/component/modal/addOrEdit/types/index.ts b/src/pages/admin-portal/device-center/help/content/component/modal/addOrEdit/types/index.ts new file mode 100644 index 0000000..15d9fea --- /dev/null +++ b/src/pages/admin-portal/device-center/help/content/component/modal/addOrEdit/types/index.ts @@ -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 +} diff --git a/src/pages/admin-portal/device-center/help/content/content.vue b/src/pages/admin-portal/device-center/help/content/content.vue new file mode 100644 index 0000000..0e7b69b --- /dev/null +++ b/src/pages/admin-portal/device-center/help/content/content.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/src/pages/admin-portal/device-center/help/content/init/usePage.ts b/src/pages/admin-portal/device-center/help/content/init/usePage.ts new file mode 100644 index 0000000..e1be209 --- /dev/null +++ b/src/pages/admin-portal/device-center/help/content/init/usePage.ts @@ -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(createSearchKey()) + + const table = reactive>({ + columns: tableColumns, + dataSource: [], + sort: { field: '', order: null }, + pagination: createPaginationConfig() as TablePaginationConfig, + }) + + const pageLoading = ref(false) + const selectedRowKeys = ref<(string | number)[]>([]) + + /** 新增/编辑弹框 ref */ + const addOrEditRef = ref | null>(null) + /** 详情弹窗 ref */ + const detailRef = ref | 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, + } +} diff --git a/src/pages/admin-portal/device-center/help/content/init/useSearch.ts b/src/pages/admin-portal/device-center/help/content/init/useSearch.ts new file mode 100644 index 0000000..4a74a61 --- /dev/null +++ b/src/pages/admin-portal/device-center/help/content/init/useSearch.ts @@ -0,0 +1,7 @@ +import type { SearchForm } from '../types' + +/** 搜索表单工厂:统一重置入口 */ +export const createSearchKey = (): SearchForm => ({ + keyword: undefined, + category: undefined, +}) diff --git a/src/pages/admin-portal/device-center/help/content/init/useTable.ts b/src/pages/admin-portal/device-center/help/content/init/useTable.ts new file mode 100644 index 0000000..97a3d38 --- /dev/null +++ b/src/pages/admin-portal/device-center/help/content/init/useTable.ts @@ -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 +} diff --git a/src/pages/admin-portal/device-center/help/content/types/index.ts b/src/pages/admin-portal/device-center/help/content/types/index.ts new file mode 100644 index 0000000..243e285 --- /dev/null +++ b/src/pages/admin-portal/device-center/help/content/types/index.ts @@ -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 = { + general: '通用', + specified: '指定设备', +} + +/** 适用范围 → Tag 颜色 */ +export const scopeColorMap: Record = { + 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 = { + '操作说明': 'blue', + '常见问题': 'orange', + '新手指引': 'green', + '系统版本': 'purple', + '其他': 'default', +}