@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.
Files changed (107) hide show
  1. package/README.md +10 -2
  2. package/dist/147.js +9 -0
  3. package/dist/assets.d.ts +5 -0
  4. package/dist/assets.js +4 -0
  5. package/dist/index.d.ts +43 -0
  6. package/dist/index.js +1378 -0
  7. package/dist/plugin.d.ts +102 -0
  8. package/dist/plugin.js +4995 -0
  9. package/dist/preview.js +16 -0
  10. package/dist/rslib-runtime.js +33 -0
  11. package/dist/skills/doubao-plugin-app/SKILL.md +21 -0
  12. package/dist/skills/doubao-plugin-app/references/app-development.md +85 -0
  13. package/dist/skills/doubao-plugin-app/references/frontend/components/container-overlay.md +131 -0
  14. package/dist/skills/doubao-plugin-app/references/frontend/components/form-controls.md +146 -0
  15. package/dist/skills/doubao-plugin-app/references/frontend/components/interactive-controls.md +120 -0
  16. package/dist/skills/doubao-plugin-app/references/frontend/components/map-canvas.md +107 -0
  17. package/dist/skills/doubao-plugin-app/references/frontend/components/overview.md +43 -0
  18. package/dist/skills/doubao-plugin-app/references/frontend/components/scroll-list.md +149 -0
  19. package/dist/skills/doubao-plugin-app/references/frontend/components/text-media.md +209 -0
  20. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/01-/345/237/272/347/241/200.md +1389 -0
  21. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/02-/347/263/273/347/273/237.md +1376 -0
  22. 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
  23. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/04-/345/256/232/344/275/215.md +784 -0
  24. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/05-/345/255/230/345/202/250.md +1176 -0
  25. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/06-/350/267/257/347/224/261.md +839 -0
  26. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/07-/347/225/214/351/235/242.md +376 -0
  27. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/08-/344/272/244/344/272/222.md +1319 -0
  28. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/09-/350/276/223/345/205/245.md +192 -0
  29. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/10-/347/275/221/347/273/234.md +900 -0
  30. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/11-/345/252/222/344/275/223.md +1496 -0
  31. 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
  32. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/13-/347/231/273/345/275/225.md +511 -0
  33. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/14-/346/224/257/344/273/230.md +943 -0
  34. 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
  35. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/16-/350/223/235/347/211/231.md +3364 -0
  36. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/17-wi-fi.md +1028 -0
  37. 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
  38. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/19-ibeacon.md +508 -0
  39. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/20-/347/275/227/347/233/230.md +320 -0
  40. 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
  41. 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
  42. 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
  43. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/24-/347/237/255/344/277/241.md +155 -0
  44. 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
  45. 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
  46. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/27-/347/224/265/346/261/240.md +235 -0
  47. 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
  48. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/29-/345/212/240/345/257/206.md +126 -0
  49. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/30-/346/227/245/345/216/206.md +417 -0
  50. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/31-/347/224/265/350/257/235.md +144 -0
  51. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/32-/346/211/253/347/240/201.md +236 -0
  52. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/33-/345/261/217/345/271/225.md +747 -0
  53. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/34-/351/234/207/345/212/250.md +277 -0
  54. 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
  55. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/common-errors.md +69 -0
  56. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/groups.md +43 -0
  57. package/dist/skills/doubao-plugin-app/references/frontend/doubao-agentic-service-api/quick-reference.md +436 -0
  58. package/dist/skills/doubao-plugin-app/references/frontend/examples/common-patterns.md +590 -0
  59. package/dist/skills/doubao-plugin-app/references/frontend/examples/doubao-agentic-service-api-recipes.md +209 -0
  60. package/dist/skills/doubao-plugin-app/references/frontend/examples/page-widget-basics.md +368 -0
  61. package/dist/skills/doubao-plugin-app/references/frontend/examples/page-widget-recipes.md +65 -0
  62. package/dist/skills/doubao-plugin-app/references/frontend/framework/core.md +283 -0
  63. package/dist/skills/doubao-plugin-app/references/frontend/guides/auth.md +277 -0
  64. package/dist/skills/doubao-plugin-app/references/frontend/guides/best-practices.md +574 -0
  65. package/dist/skills/doubao-plugin-app/references/frontend/guides/client-tools-and-events.md +209 -0
  66. package/dist/skills/doubao-plugin-app/references/frontend/guides/component-development.md +832 -0
  67. package/dist/skills/doubao-plugin-app/references/frontend/guides/debugger-mcp.md +537 -0
  68. package/dist/skills/doubao-plugin-app/references/frontend/guides/expired-widget.md +101 -0
  69. package/dist/skills/doubao-plugin-app/references/frontend/guides/performance-optimization.md +417 -0
  70. package/dist/skills/doubao-plugin-app/references/frontend/guides/troubleshooting.md +278 -0
  71. package/dist/skills/doubao-plugin-app/references/frontend/lynx/best-practices.md +720 -0
  72. package/dist/skills/doubao-plugin-app/references/frontend/lynx/css/pseudo-classes.md +149 -0
  73. package/dist/skills/doubao-plugin-app/references/frontend/lynx/css/selectors.md +224 -0
  74. package/dist/skills/doubao-plugin-app/references/frontend/lynx/css/supported-properties.md +269 -0
  75. package/dist/skills/doubao-plugin-app/references/frontend/lynx/css/values-and-units.md +314 -0
  76. package/dist/skills/doubao-plugin-app/references/frontend/lynx/layout/flex-layout.md +334 -0
  77. package/dist/skills/doubao-plugin-app/references/frontend/lynx/layout/grid-layout.md +408 -0
  78. package/dist/skills/doubao-plugin-app/references/frontend/lynx/layout/linear-layout.md +355 -0
  79. package/dist/skills/doubao-plugin-app/references/frontend/lynx/layout/relative-layout.md +189 -0
  80. package/dist/skills/doubao-plugin-app/references/frontend/lynx/lynx-vs-web/css-differences.md +737 -0
  81. package/dist/skills/doubao-plugin-app/references/frontend/lynx/lynx-vs-web/migration-guide.md +669 -0
  82. package/dist/skills/doubao-plugin-app/references/frontend/lynx/lynx-vs-web/unsupported-features.md +290 -0
  83. package/dist/skills/doubao-plugin-app/references/frontend/lynx/overview.md +60 -0
  84. package/dist/skills/doubao-plugin-app/references/frontend/lynx/patterns/animation.md +243 -0
  85. package/dist/skills/doubao-plugin-app/references/frontend/lynx/patterns/responsive.md +194 -0
  86. package/dist/skills/doubao-plugin-app/references/frontend/lynx/patterns/theming.md +143 -0
  87. package/dist/skills/doubao-plugin-app/references/frontend/lynx/quick-reference.md +229 -0
  88. package/dist/skills/doubao-plugin-app/references/frontend/rules/dos-and-donts.md +479 -0
  89. package/dist/skills/doubao-plugin-app/references/frontend/widget-templates/design-boundary.md +46 -0
  90. package/dist/skills/doubao-plugin-app/references/frontend/widget-templates/overview.md +118 -0
  91. package/dist/skills/doubao-plugin-app/references/frontend/widget-templates/props.md +301 -0
  92. package/dist/skills/doubao-plugin-app/references/mcp-and-cards.md +38 -0
  93. package/dist/templates/README.md.template +11 -0
  94. package/dist/templates/com.doubao/app/manifest.yaml +41 -0
  95. package/dist/templates/com.doubao/app/package.json.template +13 -0
  96. package/dist/templates/com.doubao/app/src/app.config.ts.template +5 -0
  97. package/dist/templates/com.doubao/app/src/app.ts.template +3 -0
  98. package/dist/templates/com.doubao/app/src/pages/home/index.css +14 -0
  99. package/dist/templates/com.doubao/app/src/pages/home/index.tsx.template +14 -0
  100. package/dist/templates/com.doubao/app/src/widgets/example/index.tsx.template +29 -0
  101. package/dist/templates/com.doubao/app/tsconfig.json +19 -0
  102. package/dist/templates/mcp.json +9 -0
  103. package/dist/templates/plugin.json.template +29 -0
  104. package/dist/templates/skills/example/SKILL.md.template +14 -0
  105. package/dist/upload.d.ts +44 -0
  106. package/dist/upload.js +38 -0
  107. package/package.json +60 -3
