From 3f85e5410dda49e7e6a6292859649513f3644cb2 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 28 八月 2026 14:44:12 +0800
Subject: [PATCH] 天津豹鸣app 1.添加扫码查看设备变更记录功能

---
 src/api/device/statusChangeRecord.js               |    9 +
 src/pages/index.vue                                |   46 ++++++
 src/pages.json                                     |    7 +
 src/pages/works.vue                                |   46 ++++++
 src/api/equipmentManagement/deviceInfo.js          |    9 +
 src/pages/equipmentManagement/deviceInfo/index.vue |  275 +++++++++++++++++++++++++++++++++++++++
 src/config.js                                      |    2 
 src/static/images/icon/saomashebei.svg             |    9 +
 8 files changed, 400 insertions(+), 3 deletions(-)

diff --git a/src/api/device/statusChangeRecord.js b/src/api/device/statusChangeRecord.js
new file mode 100644
index 0000000..a4a5367
--- /dev/null
+++ b/src/api/device/statusChangeRecord.js
@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+export function listStatusChangeRecord(query) {
+  return request({
+    url: '/device/statusChangeRecord/list',
+    method: 'get',
+    params: query
+  })
+}
diff --git a/src/api/equipmentManagement/deviceInfo.js b/src/api/equipmentManagement/deviceInfo.js
new file mode 100644
index 0000000..fa912b1
--- /dev/null
+++ b/src/api/equipmentManagement/deviceInfo.js
@@ -0,0 +1,9 @@
+import request from "@/utils/request";
+
+// 鑾峰彇璁惧鍩烘湰淇℃伅
+export function getDeviceInfo(id) {
+  return request({
+    url: `/device/ledger/${id}`,
+    method: "get",
+  });
+}
diff --git a/src/config.js b/src/config.js
index 27244b3..2654b1c 100644
--- a/src/config.js
+++ b/src/config.js
@@ -1,6 +1,6 @@
 // 搴旂敤鍏ㄥ眬閰嶇疆
 const config = {
-  baseUrl: "http://1.15.17.182:9048",
+  baseUrl: "http://192.168.0.31:25615",
   fileUrl: "http://1.15.17.182:9049",
   // 搴旂敤淇℃伅
   appInfo: {
diff --git a/src/pages.json b/src/pages.json
index 1aa49ad..e61cc28 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -661,6 +661,13 @@
       }
     },
     {
+      "path": "pages/equipmentManagement/deviceInfo/index",
+      "style": {
+        "navigationBarTitleText": "璁惧淇℃伅",
+        "navigationStyle": "custom"
+      }
+    },
+    {
       "path": "pages/equipmentManagement/runManagement/index",
       "style": {
         "navigationBarTitleText": "杩愯绠$悊",
diff --git a/src/pages/equipmentManagement/deviceInfo/index.vue b/src/pages/equipmentManagement/deviceInfo/index.vue
new file mode 100644
index 0000000..5972626
--- /dev/null
+++ b/src/pages/equipmentManagement/deviceInfo/index.vue
@@ -0,0 +1,275 @@
+<template>
+  <view class="device-info-container">
+    <PageHeader title="璁惧淇℃伅" @back="goBack" />
+
+    <!-- 鍩烘湰淇℃伅 -->
+    <view class="info-card">
+      <view class="card-header">
+        <text class="card-title">鍩烘湰淇℃伅</text>
+        <text class="device-status">姝e父</text>
+      </view>
+      <view class="card-content">
+        <view class="info-row">
+          <text class="label">璁惧鍚嶇О锛�</text>
+          <text class="value">{{ deviceInfo.deviceName || '--' }}</text>
+        </view>
+        <view class="info-row">
+          <text class="label">瑙勬牸鍨嬪彿锛�</text>
+          <text class="value">{{ deviceInfo.deviceModel || '--' }}</text>
+        </view>
+        <view class="info-row">
+          <text class="label">鐢熶骇鍘傚锛�</text>
+          <text class="value">{{ deviceInfo.supplierName || '--' }}</text>
+        </view>
+        <view class="info-row">
+          <text class="label">鍗曚綅锛�</text>
+          <text class="value">{{ deviceInfo.unit || '--' }}</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 鍙樻洿璁板綍 -->
+    <view class="info-card">
+      <view class="card-header">
+        <text class="card-title">鍙樻洿璁板綍</text>
+      </view>
+      <view class="card-content">
+        <view v-if="!recordsLoading && changeRecords.length === 0" class="empty-tip">鏆傛棤鍙樻洿璁板綍</view>
+        <view v-else class="change-record" v-for="(item, index) in changeRecords" :key="index">
+          <view class="record-header">
+            <text class="record-time">{{ item.createTime }}</text>
+            <text class="record-status" :class="approvalClass(item.approvalStatus)">{{ approvalText(item.approvalStatus) }}</text>
+          </view>
+          <view class="record-line">
+            <text class="record-status-change">{{ statusText(item.originalStatus) }} 鈫� {{ statusText(item.targetStatus) }}</text>
+            <text class="record-user">鐢宠浜猴細{{ item.createUserName || '--' }}</text>
+          </view>
+          <view class="record-reason">鍙樻洿鍘熷洜锛歿{ item.reason || '--' }}</view>
+          <view v-if="item.approvalStatus === 'REJECTED' && item.rejectReason" class="record-reject">椹冲洖鍘熷洜锛歿{ item.rejectReason }}</view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, reactive } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+import PageHeader from '@/components/PageHeader.vue'
+import { getDeviceInfo } from '@/api/equipmentManagement/deviceInfo'
+import { listStatusChangeRecord } from '@/api/device/statusChangeRecord'
+
+const deviceInfo = reactive({
+  deviceName: '',
+  deviceModel: '',
+  supplierName: '',
+  unit: '',
+})
+
+const changeRecords = ref([])
+const recordsLoading = ref(false)
+
+const statusText = (s) => (s == 1 ? '鍚敤' : '鍋滅敤')
+
+const approvalText = (s) => {
+  const map = { PENDING: '瀹℃壒涓�', APPROVED: '宸查�氳繃', REJECTED: '宸查┏鍥�' }
+  return map[s] || s || '--'
+}
+
+const approvalClass = (s) => {
+  const map = { PENDING: 'status-pending', APPROVED: 'status-approved', REJECTED: 'status-rejected' }
+  return map[s] || ''
+}
+
+const fetchDeviceInfo = async (deviceId) => {
+  try {
+    const res = await getDeviceInfo(deviceId)
+    console.log('璁惧淇℃伅鍝嶅簲:', res)
+    if (res.code === 200 && res.data) {
+      Object.assign(deviceInfo, res.data)
+    }
+  } catch (error) {
+    console.error('鑾峰彇璁惧淇℃伅澶辫触:', error)
+  }
+}
+
+const fetchChangeRecords = async (deviceId) => {
+  recordsLoading.value = true
+  try {
+    const res = await listStatusChangeRecord({ deviceId, pageNum: 1, pageSize: 20 })
+    console.log('鍙樻洿璁板綍鍝嶅簲:', res)
+    if (res.code === 200) {
+      changeRecords.value = res.data?.records || res.data || []
+    }
+  } catch (error) {
+    console.error('鑾峰彇鍙樻洿璁板綍澶辫触:', error)
+  } finally {
+    recordsLoading.value = false
+  }
+}
+
+const goBack = () => {
+  uni.navigateBack()
+}
+
+onLoad((options) => {
+  const deviceId = options?.deviceId || ''
+  if (!deviceId) {
+    uni.showToast({ title: '缂哄皯璁惧ID', icon: 'none' })
+    return
+  }
+  fetchDeviceInfo(deviceId)
+  fetchChangeRecords(deviceId)
+})
+</script>
+
+<style scoped lang="scss">
+.device-info-container {
+  min-height: 100vh;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  padding: 16px 20px 20px;
+  box-sizing: border-box;
+}
+
+.info-card {
+  background: rgba(255, 255, 255, 0.95);
+  border-radius: 16px;
+  margin-bottom: 16px;
+  overflow: hidden;
+}
+
+.card-header {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: #ffffff;
+  padding: 14px 20px;
+  font-weight: 500;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.card-title {
+  font-size: 15px;
+  color: #ffffff;
+}
+
+.device-status {
+  padding: 4px 14px;
+  border-radius: 20px;
+  font-size: 12px;
+  color: #ffffff;
+  background: #52c41a;
+}
+
+.card-content {
+  padding: 16px 20px;
+}
+
+.info-row {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 12px;
+
+  &:last-child {
+    margin-bottom: 0;
+  }
+}
+
+.label {
+  width: 88px;
+  flex-shrink: 0;
+  color: #666666;
+  font-size: 14px;
+}
+
+.value {
+  flex: 1;
+  color: #2c3e50;
+  font-weight: 500;
+  font-size: 14px;
+  word-break: break-all;
+}
+
+.status-normal {
+  color: #52c41a;
+}
+
+.empty-tip {
+  color: #999999;
+  text-align: center;
+  padding: 20px 0;
+}
+
+.change-record {
+  padding: 14px 0;
+  border-bottom: 1px solid #f0f0f0;
+
+  &:last-child {
+    border-bottom: none;
+  }
+}
+
+.record-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 8px;
+}
+
+.record-time {
+  color: #666666;
+  font-size: 13px;
+}
+
+.record-status {
+  font-size: 12px;
+  padding: 2px 10px;
+  border-radius: 12px;
+  background: #f5f5f5;
+  color: #666666;
+}
+
+.status-pending {
+  background: #fff7e6;
+  color: #fa8c16;
+}
+
+.status-approved {
+  background: #f6ffed;
+  color: #52c41a;
+}
+
+.status-rejected {
+  background: #fff1f0;
+  color: #f5222d;
+}
+
+.record-line {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 6px;
+}
+
+.record-status-change {
+  color: #2c3e50;
+  font-weight: 500;
+  font-size: 14px;
+}
+
+.record-user {
+  color: #999999;
+  font-size: 13px;
+}
+
+.record-reason {
+  color: #666666;
+  font-size: 13px;
+}
+
+.record-reject {
+  color: #f5222d;
+  font-size: 13px;
+  margin-top: 4px;
+}
+</style>
diff --git a/src/pages/index.vue b/src/pages/index.vue
index 2d253b2..47fd3fe 100644
--- a/src/pages/index.vue
+++ b/src/pages/index.vue
@@ -231,6 +231,11 @@
     action: "scan",
   },
   {
+    label: "鎵爜鏌ヨ澶�",
+    icon: "/static/images/icon/saomashebei.svg",
+    action: "scanDevice",
+  },
+  {
     label: "璁惧宸℃",
     icon: "/static/images/icon/xunjianshangchuan.svg",
     route: "/pages/inspectionUpload/index",
@@ -265,6 +270,41 @@
   overviewExpanded.value = !overviewExpanded.value;
 }
 
