openyida 2026.7.7 → 2026.7.8

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.
Files changed (63) hide show
  1. package/README.md +4 -2
  2. package/bin/yida.js +4 -4
  3. package/lib/app/canvas-compile.js +303 -0
  4. package/lib/app/publish.js +190 -39
  5. package/lib/auth/auth.js +11 -3
  6. package/lib/auth/login.js +34 -15
  7. package/lib/core/command-manifest.js +1 -1
  8. package/lib/core/env.js +19 -4
  9. package/lib/core/locales/ar.js +6 -2
  10. package/lib/core/locales/de.js +6 -2
  11. package/lib/core/locales/en.js +7 -3
  12. package/lib/core/locales/es.js +6 -2
  13. package/lib/core/locales/fr.js +6 -2
  14. package/lib/core/locales/hi.js +6 -2
  15. package/lib/core/locales/ja.js +7 -3
  16. package/lib/core/locales/ko.js +6 -2
  17. package/lib/core/locales/pt.js +6 -2
  18. package/lib/core/locales/vi.js +6 -2
  19. package/lib/core/locales/zh-HK.js +7 -3
  20. package/lib/core/locales/zh.js +7 -3
  21. package/lib/core/utils.js +195 -15
  22. package/lib/samples/yida-custom-page/custom-page-template.js +6 -6
  23. package/lib/samples/yida-custom-page/design-tokens.js +14 -12
  24. package/package.json +1 -1
  25. package/scripts/e2e-real/skill-coverage.js +4 -0
  26. package/scripts/validate-ci.sh +6 -0
  27. package/scripts/validate-package-size.js +12 -1
  28. package/yida-skills/SKILL.md +19 -5
  29. package/yida-skills/references/development-rules.md +2 -2
  30. package/yida-skills/references/field-and-url-reference.md +14 -1
  31. package/yida-skills/skills/yida-app/SKILL.md +3 -2
  32. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +113 -0
  33. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-authoring-examples.md +163 -0
  34. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-design-system.md +148 -0
  35. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +115 -0
  36. package/yida-skills/skills/yida-canvas-custom-page/references/dependencies-and-cdn.md +45 -0
  37. package/yida-skills/skills/yida-canvas-custom-page/references/employeefield-verification.md +68 -0
  38. package/yida-skills/skills/yida-canvas-upgrade/SKILL.md +99 -0
  39. package/yida-skills/skills/yida-canvas-upgrade/references/migration-examples.md +69 -0
  40. package/yida-skills/skills/yida-custom-page/SKILL.md +7 -2
  41. package/yida-skills/skills/yida-custom-page/examples/attachment-upload.js +1 -1
  42. package/yida-skills/skills/yida-custom-page/references/coding-guide.md +3 -3
  43. package/yida-skills/skills/yida-custom-page/references/component-jsx-guide.md +3 -2
  44. package/yida-skills/skills/yida-custom-page/references/design-system.md +30 -13
  45. package/yida-skills/skills/yida-nav-shell/SKILL.md +119 -0
  46. package/yida-skills/skills/yida-nav-shell/references/nav-shell-patterns.md +302 -0
  47. package/yida-skills/skills/yida-page-config/SKILL.md +1 -0
  48. package/yida-skills/skills/yida-page-uiux/SKILL.md +68 -0
  49. package/yida-skills/skills/yida-page-uiux/references/scenes/dashboard.md +128 -0
  50. package/yida-skills/skills/yida-page-uiux/references/scenes/detail.md +79 -0
  51. package/yida-skills/skills/yida-page-uiux/references/scenes/landing.md +101 -0
  52. package/yida-skills/skills/yida-page-uiux/references/scenes/list.md +78 -0
  53. package/yida-skills/skills/yida-page-uiux/references/scenes/workbench.md +74 -0
  54. package/yida-skills/skills/yida-page-uiux/references/visual-decision-engine.md +151 -0
  55. package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +36 -0
  56. package/yida-skills/skills/yida-page-uiux/workflow/step-0-nav-shape.md +39 -0
  57. package/yida-skills/skills/yida-page-uiux/workflow/step-1-page-type.md +25 -0
  58. package/yida-skills/skills/yida-page-uiux/workflow/step-2-intent-decode.md +25 -0
  59. package/yida-skills/skills/yida-page-uiux/workflow/step-3-scene-routing.md +29 -0
  60. package/yida-skills/skills/yida-page-uiux/workflow/step-4-visual-decision.md +32 -0
  61. package/yida-skills/skills/yida-page-uiux/workflow/step-5-icon-and-assets.md +36 -0
  62. package/yida-skills/skills/yida-page-uiux/workflow/step-6-deai-selfcheck.md +37 -0
  63. package/yida-skills/skills/yida-publish-page/SKILL.md +7 -3
@@ -54,7 +54,8 @@ description: 宜搭完整应用开发技能。从零到一搭建完整宜搭应
54
54
  [Step 5](按需,需求含「审批」「流程」「申请」「审核」「工单」等关键词时必须执行)
55
55
  配置流程 → 读取 skills/yida-create-process/SKILL.md → openyida create-process / configure-process
56
56
 
57
- [Step 6] 编写自定义页面代码 → yida-custom-page 规范 → pages/src/<项目名>.oyd.jsx
57
+ [Step 6] 编写自定义页面代码 → yida-page-uiux 定视觉方向,再 yida-custom-page 落地 → pages/src/<项目名>.oyd.jsx
58
+ ↓ (首次生成必做:先用 yida-page-uiux 产出「视觉方向决策块」,避免统一灰白圆角的 AI 味模板脸)
58
59
  ↓ openyida check-page / compile 预检(.oyd.jsx 自动兼容构建)
59
60
  ↓ (列表/表格类页面:参考 yida-density 技能选择合适的信息密度)
60
61
 
@@ -69,7 +70,7 @@ description: 宜搭完整应用开发技能。从零到一搭建完整宜搭应
69
70
  [Step 10] 输出访问链接,用系统浏览器打开
