前言

先放社区入口:鸿蒙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 写得太泛,后续回找会很痛苦。

agendadecisions 要分开。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 篇从灵感捕捉切入,这篇从行动项追踪切入。它们都属于桌面轻工具,但文章结构、字段逻辑和解释重点不一样。这样写出来更像不同场景下的真实开发记录,而不是同一个模板换标题。


相关资源

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