DIP1 UAT 可用性排查报告¶
编号:DWLG-20260810-03 测试人:听写(DT) 测试日期:2026-08-10 测试范围:opr-admin 运营后台 18 个页面 + 后端 27 个 API 端点 + 6 条核心业务流程 测试方法:浏览器实跑 + curl smoke test + 静态代码审查 交付对象:kimi(Code Agent)按本报告 §四 修改策略执行修复
一、执行摘要¶
1.1 总体结论¶
| 维度 | 结果 |
|---|---|
| 登录链路 | ✅ 已修复(forwardRef bug 已解决) |
| 18 个页面可用性 | 6 个正常 / 5 个部分异常 / 7 个阻断(含 1 个白屏) |
| 27 个后端端点 | 21 个正常 / 6 个缺失或路径不匹配 |
| 6 条核心流程 | 4 条基本打通 / 2 条断裂(报价管理、客户订单关联) |
| 种子数据 | ✅ 已就位(3 客户 + 3 师傅 + 2 楼宇 + 2 报价 + 4 订单 L1/L3/L6/DONE) |
1.2 严重程度分布¶
| 严重程度 | 数量 | 说明 |
|---|---|---|
| 🔴 P0 阻断(页面不可用) | 7 | 必须修复才能开展 UAT |
| 🟡 P1 异常(部分功能失效) | 5 | 影响 UX 但页面可访问 |
| 🟢 P2 待优化 | 4 | 不阻断但建议修复 |
1.3 已修复的 Bug(本次排查中处置)¶
| # | Bug | 文件 | 处置 |
|---|---|---|---|
| F0 | 登录表单 Input 组件未用 forwardRef,导致 react-hook-form 捕获不到输入值 | form.tsx L26-L34 | 已修复 |
| F1 | demo.py _cleanup 未清理 cst_sessions 外键引用,导致重跑失败 | demo.py L57-L63 | 已修复 |
二、测试环境¶
2.1 服务端口¶
| 服务 | 端口 | 状态 |
|---|---|---|
| 后端 FastAPI | localhost:8000 | ✅ 运行中 |
| 前端 Next.js (opr-admin) | localhost:3000 | ✅ 运行中 |
| PostgreSQL | localhost:5432 | ✅ 运行中 |
| Redis | localhost:6379 | ✅ 运行中 |
2.2 种子数据(demo.py 生成)¶
| 实体 | 数量 | 示例 ID |
|---|---|---|
| 报价参数版本 | 1 | QPV0DEMO00000000000001 |
| 楼宇 | 2 | BLD0DEMO00000000000001/02 |
| 师傅 | 3 | WKR0DEMO00000000000001/02/03(L1/L2/L3) |
| 客户 | 3 | CUS0DEMO00000000000001/02/03 |
| 报价 | 2 | QTE0DEMO01(DRAFT)/ QTE0DEMO02(CONFIRMED) |
| 订单 | 4 | ORD0DEMO01(L1)/02(L3)/03(L6)/04(DONE) |
| 管理员 | 1 | admin / admin123(TL 角色,全权限) |
三、Bug 清单(按严重程度排序)¶
3.1 🔴 P0 阻断级(7 个)¶
BUG-01:CPT 看板 pool 键完全不匹配¶
- 影响页面:
/(驾驶舱)、/orders(订单看板) - 现象:驾驶舱 6 个跟踪池卡片全部显示 0;订单看板 6 列全部"暫無卡片"。实际数据库有 4 个订单(L1/L3/L6/DONE)
- 根因:
- 后端 cpt_service.py:64 返回
_POOL_LABELS = ("TODAY", "OVERDUE", "APPOINTMENT_SOON", "NORMAL")— 时间维度 4 池 - 前端 format.ts:99-106 和 page.tsx:13-20 期望
POOL_L1_NEW_LEADS / POOL_L3_PENDING_QUOTE / POOL_L4_PENDING_ASSIGN / POOL_L6_INSTALL_TODAY / POOL_L8_PENDING_FOLLOWUP / POOL_EXCEPTION— 阶段维度 6 池 - 实测:
GET /api/v1/cpt/orders/board返回{"TODAY":[1单],"OVERDUE":[],"APPOINTMENT_SOON":[1单],"NORMAL":[1单]} - 修复策略:见 §四 策略 1
BUG-02:GET /qsv/quotes 列表端点缺失(405)¶
- 影响页面:
/quotes(报价列表)、/(驾驶舱"待審批報價"统计卡) - 现象:报价列表页显示"重試"按钮(query.isError);驾驶舱"待審批報價"显示"—"
- 根因:后端 qsv.py 只有
POST /quotes和GET /quotes/{quote_id},无 GET 列表端点 - 实测:
GET /api/v1/qsv/quotes?page=1&page_size=20→ 405 Method Not Allowed - 修复策略:见 §四 策略 2
BUG-03:POST /qsv/calculate 路径错误(404)¶
- 影响页面:
/quotes/new(报价试算器) - 现象:报价试算器实时预览永远显示错误;品类下拉框为空
- 根因:前端 quotes/new/page.tsx:98 调用
POST /qsv/calculate,但后端实际端点是POST /qsv/calculator/preview - 实测:
POST /api/v1/qsv/calculate→ 404;POST /api/v1/qsv/calculator/preview→ 200 - 修复策略:见 §四 策略 3
BUG-04:/mds/categories 返回裸数组而非分页对象¶
- 影响页面:
/categories(品类库,白屏)、/quotes/new(品类下拉空)、/workers/[id](技能矩阵空) - 现象:品类库页面完全白屏(0 个 DOM 元素);报价试算器品类下拉为空
- 根因:
- 后端 mds.py:39-53 返回
list[MdsSclCategory](裸数组) - 前端 3 处用
api.get<Page<SclCategory>>("/mds/categories")然后访问data.items,得到undefined - 品类库页面访问
query.data.items.length抛出 TypeError 导致白屏 - 实测:
GET /api/v1/mds/categories返回[{category_id:"CAT...",...}](裸数组) - 修复策略:见 §四 策略 4
BUG-05:PATCH /qsv/quotes/{id} 端点缺失(405)¶
- 影响页面:
/quotes/[id](报价详情) - 现象:点击"確認報價"或"拒絕"按钮会 405 报错
- 根因:后端 qsv.py 无 PATCH 端点;前端 quotes/[id]/page.tsx:39 调用
PATCH /qsv/quotes/{id} - 实测:
PATCH /api/v1/qsv/quotes/QTE0DEMO01→ 405 - 修复策略:见 §四 策略 5
BUG-06:客户详情"歷史訂單"永远空¶
- 影响页面:
/customers/[id](客户详情) - 现象:客户详情页"歷史訂單"区块永远显示"暫無訂單記錄",实际该客户有 2 个订单
- 根因:前端 customers/[id]/page.tsx:37 用
q: customerId(CUS0DEMO...)查询订单,但后端q参数按customer_name模糊搜索 - 实测:
GET /api/v1/cpt/orders?q=CUS0DEMO00000000000001返回{total:0};GET /api/v1/cpt/orders?q=陳太返回 2 个订单 - 修复策略:见 §四 策略 6
BUG-07:/qsv/params/versions 端点缺失(404)¶
- 影响页面:
/categories(品类库下半部分) - 现象:品类库页面"QSV 参数版本历史"区块显示 ErrorState
- 根因:后端 qsv.py 无
/params/versions路由 - 实测:
GET /api/v1/qsv/params/versions→ 404 - 修复策略:见 §四 策略 7
3.2 🟡 P1 异常级(5 个)¶
BUG-08:登录响应缺 user 字段¶
- 影响:全局(所有依赖
user.roles的 UI) - 现象:登录后
localStorage.weavely_current_user未存储;getCachedUser()返回 null;/admin页面显示"⚠ 當前賬號角色(未知)非 TL/SPO"警告 - 根因:后端 auth.py:36-40
TokenResponse只有{access_token, refresh_token, token_type, expires_in},无user字段;前端 auth.ts:11-16 期望data.user - 修复策略:见 §四 策略 8
BUG-09:/auth/me 字段名 role vs roles 不匹配¶
- 影响:全局(角色判断失效)
- 现象:即使调用
fetchCurrentUser(),user.roles仍是undefined,hasRole()永远返回 false - 根因:后端
UserMeResponse返回role: "TL"(单字符串);前端CurrentUser期望roles: Role[](数组) - 修复策略:见 §四 策略 9
BUG-10:报价详情字段名 calc vs calc_snapshot 不匹配¶
- 影响页面:
/quotes/[id](报价详情因子展开卡) - 现象:因子展开卡显示"後端未返回 calc 快照"
- 根因:前端 types.ts:133 期望
quote.calc;后端返回calc_snapshot;且factor_breakdown后端是对象,前端期望数组 - 修复策略:见 §四 策略 10
BUG-11:/admin 权限键名完全不匹配¶
- 影响页面:
/admin(角色权限矩阵 Tab) - 现象:角色权限矩阵显示 42 个复选框(21 个 false 的前端硬编码 + 21 个 true 的后端键);保存时会把无效键 PUT 给后端
- 根因:前端 admin/page.tsx:28-34
PERMISSION_KEYS用qsv:calculate / cpt:order:view / mds:customer:view;后端返回qsv:calc / cpt:list:all / mds:cpl:view - 修复策略:见 §四 策略 11
BUG-12:/qsv/quotes/{id}/export 端点缺失(404)¶
- 影响页面:
/quotes/[id](导出 PDF 按钮) - 现象:点击"導出 PDF"按钮 Toast 报错
- 根因:后端无 PDF 导出端点
- 修复策略:见 §四 策略 12
3.3 🟢 P2 待优化(4 个)¶
BUG-13:后端金额字段返回字符串而非数字¶
- 影响:全局金额显示(如报价详情)
- 现象:金额显示为 "HK$ 380.00" 而非 "HK$ 380"
- 根因:后端 DTO 返回
"final_hkd":"380.00"(字符串);前端formatMoney(hkd?: number)期望数字 - 修复策略:见 §四 策略 13
BUG-14:admin 用户 roles 为空数组¶
- 影响页面:
/admin(用户管理表格"角色"列) - 现象:admin 用户的"角色"列显示为空
- 根因:种子数据 data.py 未为 admin 用户填充
sys_user_roles关联 - 修复策略:见 §四 策略 14
BUG-15:驾驶舱调试代码泄漏¶
- 影响页面:
/(驾驶舱"逾期訂單"统计卡) - 现象:页面文本显示
pool_label = OVERDUE(变量名泄漏到 UI) - 根因:前端 page.tsx 模板字符串未正确转义
- 修复策略:见 §四 策略 15
BUG-16:楼宇 autocomplete 未用专用端点¶
- 影响页面:
/buildings(地址联想) - 现象:功能可用但用了
/mds/buildings?q=而非专用的/mds/buildings/autocomplete - 修复策略:见 §四 策略 16
四、修改策略指导(交付给 kimi)¶
执行原则:每个策略都标注了【后端】或【前端】侧的修改,建议成对修复以避免字段名/结构再次错配。修改后请跑
pytest tests/+ruff check app tests确保不回归。
策略 1:统一 CPT 看板 pool 键(BUG-01)— 最高优先级¶
推荐方案:后端改为返回阶段维度 6 池(对齐 CPT-D 设计文档的 8 阶段跟踪池语义)
后端修改(cpt_service.py):
# 当前(错误):
_POOL_LABELS = ("TODAY", "OVERDUE", "APPOINTMENT_SOON", "NORMAL")
# 改为(对齐前端期望):
_POOL_LABELS = (
"POOL_L1_NEW_LEADS", # L1 新线索
"POOL_L3_PENDING_QUOTE", # L3 待报价
"POOL_L4_PENDING_ASSIGN", # L4 待派单
"POOL_L6_INSTALL_TODAY", # L6 今日安装
"POOL_L8_PENDING_FOLLOWUP",# L8 待回访
"POOL_EXCEPTION", # 异常池
)
# 同时修改 _classify_order 方法,按 order.stage + order.status 分类:
# - stage=L1 → POOL_L1_NEW_LEADS
# - stage=L3 且无 confirmed_quote → POOL_L3_PENDING_QUOTE
# - stage=L4 且无 assigned_worker → POOL_L4_PENDING_ASSIGN
# - stage=L6 且 expected_start_at=today → POOL_L6_INSTALL_TODAY
# - stage=L8 且无 NPS → POOL_L8_PENDING_FOLLOWUP
# - status=EXCEPTION_PAUSED → POOL_EXCEPTION
# - 其他 → 不入池(或入 NORMAL 备用池)
前端无需修改(已对齐)。
验证:GET /api/v1/cpt/orders/board 应返回 {"POOL_L1_NEW_LEADS":[1单],"POOL_L3_PENDING_QUOTE":[1单],"POOL_L6_INSTALL_TODAY":[1单],"POOL_L8_PENDING_FOLLOWUP":[1单],...}
策略 2:新增 GET /qsv/quotes 列表端点(BUG-02)¶
后端修改(qsv.py):
@router.get("", response_model=Page[QsvQuoteSummary])
async def list_quotes(
status: str | None = Query(None, description="按状态过滤: DRAFT/CONFIRMED/REJECTED/EXPIRED"),
customer_id: str | None = Query(None),
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
session: AsyncSession = Depends(get_session),
_: TokenClaims = Depends(require_permission("qsv:read")),
) -> Page[QsvQuoteSummary]:
"""报价列表(分页 + 状态过滤)."""
# 返回标准分页结构 {total, page, page_size, items}
# items 中每条包含:quote_id, customer_id, customer_name, category_code,
# final_hkd, status, created_at, confirmed_at
前端无需修改(已用 api.get<Page<QsvQuote>>("/qsv/quotes", {params:{page,page_size,status}}))。
验证:GET /api/v1/qsv/quotes?page=1&page_size=20 → 200 + {total:2, items:[...]}
策略 3:修正报价试算 API 路径(BUG-03)¶
前端修改(quotes/new/page.tsx:98):
// 当前(错误):
const { data } = await api.post<QsvCalcPreview>("/qsv/calculate", payload);
// 改为:
const { data } = await api.post<QsvCalcPreview>("/qsv/calculator/preview", payload);
同时检查响应字段名(types.ts):
- 后端返回 base_fee_hkd,前端期望 base_fee → 统一为 base_fee_hkd
- 后端返回 capped_flag,前端期望 capped → 统一为 capped_flag
- 后端 factor_breakdown 是对象 {category_base: {name_zh, value, running_total}},前端期望数组 → 前端改为 Object.values(data.factor_breakdown)
策略 4:/mds/categories 包装为分页响应(BUG-04)— 导致白屏,紧急¶
后端修改(mds.py:39-53):
@router.get("/categories", response_model=Page[MdsSclCategory])
async def list_categories(
page: int = Query(1, ge=1),
page_size: int = Query(100, ge=1, le=500),
session: AsyncSession = Depends(get_session),
_: TokenClaims = Depends(require_permission("mds:scl:view")),
) -> Page[MdsSclCategory]:
"""品类列表(分页)."""
# 返回 {total, page, page_size, items}
前端无需修改(3 处都已用 api.get<Page<SclCategory>> + data.items)。
替代方案(如果不想改后端):前端 3 处去掉 .items 访问,直接用 data 作为数组。但建议统一为分页结构(与其他端点一致)。
策略 5:新增 PATCH /qsv/quotes/{id} 端点(BUG-05)¶
后端修改(qsv.py):
class QuoteStatusUpdate(BaseModel):
status: Literal["CONFIRMED", "REJECTED", "EXPIRED"]
reject_reason: str | None = None
@router.patch("/{quote_id}", response_model=QsvQuote)
async def update_quote_status(
quote_id: str,
payload: QuoteStatusUpdate,
session: AsyncSession = Depends(get_session),
claims: TokenClaims = Depends(require_permission("qsv:update")),
) -> QsvQuote:
"""更新报价状态(确认/拒绝/过期)."""
# 若 status=CONFIRMED,设置 confirmed_by + confirmed_at
# 若 status=REJECTED,记录 reject_reason
前端无需修改(已用 api.patch("/qsv/quotes/{id}", {status, reject_reason}))。
策略 6:客户详情订单查询改用 customer_id(BUG-06)¶
推荐方案:后端 list_orders 增加 customer_id 查询参数(更语义化)
后端修改(cpt.py 的 list_orders):
@router.get("", response_model=Page[CptOrderCard])
async def list_orders(
q: str | None = Query(None, description="按客户名/地址模糊搜索"),
customer_id: str | None = Query(None, description="按客户ID精确过滤"),
stage: str | None = Query(None),
# ... 其他参数
):
if customer_id:
query = query.where(CptOrder.customer_id == customer_id)
if q:
query = query.where(CptOrder.customer_name.ilike(f"%{q}%"))
前端修改(customers/[id]/page.tsx:37):
// 当前(错误):
const { data } = await api.get<Page<CptOrderCard>>("/cpt/orders", {
params: { q: customerId, page: 1, page_size: 50 },
// 改为:
const { data } = await api.get<Page<CptOrderCard>>("/cpt/orders", {
params: { customer_id: customerId, page: 1, page_size: 50 },
策略 7:处理 /qsv/params/versions 404(BUG-07)¶
方案 A(推荐):后端新增端点
@router.get("/params/versions", response_model=Page[QsvParamVersion])
async def list_param_versions(
page: int = Query(1, ge=1),
page_size: int = Query(10, ge=1, le=50),
session: AsyncSession = Depends(get_session),
_: TokenClaims = Depends(require_permission("qsv:read")),
) -> Page[QsvParamVersion]:
"""报价参数版本列表."""
方案 B(快速):前端移除该区块
- categories/page.tsx:29 删除 versionsQuery + 对应的"QSV 参数版本历史"UI 区块
策略 8:登录响应增加 user 字段(BUG-08)¶
后端修改(auth.py 的 login 端点):
class LoginResponse(BaseModel):
access_token: str
refresh_token: str
token_type: str = "bearer"
expires_in: int
user: CurrentUser # 新增
@router.post("/login", response_model=LoginResponse)
async def login(payload: LoginRequest, session: AsyncSession = Depends(get_session)):
# ... 验证密码 ...
user_info = CurrentUser(
user_id=user.user_id,
username=user.username,
display_name=user.display_name,
roles=[user.role], # 转为数组
permissions=[...], # 查询权限
)
return LoginResponse(
access_token=..., refresh_token=..., expires_in=900,
user=user_info,
)
或前端修改(auth.ts:11-16):登录后立即调用 fetchCurrentUser():
export async function login(username: string, password: string): Promise<CurrentUser> {
const { data } = await api.post<LoginResponse>("/auth/login", { username, password });
window.localStorage.setItem(TOKEN_KEY, data.access_token);
if (data.refresh_token) window.localStorage.setItem(REFRESH_KEY, data.refresh_token);
// 登录后立即获取用户信息(如果后端未在 login 响应中返回 user)
const user = data.user ?? await fetchCurrentUser();
window.localStorage.setItem(USER_KEY, JSON.stringify(user));
return user;
}
策略 9:/auth/me 字段名 role → roles(BUG-09)¶
后端修改(auth.py 的 UserMeResponse):
class UserMeResponse(BaseModel):
user_id: str
username: str
display_name: str
roles: list[str] # 改为复数数组(当前是 role: str)
permissions: list[str]
前端无需修改(已期望 roles: Role[])。
策略 10:报价详情字段名对齐(BUG-10)¶
前端修改(types.ts:133):
export interface QsvQuote {
// ...
calc_snapshot: { // 改名:calc → calc_snapshot
category_code: string;
factors?: Record<string, { name_zh: string; value: string; running_total: string }>;
};
factor_breakdown: Record<string, { name_zh: string; value: string; running_total: string }>; // 改为数组或保持对象
}
前端渲染修改(quotes/[id]/page.tsx):
// 因子展开卡:从 factor_breakdown 对象转为数组渲染
const factors = quote.factor_breakdown ? Object.values(quote.factor_breakdown) : [];
factors.map(f => <li key={f.name_zh}>{f.name_zh}: {f.value} (累計 {f.running_total})</li>)
策略 11:权限键名对齐(BUG-11)¶
推荐方案:前端从后端动态获取权限键,不硬编码
前端修改(admin/page.tsx:28-34):
// 当前(错误):硬编码 PERMISSION_KEYS
const PERMISSION_KEYS = ["qsv:calculate", "cpt:order:view", ...];
// 改为:从后端响应中动态提取
const PERMISSION_KEYS = Object.keys(rolesPermissionsResponse); // 后端返回的键就是全部权限键
或后端新增权限字典端点 GET /admin/permissions 返回所有权限键+中文名。
策略 12:处理报价导出 404(BUG-12)¶
方案 A:后端新增 PDF 导出端点(工作量大,建议后期实施) 方案 B(推荐):前端移除"導出 PDF"按钮,或改为浏览器原生打印
// quotes/[id]/page.tsx
// 移除 exportPdf 函数,按钮改为 window.print()
策略 13:金额字段类型统一(BUG-13)¶
后端修改(qsv.py DTO):
class QsvQuote(BaseModel):
# 当前(错误):返回字符串
# final_hkd: str
# 改为:
final_hkd: float
base_fee_hkd: float
subtotal_hkd: float
cap_price_hkd: float
# ... 所有金额字段统一为 float
或前端兼容(format.ts):
export function formatMoney(hkd?: number | string | null): string {
if (hkd == null) return "—";
const n = typeof hkd === "string" ? parseFloat(hkd) : hkd;
return `HK$ ${n.toLocaleString("zh-HK", { maximumFractionDigits: 0 })}`;
}
策略 14:种子数据填充 admin 用户角色(BUG-14)¶
后端修改(data.py):
# 创建 admin 用户后,插入 sys_user_roles 关联
await session.execute(
text("INSERT INTO sys_user_roles (user_id, role_name) VALUES (:uid, 'TL') ON CONFLICT DO NOTHING"),
{"uid": ADMIN_USER_ID},
)
策略 15:移除驾驶舱调试代码(BUG-15)¶
前端修改(page.tsx):
// 当前(错误):模板字符串泄漏变量名
`逾期訂單 ⏰ ${overdueCount} pool_label = OVERDUE`
// 改为:
`逾期訂單 ⏰ ${overdueCount}`
策略 16:楼宇 autocomplete 改用专用端点(BUG-16)¶
前端修改(buildings/page.tsx:51):
// 当前:api.get("/mds/buildings", {params:{q, page_size:8}})
// 改为:api.get("/mds/buildings/autocomplete", {params:{q}})
五、流程交叉逻辑验证¶
5.1 ✅ 已打通的流程(4 条)¶
| 流程 | 链路 | 验证结果 |
|---|---|---|
| L1→L8 生命周期 | 订单详情 → 阶段 Tab → 流转/阶段数据 | GET /cpt/orders/{id} 200;POST /cpt/orders/{id}/transition 端点存在;PUT /cpt/orders/{id}/stages/{stage}/data 端点存在;L6 6S表单 + L8 NPS表单可用 |
| 线索→订单 | 线索池 → 创建线索 → 转化为订单 | GET /ops/leads 200;POST /ops/leads 201;POST /ops/leads/{id}/convert 端点存在 |
| 派单 | 订单详情 → 推荐师傅 → 指派 | GET /dsp/orders/{id}/recommend-workers 200;POST /dsp/orders/{id}/assign 端点存在 |
| 奖励 | 奖励列表 → 审批 → 发放 | GET /ops/rewards 200;POST /ops/rewards/{id}/approve + /pay 端点存在 |
5.2 ❌ 断裂的流程(2 条)¶
| 流程 | 断裂点 | 影响 | 修复后验证方法 |
|---|---|---|---|
| 报价管理 | GET /qsv/quotes 405 + PATCH /qsv/quotes/{id} 405 + POST /qsv/calculate 404 |
报价列表不可用 + 报价试算不可用 + 报价状态更新不可用 | 修复 BUG-02/03/05 后:创建报价 → 列表查看 → 详情确认 → 状态流转 |
| 客户订单关联 | 客户详情用 customer_id 查 q,但 q 按 name 搜索 | 客户历史订单永远空 | 修复 BUG-06 后:客户详情页应显示该客户的 2 个订单 |
六、修复优先级建议¶
第一批(P0 阻断,必须修复才能开展 UAT)¶
| 顺序 | Bug | 预估工时 | 依赖 |
|---|---|---|---|
| 1 | BUG-04 品类库白屏(/mds/categories 分页) |
0.5h | 无 |
| 2 | BUG-01 看板 pool 键不匹配 | 2h | 无 |
| 3 | BUG-02 GET /qsv/quotes 列表端点 |
1h | 无 |
| 4 | BUG-03 试算 API 路径 + 字段名 | 1h | 无 |
| 5 | BUG-05 PATCH /qsv/quotes/{id} |
0.5h | 无 |
| 6 | BUG-06 客户订单关联 | 0.5h | 无 |
| 7 | BUG-07 /qsv/params/versions |
0.5h(方案B) / 1h(方案A) | 无 |
第一批合计:约 6-6.5h,修复后 7 个阻断页面可恢复可用。
第二批(P1 异常,修复后 UX 显著提升)¶
| 顺序 | Bug | 预估工时 |
|---|---|---|
| 8 | BUG-08 + BUG-09 登录响应 + auth/me 字段 | 1h |
| 9 | BUG-10 报价详情字段名 | 0.5h |
| 10 | BUG-11 权限键名 | 1h |
| 11 | BUG-12 导出 PDF | 0.5h(移除按钮) |
第三批(P2 待优化,可延后)¶
| 顺序 | Bug | 预估工时 |
|---|---|---|
| 12 | BUG-13 金额类型 | 0.5h |
| 13 | BUG-14 admin 角色种子 | 0.5h |
| 14 | BUG-15 调试代码 | 0.1h |
| 15 | BUG-16 autocomplete 端点 | 0.5h |
七、测试通过页面清单(无需修改)¶
以下 6 个页面经测试功能正常,无需修改:
| 页面 | URL | 说明 |
|---|---|---|
| 线索池 | /leads |
6 场景 Tab + 状态筛选 + 新建线索 + 转化 |
| 推荐奖励 | /rewards |
状态筛选 + 审批 + 发放 |
| 订单详情(L1/L6/DONE) | /orders/[id] |
8 阶段 Tab + 流转 + 阶段数据 + 6S表单 + NPS表单 |
| 客户档案 | /customers |
搜索 + 标签筛选 + 列表 |
| 师傅档案 | /workers |
等级/状态筛选 + 列表 |
| 楼宇库 | /buildings |
地址联想 + 列表 + 新建 |
八、附录:后端 API 端点实测结果¶
| 状态码 | 端点 | 说明 |
|---|---|---|
| ✅ 200 | GET /cpt/orders |
订单列表(含 stage 过滤) |
| ✅ 200 | GET /cpt/orders/{id} |
订单详情 |
| ✅ 200 | GET /cpt/orders/board |
看板(但 pool 键不匹配) |
| ✅ 200 | GET /mds/customers |
客户列表 |
| ✅ 200 | GET /mds/customers/{id} |
客户详情 |
| ✅ 200 | GET /mds/workers |
师傅列表 |
| ✅ 200 | GET /mds/workers/{id} |
师傅详情 |
| ⚠️ 200 | GET /mds/categories |
返回裸数组(应为分页对象) |
| ✅ 200 | GET /mds/buildings |
楼宇列表 |
| ✅ 200 | GET /ops/leads |
线索列表 |
| ✅ 200 | GET /ops/rewards |
奖励列表 |
| ✅ 200 | GET /admin/users |
用户列表 |
| ✅ 200 | GET /admin/audit-logs |
审计日志 |
| ✅ 200 | GET /admin/roles/{role}/permissions |
角色权限 |
| ✅ 200 | GET /auth/me |
当前用户(字段名不匹配) |
| ✅ 200 | GET /dsp/orders/{id}/recommend-workers |
推荐师傅 |
| ✅ 200 | GET /dsp/workers/load-board |
师傅负载 |
| ✅ 200 | GET /qsv/quotes/{id} |
报价详情(字段名部分不匹配) |
| ✅ 201 | POST /auth/login |
登录(缺 user 字段) |
| ✅ 201 | POST /qsv/quotes |
创建报价 |
| ✅ 200 | POST /qsv/calculator/preview |
报价试算(前端路径错误) |
| ❌ 405 | GET /qsv/quotes |
列表端点缺失 |
| ❌ 405 | PATCH /qsv/quotes/{id} |
状态更新端点缺失 |
| ❌ 404 | POST /qsv/calculate |
路径错误(应为 /calculator/preview) |
| ❌ 404 | GET /qsv/params/versions |
端点缺失 |
| ❌ 404 | GET /qsv/quotes/{id}/export |
端点缺失 |
报告结束。kimi 请按 §四 策略 1-7(第一批)优先修复,修复后通知 DT 复测。