openyida 2026.9.8-1 → 2026.9.9
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/lib/app/services/canvas-page-schema-builder.js +3 -3
- package/lib/core/sample.js +13 -1
- package/lib/design-plan/materialize.js +6 -10
- package/lib/samples/openyida-scaffold/canvas-form-drawer.canvas.jsx +107 -23
- package/lib/samples/openyida-scaffold/canvas-nav/content.jsx +42 -0
- package/lib/samples/openyida-scaffold/canvas-nav/shared.jsx +1 -1
- package/lib/samples/yida-canvas-table-form/table-form-batch-submit.canvas.jsx +84 -17
- package/lib/samples/yida-rechart/trend-combo.canvas.jsx +97 -18
- package/package.json +1 -1
- package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +15 -3
- package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +4 -1
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +27 -7
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-authoring-examples.md +2 -41
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +93 -65
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-theme-provider.md +89 -0
- package/yida-skills/skills/yida-canvas-custom-page/references/component-library-guide.md +2 -2
- package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +11 -3
- package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +6 -2
- package/yida-skills/skills/yida-canvas-custom-page/scripts/build-canvas-theme.js +122 -0
- package/yida-skills/skills/yida-canvas-custom-page/scripts/canvas-theme-provider.template.jsx +102 -0
- package/yida-skills/skills/yida-canvas-table-form/SKILL.md +2 -2
- package/yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css +2 -4
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-media-grid.md +2 -2
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-modular-clarity.md +3 -3
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-structured-clarity.md +3 -3
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ambient-halo-layered.md +4 -4
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-focus-layered.md +3 -3
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-luminous-modular.md +7 -7
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/deep-stage-duotone.md +2 -2
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/hairline-runway-clarity.md +4 -4
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/high-contrast-modular.md +3 -3
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/interlocked-vivid-modules.md +5 -5
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/matrix-grid-focus.md +2 -2
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/media-rail-inspector.md +4 -4
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mist-layered-signal.md +2 -2
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/modular-rail-signal.md +2 -2
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mono-grid-signal.md +4 -4
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ribbon-ledger-lift.md +4 -4
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/segmented-meter-clarity.md +5 -5
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/split-rail-precision.md +2 -2
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/status-framed-media-grid.md +3 -3
- package/yida-skills/skills/yida-design/workflow/output-design.md +1 -1
- package/yida-skills/skills/yida-nav-shell/SKILL.md +7 -2
- package/yida-skills/skills/yida-nav-shell/references/nav-shell-patterns.md +84 -3
|
@@ -530,8 +530,8 @@ function buildCanvasPageSchemaObject(sourceCode, runtimeCode, importedModules, f
|
|
|
530
530
|
componentName: 'Page',
|
|
531
531
|
id: nextNodeId(),
|
|
532
532
|
props: {
|
|
533
|
-
contentBgColor: 'var(--
|
|
534
|
-
pageStyle: { backgroundColor: 'var(--
|
|
533
|
+
contentBgColor: 'var(--pod-page-bg-color, var(--color-white, #fff))',
|
|
534
|
+
pageStyle: { backgroundColor: 'var(--pod-page-bg-color, var(--color-white, #fff))' },
|
|
535
535
|
contentMargin: '0',
|
|
536
536
|
contentPadding: '0',
|
|
537
537
|
showTitle: false,
|
|
@@ -539,7 +539,7 @@ function buildCanvasPageSchemaObject(sourceCode, runtimeCode, importedModules, f
|
|
|
539
539
|
templateVersion: '1.0.0',
|
|
540
540
|
contentMarginMobile: '0',
|
|
541
541
|
className: 'page_' + nextSuffix(),
|
|
542
|
-
contentBgColorMobile: 'var(--
|
|
542
|
+
contentBgColorMobile: 'var(--pod-page-bg-color, var(--color-white, #fff))',
|
|
543
543
|
},
|
|
544
544
|
condition: true,
|
|
545
545
|
css: [
|
package/lib/core/sample.js
CHANGED
|
@@ -51,12 +51,14 @@ const SAMPLES = {
|
|
|
51
51
|
'canvas-form-drawer': 'canvas-form-drawer.canvas.jsx',
|
|
52
52
|
'form-open-container': 'canvas-form-drawer.canvas.jsx',
|
|
53
53
|
'canvas-dialog': 'canvas-dialog.canvas.jsx',
|
|
54
|
+
'canvas-theme': 'canvas-form-drawer.canvas.jsx',
|
|
54
55
|
'canvas-nav-side': 'canvas-nav/side.jsx',
|
|
55
56
|
'canvas-nav-top': 'canvas-nav/top.jsx',
|
|
56
57
|
'canvas-nav-mixed': 'canvas-nav/mixed.jsx',
|
|
57
58
|
'canvas-nav-dock': 'canvas-nav/dock.jsx',
|
|
58
59
|
'canvas-nav-tabs': 'canvas-nav/tabs.jsx',
|
|
59
60
|
'canvas-nav-data': 'canvas-nav/data.jsx',
|
|
61
|
+
'canvas-nav-content': 'canvas-nav/content.jsx',
|
|
60
62
|
},
|
|
61
63
|
'yida-design': {
|
|
62
64
|
'app-theme': 'app-custom-theme-template.css',
|
|
@@ -91,12 +93,15 @@ const SAMPLE_OUTPUT_FILENAMES = {
|
|
|
91
93
|
'openyida-page-template/canvas-nav-dock': 'canvas-nav-dock.jsx',
|
|
92
94
|
'openyida-page-template/canvas-nav-tabs': 'canvas-nav-tabs.jsx',
|
|
93
95
|
'openyida-page-template/canvas-nav-data': 'canvas-nav-data.jsx',
|
|
96
|
+
'openyida-page-template/canvas-nav-content': 'canvas-nav-content.jsx',
|
|
94
97
|
'openyida-page-template/form-open-container': 'form-open-container.jsx',
|
|
95
98
|
'openyida-page-template/canvas-dialog': 'canvas-dialog.jsx',
|
|
99
|
+
'openyida-page-template/canvas-theme': 'canvas-theme.jsx',
|
|
96
100
|
'yida-design/app-theme': 'app-theme.css',
|
|
97
101
|
};
|
|
98
102
|
|
|
99
103
|
const SAMPLE_HINTS = {
|
|
104
|
+
'openyida-page-template/canvas-nav-content': '合并 CanvasNavigationContent;顶部导航传 navigation,工作台传 children,原生页传 iframeSrc 和 title;宿主已分配高度时显式传 height。',
|
|
100
105
|
'openyida-page-template/canvas-nav-data': '向 loadCanvasNavigation 传入 PRD 规划的 items,通常工作台在首位;接口只过滤不可见入口,保留规划顺序与分组。',
|
|
101
106
|
'openyida-page-template/form-open-container': '将 import、抽屉组件和辅助函数合并到现有 Canvas 页面;表单使用 useYidaFormOpen,其他内容使用 CanvasDrawer。',
|
|
102
107
|
'yida-design/app-theme': '已复制应用主题模板;请由 yida-design 按 design.md 定点修改需要变化的 token。',
|
|
@@ -251,7 +256,7 @@ async function run(args) {
|
|
|
251
256
|
const defaultOutputPath = path.join(process.cwd(), '.cache', 'samples', outputFilename);
|
|
252
257
|
const { outputPath, variables, designFile } = parseOptions(rest, defaultOutputPath);
|
|
253
258
|
const isNavSample = skill === 'openyida-page-template' && name.startsWith('canvas-nav-');
|
|
254
|
-
const prefixPath = isNavSample && !['canvas-nav-tabs', 'canvas-nav-data'].includes(name) ? path.join(path.dirname(sourcePath), 'shared.jsx') : null;
|
|
259
|
+
const prefixPath = isNavSample && !['canvas-nav-tabs', 'canvas-nav-data', 'canvas-nav-content'].includes(name) ? path.join(path.dirname(sourcePath), 'shared.jsx') : null;
|
|
255
260
|
const sidebarPath = isNavSample && ['canvas-nav-side', 'canvas-nav-mixed'].includes(name) ? path.join(path.dirname(sourcePath), 'sidebar.jsx') : null;
|
|
256
261
|
if ([sourcePath, prefixPath, sidebarPath, designFile].filter(Boolean).some(file => path.resolve(outputPath) === path.resolve(file))) {
|
|
257
262
|
throw new CliError('输出路径不能覆盖模板或 design.md', { code: 'SAMPLE_OUTPUT_CONFLICT' });
|
|
@@ -289,6 +294,13 @@ async function run(args) {
|
|
|
289
294
|
} else if (prefixPath) {
|
|
290
295
|
const content = [prefixPath, sidebarPath, sourcePath].filter(Boolean).map(file => fs.readFileSync(file, 'utf8')).join('\n');
|
|
291
296
|
fs.writeFileSync(outputPath, applyTemplateVariables(content, variables), 'utf8');
|
|
297
|
+
} else if (sampleKey === 'openyida-page-template/canvas-theme') {
|
|
298
|
+
const sourceContent = fs.readFileSync(sourcePath, 'utf8');
|
|
299
|
+
const fragment = sourceContent.match(/\/\/ @openyida-canvas-theme:start\r?\n([\s\S]*?)\/\/ @openyida-canvas-theme:end/);
|
|
300
|
+
if (!fragment) {
|
|
301
|
+
throw new CliError('Canvas theme fragment missing', { code: 'SAMPLE_FRAGMENT_MISSING' });
|
|
302
|
+
}
|
|
303
|
+
fs.writeFileSync(outputPath, "import React from 'react';\n\n" + fragment[1], 'utf8');
|
|
292
304
|
} else if (sampleKey === 'openyida-page-template/form-open-container') {
|
|
293
305
|
const sourceContent = fs.readFileSync(sourcePath, 'utf8');
|
|
294
306
|
const fragment = sourceContent.match(/\/\/ @openyida-form-drawer:start\r?\n([\s\S]*?)\/\/ @openyida-form-drawer:end/);
|
|
@@ -491,8 +491,7 @@ function deriveSurfacePalette(primaryColor, surfaceTone, template) {
|
|
|
491
491
|
const darkSurface = surface && [1, 3, 5].every(index => parseInt(surface.slice(index, index + 2), 16) < 128);
|
|
492
492
|
if (surfaceTone !== 'brand-tinted' || darkSurface) {return {};}
|
|
493
493
|
return {
|
|
494
|
-
//
|
|
495
|
-
// 不覆盖上方按导航归属生成的 --oyd-page-background。
|
|
494
|
+
// 品牌氛围属于 Shell;原生页面和 Canvas 共用页面底色,不受导航归属影响。
|
|
496
495
|
'--pod-shell-bg-color-light': mixHex(primaryColor, [255, 255, 255], 0.94),
|
|
497
496
|
'--pod-card-bg-color': 'var(--color-white, #fff)',
|
|
498
497
|
'--color-fill1-1': mixHex(primaryColor, [255, 255, 255], 0.97),
|
|
@@ -684,12 +683,9 @@ function renderDesign(plan) {
|
|
|
684
683
|
for (const [token, value] of Object.entries(derived)) {
|
|
685
684
|
output = replaceTokenValue(output, token, value);
|
|
686
685
|
}
|
|
687
|
-
//
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
output = replaceTokenValue(output, '--oyd-page-background', 'transparent');
|
|
691
|
-
} else if (!selected.themeId.startsWith('dark-')) {
|
|
692
|
-
output = replaceTokenValue(output, '--oyd-page-background', 'var(--pod-page-bg-color, var(--color-white, #fff))');
|
|
686
|
+
// 页面底色统一使用平台 token;导航形态不改变底色,项目显式设计仍优先。
|
|
687
|
+
if (!selected.themeId.startsWith('dark-')) {
|
|
688
|
+
output = replaceTokenValue(output, '--pod-page-bg-color', 'var(--color-white, #fff)');
|
|
693
689
|
}
|
|
694
690
|
const explicitTokens = visual.tokens || {};
|
|
695
691
|
if (typeof explicitTokens !== 'object' || Array.isArray(explicitTokens)) {
|
|
@@ -718,9 +714,9 @@ function renderDesign(plan) {
|
|
|
718
714
|
output = injectAfterFrontmatter(output, [
|
|
719
715
|
'## 项目配色适配(优先于模板默认配色)', '',
|
|
720
716
|
`用户选择的 ${text(color.primaryColorName, primaryColor)} 用于整体浅色氛围。Shell 浅色背景、填充和边界已按同一主色派生,项目显式 token 覆盖优先;最终取值以本文 tokens 为准。`,
|
|
721
|
-
'
|
|
717
|
+
'背景分层:Shell 承载外层氛围;原生页面与自定义页统一消费 --pod-page-bg-color,卡片与面板消费 --pod-card-bg-color。导航隐藏不自动改为透明;渐变、纹理、图片属于页面局部装饰层。明确的应用背景设计通过 app-theme.css 配置平台 token。',
|
|
722
718
|
'模板中“固定无彩画布”“中性灰必须 R=G=B”“禁止主题色进入背景”等默认限制在本项目不适用;保留布局、圆角、阴影与组件结构。不要为了保留模板灰底,把用户选择的浅彩导航改白。',
|
|
723
|
-
'页面背景消费 --
|
|
719
|
+
'页面背景消费 --pod-page-bg-color,卡片消费 --pod-card-bg-color(回退 --color-white),边界消费 --pod-card-border;近白页面与白卡必须保留可见细边框,不能只靠阴影分层。导航、表头、嵌套区域与选中态消费同一组品牌/填充 token。',
|
|
724
720
|
'正文与标题使用 --color-text1-4,表头与 placeholder 使用 --color-text1-10,辅助说明使用 --color-text1-3;保持中性文字明度层级,不把整页文字染成主色。成功、警告、错误沿用应用语义色。',
|
|
725
721
|
'antd 的主色、容器、浮层、文字、边框、填充及语义色都须映射应用 token;浅彩氛围不能仅出现在导航和主按钮。页面实现前先读本节,再读取下面的结构与组件规则。', '',
|
|
726
722
|
].join('\n'));
|
|
@@ -64,6 +64,7 @@ function buildYidaFormUrl(request, currentAppType) {
|
|
|
64
64
|
/** 抽屉外壳默认跟随应用主题,background 可为当前抽屉指定颜色、渐变或 CSS token。 */
|
|
65
65
|
function CanvasDrawer({
|
|
66
66
|
open, title, onClose, onOpenInNewWindow, extra, children,
|
|
67
|
+
contentMode = 'content',
|
|
67
68
|
background = 'var(--pod-shell-theme-bg-color, var(--drawer-bg, var(--color-brand1-1, #f4f6ff)))',
|
|
68
69
|
}) {
|
|
69
70
|
const [fullScreen, setFullScreen] = useState(false);
|
|
@@ -120,6 +121,7 @@ function CanvasDrawer({
|
|
|
120
121
|
.openyida-form-drawer.ant-drawer .ant-drawer-title {
|
|
121
122
|
color: inherit;
|
|
122
123
|
font-size: inherit;
|
|
124
|
+
font-weight: inherit;
|
|
123
125
|
}
|
|
124
126
|
.openyida-form-drawer .oy-drawer-actions {
|
|
125
127
|
display: flex;
|
|
@@ -147,6 +149,12 @@ function CanvasDrawer({
|
|
|
147
149
|
outline: 2px solid var(--color-brand1-6, #1677ff);
|
|
148
150
|
outline-offset: 2px;
|
|
149
151
|
}
|
|
152
|
+
.openyida-form-drawer .oy-drawer-frame {
|
|
153
|
+
position: relative;
|
|
154
|
+
flex: 1 1 0;
|
|
155
|
+
min-height: 0;
|
|
156
|
+
overflow: hidden;
|
|
157
|
+
}
|
|
150
158
|
.openyida-form-drawer .oy-drawer-card {
|
|
151
159
|
height: 100%;
|
|
152
160
|
min-height: 0;
|
|
@@ -213,13 +221,22 @@ function CanvasDrawer({
|
|
|
213
221
|
overflow: 'hidden',
|
|
214
222
|
},
|
|
215
223
|
header: {
|
|
216
|
-
background: '
|
|
224
|
+
background: 'transparent',
|
|
225
|
+
boxSizing: 'border-box',
|
|
226
|
+
height: 'var(--pod-nav-platform-header-height, 48px)',
|
|
227
|
+
minHeight: 'var(--pod-nav-platform-header-height, 48px)',
|
|
228
|
+
flex: '0 0 var(--pod-nav-platform-header-height, 48px)',
|
|
217
229
|
color: 'var(--drawer-title-color, var(--pod-page-header-text-color, var(--color-text1-4, #1f2329)))',
|
|
218
|
-
fontSize: 'var(--
|
|
219
|
-
|
|
230
|
+
fontSize: 'var(--pod-page-title-font-size, 16px)',
|
|
231
|
+
fontWeight: 'var(--pod-page-title-font-weight, 500)',
|
|
232
|
+
padding: '0 var(--drawer-title-padding-left-right, 20px)',
|
|
220
233
|
borderBottom: 'var(--drawer-title-border-width, 0px) solid var(--drawer-title-border-color, var(--drawer-border-color, transparent))',
|
|
221
234
|
},
|
|
222
|
-
body: {
|
|
235
|
+
body: {
|
|
236
|
+
display: 'flex', flexDirection: 'column', flex: '1 1 0',
|
|
237
|
+
padding: contentMode === 'iframe' ? 0 : '0 8px 8px',
|
|
238
|
+
minHeight: 0, overflow: 'hidden',
|
|
239
|
+
},
|
|
223
240
|
}}
|
|
224
241
|
>
|
|
225
242
|
{!fullScreen ? (
|
|
@@ -250,12 +267,13 @@ function CanvasDrawer({
|
|
|
250
267
|
}}
|
|
251
268
|
/>
|
|
252
269
|
) : null}
|
|
253
|
-
<div className=
|
|
270
|
+
<div className={contentMode === 'iframe' ? 'oy-drawer-frame' : 'oy-drawer-card'}>{children}</div>
|
|
254
271
|
</Drawer>
|
|
255
272
|
</>
|
|
256
273
|
);
|
|
257
274
|
}
|
|
258
275
|
|
|
276
|
+
/** 原生提交页和详情页自带页面布局,外层只提供 iframe 视口,不再套内容卡片。 */
|
|
259
277
|
function FormOpenContainer({ request, currentAppType, onClose, onAfterClose }) {
|
|
260
278
|
const iframeSrc = useMemo(() => buildYidaFormUrl(request, currentAppType), [request, currentAppType]);
|
|
261
279
|
const title = request && request.title ? request.title : '表单';
|
|
@@ -263,6 +281,7 @@ function FormOpenContainer({ request, currentAppType, onClose, onAfterClose }) {
|
|
|
263
281
|
<CanvasDrawer
|
|
264
282
|
title={title}
|
|
265
283
|
open={!!request}
|
|
284
|
+
contentMode="iframe"
|
|
266
285
|
background={request?.background}
|
|
267
286
|
onOpenInNewWindow={iframeSrc ? () => window.open(iframeSrc, '_blank', 'noopener,noreferrer') : undefined}
|
|
268
287
|
onClose={() => {
|
|
@@ -270,8 +289,8 @@ function FormOpenContainer({ request, currentAppType, onClose, onAfterClose }) {
|
|
|
270
289
|
if (typeof onAfterClose === 'function') onAfterClose();
|
|
271
290
|
}}
|
|
272
291
|
>
|
|
273
|
-
{/*
|
|
274
|
-
{iframeSrc ? <iframe title={title} src={iframeSrc} style={{ width: '100%', height: '100%', minHeight:
|
|
292
|
+
{/* iframe 填满 flex 分配的剩余空间,只由内页滚动,不按视口猜测标题高度。 */}
|
|
293
|
+
{iframeSrc ? <iframe title={title} src={iframeSrc} style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', minHeight: 0, border: 0, display: 'block' }} /> : null}
|
|
275
294
|
</CanvasDrawer>
|
|
276
295
|
);
|
|
277
296
|
}
|
|
@@ -346,11 +365,6 @@ function resolveConfigValue(value, fallback) {
|
|
|
346
365
|
return isUnresolvedTemplate(value) ? fallback : String(value);
|
|
347
366
|
}
|
|
348
367
|
|
|
349
|
-
function readThemeColor(name, fallback) {
|
|
350
|
-
if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') return fallback;
|
|
351
|
-
return window.getComputedStyle(document.documentElement).getPropertyValue(name).trim() || fallback;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
368
|
function normalizeRows(rows) {
|
|
355
369
|
const source = Array.isArray(rows) && rows.length ? rows : SAMPLE_ROWS;
|
|
356
370
|
return source.map((row, index) => ({
|
|
@@ -367,7 +381,85 @@ function normalizeRows(rows) {
|
|
|
367
381
|
}));
|
|
368
382
|
}
|
|
369
383
|
|
|
384
|
+
// @openyida-canvas-theme:start
|
|
385
|
+
// Standalone samples keep this fragment identical; see canvas-theme.test.js.
|
|
386
|
+
function readCanvasTheme(root, fallback) {
|
|
387
|
+
const roles = {
|
|
388
|
+
colorPrimary: '--color-brand1-6', colorLink: '--color-brand1-6',
|
|
389
|
+
colorPrimaryHover: '--color-brand1-5', colorPrimaryActive: '--color-brand1-9',
|
|
390
|
+
colorBgLayout: ['--pod-page-bg-color', '--color-white'],
|
|
391
|
+
colorBgContainer: ['--pod-card-bg-color', '--color-white'],
|
|
392
|
+
colorBgElevated: ['--pod-card-bg-color', '--color-white'], colorText: '--color-text1-4',
|
|
393
|
+
colorTextHeading: '--color-text1-4', colorTextSecondary: '--color-text1-3',
|
|
394
|
+
colorTextDescription: '--color-text1-3', colorTextPlaceholder: '--color-text1-10',
|
|
395
|
+
colorBorder: '--color-line1-2', colorBorderSecondary: '--color-line1-1',
|
|
396
|
+
colorFillAlter: '--color-fill1-1', colorFillSecondary: '--color-fill1-2',
|
|
397
|
+
};
|
|
398
|
+
const token = { ...fallback };
|
|
399
|
+
if (!root) return token;
|
|
400
|
+
const doc = root.ownerDocument;
|
|
401
|
+
const view = doc.defaultView;
|
|
402
|
+
const scope = view.getComputedStyle(root);
|
|
403
|
+
const probe = doc.createElement('span');
|
|
404
|
+
probe.style.cssText = 'position:absolute;visibility:hidden;pointer-events:none;';
|
|
405
|
+
root.appendChild(probe);
|
|
406
|
+
try {
|
|
407
|
+
Object.keys(roles).forEach((role) => {
|
|
408
|
+
const names = Array.isArray(roles[role]) ? roles[role] : [roles[role]];
|
|
409
|
+
const value = names.map((name) => scope.getPropertyValue(name).trim()).find(Boolean) || '';
|
|
410
|
+
if (!value || !view.CSS.supports('color', value)) return;
|
|
411
|
+
probe.style.color = value;
|
|
412
|
+
const color = view.getComputedStyle(probe).color;
|
|
413
|
+
if (color) token[role] = color;
|
|
414
|
+
});
|
|
415
|
+
} finally {
|
|
416
|
+
probe.remove();
|
|
417
|
+
}
|
|
418
|
+
return token;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function useCanvasTheme(fallback) {
|
|
422
|
+
const rootRef = React.useRef(null);
|
|
423
|
+
const fallbackRef = React.useRef(fallback);
|
|
424
|
+
const [token, setToken] = React.useState(fallback);
|
|
425
|
+
React.useLayoutEffect(() => {
|
|
426
|
+
const root = rootRef.current;
|
|
427
|
+
if (!root) return undefined;
|
|
428
|
+
const doc = root.ownerDocument;
|
|
429
|
+
const view = doc.defaultView;
|
|
430
|
+
let frame;
|
|
431
|
+
const refresh = () => {
|
|
432
|
+
const next = readCanvasTheme(root, fallbackRef.current);
|
|
433
|
+
setToken((previous) => JSON.stringify(previous) === JSON.stringify(next) ? previous : next);
|
|
434
|
+
};
|
|
435
|
+
const schedule = () => {
|
|
436
|
+
view.cancelAnimationFrame(frame);
|
|
437
|
+
frame = view.requestAnimationFrame(refresh);
|
|
438
|
+
};
|
|
439
|
+
refresh();
|
|
440
|
+
// Observe only theme-bearing ancestors and stylesheet changes, not business DOM.
|
|
441
|
+
const observer = new view.MutationObserver(schedule);
|
|
442
|
+
for (let node = root; node; node = node.parentElement) {
|
|
443
|
+
observer.observe(node, { attributes: true });
|
|
444
|
+
}
|
|
445
|
+
if (doc.head) observer.observe(doc.head, { subtree: true, childList: true, characterData: true, attributes: true });
|
|
446
|
+
doc.addEventListener('load', schedule, true);
|
|
447
|
+
view.addEventListener('resize', schedule);
|
|
448
|
+
view.addEventListener('openyida:theme-change', schedule);
|
|
449
|
+
return () => {
|
|
450
|
+
observer.disconnect();
|
|
451
|
+
view.cancelAnimationFrame(frame);
|
|
452
|
+
doc.removeEventListener('load', schedule, true);
|
|
453
|
+
view.removeEventListener('resize', schedule);
|
|
454
|
+
view.removeEventListener('openyida:theme-change', schedule);
|
|
455
|
+
};
|
|
456
|
+
}, []);
|
|
457
|
+
return { rootRef, token };
|
|
458
|
+
}
|
|
459
|
+
// @openyida-canvas-theme:end
|
|
460
|
+
|
|
370
461
|
function YidaComp(props) {
|
|
462
|
+
const { rootRef, token } = useCanvasTheme({ colorPrimary: "#1677ff", borderRadius: 9 });
|
|
371
463
|
const rawAppType = props && props.appType ? props.appType : RAW_APP_TYPE;
|
|
372
464
|
const rawFormUuid = props && props.formUuid ? props.formUuid : RAW_FORM_UUID;
|
|
373
465
|
const appType = resolveConfigValue(rawAppType, 'APP_XXX');
|
|
@@ -461,15 +553,7 @@ function YidaComp(props) {
|
|
|
461
553
|
|
|
462
554
|
return (
|
|
463
555
|
<ConfigProvider
|
|
464
|
-
theme={{
|
|
465
|
-
token: {
|
|
466
|
-
colorPrimary: readThemeColor('--color-brand1-6', '#1677FF'),
|
|
467
|
-
colorInfo: readThemeColor('--color-brand1-6', '#1677FF'),
|
|
468
|
-
colorText: readThemeColor('--color-text1-4', '#1F2329'),
|
|
469
|
-
colorBgLayout: readThemeColor('--color-fill1-1', '#F5F6F7'),
|
|
470
|
-
borderRadius: 8,
|
|
471
|
-
},
|
|
472
|
-
}}
|
|
556
|
+
theme={{ token }}
|
|
473
557
|
getPopupContainer={(triggerNode) => (triggerNode && triggerNode.parentElement) || document.body}
|
|
474
558
|
>
|
|
475
559
|
<style>{`
|
|
@@ -478,7 +562,7 @@ function YidaComp(props) {
|
|
|
478
562
|
min-height: 100vh;
|
|
479
563
|
box-sizing: border-box;
|
|
480
564
|
padding: 24px;
|
|
481
|
-
background: var(--
|
|
565
|
+
background: var(--pod-page-bg-color, var(--color-white, #fff));
|
|
482
566
|
color: var(--color-text1-4);
|
|
483
567
|
}
|
|
484
568
|
.oys-shell {
|
|
@@ -536,7 +620,7 @@ function YidaComp(props) {
|
|
|
536
620
|
}
|
|
537
621
|
`}</style>
|
|
538
622
|
|
|
539
|
-
<div className="oys-page">
|
|
623
|
+
<div ref={rootRef} className="oys-page">
|
|
540
624
|
<div className="oys-shell">
|
|
541
625
|
<div className="oys-header">
|
|
542
626
|
<div>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
// 顶部导航与内容的高度链;侧栏布局可在侧栏右侧已分配高度的区域使用 height="100%"。
|
|
4
|
+
// 只管理布局:菜单权限、URL/hash、离开未保存表单的确认由调用方处理。
|
|
5
|
+
// 合并到页面时保留组件,合并重复 import;不要额外包裹滚动卡片。
|
|
6
|
+
function CanvasNavigationContent({
|
|
7
|
+
navigation,
|
|
8
|
+
children,
|
|
9
|
+
iframeSrc,
|
|
10
|
+
title = '页面内容',
|
|
11
|
+
contentKey,
|
|
12
|
+
height = '100dvh',
|
|
13
|
+
maxWidth = 1280,
|
|
14
|
+
gutter = 'clamp(12px, 2vw, 24px)',
|
|
15
|
+
radius = 16,
|
|
16
|
+
background = 'transparent',
|
|
17
|
+
}) {
|
|
18
|
+
const embedded = Boolean(iframeSrc);
|
|
19
|
+
return (
|
|
20
|
+
<div className="openyida-nav-layout" style={{
|
|
21
|
+
height, minHeight: 0, minWidth: 0, display: 'flex', flexDirection: 'column',
|
|
22
|
+
overflow: 'hidden', background,
|
|
23
|
+
}}>
|
|
24
|
+
{navigation != null && <div style={{ flexShrink: 0 }}>{navigation}</div>}
|
|
25
|
+
<main className="openyida-nav-main" aria-label={title} style={{
|
|
26
|
+
display: 'flex', flexDirection: 'column', flex: '1 1 0', minHeight: 0,
|
|
27
|
+
minWidth: 0, overflow: 'hidden', padding: gutter, boxSizing: 'border-box',
|
|
28
|
+
}}>
|
|
29
|
+
<div key={contentKey} className="openyida-nav-content" style={{
|
|
30
|
+
position: 'relative', flex: '1 1 0', minHeight: 0, minWidth: 0,
|
|
31
|
+
width: '100%', maxWidth, margin: '0 auto', borderRadius: radius,
|
|
32
|
+
overflow: embedded ? 'hidden' : 'auto',
|
|
33
|
+
}}>
|
|
34
|
+
{embedded ? <iframe title={title} src={iframeSrc} style={{
|
|
35
|
+
position: 'absolute', inset: 0, width: '100%', height: '100%',
|
|
36
|
+
display: 'block', border: 0,
|
|
37
|
+
}} /> : children}
|
|
38
|
+
</div>
|
|
39
|
+
</main>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -4,7 +4,7 @@ import React from 'react';
|
|
|
4
4
|
// 这是可选实现示例;导航外观按 design.md 设计,已有 UI 无需替换成示例默认样式。
|
|
5
5
|
// 选中菜单标识当前页面,main 用 aria-label 命名,children 直接承载业务内容。
|
|
6
6
|
const CANVAS_NAV_CSS = `
|
|
7
|
-
.oy-canvas-nav { display: flow-root; min-width: 0; min-height: 100vh; background: var(--
|
|
7
|
+
.oy-canvas-nav { display: flow-root; min-width: 0; min-height: 100vh; background: var(--pod-page-bg-color, var(--color-white, #fff)); }
|
|
8
8
|
.oy-canvas-nav .oy-nav-surface {
|
|
9
9
|
background: var(--pod-shell-theme-bg-color, var(--pod-nav-popup-bg-color, var(--color-white, #fff)));
|
|
10
10
|
color: var(--pod-nav-item-text-color, var(--color-text1-4, #1f2329));
|
|
@@ -89,11 +89,6 @@ function isFieldMapReady(fieldMap) {
|
|
|
89
89
|
);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
function readThemeColor(name, fallback) {
|
|
93
|
-
if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') return fallback;
|
|
94
|
-
return window.getComputedStyle(document.documentElement).getPropertyValue(name).trim() || fallback;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
92
|
function toPayload(row, fieldMap) {
|
|
98
93
|
return {
|
|
99
94
|
[fieldMap.name]: String(row.name).trim(),
|
|
@@ -115,7 +110,86 @@ function getBridgeState(props) {
|
|
|
115
110
|
return { ready: true, reason: '', writeBridge, fieldMap };
|
|
116
111
|
}
|
|
117
112
|
|
|
113
|
+
// @openyida-canvas-theme:start
|
|
114
|
+
// Standalone samples keep this fragment identical; see canvas-theme.test.js.
|
|
115
|
+
function readCanvasTheme(root, fallback) {
|
|
116
|
+
const roles = {
|
|
117
|
+
colorPrimary: '--color-brand1-6', colorLink: '--color-brand1-6',
|
|
118
|
+
colorPrimaryHover: '--color-brand1-5', colorPrimaryActive: '--color-brand1-9',
|
|
119
|
+
colorBgLayout: ['--pod-page-bg-color', '--color-white'],
|
|
120
|
+
colorBgContainer: ['--pod-card-bg-color', '--color-white'],
|
|
121
|
+
colorBgElevated: ['--pod-card-bg-color', '--color-white'], colorText: '--color-text1-4',
|
|
122
|
+
colorTextHeading: '--color-text1-4', colorTextSecondary: '--color-text1-3',
|
|
123
|
+
colorTextDescription: '--color-text1-3', colorTextPlaceholder: '--color-text1-10',
|
|
124
|
+
colorBorder: '--color-line1-2', colorBorderSecondary: '--color-line1-1',
|
|
125
|
+
colorFillAlter: '--color-fill1-1', colorFillSecondary: '--color-fill1-2',
|
|
126
|
+
};
|
|
127
|
+
const token = { ...fallback };
|
|
128
|
+
if (!root) return token;
|
|
129
|
+
const doc = root.ownerDocument;
|
|
130
|
+
const view = doc.defaultView;
|
|
131
|
+
const scope = view.getComputedStyle(root);
|
|
132
|
+
const probe = doc.createElement('span');
|
|
133
|
+
probe.style.cssText = 'position:absolute;visibility:hidden;pointer-events:none;';
|
|
134
|
+
root.appendChild(probe);
|
|
135
|
+
try {
|
|
136
|
+
Object.keys(roles).forEach((role) => {
|
|
137
|
+
const names = Array.isArray(roles[role]) ? roles[role] : [roles[role]];
|
|
138
|
+
const value = names.map((name) => scope.getPropertyValue(name).trim()).find(Boolean) || '';
|
|
139
|
+
if (!value || !view.CSS.supports('color', value)) return;
|
|
140
|
+
probe.style.color = value;
|
|
141
|
+
const color = view.getComputedStyle(probe).color;
|
|
142
|
+
if (color) token[role] = color;
|
|
143
|
+
});
|
|
144
|
+
} finally {
|
|
145
|
+
probe.remove();
|
|
146
|
+
}
|
|
147
|
+
return token;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function useCanvasTheme(fallback) {
|
|
151
|
+
const rootRef = React.useRef(null);
|
|
152
|
+
const fallbackRef = React.useRef(fallback);
|
|
153
|
+
const [token, setToken] = React.useState(fallback);
|
|
154
|
+
React.useLayoutEffect(() => {
|
|
155
|
+
const root = rootRef.current;
|
|
156
|
+
if (!root) return undefined;
|
|
157
|
+
const doc = root.ownerDocument;
|
|
158
|
+
const view = doc.defaultView;
|
|
159
|
+
let frame;
|
|
160
|
+
const refresh = () => {
|
|
161
|
+
const next = readCanvasTheme(root, fallbackRef.current);
|
|
162
|
+
setToken((previous) => JSON.stringify(previous) === JSON.stringify(next) ? previous : next);
|
|
163
|
+
};
|
|
164
|
+
const schedule = () => {
|
|
165
|
+
view.cancelAnimationFrame(frame);
|
|
166
|
+
frame = view.requestAnimationFrame(refresh);
|
|
167
|
+
};
|
|
168
|
+
refresh();
|
|
169
|
+
// Observe only theme-bearing ancestors and stylesheet changes, not business DOM.
|
|
170
|
+
const observer = new view.MutationObserver(schedule);
|
|
171
|
+
for (let node = root; node; node = node.parentElement) {
|
|
172
|
+
observer.observe(node, { attributes: true });
|
|
173
|
+
}
|
|
174
|
+
if (doc.head) observer.observe(doc.head, { subtree: true, childList: true, characterData: true, attributes: true });
|
|
175
|
+
doc.addEventListener('load', schedule, true);
|
|
176
|
+
view.addEventListener('resize', schedule);
|
|
177
|
+
view.addEventListener('openyida:theme-change', schedule);
|
|
178
|
+
return () => {
|
|
179
|
+
observer.disconnect();
|
|
180
|
+
view.cancelAnimationFrame(frame);
|
|
181
|
+
doc.removeEventListener('load', schedule, true);
|
|
182
|
+
view.removeEventListener('resize', schedule);
|
|
183
|
+
view.removeEventListener('openyida:theme-change', schedule);
|
|
184
|
+
};
|
|
185
|
+
}, []);
|
|
186
|
+
return { rootRef, token };
|
|
187
|
+
}
|
|
188
|
+
// @openyida-canvas-theme:end
|
|
189
|
+
|
|
118
190
|
function YidaComp(props) {
|
|
191
|
+
const [modal, modalContextHolder] = Modal.useModal();
|
|
192
|
+
const { rootRef, token } = useCanvasTheme({ colorPrimary: "#1677ff", borderRadius: 9 });
|
|
119
193
|
const formUuid = props && props.formUuid ? props.formUuid : 'sample-unbound';
|
|
120
194
|
const draftKey = 'openyida_canvas_table_form_draft_' + formUuid;
|
|
121
195
|
const [rows, setRows] = useState(() => loadDraft(draftKey));
|
|
@@ -262,7 +336,7 @@ function YidaComp(props) {
|
|
|
262
336
|
}
|
|
263
337
|
if (!validated.length) return;
|
|
264
338
|
|
|
265
|
-
|
|
339
|
+
modal.confirm({
|
|
266
340
|
title: '确认批量提交',
|
|
267
341
|
content: '将提交 ' + validated.length + ' 行数据,首行事项为“' + validated[0].name + '”。提交后成功行不会重复写入。',
|
|
268
342
|
okText: '确认提交',
|
|
@@ -386,15 +460,7 @@ function YidaComp(props) {
|
|
|
386
460
|
|
|
387
461
|
return (
|
|
388
462
|
<ConfigProvider
|
|
389
|
-
theme={{
|
|
390
|
-
token: {
|
|
391
|
-
colorPrimary: readThemeColor('--color-brand1-6', '#1677FF'),
|
|
392
|
-
colorInfo: readThemeColor('--color-brand1-6', '#1677FF'),
|
|
393
|
-
borderRadius: 9,
|
|
394
|
-
colorText: readThemeColor('--color-text1-4', '#1F2329'),
|
|
395
|
-
colorBgLayout: readThemeColor('--color-fill1-1', '#F5F6F7'),
|
|
396
|
-
},
|
|
397
|
-
}}
|
|
463
|
+
theme={{ token }}
|
|
398
464
|
>
|
|
399
465
|
<style>{`
|
|
400
466
|
.canvas-table-page {
|
|
@@ -402,7 +468,7 @@ function YidaComp(props) {
|
|
|
402
468
|
min-height: 100vh;
|
|
403
469
|
box-sizing: border-box;
|
|
404
470
|
padding: 28px;
|
|
405
|
-
background: var(--
|
|
471
|
+
background: var(--pod-page-bg-color, var(--color-white, #fff));
|
|
406
472
|
}
|
|
407
473
|
.canvas-table-shell { max-width: 1320px; margin: 0 auto; }
|
|
408
474
|
.canvas-table-header {
|
|
@@ -437,7 +503,8 @@ function YidaComp(props) {
|
|
|
437
503
|
}
|
|
438
504
|
`}</style>
|
|
439
505
|
|
|
440
|
-
<div className="canvas-table-page">
|
|
506
|
+
<div ref={rootRef} className="canvas-table-page">
|
|
507
|
+
{modalContextHolder}
|
|
441
508
|
<div className="canvas-table-shell">
|
|
442
509
|
<div className="canvas-table-header">
|
|
443
510
|
<div>
|