前言

欢迎加入鸿蒙PC开发者社区,共同打造开发者工具生态:鸿蒙PC开发者社区 :https://harmonypc.csdn.net/

项目开源地址:https://AtomGit.com/lqjmac/ele-caogaoxiang

草稿箱这一篇,我更想按一次真实改项目的节奏来写。
草稿工具不只是保存未完成文本,还要记录场景、阶段、标题钩子、受众和下一步动作。
它面向的是同时写文章、脚本、短帖、方案,手里有很多半成品内容的人。

草稿箱这一篇我会把重点放在“续写感”上。
半成品最怕重新打开时不知道当初想写什么,所以它要保存的不只是文本,还有阶段、受众和下一步。

一、先承认草稿需要状态

1.1 草稿箱真正要解决什么

草稿工具不只是保存未完成文本,还要记录场景、阶段、标题钩子、受众和下一步动作。
很多草稿失败不是因为质量差,而是放了几天以后,作者已经忘了它要往哪里走。

所以第一版围绕续写做减法:

  1. 草稿要有阶段,不只是保存正文
  2. 钩子、受众、提纲要留住当时的判断
  3. 下一步动作要明确,方便重新打开后继续

1.2 为什么不做成大而全

草稿箱很容易变成另一个完整写作软件。
我没有加入复杂排版和发布流程,而是把它定位成内容半成品的中转站。

内容环节 当前处理 原因
场景 保留 区分文章、脚本、短帖、方案
阶段 保留 判断是想法、提纲还是待润色
受众 保留 决定语气和展开方式
发布流程 暂不做 草稿箱先负责推进,不负责分发

这个定位清楚以后,草稿箱就不会和墨案写作混在一起。

二、文件分工对应写作推进

2.1 主要文件职责

文件 职责 这篇关注点
Home.vue 组织草稿工作台 收件箱、草稿泳道、编辑台在这里汇合
NoteSidebar.vue 管草稿入口 按场景和阶段找半成品内容
NoteEditor.vue 记录续写线索 钩子、受众、提纲和下一步都在这里维护
NoteToolbar.vue 放推进动作 新建、复制、导出、删除
useNotes.ts 管草稿状态 保存、筛选、排序、当前草稿
useNativeBridge.ts 处理复制导出 把草稿带到外部写作工具

草稿箱的组件边界要围绕“找回当时的上下文”来理解。
只讲通用组件名,会把这个项目最重要的续写感讲丢。

三、整体结构围绕半成品内容

3.1 页面结构图

在这里插入图片描述

草稿箱结构图展示收件箱、草稿泳道和编辑工作台之间的关系。

3.2 布局为什么这样分

草稿箱采用的是 内容场景收件箱 + 草稿泳道 + 编辑工作台
它更像一个半成品流水线:先看属于哪个场景,再看处于哪个阶段,最后决定怎么推进。

区域 承担的任务 设计注意点
场景收件箱 找到内容类型 文章、脚本、短帖不要混在一起
草稿泳道 判断推进阶段 阶段标签要比时间更醒目
编辑工作台 补提纲和下一步 不追求完整排版,先让内容继续走
顶部动作 复制和导出 方便把可推进草稿带出去

草稿越多,越需要阶段感。
否则所有半成品都会挤成一堆无法判断的标题。

四、字段设计要包含阶段和受众

4.1 草稿箱的核心字段

草稿字段要回答一个问题:我下次回来时,还能不能接上?
所以它不能只存标题和正文。

字段 含义 页面位置
id 草稿标识 状态层
scene 内容场景 列表/筛选
stage 当前阶段 列表/编辑区
hook 标题钩子或开头方向 编辑区
audience 面向谁写 编辑区
outline 当前提纲 编辑区/导出
nextAction 下次打开先做什么 侧栏/导出

4.2 TypeScript 类型

export interface AppItem {
  id: string;
  scene: string;
  stage: string;
  hook: number | string;
  audience: string;
  outline: string;
  nextAction: number | string;
}

export type AppFilter = 'all' | 'active' | 'archived';

这套字段的价值在“恢复上下文”。
草稿箱不是为了保存完美文本,而是为了让不完美文本继续往前走。

五、默认草稿要像真实写作现场

5.1 为什么要写种子数据

默认草稿要像真的半成品。
如果每个字段都只是占位文案,就体现不出阶段和下一步的价值。

我希望默认草稿满足:

  • 一眼看出内容场景
  • 知道目前卡在哪
  • 下一步可以直接执行

5.2 示例数据

export const seedAppItems: AppItem[] = [
  {
    id: 'caogao_xiang-001',
    scene: '技术文章',
    stage: '补案例',
    hook: '内容推进',
    audience: '鸿蒙 PC 开发者',
    outline: '从白屏日志切入,再讲 native so 修复和 HAP 验证。',
    nextAction: '补一段日志分析截图说明',
  },
];

