openyida 2026.7.18 → 2026.7.21

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 (200) hide show
  1. package/README.md +37 -2
  2. package/bin/yida.js +113 -18
  3. package/docs/capabilities.md +202 -0
  4. package/lib/agent-center/agent-center.js +2 -3
  5. package/lib/app/app-list.js +3 -4
  6. package/lib/app/build-page.js +13 -6
  7. package/lib/app/check-page.js +16 -6
  8. package/lib/app/compile.js +17 -7
  9. package/lib/app/create-app.js +14 -2
  10. package/lib/app/create-form/api-path.js +25 -0
  11. package/lib/app/create-form/args.js +13 -0
  12. package/lib/app/create-form/commands.js +4 -0
  13. package/lib/app/create-form/definition-reader.js +23 -23
  14. package/lib/app/create-form/field-normalizers.js +26 -7
  15. package/lib/app/create-form/schema-patch.js +4 -1
  16. package/lib/app/create-form.js +155 -68
  17. package/lib/app/create-page.js +47 -10
  18. package/lib/app/generate-page.js +20 -1
  19. package/lib/app/get-form-config.js +2 -1
  20. package/lib/app/get-schema.js +67 -15
  21. package/lib/app/import-app.js +94 -42
  22. package/lib/app/list-forms.js +4 -8
  23. package/lib/app/page-ir.js +140 -0
  24. package/lib/app/publish.js +289 -556
  25. package/lib/app/schema-field-resolution.js +370 -0
  26. package/lib/app/services/app-reader.js +85 -0
  27. package/lib/app/services/app-service.js +235 -0
  28. package/lib/app/services/canvas-page-compiler.js +91 -0
  29. package/lib/app/services/canvas-page-schema-builder.js +149 -0
  30. package/lib/app/services/field-bindings.js +68 -0
  31. package/lib/app/services/form-compiler.js +1922 -0
  32. package/lib/app/services/form-mode-service.js +182 -0
  33. package/lib/app/services/form-schema-patcher.js +333 -0
  34. package/lib/app/services/form-schema-reader.js +59 -0
  35. package/lib/app/services/form-service.js +379 -0
  36. package/lib/app/services/form-validation.js +562 -0
  37. package/lib/app/services/native-page-compiler.js +92 -0
  38. package/lib/app/services/native-page-schema-builder.js +428 -0
  39. package/lib/app/services/page-resource-service.js +185 -0
  40. package/lib/app/update-app.js +22 -9
  41. package/lib/app/update-form-config.js +4 -2
  42. package/lib/asset/asset-cmd.js +19 -6
  43. package/lib/auth/oauth-loopback.js +376 -15
  44. package/lib/auth/token-auth.js +34 -3
  45. package/lib/auth/token-store.js +22 -4
  46. package/lib/basic-info/basic-info.js +2 -1
  47. package/lib/bridge/bridge.js +43 -18
  48. package/lib/core/agent-capabilities.js +5 -3
  49. package/lib/core/command-errors.js +38 -0
  50. package/lib/core/command-manifest.js +59 -1
  51. package/lib/core/copy.js +12 -0
  52. package/lib/core/doctor.js +15 -4
  53. package/lib/core/env-cmd.js +1 -1
  54. package/lib/core/env.js +7 -6
  55. package/lib/core/i18n.js +66 -8
  56. package/lib/core/legacy-schema-guard.js +408 -0
  57. package/lib/core/locales/en.js +38 -46
  58. package/lib/core/locales/zh.js +30 -46
  59. package/lib/core/sample.js +12 -2
  60. package/lib/core/utils.js +334 -66
  61. package/lib/core/yida-client.js +108 -5
  62. package/lib/corp-efficiency/corp-efficiency.js +2 -2
  63. package/lib/corp-manager/corp-manager.js +3 -3
  64. package/lib/formula/evaluate.js +11 -68
  65. package/lib/formula/field-refs.js +83 -0
  66. package/lib/i18n-management/i18n-management.js +2 -3
  67. package/lib/process/configure-process.js +44 -1992
  68. package/lib/process/create-process.js +54 -87
  69. package/lib/process/preview-process.js +20 -8
  70. package/lib/process/services/process-compiler.js +2056 -0
  71. package/lib/process/services/process-reader.js +232 -0
  72. package/lib/process/services/process-resource-service.js +571 -0
  73. package/lib/process/services/process-service.js +122 -0
  74. package/lib/process/services/process-stage-checkpoint.js +246 -0
  75. package/lib/report/append.js +7 -26
  76. package/lib/report/http.js +31 -5
  77. package/lib/report/index.js +10 -2
  78. package/lib/samples/yida-canvas-custom-page/business-list.canvas.jsx +63 -14
  79. package/lib/samples/yida-canvas-custom-page/dashboard-overview.canvas.jsx +176 -32
  80. package/lib/samples/yida-canvas-custom-page/dashboard-starter.canvas.jsx +5 -2
  81. package/lib/samples/yida-canvas-custom-page/data-management.canvas.jsx +228 -14
  82. package/lib/samples/yida-canvas-custom-page/portal-shell-home.canvas.jsx +2 -2
  83. package/lib/samples/yida-canvas-custom-page/split-pane-detail.canvas.jsx +206 -10
  84. package/lib/samples/yida-custom-page/data-management.oyd.jsx +14 -3
  85. package/lib/samples/yida-custom-page/design-tokens.js +2 -2
  86. package/lib/samples/yida-custom-page/detail-profile.oyd.jsx +1 -1
  87. package/lib/samples/yida-custom-page/portal-shell-home.oyd.jsx +7 -2
  88. package/lib/samples/yida-custom-page/split-pane-detail.oyd.jsx +2 -1
  89. package/lib/schema/adapters/app-adapter.js +210 -0
  90. package/lib/schema/adapters/form-adapter.js +1362 -0
  91. package/lib/schema/adapters/keys.js +19 -0
  92. package/lib/schema/adapters/page-adapter.js +566 -0
  93. package/lib/schema/adapters/process-adapter.js +519 -0
  94. package/lib/schema/applier.js +1878 -0
  95. package/lib/schema/apply-store.js +1083 -0
  96. package/lib/schema/command.js +645 -0
  97. package/lib/schema/dependency-graph.js +83 -0
  98. package/lib/schema/errors.js +481 -0
  99. package/lib/schema/hash.js +9 -0
  100. package/lib/schema/manifest-limits.js +177 -0
  101. package/lib/schema/manifest-loader.js +309 -0
  102. package/lib/schema/manifest-schema-v1.json +193 -0
  103. package/lib/schema/normalize-manifest.js +281 -0
  104. package/lib/schema/page-canvas-foundation.js +447 -0
  105. package/lib/schema/page-data-source-builder.js +561 -0
  106. package/lib/schema/page-foundation.js +556 -0
  107. package/lib/schema/page-source-loader.js +314 -0
  108. package/lib/schema/planner.js +691 -0
  109. package/lib/schema/remote-dispatch-boundary.js +39 -0
  110. package/lib/schema/remote-missing.js +21 -0
  111. package/lib/schema/remote-reader.js +189 -0
  112. package/lib/schema/resource-registry.js +157 -0
  113. package/lib/schema/server-revision.js +71 -0
  114. package/lib/schema/sort.js +28 -0
  115. package/lib/schema/state-store.js +589 -0
  116. package/package.json +10 -8
  117. package/scripts/check-release-risks.js +254 -0
  118. package/scripts/generate-command-docs.js +6 -1
  119. package/scripts/i18n-baseline.json +11 -11
  120. package/scripts/postinstall.js +8 -12
  121. package/scripts/validate-ci.sh +15 -11
  122. package/scripts/validate-i18n.js +30 -9
  123. package/scripts/validate-package-size.js +5 -2
  124. package/scripts/validate-skills.js +4 -0
  125. package/scripts/validate-structure.js +15 -0
  126. package/yida-skills/SKILL.md +117 -30
  127. package/yida-skills/references/schema-as-code-phase1.md +99 -0
  128. package/yida-skills/references/setup-and-env.md +59 -59
  129. package/yida-skills/references/task-retrospective.md +6 -6
  130. package/yida-skills/skills/yida-app/SKILL.md +78 -17
  131. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +18 -7
  132. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +3 -3
  133. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +15 -0
  134. package/yida-skills/skills/yida-create-app/SKILL.md +21 -3
  135. package/yida-skills/skills/yida-create-form-page/SKILL.md +19 -1
  136. package/yida-skills/skills/yida-create-page/SKILL.md +16 -3
  137. package/yida-skills/skills/yida-create-process/SKILL.md +100 -4
  138. package/yida-skills/skills/yida-custom-page/SKILL.md +19 -4
  139. package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
  140. package/yida-skills/skills/yida-dashboard/references/pitfalls.md +1 -1
  141. package/yida-skills/skills/yida-get-schema/SKILL.md +49 -5
  142. package/yida-skills/skills/yida-i18n/SKILL.md +1 -1
  143. package/yida-skills/skills/yida-integration/SKILL.md +7 -1
  144. package/yida-skills/skills/yida-login/SKILL.md +50 -99
  145. package/yida-skills/skills/yida-page-config/SKILL.md +7 -1
  146. package/yida-skills/skills/yida-page-uiux/references/app/blueprint.md +0 -1
  147. package/yida-skills/skills/yida-page-uiux/references/app/navigation-patterns.md +0 -1
  148. package/yida-skills/skills/yida-page-uiux/references/app/role-journey.md +0 -1
  149. package/yida-skills/skills/yida-page-uiux/references/dashboard/app-blueprint.md +0 -1
  150. package/yida-skills/skills/yida-page-uiux/references/dashboard/chart-contracts.md +0 -1
  151. package/yida-skills/skills/yida-page-uiux/references/dashboard/component-contracts.md +0 -1
  152. package/yida-skills/skills/yida-page-uiux/references/dashboard/layout-patterns.md +0 -1
  153. package/yida-skills/skills/yida-page-uiux/references/dashboard/theme-recipes.md +0 -1
  154. package/yida-skills/skills/yida-page-uiux/references/detail/object-narrative.md +0 -1
  155. package/yida-skills/skills/yida-page-uiux/references/detail/section-patterns.md +0 -1
  156. package/yida-skills/skills/yida-page-uiux/references/detail/timeline-and-related.md +0 -1
  157. package/yida-skills/skills/yida-page-uiux/references/landing/assets-workflow.md +0 -1
  158. package/yida-skills/skills/yida-page-uiux/references/landing/industry-playbooks.md +0 -1
  159. package/yida-skills/skills/yida-page-uiux/references/landing/research-levels.md +0 -1
  160. package/yida-skills/skills/yida-page-uiux/references/landing/section-patterns.md +0 -1
  161. package/yida-skills/skills/yida-page-uiux/references/list/drawer-detail.md +0 -1
  162. package/yida-skills/skills/yida-page-uiux/references/list/empty-loading-error.md +0 -1
  163. package/yida-skills/skills/yida-page-uiux/references/list/filter-patterns.md +0 -1
  164. package/yida-skills/skills/yida-page-uiux/references/list/table-patterns.md +0 -1
  165. package/yida-skills/skills/yida-page-uiux/references/scenes/screen.md +0 -1
  166. package/yida-skills/skills/yida-page-uiux/references/workbench/entry-patterns.md +0 -1
  167. package/yida-skills/skills/yida-page-uiux/references/workbench/portal-layouts.md +0 -1
  168. package/yida-skills/skills/yida-page-uiux/references/workbench/task-feed.md +0 -1
  169. package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +1 -1
  170. package/yida-skills/skills/yida-process-rule/SKILL.md +35 -1
  171. package/yida-skills/skills/yida-publish-page/SKILL.md +46 -4
  172. package/yida-skills/skills/yida-report/SKILL.md +9 -235
  173. package/yida-skills/skills/yida-report/references/schema-builder-details.md +239 -0
  174. package/yida-skills/skills/yida-skill-evaluator/SKILL.md +170 -0
  175. package/yida-skills/skills-index.json +238 -54
  176. package/lib/core/locales/ar.js +0 -834
  177. package/lib/core/locales/de.js +0 -834
  178. package/lib/core/locales/es.js +0 -834
  179. package/lib/core/locales/fr.js +0 -834
  180. package/lib/core/locales/hi.js +0 -834
  181. package/lib/core/locales/ja.js +0 -1227
  182. package/lib/core/locales/ko.js +0 -836
  183. package/lib/core/locales/pt.js +0 -834
  184. package/lib/core/locales/vi.js +0 -834
  185. package/lib/core/locales/zh-HK.js +0 -1263
  186. package/project/pages/src/demo-birthday-game.oyd.jsx +0 -832
  187. package/project/pages/src/demo-chip-insight.oyd.jsx +0 -983
  188. package/project/pages/src/demo-compat-smoke.oyd.jsx +0 -58
  189. package/project/pages/src/demo-crm-batch-entry.oyd.jsx +0 -805
  190. package/project/pages/src/demo-crm-dashboard.oyd.jsx +0 -677
  191. package/project/pages/src/demo-future-vision-2026.oyd.jsx +0 -1102
  192. package/project/pages/src/demo-ppt.oyd.jsx +0 -1192
  193. package/project/pages/src/demo-salary-calculator.oyd.jsx +0 -904
  194. package/project/pages/src/openyida-knowledge-doc.oyd.jsx +0 -1714
  195. package/yida-skills/skills/large-file-write/SKILL.md +0 -93
  196. package/yida-skills/skills/large-file-write/references/write-patterns.md +0 -147
  197. package/yida-skills/skills/large-file-write/scripts/write.js +0 -157
  198. package/yida-skills/skills/sls-log-workbench/SKILL.md +0 -134
  199. package/yida-skills/skills/sls-log-workbench/references/workbench-reference.md +0 -130
  200. package/yida-skills/skills/sls-log-workbench/sls-query.js +0 -295
