-
Data Governance Dev authored
**后端** - 新增 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 守卫)62f0ce1a