@doubao-apps/ai 0.0.33 → 0.0.34

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 (69) hide show
  1. package/README.md +1 -32
  2. package/dist/733.js +7 -15
  3. package/dist/cli.js +1 -1
  4. package/dist/manifest.json +3 -11
  5. package/dist/skills/doubao-apps-dev/SKILL.md +103 -227
  6. package/dist/skills/doubao-apps-dev/references/examples/common-patterns.md +80 -66
  7. package/dist/skills/doubao-apps-dev/references/examples/component-basics.md +24 -3
  8. package/dist/skills/doubao-apps-dev/references/examples/component-recipes.md +71 -0
  9. package/dist/skills/doubao-apps-dev/references/examples/open-api-recipes.md +203 -0
  10. package/dist/skills/doubao-apps-dev/references/guides/component-development.md +66 -42
  11. package/dist/skills/doubao-apps-dev/references/guides/expired-widget.md +113 -0
  12. package/dist/skills/doubao-apps-dev/references/guides/mcp-ui.md +275 -0
  13. package/dist/skills/doubao-apps-dev/references/guides/performance-optimization.md +24 -7
  14. package/dist/skills/doubao-apps-dev/references/reference/components-quick-ref.md +18 -7
  15. package/dist/skills/doubao-apps-dev/references/reference/framework-api-quick-ref.md +125 -107
  16. package/dist/skills/doubao-apps-dev/references/reference/open-api/01-/345/237/272/347/241/200.md +136 -0
  17. package/dist/skills/doubao-apps-dev/references/reference/open-api/02-/347/263/273/347/273/237.md +257 -0
  18. package/dist/skills/doubao-apps-dev/references/reference/open-api/03-/345/256/232/344/275/215.md +326 -0
  19. package/dist/skills/doubao-apps-dev/references/reference/open-api/04-/345/255/230/345/202/250.md +284 -0
  20. package/dist/skills/doubao-apps-dev/references/reference/open-api/05-/350/267/257/347/224/261.md +162 -0
  21. package/dist/skills/doubao-apps-dev/references/reference/open-api/06-/344/272/244/344/272/222.md +290 -0
  22. package/dist/skills/doubao-apps-dev/references/reference/open-api/07-/350/276/223/345/205/245.md +63 -0
  23. package/dist/skills/doubao-apps-dev/references/reference/open-api/08-/347/275/221/347/273/234.md +124 -0
  24. package/dist/skills/doubao-apps-dev/references/reference/open-api/09-/345/252/222/344/275/223.md +192 -0
  25. package/dist/skills/doubao-apps-dev/references/reference/open-api/10-/344/270/232/345/212/241/350/203/275/345/212/233.md +431 -0
  26. package/dist/skills/doubao-apps-dev/references/reference/open-api/11-/347/231/273/345/275/225.md +110 -0
  27. package/dist/skills/doubao-apps-dev/references/reference/open-api/12-/346/224/257/344/273/230.md +224 -0
  28. package/dist/skills/doubao-apps-dev/references/reference/open-api/13-/346/225/260/346/215/256/345/210/206/346/236/220.md +43 -0
  29. package/dist/skills/doubao-apps-dev/references/reference/open-api/14-/350/223/235/347/211/231.md +598 -0
  30. package/dist/skills/doubao-apps-dev/references/reference/open-api/15-wi-fi.md +210 -0
  31. package/dist/skills/doubao-apps-dev/references/reference/open-api/16-/345/212/240/351/200/237/345/272/246/350/256/241.md +87 -0
  32. package/dist/skills/doubao-apps-dev/references/reference/open-api/17-ibeacon.md +97 -0
  33. package/dist/skills/doubao-apps-dev/references/reference/open-api/18-/347/275/227/347/233/230.md +78 -0
  34. package/dist/skills/doubao-apps-dev/references/reference/open-api/19-/350/256/276/345/244/207/346/226/271/345/220/221.md +86 -0
  35. package/dist/skills/doubao-apps-dev/references/reference/open-api/20-/351/231/200/350/236/272/344/273/252.md +87 -0
  36. package/dist/skills/doubao-apps-dev/references/reference/open-api/21-/350/256/276/345/244/207-/347/275/221/347/273/234.md +102 -0
  37. package/dist/skills/doubao-apps-dev/references/reference/open-api/22-/347/237/255/344/277/241.md +40 -0
  38. package/dist/skills/doubao-apps-dev/references/reference/open-api/23-/346/227/240/351/232/234/347/242/215.md +40 -0
  39. package/dist/skills/doubao-apps-dev/references/reference/open-api/24-/345/237/272/347/241/200/344/277/241/346/201/257.md +86 -0
  40. package/dist/skills/doubao-apps-dev/references/reference/open-api/25-/347/224/265/346/261/240.md +63 -0
  41. package/dist/skills/doubao-apps-dev/references/reference/open-api/26-/346/227/245/345/216/206.md +95 -0
  42. package/dist/skills/doubao-apps-dev/references/reference/open-api/27-/345/211/252/350/264/264/346/235/277.md +64 -0
  43. package/dist/skills/doubao-apps-dev/references/reference/open-api/28-/350/201/224/347/263/273/344/272/272.md +101 -0
  44. package/dist/skills/doubao-apps-dev/references/reference/open-api/29-/345/212/240/345/257/206.md +43 -0
  45. package/dist/skills/doubao-apps-dev/references/reference/open-api/30-/347/224/265/350/257/235.md +36 -0
  46. package/dist/skills/doubao-apps-dev/references/reference/open-api/31-/346/211/253/347/240/201.md +58 -0
  47. package/dist/skills/doubao-apps-dev/references/reference/open-api/32-/345/261/217/345/271/225.md +136 -0
  48. package/dist/skills/doubao-apps-dev/references/reference/open-api/33-/351/234/207/345/212/250.md +62 -0
  49. package/dist/skills/doubao-apps-dev/references/reference/open-api/34-/346/226/207/344/273/266/347/263/273/347/273/237.md +73 -0
  50. package/dist/skills/doubao-apps-dev/references/reference/open-api/README.md +34 -0
  51. package/dist/skills/doubao-apps-dev/references/reference/open-api.md +384 -2
  52. package/dist/skills/doubao-apps-dev/references/rules/dos-and-donts.md +44 -27
  53. package/dist/skills/h5-to-doubao/SKILL.md +8 -4
  54. package/dist/skills/weixin-to-doubao/SKILL.md +8 -4
  55. package/dist/skills/weixin-to-doubao/assets/migration-checklist-template.md +1 -1
  56. package/package.json +2 -3
  57. package/dist/contexts/doubao-apps-dev/AGENTS.md +0 -300
  58. package/dist/contexts/doubao-apps-dev/CLAUDE.md +0 -31
  59. package/dist/contexts/doubao-apps-dev/references/examples/common-patterns.md +0 -589
  60. package/dist/contexts/doubao-apps-dev/references/examples/component-basics.md +0 -526
  61. package/dist/contexts/doubao-apps-dev/references/guides/best-practices.md +0 -571
  62. package/dist/contexts/doubao-apps-dev/references/guides/component-development.md +0 -891
  63. package/dist/contexts/doubao-apps-dev/references/guides/performance-optimization.md +0 -402
  64. package/dist/contexts/doubao-apps-dev/references/guides/troubleshooting.md +0 -287
  65. package/dist/contexts/doubao-apps-dev/references/reference/components-quick-ref.md +0 -103
  66. package/dist/contexts/doubao-apps-dev/references/reference/framework-api-quick-ref.md +0 -550
  67. package/dist/contexts/doubao-apps-dev/references/reference/open-api/README.md +0 -8
  68. package/dist/contexts/doubao-apps-dev/references/reference/open-api.md +0 -11
  69. package/dist/contexts/doubao-apps-dev/references/rules/dos-and-donts.md +0 -467
