鸿蒙PC:青会纪要实战:会议结束以后,行动项不能跟着散掉
本文介绍了鸿蒙PC开发者社区中的青会纪要工具设计思路,重点突出会议后的行动追踪而非会中记录。工具将会议信息分为议题、决策、行动项和风险三层,通过结构化字段(如topic、agenda、decisions等)确保纪要可执行。采用localStorage实现自动保存,默认数据模拟真实会议场景,并设计了筛选搜索功能以便跟进待办事项。页面布局强调结论和行动项,使会议纪要真正成为推动工作的工具而非简单记录。
前言
先放社区入口:鸿蒙PC开发者社区:https://harmonypc.csdn.net/
青会纪要这篇不从“页面长什么样”开始讲,而是从会议结束后的那几分钟开始讲。很多会议并不是没有记录,而是记录完以后没人知道接下来谁负责、什么时候交付、风险有没有人继续盯。会议纪要如果只是一段正文,它很容易变成“看起来写了,实际上没人执行”的材料。
所以这一版青会纪要的重点不是把会议过程写得很完整,而是把会后还能推动事情继续往前走的对象拆出来:议题、结论、行动项、风险。页面、状态层、导出和桌面反馈都围绕这几个对象展开。
纪要的价值不在于写了多少字,而在于会后是否还能推动行动项。
一、这类工具先看会后动作,而不是会中记录
如果把会议纪要当成普通笔记,页面大概率会变成一个大文本框。用户把会议内容粘进去,看似完成了记录,但后面追行动项时还是要重新读一遍正文。青会纪要不应该这样做,它要把“能执行的内容”从会议记录里提出来。
我先把会后信息拆成三层。第一层是议题,用来说明这次会讨论什么。第二层是决策,用来说明会议最终定了什么。第三层是行动项和风险,用来说明会后要继续做什么,以及哪些事情可能阻塞交付。

