鸿蒙PC:鸿蒙electron跨端框架PC归档流水线实战:把散落文件整理成可追踪的桌面归档流程
欢迎加入鸿蒙PC开发者社区,共同打造开发者工具生态:鸿蒙PC开发者社区 :https://harmonypc.csdn.net/项目开源地址:https://AtomGit.com/lqjmac/ele_guidangliushuixian桌面文件夹最常见的问题不是文件太多,而是很多文件“当时为什么留下”已经说不清了。会议附件、临时截图、导出的表格、几版方案、压缩包和别人发来的补充材料,刚保存的时
前言
欢迎加入鸿蒙PC开发者社区,共同打造开发者工具生态:鸿蒙PC开发者社区 :https://harmonypc.csdn.net/
项目开源地址:https://AtomGit.com/lqjmac/ele_guidangliushuixian
桌面文件夹最常见的问题不是文件太多,而是很多文件“当时为什么留下”已经说不清了。
会议附件、临时截图、导出的表格、几版方案、压缩包和别人发来的补充材料,刚保存的时候都有用,过一段时间就会变成一堆难以判断的本地资料。
所以这个工具不做传统的文件夹美化,而是做成 归档流水线。
它的核心目标是让一份资料经历“进入、判断、备注、归档、导出”这条路径。
适合处理的场景包括:
- 项目结束后整理交付材料
- 把桌面临时资料收口
- 给保留文件补充原因
- 导出一份归档说明给团队复盘
归档不是把文件移走,而是把文件为什么值得留下说清楚。
本文会从数据模型、页面结构、状态流转、Electron 桥接、Markdown 导出和构建检查几个角度拆解。
一、先把归档动作拆清楚
1.1 归档和删除不是一回事
很多整理工具把“归档”做得像“隐藏”。
但真实使用中,归档更像一种状态:这份资料已经有去处、有解释、有复查点。
| 动作 | 含义 | 是否可恢复 | 是否需要说明 |
|---|---|---|---|
| 删除 | 不再需要 | 通常不可恢复 | 不一定 |
| 隐藏 | 暂时不显示 | 可以恢复 | 不一定 |
| 归档 | 处理完成并保留 | 可以恢复 | 需要 |
| 待确认 | 暂时不能判断 | 可以继续处理 | 需要 |
这张表决定了按钮文案不能混在一起。
删除是破坏性动作,归档是流程动作。
1.2 第一版做轻流程
归档流水线第一版不急着做真实文件移动。
真实文件移动涉及目录权限、冲突处理、同名覆盖、失败回滚和批量任务。
第一版先把资料判断过程做顺:
- 新建资料条目
- 填写来源和保留原因
- 标记待归档、已入库或待确认
- 复制归档摘要
- 导出 Markdown 归档单
这条链路顺了以后,再接文件系统能力才更稳。
二、文件分工围绕归档流转
2.1 组件职责
归档工具的组件要围绕“资料如何流动”来拆。
| 文件 | 职责 | 归档场景里的作用 |
|---|---|---|
| Home.vue | 页面总装 | 组织收件栏、流水线和编辑区 |
| ArchiveSidebar.vue | 左侧入口 | 搜索、分类、统计、收口入口 |
| ArchiveEditor.vue | 编辑资料 | 来源、主题、说明、复查时间 |
| ArchiveToolbar.vue | 操作栏 | 新建、复制、导出、归档、删除 |
| useArchiveItems.ts | 状态层 | 本地保存、筛选、排序 |
| useNativeBridge.ts | 桥接层 | 剪贴板、文件保存、系统通知 |
如果项目里沿用通用组件名,也可以在文章里按业务职责解释。
关键是让读者知道每个文件服务哪一个归档动作。
2.2 页面和桥接要解耦
页面不要直接写文件保存细节。
它只需要调用 exportMarkdown,至于浏览器预览还是鸿蒙 Electron 运行时,由桥接层判断。
const { copyText, exportMarkdown, notify, isNativeRuntime } = useNativeBridge();
这种写法让页面更干净,也方便在浏览器里先调试界面。
三、页面结构图
3.1 归档流水线结构图