@@ -0,0 +1,203 @@
1
+ # Open API 使用配方
2
+
3
+ 本文件放高频端能力的组合示例。完整参数和返回类型以 [Open API 目录](../reference/open-api/README.md)
4
+ 和 `@doubao-apps/framework/api` 类型提示为准;这里重点展示端能力如何和 Page / Widget、生命周期、事件处理、状态反馈组合使用。
5
+
6
+ ---
7
+
8
+ ## 使用原则
9
+
10
+ - Open API 从 `@doubao-apps/framework/api` 导入。
11
+ - 首次加载或随输入变化的调用放在 `useEffect` 或生命周期钩子中。
12
+ - 用户触发的调用放在事件处理函数中。
13
+ - Page / Widget 默认用 `getViewData<T>()` 声明输入类型。
14
+ - `request` 不支持泛型参数,不要写 `request<T>()`;业务返回类型在 `response.data` 上做类型收窄或断言。
15
+ - 异步调用提供 loading、success、error 等可见状态。
16
+
17
+ ---
18
+
19
+ ## 位置能力 Widget
20
+
21
+ `getLocation` 放在 `useEffect` 中调用,不要在 `render()` 中直接调用。
22
+
23
+ ```tsx
24
+ import { defineWidget, getViewData, useEffect, useState } from '@doubao-apps/framework';
25
+ import { getLocation } from '@doubao-apps/framework/api';
26
+ import './index.scss';
27
+
28
+ interface LocationCardData {
29
+ title?: string;
30
+ }
31
+
32
+ interface LocationInfo {
33
+ latitude: number;
34
+ longitude: number;
35
+ accuracy: number;
36
+ speed: number;
37
+ timestamp: string;
38
+ }
39
+
40
+ function LocationCard() {
41
+ const viewData = getViewData<LocationCardData>();
42
+ const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
43
+ const [location, setLocation] = useState<LocationInfo | null>(null);
44
+ const [error, setError] = useState('');
45
+
46
+ useEffect(() => {
47
+ let active = true;
48
+
49
+ const loadLocation = async () => {
50
+ try {
51
+ setStatus('loading');
52
+ setError('');
53
+ const result = await getLocation();
54
+ if (!active) {
55
+ return;
56
+ }
57
+ setLocation({
58
+ latitude: result.latitude,
59
+ longitude: result.longitude,
60
+ accuracy: result.accuracy,
61
+ speed: result.speed,
62
+ timestamp: result.timestamp
63
+ });
64
+ setStatus('success');
65
+ } catch (caughtError) {
66
+ if (!active) {
67
+ return;
68
+ }
69
+ setError(caughtError instanceof Error ? caughtError.message : '定位失败');
70
+ setStatus('error');
71
+ }
72
+ };
73
+
74
+ void loadLocation();
75
+
76
+ return () => {
77
+ active = false;
78
+ };
79
+ }, []);
80
+
81
+ return (
82
+ <view className="location-card">
83
+ <text className="location-title">{viewData.title || '位置能力'}</text>
84
+ {status === 'loading' && <text className="location-status">定位中...</text>}
85
+ {status === 'error' && <text className="location-error">{error}</text>}
86
+ {status === 'success' && location && (
87
+ <view className="location-result">
88
+ <text>纬度: {location.latitude}</text>
89
+ <text>经度: {location.longitude}</text>
90
+ <text>精度: {location.accuracy}</text>
91
+ <text>速度: {location.speed}</text>
92
+ <text>时间: {location.timestamp}</text>
93
+ </view>
94
+ )}
95
+ </view>
96
+ );
97
+ }
98
+
99
+ export default defineWidget({
100
+ render() {
101
+ return <LocationCard />;
102
+ }
103
+ });
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Storage 状态持久化 Widget
109
+
110
+ `getStorage` 放在 `useEffect` 中读取初始状态,`setStorage` 放在事件处理函数中保存用户操作。Storage 支持
111
+ JSON 可序列化数据,读取时用泛型声明 `result.data` 的业务类型。
112
+
113
+ ```tsx
114
+ import { defineWidget, getViewData, useEffect, useState } from '@doubao-apps/framework';
115
+ import { getStorage, setStorage } from '@doubao-apps/framework/api';
116
+ import './index.scss';
117
+
118
+ interface StorageCounterData {
119
+ title?: string;
120
+ storageKey?: string;
121
+ defaultValue?: number;
122
+ }
123
+
124
+ interface StoredCounter {
125
+ value: number;
126
+ updatedAt: string;
127
+ }
128
+
129
+ function StorageCounter() {
130
+ const viewData = getViewData<StorageCounterData>();
131
+ const storageKey = viewData.storageKey || 'counter';
132
+ const defaultCount = viewData.defaultValue ?? 0;
133
+ const [count, setCount] = useState(defaultCount);
134
+ const [status, setStatus] = useState<'loading' | 'ready' | 'saving' | 'saved' | 'error'>('loading');
135
+ const [error, setError] = useState('');
136
+
137
+ useEffect(() => {
138
+ let active = true;
139
+
140
+ const loadStoredValue = async () => {
141
+ try {
142
+ setStatus('loading');
143
+ setError('');
144
+ const result = await getStorage<StoredCounter>({ key: storageKey });
145
+ if (!active) {
146
+ return;
147
+ }
148
+ setCount(typeof result.data.value === 'number' ? result.data.value : defaultCount);
149
+ setStatus('ready');
150
+ } catch (caughtError) {
151
+ if (!active) {
152
+ return;
153
+ }
154
+ setError(caughtError instanceof Error ? caughtError.message : '读取缓存失败,已使用默认值');
155
+ setStatus('ready');
156
+ }
157
+ };
158
+
159
+ void loadStoredValue();
160
+
161
+ return () => {
162
+ active = false;
163
+ };
164
+ }, [storageKey, defaultCount]);
165
+
166
+ const handleSave = async () => {
167
+ try {
168
+ setStatus('saving');
169
+ setError('');
170
+ await setStorage<StoredCounter>({
171
+ key: storageKey,
172
+ data: {
173
+ value: count,
174
+ updatedAt: new Date().toISOString()
175
+ }
176
+ });
177
+ setStatus('saved');
178
+ } catch (caughtError) {
179
+ setError(caughtError instanceof Error ? caughtError.message : '保存失败');
180
+ setStatus('error');
181
+ }
182
+ };
183
+
184
+ return (
185
+ <view className="storage-counter">
186
+ <text className="storage-title">{viewData.title || '本地计数'}</text>
187
+ <text className="storage-value">{count}</text>
188
+ <button onClick={() => setCount(current => current + 1)}>增加</button>
189
+ <button onClick={handleSave}>保存</button>
190
+ {status === 'loading' && <text>读取中...</text>}
191
+ {status === 'saving' && <text>保存中...</text>}
192
+ {status === 'saved' && <text>已保存</text>}
193
+ {error && <text className="storage-error">{error}</text>}
194
+ </view>
195
+ );
196
+ }
197
+
198
+ export default defineWidget({
199
+ render() {
200
+ return <StorageCounter />;
201
+ }
202
+ });
203
+ ```
@@ -8,16 +8,16 @@ Page(页面)和 Widget(卡片)组件的完整开发指南。
8
8
 
9
9
  ### App 配置、可选入口与多级目录
10
10
 
11
- `src/app.config.ts` 是应用配置文件,需要配置 `appId` 和 `name`。`pages` / `widgets` 是可选的显式入口配置。
11
+ `src/app.config.ts` 是应用配置文件,需要配置 `appId` 和 `name`,`appId` 使用开放平台 `db_xxxxxx` 风格。`pages` / `widgets` 是可选的显式入口配置。
12
12
  一级页面目录会自动发现,例如 `src/pages/home/index.tsx` 会产出 `pageId: 'home'`;不配置到 `pages` 时仍可构建,
13
13
  但 `title`、`description` 等页面 metadata 为空。
14
14
 
15
- 需要补 metadata、固定首页顺序或声明多级目录时,再配置 `pages` 数组。`pages` 是有序数组,第一项会作为应用首页
16
- (最终 manifest 的 `appletEntry`);如果希望 `home` 是默认打开页面,把 `pages/home/index` 放在第一项。
17
- 数组项可以是字符串,也可以是 `{ entry, ...metadata }` 对象:只声明入口时用字符串;需要补 metadata 时用对象。
15
+ 需要补 metadata、固定首页顺序或声明多级目录时,再配置 `pages` 数组。数组第一项会作为应用首页
16
+ (最终 manifest 的 `appletEntry`)。显式声明入口时,entry 写到 `index`,例如 `'pages/home/index'`,
17
+ 不要省略入口文件名。
18
18
 
19
19
  如果不配置 `pages` / `widgets`,一级 Page / Widget 仍会按目录产出;Page / Widget 的默认 ID 取入口最后一级目录名,
20
- 页面/卡片 metadata 字段为空,Widget 的 `boxType` 默认为 `inbox`。没有显式 `pages` 顺序时,首页入口会走构建兜底/历史默认
20
+ 页面/卡片 metadata 字段为空,Widget 的 `boxType` 默认为 `inbox`。没有显式 `pages` 顺序时,默认首页入口为
21
21
  `index`;首页不是 `index` 时建议配置 `pages` 第一项。
22
22
 
23
23
  多级目录不会被一级扫描自动发现,例如 `src/pages/account/demo/index.tsx` 需要显式写
@@ -27,7 +27,7 @@ Page(页面)和 Widget(卡片)组件的完整开发指南。
27
27
  import { defineAppConfig } from '@doubao-apps/kit';
28
28
 
29
29
  export default defineAppConfig({
30
- appId: 'com.example.my-doubao-app',
30
+ appId: 'db_xxxxxx',
31
31
  name: '我的豆包应用',
32
32
  pages: [
33
33
  'pages/home/index',
@@ -48,6 +48,10 @@ export default defineAppConfig({
48
48
  import { definePage, getViewData, useState, useEffect } from '@doubao-apps/framework';
49
49
  import './index.scss';
50
50
 
51
+ interface PageViewData {
52
+ title: string;
53
+ }
54
+
51
55
  export default definePage({
52
56
  // 1. 生命周期钩子
53
57
  onShow() {
@@ -67,8 +71,8 @@ export default definePage({
67
71
 
68
72
  // 2. 渲染函数
69
73
  render() {
70
- // 使用 getViewData() 和 getViewContext() 获取数据和上下文
71
- const viewData = getViewData();
74
+ // 使用 getViewData<T>() 和 getViewContext() 获取数据和上下文
75
+ const viewData = getViewData<PageViewData>();
72
76
 
73
77
  return <MyPageComponent {...viewData} />;
74
78
  }
@@ -148,22 +152,22 @@ import { navigateTo, redirectTo, reLaunch } from '@doubao-apps/framework/api';
148
152
 
149
153
  // 保留当前页面,打开新页面
150
154
  navigateTo({
151
- url: 'user-detail?userId=123&tab=profile'
155
+ url: '/pages/user-detail/index?userId=123&tab=profile'
152
156
  });
153
157
 
154
158
  // 替换当前页面
155
159
  redirectTo({
156
- url: 'settings'
160
+ url: '/pages/settings/index'
157
161
  });
158
162
 
159
163
  // 关闭所有页面并回到首页
160
164
  reLaunch({
161
- url: 'home'
165
+ url: '/pages/home/index'
162
166
  });
163
167
  ```
