Files
zn-ai/docs/ChannelRefactorPlan.md
DEV_DSW 79bea4f107 Refactor UUID generation, remove unused logger and encryption utilities, and clean up request handling
- Updated `generateUUID` function for improved readability and performance.
- Deleted `logger.ts`, `other.ts`, `request.ts`, `storage.ts`, `tansParams.ts`, and `validate.ts` as they were no longer needed.
- Simplified TypeScript configuration by removing unnecessary paths and aliases.
- Enhanced Vite configuration for better project structure and maintainability.
2026-04-17 15:38:08 +08:00

239 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 渠道硬编码移除与动态关联重构计划
## 上下文与目标
当前 `zn-ai` 的"一键打开各渠道"功能存在以下问题:
1. **渠道数据硬编码**在 `src/constant/channel.ts` 中,仅包含 fliggy/meituan/douyin 三个固定渠道;
2. **`AddChannelDialog.vue` 是纯 UI 空壳**,收集表单后不做任何持久化或事件抛出;
3. **`TaskCenter.vue` 存在 bug**:将普通数组 `channels` 当作 ref 使用(`channels.value`),实际传递 `undefined` 给主进程;
4. **脚本管理功能**中,每个 `AutomationScript` 已有 `channel` 字段(存储渠道名称或 URL但未被"一键打开"功能复用。
**目标**
- 移除 `channels.ts` 中的硬编码主数据源,改为从脚本管理中动态聚合可用渠道;
- 重塑 `AddChannelDialog` 的交互:快捷搜索选择渠道 → 展示已选列表 → 保存;
- 修复 `TaskCenter.vue` 的渠道传递逻辑,使用用户动态配置的列表;
- 确保 `open_all_channel.js` 接收的数据格式正确,按需同步调整。
---
## 关键文件清单
| 路径 | 角色 | 改动类型 |
|------|------|----------|
| `zn-ai/src/constant/channel.ts` | 硬编码渠道常量 | 移除数组,保留字典+解析工具 |
| `zn-ai/src/stores/channel.ts` | **新建**渠道状态 store | 新增 |
| `zn-ai/src/stores/script.ts` | 脚本 Pinia store | 只读依赖(聚合 channel |
| `zn-ai/src/pages/home/components/AddChannelDialog.vue` | 渠道关联弹窗 | **重写**模板与逻辑 |
| `zn-ai/src/pages/home/TaskCenter.vue` | 任务中心卡片 | 修复+替换数据源 |
| `zn-ai/src/pages/home/index.vue` | 首页容器 | 可能需补充 store 初始化 |
| `zn-ai/src/pages/scripts/components/ScriptEditorDialog.vue` | 脚本编辑弹窗 | 替换 `channels` 引用为字典 |
| `zn-ai/electron/scripts/open_all_channel.js` | 打开渠道的 Playwright 脚本 | 按需调整字段读取 |
| `zn-ai/electron/process/runTaskOperationService.ts` | IPC 处理OPEN_CHANNEL | 按需做数据格式适配 |
---
## 方案设计
### 1. 数据层:`constant/channel.ts` + 新建 `stores/channel.ts`
#### `constant/channel.ts` 改造
- **移除** `export const channels: Item[]` 硬编码数组。
- **保留** `Item` 接口(`{id, channelName, channelUrl}`)。
- **新增** `channelDictionary: Record<string, string>`,仅作为"已知渠道名称→URL"的参考字典(从原数组迁移)。
- **新增** `resolveChannel(value: string): { name?: string; url: string }`
-`value` 匹配 `channelDictionary` 的 key返回 `{name: value, url: dictionary[value]}`
-`value` 本身像 URL以 http 开头),尝试从字典反向查找 name找不到则 name 取 hostname 或原值;
- 其他情况返回 `{name: value, url: value}`
#### 新建 `stores/channel.ts`Pinia
使用 Composition API 风格,与 `script.ts`/`cron.ts` 保持一致。
```ts
export interface ChannelItem {
id: string;
channelName: string;
channelUrl: string;
}
const STORAGE_KEY = 'zn-ai:selected-channels';
export const useChannelStore = defineStore('channel', () => {
const scriptStore = useScriptStore();
// 用户选中的"一键打开"渠道(持久化到 localStorage
const selectedChannels = ref<ChannelItem[]>([]);
// 从脚本 store 动态聚合可用渠道(去重,按 URL 去重)
const availableChannels = computed<ChannelItem[]>(() => {
const map = new Map<string, ChannelItem>();
for (const script of scriptStore.safeScripts) {
if (!script.channel) continue;
const resolved = resolveChannel(script.channel);
const url = resolved.url;
if (!map.has(url)) {
map.set(url, {
id: `channel-${url}`,
channelName: resolved.name || url,
channelUrl: url,
});
}
}
return Array.from(map.values());
});
const loadSelectedChannels = () => { ... };
const saveSelectedChannels = () => { ... };
const addSelectedChannel = (item: ChannelItem) => { ... };
const removeSelectedChannel = (id: string) => { ... };
const setSelectedChannels = (items: ChannelItem[]) => { ... };
return {
selectedChannels,
availableChannels,
loadSelectedChannels,
saveSelectedChannels,
addSelectedChannel,
removeSelectedChannel,
setSelectedChannels,
};
});
```
**注意**`AddChannelDialog` 打开时应基于 `availableChannels` 做搜索,确认保存时调用 `setSelectedChannels` + `saveSelectedChannels`
---
### 2. UI 层:`AddChannelDialog.vue` 重塑
当前弹窗是两个空白输入框(名称+URL需要改为**搜索选择 + 已选列表**。
#### 新交互流程
1. **顶部搜索区**`el-autocomplete`(或 `el-input` + 自定义下拉列表)绑定 `availableChannels` 过滤结果;
2. **选择后**:将渠道添加到下方的"已选渠道"列表(去重检查);
3. **已选列表**:使用卡片/表格形式展示,每行包含:
- 渠道名称
- 渠道 URL截断显示tooltip 展示完整)
- 删除按钮(`el-icon Delete`
4. **底部操作**:取消 / 确认。确认时持久化到 store。
#### 状态隔离
弹窗打开时,先 `loadSelectedChannels()` 到本地临时数组;编辑过程中操作临时数组;确认时写入 store 并持久化;取消时直接关闭,不污染 store。
#### 样式适配
复用项目已有的 `custom-script-dialog` 圆角/深色模式变量,保持视觉一致性。
---
### 3. 修复 `TaskCenter.vue`
- **移除** `import { channels } from '@constant/channel'`
- **注入** `useChannelStore`
- 点击"一键打开各渠道"时:
```ts
const channelStore = useChannelStore();
window.api.openChannel(channelStore.selectedChannels)
```
- 如果 `selectedChannels` 为空,可给出提示(`ElMessage.warning`)或仍然允许空数组由主进程处理。
---
### 4. `ScriptEditorDialog.vue` 适配
- 将 `import { channels } from '@constant/channel'` 改为 `import { channelDictionary, resolveChannel } from '@constant/channel'`。
- `getChannelUrl(channel)` 改为在 `channelDictionary` 中查找。
- channel URL 替换 watcher 逻辑同步使用 `channelDictionary` 的 values。
---
### 5. `open_all_channel.js` 与主进程适配
当前 `open_all_channel.js` 读取环境变量 `CHANNELS`,期望元素结构为 `{ channelUrl: string }`(第 81 行 `channels[i]?.channelUrl`)。
`channelStore.selectedChannels` 的结构保持为 `Item``{ id, channelName, channelUrl }`),因此**主进程接收到的数组本身已包含 `channelUrl` 字段**。
需要确认 `runTaskOperationService.ts` 中 `IPC_EVENTS.OPEN_CHANNEL` handler
```ts
ipcMain.handle(IPC_EVENTS.OPEN_CHANNEL, async (_event, channels: any) => { ... })
```
它直接将 `channels` 传给 `executeScriptServiceInstance.executeScript(scriptPath, { channels })`,后者 JSON.stringify 后写入 `CHANNELS` 环境变量。
**结论**:只要 `TaskCenter.vue` 传递的数组元素包含 `channelUrl``open_all_channel.js` **无需修改**即可正常工作。
但用户要求"同步更新 open_all_channel.js",为确保严谨,计划内包含**兼容性检查**:若发现字段名不一致,统一调整为 `channelUrl`;若需要增强(如按名称去重、空值过滤),则在 `runTaskOperationService.ts` 的 handler 中做适配,保持 `open_all_channel.js` 的输入契约稳定。
---
## Sub-agent 分工(共 3 个)
### Agent A — 数据层与字典迁移
**负责范围**
- 改造 `zn-ai/src/constant/channel.ts`(移除数组,保留字典+`resolveChannel`)。
- 新建 `zn-ai/src/stores/channel.ts`Pinia store含 `availableChannels` / `selectedChannels` / localStorage 持久化)。
- 修改 `ScriptEditorDialog.vue` 中的引用,从原 `channels` 数组切换到 `channelDictionary`。
**输入依赖**:已确认的 `script.ts` 结构、`channel.ts` 原数组。
**输出产物**:可运行的数据层代码。
---
### Agent B — AddChannelDialog UI 重构
**负责范围**
- 重写 `zn-ai/src/pages/home/components/AddChannelDialog.vue`。
- 实现搜索选择(`el-autocomplete` 或自定义搜索+下拉)与已选列表展示。
- 对接 `useChannelStore`:读取 `availableChannels`,保存时写入 `selectedChannels`。
- 保持与项目一致的 dark/light 样式。
**输入依赖**Agent A 完成的 `stores/channel.ts`。
**工作方式**:可以并行启动,但代码编辑需等待 Agent A 的 store 文件就位(或先在本地 mock 接口)。
---
### Agent C — TaskCenter 修复与端到端验证
**负责范围**
- 修改 `zn-ai/src/pages/home/TaskCenter.vue`:移除硬编码导入,注入 `useChannelStore`,修复 `.value` bug。
- 检查 `zn-ai/electron/process/runTaskOperationService.ts` 与 `open_all_channel.js` 的数据格式兼容性,必要时做适配。
- 在 `zn-ai/src/pages/home/index.vue` 中添加 `channelStore.loadSelectedChannels()` 初始化onMounted 或合适位置)。
- 运行项目(`pnpm run dev` 或等效命令),验证:
1. 脚本管理中有渠道数据时,`AddChannelDialog` 能搜索到;
2. 选择并保存后,`TaskCenter` 能正确传递数组;
3. `open_all_channel.js` 能正常解析并打开页面。
**输入依赖**Agent A 与 Agent B 的产物。
**工作方式**:必须在 A、B 完成后启动。
---
## 执行顺序
```
Agent A (数据层) ─┬─► Agent B (UI 弹窗)
└─► Agent C (流程修复与验证)
```
- **A 与 B 可并行或准并行**B 可以在 A 提交 store 初版后立即开始对接。
- **C 必须在 A、B 都完成后启动**,负责联调与验证。
---
## 风险与回退策略
| 风险 | 缓解措施 |
|------|----------|
| `availableChannels` 为空(脚本管理无渠道数据) | `AddChannelDialog` 搜索下拉显示"暂无可用渠道",已选列表允许空状态;`TaskCenter` 点击时给出提示 |
| 脚本 `channel` 字段格式混乱(既有 URL 又有名称) | `resolveChannel()` 统一做归一化处理,以 URL 为唯一键 |
| `open_all_channel.js` 字段契约被打破 | 在 `runTaskOperationService.ts` 的 handler 中做字段映射/过滤,保证脚本侧输入不变 |
| localStorage 中旧数据格式不兼容 | `loadSelectedChannels` 读取时做 schema 校验,不兼容则清空并回退到空数组 |
---
## 验证清单Agent C 负责)
- [ ] `channels.ts` 中不再导出硬编码数组。
- [ ] `AddChannelDialog` 打开后,能从脚本中搜索出渠道(如 fliggy / meituan / douyin
- [ ] 选择多个渠道后列表正确显示,删除单个后状态正确。
- [ ] 点击"确认"关闭弹窗,再次打开能恢复上次保存的选项。
- [ ] `TaskCenter` 中点击"一键打开各渠道"`openChannel` 参数为正确的 `ChannelItem[]`(非 undefined
- [ ] `open_all_channel.js` 成功解析 `CHANNELS` 并依次打开对应页面。
- [ ] 脚本编辑弹窗中 `getChannelUrl` 仍能正确将 `fliggy` 解析为对应 URL。