@@ -1,805 +0,0 @@
1
- // ============================================================
2
- // CRM 批量数据录入页面
3
- // 支持:客户信息、商机管理、客户回访 三种表单的批量录入
4
- // ============================================================
5
-
6
- // ── 配置区 ─────────────────────────────────────────────────
7
-
8
- var APP_TYPE = 'APP_MRNQXYA404IHD2P6JHSD';
9
- var DRAFT_KEY_PREFIX = 'yida_crm_batch_entry_';
10
-
11
- var FORM_CONFIG = {
12
- customer: {
13
- formUuid: 'FORM-48FB56AFA31E43B09BDE30B7165D3E92D7WT',
14
- name: '客户信息',
15
- columns: [
16
- { label: '客户名称', field: 'textField_l1h42qsqz', type: 'text', required: true },
17
- { label: '客户类型', field: 'radioField_l1h43yhd1', type: 'select', options: ['企业客户', '个人客户'] },
18
- { label: '客户级别', field: 'selectField_l1h54iojj', type: 'select', options: ['A级', 'B级', 'C级', 'D级'] },
19
- { label: '联系人', field: 'textField_l1h55ogqo', type: 'text' },
20
- { label: '联系电话', field: 'textField_l1h566r7t', type: 'text' },
21
- { label: '联系邮箱', field: 'textField_l1h57ofjl', type: 'text' }
22
- ]
23
- },
24
- opportunity: {
25
- formUuid: 'FORM-ECCF67D4CC994968B2675C9CBD0F04A536P0',
26
- name: '商机管理',
27
- columns: [
28
- { label: '商机名称', field: 'textField_ozj624rw0', type: 'text', required: true },
29
- { label: '商机阶段', field: 'selectField_ozj64pdqc', type: 'select', options: ['初步接触', '需求确认', '方案报价', '谈判', '赢单', '输单'] },
30
- { label: '预计金额(万)', field: 'numberField_ozj651thb', type: 'number' },
31
- { label: '成交概率(%)', field: 'numberField_ozj66qh91', type: 'number' },
32
- { label: '商机来源', field: 'radioField_ozj69jqrp', type: 'select', options: ['新开发', '老客户', '合作伙伴'] }
33
- ]
34
- },
35
- visit: {
36
- formUuid: 'FORM-21CC57767B184FB389DD0E9E765F028D77OA',
37
- name: '客户回访',
38
- columns: [
39
- { label: '回访方式', field: 'radioField_pjlt40nnj', type: 'select', options: ['电话', '上门', '视频', '邮件'] },
40
- { label: '满意度(1-5)', field: 'rateField_pjlt5hp2q', type: 'number' },
41
- { label: '回访记录', field: 'textareaField_pjlt8k3dg', type: 'text' }
42
- ]
43
- }
44
- };
45
-
46
- var FORM_OPTIONS = [
47
- { key: 'customer', label: '客户信息' },
48
- { key: 'opportunity', label: '商机管理' },
49
- { key: 'visit', label: '客户回访' }
50
- ];
51
-
52
- // ── 状态 ───────────────────────────────────────────────────
53
-
54
- var _customState = {
55
- selectedForm: 'customer', // 当前选择的表单类型
56
- rows: [], // 行数据列表
57
- selectedRows: {}, // 选中的行 { rowId: true }
58
- submitting: false, // 是否正在提交
59
- submitProgress: { current: 0, total: 0 }, // 提交进度
60
- submitResult: null // { success: number, failed: number }
61
- };
62
-
63
- // ── 工具函数 ───────────────────────────────────────────────
64
-
65
- function generateRowId() {
66
- return 'row_' + Date.now() + '_' + Math.random().toString(36).slice(2, 7);
67
- }
68
-
69
- function getCurrentFormConfig() {
70
- return FORM_CONFIG[_customState.selectedForm];
71
- }
72
-
73
- function getDraftKey() {
74
- return DRAFT_KEY_PREFIX + _customState.selectedForm;
75
- }
76
-
77
- function createEmptyRow() {
78
- var row = { id: generateRowId(), _status: 'valid', _errors: {} };
79
- var config = getCurrentFormConfig();
80
- config.columns.forEach(function(col) { row[col.field] = ''; });
81
- return row;
82
- }
83
-
84
- function validateRow(row) {
85
- var errors = {};
86
- var config = getCurrentFormConfig();
87
- config.columns.forEach(function(col) {
88
- if (col.required && !row[col.field]) {
89
- errors[col.field] = col.label + '不能为空';
90
- }
91
- if (col.type === 'number' && row[col.field]) {
92
- var num = Number(row[col.field]);
93
- if (isNaN(num)) {
94
- errors[col.field] = col.label + '必须是数字';
95
- }
96
- }
97
- });
98
- return errors;
99
- }
100
-
101
- function saveDraft(rows) {
102
- try {
103
- localStorage.setItem(getDraftKey(), JSON.stringify(rows));
104
- } catch (e) { /* ignore */ }
105
- }
106
-
107
- function loadDraft() {
108
- try {
109
- var saved = localStorage.getItem(getDraftKey());
110
- return saved ? JSON.parse(saved) : null;
111
- } catch (e) {
112
- return null;
113
- }
114
- }
115
-
116
- function clearDraft() {
117
- try { localStorage.removeItem(getDraftKey()); } catch (e) { /* ignore */ }
118
- }
119
-
120
- // ── 状态管理 ───────────────────────────────────────────────
121
-
122
- export function getCustomState(key) {
123
- if (key) return _customState[key];
124
- return _.clone(_customState);
125
- }
126
-
127
- export function setCustomState(newState) {
128
- Object.keys(newState).forEach(function(key) {
129
- _customState[key] = newState[key];
130
- });
131
- this.forceUpdate();
132
- }
133
-
134
- export function forceUpdate() {
135
- this.setState({ timestamp: new Date().getTime() });
136
- }
137
-
138
- // ── 生命周期 ───────────────────────────────────────────────
139
-
140
- export function didMount() {
141
- // 绑定粘贴事件
142
- var self = this;
143
- self._pasteHandler = function(e) { self.handlePaste(e); };
144
- document.addEventListener('paste', self._pasteHandler);
145
-
146
- // 恢复草稿
147
- var draft = loadDraft();
148
- if (draft && draft.length > 0) {
149
- _customState.rows = draft;
150
- } else {
151
- _customState.rows = [createEmptyRow(), createEmptyRow(), createEmptyRow()];
152
- }
153
- this.forceUpdate();
154
- }
155
-
156
- export function didUnmount() {
157
- // 清理粘贴事件监听
158
- if (this._pasteHandler) {
159
- document.removeEventListener('paste', this._pasteHandler);
160
- }
161
- }
162
-
163
- // ── 表单切换 ───────────────────────────────────────────────
164
-
165
- export function handleFormChange(e) {
166
- var newFormKey = e.target.value;
167
- // 保存当前表单的草稿
168
- saveDraft(_customState.rows);
169
-
170
- // 切换表单
171
- _customState.selectedForm = newFormKey;
172
- _customState.selectedRows = {};
173
- _customState.submitResult = null;
174
-
175
- // 加载新表单的草稿
176
- var draft = loadDraft();
177
- if (draft && draft.length > 0) {
178
- _customState.rows = draft;
179
- } else {
180
- _customState.rows = [createEmptyRow(), createEmptyRow(), createEmptyRow()];
181
- }
182
- this.forceUpdate();
183
- }
184
-
185
- // ── 行操作 ─────────────────────────────────────────────────
186
-
187
- export function addRow() {
188
- _customState.rows.push(createEmptyRow());
189
- saveDraft(_customState.rows);
190
- this.forceUpdate();
191
- }
192
-
193
- export function deleteRow(rowId) {
194
- _customState.rows = _customState.rows.filter(function(row) {
195
- return row.id !== rowId;
196
- });
197
- delete _customState.selectedRows[rowId];
198
- if (_customState.rows.length === 0) {
199
- _customState.rows.push(createEmptyRow());
200
- }
201
- saveDraft(_customState.rows);
202
- this.forceUpdate();
203
- }
204
-
205
- export function deleteSelectedRows() {
206
- var selectedIds = Object.keys(_customState.selectedRows);
207
- if (selectedIds.length === 0) {
208
- this.utils.toast({ title: '请先选择要删除的行', type: 'warning' });
209
- return;
210
- }
211
- _customState.rows = _customState.rows.filter(function(row) {
212
- return !_customState.selectedRows[row.id];
213
- });
214
- _customState.selectedRows = {};
215
- if (_customState.rows.length === 0) {
216
- _customState.rows.push(createEmptyRow());
217
- }
218
- saveDraft(_customState.rows);
219
- this.forceUpdate();
220
- }
221
-
222
- export function clearAllRows() {
223
- _customState.rows = [createEmptyRow(), createEmptyRow(), createEmptyRow()];
224
- _customState.selectedRows = {};
225
- _customState.submitResult = null;
226
- clearDraft();
227
- this.forceUpdate();
228
- this.utils.toast({ title: '已清空所有数据', type: 'success' });
229
- }
230
-
231
- export function toggleRowSelection(rowId) {
232
- if (_customState.selectedRows[rowId]) {
233
- delete _customState.selectedRows[rowId];
234
- } else {
235
- _customState.selectedRows[rowId] = true;
236
- }
237
- this.forceUpdate();
238
- }
239
-
240
- export function toggleSelectAll() {
241
- var allSelected = _customState.rows.length > 0 &&
242
- _customState.rows.every(function(row) { return _customState.selectedRows[row.id]; });
243
-
244
- if (allSelected) {
245
- _customState.selectedRows = {};
246
- } else {
247
- _customState.rows.forEach(function(row) {
248
- _customState.selectedRows[row.id] = true;
249
- });
250
- }
251
- this.forceUpdate();
252
- }
253
-
254
- export function updateCell(rowId, field, value) {
255
- var row = _customState.rows.find(function(r) { return r.id === rowId; });
256
- if (!row) return;
257
- row[field] = value;
258
- // 清除该字段的错误
259
- if (row._errors[field]) {
260
- delete row._errors[field];
261
- row._status = Object.keys(row._errors).length === 0 ? 'valid' : 'invalid';
262
- }
263
- saveDraft(_customState.rows);
264
- // 不触发重渲染,静默更新
265
- }
266
-
267
- // ── Excel 粘贴导入 ─────────────────────────────────────────
268
-
269
- export function handlePaste(event) {
270
- var clipboardData = event.clipboardData || window.clipboardData;
271
- if (!clipboardData) return;
272
- var text = clipboardData.getData('text');
273
- if (!text) return;
274
-
275
- var config = getCurrentFormConfig();
276
- var lines = text.trim().split('\n');
277
- var newRows = lines.map(function(line) {
278
- var cells = line.split('\t');
279
- var row = createEmptyRow();
280
- config.columns.forEach(function(col, index) {
281
- if (cells[index] !== undefined) {
282
- row[col.field] = cells[index].trim();
283
- }
284
- });
285
- return row;
286
- });
287
-
288
- // 过滤掉全空行
289
- newRows = newRows.filter(function(row) {
290
- return config.columns.some(function(col) { return row[col.field]; });
291
- });
292
-
293
- if (newRows.length === 0) return;
294
-
295
- // 追加到现有行(过滤掉现有的全空行)
296
- var nonEmptyExisting = _customState.rows.filter(function(row) {
297
- return config.columns.some(function(col) { return row[col.field]; });
298
- });
299
- _customState.rows = nonEmptyExisting.concat(newRows);
300
- saveDraft(_customState.rows);
301
- this.forceUpdate();
302
- this.utils.toast({ title: '已导入 ' + newRows.length + ' 行数据', type: 'success' });
303
- }
304
-
305
- // ── 批量提交 ───────────────────────────────────────────────
306
-
307
- export function submitAll() {
308
- var self = this;
309
- var config = getCurrentFormConfig();
310
-
311
- // 1. 验证所有行
312
- var hasError = false;
313
- _customState.rows.forEach(function(row) {
314
- var errors = validateRow(row);
315
- row._errors = errors;
316
- if (Object.keys(errors).length > 0) {
317
- row._status = 'invalid';
318
- hasError = true;
319
- }
320
- });
321
-
322
- if (hasError) {
323
- self.forceUpdate();
324
- self.utils.toast({ title: '请修正表格中的错误后再提交', type: 'error' });
325
- return;
326
- }
327
-
328
- // 2. 过滤掉全空行
329
- var rowsToSubmit = _customState.rows.filter(function(row) {
330
- return config.columns.some(function(col) { return row[col.field]; });
331
- });
332
-
333
- if (rowsToSubmit.length === 0) {
334
- self.utils.toast({ title: '请至少填写一行数据', type: 'error' });
335
- return;
336
- }
337
-
338
- _customState.submitting = true;
339
- _customState.submitProgress = { current: 0, total: rowsToSubmit.length };
340
- _customState.submitResult = null;
341
- self.forceUpdate();
342
-
343
- // 3. 批量提交(并发)
344
- var completedCount = 0;
345
- var promises = rowsToSubmit.map(function(row) {
346
- var formDataJson = {};
347
- config.columns.forEach(function(col) {
348
- var val = row[col.field];
349
- // 数字类型转换
350
- if (col.type === 'number' && val) {
351
- formDataJson[col.field] = Number(val);
352
- } else {
353
- formDataJson[col.field] = val;
354
- }
355
- });
356
-
357
- row._status = 'submitting';
358
-
359
- return self.utils.yida.saveFormData({
360
- formUuid: config.formUuid,
361
- appType: APP_TYPE,
362
- formDataJson: JSON.stringify(formDataJson)
363
- }).then(function() {
364
- row._status = 'submitted';
365
- completedCount++;
366
- _customState.submitProgress.current = completedCount;
367
- self.forceUpdate();
368
- }).catch(function(err) {
369
- row._status = 'invalid';
370
- row._errors._submit = err.message || '提交失败';
371
- completedCount++;
372
- _customState.submitProgress.current = completedCount;
373
- self.forceUpdate();
374
- });
375
- });
376
-
377
- Promise.all(promises).then(function() {
378
- var successCount = rowsToSubmit.filter(function(r) { return r._status === 'submitted'; }).length;
379
- var failedCount = rowsToSubmit.filter(function(r) { return r._status === 'invalid'; }).length;
380
-
381
- _customState.submitting = false;
382
- _customState.submitResult = { success: successCount, failed: failedCount };
383
-
384
- if (failedCount === 0) {
385
- clearDraft();
386
- _customState.rows = [createEmptyRow(), createEmptyRow(), createEmptyRow()];
387
- self.utils.toast({ title: '全部提交成功,共 ' + successCount + ' 条', type: 'success' });
388
- } else {
389
- self.utils.toast({
390
- title: '提交完成:' + successCount + ' 条成功,' + failedCount + ' 条失败',
391
- type: 'error'
392
- });
393
- }
394
- self.forceUpdate();
395
- }).catch(function(err) {
396
- _customState.submitting = false;
397
- self.forceUpdate();
398
- self.utils.toast({ title: '提交异常:' + err.message, type: 'error' });
399
- });
400
- }
401
-
402
- // ── 渲染辅助 ───────────────────────────────────────────────
403
-
404
- export function renderCellInput(row, col) {
405
- var self = this;
406
- var value = row[col.field];
407
- var hasError = !!row._errors[col.field];
408
- var isSubmitted = row._status === 'submitted';
409
-
410
- var baseInputStyle = {
411
- width: '100%',
412
- height: '32px',
413
- padding: '0 8px',
414
- border: '1px solid ' + (hasError ? '#ff4d4f' : '#d9d9d9'),
415
- borderRadius: '4px',
416
- fontSize: '13px',
417
- outline: 'none',
418
- background: isSubmitted ? '#f6ffed' : '#fff',
419
- boxSizing: 'border-box'
420
- };
421
-
422
- if (col.type === 'select') {
423
- return (
424
- <select
425
- defaultValue={value}
426
- disabled={isSubmitted}
427
- onChange={(e) => { self.updateCell(row.id, col.field, e.target.value); }}
428
- style={baseInputStyle}
429
- >
430
- <option value="">请选择</option>
431
- {(col.options || []).map(function(opt) {
432
- return <option key={opt} value={opt}>{opt}</option>;
433
- })}
434
- </select>
435
- );
436
- }
437
-
438
- return (
439
- <input
440
- type={col.type === 'number' ? 'number' : 'text'}
441
- defaultValue={value}
442
- disabled={isSubmitted}
443
- placeholder={col.required ? col.label + '(必填)' : col.label}
444
- onChange={(e) => { self.updateCell(row.id, col.field, e.target.value); }}
445
- style={baseInputStyle}
446
- />
447
- );
448
- }
449
-
450
- // ── 主渲染 ─────────────────────────────────────────────────
451
-
452
- export function renderJsx() {
453
- var self = this;
454
- var timestamp = this.state.timestamp;
455
- var config = getCurrentFormConfig();
456
- var columns = config.columns;
457
-
458
- var allSelected = _customState.rows.length > 0 &&
459
- _customState.rows.every(function(row) { return _customState.selectedRows[row.id]; });
460
- var selectedCount = Object.keys(_customState.selectedRows).length;
461
-
462
- // ── 样式定义 ─────────────────────────────────────────────
463
- var styles = {
464
- container: {
465
- padding: '16px',
466
- background: '#f5f7fa',
467
- minHeight: '100vh',
468
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
469
- },
470
- header: {
471
- display: 'flex',
472
- justifyContent: 'space-between',
473
- alignItems: 'center',
474
- marginBottom: '16px',
475
- padding: '16px 20px',
476
- background: '#fff',
477
- borderRadius: '8px',
478
- boxShadow: '0 1px 3px rgba(0,0,0,0.08)'
479
- },
480
- title: {
481
- fontSize: '18px',
482
- fontWeight: 600,
483
- color: '#1f2937'
484
- },
485
- formSelect: {
486
- height: '36px',
487
- padding: '0 12px',
488
- fontSize: '14px',
489
- border: '1px solid #d9d9d9',
490
- borderRadius: '6px',
491
- background: '#fff',
492
- marginLeft: '12px',
493
- cursor: 'pointer'
494
- },
495
- toolbar: {
496
- display: 'flex',
497
- gap: '8px',
498
- marginBottom: '12px',
499
- padding: '12px 16px',
500
- background: '#fff',
501
- borderRadius: '8px',
502
- boxShadow: '0 1px 3px rgba(0,0,0,0.08)',
503
- flexWrap: 'wrap',
504
- alignItems: 'center'
505
- },
506
- btn: {
507
- height: '32px',
508
- padding: '0 14px',
509
- fontSize: '13px',
510
- border: '1px solid #d9d9d9',
511
- borderRadius: '6px',
512
- background: '#fff',
513
- cursor: 'pointer',
514
- display: 'flex',
515
- alignItems: 'center',
516
- gap: '4px'
517
- },
518
- btnPrimary: {
519
- height: '32px',
520
- padding: '0 16px',
521
- fontSize: '13px',
522
- border: 'none',
523
- borderRadius: '6px',
524
- background: '#1677ff',
525
- color: '#fff',
526
- cursor: 'pointer',
527
- display: 'flex',
528
- alignItems: 'center',
529
- gap: '4px'
530
- },
531
- btnDanger: {
532
- height: '32px',
533
- padding: '0 14px',
534
- fontSize: '13px',
535
- border: '1px solid #ff4d4f',
536
- borderRadius: '6px',
537
- background: '#fff',
538
- color: '#ff4d4f',
539
- cursor: 'pointer'
540
- },
541
- tableContainer: {
542
- background: '#fff',
543
- borderRadius: '8px',
544
- overflow: 'hidden',
545
- boxShadow: '0 1px 3px rgba(0,0,0,0.08)'
546
- },
547
- tableHeader: {
548
- display: 'grid',
549
- gridTemplateColumns: '40px ' + columns.map(function() { return '1fr'; }).join(' ') + ' 60px',
550
- background: '#fafafa',
551
- borderBottom: '1px solid #f0f0f0',
552
- padding: '10px 12px'
553
- },
554
- headerCell: {
555
- fontSize: '13px',
556
- fontWeight: 600,
557
- color: '#595959'
558
- },
559
- tableRow: {
560
- display: 'grid',
561
- gridTemplateColumns: '40px ' + columns.map(function() { return '1fr'; }).join(' ') + ' 60px',
562
- padding: '8px 12px',
563
- borderBottom: '1px solid #f0f0f0',
564
- alignItems: 'start'
565
- },
566
- checkbox: {
567
- width: '16px',
568
- height: '16px',
569
- cursor: 'pointer',
570
- marginTop: '8px'
571
- },
572
- cellWrapper: {
573
- paddingRight: '8px'
574
- },
575
- errorText: {
576
- fontSize: '11px',
577
- color: '#ff4d4f',
578
- marginTop: '2px'
579
- },
580
- deleteBtn: {
581
- border: 'none',
582
- background: 'none',
583
- color: '#ff4d4f',
584
- cursor: 'pointer',
585
- fontSize: '16px',
586
- padding: '4px 8px'
587
- },
588
- progressBar: {
589
- marginTop: '12px',
590
- padding: '12px 16px',
591
- background: '#fff',
592
- borderRadius: '8px',
593
- boxShadow: '0 1px 3px rgba(0,0,0,0.08)'
594
- },
595
- progressTrack: {
596
- height: '8px',
597
- background: '#f0f0f0',
598
- borderRadius: '4px',
599
- overflow: 'hidden',
600
- marginTop: '8px'
601
- },
602
- progressFill: {
603
- height: '100%',
604
- background: '#1677ff',
605
- borderRadius: '4px',
606
- transition: 'width 0.3s ease'
607
- },
608
- resultBox: {
609
- marginTop: '12px',
610
- padding: '12px 16px',
611
- borderRadius: '8px',
612
- fontSize: '14px'
613
- },
614
- tip: {
615
- marginTop: '8px',
616
- fontSize: '12px',
617
- color: '#8c8c8c',
618
- textAlign: 'right'
619
- }
620
- };
621
-
622
- return (
623
- <div style={styles.container}>
624
- {/* 隐藏的 timestamp 用于触发重渲染 */}
625
- <div style={{ display: 'none' }}>{timestamp}</div>
626
-
627
- {/* 标题栏 */}
628
- <div style={styles.header}>
629
- <div style={{ display: 'flex', alignItems: 'center' }}>
630
- <span style={styles.title}>批量数据录入</span>
631
- <select
632
- value={_customState.selectedForm}
633
- onChange={(e) => { self.handleFormChange(e); }}
634
- style={styles.formSelect}
635
- >
636
- {FORM_OPTIONS.map(function(opt) {
637
- return <option key={opt.key} value={opt.key}>{opt.label}</option>;
638
- })}
639
- </select>
640
- </div>
641
- <div style={{ fontSize: '13px', color: '#8c8c8c' }}>
642
- 当前:{config.name}({_customState.rows.length} 行)
643
- </div>
644
- </div>
645
-
646
- {/* 工具栏 */}
647
- <div style={styles.toolbar}>
648
- <button
649
- onClick={() => { self.addRow(); }}
650
- style={styles.btn}
651
- >
652
- ➕ 添加行
653
- </button>
654
- <button
655
- onClick={() => { self.deleteSelectedRows(); }}
656
- style={Object.assign({}, styles.btn, selectedCount > 0 ? { borderColor: '#ff4d4f', color: '#ff4d4f' } : {})}
657
- >
658
- 🗑 删除选中 {selectedCount > 0 ? '(' + selectedCount + ')' : ''}
659
- </button>
660
- <button
661
- onClick={() => { self.clearAllRows(); }}
662
- style={styles.btn}
663
- >
664
- 🔄 清空
665
- </button>
666
- <div style={{ flex: 1 }} />
667
- <span style={{ fontSize: '12px', color: '#8c8c8c', marginRight: '8px' }}>
668
- 💡 提示:可直接从 Excel 复制数据粘贴导入
669
- </span>
670
- <button
671
- onClick={() => { self.submitAll(); }}
672
- disabled={_customState.submitting}
673
- style={Object.assign({}, styles.btnPrimary, _customState.submitting ? { background: '#bfbfbf', cursor: 'not-allowed' } : {})}
674
- >
675
- {_customState.submitting ? '⏳ 提交中...' : '✓ 批量提交'}
676
- </button>
677
- </div>
678
-
679
- {/* 表格 */}
680
- <div style={styles.tableContainer}>
681
- {/* 表头 */}
682
- <div style={styles.tableHeader}>
683
- <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
684
- <input
685
- type="checkbox"
686
- checked={allSelected}
687
- onChange={() => { self.toggleSelectAll(); }}
688
- style={styles.checkbox}
689
- />
690
- </div>
691
- {columns.map(function(col) {
692
- return (
693
- <div key={col.field} style={styles.headerCell}>
694
- {col.required && <span style={{ color: '#ff4d4f', marginRight: '2px' }}>*</span>}
695
- {col.label}
696
- </div>
697
- );
698
- })}
699
- <div style={Object.assign({}, styles.headerCell, { textAlign: 'center' })}>操作</div>
700
- </div>
701
-
702
- {/* 数据行 */}
703
- {_customState.rows.map(function(row) {
704
- var rowBg = row._status === 'submitted' ? '#f6ffed'
705
- : row._status === 'invalid' ? '#fff2f0'
706
- : row._status === 'submitting' ? '#e6f7ff'
707
- : '#fff';
708
-
709
- return (
710
- <div key={row.id} style={Object.assign({}, styles.tableRow, { background: rowBg })}>
711
- {/* 选择框 */}
712
- <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
713
- <input
714
- type="checkbox"
715
- checked={!!_customState.selectedRows[row.id]}
716
- disabled={row._status === 'submitted'}
717
- onChange={() => { self.toggleRowSelection(row.id); }}
718
- style={styles.checkbox}
719
- />
720
- </div>
721
-
722
- {/* 数据列 */}
723
- {columns.map(function(col) {
724
- return (
725
- <div key={col.field} style={styles.cellWrapper}>
726
- {self.renderCellInput(row, col)}
727
- {row._errors[col.field] && (
728
- <div style={styles.errorText}>{row._errors[col.field]}</div>
729
- )}
730
- {row._errors._submit && col === columns[0] && (
731
- <div style={styles.errorText}>{row._errors._submit}</div>
732
- )}
733
- </div>
734
- );
735
- })}
736
-
737
- {/* 操作列 */}
738
- <div style={{ textAlign: 'center' }}>
739
- {row._status === 'submitted' && (
740
- <span style={{ color: '#52c41a', fontSize: '16px' }}>✓</span>
741
- )}
742
- {row._status === 'submitting' && (
743
- <span style={{ color: '#1677ff', fontSize: '12px' }}>提交中</span>
744
- )}
745
- {row._status !== 'submitted' && row._status !== 'submitting' && (
746
- <button
747
- onClick={() => { self.deleteRow(row.id); }}
748
- style={styles.deleteBtn}
749
- title="删除此行"
750
- >
751
- 🗑
752
- </button>
753
- )}
754
- </div>
755
- </div>
756
- );
757
- })}
758
- </div>
759
-
760
- {/* 提交进度条 */}
761
- {_customState.submitting && (
762
- <div style={styles.progressBar}>
763
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
764
- <span style={{ fontSize: '14px', fontWeight: 500 }}>提交进度</span>
765
- <span style={{ fontSize: '13px', color: '#1677ff' }}>
766
- {_customState.submitProgress.current} / {_customState.submitProgress.total}
767
- </span>
768
- </div>
769
- <div style={styles.progressTrack}>
770
- <div
771
- style={Object.assign({}, styles.progressFill, {
772
- width: (_customState.submitProgress.total > 0
773
- ? Math.round(_customState.submitProgress.current / _customState.submitProgress.total * 100)
774
- : 0) + '%'
775
- })}
776
- />
777
- </div>
778
- </div>
779
- )}
780
-
781
- {/* 提交结果 */}
782
- {_customState.submitResult && (
783
- <div style={Object.assign({}, styles.resultBox, {
784
- background: _customState.submitResult.failed === 0 ? '#f6ffed' : '#fff2f0',
785
- border: '1px solid ' + (_customState.submitResult.failed === 0 ? '#b7eb8f' : '#ffccc7')
786
- })}>
787
- 提交完成:
788
- <span style={{ color: '#52c41a', fontWeight: 600 }}>
789
- {_customState.submitResult.success} 条成功
790
- </span>
791
- {_customState.submitResult.failed > 0 && (
792
- <span style={{ color: '#ff4d4f', fontWeight: 600, marginLeft: '8px' }}>
793
- {_customState.submitResult.failed} 条失败(请修正红色行后重新提交)
794
- </span>
795
- )}
796
- </div>
797
- )}
798
-
799
- {/* 草稿提示 */}
800
- <div style={styles.tip}>
801
- 数据已自动保存为草稿,刷新页面后可继续编辑
802
- </div>
803
- </div>
804
- );
805
- }