+function getDeviceIdFromScan(scanResult) {
+  if (!scanResult) return null;
+  const match = scanResult.match(/deviceId=(\d+)/);
+  if (match && match[1]) return match[1];
+  if (/^\d+$/.test(scanResult.trim())) return scanResult.trim();
+  try {
+    const obj = JSON.parse(scanResult);
+    if (obj.deviceId) return String(obj.deviceId);
+  } catch (e) {
+    // 蹇界暐闈� JSON 鍐呭
+  }
+  return null;
+}
+
+function scanDevice() {
+  uni.scanCode({
+    scanType: ["qrCode", "barCode"],
+    success: (res) => {
+      console.log("鎵爜缁撴灉:", res.result);
+      const deviceId = getDeviceIdFromScan(res.result);
+      if (!deviceId) {
+        uni.showToast({ title: "鏈瘑鍒埌璁惧ID", icon: "none" });
+        return;
+      }
+      uni.navigateTo({
+        url: `/pages/equipmentManagement/deviceInfo/index?deviceId=${deviceId}`,
+      });
+    },
+    fail: (err) => {
+      console.error("鎵爜澶辫触:", err);
+      uni.showToast({ title: "鎵爜澶辫触锛岃閲嶈瘯", icon: "none" });
+    },
+  });
+}
+
 function handleQuickTool(item) {
   if (item?.action === "scan") {
     // 鐢熶骇鎶ュ伐 - 璋冪敤鎵爜
@@ -286,6 +326,10 @@
         console.error("鎵爜澶辫触:", err);
       }
     });
