@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.
- package/README.md +1 -32
- package/dist/733.js +7 -15
- package/dist/cli.js +1 -1
- package/dist/manifest.json +3 -11
- package/dist/skills/doubao-apps-dev/SKILL.md +103 -227
- package/dist/skills/doubao-apps-dev/references/examples/common-patterns.md +80 -66
- package/dist/skills/doubao-apps-dev/references/examples/component-basics.md +24 -3
- package/dist/skills/doubao-apps-dev/references/examples/component-recipes.md +71 -0
- package/dist/skills/doubao-apps-dev/references/examples/open-api-recipes.md +203 -0
- package/dist/skills/doubao-apps-dev/references/guides/component-development.md +66 -42
- package/dist/skills/doubao-apps-dev/references/guides/expired-widget.md +113 -0
- package/dist/skills/doubao-apps-dev/references/guides/mcp-ui.md +275 -0
- package/dist/skills/doubao-apps-dev/references/guides/performance-optimization.md +24 -7
- package/dist/skills/doubao-apps-dev/references/reference/components-quick-ref.md +18 -7
- package/dist/skills/doubao-apps-dev/references/reference/framework-api-quick-ref.md +125 -107
- package/dist/skills/doubao-apps-dev/references/reference/open-api/01-/345/237/272/347/241/200.md +136 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/02-/347/263/273/347/273/237.md +257 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/03-/345/256/232/344/275/215.md +326 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/04-/345/255/230/345/202/250.md +284 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/05-/350/267/257/347/224/261.md +162 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/06-/344/272/244/344/272/222.md +290 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/07-/350/276/223/345/205/245.md +63 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/08-/347/275/221/347/273/234.md +124 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/09-/345/252/222/344/275/223.md +192 -0
- 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
- package/dist/skills/doubao-apps-dev/references/reference/open-api/11-/347/231/273/345/275/225.md +110 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/12-/346/224/257/344/273/230.md +224 -0
- 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
- package/dist/skills/doubao-apps-dev/references/reference/open-api/14-/350/223/235/347/211/231.md +598 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/15-wi-fi.md +210 -0
- 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
- package/dist/skills/doubao-apps-dev/references/reference/open-api/17-ibeacon.md +97 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/18-/347/275/227/347/233/230.md +78 -0
- 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
- package/dist/skills/doubao-apps-dev/references/reference/open-api/20-/351/231/200/350/236/272/344/273/252.md +87 -0
- 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
- package/dist/skills/doubao-apps-dev/references/reference/open-api/22-/347/237/255/344/277/241.md +40 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/23-/346/227/240/351/232/234/347/242/215.md +40 -0
- 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
- package/dist/skills/doubao-apps-dev/references/reference/open-api/25-/347/224/265/346/261/240.md +63 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/26-/346/227/245/345/216/206.md +95 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/27-/345/211/252/350/264/264/346/235/277.md +64 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/28-/350/201/224/347/263/273/344/272/272.md +101 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/29-/345/212/240/345/257/206.md +43 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/30-/347/224/265/350/257/235.md +36 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/31-/346/211/253/347/240/201.md +58 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/32-/345/261/217/345/271/225.md +136 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api/33-/351/234/207/345/212/250.md +62 -0
- 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
- package/dist/skills/doubao-apps-dev/references/reference/open-api/README.md +34 -0
- package/dist/skills/doubao-apps-dev/references/reference/open-api.md +384 -2
- package/dist/skills/doubao-apps-dev/references/rules/dos-and-donts.md +44 -27
- package/dist/skills/h5-to-doubao/SKILL.md +8 -4
- package/dist/skills/weixin-to-doubao/SKILL.md +8 -4
- package/dist/skills/weixin-to-doubao/assets/migration-checklist-template.md +1 -1
- package/package.json +2 -3
- package/dist/contexts/doubao-apps-dev/AGENTS.md +0 -300
- package/dist/contexts/doubao-apps-dev/CLAUDE.md +0 -31
- package/dist/contexts/doubao-apps-dev/references/examples/common-patterns.md +0 -589
- package/dist/contexts/doubao-apps-dev/references/examples/component-basics.md +0 -526
- package/dist/contexts/doubao-apps-dev/references/guides/best-practices.md +0 -571
- package/dist/contexts/doubao-apps-dev/references/guides/component-development.md +0 -891
- package/dist/contexts/doubao-apps-dev/references/guides/performance-optimization.md +0 -402
- package/dist/contexts/doubao-apps-dev/references/guides/troubleshooting.md +0 -287
- package/dist/contexts/doubao-apps-dev/references/reference/components-quick-ref.md +0 -103
- package/dist/contexts/doubao-apps-dev/references/reference/framework-api-quick-ref.md +0 -550
- package/dist/contexts/doubao-apps-dev/references/reference/open-api/README.md +0 -8
- package/dist/contexts/doubao-apps-dev/references/reference/open-api.md +0 -11
- 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
|
|
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`
|
|
16
|
-
(最终 manifest 的 `appletEntry
|
|
17
|
-
|
|
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: '
|
|
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`
|
|
166
|
-
|
|
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(
|
|
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:
|
|
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: '
|
|
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`
|
|
270
|
-
|
|
271
|
-
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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,
|
|
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
|
|
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('操作失败:',
|
|
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
|
|
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
|
+
```
|