70
71
  ```
71
72
 
72
- ### 编写自定义页面代码前必须完整学习 `skills/yida-custom-page/SKILL.md`
73
+ ### 编写自定义页面代码前先读 `skills/yida-page-uiux/SKILL.md` 定视觉方向,再完整学习 `skills/yida-custom-page/SKILL.md`
73
74
 
74
75
  ### 生成表单 schema 前必须完整学习 `skills/yida-create-form-page/SKILL.md`
75
76
 
@@ -0,0 +1,113 @@
1
+ ---
2
+ name: yida-canvas-custom-page
3
+ description: 宜搭 Code Canvas / 代码画布自定义页面开发规范,是**自定义页面的默认链路**——现代 React 交互、hooks 状态、可视化、AI 生成或需要崩溃隔离的页面都用它(真 React18 + `runtimeCode` + `importedModules`)。只需通过开放 API(HTTP fetch)读写宜搭数据的页面也用它——Canvas 无 this 数据桥,在 YidaComp 内自写 HTTP 桥即可。也覆盖用户明确提到 code canvas、代码画布、YidaCodeCanvas、runtimeCode、importedModules,或在 Code Canvas 中使用 EmployeeField、SelectField、DepartmentSelectField 等原生组件的场景。仅当页面强依赖原生实例数据桥(this.$(fieldId) 双向绑定、this.utils.yida.*、dataSourceMap、提交流程深度耦合)时才回退 yida-custom-page。
4
+ ---
5
+
6
+ # 宜搭 Code Canvas 自定义页面开发
7
+
8
+ ## 核心定位
9
+
10
+ Code Canvas 是宜搭的**代码画布自定义页面链路**:以 `YidaCodeCanvas` 物料为承载,用户写标准 React18 函数组件源码,编译为 `runtimeCode` + 依赖清单(OpenYida CLI 本地用 Babel 完成),运行时按依赖白名单加载资源并 `new Function` 执行,渲染出真正的 React 组件。相较宜搭原生 `.oyd.jsx` 页面,它提供现代 hooks 写法、组件级崩溃隔离(ErrorBoundary)、Tailwind 运行时样式,适合现代交互 / 可视化 / AI 生成页面。
11
+
12
+ 运行链路(源码核实自 `vc-deep-yida` 物料):
13
+
14
+ 1. 编译阶段把用户 React 源码转译为 `runtimeCode` + 依赖清单 `importedModules`(OpenYida CLI **本地用 Babel 完成**:JSX/TS → ES5、`import` 改写成 `window.<别名>` 引用、`export default` 收敛为 `YidaComp`,依赖名正则抽取)。
15
+ 2. `YidaCodeCanvas` 运行时按依赖别名表把 `importedModules` 命中的白名单依赖加载 CDN 资源到 `window[windowAlias]`。
16
+ 3. `runtimeCode` 在宿主页真实 `window` 中 `new Function` 执行,返回 `YidaComp` / `YidaComp.default` / 组件函数并渲染。
17
+
18
+ 本技能处理从零编写 Code Canvas 页面。宜搭原生 `.oyd.jsx` 页面的 `export function renderJsx()`、`_customState`、`this.utils.yida.*`、`openyida publish` 规范由 `yida-custom-page` 负责;把已有原生页面迁移到 Code Canvas 链路由 `yida-canvas-upgrade` 负责。
19
+
20
+ ## 先判断能不能用 Code Canvas
21
+
22
+ | 证据 | 判断 |
23
+ | --- | --- |
24
+ | 现代 React 交互 / hooks 状态 / 可视化 / AI 生成 / 需崩溃隔离的自定义页 | **默认使用本技能** |
25
+ | 页面 Schema 或组件树里有 `componentName: "YidaCodeCanvas"` | 使用本技能 |
26
+ | 需求明确是“代码画布 / Code Canvas / runtimeCode / importedModules” | 使用本技能 |
27
+ | 需求明确是“从 OpenYida 原链路升级/迁移到 Canvas” | 切到 `yida-canvas-upgrade` |
28
+ | 只需通过开放 API(HTTP)读写宜搭数据的页面 | **仍用本技能**:在 `YidaComp` 内自写 fetch 调开放 API / 连接器代理(Canvas 无 `this` 桥,需自建 HTTP 数据桥) |
29
+ | 强依赖原生实例数据桥:表单内字段双向绑定 `this.$(fieldId)`、`this.utils.yida.*`、`dataSourceMap`、提交流程深度耦合 | 回退 `yida-custom-page`(该实例桥仅 native 免费提供) |
30
+ | 需要字段结构、公式、联动、权限、报表、流程 | 切到对应配置型技能,不用 Code Canvas 承载 |
31
+
32
+ OpenYida CLI 已支持发布 `YidaCodeCanvas` 页面:把 Code Canvas 源码写成 `.canvas.jsx`(推荐 `project/pages/src/<页面名>.canvas.jsx`),执行 `openyida publish <源文件> <appType> <formUuid>` 即自动走 Canvas 链路——`.canvas.jsx` 扩展名会被识别,CLI **本地用 Babel 编译**出 `runtimeCode` + `importedModules`(无需在线编译服务、无需登录态即可完成编译),构造 `YidaCodeCanvas` 组件的 Schema 并保存。扩展名不规范但确为 Canvas 源码时,加 `--canvas` 显式指定。**不要**把 Canvas 源码用普通 `.oyd.jsx`/`.jsx` 走 native 链路发布(会被 Babel 兼容编译器当 `renderJsx` 处理而失败)。
33
+
34
+ ## 运行时事实(速记)
35
+
36
+ - `runtimeCode` 在宿主页真实 `window` 中 `new Function` 执行(`fn(window, window)`),执行后必须返回 `YidaComp` / `YidaComp.default` / 组件函数。
37
+ - 物料只透传 `code / runtimeCode / importedModules / pageType`,无 `this` 上下文、无 `dataSourceMap` 数据桥;`this.utils.yida.*`、`didMount()` 等普通页契约都不可用。
38
+ - 宿主 window 全局不是数据桥:`__yida_plugin_runtime__` 是插件扩展点管理器、`__VcDeepYidaUtils__` 只是 `{ VuMicroUtils }`,也无 `window.Deep` 字段组件。不要指望从 window 直接拿表单数据。
39
+ - **读宜搭数据 = 自建 HTTP 桥**:在 `YidaComp` 内 `fetch` 调宜搭开放 API 或已配置连接器代理(同源带 cookie),自行处理 appType / formUuid / 鉴权;这是 Canvas 唯一干净的数据读写路径。
40
+ - 依赖走白名单(React、ReactDOM、antd、ahooks、d3、recharts、Radix、lucide-react、framer-motion…);新增依赖须被 CLI 编译识别(能抽入 `importedModules`)、有 window alias 映射、CSS 可加载。白名单无 yida-utils / `@ali/deep` / 原生字段组件,不能 `import`。
41
+ - `EmployeeField` 等宜搭原生字段组件不能因表单支持同名字段就默认可用,必须先做最小验证;缺证据时降级为 antd/自定义控件,只存已知 userId / unionId。
42
+
43
+ > 📖 运行时事实、原生组件判断、EmployeeField JSX 示例与验收清单 → [references/employeefield-verification.md](references/employeefield-verification.md)
44
+ > 📦 依赖白名单、windowAlias / CDN、antd/dayjs `is not defined` 根因与物料侧修复 → [references/dependencies-and-cdn.md](references/dependencies-and-cdn.md)
45
+ > 🔌 在 `YidaComp` 内自写 HTTP 桥读写宜搭数据(连接器代理 / 同源 fetch / 可复用 hook)→ [references/data-bridge-guide.md](references/data-bridge-guide.md)
46
+ > 🎨 主色跟随 App 品牌:antd `ConfigProvider.colorPrimary` / Tailwind CSS 变量 / 图表配色的落地写法 → [references/canvas-design-system.md](references/canvas-design-system.md)
47
+
48
+ ## 视觉方向:先定方向,再写代码
49
+
50
+ 写页面前先定视觉方向,避免首次就生成「默认蓝 + 大圆角 + emoji」的 AI 味套版。视觉决策是**栈无关**的,与 native 链路共用决策层技能 `yida-page-uiux`:读它的 `SKILL.md` 按页面类型(工作台/仪表盘/列表/详情)做 5 维差异化、走去 AI 味清单、严禁 emoji。
51
+
52
+ Canvas 这套栈(React18 + antd + Tailwind)怎么把「主色跟随 App 品牌」落地由 [references/canvas-design-system.md](references/canvas-design-system.md) 负责。一句话记忆:**CSS 变量 `var(--color-brand1-*)` 对普通 DOM / Tailwind 直接可用(Canvas 跑在真 window、节点在宿主树内会级联);antd token 和图表颜色是 JS 消费,需用 `getComputedStyle` 把品牌色解析成真实色值再喂进去。** 语义色(成功/警告/错误)保持固定,不随主题变。
53
+
54
+ ## 编码规则
55
+
56
+ 1. **不要写普通页面契约**:不要写 `export function renderJsx()`、`didMount()`、`this.forceUpdate()`、`this.utils.yida.*`,除非已验证 Code Canvas props 中提供等价能力。
57
+ 2. **入口必须明确**:源码必须导出或返回 `YidaComp`;不要只定义局部组件却不导出。
58
+ 3. **依赖必须可追踪**:所有 import 都要能出现在编译结果 `dependencies` 中;不在白名单的依赖不要交付。
59
+ 4. **状态按 React 组件写法处理**:Code Canvas 组件用标准 React hooks 写法(本地 Babel 编译支持);不要套用普通自定义页 `_customState` 模式。
60
+ 5. **副作用要清理**:使用 `useEffect` 注册事件、定时器、图表实例时必须返回 cleanup。
61
+ 6. **不要绕过数据源治理**:调用连接器或远程 API 时,优先通过平台已配置数据源或传入 props;不要在组件中硬编码外部 URL、Cookie、CSRF。
62
+ 7. **先验收依赖,再扩展业务**:原生组件、上传、组织搜索、弹层类能力必须先做最小验证页,确认可用后再写复杂业务页面。
63
+ 8. **主色跟随 App 品牌,勿硬编码蓝**:antd 页面在最外层包 `ConfigProvider`,`token.colorPrimary` 用 `getComputedStyle` 读平台 `--color-brand1-6` 解析出的真实色值;Tailwind / 普通 DOM 直接用 `var(--color-brand1-*)`;图表颜色同样读品牌色。语义色(成功/警告/错误)保持固定。禁止散落 `#1677ff` / `bg-blue-500` 等硬编码蓝。详见 [references/canvas-design-system.md](references/canvas-design-system.md)。
64
+
65
+ > 📖 最小 `YidaComp`、副作用清理、recharts 图表、数据拉取组件等 vetted 脚手架模板 → [references/canvas-authoring-examples.md](references/canvas-authoring-examples.md)
66
+
67
+ ## 开发流程
68
+
69
+ ```bash
70
+ # 1. 只读检查环境和登录态;真实创建资源前必须通过
71
+ openyida env --json
72
+ openyida login --check-only --json
73
+
74
+ # 2. 如需新页面,先创建空白自定义页拿 formUuid
75
+ openyida create-page <appType> "<页面名>"
76
+
77
+ # 3. 编写 Code Canvas 源码,写成 .canvas.jsx(推荐 project/pages/src/<页面名>.canvas.jsx)
78
+ # 只用白名单依赖;入口导出/返回 YidaComp;副作用带 cleanup。
79
+
80
+ # 4. 本地预检:不要用 openyida check-page/compile 校验 Canvas,它们当前是 native 页面检查器。
81
+ # Canvas 预检以 publish 的 Code Canvas 编译阶段为准;需要离线快检时可调用 lib/app/canvas-compile。
82
+ node -e "const fs=require('fs'); const {compileCanvasLocal}=require('./lib/app/canvas-compile'); const src=fs.readFileSync('project/pages/src/<页面名>.canvas.jsx','utf8'); const out=compileCanvasLocal(src); console.log(out.importedModules)"
83
+
84
+ # 5. 发布:.canvas.jsx 自动走 Canvas 链路,CLI 本地用 Babel 编译出
85
+ # runtimeCode + importedModules,构造 YidaCodeCanvas Schema 保存。
86
+ openyida publish project/pages/src/<页面名>.canvas.jsx <appType> <formUuid>
87
+ # 扩展名不规范但确为 Canvas 源码时,显式加 --canvas。
88
+
89
+ # 6. 发布后回读 Schema 验收:确认组件树存在 YidaCodeCanvas,且 runtimeCode 非空
90
+ openyida get-schema <appType> <formUuid> > .cache/openyida/<页面名>-schema.json
91
+ ```
92
+
93
+ ## 与其他技能分工
94
+
95
+ | 需求 | 使用 |
96
+ | --- | --- |
97
+ | 普通 `.oyd.jsx` 自定义页 | `yida-custom-page` |
98
+ | 发布 Canvas 页(`.canvas.jsx` → `openyida publish` 自动走 Canvas 链路) | 本技能开发流程 step 4 |
99
+ | 编译并发布 native `.oyd.jsx`/`.jsx` 页面 | `yida-publish-page` |
100
+ | 创建空白自定义页面 | `yida-create-page` |
101
+ | 通过数据源调用连接器 | `yida-data-source-connectors` |
102
+ | 创建或管理 HTTP 连接器 | `yida-connector` |
103
+ | 配表单字段结构,如真正的 EmployeeField 字段 | `yida-create-form-page` |
104
+
105
+ ## 常见误区
106
+
107
+ | 误区 | 正确处理 |
108
+ | --- | --- |
109
+ | 看到表单支持 `EmployeeField`,就在 Code Canvas 写 `<EmployeeField />` | 先验证依赖映射、CSS、上下文和 `onChange` 结构 |
110
+ | 把 Code Canvas 源码用 `.oyd.jsx`/`.jsx` 发布 | native 链路会走 Babel 兼容编译器当 `renderJsx` 处理而失败;Canvas 源码要写成 `.canvas.jsx`(或加 `--canvas`),`openyida publish` 会自动走 Canvas 链路生成 `YidaCodeCanvas` Schema |
111
+ | 在 Code Canvas 中照搬 `this.utils.yida.searchFormDatas` | Code Canvas 组件没有普通页面实例契约;先看 props 或数据源注入方式 |
112
+ | 依赖加载失败后继续写业务 | 先补依赖 alias/CDN 或降级到已支持组件 |
113
+ | 用 `openyida check-page` / `openyida compile` 校验 `.canvas.jsx` | 这两个命令当前面向 native `.oyd.jsx`,会误报 Hook / default export 限制;Canvas 用 `compileCanvasLocal` 离线快检,最终以 `openyida publish .canvas.jsx` 的 Code Canvas 编译阶段为准 |
@@ -0,0 +1,163 @@
1
+ # Code Canvas 编写示例 / 脚手架
2
+
3
+ 本文件是从零写 Code Canvas 页面的 vetted 模板集。所有示例都遵守运行时事实:`YidaComp` 是普通 React18 函数组件,必须**导出或返回** `YidaComp` / `YidaComp.default`;只 `import` 依赖白名单内的包;副作用在 `useEffect` 里注册并返回 cleanup。
4
+
5
+ > 白名单、windowAlias、CDN 与 antd/dayjs 陷阱见 [dependencies-and-cdn.md](dependencies-and-cdn.md);读写宜搭数据见 [data-bridge-guide.md](data-bridge-guide.md)。
6
+
7
+ ## 1. 最小可运行组件(入口 + 本地状态)
8
+
9
+ 最小骨架:明确入口导出、用 `useState` 管本地状态,不写任何普通页面契约(无 `renderJsx` / `didMount` / `this.*`)。
10
+
11
+ ```jsx
12
+ import React, { useState } from 'react';
13
+
14
+ function YidaComp(props) {
15
+ var s = React.useState(0);
16
+ var count = s[0];
17
+ var setCount = s[1];
18
+
19
+ return (
20
+ <div style={{ padding: 16 }}>
21
+ <p>当前计数:{count}</p>
22
+ <button onClick={function () { setCount(count + 1); }}>加一</button>
23
+ </div>
24
+ );
25
+ }
26
+
27
+ export default YidaComp;
28
+ ```
29
+
30
+ ## 2. 带副作用清理(定时器 / 事件监听)
31
+
32
+ `useEffect` 注册的定时器、事件、图表实例,**必须**在返回的 cleanup 里销毁,否则重渲染 / 卸载会泄漏。
33
+
34
+ ```jsx
35
+ import React, { useEffect, useState } from 'react';
36
+
37
+ function YidaComp(props) {
38
+ var t = React.useState(0);
39
+ var seconds = t[0];
40
+ var setSeconds = t[1];
41
+
42
+ React.useEffect(function () {
43
+ var timer = setInterval(function () {
44
+ setSeconds(function (prev) { return prev + 1; });
45
+ }, 1000);
46
+ function onResize() { /* 响应窗口变化 */ }
47
+ window.addEventListener('resize', onResize);
48
+
49
+ return function cleanup() {
50
+ clearInterval(timer);
51
+ window.removeEventListener('resize', onResize);
52
+ };
53
+ }, []);
54
+
55
+ return <div style={{ padding: 16 }}>已运行 {seconds}s</div>;
56
+ }
57
+
58
+ export default YidaComp;
59
+ ```
60
+
61
+ ## 3. 可视化:recharts 图表
62
+
63
+ `recharts` 在白名单内(windowAlias `Recharts`)。标准 `import` 即可,CLI 本地编译会把它计入 `importedModules`。图表容器给定高度,避免 0 高度不渲染。图表颜色是 JS 传给库的字符串,**不能硬编码蓝**——用 `readBrandColor` 读平台品牌色,让线条跟随 App 主题(见 [canvas-design-system.md](canvas-design-system.md))。
64
+
65
+ ```jsx
66
+ import React from 'react';
67
+ import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
68
+
69
+ // 读平台品牌色(跑在真 window,getComputedStyle 可直接解析),缺失时退默认蓝
70
+ function readBrandColor(level, fallback) {
71
+ try {
72
+ var v = getComputedStyle(document.documentElement)
73
+ .getPropertyValue('--color-brand1-' + (level || 6)).trim();
74
+ return v || fallback;
75
+ } catch (e) { return fallback; }
76
+ }
77
+
78
+ function YidaComp(props) {
79
+ var brand = readBrandColor(6, '#1677ff');
80
+ var data = [
81
+ { name: '1月', value: 120 },
82
+ { name: '2月', value: 200 },
83
+ { name: '3月', value: 150 },
84
+ { name: '4月', value: 320 },
85
+ ];
86
+
87
+ return (
88
+ <div style={{ width: '100%', height: 300, padding: 16 }}>
89
+ <ResponsiveContainer width="100%" height="100%">
90
+ <LineChart data={data}>
91
+ <XAxis dataKey="name" />
92
+ <YAxis />
93
+ <Tooltip />
94
+ <Line type="monotone" dataKey="value" stroke={brand} />
95
+ </LineChart>
96
+ </ResponsiveContainer>
97
+ </div>
98
+ );
99
+ }
100
+
101
+ export default YidaComp;
102
+ ```
103
+
104
+ ## 4. 数据拉取组件(接数据桥)
105
+
106
+ 结合 [data-bridge-guide.md](data-bridge-guide.md) 的 `useYidaFetch`:同源 `fetch` + `credentials: 'include'` + AbortController 清理。此处只演示消费侧结构。
107
+
108
+ ```jsx
109
+ import React, { useEffect, useRef, useState } from 'react';
110
+
111
+ function YidaComp(props) {
112
+ var st = React.useState({ loading: true, rows: [], error: null });
113
+ var state = st[0];
114
+ var setState = st[1];
115
+ var abortRef = React.useRef(null);
116
+
117
+ React.useEffect(function () {
118
+ var controller = new AbortController();
119
+ abortRef.current = controller;
120
+
121
+ fetch('/your-connector-proxy/searchFormDatas', {
122
+ method: 'POST',
123
+ credentials: 'include',
124
+ headers: { 'Content-Type': 'application/json' },
125
+ body: JSON.stringify({ appType: props.appType, formUuid: props.formUuid, pageSize: 20 }),
126
+ signal: controller.signal,
127
+ })
128
+ .then(function (r) { if (!r.ok) { throw new Error('HTTP ' + r.status); } return r.json(); })
129
+ .then(function (json) {
130
+ var rows = (json && json.result && json.result.data) || [];
131
+ setState({ loading: false, rows: rows, error: null });
132
+ })
133
+ .catch(function (e) {
134
+ if (e.name === 'AbortError') { return; }
135
+ setState({ loading: false, rows: [], error: e.message });
136
+ });
137
+
138
+ return function () { controller.abort(); };
139
+ }, [props.appType, props.formUuid]);
140
+
141
+ if (state.loading) { return <div>加载中…</div>; }
142
+ if (state.error) { return <div style={{ color: 'red' }}>加载失败:{state.error}</div>; }
143
+
144
+ return (
145
+ <ul style={{ padding: 16 }}>
146
+ {state.rows.map(function (row) {
147
+ return <li key={row.formInstanceId}>{row.title}</li>;
148
+ })}
149
+ </ul>
150
+ );
151
+ }
152
+
153
+ export default YidaComp;
154
+ ```
155
+
156
+ ## 通用自查清单
157
+
158
+ - 入口:源码有 `export default YidaComp`(或返回组件函数),不是只定义局部组件。
159
+ - 依赖:所有 `import` 都在白名单内,能出现在编译结果 `dependencies` 里。
160
+ - 副作用:每个 `useEffect` 的定时器 / 监听 / 图表实例都有 cleanup。
161
+ - 数据:读写走同源 `fetch` + `credentials: 'include'`,无硬编码 Cookie / CSRF / appSecret。
162
+ - 主色:antd 走 `ConfigProvider.colorPrimary`、Tailwind 走 `var(--color-brand1-*)`、图表走 `readBrandColor`,无散落的 `#1677ff` / `bg-blue-500`(见 [canvas-design-system.md](canvas-design-system.md))。
163
+ - 原生字段组件(`EmployeeField` 等):先按 [employeefield-verification.md](employeefield-verification.md) 最小验证,缺证据就降级。
@@ -0,0 +1,148 @@
1
+ # Code Canvas 主色对齐与视觉落地
2
+
3
+ 本文件是 Code Canvas 页面的**实现层**引导:怎么让首次生成的页面跟随宿主 App 的品牌主题色,而不是永远一片 antd 默认蓝。视觉方向怎么定(页面类型、差异化、去 AI 味)是**栈无关**的,走共用的决策层技能 `yida-page-uiux`,本文件只讲 Canvas(React18 + antd + Tailwind)这套栈怎么把主色落地。
4
+
5
+ > 决策层:读 `yida-page-uiux/SKILL.md` 定视觉方向(先做 Step 0 导航形态判定,再定工作台/仪表盘/列表/详情、5 维差异化、去 AI 味、禁 emoji)。
6
+ > 实现层:本文件负责把「主色跟随 App 品牌」落到 antd token / Tailwind / 图表。
7
+
8
+ > **前提是导航可见**:跟随品牌主色是为了跟应用框架融合。若页面隐藏了应用导航(`isRenderNav=false`,沉浸/独立/门户/大屏,由 `yida-page-uiux` Step 0 判定),主色相可自立、不必严格跟品牌——此时把下文 `readBrandColor` 的取值换成自定的主色即可(antd `colorPrimary` / 图表色都改喂自定色),**语义色仍固定、去 AI 味红线仍生效**。拿不准就按「跟随品牌」这个更安全的默认走。
9
+
10
+ ## 核心事实:CSS 变量能穿透,antd token 不能
11
+
12
+ Canvas 的 `runtimeCode` 在**宿主页真实 `window`** 里 `new Function` 执行(见 SKILL.md「运行时事实」),组件挂在宿主 DOM 树内。由此得到主色落地的分界:
13
+
14
+ | 消费方 | 品牌色怎么给 | 原因 |
15
+ | --- | --- | --- |
16
+ | 普通 DOM / Tailwind 元素(`style` / `className`) | **直接用 CSS 变量** `var(--color-brand1-6)` | CSS 变量沿 DOM 树级联,Canvas 节点在宿主树内,能读到平台注入的 `--color-brand1-*` |
17
+ | antd 组件(Button / Table / Tabs…) | **JS 解析成真实色值**喂 `ConfigProvider.theme.token.colorPrimary` | antd 的色板(hover/active/disabled)由 JS 算法从一个真实颜色推导,`var(...)` 是字符串塞不进算法 |
18
+ | JS 消费的颜色:recharts `stroke`/`fill`、canvas 绘制、图表配色数组 | **JS 解析成真实色值** | 传给库的是运行时字符串,不走 CSS 级联 |
19
+
20
+ 所以只有「JS 要拿到真实颜色」的场景才需要读值,其余直接用 CSS 变量最省事。
21
+
22
+ ## 读品牌色的 helper(JS 消费场景用)
23
+
24
+ 因为跑在真 window,直接读根节点计算样式即可。带兜底,读不到时退平台默认蓝。
25
+
26
+ ```jsx
27
+ // 品牌色阶:1 最浅 → 6 主色 → 10 最深,与平台 --color-brand1-* 对齐
28
+ function readBrandColor(level, fallback) {
29
+ try {
30
+ var el = document.documentElement;
31
+ var v = getComputedStyle(el).getPropertyValue('--color-brand1-' + (level || 6)).trim();
32
+ return v || fallback;
33
+ } catch (e) {
34
+ return fallback;
35
+ }
36
+ }
37
+
38
+ // hook 形式:首帧同步取值,无闪烁
39
+ function useBrandColor(level, fallback) {
40
+ var s = React.useState(function () { return readBrandColor(level, fallback); });
41
+ return s[0];
42
+ }
43
+ ```
44
+
45
+ > ⚠️ **变量作用域**:若平台把 `--color-brand1-*` 定义在某个容器而非 `:root`,`document.documentElement` 可能读到空串 → 命中 fallback。更稳的做法是给组件根节点挂 `ref`,在 `useEffect` 里读 `getComputedStyle(rootRef.current)`(组件节点一定在变量作用域内),读到后 `setState` 触发一次重渲染。先用 `documentElement` 同步取,空串再降级到 ref 方案即可。
46
+
47
+ ## antd:ConfigProvider 注入 colorPrimary
48
+
49
+ 用 `readBrandColor` 取主色,交给 `ConfigProvider`,antd 会自动推导 hover/active/disabled 整套色板。语义色(success/warning/error)用 antd 默认,不覆盖,保证语义稳定。
50
+
51
+ ```jsx
52
+ import React from 'react';
53
+ import { ConfigProvider, Button, Table } from 'antd';
54
+
55
+ function readBrandColor(level, fallback) {
56
+ try {
57
+ var v = getComputedStyle(document.documentElement)
58
+ .getPropertyValue('--color-brand1-' + (level || 6)).trim();
59
+ return v || fallback;
60
+ } catch (e) { return fallback; }
61
+ }
62
+
63
+ function YidaComp(props) {
64
+ var colorPrimary = readBrandColor(6, '#1677ff'); // 缺失时退平台默认蓝
65
+ return (
66
+ <ConfigProvider
67
+ theme={{
68
+ token: {
69
+ colorPrimary: colorPrimary, // 主色跟随 App 品牌
70
+ borderRadius: 8, // 圆角等非主色 token 可按视觉方向调
71
+ },
72
+ // 不覆盖 colorSuccess/colorWarning/colorError,语义色保持固定
73
+ }}
74
+ >
75
+ <div style={{ padding: 16 }}>
76
+ <Button type="primary">主操作</Button>
77
+ </div>
78
+ </ConfigProvider>
79
+ );
80
+ }
81
+
82
+ export default YidaComp;
83
+ ```
84
+
85
+ **要点**:`ConfigProvider` 包在组件最外层,页面内所有 antd 组件才统一吃到品牌色。只设 `colorPrimary` 一个入口,不要逐组件手写颜色。
86
+
87
+ ## Tailwind:CSS 变量直接用
88
+
89
+ Canvas 节点在宿主树内,Tailwind 运行时对普通元素直接用 arbitrary value 引用 CSS 变量即可,**不需要 JS**:
90
+
91
+ ```jsx
92
+ // 主色文字 / 背景 / 边框,直接引平台变量,跟随 App 主题
93
+ <div className="text-[var(--color-brand1-6)] border border-[var(--color-brand1-3)]">…</div>
94
+ <button className="bg-[var(--color-brand1-6)] hover:bg-[var(--color-brand1-5)] text-white rounded-lg px-4 py-2">
95
+ 主操作
96
+ </button>
97
+ ```
98
+
99
+ 色阶对应(与 native `design-system.md` 一致):主色 `brand1-6`、填充按钮 hover 亮一档 `brand1-5`、按下深一档 `brand1-7`、通用浅色 hover 底 `brand1-1`、选中/标签浅底 `brand1-2`。
100
+
101
+ ## 图表 / recharts:用解析后的品牌色
102
+
103
+ 图表颜色是 JS 传给库的字符串,必须用 `readBrandColor`,不能硬编码 `#1677ff`。
104
+
105
+ ```jsx
106
+ import React from 'react';
107
+ import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
108
+
109
+ function readBrandColor(level, fallback) {
110
+ try {
111
+ var v = getComputedStyle(document.documentElement)
112
+ .getPropertyValue('--color-brand1-' + (level || 6)).trim();
113
+ return v || fallback;
114
+ } catch (e) { return fallback; }
115
+ }
116
+
117
+ function YidaComp(props) {
118
+ var brand = readBrandColor(6, '#1677ff');
119
+ var data = [
120
+ { name: '1月', value: 120 }, { name: '2月', value: 200 },
121
+ { name: '3月', value: 150 }, { name: '4月', value: 320 },
122
+ ];
123
+ return (
124
+ <div style={{ width: '100%', height: 300, padding: 16 }}>
125
+ <ResponsiveContainer width="100%" height="100%">
126
+ <LineChart data={data}>
127
+ <XAxis dataKey="name" />
128
+ <YAxis />
129
+ <Tooltip />
130
+ <Line type="monotone" dataKey="value" stroke={brand} />
131
+ </LineChart>
132
+ </ResponsiveContainer>
133
+ </div>
134
+ );
135
+ }
136
+
137
+ export default YidaComp;
138
+ ```
139
+
140
+ 多色系列需要区分时,用品牌色 + 中性色/语义色,不要整排饱和撞色(见 `yida-page-uiux` 去 AI 味清单)。
141
+
142
+ ## 自查清单(主色相关)
143
+
144
+ - 页面最外层有 `ConfigProvider` 且 `token.colorPrimary` 来自 `readBrandColor`,不是硬编码色值。
145
+ - Tailwind 主色类用 `var(--color-brand1-*)`,没有散落的 `#1677ff` / `bg-blue-500`。
146
+ - 图表 / canvas 绘制颜色走 `readBrandColor`,无硬编码蓝。
147
+ - 语义色(成功/警告/错误)保持 antd 默认或平台语义变量,未被主色覆盖。
148
+ - 视觉方向已按 `yida-page-uiux` 决策:不是默认蓝 + 大圆角 + emoji 的 AI 味套版。
@@ -0,0 +1,115 @@
1
+ # Code Canvas 数据桥指南(自写 HTTP 读写宜搭数据)
2
+
3
+ Code Canvas 运行时**没有** `this.utils.yida.*` / `dataSourceMap` / `this.$(fieldId)` 实例数据桥(核实自 `factory.tsx`:物料只透传 `code / runtimeCode / importedModules / pageType`,`YidaComp` 是普通函数组件,wrapper 只注入 `window`)。因此 `YidaComp` 要读写宜搭数据,只能**自己补一座 HTTP 桥**。本文件给出干净、可复用、合规的写法。
4
+
5
+ ## 三条数据路径,先选对
6
+
7
+ | 路径 | 是否可在浏览器(Canvas)直接调 | 说明 |
8
+ | --- | --- | --- |
9
+ | 宜搭开放 API(OpenAPI,`appKey`/`appSecret` 签名) | **不可** | 需服务端签名;在浏览器里必然泄露 secret。只能由后端 / 连接器代理调,Canvas 不直连。 |
10
+ | 平台已配置**连接器**(HTTP 连接器暴露的同源代理端点) | **推荐** | 同源 `fetch(url, { credentials: 'include' })` 带 cookie 即可,鉴权与密钥留在平台侧,符合数据源治理。 |
11
+ | 内部表单数据端点(同源、依赖登录 cookie + CSRF) | 可,但要谨慎 | 与 native `this.utils.yida.searchFormDatas` 命中的是同类端点;需自行带 CSRF token,端点随环境可能变化,不要硬编码跨域绝对地址。 |
12
+
13
+ 选路原则:**优先走连接器代理**(编码规则 #6「不要绕过数据源治理」)。真需要直连内部端点时,只用**同源相对路径** + `credentials: 'include'`,绝不在源码里硬编码 Cookie / CSRF / appSecret。
14
+
15
+ ## 可复用读数据 Hook
16
+
17
+ ```jsx
18
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
19
+
20
+ // 通用:同源、带 cookie、可取消、带 loading/error 的 fetch hook
21
+ function useYidaFetch(buildRequest, deps) {
22
+ var stateHook = React.useState({ loading: false, data: null, error: null });
23
+ var state = stateHook[0];
24
+ var setState = stateHook[1];
25
+ var abortRef = React.useRef(null);
26
+
27
+ var run = React.useCallback(function () {
28
+ if (abortRef.current) { abortRef.current.abort(); }
29
+ var controller = new AbortController();
30
+ abortRef.current = controller;
31
+ setState({ loading: true, data: null, error: null });
32
+
33
+ var req = buildRequest(); // { url, method, body }
34
+ return fetch(req.url, {
35
+ method: req.method || 'GET',
36
+ credentials: 'include', // 同源带登录 cookie,不手动塞 Cookie 头
37
+ headers: { 'Content-Type': 'application/json' },
38
+ body: req.body ? JSON.stringify(req.body) : undefined,
39
+ signal: controller.signal,
40
+ })
41
+ .then(function (resp) {
42
+ if (!resp.ok) { throw new Error('HTTP ' + resp.status); }
43
+ return resp.json();
44
+ })
45
+ .then(function (json) {
46
+ // 宜搭返回体通常形如 { success, result / data, errorMsg },按真实结构解析
47
+ if (json && json.success === false) {
48
+ throw new Error(json.errorMsg || 'request failed');
49
+ }
50
+ setState({ loading: false, data: json.result || json.data || json, error: null });
51
+ })
52
+ .catch(function (err) {
53
+ if (err.name === 'AbortError') { return; }
54
+ setState({ loading: false, data: null, error: err.message });
55
+ });
56
+ }, deps || []);
57
+
58
+ React.useEffect(function () {
59
+ run();
60
+ return function () { if (abortRef.current) { abortRef.current.abort(); } };
61
+ }, deps || []);
62
+
63
+ return { loading: state.loading, data: state.data, error: state.error, refetch: run };
64
+ }
65
+ ```
66
+
67
+ 要点:
68
+
69
+ - `credentials: 'include'` 让浏览器带上同源登录态,**不要**手动拼 `Cookie` / `x-csrf-token` 硬编码值;如需 CSRF,从页面已有上下文动态读取,别写死。
70
+ - 用 `AbortController` 在卸载 / 依赖变化时取消,避免 setState-after-unmount(对应编码规则 #5 副作用清理)。
71
+ - 解析响应按**真实返回结构**处理(宜搭常见 `{ success, result, errorMsg }`),不要假设字段名。
72
+
73
+ ## 在组件里用
74
+
75
+ ```jsx
76
+ function YidaComp(props) {
77
+ var appType = props.appType || '<APP_TYPE>'; // 来自 props 或页面约定
78
+ var formUuid = props.formUuid || '<FORM_UUID>';
79
+
80
+ var q = useYidaFetch(function () {
81
+ return {
82
+ url: '/your-connector-proxy/searchFormDatas', // 连接器同源代理端点(示意)
83
+ method: 'POST',
84
+ body: { appType: appType, formUuid: formUuid, pageSize: 20, pageNumber: 1 },
85
+ };
86
+ }, [appType, formUuid]);
87
+
88
+ if (q.loading) { return <div>加载中…</div>; }
89
+ if (q.error) { return <div style={{ color: 'red' }}>加载失败:{q.error}</div>; }
90
+
91
+ var rows = (q.data && q.data.data) || [];
92
+ return (
93
+ <ul>
94
+ {rows.map(function (row) { return <li key={row.formInstanceId}>{row.title}</li>; })}
95
+ </ul>
96
+ );
97
+ }
98
+
99
+ export default YidaComp;
100
+ ```
101
+
102
+ `url`、`body` 字段按你实际接的连接器 / 端点契约填;上面是结构示意,不是可直接跑的真实端点。
103
+
104
+ ## 写数据(新增 / 更新 / 删除)额外红线
105
+
106
+ - **确认再写**:删除、批量更新等不可逆操作,先让用户在 UI 里显式确认,不在 `useEffect` 里静默触发。
107
+ - **幂等**:提交按钮加 loading 锁与去重键,避免重复写入。
108
+ - **权限**:写操作是否允许由平台权限决定,浏览器侧不要伪造身份;失败按后端返回的 `errorMsg` 提示,不吞错。
109
+ - **不硬编码密钥**:任何 `appSecret` / 签名逻辑都必须留在服务端 / 连接器,Canvas 源码里只出现同源相对路径与业务参数。
110
+
111
+ ## 与其他技能的边界
112
+
113
+ - 需要**创建 / 管理 HTTP 连接器** → `yida-connector`。
114
+ - 需要**通过数据源调用连接器**的页面级配置 → `yida-data-source-connectors`。
115
+ - 页面**强依赖实例数据桥**(表单内字段双向绑定、提交流程深度耦合)→ 回退 `yida-custom-page`(native 免费提供该桥)。
@@ -0,0 +1,45 @@
1
+ # Code Canvas 依赖白名单与 CDN 加载
2
+
3
+ 本文件承载 Code Canvas 的依赖白名单、windowAlias 映射、编译改写规则,以及「预发正常、线上 `antd is not defined`」的根因与物料侧修复方向。核实自 `vc-deep-yida/src/components/yida-code-canvas` 源码(`dependencies.ts` / `factory.tsx`)。
4
+
5
+ ## 依赖白名单(核实自 `yida-code-canvas/dependencies.ts`)
6
+
7
+ 编译阶段把 `import` 改写为下列白名单的 `windowAlias` 引用,运行时按 `windowAlias` 加载到 `window` 上。带 `${cdn}` 的资源在预发走 `dev.g.alicdn.com`、线上走 `g.alicdn.com`(由 `isProd` 决定,见下方「预发/线上差异」)。
8
+
9
+ | 包名 | windowAlias | 资源 |
10
+ | --- | --- | --- |
11
+ | react | `React` | g.alicdn.com react 18.3.1 |
12
+ | react-dom | `ReactDOM` | g.alicdn.com react-dom 18.3.1 |
13
+ | antd | `antd` | g.alicdn.com antd **5.23.3** `antd-with-locales.js`(**依赖外部 `window.dayjs`**)|
14
+ | @ant-design/icons | `icons` | g.alicdn.com ant-design-icons 5.5.1 |
15
+ | dayjs | `dayjs` | 仅 `assetUrlPlaceholder`(dev CDN 的 locale 文件),**无 `assetUrl`** → 画布自身不会加载 dayjs core,依赖宿主提供 |
16
+ | ahooks | `ahooks` | `${cdn}/platform/yida-assets/ahooks.js`(默认追加) |
17
+ | d3 | `d3` | g.alicdn.com d3 7.9.0 |
18
+ | recharts | `Recharts` | g.alicdn.com recharts 2.15.0 |
19
+ | @radix-ui/themes | `Radix` | `${cdn}/.../radix.js` + `radix.css` |
20
+ | lucide-react | `DynamicIcon` | `${cdn}/.../lucideReact.js` |
21
+ | framer-motion | `FramerMotion` | `${cdn}/.../framerMotion.js` |
22
+ | yida-plugin-markdown | `YidaMarkdown` | moduleFederation 0.0.4 |
23
+
24
+ 新增依赖必须同时满足:① 编译能把 import 抽进 `importedModules` 并映射到 windowAlias(见 `canvas-compile.js` 的 `MODULE_ALIAS_MAP`);② 上表或平台运行时能把依赖加载到 window;③ `runtimeCode` 引用的变量名与 windowAlias 一致;④ CSS 资源可加载,否则组件可能渲染但样式/弹层异常。白名单外的包(yida-utils、`@ali/deep`、原生字段组件等)不能 `import`。
25
+
26
+ 编译位置:OpenYida CLI **本地用 Babel** 把源码转译为 `runtimeCode` + `importedModules`(`import`→`window.<别名>`、`export default`→`YidaComp`、依赖名正则抽取),不调用任何在线编译服务,因此不依赖登录态、不经过风控。别名映射逐条镜像自 `dependencies.ts` 的 `getModuleAliasMap()`;运行时消费契约见 `factory.tsx`(`new Function` 执行 `runtimeCode` 取 `YidaComp`)。
27
+
28
+ ## 预发正常、线上报 `antd is not defined` 的根因
29
+
30
+ 现象:预发(`pre-*.alibaba-inc.com`)页面正常;线上(如 `*.aliwork.com`)白屏,控制台报 `antd is not defined` + `antd-with-locales.js` / `dayjs.js` 抛 `Cannot read properties of undefined (reading 'extend')`。
31
+
32
+ 根因(核实自源码):
33
+
34
+ - `antd-with-locales.js`(UMD)初始化时会执行 `dayjs.extend(...)`,其中 `dayjs` 是**外部依赖**,解析为 `window.dayjs`。若加载 antd 前 `window.dayjs` 未就绪 → `undefined.extend` 抛错 → antd 模块未定义 → 业务代码引用 `antd` 报 `antd is not defined`。
35
+ - 正确顺序见 `vc-procode-comp/view.js`:先 `window.dayjs = window.__YCCommon?.Common?.dayjs` 再注入 antd。**但 `yida-code-canvas/factory.tsx` 原实现没有这步**:它 `loadDependencies` 时对 antd 只 `push(assetUrl)`,而 dayjs 条目只有 `assetUrlPlaceholder`、无 `assetUrl`,画布自身根本不会加载 dayjs;且加载前 `if (window[windowAlias]) return` 会跳过已存在依赖。
36
+ - 于是环境差异被放大:
37
+ - **预发**:宿主设计器 / 同页其它 procode 组件已把 `window.antd`(含 dayjs)挂到全局 → 画布命中 `if (window.antd) return`,直接复用宿主 antd,正常。
38
+ - **线上该租户域**:宿主未预置 `window.antd` / `window.dayjs` → 画布现加载 `antd-with-locales.js`,但没有先设 `window.dayjs` → antd 初始化即抛错。(浏览器 debugger 里可见 `finalDependencies` 含 `antd` 却不含 `dayjs`,`window.dayjs` 为 `undefined`,即命中此缺陷。)
39
+ - 附带因素:`isProd = location.hostname.indexOf('pre-') === -1` 是脆弱的域名启发式,非 `pre-` 前缀域一律判为线上,走 `g.alicdn.com` 前缀;antd 本身两端都硬编码 `g.alicdn.com`,故该差异不是 antd 主因,但会影响 `${cdn}` 类依赖(ahooks/radix/lucide/framer-motion/tailwind)的可达性。
40
+
41
+ ## 物料侧修复方向
42
+
43
+ 这是**物料层缺陷**(`factory.tsx` 未在 antd 前保证 `window.dayjs`),页面作者难以在 Canvas 源码内自救(`import dayjs` 也因 dayjs 无 `assetUrl` 不落地)。正确修复在 `factory.tsx` 的 `loadDependencies`:当 `finalDependencies` 含 `antd` 且 `window.dayjs` 未就绪时,**先**复用宿主 `window.__YCCommon?.Common?.dayjs`,否则**单独 `await` 加载 dayjs core**(如 `g.alicdn.com/code/lib/dayjs/<ver>/dayjs.min.js`),确保早于 antd 脚本执行,再进入原有 `assetsUrls` 批量加载。要点:dayjs 必须在**独立且被 await 的加载步骤**里先就绪,避免与 antd 放同一批并行加载产生竞态。
44
+
45
+ 临时规避(未改物料时):让宿主页先行加载 antd/dayjs,或页面改用不依赖 dayjs 的轻组件(避开 DatePicker、时间 locale 等触发 `dayjs.extend` 的能力)。