first commit

This commit is contained in:
2026-09-02 11:44:52 +08:00
commit 0c8fa2653e
309 changed files with 57278 additions and 0 deletions
@@ -0,0 +1,340 @@
"""
宜搭 schema 公共工具模块
提供 form / report / custom-page 三类 builder 共用的基础函数:
- next_node_id / generate_field_id — ID 生成
- i18n / build_yida_i18n — 国际化包装
- build_option_data_source — 选项 dataSource 构造 + 语义色彩
- build_components_map — componentsMap 构造
- FIELD_TYPE_ALIAS — 字段类型别名映射
"""
from __future__ import annotations
import random
import string
import time
from typing import Any, Optional
# ---------------------------------------------------------------------------
# 常量
# ---------------------------------------------------------------------------
COMPONENT_PACKAGE = "@ali/vc-deep-yida"
COMPONENT_VERSION = "1.5.169"
REPORT_COMPONENT_PACKAGE = "@/components/vc-yida-report"
REPORT_COMPONENT_VERSION = "1.0.6"
UTILS_LEGAO_BUILTIN = {
"name": "legaoBuiltin",
"type": "npm",
"content": {
"package": "@ali/vu-legao-builtin",
"version": "3.0.0",
"exportName": "legaoBuiltin",
},
}
UTILS_YIDA_PLUGIN = {
"name": "yidaPlugin",
"type": "npm",
"content": {
"package": "@ali/vu-yida-plugin",
"version": "1.0.13",
"exportName": "yidaPlugin",
},
}
# 字段类型别名 → 标准 componentName
FIELD_TYPE_ALIAS: dict[str, str] = {
"text": "TextField",
"textfield": "TextField",
"textarea": "TextareaField",
"textareafield": "TextareaField",
"number": "NumberField",
"numberfield": "NumberField",
"rate": "RateField",
"ratefield": "RateField",
"date": "DateField",
"datefield": "DateField",
"cascadedate": "CascadeDateField",
"cascadedatefield": "CascadeDateField",
"daterange": "CascadeDateField",
"radio": "RadioField",
"radiofield": "RadioField",
"select": "SelectField",
"selectfield": "SelectField",
"checkbox": "CheckboxField",
"checkboxfield": "CheckboxField",
"multiselect": "MultiSelectField",
"multiselectfield": "MultiSelectField",
"country": "CountrySelectField",
"countryselectfield": "CountrySelectField",
"address": "AddressField",
"addressfield": "AddressField",
"attachment": "AttachmentField",
"attachmentfield": "AttachmentField",
"image": "ImageField",
"imagefield": "ImageField",
"employee": "EmployeeField",
"employeefield": "EmployeeField",
"department": "DepartmentSelectField",
"departmentselectfield": "DepartmentSelectField",
"table": "TableField",
"tablefield": "TableField",
"association": "AssociationFormField",
"associationformfield": "AssociationFormField",
"serialnumber": "SerialNumberField",
"serialnumberfield": "SerialNumberField",
"divider": "Divider",
}
DATA_SOURCE_FIT_COMPILED = (
"'use strict';\n\nvar __preParser__ = function fit(response) {\n"
" var content = response.content !== undefined ? response.content : response;\n"
" var error = {\n"
" message: response.errorMsg || response.errors && response.errors[0] && response.errors[0].msg || response.content || '远程数据源请求出错,success is false'\n"
" };\n"
" var success = true;\n"
" if (response.success !== undefined) {\n"
" success = response.success;\n"
" } else if (response.hasError !== undefined) {\n"
" success = !response.hasError;\n"
" }\n"
" return {\n"
" content: content,\n"
" success: success,\n"
" error: error\n"
" };\n"
"};"
)
DATA_SOURCE_FIT_SOURCE = (
"function fit(response) {\r\n"
" const content = (response.content !== undefined) ? response.content : response;\r\n"
" const error = {\r\n"
" message: response.errorMsg ||\r\n"
" (response.errors && response.errors[0] && response.errors[0].msg) ||\r\n"
" response.content || '远程数据源请求出错,success is false',\r\n"
" };\r\n"
" let success = true;\r\n"
" if (response.success !== undefined) {\r\n"
" success = response.success;\r\n"
" } else if (response.hasError !== undefined) {\r\n"
" success = !response.hasError;\r\n"
" }\r\n"
" return {\r\n"
" content,\r\n"
" success,\r\n"
" error,\r\n"
" };\r\n"
"}"
)
SUPPORTED_FIELD_TYPES = {
"TextField", "TextareaField", "NumberField", "RateField",
"DateField", "CascadeDateField",
"RadioField", "SelectField", "CheckboxField", "MultiSelectField",
"CountrySelectField", "AddressField",
"AttachmentField", "ImageField",
"EmployeeField", "DepartmentSelectField",
"TableField", "AssociationFormField", "SerialNumberField",
"Divider",
}
OPTION_FIELD_TYPES = {"RadioField", "SelectField", "CheckboxField", "MultiSelectField"}
# ---------------------------------------------------------------------------
# ID 生成
# ---------------------------------------------------------------------------
_node_counter = 0
def _random_chars(n: int = 6) -> str:
return "".join(random.choices(string.ascii_letters + string.digits, k=n))
def unique_id(prefix: str = "", separator: str = "") -> str:
timestamp = str(int(time.time() * 1000000))[-6:]
random_part = _random_chars(6)
return f"{prefix}{separator}{timestamp}{random_part}"
def next_node_id() -> str:
global _node_counter
_node_counter += 1
ts = int(time.time() * 1000)
base36 = ""
n = ts
while n > 0:
n, r = divmod(n, 36)
base36 = "0123456789abcdefghijklmnopqrstuvwxyz"[r] + base36
return f"node_oc{base36}{_node_counter}"
def generate_field_id(component_name: str) -> str:
if not component_name:
return unique_id()
first_char_lower = component_name[0].lower()
rest_of_name = component_name[1:]
return f"{first_char_lower}{rest_of_name}_{unique_id()}"
# ---------------------------------------------------------------------------
# 国际化
# ---------------------------------------------------------------------------
def i18n(text: str, en_text: Optional[str] = None) -> dict[str, str]:
return {
"type": "i18n",
"zh_CN": text,
"en_US": en_text or text,
}
def build_yida_i18n(text: str, translations: Optional[dict[str, str]] = None) -> dict[str, str]:
result: dict[str, str] = {"type": "i18n", "zh_CN": text, "en_US": text}
if translations:
result.update(translations)
return result
# ---------------------------------------------------------------------------
# 语义色彩
# ---------------------------------------------------------------------------
_NEGATIVE_KEYWORDS = ['不通过', '拒绝', '失败', '错误', '', '不同意', '驳回', '取消', '删除', '禁止', '异常', '警告', '危险']
_POSITIVE_KEYWORDS = ['通过', '同意', '成功', '完成', '', '正常', '确认', '批准', '接受', '优秀', '合格']
_PROCESSING_KEYWORDS = ['处理中', '进行中', '待审核', '审核中', '处理', '待定', '等待']
_WARNING_KEYWORDS = ['注意', '提醒', '待办', '紧急', '重要']
_PAUSE_KEYWORDS = ['暂停', '挂起', '冻结', '停用']
COLOR_PALETTE = [
'#e0f0ff', '#e0f4e6', '#fff2e0', '#ffece6', '#eee9fe',
'#e0f2f2', '#fff7e0', '#fde5ec', '#f6e4ff', '#e8ebfc',
'#f6f6f7', '#bbddff', '#bbe7c8', '#ffe2bb', '#ffcfd8',
'#d9cefd', '#bbe3e3', '#ffeebb', '#fac4d4', '#ebc4ff',
'#cbd2f8', '#edeeef',
'#007fff', '#00a532', '#fd9100', '#f2510c', '#704af7',
'#009595', '#fdbd00', '#e9235d', '#b421fd', '#3954e4',
'#76787a', '#0058b1', '#007423', '#b16600', '#a62700',
'#4f34af', '#006868', '#b18500', '#a41841', '#7e17b1',
'#2a3d9f', '#181c1f',
]
def get_semantic_color(text: str) -> Optional[str]:
text_lower = text.lower().strip()
if any(k in text_lower for k in _NEGATIVE_KEYWORDS):
return '#FF4D4F'
if any(k in text_lower for k in _POSITIVE_KEYWORDS):
return '#52C41A'
if any(k in text_lower for k in _PROCESSING_KEYWORDS):
return '#1890FF'
if any(k in text_lower for k in _WARNING_KEYWORDS):
return '#FA8C16'
if any(k in text_lower for k in _PAUSE_KEYWORDS):
return '#8C8C8C'
return None
def build_option_data_source(
options: list[str],
*,
is_checkbox: bool = False,
used_colors: Optional[set[str]] = None,
) -> tuple[list[dict[str, Any]], set[str]]:
if used_colors is None:
used_colors = set()
color_idx = 0
data_source: list[dict[str, Any]] = []
for idx, opt_name in enumerate(options):
semantic = get_semantic_color(opt_name)
if semantic:
color = semantic
else:
while color_idx < len(COLOR_PALETTE) and COLOR_PALETTE[color_idx] in used_colors:
color_idx += 1
color = COLOR_PALETTE[color_idx % len(COLOR_PALETTE)]
color_idx += 1
used_colors.add(color)
data_source.append({
"text": i18n(opt_name),
"value": opt_name,
"defaultChecked": False if is_checkbox else (idx == 0),
"color": color,
})
return data_source, used_colors
# ---------------------------------------------------------------------------
# componentsMap
# ---------------------------------------------------------------------------
def build_components_map(component_names: list[str]) -> list[dict[str, str]]:
seen: set[str] = set()
result: list[dict[str, str]] = []
for name in component_names:
if name not in seen:
seen.add(name)
result.append({
"package": COMPONENT_PACKAGE,
"version": COMPONENT_VERSION,
"componentName": name,
})
return result
_REPORT_LOWCODE_COMPONENTS = {"YoushuSelect"}
def build_report_components_map(component_names: list[str]) -> list[dict[str, str]]:
seen: set[str] = set()
result: list[dict[str, str]] = []
for name in component_names:
if name not in seen:
seen.add(name)
if name in _REPORT_LOWCODE_COMPONENTS:
result.append({
"devMode": "lowcode",
"componentName": name,
})
else:
result.append({
"package": REPORT_COMPONENT_PACKAGE,
"version": REPORT_COMPONENT_VERSION,
"componentName": name,
})
return result
def collect_component_names(field_nodes: list[dict[str, Any]]) -> list[str]:
names: list[str] = []
def _walk(node: dict[str, Any]) -> None:
cn = node.get("componentName", "")
if cn:
names.append(cn)
for child in node.get("children", []):
_walk(child)
for node in field_nodes:
_walk(node)
return names
# ---------------------------------------------------------------------------
# 类型解析
# ---------------------------------------------------------------------------
def normalize_field_type(raw_type: str) -> str:
lower = raw_type.lower().replace("_", "").replace("-", "")
if lower in FIELD_TYPE_ALIAS:
return FIELD_TYPE_ALIAS[lower]
if raw_type in SUPPORTED_FIELD_TYPES:
return raw_type
raise ValueError(f"不支持的字段类型: {raw_type}")