@qingflow-tech/qingflow-cli 1.1.50-beta.3 → 1.1.50-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-cli/SKILL.md +37 -0
- package/skills/qingflow-cli/reference/builder/50-views.md +19 -4
- package/skills/qingflow-cli/reference/builder/80-buttons-associated-resources.md +2 -0
- package/src/qingflow_mcp/app_form_validator.py +53 -14
- package/src/qingflow_mcp/builder_facade/app_form.py +152 -47
- package/src/qingflow_mcp/builder_facade/models.py +19 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/complete.json +3 -3
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/address.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/amount.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/attachment.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/code_block.json +3 -3
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/data_relation.json +3 -3
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/date.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/datetime.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/department.json +3 -3
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/email.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/link.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/long_text.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/member.json +3 -3
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/multi_select.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/number.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/phone.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/q_linker.json +3 -3
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/relation.json +3 -3
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/rich_text.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_choice.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_select.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/subtable.json +3 -3
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/text.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/formula.json +35 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/summary.json +2 -2
- package/src/qingflow_mcp/builder_facade/schemas/views/manifest.json +2 -3
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/complete.json +68 -16
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/button-config.json +6 -6
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/data-permission.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/display.json +13 -11
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/operation-permission.json +3 -3
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/query-condition.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/summary.json +51 -6
- package/src/qingflow_mcp/builder_facade/service.py +198 -83
- package/src/qingflow_mcp/builder_facade/views_extract.py +21 -1
- package/src/qingflow_mcp/cli/commands/builder.py +4 -2
- package/src/qingflow_mcp/server_app_builder.py +2 -2
- package/src/qingflow_mcp/solution/compiler/view_compiler.py +2 -1
- package/src/qingflow_mcp/solution/executor.py +3 -1
- package/src/qingflow_mcp/solution/spec_models.py +1 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +7 -5
|
@@ -18,6 +18,7 @@ from ..tools.app_tools import AppTools
|
|
|
18
18
|
from ..tools.package_tools import PackageTools
|
|
19
19
|
from .app_form_schema import AppFormContractError, AppFormSchemaContext, AppFormSchemaRegistry
|
|
20
20
|
from .service import (
|
|
21
|
+
AmbiguousFieldSelector,
|
|
21
22
|
_coerce_any_int,
|
|
22
23
|
_compile_code_block_binding_fields,
|
|
23
24
|
_compile_q_linker_binding_fields,
|
|
@@ -722,14 +723,26 @@ class AppFormNormalizer:
|
|
|
722
723
|
]
|
|
723
724
|
if fields:
|
|
724
725
|
body.append({"kind": "row", "fields": fields})
|
|
726
|
+
# 空表单 + 悬空 dataTitleQueId/dataCoverQueId(模板安装出的空壳应用常见:
|
|
727
|
+
# 后端 GET 原样吐出指向已不存在字段的 id)读面 canonical 化为 null——
|
|
728
|
+
# 写回 null 后端 normalizeDataTitleQueId 兜底、空表单落库等价,避免
|
|
729
|
+
# validate/apply 的 INVALID_DATA_TITLE_FIELD 拒掉自家写面(round-trip 断裂)。
|
|
730
|
+
has_fields = any(
|
|
731
|
+
bool(block.get("fields")) for block in body if block.get("kind") == "row"
|
|
732
|
+
) or any(
|
|
733
|
+
bool(row.get("fields"))
|
|
734
|
+
for block in body
|
|
735
|
+
if block.get("kind") == "section"
|
|
736
|
+
for row in block.get("rows") or []
|
|
737
|
+
)
|
|
725
738
|
resource = {
|
|
726
739
|
"apiVersion": context.api_version,
|
|
727
740
|
"appKey": app_key,
|
|
728
741
|
"spec": {
|
|
729
742
|
"appName": _compat_label(form.get("formTitle"), "App", app_key),
|
|
730
743
|
"appDescription": form.get("formDesc") or None,
|
|
731
|
-
"dataTitleField": _selector(title_id, field_names.get(title_id or 0)) if title_id is not None else None,
|
|
732
|
-
"dataCoverField": _selector(cover_id, field_names.get(cover_id or 0)) if cover_id is not None else None,
|
|
744
|
+
"dataTitleField": _selector(title_id, field_names.get(title_id or 0)) if title_id is not None and has_fields else None,
|
|
745
|
+
"dataCoverField": _selector(cover_id, field_names.get(cover_id or 0)) if cover_id is not None and has_fields else None,
|
|
733
746
|
"attachments": self._attachments(form.get("formAttach")),
|
|
734
747
|
"serialNumber": self._serial_number(form.get("serialNumType"), form.get("serialNumConfig")),
|
|
735
748
|
"body": body,
|
|
@@ -1743,6 +1756,8 @@ class AppFormCompiler:
|
|
|
1743
1756
|
relation_context["questionRelations"] = relations
|
|
1744
1757
|
internal_fields, relations = _compile_q_linker_binding_fields(fields=internal_fields, current_schema=relation_context)
|
|
1745
1758
|
self._validate_unique_relation_targets(relations, internal_fields)
|
|
1759
|
+
except AmbiguousFieldSelector as error:
|
|
1760
|
+
raise AppFormFailure("AMBIGUOUS_FIELD_SELECTOR", str(error), "CONTRACT_PRECHECK", details={"candidateQueIds": error.candidates}) from error
|
|
1746
1761
|
except (KeyError, ValueError) as error:
|
|
1747
1762
|
raise AppFormFailure("UNSUPPORTED_FIELD_CONFIG", str(error), "CONTRACT_PRECHECK") from error
|
|
1748
1763
|
self._assign_subfield_temp_ids(internal_fields)
|
|
@@ -2073,20 +2088,15 @@ class AppFormCompiler:
|
|
|
2073
2088
|
) -> tuple[JSONObject | None, JSONObject | None]:
|
|
2074
2089
|
body = spec.get("body") or []
|
|
2075
2090
|
fields = list(_iter_fields(body))
|
|
2076
|
-
|
|
2091
|
+
# 方案 A(duplicate-field-name):同父级允许同名——后端以 queId 为字段身份
|
|
2092
|
+
# (40013 实测 1624 组同名),get 提出的合法形态必须能写回;name 解析的
|
|
2093
|
+
# 歧义由各 selector resolver 以 AMBIGUOUS_FIELD_SELECTOR 显式拒绝,
|
|
2094
|
+
# 不在目标校验层做 name 唯一性强校验。
|
|
2077
2095
|
for field in fields:
|
|
2078
|
-
name = str(field.get("name") or "")
|
|
2079
|
-
if name in names:
|
|
2080
|
-
raise AppFormFailure("DUPLICATE_FIELD_NAME", f"duplicate top-level field name: {name}", "CONTRACT_PRECHECK")
|
|
2081
|
-
names.add(name)
|
|
2082
2096
|
if _field_type(field) == "subtable":
|
|
2083
|
-
sub_names: set[str] = set()
|
|
2084
2097
|
for subfield in field.get("subfields") or []:
|
|
2085
2098
|
if _field_type(subfield) not in SUBFIELD_TYPES:
|
|
2086
2099
|
raise AppFormFailure("INVALID_SPEC", "unsupported subtable field type", "CONTRACT_PRECHECK", details={"allowedTypes": sorted(SUBFIELD_TYPES)})
|
|
2087
|
-
if subfield.get("name") in sub_names:
|
|
2088
|
-
raise AppFormFailure("DUPLICATE_FIELD_NAME", f"duplicate subtable field name: {subfield.get('name')}", "CONTRACT_PRECHECK")
|
|
2089
|
-
sub_names.add(str(subfield.get("name") or ""))
|
|
2090
2100
|
title_selector = spec.get("dataTitleField")
|
|
2091
2101
|
data_title: JSONObject | None = None
|
|
2092
2102
|
if fields:
|
|
@@ -2145,6 +2155,8 @@ class AppFormCompiler:
|
|
|
2145
2155
|
relation_context["questionRelations"] = relations
|
|
2146
2156
|
internal_fields, relations = _compile_q_linker_binding_fields(fields=internal_fields, current_schema=relation_context)
|
|
2147
2157
|
self._validate_unique_relation_targets(relations, internal_fields)
|
|
2158
|
+
except AmbiguousFieldSelector as error:
|
|
2159
|
+
raise AppFormFailure("AMBIGUOUS_FIELD_SELECTOR", str(error), "COMPILE", details={"candidateQueIds": error.candidates}) from error
|
|
2148
2160
|
except (KeyError, ValueError) as error:
|
|
2149
2161
|
raise AppFormFailure("UNSUPPORTED_FIELD_CONFIG", str(error), "COMPILE") from error
|
|
2150
2162
|
self._assign_subfield_temp_ids(internal_fields)
|
|
@@ -2626,8 +2638,7 @@ class AppFormCompiler:
|
|
|
2626
2638
|
|
|
2627
2639
|
def _reference_config(self, profile: str, config: JSONObject, template: JSONObject | None = None) -> JSONObject:
|
|
2628
2640
|
target_app_key = str(config.get("targetAppKey") or "")
|
|
2629
|
-
|
|
2630
|
-
candidates = _backend_question_candidates(target_form)
|
|
2641
|
+
candidates, _target_form = self._target_form_candidates(profile, target_app_key)
|
|
2631
2642
|
explicit_display = isinstance(config.get("displayFields"), list)
|
|
2632
2643
|
deprecated_keys = [
|
|
2633
2644
|
key
|
|
@@ -2714,8 +2725,7 @@ class AppFormCompiler:
|
|
|
2714
2725
|
target_app_key = str(relation.get("targetAppKey") or "")
|
|
2715
2726
|
if not target_app_key:
|
|
2716
2727
|
raise AppFormFailure("UNSUPPORTED_FIELD_CONFIG", "select scope relation requires targetAppKey", "COMPILE")
|
|
2717
|
-
target_form = self.
|
|
2718
|
-
candidates = _backend_question_candidates(target_form)
|
|
2728
|
+
candidates, target_form = self._target_form_candidates(profile, target_app_key)
|
|
2719
2729
|
displayed = _resolve_backend_selector(relation.get("displayedField") or {}, candidates, "selectScopeRelation.displayedField")
|
|
2720
2730
|
payload: JSONObject = {
|
|
2721
2731
|
"queId": _positive_int(internal.get("que_id")) or int(internal.get("que_temp_id") or 0),
|
|
@@ -2737,14 +2747,27 @@ class AppFormCompiler:
|
|
|
2737
2747
|
def _resolve_subfield_runtime_id(selector: JSONObject, internal_fields: list[JSONObject], location: str) -> int:
|
|
2738
2748
|
if not isinstance(selector, dict):
|
|
2739
2749
|
raise AppFormFailure("INVALID_FIELD_SELECTOR", f"{location} must be an object", "COMPILE")
|
|
2750
|
+
# 同 _resolve_backend_selector:name 两侧 strip(存量字段名带前后空格,queId 兜底唯一性)。
|
|
2740
2751
|
que_id = _positive_int(selector.get("queId"))
|
|
2741
|
-
name = str(selector.get("name") or "")
|
|
2752
|
+
name = str(selector.get("name") or "").strip()
|
|
2742
2753
|
matches = [
|
|
2743
2754
|
item for item in internal_fields
|
|
2744
2755
|
if (que_id is None or _positive_int(item.get("que_id")) == que_id)
|
|
2745
|
-
and (not name or str(item.get("name") or "") == name)
|
|
2756
|
+
and (not name or str(item.get("name") or "").strip() == name)
|
|
2746
2757
|
]
|
|
2747
2758
|
if len(matches) != 1:
|
|
2759
|
+
if que_id is None and matches:
|
|
2760
|
+
raise AppFormFailure(
|
|
2761
|
+
"AMBIGUOUS_FIELD_SELECTOR",
|
|
2762
|
+
f"{location} matches multiple subfields with the same name; use queId to disambiguate",
|
|
2763
|
+
"COMPILE",
|
|
2764
|
+
details={
|
|
2765
|
+
"selector": selector,
|
|
2766
|
+
"candidateQueIds": sorted(
|
|
2767
|
+
{_positive_int(item.get("que_id")) or 0 for item in matches}
|
|
2768
|
+
),
|
|
2769
|
+
},
|
|
2770
|
+
)
|
|
2748
2771
|
raise AppFormFailure("INVALID_FIELD_SELECTOR", f"{location} must resolve to exactly one subfield", "COMPILE", details={"selector": selector, "matches": len(matches)})
|
|
2749
2772
|
return _positive_int(matches[0].get("que_id")) or int(matches[0].get("que_temp_id") or 0)
|
|
2750
2773
|
|
|
@@ -2796,8 +2819,7 @@ class AppFormCompiler:
|
|
|
2796
2819
|
source_fields = available_internal_fields
|
|
2797
2820
|
source_fields_are_backend = False
|
|
2798
2821
|
if source.get("kind") == "app":
|
|
2799
|
-
|
|
2800
|
-
source_fields = _backend_question_candidates(target_form)
|
|
2822
|
+
source_fields, _target_form = self._target_form_candidates(profile, str(source.get("appKey") or ""))
|
|
2801
2823
|
source_fields_are_backend = True
|
|
2802
2824
|
target = _resolve_backend_selector(source.get("field") or {}, source_fields, "source.field")
|
|
2803
2825
|
relation.update(
|
|
@@ -3237,6 +3259,34 @@ class AppFormCompiler:
|
|
|
3237
3259
|
raise AppFormFailure("FORM_READ_FAILED", "backend returned an invalid form", "READ")
|
|
3238
3260
|
return value
|
|
3239
3261
|
|
|
3262
|
+
def _target_form_candidates(self, profile: str, app_key: str) -> tuple[list[JSONObject], JSONObject]:
|
|
3263
|
+
"""跨表引用解析候选集:目标表 草稿∪发布 并集(queId 为 key,草稿覆盖)。
|
|
3264
|
+
|
|
3265
|
+
后端写链对跨表 queId(referQuestions/displayedQueId/matchRules 源字段)无
|
|
3266
|
+
存在性校验、原样落库(FormSvcImpl.updateFormInfo);链上唯一读目标字段的
|
|
3267
|
+
Formula.matchRulesToFormula 取 question∪draft_question 且草稿覆盖,编辑器
|
|
3268
|
+
候选接口(/question/{appKey}/relation)同样支持 beingDraft 草稿态。get 按
|
|
3269
|
+
草稿姿态提取声明,apply 只读发布会漏掉目标表「只加在草稿、从未发布」的字段
|
|
3270
|
+
(round-trip 断裂);只读草稿又会漏掉发布后草稿里删掉的旧字段——并集与
|
|
3271
|
+
后端写链语义一致。草稿读失败(目标表无草稿等)退回仅发布态。
|
|
3272
|
+
"""
|
|
3273
|
+
draft_form: JSONObject = {}
|
|
3274
|
+
try:
|
|
3275
|
+
draft_form = self._read_form(profile, app_key, being_draft=True)
|
|
3276
|
+
except AppFormFailure:
|
|
3277
|
+
draft_form = {}
|
|
3278
|
+
published_form = self._read_form(profile, app_key, being_draft=False)
|
|
3279
|
+
merged: list[JSONObject] = []
|
|
3280
|
+
seen: set[int] = set()
|
|
3281
|
+
for item in [*_backend_question_candidates(draft_form), *_backend_question_candidates(published_form)]:
|
|
3282
|
+
que_id = _positive_int(item.get("queId"))
|
|
3283
|
+
if que_id is not None:
|
|
3284
|
+
if que_id in seen:
|
|
3285
|
+
continue
|
|
3286
|
+
seen.add(que_id)
|
|
3287
|
+
merged.append(item)
|
|
3288
|
+
return merged, (draft_form or published_form)
|
|
3289
|
+
|
|
3240
3290
|
|
|
3241
3291
|
class AppFormReconciler:
|
|
3242
3292
|
RUNTIME_VERSION = "v1alpha1"
|
|
@@ -4395,33 +4445,46 @@ def _snake_code_block(value: JSONObject) -> JSONObject:
|
|
|
4395
4445
|
|
|
4396
4446
|
|
|
4397
4447
|
def _backend_question_candidates(form: JSONObject) -> list[JSONObject]:
|
|
4448
|
+
"""目标表字段的解析候选集(含表格子字段及其「父表·子字段」前缀变体)。
|
|
4449
|
+
|
|
4450
|
+
遍历与后端对齐:FormSvcImpl.fillMatchRulesList 按 supId 全局解析父表标题、
|
|
4451
|
+
不看容器嵌套层级,FormQuestionSvcImpl 的候选集同样把段落内字段展开——因此
|
|
4452
|
+
表格(queType=18)无论位于 formQues 顶层、baseQues 还是段落(queType=24)
|
|
4453
|
+
内部,其 subQuestions 都是合法候选;段落本身不作为候选。
|
|
4454
|
+
"""
|
|
4398
4455
|
result: list[JSONObject] = []
|
|
4456
|
+
|
|
4457
|
+
def _visit(item: JSONObject) -> None:
|
|
4458
|
+
que_type = _positive_int(item.get("queType"))
|
|
4459
|
+
if que_type == 24:
|
|
4460
|
+
for inner in item.get("innerQuestions") or []:
|
|
4461
|
+
for nested in inner if isinstance(inner, list) else []:
|
|
4462
|
+
if isinstance(nested, dict):
|
|
4463
|
+
_visit(nested)
|
|
4464
|
+
return
|
|
4465
|
+
result.append(item)
|
|
4466
|
+
# 表格(queType=18)子字段同样是合法的目标字段:后端 data_relation
|
|
4467
|
+
# 运行时按 queId 解析目标字段并把表格子字段纳入 targetQuestionMap
|
|
4468
|
+
# (DataRelationCalService 先按 queId 查 question、再把表格的
|
|
4469
|
+
# subQuestions 合入)。displayedQueId 指向表格子字段是存量合法
|
|
4470
|
+
# 配置,提取能读出、编译必须能写回(两闸门同口径)。
|
|
4471
|
+
# matchRules 的筛选源字段(sourceField)在存量里带父表名前缀
|
|
4472
|
+
# (「父表·子字段」,_data_relation_field_name 按同形态写回)。
|
|
4473
|
+
if que_type == 18:
|
|
4474
|
+
table_title = str(item.get("queTitle") or "")
|
|
4475
|
+
for sub in item.get("subQuestions") or []:
|
|
4476
|
+
if isinstance(sub, dict):
|
|
4477
|
+
prefixed = dict(sub)
|
|
4478
|
+
prefixed["_sup_que_title"] = table_title
|
|
4479
|
+
result.append(prefixed)
|
|
4480
|
+
|
|
4399
4481
|
for item in form.get("baseQues") or []:
|
|
4400
4482
|
if isinstance(item, dict):
|
|
4401
|
-
|
|
4483
|
+
_visit(item)
|
|
4402
4484
|
for row in form.get("formQues") or []:
|
|
4403
4485
|
for item in row if isinstance(row, list) else []:
|
|
4404
|
-
if
|
|
4405
|
-
|
|
4406
|
-
if _positive_int(item.get("queType")) == 24:
|
|
4407
|
-
for inner in item.get("innerQuestions") or []:
|
|
4408
|
-
result.extend(nested for nested in inner if isinstance(inner, list) and isinstance(nested, dict))
|
|
4409
|
-
else:
|
|
4410
|
-
result.append(item)
|
|
4411
|
-
# 表格(queType=18)子字段同样是合法的目标字段:后端 data_relation
|
|
4412
|
-
# 运行时按 queId 解析目标字段并把表格子字段纳入 targetQuestionMap
|
|
4413
|
-
# (DataRelationCalService 先按 queId 查 question、再把表格的
|
|
4414
|
-
# subQuestions 合入)。displayedQueId 指向表格子字段是存量合法
|
|
4415
|
-
# 配置,提取能读出、编译必须能写回(两闸门同口径)。
|
|
4416
|
-
# matchRules 的筛选源字段(sourceField)在存量里带父表名前缀
|
|
4417
|
-
# (「父表·子字段」,_data_relation_field_name 按同形态写回)。
|
|
4418
|
-
if _positive_int(item.get("queType")) == 18:
|
|
4419
|
-
table_title = str(item.get("queTitle") or "")
|
|
4420
|
-
for sub in item.get("subQuestions") or []:
|
|
4421
|
-
if isinstance(sub, dict):
|
|
4422
|
-
prefixed = dict(sub)
|
|
4423
|
-
prefixed["_sup_que_title"] = table_title
|
|
4424
|
-
result.append(prefixed)
|
|
4486
|
+
if isinstance(item, dict):
|
|
4487
|
+
_visit(item)
|
|
4425
4488
|
return result
|
|
4426
4489
|
|
|
4427
4490
|
|
|
@@ -4451,29 +4514,58 @@ def _backend_selector_titles(item: JSONObject) -> list[str]:
|
|
|
4451
4514
|
|
|
4452
4515
|
|
|
4453
4516
|
def _resolve_backend_selector(selector: JSONObject, candidates: list[JSONObject], location: str) -> JSONObject:
|
|
4517
|
+
# name 比较两侧 strip:存量字段名带前导/尾随空格(后端无空白校验),声明里的
|
|
4518
|
+
# name 与候选 queTitle 一个空格即失配;queId 已同时给出,空格差异不构成歧义。
|
|
4454
4519
|
que_id = _positive_int(selector.get("queId"))
|
|
4455
|
-
name = str(selector.get("name") or "")
|
|
4520
|
+
name = str(selector.get("name") or "").strip()
|
|
4456
4521
|
matches = [
|
|
4457
4522
|
item
|
|
4458
4523
|
for item in candidates
|
|
4459
4524
|
if (que_id is None or _positive_int(item.get("queId")) == que_id)
|
|
4460
|
-
and (not name or name in _backend_selector_titles(item))
|
|
4525
|
+
and (not name or name in {title.strip() for title in _backend_selector_titles(item)})
|
|
4461
4526
|
]
|
|
4462
4527
|
if len(matches) != 1:
|
|
4528
|
+
# 方案 A:后端允许同父级同名(queId 为身份)。name-only 命中多个时显式报
|
|
4529
|
+
# 歧义并给出候选 queId,禁止静默取第一个。
|
|
4530
|
+
if que_id is None and matches:
|
|
4531
|
+
raise AppFormFailure(
|
|
4532
|
+
"AMBIGUOUS_FIELD_SELECTOR",
|
|
4533
|
+
f"{location} matches multiple fields with the same name; use queId to disambiguate",
|
|
4534
|
+
"CONTRACT_PRECHECK",
|
|
4535
|
+
details={
|
|
4536
|
+
"selector": selector,
|
|
4537
|
+
"candidateQueIds": sorted(
|
|
4538
|
+
{_positive_int(item.get("queId")) or 0 for item in matches}
|
|
4539
|
+
),
|
|
4540
|
+
},
|
|
4541
|
+
)
|
|
4463
4542
|
raise AppFormFailure("INVALID_FIELD_SELECTOR", f"{location} must resolve to exactly one field", "CONTRACT_PRECHECK", details={"selector": selector, "matches": len(matches)})
|
|
4464
4543
|
return matches[0]
|
|
4465
4544
|
|
|
4466
4545
|
|
|
4467
4546
|
def _resolve_canonical_selector(selector: JSONObject, candidates: list[JSONObject], location: str) -> JSONObject:
|
|
4547
|
+
# 同 _resolve_backend_selector:name 两侧 strip(存量字段名带前后空格,queId 兜底唯一性)。
|
|
4468
4548
|
que_id = _positive_int(selector.get("queId"))
|
|
4469
|
-
name = str(selector.get("name") or "")
|
|
4549
|
+
name = str(selector.get("name") or "").strip()
|
|
4470
4550
|
matches = [
|
|
4471
4551
|
item
|
|
4472
4552
|
for item in candidates
|
|
4473
4553
|
if (que_id is None or _positive_int(item.get("queId")) == que_id)
|
|
4474
|
-
and (not name or str(item.get("name") or "") == name)
|
|
4554
|
+
and (not name or str(item.get("name") or "").strip() == name)
|
|
4475
4555
|
]
|
|
4476
4556
|
if len(matches) != 1:
|
|
4557
|
+
if que_id is None and matches:
|
|
4558
|
+
raise AppFormFailure(
|
|
4559
|
+
"AMBIGUOUS_FIELD_SELECTOR",
|
|
4560
|
+
f"{location} matches multiple fields with the same name; use queId to disambiguate",
|
|
4561
|
+
"CONTRACT_PRECHECK",
|
|
4562
|
+
details={
|
|
4563
|
+
"selector": selector,
|
|
4564
|
+
"candidateQueIds": sorted(
|
|
4565
|
+
{_positive_int(item.get("queId")) or 0 for item in matches}
|
|
4566
|
+
),
|
|
4567
|
+
},
|
|
4568
|
+
)
|
|
4477
4569
|
raise AppFormFailure(
|
|
4478
4570
|
"INVALID_FIELD_SELECTOR",
|
|
4479
4571
|
f"{location} must resolve to exactly one top-level field",
|
|
@@ -4484,14 +4576,27 @@ def _resolve_canonical_selector(selector: JSONObject, candidates: list[JSONObjec
|
|
|
4484
4576
|
|
|
4485
4577
|
|
|
4486
4578
|
def _resolve_internal_selector(selector: JSONObject, fields: list[JSONObject], location: str) -> JSONObject:
|
|
4579
|
+
# 同 _resolve_backend_selector:name 两侧 strip(存量字段名带前后空格,queId 兜底唯一性)。
|
|
4487
4580
|
que_id = _positive_int(selector.get("queId") or selector.get("que_id"))
|
|
4488
|
-
name = str(selector.get("name") or "")
|
|
4581
|
+
name = str(selector.get("name") or "").strip()
|
|
4489
4582
|
matches = [
|
|
4490
4583
|
item
|
|
4491
4584
|
for item in fields
|
|
4492
4585
|
if (que_id is None or _positive_int(item.get("que_id")) == que_id)
|
|
4493
|
-
and (not name or str(item.get("name") or "") == name)
|
|
4586
|
+
and (not name or str(item.get("name") or "").strip() == name)
|
|
4494
4587
|
]
|
|
4495
4588
|
if len(matches) != 1:
|
|
4589
|
+
if que_id is None and matches:
|
|
4590
|
+
raise AppFormFailure(
|
|
4591
|
+
"AMBIGUOUS_FIELD_SELECTOR",
|
|
4592
|
+
f"{location} matches multiple fields with the same name; use queId to disambiguate",
|
|
4593
|
+
"COMPILE",
|
|
4594
|
+
details={
|
|
4595
|
+
"selector": selector,
|
|
4596
|
+
"candidateQueIds": sorted(
|
|
4597
|
+
{_positive_int(item.get("que_id")) or 0 for item in matches}
|
|
4598
|
+
),
|
|
4599
|
+
},
|
|
4600
|
+
)
|
|
4496
4601
|
raise AppFormFailure("INVALID_FIELD_SELECTOR", f"{location} must resolve to exactly one field", "COMPILE", details={"selector": selector, "matches": len(matches)})
|
|
4497
4602
|
return matches[0]
|
|
@@ -135,6 +135,7 @@ class PublicViewType(str, Enum):
|
|
|
135
135
|
board = "board"
|
|
136
136
|
gantt = "gantt"
|
|
137
137
|
hierarchy = "hierarchy"
|
|
138
|
+
calendar = "calendar"
|
|
138
139
|
|
|
139
140
|
|
|
140
141
|
class PublicButtonTriggerAction(str, Enum):
|
|
@@ -1273,6 +1274,14 @@ class ViewHierarchyConfigPatch(StrictModel):
|
|
|
1273
1274
|
)
|
|
1274
1275
|
|
|
1275
1276
|
|
|
1277
|
+
class ViewCalendarConfigPatch(StrictModel):
|
|
1278
|
+
"""日历视图特有配置 → viewgraphCalendarConfigVO。"""
|
|
1279
|
+
|
|
1280
|
+
title_que_id: int = Field(validation_alias=AliasChoices("title_que_id", "titleQueId"))
|
|
1281
|
+
date_que_id: int = Field(validation_alias=AliasChoices("date_que_id", "dateQueId"))
|
|
1282
|
+
color_que_id: int | None = Field(default=None, validation_alias=AliasChoices("color_que_id", "colorQueId"))
|
|
1283
|
+
|
|
1284
|
+
|
|
1276
1285
|
class ViewDisplayConfigPatch(StrictModel):
|
|
1277
1286
|
"""展示杂项 → defaultRowHigh/beingPinNavigate/remark。"""
|
|
1278
1287
|
|
|
@@ -1429,6 +1438,7 @@ class ViewUpsertPatch(StrictModel):
|
|
|
1429
1438
|
card: ViewCardConfigPatch | None = Field(default=None)
|
|
1430
1439
|
gantt: ViewGanttConfigPatch | None = Field(default=None)
|
|
1431
1440
|
hierarchy: ViewHierarchyConfigPatch | None = Field(default=None)
|
|
1441
|
+
calendar: ViewCalendarConfigPatch | None = Field(default=None)
|
|
1432
1442
|
display: ViewDisplayConfigPatch | None = Field(default=None)
|
|
1433
1443
|
record: ViewRecordConfigPatch | None = Field(default=None)
|
|
1434
1444
|
share: ViewShareConfigPatch | None = Field(default=None)
|
|
@@ -1497,6 +1507,10 @@ class ViewUpsertPatch(StrictModel):
|
|
|
1497
1507
|
payload["type"] = "board"
|
|
1498
1508
|
elif normalized == "ganttview":
|
|
1499
1509
|
payload["type"] = "gantt"
|
|
1510
|
+
elif normalized == "hierarchyview":
|
|
1511
|
+
payload["type"] = "hierarchy"
|
|
1512
|
+
elif normalized == "calendarview":
|
|
1513
|
+
payload["type"] = "calendar"
|
|
1500
1514
|
return payload
|
|
1501
1515
|
|
|
1502
1516
|
@model_validator(mode="after")
|
|
@@ -1512,6 +1526,11 @@ class ViewUpsertPatch(StrictModel):
|
|
|
1512
1526
|
raise ValueError("hierarchy view requires hierarchy.title_que_id and children_que_id")
|
|
1513
1527
|
elif self.hierarchy is not None:
|
|
1514
1528
|
raise ValueError("hierarchy config is only allowed on hierarchy views")
|
|
1529
|
+
if self.type == PublicViewType.calendar:
|
|
1530
|
+
if self.calendar is None:
|
|
1531
|
+
raise ValueError("calendar view requires calendar.title_que_id and date_que_id")
|
|
1532
|
+
elif self.calendar is not None:
|
|
1533
|
+
raise ValueError("calendar config is only allowed on calendar views")
|
|
1515
1534
|
if self.type != PublicViewType.card and self.card is not None:
|
|
1516
1535
|
raise ValueError("card config is only allowed on card views")
|
|
1517
1536
|
if self.type != PublicViewType.gantt and self.gantt is not None:
|
|
@@ -1275,7 +1275,7 @@
|
|
|
1275
1275
|
"description": "普通字段的默认值;后端 queDefaultValue 为字符串,因此数字等字面量也必须以字符串传入。defaultType 为 1 时表示静态文本,为 3 时表示公式文本,为 2 时不携带字面量默认值。表格、地址、附件、引用等结构化默认值必须放在具体字段 config.defaultValues。显式 null 表示清除默认值。"
|
|
1276
1276
|
},
|
|
1277
1277
|
"name": {
|
|
1278
|
-
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId
|
|
1278
|
+
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId 时省略;同父级允许同名(后端以 queId 为字段身份),配置引用建议携带 queId。",
|
|
1279
1279
|
"minLength": 1,
|
|
1280
1280
|
"type": "string"
|
|
1281
1281
|
},
|
|
@@ -1337,10 +1337,10 @@
|
|
|
1337
1337
|
]
|
|
1338
1338
|
}
|
|
1339
1339
|
],
|
|
1340
|
-
"description": "字段选择器;用于定位应用的数据标题、数据封面、自定义编号取值来源或其他配置引用的字段。至少提供 queId 或 name
|
|
1340
|
+
"description": "字段选择器;用于定位应用的数据标题、数据封面、自定义编号取值来源或其他配置引用的字段。至少提供 queId 或 name,两者同时提供时必须指向同一字段。后端允许同父级同名字段,queId 是稳定身份;name 仅在唯一时可解析,命中多个同名时报 AMBIGUOUS_FIELD_SELECTOR,此时必须改用 queId。",
|
|
1341
1341
|
"properties": {
|
|
1342
1342
|
"name": {
|
|
1343
|
-
"description": "
|
|
1343
|
+
"description": "字段名称;仅在当前作用域内唯一时可用于解析,同名命中多个字段时报 AMBIGUOUS_FIELD_SELECTOR,应改用 queId。",
|
|
1344
1344
|
"minLength": 1,
|
|
1345
1345
|
"type": "string"
|
|
1346
1346
|
},
|
|
@@ -301,7 +301,7 @@
|
|
|
301
301
|
"description": "普通字段的默认值;后端 queDefaultValue 为字符串,因此数字等字面量也必须以字符串传入。defaultType 为 1 时表示静态文本,为 3 时表示公式文本,为 2 时不携带字面量默认值。表格、地址、附件、引用等结构化默认值必须放在具体字段 config.defaultValues。显式 null 表示清除默认值。"
|
|
302
302
|
},
|
|
303
303
|
"name": {
|
|
304
|
-
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId
|
|
304
|
+
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId 时省略;同父级允许同名(后端以 queId 为字段身份),配置引用建议携带 queId。",
|
|
305
305
|
"minLength": 1,
|
|
306
306
|
"type": "string"
|
|
307
307
|
},
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"description": "普通字段的默认值;后端 queDefaultValue 为字符串,因此数字等字面量也必须以字符串传入。defaultType 为 1 时表示静态文本,为 3 时表示公式文本,为 2 时不携带字面量默认值。表格、地址、附件、引用等结构化默认值必须放在具体字段 config.defaultValues。显式 null 表示清除默认值。"
|
|
79
79
|
},
|
|
80
80
|
"name": {
|
|
81
|
-
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId
|
|
81
|
+
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId 时省略;同父级允许同名(后端以 queId 为字段身份),配置引用建议携带 queId。",
|
|
82
82
|
"minLength": 1,
|
|
83
83
|
"type": "string"
|
|
84
84
|
},
|
|
@@ -334,7 +334,7 @@
|
|
|
334
334
|
"description": "普通字段的默认值;后端 queDefaultValue 为字符串,因此数字等字面量也必须以字符串传入。defaultType 为 1 时表示静态文本,为 3 时表示公式文本,为 2 时不携带字面量默认值。表格、地址、附件、引用等结构化默认值必须放在具体字段 config.defaultValues。显式 null 表示清除默认值。"
|
|
335
335
|
},
|
|
336
336
|
"name": {
|
|
337
|
-
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId
|
|
337
|
+
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId 时省略;同父级允许同名(后端以 queId 为字段身份),配置引用建议携带 queId。",
|
|
338
338
|
"minLength": 1,
|
|
339
339
|
"type": "string"
|
|
340
340
|
},
|
|
@@ -221,7 +221,7 @@
|
|
|
221
221
|
"description": "普通字段的默认值;后端 queDefaultValue 为字符串,因此数字等字面量也必须以字符串传入。defaultType 为 1 时表示静态文本,为 3 时表示公式文本,为 2 时不携带字面量默认值。表格、地址、附件、引用等结构化默认值必须放在具体字段 config.defaultValues。显式 null 表示清除默认值。"
|
|
222
222
|
},
|
|
223
223
|
"name": {
|
|
224
|
-
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId
|
|
224
|
+
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId 时省略;同父级允许同名(后端以 queId 为字段身份),配置引用建议携带 queId。",
|
|
225
225
|
"minLength": 1,
|
|
226
226
|
"type": "string"
|
|
227
227
|
},
|
|
@@ -264,10 +264,10 @@
|
|
|
264
264
|
]
|
|
265
265
|
}
|
|
266
266
|
],
|
|
267
|
-
"description": "字段选择器;用于定位应用的数据标题、数据封面、自定义编号取值来源或其他配置引用的字段。至少提供 queId 或 name
|
|
267
|
+
"description": "字段选择器;用于定位应用的数据标题、数据封面、自定义编号取值来源或其他配置引用的字段。至少提供 queId 或 name,两者同时提供时必须指向同一字段。后端允许同父级同名字段,queId 是稳定身份;name 仅在唯一时可解析,命中多个同名时报 AMBIGUOUS_FIELD_SELECTOR,此时必须改用 queId。",
|
|
268
268
|
"properties": {
|
|
269
269
|
"name": {
|
|
270
|
-
"description": "
|
|
270
|
+
"description": "字段名称;仅在当前作用域内唯一时可用于解析,同名命中多个字段时报 AMBIGUOUS_FIELD_SELECTOR,应改用 queId。",
|
|
271
271
|
"minLength": 1,
|
|
272
272
|
"type": "string"
|
|
273
273
|
},
|
|
@@ -480,7 +480,7 @@
|
|
|
480
480
|
"description": "普通字段的默认值;后端 queDefaultValue 为字符串,因此数字等字面量也必须以字符串传入。defaultType 为 1 时表示静态文本,为 3 时表示公式文本,为 2 时不携带字面量默认值。表格、地址、附件、引用等结构化默认值必须放在具体字段 config.defaultValues。显式 null 表示清除默认值。"
|
|
481
481
|
},
|
|
482
482
|
"name": {
|
|
483
|
-
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId
|
|
483
|
+
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId 时省略;同父级允许同名(后端以 queId 为字段身份),配置引用建议携带 queId。",
|
|
484
484
|
"minLength": 1,
|
|
485
485
|
"type": "string"
|
|
486
486
|
},
|
|
@@ -523,10 +523,10 @@
|
|
|
523
523
|
]
|
|
524
524
|
}
|
|
525
525
|
],
|
|
526
|
-
"description": "字段选择器;用于定位应用的数据标题、数据封面、自定义编号取值来源或其他配置引用的字段。至少提供 queId 或 name
|
|
526
|
+
"description": "字段选择器;用于定位应用的数据标题、数据封面、自定义编号取值来源或其他配置引用的字段。至少提供 queId 或 name,两者同时提供时必须指向同一字段。后端允许同父级同名字段,queId 是稳定身份;name 仅在唯一时可解析,命中多个同名时报 AMBIGUOUS_FIELD_SELECTOR,此时必须改用 queId。",
|
|
527
527
|
"properties": {
|
|
528
528
|
"name": {
|
|
529
|
-
"description": "
|
|
529
|
+
"description": "字段名称;仅在当前作用域内唯一时可用于解析,同名命中多个字段时报 AMBIGUOUS_FIELD_SELECTOR,应改用 queId。",
|
|
530
530
|
"minLength": 1,
|
|
531
531
|
"type": "string"
|
|
532
532
|
},
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"description": "普通字段的默认值;后端 queDefaultValue 为字符串,因此数字等字面量也必须以字符串传入。defaultType 为 1 时表示静态文本,为 3 时表示公式文本,为 2 时不携带字面量默认值。表格、地址、附件、引用等结构化默认值必须放在具体字段 config.defaultValues。显式 null 表示清除默认值。"
|
|
84
84
|
},
|
|
85
85
|
"name": {
|
|
86
|
-
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId
|
|
86
|
+
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId 时省略;同父级允许同名(后端以 queId 为字段身份),配置引用建议携带 queId。",
|
|
87
87
|
"minLength": 1,
|
|
88
88
|
"type": "string"
|
|
89
89
|
},
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"description": "普通字段的默认值;后端 queDefaultValue 为字符串,因此数字等字面量也必须以字符串传入。defaultType 为 1 时表示静态文本,为 3 时表示公式文本,为 2 时不携带字面量默认值。表格、地址、附件、引用等结构化默认值必须放在具体字段 config.defaultValues。显式 null 表示清除默认值。"
|
|
84
84
|
},
|
|
85
85
|
"name": {
|
|
86
|
-
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId
|
|
86
|
+
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId 时省略;同父级允许同名(后端以 queId 为字段身份),配置引用建议携带 queId。",
|
|
87
87
|
"minLength": 1,
|
|
88
88
|
"type": "string"
|
|
89
89
|
},
|
|
@@ -201,7 +201,7 @@
|
|
|
201
201
|
"description": "普通字段的默认值;后端 queDefaultValue 为字符串,因此数字等字面量也必须以字符串传入。defaultType 为 1 时表示静态文本,为 3 时表示公式文本,为 2 时不携带字面量默认值。表格、地址、附件、引用等结构化默认值必须放在具体字段 config.defaultValues。显式 null 表示清除默认值。"
|
|
202
202
|
},
|
|
203
203
|
"name": {
|
|
204
|
-
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId
|
|
204
|
+
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId 时省略;同父级允许同名(后端以 queId 为字段身份),配置引用建议携带 queId。",
|
|
205
205
|
"minLength": 1,
|
|
206
206
|
"type": "string"
|
|
207
207
|
},
|
|
@@ -244,10 +244,10 @@
|
|
|
244
244
|
]
|
|
245
245
|
}
|
|
246
246
|
],
|
|
247
|
-
"description": "字段选择器;用于定位应用的数据标题、数据封面、自定义编号取值来源或其他配置引用的字段。至少提供 queId 或 name
|
|
247
|
+
"description": "字段选择器;用于定位应用的数据标题、数据封面、自定义编号取值来源或其他配置引用的字段。至少提供 queId 或 name,两者同时提供时必须指向同一字段。后端允许同父级同名字段,queId 是稳定身份;name 仅在唯一时可解析,命中多个同名时报 AMBIGUOUS_FIELD_SELECTOR,此时必须改用 queId。",
|
|
248
248
|
"properties": {
|
|
249
249
|
"name": {
|
|
250
|
-
"description": "
|
|
250
|
+
"description": "字段名称;仅在当前作用域内唯一时可用于解析,同名命中多个字段时报 AMBIGUOUS_FIELD_SELECTOR,应改用 queId。",
|
|
251
251
|
"minLength": 1,
|
|
252
252
|
"type": "string"
|
|
253
253
|
},
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"description": "普通字段的默认值;后端 queDefaultValue 为字符串,因此数字等字面量也必须以字符串传入。defaultType 为 1 时表示静态文本,为 3 时表示公式文本,为 2 时不携带字面量默认值。表格、地址、附件、引用等结构化默认值必须放在具体字段 config.defaultValues。显式 null 表示清除默认值。"
|
|
79
79
|
},
|
|
80
80
|
"name": {
|
|
81
|
-
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId
|
|
81
|
+
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId 时省略;同父级允许同名(后端以 queId 为字段身份),配置引用建议携带 queId。",
|
|
82
82
|
"minLength": 1,
|
|
83
83
|
"type": "string"
|
|
84
84
|
},
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"description": "普通字段的默认值;后端 queDefaultValue 为字符串,因此数字等字面量也必须以字符串传入。defaultType 为 1 时表示静态文本,为 3 时表示公式文本,为 2 时不携带字面量默认值。表格、地址、附件、引用等结构化默认值必须放在具体字段 config.defaultValues。显式 null 表示清除默认值。"
|
|
79
79
|
},
|
|
80
80
|
"name": {
|
|
81
|
-
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId
|
|
81
|
+
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId 时省略;同父级允许同名(后端以 queId 为字段身份),配置引用建议携带 queId。",
|
|
82
82
|
"minLength": 1,
|
|
83
83
|
"type": "string"
|
|
84
84
|
},
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"description": "普通字段的默认值;后端 queDefaultValue 为字符串,因此数字等字面量也必须以字符串传入。defaultType 为 1 时表示静态文本,为 3 时表示公式文本,为 2 时不携带字面量默认值。表格、地址、附件、引用等结构化默认值必须放在具体字段 config.defaultValues。显式 null 表示清除默认值。"
|
|
79
79
|
},
|
|
80
80
|
"name": {
|
|
81
|
-
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId
|
|
81
|
+
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId 时省略;同父级允许同名(后端以 queId 为字段身份),配置引用建议携带 queId。",
|
|
82
82
|
"minLength": 1,
|
|
83
83
|
"type": "string"
|
|
84
84
|
},
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
"description": "普通字段的默认值;后端 queDefaultValue 为字符串,因此数字等字面量也必须以字符串传入。defaultType 为 1 时表示静态文本,为 3 时表示公式文本,为 2 时不携带字面量默认值。表格、地址、附件、引用等结构化默认值必须放在具体字段 config.defaultValues。显式 null 表示清除默认值。"
|
|
151
151
|
},
|
|
152
152
|
"name": {
|
|
153
|
-
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId
|
|
153
|
+
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId 时省略;同父级允许同名(后端以 queId 为字段身份),配置引用建议携带 queId。",
|
|
154
154
|
"minLength": 1,
|
|
155
155
|
"type": "string"
|
|
156
156
|
},
|
|
@@ -193,10 +193,10 @@
|
|
|
193
193
|
]
|
|
194
194
|
}
|
|
195
195
|
],
|
|
196
|
-
"description": "字段选择器;用于定位应用的数据标题、数据封面、自定义编号取值来源或其他配置引用的字段。至少提供 queId 或 name
|
|
196
|
+
"description": "字段选择器;用于定位应用的数据标题、数据封面、自定义编号取值来源或其他配置引用的字段。至少提供 queId 或 name,两者同时提供时必须指向同一字段。后端允许同父级同名字段,queId 是稳定身份;name 仅在唯一时可解析,命中多个同名时报 AMBIGUOUS_FIELD_SELECTOR,此时必须改用 queId。",
|
|
197
197
|
"properties": {
|
|
198
198
|
"name": {
|
|
199
|
-
"description": "
|
|
199
|
+
"description": "字段名称;仅在当前作用域内唯一时可用于解析,同名命中多个字段时报 AMBIGUOUS_FIELD_SELECTOR,应改用 queId。",
|
|
200
200
|
"minLength": 1,
|
|
201
201
|
"type": "string"
|
|
202
202
|
},
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
"description": "普通字段的默认值;后端 queDefaultValue 为字符串,因此数字等字面量也必须以字符串传入。defaultType 为 1 时表示静态文本,为 3 时表示公式文本,为 2 时不携带字面量默认值。表格、地址、附件、引用等结构化默认值必须放在具体字段 config.defaultValues。显式 null 表示清除默认值。"
|
|
147
147
|
},
|
|
148
148
|
"name": {
|
|
149
|
-
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId
|
|
149
|
+
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId 时省略;同父级允许同名(后端以 queId 为字段身份),配置引用建议携带 queId。",
|
|
150
150
|
"minLength": 1,
|
|
151
151
|
"type": "string"
|
|
152
152
|
},
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"description": "普通字段的默认值;后端 queDefaultValue 为字符串,因此数字等字面量也必须以字符串传入。defaultType 为 1 时表示静态文本,为 3 时表示公式文本,为 2 时不携带字面量默认值。表格、地址、附件、引用等结构化默认值必须放在具体字段 config.defaultValues。显式 null 表示清除默认值。"
|
|
79
79
|
},
|
|
80
80
|
"name": {
|
|
81
|
-
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId
|
|
81
|
+
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId 时省略;同父级允许同名(后端以 queId 为字段身份),配置引用建议携带 queId。",
|
|
82
82
|
"minLength": 1,
|
|
83
83
|
"type": "string"
|
|
84
84
|
},
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"description": "普通字段的默认值;后端 queDefaultValue 为字符串,因此数字等字面量也必须以字符串传入。defaultType 为 1 时表示静态文本,为 3 时表示公式文本,为 2 时不携带字面量默认值。表格、地址、附件、引用等结构化默认值必须放在具体字段 config.defaultValues。显式 null 表示清除默认值。"
|
|
79
79
|
},
|
|
80
80
|
"name": {
|
|
81
|
-
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId
|
|
81
|
+
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId 时省略;同父级允许同名(后端以 queId 为字段身份),配置引用建议携带 queId。",
|
|
82
82
|
"minLength": 1,
|
|
83
83
|
"type": "string"
|
|
84
84
|
},
|