鸿蒙PC:鸿蒙electron跨端框架PC素材情绪板实战:把灵感、配色和参考资料收进桌面创作面板
欢迎加入鸿蒙PC开发者社区,共同打造开发者工具生态:鸿蒙PC开发者社区 :https://harmonypc.csdn.net/项目开源地址:https://AtomGit.com/lqjmac/ele_suacaiqingxuban素材整理不只是把文件放进文件夹。做设计、写文章、做活动页、整理产品参考时,真正容易丢的是“当时为什么觉得这份素材有用”。所以这个案例做成素材情绪板。它不像后台管理工具
前言
欢迎加入鸿蒙PC开发者社区,共同打造开发者工具生态:鸿蒙PC开发者社区 :https://harmonypc.csdn.net/
项目开源地址:https://AtomGit.com/lqjmac/ele_suacaiqingxuban
素材整理不只是把文件放进文件夹。
做设计、写文章、做活动页、整理产品参考时,真正容易丢的是“当时为什么觉得这份素材有用”。
所以这个案例做成 素材情绪板。
它不像后台管理工具,也不像严肃的文件系统,而更像一张可以持续贴素材、写判断、留线索的桌面板。
它适合这些场景:
- 收集视觉参考和配色方向
- 记录文案片段和情绪关键词
- 给素材补充适用场景
- 把可复用线索导出到项目文档
素材的价值不只在文件本身,也在它被收藏时的判断和语境。
本文会从创作场景出发,拆解素材模型、情绪板布局、Vue 状态管理、Electron 桥接和导出能力。
一、素材情绪板先解决什么问题
1.1 素材管理和情绪板的差别
传统素材管理偏“存储”。
情绪板偏“唤起”。
| 对比项 | 素材管理器 | 素材情绪板 |
|---|---|---|
| 核心目标 | 找到文件 | 找回灵感和判断 |
| 常见字段 | 文件名、标签、路径 | 来源、情绪、关键词、适用场景 |
| 使用节奏 | 查找和下载 | 收集、筛选、复用 |
| 输出内容 | 文件或链接 | 素材说明和复用线索 |
这就是为什么这个工具不急着做成资源管理器。
第一版更应该把素材线索写清楚。
1.2 不做大而全
素材类工具很容易加功能:
- 图片上传
- 拖拽排序
- 标签云
- 多画板
- 云同步
这些都不错,但第一版先聚焦一条主线:
- 收进一条素材线索
- 标记素材类型和可用状态
- 写下摘要、关键词和高亮
- 复制下一步可用的素材说明
- 导出 Markdown 素材卡
这条主线能跑通,工具就已经能帮助创作。
二、用文件分工承接创作面板
2.1 组件职责
素材情绪板需要的不是复杂后台组件,而是轻量创作面板。
| 文件 | 职责 | 素材场景里的说明 |
|---|---|---|
| Home.vue | 页面总装 | 组织素材栏、画板和编辑区 |
| MaterialSidebar.vue | 收纳栏 | 搜索、分类、统计、新建 |
| MaterialBoard.vue | 素材面板 | 置顶素材和素材卡片 |
| MaterialEditor.vue | 编辑区 | 来源、主题、关键词、摘要 |
| useMaterials.ts | 状态层 | 本地保存、筛选、排序 |
| useNativeBridge.ts | 桥接层 | 复制、导出、通知 |
如果项目内部仍然使用通用命名,也可以在文章里用业务职责解释清楚。
代码结构服务的是可维护性,不是表面命名。
2.2 状态层不要塞进组件
素材筛选、排序、本地保存、当前选中项都应放在 composable 中。
组件只负责展示和派发事件。
const {
visibleMaterials,
currentMaterial,
createMaterial,
updateMaterial,
togglePinned,
toggleArchived,
} = useMaterials();
这样页面会更像“组装面板”,而不是一整个大组件。
三、页面结构图
3.1 情绪板结构图

