From 8ffaad6d2d69a81ace44177580bfca2610be858f Mon Sep 17 00:00:00 2001
From: ai-coding <ai-coding@devgenie.local>
Date: 星期四, 20 八月 2026 20:25:52 +0800
Subject: [PATCH] Merge origin/feat-0819-opt into feat/deploy-uat

---
 src/views/assetManagement/vehicleManagement/operatingVehicle/components/PreparationDetailModal.vue |   61 +++++++++++++++
 src/views/assetManagement/vehicleManagement/operatingVehicle/detail.vue                            |   40 ++++++++++
 src/views/userContent/contentCenter/opsApp.vue                                                     |   46 +++-------
 src/views/assetManagement/vehicleManagement/operatingVehicle/components/DetectionDetailModal.vue   |   70 +++++++++++++++++
 4 files changed, 185 insertions(+), 32 deletions(-)

diff --git a/src/views/assetManagement/vehicleManagement/operatingVehicle/components/DetectionDetailModal.vue b/src/views/assetManagement/vehicleManagement/operatingVehicle/components/DetectionDetailModal.vue
index 4b79c1c..f950075 100644
--- a/src/views/assetManagement/vehicleManagement/operatingVehicle/components/DetectionDetailModal.vue
+++ b/src/views/assetManagement/vehicleManagement/operatingVehicle/components/DetectionDetailModal.vue
@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { NImage, NModal } from 'naive-ui'
+import { NImage, NModal, NTag } from 'naive-ui'
 import { computed } from 'vue'
 import dayjs from 'dayjs'
 
@@ -29,6 +29,24 @@
 function handleClose() {
   emit('update:show', false)
 }
+
+/**
+ * 车辆运维字段优化:AEB调至"中" 胶囊状态(只读回显,不可编辑)
+ *
+ * 状态枚举:0=历史无数据(存量/老版本未上报,DB DEFAULT)、1=检测完成、2=待检测。
+ * 判定规则:检测完成 = (aebMiddle === 1) || (存在照片)
+ *
+ * 为什么要「有照片」兜底:公司 DDL 门禁要求新增 int 列显式 NOT NULL,
+ * 故 `aeb_middle` 为 NOT NULL DEFAULT 0——"老版本 APP 只传照片、不传状态"的记录会落为 0,
+ * 此时以有照片判定为检测完成,与 APP 端"识别到照片上传自动流转"的语义保持一致。
+ * 注:枚举里 1 固定表示"检测完成"(与同表既有检测项 1=pass 对齐),故此处判 === 1。
+ */
+const aebMiddleDone = computed(() => {
+  const status = props.detailData?.aebMiddleStatus
+  if (status !== null && status !== undefined && Number(status) === 1)
+    return true
+  return Boolean(props.detailData?.aebMiddleImages?.length)
+})
 </script>
 
 <template>
@@ -181,6 +199,56 @@
             </div>
           </div>
 
+          <!--
+            车辆运维字段优化:AEB调至"中"
+            回显胶囊状态(不可编辑)+ 照片原图;状态优先取后端 aebMiddle,为空(历史数据)时按有无照片派生
+          -->
+          <div class="manual-item">
+            <div class="check-label">
+              AEB调至"中":
+            </div>
+            <div class="image-list">
+              <NTag :bordered="false" :type="aebMiddleDone ? 'success' : 'default'" size="small">
+                {{ aebMiddleDone ? '检测完成' : '待检测' }}
+              </NTag>
+              <template v-if="detailData.aebMiddleImages && detailData.aebMiddleImages.length">
+                <NImage
+                  v-for="(img, index) in detailData.aebMiddleImages"
+                  :key="index"
+                  :src="img || 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png'"
+                  :width="56"
+                  :height="56"
+                  object-fit="cover"
+                  fallback-src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png"
+                  class="check-image"
+                />
+              </template>
+              <span v-else class="no-data">-</span>
+            </div>
+          </div>
+
+          <!-- 车辆运维字段优化:二排座椅调节(按需求仅回显照片) -->
+          <div class="manual-item">
+            <div class="check-label">
+              二排座椅调节:
+            </div>
+            <div class="image-list">
+              <template v-if="detailData.secondRowSeatImages && detailData.secondRowSeatImages.length">
+                <NImage
+                  v-for="(img, index) in detailData.secondRowSeatImages"
+                  :key="index"
+                  :src="img || 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png'"
+                  :width="56"
+                  :height="56"
+                  object-fit="cover"
+                  fallback-src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png"
+                  class="check-image"
+                />
+              </template>
+              <span v-else class="no-data">-</span>
+            </div>
+          </div>
+
           <!-- 支付功能 -->
           <!-- <div class="manual-item">
             <div class="check-label">
