diff --git a/CLAUDE.md b/CLAUDE.md index 6dc4dca..cdd5a95 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -199,11 +199,28 @@ yx-platform-prototype/ │ │ │ ├── make/Home.vue │ │ │ └── me/Home.vue │ │ └── ops/ 实施运维小程序(仅运维工程师) - │ │ ├── workbench/Home.vue - │ │ ├── workOrder/Home.vue - │ │ ├── scan/Home.vue - │ │ ├── inspect/Home.vue - │ │ └── me/Home.vue + │ │ ├── workbench/ 工作台 Tab(含告警入口) + │ │ │ ├── Home.vue + │ │ │ ├── alarmList.vue 告警列表子页 + │ │ │ └── alarmDetail.vue 告警详情子页 + │ │ ├── workOrder/ 工单 Tab + │ │ │ ├── Home.vue + │ │ │ ├── detail.vue 工单详情子页 + │ │ │ └── repairReport.vue 关单前维修上报纸页 + │ │ ├── scan/ 扫码 Tab(6 个子操作) + │ │ │ ├── Home.vue + │ │ │ ├── sceneMenu.vue 场景操作菜单 + │ │ │ ├── init.vue 设备初始化 + │ │ │ ├── implRecord.vue 实施记录上报 + │ │ │ ├── repair.vue 维修上报(双入口) + │ │ │ ├── ota.vue OTA 升级 + │ │ │ └── monitor.vue 设备监测(多 Tab 简化版) + │ │ ├── inspect/ 巡检 Tab + │ │ │ ├── Home.vue + │ │ │ └── detail.vue 巡检任务详情 + │ │ └── me/ 我的 Tab + │ │ ├── Home.vue + │ │ └── messageCenter.vue 消息中心子页 │ └── hardware/ 硬件业务页面 │ └── cabinet/Home.vue │ diff --git a/src/axios/handlers/index.ts b/src/axios/handlers/index.ts index c71267b..0042043 100644 --- a/src/axios/handlers/index.ts +++ b/src/axios/handlers/index.ts @@ -64,3 +64,13 @@ import './device-center/distribute/config' import './device-center/distribute/record' // 设备中心 - 数据分发 / 失败重投(死信队列,含手动重投、永久丢弃) import './device-center/distribute/deadletter' +// 实施运维小程序 - 工作台 + 告警(今日待办 / 本周统计 / 告警列表 + 详情 + 处置) +import './miniprogram/ops/workbench' +// 实施运维小程序 - 工单(列表 3 段 + 详情 + 接单/关单/维修上报) +import './miniprogram/ops/workOrder' +// 实施运维小程序 - 扫码(识别 / 历史 / 初始化 / 实施记录 / 维修上报 / OTA / 设备监测 5 Tab) +import './miniprogram/ops/scan' +// 实施运维小程序 - 巡检(计划列表 4 状态 + 任务详情逐项填报 + 异常转工单) +import './miniprogram/ops/inspect' +// 实施运维小程序 - 我的(个人统计 + 消息中心 + 离线缓存) +import './miniprogram/ops/me' diff --git a/src/axios/handlers/miniprogram/ops/inspect.ts b/src/axios/handlers/miniprogram/ops/inspect.ts new file mode 100644 index 0000000..74a65cc --- /dev/null +++ b/src/axios/handlers/miniprogram/ops/inspect.ts @@ -0,0 +1,154 @@ +/** + * 实施运维小程序 / 巡检 mock + * - 计划列表(4 状态) + * - 任务详情(按设备 × 巡检项) + * - 提交项结果 + 完成任务 + */ + +import { registerMocks } from '@axios/mockBus' +import type { ApiResponse, RequestParameter } from '@axios' +import type { + InspectItem, + InspectItemSubmit, + InspectPlan, + InspectPlanStatus, + InspectTaskDetail, +} from '@pages/miniprogram/ops/inspect/types' + +const ok = (msg: string, data: T = null as T): ApiResponse => ({ + code: '00000', + msg, + data, +}) + +const getParams = >(params: RequestParameter): T => + (params ?? {}) as T + +const plans: InspectPlan[] = [ + { + id: 'ip-001', name: '上海浦东张江食堂 - 月度巡检', site: '上海浦东 · 张江食堂', + deviceCategory: '智养餐饮设备', deviceCount: 5, status: 'doing', + deadline: '2026-07-03 18:00:00', inspector: '李工程师', itemCount: 6, + }, + { + id: 'ip-002', name: '北京朝阳体检站 - 周巡检', site: '北京朝阳 · 朝阳体检站', + deviceCategory: '智能穿戴设备', deviceCount: 12, status: 'todo', + deadline: '2026-07-04 12:00:00', inspector: '李工程师', itemCount: 5, + }, + { + id: 'ip-003', name: '广州天银食堂 - 临时巡检', site: '广州天河 · 天银食堂', + deviceCategory: '智养餐饮设备', deviceCount: 3, status: 'overdue', + deadline: '2026-07-02 18:00:00', inspector: '王工程师', itemCount: 6, + }, + { + id: 'ip-004', name: '杭州西湖体检站 - 月度巡检', site: '杭州西湖 · 西湖体检站', + deviceCategory: '体重测量设备', deviceCount: 4, status: 'done', + deadline: '2026-07-01 18:00:00', inspector: '赵工程师', itemCount: 5, + }, + { + id: 'ip-005', name: '重庆解放碑健身房 - 周巡检', site: '重庆渝中 · 解放碑健身房', + deviceCategory: '健身器材设备', deviceCount: 8, status: 'todo', + deadline: '2026-07-05 12:00:00', inspector: '李工程师', itemCount: 5, + }, + { + id: 'ip-006', name: '深圳科技园体检站 - 月度巡检', site: '深圳南山 · 科技园体检站', + deviceCategory: '健康监测设备', deviceCount: 2, status: 'doing', + deadline: '2026-07-04 18:00:00', inspector: '赵工程师', itemCount: 7, + }, + { + id: 'ip-007', name: '上海闵行莘庄食堂 - 月度巡检', site: '上海闵行 · 莘庄食堂', + deviceCategory: '智养餐饮设备', deviceCount: 4, status: 'done', + deadline: '2026-06-30 18:00:00', inspector: '李工程师', itemCount: 6, + }, +] + +const buildItems = (): InspectItem[] => [ + { id: 'it-1', name: '设备外观', type: 'appearance', standard: '无明显损伤 / 变形', result: 'pending', remark: '' }, + { id: 'it-2', name: '功能测试', type: 'function', standard: '按键 / 显示正常', result: 'pending', remark: '' }, + { id: 'it-3', name: '网络连通', type: 'network', standard: '心跳正常上报', result: 'pending', remark: '' }, + { id: 'it-4', name: '传感器读数', type: 'sensor', standard: '温度 / 湿度在阈值内', result: 'pending', remark: '' }, + { id: 'it-5', name: '固件版本', type: 'firmware', standard: '与最新版本一致', result: 'pending', remark: '' }, + { id: 'it-6', name: '电源稳定性', type: 'function', standard: '无掉电告警', result: 'pending', remark: '' }, +] + +const taskDetailMap: Record = { + 'ip-001': { + plan: plans[0], + devices: [ + { code: 'CAB-SH-001', name: '1号净菜柜', items: buildItems(), scanned: true }, + { code: 'CAB-SH-002', name: '2号净菜柜', items: buildItems(), scanned: false }, + { code: 'CAB-SH-003', name: '3号净菜柜', items: buildItems(), scanned: false }, + ], + }, + 'ip-002': { + plan: plans[1], + devices: [ + { code: 'WATCH-BJ-014', name: '14号健康手表', items: buildItems().slice(0, 5), scanned: false }, + { code: 'WATCH-BJ-015', name: '15号健康手表', items: buildItems().slice(0, 5), scanned: false }, + ], + }, +} + +registerMocks([ + // 计划列表 + { + url: '/mp/ops/inspect/list', + method: 'POST', + handler: (ctx): ApiResponse => { + const { status } = getParams<{ status?: InspectPlanStatus | '' }>(ctx.params) + const rows = status ? plans.filter((p) => p.status === status) : plans + return ok('查询成功', rows) + }, + }, + // 任务详情 + { + url: '/mp/ops/inspect/detail', + method: 'GET', + handler: (ctx): ApiResponse => { + const { id } = getParams<{ id?: string }>(ctx.params) + const detail = taskDetailMap[id ?? ''] + if (!detail) { + // 兜底:构造一个空任务 + const plan = plans.find((p) => p.id === id) + if (!plan) return ok('计划不存在', null) + return ok('查询成功', { + plan, + devices: [ + { code: `${plan.id}-D001`, name: '示例设备', items: buildItems().slice(0, plan.itemCount), scanned: false }, + ], + }) + } + return ok('查询成功', detail) + }, + }, + // 提交巡检项结果 + { + url: '/mp/ops/inspect/submit', + method: 'POST', + handler: (ctx): ApiResponse => { + const { planId, deviceCode, itemId, result, remark } = getParams(ctx.params) + const task = taskDetailMap[planId] + if (task) { + const dev = task.devices.find((d) => d.code === deviceCode) + const item = dev?.items.find((i) => i.id === itemId) + if (item) { + item.result = result + item.remark = remark + } + } + // 异常项自动转工单(mock 不实际创建,仅返回成功) + return ok(result === 'abnormal' ? '已记录异常,并自动转工单' : '已记录', null) + }, + }, + // 完成巡检任务 + { + url: '/mp/ops/inspect/finish', + method: 'POST', + handler: (ctx): ApiResponse => { + const { planId } = getParams<{ planId: string }>(ctx.params) + const plan = plans.find((p) => p.id === planId) + if (plan) plan.status = 'done' + return ok('巡检任务已完成', null) + }, + }, +]) diff --git a/src/axios/handlers/miniprogram/ops/me.ts b/src/axios/handlers/miniprogram/ops/me.ts new file mode 100644 index 0000000..6dd1fca --- /dev/null +++ b/src/axios/handlers/miniprogram/ops/me.ts @@ -0,0 +1,144 @@ +/** + * 实施运维小程序 / 我的 mock + * - 个人统计(月度工单 / 巡检 / 装机 / OTA + SLA 达成率) + * - 消息中心(7 类消息) + * - 离线缓存状态 + 同步 / 清除 + */ + +import { registerMocks } from '@axios/mockBus' +import type { ApiResponse, RequestParameter } from '@axios' +import type { MessageItem, MessageType, OfflineStatus, PersonalStats } from '@pages/miniprogram/ops/me/types' + +const ok = (msg: string, data: T = null as T): ApiResponse => ({ + code: '00000', + msg, + data, +}) + +const getParams = >(params: RequestParameter): T => + (params ?? {}) as T + +const stats: PersonalStats = { + monthOrders: 23, + monthInspections: 18, + monthInstalls: 6, + monthOta: 9, + avgProcessHours: 4.5, + slaRate: '92.5%', +} + +const messages: MessageItem[] = [ + { + id: 'msg-001', type: 'alarmCritical', title: '严重告警 · 净菜柜压缩机故障', + content: 'CAB-SH-001 自检失败:压缩机停转 3 次/小时', + createdAt: '2026-07-03 09:25:02', read: false, + link: '/miniprogram/ops/alarm-detail/alm-001', + }, + { + id: 'msg-002', type: 'workOrder', title: '工单派单 · 净菜柜维修', + content: 'WO20260703001 已派给您,SLA 截止 18:00', + createdAt: '2026-07-03 09:00:00', read: false, + link: '/miniprogram/ops/work-order-detail/wo-001', + }, + { + id: 'msg-003', type: 'ota', title: 'OTA 任务下发 · 健康手表', + content: 'WATCH-BJ-014 目标版本 v1.1.0,请前往升级', + createdAt: '2026-07-03 08:30:00', read: false, + link: '/miniprogram/ops/ota/WATCH-BJ-014', + }, + { + id: 'msg-004', type: 'inspect', title: '巡检任务下发 · 张江食堂', + content: '上海浦东张江食堂月度巡检,截止 7-3 18:00', + createdAt: '2026-07-03 08:00:00', read: true, + link: '/miniprogram/ops/inspect-detail/ip-001', + }, + { + id: 'msg-005', type: 'overSla', title: '工单超 SLA · 货道错位复位', + content: 'WO20260702018 已超 SLA,请尽快处置', + createdAt: '2026-07-02 18:00:00', read: true, + link: '/miniprogram/ops/work-order-detail/wo-003', + }, + { + id: 'msg-006', type: 'alarmDead', title: '数据分发死信告警', + content: '营养系统订阅通道累积死信 12 条,请处理', + createdAt: '2026-07-02 11:50:00', read: true, + link: '/miniprogram/ops/alarm-detail/alm-005', + }, + { + id: 'msg-007', type: 'system', title: '系统公告 · OTA 升级策略调整', + content: '7 月 5 日起灰度比例默认 10%,详见运营公告', + createdAt: '2026-07-01 10:00:00', read: true, + }, +] + +const offline: OfflineStatus = { + enabled: true, + size: '12.3MB', + pendingCount: 2, + lastSyncTime: '2026-07-03 08:45:11', +} + +registerMocks([ + // 个人统计 + { + url: '/mp/ops/me/stats', + method: 'POST', + handler: (): ApiResponse => ok('查询成功', stats), + }, + // 消息列表 + { + url: '/mp/ops/me/messages', + method: 'POST', + handler: (ctx): ApiResponse => { + const { type } = getParams<{ type?: MessageType | '' }>(ctx.params) + const rows = type ? messages.filter((m) => m.type === type) : messages + return ok('查询成功', rows) + }, + }, + // 标记已读 + { + url: '/mp/ops/me/message-read', + method: 'POST', + handler: (ctx): ApiResponse => { + const { id } = getParams<{ id: string }>(ctx.params) + const target = messages.find((m) => m.id === id) + if (target) target.read = true + return ok('已标记已读', null) + }, + }, + // 全部已读 + { + url: '/mp/ops/me/message-read-all', + method: 'POST', + handler: (): ApiResponse => { + messages.forEach((m) => { m.read = true }) + return ok('已全部标记已读', null) + }, + }, + // 离线缓存状态 + { + url: '/mp/ops/me/offline-status', + method: 'GET', + handler: (): ApiResponse => ok('查询成功', offline), + }, + // 手动同步 + { + url: '/mp/ops/me/offline-sync', + method: 'POST', + handler: (): ApiResponse => { + offline.pendingCount = 0 + offline.lastSyncTime = new Date().toISOString().slice(0, 19).replace('T', ' ') + return ok('同步完成', null) + }, + }, + // 清除缓存 + { + url: '/mp/ops/me/offline-clear', + method: 'POST', + handler: (): ApiResponse => { + offline.size = '0MB' + offline.pendingCount = 0 + return ok('清除完成', null) + }, + }, +]) diff --git a/src/axios/handlers/miniprogram/ops/scan.ts b/src/axios/handlers/miniprogram/ops/scan.ts new file mode 100644 index 0000000..58bcdf5 --- /dev/null +++ b/src/axios/handlers/miniprogram/ops/scan.ts @@ -0,0 +1,272 @@ +/** + * 实施运维小程序 / 扫码 mock + * - 扫码识别设备(按编码查摘要) + * - 扫码历史 + * - 设备初始化 / 实施记录 / 维修上报 / OTA 升级 / 设备监测 + */ + +import { registerMocks } from '@axios/mockBus' +import type { ApiResponse, RequestParameter } from '@axios' +import type { + DeviceMonitor, + DeviceSummary, + ImplRecordForm, + InitForm, + MonitorRepairItem, + MonitorUpdateItem, + OtaTask, + OtaVersion, + RepairForm, + RunLogItem, +} from '@pages/miniprogram/ops/scan/types' + +const ok = (msg: string, data: T = null as T): ApiResponse => ({ + code: '00000', + msg, + data, +}) + +const getParams = >(params: RequestParameter): T => + (params ?? {}) as T + +/** 设备档案数据集(覆盖 5 大顶级分类 × 4 状态) */ +const deviceMap: Record = { + 'CAB-SH-001': { + id: 'dev-001', code: 'CAB-SH-001', name: '1号净菜柜(上海浦东)', + category: 'catering', model: 'NZG-200', + status: 'inUse', site: '上海浦东 · 张江食堂', tenant: 'CQ 能源集团', + firmwareVersion: 'V2.3.1', lastHeartbeat: '2026-07-03 09:30:11', + pendingWorkOrders: 1, isIot: true, dataDistributeEnabled: false, + }, + 'WATCH-BJ-014': { + id: 'dev-014', code: 'WATCH-BJ-014', name: '14号健康手表(北京朝阳)', + category: 'wearable', model: 'WATCH5-PRO', + status: 'inUse', site: '北京朝阳 · 朝阳体检站', tenant: '北京健康管理机构', + firmwareVersion: 'v1.0.4', lastHeartbeat: '2026-07-03 09:32:48', + pendingWorkOrders: 1, isIot: true, dataDistributeEnabled: true, + }, + 'TREADMILL-CQ-002': { + id: 'dev-022', code: 'TREADMILL-CQ-002', name: '2号跑步机(重庆渝中)', + category: 'fitness', model: 'TM-X1', + status: 'inUse', site: '重庆渝中 · 解放碑健身房', tenant: '重庆健身中心', + firmwareVersion: 'V1.5.2', lastHeartbeat: '2026-07-03 09:25:00', + pendingWorkOrders: 1, isIot: true, dataDistributeEnabled: true, + }, + 'BODYFAT-HZ-005': { + id: 'dev-035', code: 'BODYFAT-HZ-005', name: '5号体脂仪(杭州西湖)', + category: 'body', model: 'InBody-770', + status: 'inUse', site: '杭州西湖 · 西湖体检站', tenant: '杭州健康管理机构', + firmwareVersion: 'V3.0.1', lastHeartbeat: '2026-07-03 09:28:33', + pendingWorkOrders: 0, isIot: true, dataDistributeEnabled: true, + }, + 'CHECK-SZ-003': { + id: 'dev-048', code: 'CHECK-SZ-003', name: '3号体检一体机(深圳南山)', + category: 'health', model: 'ALL-IN-ONE-15', + status: 'inUse', site: '深圳南山 · 科技园体检站', tenant: '深圳健康管理机构', + firmwareVersion: 'V2.1.0', lastHeartbeat: '2026-07-03 09:20:11', + pendingWorkOrders: 1, isIot: true, dataDistributeEnabled: true, + }, + 'CAB-NEW-001': { + id: 'dev-091', code: 'CAB-NEW-001', name: '新装净菜柜(待激活)', + category: 'catering', model: 'NZG-200', + status: 'inactive', site: '上海浦东 · 张江食堂', tenant: 'CQ 能源集团', + firmwareVersion: '—', lastHeartbeat: '—', + pendingWorkOrders: 0, isIot: true, dataDistributeEnabled: false, + }, +} + +/** 扫码历史(最近 6 条) */ +const scanHistory = [ + { id: 'h-001', deviceCode: 'CAB-SH-001', deviceName: '1号净菜柜(上海浦东)', scannedAt: '2026-07-03 09:10:00', action: '查看档案' }, + { id: 'h-002', deviceCode: 'WATCH-BJ-014', deviceName: '14号健康手表(北京朝阳)', scannedAt: '2026-07-03 08:45:00', action: '维修上报' }, + { id: 'h-003', deviceCode: 'BODYFAT-HZ-005', deviceName: '5号体脂仪(杭州西湖)', scannedAt: '2026-07-02 17:30:00', action: '巡检打卡' }, + { id: 'h-004', deviceCode: 'CAB-NEW-001', deviceName: '新装净菜柜(待激活)', scannedAt: '2026-07-02 14:20:00', action: '设备初始化' }, + { id: 'h-005', deviceCode: 'TREADMILL-CQ-002', deviceName: '2号跑步机(重庆渝中)', scannedAt: '2026-07-02 10:15:00', action: '查看档案' }, + { id: 'h-006', deviceCode: 'CHECK-SZ-003', deviceName: '3号体检一体机(深圳南山)', scannedAt: '2026-07-01 16:40:00', action: 'OTA 升级' }, +] + +/** OTA 可用版本 */ +const otaVersionsMap: Record = { + 'WATCH-BJ-014': [ + { version: 'v1.1.0', releaseNote: '新增睡眠分析、修复心率异常告警', status: 'full', size: '4.2MB' }, + { version: 'v1.0.5', releaseNote: '修复蓝牙连接稳定性', status: 'gray', size: '2.1MB' }, + ], + 'CAB-SH-001': [ + { version: 'V2.4.0', releaseNote: '压缩机优化、温度校准算法升级', status: 'full', size: '8.5MB' }, + ], +} + +/** OTA 任务(按设备编码存) */ +const otaTaskMap: Record = {} + +/** 运行日志(按设备编码存) */ +const runLogsMap: Record = { + 'CAB-SH-001': [ + { id: 'log-1', time: '2026-07-03 09:30:11', event: '心跳上报', level: 'info' }, + { id: 'log-2', time: '2026-07-03 09:25:08', event: '自检告警:压缩机停转', level: 'error' }, + { id: 'log-3', time: '2026-07-03 09:00:00', event: '开机', level: 'info' }, + { id: 'log-4', time: '2026-07-03 08:30:00', event: '温度漂移 2.4℃', level: 'warn' }, + ], + 'WATCH-BJ-014': [ + { id: 'log-1', time: '2026-07-03 09:32:48', event: '心跳上报', level: 'info' }, + { id: 'log-2', time: '2026-07-03 09:15:00', event: 'OTA 升级失败已回滚', level: 'error' }, + { id: 'log-3', time: '2026-07-03 08:00:00', event: '开机', level: 'info' }, + ], +} + +/** 维修记录 */ +const repairsMap: Record = { + 'CAB-SH-001': [ + { id: 'rep-1', time: '2026-06-15 14:20:00', faultType: '硬件故障', repairMethod: '更换压缩机', repairman: '李工程师', workOrderId: 'WO20260615003' }, + { id: 'rep-2', time: '2026-05-20 10:30:00', faultType: '传感器故障', repairMethod: '校准温度传感器', repairman: '王工程师', workOrderId: 'WO20260520001' }, + ], + 'BODYFAT-HZ-005': [ + { id: 'rep-1', time: '2026-07-01 17:20:00', faultType: '电源故障', repairMethod: '更换电池', repairman: '赵工程师', workOrderId: 'WO20260701022' }, + ], +} + +/** 更新记录 */ +const updatesMap: Record = { + 'WATCH-BJ-014': [ + { id: 'upd-1', time: '2026-07-03 10:12:48', fromVersion: 'v1.0.4', toVersion: 'v1.1.0', result: 'rolledback', operator: '系统' }, + { id: 'upd-2', time: '2026-06-15 09:00:00', fromVersion: 'v1.0.3', toVersion: 'v1.0.4', result: 'success', operator: '李工程师' }, + ], +} + +registerMocks([ + // 扫码识别设备 + { + url: '/mp/ops/scan/scan', + method: 'GET', + handler: (ctx): ApiResponse => { + const { deviceCode } = getParams<{ deviceCode?: string }>(ctx.params) + const dev = deviceMap[deviceCode ?? ''] + if (!dev) return ok('未找到设备,请联系管理员', null) + return ok('识别成功', dev) + }, + }, + // 扫码历史 + { + url: '/mp/ops/scan/history', + method: 'GET', + handler: (): ApiResponse => ok('查询成功', scanHistory), + }, + // 设备初始化 + { + url: '/mp/ops/scan/init', + method: 'POST', + handler: (ctx): ApiResponse => { + const form = getParams(ctx.params) + const dev = deviceMap[form.deviceCode] + if (dev && form.result === 'success') { + dev.status = 'activated' + dev.firmwareVersion = form.firmwareBaseline + } + return ok('初始化上报成功', null) + }, + }, + // 实施记录上报 + { + url: '/mp/ops/scan/impl-record', + method: 'POST', + handler: (): ApiResponse => ok('实施记录上报成功', null), + }, + // 维修上报(无工单独立入口) + { + url: '/mp/ops/scan/repair', + method: 'POST', + handler: (ctx): ApiResponse => { + const _form = getParams(ctx.params) + void _form + return ok('维修上报成功', null) + }, + }, + // 可用 OTA 版本 + { + url: '/mp/ops/scan/ota-versions', + method: 'GET', + handler: (ctx): ApiResponse => { + const { deviceCode } = getParams<{ deviceCode?: string }>(ctx.params) + return ok('查询成功', otaVersionsMap[deviceCode ?? ''] ?? []) + }, + }, + // 下发 OTA 任务 + { + url: '/mp/ops/scan/ota-push', + method: 'POST', + handler: (ctx): ApiResponse => { + const { deviceCode, targetVersion } = getParams<{ deviceCode: string; targetVersion: string }>(ctx.params) + const dev = deviceMap[deviceCode] + const task: OtaTask = { + id: `ota-${Date.now()}`, + deviceCode, + currentVersion: dev?.firmwareVersion ?? '—', + targetVersion, + progress: 0, + status: 'upgrading', + failReason: '', + createdAt: new Date().toISOString().slice(0, 19).replace('T', ' '), + } + otaTaskMap[deviceCode] = task + return ok('OTA 任务下发成功', task) + }, + }, + // 查询 OTA 任务进度 + { + url: '/mp/ops/scan/ota-task', + method: 'GET', + handler: (ctx): ApiResponse => { + const { deviceCode } = getParams<{ deviceCode?: string }>(ctx.params) + const task = otaTaskMap[deviceCode ?? ''] + if (task) { + // 模拟进度推进 + if (task.status === 'upgrading' && task.progress < 100) { + task.progress = Math.min(100, task.progress + 25) + if (task.progress >= 100) { + task.status = 'success' + const dev = deviceMap[deviceCode!] + if (dev) dev.firmwareVersion = task.targetVersion + } + } + } + return ok('查询成功', task ?? null) + }, + }, + // OTA 回滚 + { + url: '/mp/ops/scan/ota-rollback', + method: 'POST', + handler: (ctx): ApiResponse => { + const { deviceCode } = getParams<{ deviceCode: string }>(ctx.params) + const task = otaTaskMap[deviceCode] + if (task) { + task.status = 'rolledback' + const dev = deviceMap[deviceCode] + if (dev) dev.firmwareVersion = task.currentVersion + } + return ok('回滚成功', null) + }, + }, + // 设备监测详情 + { + url: '/mp/ops/scan/monitor', + method: 'GET', + handler: (ctx): ApiResponse => { + const { deviceCode } = getParams<{ deviceCode?: string }>(ctx.params) + const dev = deviceMap[deviceCode ?? ''] + if (!dev) return ok('设备不存在', null) + const monitor: DeviceMonitor = { + summary: dev, + runLogs: runLogsMap[deviceCode!] ?? [ + { id: 'log-default', time: dev.lastHeartbeat, event: '心跳上报', level: 'info' }, + ], + alarms: dev.pendingWorkOrders > 0 + ? [{ id: 'alm-link', content: '该设备有告警,点击查看', level: 'warn', triggerTime: dev.lastHeartbeat }] + : [], + repairs: repairsMap[deviceCode!] ?? [], + updates: updatesMap[deviceCode!] ?? [], + } + return ok('查询成功', monitor) + }, + }, +]) diff --git a/src/axios/handlers/miniprogram/ops/workOrder.ts b/src/axios/handlers/miniprogram/ops/workOrder.ts new file mode 100644 index 0000000..04ae544 --- /dev/null +++ b/src/axios/handlers/miniprogram/ops/workOrder.ts @@ -0,0 +1,197 @@ +/** + * 实施运维小程序 / 工单 mock + * - 列表(按状态分段,类型筛选) + * - 详情(含客户报障附件 + 处理时间线) + * - 接单 / 关单 / 提交维修上报 + */ + +import { registerMocks } from '@axios/mockBus' +import type { ApiResponse, RequestParameter } from '@axios' +import type { + RepairReportForm, + WorkOrderDetail, + WorkOrderItem, + WorkOrderListParams, + WorkOrderStatus, + WorkOrderType, +} from '@pages/miniprogram/ops/workOrder/types' + +const ok = (msg: string, data: T = null as T): ApiResponse => ({ + code: '00000', + msg, + data, +}) + +const getParams = >(params: RequestParameter): T => + (params ?? {}) as T + +const workOrders: WorkOrderItem[] = [ + { + id: 'wo-001', code: 'WO20260703001', title: '净菜柜压缩机故障维修', type: 'repair', + status: 'pending', deviceCode: 'CAB-SH-001', deviceName: '1号净菜柜(上海浦东)', + site: '上海浦东 · 张江食堂', tenant: 'CQ 能源集团', + description: '客户报修:制冷效果差,压缩机异响', + slaDeadline: '2026-07-03 18:00:00', createdAt: '2026-07-03 08:30:00', + assignee: '李工程师', overSla: false, + }, + { + id: 'wo-002', code: 'WO20260703002', title: '健康手表 OTA 失败处置', type: 'selfCheck', + status: 'processing', deviceCode: 'WATCH-BJ-014', deviceName: '14号健康手表(北京朝阳)', + site: '北京朝阳 · 朝阳体检站', tenant: '北京健康管理机构', + description: 'OTA 升级失败已自动回滚,需现场排查', + slaDeadline: '2026-07-03 20:00:00', createdAt: '2026-07-03 10:15:00', + assignee: '李工程师', overSla: false, + }, + { + id: 'wo-003', code: 'WO20260702018', title: '净菜柜货道错位复位', type: 'inspection', + status: 'pending', deviceCode: 'CAB-GZ-007', deviceName: '7号净菜柜(广州天河)', + site: '广州天河 · 天银食堂', tenant: '广州餐饮集团', + description: '巡检发现:货道 5 错位,需现场复位', + slaDeadline: '2026-07-03 12:00:00', createdAt: '2026-07-02 16:30:00', + assignee: '王工程师', overSla: true, + }, + { + id: 'wo-004', code: 'WO20260702015', title: '体检一体机离线排查', type: 'sla', + status: 'processing', deviceCode: 'CHECK-SZ-003', deviceName: '3号体检一体机(深圳南山)', + site: '深圳南山 · 科技园体检站', tenant: '深圳健康管理机构', + description: '连续 3 次心跳未上报,疑似网络故障', + slaDeadline: '2026-07-02 18:00:00', createdAt: '2026-07-02 14:05:00', + assignee: '赵工程师', overSla: true, + }, + { + id: 'wo-005', code: 'WO20260702009', title: '数据分发死信重投', type: 'repair', + status: 'done', deviceCode: 'CAB-SH-002', deviceName: '2号净菜柜(上海闵行)', + site: '上海闵行 · 莘庄食堂', tenant: 'CQ 能源集团', + description: '营养系统订阅 4 次重试失败,已人工重投成功', + slaDeadline: '2026-07-02 16:00:00', createdAt: '2026-07-02 11:48:00', + assignee: '李工程师', overSla: false, + }, + { + id: 'wo-006', code: 'WO20260701022', title: '体脂仪电池更换', type: 'repair', + status: 'done', deviceCode: 'BODYFAT-HZ-005', deviceName: '5号体脂仪(杭州西湖)', + site: '杭州西湖 · 西湖体检站', tenant: '杭州健康管理机构', + description: '电池电量低于 15%,已更换', + slaDeadline: '2026-07-01 18:00:00', createdAt: '2026-07-01 17:20:00', + assignee: '赵工程师', overSla: false, + }, + { + id: 'wo-007', code: 'WO20260703005', title: '跑步机过载维修', type: 'repair', + status: 'pending', deviceCode: 'TREADMILL-CQ-002', deviceName: '2号跑步机(重庆渝中)', + site: '重庆渝中 · 解放碑健身房', tenant: '重庆健身中心', + description: '连续 10 分钟过载,疑似电机故障', + slaDeadline: '2026-07-03 22:00:00', createdAt: '2026-07-03 08:50:00', + assignee: '李工程师', overSla: false, + }, + { + id: 'wo-008', code: 'WO20260703008', title: '净菜柜温度校准', type: 'selfCheck', + status: 'processing', deviceCode: 'CAB-BJ-003', deviceName: '3号净菜柜(北京海淀)', + site: '北京海淀 · 中关村食堂', tenant: '北京餐饮集团', + description: '温度传感器读数漂移 2.4℃,需校准', + slaDeadline: '2026-07-04 12:00:00', createdAt: '2026-07-01 09:15:00', + assignee: '王工程师', overSla: false, + }, +] + +const detailMap: Record> = { + 'wo-001': { + faultImages: ['https://picsum.photos/200/150?random=1', 'https://picsum.photos/200/150?random=2'], + customerVoice: '机器一直嗡嗡响,东西放进去不凉', + processLog: [ + { time: '2026-07-03 08:30:00', action: '客户报修', operator: '客户' }, + { time: '2026-07-03 08:35:00', action: '系统创建工单', operator: '系统' }, + { time: '2026-07-03 09:00:00', action: '派单给李工程师', operator: '系统' }, + ], + }, + 'wo-002': { + faultImages: [], + processLog: [ + { time: '2026-07-03 10:12:48', action: 'OTA 升级失败自动告警', operator: '系统' }, + { time: '2026-07-03 10:13:30', action: '自动回滚到 v1.0.4', operator: '系统' }, + { time: '2026-07-03 10:20:00', action: '生成工单并派单', operator: '系统' }, + { time: '2026-07-03 10:25:00', action: '李工程师接单', operator: '李工程师' }, + ], + }, +} + +const filterByStatus = (rows: WorkOrderItem[], status: WorkOrderStatus): WorkOrderItem[] => + rows.filter((r) => r.status === status) + +const filterByType = (rows: WorkOrderItem[], type: WorkOrderType | ''): WorkOrderItem[] => + type ? rows.filter((r) => r.type === type) : rows + +const filterByKeyword = (rows: WorkOrderItem[], kw: string): WorkOrderItem[] => { + if (!kw) return rows + const k = kw.toLowerCase() + return rows.filter((r) => + r.code.toLowerCase().includes(k) || + r.title.toLowerCase().includes(k) || + r.deviceCode.toLowerCase().includes(k) || + r.deviceName.toLowerCase().includes(k), + ) +} + +registerMocks([ + // 工单列表 + { + url: '/mp/ops/work-order/list', + method: 'POST', + handler: (ctx): ApiResponse => { + const p = getParams(ctx.params) + let rows = filterByStatus(workOrders, p.status) + rows = filterByType(rows, p.type ?? '') + rows = filterByKeyword(rows, p.keyword ?? '') + return ok('查询成功', rows) + }, + }, + // 工单详情 + { + url: '/mp/ops/work-order/detail', + method: 'GET', + handler: (ctx): ApiResponse => { + const { id } = getParams<{ id?: string }>(ctx.params) + const base = workOrders.find((w) => w.id === id) + if (!base) return ok('工单不存在', null) + const ext = detailMap[id!] ?? { + faultImages: [], + processLog: [{ time: base.createdAt, action: '工单创建', operator: '系统' }], + } + return ok('查询成功', { ...base, ...ext } as WorkOrderDetail) + }, + }, + // 接单 + { + url: '/mp/ops/work-order/accept', + method: 'POST', + handler: (ctx): ApiResponse => { + const { id } = getParams<{ id?: string }>(ctx.params) + const target = workOrders.find((w) => w.id === id) + if (target) { + target.status = 'processing' + target.assignee = '当前用户' + } + return ok('接单成功', null) + }, + }, + // 关单 + { + url: '/mp/ops/work-order/close', + method: 'POST', + handler: (ctx): ApiResponse => { + const { id } = getParams<{ id?: string }>(ctx.params) + const target = workOrders.find((w) => w.id === id) + if (target) target.status = 'done' + return ok('关单成功', null) + }, + }, + // 提交维修上报 + { + url: '/mp/ops/work-order/repair-report', + method: 'POST', + handler: (ctx): ApiResponse => { + const _form = getParams(ctx.params) + // 原型阶段仅记录日志,不实际更新工单状态 + void _form + return ok('维修上报成功', null) + }, + }, +]) diff --git a/src/axios/handlers/miniprogram/ops/workbench.ts b/src/axios/handlers/miniprogram/ops/workbench.ts new file mode 100644 index 0000000..137e96c --- /dev/null +++ b/src/axios/handlers/miniprogram/ops/workbench.ts @@ -0,0 +1,233 @@ +/** + * 实施运维小程序 / 工作台 + 告警 mock + * - 今日待办 5 卡统计 + * - 本周统计 3 卡 + * - 告警列表 + 详情 + 处置 + * + * 与 PC 端告警管理(device-center/ops/alarm.ts)数据风格一致但字段精简,符合小程序端只读查看场景。 + */ + +import { registerMocks } from '@axios/mockBus' +import type { ApiResponse, RequestParameter } from '@axios' +import type { + AlarmDetail, + AlarmHandleAction, + AlarmItem, + AlarmLevel, + AlarmSource, + AlarmStatus, + TodoStats, + WeekStats, +} from '@pages/miniprogram/ops/workbench/types' + +interface AlarmListParams { + level?: AlarmLevel | '' + source?: AlarmSource | '' + status?: AlarmStatus | '' + keyword?: string +} + +interface AlarmHandleBody { + id: string + action: AlarmHandleAction + reason: string +} + +const ok = (msg: string, data: T = null as T): ApiResponse => ({ + code: '00000', + msg, + data, +}) + +const getParams = >(params: RequestParameter): T => + (params ?? {}) as T + +/** 告警数据集(8 条,覆盖 6 来源 × 3 级别 × 4 状态) */ +const alarmList: AlarmItem[] = [ + { + id: 'alm-001', alarmCode: 'ALM20260703001', source: 'selfCheck', level: 'critical', + deviceCode: 'CAB-SH-001', deviceName: '1号净菜柜(上海浦东)', + content: '自检失败:压缩机停转 3 次/小时,疑似硬件故障', + triggerTime: '2026-07-03 09:24:11', status: 'pending', handler: null, + }, + { + id: 'alm-002', alarmCode: 'ALM20260703002', source: 'ota', level: 'warn', + deviceCode: 'WATCH-BJ-014', deviceName: '14号健康手表(北京朝阳)', + content: 'OTA 升级失败:v1.0.4 → v1.1.0 失败已回滚', + triggerTime: '2026-07-03 10:12:48', status: 'processing', handler: '李工程师', + }, + { + id: 'alm-003', alarmCode: 'ALM20260702018', source: 'inspection', level: 'warn', + deviceCode: 'CAB-GZ-007', deviceName: '7号净菜柜(广州天河)', + content: '巡检异常:货道 5 错位,建议现场复位', + triggerTime: '2026-07-02 16:30:22', status: 'workorder', handler: '王工程师', + }, + { + id: 'alm-004', alarmCode: 'ALM20260702015', source: 'sla', level: 'critical', + deviceCode: 'CHECK-SZ-003', deviceName: '3号体检一体机(深圳南山)', + content: 'SLA 超时:连续 3 次心跳未上报,离线 25 分钟', + triggerTime: '2026-07-02 14:05:09', status: 'ignored', handler: '赵工程师', + }, + { + id: 'alm-005', alarmCode: 'ALM20260702009', source: 'dataDistribute', level: 'warn', + deviceCode: 'CAB-SH-002', deviceName: '2号净菜柜(上海闵行)', + content: '数据分发失败:营养系统订阅 4 次重试失败', + triggerTime: '2026-07-02 11:48:33', status: 'workorder', handler: '李工程师', + }, + { + id: 'alm-006', alarmCode: 'ALM20260701022', source: 'runLog', level: 'info', + deviceCode: 'BODYFAT-HZ-005', deviceName: '5号体脂仪(杭州西湖)', + content: '运行日志:电池电量低于 15%,建议安排更换', + triggerTime: '2026-07-01 17:20:00', status: 'ignored', handler: null, + }, + { + id: 'alm-007', alarmCode: 'ALM20260701008', source: 'selfCheck', level: 'warn', + deviceCode: 'CAB-BJ-003', deviceName: '3号净菜柜(北京海淀)', + content: '自检告警:温度传感器读数漂移 2.4℃,建议校准', + triggerTime: '2026-07-01 09:15:42', status: 'workorder', handler: '王工程师', + }, + { + id: 'alm-008', alarmCode: 'ALM20260703005', source: 'runLog', level: 'critical', + deviceCode: 'TREADMILL-CQ-002', deviceName: '2号跑步机(重庆渝中)', + content: '运行异常:连续 10 分钟过载,疑似电机故障', + triggerTime: '2026-07-03 08:50:17', status: 'pending', handler: null, + }, +] + +/** 告警详情扩展信息 */ +const alarmDetailMap: Record> = { + 'alm-001': { + deviceModel: '智养净菜柜 V2 / 型号 NZG-200', + site: '上海浦东 · 张江食堂', + tenant: 'CQ 能源集团', + processTimeline: [ + { time: '2026-07-03 09:24:11', action: '告警触发(自检失败)', operator: '系统' }, + { time: '2026-07-03 09:25:02', action: '推送至运维工程师手机', operator: '系统' }, + ], + }, + 'alm-002': { + deviceModel: '华为 Watch 5 / 型号 WATCH5-PRO', + site: '北京朝阳 · 朝阳体检站', + tenant: '北京健康管理机构', + processTimeline: [ + { time: '2026-07-03 10:12:48', action: 'OTA 升级失败', operator: '系统' }, + { time: '2026-07-03 10:13:30', action: '自动回滚到 v1.0.4', operator: '系统' }, + { time: '2026-07-03 10:20:00', action: '工程师接单处理', operator: '李工程师' }, + ], + }, +} + +const filterAlarms = (rows: AlarmItem[], params: AlarmListParams): AlarmItem[] => + rows.filter((r) => { + if (params.level && r.level !== params.level) return false + if (params.source && r.source !== params.source) return false + if (params.status && r.status !== params.status) return false + if (params.keyword) { + const kw = params.keyword.toLowerCase() + const hit = + r.deviceCode.toLowerCase().includes(kw) || + r.deviceName.toLowerCase().includes(kw) || + r.content.toLowerCase().includes(kw) || + r.alarmCode.toLowerCase().includes(kw) + if (!hit) return false + } + return true + }) + +const todoStats: TodoStats = { + pendingWorkOrders: 5, + overSlaOrders: 1, + todayInspections: 8, + inspectionDone: 3, + todayOtaTasks: 2, + todayInitDevices: 4, + pendingAlarms: 6, + criticalAlarms: 2, +} + +const weekStats: WeekStats = { + weekOrders: 23, + avgProcessHours: 4.5, + weekInspectionRate: '87.5%', + weekInspectionAbnormalRate: '12.5%', + monthInstallCount: 18, +} + +registerMocks([ + // 工作台今日待办统计 + { + url: '/mp/ops/workbench/todo-stats', + method: 'POST', + handler: (): ApiResponse => ok('查询成功', todoStats), + }, + // 工作台本周统计 + { + url: '/mp/ops/workbench/week-stats', + method: 'POST', + handler: (): ApiResponse => ok('查询成功', weekStats), + }, + // 告警列表 + { + url: '/mp/ops/alarm/list', + method: 'POST', + handler: (ctx): ApiResponse => { + const params = getParams(ctx.params) + return ok('查询成功', filterAlarms(alarmList, params)) + }, + }, + // 告警详情 + { + url: '/mp/ops/alarm/detail', + method: 'GET', + handler: (ctx): ApiResponse => { + const { id } = getParams<{ id?: string }>(ctx.params) + const base = alarmList.find((a) => a.id === id) + if (!base) return ok('告警不存在', null) + const ext = alarmDetailMap[id!] ?? { + deviceModel: '—', + site: '—', + tenant: '—', + processTimeline: [{ time: base.triggerTime, action: '告警触发', operator: '系统' }], + } + return ok('查询成功', { ...base, ...ext } as AlarmDetail) + }, + }, + // 告警处置 + { + url: '/mp/ops/alarm/handle', + method: 'POST', + handler: (ctx): ApiResponse => { + const { id, action, reason } = getParams(ctx.params) + const target = alarmList.find((a) => a.id === id) + if (!target) return ok('告警不存在', null) + const actionText: Record = { + toWorkOrder: '转工单', + ignore: '忽略', + resolve: '标记已处理', + } + const nextStatus: Record = { + toWorkOrder: 'workorder', + ignore: 'ignored', + resolve: 'processing', + } + target.status = nextStatus[action] + target.handler = '当前用户' + // 处置时间线追加到详情扩展信息(详情查询时合并展示) + const ext = alarmDetailMap[id!] ?? { + deviceModel: '—', + site: '—', + tenant: '—', + processTimeline: [{ time: target.triggerTime, action: '告警触发', operator: '系统' }], + } + ext.processTimeline = (ext.processTimeline ?? []).concat([ + { + time: new Date().toISOString().slice(0, 19).replace('T', ' '), + action: `${actionText[action]}:${reason || '无说明'}`, + operator: '当前用户', + }, + ]) + alarmDetailMap[id!] = ext + return ok('处置成功', null) + }, + }, +]) diff --git a/src/docs/device/设备管理服务器功能现状.md b/src/docs/device/设备管理服务器功能现状.md index d502bfb..a89ff3f 100644 --- a/src/docs/device/设备管理服务器功能现状.md +++ b/src/docs/device/设备管理服务器功能现状.md @@ -4,7 +4,7 @@ > > 老系统参考后台地址:https://device.shuziweidao.com/canteen/home,登录账号:admin,登录密码:Q4Zz64285886@629 > -> _Last updated: 2026-07-02_ +> _Last updated: 2026-07-03_ --- @@ -717,18 +717,25 @@ ``` 实施运维小程序 │ -├─ 工作台 今日待办、统计卡、消息入口 +├─ 工作台 今日待办(含待处理告警卡)、统计卡、快捷入口、告警入口 +│ └─ 子页:告警列表 / 告警详情 │ ├─ 工单 工单列表 + 详情 + 处理 + 关单上报 +│ └─ 子页:工单详情 / 维修上报 │ ├─ ⚡ 扫码 中间凸起按钮,扫设备后弹"场景操作菜单" +│ └─ 子页:场景菜单 / 设备初始化 / 实施记录上报 / 维修上报 / OTA 升级 / 设备监测 │ ├─ 巡检 巡检计划 + 任务打卡 + 结果上报 +│ └─ 子页:巡检任务详情 │ └─ 我的 个人统计、消息中心、离线缓存设置 + └─ 子页:消息中心 ``` > **设计理由**:现场最高频动作是"扫码识别设备",把扫码独立成 Tab 可直达,扫完按设备状态动态弹出可执行操作,符合一线人员肌肉记忆。其余 4 个 Tab 分别承载任务接收(工作台)、被动处置(工单)、主动作业(巡检)、个人空间(我的)。 +> +> **告警不独立成 Tab 的原因**:告警不是高频主动操作(运维人员一天查看数次即可),独立成 Tab 会挤占底部 5 个位置;将入口放在工作台首页"待处理告警卡"+ 我的 → 消息中心推送兜底,既保证可见性又不破坏 5 Tab 节奏。 ### 8.3 扫码后场景菜单(核心交互) @@ -737,28 +744,29 @@ | 设备状态 | 弹出的可选操作 | |---------|--------------| | 未激活(仅入库) | 「设备初始化」 | -| 已激活未装机 | 「实施记录上报」「设备初始化(重做)」 | -| 在用且无待处理工单 | 「巡检打卡」「维修上报」「OTA 升级」「查看档案」 | +| 已激活未装机 | 「实施记录上报」「设备初始化(重做)」「设备监测」 | +| 在用且无待处理工单 | 「巡检打卡」「维修上报」「OTA 升级」「设备监测」 | | 在用且有待处理工单 | 顶部红点提示「该设备有 N 条待处理工单」+ 上述操作 | -| 已报废 / 已退租 | 仅「查看档案」 | +| 已报废 / 已退租 | 仅「设备监测」(只读) | **操作菜单项**(共 6 项,按状态过滤显示): -1. **设备初始化**:扫码 → 烧录证书 / 预置场所配置 / 固件基线版本 → 上报初始化结果 +1. **设备初始化**:扫码 → 烧录证书 / 预置场所配置 / 固件基线版本 / 初始化结果上报(成功 / 失败 + 失败原因)→ 失败可重做 2. **实施记录上报**:扫码 → 选实施类型(新装 / 迁移 / 拆除 / 复位)→ 拍照 → 客户签收 → 提交 3. **巡检打卡**:扫码确认到点 → 填报巡检项 → 异常项自动转工单 -4. **维修上报**:扫码 → 关联工单(如有)→ 填故障类型 / 维修方式 / 工时 / 备件 / 图片 → 提交 +4. **维修上报**:扫码 → **双入口**(关联工单上报 / 无工单独立新增)→ 填故障类型 / 维修方式 / 工时 / 备件 / 图片 → 提交 5. **OTA 升级**:扫码 → 选目标版本 → 下发升级任务 → 监控进度 → 失败可回滚 -6. **查看档案**:扫码 → 设备台账卡片 + 近期运行日志 + 历史维修履历(聚合自设备监测详情页多 Tab)+ 当前租约状态 +6. **设备监测**:扫码 → 多 Tab 简化版(概览 / 运行日志 / 告警 / 维修记录 / 更新记录),参考后台"设备运维 → 设备监测.详情页"做只读简化 ### 8.4 各 Tab 模块详细说明 #### 8.4.1 工作台 -**今日待办**: +**今日待办**(5 张卡,红点表示需立即处置): - 今日待处理工单数(含超 SLA 标红) - 今日巡检任务数(含已完成 / 未完成) - 今日 OTA 任务数 - 今日待初始化设备数 +- **待处理告警数**(含严重告警数标红,点击进入告警列表子页) **统计卡**: - 本周处理工单数 / 平均处理时长 @@ -767,6 +775,8 @@ **快捷入口**:扫码、消息中心、离线任务、个人统计 +**告警入口**:工作台首页"待处理告警卡"点击 → 告警列表子页(详见 §8.4.6);严重告警同时通过微信订阅消息推送 + 我的 → 消息中心兜底 + #### 8.4.2 工单 **工单列表**: @@ -795,7 +805,41 @@ **异常处理**: - 二维码无法识别 → 手动输入设备编码 - 设备不存在 → 提示"未找到设备,请联系管理员" -- 设备已报废 → 仅显示档案查看 +- 设备已报废 → 仅显示设备监测(只读) + +**子页面 1:场景操作菜单(scene-menu)** +- 底部操作面板,按 §8.3 设备状态过滤显示 6 项操作 +- 顶部展示设备摘要卡(编码 / 型号 / 场所 / 状态 / 待处理工单数红点) + +**子页面 2:设备初始化(init)** — 现场激活设备 +- 表单字段:设备编码(扫码带入,只读)、绑定密钥、证书烧录状态、预置场所配置(默认带入设备档案的所属场所,可改)、固件基线版本(下拉选择)、初始化结果(成功 / 失败)、失败原因(失败时必填)、操作人、操作时间 +- 操作:提交上报、保存草稿(离线缓存) +- 与 PC 端衔接:上报后回写"设备运维 → 设备初始化"任务,设备状态从"未激活"流转为"已激活未装机" + +**子页面 3:实施记录上报(impl-record)** +- 表单字段:设备编码、实施类型(新装 / 迁移 / 拆除 / 复位)、实施前后状态对比、实施图片(最多 9 张)、客户签收(电子签名 / 拍照)、实施人、实施时间 +- 操作:提交、保存草稿 + +**子页面 4:维修上报(repair)** — 双入口 +- **入口 A 关联工单**:从工单详情 → 关单前维修上报进入,工单 ID 自动带入不可改 +- **入口 B 无工单独立新增**:从扫码场景菜单进入,工单 ID 可选填(如现场临时维修未走工单流程) +- 表单字段:设备编码、关联工单 ID(可选)、故障类型、故障描述、维修方式、维修图片(最多 9 张)、工时(小时)、备件消耗(备件名 + 数量,可多行)、维修人、维修时间 +- 操作:提交、保存草稿 +- 与 PC 端衔接:上报后回写"设备运维 → 设备监测.详情.维修记录 Tab";若有关联工单则同时触发工单关单流程 + +**子页面 5:OTA 升级(ota)** +- 流程:选目标版本(从版本管理拉取该设备型号的可用版本)→ 下发升级任务 → 监控进度(进度条 + 状态)→ 结果(成功 / 失败 / 已回滚)→ 失败可一键回滚 +- 字段:设备编码、当前版本、目标版本、升级计划时间、升级进度、结果状态、失败原因 +- 与 PC 端衔接:升级记录回写"设备运维 → 设备监测.详情.更新记录 Tab";版本档案来自"设备运维 → 版本管理" + +**子页面 6:设备监测(monitor)** — 多 Tab 简化版(参考后台设备运维 → 设备监测.详情页) +- **Tab 1 概览**:设备基础卡(编码 / 型号 / 所属租户 / 场所 / 运行状态 / 最近心跳时间 / 固件版本 / 是否 IOT / 是否启用数据分发) +- **Tab 2 运行日志**:最近 50 条运行埋点(开机关机 / 心跳 / 关键事件流水),按时间倒序 +- **Tab 3 告警**:该设备当前告警列表(未处理 / 处理中状态),点击进告警详情子页(§8.4.6) +- **Tab 4 维修记录**:该设备历史维修档案(时间 / 故障类型 / 维修方式 / 维修人),只读 +- **Tab 5 更新记录**:该设备 OTA 升级历史(时间 / 目标版本 / 结果),只读 +- 不放实施记录 / 自检记录 / 巡检记录 3 个 Tab:小程序端不做历史台账查看,避免过载 +- 与 PC 端关系:只读聚合,所有数据来自 PC 端"设备资产 → 设备管理"+"设备运维 → 设备监测.详情页"多 Tab #### 8.4.4 巡检 @@ -817,12 +861,16 @@ - 本月工单处理数 / 巡检完成数 / 装机数 / OTA 升级数 - 工单平均处理时长、SLA 达成率 -**消息中心**: +**消息中心**(子页面 message-center.vue): - 工单派单通知 - 巡检任务下发通知 - OTA 任务下发通知 - OTA 失败告警 +- 巡检逾期告警 +- 自检异常告警 +- 严重告警推送(与工作台告警入口互补,作为推送兜底) - 系统公告 +- 按类型筛选 + 全部已读操作 **离线缓存设置**: - 开关:启用离线缓存(默认开) @@ -832,6 +880,24 @@ **设置**:账号信息、修改密码、退出登录 +#### 8.4.6 告警管理(挂工作台下) + +> 告警入口在工作台首页"待处理告警卡",子页面独立组织。告警作为运维人员被动接收的异常事件视图,与工单(主动处置)互补。 + +**告警列表(alarm-list)**: +- 字段:告警 ID、告警来源、告警级别、设备编码、告警内容、触发时间、状态 +- 筛选:按级别(提示 / 警告 / 严重)、来源(运行日志 / 自检 / 巡检 / SLA 超时 / OTA 失败 / 数据分发失败)、状态(未处理 / 处理中 / 已忽略 / 已转工单)、时间范围 +- 顶部统计卡:今日新增告警 / 待处理告警 / 严重告警数 +- 严重告警在列表项标红 + 顶部红点 +- 操作:点击进入告警详情 + +**告警详情(alarm-detail)**: +- 字段:告警 ID、来源、级别、设备编码(点击可跳转设备监测子页)、告警内容、触发时间、当前状态、处理人、处理记录时间线 +- 操作:转工单(自动带告警上下文,跳转工单详情)、忽略(必填忽略原因)、标记已处理(必填处理说明)、查看设备监测(跳转 §8.4.3 子页 6) +- 与 PC 端衔接:所有处置操作回写"设备运维 → 告警管理";转工单后告警状态变为"已转工单",工单 ID 回显 + +**与 PC 端映射**:PC 端"设备运维 → 告警管理"。小程序端只做"查看 + 转工单 + 简单处置",不替代 PC 端的批量处理 / 复杂筛选 / 业务日志视图。 + ### 8.5 消息推送设计 **推送通道**:微信小程序订阅消息(按微信能力,需用户授权)+ 应用内消息中心 @@ -846,6 +912,8 @@ | OTA 失败告警 | 升级任务失败 | 设备编码、失败原因 | | 巡检逾期告警 | 巡检任务逾期 | 任务名、逾期时长 | | 自检异常告警 | 设备自检异常 | 设备编码、异常项 | +| **严重告警** 🆕 | 告警级别 = 严重 | 告警 ID、设备编码、告警内容、来源 | +| **数据分发死信告警** 🆕 | 死信累积超阈值 | 设备编码、下游系统、累积条数 | **用户授权策略**: - 首次登录引导用户一次性授权所有订阅类型 @@ -862,6 +930,7 @@ - 待上报的实施记录 - 待上报的初始化结果 - 已扫码但未提交的 OTA 升级确认 +- 告警处置结果(忽略 / 标记已处理的离线提交) **不缓存的数据**: - 工单列表(实时拉取,避免误派单) @@ -881,16 +950,18 @@ | 小程序模块 | 小程序动作 | PC 端对应位置 | 数据流向 | |----------|----------|------------|---------| -| 工作台 | 查看今日待办 | 工作台 → 实时监控大屏(个人视角) | PC → 小程序(只读) | +| 工作台 | 查看今日待办(含待处理告警卡) | 工作台 → 实时监控大屏(个人视角)+ 设备运维 → 告警管理(告警数) | PC → 小程序(只读) | +| 工作台 → 告警列表 | 查看 / 筛选告警 | 设备运维 → 告警管理 | PC → 小程序(只读) | +| 工作台 → 告警详情 | 转工单 / 忽略 / 标记已处理 | 设备运维 → 告警管理 + 工单管理 | 小程序 → PC | | 工单 | 接单 / 处理 / 关单上报 | 设备运维 → 工单管理 | 小程序 → PC | | 工单(关单时) | 上报维修记录 | 设备运维 → 设备监测.详情.维修记录 Tab | 小程序 → PC | | 扫码 → 设备初始化 | 烧录证书 / 预置配置 / 上报 | 设备运维 → 设备初始化 | 小程序 → PC | | 扫码 → 实施记录上报 | 装机打卡 / 拍照 / 签收 | 设备运维 → 设备监测.详情.实施记录 Tab | 小程序 → PC | -| 扫码 → 维修上报 | 关联工单 + 填写维修信息 | 设备运维 → 设备监测.详情.维修记录 Tab | 小程序 → PC | +| 扫码 → 维修上报(双入口) | 关联工单 / 无工单独立新增维修记录 | 设备运维 → 设备监测.详情.维修记录 Tab + 工单管理(关单) | 小程序 → PC | | 扫码 → OTA 升级 | 下发 / 监控 / 回滚 | 设备运维 → 设备监测.详情.更新记录 Tab + 版本管理 | 双向 | -| 扫码 → 查看档案 | 只读查看 | 设备资产 → 设备管理 + 设备运维 → 设备监测.详情页(多 Tab 聚合) | PC → 小程序(只读) | +| 扫码 → 设备监测 | 只读查看设备多视角(概览/日志/告警/维修/更新) | 设备资产 → 设备管理 + 设备运维 → 设备监测.详情页(多 Tab 聚合) | PC → 小程序(只读) | | 巡检 | 打卡 / 填报 / 异常转工单 | 设备运维 → 设备监测.详情.巡检记录 Tab | 小程序 → PC | -| 我的 → 消息中心 | 接收派单 / 告警 | 设备运维 → 告警管理(告警源) | PC → 小程序 | +| 我的 → 消息中心 | 接收派单 / 告警推送 | 设备运维 → 告警管理(告警源)+ 工单管理(派单) | PC → 小程序 | ### 8.8 关键设计原则 @@ -900,3 +971,5 @@ 4. **不做管理类操作**:订单 / 租约 / 客户 / 系统配置等管理类功能不进小程序,保持现场作业专注 5. **PC 端可替代**:所有小程序操作在 PC 端也能完成(用于复核 / 补录 / 数据修正),小程序不是唯一入口 6. **权限隔离**:仅实施运维工程师角色可登录小程序,管理员 / 客户经理 / 销售等角色无小程序权限 +7. **告警与工单互补**:告警是被动接收的异常事件视图(在工作台入口),工单是主动处置的流转中心;告警可一键转工单衔接流转,避免在两个域间重复操作 +8. **设备监测只读简化**:小程序端不做全量历史台账查看(实施/自检/巡检记录 3 Tab 不放),只保留运维现场最高频的 5 个视角(概览/运行日志/告警/维修记录/更新记录),完整版在 PC 端 diff --git a/src/meta/nav.ts b/src/meta/nav.ts index 6ec46b6..83d8d6c 100644 --- a/src/meta/nav.ts +++ b/src/meta/nav.ts @@ -951,6 +951,18 @@ export const miniprograms: NavMiniprogram[] = [ component: () => import('@pages/miniprogram/ops/workbench/Home.vue'), status: 'draft', }, + subPages: [ + { + key: 'mp-ops-alarm-list', label: '告警列表', path: 'alarm-list', + component: () => import('@pages/miniprogram/ops/workbench/alarmList.vue'), + status: 'draft', + }, + { + key: 'mp-ops-alarm-detail', label: '告警详情', path: 'alarm-detail/:id', + component: () => import('@pages/miniprogram/ops/workbench/alarmDetail.vue'), + status: 'draft', + }, + ], }, { key: 'work-order', @@ -961,6 +973,18 @@ export const miniprograms: NavMiniprogram[] = [ component: () => import('@pages/miniprogram/ops/workOrder/Home.vue'), status: 'draft', }, + subPages: [ + { + key: 'mp-ops-work-order-detail', label: '工单详情', path: 'work-order-detail/:id', + component: () => import('@pages/miniprogram/ops/workOrder/detail.vue'), + status: 'draft', + }, + { + key: 'mp-ops-repair-report', label: '维修上报', path: 'repair-report/:id', + component: () => import('@pages/miniprogram/ops/workOrder/repairReport.vue'), + status: 'draft', + }, + ], }, { key: 'scan', @@ -971,6 +995,38 @@ export const miniprograms: NavMiniprogram[] = [ component: () => import('@pages/miniprogram/ops/scan/Home.vue'), status: 'draft', }, + subPages: [ + { + key: 'mp-ops-scan-scene-menu', label: '场景操作菜单', path: 'scene-menu/:deviceId', + component: () => import('@pages/miniprogram/ops/scan/sceneMenu.vue'), + status: 'draft', + }, + { + key: 'mp-ops-scan-init', label: '设备初始化', path: 'init/:deviceId', + component: () => import('@pages/miniprogram/ops/scan/init.vue'), + status: 'draft', + }, + { + key: 'mp-ops-scan-impl-record', label: '实施记录上报', path: 'impl-record/:deviceId', + component: () => import('@pages/miniprogram/ops/scan/implRecord.vue'), + status: 'draft', + }, + { + key: 'mp-ops-scan-repair', label: '维修上报', path: 'repair/:deviceId', + component: () => import('@pages/miniprogram/ops/scan/repair.vue'), + status: 'draft', + }, + { + key: 'mp-ops-scan-ota', label: 'OTA 升级', path: 'ota/:deviceId', + component: () => import('@pages/miniprogram/ops/scan/ota.vue'), + status: 'draft', + }, + { + key: 'mp-ops-scan-monitor', label: '设备监测', path: 'monitor/:deviceId', + component: () => import('@pages/miniprogram/ops/scan/monitor.vue'), + status: 'draft', + }, + ], }, { key: 'inspect', @@ -981,6 +1037,13 @@ export const miniprograms: NavMiniprogram[] = [ component: () => import('@pages/miniprogram/ops/inspect/Home.vue'), status: 'draft', }, + subPages: [ + { + key: 'mp-ops-inspect-detail', label: '巡检任务详情', path: 'inspect-detail/:id', + component: () => import('@pages/miniprogram/ops/inspect/detail.vue'), + status: 'draft', + }, + ], }, { key: 'me', @@ -991,6 +1054,13 @@ export const miniprograms: NavMiniprogram[] = [ component: () => import('@pages/miniprogram/ops/me/Home.vue'), status: 'draft', }, + subPages: [ + { + key: 'mp-ops-message-center', label: '消息中心', path: 'message-center', + component: () => import('@pages/miniprogram/ops/me/messageCenter.vue'), + status: 'draft', + }, + ], }, ], }, diff --git a/src/pages/miniprogram/ops/inspect/Home.vue b/src/pages/miniprogram/ops/inspect/Home.vue index 6c083af..3decde1 100644 --- a/src/pages/miniprogram/ops/inspect/Home.vue +++ b/src/pages/miniprogram/ops/inspect/Home.vue @@ -1,12 +1,176 @@ + + diff --git a/src/pages/miniprogram/ops/inspect/api.ts b/src/pages/miniprogram/ops/inspect/api.ts new file mode 100644 index 0000000..0c624db --- /dev/null +++ b/src/pages/miniprogram/ops/inspect/api.ts @@ -0,0 +1,29 @@ +/** + * 实施运维小程序 / 巡检 接口 + */ +import { getRequest, postRequest } from '@axios' +import type { InspectItemSubmit, InspectPlan, InspectPlanStatus, InspectTaskDetail } from './types' + +/** 巡检计划列表 */ +export const listInspectPlans = ( + params: { status?: InspectPlanStatus | '' }, +): Promise<{ code: string; msg: string; data: InspectPlan[] }> => + postRequest('axiosRequest', '/mp/ops/inspect/list', params) + +/** 巡检任务详情 */ +export const getInspectDetail = ( + id: string, +): Promise<{ code: string; msg: string; data: InspectTaskDetail }> => + getRequest('axiosRequest', '/mp/ops/inspect/detail', { id }) + +/** 提交巡检项结果 */ +export const submitInspectItem = ( + params: InspectItemSubmit, +): Promise<{ code: string; msg: string; data: null }> => + postRequest('axiosRequest', '/mp/ops/inspect/submit', params) + +/** 完成巡检任务(提交全部结果) */ +export const finishInspectTask = ( + planId: string, +): Promise<{ code: string; msg: string; data: null }> => + postRequest('axiosRequest', '/mp/ops/inspect/finish', { planId }) diff --git a/src/pages/miniprogram/ops/inspect/detail.vue b/src/pages/miniprogram/ops/inspect/detail.vue new file mode 100644 index 0000000..cad4be8 --- /dev/null +++ b/src/pages/miniprogram/ops/inspect/detail.vue @@ -0,0 +1,281 @@ + + + + + diff --git a/src/pages/miniprogram/ops/inspect/types.ts b/src/pages/miniprogram/ops/inspect/types.ts new file mode 100644 index 0000000..c5ffa23 --- /dev/null +++ b/src/pages/miniprogram/ops/inspect/types.ts @@ -0,0 +1,55 @@ +/** + * 实施运维小程序 / 巡检 类型定义 + */ + +/** 巡检计划状态 */ +export type InspectPlanStatus = 'todo' | 'doing' | 'done' | 'overdue' + +/** 巡检项类型 */ +export type InspectItemType = 'appearance' | 'function' | 'network' | 'sensor' | 'firmware' + +/** 巡检项结果 */ +export type InspectItemResult = 'pending' | 'pass' | 'abnormal' | 'na' + +/** 巡检计划 */ +export interface InspectPlan { + id: string + name: string + site: string + deviceCategory: string + deviceCount: number + status: InspectPlanStatus + deadline: string + inspector: string + itemCount: number +} + +/** 巡检项 */ +export interface InspectItem { + id: string + name: string + type: InspectItemType + standard: string + result: InspectItemResult + remark: string +} + +/** 巡检任务详情 */ +export interface InspectTaskDetail { + plan: InspectPlan + devices: Array<{ + code: string + name: string + items: InspectItem[] + scanned: boolean + }> +} + +/** 巡检项提交参数 */ +export interface InspectItemSubmit { + planId: string + deviceCode: string + itemId: string + result: 'pass' | 'abnormal' + remark: string +} diff --git a/src/pages/miniprogram/ops/me/Home.vue b/src/pages/miniprogram/ops/me/Home.vue index 0fa5224..c9d2fbf 100644 --- a/src/pages/miniprogram/ops/me/Home.vue +++ b/src/pages/miniprogram/ops/me/Home.vue @@ -1,12 +1,304 @@ + + diff --git a/src/pages/miniprogram/ops/me/api.ts b/src/pages/miniprogram/ops/me/api.ts new file mode 100644 index 0000000..bbb5dae --- /dev/null +++ b/src/pages/miniprogram/ops/me/api.ts @@ -0,0 +1,35 @@ +/** + * 实施运维小程序 / 我的 接口 + */ +import { getRequest, postRequest } from '@axios' +import type { MessageItem, MessageType, OfflineStatus, PersonalStats } from './types' + +/** 个人统计 */ +export const getPersonalStats = (): Promise<{ code: string; msg: string; data: PersonalStats }> => + postRequest('axiosRequest', '/mp/ops/me/stats') + +/** 消息列表 */ +export const listMessages = ( + params: { type?: MessageType | '' }, +): Promise<{ code: string; msg: string; data: MessageItem[] }> => + postRequest('axiosRequest', '/mp/ops/me/messages', params) + +/** 标记消息已读 */ +export const readMessage = (id: string): Promise<{ code: string; msg: string; data: null }> => + postRequest('axiosRequest', '/mp/ops/me/message-read', { id }) + +/** 全部已读 */ +export const readAllMessages = (): Promise<{ code: string; msg: string; data: null }> => + postRequest('axiosRequest', '/mp/ops/me/message-read-all') + +/** 离线缓存状态 */ +export const getOfflineStatus = (): Promise<{ code: string; msg: string; data: OfflineStatus }> => + getRequest('axiosRequest', '/mp/ops/me/offline-status') + +/** 手动同步离线缓存 */ +export const syncOffline = (): Promise<{ code: string; msg: string; data: null }> => + postRequest('axiosRequest', '/mp/ops/me/offline-sync') + +/** 清除离线缓存 */ +export const clearOffline = (): Promise<{ code: string; msg: string; data: null }> => + postRequest('axiosRequest', '/mp/ops/me/offline-clear') diff --git a/src/pages/miniprogram/ops/me/messageCenter.vue b/src/pages/miniprogram/ops/me/messageCenter.vue new file mode 100644 index 0000000..7c77c00 --- /dev/null +++ b/src/pages/miniprogram/ops/me/messageCenter.vue @@ -0,0 +1,192 @@ + + + + + diff --git a/src/pages/miniprogram/ops/me/types.ts b/src/pages/miniprogram/ops/me/types.ts new file mode 100644 index 0000000..0f56698 --- /dev/null +++ b/src/pages/miniprogram/ops/me/types.ts @@ -0,0 +1,42 @@ +/** + * 实施运维小程序 / 我的 类型定义 + */ + +/** 个人统计 */ +export interface PersonalStats { + monthOrders: number + monthInspections: number + monthInstalls: number + monthOta: number + avgProcessHours: number + slaRate: string +} + +/** 消息类型 */ +export type MessageType = + | 'workOrder' // 工单派单 + | 'inspect' // 巡检下发 + | 'ota' // OTA 下发 + | 'alarmCritical' // 严重告警 + | 'alarmDead' // 死信告警 + | 'overSla' // 超 SLA + | 'system' // 系统公告 + +/** 消息项 */ +export interface MessageItem { + id: string + type: MessageType + title: string + content: string + createdAt: string + read: boolean + link?: string +} + +/** 离线缓存状态 */ +export interface OfflineStatus { + enabled: boolean + size: string + pendingCount: number + lastSyncTime: string +} diff --git a/src/pages/miniprogram/ops/scan/Home.vue b/src/pages/miniprogram/ops/scan/Home.vue index 58cd9b4..eb9f35e 100644 --- a/src/pages/miniprogram/ops/scan/Home.vue +++ b/src/pages/miniprogram/ops/scan/Home.vue @@ -1,12 +1,203 @@ + + diff --git a/src/pages/miniprogram/ops/scan/api.ts b/src/pages/miniprogram/ops/scan/api.ts new file mode 100644 index 0000000..59915b8 --- /dev/null +++ b/src/pages/miniprogram/ops/scan/api.ts @@ -0,0 +1,72 @@ +/** + * 实施运维小程序 / 扫码 接口 + */ +import { getRequest, postRequest } from '@axios' +import type { + DeviceMonitor, + DeviceSummary, + ImplRecordForm, + InitForm, + OtaTask, + OtaVersion, + RepairForm, + ScanHistoryItem, +} from './types' + +/** 扫码识别设备(按设备编码查摘要) */ +export const scanDevice = ( + deviceCode: string, +): Promise<{ code: string; msg: string; data: DeviceSummary | null }> => + getRequest('axiosRequest', '/mp/ops/scan/scan', { deviceCode }) + +/** 扫码历史 */ +export const listScanHistory = (): Promise<{ code: string; msg: string; data: ScanHistoryItem[] }> => + getRequest('axiosRequest', '/mp/ops/scan/history') + +/** 提交设备初始化 */ +export const submitInit = ( + params: InitForm, +): Promise<{ code: string; msg: string; data: null }> => + postRequest('axiosRequest', '/mp/ops/scan/init', params) + +/** 提交实施记录上报 */ +export const submitImplRecord = ( + params: ImplRecordForm, +): Promise<{ code: string; msg: string; data: null }> => + postRequest('axiosRequest', '/mp/ops/scan/impl-record', params) + +/** 提交维修上报(无工单独立入口) */ +export const submitRepair = ( + params: RepairForm, +): Promise<{ code: string; msg: string; data: null }> => + postRequest('axiosRequest', '/mp/ops/scan/repair', params) + +/** 获取可用 OTA 版本 */ +export const listOtaVersions = ( + deviceCode: string, +): Promise<{ code: string; msg: string; data: OtaVersion[] }> => + getRequest('axiosRequest', '/mp/ops/scan/ota-versions', { deviceCode }) + +/** 下发 OTA 升级任务 */ +export const pushOtaTask = ( + params: { deviceCode: string; targetVersion: string }, +): Promise<{ code: string; msg: string; data: OtaTask }> => + postRequest('axiosRequest', '/mp/ops/scan/ota-push', params) + +/** 查询 OTA 任务进度 */ +export const getOtaTask = ( + deviceCode: string, +): Promise<{ code: string; msg: string; data: OtaTask | null }> => + getRequest('axiosRequest', '/mp/ops/scan/ota-task', { deviceCode }) + +/** OTA 回滚 */ +export const rollbackOta = ( + deviceCode: string, +): Promise<{ code: string; msg: string; data: null }> => + postRequest('axiosRequest', '/mp/ops/scan/ota-rollback', { deviceCode }) + +/** 设备监测详情(多 Tab 聚合) */ +export const getDeviceMonitor = ( + deviceCode: string, +): Promise<{ code: string; msg: string; data: DeviceMonitor | null }> => + getRequest('axiosRequest', '/mp/ops/scan/monitor', { deviceCode }) diff --git a/src/pages/miniprogram/ops/scan/implRecord.vue b/src/pages/miniprogram/ops/scan/implRecord.vue new file mode 100644 index 0000000..784ef49 --- /dev/null +++ b/src/pages/miniprogram/ops/scan/implRecord.vue @@ -0,0 +1,208 @@ + + + + + diff --git a/src/pages/miniprogram/ops/scan/init.vue b/src/pages/miniprogram/ops/scan/init.vue new file mode 100644 index 0000000..ccb3644 --- /dev/null +++ b/src/pages/miniprogram/ops/scan/init.vue @@ -0,0 +1,220 @@ + + + + + diff --git a/src/pages/miniprogram/ops/scan/monitor.vue b/src/pages/miniprogram/ops/scan/monitor.vue new file mode 100644 index 0000000..e8e7c27 --- /dev/null +++ b/src/pages/miniprogram/ops/scan/monitor.vue @@ -0,0 +1,219 @@ + + + + + diff --git a/src/pages/miniprogram/ops/scan/ota.vue b/src/pages/miniprogram/ops/scan/ota.vue new file mode 100644 index 0000000..2c82bcb --- /dev/null +++ b/src/pages/miniprogram/ops/scan/ota.vue @@ -0,0 +1,324 @@ + + + + + diff --git a/src/pages/miniprogram/ops/scan/repair.vue b/src/pages/miniprogram/ops/scan/repair.vue new file mode 100644 index 0000000..426407f --- /dev/null +++ b/src/pages/miniprogram/ops/scan/repair.vue @@ -0,0 +1,244 @@ + + + + + diff --git a/src/pages/miniprogram/ops/scan/sceneMenu.vue b/src/pages/miniprogram/ops/scan/sceneMenu.vue new file mode 100644 index 0000000..2692958 --- /dev/null +++ b/src/pages/miniprogram/ops/scan/sceneMenu.vue @@ -0,0 +1,231 @@ + + + + + diff --git a/src/pages/miniprogram/ops/scan/types.ts b/src/pages/miniprogram/ops/scan/types.ts new file mode 100644 index 0000000..4363112 --- /dev/null +++ b/src/pages/miniprogram/ops/scan/types.ts @@ -0,0 +1,140 @@ +/** + * 实施运维小程序 / 扫码 类型定义 + */ + +/** 设备运行状态 */ +export type DeviceStatus = 'inactive' | 'activated' | 'inUse' | 'scrapped' + +/** 设备顶级分类 */ +export type DeviceCategory = + | 'catering' // 智养餐饮设备 + | 'body' // 体重测量设备 + | 'wearable' // 智能穿戴设备 + | 'fitness' // 健身器材设备 + | 'health' // 健康监测设备 + +/** 扫码后的设备摘要(场景菜单用) */ +export interface DeviceSummary { + id: string + code: string + name: string + category: DeviceCategory + model: string + status: DeviceStatus + site: string + tenant: string + firmwareVersion: string + lastHeartbeat: string + pendingWorkOrders: number + isIot: boolean + dataDistributeEnabled: boolean +} + +/** 扫码历史记录 */ +export interface ScanHistoryItem { + id: string + deviceCode: string + deviceName: string + scannedAt: string + action: string +} + +/** 实施类型 */ +export type ImplType = 'install' | 'move' | 'remove' | 'reset' + +/** 实施记录表单 */ +export interface ImplRecordForm { + deviceCode: string + implType: ImplType + beforeStatus: string + afterStatus: string + images: string[] + customerSign: string + operator: string + operateTime: string + remark: string +} + +/** 设备初始化表单 */ +export interface InitForm { + deviceCode: string + bindKey: string + certBurned: boolean + siteConfig: string + firmwareBaseline: string + result: 'success' | 'fail' + failReason: string + operator: string + operateTime: string +} + +/** 维修上报表单(无工单独立入口) */ +export interface RepairForm { + deviceCode: string + workOrderId: string + faultType: string + faultDescription: string + repairMethod: string + images: string[] + workHours: number + spareParts: Array<{ name: string; qty: number }> + repairman: string + repairTime: string +} + +/** OTA 可用版本 */ +export interface OtaVersion { + version: string + releaseNote: string + status: 'gray' | 'full' | 'stopped' + size: string +} + +/** OTA 升级任务 */ +export interface OtaTask { + id: string + deviceCode: string + currentVersion: string + targetVersion: string + progress: number + status: 'pending' | 'upgrading' | 'success' | 'failed' | 'rolledback' + failReason: string + createdAt: string +} + +/** 设备监测 - 运行日志 */ +export interface RunLogItem { + id: string + time: string + event: string + level: 'info' | 'warn' | 'error' +} + +/** 设备监测 - 维修记录 */ +export interface MonitorRepairItem { + id: string + time: string + faultType: string + repairMethod: string + repairman: string + workOrderId: string +} + +/** 设备监测 - 更新记录 */ +export interface MonitorUpdateItem { + id: string + time: string + fromVersion: string + toVersion: string + result: 'success' | 'failed' | 'rolledback' + operator: string +} + +/** 设备监测详情(多 Tab 聚合) */ +export interface DeviceMonitor { + summary: DeviceSummary + runLogs: RunLogItem[] + alarms: Array<{ id: string; content: string; level: 'info' | 'warn' | 'critical'; triggerTime: string }> + repairs: MonitorRepairItem[] + updates: MonitorUpdateItem[] +} diff --git a/src/pages/miniprogram/ops/workOrder/Home.vue b/src/pages/miniprogram/ops/workOrder/Home.vue index d4b13f8..f6df82a 100644 --- a/src/pages/miniprogram/ops/workOrder/Home.vue +++ b/src/pages/miniprogram/ops/workOrder/Home.vue @@ -1,12 +1,261 @@ + + diff --git a/src/pages/miniprogram/ops/workOrder/api.ts b/src/pages/miniprogram/ops/workOrder/api.ts new file mode 100644 index 0000000..4ff69eb --- /dev/null +++ b/src/pages/miniprogram/ops/workOrder/api.ts @@ -0,0 +1,40 @@ +/** + * 实施运维小程序 / 工单 接口 + */ +import { getRequest, postRequest } from '@axios' +import type { + RepairReportForm, + WorkOrderDetail, + WorkOrderItem, + WorkOrderListParams, +} from './types' + +/** 工单列表 */ +export const listWorkOrders = ( + params: WorkOrderListParams, +): Promise<{ code: string; msg: string; data: WorkOrderItem[] }> => + postRequest('axiosRequest', '/mp/ops/work-order/list', params) + +/** 工单详情 */ +export const getWorkOrderDetail = ( + id: string, +): Promise<{ code: string; msg: string; data: WorkOrderDetail }> => + getRequest('axiosRequest', '/mp/ops/work-order/detail', { id }) + +/** 接单 */ +export const acceptWorkOrder = ( + id: string, +): Promise<{ code: string; msg: string; data: null }> => + postRequest('axiosRequest', '/mp/ops/work-order/accept', { id }) + +/** 关单 */ +export const closeWorkOrder = ( + id: string, +): Promise<{ code: string; msg: string; data: null }> => + postRequest('axiosRequest', '/mp/ops/work-order/close', { id }) + +/** 提交维修上报 */ +export const submitRepairReport = ( + params: RepairReportForm, +): Promise<{ code: string; msg: string; data: null }> => + postRequest('axiosRequest', '/mp/ops/work-order/repair-report', params) diff --git a/src/pages/miniprogram/ops/workOrder/detail.vue b/src/pages/miniprogram/ops/workOrder/detail.vue new file mode 100644 index 0000000..8be8b9e --- /dev/null +++ b/src/pages/miniprogram/ops/workOrder/detail.vue @@ -0,0 +1,273 @@ + + + + + diff --git a/src/pages/miniprogram/ops/workOrder/repairReport.vue b/src/pages/miniprogram/ops/workOrder/repairReport.vue new file mode 100644 index 0000000..379fb94 --- /dev/null +++ b/src/pages/miniprogram/ops/workOrder/repairReport.vue @@ -0,0 +1,250 @@ + + + + + diff --git a/src/pages/miniprogram/ops/workOrder/types.ts b/src/pages/miniprogram/ops/workOrder/types.ts new file mode 100644 index 0000000..54109ba --- /dev/null +++ b/src/pages/miniprogram/ops/workOrder/types.ts @@ -0,0 +1,55 @@ +/** + * 实施运维小程序 / 工单 类型定义 + */ + +/** 工单状态 */ +export type WorkOrderStatus = 'pending' | 'processing' | 'done' + +/** 工单类型 */ +export type WorkOrderType = 'repair' | 'inspection' | 'selfCheck' | 'sla' | 'afterSale' + +/** 工单列表项 */ +export interface WorkOrderItem { + id: string + code: string + title: string + type: WorkOrderType + status: WorkOrderStatus + deviceCode: string + deviceName: string + site: string + tenant: string + description: string + slaDeadline: string + createdAt: string + assignee: string + overSla: boolean +} + +/** 工单详情(含客户报障附件 + 处理记录) */ +export interface WorkOrderDetail extends WorkOrderItem { + faultImages: string[] + customerVoice?: string + processLog: Array<{ time: string; action: string; operator: string }> +} + +/** 工单列表筛选 */ +export interface WorkOrderListParams { + status: WorkOrderStatus + type?: WorkOrderType | '' + keyword?: string +} + +/** 维修上报表单 */ +export interface RepairReportForm { + workOrderId: string + deviceCode: string + faultType: string + faultDescription: string + repairMethod: string + repairImages: string[] + workHours: number + spareParts: Array<{ name: string; qty: number }> + repairman: string + repairTime: string +} diff --git a/src/pages/miniprogram/ops/workbench/Home.vue b/src/pages/miniprogram/ops/workbench/Home.vue index 01321fc..5e6f80e 100644 --- a/src/pages/miniprogram/ops/workbench/Home.vue +++ b/src/pages/miniprogram/ops/workbench/Home.vue @@ -1,12 +1,354 @@ + + diff --git a/src/pages/miniprogram/ops/workbench/alarmDetail.vue b/src/pages/miniprogram/ops/workbench/alarmDetail.vue new file mode 100644 index 0000000..fdf03a0 --- /dev/null +++ b/src/pages/miniprogram/ops/workbench/alarmDetail.vue @@ -0,0 +1,257 @@ + + + + + diff --git a/src/pages/miniprogram/ops/workbench/alarmList.vue b/src/pages/miniprogram/ops/workbench/alarmList.vue new file mode 100644 index 0000000..9f4ab13 --- /dev/null +++ b/src/pages/miniprogram/ops/workbench/alarmList.vue @@ -0,0 +1,294 @@ + + + + + diff --git a/src/pages/miniprogram/ops/workbench/api.ts b/src/pages/miniprogram/ops/workbench/api.ts new file mode 100644 index 0000000..b5802a5 --- /dev/null +++ b/src/pages/miniprogram/ops/workbench/api.ts @@ -0,0 +1,38 @@ +/** + * 实施运维小程序 / 工作台 + 告警 接口 + */ +import { getRequest, postRequest } from '@axios' +import type { + AlarmDetail, + AlarmHandleParams, + AlarmItem, + AlarmListParams, + TodoStats, + WeekStats, +} from './types' + +/** 工作台今日待办统计 */ +export const getTodoStats = (): Promise<{ code: string; msg: string; data: TodoStats }> => + postRequest('axiosRequest', '/mp/ops/workbench/todo-stats') + +/** 工作台本周统计 */ +export const getWeekStats = (): Promise<{ code: string; msg: string; data: WeekStats }> => + postRequest('axiosRequest', '/mp/ops/workbench/week-stats') + +/** 告警列表 */ +export const listAlarms = ( + params: AlarmListParams, +): Promise<{ code: string; msg: string; data: AlarmItem[] }> => + postRequest('axiosRequest', '/mp/ops/alarm/list', params) + +/** 告警详情 */ +export const getAlarmDetail = ( + id: string, +): Promise<{ code: string; msg: string; data: AlarmDetail }> => + getRequest('axiosRequest', '/mp/ops/alarm/detail', { id }) + +/** 告警处置 */ +export const handleAlarm = ( + params: AlarmHandleParams, +): Promise<{ code: string; msg: string; data: null }> => + postRequest('axiosRequest', '/mp/ops/alarm/handle', params) diff --git a/src/pages/miniprogram/ops/workbench/types.ts b/src/pages/miniprogram/ops/workbench/types.ts new file mode 100644 index 0000000..34eb700 --- /dev/null +++ b/src/pages/miniprogram/ops/workbench/types.ts @@ -0,0 +1,81 @@ +/** + * 实施运维小程序 / 工作台 + 告警 类型定义 + */ + +/** 今日待办统计(工作台 5 张卡) */ +export interface TodoStats { + pendingWorkOrders: number + overSlaOrders: number + todayInspections: number + inspectionDone: number + todayOtaTasks: number + todayInitDevices: number + pendingAlarms: number + criticalAlarms: number +} + +/** 本周统计(工作台 3 张卡) */ +export interface WeekStats { + weekOrders: number + avgProcessHours: number + weekInspectionRate: string + weekInspectionAbnormalRate: string + monthInstallCount: number +} + +/** 快捷入口项 */ +export interface QuickEntry { + key: string + label: string + icon: string + path: string +} + +/** 告警来源 */ +export type AlarmSource = 'runLog' | 'selfCheck' | 'inspection' | 'sla' | 'ota' | 'dataDistribute' + +/** 告警级别 */ +export type AlarmLevel = 'info' | 'warn' | 'critical' + +/** 告警状态 */ +export type AlarmStatus = 'pending' | 'processing' | 'ignored' | 'workorder' + +/** 告警列表项 */ +export interface AlarmItem { + id: string + alarmCode: string + source: AlarmSource + level: AlarmLevel + deviceCode: string + deviceName: string + content: string + triggerTime: string + status: AlarmStatus + handler: string | null +} + +/** 告警详情(含时间线 + 设备扩展信息) */ +export interface AlarmDetail extends AlarmItem { + deviceModel: string + site: string + tenant: string + processTimeline: Array<{ time: string; action: string; operator: string }> +} + +/** 告警列表查询参数 */ +export interface AlarmListParams { + level?: AlarmLevel | '' + source?: AlarmSource | '' + status?: AlarmStatus | '' + keyword?: string +} + +/** 告警处置动作 */ +export type AlarmHandleAction = 'toWorkOrder' | 'ignore' | 'resolve' + +/** 告警处置参数 */ +export interface AlarmHandleParams { + id: string + action: AlarmHandleAction + reason: string +}