+    return;
+  }
+  if (item?.action === "scanDevice") {
+    scanDevice();
     return;
   }
   if (!item?.route) return;
@@ -322,7 +366,7 @@
   allowedMenuTitles.value = titles;
 
   quickTools.value = quickToolSource.filter((item) =>
-    titles.has(item.label)
+    item.action === "scanDevice" || titles.has(item.label)
   );
 }
 
diff --git a/src/pages/works.vue b/src/pages/works.vue
index 9e89ddf..3b7ffa7 100644
--- a/src/pages/works.vue
+++ b/src/pages/works.vue
@@ -656,6 +656,10 @@
     // 	label: '璁惧鍙拌处',
     // },
     {
+      icon: "/static/images/icon/saomashebei.svg",
+      label: "鎵爜鏌ヨ澶�",
+    },
+    {
       icon: "/static/images/icon/yunxingguanli.svg",
       label: "杩愯绠$悊",
     },
@@ -672,6 +676,42 @@
       label: "璁惧宸℃",
     },
   ]);
+
+  // 鎵爜鏌ヨ澶囷細瑙f瀽 deviceId 鍚庤烦杞埌璁惧淇℃伅椤�
+  const getDeviceIdFromScan = scanResult => {
+    if (!scanResult) return null;
+    const match = scanResult.match(/deviceId=(\d+)/);
+    if (match && match[1]) return match[1];
+    if (/^\d+$/.test(scanResult.trim())) return scanResult.trim();
+    try {
+      const obj = JSON.parse(scanResult);
+      if (obj.deviceId) return String(obj.deviceId);
+    } catch (e) {
+      // 蹇界暐闈� JSON 鍐呭
+    }
+    return null;
+  };
+
+  const startScanDevice = () => {
+    uni.scanCode({
+      scanType: ["qrCode", "barCode"],
+      success: res => {
+        console.log("鎵爜缁撴灉:", res.result);
+        const deviceId = getDeviceIdFromScan(res.result);
+        if (!deviceId) {
+          uni.showToast({ title: "鏈瘑鍒埌璁惧ID", icon: "none" });
+          return;
+        }
+        uni.navigateTo({
+          url: `/pages/equipmentManagement/deviceInfo/index?deviceId=${deviceId}`,
+        });
+      },
+      fail: err => {
+        console.error("鎵爜澶辫触:", err);
+        uni.showToast({ title: "鎵爜澶辫触锛岃閲嶈瘯", icon: "none" });
+      },
+    });
+  };
 
   // 澶勭悊甯哥敤鍔熻兘鐐瑰嚮
   const handleCommonItemClick = item => {
@@ -938,6 +978,9 @@
         uni.navigateTo({
           url: "/pages/equipmentManagement/ledger/index",
         });
+        break;
+      case "鎵爜鏌ヨ澶�":
+        startScanDevice();
         break;
       case "杩愯绠$悊":
         uni.navigateTo({
@@ -1258,7 +1301,7 @@
 
     // 鏀堕泦鎵�鏈夋湁鏉冮檺鐨勮彍鍗曟爣棰橈紙鏍规嵁 meta.title锛�
     const allowedMenuTitles = new Set();
-    const alwaysShowTitles = new Set(["閲囪喘閫�璐у崟", "渚涘簲鍟嗙鐞�"]);
+    const alwaysShowTitles = new Set(["閲囪喘閫�璐у崟", "渚涘簲鍟嗙鐞�", "鎵爜鏌ヨ澶�"]);
     const collectMenuTitles = routes => {
       if (!Array.isArray(routes)) return;
       routes.forEach(route => {
@@ -1284,6 +1327,7 @@
     // 閫氱敤杩囨护鍑芥暟
     const filterArray = (targetArray, specialMapping) => {
       const filtered = targetArray.filter(item => {
+        if (alwaysShowTitles.has(item.label)) return true;
         let matched = allowedMenuTitles.has(item.label);
         if (specialMapping && !matched && specialMapping[item.label]) {
           matched = allowedMenuTitles.has(specialMapping[item.label]);
diff --git a/src/static/images/icon/saomashebei.svg b/src/static/images/icon/saomashebei.svg
new file mode 100644
index 0000000..84852ea
--- /dev/null
+++ b/src/static/images/icon/saomashebei.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" fill="none" version="1.1" width="28" height="28" viewBox="0 0 28 28">
+  <rect x="1" y="1" width="26" height="26" rx="6" fill="#9C27B0"/>
+  <rect x="7" y="7" width="5" height="5" rx="1" fill="#FFFFFF"/>
+  <rect x="16" y="7" width="5" height="5" rx="1" fill="#FFFFFF"/>
+  <rect x="7" y="16" width="5" height="5" rx="1" fill="#FFFFFF"/>
+  <rect x="16" y="16" width="2.5" height="2.5" rx="0.5" fill="#FFFFFF"/>
+  <rect x="19.5" y="16" width="2.5" height="2.5" rx="0.5" fill="#FFFFFF"/>
+  <rect x="16" y="19.5" width="2.5" height="2.5" rx="0.5" fill="#FFFFFF"/>
+</svg>

--
Gitblit v1.9.3