164
168
 
165
- `url` 在常规页面跳转里填写页面的 `pageId`。显式配置时取 `src/app.config.ts` 中对应 page 的 `id`;不配置时取入口目录名,例如
166
- `src/pages/detail/index.tsx` 默认为 `detail`。
169
+ `url` 在常规页面跳转里填写目标 Page 路径,通常和 `src/pages/<page-name>/index.tsx` 对应,例如
170
+ `/pages/detail/index`。
167
171
 
168
172
  页面内可继续通过 `getViewData<PageViewData>()` 读取 URL 中传入的参数。
169
173
 
@@ -175,28 +179,33 @@ reLaunch({
175
179
  import { useEffect, useState } from '@doubao-apps/framework';
176
180
  import { request } from '@doubao-apps/framework/api';
177
181
 
182
+ interface UserData {
183
+ name?: string;
184
+ }
185
+
178
186
  function MyPage({ userId }: { userId: string }) {
179
- const [data, setData] = useState(null);
187
+ const [data, setData] = useState<UserData | null>(null);
180
188
  const [loading, setLoading] = useState(true);
181
- const [error, setError] = useState(null);
189
+ const [error, setError] = useState('');
182
190
 
183
191
  useEffect(() => {
184
192
  const fetchData = async () => {
185
193
  try {
186
194
  setLoading(true);
195
+ setError('');
187
196
  const result = await request({
188
- url: `/api/users/${userId}`,
197
+ url: `https://api.example.com/users/${userId}`,
189
198
  method: 'GET'
190
199
  });
191
- setData(result.data);
200
+ setData(result.data as UserData);
192
201
  } catch (err) {
193
- setError(err.message);
202
+ setError(err instanceof Error ? err.message : '加载失败');
194
203
  } finally {
195
204
  setLoading(false);
196
205
  }
197
206
  };
198
207
 
199
- fetchData();
208
+ void fetchData();
200
209
  }, [userId]);
201
210
 
202
211
  if (loading) return <text>加载中...</text>;
@@ -224,7 +233,7 @@ function FormPage() {
224
233
  try {
225
234
  setSubmitting(true);
226
235
  await request({
227
- url: '/api/submit',
236
+ url: 'https://api.example.com/submit',
228
237
  method: 'POST',
229
238
  data: formData
230
239
  });
@@ -266,15 +275,16 @@ function FormPage() {
266
275
  一级 Widget 目录会自动发现,例如 `src/widgets/my-widget/index.tsx` 会产出 `widgetId: 'my-widget'`。不配置
267
276
  `widgets` 时仍可构建,但 `name`、`description` 等 metadata 为空,`boxType` 默认是 `inbox`。
268
277
 
269
- 需要补 metadata 或声明多级目录时,再配置 `widgets` 数组。多级目录如 `src/widgets/order/detail/index.tsx`
270
- 需要显式写 `entry: 'widgets/order/detail/index'`;如果不配置 `id`,默认 `widgetId` 是最后一级目录名 `detail`。
271
- 数组项可以是字符串,也可以是 `{ entry, ...metadata }` 对象:只声明入口时用字符串;需要补 metadata 时用对象。
278
+ 需要补 metadata 或声明多级目录时,再配置 `widgets` 数组。显式声明入口时,entry 写到 `index`,例如
279
+ `'widgets/my-widget/index'`,不要省略入口文件名。
280
+ 多级目录如 `src/widgets/order/detail/index.tsx` 需要显式写 `'widgets/order/detail/index'`。
281
+ 如果不配置 `id`,默认 `widgetId` 是最后一级目录名 `detail`。
272
282
 
273
283
  ```ts
274
284
  import { defineAppConfig } from '@doubao-apps/kit';
275
285
 
276
286
  export default defineAppConfig({
277
- appId: 'com.example.my-doubao-app',
287
+ appId: 'db_xxxxxx',
278
288
  name: '我的豆包应用',
279
289
  widgets: [
280
290
  'widgets/simple-card/index',
@@ -353,7 +363,7 @@ export default defineWidget({
353
363
  import { defineAppConfig } from '@doubao-apps/kit';
354
364
 
355
365
  export default defineAppConfig({
356
- appId: 'com.example.my-doubao-app',
366
+ appId: 'db_xxxxxx',
357
367
  name: '我的豆包应用',
358
368
  widgets: [
359
369
  {
@@ -390,7 +400,7 @@ export default defineWidget({
390
400
  import { defineAppConfig } from '@doubao-apps/kit';
391
401
 
392
402
  export default defineAppConfig({
393
- appId: 'com.example.my-doubao-app',
403
+ appId: 'db_xxxxxx',
394
404
  name: '我的豆包应用',
395
405
  widgets: [
396
406
  {
@@ -464,29 +474,38 @@ export default defineWidget({
464
474
  import { useEffect, useState } from '@doubao-apps/framework';
465
475
  import { request } from '@doubao-apps/framework/api';
466
476
 
477
+ interface WeatherInfo {
478
+ city: string;
479
+ temperature: number;
480
+ condition: string;
481
+ }
482
+
467
483
  function WeatherWidget({ city }: { city: string }) {
468
- const [weather, setWeather] = useState(null);
484
+ const [weather, setWeather] = useState<WeatherInfo | null>(null);
469
485
  const [loading, setLoading] = useState(true);
486
+ const [error, setError] = useState('');
470
487
 
471
488
  useEffect(() => {
472
489
  const fetchWeather = async () => {
473
490
  try {
491
+ setError('');
474
492
  const response = await request({
475
- url: `/api/weather?city=${city}`,
493
+ url: `https://api.example.com/weather?city=${city}`,
476
494
  method: 'GET'
477
495
  });
478
- setWeather(response.data);
496
+ setWeather(response.data as WeatherInfo);
479
497
  } catch (err) {
480
- console.error('获取天气失败:', err);
498
+ setError(err instanceof Error ? err.message : '获取天气失败');
481
499
  } finally {
482
500
  setLoading(false);
483
501
  }
484
502
  };
485
503
 
486
- fetchWeather();
504
+ void fetchWeather();
487
505
  }, [city]);
