@doubao-dev/plugin-app-dev 0.0.0-stage → 0.0.40-canary-dea0ddc73-20261026073950
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 +10 -2
- package/dist/147.js +9 -0
- package/dist/assets.d.ts +5 -0
- package/dist/assets.js +4 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +1378 -0
- package/dist/plugin.d.ts +102 -0
- package/dist/plugin.js +4995 -0
- package/dist/preview.js +16 -0
- package/dist/rslib-runtime.js +33 -0
- package/dist/skills/doubao-plugin-app/SKILL.md +21 -0
- package/dist/skills/doubao-plugin-app/references/app-development.md +85 -0
- package/dist/skills/doubao-plugin-app/references/frontend/components/container-overlay.md +131 -0
- package/dist/skills/doubao-plugin-app/references/frontend/components/form-controls.md +146 -0
- package/dist/skills/doubao-plugin-app/references/frontend/components/interactive-controls.md +120 -0
- package/dist/skills/doubao-plugin-app/references/frontend/components/map-canvas.md +107 -0
- package/dist/skills/doubao-plugin-app/references/frontend/components/overview.md +43 -0
- package/dist/skills/doubao-plugin-app/references/frontend/components/scroll-list.md +149 -0
- package/dist/skills/doubao-plugin-app/references/frontend/components/text-media.md +209 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/01-/345/237/272/347/241/200.md +1389 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/02-/347/263/273/347/273/237.md +1376 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/03-/350/207/252/345/256/232/344/271/211/351/200/232/344/277/241.md +127 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/04-/345/256/232/344/275/215.md +784 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/05-/345/255/230/345/202/250.md +1176 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/06-/350/267/257/347/224/261.md +839 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/07-/347/225/214/351/235/242.md +376 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/08-/344/272/244/344/272/222.md +1319 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/09-/350/276/223/345/205/245.md +192 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/10-/347/275/221/347/273/234.md +900 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/11-/345/252/222/344/275/223.md +1496 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/12-/344/270/232/345/212/241/350/203/275/345/212/233.md +1281 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/13-/347/231/273/345/275/225.md +511 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/14-/346/224/257/344/273/230.md +943 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/15-/346/225/260/346/215/256/345/210/206/346/236/220.md +124 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/16-/350/223/235/347/211/231.md +3364 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/17-wi-fi.md +1028 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/18-/345/212/240/351/200/237/345/272/246/350/256/241.md +354 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/19-ibeacon.md +508 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/20-/347/275/227/347/233/230.md +320 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/21-/350/256/276/345/244/207/346/226/271/345/220/221.md +351 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/22-/351/231/200/350/236/272/344/273/252.md +354 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/23-/345/252/222/344/275/223-/350/256/276/345/244/207-/347/275/221/347/273/234.md +264 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/24-/347/237/255/344/277/241.md +155 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/25-/346/227/240/351/232/234/347/242/215.md +144 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/26-/345/237/272/347/241/200/344/277/241/346/201/257.md +238 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/27-/347/224/265/346/261/240.md +235 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/28-/345/211/252/350/264/264/346/235/277.md +303 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/29-/345/212/240/345/257/206.md +126 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/30-/346/227/245/345/216/206.md +417 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/31-/347/224/265/350/257/235.md +144 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/32-/346/211/253/347/240/201.md +236 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/33-/345/261/217/345/271/225.md +747 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/34-/351/234/207/345/212/250.md +277 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/35-/346/226/207/344/273/266/347/263/273/347/273/237.md +431 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/common-errors.md +69 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/groups.md +43 -0
- package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/quick-reference.md +436 -0
- package/dist/skills/doubao-plugin-app/references/frontend/examples/common-patterns.md +590 -0
- package/dist/skills/doubao-plugin-app/references/frontend/examples/doubao-agentic-service-api-recipes.md +209 -0
- package/dist/skills/doubao-plugin-app/references/frontend/examples/page-widget-basics.md +368 -0
- package/dist/skills/doubao-plugin-app/references/frontend/examples/page-widget-recipes.md +65 -0
- package/dist/skills/doubao-plugin-app/references/frontend/framework/core.md +283 -0
- package/dist/skills/doubao-plugin-app/references/frontend/guides/auth.md +277 -0
- package/dist/skills/doubao-plugin-app/references/frontend/guides/best-practices.md +574 -0
- package/dist/skills/doubao-plugin-app/references/frontend/guides/client-tools-and-events.md +209 -0
- package/dist/skills/doubao-plugin-app/references/frontend/guides/component-development.md +832 -0
- package/dist/skills/doubao-plugin-app/references/frontend/guides/debugger-mcp.md +537 -0
- package/dist/skills/doubao-plugin-app/references/frontend/guides/expired-widget.md +101 -0
- package/dist/skills/doubao-plugin-app/references/frontend/guides/performance-optimization.md +417 -0
- package/dist/skills/doubao-plugin-app/references/frontend/guides/troubleshooting.md +278 -0
- package/dist/skills/doubao-plugin-app/references/frontend/lynx/best-practices.md +720 -0
- package/dist/skills/doubao-plugin-app/references/frontend/lynx/css/pseudo-classes.md +149 -0
- package/dist/skills/doubao-plugin-app/references/frontend/lynx/css/selectors.md +224 -0
- package/dist/skills/doubao-plugin-app/references/frontend/lynx/css/supported-properties.md +269 -0
- package/dist/skills/doubao-plugin-app/references/frontend/lynx/css/values-and-units.md +314 -0
- package/dist/skills/doubao-plugin-app/references/frontend/lynx/layout/flex-layout.md +334 -0
- package/dist/skills/doubao-plugin-app/references/frontend/lynx/layout/grid-layout.md +408 -0
- package/dist/skills/doubao-plugin-app/references/frontend/lynx/layout/linear-layout.md +355 -0
- package/dist/skills/doubao-plugin-app/references/frontend/lynx/layout/relative-layout.md +189 -0
- package/dist/skills/doubao-plugin-app/references/frontend/lynx/lynx-vs-web/css-differences.md +737 -0
- package/dist/skills/doubao-plugin-app/references/frontend/lynx/lynx-vs-web/migration-guide.md +669 -0
- package/dist/skills/doubao-plugin-app/references/frontend/lynx/lynx-vs-web/unsupported-features.md +290 -0
- package/dist/skills/doubao-plugin-app/references/frontend/lynx/overview.md +60 -0
- package/dist/skills/doubao-plugin-app/references/frontend/lynx/patterns/animation.md +243 -0
- package/dist/skills/doubao-plugin-app/references/frontend/lynx/patterns/responsive.md +194 -0
- package/dist/skills/doubao-plugin-app/references/frontend/lynx/patterns/theming.md +143 -0
- package/dist/skills/doubao-plugin-app/references/frontend/lynx/quick-reference.md +229 -0
- package/dist/skills/doubao-plugin-app/references/frontend/rules/dos-and-donts.md +479 -0
- package/dist/skills/doubao-plugin-app/references/frontend/widget-templates/design-boundary.md +46 -0
- package/dist/skills/doubao-plugin-app/references/frontend/widget-templates/overview.md +118 -0
- package/dist/skills/doubao-plugin-app/references/frontend/widget-templates/props.md +301 -0
- package/dist/skills/doubao-plugin-app/references/mcp-and-cards.md +38 -0
- package/dist/templates/README.md.template +11 -0
- package/dist/templates/com.doubao/app/manifest.yaml +41 -0
- package/dist/templates/com.doubao/app/package.json.template +13 -0
- package/dist/templates/com.doubao/app/src/app.config.ts.template +5 -0
- package/dist/templates/com.doubao/app/src/app.ts.template +3 -0
- package/dist/templates/com.doubao/app/src/pages/home/index.css +14 -0
- package/dist/templates/com.doubao/app/src/pages/home/index.tsx.template +14 -0
- package/dist/templates/com.doubao/app/src/widgets/example/index.tsx.template +29 -0
- package/dist/templates/com.doubao/app/tsconfig.json +19 -0
- package/dist/templates/mcp.json +9 -0
- package/dist/templates/plugin.json.template +29 -0
- package/dist/templates/skills/example/SKILL.md.template +14 -0
- package/dist/upload.d.ts +44 -0
- package/dist/upload.js +38 -0
- package/package.json +60 -3
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# 豆包应用插件的端能力 API 使用配方
|
|
2
|
+
|
|
3
|
+
本文件放高频豆包应用插件的端能力 API 的组合示例。这些 API 也称 Open API,代码统一从
|
|
4
|
+
`@doubao-dev/framework/api` 导入。完整参数和返回类型以 [豆包应用插件的端能力 API 目录](../doubao-agentic-service-api/groups.md)
|
|
5
|
+
和 IDE 类型提示为准;这里重点展示端能力如何和 Page / Widget、生命周期、事件处理、状态反馈组合使用。
|
|
6
|
+
Widget 示例只展示端能力和状态管理组合,卡片展示层使用 [Widget 模板库](../widget-templates/overview.md) 中的模板。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 使用原则
|
|
11
|
+
|
|
12
|
+
- 豆包应用插件的端能力 API 从 `@doubao-dev/framework/api` 导入。
|
|
13
|
+
- 首次加载或随输入变化的调用放在 `useEffect` 或生命周期钩子中。
|
|
14
|
+
- 用户触发的调用放在事件处理函数中。
|
|
15
|
+
- Page / Widget 默认用 `useViewData<T>()` 声明输入类型。
|
|
16
|
+
- `request` 不支持泛型参数,不要写 `request<T>()`;业务返回类型在 `response.data` 上做类型收窄或断言。
|
|
17
|
+
- 异步调用提供 loading、success、error 等可见状态。
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 位置能力 Widget
|
|
22
|
+
|
|
23
|
+
`getLocation` 放在 `useEffect` 中调用,不要在 `render()` 中直接调用。
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { useEffect, useState, useViewData } from '@doubao-dev/framework';
|
|
27
|
+
import { getLocation } from '@doubao-dev/framework/api';
|
|
28
|
+
import { ContentCard, type ContentCardItem } from '@doubao-dev/template';
|
|
29
|
+
|
|
30
|
+
interface LocationCardData {
|
|
31
|
+
title?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface LocationInfo {
|
|
35
|
+
latitude: number;
|
|
36
|
+
longitude: number;
|
|
37
|
+
accuracy: number;
|
|
38
|
+
speed: number;
|
|
39
|
+
timestamp: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default function LocationCard() {
|
|
43
|
+
const viewData = useViewData<LocationCardData>();
|
|
44
|
+
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
|
|
45
|
+
const [location, setLocation] = useState<LocationInfo | null>(null);
|
|
46
|
+
const [error, setError] = useState('');
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
let active = true;
|
|
50
|
+
|
|
51
|
+
const loadLocation = async () => {
|
|
52
|
+
try {
|
|
53
|
+
setStatus('loading');
|
|
54
|
+
setError('');
|
|
55
|
+
const result = await getLocation();
|
|
56
|
+
if (!active) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
setLocation({
|
|
60
|
+
latitude: result.latitude,
|
|
61
|
+
longitude: result.longitude,
|
|
62
|
+
accuracy: result.accuracy,
|
|
63
|
+
speed: result.speed,
|
|
64
|
+
timestamp: result.timestamp
|
|
65
|
+
});
|
|
66
|
+
setStatus('success');
|
|
67
|
+
} catch (caughtError) {
|
|
68
|
+
if (!active) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
setError(caughtError instanceof Error ? caughtError.message : '定位失败');
|
|
72
|
+
setStatus('error');
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
void loadLocation();
|
|
77
|
+
|
|
78
|
+
return () => {
|
|
79
|
+
active = false;
|
|
80
|
+
};
|
|
81
|
+
}, []);
|
|
82
|
+
|
|
83
|
+
if (status === 'loading') {
|
|
84
|
+
return <ContentCard items={[{ key: 'status', title: viewData.title || '位置能力', subtitle: '定位中...' }]} />;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (status === 'error') {
|
|
88
|
+
return <ContentCard items={[{ key: 'error', title: viewData.title || '位置能力', subtitle: error }]} />;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const items: ContentCardItem[] = location
|
|
92
|
+
? [
|
|
93
|
+
{ key: 'lat', title: '纬度', subtitle: location.latitude },
|
|
94
|
+
{ key: 'lng', title: '经度', subtitle: location.longitude }
|
|
95
|
+
]
|
|
96
|
+
: [{ key: 'empty', title: viewData.title || '位置能力', subtitle: '暂无数据' }];
|
|
97
|
+
|
|
98
|
+
return <ContentCard header={{ actionText: viewData.title || '位置能力' }} items={items} />;
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Storage 状态持久化 Widget
|
|
105
|
+
|
|
106
|
+
`getStorage` 放在 `useEffect` 中读取初始状态,`setStorage` 放在事件处理函数中保存用户操作。Storage 支持
|
|
107
|
+
JSON 可序列化数据,读取时用泛型声明 `result.data` 的业务类型。
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import { useEffect, useState, useViewData } from '@doubao-dev/framework';
|
|
111
|
+
import { getStorage, setStorage } from '@doubao-dev/framework/api';
|
|
112
|
+
import { ContentCard, type ContentCardItem } from '@doubao-dev/template';
|
|
113
|
+
|
|
114
|
+
interface StorageCounterData {
|
|
115
|
+
title?: string;
|
|
116
|
+
storageKey?: string;
|
|
117
|
+
defaultValue?: number;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface StoredCounter {
|
|
121
|
+
value: number;
|
|
122
|
+
updatedAt: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default function StorageCounter() {
|
|
126
|
+
const viewData = useViewData<StorageCounterData>();
|
|
127
|
+
const storageKey = viewData.storageKey || 'counter';
|
|
128
|
+
const defaultCount = viewData.defaultValue ?? 0;
|
|
129
|
+
const [count, setCount] = useState(defaultCount);
|
|
130
|
+
const [status, setStatus] = useState<'loading' | 'ready' | 'saving' | 'saved' | 'error'>('loading');
|
|
131
|
+
const [error, setError] = useState('');
|
|
132
|
+
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
let active = true;
|
|
135
|
+
|
|
136
|
+
const loadStoredValue = async () => {
|
|
137
|
+
try {
|
|
138
|
+
setStatus('loading');
|
|
139
|
+
setError('');
|
|
140
|
+
const result = await getStorage<StoredCounter>({ key: storageKey });
|
|
141
|
+
if (!active) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
setCount(typeof result.data.value === 'number' ? result.data.value : defaultCount);
|
|
145
|
+
setStatus('ready');
|
|
146
|
+
} catch (caughtError) {
|
|
147
|
+
if (!active) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
setError(caughtError instanceof Error ? caughtError.message : '读取缓存失败,已使用默认值');
|
|
151
|
+
setStatus('ready');
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
void loadStoredValue();
|
|
156
|
+
|
|
157
|
+
return () => {
|
|
158
|
+
active = false;
|
|
159
|
+
};
|
|
160
|
+
}, [storageKey, defaultCount]);
|
|
161
|
+
|
|
162
|
+
const handleSave = async () => {
|
|
163
|
+
try {
|
|
164
|
+
setStatus('saving');
|
|
165
|
+
setError('');
|
|
166
|
+
await setStorage<StoredCounter>({
|
|
167
|
+
key: storageKey,
|
|
168
|
+
data: {
|
|
169
|
+
value: count,
|
|
170
|
+
updatedAt: new Date().toISOString()
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
setStatus('saved');
|
|
174
|
+
} catch (caughtError) {
|
|
175
|
+
setError(caughtError instanceof Error ? caughtError.message : '保存失败');
|
|
176
|
+
setStatus('error');
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const items: ContentCardItem[] = [
|
|
181
|
+
{
|
|
182
|
+
key: 'status',
|
|
183
|
+
title: viewData.title || '本地计数',
|
|
184
|
+
subtitle: status === 'loading' ? '读取中...' : status === 'saving' ? '保存中...' : status === 'saved' ? '已保存' : '就绪'
|
|
185
|
+
},
|
|
186
|
+
...(error ? [{ key: 'error', title: '错误', subtitle: error }] : []),
|
|
187
|
+
{ key: 'count', title: '当前计数', subtitle: count }
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<ContentCard
|
|
192
|
+
items={items}
|
|
193
|
+
footer={{
|
|
194
|
+
secondaryActionButton: {
|
|
195
|
+
text: '保存',
|
|
196
|
+
loading: status === 'saving',
|
|
197
|
+
disabled: status === 'loading',
|
|
198
|
+
onClick: handleSave
|
|
199
|
+
},
|
|
200
|
+
primaryActionButton: {
|
|
201
|
+
text: '增加',
|
|
202
|
+
disabled: status === 'loading' || status === 'saving',
|
|
203
|
+
onClick: () => setCount(current => current + 1)
|
|
204
|
+
}
|
|
205
|
+
}}
|
|
206
|
+
/>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
```
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# 组件基础示例
|
|
2
|
+
|
|
3
|
+
> 项目配置:下文 `defineAppConfig` 和 `appId` 示例适用于独立应用项目,沿用其配置要求。Plugin CLI 项目以根目录的 `plugin.json` 为标识,`src/` 位于 `com.doubao/app/` 下;`app.config.ts` 默认导出普通对象,不填写 `appId`,由 CLI 查询注入。配置示例以 `doubao-plugin-app` Skill 的应用开发指南为准,本文的组件、Hooks 和入口 metadata 用法共用。
|
|
4
|
+
|
|
5
|
+
Page(页面)和 Widget(卡片)组件的基础用法对比。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📋 核心差异
|
|
10
|
+
|
|
11
|
+
| 特性 | Page | Widget |
|
|
12
|
+
|------|------|--------|
|
|
13
|
+
| **使用场景** | 全屏页面、九宫格、浮窗 | 聊天流中的卡片 |
|
|
14
|
+
| **定义方式** | 默认导出组件函数 | 默认导出组件函数 |
|
|
15
|
+
| **输入数据** | URL 参数、页面配置 | AI 生成的结构化数据 |
|
|
16
|
+
| **显示位置** | 独立页面/窗口 | 聊天对话中 |
|
|
17
|
+
| **交互方式** | 完整的页面交互 | 卡片内交互 + 消息发送 |
|
|
18
|
+
| **生命周期** | 函数入口使用 `useCreated`, `useMounted`, `useShow`, `useHide`, `useDestroy`, `useError`;对象入口使用对应的 `onXxx` 字段 | 与 Page 相同,函数入口额外支持 `useForeground`, `useBackground`;对象入口使用对应的 `onXxx` 字段 |
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 🎯 Page 基础示例
|
|
23
|
+
|
|
24
|
+
### 用户资料页面
|
|
25
|
+
|
|
26
|
+
**文件结构**:
|
|
27
|
+
```
|
|
28
|
+
src/pages/user-profile/
|
|
29
|
+
├── index.tsx # 组件逻辑
|
|
30
|
+
└── index.scss # 组件样式
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### app.config.ts
|
|
34
|
+
|
|
35
|
+
独立应用项目的 `app.config.ts` 需要配置 `appId` 和 `name`。这里额外配置 `pages` 是为了补充标题和描述;页面使用
|
|
36
|
+
`/pages/user-profile/index` 作为完整路径。
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { defineAppConfig } from '@doubao-dev/framework/config';
|
|
40
|
+
|
|
41
|
+
export default defineAppConfig({
|
|
42
|
+
appId: 'db_xxxxxx', // 独立应用项目填写;Plugin CLI 项目不填写
|
|
43
|
+
name: '我的豆包应用',
|
|
44
|
+
pages: [
|
|
45
|
+
{
|
|
46
|
+
entry: 'pages/user-profile/index',
|
|
47
|
+
title: '用户资料',
|
|
48
|
+
description: '显示用户个人信息和统计数据'
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### index.tsx
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import { useEffect, useHide, useShow, useState, useViewData } from '@doubao-dev/framework';
|
|
58
|
+
import './index.scss';
|
|
59
|
+
|
|
60
|
+
interface UserProfilePageData {
|
|
61
|
+
userId?: string;
|
|
62
|
+
title?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface UserProfile {
|
|
66
|
+
id: string;
|
|
67
|
+
name: string;
|
|
68
|
+
avatar: string;
|
|
69
|
+
email: string;
|
|
70
|
+
stats: {
|
|
71
|
+
posts: number;
|
|
72
|
+
followers: number;
|
|
73
|
+
following: number;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default function UserProfilePage() {
|
|
78
|
+
const viewData = useViewData<UserProfilePageData>();
|
|
79
|
+
const [user, setUser] = useState<UserProfile | null>(null);
|
|
80
|
+
const [loading, setLoading] = useState(true);
|
|
81
|
+
const [error, setError] = useState<string | null>(null);
|
|
82
|
+
useShow(() => console.log('页面显示'));
|
|
83
|
+
useHide(() => console.log('页面隐藏'));
|
|
84
|
+
|
|
85
|
+
// 数据获取
|
|
86
|
+
const fetchUserProfile = async () => {
|
|
87
|
+
try {
|
|
88
|
+
setLoading(true);
|
|
89
|
+
setError(null);
|
|
90
|
+
|
|
91
|
+
// 模拟 API 请求
|
|
92
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
93
|
+
|
|
94
|
+
const mockData: UserProfile = {
|
|
95
|
+
id: viewData.userId || '12345',
|
|
96
|
+
name: '张三',
|
|
97
|
+
avatar: 'https://via.placeholder.com/150',
|
|
98
|
+
email: 'zhangsan@example.com',
|
|
99
|
+
stats: {
|
|
100
|
+
posts: 42,
|
|
101
|
+
followers: 1234,
|
|
102
|
+
following: 567
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
setUser(mockData);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
setError('加载失败,请重试');
|
|
109
|
+
} finally {
|
|
110
|
+
setLoading(false);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// 编辑按钮
|
|
115
|
+
const handleEdit = () => {
|
|
116
|
+
console.log('编辑用户资料');
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
void fetchUserProfile();
|
|
121
|
+
}, [viewData.userId]);
|
|
122
|
+
|
|
123
|
+
// 渲染
|
|
124
|
+
if (loading) {
|
|
125
|
+
return (
|
|
126
|
+
<view className="profile-container">
|
|
127
|
+
<view className="loading">加载中...</view>
|
|
128
|
+
</view>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (error) {
|
|
133
|
+
return (
|
|
134
|
+
<view className="profile-container">
|
|
135
|
+
<view className="error">{error}</view>
|
|
136
|
+
<button onClick={fetchUserProfile}>重试</button>
|
|
137
|
+
</view>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!user) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<view className="profile-container">
|
|
147
|
+
{/* 头部 */}
|
|
148
|
+
<view className="profile-header">
|
|
149
|
+
<text className="page-title">{viewData.title || '用户资料'}</text>
|
|
150
|
+
<image className="avatar" src={user.avatar} />
|
|
151
|
+
<text className="name">{user.name}</text>
|
|
152
|
+
<text className="email">{user.email}</text>
|
|
153
|
+
<button className="edit-btn" onClick={handleEdit}>
|
|
154
|
+
编辑资料
|
|
155
|
+
</button>
|
|
156
|
+
</view>
|
|
157
|
+
|
|
158
|
+
{/* 数据统计 */}
|
|
159
|
+
<view className="profile-stats">
|
|
160
|
+
<view className="stat-item">
|
|
161
|
+
<text className="stat-value">{user.stats.posts}</text>
|
|
162
|
+
<text className="stat-label">帖子</text>
|
|
163
|
+
</view>
|
|
164
|
+
<view className="stat-item">
|
|
165
|
+
<text className="stat-value">{user.stats.followers}</text>
|
|
166
|
+
<text className="stat-label">关注者</text>
|
|
167
|
+
</view>
|
|
168
|
+
<view className="stat-item">
|
|
169
|
+
<text className="stat-value">{user.stats.following}</text>
|
|
170
|
+
<text className="stat-label">关注中</text>
|
|
171
|
+
</view>
|
|
172
|
+
</view>
|
|
173
|
+
</view>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### index.scss
|
|
179
|
+
|
|
180
|
+
```scss
|
|
181
|
+
.profile-container {
|
|
182
|
+
padding: 32px;
|
|
183
|
+
background-color: #f5f5f5;
|
|
184
|
+
|
|
185
|
+
.loading,
|
|
186
|
+
.error {
|
|
187
|
+
text-align: center;
|
|
188
|
+
padding: 40px;
|
|
189
|
+
font-size: 28px;
|
|
190
|
+
color: #666;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.profile-header {
|
|
194
|
+
background-color: #fff;
|
|
195
|
+
border-radius: 16px;
|
|
196
|
+
padding: 40px;
|
|
197
|
+
text-align: center;
|
|
198
|
+
margin-bottom: 32px;
|
|
199
|
+
|
|
200
|
+
.page-title {
|
|
201
|
+
display: block;
|
|
202
|
+
font-size: 32px;
|
|
203
|
+
font-weight: 600;
|
|
204
|
+
color: #222;
|
|
205
|
+
margin-bottom: 24px;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.avatar {
|
|
209
|
+
width: 160px;
|
|
210
|
+
height: 160px;
|
|
211
|
+
border-radius: 80px;
|
|
212
|
+
margin-bottom: 24px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.name {
|
|
216
|
+
font-size: 36px;
|
|
217
|
+
font-weight: bold;
|
|
218
|
+
color: #333;
|
|
219
|
+
margin-bottom: 12px;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.email {
|
|
223
|
+
font-size: 28px;
|
|
224
|
+
color: #666;
|
|
225
|
+
margin-bottom: 24px;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.edit-btn {
|
|
229
|
+
padding: 16px 48px;
|
|
230
|
+
background-color: #1890ff;
|
|
231
|
+
color: #fff;
|
|
232
|
+
border-radius: 8px;
|
|
233
|
+
font-size: 28px;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.profile-stats {
|
|
238
|
+
display: flex;
|
|
239
|
+
justify-content: space-around;
|
|
240
|
+
background-color: #fff;
|
|
241
|
+
border-radius: 16px;
|
|
242
|
+
padding: 32px;
|
|
243
|
+
|
|
244
|
+
.stat-item {
|
|
245
|
+
text-align: center;
|
|
246
|
+
|
|
247
|
+
.stat-value {
|
|
248
|
+
font-size: 36px;
|
|
249
|
+
font-weight: bold;
|
|
250
|
+
color: #333;
|
|
251
|
+
margin-bottom: 8px;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.stat-label {
|
|
255
|
+
font-size: 24px;
|
|
256
|
+
color: #999;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## 🎴 Widget 基础示例
|
|
266
|
+
|
|
267
|
+
Widget 卡片内容和布局必须使用 [Widget 模板库](../widget-templates/overview.md) 中的 `@doubao-dev/template` 模板。先根据业务场景在模板库中选型,
|
|
268
|
+
再在入口组件中读取 `viewData` 并直接返回模板组件。
|
|
269
|
+
|
|
270
|
+
### 列表卡片
|
|
271
|
+
|
|
272
|
+
**文件结构**:
|
|
273
|
+
```
|
|
274
|
+
src/widgets/recommend-list/
|
|
275
|
+
├── index.tsx # 组件逻辑
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### index.tsx
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
import { useDestroy, useMounted, useViewData } from '@doubao-dev/framework';
|
|
282
|
+
import { ContentCard, type ContentCardItem } from '@doubao-dev/template';
|
|
283
|
+
|
|
284
|
+
interface RecommendListData {
|
|
285
|
+
items: ContentCardItem[];
|
|
286
|
+
actionText?: string;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export default function RecommendList() {
|
|
290
|
+
const viewData = useViewData<RecommendListData>();
|
|
291
|
+
useMounted(() => console.log('卡片挂载'));
|
|
292
|
+
useDestroy(() => console.log('卡片销毁'));
|
|
293
|
+
|
|
294
|
+
return (
|
|
295
|
+
<ContentCard
|
|
296
|
+
items={viewData.items}
|
|
297
|
+
footer={
|
|
298
|
+
viewData.actionText
|
|
299
|
+
? {
|
|
300
|
+
primaryActionButton: {
|
|
301
|
+
text: viewData.actionText,
|
|
302
|
+
onClick: () => console.log('查看更多')
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
: undefined
|
|
306
|
+
}
|
|
307
|
+
/>
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## 🔑 关键要点
|
|
315
|
+
|
|
316
|
+
### Page 开发要点
|
|
317
|
+
|
|
318
|
+
1. **完整的页面生命周期**
|
|
319
|
+
- `useShow()` - 页面显示时调用
|
|
320
|
+
- `useHide()` - 页面隐藏时调用
|
|
321
|
+
- `useDestroy()` - 页面销毁时调用
|
|
322
|
+
|
|
323
|
+
2. **页面配置(可选 pages)**
|
|
324
|
+
- 一级目录不写进 `pages` 也会自动发现,默认 `id` 是目录名
|
|
325
|
+
- 需要 `title`、`description`、固定首页或多级目录时再配置
|
|
326
|
+
- 多级目录需要显式写 `entry`
|
|
327
|
+
|
|
328
|
+
3. **状态管理**
|
|
329
|
+
- 使用 React Hooks(useState, useEffect)
|
|
330
|
+
- 处理加载、错误、数据状态
|
|
331
|
+
|
|
332
|
+
4. **用户交互**
|
|
333
|
+
- 按钮点击、表单提交
|
|
334
|
+
- 页面跳转、数据更新
|
|
335
|
+
|
|
336
|
+
### Widget 开发要点
|
|
337
|
+
|
|
338
|
+
1. **卡片生命周期**
|
|
339
|
+
- `useShow()` / `useHide()` - 卡片显示/隐藏
|
|
340
|
+
- `useForeground()` / `useBackground()` - 应用前后台切换
|
|
341
|
+
- `useMounted()` / `useDestroy()` - 挂载/销毁
|
|
342
|
+
|
|
343
|
+
2. **ViewData 定义**
|
|
344
|
+
- 使用 TypeScript 类型定义 `useViewData<T>()` 的 viewData 结构
|
|
345
|
+
- 数据结构优先贴近 `@doubao-dev/template` 的模板 props 或列表项类型
|
|
346
|
+
- 对可选字段和空数据做兜底处理
|
|
347
|
+
- 避免直接使用 `any`
|
|
348
|
+
|
|
349
|
+
3. **卡片类型 (boxType)**
|
|
350
|
+
- `inbox` - 普通卡片
|
|
351
|
+
- `full_box` - 全宽卡片
|
|
352
|
+
- `custom_box` - 背景、圆角和描边由业务内容设置,标题栏仍遵循 `titleType`
|
|
353
|
+
|
|
354
|
+
4. **消息交互**
|
|
355
|
+
- 接收 AI 传入的数据
|
|
356
|
+
- 可以发送消息回 Bot
|
|
357
|
+
- 支持通过模板按钮和事件进行卡片内交互
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## 📚 延伸阅读
|
|
362
|
+
|
|
363
|
+
- **组件开发完整指南** → [../guides/component-development.md](../guides/component-development.md)
|
|
364
|
+
- **常用开发模式** → [./common-patterns.md](./common-patterns.md)
|
|
365
|
+
- **豆包应用插件的端能力 API 使用配方** → [./doubao-agentic-service-api-recipes.md](./doubao-agentic-service-api-recipes.md)
|
|
366
|
+
- **Page 组件配方** → [./page-widget-recipes.md](./page-widget-recipes.md)
|
|
367
|
+
- **Framework 核心入口、生命周期和 Hooks** → [../framework/core.md](../framework/core.md)
|
|
368
|
+
- **豆包应用插件的端能力 API 速查** → [../doubao-agentic-service-api/quick-reference.md](../doubao-agentic-service-api/quick-reference.md)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Page 组件配方
|
|
2
|
+
|
|
3
|
+
本文件放内置组件和 Page 状态组合的示例。组件的完整 props 和事件签名见 [组件 API](../components/overview.md);这里重点展示受控状态、事件处理和保存态如何组合。
|
|
4
|
+
Widget 卡片整体布局和操作区必须使用 [Widget 模板库](../widget-templates/overview.md) 中的模板,不手写卡片布局。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Settings Page
|
|
9
|
+
|
|
10
|
+
内置组件用小写标签直接写 JSX,不要 import 组件。需要按钮、开关和滑块时,三个标签都要直接出现在 TSX 中:
|
|
11
|
+
`button` 用 `onClick`;`switch` / `slider` 的 `onChange` 直接接收值,不是事件对象。
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { useState, useViewData } from '@doubao-dev/framework';
|
|
15
|
+
import './index.scss';
|
|
16
|
+
|
|
17
|
+
interface SettingsPanelData {
|
|
18
|
+
title: string;
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
volume: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default function SettingsPanel() {
|
|
24
|
+
const viewData = useViewData<SettingsPanelData>();
|
|
25
|
+
const [enabled, setEnabled] = useState(viewData.enabled);
|
|
26
|
+
const [volume, setVolume] = useState(viewData.volume);
|
|
27
|
+
const [saving, setSaving] = useState(false);
|
|
28
|
+
const [message, setMessage] = useState('');
|
|
29
|
+
|
|
30
|
+
const handleSave = async () => {
|
|
31
|
+
setSaving(true);
|
|
32
|
+
setMessage('');
|
|
33
|
+
try {
|
|
34
|
+
await Promise.resolve();
|
|
35
|
+
setMessage(`已保存: ${enabled ? '开启' : '关闭'} / ${volume}`);
|
|
36
|
+
} catch {
|
|
37
|
+
setMessage('保存失败,请重试');
|
|
38
|
+
} finally {
|
|
39
|
+
setSaving(false);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<view className="settings-panel">
|
|
45
|
+
<text className="settings-title">{viewData.title}</text>
|
|
46
|
+
<button
|
|
47
|
+
text={saving ? '保存中' : '保存'}
|
|
48
|
+
type="primary"
|
|
49
|
+
loading={saving}
|
|
50
|
+
disabled={saving}
|
|
51
|
+
onClick={handleSave}
|
|
52
|
+
/>
|
|
53
|
+
<switch checked={enabled} onChange={(checked: boolean) => setEnabled(checked)} />
|
|
54
|
+
<slider
|
|
55
|
+
value={volume}
|
|
56
|
+
min={0}
|
|
57
|
+
max={100}
|
|
58
|
+
showValue={true}
|
|
59
|
+
onChange={(nextVolume: number) => setVolume(nextVolume)}
|
|
60
|
+
/>
|
|
61
|
+
{message && <text className="settings-message">{message}</text>}
|
|
62
|
+
</view>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
```
|