openyida 2026.10.21 → 2026.10.22-beta.0

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 (57) hide show
  1. package/README.md +24 -3
  2. package/bin/yida.js +4 -0
  3. package/lib/app/canvas-compile.js +8 -0
  4. package/lib/app/page-linter.js +10 -0
  5. package/lib/app/responsive-layout-guard.js +82 -0
  6. package/lib/auth/qwenwork-client.js +190 -0
  7. package/lib/auth/qwenwork.js +37 -0
  8. package/lib/core/command-manifest.js +18 -10
  9. package/lib/core/locales/en.js +63 -4
  10. package/lib/core/locales/zh.js +62 -3
  11. package/lib/core/query-data.js +40 -2
  12. package/lib/core/sample.js +3 -0
  13. package/lib/feedback/feedback.js +2 -1
  14. package/lib/feedback/github-setup.js +97 -0
  15. package/lib/feedback/github.js +17 -8
  16. package/lib/feedback/report.js +35 -15
  17. package/lib/integration/integration-triggers.js +8 -3
  18. package/lib/process/configure-process.js +8 -2
  19. package/lib/process/process-tasks.js +175 -0
  20. package/lib/process/services/process-actor-config.js +3 -0
  21. package/lib/process/services/process-compiler.js +15 -0
  22. package/lib/process/services/process-dynamic-actors.js +165 -0
  23. package/lib/samples/openyida-scaffold/canvas-nav/mixed.jsx +1 -1
  24. package/lib/samples/openyida-scaffold/canvas-nav/shared.jsx +1 -1
  25. package/lib/samples/openyida-scaffold/canvas-nav/side.jsx +1 -1
  26. package/lib/samples/openyida-scaffold/canvas-nav/sidebar.jsx +36 -24
  27. package/lib/samples/openyida-scaffold/canvas-nav/top.jsx +1 -1
  28. package/lib/samples/openyida-scaffold/canvas-responsive-layout.jsx +104 -0
  29. package/package.json +1 -1
  30. package/scripts/postinstall.js +1 -0
  31. package/yida-skills/SKILL.md +5 -2
  32. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +5 -1
  33. package/yida-skills/skills/yida-custom-page/SKILL.md +6 -2
  34. package/yida-skills/skills/yida-data-management/SKILL.md +24 -3
  35. package/yida-skills/skills/yida-data-management/references/process-task-transfer.md +53 -0
  36. package/yida-skills/skills/yida-density/SKILL.md +6 -1
  37. package/yida-skills/skills/yida-design/SKILL.md +1 -1
  38. package/yida-skills/skills/yida-design/references/responsive-layout.md +69 -22
  39. package/yida-skills/skills/yida-design/references/task-driven-visual-design.md +2 -0
  40. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/SKILL.md +1 -0
  41. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-compact-schema.md +1 -1
  42. package/yida-skills/skills/yida-design/workflow/output-design.md +2 -2
  43. package/yida-skills/skills/yida-design/workflow/step-1-read-brief.md +2 -0
  44. package/yida-skills/skills/yida-design/workflow/step-4-wireframe-interaction.md +10 -10
  45. package/yida-skills/skills/yida-feedback/SKILL.md +22 -5
  46. package/yida-skills/skills/yida-feedback/references/github-contribution.md +14 -8
  47. package/yida-skills/skills/yida-feedback/references/github-setup.md +74 -0
  48. package/yida-skills/skills/yida-feedback/references/report-input.md +10 -2
  49. package/yida-skills/skills/yida-feedback/references/user-interaction.md +19 -2
  50. package/yida-skills/skills/yida-flow-node-config/SKILL.md +1 -1
  51. package/yida-skills/skills/yida-flow-node-config/references/actors.md +4 -0
  52. package/yida-skills/skills/yida-flow-node-config/references/dynamic-actors.md +84 -0
  53. package/yida-skills/skills/yida-integration/SKILL.md +1 -0
  54. package/yida-skills/skills/yida-integration/references/triggers.md +14 -1
  55. package/yida-skills/skills/yida-login/SKILL.md +12 -0
  56. package/yida-skills/skills/yida-prd/SKILL.md +1 -0
  57. package/yida-skills/skills-index.json +13 -4
