瀏覽代碼

feat(web-admin): add vehicle photo on self-vehicle page

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 3 月之前
父節點
當前提交
9089e455e9
共有 1 個文件被更改,包括 95 次插入 和 5 次删除
  1. 95 5
      web-admin/src/views/pl/self-vehicle/index.vue

+ 95 - 5
web-admin/src/views/pl/self-vehicle/index.vue

@@ -107,6 +107,23 @@
             header-align="center"
             :index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
           />
+          <el-table-column
+            v-if="isColumnVisible('photoUrls')"
+            label="照片"
+            min-width="80"
+            align="center"
+            header-align="center"
+          >
+            <template #default="{ row }">
+              <img
+                v-if="firstPhotoUrl(row.photoUrls)"
+                class="pl-photo-thumb"
+                :src="resolvePhotoDisplayUrl(firstPhotoUrl(row.photoUrls))"
+                alt="车辆照片"
+              />
+              <span v-else class="pl-photo-thumb pl-photo-thumb--empty">未上传</span>
+            </template>
+          </el-table-column>
           <el-table-column
             prop="licensePlate"
             label="车牌号"
@@ -127,6 +144,17 @@
               </div>
             </template>
           </el-table-column>
+          <el-table-column
+            v-if="isColumnVisible('certAttachments')"
+            label="证书附件"
+            min-width="340"
+            align="left"
+            header-align="left"
+          >
+            <template #default="{ row }">
+              <PlCertListAttachmentCell :cert-list="row.certList" show-vehicle-part />
+            </template>
+          </el-table-column>
           <el-table-column label="车辆类型" min-width="90" align="center" header-align="center" v-if="isColumnVisible('vehicleType')">
             <template #default>
               <el-tag type="success" size="small">自有</el-tag>
@@ -249,6 +277,15 @@
               maxlength="512"
             />
           </el-form-item>
+          <el-form-item label="车辆照片" style="grid-column: 1 / -1">
+            <AttachmentUploadField
+              ref="photoUploadRef"
+              v-model="formData.photoUrls"
+              :limit="1"
+              accept="image/*"
+              upload-folder="pl/vehicle-photo"
+            />
+          </el-form-item>
         </div>
 
         <div class="sub-table-section">
@@ -364,6 +401,16 @@
           <el-icon><Delete /></el-icon>删除
         </el-button>
       </template>
+      <template #photo>
+        <AttachmentUploadField
+          v-if="viewDetail?.photoUrls"
+          :model-value="viewDetail.photoUrls"
+          disabled
+          :limit="1"
+          accept="image/*"
+        />
+        <span v-else>—</span>
+      </template>
       <template #vehicleDetailTabs>
         <PlVehicleCertPersonnelTabs
           :cert-list="viewDetail?.certList || []"
@@ -383,6 +430,7 @@
 <script setup lang="ts">
 import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
 import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