这张图对应素材情绪板的基本结构:左侧收纳素材,中间展示可复用卡片,右侧保留判断和关联线索。
3.2 页面气质要轻一点
素材情绪板不适合做得像报表后台。
它应该更接近一张创作前的桌面板。
| 区域 | 作用 | 视觉建议 |
|---|---|---|
| 收纳栏 | 快速进入分类 | 简洁、少说明 |
| 置顶区 | 放近期要用素材 | 更醒目 |
| 卡片区 | 扫描素材线索 | 允许轻微错落 |
| 编辑区 | 写判断和摘要 | 保持可读 |
页面可以有一点暖色,但不要过度装饰。
技术文章里也要说明为什么这样设计,而不是只贴 CSS。
四、数据模型要保留素材语境
4.1 核心字段
素材条目不是文件索引。
它要保留素材为什么可用。
| 字段 | 含义 | 示例 |
|---|---|---|
| title | 素材标题 | 活动主视觉参考 |
| source | 来源 | 设计稿、网站、项目复盘 |
| topic | 主题 | 低饱和配色 |
| category | 类型 | 视觉素材、情绪线索 |
| state | 状态 | 待整理、可复用、待确认 |
| keywords | 关键词 | 留白、轻量、温暖 |
| summary | 摘要 | 适合作为卡片页氛围参考 |
| highlights | 高亮 | 标题区和按钮层级清楚 |
字段越贴近创作动作,工具越不像空壳。
4.2 TypeScript 类型
export type MaterialState = 'draft' | 'reusable' | 'checking';
export type MaterialCategory =
| 'visual'
| 'mood'
| 'action'
| 'reference';
export interface MaterialItem {
id: string;
title: string;
source: string;
topic: string;
category: MaterialCategory;
state: MaterialState;
keywords: string;
summary: string;
highlights: string;
related: string;
content: string;
pinned: boolean;
archived: boolean;
createdAt: number;
updatedAt: number;
}
related 字段很重要。
很多素材不是独立有用,而是和另一个项目、页面、配色或文案一起产生价值。
五、文案映射要有创作味道
5.1 类型和状态
const categoryLabelMap: Record<MaterialCategory, string> = {
visual: '视觉素材',
mood: '情绪线索',
action: '整理动作',
reference: '参考资料',
};
const stateLabelMap: Record<MaterialState, string> = {
draft: '待整理',
reusable: '可复用',
checking: '待确认',
};
这些文案要贯穿整个工具。
不要一会儿叫素材,一会儿叫资料,一会儿又叫条目。
5.2 状态含义
| 状态 | 含义 | 适合的动作 |
|---|---|---|
| 待整理 | 刚收进来,还没判断 | 补摘要、补关键词 |
| 可复用 | 已经明确用途 | 复制说明、导出 |
| 待确认 | 权限、尺寸或上下文不清 | 继续复核 |
把状态含义写清楚,用户才知道下一步怎么处理。
六、默认素材要像真的收集过
6.1 示例数据
export const seedMaterials: MaterialItem[] = [
{
id: 'material-soft-dashboard',
title: '低饱和数据卡片参考',
source: '产品官网截图',
topic: '轻量工作台视觉',
category: 'visual',
state: 'reusable',
keywords: '低饱和,卡片,留白,柔和阴影',
summary: '适合作为桌面工具首页的数据卡片参考。',
highlights: '标题层级清楚,按钮不抢主内容注意力。',
related: '可用于归档工具、模板工具首页视觉。',
content: '整体色彩克制,适合信息密度较高但不想显得压迫的工具页面。',
pinned: true,
archived: false,
createdAt: Date.now() - 5400_000,
updatedAt: Date.now(),
},
];
这条素材不是“素材1”,而是有来源、有主题、有用途。
6.2 种子内容标准
好的默认素材应该满足:
- 一眼知道它能用在哪里
- 摘要能直接复制给别人
- 高亮能说明为什么留下
- 关键词能帮助再次找回
- 关联能提示后续组合
情绪板的默认数据越真实,读者越容易理解字段为什么这样设计。
七、本地保存让素材可回访
7.1 读取数据
const STORAGE_KEY = 'sucai-qingxuban-materials:v1';
function loadMaterials(): MaterialItem[] {
if (typeof window === 'undefined') return seedMaterials;
try {
const raw = window.localStorage.getItem(STORAGE_KEY);
if (!raw) return seedMaterials;
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed : seedMaterials;
} catch {
return seedMaterials;
}
}
本地保存不用一开始做得很重。
但必须稳定。
7.2 创建素材
function createMaterial() {
const now = Date.now();
const item: MaterialItem = {
id: `material-${now}-${Math.random().toString(16).slice(2)}`,
title: '新的素材线索',
source: '',
topic: '',
category: 'visual',
state: 'draft',
keywords: '',
summary: '',
highlights: '',
related: '',
content: '',
pinned: false,
archived: false,
createdAt: now,
updatedAt: now,
};
materials.value = [item, ...materials.value];
currentMaterialId.value = item.id;
schedulePersist();
}
默认分类设为视觉素材,是为了降低新建成本。
用户可以再切换类型。
八、筛选要支持灵感回找
8.1 搜索范围
const searchTerm = ref('');
const activeCategory = ref<'all' | MaterialCategory>('all');
const visibleMaterials = computed(() => {
const keyword = searchTerm.value.trim().toLowerCase();
return materials.value
.filter(item => {
if (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,
item.related,
].join(' ').toLowerCase().includes(keyword);
})
.sort(sortMaterials);
});
素材回找经常不是按标题。
用户更可能记得一个颜色、一个语气、一个项目名。
8.2 排序规则
function sortMaterials(a: MaterialItem, b: MaterialItem) {
if (a.pinned !== b.pinned) return a.pinned ? -1 : 1;
const order: Record<MaterialState, number> = {
reusable: 0,
checking: 1,
draft: 2,
};
if (a.state !== b.state) {
return order[a.state] - order[b.state];
}
return b.updatedAt - a.updatedAt;
}
可复用素材靠前,符合情绪板的使用习惯。
九、Vue 页面像一张创作桌
9.1 页面骨架
<template>
<section class="material-app">
<MaterialSidebar
:summary="summary"
v-model:search-term="searchTerm"
@create="createMaterial"
@set-category="setCategory"
/>
<main class="moodboard">
<MaterialToolbar
:has-material="Boolean(currentMaterial)"
@copy="handleCopy"
@export="handleExport"
@pin="handlePin"
/>
<MaterialBoard
:materials="visibleMaterials"
:current-id="currentMaterialId"
@select="selectMaterial"
/>
</main>
<MaterialEditor
v-if="currentMaterial"
:material="currentMaterial"
@update="updateMaterial"
/>
</section>
</template>
这个结构把收纳、展示和编辑分成三个角色。
读者照着看也比较容易迁移。
9.2 反馈提示
const feedback = ref('');
function showFeedback(message: string) {
feedback.value = message;
window.setTimeout(() => {
feedback.value = '';
}, 2200);
}
情绪板的反馈不需要打断用户。
短提示就够了。
十、编辑器保留判断
10.1 编辑区结构
<template>
<article class="material-editor">
<input
class="title-input"
:value="material.title"
placeholder="素材标题"
@input="updateField('title', $event)"
/>
<label>
<span>素材来源</span>
<input :value="material.source" @input="updateField('source', $event)" />
</label>
<label>
<span>情绪关键词</span>
<textarea :value="material.keywords" @input="updateField('keywords', $event)" />
</label>
<label>
<span>为什么留下它</span>
<textarea :value="material.summary" @input="updateField('summary', $event)" />
</label>
</article>
</template>
这里的重点不是“正文”,而是“为什么留下它”。
这会让工具更像创作辅助,而不是资料录入表。
10.2 关键词拆分
const keywordItems = computed(() => {
if (!currentMaterial.value?.keywords.trim()) return [];
return currentMaterial.value.keywords
.split(/[,\n,]/)
.map(item => item.trim())
.filter(Boolean);
});
关键词拆成标签后,右侧信息区会更有情绪板的感觉。
十一、复制动作复制素材说明
11.1 复制逻辑
async function handleCopyMaterial() {
if (!currentMaterial.value) return;
const text =
currentMaterial.value.summary ||
currentMaterial.value.highlights ||
currentMaterial.value.title;
const ok = await copyText(text);
if (ok) {
showFeedback('素材说明已复制');
await notify('素材情绪板', '当前素材说明已经复制到剪贴板');
}
}
素材工具复制出去的内容应该是可读说明。
直接复制标题通常不够用。
11.2 剪贴板差异
可以参考 Electron clipboard 和 MDN Clipboard API。
在桌面端,剪贴板是非常高频的流转入口。
所以它要被单独封装,不要写散。
十二、导出 Markdown 做成素材卡
12.1 导出内容
function buildMaterialMarkdown(material: MaterialItem) {
return [
`# ${material.title || '未命名素材'}`,
'',
`- 类型:${categoryLabelMap[material.category]}`,
`- 状态:${stateLabelMap[material.state]}`,
`- 来源:${material.source || '未设置'}`,
`- 主题:${material.topic || '未设置'}`,
`- 关键词:${material.keywords || '未设置'}`,
'',
'## 这份素材为什么值得留下',
'',
material.summary || '暂无摘要',
'',
'## 可复用线索',
'',
material.highlights || '暂无高亮',
'',
'## 关联参考',
'',
material.related || '暂无关联',
'',
'## 备注',
'',
material.content || '暂无正文',
].join('\n');
}
导出后更像一张素材卡。
它可以直接放进项目复盘或设计说明。
12.2 文件名处理
function safeMaterialFileName(value: string) {
return (value.trim() || '素材卡')
.replace(/[\\/:*?"<>|]/g, '-')
.slice(0, 80);
}
保存文件名要干净。
不要把太多字段塞到文件名里。
十三、主进程加载本地页面
13.1 BrowserWindow 设置
const { app, BrowserWindow } = require('electron');
const path = require('path');
function createWindow() {
const win = new BrowserWindow({
width: 1225,
height: 850,
minWidth: 960,
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);
桌面端标题要和应用主题一致。
不要让运行窗口还显示旧名字。
13.2 构建检查
npm run build
test -f dist/index.html
find dist -maxdepth 2 -type f | sort
这个检查可以快速发现前端资源是否生成。
如果 HAP 包内没有 dist/index.html,运行时必然加载失败。
十四、样式要区别于后台工具
14.1 情绪板主题
.material-app {
min-height: 100%;
display: grid;
grid-template-columns: 280px minmax(0, 1fr) 360px;
background: #f6efe7;
color: #28313a;
}
.moodboard-card {
border: 1px solid #ead7c4;
border-radius: 8px;
background: #fffaf4;
padding: 14px;
box-shadow: 8px 8px 0 rgba(40, 49, 58, 0.08);
}
.moodboard-card.active {
border-color: #28313a;
box-shadow: 8px 8px 0 #f06f52;
}
这套样式带一点手工板感,但仍然保持克制。
14.2 标签样式
.keyword-chip {
display: inline-flex;
align-items: center;
min-height: 24px;
padding: 0 8px;
border-radius: 999px;
background: #ffe1d4;
color: #8a3f2c;
font-size: 12px;
font-weight: 700;
}
标签是素材情绪板的重要视觉元素。
它能让用户快速抓到风格线索。
十五、滚动和响应式处理
15.1 三栏滚动
.material-app {
height: 100vh;
min-height: 0;
overflow: hidden;
}
.moodboard,
.material-editor,
.material-sidebar {
min-height: 0;
overflow: auto;
}
三栏布局最容易出的问题是中间或右侧不能滚动。
每一栏都要明确滚动边界。
15.2 窗口缩小时
@media (max-width: 1100px) {
.material-app {
grid-template-columns: 260px minmax(0, 1fr);
}
.material-editor {
grid-column: 1 / -1;
}
}
PC 应用不等于永远大窗口。
用户可能会把它和浏览器、文档并排打开。
十六、原生桥接封装
16.1 预加载脚本
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('desktopBridge', {
copyText: text => ipcRenderer.invoke('copy-text', text),
saveMarkdown: payload => ipcRenderer.invoke('save-markdown', payload),
showNotification: payload => ipcRenderer.invoke('show-notification', payload),
});
预加载脚本只暴露必要能力。
这样比把 Node 能力直接打开更安全。
16.2 页面调用
async function exportCurrentMaterial() {
if (!currentMaterial.value) return;
const markdown = buildMaterialMarkdown(currentMaterial.value);
const result = await exportMarkdown(currentMaterial.value.title, markdown);
if (result.ok) {
showFeedback('素材卡已导出');
}
}
页面只关心结果,不关心底层保存方式。
十七、发布前检查
17.1 功能检查
发布前检查下面这些点:
- 标题显示为素材情绪板
- 默认素材不是空占位
- 关键词可以被搜索
- 标签展示不会撑破卡片
- 复制素材说明可用
- 导出 Markdown 像素材卡
- 三栏内容都能滚动
这些检查能覆盖创作型桌面工具的主要风险。
17.2 文章发布检查
| 检查项 | 结果 | 说明 |
|---|---|---|
| 图片存在 | 通过 | 结构图能显示 |
| 表格数量 | 通过 | 覆盖对比、字段、检查 |
| 代码块 | 通过 | 覆盖类型、状态、组件、导出 |
| 链接数量 | 通过 | 正文和资源区包含有效链接 |
| 总结和投票 | 通过 | 文末保留引导 |
技术文章不是代码贴得越多越好,而是要让读者知道每段代码为什么出现。
十八、后续增强方向
18.1 素材导入能力
后续可以逐步增加:
- 图片文件导入
- 拖拽上传
- 缩略图缓存
- 颜色提取
- 多画板分组
真实文件处理可以参考 MDN File API。
18.2 创作辅助能力
更进一步可以做:
- 根据关键词推荐相关素材
- 生成项目素材包
- 导出设计说明
- 按主题生成情绪板视图
- 支持素材归档和恢复
这些能力都建立在素材字段已经稳定的基础上。
总结
素材情绪板的重点不是存更多素材,而是保留素材被收藏时的语境。
通过素材类型、可用状态、关键词、摘要、高亮、复制和导出,桌面端可以变成一个轻量创作准备台。
后续如果继续扩展,我会优先做真实图片导入和缩略图缓存,再考虑颜色分析和多画板。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- 鸿蒙PC开发者社区:https://harmonypc.csdn.net/
- OpenHarmony 文档:https://docs.openharmony.cn/
- Electron 官方文档:https://www.electronjs.org/docs/latest/
更多推荐



所有评论(0)