diff --git a/src/views/assetManagement/vehicleManagement/operatingVehicle/components/PreparationDetailModal.vue b/src/views/assetManagement/vehicleManagement/operatingVehicle/components/PreparationDetailModal.vue
index 073930e..96212d4 100644
--- a/src/views/assetManagement/vehicleManagement/operatingVehicle/components/PreparationDetailModal.vue
+++ b/src/views/assetManagement/vehicleManagement/operatingVehicle/components/PreparationDetailModal.vue
@@ -30,6 +30,17 @@
   emit("update:show", false);
 }
 
+/**
+ * 车辆运维字段优化:车内摄像头分组
+ * 「车内摄像头」标签下分摄像头1/2/3 三行展示,逐个可追溯;
+ * 数据由 detail.vue 用 collectImageUrls 解析后传入,历史数据为空时模板显示 "-"
+ */
+const cameraGroups = computed(() => [
+  { label: '摄像头1', images: props.detailData?.camera1Images ?? [] },
+  { label: '摄像头2', images: props.detailData?.camera2Images ?? [] },
+  { label: '摄像头3', images: props.detailData?.camera3Images ?? [] },
+])
+
 function formatPressure(val: any) {
   if (val === null || val === undefined || val === "") return "-";
   const num = Number(val);
@@ -104,6 +115,30 @@
           </div>
           <div class="value" style="min-width: 200px">
             右后轮:{{ formatPressure(detailData.tirePressures[3].value) }}
+          </div>
+        </div>
+        <!-- 车辆运维字段优化:胎压照片(最多2张,无数据显示 "-") -->
+        <div class="flex mt-12px">
+          <div style="flex: 1">
+            <div class="label">胎压照片</div>
+            <div
+              class="flex gap-16px"
+              v-if="detailData.tirePressureImages && detailData.tirePressureImages.length"
+            >
+              <div
+                class="pic"
+                v-for="(img, idx) in detailData.tirePressureImages"
+                :key="idx"
+              >
+                <NImage
+                  :src="img"
+                  width="56"
+                  height="56"
+                  fallback-src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png"
+                />
+              </div>
+            </div>
+            <div v-else class="value">-</div>
           </div>
         </div>
         <div class="flex mt-12px">
@@ -247,6 +282,32 @@
             </div>
           </div>
         </div>
+        <!--
+          车辆运维字段优化:车内摄像头
+          「车内摄像头」一个标签下分 摄像头1/2/3 三行展示,便于追溯具体摄像头;
+          每个摄像头最多 2 张,历史数据为空时显示 "-"
+        -->
+        <div class="mt-12px">
+          <div class="label">车内摄像头</div>
+          <div
+            class="mt-8px"
+            v-for="cam in cameraGroups"
+            :key="cam.label"
+          >
+            <div class="label">{{ cam.label }}</div>
+            <div class="flex gap-16px" v-if="cam.images && cam.images.length">
+              <div class="pic" v-for="(img, idx) in cam.images" :key="idx">
+                <NImage
+                  :src="img"
+                  width="56"
+                  height="56"
+                  fallback-src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png"
+                />
+              </div>
+            </div>
+            <div v-else class="value">-</div>
+          </div>
+        </div>
       </div>
     </div>
   </NModal>
diff --git a/src/views/assetManagement/vehicleManagement/operatingVehicle/detail.vue b/src/views/assetManagement/vehicleManagement/operatingVehicle/detail.vue
index d63330f..2b52707 100644
--- a/src/views/assetManagement/vehicleManagement/operatingVehicle/detail.vue
+++ b/src/views/assetManagement/vehicleManagement/operatingVehicle/detail.vue
@@ -582,6 +582,7 @@
   { title: '清洁时间', key: 'operationTime', align: 'left' as const, width: 180, render: (row: any) => row.operationTime ? dayjs(row.operationTime).format('YYYY/MM/DD HH:mm:ss') : '-' },
   { title: '车身标识及清洁', key: 'bodyEmblem', align: 'left' as const, width: 160, render: (row: any) => renderImageCell(row.bodyEmblem) },
   { title: '产生总费用', key: 'totalCost', align: 'left' as const, width: 140, render: (row: any) => (row.totalCost != null ? `¥${row.totalCost}` : '-') },
+  { title: '车损', key: 'vehicleDamage', align: 'left' as const, width: 180, render: (row: any) => renderVehicleDamageCell(row) },
   { title: '操作人', key: 'operationsUserName', align: 'left' as const, width: 140 },
   {
     title: '操作',
@@ -944,6 +945,35 @@
     return <span>-</span>
   return (
     <NSpace size="small" justify="flex-start">
+      {urls.map((u: string, idx: number) => (
+        <NImage
+          key={idx}
+          src={u || 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png'}
+          width={40}
+          height={40}
+          object-fit="cover"
+          fallback-src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png"
+          style={{ borderRadius: '4px', cursor: 'pointer' }}
+        />
+      ))}
+    </NSpace>
+  )
+}
+
+/**
+ * 车辆运维字段优化:车身清洁列表「车损」列
+ * 需求:回显车损照片(原图);若 APP 选"否"未上传照片,后台回显 "-"。
+ * vehicleDamage: 0=否 1=是(历史数据为 null,按"否"处理)
+ */
+function renderVehicleDamageCell(row: any) {
+  const isDamaged = Number(row?.vehicleDamage) === 1
+  const urls = collectImageUrls(row?.vehicleDamagePicture)
+  // 选"否"或无照片:按需求统一显示 "-"
+  if (!isDamaged && !urls.length)
+    return <span>-</span>
+  return (
+    <NSpace size="small" justify="start" align="center">
+      <span>{isDamaged ? '是' : '否'}</span>
       {urls.map((u: string, idx: number) => (
         <NImage
           key={idx}
@@ -1349,6 +1379,11 @@
       orderSystemCheckImages: collectImageUrls(row.systemCheckPicture),
       interactiveScreenImages: collectImageUrls(row.interactiveScreenPicture),
       paymentCheckImages: collectImageUrls(row.paymentFunctionPicture),
+      // 车辆运维字段优化:AEB调至"中"(状态+照片)、二排座椅调节(照片)
+      // 胶囊状态优先取后端 aebMiddle(0待检测/1检测完成),为空(历史数据)时按有无照片派生
+      aebMiddleImages: collectImageUrls(row.aebMiddlePicture),
+      aebMiddleStatus: row.aebMiddle ?? null,
+      secondRowSeatImages: collectImageUrls(row.secondRowSeatPicture),
       vehicleLocation: row.vehicleLocation || '-',
       operator: row.operatorStartName || '-',
       detectionTime: row.operationTime,
@@ -1415,6 +1450,11 @@
       // bodyCleanImages: collectImageUrls(row.bodyEmblem),
       interiorCleanImages: collectImageUrls(row.cabinCleaning),
       lostItemImages: collectImageUrls(row.lostItems),
+      // 车辆运维字段优化:胎压照片 + 车内摄像头1/2/3(历史数据为空时弹窗内显示 "-")
+      tirePressureImages: collectImageUrls(row.tirePressurePicture),
+      camera1Images: collectImageUrls(row.camera1Picture),
+      camera2Images: collectImageUrls(row.camera2Picture),
+      camera3Images: collectImageUrls(row.camera3Picture),
       vehicleLocation: row.vehicleLocation || '-',
       lostItemRemark: row.lostItemsRemark || row.lostRemark || row.lostDescribe || '',
       operator: row.operationsUserName || '-',
diff --git a/src/views/userContent/contentCenter/opsApp.vue b/src/views/userContent/contentCenter/opsApp.vue
index 0d688b3..1df439b 100644
--- a/src/views/userContent/contentCenter/opsApp.vue
+++ b/src/views/userContent/contentCenter/opsApp.vue
@@ -5,7 +5,8 @@
   getVehicleDescription,
   saveVehicleDescription,
 } from '@/service/api/contentCenter'
-import VehiclePrepareForm from './components/VehiclePrepareForm.vue'
+// 车辆运维字段优化:整备标准说明收敛为单一富文本后,不再使用 VehiclePrepareForm(子分项表单)
+// 该组件文件暂予保留,如后续确认无回滚需求可一并删除
 import SimpleRichTextForm from './components/SimpleRichTextForm.vue'
 import AgreementListForm from './components/AgreementListForm.vue'
 
@@ -35,21 +36,14 @@
       const data = currentForm.getData()
       let saveRes: any = null
 
-      if (activeTab.value === 'prepare') {
-        const map: Record<string, number> = {
-          tirePressure: 1,
-          lighting: 2,
-          cabinClean: 4,
-        }
-        const activeKey = typeof currentForm.getActiveKey === 'function' ? currentForm.getActiveKey() : 'tirePressure'
-        const t = map[activeKey] ?? 1
-        const content = data[activeKey] || ''
-        saveRes = await saveVehicleDescription({ descriptionType: t, descriptionContent: content })
-      }
-      else if (['charge', 'inspect', 'bodyClean', 'insurance', 'annual', 'maintenance', 'qualification'].includes(activeTab.value)) {
+      if (['prepare', 'charge', 'inspect', 'bodyClean', 'insurance', 'annual', 'maintenance', 'qualification'].includes(activeTab.value)) {
         const typeMap: Record<string, number> = {
+          // 车辆运维字段优化:车辆整备标准说明收敛为单一富文本,写入新增的 descriptionType=12
+          // (原按胎压1/灯光2/舱内清洁4 三个子分项配置,收敛后不再区分模块)
+          prepare: 12,
           charge: 6,
           inspect: 7,
+          // 车辆清洁标准说明复用既有 type 3(车身标识及清洁),不新增类型,避免与存量文案数据分裂
           bodyClean: 3,
           insurance: 8,
           annual: 9,
@@ -90,20 +84,10 @@
       return
     }
 
-    if (tabKey === 'prepare') {
-      const types = [1, 2, 4]
-      const res = await Promise.all(types.map(t => getVehicleDescription(t)))
-      if (res.every(r => r && r.code === 200)) {
-        const data: Record<string, string> = {
-          tirePressure: res[0].data?.descriptionContent || '',
-          lighting: res[1].data?.descriptionContent || '',
-          cabinClean: res[2].data?.descriptionContent || '',
-        }
-        currentForm.setData(data)
-      }
-    }
-    else if (['charge', 'inspect', 'bodyClean', 'insurance', 'annual', 'maintenance', 'qualification'].includes(tabKey)) {
+    if (['prepare', 'charge', 'inspect', 'bodyClean', 'insurance', 'annual', 'maintenance', 'qualification'].includes(tabKey)) {
       const typeMap: Record<string, number> = {
+        // 车辆运维字段优化:整备读取收敛后的单一富文本 type=12
+        prepare: 12,
         charge: 6,
         inspect: 7,
         bodyClean: 3,
@@ -153,12 +137,12 @@
           >
             <n-tab-pane v-for="tab in tabs" :key="tab.key" :name="tab.key" :tab="tab.label">
               <div class="tab-scroll-area">
-                <VehiclePrepareForm
-                  v-if="tab.key === 'prepare'"
-                  :ref="(el) => { if (el) contentFormRefs[tab.key] = el }"
-                />
+                <!--
+                  车辆运维字段优化:车辆整备标准说明已收敛为单一富文本(descriptionType=12),
+                  与车辆清洁(3)/车辆检测(7)等一致复用 SimpleRichTextForm,不再按子分项配置
+                -->
                 <SimpleRichTextForm
-                  v-else-if="['charge', 'inspect', 'bodyClean', 'insurance', 'annual', 'maintenance', 'qualification'].includes(tab.key)"
+                  v-if="['prepare', 'charge', 'inspect', 'bodyClean', 'insurance', 'annual', 'maintenance', 'qualification'].includes(tab.key)"
                   :ref="(el) => { if (el) contentFormRefs[tab.key] = el }"
                 />
                 <AgreementListForm

--
Gitblit v1.7.1