这张结构图可以理解成“会议队列 + 当前纪要 + 行动项侧栏”。左侧负责快速切换不同会议,中间负责记录议程和结论,右侧负责突出行动项和风险。这样的布局比单纯正文更适合会后追踪。
二、字段从行动项往回推
青会纪要的字段不能只围绕标题和正文设计。它要能回答:这次会定了什么、谁要继续做、哪里有风险。基于这个目标,我保留下面这些字段。
| 字段 | 作用 | 为什么保留 |
|---|---|---|
| id | 唯一标识 | 用来选中、更新和导出某条纪要 |
| topic | 会议主题 | 列表主标题,方便快速找到会议 |
| agenda | 会议议程 | 帮助说明讨论范围,避免纪要散成流水账 |
| decisions | 会议结论 | 会后最需要被复述和确认的内容 |
| actions | 行动项 | 决定纪要有没有执行价值 |
| risks | 风险点 | 防止阻塞事项藏在正文里 |
export interface 青会纪要Item {
id: string;
topic: string;
agenda: string;
decisions: string;
actions: string;
risks: string;
}
export type 青会纪要Filter = 'all' | 'open' | 'done';
这段类型先把一条会议纪要的结构固定下来。id 仍然是状态层操作的基础,列表切换、编辑更新、导出当前纪要都依赖它。topic 不只是标题,它决定用户能不能从列表里快速定位会议;如果 topic 写得太泛,后续回找会很痛苦。
agenda 和 decisions 要分开。agenda 是会议讨论范围,decisions 是最终结论。很多纪要混乱,就是因为讨论过程和最终决定写在一起,读者很难判断哪些只是讨论过,哪些已经定下来了。把这两个字段拆开,导出给别人时也更清楚。
actions 是这类工具的核心字段。会议记录如果没有行动项,往往只能证明“我们聊过”;有了行动项,纪要才开始变成执行材料。risks 则用于单独存放阻塞点,避免风险被埋在正文末尾,等到真正出问题才被想起来。
青会纪要Filter 这里写成 all、open、done,是为了后续能按会后状态过滤。第一版可以先只用 all,后面如果加“已完成会议”或“待跟进会议”,就不需要重新改类型思路。
三、默认纪要要像一场真实会议
默认数据不能写成“会议一”“测试议题”。会议类工具特别依赖真实语境,因为行动项、风险和结论是否清楚,只有放进一个具体会议里才能看出来。
export const seed青会纪要Items: 青会纪要Item[] = [
{
id: 'qinghui_meeting-001',
topic: '版本评审同步会',
agenda: '确认本周上线范围、遗留问题和测试回归安排',
decisions: '核心流程本周保留上线,报表配置延期到下一轮。',
actions: '前端补导出提示;测试回归主链路;产品同步延期说明。',
risks: '接口字段还可能调整,测试环境数据不完整。',
},
];
这条 seed 数据故意写成一个常见的版本评审会。topic 能测试列表标题;agenda 能测试中等长度文本;decisions 能测试导出时的结论展示;actions 能测试行动项密度;risks 能测试侧栏是否能突出风险。
这里的默认数据还承担文章解释功能。读者一眼能看出每个字段应该填什么,而不是看到一堆“示例内容”。会议工具的默认数据越真实,越能暴露页面布局是否适合扫读。例如 actions 一多,是否还容易找到负责人?risks 是否会被正文挤下去?这些都要靠真实样本验证。
四、状态层先保证纪要不会丢
会议纪要最怕写完以后刷新丢失。第一版先用 localStorage 做本地保存,足够支撑轻量桌面工具的基本闭环。
const STORAGE_KEY = 'qinghui-meeting-notes';
const items = ref<青会纪要Item[]>(loadItems());
const activeId = ref(items.value[0]?.id ?? '');
function loadItems() {
const raw = localStorage.getItem(STORAGE_KEY);
return raw ? JSON.parse(raw) : seed青会纪要Items;
}
function persist() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(items.value));
}
STORAGE_KEY 使用 qinghui-meeting-notes,避免和其他工具的数据混在一起。状态层初始化时先调用 loadItems(),有本地数据就加载本地数据,没有本地数据就回到 seed。这个逻辑保证第一次打开有示例,第二次打开保留用户内容。
activeId 记录当前正在编辑哪条纪要。它取第一条数据的 id 作为默认值,数组为空时回退为空字符串。这样页面组件不需要自己猜当前条目是谁,只需要通过 activeId 和 items 派生 currentItem。
persist() 把 items 序列化后写入 localStorage。这里没有让用户手动点击保存,因为会议纪要这种工具应该默认自动保存。用户在会后整理时已经很忙,不应该再记着“写完要保存一下”。
五、行动项更新要比正文编辑更明确
会议纪要会频繁修改行动项和风险,所以更新函数要清楚。第一版可以直接用完整对象更新,后面再拆更细的字段更新。
function updateItem(nextItem: 青会纪要Item) {
items.value = items.value.map(item =>
item.id === nextItem.id ? nextItem : item
);
persist();
}
function createMeeting() {
const item: 青会纪要Item = {
id: `qinghui-${Date.now()}`,
topic: '新的会议纪要',
agenda: '补充会议议程',
decisions: '补充会议结论',
actions: '补充行动项',
risks: '补充风险点',
};
items.value = [item, ...items.value];
activeId.value = item.id;
persist();
}
updateItem 通过 id 找到要替换的纪要,避免依赖数组下标。会议列表可能会搜索、筛选或排序,数组下标不稳定,id 才是安全的更新依据。更新后立刻 persist,确保行动项刚改完就落到本地。
createMeeting 新建一条会议纪要,并把它放到列表最前面。会后整理时,新会议通常是当前最重要的对象,放在最前面更符合使用习惯。新建后立即把 activeId 切到新条目,让用户可以直接编辑,不需要再点一次列表。
默认字段里没有留空,而是写了“补充会议议程”“补充行动项”这类提示。这样列表和编辑器不会出现大片空白,也能提醒用户会议纪要应该补哪些内容。实际产品里可以把这些提示改成 placeholder,而不是字段值。
六、筛选和搜索要服务待跟进会议
会议纪要积累多了以后,用户最常找的不是所有会议,而是待跟进会议。第一版先把搜索和状态筛选放到 computed 里,避免模板承担太多逻辑。
const keyword = ref('');
const filter = ref<青会纪要Filter>('all');
const visibleItems = computed(() => {
const text = keyword.value.trim().toLowerCase();
return items.value
.filter(item => {
if (filter.value === 'open') return item.actions.trim().length > 0;
if (filter.value === 'done') return item.actions.trim().length === 0;
return true;
})
.filter(item => {
return [item.topic, item.agenda, item.decisions, item.actions, item.risks]
.join(' ')
.toLowerCase()
.includes(text);
});
});
这段 computed 先按 filter 过滤,再按 keyword 搜索。open 和 done 的判断在示例里用 actions 是否为空来表达,真实项目里可以增加 status 字段,让状态更明确。文章里这样写,是为了突出第一版最核心的待跟进逻辑。
搜索范围包括 topic、agenda、decisions、actions 和 risks。这样用户搜某个负责人、某个风险关键词、某个议题,都能找到对应纪要。搜索逻辑放在状态层,NoteSidebar 只负责展示 visibleItems,不需要知道字段如何拼接。
如果后续要按日期、负责人、会议类型筛选,也可以继续扩展 visibleItems。关键是不要把搜索规则散落在多个组件里,否则列表、导出和统计很容易各算各的。
七、页面结构突出结论和行动项
Home.vue 只负责把工具栏、会议列表和编辑区组合起来。它不直接处理 localStorage,也不直接拼 Markdown。
<template>
<main class="qinghui_meeting-page">
<MeetingToolbar
:current-item="currentItem"
@create="createMeeting"
@copy="copyCurrent"
@export="exportCurrent"
/>
<section class="meeting-workspace">
<MeetingList
:items="visibleItems"
:active-id="activeId"
@select="selectItem"
/>
<MeetingEditor
:item="currentItem"
@update="updateItem"
/>
</section>
</main>
</template>
这个模板和青瞬速记的结构不完全一样:这里的业务命名更明确,组件叫 MeetingToolbar、MeetingList、MeetingEditor,是为了强调会议纪要的执行属性。工具栏处理会后动作,列表处理会议切换,编辑器处理当前纪要。
currentItem 传给工具栏,是为了让复制和导出按钮知道当前是否有内容可操作;visibleItems 传给列表,说明列表展示的是搜索和筛选后的结果;activeId 用于高亮当前会议;updateItem 由编辑器触发,保证编辑结果回到状态层统一保存。
页面层没有出现任何 Markdown 拼接,也没有出现剪贴板 API。这样的结构说明桥接和导出被放到了更合适的位置,Home.vue 只是把各块串起来。
八、编辑器不要把行动项藏在正文里
会议纪要编辑器最重要的不是写一大段正文,而是让结论、行动项、风险分区清楚。下面是一个简化编辑器。
<script setup lang="ts">
const props = defineProps<{ item: 青会纪要Item | null }>();
const emit = defineEmits<{ update: [item: 青会纪要Item] }>();
function patchItem(partial: Partial<青会纪要Item>) {
if (!props.item) return;
emit('update', { ...props.item, ...partial });
}
</script>
<template>
<form v-if="item" class="meeting-editor">
<input
:value="item.topic"
@input="patchItem({ topic: ($event.target as HTMLInputElement).value })"
/>
<textarea
:value="item.decisions"
@input="patchItem({ decisions: ($event.target as HTMLTextAreaElement).value })"
/>
<textarea
:value="item.actions"
@input="patchItem({ actions: ($event.target as HTMLTextAreaElement).value })"
/>
</form>
</template>
这里仍然使用 patchItem 做局部更新,而不是在子组件里直接修改 props。会议纪要字段比较多,如果每个输入框都自己组完整对象,代码会很重复;用 partial 合并,可以让每个控件只关心自己负责的字段。
topic 用 input,因为它应该短而明确。decisions 和 actions 用 textarea,因为它们通常需要写多行。风险字段可以继续加一个 textarea,或者放在右侧风险面板里,让它比普通备注更醒目。
编辑器不处理保存动作,输入后通过 update 事件交给状态层。这样会议纪要在编辑过程中可以自动保存,也能保证列表、导出、复制看到的都是同一份数据。
九、复制摘要要适合发到群里
会议纪要经常需要发到工作群或项目文档里,所以复制摘要要比完整导出更短,突出结论和行动项。
function build青会纪要Summary(item: 青会纪要Item) {
return [
`# ${item.topic}`,
'',
`结论:${item.decisions}`,
'',
`行动项:${item.actions}`,
'',
`风险:${item.risks || '暂无明确风险'}`,
].join('\n');
}
这个函数只负责生成摘要文本,不直接复制。它把 topic 放成标题,下面依次写结论、行动项和风险。这样的顺序适合会后流转:读者先知道会议主题,再看定了什么,最后看谁要继续推进以及哪里可能卡住。
风险字段给了一个兜底文案“暂无明确风险”。这样导出或复制时不会出现空白段落。会议类文本里,空白风险容易让人误解为漏写;明确写暂无风险反而更稳。
摘要函数保持纯函数形态,后续可以被工具栏、快捷键、右键菜单复用。复制方式怎么变,都不影响摘要格式。
十、桌面桥接只负责复制和反馈
会议纪要的桌面能力第一版不需要很多。复制摘要和复制 Markdown 是高频动作,通知用于告诉用户动作完成。
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 };
}
桥接层先尝试使用宿主注入的 API,再回退到浏览器剪贴板。这让页面在桌面壳里可以走原生能力,在开发阶段直接跑浏览器也能复制文本。会议纪要这种工具如果没有复制兜底,调试会很不方便。
notify 只在宿主支持时调用,不强制做浏览器通知。因为浏览器通知涉及权限,而会议纪要第一版最重要的是复制成功。通知只是反馈层,不能为了它让开发流程变复杂。
这段桥接代码和业务字段完全无关。它不认识 topic、actions、risks,只认识 text 和 message。这样的边界才是稳定的桥接层。
十一、Markdown 导出要能直接流转
完整导出比复制摘要更正式,适合放进项目文档或归档。
function export青会纪要Markdown(item: 青会纪要Item) {
return [
`# ${item.topic}`,
'',
'## 会议议程',
item.agenda,
'',
'## 会议结论',
item.decisions,
'',
'## 行动项',
item.actions,
'',
'## 风险点',
item.risks || '暂无明确风险',
].join('\n');
}
async function exportCurrent() {
if (!currentItem.value) return;
const markdown = export青会纪要Markdown(currentItem.value);
await bridge.copyText(markdown);
await bridge.notify('会议纪要已复制为 Markdown');
}
导出函数按阅读顺序组织内容:议程说明背景,结论说明结果,行动项说明下一步,风险说明阻塞点。它没有把所有字段堆成表格,因为 Markdown 文档后续可能还要继续补充,分章节更适合编辑。
exportCurrent 先判断 currentItem 是否存在,再生成 Markdown,最后通过桥接层复制并通知。这里依旧保持分层:导出函数管文本结构,bridge 管复制,当前条目判断管动作安全。
如果后续支持保存到文件,只要把 markdown 传给 saveFile 即可,不需要改导出格式函数。这就是内容生成和动作执行分开的好处。
十二、样式要方便扫读
会议纪要是办公场景,视觉不需要太花,重点是层级清楚、行动项醒目、风险不被埋掉。
.qinghui_meeting-page {
min-height: 100vh;
background: #f7f8fb;
color: #1f2937;
}
.meeting-workspace {
display: grid;
grid-template-columns: 300px minmax(0, 1fr);
gap: 16px;
padding: 16px;
}
.meeting-editor {
display: grid;
gap: 14px;
}
页面背景使用浅灰,文字使用深灰,是为了保持办公工具的稳重感。会议纪要通常要长时间阅读和修改,视觉上不应该太刺激。
工作区左列 300px,比速记工具略宽,因为会议主题和日期信息通常更长。右侧编辑区使用 minmax(0, 1fr),避免长行动项把布局撑开。编辑器用 grid 和 gap 保持控件间距,让结论和行动项分区更清楚。
如果后续增加风险侧栏,可以把 grid 改成 300px minmax(0, 1fr) 280px。但第一版先保持两栏,减少认知负担。
十三、构建检查重点看主题和行动项
# 在前端应用中执行
npm install
npm run build
rg "qinghui-meeting-notes|青会纪要|会议纪要" src package.json
rg "旧标题|测试数据|TODO" src
构建命令用于确认页面和类型没有基础错误。会议纪要字段比较多,最容易出现某个组件还在读旧字段、导出函数字段名写错、默认数据没补全等问题。npm run build 至少能挡住一部分低级错误。
第一条 rg 搜索当前主题相关关键词,确认 storage key、中文标题和业务文案没有串到别的工具。第二条 rg 搜索风险词,检查是否还有旧标题、测试数据或 TODO。系列文章最怕主题串,这一步不能省。
十四、这版工具的使用闭环
用户打开青会纪要后,状态层先加载本地纪要。如果没有历史内容,就展示版本评审同步会这条示例。用户可以新建会议,也可以从左侧列表选择旧会议。
编辑过程中,topic 用来定位会议,agenda 用来说明讨论范围,decisions 用来沉淀结论,actions 用来推动会后执行,risks 用来提醒阻塞点。每次更新都会进入状态层,再保存到 localStorage。
会后需要同步给别人时,用户可以复制摘要。摘要只保留主题、结论、行动项和风险,适合快速发到群里。需要归档时,用户可以导出 Markdown,内容结构更完整,适合放进项目文档。
这一版闭环不复杂,但能解决会议纪要最常见的问题:写了很多字,却没有把下一步讲清楚。
十五、后续可以补什么
后续可以增加负责人字段,把行动项拆成数组;可以增加 dueDate,让每个行动项有截止时间;可以增加状态字段,让行动项从“待处理”流转到“已完成”;也可以增加会议模板,让评审会、同步会、客户沟通会有不同默认结构。
还可以把风险单独做成侧栏卡片,让未关闭风险始终可见;把复制摘要接入快捷键,让会议结束后快速发出;把 Markdown 导出扩展成完整文件保存,而不是只复制到剪贴板。
但这些增强都应该围绕会后执行,而不是把工具变成聊天记录仓库。青会纪要的核心不是记录所有话,而是让会议结束后的事情继续往前走。
总结
青会纪要这一版围绕“会后执行”重新组织字段和页面。它把会议主题、议程、结论、行动项和风险拆成结构化数据,再通过状态层、本地保存、复制摘要和 Markdown 导出形成闭环。
这篇和第 9 篇的写法不同:第 9 篇从灵感捕捉切入,这篇从行动项追踪切入。它们都属于桌面轻工具,但文章结构、字段逻辑和解释重点不一样。这样写出来更像不同场景下的真实开发记录,而不是同一个模板换标题。
相关资源
更多推荐

所有评论(0)