返回 AiToEarn
README.md
1 # PublishDialog 组件架构文档
2
3 ## 概述
4
5 PublishDialog 是发布作品的核心弹框组件,支持多平台、多账号同时发布内容。
6
7 ## ⚠️ 重要提醒:双端架构
8
9 **本组件采用 PC 端和移动端分离的架构,修改或新增功能时必须同时考虑两端!**
10
11 | 端 | 入口文件 | 说明 |
12 | ------ | ------------------------------------------- | ----------------------------------- |
13 | PC 端 | `index.tsx` + `DesktopPublishContent` | 全屏工作台,左内容管理 + 右发布编辑 |
14 | 移动端 | `compoents/mobile/MobilePublishContent.tsx` | 精简版发布编辑,无内容管理左栏 |
15
16 ### 判断逻辑(index.tsx)
17
18 ```tsx
19 const isMobile = useIsMobile()
20
21 if (isMobile) {
22 return <MobilePublishContent ... /> // 移动端渲染
23 }
24
25 // PC 端渲染
26 return <DesktopPublishContent ... />
27 ```
28
29 ## 目录结构
30
31 ```
32 PublishDialog/
33 ├── index.tsx # 主入口(状态管理 + 双端判断)
34 ├── README.md # 本文档
35 ├── publishDialog.type.ts # 类型定义
36 ├── PublishDialog.util.ts # 工具函数(含拖拽素材转发布参数)
37 ├── usePublishDialog.ts # 核心状态管理 store
38 ├── usePublishDialogData.ts # 数据处理 hooks
39 ├── usePublishDialogStorageStore.tsx # 持久化存储 store(发布数据 + PC 双栏宽度)
40
41 ├── hooks/ # 业务逻辑 hooks
42 │ ├── usePublishState.ts # 弹窗状态管理(loading、modal显示状态)
43 │ ├── usePlatformAuth.ts # 平台授权跳转逻辑
44 │ ├── usePublishActions.ts # 发布操作核心逻辑
45 │ ├── useUploadSync.ts # 上传结果同步到发布参数
46 │ └── usePubParamsVerify.tsx # 参数校验 hook(双端共用)
47
48 ├── compoents/
49 │ ├── mobile/
50 │ │ └── MobilePublishContent.tsx # 【移动端】完整内容组件
51 │ │
52 │ ├── DesktopPublishContent/ # 【PC端】主内容组件
53 │ │ ├── index.tsx
54 │ │ ├── PublishDialogDraftPanel.tsx # 【PC端】内容管理左栏
55 │ │ └── useDesktopPublishLayout.ts # 【PC端】双栏拖拽与宽度计算
56 │ ├── AccountSelector/ # 账户选择器组件
57 │ │ └── index.tsx
58 │ ├── PublishFooter/ # 底部操作栏(发布按钮等)
59 │ │ └── index.tsx
60 │ ├── PublishDialogSkeleton/ # 发布弹窗骨架屏(双端共用)
61 │ │ └── index.tsx
62 │ ├── PublishModals/ # 弹窗组件集合(Facebook页面等)
63 │ │ └── index.tsx
64 │ │
65 │ ├── ErrorSummary/ # 错误汇总组件(双端共用)
66 │ ├── PlatParamsSetting/ # 平台参数设置(双端共用)
67 │ │ └── plats/TwitterParams/ # Twitter 发布参数(基础设置、投票、媒体增强)
68 │ ├── PublishDatePicker/ # 发布时间选择器(双端共用)
69 │ ├── PubParmasTextarea/ # 发布内容编辑器(双端共用)
70 │ │
71 │ ├── Choose/ # 选择器组件
72 │ ├── DouyinQRCodeModal.tsx # 抖音二维码弹窗
73 │ ├── DraftSelectionModal/ # 草稿选择弹窗
74 │ ├── MaterialSelectionModal/ # 素材选择弹窗
75 │ └── PublishManageUpload/ # 上传管理
76
77 └── svgs/ # SVG 图标资源
78 ```
79
80 ## Hooks 职责说明
81
82 | Hook | 职责 |
83 | -------------------- | -------------------------------------------- |
84 | `usePublishState` | 管理弹窗内各种临时状态(loading、modal显隐) |
85 | `usePlatformAuth` | 处理离线账户点击时的平台授权跳转 |
86 | `usePublishActions` | 发布操作核心逻辑,包含 API 发布和插件发布 |
87 | `useUploadSync` | 监听上传完成,同步 ossUrl 到发布参数 |
88 | `usePubParamsVerify` | 校验发布参数,返回错误和警告信息 |
89
90 ## Twitter 发布参数
91
92 Twitter 参数组件位于 `compoents/PlatParamsSetting/plats/TwitterParams/`,PC 与移动端共用同一套模块:
93
94 - `TwitterBaseSection`:回复权限、AI 内容标记
95 - `TwitterPollSection`:投票选项、持续时间、投票回复权限
96 - `TwitterMediaSection`:图片标记用户、图片/视频替代文本
97 - `validation.ts`:Twitter 专属发布校验,由 `usePubParamsVerify` 调用
98
99 投票与媒体互斥;图片标记用户仅在图片帖展示;替代文本按当前媒体顺序写入 `mediaMetadata`。
100
101 ## 双端功能对照表
102
103 | 功能 | PC 端 | 移动端 | 共用组件 |
104 | ------------ | ----- | ------ | -------------------- |
105 | 账号选择 | ✅ | ✅ | `AccountSelector` |
106 | 错误汇总展示 | ✅ | ✅ | `ErrorSummary` |
107 | 平台参数设置 | ✅ | ✅ | `PlatParamsSetting` |
108 | 内容编辑器 | ✅ | ✅ | `PubParmasTextarea` |
109 | 发布时间选择 | ✅ | ✅ | `PublishDatePicker` |
110 | 内容管理左栏 | ✅ | ❌ | `DraftContentModule` |
111
112 ## PC 端工作台布局
113
114 PC 端使用全屏弹框,外层尺寸为 `100vw` × `100vh`,不保留外边距。
115
116 ```text
117 ┌──────────────────────────────────────────────────────────────────┐
118 │ 内容管理左栏(草稿箱模块) │ 拖拽条 │ 发布编辑右栏 │
119 │ 可拖动宽度,持久化存储 │ │ 多平台发布编辑 │
120 └──────────────────────────────────────────────────────────────────┘
121 ```
122
123 - 左栏复用 `src/app/[lng]/draft-box/components/DraftContentModule`,并以 `embedded` 模式禁用内部 `PublishDialog`,避免弹框嵌套。
124 - 双栏宽度由 `DesktopPublishContent/useDesktopPublishLayout.ts` 计算,使用 `ResizeObserver` 根据容器宽度动态归一化。
125 - 用户拖拽后的 `draftPanelWidth` 和 `publishPanelWidth` 持久化到 `usePublishDialogStorageStore.tsx` 的 `desktopLayout`。
126
127 ## 外部触发发布流程
128
129 ### 方式一:URL 参数触发(推荐)
130
131 通过跳转到 `/accounts` 页面并携带特定参数,可以自动打开发布弹框并预填内容。
132
133 **必需参数:**
134
135 | 参数 | 类型 | 说明 |
136 | ------------- | -------- | ---------------------------------------------- |
137 | `aiGenerated` | `'true'` | **必需**,标识为 AI 生成内容,触发自动发布流程 |
138
139 **可选参数:**
140
141 | 参数 | 类型 | 说明 |
142 | ------------- | ---------- | ------------------------------------- |
143 | `description` | `string` | 发布内容描述(需 URL 编码) |
144 | `title` | `string` | 发布标题(需 URL 编码) |
145 | `tags` | `string` | 标签数组的 JSON 字符串(需 URL 编码) |
146 | `medias` | `string` | 媒体数组的 JSON 字符串(需 URL 编码) |
147 | `accountId` | `string` | 指定发布的账号 ID |
148 | `platform` | `PlatType` | 指定发布平台类型 |
149 | `taskId` | `string` | 关联的任务 ID |
150
151 **使用示例:**
152
153 ```tsx
154 // 从分享模块跳转到发布
155 const params = new URLSearchParams()
156 params.set('aiGenerated', 'true')
157 params.set('description', encodeURIComponent('分享内容描述'))
158 params.set('title', encodeURIComponent('标题'))
159 params.set('tags', encodeURIComponent(JSON.stringify(['tag1', 'tag2'])))
160 params.set('accountId', 'account-123')
161
162 router.push(`/accounts?${params.toString()}`)
163 ```
164
165 **处理逻辑位置:** `src/app/[lng]/accounts/accountCore.tsx` 第 250-330 行
166
167 ### 方式二:直接调用组件
168
169 ```tsx
170 import PublishDialog from '@/components/PublishDialog'
171 ;<PublishDialog
172 open={isOpen}
173 onClose={() => setIsOpen(false)}
174 accounts={accountList}
175 defaultAccountIds={['account-1', 'account-2']}
176 accountListInitialLoading={accountLoading && !accountListInitialized}
177 autoPublishOnReady={false}
178 onPubSuccess={() => console.log('发布成功')}
179 />
180 ```
181
182 `accountListInitialLoading` 仅用于账号列表首轮加载遮罩,后续刷新账号列表不应传入 loading。
183
184 ### 方式三:通过 Store 预设数据
185
186 ```tsx
187 import { usePublishDialogStorageStore } from '@/components/PublishDialog/usePublishDialogStorageStore'
188
189 // 预设发布数据
190 usePublishDialogStorageStore.getState().setPubData({
191 title: '标题',
192 description: '描述内容',
193 tags: ['tag1', 'tag2'],
194 medias: [{ url: '...', type: 'image' }],
195 })
196
197 // 然后打开发布弹框,数据会自动填充
198 ```
199
200 ## 开发规范
201
202 ### 1. 新增功能必须检查双端
203
204 新增任何展示组件或功能时,**必须**检查:
205
206 - [ ] PC 端是否需要该功能?→ 修改 `DesktopPublishContent`
207 - [ ] 移动端是否需要该功能?→ 修改 `MobilePublishContent.tsx`
208 - [ ] 是否可以抽取为共用组件?
209
210 ### 2. 共用组件设计原则
211
212 共用组件应支持 `isMobile` prop 进行适配:
213
214 ```tsx
215 interface Props {
216 isMobile?: boolean // 移动端标识
217 }
218
219 const MyComponent = ({ isMobile }: Props) => {
220 return <div className={isMobile ? 'mobile-style' : 'pc-style'}>...</div>
221 }
222 ```
223
224 ### 3. 状态管理
225
226 双端共用同一个 store(`usePublishDialog`),确保状态同步。
227
228 ### 4. 业务逻辑抽离
229
230 将复杂的业务逻辑抽离到 `hooks/` 目录下的独立 hook 中:
231
232 ```tsx
233 // ❌ 错误:在组件中直接写大量业务逻辑
234 const Component = () => {
235 // 100+ 行的业务逻辑...
236 }
237
238 // ✅ 正确:抽离到独立 hook
239 const Component = () => {
240 const { handlePublish, loading } = usePublishActions(...)
241 }
242 ```
243
244 ### 5. 典型错误案例
245
246 **错误示例**:只在 PC 端添加 `ErrorSummary`,忘记在移动端添加。
247
248 ```tsx
249 // ❌ 错误:只在 DesktopPublishContent 中添加
250 <ErrorSummary ... />
251
252 // ✅ 正确:同时在 MobilePublishContent.tsx 中添加
253 <ErrorSummary ... />
254 ```
255
256 ## 核心数据流
257
258 ```
259 ┌─────────────────────────────────────────────────────────────┐
260 │ usePublishDialog (store) │
261 │ ├── pubList - 所有可发布账号列表 │
262 │ ├── pubListChoosed - 已选中的账号列表 │
263 │ ├── step - 当前步骤 (0: 统一编辑, 1: 单独编辑) │
264 │ ├── expandedPubItem - 当前展开编辑的账号 │
265 │ ├── commonPubParams - 统一参数(多账号时) │
266 │ └── pubTime - 发布时间 │
267 └─────────────────────────────────────────────────────────────┘
268
269 ┌───────────────┴───────────────┐
270 ▼ ▼
271 ┌──────────────────────┐ ┌──────────────────────┐
272 │ PC 端 │ │ 移动端 │
273 │ DesktopPublishContent│ │ MobilePublishContent │
274 └──────────────────────┘ └──────────────────────┘
275 ```
276
277 ## 外部触发流程图
278
279 ```
280 ┌─────────────────────────────────────────────────────────────────┐
281 │ 外部调用方 │
282 │ (ShareModal / Agent结果 / 任务页面 / 素材库) │
283 └─────────────────────────────────────────────────────────────────┘
284
285 │ router.push('/accounts?aiGenerated=true&...')
286
287 ┌─────────────────────────────────────────────────────────────────┐
288 │ accountCore.tsx │
289 │ 1. 检测 aiGenerated=true 参数 │
290 │ 2. 解析 description/title/tags/medias 等参数 │
291 │ 3. 调用 usePublishDialogStorageStore.setPubData() │
292 │ 4. 选择目标账号(accountId 或 platform 匹配) │
293 │ 5. 设置 aiGeneratedData 状态 │
294 │ 6. 清除 URL 参数 │
295 └─────────────────────────────────────────────────────────────────┘
296
297 │ aiGeneratedData 变化触发
298
299 ┌─────────────────────────────────────────────────────────────────┐
300 │ PublishDialog │
301 │ 1. 检测到 aiGeneratedData │
302 │ 2. 从 storage store 恢复数据 │
303 │ 3. 自动打开弹框并填充内容 │
304 └─────────────────────────────────────────────────────────────────┘
305 ```
306
307 ## 参数校验流程
308
309 ```
310 usePubParamsVerify(pubListChoosed)
311
312
313 ┌─────────────────┐
314 │ errParamsMap │ ← 错误信息 Map<accountId, error>
315 │ warningParamsMap│ ← 警告信息 Map<accountId, warning>
316 └─────────────────┘
317
318
319 ┌─────────────────┐
320 │ ErrorSummary │ ← 统一展示错误/警告(双端)
321 └─────────────────┘
322 ```
323
324 ## 文件职责说明
325
326 | 文件 | 职责 |
327 | ---------------------------------- | --------------------------------------------------- |
328 | `index.tsx` | 主入口,整合所有 hooks 和状态,根据设备类型分发渲染 |
329 | `usePublishDialog.ts` | 核心状态管理,包含所有发布相关状态和方法 |
330 | `usePublishDialogStorageStore.tsx` | 发布数据的持久化存储(IndexedDB) |
331 | `publishDialog.type.ts` | 类型定义,包括 PubItem、发布参数等 |
332 | `hooks/usePublishActions.ts` | 发布操作核心逻辑(API发布 + 插件发布) |
333 | `compoents/DesktopPublishContent/` | PC 端双栏工作台渲染组件 |
334 | `compoents/AccountSelector/` | 账户选择器 UI 组件 |
335 | `compoents/PublishFooter/` | 底部操作栏 UI 组件 |
336 | `compoents/PublishModals/` | 各种弹窗组件集合 |
337
338 ## 更新记录
339
340 - 2026-05-22:PC 端支持从内容管理左栏拖拽草稿/视频/图片到发布编辑区,并复用转换工具填充发布参数
341 - 2026-01-06:重构组件架构,拆分业务逻辑到独立 hooks,主文件从 1500 行精简至 543 行
342 - 2026-01-06:新增 `hooks/` 目录,包含 6 个业务逻辑 hooks
343 - 2026-01-06:新增 `DesktopPublishContent`、`AccountSelector`、`PublishFooter`、`PublishModals` 组件
344 - 2026-01-06:添加外部触发发布流程文档
345 - 2026-01-06:添加 `ErrorSummary` 组件到移动端
346 - 2026-01-06:创建架构文档
347
347 lines MARKDOWN