|
|
@@ -0,0 +1,661 @@
|
|
|
+# 车辆/驾押人员照片字段 Implementation Plan
|
|
|
+
|
|
|
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
+
|
|
|
+**Goal:** 为 `pl_vehicle` / `pl_personnel` 增加可选 `photo_urls`(默认 1 张图),并在三方车辆、山峡自有车辆、三方/山峡驾押人员四页完成列表缩略图、表单上传与查看展示。
|
|
|
+
|
|
|
+**Architecture:** 库表固定字段存逗号分隔 MinIO URL;后端 Schema/Create/Update/Read 透传 `photoUrls`;前端复用 `AttachmentUploadField`(`limit=1`、`accept="image/*"`),列表用 48×48 缩略图取第一段 URL。
|
|
|
+
|
|
|
+**Tech Stack:** MySQL 5.7 · FastAPI · SQLAlchemy · Pydantic v2 · Vue 3 · Element Plus · MinIO
|
|
|
+
|
|
|
+**Spec:** `docs/superpowers/specs/2026-08-02-pl-vehicle-personnel-photo-design.md`
|
|
|
+**Demo:** `docs/fresh-mint-pl-vehicle-personnel-photo-demo.html`
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 文件结构
|
|
|
+
|
|
|
+| 操作 | 文件 | 职责 |
|
|
|
+|------|------|------|
|
|
|
+| Create | `backend/scripts/migrate_20260802_pl_vehicle_personnel_photo_urls.sql` | 加列迁移 |
|
|
|
+| Modify | `docs/sql/shanxia2.0.sql` | 基线 DDL 同步 |
|
|
|
+| Modify | `backend/app/models/pl_models.py` | `PlVehicle` / `PlPersonnel` 列 |
|
|
|
+| Modify | `backend/app/schemas/pl/base_data.py` | Create/Update/Base 增加 `photoUrls` |
|
|
|
+| Modify | `backend/app/api/pl/base_data.py` | create 构造写入 `photo_urls`(update 经 setattr 自动) |
|
|
|
+| Create | `backend/tests/test_pl_vehicle_personnel_photo_schema.py` | Schema 接受 `photoUrls` |
|
|
|
+| Modify | `web-admin/src/api/pl/index.ts` | 类型增加 `photoUrls` |
|
|
|
+| Create | `web-admin/src/utils/pl-photo.ts` | 取首图 URL + MinIO 完整地址 |
|
|
|
+| Modify | `web-admin/src/views/pl/vehicle/index.vue` | 三方车辆 UI |
|
|
|
+| Modify | `web-admin/src/views/pl/self-vehicle/index.vue` | 自有车辆 UI |
|
|
|
+| Modify | `web-admin/src/views/pl/personnel/index.vue` | 驾押人员 UI(双归属共用) |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 部署顺序
|
|
|
+
|
|
|
+```
|
|
|
+1. 执行 migrate_20260802_pl_vehicle_personnel_photo_urls.sql
|
|
|
+2. 部署 backend
|
|
|
+3. 部署 web-admin
|
|
|
+4. 按 Task 8 验收
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 1: DB 迁移与 SQL 基线
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Create: `backend/scripts/migrate_20260802_pl_vehicle_personnel_photo_urls.sql`
|
|
|
+- Modify: `docs/sql/shanxia2.0.sql`(`pl_vehicle` / `pl_personnel` 的 `CREATE TABLE`)
|
|
|
+
|
|
|
+- [ ] **Step 1: 写迁移脚本**
|
|
|
+
|
|
|
+```sql
|
|
|
+-- backend/scripts/migrate_20260802_pl_vehicle_personnel_photo_urls.sql
|
|
|
+-- 车辆/驾押人员:增加照片 URL 字段(逗号分隔,默认 1 张)
|
|
|
+
|
|
|
+ALTER TABLE `pl_vehicle`
|
|
|
+ ADD COLUMN `photo_urls` varchar(2000) DEFAULT NULL COMMENT '车辆照片URL,逗号分隔(默认1张)' AFTER `vehicle_status`;
|
|
|
+
|
|
|
+ALTER TABLE `pl_personnel`
|
|
|
+ ADD COLUMN `photo_urls` varchar(2000) DEFAULT NULL COMMENT '人员照片URL,逗号分隔(默认1张)' AFTER `status`;
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 更新 `docs/sql/shanxia2.0.sql`**
|
|
|
+
|
|
|
+在 `CREATE TABLE pl_vehicle` 中,于 `` `vehicle_status` ... `` 行后插入:
|
|
|
+
|
|
|
+```sql
|
|
|
+ `photo_urls` varchar(2000) DEFAULT NULL COMMENT '车辆照片URL,逗号分隔(默认1张)',
|
|
|
+```
|
|
|
+
|
|
|
+在 `CREATE TABLE pl_personnel` 中,于 `` `status` ... `` 行后插入:
|
|
|
+
|
|
|
+```sql
|
|
|
+ `photo_urls` varchar(2000) DEFAULT NULL COMMENT '人员照片URL,逗号分隔(默认1张)',
|
|
|
+```
|
|
|
+
|
|
|
+(若基线中有对应 INSERT 列清单,无需改历史 INSERT;新列可为 NULL。)
|
|
|
+
|
|
|
+- [ ] **Step 3: 在目标库执行迁移(本地)**
|
|
|
+
|
|
|
+Run(按本地习惯选客户端):
|
|
|
+
|
|
|
+```bash
|
|
|
+# 示例:在 backend 环境对当前库执行
|
|
|
+cd backend && source venv/bin/activate
|
|
|
+# 使用项目惯用方式执行 SQL;或 mysql 客户端:
|
|
|
+# mysql ... < scripts/migrate_20260802_pl_vehicle_personnel_photo_urls.sql
|
|
|
+```
|
|
|
+
|
|
|
+Expected: 两表均有 `photo_urls` 列,无报错。若列已存在则跳过或先检查 `SHOW COLUMNS`。
|
|
|
+
|
|
|
+- [ ] **Step 4: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add backend/scripts/migrate_20260802_pl_vehicle_personnel_photo_urls.sql docs/sql/shanxia2.0.sql
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+chore(db): add photo_urls to pl_vehicle and pl_personnel
|
|
|
+
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 2: ORM 模型
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `backend/app/models/pl_models.py`
|
|
|
+
|
|
|
+- [ ] **Step 1: 在 `PlVehicle` 增加列**
|
|
|
+
|
|
|
+在 `vehicle_status = Column(...)` 之后、`tenant_no = Column(...)` 之前插入:
|
|
|
+
|
|
|
+```python
|
|
|
+ photo_urls = Column(String(2000), nullable=True, comment="车辆照片URL,逗号分隔(默认1张)")
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 在 `PlPersonnel` 增加列**
|
|
|
+
|
|
|
+在 `status = Column(...)` 之后、`tenant_no = Column(...)` 之前插入:
|
|
|
+
|
|
|
+```python
|
|
|
+ photo_urls = Column(String(2000), nullable=True, comment="人员照片URL,逗号分隔(默认1张)")
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add backend/app/models/pl_models.py
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+feat(pl): map photo_urls on vehicle and personnel models
|
|
|
+
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 3: Schema + API 透传(含单测)
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `backend/app/schemas/pl/base_data.py`
|
|
|
+- Modify: `backend/app/api/pl/base_data.py`
|
|
|
+- Create: `backend/tests/test_pl_vehicle_personnel_photo_schema.py`
|
|
|
+
|
|
|
+- [ ] **Step 1: 写失败测试**
|
|
|
+
|
|
|
+```python
|
|
|
+"""车辆/人员 Schema 接受 photoUrls。"""
|
|
|
+from __future__ import annotations
|
|
|
+
|
|
|
+from decimal import Decimal
|
|
|
+
|
|
|
+from app.schemas.pl.base_data import PersonnelCreate, PersonnelUpdate, VehicleCreate, VehicleUpdate
|
|
|
+
|
|
|
+
|
|
|
+def test_vehicle_create_accepts_photo_urls():
|
|
|
+ body = VehicleCreate.model_validate(
|
|
|
+ {
|
|
|
+ "vehicleType": "carrier",
|
|
|
+ "licensePlate": "皖A12345",
|
|
|
+ "vehicleStatus": "available",
|
|
|
+ "photoUrls": "pl/vehicle-photo/a.jpg",
|
|
|
+ }
|
|
|
+ )
|
|
|
+ assert body.photo_urls == "pl/vehicle-photo/a.jpg"
|
|
|
+
|
|
|
+
|
|
|
+def test_vehicle_update_accepts_photo_urls():
|
|
|
+ body = VehicleUpdate.model_validate({"photoUrls": "pl/vehicle-photo/b.jpg"})
|
|
|
+ assert body.photo_urls == "pl/vehicle-photo/b.jpg"
|
|
|
+
|
|
|
+
|
|
|
+def test_personnel_create_accepts_photo_urls():
|
|
|
+ body = PersonnelCreate.model_validate(
|
|
|
+ {
|
|
|
+ "personnelName": "张三",
|
|
|
+ "personnelType": "driver",
|
|
|
+ "personnelAffiliation": "self",
|
|
|
+ "status": "1",
|
|
|
+ "photoUrls": "pl/personnel-photo/c.jpg",
|
|
|
+ }
|
|
|
+ )
|
|
|
+ assert body.photo_urls == "pl/personnel-photo/c.jpg"
|
|
|
+
|
|
|
+
|
|
|
+def test_personnel_update_accepts_photo_urls():
|
|
|
+ body = PersonnelUpdate.model_validate({"photoUrls": ""})
|
|
|
+ assert body.photo_urls == ""
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 跑测确认失败**
|
|
|
+
|
|
|
+Run: `cd backend && source venv/bin/activate && pytest tests/test_pl_vehicle_personnel_photo_schema.py -v`
|
|
|
+Expected: FAIL(`photoUrls` / `photo_urls` 额外字段被忽略或校验失败,断言失败)
|
|
|
+
|
|
|
+- [ ] **Step 3: 改 Schema**
|
|
|
+
|
|
|
+在 `VehicleBase`(约 `vehicle_status` 行后、`remark` 前)增加:
|
|
|
+
|
|
|
+```python
|
|
|
+ photo_urls: Optional[str] = Field(
|
|
|
+ default=None, max_length=2000, alias="photoUrls", description="车辆照片URL,逗号分隔"
|
|
|
+ )
|
|
|
+```
|
|
|
+
|
|
|
+在 `VehicleUpdate`(`vehicle_status` 与 `remark` 之间)增加:
|
|
|
+
|
|
|
+```python
|
|
|
+ photo_urls: Optional[str] = Field(default=None, max_length=2000, alias="photoUrls")
|
|
|
+```
|
|
|
+
|
|
|
+在 `PersonnelBase`(`status` 与 `remark` 之间)增加:
|
|
|
+
|
|
|
+```python
|
|
|
+ photo_urls: Optional[str] = Field(
|
|
|
+ default=None, max_length=2000, alias="photoUrls", description="人员照片URL,逗号分隔"
|
|
|
+ )
|
|
|
+```
|
|
|
+
|
|
|
+在 `PersonnelUpdate`(`status` 与 `remark` 之间)增加:
|
|
|
+
|
|
|
+```python
|
|
|
+ photo_urls: Optional[str] = Field(default=None, max_length=2000, alias="photoUrls")
|
|
|
+```
|
|
|
+
|
|
|
+确保文件顶部已有 `Optional` 导入(已有则不动)。
|
|
|
+
|
|
|
+- [ ] **Step 4: 改 API create 写入**
|
|
|
+
|
|
|
+`create_vehicle` 中 `PlVehicle(...)` 增加:
|
|
|
+
|
|
|
+```python
|
|
|
+ photo_urls=payload.photo_urls,
|
|
|
+```
|
|
|
+
|
|
|
+`create_personnel` 中 `PlPersonnel(...)` 增加:
|
|
|
+
|
|
|
+```python
|
|
|
+ photo_urls=payload.photo_urls,
|
|
|
+```
|
|
|
+
|
|
|
+`update_vehicle` / `update_personnel` 已用 `model_dump(exclude_unset=True)` + `setattr`,无需额外分支。
|
|
|
+
|
|
|
+List/Detail:`VehicleRead.model_validate(vehicle)` / personnel 等同理,ORM 有列后自动带出。
|
|
|
+
|
|
|
+- [ ] **Step 5: 跑测确认通过**
|
|
|
+
|
|
|
+Run: `cd backend && source venv/bin/activate && pytest tests/test_pl_vehicle_personnel_photo_schema.py -v`
|
|
|
+Expected: PASS(4 passed)
|
|
|
+
|
|
|
+- [ ] **Step 6: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add backend/app/schemas/pl/base_data.py backend/app/api/pl/base_data.py backend/tests/test_pl_vehicle_personnel_photo_schema.py
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+feat(pl): expose photoUrls on vehicle and personnel APIs
|
|
|
+
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 4: 前端类型与照片工具函数
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `web-admin/src/api/pl/index.ts`
|
|
|
+- Create: `web-admin/src/utils/pl-photo.ts`
|
|
|
+
|
|
|
+- [ ] **Step 1: 扩展类型**
|
|
|
+
|
|
|
+在 `Vehicle` 接口(`remark?: string` 附近)增加:
|
|
|
+
|
|
|
+```ts
|
|
|
+ photoUrls?: string
|
|
|
+```
|
|
|
+
|
|
|
+在 `VehiclePayload` 同样增加:
|
|
|
+
|
|
|
+```ts
|
|
|
+ photoUrls?: string
|
|
|
+```
|
|
|
+
|
|
|
+在 `Personnel` 与 `PersonnelPayload` 同样增加:
|
|
|
+
|
|
|
+```ts
|
|
|
+ photoUrls?: string
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 创建 `pl-photo.ts`**
|
|
|
+
|
|
|
+```ts
|
|
|
+import { normalizeAttachmentUrls } from '@/utils/download-attachment'
|
|
|
+
|
|
|
+/** 取逗号分隔附件中的第一张 URL(相对或绝对) */
|
|
|
+export function firstPhotoUrl(raw?: string | null): string {
|
|
|
+ const normalized = normalizeAttachmentUrls(raw)
|
|
|
+ if (!normalized) return ''
|
|
|
+ return normalized.split(',').map((s) => s.trim()).filter(Boolean)[0] || ''
|
|
|
+}
|
|
|
+
|
|
|
+/** 拼 MinIO 完整访问地址(与 AttachmentUploadField 一致) */
|
|
|
+export function resolvePhotoDisplayUrl(url?: string | null): string {
|
|
|
+ const trimmed = String(url || '').trim()
|
|
|
+ if (!trimmed) return ''
|
|
|
+ if (/^https?:\/\//i.test(trimmed)) return trimmed
|
|
|
+ const base = String(import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
|
|
|
+ return base ? `${base}/${trimmed.replace(/^\//, '')}` : trimmed
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add web-admin/src/api/pl/index.ts web-admin/src/utils/pl-photo.ts
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+feat(web-admin): add photoUrls types and pl-photo helpers
|
|
|
+
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 5: 三方车辆页 `vehicle/index.vue`
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `web-admin/src/views/pl/vehicle/index.vue`
|
|
|
+
|
|
|
+- [ ] **Step 1: 导入与 ref**
|
|
|
+
|
|
|
+在 script 中增加(保持与现有 import 风格一致):
|
|
|
+
|
|
|
+```ts
|
|
|
+import AttachmentUploadField from '@/components/business/attachment-upload-field/index.vue'
|
|
|
+import { firstPhotoUrl, resolvePhotoDisplayUrl } from '@/utils/pl-photo'
|
|
|
+```
|
|
|
+
|
|
|
+```ts
|
|
|
+const photoUploadRef = ref<InstanceType<typeof AttachmentUploadField>>()
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 列定义与持久化 key**
|
|
|
+
|
|
|
+`listColumnDefs` 最前增加:
|
|
|
+
|
|
|
+```ts
|
|
|
+ { prop: 'photoUrls', label: '照片' },
|
|
|
+```
|
|
|
+
|
|
|
+将 `usePersistedListColumns('pl-vehicle-list-columns-v2', ...)` 改为:
|
|
|
+
|
|
|
+```ts
|
|
|
+} = usePersistedListColumns('pl-vehicle-list-columns-v3', listColumnDefs)
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: 表格列(序号后、车牌号前)**
|
|
|
+
|
|
|
+```vue
|
|
|
+ <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>
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 4: 表单字段与 formData**
|
|
|
+
|
|
|
+`formData` 增加 `photoUrls: ''`。
|
|
|
+
|
|
|
+在备注 `el-form-item` 之后、关联人员区块之前增加:
|
|
|
+
|
|
|
+```vue
|
|
|
+ <el-form-item label="车辆照片" class="form-col-span-2" style="grid-column: 1 / -1">
|
|
|
+ <AttachmentUploadField
|
|
|
+ ref="photoUploadRef"
|
|
|
+ v-model="formData.photoUrls"
|
|
|
+ :limit="1"
|
|
|
+ accept="image/*"
|
|
|
+ upload-folder="pl/vehicle-photo"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+```
|
|
|
+
|
|
|
+(若当前表单用 `form-grid-3` 且无 `form-col-span-2` 全局样式生效,用内联 `grid-column: 1 / -1` 或包一层全宽容器。)
|
|
|
+
|
|
|
+- [ ] **Step 5: 查看弹窗**
|
|
|
+
|
|
|
+`viewSections` 的「车辆信息」`fields` 中,在备注前增加一项不够直观时,改为增加独立 section:
|
|
|
+
|
|
|
+```ts
|
|
|
+ {
|
|
|
+ title: '车辆照片',
|
|
|
+ fields: [],
|
|
|
+ slot: 'photo'
|
|
|
+ },
|
|
|
+```
|
|
|
+
|
|
|
+放在「车辆信息」section 之后(或之前均可;推荐车辆信息后、证书相关前)。
|
|
|
+
|
|
|
+在 `SystemViewDialog` 内:
|
|
|
+
|
|
|
+```vue
|
|
|
+ <template #photo>
|
|
|
+ <AttachmentUploadField
|
|
|
+ v-if="viewDetail?.photoUrls"
|
|
|
+ :model-value="viewDetail.photoUrls"
|
|
|
+ disabled
|
|
|
+ :limit="1"
|
|
|
+ accept="image/*"
|
|
|
+ />
|
|
|
+ <span v-else>—</span>
|
|
|
+ </template>
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 6: 读写与提交**
|
|
|
+
|
|
|
+- `handleAdd` 的 `Object.assign` 增加 `photoUrls: ''`
|
|
|
+- `handleEdit` 赋值 `formData` 时增加 `photoUrls: detail.photoUrls || ''`(或 row 详情字段)
|
|
|
+- `handleSubmit`:在证书上传检查旁增加:
|
|
|
+
|
|
|
+```ts
|
|
|
+ if (photoUploadRef.value?.hasUploading?.()) {
|
|
|
+ ElMessage.warning('附件上传中,请稍候')
|
|
|
+ return
|
|
|
+ }
|
|
|
+```
|
|
|
+
|
|
|
+payload 增加:
|
|
|
+
|
|
|
+```ts
|
|
|
+ photoUrls: formData.photoUrls || undefined,
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 7: 样式**
|
|
|
+
|
|
|
+在该页 `scoped` style 增加:
|
|
|
+
|
|
|
+```scss
|
|
|
+.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;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 8: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add web-admin/src/views/pl/vehicle/index.vue
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+feat(web-admin): add vehicle photo on tripartite vehicle page
|
|
|
+
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 6: 山峡自有车辆页 `self-vehicle/index.vue`
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `web-admin/src/views/pl/self-vehicle/index.vue`
|
|
|
+
|
|
|
+与 Task 5 **同样改动**,差异仅:
|
|
|
+
|
|
|
+| 项 | 值 |
|
|
|
+|----|-----|
|
|
|
+| 列表持久化 key | `pl-self-vehicle-list-columns-v2` → `pl-self-vehicle-list-columns-v3` |
|
|
|
+| 表单 label | `车辆照片` |
|
|
|
+| `upload-folder` | `pl/vehicle-photo` |
|
|
|
+| 查看 slot | `#photo` + `AttachmentUploadField` disabled |
|
|
|
+
|
|
|
+导入、`photoUploadRef`、`listColumnDefs` 照片列、表格列、formData、handleAdd/Edit/Submit、scoped 样式与 Task 5 相同模式。
|
|
|
+
|
|
|
+- [ ] **Step 1: 按上表完成全部改动**
|
|
|
+- [ ] **Step 2: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add web-admin/src/views/pl/self-vehicle/index.vue
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+feat(web-admin): add vehicle photo on self-vehicle page
|
|
|
+
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 7: 驾押人员页 `personnel/index.vue`
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `web-admin/src/views/pl/personnel/index.vue`
|
|
|
+
|
|
|
+- [ ] **Step 1: 导入与 ref**(同 Task 5,folder 不同)
|
|
|
+
|
|
|
+```ts
|
|
|
+import AttachmentUploadField from '@/components/business/attachment-upload-field/index.vue'
|
|
|
+import { firstPhotoUrl, resolvePhotoDisplayUrl } from '@/utils/pl-photo'
|
|
|
+
|
|
|
+const photoUploadRef = ref<InstanceType<typeof AttachmentUploadField>>()
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 列与 key**
|
|
|
+
|
|
|
+`listColumnDefs` 最前:
|
|
|
+
|
|
|
+```ts
|
|
|
+ { prop: 'photoUrls', label: '照片' },
|
|
|
+```
|
|
|
+
|
|
|
+持久化 key:`pl-personnel-list-columns-v2` → `pl-personnel-list-columns-v3`
|
|
|
+
|
|
|
+- [ ] **Step 3: 表格列**(序号后、姓名前)— 模板同车辆页,`alt="人员照片"`
|
|
|
+
|
|
|
+- [ ] **Step 4: 表单**
|
|
|
+
|
|
|
+`formData` 增加 `photoUrls: ''`。
|
|
|
+
|
|
|
+在备注之后、证书区块之前:
|
|
|
+
|
|
|
+```vue
|
|
|
+ <el-form-item label="人员照片" class="form-col-span-2" style="grid-column: 1 / -1">
|
|
|
+ <AttachmentUploadField
|
|
|
+ ref="photoUploadRef"
|
|
|
+ v-model="formData.photoUrls"
|
|
|
+ :limit="1"
|
|
|
+ accept="image/*"
|
|
|
+ upload-folder="pl/personnel-photo"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 5: 查看**
|
|
|
+
|
|
|
+`viewSections` 增加:
|
|
|
+
|
|
|
+```ts
|
|
|
+ {
|
|
|
+ title: '人员照片',
|
|
|
+ fields: [],
|
|
|
+ slot: 'photo'
|
|
|
+ },
|
|
|
+```
|
|
|
+
|
|
|
+(放在「基本信息」与「人员证书」之间。)
|
|
|
+
|
|
|
+```vue
|
|
|
+ <template #photo>
|
|
|
+ <AttachmentUploadField
|
|
|
+ v-if="viewDetail?.photoUrls"
|
|
|
+ :model-value="viewDetail.photoUrls"
|
|
|
+ disabled
|
|
|
+ :limit="1"
|
|
|
+ accept="image/*"
|
|
|
+ />
|
|
|
+ <span v-else>—</span>
|
|
|
+ </template>
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 6: 提交链路**
|
|
|
+
|
|
|
+- add/edit 重置与回填 `photoUrls`
|
|
|
+- submit 前 `photoUploadRef.hasUploading` 检查
|
|
|
+- payload:`photoUrls: formData.photoUrls || undefined`
|
|
|
+
|
|
|
+- [ ] **Step 7: 样式**(同 Task 5 的 `.pl-photo-thumb`)
|
|
|
+
|
|
|
+- [ ] **Step 8: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add web-admin/src/views/pl/personnel/index.vue
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+feat(web-admin): add personnel photo on driver/escort pages
|
|
|
+
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 8: 验收与图谱更新
|
|
|
+
|
|
|
+**Files:** 无新文件(或按仓库惯例更新 graphify)
|
|
|
+
|
|
|
+- [ ] **Step 1: 手工验收清单**
|
|
|
+
|
|
|
+1. 迁移后四页列表出现「照片」列且默认可见
|
|
|
+2. 车辆新增上传 1 张 → 列表缩略图 → 查看可见
|
|
|
+3. 人员新增上传 1 张 → 同上
|
|
|
+4. 不上传可保存
|
|
|
+5. `limit=1` 无法再选第二张
|
|
|
+6. 编辑删除照片后列表显示「未上传」
|
|
|
+7. 三方车辆 / 自有车辆 / 三方人员 / 山峡人员行为一致
|
|
|
+
|
|
|
+- [ ] **Step 2: 更新图谱**
|
|
|
+
|
|
|
+```bash
|
|
|
+./scripts/update-graph.sh all
|
|
|
+# 或分别:backend / frontend
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: 将设计文档状态改为已实现(可选)**
|
|
|
+
|
|
|
+`docs/superpowers/specs/2026-08-02-pl-vehicle-personnel-photo-design.md` 顶部状态:`待审阅` → `已实现`
|
|
|
+
|
|
|
+- [ ] **Step 4: Commit(若有图谱/状态变更)**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add graphify-out web-admin/graphify-out docs/superpowers/specs/2026-08-02-pl-vehicle-personnel-photo-design.md
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+chore: update graphify and mark vehicle/personnel photo design implemented
|
|
|
+
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Spec 覆盖自检
|
|
|
+
|
|
|
+| Spec 要求 | Task |
|
|
|
+|-----------|------|
|
|
|
+| `pl_vehicle.photo_urls` / `pl_personnel.photo_urls` | 1–2 |
|
|
|
+| Schema/API `photoUrls` 透传、非必填 | 3 |
|
|
|
+| 前端类型 | 4 |
|
|
|
+| 列表方案 A 48×48 | 5–7 |
|
|
|
+| 表单 AttachmentUploadField limit=1 | 5–7 |
|
|
|
+| folder `pl/vehicle-photo` / `pl/personnel-photo` | 5–7 |
|
|
|
+| 查看弹窗只读 | 5–7 |
|
|
|
+| 列默认可见 + 持久化 key 升版 | 5–7 |
|
|
|
+| 上传中拦截 | 5–7 |
|
|
|
+| 不做多图/OCR/小程序 | 无任务(YAGNI) |
|