From 0e884b884ae3b4d1d07392d77691db3b548cfeea Mon Sep 17 00:00:00 2001 From: fengpu <18691763612@163.com> Date: Fri, 10 Jul 2026 17:07:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(ops):=20=E8=BF=90=E7=BB=B4=E5=B0=8F?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E6=89=AB=E7=A0=81=20Tab=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=9F=A5=E8=AF=A2=E4=B8=8E=E6=89=AB=E7=A0=81?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E5=AD=90=E9=A1=B5=20+=20=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E5=8F=B0=E5=85=A5=E5=8F=A3=E8=A1=A5=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 scan/deviceList.vue(设备查询)与 scanHistory.vue(扫码历史)两个子页 - 扫码 Home / sceneMenu 流程调整,api 与 types 补充对应字段 - nav.ts 同步新增两个子页菜单项 - handlers/index 与 scan mock 注册配合调整 Co-Authored-By: Claude Opus 4.7 --- src/axios/handlers/index.ts | 2 +- src/axios/handlers/miniprogram/ops/scan.ts | 44 +++ src/meta/nav.ts | 10 + src/pages/miniprogram/ops/scan/Home.vue | 98 +++--- src/pages/miniprogram/ops/scan/api.ts | 8 + src/pages/miniprogram/ops/scan/deviceList.vue | 284 ++++++++++++++++++ .../miniprogram/ops/scan/scanHistory.vue | 70 +++++ src/pages/miniprogram/ops/scan/sceneMenu.vue | 23 +- src/pages/miniprogram/ops/scan/types.ts | 24 ++ src/pages/miniprogram/ops/workbench/Home.vue | 60 ++++ 10 files changed, 546 insertions(+), 77 deletions(-) create mode 100644 src/pages/miniprogram/ops/scan/deviceList.vue create mode 100644 src/pages/miniprogram/ops/scan/scanHistory.vue diff --git a/src/axios/handlers/index.ts b/src/axios/handlers/index.ts index e34911f..9b60c20 100644 --- a/src/axios/handlers/index.ts +++ b/src/axios/handlers/index.ts @@ -82,7 +82,7 @@ import './miniprogram/ops/workbench' import './miniprogram/ops/stock' // 实施运维小程序 - 工单(列表 3 段 + 详情 + 接单/关单/维修上报) import './miniprogram/ops/workOrder' -// 实施运维小程序 - 扫码(识别 / 历史 / 初始化 / 实施记录 / 维修上报 / OTA / 设备监测 5 Tab) +// 实施运维小程序 - 扫码(识别 / 历史 / 设备列表 / 初始化 / 实施记录 / 维修上报 / OTA / 设备监测 5 Tab) import './miniprogram/ops/scan' // 实施运维小程序 - 巡检(计划列表 4 状态 + 任务详情逐项填报 + 异常转工单) import './miniprogram/ops/inspect' diff --git a/src/axios/handlers/miniprogram/ops/scan.ts b/src/axios/handlers/miniprogram/ops/scan.ts index c7450a6..394699f 100644 --- a/src/axios/handlers/miniprogram/ops/scan.ts +++ b/src/axios/handlers/miniprogram/ops/scan.ts @@ -2,16 +2,19 @@ * 实施运维小程序 / 扫码 mock * - 扫码识别设备(按编码查摘要) * - 扫码历史 + * - 设备列表(按客户 / 场所 / 类别 / 编码筛选,扫码兜底入口) * - 设备初始化 / 实施记录 / 维修上报 / OTA 升级 / 设备监测 */ import { registerMocks } from '@axios/mockBus' import type { ApiResponse, RequestParameter } from '@axios' import type { + DeviceListFilter, DeviceMonitor, DeviceSummary, ImplRecordForm, InitForm, + ListDeviceItem, MonitorRepairItem, MonitorUpdateItem, OtaTask, @@ -74,6 +77,20 @@ const deviceMap: Record = { packageName: '—', softwareVersion: '—', lastHeartbeat: '—', pendingWorkOrders: 0, isIot: true, dataDistributeEnabled: false, }, + 'BIKE-NJ-006': { + id: 'dev-103', deviceCode: 'BIKE-NJ-006', deviceName: '6号智能单车(南京玄武)', + category: 'fitness', model: 'BK-Pro', + status: 'activated', siteName: '南京玄武 · 玄武湖健身房', tenantName: '南京健身中心', + packageName: '健身单车App', softwareVersion: 'V1.0.0', lastHeartbeat: '2026-07-03 08:50:00', + pendingWorkOrders: 0, isIot: true, dataDistributeEnabled: false, + }, + 'SCALE-GZ-008': { + id: 'dev-115', deviceCode: 'SCALE-GZ-008', deviceName: '8号体脂仪(广州天河·已报废)', + category: 'body', model: 'InBody-770', + status: 'scrapped', siteName: '广州天河 · 天河体检站', tenantName: '广州健康管理机构', + packageName: '体脂仪App', softwareVersion: 'V2.9.4', lastHeartbeat: '2026-06-20 16:08:42', + pendingWorkOrders: 0, isIot: false, dataDistributeEnabled: false, + }, } /** 扫码历史(最近 6 条) */ @@ -149,6 +166,33 @@ const genArcsoftKey = (): string => { } registerMocks([ + // 设备列表(按客户 / 场所 / 类别 / 编码筛选,用于扫码兜底入口) + { + url: '/mp/ops/scan/device-list', + method: 'GET', + handler: (ctx): ApiResponse => { + const { tenantName, siteName, category, deviceCode } = getParams(ctx.params) + // 从 deviceMap 派生精简列表项 + const list: ListDeviceItem[] = Object.values(deviceMap).map((d) => ({ + id: d.id, + deviceCode: d.deviceCode, + deviceName: d.deviceName, + category: d.category, + status: d.status, + siteName: d.siteName, + tenantName: d.tenantName, + model: d.model, + })) + const filtered = list.filter((d) => { + if (tenantName && d.tenantName !== tenantName) return false + if (siteName && d.siteName !== siteName) return false + if (category && d.category !== category) return false + if (deviceCode && !d.deviceCode.toLowerCase().includes(deviceCode.toLowerCase())) return false + return true + }) + return ok('查询成功', filtered) + }, + }, // 扫码识别设备 { url: '/mp/ops/scan/scan', diff --git a/src/meta/nav.ts b/src/meta/nav.ts index f7be16f..ac14754 100644 --- a/src/meta/nav.ts +++ b/src/meta/nav.ts @@ -1084,6 +1084,16 @@ export const miniprograms: NavMiniprogram[] = [ status: 'draft', }, subPages: [ + { + key: 'mp-ops-scan-device-list', label: '设备查询', path: 'device-list', + component: () => import('@pages/miniprogram/ops/scan/deviceList.vue'), + status: 'draft', + }, + { + key: 'mp-ops-scan-history', label: '扫码历史', path: 'scan-history', + component: () => import('@pages/miniprogram/ops/scan/scanHistory.vue'), + status: 'draft', + }, { key: 'mp-ops-scan-scene-menu', label: '场景操作菜单', path: 'scene-menu/:deviceId', component: () => import('@pages/miniprogram/ops/scan/sceneMenu.vue'), diff --git a/src/pages/miniprogram/ops/scan/Home.vue b/src/pages/miniprogram/ops/scan/Home.vue index eb9f35e..c8d8c66 100644 --- a/src/pages/miniprogram/ops/scan/Home.vue +++ b/src/pages/miniprogram/ops/scan/Home.vue @@ -10,9 +10,11 @@
将设备机身二维码对准框内
- - 手动输入编码 - + +
+ 手动输入编码 + 按条件查找 +
@@ -22,22 +24,18 @@ 模拟扫码 · 待激活设备 - -
-
最近扫码
- - - -
+ + + + /** * 实施运维小程序 / 扫码首页 - * - 全屏取景框(原型无真扫码,提供模拟按钮) - * - 历史扫码列表 - * - 手动输入设备编码兜底 + * - 全屏取景框(原型无真扫码,提供模拟按钮) + * - 兜底入口:手动输入编码 / 按条件查找设备(跳 deviceList) + * - 扫码历史抽离到 scan-history 子页,首页只保留入口,布局更紧凑 */ -import { onMounted, ref } from 'vue' +import { ref } from 'vue' import { useRouter } from 'vue-router' import { showFailToast } from 'vant' -import { listScanHistory, scanDevice } from './api' -import type { ScanHistoryItem } from './types' +import { scanDevice } from './api' const router = useRouter() -const history = ref([]) const inputShow = ref(false) const inputCode = ref('') @@ -97,29 +93,19 @@ const onInputConfirm = (): void => { onSimScan(inputCode.value.trim()) } -const onRescan = (deviceCode: string): void => { - onSimScan(deviceCode) +const goDeviceList = (): void => { + router.push('/miniprogram/ops/device-list') } -const loadHistory = (): void => { - listScanHistory() - .then((res) => { - if (res.code === '00000') history.value = res.data - }) - .catch(() => undefined) - .finally(() => undefined) +const goScanHistory = (): void => { + router.push('/miniprogram/ops/scan-history') } - -onMounted(() => { - loadHistory() -}) diff --git a/src/pages/miniprogram/ops/scan/api.ts b/src/pages/miniprogram/ops/scan/api.ts index 892e32b..16bcec3 100644 --- a/src/pages/miniprogram/ops/scan/api.ts +++ b/src/pages/miniprogram/ops/scan/api.ts @@ -3,10 +3,12 @@ */ import { getRequest, postRequest } from '@axios' import type { + DeviceListFilter, DeviceMonitor, DeviceSummary, ImplRecordForm, InitForm, + ListDeviceItem, OtaTask, OtaVersion, RepairForm, @@ -24,6 +26,12 @@ export const scanDevice = ( export const listScanHistory = (): Promise<{ code: string; msg: string; data: ScanHistoryItem[] }> => getRequest('axiosRequest', '/mp/ops/scan/history') +/** 设备列表(按客户 / 场所 / 类别 / 编码筛选,用于扫码兜底场景) */ +export const listDevices = ( + filters: DeviceListFilter, +): Promise<{ code: string; msg: string; data: ListDeviceItem[] }> => + getRequest('axiosRequest', '/mp/ops/scan/device-list', filters) + /** 提交设备初始化 */ export const submitInit = ( params: InitForm, diff --git a/src/pages/miniprogram/ops/scan/deviceList.vue b/src/pages/miniprogram/ops/scan/deviceList.vue new file mode 100644 index 0000000..571b084 --- /dev/null +++ b/src/pages/miniprogram/ops/scan/deviceList.vue @@ -0,0 +1,284 @@ + + + + + diff --git a/src/pages/miniprogram/ops/scan/scanHistory.vue b/src/pages/miniprogram/ops/scan/scanHistory.vue new file mode 100644 index 0000000..39cdba5 --- /dev/null +++ b/src/pages/miniprogram/ops/scan/scanHistory.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/src/pages/miniprogram/ops/scan/sceneMenu.vue b/src/pages/miniprogram/ops/scan/sceneMenu.vue index e32ea41..018f236 100644 --- a/src/pages/miniprogram/ops/scan/sceneMenu.vue +++ b/src/pages/miniprogram/ops/scan/sceneMenu.vue @@ -58,7 +58,7 @@ /** * 实施运维小程序 / 扫码场景操作菜单 * - 设备摘要卡(编码 / 型号 / 状态 / 待处理工单红点) - * - 9 项操作按设备状态过滤显示(6 项运维 + 3 项库存作业) + * - 6 项运维操作按设备状态过滤显示(库存作业请走工作台「库存作业」入口) */ import { computed, onMounted, ref } from 'vue' import { useRoute, useRouter } from 'vue-router' @@ -77,8 +77,6 @@ interface SceneAction { icon: string path: (deviceId: string) => string statuses: DeviceStatus[] - /** 库存相关场景独立于设备生命周期,可与任意状态并存 */ - isStock?: boolean } const ALL_ACTIONS: SceneAction[] = [ @@ -112,25 +110,6 @@ const ALL_ACTIONS: SceneAction[] = [ icon: 'eye-o', path: (id) => `/miniprogram/ops/monitor/${id}`, statuses: ['activated', 'inUse', 'scrapped'], }, - // 库存作业 3 个场景(扫码进入时自动带入 deviceCode) - { - key: 'stock-inbound', label: '扫码入库', desc: '将本设备登记到收货入库清单', - icon: 'after-sale', path: (code) => `/miniprogram/ops/stock-inbound?deviceCode=${code}`, - statuses: ['inactive', 'activated', 'inUse', 'scrapped'], - isStock: true, - }, - { - key: 'stock-outbound', label: '扫码出库', desc: '将本设备加入装机出库清单', - icon: 'logistics', path: (code) => `/miniprogram/ops/stock-outbound?deviceCode=${code}`, - statuses: ['inactive', 'activated', 'inUse', 'scrapped'], - isStock: true, - }, - { - key: 'stock-inventory', label: '扫码盘点', desc: '在当前盘点单中标记本设备实盘结果', - icon: 'balance-list-o', path: (code) => `/miniprogram/ops/stock-inventory?deviceCode=${code}&id=inv-006`, - statuses: ['inactive', 'activated', 'inUse', 'scrapped'], - isStock: true, - }, ] const availableActions = computed(() => { diff --git a/src/pages/miniprogram/ops/scan/types.ts b/src/pages/miniprogram/ops/scan/types.ts index f99af98..6c38f4b 100644 --- a/src/pages/miniprogram/ops/scan/types.ts +++ b/src/pages/miniprogram/ops/scan/types.ts @@ -167,3 +167,27 @@ export interface DeviceMonitor { repairs: MonitorRepairItem[] updates: MonitorUpdateItem[] } + +/** 设备列表项(查询场景精简版,剔除心跳 / 工单 / IOT 等监测字段,仅用于列表展示) */ +export interface ListDeviceItem { + id: string + deviceCode: string + deviceName: string + category: DeviceCategory + status: DeviceStatus + siteName: string + tenantName: string + model: string +} + +/** 设备列表筛选条件(用于"按客户 / 场所 / 类别 / 编码"快速定位设备) */ +export interface DeviceListFilter { + /** 客户(租户)名,空表示不限 */ + tenantName?: string + /** 场所名,空表示不限 */ + siteName?: string + /** 设备顶级分类,空表示不限 */ + category?: DeviceCategory + /** 设备编码模糊匹配 */ + deviceCode?: string +} diff --git a/src/pages/miniprogram/ops/workbench/Home.vue b/src/pages/miniprogram/ops/workbench/Home.vue index 3dc4092..a9400ec 100644 --- a/src/pages/miniprogram/ops/workbench/Home.vue +++ b/src/pages/miniprogram/ops/workbench/Home.vue @@ -103,6 +103,19 @@ + + +
+
设备查询
+
+ +
+
按客户 / 场所 / 类别 / 编码查找设备
+
不在现场或二维码损坏时,从列表快速定位
+
+ +
+
@@ -141,6 +154,10 @@ const goPath = (path: string): void => { router.push(path) } +const goDeviceList = (): void => { + router.push('/miniprogram/ops/device-list') +} + const goAlarmList = (): void => { router.push('/miniprogram/ops/alarm-list') } @@ -454,4 +471,47 @@ onMounted(() => { .quick-entry__icon { color: #595959; } } } + +.dev-search-card { + display: flex; + align-items: center; + gap: 12px; + padding: 14px 16px; + background: #fff; + border-radius: var(--radius-md); + border-left: 3px solid var(--primary); + box-shadow: var(--shadow-sm); + transition: transform 0.15s ease; + + &:active { + transform: scale(0.99); + } + + &__icon { + font-size: 24px; + color: var(--primary); + } + + &__main { + flex: 1; + min-width: 0; + } + + &__label { + font-size: var(--font-base); + font-weight: 600; + color: var(--text-primary); + } + + &__sub { + margin-top: 2px; + font-size: var(--font-xs); + color: var(--text-secondary); + } + + &__arrow { + font-size: 16px; + color: var(--text-placeholder); + } +}