diff --git a/CHANGELOG.md b/CHANGELOG.md index c388db1..bffcdf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ - 小节正文写用户能看懂的变化(新能力、修复、注意事项),避免堆砌内部实现细节 - 打 tag 前**必须**为即将发布的版本新增一节;找不到匹配小节时,本次发布不会更新 changelog 字段 +## 1.0.63 + +- 数据管理支持字段级 lookup:`options_json` 可声明同库关联表(`ref_table` / `value_column` / `label_column`),宿主新建/编辑时渲染下拉 +- 更新 SCHEMA.md 与元数据校验,兼容静态枚举数组与 lookup 对象两种形态 + ## 1.0.62 - Skill Action 新增 `interaction` / `interactionByPlacement`:声明数据管理点击是直通、轻确认还是参数表单(与 `executionProfile` 正交) diff --git a/SKILL.md b/SKILL.md index 69a5916..8c5086e 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,7 +1,7 @@ --- name: 技能开发模板(通用业务版) description: "OpenClaw 通用业务技能开发模板,供复制后定制新业务 skill。定制步骤见 development/DEVELOPMENT.md。" -version: 1.0.62 +version: 1.0.63 author: 深圳匠厂科技有限公司 metadata: openclaw: diff --git a/references/SCHEMA.md b/references/SCHEMA.md index f7d291b..91bc8f1 100644 --- a/references/SCHEMA.md +++ b/references/SCHEMA.md @@ -354,7 +354,11 @@ CREATE TABLE demo_items ( ## 枚举字段 options_json -`options_json` 为 JSON 数组,每项包含 `value` 与 `label`: +`options_json` 支持两种形态: + +### 1)静态枚举(数组) + +每项包含 `value` 与 `label`: ```json [ @@ -366,6 +370,31 @@ CREATE TABLE demo_items ( 宿主据此渲染选择框;技能侧负责保证 JSON 合法且与物理字段取值一致。 +### 2)表关联 lookup(对象,推荐用于外键式字段) + +当字段取值依赖同库另一张业务表时,`display_type` 设为 `lookup`,`options_json` 写对象(**不是**数组): + +```json +{ + "ref_table": "platforms", + "value_column": "platform_key", + "label_column": "display_name", + "filter_column": "enabled", + "filter_value": 1 +} +``` + +| 字段 | 必填 | 说明 | +|---|---|---| +| `ref_table` | 是 | 同库被引用表名(snake_case 标识符) | +| `value_column` | 是 | 写入当前字段的值列 | +| `label_column` | 是 | 下拉展示名列 | +| `filter_column` / `filter_value` | 否 | 等值过滤(如只列出 `enabled=1`) | + +宿主在加载表 schema 时查询 `ref_table`,把结果展开成 `{value,label}` 下拉选项;**物理列仍存标量**(如 `platform_key` 文本、`account_id` 整数),不做嵌套子表。 + +示例:账号表的平台字段 → 平台信息表;凭据表的账号编号 → 账号信息表。 + ## 幂等初始化标准 - `_jiangchang_tables` 和 `_jiangchang_columns` 必须通过 upsert 初始化 diff --git a/scripts/db/display_metadata_validator.py b/scripts/db/display_metadata_validator.py index f2646e7..6c1cfba 100644 --- a/scripts/db/display_metadata_validator.py +++ b/scripts/db/display_metadata_validator.py @@ -196,7 +196,7 @@ def _default_uses_unixepoch(default_value: object) -> bool: def validate_options_json(options_json: str | None) -> tuple[list[str], list[str]]: - """校验枚举 options_json 可被解析为 [{value, label}, ...] 结构。""" + """校验 options_json:静态枚举数组,或 lookup 对象。""" errors: list[str] = [] warnings: list[str] = [] if options_json is None or str(options_json).strip() == "": @@ -206,8 +206,24 @@ def validate_options_json(options_json: str | None) -> tuple[list[str], list[str except json.JSONDecodeError as exc: errors.append(f"options_json 不是合法 JSON:{exc}") return errors, warnings + + if isinstance(parsed, dict): + for key in ("ref_table", "value_column", "label_column"): + if not str(parsed.get(key) or "").strip(): + errors.append(f"options_json lookup 必须包含非空 {key}") + ident = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$") + for key in ("ref_table", "value_column", "label_column", "filter_column"): + raw = parsed.get(key) + if raw is None or raw == "": + continue + if not ident.match(str(raw)): + errors.append(f"options_json.{key} 必须是安全标识符,当前为 {raw!r}") + if parsed.get("filter_column") not in (None, "") and "filter_value" not in parsed: + warnings.append("options_json lookup 声明了 filter_column 但未提供 filter_value") + return errors, warnings + if not isinstance(parsed, list): - errors.append("options_json 必须是 JSON 数组") + errors.append("options_json 必须是 JSON 数组(静态枚举)或对象(lookup)") return errors, warnings for index, item in enumerate(parsed): if not isinstance(item, dict):