@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,275 @@
1
+ # MCP UI 约定入口
2
+
3
+ `src/mcp-ui` 用于登录、隐私授权和 MCP 相关特殊页面。该目录由构建工具按约定扫描,不需要写到
4
+ `src/app.config.ts` 的 `pages` / `widgets` 中。
5
+
6
+ ---
7
+
8
+ ## 入口类型
9
+
10
+ | 入口 | 文件路径 | 是否手写 | 用途 |
11
+ |------|----------|----------|------|
12
+ | 隐私协议回调 | `src/mcp-ui/privacy.ts` | 按需手写 | 处理同意或拒绝隐私协议 |
13
+ | 登录回调 | `src/mcp-ui/login.ts` | 按需手写 | 处理手机号授权登录或拒绝登录 |
14
+ | 登录页面 | `src/mcp-ui/mcp-login-page/index.tsx` | 按需手写 | 展示自定义登录页 |
15
+ | 登录/隐私卡片 | `mcp-privacy-login-card` | 框架生成 | 根据 `privacy.ts` / `login.ts` 自动挂接回调 |
16
+
17
+ `mcp-privacy-login-card` 是内置虚拟 Widget,不要在 `src/widgets` 中创建同名目录,也不要手动注册到 `app.config.ts`。
18
+
19
+ ---
20
+
21
+ ## privacy.ts
22
+
23
+ `privacy.ts` 只导出隐私协议回调,不定义 UI。
24
+
25
+ ```ts
26
+ import { definePrivacyApi, type PrivacyAgreeResult } from '@doubao-apps/framework';
27
+
28
+ export default definePrivacyApi({
29
+ async onAgreePrivacy(res: PrivacyAgreeResult) {
30
+ console.log('onAgreePrivacy', res);
31
+ },
32
+
33
+ async onRefusePrivacy() {
34
+ console.log('onRefusePrivacy');
35
+ }
36
+ });
37
+ ```
38
+
39
+ ---
40
+
41
+ ## login.ts
42
+
43
+ `login.ts` 只导出登录回调,不定义 UI。内置登录卡完成手机号授权后,会把手机号授权结果传给 `onLogin`。
44
+
45
+ 最小登录闭环:
46
+
47
+ 1. 从 `LoginResult` 读取手机号授权 `code`。
48
+ 2. 调用 `login()` 获取小程序登录 `code`。
49
+ 3. 把两个 code 发给业务后端换取业务登录凭证。
50
+ 4. 按需保存业务 session。
51
+ 5. 调用 `postLoginResult()` 把登录结果回传给宿主。
52
+
53
+ 示例中的后端地址是占位 URL,实际项目需要替换成自己的 HTTPS 登录接口。
54
+
55
+ ```ts
56
+ import { defineLoginApi, type LoginResult } from '@doubao-apps/framework';
57
+ import { login, postLoginResult, request, setStorage } from '@doubao-apps/framework/api';
58
+
59
+ interface AuthResponse {
60
+ loginCode: string;
61
+ sessionKey: string;
62
+ }
63
+
64
+ function isAuthResponse(data: unknown): data is AuthResponse {
65
+ return (
66
+ typeof data === 'object' &&
67
+ data !== null &&
68
+ 'loginCode' in data &&
69
+ 'sessionKey' in data &&
70
+ typeof data.loginCode === 'string' &&
71
+ typeof data.sessionKey === 'string'
72
+ );
73
+ }
74
+
75
+ async function exchangeLoginCode(params: { appId: string; loginCode: string; phoneCode: string }) {
76
+ const response = await request({
77
+ url: 'https://example.com/api/doubao-login',
78
+ method: 'POST',
79
+ header: {
80
+ 'content-type': 'application/json'
81
+ },
82
+ data: params
83
+ });
84
+
85
+ if (response.statusCode < 200 || response.statusCode >= 300 || !isAuthResponse(response.data)) {
86
+ throw new Error('登录响应无效');
87
+ }
88
+
89
+ return response.data;
90
+ }
91
+
92
+ export default defineLoginApi({
93
+ async onLogin(res: LoginResult) {
94
+ if (!res.code) {
95
+ await postLoginResult({ result: false });
96
+ return;
97
+ }
98
+
99
+ try {
100
+ const loginResult = await login({ timeout: 30000 });
101
+ const auth = await exchangeLoginCode({
102
+ appId: 'your-app-id',
103
+ loginCode: loginResult.code,
104
+ phoneCode: res.code
105
+ });
106
+
107
+ await setStorage({
108
+ key: 'session_key',
109
+ data: auth.sessionKey
110
+ });
111
+
112
+ await postLoginResult({
113
+ result: true,
114
+ code: auth.loginCode
115
+ });
116
+ } catch (error) {
117
+ console.error('onLogin failed', error);
118
+ await postLoginResult({ result: false });
119
+ }
120
+ },
121
+
122
+ async onRefuseLogin() {
123
+ await postLoginResult({ result: false });
124
+ }
125
+ });
126
+ ```
127
+
128
+ ---
129
+
130
+ ## mcp-login-page
131
+
132
+ 自定义登录页放在 `src/mcp-ui/mcp-login-page/index.tsx`,使用 `defineLoginPage()`,不要用普通 `definePage()`。
133
+ 构建工具会把它作为特殊 Page 处理,metadata 使用内置 `id: 'mcp-login-page'` 和 `path: '/pages/mcp-login-page/index'`。
134
+
135
+ ```tsx
136
+ import { defineLoginPage, getViewData, type LoginResult, useEffect, useState } from '@doubao-apps/framework';
137
+ import {
138
+ login,
139
+ navigateBack,
140
+ postLoginResult,
141
+ request,
142
+ setStorage,
143
+ showToast
144
+ } from '@doubao-apps/framework/api';
145
+ import './index.scss';
146
+
147
+ interface McpLoginPageData {
148
+ title?: string;
149
+ description?: string;
150
+ }
151
+
152
+ interface AuthResponse {
153
+ loginCode: string;
154
+ sessionKey: string;
155
+ }
156
+
157
+ function isAuthResponse(data: unknown): data is AuthResponse {
158
+ return (
159
+ typeof data === 'object' &&
160
+ data !== null &&
161
+ 'loginCode' in data &&
162
+ 'sessionKey' in data &&
163
+ typeof data.loginCode === 'string' &&
164
+ typeof data.sessionKey === 'string'
165
+ );
166
+ }
167
+
168
+ let loginResultPosted = false;
169
+
170
+ async function postLoginFailure() {
171
+ if (loginResultPosted) {
172
+ return;
173
+ }
174
+
175
+ await postLoginResult({ result: false });
176
+ loginResultPosted = true;
177
+ }
178
+
179
+ async function exchangeLoginCode(params: { appId: string; loginCode: string; phoneCode: string }) {
180
+ const response = await request({
181
+ url: 'https://example.com/api/doubao-login',
182
+ method: 'POST',
183
+ header: {
184
+ 'content-type': 'application/json'
185
+ },
186
+ data: params
187
+ });
188
+
189
+ if (response.statusCode < 200 || response.statusCode >= 300 || !isAuthResponse(response.data)) {
190
+ throw new Error('登录响应无效');
191
+ }
192
+
193
+ return response.data;
194
+ }
195
+
196
+ function McpLoginPage() {
197
+ const viewData = getViewData<McpLoginPageData>();
198
+ const [submitting, setSubmitting] = useState(false);
199
+
200
+ useEffect(() => {
201
+ loginResultPosted = false;
202
+ }, []);
203
+
204
+ const handlePhoneLogin = async (res?: LoginResult) => {
205
+ if (submitting) {
206
+ return;
207
+ }
208
+
209
+ if (!res.code) {
210
+ showToast({ message: res.message || '未完成手机号授权', type: 'warning' });
211
+ return;
212
+ }
213
+
214
+ try {
215
+ setSubmitting(true);
216
+ const loginResult = await login({ timeout: 30000 });
217
+ const auth = await exchangeLoginCode({
218
+ appId: 'your-app-id',
219
+ loginCode: loginResult.code,
220
+ phoneCode: res.code
221
+ });
222
+
223
+ await setStorage({
224
+ key: 'session_key',
225
+ data: auth.sessionKey
226
+ });
227
+
228
+ await postLoginResult({
229
+ result: true,
230
+ code: auth.loginCode
231
+ });
232
+ loginResultPosted = true;
233
+ await navigateBack();
234
+ } catch (error) {
235
+ console.error('mcp login failed', error);
236
+ showToast({ message: '登录失败,请稍后重试', type: 'error' });
237
+ } finally {
238
+ setSubmitting(false);
239
+ }
240
+ };
241
+
242
+ const handleCancel = async () => {
243
+ await postLoginFailure();
244
+ await navigateBack();
245
+ };
246
+
247
+ return (
248
+ <view className="mcp-login-page">
249
+ <text className="mcp-login-page__title">{viewData.title || '登录'}</text>
250
+ <text className="mcp-login-page__description">{viewData.description || '请完成登录后继续使用'}</text>
251
+ <button
252
+ text={submitting ? '登录中...' : '手机号一键登录'}
253
+ type="primary"
254
+ openType="getPhoneNumber"
255
+ loading={submitting}
256
+ disabled={submitting}
257
+ onGetPhoneNumber={(res) => {
258
+ void handlePhoneLogin(res);
259
+ }}
260
+ />
261
+ <button text="暂不登录" disabled={submitting} onClick={handleCancel} />
262
+ </view>
263
+ );
264
+ }
265
+
266
+ export default defineLoginPage({
267
+ onDestroy() {
268
+ void postLoginFailure();
269
+ },
270
+
271
+ render() {
272
+ return <McpLoginPage />;
273
+ }
274
+ });
275
+ ```
@@ -168,14 +168,16 @@ function InfiniteList() {
168
168
  // ✅ 好的做法 - 图片懒加载
169
169
  function LazyImage({ src, placeholder }: Props) {
170
170
  const [loaded, setLoaded] = useState(false);
171
+ const imageClassName = loaded
172
+ ? 'lazy-image__actual lazy-image__actual--visible'
173
+ : 'lazy-image__actual lazy-image__actual--hidden';
171
174
 
172
175
  return (
173
176
  <view className="lazy-image">
174
177
  {!loaded && <image src={placeholder} className="lazy-image__placeholder" />}
175
178
  <image
176
179
  src={src}
177
- className="lazy-image__actual"
178
- style={{ display: loaded ? 'block' : 'none' }}
180
+ className={imageClassName}
179
181
  onLoad={() => setLoaded(true)}
180
182
  onError={() => console.error('Failed to load image')}
181
183
  />
@@ -184,6 +186,16 @@ function LazyImage({ src, placeholder }: Props) {
184
186
  }
185
187
  ```
186
188
 
189
+ ```scss
190
+ .lazy-image__actual--hidden {
191
+ display: none;
192
+ }
193
+
194
+ .lazy-image__actual--visible {
195
+ display: block;
196
+ }
197
+ ```
198
+
187
199
  #### 图片尺寸优化
188
200
 
189
201
  ```tsx
@@ -220,7 +232,7 @@ function UnoptimizedImage({ src }: Props) {
220
232
  // ✅ 好的做法 - 清理定时器和订阅
221
233
  function TimerComponent() {
222
234
  const [count, setCount] = useState(0);
223
- const timerRef = useRef<any>(null);
235
+ const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
224
236
 
225
237
  useEffect(() => {
226
238
  // 创建定时器
@@ -291,9 +303,12 @@ function DataFetcher() {
291
303
 
292
304
  ```tsx
293
305
  // ✅ 好的做法 - 分批处理大数据
294
- async function processBigData(data: any[]) {
306
+ async function processBigData<T, R>(
307
+ data: T[],
308
+ processBatch: (items: T[]) => Promise<R[]>
309
+ ): Promise<R[]> {
295
310
  const BATCH_SIZE = 100;
296
- const results = [];
311
+ const results: R[] = [];
297
312
 
298
313
  for (let i = 0; i < data.length; i += BATCH_SIZE) {
299
314
  const batch = data.slice(i, i + BATCH_SIZE);
@@ -308,7 +323,7 @@ async function processBigData(data: any[]) {
308
323
  }
309
324
 
310
325
  // ❌ 不好的做法 - 一次性处理所有数据
311
- async function processBigData(data: any[]) {
326
+ async function processBigData<T>(data: T[]) {
312
327
  // 如果数据量大,会阻塞 UI
313
328
  return data.map(item => expensiveOperation(item));
314
329
  }
@@ -322,8 +337,10 @@ async function processBigData(data: any[]) {
322
337
 
323
338
  ```tsx
324
339
  // ✅ 好的做法 - 动态导入大组件
340
+ type HeavyComponent = () => JSX.Element;
341
+
325
342
  function HeavyFeature() {
326
- const [Component, setComponent] = useState<any>(null);
343
+ const [Component, setComponent] = useState<HeavyComponent | null>(null);
327
344
 
328
345
  useEffect(() => {
329
346
  // 只在需要时加载
@@ -10,22 +10,28 @@
10
10
  |---------|------|----------|
11
11
  | `<button>` | 按钮交互 | `text`, `type`, `size`, `loading`, `disabled`, `openType`, `onClick` |
12
12
  | `<switch>` | 开关切换 | `checked`, `defaultChecked`, `disabled`, `onChange` |
13
- | `<slider>` | 滑块选择 | `value`(必填), `min`, `max`, `showValue`, `onChange` |
13
+ | `<slider>` | 滑块选择 | `value`(必填), `min`, `max`, `showValue`, `disabled`, `onChange` |
14
14
  | `<swiper>` | 轮播容器 | `data`, `itemWidth`, `itemHeight`, `loop`, `duration`, `onChange` |
15
15
  | `<swiper-item>` | 轮播项 | `index`, `realIndex` |
16
16
 
17
17
  ---
18
18
 
19
+ ## 事件命名
20
+
21
+ 应用 TSX 中使用 React 风格事件名。`button` 点击使用 `onClick`;不要在业务代码里写 `onTap`、`bindtap` 或 `catchtap`。
22
+ `switch` / `slider` / `swiper` 使用 `onChange`,回调直接接收当前值。
23
+
24
+ ---
25
+
19
26
  ## button 按钮
20
27
 
21
28
  ```tsx
22
29
  <button
23
30
  text="提交"
24
31
  type="primary" // primary | default | plain
25
- size="default" // default | small | large
32
+ size="default" // default | mini | large
26
33
  loading={false}
27
34
  disabled={false}
28
- icon={<IconNode />}
29
35
  throttle={500} // 节流时间(ms)
30
36
  onClick={() => {}}
31
37
  />
@@ -68,6 +74,7 @@ const [value, setValue] = useState(50);
68
74
  ```
69
75
 
70
76
  > `onChange` 回调直接接收 `number` 值,而非事件对象。
77
+ > `slider` 不支持 `step`、`defaultValue` 或事件对象参数;不要传这些 props。
71
78
 
72
79
  ---
73
80
 
@@ -97,7 +104,11 @@ const [value, setValue] = useState(50);
97
104
  ## 注意事项
98
105
 
99
106
  1. **无需 import**:所有组件直接使用小写标签,不需要从任何包导入
100
- 2. **onChange 回调签名**:switch/slider/swiper `onChange` 直接接收值(boolean/number/number),不是事件对象
101
- 3. **Lynx 不支持 CSS 继承**:组件样式自包含,不依赖父元素样式
102
- 4. **受控/非受控**:switch 支持两种模式,slider 只支持受控模式
103
- 5. **节流处理**:button 内置 500ms 节流,可通过 `throttle` 自定义
107
+ 2. **事件命名**:button 使用 `onClick`,不要写 `onTap`、`bindtap` 或 `catchtap`
108
+ 3. **onChange 回调签名**:switch/slider/swiper `onChange` 直接接收值(boolean/number/number),不是事件对象
109
+ 4. **Lynx 不支持 CSS 继承**:组件样式自包含,不依赖父元素样式
110
+ 5. **受控/非受控**:switch 支持两种模式,slider 只支持受控模式
111
+ 6. **节流处理**:button 内置 500ms 节流,可通过 `throttle` 自定义
112
+ 7. **Slider props**:slider 只使用 `value`、`min`、`max`、`showValue`、`disabled`、`onChange`,不要写 `step`
113
+
114
+ 完整 Page 组合示例见 [内置组件使用配方](../examples/component-recipes.md)。