# Vditor Unification Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 将所有 Markdown 可编辑输入场景统一到 `Vditor`,并满足 PC 端完整编辑体验与移动端编辑优先体验。 **Architecture:** 保留 `PostBodyMarkdownEditor` 作为唯一入口组件,对外接口继续使用 `v-model`,内部替换为 `Vditor` 实例并集中处理生命周期、上传回调与端侧差异化配置。页面层尽量不改动业务逻辑,只做必要的样式/交互对齐;通过组件级测试与关键路径手测保障保存、回填、上传与移动端可用性。 **Tech Stack:** Nuxt 4、Vue 3、TypeScript、Vditor、Bun --- ## File Structure **Create** - `app/components/PostBodyMarkdownEditor.vditor.test.ts`:编辑器组件行为测试(同步、上传、移动端配置切换) **Modify** - `package.json`:新增 `vditor` 依赖,移除 `md-editor-v3`(如无其他引用) - `app/components/PostBodyMarkdownEditor.vue`:内部从 `md-editor-v3` 替换为 `Vditor` - `app/pages/me/posts/new.vue`:仅在必要时调整容器样式(若编辑器高度或移动端布局需要) - `app/pages/me/posts/[id].vue`:仅在必要时调整容器样式(与新建页一致) **Test** - `app/components/PostBodyMarkdownEditor.vditor.test.ts` - 手工验证页面:`app/pages/me/posts/new.vue`、`app/pages/me/posts/[id].vue` --- ### Task 1: 依赖切换与基线校验 **Files:** - Modify: `package.json` - [ ] **Step 1: 写失败检查(确认旧编辑器仍被引用)** ```bash rg "md-editor-v3" app package.json ``` Expected: 至少包含 `PostBodyMarkdownEditor.vue` 与 `package.json` 命中,表示替换前基线成立。 - [ ] **Step 2: 运行检查确认基线** Run: `rg "md-editor-v3" app package.json` Expected: 有匹配结果(旧实现仍在) - [ ] **Step 3: 更新依赖为 Vditor** ```json { "dependencies": { "vditor": "^3.10.0" } } ``` 并移除: ```json { "dependencies": { "md-editor-v3": "..." } } ``` - [ ] **Step 4: 安装依赖并校验 lockfile 变更** Run: `bun install` Expected: `vditor` 安装成功,依赖树无冲突错误 - [ ] **Step 5: 提交** ```bash git add package.json bun.lock git commit -m "chore(markdown): replace md-editor-v3 dependency with vditor" ``` --- ### Task 2: 重写 PostBodyMarkdownEditor 组件为 Vditor 实现 **Files:** - Modify: `app/components/PostBodyMarkdownEditor.vue` - Test: `app/components/PostBodyMarkdownEditor.vditor.test.ts` - [ ] **Step 1: 写失败测试(v-model 双向同步)** ```ts import { describe, test, expect } from "bun:test"; describe("PostBodyMarkdownEditor v-model sync", () => { test("emits update when editor content changes", async () => { // mount component with modelValue = "a" // simulate editor input -> "b" // expect update:modelValue emitted with "b" expect(true).toBe(false); }); }); ``` - [ ] **Step 2: 运行测试确认失败** Run: `bun test app/components/PostBodyMarkdownEditor.vditor.test.ts` Expected: FAIL(测试占位断言失败或组件尚未适配 Vditor) - [ ] **Step 3: 写最小实现(客户端初始化 + 销毁 + 双向同步)** ```ts import Vditor from "vditor"; import "vditor/dist/index.css"; const editor = shallowRef(null); const hostEl = ref(null); onMounted(() => { if (!hostEl.value) return; editor.value = new Vditor(hostEl.value, { value: props.modelValue, input(value) { emit("update:modelValue", value); }, }); }); watch( () => props.modelValue, (next) => { const inst = editor.value; if (!inst) return; if (inst.getValue() !== next) { inst.setValue(next, true); } }, ); onBeforeUnmount(() => { editor.value?.destroy(); editor.value = null; }); ``` - [ ] **Step 4: 运行测试确认通过** Run: `bun test app/components/PostBodyMarkdownEditor.vditor.test.ts` Expected: PASS(双向同步、实例销毁断言通过) - [ ] **Step 5: 提交** ```bash git add app/components/PostBodyMarkdownEditor.vue app/components/PostBodyMarkdownEditor.vditor.test.ts git commit -m "refactor(editor): migrate PostBodyMarkdownEditor to vditor core" ``` --- ### Task 3: 实现 PC/移动端差异化配置(A/B 策略) **Files:** - Modify: `app/components/PostBodyMarkdownEditor.vue` - Test: `app/components/PostBodyMarkdownEditor.vditor.test.ts` - [ ] **Step 1: 写失败测试(按断点应用不同 toolbar/preview 策略)** ```ts describe("PostBodyMarkdownEditor responsive config", () => { test("uses full toolbar on desktop and compact toolbar on mobile", async () => { // mock matchMedia for desktop/mobile // assert Vditor config differs by mode expect(true).toBe(false); }); }); ``` - [ ] **Step 2: 运行测试确认失败** Run: `bun test app/components/PostBodyMarkdownEditor.vditor.test.ts -t "responsive config"` Expected: FAIL(断点策略尚未实现) - [ ] **Step 3: 增加断点配置逻辑** ```ts const isMobile = useMediaQuery("(max-width: 768px)"); const desktopToolbar = [ "headings", "bold", "italic", "strike", "|", "list", "ordered-list", "check", "|", "quote", "line", "code", "inline-code", "|", "link", "upload", "table", "|", "preview", "fullscreen" ]; const mobileToolbar = ["bold", "italic", "headings", "list", "link", "upload", "code"]; const options = { toolbar: isMobile.value ? mobileToolbar : desktopToolbar, mode: isMobile.value ? "sv" : "wysiwyg", preview: { mode: isMobile.value ? "editor" : "both", }, }; ``` 并确保移动端存在可进入预览的入口(按钮或工具栏项)。 - [ ] **Step 4: 运行测试确认通过** Run: `bun test app/components/PostBodyMarkdownEditor.vditor.test.ts -t "responsive config"` Expected: PASS - [ ] **Step 5: 提交** ```bash git add app/components/PostBodyMarkdownEditor.vue app/components/PostBodyMarkdownEditor.vditor.test.ts git commit -m "feat(editor): add desktop/mobile tailored vditor modes" ``` --- ### Task 4: 接入现有图片上传链路并对齐错误提示 **Files:** - Modify: `app/components/PostBodyMarkdownEditor.vue` - Test: `app/components/PostBodyMarkdownEditor.vditor.test.ts` - [ ] **Step 1: 写失败测试(上传成功插入 URL,失败提示)** ```ts describe("PostBodyMarkdownEditor image upload", () => { test("inserts uploaded urls into markdown", async () => { // mock fetchData('/api/file/upload') -> { files: [{ url: "https://x/a.png" }] } // trigger upload hook // expect markdown contains image syntax with returned url expect(true).toBe(false); }); }); ``` - [ ] **Step 2: 运行测试确认失败** Run: `bun test app/components/PostBodyMarkdownEditor.vditor.test.ts -t "image upload"` Expected: FAIL(上传钩子尚未对接) - [ ] **Step 3: 实现上传适配** ```ts upload: { accept: "image/*", handler: async (files) => { const form = new FormData(); files.forEach((f) => form.append("file", f)); try { const { files: uploaded } = await fetchData("/api/file/upload", { method: "POST", body: form, }); toast.add({ title: "图片已上传", color: "success" }); return JSON.stringify({ msg: "", code: 0, data: { errFiles: [], succMap: Object.fromEntries(uploaded.map((x) => [x.url, x.url])) }, }); } catch { toast.add({ title: "图片上传失败", color: "warning" }); return JSON.stringify({ msg: "upload failed", code: 1, data: { errFiles: [], succMap: {} } }); } }, }, ``` - [ ] **Step 4: 运行测试确认通过** Run: `bun test app/components/PostBodyMarkdownEditor.vditor.test.ts -t "image upload"` Expected: PASS - [ ] **Step 5: 提交** ```bash git add app/components/PostBodyMarkdownEditor.vue app/components/PostBodyMarkdownEditor.vditor.test.ts git commit -m "feat(editor): wire vditor image upload to existing api" ``` --- ### Task 5: 页面回归与移动端可用性验证 **Files:** - Modify: `app/pages/me/posts/new.vue`(如需最小样式调整) - Modify: `app/pages/me/posts/[id].vue`(如需最小样式调整) - [ ] **Step 1: 写失败检查(页面仍依赖统一编辑器组件)** ```bash rg "