这张图表达的是资料从左侧收口进入,中间进入状态流转,右侧补充归档说明和复查信息。
3.2 为什么不是文件树
很多人做文件管理,第一反应是树形目录。
但归档流水线不以目录为核心,而以资料状态为核心。
| 区域 | 负责什么 | 不做什么 |
|---|---|---|
| 收件栏 | 接住散落资料 | 不做复杂目录树 |
| 流水线 | 展示待处理状态 | 不做审批系统 |
| 编辑区 | 写清保留原因 | 不只当备注框 |
| 工具栏 | 复制和导出 | 不堆无关按钮 |
这样用户打开后先看到的是“哪些资料还没处理”,而不是“文件夹层级有多深”。
四、数据模型要表达归档理由
4.1 核心字段
归档条目至少需要这些字段:
| 字段 | 说明 | 示例 |
|---|---|---|
| id | 唯一标识 | archive-001 |
| title | 资料标题 | 五月验收截图包 |
| source | 资料来源 | 项目群附件 |
| topic | 归档主题 | 验收资料 |
| state | 处理状态 | 待归档 |
| summary | 保留摘要 | 用于验收复盘 |
| highlights | 关键说明 | 包含最终确认图 |
| nextReview | 复查时间 | 2026-05-30 |
这些字段让一份资料脱离原始文件名后仍然能被理解。
4.2 TypeScript 类型
export type ArchiveState = 'incoming' | 'stored' | 'checking';
export type ArchiveCategory =
| 'inbox'
| 'rule'
| 'process'
| 'reference';
export interface ArchiveItem {
id: string;
title: string;
source: string;
topic: string;
category: ArchiveCategory;
state: ArchiveState;
keywords: string;
summary: string;
highlights: string;
content: string;
nextReview: string;
pinned: boolean;
archived: boolean;
createdAt: number;
updatedAt: number;
}
这里保留 archived 字段,是为了区分“已入库状态”和“是否从活跃列表移走”。
五、状态文案要贴近资料整理
5.1 状态映射
const stateLabelMap: Record<ArchiveState, string> = {
incoming: '待归档',
stored: '已入库',
checking: '待确认',
};
const categoryLabelMap: Record<ArchiveCategory, string> = {
inbox: '待整理资料',
rule: '归档规则',
process: '流转过程',
reference: '发布参考',
};
这组映射要贯穿列表、筛选、编辑器和导出。
否则用户会在不同区域看到不一致的表达。
5.2 状态优先级
归档流水线的优先级不是按创建时间简单倒序。
我更倾向于这样排:
- 待确认优先,因为它容易卡住后续归档
- 待归档其次,因为它还在活跃工作流里
- 已入库靠后,因为它已经完成
- 已隐藏或归档记录最后展示
这套排序会让用户打开应用后先处理最该处理的资料。
六、种子数据像真实桌面资料
6.1 示例数据
默认数据必须像真的从桌面整理出来。
export const seedArchiveItems: ArchiveItem[] = [
{
id: 'archive-acceptance-screenshots',
title: '验收截图包整理',
source: '项目群临时附件',
topic: '验收材料',
category: 'inbox',
state: 'checking',
keywords: '截图,验收,交付',
summary: '截图包需要确认是否包含最终版本页面,确认后再入库。',
highlights: '有两张截图命名不清楚,需要对照提交记录。',
content: '资料来自项目群,需要补充版本说明和使用场景。',
nextReview: '2026-05-30',
pinned: true,
archived: false,
createdAt: Date.now() - 7200_000,
updatedAt: Date.now(),
},
];
这条记录能体现“来源不清、需要确认、暂不入库”的真实状态。
6.2 种子数据检查
种子数据至少要覆盖:
- 待归档
- 已入库
- 待确认
- 置顶资料
- 已归档资料
这样才能检查筛选、排序、样式和导出。
七、本地存储保证资料不丢
7.1 读取逻辑
const STORAGE_KEY = 'guidang-liushuixian-items:v1';
function loadArchiveItems(): ArchiveItem[] {
if (typeof window === 'undefined') return seedArchiveItems;
try {
const raw = window.localStorage.getItem(STORAGE_KEY);
if (!raw) return seedArchiveItems;
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed : seedArchiveItems;
} catch {
return seedArchiveItems;
}
}
这段代码保持保守。
本地缓存损坏时回到默认数据,而不是让页面崩掉。
7.2 保存节奏
const archiveItems = ref<ArchiveItem[]>(loadArchiveItems());
const isSaving = ref(false);
const lastSavedAt = ref<number | null>(null);
function persistArchiveItems() {
if (typeof window === 'undefined') return;
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(archiveItems.value));
lastSavedAt.value = Date.now();
isSaving.value = false;
}
编辑器频繁输入时,可以外面再包一层防抖。
本地优先工具一定要给用户“数据已经落盘”的信号。
八、筛选逻辑不能打断当前编辑
8.1 可见列表
const searchTerm = ref('');
const activeCategory = ref<'all' | ArchiveCategory>('all');
const showArchived = ref(false);
const visibleArchiveItems = computed(() => {
const keyword = searchTerm.value.trim().toLowerCase();
return archiveItems.value
.filter(item => {
if (!showArchived.value && item.archived) return false;
if (activeCategory.value !== 'all' && item.category !== activeCategory.value) {
return false;
}
if (!keyword) return true;
return [
item.title,
item.source,
item.topic,
item.keywords,
item.summary,
item.highlights,
].join(' ').toLowerCase().includes(keyword);
})
.sort(sortArchiveItems);
});
用户可能按来源找,也可能按主题找。
搜索字段要覆盖真实记忆点。
8.2 当前条目兜底
function ensureSelectedArchiveItem() {
const exists = archiveItems.value.some(item => item.id === currentArchiveId.value);
if (exists) return;
currentArchiveId.value =
archiveItems.value.find(item => !item.archived)?.id ??
archiveItems.value[0]?.id ??
'';
}
筛选或删除后不要让编辑区出现不可解释的空白。
这个兜底能让体验稳定很多。
九、流水线列表要突出状态
9.1 卡片内容
归档卡片不应该只显示标题。
<template>
<button class="archive-card" :class="item.state" @click="$emit('select', item.id)">
<span class="state-badge">{{ stateLabelMap[item.state] }}</span>
<strong>{{ item.title || '未命名资料' }}</strong>
<span class="source-line">{{ item.source || '未设置来源' }}</span>
<p>{{ excerpt(item.summary || item.content) }}</p>
</button>
</template>
状态、来源、摘要都要露出来。
这样用户扫列表时能判断先处理哪条。
9.2 摘要截断
function excerpt(value: string) {
return value.trim().replace(/\s+/g, ' ').slice(0, 92) || '暂无归档说明';
}
摘要不是越长越好。
列表层只负责提示,不负责承载完整内容。
十、编辑器要写清保留依据
10.1 表单结构
<template>
<article class="archive-editor">
<input
class="title-input"
:value="item.title"
placeholder="资料标题"
@input="updateField('title', $event)"
/>
<div class="meta-grid">
<label>
<span>资料来源</span>
<input :value="item.source" @input="updateField('source', $event)" />
</label>
<label>
<span>归档主题</span>
<input :value="item.topic" @input="updateField('topic', $event)" />
</label>
</div>
<textarea
class="content-input"
:value="item.content"
placeholder="写清这份资料为什么保留、归到哪里、后面谁会用"
@input="updateField('content', $event)"
/>
</article>
</template>
归档说明要能回答三个问题:
- 为什么保留
- 放到哪里
- 后续谁会用
10.2 字段更新
function updateArchiveItem(id: string, patch: Partial<ArchiveItem>) {
archiveItems.value = archiveItems.value.map(item =>
item.id === id
? { ...item, ...patch, updatedAt: Date.now() }
: item
);
schedulePersist();
}
统一入口更新可以确保 updatedAt 和保存逻辑不会漏。
十一、归档动作要和删除分开
11.1 切换归档状态
function toggleArchived(id: string) {
const target = archiveItems.value.find(item => item.id === id);
if (!target) return;
updateArchiveItem(id, {
archived: !target.archived,
pinned: target.archived ? target.pinned : false,
});
}
归档后取消置顶,是为了避免隐藏资料仍占据重点位。
11.2 删除动作
function deleteArchiveItem(id: string) {
archiveItems.value = archiveItems.value.filter(item => item.id !== id);
ensureSelectedArchiveItem();
schedulePersist();
}
删除可以做二次确认。
不过初始版本如果定位为个人工具,也可以先保证按钮样式明显区分。
十二、复制摘要服务流转
12.1 复制逻辑
async function handleCopyArchiveSummary() {
if (!currentArchiveItem.value) return;
const text =
currentArchiveItem.value.summary ||
currentArchiveItem.value.highlights ||
currentArchiveItem.value.title;
const ok = await copyText(text);
if (ok) {
showFeedback('归档摘要已复制');
await notify('归档流水线', '当前归档摘要已经复制到剪贴板');
}
}
复制摘要适合发到聊天、日报或项目文档里。
它不需要把整篇内容都复制走。
12.2 剪贴板能力
Electron 里可以使用 clipboard API。
浏览器预览里可以使用 Clipboard API。
桥接层统一封装后,页面就不用关心差异。
十三、导出 Markdown 形成归档单
13.1 导出结构
function buildArchiveMarkdown(item: ArchiveItem) {
return [
`# ${item.title || '未命名归档资料'}`,
'',
`- 类型:${categoryLabelMap[item.category]}`,
`- 状态:${stateLabelMap[item.state]}`,
`- 来源:${item.source || '未设置'}`,
`- 主题:${item.topic || '未设置'}`,
`- 关键词:${item.keywords || '未设置'}`,
`- 下次复查:${item.nextReview || '未设置'}`,
'',
'## 归档摘要',
'',
item.summary || '暂无摘要',
'',
'## 保留依据',
'',
item.content || '暂无说明',
'',
'## 关键说明',
'',
item.highlights || '暂无高亮',
].join('\n');
}
这份归档单能解释资料的来源、状态和保留原因。
13.2 保存文件名
function getArchiveFileName(item: ArchiveItem) {
return `${safeFileName(item.title || '归档资料')}.md`;
}
文件名不建议带太多状态信息。
状态已经写在 Markdown 内容里。
十四、主进程保证本地页面加载
14.1 BrowserWindow 配置
const { app, BrowserWindow } = require('electron');
const path = require('path');
function createWindow() {
const win = new BrowserWindow({
width: 1225,
height: 850,
minWidth: 980,
minHeight: 720,
title: '归档流水线',
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
});
win.loadFile(path.join(__dirname, '..', 'dist', 'index.html'));
}
app.whenReady().then(createWindow);
这里要注意 dist/index.html 是否进入最终资源目录。
如果路径错了,用户看到的就是白屏或加载失败。
14.2 资源检查
npm run build
test -f dist/index.html
find dist/assets -type f | sort
构建产物检查应该成为发布前固定动作。
十五、样式要像一条流水线
15.1 主布局样式
归档流水线适合使用稳重的纸张色和清晰边框。
.archive-app {
min-height: 100%;
display: grid;
grid-template-columns: 300px minmax(0, 1fr);
background: #f3efe6;
color: #27313d;
}
.archive-board {
min-height: 0;
overflow: auto;
padding: 20px;
}
.archive-card {
border: 1px solid #d7c8ae;
border-radius: 8px;
background: #fffaf0;
padding: 14px;
}
它不需要太多装饰。
重点是让资料状态和归档说明读起来清楚。
15.2 状态徽标
.state-badge {
display: inline-flex;
align-items: center;
height: 24px;
padding: 0 8px;
border-radius: 999px;
font-size: 12px;
font-weight: 700;
}
.archive-card.checking .state-badge {
background: #fff1c2;
color: #8a5a00;
}
状态徽标要小而明确。
不要抢走标题和摘要的注意力。
十六、滚动和窗口体验
16.1 内容滚动
归档说明可能很长,外层必须允许滚动。
html,
body,
#app {
height: 100%;
margin: 0;
}
.app-shell {
height: 100vh;
min-height: 0;
overflow: hidden;
}
.app-content {
height: 100%;
min-height: 0;
overflow: auto;
}
如果没有 min-height: 0,Flex 或 Grid 内部滚动经常会失效。
16.2 原生窗口栏
归档工具不需要自己做一套窗口按钮。
使用原生窗口栏更稳,最小化、最大化、关闭都由系统处理。
业务页面只负责内容。
十七、发布前验证
17.1 功能验证
我会按下面清单检查:
- 应用标题显示为归档流水线
- 新建资料后默认进入待归档状态
- 分类筛选不会导致编辑区异常空白
- 复制摘要可以写入剪贴板
- 导出 Markdown 包含来源和保留依据
- 已归档资料可以隐藏和恢复
- 删除不会误删其他资料
这些动作覆盖了主要归档链路。
17.2 发布检查表
| 检查项 | 结果 | 说明 |
|---|---|---|
| 标题一致 | 通过 | 页面、窗口和导出都叫归档流水线 |
| 图片存在 | 通过 | 结构图可显示 |
| 代码块完整 | 通过 | 覆盖类型、状态、组件、导出 |
| 资源链接 | 通过 | 保留社区、文档和 Electron |
| 投票引导 | 通过 | 文末保留互动引导 |
技术文章写到最后,一定要回到“读者能不能照着检查”。
十八、后续扩展方向
18.1 文件系统能力
后续可以接真实文件处理:
- 选择文件夹
- 批量导入文件清单
- 移动到归档目录
- 同名文件冲突提示
- 失败任务重试
这些能力可以逐步接,不要一次全塞进去。
18.2 更强的归档规则
规则层可以继续增强:
- 按文件类型生成建议分类
- 按关键词推荐归档主题
- 按项目名自动生成目录
- 导出归档报告
- 增加批量状态修改
做这些之前,先确保当前的单条归档链路足够稳定。
总结
归档流水线把文件整理从“移动文件”改成“解释资料为什么留下”。
它通过状态建模、本地保存、筛选排序、复制摘要和 Markdown 导出,把桌面资料整理做成一条可以追踪的轻流程。
后续如果要增强,我会先接文件选择和批量导入,再逐步扩展真实移动、规则推荐和归档报告。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- 鸿蒙PC开发者社区:https://harmonypc.csdn.net/
- OpenHarmony 文档:https://docs.openharmony.cn/
- Electron 官方文档:https://www.electronjs.org/docs/latest/
更多推荐




所有评论(0)