真实草稿能测试长提纲、阶段标签和下一步提示是否撑得住。

六、状态层处理保存和推进

6.1 composable 的职责

useNotes.ts 这层我更愿意把它理解成“当前工具的数据服务”。
页面不应该直接处理太多 localStorage、排序和导出拼接。

const STORAGE_KEY = 'caogao-xiang';

const items = ref<AppItem[]>(loadItems());
const activeId = ref(items.value[0]?.id ?? '');

function persist() {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(items.value));
}

function loadItems() {
  const raw = localStorage.getItem(STORAGE_KEY);
  return raw ? JSON.parse(raw) : seedAppItems;
}

6.2 本地存储 key 一定要独立

这里的 key 我会明确写成 caogao-xiang
这样做可以避免不同工具之间互相读到旧数据。

本地数据一旦串了,页面看起来像小问题,实际会让调试和截图都变得很难判断。

七、筛选排序让可推进稿件靠前

7.1 computed 更适合承接派生视图

筛选、搜索、排序这些逻辑如果直接写在模板里,很快会让页面变得难读。
我更倾向于让状态层先准备好可展示列表。

const keyword = ref('');
const filter = ref<'all' | 'scene'>('all');

const visibleItems = computed(() => {
  const text = keyword.value.trim().toLowerCase();
  return items.value
    .filter(item => JSON.stringify(item).toLowerCase().includes(text))
    .sort((a, b) => String(b.id).localeCompare(String(a.id)));
});

7.2 排序服务于场景

草稿箱工具里,排序不是“哪个字段容易写就按哪个排”。
它应该服务用户打开应用时最想看到的那批内容。

  1. 未处理内容优先出现
  2. 置顶或高优先级内容靠前
  3. 最近更新内容不要沉底

八、Vue 页面组织草稿工作台

8.1 Home.vue 只做编排

我不希望 Home.vue 变成所有逻辑的大杂烩。
它更适合负责页面骨架和组件之间的数据传递。

<template>
  <main class="caogao_xiang-page">
    <NoteToolbar
      @create="createItem"
      @copy="copyCurrent"
      @export="exportCurrent"
    />
    <section class="workspace">
      <NoteSidebar :items="visibleItems" @select="selectItem" />
      <NoteEditor :item="currentItem" @update="updateItem" />
    </section>
  </main>
</template>

8.2 组件之间的边界

组件 应该知道什么 不应该知道什么
NoteToolbar 当前能触发哪些动作 具体字段如何存储
NoteSidebar 列表、筛选、选中项 导出 Markdown 细节
NoteEditor 当前对象字段 全局搜索逻辑

边界清楚以后,后续改样式和改字段都会轻很多。

九、编辑器要留下下一步线索

9.1 不要只留下标题和正文

草稿箱如果只保留标题和正文,就会退回普通记事本。
所以编辑器必须把核心字段摆出来。

<script setup lang="ts">
defineProps<{ item: AppItem | null }>();
const emit = defineEmits<{ update: [item: AppItem] }>();
</script>

<template>
  <form v-if="item" class="editor-form">
    <input v-model="item.scene" />
    <textarea v-model="item.audience" />
  </form>
</template>

9.2 表单不是越多越好

我会优先放能影响用户判断的字段。
辅助字段可以放到右侧信息区,或者只在导出时使用。

十、工具栏围绕复制和导出

10.1 工具栏放哪些按钮

工具栏最容易变成按钮仓库。
草稿箱里我只保留和主流程强相关的动作。

  • 收集草稿
  • 切换阶段
  • 补提纲
  • 记录下一步
  • 复制写作摘要
  • 导出草稿

10.2 复制摘要

function buildAppSummary(item: AppItem) {
  return [
    '# 草稿箱摘要',
    '- scene: ' + item.scene,
    '- stage: ' + item.stage,
    '- hook: ' + item.hook,
    '- audience: ' + item.audience,
  ].join('\n');
}

复制摘要的好处是很实际的。
用户不一定每次都要导出文件,有时只是想把当前内容发到聊天窗口或文档里。

十一、桥接层收住剪贴板动作

11.1 桥接层只暴露稳定动作

页面不应该知道底层是 Electron clipboard,还是 OpenHarmony 侧的能力。
它只需要知道“复制”“导出”“通知”这些动作。

export function useNativeBridge() {
  const api = window.ohosBridge ?? window.electronAPI;

  async function copyText(text: string) {
    if (api?.copyText) return api.copyText(text);
    return navigator.clipboard.writeText(text);
  }

  async function notify(message: string) {
    if (api?.notify) return api.notify(message);
  }

  return { copyText, notify };
}

11.2 为什么要有浏览器兜底

开发阶段经常会直接跑 Vite。
如果没有浏览器兜底,页面调试会被原生环境绑得太死。

十二、导出 Markdown 保留草稿状态

12.1 导出内容要能独立阅读