@@ -0,0 +1,283 @@
1
+ # Framework 核心入口、生命周期和 Hooks
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
+ 本文件只覆盖 `@doubao-dev/framework` 和 `@doubao-dev/framework/config` 的开发范式:
6
+ App / Page / Widget 入口、生命周期、Hooks、viewData 和 `app.config.ts` 配置。
7
+
8
+ 豆包应用插件的端能力 API(也称 Open API,例如网络、Storage、位置、路由、Toast、模型上下文等)从
9
+ `@doubao-dev/framework/api` 导入,不在本文件展开。只要代码要从该路径导入 API,必须先读
10
+ [豆包应用插件的端能力 API 速查](../doubao-agentic-service-api/quick-reference.md) 和 [分组目录](../doubao-agentic-service-api/groups.md)。
11
+
12
+ ---
13
+
14
+ ## 导入边界
15
+
16
+ | 能力 | 导入路径 | 本文件是否展开 |
17
+ |-----|----------|----------------|
18
+ | 入口定义、Hooks、viewData | `@doubao-dev/framework` | 是 |
19
+ | App 配置、入口 metadata | `@doubao-dev/framework/config` | 是 |
20
+ | 网络、Storage、位置、路由、Toast、模型上下文等端能力 | `@doubao-dev/framework/api` | 否,见 `../doubao-agentic-service-api/` |
21
+
22
+ 不要从 `react` 直接导入 Hooks;在豆包应用插件代码里使用 `@doubao-dev/framework` 导出的 Hooks。
23
+
24
+ ---
25
+
26
+ ## 入口定义
27
+
28
+ | 入口 | 文件位置 | 用途 |
29
+ |-----|----------|------|
30
+ | `defineApp()` | `src/app.ts` | 定义应用入口和 App 生命周期 |
31
+ | 默认导出组件函数 | `src/pages/<page-name>/index.tsx` | 推荐的 Page(全屏页面)入口 |
32
+ | 默认导出组件函数 | `src/widgets/<widget-name>/index.tsx` | 推荐的 Widget(聊天卡片)入口 |
33
+ | 默认导出组件函数 | `src/auth/login-page/index.tsx` | 推荐的自定义登录页入口;没有 `src/auth` 入口时旧路径 `src/mcp-ui/mcp-login-page/index.tsx` 继续兼容 |
34
+
35
+ Page / Widget 的名称、标题、描述、`boxType` 等 metadata 写在 `src/app.config.ts`,不要写到
36
+ 源码入口。Page / Widget 直接默认导出组件函数,并在组件内使用生命周期 Hooks。
37
+
38
+ `src/auth/login-page/index.tsx` 与兼容的 `mcp-login-page` 约定路径会由构建工具自动注入登录页固定 metadata。
39
+ 已有 `defineLoginPage()` 对象入口无需迁移;新登录页不要使用其他目录名。
40
+
41
+ ### Page 示例
42
+
43
+ ```tsx
44
+ import { useMounted, useState, useViewData } from '@doubao-dev/framework';
45
+ import './index.scss';
46
+
47
+ interface DetailPageData {
48
+ id: string;
49
+ title?: string;
50
+ }
51
+
52
+ export default function DetailPage() {
53
+ const viewData = useViewData<DetailPageData>();
54
+ const [selected, setSelected] = useState(false);
55
+ useMounted(() => console.log('detail page mounted'));
56
+
57
+ return (
58
+ <view className="detail-page">
59
+ <text className="title">{viewData.title || '详情'}</text>
60
+ <text className="id">{viewData.id}</text>
61
+ <button text={selected ? '已选择' : '选择'} onClick={() => setSelected(true)} />
62
+ </view>
63
+ );
64
+ }
65
+ ```
66
+
67
+ ### Widget 示例
68
+
69
+ Widget 卡片展示层必须使用 [Widget 模板库](../widget-templates/overview.md)。本文件只展示 Framework 入口形态。
70
+
71
+ ```tsx
72
+ import { useViewData } from '@doubao-dev/framework';
73
+ import { InfoListTemplate, type InfoListTemplateProps } from '@doubao-dev/template';
74
+
75
+ interface ProductWidgetData {
76
+ title: string;
77
+ items: InfoListTemplateProps['items'];
78
+ }
79
+
80
+ export default function ProductWidget() {
81
+ const viewData = useViewData<ProductWidgetData>();
82
+
83
+ return <InfoListTemplate title={viewData.title} items={viewData.items} />;
84
+ }
85
+ ```
86
+
87
+ ---
88
+
89
+ ## App 生命周期
90
+
91
+ `defineApp()` 放在 `src/app.ts`,只负责应用级生命周期。App metadata 写在 `src/app.config.ts`。
92
+
93
+ | 钩子 | 触发时机 | 常见用途 |
94
+ |------|----------|----------|
95
+ | `onLaunch()` | 应用启动 | 初始化全局状态、注册全局监听 |
96
+ | `onPageOpened({ viewId })` | Page 打开 | 记录页面打开事件、调试 viewId |
97
+ | `onForeground()` | 应用进入前台 | 恢复全局订阅、刷新全局状态 |
98
+ | `onBackground()` | 应用进入后台 | 暂停任务、保存状态 |
99
+ | `onDestroy()` | 应用销毁 | 清理全局资源 |
100
+ | `onError(error)` | 生命周期或工具调用报错 | 兜底上报、错误隔离 |
101
+
102
+ ```tsx
103
+ import { defineApp } from '@doubao-dev/framework';
104
+
105
+ export default defineApp({
106
+ onLaunch() {
107
+ console.log('app launched');
108
+ },
109
+ onPageOpened({ viewId }) {
110
+ console.log('page opened', viewId);
111
+ },
112
+ onForeground() {
113
+ console.log('app foreground');
114
+ },
115
+ onBackground() {
116
+ console.log('app background');
117
+ },
118
+ onDestroy() {
119
+ console.log('app destroyed');
120
+ },
121
+ onError(error) {
122
+ console.error(error);
123
+ }
124
+ });
125
+ ```
126
+
127
+ ---
128
+
129
+ ## Page / Widget 生命周期
130
+
131
+ 函数入口使用生命周期 Hooks;对象入口继续支持对应的 `onXxx` 字段:
132
+
133
+ | Hook / 对象字段 | 触发时机 | 常见用途 |
134
+ |------|----------|----------|
135
+ | `useCreated()` / `onCreated()` | View 创建时 | 初始化非渲染数据结构 |
136
+ | `useMounted()` / `onMounted()` | 首次渲染后,只触发一次 | 首次加载、订阅、启动一次性任务 |
137
+ | `useShow()` / `onShow()` | 首次显示或从隐藏恢复 | 刷新可见数据、恢复轮询 |
138
+ | `useHide()` / `onHide()` | View 隐藏 | 暂停任务、保存草稿 |
139
+ | `useDestroy()` / `onDestroy()` | View 销毁 | 清理订阅、计时器和外部资源 |
140
+ | `useError()` / `onError()` | View 生命周期报错 | 错误兜底和上报 |
141
+
142
+ Widget 额外支持:
143
+
144
+ | 钩子 | 触发时机 | 常见用途 |
145
+ |------|----------|----------|
146
+ | `useForeground()` / `onForeground()` | 应用回到前台或设备解锁 | 恢复卡片可见内容 |
147
+ | `useBackground()` / `onBackground()` | 应用进入后台或设备锁屏 | 暂停卡片任务 |
148
+
149
+ 异步端能力不要直接写在组件函数或对象入口的 `render()` 中;首次加载放在 `useEffect` / `useMounted()`,用户动作放在事件处理函数。
150
+
151
+ ---
152
+
153
+ ## Hooks
154
+
155
+ 常用 Hooks 从 `@doubao-dev/framework` 导入:
156
+
157
+ ```tsx
158
+ import {
159
+ createContext,
160
+ useCallback,
161
+ useContext,
162
+ useEffect,
163
+ useMemo,
164
+ useReducer,
165
+ useState
166
+ } from '@doubao-dev/framework';
167
+ ```
168
+
169
+ | Hook | 用途 |
170
+ |------|------|
171
+ | `useState(initial)` | 组件局部状态 |
172
+ | `useEffect(effect, deps)` | 副作用、订阅和清理 |
173
+ | `useReducer(reducer, initial)` | 复杂状态流转 |
174
+ | `useMemo(factory, deps)` | 缓存计算结果 |
175
+ | `useCallback(callback, deps)` | 缓存事件处理函数 |
176
+ | `createContext(defaultValue)` / `useContext(context)` | 跨组件共享状态 |
177
+
178
+ Framework 还提供视图生命周期 Hooks:
179
+
180
+ | Hook | 对应能力 |
181
+ |------|----------|
182
+ | `useCreated(callback)` | View created |
183
+ | `useMounted(callback)` | View mounted |
184
+ | `useShow(callback)` / `useHide(callback)` | View show / hide |
185
+ | `useDestroy(callback)` | View destroy |
186
+ | `useError(callback)` | View error |
187
+ | `useForeground(callback)` / `useBackground(callback)` | 应用前后台变化 |
188
+ | `useViewData<T>()` | 读取并订阅 viewData 更新 |
189
+
190
+ ---
191
+
192
+ ## View Data
193
+
194
+ | API | 用途 |
195
+ |-----|------|
196
+ | `useViewData<T>()` | 读取 Page / Widget 输入数据并响应后续更新,必须传泛型 |
197
+ | `getWidgetInstanceId()` | 在 Widget 中获取当前实例 ID,Page 中通常为空 |
198
+ | `getCurrentPages()` | 获取当前页面栈,返回 `CurrentPage[]`,当前页面信息包含 `route` |
199
+
200
+ ```tsx
201
+ import {
202
+ getCurrentPages,
203
+ getWidgetInstanceId,
204
+ useViewData
205
+ } from '@doubao-dev/framework';
206
+
207
+ interface CardData {
208
+ title: string;
209
+ count?: number;
210
+ }
211
+
212
+ export default function Card() {
213
+ const viewData = useViewData<CardData>();
214
+ const widgetInstanceId = getWidgetInstanceId();
215
+ const pages = getCurrentPages();
216
+
217
+ return (
218
+ <view>
219
+ <text>{viewData.title}</text>
220
+ <text>{pages.at(-1)?.route || ''}</text>
221
+ <text>{widgetInstanceId || ''}</text>
222
+ </view>
223
+ );
224
+ }
225
+ ```
226
+
227
+ 在 Page / Widget 组件内使用 `useViewData<T>()` 读取输入数据;它会在 viewData 更新时触发重新渲染。
228
+
229
+ ---
230
+
231
+ ## App Config
232
+
233
+ 独立应用项目的 `src/app.config.ts` 使用 `defineAppConfig()` 配置 App 元信息和入口 metadata。
234
+
235
+ ```ts
236
+ import { defineAppConfig } from '@doubao-dev/framework/config';
237
+
238
+ export default defineAppConfig({
239
+ appId: 'db_xxxxxx', // 独立应用项目填写;Plugin CLI 项目不填写
240
+ name: '我的豆包应用',
241
+ description: '应用描述',
242
+ keywords: ['doubao', 'demo'],
243
+ pages: [
244
+ 'pages/home/index',
245
+ {
246
+ entry: 'pages/account/demo/index',
247
+ title: '账号示例页'
248
+ }
249
+ ],
250
+ widgets: [
251
+ {
252
+ entry: 'widgets/product-card/index',
253
+ name: '商品卡片',
254
+ description: '展示商品摘要',
255
+ boxType: 'inbox',
256
+ border: true,
257
+ titleType: 'none'
258
+ }
259
+ ]
260
+ });
261
+ ```
262
+
263
+ 配置规则:
264
+
265
+ - 独立应用项目的 `appId` 使用开放平台 `db_xxxxxx` 风格,`name` 必填。
266
+ - `pages` / `widgets` 可不写;不写时会按一级目录自动发现入口。
267
+ - 需要稳定首页顺序、补标题或补 Widget metadata 时,使用数组配置。
268
+ - 字符串数组项只声明入口;对象数组项用 `entry` 声明入口并补 metadata。
269
+ - `entry` 写完整到 `index`,例如 `pages/home/index`、`widgets/product-card/index`。
270
+ - Page 对象常用 `title`;Widget 对象常用 `name`、`description`、`boxType`、`border`、`titleType`。
271
+ - Widget 默认无标题时显式配置 `titleType: 'none'`。
272
+
273
+ 更完整的入口、目录和 metadata 规则见 [开发规则](../rules/dos-and-donts.md)。
274
+
275
+ ---
276
+
277
+ ## 相关文档
278
+
279
+ - [组件开发完整指南](../guides/component-development.md)
280
+ - [开发规则](../rules/dos-and-donts.md)
281
+ - [Page / Widget 基础示例](../examples/page-widget-basics.md)
282
+ - [Widget 模板库](../widget-templates/overview.md)
283
+ - [豆包应用插件的端能力 API 速查](../doubao-agentic-service-api/quick-reference.md)
@@ -0,0 +1,277 @@
1
+ # 登录与授权约定入口
2
+
3
+ `src/auth` 用于登录、隐私授权和自定义登录页。没有 `src/auth` 入口时,旧目录 `src/mcp-ui` 继续作为兼容 fallback。该目录由构建工具按约定扫描,不需要写到 `src/app.config.ts` 的 `pages` / `widgets` 中。
4
+
5
+ ---
6
+
7
+ ## 入口类型
8
+
9
+ | 入口 | 文件路径 | 是否手写 | 用途 |
10
+ |------|----------|----------|------|
11
+ | 隐私协议回调 | `src/auth/privacy.ts` | 按需手写 | 处理同意或拒绝隐私协议 |
12
+ | 登录回调 | `src/auth/login.ts` | 按需手写 | 处理手机号授权登录或拒绝登录 |
13
+ | 登录页面 | `src/auth/login-page/index.tsx` | 按需手写 | 展示自定义登录页 |
14
+ | 登录/隐私卡片 | `mcp-privacy-login-card` | 框架生成 | 根据 `privacy.ts` / `login.ts` 自动挂接回调 |
15
+
16
+ `mcp-privacy-login-card` 是内置虚拟 Widget,构建工具会自动注入固定 metadata。不要在 `src/widgets` 中创建同名目录,
17
+ 也不要手动注册到 `app.config.ts`。
18
+
19
+ ---
20
+
21
+ ## privacy.ts
22
+
23
+ `privacy.ts` 只导出隐私协议回调,不定义 UI。
24
+
25
+ ```ts
26
+ import { definePrivacyApi, type PrivacyAgreeResult } from '@doubao-dev/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-dev/framework';
57
+ import { login, postLoginResult, request, setStorage } from '@doubao-dev/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
+ ## login-page
131
+
132
+ 自定义登录页放在 `src/auth/login-page/index.tsx`,直接默认导出组件函数。
133
+ 旧路径 `src/auth/mcp-login-page/index.tsx` 继续兼容;没有 `src/auth` 入口时,也会继续兼容 `src/mcp-ui/mcp-login-page/index.tsx`。
134
+ 构建工具会把它作为特殊 Page 处理,metadata 仍使用内置 `id: 'mcp-login-page'` 和 `path: '/pages/mcp-login-page/index'`。
135
+ 已有 `defineLoginPage()` 对象入口无需迁移;新登录页不要使用其他目录名。
136
+
137
+ ```tsx
138
+ import {
139
+ useViewData,
140
+ type LoginResult,
141
+ useDestroy,
142
+ useEffect,
143
+ useState
144
+ } from '@doubao-dev/framework';
145
+ import {
146
+ login,
147
+ navigateBack,
148
+ postLoginResult,
149
+ request,
150
+ setStorage,
151
+ showToast
152
+ } from '@doubao-dev/framework/api';
153
+ import './index.scss';
154
+
155
+ interface McpLoginPageData {
156
+ title?: string;
157
+ description?: string;
158
+ }
159
+
160
+ interface AuthResponse {
161
+ loginCode: string;
162
+ sessionKey: string;
163
+ }
164
+
165
+ function isAuthResponse(data: unknown): data is AuthResponse {
166
+ return (
167
+ typeof data === 'object' &&
168
+ data !== null &&
169
+ 'loginCode' in data &&
170
+ 'sessionKey' in data &&
171
+ typeof data.loginCode === 'string' &&
172
+ typeof data.sessionKey === 'string'
173
+ );
174
+ }
175
+
176
+ let loginResultPosted = false;
177
+
178
+ async function postLoginFailure() {
179
+ if (loginResultPosted) {
180
+ return;
181
+ }
182
+
183
+ await postLoginResult({ result: false });
184
+ loginResultPosted = true;
185
+ }
186
+
187
+ async function exchangeLoginCode(params: { appId: string; loginCode: string; phoneCode: string }) {
188
+ const response = await request({
189
+ url: 'https://example.com/api/doubao-login',
190
+ method: 'POST',
191
+ header: {
192
+ 'content-type': 'application/json'
193
+ },
194
+ data: params
195
+ });
196
+
197
+ if (response.statusCode < 200 || response.statusCode >= 300 || !isAuthResponse(response.data)) {
198
+ throw new Error('登录响应无效');
199
+ }
200
+
201
+ return response.data;
202
+ }
203
+
204
+ export default function McpLoginPage() {
205
+ const viewData = useViewData<McpLoginPageData>();
206
+ const [submitting, setSubmitting] = useState(false);
207
+
208
+ useEffect(() => {
209
+ loginResultPosted = false;
210
+ }, []);
211
+
212
+ useDestroy(() => {
213
+ void postLoginFailure();
214
+ });
215
+
216
+ const handlePhoneLogin = async (res?: LoginResult) => {
217
+ if (submitting) {
218
+ return;
219
+ }
220
+
221
+ if (!res?.code) {
222
+ showToast({ message: res?.message || '未完成手机号授权', type: 'warning' });
223
+ return;
224
+ }
225
+
226
+ try {
227
+ setSubmitting(true);
228
+ const loginResult = await login({ timeout: 30000 });
229
+ const auth = await exchangeLoginCode({
230
+ appId: 'your-app-id',
231
+ loginCode: loginResult.code,
232
+ phoneCode: res.code
233
+ });
234
+
235
+ await setStorage({
236
+ key: 'session_key',
237
+ data: auth.sessionKey
238
+ });
239
+
240
+ await postLoginResult({
241
+ result: true,
242
+ code: auth.loginCode
243
+ });
244
+ loginResultPosted = true;
245
+ await navigateBack();
246
+ } catch (error) {
247
+ console.error('mcp login failed', error);
248
+ showToast({ message: '登录失败,请稍后重试', type: 'error' });
249
+ } finally {
250
+ setSubmitting(false);
251
+ }
252
+ };
253
+
254
+ const handleCancel = async () => {
255
+ await postLoginFailure();
256
+ await navigateBack();
257
+ };
258
+
259
+ return (
260
+ <view className="mcp-login-page">
261
+ <text className="mcp-login-page__title">{viewData.title || '登录'}</text>
262
+ <text className="mcp-login-page__description">{viewData.description || '请完成登录后继续使用'}</text>
263
+ <button
264
+ text={submitting ? '登录中...' : '手机号一键登录'}
265
+ type="primary"
266
+ openType="getPhoneNumber"
267
+ loading={submitting}
268
+ disabled={submitting}
269
+ onGetPhoneNumber={(res) => {
270
+ void handlePhoneLogin(res);
271
+ }}
272
+ />
273
+ <button text="暂不登录" disabled={submitting} onClick={handleCancel} />
274
+ </view>
275
+ );
276
+ }
277
+ ```