first commit
This commit is contained in:
@@ -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}")
|
||||
Reference in New Issue
Block a user