@@ -5,15 +5,15 @@ function CanvasSidebar({ children, title, logo, actions, activeKey, defaultWidth
5
5
  const [collapsed, setCollapsed] = React.useState(defaultCollapsed);
6
6
  const [mobileOpen, setMobileOpen] = React.useState(false);
7
7
  const [width, setWidth] = React.useState(null);
8
- const [viewport, setViewport] = React.useState(() => window.innerWidth);
8
+ const [containerWidth, setContainerWidth] = React.useState(() => window.innerWidth);
9
9
  const [resizing, setResizing] = React.useState(false);
10
10
  const [measuredWidth, setMeasuredWidth] = React.useState(216);
11
11
  const sidebar = React.useRef(null);
12
12
  const drag = React.useRef(null);
13
13
  const menuId = React.useId();
14
- const mobile = viewport < 768;
14
+ const mobile = containerWidth < 768;
15
15
  const minWidth = 180;
16
- const maxWidth = Math.min(400, Math.floor(viewport * 0.45));
16
+ const maxWidth = Math.min(400, Math.floor(containerWidth * 0.45));
17
17
  const clamp = value => Math.max(minWidth, Math.min(maxWidth, value));
18
18
  const expanded = mobile ? mobileOpen : !collapsed;
19
19
  const ToggleIcon = mobile ? Menu : collapsed ? PanelLeftOpen : PanelLeftClose;
@@ -25,19 +25,35 @@ function CanvasSidebar({ children, title, logo, actions, activeKey, defaultWidth
25
25
  setResizing(false);
26
26
  }
27
27
  React.useEffect(() => {
28
- const resize = () => { finishResize(); setViewport(window.innerWidth); };
29
- window.addEventListener('resize', resize);
30
28
  window.addEventListener('blur', finishResize);
31
- return () => { window.removeEventListener('resize', resize); window.removeEventListener('blur', finishResize); finishResize(); };
29
+ return () => { window.removeEventListener('blur', finishResize); finishResize(); };
32
30
  }, []);
33
31
  React.useEffect(() => setMobileOpen(false), [activeKey]);
34
32
  React.useLayoutEffect(() => {
35
- const observer = new window.ResizeObserver(() => setMeasuredWidth(Math.round(sidebar.current.getBoundingClientRect().width)));
36
- observer.observe(sidebar.current);
37
- return () => observer.disconnect();
33
+ const aside = sidebar.current;
34
+ const container = aside?.parentElement || aside;
35
+ if (!aside || !container) return undefined;
36
+ const measure = () => {
37
+ const nextContainerWidth = Math.round(container.getBoundingClientRect().width);
38
+ const nextSidebarWidth = Math.round(aside.getBoundingClientRect().width);
39
+ setContainerWidth(current => current === nextContainerWidth ? current : nextContainerWidth);
40
+ setMeasuredWidth(current => current === nextSidebarWidth ? current : nextSidebarWidth);
41
+ };
42
+ measure();
43
+ const observer = window.ResizeObserver ? new window.ResizeObserver(measure) : null;
44
+ if (observer) {
45
+ observer.observe(container);
46
+ observer.observe(aside);
47
+ } else {
48
+ window.addEventListener('resize', measure);
49
+ }
50
+ return () => {
51
+ if (observer) observer.disconnect();
52
+ else window.removeEventListener('resize', measure);
53
+ };
38
54
  }, []);
39
55
 
40
- return <aside ref={sidebar} className="oy-nav-sidebar oy-nav-surface" data-collapsed={collapsed} data-mobile-open={mobileOpen}
56
+ return <aside ref={sidebar} className="oy-nav-sidebar oy-nav-surface" data-compact={mobile} data-collapsed={collapsed} data-mobile-open={mobileOpen}
41
57
  onClick={event => { if (mobile && event.target.closest('.oy-nav-menu .oy-nav-item')) setMobileOpen(false); }}
42
58
  style={{ '--oy-sidebar-width': width === null ? (typeof defaultWidth === 'number' ? `${defaultWidth}px` : defaultWidth) : `${clamp(width)}px`, '--oy-sidebar-max-width': `${maxWidth}px` }}>
43
59
  <style>{`
@@ -48,20 +64,16 @@ function CanvasSidebar({ children, title, logo, actions, activeKey, defaultWidth
48
64
  .oy-canvas-nav .oy-sidebar-resize { position: absolute; z-index: 2; top: 0; right: -4px; bottom: 0; width: 8px; cursor: col-resize; touch-action: none; }
49
65
  .oy-canvas-nav .oy-sidebar-resize:hover, .oy-canvas-nav .oy-sidebar-resize:focus-visible { background: var(--pod-nav-tab-line-selected-color, var(--color-brand1-6, #1677ff)); outline: 0; }
50
66
  .oy-canvas-nav .oy-sidebar-drag-cover { position: fixed; inset: 0; z-index: 9999; cursor: col-resize; user-select: none; }
51
- @media (min-width: 768px) {
52
- .oy-canvas-nav .oy-nav-sidebar[data-collapsed="true"] { width: var(--pod-nav-side-collapsed-width, 56px); }
53
- .oy-canvas-nav .oy-nav-sidebar[data-collapsed="true"] .oy-nav-brand { padding: 0; }
54
- .oy-canvas-nav .oy-nav-sidebar[data-collapsed="true"] .oy-nav-brand > :not(.oy-sidebar-toggle),
55
- .oy-canvas-nav .oy-nav-sidebar[data-collapsed="true"] .oy-sidebar-actions,
56
- .oy-canvas-nav .oy-nav-sidebar[data-collapsed="true"] .oy-nav-item[data-has-icon="true"] > span { display: none; }
57
- .oy-canvas-nav .oy-nav-sidebar[data-collapsed="true"] .oy-nav-item { justify-content: center; padding: 0 4px; }
58
- }
59
- @media (max-width: 767px) {
60
- .oy-canvas-nav .oy-nav-sidebar { width: 100%; border-right: 0; border-bottom: 1px solid var(--pod-nav-sub-divider-color, var(--color-line1-1, #ddd)); }
61
- .oy-canvas-nav .oy-nav-sidebar[data-mobile-open="false"] .oy-sidebar-items,
62
- .oy-canvas-nav .oy-nav-sidebar[data-mobile-open="false"] .oy-sidebar-actions { display: none; }
63
- .oy-canvas-nav .oy-sidebar-resize { display: none; }
64
- }
67
+ .oy-canvas-nav .oy-nav-sidebar[data-compact="false"][data-collapsed="true"] { width: var(--pod-nav-side-collapsed-width, 56px); }
68
+ .oy-canvas-nav .oy-nav-sidebar[data-compact="false"][data-collapsed="true"] .oy-nav-brand { padding: 0; }
69
+ .oy-canvas-nav .oy-nav-sidebar[data-compact="false"][data-collapsed="true"] .oy-nav-brand > :not(.oy-sidebar-toggle),
70
+ .oy-canvas-nav .oy-nav-sidebar[data-compact="false"][data-collapsed="true"] .oy-sidebar-actions,
71
+ .oy-canvas-nav .oy-nav-sidebar[data-compact="false"][data-collapsed="true"] .oy-nav-item[data-has-icon="true"] > span { display: none; }
72
+ .oy-canvas-nav .oy-nav-sidebar[data-compact="false"][data-collapsed="true"] .oy-nav-item { justify-content: center; padding: 0 4px; }
73
+ .oy-canvas-nav .oy-nav-sidebar[data-compact="true"] { width: 100%; border-right: 0; border-bottom: 1px solid var(--pod-nav-sub-divider-color, var(--color-line1-1, #ddd)); }
74
+ .oy-canvas-nav .oy-nav-sidebar[data-compact="true"][data-mobile-open="false"] .oy-sidebar-items,
75
+ .oy-canvas-nav .oy-nav-sidebar[data-compact="true"][data-mobile-open="false"] .oy-sidebar-actions { display: none; }
76
+ .oy-canvas-nav .oy-nav-sidebar[data-compact="true"] .oy-sidebar-resize { display: none; }
65
77
  `}</style>
66
78
  <div className="oy-nav-brand">{logo}{title ? <span>{title}</span> : null}
67
79
  <button className="oy-nav-item oy-sidebar-toggle" type="button" title={expanded ? '收起导航' : '展开导航'}
@@ -19,7 +19,7 @@ function CanvasNav({ items = [], activeKey, onSelect, title, logo, actions, chil
19
19
  .oy-nav-top .oy-nav-menu .oy-nav-item { min-height: var(--pod-nav-top-tab-height, 40px); padding: var(--pod-nav-top-tab-item-padding, 0 12px); max-width: var(--pod-nav-top-tab-item-max-width, 240px); }
20
20
  .oy-nav-top .oy-nav-menu .oy-nav-item > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
21
21
  .oy-nav-top .oy-nav-toggle { display: none; margin-left: auto; }
22
- @media (max-width: 767px) {
22
+ @container (max-width: 767px) {
23
23
  .oy-nav-top .oy-nav-header { gap: var(--pod-nav-logo-gap, 8px); }
24
24
  .oy-nav-top .oy-nav-toggle { display: flex; }
25
25
  .oy-nav-top .oy-nav-menu { order: 1; flex-basis: 100%; flex-direction: column; }
@@ -0,0 +1,104 @@
1
+ import React from 'react';
2
+
3
+ const DEFAULT_BREAKPOINTS = Object.freeze({ compact: 600, stacked: 768, wide: 1024 });
4
+
5
+ function getLayout(width, breakpoints) {
6
+ if (width < breakpoints.compact) return 'compact';
7
+ if (width < breakpoints.wide) return 'regular';
8
+ return 'wide';
9
+ }
10
+
11
+ /**
12
+ * Observe the actual content container instead of assuming it equals the browser viewport.
13
+ * Updates are coalesced with requestAnimationFrame and only committed when width/bucket changes.
14
+ */
15
+ function useContainerLayout(breakpoints = DEFAULT_BREAKPOINTS) {
16
+ const rootRef = React.useRef(null);
17
+ const frameRef = React.useRef(0);
18
+ const [measurement, setMeasurement] = React.useState({ width: 0, layout: 'regular' });
19
+
20
+ React.useLayoutEffect(() => {
21
+ const root = rootRef.current;
22
+ if (!root) return undefined;
23
+ let active = true;
24
+ const measure = () => {
25
+ if (!active) return;
26
+ if (frameRef.current) window.cancelAnimationFrame(frameRef.current);
27
+ frameRef.current = window.requestAnimationFrame(() => {
28
+ frameRef.current = 0;
29
+ if (!active) return;
30
+ const width = Math.round(root.getBoundingClientRect().width);
31
+ const layout = getLayout(width, breakpoints);
32
+ setMeasurement(current => current.width === width && current.layout === layout
33
+ ? current
34
+ : { width, layout });
35
+ });
36
+ };
37
+ measure();
38
+ const observer = window.ResizeObserver ? new window.ResizeObserver(measure) : null;
39
+ if (observer) observer.observe(root);
40
+ else window.addEventListener('resize', measure);
41
+ return () => {
42
+ active = false;
43
+ if (observer) observer.disconnect();
44
+ else window.removeEventListener('resize', measure);
45
+ if (frameRef.current) window.cancelAnimationFrame(frameRef.current);
46
+ };
47
+ }, [breakpoints.compact, breakpoints.wide]);
48
+
49
+ return { rootRef, ...measurement };
50
+ }
51
+
52
+ /**
53
+ * A reusable content shell for filters, primary/secondary panels, charts and wide tables.
54
+ * Business state stays above this shell, so crossing a breakpoint never remounts it.
55
+ */
56
+ function ResponsiveLayout({ filters, actions, primary, secondary, chart, table, breakpoints = DEFAULT_BREAKPOINTS }) {
57
+ const { rootRef, width, layout } = useContainerLayout(breakpoints);
58
+ return (
59
+ <section ref={rootRef} className="oy-responsive-root" data-layout={layout} data-container-width={width || undefined}>
60
+ <style>{`
61
+ .oy-responsive-root { container: oy-content / inline-size; min-width: 0; display: grid; gap: var(--oy-section-gap, 16px); color: var(--color-text1-4, #1f2329); }
62
+ .oy-responsive-root, .oy-responsive-root * { box-sizing: border-box; }
63
+ .oy-responsive-filters { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr)); gap: 12px; align-items: end; min-width: 0; }
64
+ .oy-responsive-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
65
+ .oy-responsive-panels { display: grid; grid-template-columns: minmax(0, 2fr) minmax(240px, 1fr); gap: 16px; align-items: stretch; min-width: 0; }
66
+ .oy-responsive-panel { min-width: 0; overflow-wrap: anywhere; }
67
+ .oy-responsive-chart { min-width: 0; min-height: 240px; }
68
+ .oy-responsive-table { min-width: 0; max-width: 100%; overflow-x: auto; overscroll-behavior-inline: contain; }
69
+ .oy-responsive-table > table { width: 100%; min-width: 720px; border-collapse: collapse; }
70
+ .oy-responsive-drawer-body { min-width: 0; container-type: inline-size; }
71
+ @container oy-content (max-width: ${breakpoints.stacked - 1}px) {
72
+ .oy-responsive-panels { grid-template-columns: minmax(0, 1fr); }
73
+ }
74
+ @container oy-content (max-width: ${breakpoints.compact - 1}px) {
75
+ .oy-responsive-filters { grid-template-columns: minmax(0, 1fr); }
76
+ .oy-responsive-actions > * { flex: 1 1 auto; min-width: 0; }
77
+ .oy-responsive-chart { min-height: 200px; }
78
+ }
79
+ `}</style>
80
+ <div className="oy-responsive-filters">{filters}<div className="oy-responsive-actions">{actions}</div></div>
81
+ <div className="oy-responsive-panels">
82
+ <div className="oy-responsive-panel">{primary}</div>
83
+ <aside className="oy-responsive-panel">{secondary}</aside>
84
+ </div>
85
+ <div className="oy-responsive-chart" data-chart-width={width || undefined}>{chart}</div>
86
+ <div className="oy-responsive-table" tabIndex={0} aria-label="可横向滚动的数据表格">{table}</div>
87
+ </section>
88
+ );
89
+ }
90
+
91
+ /** Example keeps filter state while the observed container crosses every breakpoint. */
92
+ export default function ResponsiveLayoutExample() {
93
+ const [keyword, setKeyword] = React.useState('');
94
+ return <ResponsiveLayout
95
+ filters={<label>关键词<input value={keyword} onChange={event => setKeyword(event.target.value)} /></label>}
96
+ actions={<><button type="button">查询</button><button type="button">清空</button></>}
97
+ primary={<div>主内容面板</div>}
98
+ secondary={<div>辅助信息与超长编号:OPENYIDA-RESPONSIVE-CONTAINER-CONTENT-IDENTIFIER</div>}
99
+ chart={<div role="img" aria-label="图表区域">图表应按 data-chart-width 调用自身 resize</div>}
100
+ table={<table><thead><tr><th>名称</th><th>状态</th><th>负责人</th><th>操作</th></tr></thead><tbody><tr><td>示例记录</td><td>进行中</td><td>当前用户</td><td><button type="button">查看</button></td></tr></tbody></table>}
101
+ />;
102
+ }
103
+
104
+ export { DEFAULT_BREAKPOINTS, ResponsiveLayout, getLayout, useContainerLayout };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openyida",
3
- "version": "2026.10.21",
3
+ "version": "2026.10.22-beta.0",
4
4
  "description": "OpenYida CLI - 宜搭低代码 AI 开发工具(安装即用,零配置)",
5
5
  "bin": {
6
6
  "openyida": "bin/yida.js",
@@ -307,6 +307,7 @@ openyida copy
307
307
 
308
308
  ## 子技能目录
309
309
 
310
+ 已有流程的当前任务查询、流程审批转交或管理员转交,直接加载 \`yida-data-management\`,使用 \`data query process-tasks\` 和 \`data transfer task\`,并回读实际处理人。修改流程中的转交操作权限才使用 \`yida-process-rule\`。
310
311
  原生酷卡片仅在用户明确指定宜搭原生酷卡片(交互/置顶卡片)或继续已明确上下文时加载 \`yida-cool-card\`。它用 cardPageCode/cardTemplateId;CARD 资源和 dataCardDesigner 属于 \`yida-data-card\`,Canvas 展示卡片归页面。酷卡片 CLI 负责创建、读取与模板查询,返回官方入口由用户配置保存,再由 CLI 回读;不代替编辑器保存、发布或发送,不修改权益或官方卡片开关。
311
312
  原生数据卡片仅在用户显式指定宜搭原生数据卡片,或继续已明确的原生卡片操作时加载 \`yida-data-card\`。泛称卡片、指标卡、看板、投放或数据播报不触发;完整应用搭建也不自动添加该资源。自定义页面内的卡片随页面发布;页面虽有 CARD 引用,只改外层仍属页面任务,明确修改源卡片才进入原生卡片技能。
312
313
 
@@ -16,7 +16,7 @@ Fast / Plan 选定的主题和主色属于整个应用:导航、原生页、
16
16
 
17
17
  ## 本地使用问题与贡献反馈
18
18
 
19
- 用户遇到反复报错、不会提 Issue/PR、没有 GitHub 账号或没有 OpenYida 源码,先加载 `yida-feedback`。
19
+ 用户反馈 OpenYida 使用问题或体验建议、想贡献改进、不会提 Issue/PR,先加载 `yida-feedback`;没有 GitHub 账号或源码也可先整理材料。
20
20
  它负责基于当前证据分析根因、生成脱敏本地材料,并按用户授权提交 Issue 或个人 Fork PR。无账号先引导注册,实在不行提示“反馈到AI开发者群”。
21
21
  自动发现重复失败只建议并整理本地草稿,不自动公开提交。网页关联本地 Agent 异常先按下节诊断,需要反馈时再进入此技能。普通宜搭产品 VOC 清单仍用 `yida-voc`。
22
22
 
@@ -98,6 +98,8 @@ Fast / Plan 选定的主题和主色属于整个应用:导航、原生页、
98
98
  | 登录态可用 | 进入 Step 2 |
99
99
  | 登录态缺失 | 按快照提示补登录态;未恢复前停止资源写操作 |
100
100
 
101
+ 千问云端身份接入开发或宿主身份诊断,可显式运行 `openyida auth qwenwork user-info --json`;它只验证千问身份,不建立宜搭登录态,不能据此跳过上述宜搭 auth snapshot 检查。详细边界见 `yida-login`。
102
+
101
103
  ### Step 2:解析资源上下文
102
104
 
103
105
  任何写操作前先解析目标 app/page/form/process。按本轮显式资源、外部绑定资源、workspace cache/config、会话历史的顺序选择目标;同级冲突或目标不明才询问用户。
@@ -159,7 +161,7 @@ Fast / Plan 选定的主题和主色属于整个应用:导航、原生页、
159
161
  | --- | --- |
160
162
  | 从零搭一个完整应用/系统 | `yida-app`;先分析需求并完成首次搭建确认,再规划与搭建 |
161
163
  | 已有 app 但没有任何页面,需要补成完整系统 | `yida-app`;属于首次搭建,同样先询问搭建方式,复用已有 `appType` 补齐资源 |
162
- | OpenYida 反复报错、不会提 Issue/PR、没有账号或源码 | `yida-feedback`;先脱敏本地报告,个人 Fork 仅贡献公开 OpenYida 修复 |
164
+ | OpenYida 使用问题、体验建议、希望贡献改进或不会提 Issue/PR | `yida-feedback`;先整理脱敏材料,成功提交 Issue 后可选邀请 PR,贡献代码使用个人 Fork |
163
165
  | 读取钉钉在线文档正文 | `yida-document-markdown`,使用登录态接口获取 Markdown |
164
166
  | 按 taskUuid 读取钉钉听记 | `yida-tingji`,将听记任务 ID 原样传入命令 |
165
167
  | 将钉钉开放平台官方 API 接入宜搭或 Canvas | `yida-dingtalk-openapi`;密钥不进聊天,账号由用户在宜搭或本机 TTY 配置 |
@@ -180,6 +182,7 @@ Fast / Plan 选定的主题和主色属于整个应用:导航、原生页、
180
182
  | 流程设计器字段列表空白 | `yida-process-rule`,先用 `process fields` 检查字段来源 |
181
183
  | 查字段 ID / 保存 Schema 证据 | `yida-get-schema`;凡涉及 fieldId 的数据、流程、公式、页面代码先取证 |
182
184
  | 改表单数据记录 | `yida-data-management`,不是 `yida-create-form-page` |
185
+ | 查询流程当前任务、管理员转交已有流程任务 | `yida-data-management`;先查询 process-tasks,再 transfer task 并回读实际处理人 |
183
186
  | 查询指定应用/流程表单中当前审批人为某人的实例或条数 | `yida-data-management`;`data query process --current-approver <userId> [--count]`,仅统计当前登录人经服务端授权可见的实例 |
184
187
  | 配置或修改公式、字段计算、提交校验、节点筛选、报表计算 | 共用 `yida-formula`,按实际保存内容读取说明;查看字段公式、查参数和检查用 `yida-formula-evaluate` |
185
188
  | 提交后跨表写入/更新/删除 | 默认 `yida-integration`;用户明确要业务关联规则/高级函数时用 `yida-business-rule` |
@@ -11,7 +11,11 @@ description: 宜搭自定义页面开发规范,使用 `YidaCodeCanvas` 组件
11
11
 
12
12
  待办中心或审批任务区块只有在用户明确要求自定义实现,或经 ask_human 明确同意后才交接 `yida-task-center`;已有 Canvas、普通工作台需求或 Fast 模式不构成选择。未选择时保留原生能力,不直接手写任务中心。
13
13
 
14
- 布局实现读取 [共享响应式规则](../yida-design/references/responsive-layout.md):按实际容器宽度重排筛选栏和主辅面板,统一同组控件尺寸,宽屏按需等高、窄屏自然增长,宽表格局部滚动。断点变化保留原组件状态与业务行为,发布前按该文档验收。
14
+ 布局实现分三步,详细规则见 [共享响应式规则](../yida-design/references/responsive-layout.md):
15
+
16
+ 1. **读取设计**:从 `design.md` 读取当前页的首屏焦点、布局、主操作、响应式和验收要求。
17
+ 2. **实现布局**:优先用 CSS 按内容容器宽度重排。需要参考时执行 `openyida sample openyida-page-template canvas-responsive-layout`,按需复用与当前业务对应的布局片段;图表等组件需要读取实际宽度时,再复用测量 Hook。视口媒体查询用于浏览器视口或输入方式判断。
18
+ 3. **验证状态**:切换宽度前后保留原组件状态与业务行为,发布前按共享规则检查实际页面。
15
19
 
16
20
  已有页面的美化或 UI 优化,先进入 [ui-improve](../yida-design/sub_skill/ui-improve/SKILL.md)。只有当前视觉方案已通过 `ask_human` 确认且原状态备份可恢复,才修改源码;交接需包含获批 revision、范围、备份和功能契约。本轮仅按视觉差异修改,保留现有业务表达式、事件、数据绑定、URL、状态管理和打开方式,不套用下文的新页面默认规则改变业务。
17
21
 
@@ -5,7 +5,11 @@ description: JSX 自定义页面开发规范(React 16 平台 Jsx 组件、expo
5
5
 
6
6
  # JSX 自定义页面开发
7
7
 
8
- 布局实现读取 [共享响应式规则](../yida-design/references/responsive-layout.md):按实际容器宽度重排筛选栏和主辅面板,统一同组控件尺寸,宽屏按需等高、窄屏自然增长,宽表格局部滚动。使用原 JSX 运行时支持的样式能力,断点变化保留原组件状态与业务行为,发布前按该文档验收。
8
+ 布局实现分三步,详细规则见 [共享响应式规则](../yida-design/references/responsive-layout.md):
9
+
10
+ 1. **读取设计**:从 `design.md` 读取当前页的首屏焦点、布局、主操作、响应式和验收要求。
11
+ 2. **实现布局**:使用原 JSX 运行时支持的样式能力,按需复用与当前业务对应的布局片段,实现宽窄排列、控件对齐和局部滚动。
12
+ 3. **验证状态**:切换宽度前后保留原组件状态与业务行为,发布前按共享规则检查实际页面。
9
13
 
10
14
  已有页面美化先进入 [ui-improve](../yida-design/sub_skill/ui-improve/SKILL.md):视觉方案经 `ask_human` 确认、备份可恢复后才修改。沿用获批范围和功能契约,保留原 JSX 运行时、业务表达式、数据源、事件及入口行为;下文的新开发默认规则不用于顺手重构已有业务。不满意时由该流程恢复本轮前状态。
11
15
 
@@ -57,7 +61,7 @@ UI 组件及 JSX 展示结构允许替换,业务函数及业务状态实现不
57
61
  4. **默认 Tailwind 风格层 + native 控件 reset**:面向用户的自定义页面默认使用 Tailwind utility className 组织视觉层,并默认导入 Tailwind preflight;同时必须保留 `openyida-native-control-reset` 或等效页面级样式,覆盖 input/textarea/select/自定义下拉的 focus、appearance、font-weight 和 shadow,避免浏览器黑色粗边。reset 使用 `.oyd-page` 通用作用域时可以复用全局 id,但不能因为同名 style 已存在就跳过更新;如果页面使用 `.oyd-grade-page`、`.oyd-data-page` 等自定义作用域,style id 必须页面专属,避免多 native 页面切换时下拉选项和 SVG 勾选样式丢失。运行时脚本只允许使用已验证的 `g.alicdn.com` 或企业自托管地址,未配置有效地址时走内联兜底样式
58
62
  5. **DateField 时间戳格式**:日期字段值必须是时间戳(毫秒),不能是字符串
59
63
  6. **forceUpdate 后延迟操作 DOM**:`forceUpdate()` 后 DOM 不会立即更新,ECharts/Canvas/第三方组件初始化必须放入 `setTimeout` 或 `requestAnimationFrame`
60
- 7. **多端适配**:使用 `this.utils.isMobile()` 判断设备类型,适配 PC 和移动端
64
+ 7. **多端适配**:按实际内容容器宽度决定布局;`this.utils.isMobile()` 用于选择平台移动入口等设备相关行为。触控与键盘操作按当前设计及共享响应式规则实现。
61
65
  8. **输入法组合输入处理**:使用 `_isComposing` 标记配合 `compositionstart`/`compositionend` 事件,避免输入过程中触发提交
62
66
  9. **表单打开入口统一容器**:数据列表用 `workbench/{formUuid}?iframe=true`,禁止用 `formDetail` 冒充列表;新增/提交/查看详情入口统一封装为 `FormOpenContainer`。PC 端默认用半屏 `50vw` 抽屉 iframe 承载页面级隐藏导航的 `submission/{formUuid}?isRenderNav=false` 或 `formDetail/{formUuid}?formInstId=...&navConfig.layout=1180&isRenderNav=false`,提交页和详情页使用同一宽度规则;详情实例 ID 必须优先取 `row.formInstId`,缺失时禁用或提示,不打开空 `formInstId`;移动端才整页或新页打开原生表单页;不要在按钮里直接 `window.open`
63
67
  10. **Tabs 显隐控制**:下拉值变更后自动回退到第一个可见 Tab,内容区用 `display: none` 保留 DOM
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: yida-data-management
3
- description: 查询、新增、更新宜搭表单和流程数据,按当前审批人查询流程实例或统计条数,精确删除普通表单单条记录。支持日期为空、日期区间端点、多选数量、子表条件及服务端分页;表单使用 /v1/form/,流程使用 /v1/process/。
3
+ description: 查询、新增、更新宜搭表单和流程数据,按当前审批人查询流程实例或统计条数,精确删除普通表单单条记录,查询指定流程当前任务并执行管理员转交。支持日期为空、日期区间端点、多选数量、子表条件及服务端分页;表单使用 /v1/form/,流程使用 /v1/process/。
4
4
  ---
5
5
 
6
6
  # 数据管理
@@ -50,7 +50,7 @@ description: 查询、新增、更新宜搭表单和流程数据,按当前审
50
50
  ## 严格要求 (MUST DO)
51
51
 
52
52
  - 操作前先用 query 命令确认目标数据存在
53
- - 所有 create/update/delete/execute 写操作必须携带只读预检确认的资源名称和类型;CLI 校验不一致时停止,不得换目标重试
53
+ - 所有 create/update/delete/execute/transfer 写操作必须携带只读预检确认的资源名称和类型;CLI 校验不一致时停止,不得换目标重试
54
54
  - 新增记录、生成测试数据、批量导入或发起流程必须遵守上方“创建/录入数据强制闭环”
55
55
  - 批量操作单次不超过 30 条记录;多条实例必须逐条/分批执行 `openyida data create ...`
56
56
  - 删除普通表单记录前,必须先执行 `openyida data get form <appType> --inst-id <formInstId> --form-uuid <formUuid> --json`,核对返回实例 ID 与目标表单,并向用户展示记录数量、标题/关键字段和实例 ID。宿主支持并会在执行删除命令前触发 runtime permission/approval 时,直接发起一次带 `--confirm` 的受保护删除命令,由该唯一权限卡完成用户确认;无需且不得预先调用 `ask_human` 或其他业务确认工具。宿主不支持 runtime permission/approval 时,必须先通过 `ask_human` 或等价交互工具获得用户明确确认;只有用户确认后才能执行删除,用户拒绝或未确认时不得执行
@@ -98,6 +98,7 @@ description: 查询、新增、更新宜搭表单和流程数据,按当前审
98
98
  - "查询数据"、"新增记录"、"更新数据"
99
99
  - "查看表单实例"、"发起流程"
100
100
  - "录入数据"、"批量导入"、"查询待办任务"
101
+ - "查询流程当前任务"、"管理员转交"、"把这条流程转交给某人"
101
102
  - "按当前审批人统计"、"某人的待审批流程条数"、"查询指定审批人的流程实例"
102
103
 
103
104
  > ⚠️ 表单接口(`/v1/form/`)和流程接口(`/v1/process/`)不能混用。
@@ -216,6 +217,23 @@ openyida data query tasks <appType> --type todo|created|done|cc --normalized [--
216
217
 
217
218
  只读数量、流程详情和审批历史可独立使用 `task-center count / inspect`,无需选择自定义页面;返回含义与按需装配见[流程任务能力](../yida-canvas-data-binding/references/workflow-capabilities.md)。
218
219
 
220
+ ### 流程当前任务与管理员转交
221
+
222
+ ```bash
223
+ openyida data query process-tasks <appType> --process-inst-id <processInstanceId> --json
224
+ openyida data transfer task <appType> --process-inst-id <processInstanceId> --form-uuid <formUuid> --to-user-id <userId> --expect-form-name <真实表单名称> --expect-form-type process [--remark '用户说明的理由'] [--task-id <taskId>] --json
225
+ ```
226
+
227
+ - 这是管理员对已有流程任务的转交;审批同意/拒绝仍使用 `execute task`,配置转交操作权限使用 `yida-process-rule`,流程代理使用 `yida-agent-center`。
228
+ - 先查询流程确认实例及表单,再查询 `process-tasks` 展示任务、节点和实际处理人;明确接收人的真实 userId,名称有歧义时不能猜选。不要用“我的待办”代替指定流程的管理员任务查询。
229
+ - 用户未说明转交理由时,省略 `--remark`,CLI 固定写“转交”;用户说明了理由时,使用其原话传入 `--remark`。不要自行添加“按用户要求”等理由,也不要为缺少理由追问用户。
230
+ - `taskId` 来自任务接口 `content[].id`,CLI 同时输出字符串 `taskId`;禁止使用 `currentActivityInstances[].id`、`taskSequenceId`、`taskGroupId` 或从 URL 猜测任务 ID。
231
+ - CLI 会在写前重新查询任务:只有一个运行任务时可省略 `--task-id`,多个任务必须明确指定;任务已结束或不属于目标实例时停止。
232
+ - 完成以 `transferred=true && readbackVerified=true` 为准;`ALREADY_ASSIGNED` 表示任务已由接收人处理,本次没有转交写入。接口 `success=true/content=null` 仅代表受理,CLI 还必须回读确认实际处理人。
233
+ - 收到 `RESULT_UNKNOWN` / `UNVERIFIED`、提交超时或鉴权异常时,仅按错误 `details.nextStep` 只读回查,禁止自动重放转交。此规则优先于下方通用网络重试策略。
234
+
235
+ 接口、回读规则与错误恢复见 [流程任务查询与管理员转交](references/process-task-transfer.md)。
236
+
219
237
  ## 接口总览
220
238
 
221
239
  ### 表单实例
@@ -244,6 +262,8 @@ openyida data query tasks <appType> --type todo|created|done|cc --normalized [--
244
262
  | `updateInstance` | POST | 更新 |
245
263
  | `getOperationRecords` | GET | 审批记录 |
246
264
  | `executeTask` | POST | 执行任务 |
265
+ | `getInstTaskDatas` | GET | 管理员查询指定流程任务,使用 query/formProcInstData 路径 |
266
+ | `redirectTask` | POST | 管理员转交任务,使用 query/instance 路径,单次提交并回读 |
247
267
 
248
268
  ### 任务中心
249
269
 
@@ -396,11 +416,12 @@ openyida data create form APP_xxx FORM-商机表 --expect-form-name 商机表 --
396
416
  | 错误类型 | 默认处理策略 |
397
417
  |---------|-------------|
398
418
  | 命令执行失败 | 停止执行,向用户展示错误信息,询问是否重试或调整参数 |
419
+ | 管理员转交失败或结果未验证 | 禁止自动重放转交;按 details.nextStep 只读核查并报告 mutationAccepted/readbackVerified,后续写入须重新核对当前任务和用户意图 |
399
420
  | 参数缺失(appType/formUuid/fieldId 等) | 主动询问用户补充,或引导用户使用 `yida-get-schema` 获取 |
400
421
  | 权限不足 / 登录态失效 | 停止执行,提示用户执行 `openyida login` 重新登录 |
401
422
  | 字段 ID 无效 | 停止执行,引导用户执行 `openyida get-schema` 获取真实字段 ID |
402
423
  | 删除操作前 | 必须先展示操作摘要(数量 + 关键字段);宿主支持 runtime permission/approval 时直接发起一次受保护命令,不得预先调用 `ask_human`;宿主不支持时,必须先通过 `ask_human` 或等价交互工具获得用户明确确认 |
403
424
  | QPS 超限 | 降低请求频率,单次批量操作不超过 30 条,间隔 1 秒重试 |
404
425
  | 表单/流程接口混用 | 停止执行,提示用户检查接口类型(表单用 `/v1/form/`,流程用 `/v1/process/`) |
405
- | 网络超时 | 重试 1 次,仍失败则停止并提示用户检查网络 |
426
+ | 网络超时 | 查询可重试 1 次;管理员转交禁止自动重试提交,仅按 nextStep 只读回查,仍无法验证则报告结果未知 |
406
427
  | 未知错误 | 停止执行,完整展示错误信息,建议用户反馈问题 |
@@ -0,0 +1,53 @@
1
+ # 流程任务查询与管理员转交
2
+
3
+ 现有 `data query form/process` 列表接口保持不变。以下接口属于管理员任务操作,不使用浏览器 Cookie 或抓包中的 CSRF token;复用 CLI token session、环境 baseUrl 和受管任务授权。`byManager=true` 不授予管理员权限,平台仍校验当前身份。
4
+
5
+ ## 查询指定流程任务
6
+
7
+ `openyida data query process-tasks <appType> --process-inst-id <processInstanceId> --json`
8
+
9
+ - GET `/dingtalk/web/<appType>/query/formProcInstData/getInstTaskDatas.json`
10
+ - `_api=DataManage.getInstTasks`,`procInstId=<processInstanceId>`。
11
+ - 成功响应 `content` 为任务数组,空数组表示未查到任务;查询失败不能当成无任务。
12
+ - 每条任务 `processInstanceId` 必须等于目标实例,`id` 必须是正整数,重复 ID 或不合法结构停止操作。
13
+ - CLI 保留原始任务字段,并新增字符串 `taskId`,由 `id` 转换;不会解析任务 URL。
14
+
15
+ | 返回字段 | 含义 |
16
+ | --- | --- |
17
+ | `id` / `taskId` | 转交目标任务 ID |
18
+ | `status` | 仅 `RUNNING` 可转交 |
19
+ | `activityCode` / `groupName` | 所属节点 / 节点名称 |
20
+ | `actualActionerId` / `actualActionerDisplayName` | 当前实际处理人 |
21
+ | `originalActionerId` | 原始处理人,不用于验证转交后的接收人 |
22
+ | `taskGroupId` | 任务组标识,不是任务 ID |
23
+
24
+ ## 管理员转交
25
+
26
+ ```bash
27
+ openyida data transfer task APP_xxx --process-inst-id <processInstanceId> --form-uuid FORM-xxx --to-user-id <真实接收人userId> --expect-form-name <真实表单名称> --expect-form-type process --task-id <taskId> [--remark '用户说明的理由'] --json
28
+ ```
29
+
30
+ - POST `/dingtalk/web/<appType>/query/instance/redirectTask.json`。
31
+ - `_api=DataManage.redirectTask`;表单编码参数为 `taskId`、`procInstId`、`nowActionerId`(接收人)、`remark`、`byManager=true`。
32
+ - 用户未说明理由时,省略可选参数 `--remark`,CLI 默认备注为“转交”;用户说明理由时,原样传入 `--remark`,不添加前缀或自行编写理由。显式空备注或只有 `--remark` 没有值属于参数错误。
33
+ - 写前校验资源名称、类型及实例归属,再重查任务。只有一个运行任务时可省略 taskId;多个运行任务必须明确选择,禁止默认选择第一条。
34
+ - 不修改业务字段,不需要为转交准备 data-json/data-file 或字段映射;接收人必须来自用户明确指定或真实通讯录解析,不得编造。
35
+ - 接收人已是该任务的实际处理人时,返回 `ALREADY_ASSIGNED`、`mutationPerformed=false`,不提交转交。
36
+ - 单次提交,不因登录刷新、网络超时或空响应自动重放。`success=true/content=null` 是正常受理响应。
37
+
38
+ ## 回读与结果
39
+
40
+ 受理后立即重新查该流程任务。以下证据才算转交已验证:
41
+
42
+ 1. 原 taskId 仍为 RUNNING,且 `actualActionerId` 等于接收人;或
43
+ 2. 原任务不再运行,且同 `activityCode/taskGroupId` 出现唯一新增 RUNNING 任务,实际处理人为接收人。已有并行任务、仅 originalActionerId 匹配、旧任务消失但无新任务,都不足以证明成功。
44
+
45
+ | 结果 | 行为 |
46
+ | --- | --- |
47
+ | `TRANSFERRED` | `transferred=true`、`mutationAccepted=true`、`readbackVerified=true`;输出原 taskId 与 currentTaskId |
48
+ | `ALREADY_ASSIGNED` | `transferred=false`、`readbackVerified=true`,无需转交写入 |
49
+ | `REJECTED` | 平台明确拒绝,报告错误,核对权限与任务状态 |
50
+ | `RESULT_UNKNOWN` | 提交超时、鉴权异常、非 JSON 等;仅按 details.nextStep 只读回查,禁止自动重放 |
51
+ | `UNVERIFIED` | 已受理但回读失败或不能确认接收人;仅按 details.nextStep 只读回查,禁止自动重放 |
52
+
53
+ 不得把命令退出成功、旧任务消失、列表 actionerId 为空或仅接口受理当作转交验收通过。不能证明结果时如实报告,不改用一次性脚本或浏览器请求绕过 CLI。
@@ -68,7 +68,11 @@ const DENSITY_CONFIG = {
68
68
 
69
69
  ## React Hooks 实现
70
70
 
71
- 在 Canvas 中用 `matchMedia` + hooks 管设备变化。不要在首次渲染时读一次宽度后永不更新。
71
+ 在 Canvas 中分别处理密度选择和布局宽度:
72
+
73
+ - `matchMedia` + hooks 监听浏览器视口或输入方式的变化。下方示例用视口宽度切换密度;触控能力按对应媒体条件判断。
74
+ - 页面分栏、筛选栏和主辅面板按实际内容容器宽度重排,iframe、侧栏或抽屉均读取自身可用空间。
75
+ - 按 [共享响应式规则](../yida-design/references/responsive-layout.md#复用模板与-cli-诊断) 优先用 CSS 重排。需要参考时执行 `openyida sample openyida-page-template canvas-responsive-layout`,按需复用布局片段;组件需要读取实际宽度时,再复用带清理的测量 Hook,持续响应容器变化。
72
76
 
73
77
  ```jsx
74
78
  import React, { useEffect, useMemo, useState } from 'react';
@@ -155,3 +159,4 @@ export function didMount() {
155
159
  - 移动端固定 `spacious` 且隐藏切换 UI。
156
160
  - 所有密度相关值来自统一 token。
157
161
  - `matchMedia` 监听或其他响应式副作用有 cleanup。
162
+ - 桌面宽视口中的窄 iframe、侧栏和抽屉按自身宽度重排;分别验证设备对应的密度和容器对应的布局。
@@ -20,7 +20,7 @@ description: >
20
20
 
21
21
  所有页面按[任务驱动的视觉设计](references/task-driven-visual-design.md)从主要任务和信息关系推导表达、构图与视觉重点,主动判断图片的识别、解释和氛围价值,再确定密度与降级。
22
22
 
23
- 自定义页面按 [响应式布局](references/responsive-layout.md) 明确容器宽度变化后的换行、堆叠、控件对齐和局部滚动;等高只用于宽屏下需要成组展示的面板,窄屏恢复自然高度。
23
+ 自定义页面按 [响应式布局](references/responsive-layout.md) 先明确任务和需要同时看见的信息,再安排宽屏、窄容器和触屏下的内容与操作。结果写入已有首屏焦点、布局、主操作、响应式和验收字段。宽屏中需要成组展示的面板可等高,窄屏随内容自然增长。
24
24
 
25
25
  **阅读无障碍是页面设计的基本要求。** 导航、正文、表单、详情、报表及抽屉等浮层都要让用户看得清、读得懂,放大或使用读屏时仍能获取完整信息。不能为风格、密度或装饰牺牲阅读;按 [阅读无障碍门禁](references/page-quality-gates.md#阅读无障碍门禁) 将具体要求写入页面设计与验收。
26
26
 
@@ -1,15 +1,44 @@
1
1
  # 自定义页面响应式布局
2
2
 
3
- 设计、Canvas、历史 JSX 和已有页面美化共用本规则。响应式先保证内容与操作在当前容器中可用;等高只用于宽屏下需要成组展示的面板,不要求所有区块一样高。保留项目主题和密度,规则适用于门户、工作台、列表、详情以及抽屉中的自定义内容。
3
+ 设计、Canvas、历史 JSX 和已有页面美化共用本规则。让内容和操作适应当前容器,保留项目主题和密度。门户、工作台、列表、详情和抽屉中的自定义内容均按下文设计、实现和验收。
4
4
 
5
- ## 先确定可用宽度和重排方式
5
+ ## 先确定任务与各场景的呈现
6
6
 
7
- 在已有 `design.md` 的布局、响应式和验收段落写清:哪些区域并排、何时换行或堆叠、堆叠后的顺序、表格如何滚动、哪些面板需要等高。不新增一份设计文件,不用“支持移动端”代替具体行为。
7
+ Fast、Plan 和单页设计共用以下五步。先读 PRD 中的用户、页面任务、必要信息和操作,再决定各场景的布局。结果统一写入下方已有设计字段。
8
8
 
9
- - 按内容容器的实际宽度设计,考虑平台侧栏展开/收起、拖拽调宽、抽屉和 iframe;不能只按设备名或屏幕宽度判断。宽屏中的窄抽屉也可能需要单列。
10
- - 根据控件最小可用宽度、文字长度、列间距和主内容阅读宽度决定重排点。复用已有项目断点;断点未定义时按实际内容确定并记录,不把固定的手机/平板分界当作所有页面的标准。
9
+ 1. **明确任务和必要信息**:判断本页主要用于识别、比较、分析、逐条处理还是录入,列出行动前需要同时看见的信息。根据已有需求说明选择依据;断点和组件由 Agent 判断。仅在缺失事实会改变任务或范围时向用户澄清。
10
+ 2. **安排首屏与主操作**:写清宽屏和窄容器中首先展示的业务内容、主操作位置。为核心内容保留首屏空间,按实际需要分配标题、装饰和筛选区的高度。筛选较多时,可外露常用条件,将低频条件放入展开区,并保留展开入口、已选条件和清除方式。主按钮随操作路径放置,固定底部只用于适合的场景。
11
+ 3. **按信息关系选择形式**:跨记录比较优先使用对齐表格并保留记录标识;逐条处理可使用摘要列表与详情,写清摘要字段和完整信息入口。趋势图随空间调整图例、标签密度和注释,完整数据保持可查。表格转摘要、筛选收纳均按本页任务选择。
12
+ 4. **分别处理空间和输入方式**:按容器宽度决定并排、堆叠和比例,按触控、键盘、悬停能力及可用高度调整操作。桌面窄抽屉和手机可以共用单列布局,各自保留适用的交互;触屏空间充足时也可并排。手机操作提供直接点击的入口,并检查软键盘、安全区域、可见焦点和触控间距。
13
+ 5. **保持风格和操作状态**:各场景沿用文字层级、色彩用途、形状和分组,按任务调整密度与比例。正文和表单限制阅读宽度,比较型工作区利用可用宽度,辅助面板按内容确定宽度。返回列表或放宽容器后,恢复筛选、页码、选中项、草稿和焦点。
14
+
15
+ ### 写入已有设计字段
16
+
17
+ | 已有字段 / design.md 要点 | 写入内容 |
18
+ | --- | --- |
19
+ | `firstScreenFocus` / 首屏焦点 | 首先看到的业务对象、判断信息及强调方式;窄屏如何保持重点 |
20
+ | `layout` / 布局 | 区块主次、需同时可见的信息、宽度依据,以及内容变多或文字变长时的排列方式 |
21
+ | `primaryAction` / 主操作 | 主次动作位置、收纳入口、反馈与返回路径 |
22
+ | `responsive` / 响应式 | 宽屏、窄容器、触屏的实际变化和理由;重排条件、信息入口及状态保留 |
23
+ | `acceptanceChecks` / 验收 | 具体操作和预期结果,覆盖核心内容、长内容、完整操作路径和状态保留 |
24
+
25
+ 例如财务对账页可写:“核对账面金额与实收金额,差额列与记录标识相邻;窄容器仍保留列对齐,表格局部横滑且有剩余列提示。查询为主操作,清空为次操作;宽窄切换后已选记录和筛选值不变。”逐条处理页则可选择摘要与详情。按这种具体程度描述当前页面,配色、布局和业务字段沿用本页设计。
26
+
27
+ ### 新建与已有页面的边界
28
+
29
+ - 新页面在已授权任务和平台能力内设计呈现方式。
30
+ - 已有页面保留原字段、操作、模块、权限和业务状态。折叠内容提供明确入口,所有宽度下均可访问完整信息;禁止用宽度变化静默删除内容。
31
+ - 调整表格形式、筛选收纳或打开方式前,先写入设计并按已有页面优化流程确认、备份后实施。尚未确认这些调整时,沿用原表现,仅作换行、堆叠和局部滚动。
32
+ - 超出 UI 范围的交互或业务变更交回 PRD,获得相应授权后再实施;响应式改造授权不包含业务变更。
33
+
34
+ ## 再确定可用宽度和重排方式
35
+
36
+ 在已有 `design.md` 的布局、响应式和验收段落写清:哪些区域并排、何时换行或堆叠、堆叠后的顺序、表格如何滚动、哪些面板需要等高。设备适配说明复用这份文档和已有字段,写出具体行为。
37
+
38
+ - 按内容容器的实际宽度设计,覆盖平台侧栏展开/收起、拖拽调宽、抽屉和 iframe。宽屏中的窄抽屉也按自身空间选择单列或并排。
39
+ - 根据控件最小可用宽度、文字长度、列间距和主内容阅读宽度决定重排点。优先复用项目断点;项目未定义时,按本页内容确定并记录。
11
40
  - 优先用 Grid/Flex 的自动换行和可收缩列解决布局;子项设 `min-width: 0`,需要弹性宽度的网格列使用 `minmax(0, 1fr)`。仍需分级变化时,在运行环境支持的前提下使用容器查询;使用媒体查询时额外核验窄抽屉等容器场景。
12
- - 重排保持业务阅读顺序、DOM 顺序和键盘焦点顺序一致,不用 CSS `order` 制造视觉顺序与操作顺序相反的布局。不因宽度变化隐藏原有字段、操作或模块。
41
+ - 重排保持业务阅读顺序、DOM 顺序和键盘焦点顺序一致;使用 CSS `order` 时也核对这三种顺序。原有字段、操作和模块保持可达,收纳内容遵守上方确认与入口规则。
13
42
 
14
43
  ## 筛选栏和组合控件
15
44
 
@@ -17,38 +46,56 @@
17
46
  | --- | --- |
18
47
  | 足够并排 | 标签和控件分层排列;同一行控件外框上下边缘对齐,按钮与输入控件底边对齐 |
19
48
  | 一行放不下 | 字段组自然换行,操作区跟随换行;保留字段最小可用宽度,按钮文案完整可见 |
20
- | 窄容器 | 字段按顺序纵向排列并撑满可用宽度;查询、清空等操作按可用空间并排或换行,不挤出容器 |
49
+ | 窄容器 | 字段按顺序纵向排列并撑满可用宽度;查询、清空等操作在容器内并排或换行 |
21
50
 
22
- - 同组 Input、Select、日期控件和 Button 使用一致的组件尺寸与主题高度;统一标签字号、行高和标签到控件的间距。`Input.Search` 等组合控件同时检查外层、内部输入框、附加按钮和图标,不能只改外层高度。
23
- - 标签、控件、帮助/错误信息分别占自己的布局位置。同排字段可共享标签行和控件行;长标签换行时由布局容纳最高标签,按钮仍按控件行定位,不能用各字段整个盒子的底边对齐导致错误信息把输入框顶歪。
24
- - 无标签的操作区按控件行定位,不插入可读的假标签、连续空格或固定 `margin-top` 凑齐。帮助/错误信息在控件下自然增长,不覆盖下一行。
25
- - 单行控件按主题高度保持一致;多行输入、长标签和反馈内容允许增长。不能为等高裁切文字,也不能缩小字体或压缩点击区域来维持一行。
26
- - CSS 仅作用于当前页面或组件作用域;组件库内部结构使用当前版本支持的配置/样式入口,不能用全局 input/button 选择器改变原生表单。
51
+ - 同组 Input、Select、日期控件和 Button 使用一致的组件尺寸与主题高度;统一标签字号、行高和标签到控件的间距。`Input.Search` 等组合控件一起检查外层、内部输入框、附加按钮和图标的尺寸。
52
+ - 标签、控件、帮助/错误信息各占独立布局位置。同排字段可共享标签行和控件行;标签行容纳最长标签,按钮与控件行对齐。错误信息在控件下方展开,输入框继续保持对齐。
53
+ - 无标签的操作区直接用布局对齐控件行。对齐由 Grid/Flex 完成,标签、空格和外边距仅承担各自的内容或间距用途。帮助/错误信息随内容增长,并为下一行留出空间。
54
+ - 单行控件按主题高度保持一致;多行输入、长标签和反馈内容自然增长。空间不足时换行或堆叠,保留完整文字、可读字号和可用点击区域。
55
+ - CSS 限定在当前页面或组件内;组件库内部结构使用当前版本支持的配置/样式入口。禁止用全局 input/button 选择器改变原生表单。
27
56
 
28
57
  ## 主辅面板与分页
29
58
 
30
- - 宽屏下,同一排且需要形成整体的主辅面板采用 Grid/Flex 拉伸外框;卡片内部纵向排列,主体可伸展,分页/底部操作用剩余空间置底。表格行和列表项保持自然高度,不跟着面板平均拉长。
31
- - 不写死面板像素高度,不用脚本测量另一张卡片再复制高度,不用空占位、假数据或巨大外边距补齐。不相关区块、连续文章和瀑布流不强制等高。
59
+ - 宽屏下,同一排且需要形成整体的主辅面板采用 Grid/Flex 拉伸外框;卡片内部纵向排列,主体可伸展,分页/底部操作用剩余空间置底。表格行和列表项保持自然高度。
60
+ - 面板高度由真实内容和 CSS 布局决定,等高交给 Grid/Flex。独立区块、连续文章和瀑布流保持各自的自然高度。禁止添加假数据填补空白。
32
61
  - 宽度不足时主辅面板转为上下排列;按已确定的任务顺序展示,清除用于桌面等高的固定高度、最小高度和空白占位。单列时各面板由实际内容决定高度,分页接在内容之后。
33
- - 内容差异过大时调整列宽、分组或堆叠时机,不能为了底边对齐留下整块空白。布局改变不允许增减记录、修改 `pageSize`、排序、筛选或分页状态。
34
- - 未设计独立滚动的卡片不增加内部滚动条;工作区本来有受限高度时,按 [页面与导航连续性](page-continuity.md) 保持完整可收缩高度链和单一主滚动区域。
62
+ - 内容差异过大时调整列宽、分组或堆叠时机,使空白与实际内容相称。布局改变时保留记录、`pageSize`、排序、筛选和分页状态,禁止为对齐改动业务数据。
63
+ - 卡片默认随页面滚动,独立滚动仅用于已明确设计的区域。工作区本来有受限高度时,按 [页面与导航连续性](page-continuity.md) 让各级父子容器都可收缩,并保持单一主滚动区域。
35
64
 
36
65
  ## 内容增长、表格与浮层
37
66
 
38
67
  - 长标题、帮助文字和错误信息自然换行;无空格的长编号或链接设置合适的断行策略。只在已有明确设计允许时省略文字,并保留完整内容的访问方式。
39
- - 宽表格保留列、数据和操作,通过表格自己的容器横向滚动;父级可收缩,不把整个页面撑宽,不用页面级 `overflow: hidden` 掩盖溢出。键盘操作、固定列和行内按钮仍应可达。
40
- - 空数据、加载、错误和正常内容使用同一外部布局;空态保留必要上下文并按内容收敛,不保留为多条数据写死的高度。加载只替换内容,不重建查询状态。
41
- - 抽屉和弹窗中的自定义内容也按自身宽度重排。检查下拉面板、日期面板和提示是否被裁切;不通过修改平台父容器或跨 iframe 注入样式解决局部布局问题。
42
- - 优先保留同一组件树做 CSS 重排,不因断点切换组件 `key` 或重新挂载业务区域;筛选、页码、选中项、未提交内容和焦点不得因此丢失,也不能触发额外请求。
68
+ - 宽表格保留列、数据和操作,通过表格自己的容器横向滚动;父级保持可收缩,页面宽度适应容器。出现溢出时修正列宽与容器布局,保持完整内容、键盘操作、固定列和行内按钮可达。
69
+ - 空数据、加载、错误和正常内容使用同一外部布局。空态保留必要说明和操作,高度随实际内容收敛;加载时替换内容区并保留查询状态。
70
+ - 抽屉和弹窗中的自定义内容按自身宽度重排,检查下拉面板、日期面板和提示能完整显示。局部问题在当前页面内修复,禁止修改平台父容器或跨 iframe 注入样式。
71
+ - 优先保留同一组件树,用 CSS 调整排列,保持组件 `key` 和业务区域挂载状态稳定。切换宽度后,筛选、页码、选中项、未提交内容和焦点保持,原请求及触发次数不变。
72
+
73
+ ## 复用模板与 CLI 诊断
74
+
75
+ Canvas 页面先按当前设计组织内容,需要参考实现时获取模板,并按需复用其中的布局片段:
76
+
77
+ ```bash
78
+ openyida sample openyida-page-template canvas-responsive-layout --output .cache/samples/canvas-responsive-layout.jsx
79
+ ```
80
+
81
+ 模板提供筛选排列、主辅布局、局部滚动和命名容器查询等独立片段。只选取当前业务需要的片段,保留原业务组件树。断点使用本页内容的最小可用宽度,模板数值仅供参考。
82
+
83
+ 优先用 CSS 完成重排。图表等组件需要读取实际宽度时,再复用 `useContainerLayout`;它会合并同一帧的测量通知,并在卸载时清理监听。图表收到容器宽度变化后调用自身 `resize`,尺寸随容器持续更新。
84
+
85
+ 设计决定页面结构,模板提供实现参考。CLI 检查确定的工程风险,首屏重点和视觉效果通过实际页面另行验收。
86
+
87
+ `openyida compile <页面.canvas.jsx> --json` 与普通页面的 `check-page` 会给出以下非阻断诊断:只按视口宽度重排、`ResizeObserver` 未清理、480px 以上固定宽度、整页 `scale/zoom`。这些诊断为 warning,需按页面用途判断。确认属于视口级抽屉、固定画布等合理用法后,再使用消息中的 `openyida-responsive-*-ok` 标记;普通布局问题先修复,禁止用标记跳过问题。
43
88
 
44
89
  ## 实际页面验收
45
90
 
46
- 按本页实际布局检查以下组合,记录容器宽度和结果;不以截图尺寸或编译通过代替运行态证据:
91
+ 按本页实际布局检查以下组合,记录实际容器宽度、操作和结果。编译、截图和交互检查分别记录,只有执行过的检查才能标为通过:
47
92
 
48
93
  1. 宽屏、每个重排点前后及目标最窄容器;同时测试平台侧栏展开/收起和实际抽屉调宽。缩窄后再放宽,布局能恢复,无页面整体横向溢出。
49
94
  2. 短/长标签、长标题和帮助/错误信息:标签与控件层次清楚,同排控件边缘一致,搜索组合按钮等高,操作无裁切或遮挡。
50
- 3. 主辅面板内容一多一少:宽屏需要等高的外框共边,分页位置合理;单列无多余高度,行高不被拉伸,不靠假内容填空。
95
+ 3. 主辅面板内容一多一少:宽屏需要等高的外框共边,分页位置合理;单列随真实内容自然增长,表格行和列表项保持自然高度。
51
96
  4. 正常多条、单条、空数据、加载和失败状态;宽表格只在自身容器横向滚动,完整字段与操作仍可达。
52
97
  5. 键盘遍历、放大文字或浏览器缩放后,内容与操作仍可用;调整宽度前后的筛选、分页、选择和草稿保持,原事件、请求参数及触发次数不变。
98
+ 6. 至少记录 360、600、768、1024、1280、1600px 及实际重排点前后;这些数值用于测试覆盖,布局断点仍按本页内容确定。桌面宽视口中另测同样宽度的侧栏、抽屉或 iframe,确认布局随内容容器变化。
99
+ 7. 按本页主任务实际走一遍:找到判断信息、执行主操作、展开收纳内容、返回原位置。对比同宽桌面容器与触屏,确认操作可直接点击;真实移动端另验软键盘、安全区域和焦点。用截图判断视觉层级,用真实操作验证功能;禁止仅凭生成文案、静态断言或截图评分宣称运行态通过。
53
100
 
54
101
  已有页面按 UI 美化流程先展示宽窄两种效果及必要的换行状态,经确认、备份后实施。只检查过源码或示意图时标记“响应式待运行态验证”;用本地样例或只读方式准备状态,不提交真实业务数据来制造测试场景。