488
506
 
489
507
  if (loading) return <text>加载中...</text>;
508
+ if (error) return <text>{error}</text>;
490
509
  if (!weather) return <text>无数据</text>;
491
510
 
492
511
  return (
@@ -533,7 +552,7 @@ import { navigateTo } from '@doubao-apps/framework/api';
533
552
  function ActionWidget({ itemId }: { itemId: string }) {
534
553
  const handleViewDetail = () => {
535
554
  navigateTo({
536
- url: `item-detail?id=${itemId}`
555
+ url: `/pages/item-detail/index?id=${itemId}`
537
556
  });
538
557
  };
539
558
 
@@ -693,7 +712,7 @@ function MyPage() {
693
712
  ```typescript
694
713
  import { getViewData } from '@doubao-apps/framework';
695
714
 
696
- function getViewData<T extends Record<string, any>>(): T
715
+ function getViewData<T extends Record<string, unknown>>(): T
697
716
  ```
698
717
 
699
718
  **使用场景**:
@@ -714,7 +733,7 @@ export default defineWidget({
714
733
  },
715
734
 
716
735
  render() {
717
- // render 函数中使用 getViewData() 获取数据
736
+ // render 函数中使用 getViewData<T>() 获取数据
718
737
  const viewData = getViewData<TodoData>();
719
738
  return <view>{viewData.tasks.length} 个任务</view>;
720
739
  }
@@ -743,23 +762,23 @@ function WeatherWidget() {
743
762
  ```
744
763
 
745
764
  **注意事项**:
746
- - ✅ **推荐**:在 render 函数中使用 `getViewData()` 获取数据
747
- - ✅ **推荐**:在生命周期钩子和自定义函数中使用 `getViewData()` 获取数据
765
+ - ✅ **推荐**:在 render 函数中使用 `getViewData<T>()` 获取数据
766
+ - ✅ **推荐**:在生命周期钩子和自定义函数中使用 `getViewData<T>()` 获取数据
748
767
  - 🔒 **类型安全**:始终定义明确的 TypeScript 接口并传递给泛型参数
749
768
 
750
- ### 使用 `getViewData()` 和 `getViewContext()`
769
+ ### 使用 `getViewData<T>()` 和 `getViewContext()`
751
770
 
752
- render 函数不再通过参数接收 viewData 和 context,请使用以下方法获取数据和上下文:
771
+ render 函数内通过以下 API 获取 viewData 和 context
753
772
 
754
773
  **推荐做法**:
755
774
  ```tsx
756
- // ✅ 推荐:render 函数中使用 getViewData()
775
+ // ✅ 推荐:render 函数中使用 getViewData<T>()
757
776
  render() {
758
777
  const viewData = getViewData<WeatherData>();
759
778
  return <text>{viewData.city}: {viewData.temperature}°C</text>;
760
779
  }
761
780
 
762
- // ✅ 推荐:生命周期钩子中使用 getViewData()
781
+ // ✅ 推荐:生命周期钩子中使用 getViewData<T>()
763
782
  onMounted() {
764
783
  const viewData = getViewData<WeatherData>();
765
784
  // 处理数据...
@@ -813,8 +832,9 @@ try {
813
832
  const result = await someAsyncOperation();
814
833
  setData(result);
815
834
  } catch (err) {
835
+ const message = err instanceof Error ? err.message : '未知错误';
816
836
  // 记录错误
817
- console.error('操作失败:', err);
837
+ console.error('操作失败:', message);
818
838
 
819
839
  // 显示用户友好的错误消息
820
840
  setError('操作失败,请重试');
@@ -822,7 +842,7 @@ try {
822
842
  // 可选:上报错误
823
843
  reportAppLog({
824
844
  level: 'error',
825
- message: err.message,
845
+ message,
826
846
  context: { operation: 'someAsyncOperation' }
827
847
  });
828
848
  }
@@ -885,6 +905,10 @@ useEffect(() => {
885
905
 
886
906
  - **组件示例** → [../examples/component-basics.md](../examples/component-basics.md)
887
907
  - **常用模式** → [../examples/common-patterns.md](../examples/common-patterns.md)
908
+ - **MCP UI 约定入口** → [mcp-ui.md](./mcp-ui.md)
909
+ - **失效卡片** → [expired-widget.md](./expired-widget.md)
910
+ - **Open API 使用配方** → [../examples/open-api-recipes.md](../examples/open-api-recipes.md)
911
+ - **内置组件使用配方** → [../examples/component-recipes.md](../examples/component-recipes.md)
888
912
  - **最佳实践** → [best-practices.md](./best-practices.md)
889
913
  - **性能优化** → [performance-optimization.md](./performance-optimization.md)
890
914
  - **故障排查** → [troubleshooting.md](./troubleshooting.md)
@@ -0,0 +1,113 @@
1
+ # 失效卡片
2
+
3
+ `expiredWidget()` 用于把已经过期的业务 Widget 实例更新成框架内置失效卡,通常在业务 Widget 内容失效后调用。
4
+
5
+ `__doubao_apps__expired-widget` 是框架内置虚拟 Widget,不需要在 `src/widgets` 中创建同名目录,也不要手动注册到
6
+ `src/app.config.ts`。
7
+
8
+ `expiredWidget()` 的 `url` 是用户点击失效卡后跳转的目标 Page 路径或 schema。
9
+
10
+ ---
11
+
12
+ ## 当前 Widget 标记为失效
13
+
14
+ 在 Widget 内部可以通过 `getWidgetInstanceId()` 获取当前实例 ID,再调用 `expiredWidget()`。`getWidgetInstanceId()`
15
+ 只在 Widget 环境有值,在 Page 中通常返回 `undefined`。
16
+
17
+ ```ts
18
+ import { getWidgetInstanceId } from '@doubao-apps/framework';
19
+ import { expiredWidget } from '@doubao-apps/framework/api';
20
+
21
+ async function markCurrentWidgetExpired() {
22
+ const widgetInstanceId = getWidgetInstanceId();
23
+ if (!widgetInstanceId) {
24
+ return;
25
+ }
26
+
27
+ await expiredWidget({
28
+ widgetInstanceId,
29
+ url: '/pages/detail/index?from=expired',
30
+ title: '内容已失效',
31
+ detailText: '点击查看最新内容'
32
+ });
33
+ }
34
+ ```
35
+
36
+ ---
37
+
38
+ ## 从 Widget 打开 Page 并传递 widgetInstanceId
39
+
40
+ 如果需要在 Page 中处理过期逻辑,先在 Widget 中拿到 `widgetInstanceId`,通过页面跳转参数传给目标 Page。
41
+
42
+ ```tsx
43
+ import { defineWidget, getWidgetInstanceId } from '@doubao-apps/framework';
44
+ import { navigateTo } from '@doubao-apps/framework/api';
45
+
46
+ function OrderWidget() {
47
+ const handleOpenDetail = async () => {
48
+ const widgetInstanceId = getWidgetInstanceId();
49
+ const query = widgetInstanceId ? `?widgetInstanceId=${encodeURIComponent(widgetInstanceId)}` : '';
50
+
51
+ await navigateTo({
52
+ url: `/pages/order-detail/index${query}`
53
+ });
54
+ };
55
+
56
+ return (
57
+ <view className="order-widget">
58
+ <button text="查看详情" onClick={handleOpenDetail} />
59
+ </view>
60
+ );
61
+ }
62
+
63
+ export default defineWidget({
64
+ render() {
65
+ return <OrderWidget />;
66
+ }
67
+ });
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Page 中读取 widgetInstanceId 并标记失效
73
+
74
+ Page 不直接拥有 Widget 实例 ID。需要从 `getViewData<T>()` 读取上一步传入的 `widgetInstanceId`,或通过业务数据、通信等方式拿到目标实例 ID。
75
+
76
+ ```tsx
77
+ import { definePage, getViewData } from '@doubao-apps/framework';
78
+ import { expiredWidget } from '@doubao-apps/framework/api';
79
+ import './index.scss';
80
+
81
+ interface OrderDetailPageData {
82
+ widgetInstanceId?: string;
83
+ }
84
+
85
+ function OrderDetailPage() {
86
+ const viewData = getViewData<OrderDetailPageData>();
87
+
88
+ const handleMarkExpired = async () => {
89
+ if (!viewData.widgetInstanceId) {
90
+ return;
91
+ }
92
+
93
+ await expiredWidget({
94
+ widgetInstanceId: viewData.widgetInstanceId,
95
+ url: '/pages/order-detail/index?from=expired',
96
+ title: '订单状态已更新',
97
+ detailText: '点击查看最新订单状态'
98
+ });
99
+ };
100
+
101
+ return (
102
+ <view className="order-detail-page">
103
+ <button text="标记原卡片失效" disabled={!viewData.widgetInstanceId} onClick={handleMarkExpired} />
104
+ </view>
105
+ );
106
+ }
107
+
108
+ export default definePage({
109
+ render() {
110
+ return <OrderDetailPage />;
111
+ }
112
+ });
113
+ ```