openxiangda 1.0.267 → 1.0.269

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 (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +19 -2
  3. package/lib/cli.js +70 -37
  4. package/lib/config.js +60 -56
  5. package/lib/generation-update.js +42 -0
  6. package/lib/migration-advice.js +19 -0
  7. package/lib/skills.js +8 -16
  8. package/lib/workspace-init.js +11 -4
  9. package/openxiangda-skills/SKILL.md +11 -3
  10. package/openxiangda-skills/references/architecture-patterns.md +218 -218
  11. package/openxiangda-skills/references/component-guide.md +122 -122
  12. package/openxiangda-skills/references/openxiangda-api.md +1 -1
  13. package/openxiangda-skills/references/platform-data-model.md +285 -285
  14. package/openxiangda-skills/references/resource-manifest-cheatsheet.md +1 -1
  15. package/openxiangda-skills/references/troubleshooting.md +224 -224
  16. package/openxiangda-skills/references/workspace-state.md +3 -3
  17. package/openxiangda-skills/skills/openxiangda-core/SKILL.md +1 -1
  18. package/package.json +54 -3
  19. package/packages/sdk/dist/{ProcessPreview-Cyk6uv-w.d.mts → ProcessPreview-DSUIJi5V.d.mts} +2 -3
  20. package/packages/sdk/dist/{ProcessPreview-Cyk6uv-w.d.ts → ProcessPreview-DSUIJi5V.d.ts} +2 -3
  21. package/packages/sdk/dist/components/index.d.mts +41 -42
  22. package/packages/sdk/dist/components/index.d.ts +41 -42
  23. package/packages/sdk/dist/{dataManagementApi-CE8Zyj3a.d.ts → dataManagementApi-Ro9itwm8.d.ts} +2 -2
  24. package/packages/sdk/dist/{dataManagementApi-4fSaCA5t.d.mts → dataManagementApi-p_HOhQnA.d.mts} +2 -2
  25. package/packages/sdk/dist/runtime/index.d.mts +3 -4
  26. package/packages/sdk/dist/runtime/index.d.ts +3 -4
  27. package/packages/sdk/dist/runtime/react.d.mts +5 -5
  28. package/packages/sdk/dist/runtime/react.d.ts +5 -5
  29. package/packages/sdk/src/build-source/scripts/utils/mime-types.mjs +70 -70
  30. package/packages/sdk/src/build-source/scripts/utils/oss-client.mjs +128 -128
  31. package/packages/sdk/src/build-source/scripts/utils/progress.mjs +57 -57
  32. package/templates/openxiangda-react-spa/.cursor/rules/openxiangda.mdc +6 -0
  33. package/templates/openxiangda-react-spa/.qoder/rules/openxiangda.md +6 -0
  34. package/templates/openxiangda-react-spa/AGENTS.md +6 -0
  35. package/templates/openxiangda-react-spa/app-workspace.config.ts +1 -1
  36. package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda.mdc +7 -1
  37. package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda.md +7 -1
  38. package/templates/sy-lowcode-app-workspace/AGENTS.md +8 -2
  39. package/templates/sy-lowcode-app-workspace/app-workspace.config.ts +1 -1
package/lib/skills.js CHANGED
@@ -11,9 +11,9 @@ const MANAGER = 'openxiangda';
11
11
 
12
12
  const SKILL_SPECS = [
13
13
  {
14
- name: 'openxiangda',
15
- displayName: 'OpenXiangda',
16
- shortDescription: '私有化低代码平台 CLI 与 AI skill 入口。',
14
+ name: 'openxiangda-v1',
15
+ displayName: 'OpenXiangda V1',
16
+ shortDescription: 'OpenXiangda V1 维护入口,仅用于 V1 工作区。',
17
17
  sourceRelativePath: 'openxiangda-skills',
18
18
  type: 'root',
19
19
  },
@@ -84,16 +84,8 @@ const SKILL_SPECS = [
84
84
  },
85
85
  ];
86
86
 
87
- const RETIRED_SKILL_SPECS = [
88
- { name: 'openxiangda-v2', sourceRelativePath: 'v2/skills/openxiangda-v2' },
89
- { name: 'openxiangda-v2-architecture', sourceRelativePath: 'v2/skills/openxiangda-v2-architecture' },
90
- { name: 'openxiangda-v2-frontend', sourceRelativePath: 'v2/skills/openxiangda-v2-frontend' },
91
- { name: 'openxiangda-v2-backend', sourceRelativePath: 'v2/skills/openxiangda-v2-backend' },
92
- { name: 'openxiangda-v2-data-authz', sourceRelativePath: 'v2/skills/openxiangda-v2-data-authz' },
93
- { name: 'openxiangda-v2-workflow-events', sourceRelativePath: 'v2/skills/openxiangda-v2-workflow-events' },
94
- { name: 'openxiangda-v2-delivery', sourceRelativePath: 'v2/skills/openxiangda-v2-delivery' },
95
- { name: 'openxiangda-v1-maintenance', sourceRelativePath: 'v2/skills/openxiangda-v1-maintenance' },
96
- ];
87
+ // V1 does not own V2 or the unified routing skill, including historical installs.
88
+ const RETIRED_SKILL_SPECS = [];
97
89
 
98
90
  function getDefaultCodexSkillsDir(env = process.env) {
99
91
  const codexHome = env.CODEX_HOME || path.join(os.homedir(), '.codex');
@@ -232,7 +224,7 @@ function getSkillStatusReport(options = {}) {
232
224
  const env = options.env || process.env;
233
225
 
234
226
  // 获取目标目录列表
235
- const skillsDirs = getDualSkillsDirs(agent, env);
227
+ const skillsDirs = options.dest ? [path.resolve(options.dest)] : getDualSkillsDirs(agent, env);
236
228
 
237
229
  const results = [];
238
230
  for (const skillsDir of skillsDirs) {
@@ -265,7 +257,7 @@ function installSkills(options = {}) {
265
257
  const force = Boolean(options.force);
266
258
 
267
259
  // 获取目标目录列表
268
- const skillsDirs = getDualSkillsDirs(agent, options.env);
260
+ const skillsDirs = options.dest ? [path.resolve(options.dest)] : getDualSkillsDirs(agent, options.env);
269
261
 
270
262
  const results = [];
271
263
  for (const skillsDir of skillsDirs) {
@@ -410,7 +402,7 @@ function writeAgentMetadata(spec, skillDir) {
410
402
  const defaultPrompt =
411
403
  spec.defaultPrompt ||
412
404
  (spec.name === 'openxiangda'
413
- ? '使用 $openxiangda 处理私有化低代码平台的登录、发布和诊断任务。'
405
+ ? '使用 $openxiangda-v1 处理 V1 工作区的登录、发布和诊断任务。'
414
406
  : `使用 $${spec.name} 处理对应的 OpenXiangda 低代码平台任务。`);
415
407
  const content = [
416
408
  'interface:',
@@ -41,6 +41,7 @@ function initWorkspace(options = {}) {
41
41
  throw new Error('workspace init 绑定应用时必须同时提供 --profile 和 --app-type');
42
42
  }
43
43
 
44
+ const resolvedProfile = profileName ? getProfile(loadConfig(targetDir, { exact: true }), profileName) : null;
44
45
  ensureCanInitialize(targetDir, force, templateDir);
45
46
  copyTemplate(templateDir, targetDir, {
46
47
  __WORKSPACE_PACKAGE_NAME__: packageName,
@@ -49,8 +50,7 @@ function initWorkspace(options = {}) {
49
50
 
50
51
  let bound = null;
51
52
  if (profileName && appType) {
52
- const config = loadConfig();
53
- const resolved = getProfile(config, profileName);
53
+ const resolved = resolvedProfile;
54
54
  const state = {
55
55
  version: 1,
56
56
  profiles: {
@@ -125,11 +125,18 @@ function ensureCanInitialize(targetDir, force, templateDir) {
125
125
  const entries = fs
126
126
  .readdirSync(targetDir)
127
127
  .filter(name => !['.DS_Store'].includes(name));
128
- if (entries.length > 0 && !force) {
128
+ if (entries.length > 0 && !force && !isAuthOnlyWorkspace(targetDir, entries)) {
129
129
  throw new Error(`目标目录非空: ${targetDir}。如需写入请传 --force`);
130
130
  }
131
131
  }
132
132
 
133
+ function isAuthOnlyWorkspace(targetDir, entries) {
134
+ if (entries.length !== 1 || entries[0] !== '.openxiangda') return false;
135
+ const directory = path.join(targetDir, '.openxiangda');
136
+ if (!fs.lstatSync(directory).isDirectory() || fs.lstatSync(directory).isSymbolicLink()) return false;
137
+ return fs.readdirSync(directory).every(name => ['profiles.json', '.gitignore'].includes(name) && fs.lstatSync(path.join(directory, name)).isFile() && !fs.lstatSync(path.join(directory, name)).isSymbolicLink());
138
+ }
139
+
133
140
  function normalizeRuntime(value) {
134
141
  return value === 'react-spa' || value === 'spa' ? 'react-spa' : 'legacy';
135
142
  }
@@ -154,7 +161,7 @@ function copyTemplate(sourceDir, targetDir, replacements, baseDir = sourceDir) {
154
161
  function shouldSkipTemplateEntry(baseDir, sourcePath, entryName) {
155
162
  if (TEMPLATE_IGNORE_NAMES.has(entryName)) return true;
156
163
  const relativePath = path.relative(baseDir, sourcePath).split(path.sep).join('/');
157
- return TEMPLATE_IGNORE_PATHS.has(relativePath);
164
+ return TEMPLATE_IGNORE_PATHS.has(relativePath) || /^\.openxiangda\/(profiles\.json|session\.json)(\.|$)/.test(relativePath);
158
165
  }
159
166
 
160
167
  function applyReplacements(content, replacements) {
@@ -1,6 +1,6 @@
1
1
  ---
2
- name: openxiangda
3
- description: "Use OpenXiangda for private low-code platform work: app workspaces, forms, pages, resources, functions, automations, workflows, permissions, publishing, deployment, diagnosis, profiles, and the openxiangda CLI."
2
+ name: openxiangda-v1
3
+ description: "Maintain existing OpenXiangda V1 workspaces: forms, pages, resources, workflows, permissions, publishing and diagnosis. New applications default to V2; assess V2 adoption for V1 projects still in testing when capability coverage and migration cost permit."
4
4
  ---
5
5
 
6
6
  <!-- OpenXiangda-Policy-Version: 7 -->
@@ -13,10 +13,16 @@ This file is a router and safety card. Read only the one or two subskills select
13
13
 
14
14
  ## Select the runtime generation first
15
15
 
16
- If the workspace contains `openxiangda.config.ts`, `apps/web`, and `apps/server`, it is a platform-2.0 application. Stop this 1.x resource flow and use the independently released `$openxiangda-v2` unified skill. The 2.0 CLI operates on one immutable application package and does not use SDD or per-resource publishing.
16
+ If the nearest workspace contains `openxiangda.config.ts` or `openxiangda-app.config.ts`, it is a platform-2.0 application; a Nest server is optional. Stop this 1.x resource flow and use the independently released `$openxiangda-v2` skill. The 2.0 CLI operates on one immutable application package and does not use SDD or per-resource publishing.
17
17
 
18
18
  If the workspace contains `app-workspace.config.ts`, forms/pages/resource manifests, or an existing 1.x state directory, continue with this router. Never migrate a stable 1.x application merely because platform 2.0 is available.
19
19
 
20
+ For an existing V1 project, proactively check whether V2 covers its required capabilities, whether it is still in testing, and whether migration cost is manageable. When those conditions hold, recommend V2 first and explain the benefit, rebuild scope and verification cost. Use the unified entry's `openxiangda migrate assess --to v2` for source pointers; confirm the project design, data/workflow mapping, acceptance and rollback before implementing migration. Keep unknown conditions explicit and continue current maintenance with the matching engine until migration is authorized.
21
+
22
+ ## Installation and upgrade
23
+
24
+ For CLI, Skill and MCP setup or upgrades, read the [unified installation guide](https://github.com/1377385356/openxiangda/blob/master/docs/getting-started.md#upgrade). Install the global launcher with Node.js 24+ using `npm install -g openxiangda@latest --registry=https://registry.npmjs.org`. Use that global command for `migrate assess`; a project-local V1 CLI does not implement it. Workspace updates remain on `legacy-v1`. Refresh matching skills after dependency updates and restart a configured V2 MCP client to load its new project version. Never automatically replace a V1 project dependency with V2.
25
+
20
26
  ## 1.x Delivery V2 is the normal 1.x release path
21
27
 
22
28
  When `app-workspace.config.ts` declares `deliveryVersion: 2`, all later V1 SDD,
@@ -227,3 +233,5 @@ The lease is app-level promotion ownership, while worktree ownership prevents tw
227
233
  - `../openxiangda-open-api/SKILL.md`
228
234
 
229
235
  Load a reference only when the selected subskill explicitly requires it. Workspace `AGENTS.md` remains authoritative for project-specific constraints.
236
+
237
+ 平台登录态只使用当前工作区的 `.openxiangda/profiles.json`,不再读取或合并全局 profiles。升级后请进入每个项目运行 `openxiangda login <platform-url>`。登录文件自动加入 Git 忽略规则,不得提交或打包。
@@ -1,150 +1,150 @@
1
- # Architecture Patterns
2
-
3
- > 面向 AI Agent 的平台架构范式指南:理解「表单为数据存储中枢、代码页承载业务逻辑」的低代码开发模型,并据此构建可发布的应用。
4
-
5
- ---
6
-
7
- ## 1. 平台设计理念
8
-
9
- ### 1.1 表单 = 数据存储
10
-
11
- - **每个表单(Form)= 一张数据表**:表单 schema 定义即数据结构(字段、类型、校验、关联)。
12
- - 平台底层按字段建表:简单字段使用原生 SQL 类型列,复杂字段使用 json/jsonb 列存储结构化对象。AI 无需关心建表 DDL、迁移脚本或 CRUD 接口。
13
- - 表单除了承担「数据录入 UI」,还会自动暴露:列表查询、详情、增删改查 API、字段级权限、流程触发钩子。
14
-
15
- ### 1.2 低代码 = Schema + 代码页
16
-
17
- ```
18
- 低代码应用 = 表单(定义数据结构)+ 代码页(实现业务逻辑/编排/可视化)
19
- ```
20
-
21
- - **表单层**:声明式 schema (`src/forms/xxx/schema.ts`),负责字段与数据契约。
22
- - **代码页层**:React/TSX (`src/pages/xxx/`),负责非录入场景下的业务交互、数据可视化、聚合操作。
23
-
24
- ### 1.3 「零后端开发」数据管理模式
25
-
26
- | 传统后端 | OpenXiangda 模式 |
27
- | --- | --- |
28
- | 设计表结构 + 迁移 | 编写表单 schema |
29
- | 编写 REST API | 平台自动生成数据 API |
30
- | 维护权限中间件 | 表单字段级权限 + 部门/角色 |
31
- | 自建文件存储 | `AttachmentField` 直接接入平台存储 |
1
+ # Architecture Patterns
2
+
3
+ > 面向 AI Agent 的平台架构范式指南:理解「表单为数据存储中枢、代码页承载业务逻辑」的低代码开发模型,并据此构建可发布的应用。
4
+
5
+ ---
6
+
7
+ ## 1. 平台设计理念
8
+
9
+ ### 1.1 表单 = 数据存储
10
+
11
+ - **每个表单(Form)= 一张数据表**:表单 schema 定义即数据结构(字段、类型、校验、关联)。
12
+ - 平台底层按字段建表:简单字段使用原生 SQL 类型列,复杂字段使用 json/jsonb 列存储结构化对象。AI 无需关心建表 DDL、迁移脚本或 CRUD 接口。
13
+ - 表单除了承担「数据录入 UI」,还会自动暴露:列表查询、详情、增删改查 API、字段级权限、流程触发钩子。
14
+
15
+ ### 1.2 低代码 = Schema + 代码页
16
+
17
+ ```
18
+ 低代码应用 = 表单(定义数据结构)+ 代码页(实现业务逻辑/编排/可视化)
19
+ ```
20
+
21
+ - **表单层**:声明式 schema (`src/forms/xxx/schema.ts`),负责字段与数据契约。
22
+ - **代码页层**:React/TSX (`src/pages/xxx/`),负责非录入场景下的业务交互、数据可视化、聚合操作。
23
+
24
+ ### 1.3 「零后端开发」数据管理模式
25
+
26
+ | 传统后端 | OpenXiangda 模式 |
27
+ | --- | --- |
28
+ | 设计表结构 + 迁移 | 编写表单 schema |
29
+ | 编写 REST API | 平台自动生成数据 API |
30
+ | 维护权限中间件 | 表单字段级权限 + 部门/角色 |
31
+ | 自建文件存储 | `AttachmentField` 直接接入平台存储 |
32
32
  | 业务状态流转 | 表单 + `status` 字段 + 操作日志 + domain/service |
33
33
  | 真审批流程 | 表单 + workflow + js-code-nodes |
34
34
 
35
35
  AI Agent 在该平台上写代码时,**不应该考虑数据库、表结构、后端接口**——这些由表单 schema 自动派生。
36
-
37
- ---
38
-
39
- ## 2. 标准 CRUD 数据流
40
-
41
- ### 2.1 数据流向
42
-
43
- ```
44
- ┌──────────────┐ ┌─────────────────┐ ┌────────────────┐ ┌────────────┐
45
- │ 表单页 │───▶│ 数据存储 │───▶│ 数据管理页 │───▶│ 详情页 │
46
- │ (录入/编辑) │ │ (平台数据表) │ │ (列表/查询) │ │ (查看) │
47
- └──────────────┘ └─────────────────┘ └────────────────┘ └────────────┘
48
- ▲ │
49
- └────────────────────────────────────────────┘
50
- (从列表点编辑回填)
51
- ```
52
-
53
- ### 2.2 实现步骤
54
-
55
- 1. **定义表单 schema**:`src/forms/xxx/schema.ts` → 生成数据表 + 录入页。
56
- 2. **编写数据管理页**:`src/pages/xxx/index.tsx` 内使用 `DataManagementList` 组件做列表/查询/批量操作。
57
- 3. **绑定菜单入口**:`openxiangda menu create --type=page --target=/pages/xxx`(指向自定义代码页,**而不是** view 类型)。
58
-
59
- ### 2.3 反模式:不要使用平台内置 View
60
-
61
- > ❌ `openxiangda menu create --type=view --formUuid=FORM_XXX`
62
-
63
- 平台内置 View 视图灵活性差:列宽/操作列/筛选项受限、无法嵌入业务交互、无法做联动。
64
-
65
- > ✅ **统一使用「自定义代码页 + `DataManagementList`」组合**。
66
-
67
- #### `DataManagementList` 的优势
68
-
69
- - 完全自定义列渲染(`render` 函数、React 节点)。
70
- - 自定义行操作 / 批量操作按钮。
71
- - 自定义筛选区(部门选择、人员选择、级联)。
72
- - 与页面内其他组件(Drawer、Modal、Tabs)自由组合。
73
- - 仍复用平台分页、查询、导出能力。
74
-
75
- ---
76
-
77
- ## 3. 数据管理页的标准构建方式
78
-
79
- ### 3.1 最小可用示例
80
-
81
- ```tsx
82
- // src/pages/instrument-list/index.tsx
36
+
37
+ ---
38
+
39
+ ## 2. 标准 CRUD 数据流
40
+
41
+ ### 2.1 数据流向
42
+
43
+ ```
44
+ ┌──────────────┐ ┌─────────────────┐ ┌────────────────┐ ┌────────────┐
45
+ │ 表单页 │───▶│ 数据存储 │───▶│ 数据管理页 │───▶│ 详情页 │
46
+ │ (录入/编辑) │ │ (平台数据表) │ │ (列表/查询) │ │ (查看) │
47
+ └──────────────┘ └─────────────────┘ └────────────────┘ └────────────┘
48
+ ▲ │
49
+ └────────────────────────────────────────────┘
50
+ (从列表点编辑回填)
51
+ ```
52
+
53
+ ### 2.2 实现步骤
54
+
55
+ 1. **定义表单 schema**:`src/forms/xxx/schema.ts` → 生成数据表 + 录入页。
56
+ 2. **编写数据管理页**:`src/pages/xxx/index.tsx` 内使用 `DataManagementList` 组件做列表/查询/批量操作。
57
+ 3. **绑定菜单入口**:`openxiangda menu create --type=page --target=/pages/xxx`(指向自定义代码页,**而不是** view 类型)。
58
+
59
+ ### 2.3 反模式:不要使用平台内置 View
60
+
61
+ > ❌ `openxiangda menu create --type=view --formUuid=FORM_XXX`
62
+
63
+ 平台内置 View 视图灵活性差:列宽/操作列/筛选项受限、无法嵌入业务交互、无法做联动。
64
+
65
+ > ✅ **统一使用「自定义代码页 + `DataManagementList`」组合**。
66
+
67
+ #### `DataManagementList` 的优势
68
+
69
+ - 完全自定义列渲染(`render` 函数、React 节点)。
70
+ - 自定义行操作 / 批量操作按钮。
71
+ - 自定义筛选区(部门选择、人员选择、级联)。
72
+ - 与页面内其他组件(Drawer、Modal、Tabs)自由组合。
73
+ - 仍复用平台分页、查询、导出能力。
74
+
75
+ ---
76
+
77
+ ## 3. 数据管理页的标准构建方式
78
+
79
+ ### 3.1 最小可用示例
80
+
81
+ ```tsx
82
+ // src/pages/instrument-list/index.tsx
83
83
  import { DataManagementList } from 'openxiangda';
84
- import { Tag, message } from 'antd';
85
- import { useNavigate } from 'react-router-dom';
86
-
87
- export default function InstrumentListPage() {
88
- const navigate = useNavigate();
89
-
90
- return (
91
- <DataManagementList
92
- formUuid="FORM_INSTRUMENT"
93
- columns={[
94
- { fieldId: 'name', title: '仪器名称' },
95
- { fieldId: 'code', title: '编号', width: 140 },
96
- {
97
- fieldId: 'status',
98
- title: '状态',
99
- render: (val) => (
100
- <Tag color={val.value === 'idle' ? 'green' : 'orange'}>
101
- {val.label}
102
- </Tag>
103
- ),
104
- },
105
- { fieldId: 'department', title: '所属部门' },
106
- ]}
107
- actions={[
108
- {
109
- label: '预约',
110
- onClick: (record) => navigate(`/forms/reservation/new?instrumentId=${record.id}`),
111
- },
112
- {
113
- label: '编辑',
114
- onClick: (record) => navigate(`/forms/instrument/${record.id}/edit`),
115
- },
116
- ]}
117
- batchActions={['export', 'delete']}
118
- filters={[
119
- { fieldId: 'status', type: 'select' },
120
- { fieldId: 'department', type: 'department-select' },
121
- { fieldId: 'owner', type: 'user-select' },
122
- ]}
123
- onRowClick={(record) => navigate(`/pages/instrument-detail/${record.id}`)}
124
- />
125
- );
126
- }
127
- ```
128
-
129
- ### 3.2 关键 Props 速查
130
-
131
- | Prop | 类型 | 说明 |
132
- | --- | --- | --- |
133
- | `formUuid` | `string` | 绑定的表单(数据源) |
134
- | `columns` | `Column[]` | 列定义;`render` 可返回任意 React 节点 |
135
- | `actions` | `Action[]` | 行级操作按钮 |
136
- | `batchActions` | `('export' \| 'delete' \| BatchAction)[]` | 批量操作 |
137
- | `filters` | `Filter[]` | 筛选区字段;支持 `select` / `date-range` / `user-select` / `department-select` 等 |
138
- | `queryParams` | `Record<string, any>` | 固定查询条件(如「只看我创建的」) |
139
- | `onRowClick` | `(record) => void` | 行点击事件,常用于跳转详情 |
140
-
141
- ---
142
-
143
- ## 4. 页面类型与适用场景
144
-
145
- | 页面类型 | 实现方式 | 适用场景 |
146
- | --- | --- | --- |
147
- | 表单录入 / 编辑页 | `src/forms/xxx/` (`schema.ts` + `page.tsx`) | 新增数据、修改数据 |
84
+ import { Tag, message } from 'antd';
85
+ import { useNavigate } from 'react-router-dom';
86
+
87
+ export default function InstrumentListPage() {
88
+ const navigate = useNavigate();
89
+
90
+ return (
91
+ <DataManagementList
92
+ formUuid="FORM_INSTRUMENT"
93
+ columns={[
94
+ { fieldId: 'name', title: '仪器名称' },
95
+ { fieldId: 'code', title: '编号', width: 140 },
96
+ {
97
+ fieldId: 'status',
98
+ title: '状态',
99
+ render: (val) => (
100
+ <Tag color={val.value === 'idle' ? 'green' : 'orange'}>
101
+ {val.label}
102
+ </Tag>
103
+ ),
104
+ },
105
+ { fieldId: 'department', title: '所属部门' },
106
+ ]}
107
+ actions={[
108
+ {
109
+ label: '预约',
110
+ onClick: (record) => navigate(`/forms/reservation/new?instrumentId=${record.id}`),
111
+ },
112
+ {
113
+ label: '编辑',
114
+ onClick: (record) => navigate(`/forms/instrument/${record.id}/edit`),
115
+ },
116
+ ]}
117
+ batchActions={['export', 'delete']}
118
+ filters={[
119
+ { fieldId: 'status', type: 'select' },
120
+ { fieldId: 'department', type: 'department-select' },
121
+ { fieldId: 'owner', type: 'user-select' },
122
+ ]}
123
+ onRowClick={(record) => navigate(`/pages/instrument-detail/${record.id}`)}
124
+ />
125
+ );
126
+ }
127
+ ```
128
+
129
+ ### 3.2 关键 Props 速查
130
+
131
+ | Prop | 类型 | 说明 |
132
+ | --- | --- | --- |
133
+ | `formUuid` | `string` | 绑定的表单(数据源) |
134
+ | `columns` | `Column[]` | 列定义;`render` 可返回任意 React 节点 |
135
+ | `actions` | `Action[]` | 行级操作按钮 |
136
+ | `batchActions` | `('export' \| 'delete' \| BatchAction)[]` | 批量操作 |
137
+ | `filters` | `Filter[]` | 筛选区字段;支持 `select` / `date-range` / `user-select` / `department-select` 等 |
138
+ | `queryParams` | `Record<string, any>` | 固定查询条件(如「只看我创建的」) |
139
+ | `onRowClick` | `(record) => void` | 行点击事件,常用于跳转详情 |
140
+
141
+ ---
142
+
143
+ ## 4. 页面类型与适用场景
144
+
145
+ | 页面类型 | 实现方式 | 适用场景 |
146
+ | --- | --- | --- |
147
+ | 表单录入 / 编辑页 | `src/forms/xxx/` (`schema.ts` + `page.tsx`) | 新增数据、修改数据 |
148
148
  | 数据管理页 | `src/pages/xxx/` 使用 `DataManagementList` | 列表、查询、批量操作、导出 |
149
149
  | 状态流转页 | 普通表单 + 状态字段 + 操作日志 + 代码页 service | 工单、订单、任务等业务状态变化 |
150
150
  | 流程表单页 | `src/forms/xxx/` 关联 workflow | 真实审批流程发起、节点处理、审批意见 |
@@ -159,84 +159,84 @@ export default function InstrumentListPage() {
159
159
  - 每次状态变更都由 service 完成,统一写操作日志并更新责任人、归属、更新时间和隐藏权限键等派生字段。
160
160
  - workflow 只用于真实审批:审批人、审批任务、同意/驳回、审批意见、节点权限、流程记录明确存在的场景。
161
161
  - 不要把「待处理 / 处理中 / 已完成 / 已关闭」这类状态流转建成流程表单。
162
-
163
- ---
164
-
165
- ## 5. 多端架构(PC / Mobile)
166
-
167
- ### 5.1 分离策略
168
-
169
- - **页面层按端独立**:`src/pages/pc/xxx/` 与 `src/pages/mobile/xxx/`,避免单文件内 `if (isMobile)` 满天飞。
170
- - **入口统一调度**:
171
-
172
- ```tsx
173
- // src/pages/instrument/index.tsx
162
+
163
+ ---
164
+
165
+ ## 5. 多端架构(PC / Mobile)
166
+
167
+ ### 5.1 分离策略
168
+
169
+ - **页面层按端独立**:`src/pages/pc/xxx/` 与 `src/pages/mobile/xxx/`,避免单文件内 `if (isMobile)` 满天飞。
170
+ - **入口统一调度**:
171
+
172
+ ```tsx
173
+ // src/pages/instrument/index.tsx
174
174
  import { useDeviceDetect } from 'openxiangda';
175
- import PcPage from './pc';
176
- import MobilePage from './mobile';
177
-
178
- export default function InstrumentEntry() {
179
- const { isMobile } = useDeviceDetect();
180
- return isMobile ? <MobilePage /> : <PcPage />;
181
- }
182
- ```
183
-
184
- - **UI 库分端**:
185
- - PC → `antd`
186
- - Mobile → `antd-mobile`
175
+ import PcPage from './pc';
176
+ import MobilePage from './mobile';
177
+
178
+ export default function InstrumentEntry() {
179
+ const { isMobile } = useDeviceDetect();
180
+ return isMobile ? <MobilePage /> : <PcPage />;
181
+ }
182
+ ```
183
+
184
+ - **UI 库分端**:
185
+ - PC → `antd`
186
+ - Mobile → `antd-mobile`
187
187
  - **逻辑层共享**:`src/domain/` 与 `src/shared/services/` 在两端复用,确保业务规则一处实现。
188
-
189
- ### 5.2 共享 / 分离原则
190
-
191
- | 层 | PC/Mobile 是否共享 |
192
- | --- | --- |
193
- | 业务规则(domain) | ✅ 共享 |
194
- | 数据服务(services) | ✅ 共享 |
195
- | Hooks(hooks) | ✅ 多数共享,端相关的拆 `usePcXxx` / `useMobileXxx` |
196
- | 表单 schema | ✅ 共享(平台组件已多端适配) |
197
- | 页面布局 / 组件 | ❌ 分离 |
198
-
199
- ---
200
-
201
- ## 6. 目录组织规范
202
-
203
- ```
204
- src/
205
- ├── forms/ # 表单(每个目录 = 一张数据表)
206
- │ └── instrument/
207
- │ ├── schema.ts # 字段定义
208
- │ └── page.tsx # 录入页(可选自定义布局)
209
- ├── pages/ # 自定义代码页(含数据管理页)
210
- │ ├── instrument-list/ # 列表/管理
211
- │ ├── instrument-detail/ # 详情
212
- │ └── dashboard/ # 仪表盘等业务页
213
- ├── domain/ # 纯业务逻辑(无 UI、可单测)
214
- │ └── instrument/
215
- │ ├── rules.ts
216
- │ └── types.ts
217
- ├── shared/
218
- │ ├── services/ # 数据服务层(封装 SDK 调用)
219
- │ ├── hooks/ # 跨页面 hooks
220
- │ ├── components/ # 跨页面共享组件
221
- │ └── utils/ # 通用工具函数
222
- └── js-code-nodes/ # 工作流 JS 脚本(由 build-js-code.mjs 打包)
223
- ```
224
-
225
- ### 6.1 命名约定
226
-
227
- - 目录与文件统一 `kebab-case`:`instrument-list/`,`use-instrument-form.ts`。
228
- - React 组件文件首字母大写:`DataPanel.tsx`,与默认导出一致。
229
- - 表单目录名 = 表单业务名(不含 `Form` 后缀)。
230
- - 数据管理页目录名建议 `xxx-list`,详情页 `xxx-detail`。
231
-
232
- ### 6.2 依赖方向(单向)
233
-
234
- ```
235
- pages ──▶ shared ──▶ domain
236
- forms ──▶ shared ──▶ domain
237
- ```
238
-
239
- - `domain/` 不依赖 `shared/` 或 UI。
188
+
189
+ ### 5.2 共享 / 分离原则
190
+
191
+ | 层 | PC/Mobile 是否共享 |
192
+ | --- | --- |
193
+ | 业务规则(domain) | ✅ 共享 |
194
+ | 数据服务(services) | ✅ 共享 |
195
+ | Hooks(hooks) | ✅ 多数共享,端相关的拆 `usePcXxx` / `useMobileXxx` |
196
+ | 表单 schema | ✅ 共享(平台组件已多端适配) |
197
+ | 页面布局 / 组件 | ❌ 分离 |
198
+
199
+ ---
200
+
201
+ ## 6. 目录组织规范
202
+
203
+ ```
204
+ src/
205
+ ├── forms/ # 表单(每个目录 = 一张数据表)
206
+ │ └── instrument/
207
+ │ ├── schema.ts # 字段定义
208
+ │ └── page.tsx # 录入页(可选自定义布局)
209
+ ├── pages/ # 自定义代码页(含数据管理页)
210
+ │ ├── instrument-list/ # 列表/管理
211
+ │ ├── instrument-detail/ # 详情
212
+ │ └── dashboard/ # 仪表盘等业务页
213
+ ├── domain/ # 纯业务逻辑(无 UI、可单测)
214
+ │ └── instrument/
215
+ │ ├── rules.ts
216
+ │ └── types.ts
217
+ ├── shared/
218
+ │ ├── services/ # 数据服务层(封装 SDK 调用)
219
+ │ ├── hooks/ # 跨页面 hooks
220
+ │ ├── components/ # 跨页面共享组件
221
+ │ └── utils/ # 通用工具函数
222
+ └── js-code-nodes/ # 工作流 JS 脚本(由 build-js-code.mjs 打包)
223
+ ```
224
+
225
+ ### 6.1 命名约定
226
+
227
+ - 目录与文件统一 `kebab-case`:`instrument-list/`,`use-instrument-form.ts`。
228
+ - React 组件文件首字母大写:`DataPanel.tsx`,与默认导出一致。
229
+ - 表单目录名 = 表单业务名(不含 `Form` 后缀)。
230
+ - 数据管理页目录名建议 `xxx-list`,详情页 `xxx-detail`。
231
+
232
+ ### 6.2 依赖方向(单向)
233
+
234
+ ```
235
+ pages ──▶ shared ──▶ domain
236
+ forms ──▶ shared ──▶ domain
237
+ ```
238
+
239
+ - `domain/` 不依赖 `shared/` 或 UI。
240
240
  - `shared/` 不依赖 `pages/` 或 `forms/`。
241
241
  - 反向依赖(如 domain 导入 pages)= 立刻拒绝。
242
242