+import AttachmentUploadField from '@/components/business/attachment-upload-field/index.vue'
 import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
 import { Check, Close, Delete, Edit, Plus, RefreshLeft, Search, Van } from '@element-plus/icons-vue'
 import {
@@ -399,6 +447,7 @@ import {
 import PlVehicleCertPartTabs from '@/components/business/pl-vehicle-cert-part-tabs/index.vue'
 import PlVehicleCertPersonnelTabs from '@/components/business/pl-vehicle-cert-personnel-tabs/index.vue'
 import PlVehicleCertBadges from '@/components/business/pl-vehicle-cert-badges/index.vue'
+import PlCertListAttachmentCell from '@/components/business/pl-cert-list-attachment-cell/index.vue'
 import type { VehiclePersonnelViewRow } from '@/components/business/pl-vehicle-personnel-view-list/index.vue'
 import SystemViewDialog, {
   type ViewSection
@@ -414,6 +463,7 @@ import {
   getPlVehicleStatusTagType,
   normalizePlPersonnelSummaryDisplay
 } from '@/utils/pl-display'
+import { firstPhotoUrl, resolvePhotoDisplayUrl } from '@/utils/pl-photo'
 import {
   collectSelfVehicleRequiredCertErrors,
   ensureSelfVehicleCertSlots,
@@ -442,6 +492,8 @@ const vehicleList = ref<Vehicle[]>([])
 const carrierList = ref<Carrier[]>([])
 
 const listColumnDefs = [
+  { prop: 'photoUrls', label: '照片' },
+  { prop: 'certAttachments', label: '证书附件' },
   { prop: 'vehicleType', label: '车辆类型' },
   { prop: 'vehicleStatus', label: '车辆状态' },
   { prop: 'loadCapacity', label: '载重(吨)' },
@@ -456,13 +508,14 @@ const {
   isIndeterminate,
   isColumnVisible,
   resetToDefault
-} = usePersistedListColumns('pl-self-vehicle-list-columns', listColumnDefs)
+} = usePersistedListColumns('pl-self-vehicle-list-columns-v3', listColumnDefs)
 
 const personnelOptions = ref<Personnel[]>([])
 const selectedRows = ref<Vehicle[]>([])
 const dialogVisible = ref(false)
 const dialogTitle = ref('')
 const formRef = ref()
+const photoUploadRef = ref<InstanceType<typeof AttachmentUploadField>>()
 const isEdit = ref(false)
 
 const personnelList = ref<VehiclePersonnelRow[]>([])
@@ -495,7 +548,8 @@ const formData = reactive({
   carrierId: '',
   carrierName: '',
   vehicleStatus: 'available',
-  remark: ''
+  remark: '',
+  photoUrls: ''
 })
 
 const formRules = {
@@ -563,7 +617,16 @@ const viewSections = computed<ViewSection[]>(() => {
         { label: '备注', value: d.remark || '—', span: 'full' }
       ],
       fieldLayout: 'table',
-      tableMode: 'triple',
+      tableMode: 'triple'
+    },
+    {
+      title: '车辆照片',
+      fields: [],
+      slot: 'photo'
+    },
+    {
+      title: '',
+      fields: [],
       slot: 'vehicleDetailTabs'
     }
   ]
@@ -833,7 +896,8 @@ function handleAdd() {
     carrierId: '',
     carrierName: '',
     vehicleStatus: 'available',
-    remark: ''
+    remark: '',
+    photoUrls: ''
   })
   resetSubTables()
   certList.value = ensureSelfVehicleCertSlots([], formData.trailerNo)
@@ -880,7 +944,8 @@ async function handleEdit(row: Vehicle) {
       carrierId: detail.carrierId || '',
       carrierName: detail.carrierName || '',
       vehicleStatus: detail.vehicleStatus,
-      remark: detail.remark
+      remark: detail.remark,
+      photoUrls: detail.photoUrls || ''
     })
     personnelList.value = resolvePersonnelRows(
       (detail.personnelList || []).map(p => ({
@@ -915,6 +980,10 @@ async function handleSubmit() {
     ElMessage.warning('证书附件上传中,请稍候')
     return
   }
+  if (photoUploadRef.value?.hasUploading?.()) {
+    ElMessage.warning('附件上传中,请稍候')
+    return
+  }
 
   if (formData.carrierId) {
     const carrier = carrierList.value.find(c => c.id === formData.carrierId)
@@ -934,6 +1003,7 @@ async function handleSubmit() {
       loadCapacity: formData.loadCapacity,
       vehicleStatus: formData.vehicleStatus,
       remark: formData.remark,
+      photoUrls: formData.photoUrls || undefined,
       personnelList: personnelList.value.map(p => ({
         personnelId: p.personnelId,
         personnelName: p.personnelName.trim(),
@@ -1032,4 +1102,24 @@ onMounted(() => {
   flex-wrap: nowrap;
   gap: 6px;
 }
+
+.pl-photo-thumb {
+  width: 48px;
+  height: 48px;
+  border-radius: 8px;
+  object-fit: cover;
+  border: 1px solid var(--el-border-color, #d9e8e2);
+  display: inline-block;
+  vertical-align: middle;
+  background: #f8fafc;
+}
+
+.pl-photo-thumb--empty {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 11px;
+  color: #94a3b8;
+  box-sizing: border-box;
+}
 </style>