feat(web3): P4 任务字段+校验规则持久化
**后端**
- 新增 models/field.py + models/rule.py(两张 ORM:field/task_id FK + rule/field_id FK + ON DELETE CASCADE)
- Field.__table_args__ 加 UNIQUE(task_id, field_key)(schema.sql 第 59 行的事实地约束)
- models/__init__.py 注册新模型;app.py 在 init_db 前 import models(否则 Base.metadata 不知道有这两张表)
- routers/tasks.py:POST/PUT/copy 接 fields+rules;整存整取(先删后写,DB CASCADE 带走 rule);GET 列表给 fields/rules 计数,详情带 field_list;pydantic Field 改 import as PydField 避与 SQLAlchemy Field 同名撞车;port 出参强转 str 防 conn_json 里 int 致 500
- db/database.py:新增 _migrate_field_unique()(仿 _migrate_connection_preset_unique 套路:检测 UNIQUE 缺失则 drop+recreate),init_db 里调用
**前端**
- TaskConfigView.openEdit:拆「同步设 row + 异步 getTask」两步,确保 TaskModal 拿到带 field_list 的详情(列表接口故意不带 field_list 省带宽)
- TaskModal:watch(open) 编辑场景回填 form + 抽 _fillFieldListFromTask;新加 watch(field_list) 处理 getTask 异步时序;loadColumns 重选表时按 field_key 合并 rules 不覆盖
- TaskModal:加 connVerified 标志,testConn 成功才置 true;loadTables/loadColumns 未验证直接 return(编辑场景 form.schema 从 task 回填会触发 watch(form.schema) → loadTables → 空密码连 MySQL 报 1045)
**接口契约**
- POST/PUT /api/tasks body 加 fields: [{key, show_default, rules: [{desc, regex}]}]
- GET /api/tasks 返回 fields/rules 计数;GET /api/tasks/{id} 多返 field_list
- POST /api/tasks/{id}/copy 复制时连同 fields+rules 一起克隆,名字加 _副本 自动避重
- DELETE 走 DB 层 CASCADE(field/rule 一并清)
**端到端**
- POST 3 fields + 3 rules → 列表/详情计数与字段一致
- PUT 整体替换(删 1 加 1 改 1 全部生效)
- POST /copy → field_list 全克隆
- DELETE → SQLite 直查 0 孤儿 field/rule 记录
踩坑(详见 work-logs/2026-08-20.md 主题 28 + 微调 1/2/3)
- pydantic Field 被 SQLAlchemy Field 同名遮蔽(import 时必须 as 别名)
- Base.metadata.create_all 不会改已存在表(UNIQUE 靠 ORM 声明 + 启动时迁移脚本兜底)
- 验 SQLite UNIQUE 不能只看 sqlite_master type='index',UniqueConstraint 在表内 CREATE TABLE 子句里,要 INSERT 重复值测
- 编辑场景的 watch(form.schema) 没拦住自动连数据库(connVerified 守卫)
Showing
web3/backend/models/field.py
0 → 100644
web3/backend/models/rule.py
0 → 100644
Please register or sign in to comment