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,832 +0,0 @@
1
- // ============================================================
2
- // 状态管理
3
- // ============================================================
4
-
5
- const _customState = {
6
- // 游戏阶段: 'welcome' | 'playing' | 'wishing' | 'celebrating'
7
- gameStage: 'welcome',
8
- // 寿星姓名
9
- birthdayPersonName: '',
10
- // 送祝福人姓名
11
- senderName: '',
12
- // 祝福语
13
- blessingMessage: '',
14
- // 蜡烛数量 (5-10随机)
15
- totalCandles: 0,
16
- // 已熄灭的蜡烛索引数组
17
- extinguishedCandles: [],
18
- // 倒计时剩余秒数
19
- countdownSeconds: 15,
20
- // 游戏是否超时
21
- isTimeout: false,
22
- // 彩带动画触发器
23
- confettiTrigger: 0,
24
- // 麦克风权限状态
25
- micPermission: 'prompt', // 'prompt' | 'granted' | 'denied'
26
- // 音频分析器
27
- audioAnalyser: null,
28
- // 音频上下文
29
- audioContext: null,
30
- // 麦克风流
31
- micStream: null,
32
- // 检测吹气的定时器
33
- blowDetectionInterval: null,
34
- };
35
-
36
- /**
37
- * 获取状态
38
- */
39
- export function getCustomState(key) {
40
- if (key) {
41
- return _customState[key];
42
- }
43
- return { ..._customState };
44
- }
45
-
46
- /**
47
- * 设置状态(合并更新,自动触发重新渲染)
48
- */
49
- export function setCustomState(newState) {
50
- Object.keys(newState).forEach(function(key) {
51
- _customState[key] = newState[key];
52
- });
53
- this.forceUpdate();
54
- }
55
-
56
- /**
57
- * 强制重新渲染
58
- */
59
- export function forceUpdate() {
60
- this.setState({ timestamp: new Date().getTime() });
61
- }
62
-
63
- // ============================================================
64
- // 生命周期
65
- // ============================================================
66
-
67
- export function didMount() {
68
- // 初始化随机蜡烛数量
69
- var randomCandles = Math.floor(Math.random() * 6) + 5; // 5-10
70
- _customState.totalCandles = randomCandles;
71
- this.forceUpdate();
72
- }
73
-
74
- export function didUnmount() {
75
- // 清理定时器
76
- this.stopCountdown();
77
- this.stopBlowDetection();
78
- // 停止麦克风
79
- if (_customState.micStream) {
80
- _customState.micStream.getTracks().forEach(function(track) {
81
- track.stop();
82
- });
83
- _customState.micStream = null;
84
- }
85
- if (_customState.audioContext) {
86
- _customState.audioContext.close();
87
- _customState.audioContext = null;
88
- }
89
- }
90
-
91
- // ============================================================
92
- // 游戏逻辑方法
93
- // ============================================================
94
-
95
- /**
96
- * 开始游戏
97
- */
98
- export function startGame() {
99
- var nameInput = document.getElementById('birthday-person-input');
100
- var senderInput = document.getElementById('sender-name-input');
101
-
102
- var name = nameInput ? nameInput.value.trim() : '';
103
- var sender = senderInput ? senderInput.value.trim() : '';
104
-
105
- if (!name) {
106
- this.utils.toast({ title: '请输入寿星姓名', type: 'error' });
107
- return;
108
- }
109
-
110
- _customState.birthdayPersonName = name;
111
- _customState.senderName = sender || window.loginUser.userName || '神秘好友';
112
- _customState.gameStage = 'playing';
113
- _customState.extinguishedCandles = [];
114
- _customState.countdownSeconds = 15;
115
- _customState.isTimeout = false;
116
-
117
- this.setCustomState({
118
- gameStage: 'playing',
119
- birthdayPersonName: name,
120
- senderName: _customState.senderName,
121
- extinguishedCandles: [],
122
- countdownSeconds: 15,
123
- isTimeout: false
124
- });
125
-
126
- // 启动倒计时
127
- this.startCountdown();
128
- // 请求麦克风权限并开始检测吹气
129
- this.requestMicrophone();
130
- }
131
-
132
- /**
133
- * 启动倒计时
134
- */
135
- export function startCountdown() {
136
- this.stopCountdown();
137
- _customState.countdownTimer = setInterval(function() {
138
- var current = _customState.countdownSeconds - 1;
139
- _customState.countdownSeconds = current;
140
-
141
- if (current <= 0) {
142
- this.handleTimeout();
143
- } else {
144
- this.forceUpdate();
145
- }
146
- }.bind(this), 1000);
147
- }
148
-
149
- /**
150
- * 停止倒计时
151
- */
152
- export function stopCountdown() {
153
- if (_customState.countdownTimer) {
154
- clearInterval(_customState.countdownTimer);
155
- _customState.countdownTimer = null;
156
- }
157
- }
158
-
159
- /**
160
- * 处理超时
161
- */
162
- export function handleTimeout() {
163
- this.stopCountdown();
164
- _customState.isTimeout = true;
165
- this.setCustomState({ isTimeout: true });
166
- }
167
-
168
- /**
169
- * 重试游戏
170
- */
171
- export function retryGame() {
172
- _customState.extinguishedCandles = [];
173
- _customState.countdownSeconds = 15;
174
- _customState.isTimeout = false;
175
- _customState.totalCandles = Math.floor(Math.random() * 6) + 5;
176
-
177
- this.setCustomState({
178
- extinguishedCandles: [],
179
- countdownSeconds: 15,
180
- isTimeout: false,
181
- totalCandles: _customState.totalCandles
182
- });
183
-
184
- this.startCountdown();
185
- }
186
-
187
- /**
188
- * 熄灭蜡烛(通过点击或吹气)
189
- */
190
- export function extinguishCandle(candleIndex) {
191
- var alreadyExtinguished = _customState.extinguishedCandles.indexOf(candleIndex) !== -1;
192
-
193
- if (alreadyExtinguished || _customState.isTimeout) {
194
- return;
195
- }
196
-
197
- var newExtinguished = _customState.extinguishedCandles.concat([candleIndex]);
198
- _customState.extinguishedCandles = newExtinguished;
199
-
200
- // 检查是否全部熄灭
201
- if (newExtinguished.length >= _customState.totalCandles) {
202
- this.stopCountdown();
203
- this.stopBlowDetection();
204
- setTimeout(function() {
205
- _customState.gameStage = 'wishing';
206
- this.setCustomState({ gameStage: 'wishing' });
207
- }.bind(this), 500);
208
- } else {
209
- this.forceUpdate();
210
- }
211
- }
212
-
213
- /**
214
- * 请求麦克风权限
215
- */
216
- export function requestMicrophone() {
217
- var self = this;
218
- navigator.mediaDevices.getUserMedia({ audio: true }).then(function(stream) {
219
- _customState.micStream = stream;
220
- _customState.micPermission = 'granted';
221
-
222
- // 创建音频上下文和分析器
223
- var audioContext = new (window.AudioContext || window.webkitAudioContext)();
224
- var analyser = audioContext.createAnalyser();
225
- var microphone = audioContext.createMediaStreamSource(stream);
226
-
227
- analyser.fftSize = 256;
228
- microphone.connect(analyser);
229
-
230
- _customState.audioContext = audioContext;
231
- _customState.audioAnalyser = analyser;
232
-
233
- // 开始检测吹气
234
- self.startBlowDetection();
235
- }).catch(function(err) {
236
- _customState.micPermission = 'denied';
237
- // 即使麦克风失败,用户仍可以点击蜡烛
238
- });
239
- }
240
-
241
- /**
242
- * 开始检测吹气
243
- */
244
- export function startBlowDetection() {
245
- this.stopBlowDetection();
246
- var dataArray = new Uint8Array(_customState.audioAnalyser.frequencyBinCount);
247
- var lastBlowTime = 0;
248
- var blowCooldown = 800; // 吹气冷却时间(ms)
249
-
250
- _customState.blowDetectionInterval = setInterval(function() {
251
- if (!_customState.audioAnalyser || _customState.gameStage !== 'playing') {
252
- return;
253
- }
254
-
255
- _customState.audioAnalyser.getByteFrequencyData(dataArray);
256
-
257
- // 计算平均音量
258
- var sum = 0;
259
- for (var i = 0; i < dataArray.length; i++) {
260
- sum += dataArray[i];
261
- }
262
- var average = sum / dataArray.length;
263
-
264
- // 检测吹气(音量阈值设为30)
265
- var now = Date.now();
266
- if (average > 30 && now - lastBlowTime > blowCooldown) {
267
- lastBlowTime = now;
268
-
269
- // 找到第一个未熄灭的蜡烛并熄灭它
270
- for (var j = 0; j < _customState.totalCandles; j++) {
271
- if (_customState.extinguishedCandles.indexOf(j) === -1) {
272
- this.extinguishCandle(j);
273
- break;
274
- }
275
- }
276
- }
277
- }.bind(this), 100);
278
- }
279
-
280
- /**
281
- * 停止检测吹气
282
- */
283
- export function stopBlowDetection() {
284
- if (_customState.blowDetectionInterval) {
285
- clearInterval(_customState.blowDetectionInterval);
286
- _customState.blowDetectionInterval = null;
287
- }
288
- }
289
-
290
- /**
291
- * 提交祝福
292
- */
293
- export function submitBlessing() {
294
- var blessingInput = document.getElementById('blessing-input');
295
- var message = blessingInput ? blessingInput.value.trim() : '';
296
-
297
- if (!message) {
298
- this.utils.toast({ title: '请输入祝福语', type: 'error' });
299
- return;
300
- }
301
-
302
- _customState.blessingMessage = message;
303
- _customState.gameStage = 'celebrating';
304
- _customState.confettiTrigger = Date.now();
305
-
306
- this.setCustomState({
307
- blessingMessage: message,
308
- gameStage: 'celebrating',
309
- confettiTrigger: Date.now()
310
- });
311
- }
312
-
313
- /**
314
- * 重新开始
315
- */
316
- export function restartGame() {
317
- _customState.gameStage = 'welcome';
318
- _customState.birthdayPersonName = '';
319
- _customState.senderName = '';
320
- _customState.blessingMessage = '';
321
- _customState.extinguishedCandles = [];
322
- _customState.countdownSeconds = 15;
323
- _customState.isTimeout = false;
324
- _customState.totalCandles = Math.floor(Math.random() * 6) + 5;
325
-
326
- // 清空输入框
327
- var nameInput = document.getElementById('birthday-person-input');
328
- var senderInput = document.getElementById('sender-name-input');
329
- var blessingInput = document.getElementById('blessing-input');
330
-
331
- if (nameInput) nameInput.value = '';
332
- if (senderInput) senderInput.value = '';
333
- if (blessingInput) blessingInput.value = '';
334
-
335
- this.setCustomState({
336
- gameStage: 'welcome',
337
- birthdayPersonName: '',
338
- senderName: '',
339
- blessingMessage: '',
340
- extinguishedCandles: [],
341
- countdownSeconds: 15,
342
- isTimeout: false,
343
- totalCandles: _customState.totalCandles
344
- });
345
- }
346
-
347
- // ============================================================
348
- // 样式(提取到模块顶层,供各渲染函数共享)
349
- // ============================================================
350
-
351
- var GAME_STYLES = {
352
- container: {
353
- minHeight: '100vh',
354
- background: 'linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%)',
355
- display: 'flex',
356
- flexDirection: 'column',
357
- alignItems: 'center',
358
- justifyContent: 'center',
359
- padding: '20px',
360
- fontFamily: '"PingFang SC", "Microsoft YaHei", sans-serif',
361
- position: 'relative',
362
- overflow: 'hidden'
363
- },
364
- card: {
365
- background: 'rgba(255, 255, 255, 0.95)',
366
- borderRadius: '24px',
367
- padding: '40px',
368
- maxWidth: '480px',
369
- width: '100%',
370
- boxShadow: '0 20px 60px rgba(0, 0, 0, 0.3)',
371
- textAlign: 'center',
372
- position: 'relative',
373
- zIndex: 10
374
- },
375
- title: {
376
- fontSize: '32px',
377
- fontWeight: 'bold',
378
- color: '#764ba2',
379
- marginBottom: '10px',
380
- display: 'flex',
381
- alignItems: 'center',
382
- justifyContent: 'center',
383
- gap: '10px'
384
- },
385
- subtitle: {
386
- fontSize: '16px',
387
- color: '#666',
388
- marginBottom: '30px'
389
- },
390
- input: {
391
- width: '100%',
392
- padding: '15px 20px',
393
- fontSize: '16px',
394
- border: '2px solid #e0e0e0',
395
- borderRadius: '12px',
396
- marginBottom: '20px',
397
- outline: 'none',
398
- transition: 'border-color 0.3s',
399
- boxSizing: 'border-box'
400
- },
401
- button: {
402
- width: '100%',
403
- padding: '16px 32px',
404
- fontSize: '18px',
405
- fontWeight: 'bold',
406
- color: '#fff',
407
- background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
408
- border: 'none',
409
- borderRadius: '12px',
410
- cursor: 'pointer',
411
- transition: 'transform 0.2s, box-shadow 0.2s',
412
- marginTop: '10px'
413
- },
414
- cakeContainer: {
415
- position: 'relative',
416
- margin: '30px 0'
417
- },
418
- cake: {
419
- fontSize: '120px',
420
- lineHeight: '1'
421
- },
422
- candlesRow: {
423
- position: 'absolute',
424
- top: '-20px',
425
- left: '50%',
426
- transform: 'translateX(-50%)',
427
- display: 'flex',
428
- gap: '8px',
429
- justifyContent: 'center'
430
- },
431
- candle: {
432
- fontSize: '28px',
433
- cursor: 'pointer',
434
- transition: 'transform 0.2s, opacity 0.3s',
435
- userSelect: 'none'
436
- },
437
- candleLit: {
438
- animation: 'flicker 0.5s infinite alternate'
439
- },
440
- candleExtinguished: {
441
- opacity: 0.5,
442
- transform: 'scale(0.9)'
443
- },
444
- countdown: {
445
- fontSize: '48px',
446
- fontWeight: 'bold',
447
- color: '#e74c3c',
448
- margin: '20px 0'
449
- },
450
- progressBar: {
451
- width: '100%',
452
- height: '8px',
453
- background: '#e0e0e0',
454
- borderRadius: '4px',
455
- overflow: 'hidden',
456
- marginBottom: '20px'
457
- },
458
- progressFill: {
459
- height: '100%',
460
- background: 'linear-gradient(90deg, #667eea, #764ba2)',
461
- transition: 'width 0.3s ease'
462
- },
463
- textarea: {
464
- width: '100%',
465
- padding: '15px 20px',
466
- fontSize: '16px',
467
- border: '2px solid #e0e0e0',
468
- borderRadius: '12px',
469
- minHeight: '100px',
470
- resize: 'vertical',
471
- outline: 'none',
472
- boxSizing: 'border-box',
473
- fontFamily: 'inherit'
474
- },
475
- blessingCard: {
476
- background: 'linear-gradient(135deg, #ffeaa7 0%, #fab1a0 50%, #fd79a8 100%)',
477
- borderRadius: '20px',
478
- padding: '40px',
479
- color: '#fff',
480
- textShadow: '0 2px 4px rgba(0,0,0,0.2)'
481
- },
482
- blessingTitle: {
483
- fontSize: '36px',
484
- fontWeight: 'bold',
485
- marginBottom: '20px'
486
- },
487
- blessingText: {
488
- fontSize: '24px',
489
- lineHeight: '1.6',
490
- marginBottom: '30px',
491
- fontStyle: 'italic'
492
- },
493
- blessingFrom: {
494
- fontSize: '16px',
495
- opacity: 0.9
496
- },
497
- confetti: {
498
- position: 'fixed',
499
- top: 0,
500
- left: 0,
501
- width: '100%',
502
- height: '100%',
503
- pointerEvents: 'none',
504
- zIndex: 100
505
- },
506
- micHint: {
507
- fontSize: '14px',
508
- color: '#888',
509
- marginTop: '15px',
510
- display: 'flex',
511
- alignItems: 'center',
512
- justifyContent: 'center',
513
- gap: '6px'
514
- },
515
- timeoutOverlay: {
516
- position: 'absolute',
517
- top: 0,
518
- left: 0,
519
- right: 0,
520
- bottom: 0,
521
- background: 'rgba(0,0,0,0.7)',
522
- borderRadius: '24px',
523
- display: 'flex',
524
- flexDirection: 'column',
525
- alignItems: 'center',
526
- justifyContent: 'center',
527
- color: '#fff',
528
- zIndex: 20
529
- },
530
- star: {
531
- position: 'absolute',
532
- color: '#ffd700',
533
- fontSize: '20px',
534
- animation: 'twinkle 1s infinite'
535
- }
536
- };
537
-
538
- // ============================================================
539
- // 子渲染函数(export function,this 正确绑定)
540
- // ============================================================
541
-
542
- /**
543
- * 渲染蜡烛列表
544
- */
545
- export function renderCandles() {
546
- var state = this.getCustomState();
547
- var candles = [];
548
- for (var i = 0; i < state.totalCandles; i++) {
549
- // 用立即执行函数捕获循环变量 i,避免闭包陷阱
550
- candles.push((function(candleIndex) {
551
- var isExtinguished = state.extinguishedCandles.indexOf(candleIndex) !== -1;
552
- return (
553
- <span
554
- key={candleIndex}
555
- style={Object.assign({}, GAME_STYLES.candle, isExtinguished ? GAME_STYLES.candleExtinguished : GAME_STYLES.candleLit)}
556
- onClick={(e) => { this.extinguishCandle(candleIndex); }}
557
- >
558
- {isExtinguished ? '💨' : '🕯️'}
559
- </span>
560
- );
561
- }).call(this, i));
562
- }
563
- return candles;
564
- }
565
-
566
- /**
567
- * 渲染星星装饰(庆祝阶段)
568
- */
569
- export function renderStars() {
570
- var state = this.getCustomState();
571
- if (state.gameStage !== 'celebrating') return null;
572
- var stars = [];
573
- for (var i = 0; i < 20; i++) {
574
- var starLeft = Math.random() * 100;
575
- var starTop = Math.random() * 100;
576
- var starDelay = Math.random() * 2;
577
- stars.push(
578
- <span
579
- key={i}
580
- style={Object.assign({}, GAME_STYLES.star, {
581
- left: starLeft + '%',
582
- top: starTop + '%',
583
- animationDelay: starDelay + 's'
584
- })}
585
- >
586
-
587
- </span>
588
- );
589
- }
590
- return stars;
591
- }
592
-
593
- /**
594
- * 渲染欢迎界面
595
- */
596
- export function renderWelcome() {
597
- return (
598
- <div style={GAME_STYLES.card}>
599
- <div style={GAME_STYLES.title}>
600
- 🎂 生日祝福
601
- </div>
602
- <div style={GAME_STYLES.subtitle}>
603
- 为TA点燃蜡烛,送上最真挚的祝福
604
- </div>
605
-
606
- <input
607
- id="birthday-person-input"
608
- type="text"
609
- placeholder="请输入寿星姓名"
610
- style={GAME_STYLES.input}
611
- defaultValue=""
612
- />
613
-
614
- <input
615
- id="sender-name-input"
616
- type="text"
617
- placeholder="请输入您的姓名(选填)"
618
- style={GAME_STYLES.input}
619
- defaultValue=""
620
- />
621
-
622
- <button
623
- style={GAME_STYLES.button}
624
- onMouseEnter={function(e) {
625
- e.target.style.transform = 'translateY(-2px)';
626
- e.target.style.boxShadow = '0 8px 20px rgba(102, 126, 234, 0.4)';
627
- }}
628
- onMouseLeave={function(e) {
629
- e.target.style.transform = 'translateY(0)';
630
- e.target.style.boxShadow = 'none';
631
- }}
632
- onClick={(e) => { this.startGame(); }}
633
- >
634
- 🎉 开始游戏
635
- </button>
636
-
637
- <div style={GAME_STYLES.micHint}>
638
- 🎤 支持吹灭蜡烛哦~
639
- </div>
640
- </div>
641
- );
642
- }
643
-
644
- /**
645
- * 渲染游戏界面
646
- */
647
- export function renderPlaying() {
648
- var state = this.getCustomState();
649
- var progressPercent = (state.extinguishedCandles.length / state.totalCandles) * 100;
650
-
651
- return (
652
- <div style={GAME_STYLES.card}>
653
- <div style={GAME_STYLES.title}>
654
- 🎂 点蜡烛
655
- </div>
656
-
657
- <div style={GAME_STYLES.progressBar}>
658
- <div style={Object.assign({}, GAME_STYLES.progressFill, { width: progressPercent + '%' })}></div>
659
- </div>
660
-
661
- <div style={GAME_STYLES.countdown}>
662
- {state.countdownSeconds}s
663
- </div>
664
-
665
- <div style={GAME_STYLES.cakeContainer}>
666
- <div style={GAME_STYLES.candlesRow}>
667
- {this.renderCandles()}
668
- </div>
669
- <div style={GAME_STYLES.cake}>🎂</div>
670
- </div>
671
-
672
- <div style={GAME_STYLES.subtitle}>
673
- {state.micPermission === 'denied'
674
- ? '👆 点击蜡烛将其熄灭'
675
- : '吹气或点击蜡烛'}
676
- </div>
677
-
678
- <div style={GAME_STYLES.subtitle}>
679
- 已熄灭 {state.extinguishedCandles.length} / {state.totalCandles} 根蜡烛
680
- </div>
681
-
682
- {state.isTimeout && (
683
- <div style={GAME_STYLES.timeoutOverlay}>
684
- <div style={{ fontSize: '48px', marginBottom: '20px' }}>⏰</div>
685
- <div style={{ fontSize: '24px', marginBottom: '20px' }}>时间到!</div>
686
- <button
687
- style={Object.assign({}, GAME_STYLES.button, { width: 'auto', padding: '12px 30px' })}
688
- onClick={(e) => { this.retryGame(); }}
689
- >
690
- 🔄 再试一次
691
- </button>
692
- </div>
693
- )}
694
- </div>
695
- );
696
- }
697
-
698
- /**
699
- * 渲染许愿界面
700
- */
701
- export function renderWishing() {
702
- var state = this.getCustomState();
703
-
704
- return (
705
- <div style={GAME_STYLES.card}>
706
- <div style={GAME_STYLES.title}>
707
- ⭐ 许愿时刻
708
- </div>
709
-
710
- <div style={{ fontSize: '64px', marginBottom: '20px' }}>✨</div>
711
-
712
- <div style={GAME_STYLES.subtitle}>
713
- 所有蜡烛已熄灭!请为 {state.birthdayPersonName} 送上祝福
714
- </div>
715
-
716
- <textarea
717
- id="blessing-input"
718
- placeholder="写下你的生日祝福..."
719
- style={GAME_STYLES.textarea}
720
- defaultValue=""
721
- ></textarea>
722
-
723
- <button
724
- style={GAME_STYLES.button}
725
- onClick={(e) => { this.submitBlessing(); }}
726
- >
727
- 💝 送出祝福
728
- </button>
729
- </div>
730
- );
731
- }
732
-
733
- /**
734
- * 渲染庆祝界面
735
- */
736
- export function renderCelebrating() {
737
- var state = this.getCustomState();
738
-
739
- return (
740
- <div style={Object.assign({}, GAME_STYLES.card, GAME_STYLES.blessingCard)}>
741
- {this.renderStars()}
742
-
743
- <div style={GAME_STYLES.blessingTitle}>
744
- 🎉 生日快乐
745
- </div>
746
-
747
- <div style={{ fontSize: '72px', marginBottom: '20px' }}>
748
- 🎂
749
- </div>
750
-
751
- <div style={{ fontSize: '28px', marginBottom: '10px' }}>
752
- 亲爱的 {state.birthdayPersonName}
753
- </div>
754
-
755
- <div style={GAME_STYLES.blessingText}>
756
- "{state.blessingMessage}"
757
- </div>
758
-
759
- <div style={GAME_STYLES.blessingFrom}>
760
- —— {state.senderName} 敬上
761
- </div>
762
-
763
- <button
764
- style={Object.assign({}, GAME_STYLES.button, {
765
- marginTop: '30px',
766
- background: 'rgba(255,255,255,0.3)',
767
- backdropFilter: 'blur(10px)'
768
- })}
769
- onClick={(e) => { this.restartGame(); }}
770
- >
771
- 🎮 再玩一次
772
- </button>
773
- </div>
774
- );
775
- }
776
-
777
- // ============================================================
778
- // 渲染
779
- // ============================================================
780
-
781
- export function renderJsx() {
782
- var state = this.getCustomState();
783
- var timestamp = this.state.timestamp;
784
-
785
- var currentStage;
786
- switch (state.gameStage) {
787
- case 'playing':
788
- currentStage = this.renderPlaying();
789
- break;
790
- case 'wishing':
791
- currentStage = this.renderWishing();
792
- break;
793
- case 'celebrating':
794
- currentStage = this.renderCelebrating();
795
- break;
796
- default:
797
- currentStage = this.renderWelcome();
798
- }
799
-
800
- return (
801
- <div style={GAME_STYLES.container}>
802
- {/* 用于触发重新渲染 */}
803
- <div style={{ display: 'none' }}>{timestamp}</div>
804
-
805
- {/* CSS 动画 */}
806
- <style dangerouslySetInnerHTML={{__html: `
807
- @keyframes flicker {
808
- 0% { transform: scale(1) rotate(-2deg); }
809
- 100% { transform: scale(1.1) rotate(2deg); }
810
- }
811
- @keyframes twinkle {
812
- 0%, 100% { opacity: 1; transform: scale(1); }
813
- 50% { opacity: 0.5; transform: scale(0.8); }
814
- }
815
- @keyframes fall {
816
- 0% { transform: translateY(-100vh) rotate(0deg); opacity: 1; }
817
- 100% { transform: translateY(100vh) rotate(720deg); opacity: 0; }
818
- }
819
- `}} />
820
-
821
- {/* 彩带效果 */}
822
- {state.gameStage === 'celebrating' && (
823
- <canvas
824
- id="confetti-canvas"
825
- style={GAME_STYLES.confetti}
826
- />
827
- )}
828
-
829
- {currentStage}
830
- </div>
831
- );
832
- }