docs: add implementation plan for driver transport history
Break down public history API, detail entry button, and list page; keep existing driver feedback form unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
ab46a8c999
commit
abcdf793c4
1 file changed
+590
@@ -0,0 +1,590 @@
|
||||
# 司机链接 · 运输记录 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:** 司机免登录页增加「运输记录」:按主车车牌聚合同租户计划,按取货/送货单日筛选(默认今天 + 今/明/后天快捷),点击进入对应司机详情;填写/回执 UI 与现状完全一致。
|
||||
|
||||
**Architecture:** 方案 1 — 用入口 `driver_access_token` 鉴权,解析主车 `license_plate` 后查询同车牌历史;列表项返回各单自有 token,前端跳转现有详情页;不改 feedback/attachments 契约。
|
||||
|
||||
**Tech Stack:** FastAPI + SQLAlchemy + Pydantic v2;Vue 3 + Vant(公开 H5);pytest
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-08-21-pl-driver-transport-history-design.md`
|
||||
**Demo:** `docs/demo-driver-transport-history.html`
|
||||
|
||||
**约束:** 填写界面(产品回传数量、品牌芯片、附件、备注、保存回执)**不得改版**,仅在详情底部增加「运输记录」入口;归档/作废空状态文案保持现状。
|
||||
|
||||
---
|
||||
|
||||
## 文件结构
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `backend/app/schemas/pl/transport.py` | `PublicTransportHistoryRecordRead` / `PublicTransportHistoryPageRead` |
|
||||
| `backend/app/api/pl/public_transport.py` | `GET /{token}/history`;车牌归一、日期命中查询、组装列表 |
|
||||
| `backend/tests/test_pl_public_transport_history.py` | 纯函数/标志/日期解析单测 |
|
||||
| `web-admin/src/api/pl/public-transport.ts` | `getHistory` + 类型 |
|
||||
| `web-admin/src/router/routes/staticRoutes.ts` | 注册 `/public/pl/transport/:token/history` |
|
||||
| `web-admin/src/views/public/pl/transport/index.vue` | 底部「运输记录」;voided/archived 也显示;error 不显示 |
|
||||
| `web-admin/src/views/public/pl/transport/history.vue` | 新列表页(筛选 + 列表);点击跳转详情 |
|
||||
|
||||
加密排除:`/api/public/pl/transport` 已在 `API_ENCRYPT_EXCLUDE_PATHS` 前缀排除,无需改配置。
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Schema + 日期/车牌辅助函数(TDD)
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/app/schemas/pl/transport.py`
|
||||
- Modify: `backend/app/api/pl/public_transport.py`(先加纯函数)
|
||||
- Create: `backend/tests/test_pl_public_transport_history.py`
|
||||
|
||||
- [ ] **Step 1: 写失败单测**
|
||||
|
||||
```python
|
||||
# backend/tests/test_pl_public_transport_history.py
|
||||
from datetime import date, datetime
|
||||
|
||||
from app.api.pl.public_transport import (
|
||||
_history_matches_date,
|
||||
_normalize_main_plate,
|
||||
_parse_history_date,
|
||||
_history_status_label,
|
||||
)
|
||||
|
||||
|
||||
def test_normalize_main_plate_trims():
|
||||
assert _normalize_main_plate(" 苏AD12345 ") == "苏AD12345"
|
||||
assert _normalize_main_plate(None) == ""
|
||||
assert _normalize_main_plate("") == ""
|
||||
|
||||
|
||||
def test_parse_history_date_ok_and_bad():
|
||||
assert _parse_history_date("2026-08-21") == date(2026, 8, 21)
|
||||
assert _parse_history_date("2026/08/21") is None
|
||||
assert _parse_history_date("") is None
|
||||
|
||||
|
||||
def test_history_matches_date_or_logic():
|
||||
day = date(2026, 8, 21)
|
||||
assert _history_matches_date(datetime(2026, 8, 21, 9, 0), None, day) is True
|
||||
assert _history_matches_date(None, datetime(2026, 8, 21, 18, 0), day) is True
|
||||
assert _history_matches_date(
|
||||
datetime(2026, 8, 20, 9, 0), datetime(2026, 8, 22, 18, 0), day
|
||||
) is False
|
||||
assert _history_matches_date(None, None, day) is False
|
||||
|
||||
|
||||
def test_history_status_label():
|
||||
assert _history_status_label(True, False) == "已作废"
|
||||
assert _history_status_label(True, True) == "已作废"
|
||||
assert _history_status_label(False, True) == "已归档"
|
||||
assert _history_status_label(False, False) == "进行中"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 跑测确认失败**
|
||||
|
||||
```bash
|
||||
cd backend && source venv/bin/activate && pytest tests/test_pl_public_transport_history.py -v
|
||||
```
|
||||
|
||||
Expected: FAIL(函数未定义)
|
||||
|
||||
- [ ] **Step 3: 实现辅助函数 + Schema**
|
||||
|
||||
在 `public_transport.py` 增加:
|
||||
|
||||
```python
|
||||
from datetime import date as date_cls
|
||||
|
||||
def _normalize_main_plate(value: Optional[str]) -> str:
|
||||
return (value or "").strip()
|
||||
|
||||
|
||||
def _parse_history_date(raw: Optional[str]) -> Optional[date_cls]:
|
||||
text = (raw or "").strip()
|
||||
if not text:
|
||||
return None
|
||||
try:
|
||||
return date_cls.fromisoformat(text)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def _as_date(value) -> Optional[date_cls]:
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, datetime):
|
||||
return value.date()
|
||||
if isinstance(value, date_cls):
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
def _history_matches_date(pickup_time, delivery_time, day: date_cls) -> bool:
|
||||
return _as_date(pickup_time) == day or _as_date(delivery_time) == day
|
||||
|
||||
|
||||
def _history_status_label(plan_voided: bool, plan_archived: bool) -> str:
|
||||
if plan_voided:
|
||||
return "已作废"
|
||||
if plan_archived:
|
||||
return "已归档"
|
||||
return "进行中"
|
||||
```
|
||||
|
||||
在 `schemas/pl/transport.py` 的 `PublicTransportPageRead` 后追加:
|
||||
|
||||
```python
|
||||
class PublicTransportHistoryRecordRead(BaseModel):
|
||||
plan_id: str = Field(alias="planId")
|
||||
plan_no: str = Field(alias="planNo")
|
||||
transport_id: str = Field(alias="transportId")
|
||||
driver_access_token: Optional[str] = Field(default=None, alias="driverAccessToken")
|
||||
driver_url: Optional[str] = Field(default=None, alias="driverUrl")
|
||||
license_plate: Optional[str] = Field(default=None, alias="licensePlate")
|
||||
pickup_time: Optional[datetime] = Field(default=None, alias="pickupTime")
|
||||
delivery_time: Optional[datetime] = Field(default=None, alias="deliveryTime")
|
||||
pickup_address: Optional[str] = Field(default=None, alias="pickupAddress")
|
||||
destination_address: Optional[str] = Field(default=None, alias="destinationAddress")
|
||||
plan_voided: bool = Field(default=False, alias="planVoided")
|
||||
plan_archived: bool = Field(default=False, alias="planArchived")
|
||||
status_label: str = Field(default="进行中", alias="statusLabel")
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class PublicTransportHistoryPageRead(BaseModel):
|
||||
license_plate: Optional[str] = Field(default=None, alias="licensePlate")
|
||||
date: str
|
||||
records: list[PublicTransportHistoryRecordRead] = Field(default_factory=list)
|
||||
model_config = {"populate_by_name": True}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 再跑测**
|
||||
|
||||
```bash
|
||||
cd backend && source venv/bin/activate && pytest tests/test_pl_public_transport_history.py -v
|
||||
```
|
||||
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/tests/test_pl_public_transport_history.py backend/app/api/pl/public_transport.py backend/app/schemas/pl/transport.py
|
||||
git commit -m "feat(pl): add transport history helpers and schemas"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: 实现 `GET /{token}/history`
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/app/api/pl/public_transport.py`
|
||||
|
||||
- [ ] **Step 1: 增加 `_build_driver_url` 与 history 端点**
|
||||
|
||||
在 `public_transport.py`(复用 config,与 `plan.py` 一致):
|
||||
|
||||
```python
|
||||
from fastapi import Query
|
||||
from sqlalchemy import or_, and_
|
||||
from app.core.config import settings
|
||||
from app.schemas.pl.transport import (
|
||||
PublicTransportHistoryPageRead,
|
||||
PublicTransportHistoryRecordRead,
|
||||
# ...existing imports
|
||||
)
|
||||
|
||||
def _build_driver_url(token: str) -> str:
|
||||
origin = (settings.PUBLIC_WEB_ORIGIN or "").rstrip("/")
|
||||
return f"{origin}/#/public/pl/transport/{token}"
|
||||
|
||||
|
||||
@router.get("/{token}/history")
|
||||
def get_public_transport_history(
|
||||
token: str,
|
||||
date: str = Query(..., description="筛选日 YYYY-MM-DD"),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
transport = _get_transport_by_token(db, token)
|
||||
day = _parse_history_date(date)
|
||||
if day is None:
|
||||
return error_response(400, "日期格式无效,请使用 YYYY-MM-DD")
|
||||
|
||||
plate = _normalize_main_plate(transport.license_plate)
|
||||
if not plate:
|
||||
page = PublicTransportHistoryPageRead(license_plate=None, date=day.isoformat(), records=[])
|
||||
return success_response(page.model_dump(by_alias=True))
|
||||
|
||||
# 同租户、同主车车牌、已安排:经 pl_plan_transport 关联
|
||||
rows = (
|
||||
db.query(PlPlan, PlTransport)
|
||||
.join(
|
||||
PlPlanTransport,
|
||||
and_(
|
||||
PlPlanTransport.plan_id == PlPlan.id,
|
||||
PlPlanTransport.del_flag == 0,
|
||||
),
|
||||
)
|
||||
.join(
|
||||
PlTransport,
|
||||
and_(
|
||||
PlTransport.id == PlPlanTransport.transport_id,
|
||||
PlTransport.del_flag == 0,
|
||||
),
|
||||
)
|
||||
.filter(
|
||||
PlPlan.tenant_no == transport.tenant_no,
|
||||
PlPlan.del_flag == 0,
|
||||
PlPlan.transport_status == "arranged",
|
||||
PlTransport.tenant_no == transport.tenant_no,
|
||||
PlTransport.license_plate == plate,
|
||||
or_(
|
||||
and_(PlPlan.pickup_time.isnot(None), func.date(PlPlan.pickup_time) == day),
|
||||
and_(PlPlan.delivery_time.isnot(None), func.date(PlPlan.delivery_time) == day),
|
||||
),
|
||||
)
|
||||
.order_by(PlPlan.pickup_time.desc(), PlPlan.plan_date.desc(), PlPlan.create_time.desc())
|
||||
.all()
|
||||
)
|
||||
|
||||
# 去重:同一 plan 可能多条 link 时保留最新 transport(按查询顺序首次)
|
||||
seen_plan: set[str] = set()
|
||||
records: list[PublicTransportHistoryRecordRead] = []
|
||||
for plan, tr in rows:
|
||||
if plan.id in seen_plan:
|
||||
continue
|
||||
seen_plan.add(plan.id)
|
||||
voided, archived = _resolve_driver_link_flags(plan, tr)
|
||||
tok = (tr.driver_access_token or "").strip()
|
||||
records.append(
|
||||
PublicTransportHistoryRecordRead(
|
||||
plan_id=plan.id,
|
||||
plan_no=plan.plan_no or "",
|
||||
transport_id=tr.id,
|
||||
driver_access_token=tok or None,
|
||||
driver_url=_build_driver_url(tok) if tok else None,
|
||||
license_plate=tr.license_plate or plate,
|
||||
pickup_time=plan.pickup_time,
|
||||
delivery_time=plan.delivery_time,
|
||||
pickup_address=plan.pickup_address,
|
||||
destination_address=plan.destination_address or plan.destination_name,
|
||||
plan_voided=voided,
|
||||
plan_archived=archived,
|
||||
status_label=_history_status_label(voided, archived),
|
||||
)
|
||||
)
|
||||
|
||||
page = PublicTransportHistoryPageRead(
|
||||
license_plate=plate,
|
||||
date=day.isoformat(),
|
||||
records=records,
|
||||
)
|
||||
return success_response(page.model_dump(by_alias=True))
|
||||
```
|
||||
|
||||
注意:文件顶部需 `from sqlalchemy import func`(若尚未导入)。路由须注册在 `/{token}/feedback` 等同级;FastAPI 中 `/{token}/history` 静态后缀与 `/{token}` 不冲突。
|
||||
|
||||
若 `PlTransport.license_plate` 偶发空但计划上有车牌:实现时在 filter 使用
|
||||
`(PlTransport.license_plate == plate) | ((PlTransport.license_plate.is_(None) | (PlTransport.license_plate == "")) & (PlPlan.license_plate == plate))`
|
||||
——仅当联调发现漏数时再加,首版以运输单车牌为准(与 Spec 一致)。
|
||||
|
||||
- [ ] **Step 2: 手工冒烟(有本地库时)**
|
||||
|
||||
```bash
|
||||
# 用已知有效司机 token
|
||||
curl -s "http://127.0.0.1:8000/api/public/pl/transport/<TOKEN>/history?date=$(date +%F)" | head
|
||||
```
|
||||
|
||||
Expected: `code=0`,`data.records` 为数组。
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/app/api/pl/public_transport.py
|
||||
git commit -m "feat(pl): add public driver transport history API"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 前端 API 类型与 `getHistory`
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/api/pl/public-transport.ts`
|
||||
|
||||
- [ ] **Step 1: 追加类型与方法**
|
||||
|
||||
```typescript
|
||||
export interface PublicTransportHistoryRecord {
|
||||
planId: string
|
||||
planNo: string
|
||||
transportId: string
|
||||
driverAccessToken?: string
|
||||
driverUrl?: string
|
||||
licensePlate?: string
|
||||
pickupTime?: string
|
||||
deliveryTime?: string
|
||||
pickupAddress?: string
|
||||
destinationAddress?: string
|
||||
planVoided?: boolean
|
||||
planArchived?: boolean
|
||||
statusLabel?: string
|
||||
}
|
||||
|
||||
export interface PublicTransportHistoryPage {
|
||||
licensePlate?: string
|
||||
date: string
|
||||
records: PublicTransportHistoryRecord[]
|
||||
}
|
||||
|
||||
export const publicTransportApi = {
|
||||
getPage: (token: string) => publicPlGet<PublicTransportPage>(`/public/pl/transport/${token}`),
|
||||
getHistory: (token: string, date: string) =>
|
||||
publicPlGet<PublicTransportHistoryPage>(`/public/pl/transport/${token}/history`, {
|
||||
params: { date }
|
||||
}),
|
||||
// ...saveFeedback / uploadAttachment 保持不变
|
||||
}
|
||||
```
|
||||
|
||||
确认 `publicPlGet` 支持第二参 `params`(与项目其他 public API 一致);若不支持,改为
|
||||
`` `/public/pl/transport/${token}/history?date=${encodeURIComponent(date)}` ``。
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add web-admin/src/api/pl/public-transport.ts
|
||||
git commit -m "feat(pl): add public transport history API client"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 路由 + 详情页底部「运输记录」
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/router/routes/staticRoutes.ts`
|
||||
- Modify: `web-admin/src/views/public/pl/transport/index.vue`
|
||||
|
||||
- [ ] **Step 1: 注册 history 路由(放在详情路由旁)**
|
||||
|
||||
```typescript
|
||||
{
|
||||
path: '/public/pl/transport/:token/history',
|
||||
name: 'PublicPlTransportHistory',
|
||||
component: () => import('@views/public/pl/transport/history.vue'),
|
||||
meta: { title: '运输记录', isHideTab: true, isPublicMobile: true }
|
||||
},
|
||||
{
|
||||
path: '/public/pl/transport/:token',
|
||||
name: 'PublicPlTransportDriver',
|
||||
component: () => import('@views/public/pl/transport/index.vue'),
|
||||
meta: { title: '运输计划', isHideTab: true, isPublicMobile: true }
|
||||
},
|
||||
```
|
||||
|
||||
先写 history 再写 `:token`,避免个别匹配器歧义(hash 模式下通常无妨,仍建议 history 在前)。
|
||||
|
||||
- [ ] **Step 2: 改详情页 footer(填写区逻辑不动)**
|
||||
|
||||
模板:
|
||||
|
||||
- `v-else-if="page?.planVoided"` / `planArchived` 空状态外层包一层,**空状态下方仍渲染 footer**:
|
||||
|
||||
```vue
|
||||
<van-empty v-else-if="page?.planVoided" description="运输计划已作废" />
|
||||
<van-empty v-else-if="page?.planArchived" description="运输计划已归档完成" />
|
||||
<template v-else-if="page">
|
||||
<!-- 现有取送货/产品/回执/备注 全部保持不动 -->
|
||||
...
|
||||
</template>
|
||||
|
||||
<footer
|
||||
v-if="page && !errorMessage"
|
||||
class="page-footer"
|
||||
>
|
||||
<div v-if="!page.planVoided && !page.planArchived" class="btn-row">
|
||||
<van-button block round plain type="primary" @click="goHistory">运输记录</van-button>
|
||||
<van-button type="primary" block round :loading="saving" @click="handleSave">
|
||||
保存回执
|
||||
</van-button>
|
||||
</div>
|
||||
<van-button
|
||||
v-else
|
||||
block
|
||||
round
|
||||
plain
|
||||
type="primary"
|
||||
@click="goHistory"
|
||||
>
|
||||
运输记录
|
||||
</van-button>
|
||||
</footer>
|
||||
```
|
||||
|
||||
注意:原先 footer 在 `v-else-if="page"` 内部且 voided/archived 时不渲染;需把 footer **提到** 与 empty 同级,条件用 `page && !errorMessage`。`loading` 时不显示 footer。
|
||||
|
||||
```ts
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const token = computed(() => String(route.params.token || ''))
|
||||
|
||||
function goHistory() {
|
||||
router.push({ name: 'PublicPlTransportHistory', params: { token: token.value } })
|
||||
}
|
||||
```
|
||||
|
||||
样式:`.btn-row { display:grid; grid-template-columns:1fr 1fr; gap:8px; }`(若尚无)。
|
||||
|
||||
**禁止**改动产品回传、BrandQtyChipsField、uploader、备注、`handleSave` 校验逻辑。
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add web-admin/src/router/routes/staticRoutes.ts web-admin/src/views/public/pl/transport/index.vue
|
||||
git commit -m "feat(pl): add transport history entry on driver public page"
|
||||
```
|
||||
|
||||
此时 `history.vue` 尚未创建会导致路由懒加载失败——若先提交,Task 5 必须紧接着完成;或本 Task 先放一个最小占位 `history.vue`(仅 nav + empty),Task 5 再补全。
|
||||
|
||||
**推荐:** Task 4 Step 2 同时创建占位:
|
||||
|
||||
```vue
|
||||
<!-- web-admin/src/views/public/pl/transport/history.vue 占位 -->
|
||||
<template>
|
||||
<PublicMobileShell>
|
||||
<van-nav-bar title="运输记录" left-arrow @click-left="$router.back()" />
|
||||
<van-empty description="加载中…" />
|
||||
</PublicMobileShell>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import PublicMobileShell from '@/layouts/PublicMobileShell.vue'
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: 实现 `history.vue` 列表页
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/views/public/pl/transport/history.vue`
|
||||
|
||||
- [ ] **Step 1: 完整实现列表(对齐 Demo 筛选行为)**
|
||||
|
||||
要点:
|
||||
|
||||
```ts
|
||||
function formatYmd(d: Date) {
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
return `${y}-${m}-${day}`
|
||||
}
|
||||
|
||||
function addDays(base: Date, n: number) {
|
||||
const d = new Date(base.getFullYear(), base.getMonth(), base.getDate() + n)
|
||||
return d
|
||||
}
|
||||
|
||||
const today = formatYmd(new Date())
|
||||
const pickDate = ref(today)
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await publicTransportApi.getHistory(token.value, pickDate.value)
|
||||
page.value = data
|
||||
} catch (e: any) {
|
||||
errorMessage.value = e?.message || '加载失败'
|
||||
page.value = null
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function setQuick(offset: 0 | 1 | 2) {
|
||||
pickDate.value = formatYmd(addDays(new Date(), offset))
|
||||
load()
|
||||
}
|
||||
|
||||
function openRecord(row: PublicTransportHistoryRecord) {
|
||||
const t = (row.driverAccessToken || '').trim()
|
||||
if (!t) {
|
||||
showFailToast('该记录暂无司机链接')
|
||||
return
|
||||
}
|
||||
router.push({ name: 'PublicPlTransportDriver', params: { token: t } })
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
router.push({ name: 'PublicPlTransportDriver', params: { token: token.value } })
|
||||
}
|
||||
```
|
||||
|
||||
UI:
|
||||
|
||||
- 顶栏:返回 → `goBack()`(回到进入列表时的 token 详情,不是 browser back,避免从 B 详情进列表再 back 到错误页)
|
||||
- 快捷 chip:今天/明天/后天;与 `pickDate` 相等时高亮
|
||||
- `<input type="date" v-model="pickDate" @change="load" />` 或 Vant 等价
|
||||
- 卡片列表:`planNo`、`statusLabel` Tag、取货/送货日、线路(`pickupAddress → destinationAddress`)
|
||||
- 无数据:`van-empty`「暂无运输记录」
|
||||
|
||||
样式参考 Demo / 现有 `transport/index.vue`(绿顶栏、inset card)。状态色:已作废红、已归档灰、进行中绿。
|
||||
|
||||
- [ ] **Step 2: 本地手动验收清单**
|
||||
|
||||
1. 打开有效司机链接 → 底部有「运输记录」+「保存回执」;填写区与改前一致。
|
||||
2. 进列表默认今天;点明天/后天/改日期会刷新。
|
||||
3. 点进行中记录 → 进入该单详情,可保存回执。
|
||||
4. 归档/作废详情 → 仅空状态 +「运输记录」。
|
||||
5. 无效 token → 无「运输记录」按钮。
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add web-admin/src/views/public/pl/transport/history.vue
|
||||
git commit -m "feat(pl): implement public driver transport history list page"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: 图谱更新(若本会话改了代码)
|
||||
|
||||
**Files:** 无业务文件
|
||||
|
||||
- [ ] **Step 1:** 若实现阶段改了 backend/web-admin 代码,执行:
|
||||
|
||||
```bash
|
||||
./scripts/update-graph.sh backend
|
||||
# 或 frontend / all,按实际改动
|
||||
```
|
||||
|
||||
- [ ] **Step 2:** 不必强制 commit 图谱;与项目惯例一致即可。
|
||||
|
||||
---
|
||||
|
||||
## Spec 覆盖自检
|
||||
|
||||
| Spec 项 | Task |
|
||||
|---------|------|
|
||||
| 主车车牌聚合 | Task 2 |
|
||||
| 取货日 OR 送货日 = 单日 | Task 1–2 |
|
||||
| 默认今天 + 今/明/后快捷 | Task 5 |
|
||||
| 底部运输记录(含归档/作废) | Task 4 |
|
||||
| 无「返回列表」 | Task 4–5 |
|
||||
| 互通填写(跳转目标 token) | Task 5 `openRecord` |
|
||||
| 填写 UI 与现状一致 | Task 4 明确禁止改表单 |
|
||||
| 现场链接不改 | 无对应 Task(非目标) |
|
||||
|
||||
---
|
||||
|
||||
## 执行交接
|
||||
|
||||
Plan complete and saved to `docs/superpowers/plans/2026-08-21-pl-driver-transport-history.md`. Two execution options:
|
||||
|
||||
**1. Subagent-Driven (recommended)** — 每个 Task 开新子代理,Task 间复查
|
||||
|
||||
**2. Inline Execution** — 本会话按 executing-plans 连续做完
|
||||
|
||||
Which approach?
|
||||
Reference in new issue
Block a user