导出的 Markdown 不能只是把字段拼起来。
它最好离开应用以后也能被看懂。

function exportAppMarkdown(item: AppItem) {
  return [
    '# 草稿箱',
    '',
    '> 由 草稿箱 导出。',
    '## scene', String(item.scene ?? ''),
    '## stage', String(item.stage ?? ''),
    '## hook', String(item.hook ?? ''),
    '## audience', String(item.audience ?? ''),
    '## outline', String(item.outline ?? ''),
    '## nextAction', String(item.nextAction ?? ''),
  ].join('\n');
}

12.2 导出动作和通知联动

async function exportCurrent() {
  if (!currentItem.value) return;
  const markdown = exportAppMarkdown(currentItem.value);
  await bridge.copyText(markdown);
  await bridge.notify('草稿箱内容已复制为 Markdown');
}

这样用户完成导出以后能马上得到反馈。

十三、主进程加载保证随时续写

13.1 开发环境和生产环境分开

桌面应用最常见的白屏问题之一,是生产环境还在访问开发服务器。
所以主进程里一定要把加载逻辑分清楚。

const path = require('path');

function resolveRendererUrl() {
  if (process.env.VITE_DEV_SERVER_URL) {
    return process.env.VITE_DEV_SERVER_URL;
  }
  return `file://${path.join(__dirname, '../dist/index.html')}`;
}

mainWindow.loadURL(resolveRendererUrl());

13.2 preload 只注入必要接口

const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
  copyText: text => ipcRenderer.invoke('copy-text', text),
  notify: message => ipcRenderer.invoke('notify', message),
});

接口少一点,维护起来更安心。

十四、草稿箱样式要减少压迫感

14.1 视觉气质服务使用场景

草稿箱的视觉方向是:深色收件箱、写作工作台、内容流转感
这个判断会影响间距、字号、卡片密度和按钮重量。

.caogao_xiang-page {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: #f7f8fb;
  color: #1f2937;
}

.workspace {
  display: grid;
  grid-template-columns: 280px minmax(0, 1fr);
  gap: 16px;
  min-height: 0;
}

14.2 滚动区要提前处理

桌面应用窗口经常被用户缩小。
如果滚动区没有处理好,内容一多就会挤成一团。

  • 左侧列表要能独立滚动
  • 编辑区不能把工具栏挤出屏幕
  • 右侧信息区要允许内容截断和换行

十五、构建后检查草稿主题

15.1 先确认前端产物能生成

写文章之前,我会先跑一次构建。
这一步很朴素,但能挡住不少低级问题。

cd ../../electron_for_harmony/electron-openharmony-vue3-17/ohos_hap/web_engine/src/main/resources/resfile/resources/app/vue-app
npm install
npm run build

15.2 再确认关键文件没有串主题

rg "caogao-xiang|/draft-box|草稿箱" src package.json
rg "TODO|旧标题|测试数据" src

构建通过不代表体验完美,但至少说明当前页面和依赖关系是站得住的。

十六、这版草稿箱的经验

16.1 先换问题,再换界面

草稿箱最重要的不是页面长什么样,而是它先回答了一个明确问题:草稿工具不只是保存未完成文本,还要记录场景、阶段、标题钩子、受众和下一步动作。
问题清楚以后,字段、布局和按钮才知道往哪里收。

16.2 哪些东西可以复用

  • 清晰的页面、状态层、桥接层分工
  • 状态层和本地存储节奏
  • 复制、导出、通知这组桌面动作
  • 开发环境与生产环境分开的加载逻辑

16.3 哪些东西不要硬套

  • 旧的数据字段
  • 旧的默认文案
  • 旧的视觉重心
  • 旧的排序规则

十七、后续可以补的内容能力

草稿箱目前已经能让半成品内容带着上下文重新回到工作台。
真要继续加功能,我会优先从这些方向补:

  1. 增加“卡住原因”字段
  2. 支持草稿阶段拖拽切换
  3. 增加草稿到正式稿的迁移动作
  4. 给长期未推进草稿做温和提醒
  5. 支持按受众筛选草稿

草稿箱的增强重点是让半成品重新动起来,而不是堆更多编辑功能。

十八、发布前做一次草稿检查

发布前我会按下面这张表再扫一遍,尤其确认 主题一致性 和可发布性。

检查项 结果 说明
标题和主题一致 通过 草稿箱实战:让半成品稿件重新打开时知道下一步
图片存在 通过 保留项目结构图或运行效果图
代码块数量 通过 覆盖类型、状态、组件、桥接、导出、构建
资源链接 通过 保留社区和官方文档入口

总结

草稿箱的重点是续写感。它不是单纯存文本,而是把阶段、受众和下一步留下来,让半成品重新打开时不会又从空白开始。
草稿箱最重要的是让半成品重新变得可接近。
只要下一步、受众和提纲还在,草稿就不再是一堆沉默标题,而是可以继续推进的内容资产。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