25 KiB
中试服务申请单 Phase 1 实施计划
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: 实现中试服务申请单的完整 CRUD、患者信息管理、输注方案/计划管理,含后端 API + 前端列表页+详情Tab页。
Architecture: 独立后端模块方案(方案A)。新建 crud/pilot_application.py 和 api/pilot_application.py,在 models.py 中新增 4 个 Model。前端新增列表页+详情页,路由追加到 sqxp.ts。
Tech Stack: Python 3.9 / FastAPI / SQLAlchemy / Pydantic v2 / Vue 3 / TypeScript / Element Plus / Tailwind CSS 4
Spec: docs/superpowers/specs/2026-05-24-pilot-application-phase1-design.md
Task 1: SQL 迁移文件 + Backend Models
Files:
-
Create:
docs/sql/pilot-application-migration.sql -
Modify:
backend/app/models/models.py -
Step 1: 创建 SQL 迁移文件
创建 docs/sql/pilot-application-migration.sql,内容为 ALTER biz_pilot_application 增加 4 个字段 + 唯一索引:
-- 中试服务申请单 Phase 1 迁移
-- 为 biz_pilot_application 增加申请单号、状态、渠道缓存字段
ALTER TABLE biz_pilot_application
ADD COLUMN application_no VARCHAR(64) DEFAULT NULL
COMMENT '申请单号(自动生成,前缀ZSSQ)' AFTER id,
ADD COLUMN application_status VARCHAR(16) NOT NULL DEFAULT 'dks'
COMMENT 'dks待开始 jxz进行中 ywc已完成 yzf已作废' AFTER application_no,
ADD COLUMN channel_id VARCHAR(64) DEFAULT NULL
COMMENT '渠道ID(缓存自服务订单)' AFTER channel_service_order_id,
ADD COLUMN channel_name VARCHAR(200) DEFAULT NULL
COMMENT '渠道名称(缓存)' AFTER channel_id;
ALTER TABLE biz_pilot_application
ADD UNIQUE KEY uk_application_no (application_no, del_flag);
- Step 2: 在 models.py 末尾(BizChannelServiceOrderTxn 之后)新增 4 个 Model
遵循项目已有 Model 模式(参考 BizChannelServiceOrder 在 L1761 的写法):
- 字段顺序:id → 业务字段 → public 字段(tenant_no / order_num / remark / create_by / create_dept_id / create_time / update_by / update_time / del_flag)
- 类型映射:VARCHAR→String, INT→Integer, TINYINT→SmallInteger, DATETIME→DateTime, DECIMAL→Numeric
- 添加
__tablename__和中文注释
新增的 4 个 Model:
BizPilotApplication → biz_pilot_application
- id, application_no, application_status(default='dks'), channel_service_order_id, channel_id, channel_name
-
- public fields
BizPilotPatient → biz_pilot_patient
- id, pilot_application_id, customer_id, patient_name, sex, age, id_card_no, phone_no, emergency_contact, emergency_phone, clinical_diagnosis, diagnosis_attachments, need_checkup(default=1)
-
- public fields
BizPilotInfusionPlan → biz_pilot_infusion_plan
- id, pilot_application_id, cell_type, spec_text, total_course_count, course_interval, infusion_route, infusion_start_date
-
- public fields
BizPilotInfusionSchedule → biz_pilot_infusion_schedule
-
id, infusion_plan_id, cell_type, spec_text, infusion_time, schedule_status(default=''), attachments
-
- public fields
-
Step 3: 验证
Run: cd backend && source venv/bin/activate && python -c "from app.models.models import BizPilotApplication, BizPilotPatient, BizPilotInfusionPlan, BizPilotInfusionSchedule; print('Models imported OK')"
Expected: Models imported OK
- Step 4: 运行 LSP 诊断
Run lsp_diagnostics on backend/app/models/models.py — expect 0 errors.
Task 2: Backend CRUD
Files:
-
Create:
backend/app/crud/pilot_application.py -
Step 1: 创建 CRUD 文件
创建 backend/app/crud/pilot_application.py,遵循 crud/pilot_service.py 的模式(_row_to_dict_* + 分页列表 + CRUD 函数)。
import 部分:
import uuid
import json
from datetime import datetime, date
from typing import Dict, List, Optional, Any
from sqlalchemy.orm import Session
from sqlalchemy import and_
from app.models.models import (
BizPilotApplication, BizPilotPatient, BizPilotInfusionPlan, BizPilotInfusionSchedule,
BizChannelServiceOrder, BizChannelServiceOrderProduct, BizCustomer
)
from app.core.database import get_db
_row_to_dict 函数(4 个):
-
_row_to_dict_application(row, db=None, tenant_no=None):- 基础字段:id, applicationNo, applicationStatus, channelServiceOrderId, channelId, channelName, remark, createTime, updateTime
- 扩展字段(需 db 查询):
- patientName: 从 BizPilotPatient 查询(WHERE pilot_application_id=row.id AND del_flag=0)
- infusionPlanCount: 从 BizPilotInfusionPlan COUNT(WHERE pilot_application_id=row.id AND del_flag=0)
- serviceOrderNo: 从 BizChannelServiceOrder 查询(WHERE id=row.channel_service_order_id)取其关联的订单信息
- orderType: 同上,服务订单的 order_type
-
_row_to_dict_patient(row):- id, pilotApplicationId, customerId, patientName, sex, age, idCardNo, phoneNo, emergencyContact, emergencyPhone, clinicalDiagnosis, diagnosisAttachments, needCheckup, remark, createTime, updateTime
-
_row_to_dict_infusion_plan(row, db=None, tenant_no=None):- id, pilotApplicationId, cellType, specText, totalCourseCount, courseInterval, infusionRoute, infusionStartDate, remark, createTime, updateTime
- 扩展:scheduleCount (COUNT schedules), completedCount (COUNT WHERE schedule_status='ywc')
-
_row_to_dict_infusion_schedule(row):- id, infusionPlanId, cellType, specText, infusionTime, scheduleStatus, attachments, remark, createTime, updateTime
CRUD 函数(16 个):
Application(8 个):
list_applications(db, tenant_no, page, page_size, channel_id=None, application_status=None, channel_service_order_id=None)— 分页列表,按 create_time DESC 排序get_application(db, app_id, tenant_no)— 获取详情create_application(db, data, tenant_no, user_id, dept_id)— 校验服务订单存在且 status='sxz',从服务订单缓存 channel_id/channel_name,生成 application_no(ZSSQ+YYYYMMDD+4位序列)update_application(db, app_id, data, tenant_no)— 仅 dks 状态可编辑soft_delete_application(db, app_id, tenant_no)— 仅 dks 状态start_application(db, app_id, tenant_no, user_id)— dks→jxzcomplete_application(db, app_id, tenant_no, user_id)— jxz→ywcvoid_application(db, app_id, tenant_no, user_id)— dks或jxz→yzf
Patient(2 个):
get_patient_by_application(db, app_id, tenant_no)save_patient(db, app_id, data, tenant_no, user_id, dept_id)— 如果有 customer_id 则更新 biz_customer;如果无则新建 biz_customer 并回填 customer_id。然后用 upsert 逻辑保存 biz_pilot_patient(先查再更新或新建)。
InfusionPlan(4 个):
list_infusion_plans(db, app_id, tenant_no)— 按 order_num / create_time ASCcreate_infusion_plan(db, app_id, data, tenant_no, user_id, dept_id)update_infusion_plan(db, plan_id, data, tenant_no)soft_delete_infusion_plan(db, plan_id, tenant_no)— 级联软删除其下所有 schedule
InfusionSchedule(6 个):
list_schedules_by_plan(db, plan_id, tenant_no)— 按 order_num ASCauto_generate_schedules(db, plan_id, tenant_no, user_id, dept_id)— 读取 plan.total_course_count,批量插入 N 条 schedulecreate_schedule(db, plan_id, data, tenant_no, user_id, dept_id)— 手动添加单条update_schedule(db, schedule_id, data, tenant_no)soft_delete_schedule(db, schedule_id, tenant_no)mark_schedule_complete(db, schedule_id, tenant_no, user_id)— schedule_status='ywc'
application_no 生成逻辑:
def _generate_application_no(db, tenant_no):
today_str = datetime.now().strftime('%Y%m%d')
prefix = f'ZSSQ{today_str}'
# 查今天已有的最大序号
existing = db.query(BizPilotApplication.application_no).filter(
BizPilotApplication.tenant_no == tenant_no,
BizPilotApplication.application_no.like(f'{prefix}%'),
BizPilotApplication.del_flag == 0
).order_by(BizPilotApplication.application_no.desc()).first()
seq = 1
if existing and existing[0]:
seq = int(existing[0][-4:]) + 1
return f'{prefix}{seq:04d}'
- Step 2: 验证导入
Run: cd backend && source venv/bin/activate && python -c "from app.crud.pilot_application import list_applications; print('CRUD imported OK')"
Expected: CRUD imported OK
- Step 3: 运行 LSP 诊断
Run lsp_diagnostics on backend/app/crud/pilot_application.py — expect 0 errors.
Task 3: Backend API Routes + Router Registration
Files:
-
Create:
backend/app/api/pilot_application.py -
Modify:
backend/app/api/__init__.py -
Modify:
backend/app/main.py -
Step 1: 创建 API 路由文件
创建 backend/app/api/pilot_application.py,遵循 api/pilot_service.py 的模式(Pydantic schemas 内联 + 路由函数)。
import 部分:
from fastapi import APIRouter, Depends, Query, Request
from sqlalchemy.orm import Session
from pydantic import BaseModel, Field
from typing import Optional, List
from app.core.database import get_db
from app.core.menu_guard import require_admin_or_super_admin
from app.crud import pilot_application
Pydantic Schemas(6 个):
class ApplicationCreateBody(BaseModel):
channel_service_order_id: str
remark: Optional[str] = None
class ApplicationUpdateBody(BaseModel):
remark: Optional[str] = None
class PatientSaveBody(BaseModel):
customer_id: Optional[str] = None
patient_name: str
sex: Optional[str] = None
age: Optional[int] = None
id_card_no: Optional[str] = None
phone_no: Optional[str] = None
emergency_contact: Optional[str] = None
emergency_phone: Optional[str] = None
clinical_diagnosis: Optional[str] = None
diagnosis_attachments: Optional[str] = None
need_checkup: int = 1
class InfusionPlanCreateBody(BaseModel):
pilot_application_id: str
cell_type: str
spec_text: Optional[str] = None
total_course_count: int
course_interval: Optional[str] = None
infusion_route: Optional[str] = None
infusion_start_date: Optional[str] = None
class InfusionPlanUpdateBody(BaseModel):
cell_type: Optional[str] = None
spec_text: Optional[str] = None
total_course_count: Optional[int] = None
course_interval: Optional[str] = None
infusion_route: Optional[str] = None
infusion_start_date: Optional[str] = None
class InfusionScheduleUpdateBody(BaseModel):
infusion_time: Optional[str] = None
remark: Optional[str] = None
attachments: Optional[str] = None
Router:
router = APIRouter(prefix="/web/admin", tags=["中试服务申请单管理"])
API 路由(19 个):
| 方法 | 路径 | 函数名 | 依赖 | 说明 |
|---|---|---|---|---|
| GET | /pilot-application/list |
api_list_applications | require_admin | 分页列表,query params: page/page_size/channel_id/application_status/channel_service_order_id |
| GET | /pilot-application/detail |
api_get_application | require_admin | query param: id |
| POST | /pilot-application/create |
api_create_application | require_admin | body: ApplicationCreateBody |
| PUT | /pilot-application/update |
api_update_application | require_admin | body: ApplicationUpdateBody + id |
| DELETE | /pilot-application/delete |
api_delete_application | require_admin | body: {id} |
| POST | /pilot-application/start |
api_start_application | require_admin | body: {id} |
| POST | /pilot-application/complete |
api_complete_application | require_admin | body: {id} |
| POST | /pilot-application/void |
api_void_application | require_admin | body: {id} |
| GET | /pilot-patient/detail |
api_get_patient | require_admin | query param: application_id |
| POST | /pilot-patient/save |
api_save_patient | require_admin | body: PatientSaveBody + application_id |
| GET | /pilot-infusion-plan/list |
api_list_plans | require_admin | query param: application_id |
| POST | /pilot-infusion-plan/create |
api_create_plan | require_admin | body: InfusionPlanCreateBody |
| PUT | /pilot-infusion-plan/update |
api_update_plan | require_admin | body: InfusionPlanUpdateBody + id |
| DELETE | /pilot-infusion-plan/delete |
api_delete_plan | require_admin | body: {id} |
| POST | /pilot-infusion-schedule/auto-generate |
api_auto_generate | require_admin | body: {plan_id} |
| POST | /pilot-infusion-schedule/create |
api_create_schedule | require_admin | body: {plan_id, infusion_time?, remark?} |
| PUT | /pilot-infusion-schedule/update |
api_update_schedule | require_admin | body: InfusionScheduleUpdateBody + id |
| DELETE | /pilot-infusion-schedule/delete |
api_delete_schedule | require_admin | body: {id} |
| POST | /pilot-infusion-schedule/mark-complete |
api_mark_complete | require_admin | body: {id} |
每个路由函数结构:
@router.get("/pilot-application/list")
def api_list_applications(
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
channel_id: Optional[str] = Query(None),
application_status: Optional[str] = Query(None),
channel_service_order_id: Optional[str] = Query(None),
_user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
tenant_no = _user["tenant_no"]
result = pilot_application.list_applications(db, tenant_no, page, page_size, channel_id, application_status, channel_service_order_id)
return {"code": 0, "data": result}
- Step 2: 注册路由
在 backend/app/api/__init__.py 中追加:
from app.api.pilot_application import router as pilot_application_router
并在 routers 列表中追加 pilot_application_router。
在 backend/app/main.py 中追加路由 include(参考已有的 pilot_service_router 注册方式)。
- Step 3: 验证
Run: cd backend && source venv/bin/activate && python -c "from app.api.pilot_application import router; print(f'Routes: {len(router.routes)}')"
Expected: Routes: 19
- Step 4: 运行 LSP 诊断
Run lsp_diagnostics on backend/app/api/pilot_application.py, backend/app/api/__init__.py, backend/app/main.py — expect 0 errors on all.
Task 4: Frontend API 函数 + 状态标签 + 路由
Files:
-
Modify:
web-admin/src/api/admin.ts -
Modify:
web-admin/src/utils/status-tag-registry.ts -
Modify:
web-admin/src/router/modules/sqxp.ts -
Step 1: 在 admin.ts 末尾追加 19 个 API 函数
遵循文件中已有函数模式(参考 fetchPilotApplicationList 风格),追加以下函数:
// ==================== 中试服务申请单 ====================
export function fetchPilotApplicationList(params: any) {
return request({ url: '/pilot-application/list', method: 'get', params })
}
export function fetchPilotApplicationDetail(params: any) {
return request({ url: '/pilot-application/detail', method: 'get', params })
}
export function createPilotApplication(data: any) {
return request({ url: '/pilot-application/create', method: 'post', data })
}
export function updatePilotApplication(data: any) {
return request({ url: '/pilot-application/update', method: 'put', data })
}
export function deletePilotApplication(data: any) {
return request({ url: '/pilot-application/delete', method: 'delete', data })
}
export function startPilotApplication(data: any) {
return request({ url: '/pilot-application/start', method: 'post', data })
}
export function completePilotApplication(data: any) {
return request({ url: '/pilot-application/complete', method: 'post', data })
}
export function voidPilotApplication(data: any) {
return request({ url: '/pilot-application/void', method: 'post', data })
}
// 患者信息
export function fetchPilotPatient(params: any) {
return request({ url: '/pilot-patient/detail', method: 'get', params })
}
export function savePilotPatient(data: any) {
return request({ url: '/pilot-patient/save', method: 'post', data })
}
// 输注方案
export function fetchPilotInfusionPlanList(params: any) {
return request({ url: '/pilot-infusion-plan/list', method: 'get', params })
}
export function createPilotInfusionPlan(data: any) {
return request({ url: '/pilot-infusion-plan/create', method: 'post', data })
}
export function updatePilotInfusionPlan(data: any) {
return request({ url: '/pilot-infusion-plan/update', method: 'put', data })
}
export function deletePilotInfusionPlan(data: any) {
return request({ url: '/pilot-infusion-plan/delete', method: 'delete', data })
}
// 输注计划
export function fetchPilotInfusionScheduleList(params: any) {
return request({ url: '/pilot-infusion-schedule/list', method: 'get', params })
}
export function autoGenerateSchedules(data: any) {
return request({ url: '/pilot-infusion-schedule/auto-generate', method: 'post', data })
}
export function updatePilotInfusionSchedule(data: any) {
return request({ url: '/pilot-infusion-schedule/update', method: 'put', data })
}
export function deletePilotInfusionSchedule(data: any) {
return request({ url: '/pilot-infusion-schedule/delete', method: 'delete', data })
}
export function markScheduleComplete(data: any) {
return request({ url: '/pilot-infusion-schedule/mark-complete', method: 'post', data })
}
- Step 2: 在 status-tag-registry.ts 追加申请单状态标签函数
/** 中试申请单状态 */
export function applicationStatusLabel(status: string): string {
const map: Record<string, string> = { dks: '待开始', jxz: '进行中', ywc: '已完成', yzf: '已作废' }
return map[status] || status || '-'
}
export function applicationStatusTagType(status: string): '' | 'success' | 'warning' | 'danger' | 'info' {
const map: Record<string, '' | 'success' | 'warning' | 'danger' | 'info'> = {
dks: 'info', jxz: '', ywc: 'success', yzf: 'danger'
}
return map[status] || 'info'
}
- Step 3: 在 sqxp.ts 追加 2 个路由
在 PilotService 的 children 数组末尾追加:
{
path: 'pilot-application',
name: 'PilotApplication',
component: () => import('@views/pilot-service/pilot-application/index.vue'),
meta: { title: '中试服务申请单', icon: 'i-ep:document' }
},
{
path: 'pilot-application/detail',
name: 'PilotApplicationDetail',
component: () => import('@views/pilot-service/pilot-application/ApplicationDetail.vue'),
meta: { title: '申请单详情', hidden: true }
},
注意:路由 ID 需要根据现有 PilotService 的 ID 段确定,查看 sqxp.ts 中已有的路由 ID 模式。中试服务模块 ID 段为 8000...,检查当前已使用的最大 ID 后递增。
- Step 4: 运行 LSP 诊断
Run lsp_diagnostics on all 3 modified files — expect 0 errors.
Task 5: 前端列表页
Files:
-
Create:
web-admin/src/views/pilot-service/pilot-application/index.vue -
Step 1: 创建列表页
创建目录 web-admin/src/views/pilot-service/pilot-application/ 和 index.vue。
参考模板页: src/views/system/order/index.vue(复杂列表页)
页面结构:
- 筛选区:申请单号(el-input)、渠道(el-select,数据源 fetchChannelList)、服务订单(el-select,联动渠道)、状态(el-radio-group 或 tag 按钮)
- 操作栏:新增按钮(el-button type="primary")
- 表格(el-table):列如 spec 中定义
- 分页(el-pagination)
关键交互:
- 新增:弹窗(el-dialog),选择渠道 → 联动加载该渠道的服务订单(调用已有的 fetchServiceOrderList,传入 channel_id 筛选)→ 选择服务订单 → 填写备注 → 提交 → 成功后 router.push 到详情页
- 状态操作:根据 applicationStatus 动态显示按钮
- dks:显示"开始"(primary) + "作废"(danger)
- jxz:显示"完成"(primary) + "作废"(danger)
- ywc/yzf:无按钮
- 编辑/删除:仅 dks 状态可操作
- 行点击:router.push({ name: 'PilotApplicationDetail', query: { id: row.id } })
- 状态列用 el-tag + applicationStatusLabel/applicationStatusTagType
数据加载: onMounted 调用 fetchPilotApplicationList,筛选变更时重新加载。
样式: 遵循项目 Fresh Mint 色系(--primary #10b981),使用已有的 SCSS 变量和 mixin。
- Step 2: 运行 LSP 诊断
Run lsp_diagnostics on web-admin/src/views/pilot-service/pilot-application/index.vue — expect 0 errors.
Task 6: 前端详情页 — 基本信息 Tab + 输注方案 Tab
Files:
-
Create:
web-admin/src/views/pilot-service/pilot-application/ApplicationDetail.vue -
Step 1: 创建详情页
参考模板页: src/views/system/product/index.vue(含详情展示的复杂页面)
页面结构:
- 顶部:返回按钮 + 申请单号 + 状态标签 + 操作按钮(开始/完成/作废,根据状态显示)
- el-tabs:4 个 tab(基本信息 / 输注方案 / 体检信息-locked / 交付信息-locked)
基本信息 Tab:
- 上半区:el-descriptions 展示申请单信息(只读)
- 申请单号、渠道名称、关联服务订单、状态、创建时间、备注
- 下半区:患者信息面板(el-card)
- "选择客户"按钮 → el-dialog 弹窗展示客户列表(调用已有 API 获取 biz_customer 列表,支持姓名/手机搜索)
- 选中后自动填充表单字段
- 表单字段:patientName, sex, age, idCardNo, phoneNo, emergencyContact, emergencyPhone, clinicalDiagnosis, diagnosisAttachments(附件上传), needCheckup(el-switch)
- "保存患者信息"按钮
输注方案 Tab:
- 工具栏:"新增输注方案"按钮
- 方案列表用 el-table,每行显示方案的完整信息
- 最后一列加 expand 展开行,展开后显示该方案的输注计划列表
- 展开行内的计划列表(el-table):
- 列:序号、细胞类型、规格、输注时间(可编辑)、状态、操作
- 操作:编辑(弹窗修改时间/备注)、删除、标记完成
- "手动添加计划"按钮在展开行内
新增/编辑输注方案弹窗(el-dialog):
- 表单字段:cell_type(el-select,数据源从细胞产品类型 API)、spec_text、total_course_count(el-input-number)、course_interval、infusion_route、infusion_start_date(el-date-picker)
- label-position="top",遵循 .form-grid-2 两列布局
- 保存成功后 ElMessageBox.confirm("是否立即生成 N 条输注计划?"),确认则调用 autoGenerateSchedules
数据加载: onMounted 根据 route.query.id 调用 fetchPilotApplicationDetail 获取详情(含患者+方案),同时 fetchPilotInfusionPlanList 获取方案列表,每个方案再 fetchPilotInfusionScheduleList 获取计划。
样式约定:
-
Dialog forms:
label-position="top",.dialog-form-layout,.form-grid-2 -
无 backdrop blur:
backdrop-filter: none -
Panel heading:
.title-bar-heading+::before竖线 -
状态 Tag: el-tag + semantic colors
-
Step 2: 运行 LSP 诊断
Run lsp_diagnostics on web-admin/src/views/pilot-service/pilot-application/ApplicationDetail.vue — expect 0 errors.
Task 7: 集成验证
Files: 无新文件
- Step 1: 后端启动验证
Run: cd backend && source venv/bin/activate && python -c " from app.main import app routes = [r.path for r in app.routes if hasattr(r, 'path')] pilot_routes = [r for r in routes if 'pilot-application' in r or 'pilot-patient' in r or 'pilot-infusion' in r] print(f'Pilot application routes: {len(pilot_routes)}') for r in sorted(pilot_routes): print(f' {r}') "
Expected: 19 routes listed
- Step 2: 前端构建验证
Run: cd web-admin && pnpm build
Expected: Build succeeds with exit code 0
- Step 3: 前端 LSP 全量诊断
Run lsp_diagnostics on directory web-admin/src/views/pilot-service/pilot-application/ — expect 0 errors.
- Step 4: 更新知识图谱
Run: ./scripts/update-graph.sh backend frontend
Expected: 知识图谱更新完成
自审检查
Spec Coverage
| Spec 需求 | 对应 Task |
|---|---|
| ALTER biz_pilot_application | Task 1 |
| 4 个 Model 类 | Task 1 |
| Application CRUD 8 个函数 | Task 2 |
| Patient save/get 2 个函数 | Task 2 |
| InfusionPlan CRUD 4 个函数 | Task 2 |
| InfusionSchedule 6 个函数 | Task 2 |
| API routes 19 个 | Task 3 |
| Router registration | Task 3 |
| 19 个前端 API 函数 | Task 4 |
| 状态标签函数 | Task 4 |
| 路由追加 | Task 4 |
| 列表页 | Task 5 |
| 详情页(基本信息+输注方案) | Task 6 |
| 集成验证 | Task 7 |
Placeholder 扫描
无 TBD/TODO/占位符。所有步骤含具体代码或明确指令。
Type 一致性
- Model 字段名与 CRUD _row_to_dict 输出的 camelCase key 一致
- API 路径与前端 request URL 一致
- Pydantic schema 字段与 CRUD 函数参数一致