Electron + Vue3 + AI 做了一个新闻生成器:从 0 到 1 的完整实战记录

Electron + Vue3 + AI 做了一个新闻生成器:从 0 到 1 的完整实战记录

一个周末,一杯咖啡,一个能自动生成精美新闻的桌面应用诞生了。本文将完整记录这个项目从构思到落地的全过程,包含技术选型、踩坑记录、设计思路和代码实现。


目录

  1. 项目背景:为什么要做这个东西?
  2. [技术选型:为什么选 Electron + Vue3?](#技术选型:为什么选 Electron + Vue3? "#%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B%E4%B8%BA%E4%BB%80%E4%B9%88%E9%80%89-electron--vue3")
  3. 架构设计:主进程与渲染进程的分工
  4. [核心功能实现:AI 新闻生成](#核心功能实现:AI 新闻生成 "#%E6%A0%B8%E5%BF%83%E5%8A%9F%E8%83%BD%E5%AE%9E%E7%8E%B0ai-%E6%96%B0%E9%97%BB%E7%94%9F%E6%88%90")
  5. [UI 设计:iPhone 模拟器的巧妙实现](#UI 设计:iPhone 模拟器的巧妙实现 "#ui-%E8%AE%BE%E8%AE%A1iphone-%E6%A8%A1%E6%8B%9F%E5%99%A8%E7%9A%84%E5%B7%A7%E5%A6%99%E5%AE%9E%E7%8E%B0")
  6. 踩坑记录:那些让我抓狂的问题
  7. 项目总结与展望

项目背景:为什么要做这个东西?

事情是这样的。

上周我在刷技术资讯的时候,突然想到一个问题:能不能做一个工具,让我输入一个主题,它就自动帮我搜索相关新闻,然后生成一个精美的、可以直接展示的 HTML。

图:应用主界面,左侧输入新闻主题,右侧显示生成的新闻列表

这个想法来自于几个痛点:

  1. 信息过载:每天要刷十几个技术公众号、论坛、新闻网站,信息太分散,看得头大
  2. 展示不统一:不同来源的新闻格式各异,复制粘贴后排版乱七八糟,还得重新调样式
  3. 时间成本高:手动整理新闻、设计展示样式太费时间,有这时间不如多写两行代码

于是,我决定动手做一个智能新闻生成器。

核心需求很简单:

  • 输入主题(比如"人工智能最新进展")
  • 自动搜索相关新闻
  • 生成美观的 HTML 展示页面
  • 支持在桌面端查看

技术选型:为什么选 Electron + Vue3?

桌面端方案对比

在开始前,我对比了几种桌面端开发方案:

方案 优点 缺点 适用场景
Electron 生态成熟、Vue/React 都能用、跨平台 包体积大、内存占用高 复杂桌面应用
Tauri 包体积小、性能好、Rust 安全 生态较新、学习成本高 轻量级应用
Flutter Desktop UI 统一、性能不错 Dart 学习成本、生态不成熟 跨平台应用
原生开发 性能最好、体验最佳 开发成本高、维护困难 大型专业软件

最终选择 Electron 的原因:

  1. 技术栈熟悉:我对 Vue3 + TypeScript 很熟,可以直接上手
  2. 生态完善:electron-vite 让项目搭建变得超级简单
  3. 跨平台:一套代码跑 Windows/macOS/Linux
  4. AI 集成方便:Node.js 环境下调用 OpenAI API 很顺畅

项目初始化

用 electron-vite 脚手架,一行命令搞定:

bash 复制代码
npm create electron-vite@latest news-html -- --template vue-ts

这个项目模板已经配置好了:

  • Electron 主进程 + 预加载脚本 + 渲染进程的结构
  • Vite 作为构建工具(热更新飞快)
  • TypeScript 类型支持
  • Vue 3 组合式 API

架构设计:主进程与渲染进程的分工

Electron 的核心概念是**主进程(Main Process)和渲染进程(Renderer Process)**的分离:

arduino 复制代码
┌─────────────────────────────────────────────────────────────┐
│                      Electron App                           │
├─────────────────────────────────────────────────────────────┤
│  ┌──────────────┐         ┌──────────────────────────────┐  │
│  │  Main Process│         │     Renderer Process         │  │
│  │  (Node.js)   │◄───────►│     (Chromium + Vue3)        │  │
│  │              │   IPC   │                              │  │
│  │ • 窗口管理    │         │ • UI 渲染                    │  │
│  │ • API 调用    │         │ • 用户交互                   │  │
│  │ • 文件系统    │         │ • 数据展示                   │  │
│  └──────────────┘         └──────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

我的分工设计

主进程(src/main/)负责:

  • 创建和管理应用窗口
  • 调用 OpenAI API 生成新闻
  • 处理 IPC 通信

渲染进程(src/renderer/)负责:

  • 用户界面展示
  • 用户输入处理
  • 新闻列表渲染

关键代码:主进程窗口创建

typescript 复制代码
// src/main/index.ts
function createWindow(): void {
  const mainWindow = new BrowserWindow({
    width: 1280,
    height: 720,
    webPreferences: {
      preload: join(__dirname, '../preload/index.js'),
      sandbox: false
    }
  })

  // 加载渲染进程页面
  if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
    mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
  } else {
    mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
  }
}

IPC 通信:连接两个世界

主进程和渲染进程通过 IPC(进程间通信)交换数据:

typescript 复制代码
// 渲染进程发送消息
window.electron.ipcRenderer.send('generate-news', '人工智能最新进展')

// 主进程监听并处理
ipcMain.on('generate-news', (event, arg) => {
  generateNews(arg).then((news) => {
    event.reply('generate-news', news)
  })
})

核心功能实现:AI 新闻生成

这是整个项目最核心、也最有趣的部分。

设计思路

我不想只是简单地调用 API 返回文字,而是希望:

  1. 联网搜索:获取最新的真实新闻,而不是训练数据里的旧信息
  2. 结构化输出:返回 JSON 格式,方便前端解析
  3. 自动生成 HTML:每条新闻都附带精美的 HTML 样式

OpenAI API 调用

使用了 OpenAI 的 Responses API,支持联网搜索:

typescript 复制代码
// src/main/client.ts
const response = await openai.responses.create({
  model: MODEL_ID,
  input: [
    { role: 'system', content: SYSTEM_PROMPT },
    { role: 'user', content: USER_PROMPT_TEMPLATE(input) }
  ],
  // 启用联网搜索
  tools: [{ type: 'web_search' }],
  // 强制返回 JSON 格式
  text: {
    format: {
      type: 'json_schema',
      schema: {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            title: { type: 'string' },
            source: { type: 'string' },
            date: { type: 'string' },
            content: { type: 'string' },
            url: { type: 'string' },
            html: { type: 'string' }  // 关键:包含 HTML 样式
          }
        }
      }
    }
  }
})

Prompt 工程:让 AI 生成漂亮的 HTML

Prompt 设计是这个功能的灵魂。我花了很长时间调试,最终版本包含:

1. 明确的角色设定

css 复制代码
你是一个专业的新闻内容生成助手,擅长搜索最新新闻并设计精美的HTML展示页面。

2. 详细的设计规范

  • 移动端优先(375-430px 宽度适配)
  • 内联样式(所有 CSS 用 style 属性)
  • 视觉层次清晰
  • 现代简约风格

3. 具体的样式参数

diff 复制代码
卡片设计:
- 背景:白色或 #f8f9fa
- 边框:1px solid #e9ecef
- 阴影:0 2px 8px rgba(0,0,0,0.05)
- 圆角:8-12px
- 内边距:20-24px

标题设计:
- 字体大小:20-22px
- 字重:600-700
- 颜色:#212529

效果展示

输入:"帮我查找 3 条最近一周关于 AI 的新闻"

输出:结构化的 JSON 数据,每条新闻包含:

  • 标题、来源、日期、正文
  • 一段完整的、带内联样式的 HTML 代码

这样前端只需要用 v-html 直接渲染就行,超级方便!

图:输入主题后,AI 自动搜索并生成新闻列表


UI 设计:iPhone 模拟器的巧妙实现

为了让新闻展示更有沉浸感,我设计了一个 iPhone 模拟器界面。

设计思路

  • 用一个 SVG 作为 iPhone 外框
  • 内部是一个可滚动的内容区域
  • 新闻 HTML 渲染在"手机屏幕"里

组件结构

vue 复制代码
<!-- src/renderer/src/components/Iphone.vue -->
<template>
  <div class="iphone-container">
    <!-- iPhone 外框 SVG -->
    <img src="@/assets/iphone.svg" class="iphone" />

    <!-- 拖动区域 -->
    <div class="drag-area"></div>

    <!-- 关闭按钮 -->
    <div class="close-btn" @click="closeWindow"></div>

    <!-- 屏幕内容区 -->
    <div class="iphone-screen">
      <div class="screen-top"></div>
      <div class="screen-content">
        <slot></slot>  <!-- 新闻内容插槽 -->
      </div>
      <div class="screen-bottom"></div>
    </div>
  </div>
</template>

关键 CSS 技巧

scss 复制代码
.iphone-screen {
  position: absolute;
  padding-left: 23px;   // 左边框宽度
  padding-right: 22px;  // 右边框宽度

  .screen-content {
    flex: 1;
    overflow-y: auto;   // 可滚动
    border-radius: 0 0 49px 51px;  // 底部圆角适配

    // 隐藏滚动条
    &::-webkit-scrollbar {
      display: none;
    }
  }
}

窗口拖动实现

Electron 支持 -webkit-app-region: drag 让元素可拖动:

scss 复制代码
.drag-area {
  position: absolute;
  top: 0;
  width: 280px;
  height: 40px;
  -webkit-app-region: drag;  // 关键属性
}

这样用户就可以拖动 iPhone 顶部的"刘海"区域来移动窗口。

图:点击新闻后弹出 iPhone 模拟器窗口,展示新闻详情


踩过的坑

坑 1:同一个 Vue 应用要同时充当主界面和详情界面

问题 :我的设计是点击新闻列表后,弹出一个新窗口显示详情。但两个窗口都加载同一个 index.html,怎么区分哪个是主窗口、哪个是详情窗口?

解决:用 IPC 通信传递窗口类型标识:

typescript 复制代码
// 主进程创建详情窗口后,等页面加载完发送窗口类型
detailWindow.webContents.on('did-finish-load', () => {
  detailWindow.webContents.send('window-type', 'detail')
  detailWindow.webContents.send('load-news-detail', news)
})
typescript 复制代码
// 渲染进程根据类型渲染不同内容
const isDetailWindow = ref(false)

onMounted(() => {
  window.electron.ipcRenderer.on('window-type', (_, type) => {
    if (type === 'detail') {
      isDetailWindow.value = true
    }
  })
})

然后用 v-if 判断渲染主界面还是详情界面:

vue 复制代码
<template>
  <div v-if="isDetailWindow && detailNews" class="news-detail">
    <Iphone>
      <div class="phone-content" v-html="detailNews.html"></div>
    </Iphone>
  </div>
  <el-container v-else class="container">
    <!-- 主界面内容 -->
  </el-container>
</template>

坑 2:AI 返回的 JSON 格式不稳定

问题 :我要求 OpenAI 返回 JSON 格式,但有时候它会返回 markdown 代码块(json ... ),有时候又会漏掉某些字段,导致 JSON.parse 报错。

解决 :在 parser.ts 里写了一个健壮的解析函数:

typescript 复制代码
export function parseNewsResponse(response: any): ParsedNewsResponse {
  try {
    const outputText = response.output_text || response.output?.[0]?.text

    if (!outputText) {
      return { success: false, data: [], error: '响应中没有找到输出内容' }
    }

    let parsedData: any
    try {
      parsedData = JSON.parse(outputText)
    } catch (parseError) {
      // 尝试清理 markdown 代码块标记后再解析
      const cleaned = outputText.replace(/```json\s*|\s*```/g, '')
      parsedData = JSON.parse(cleaned)
    }

    // 防御性编程:确保每个字段都有值
    const newsItems: NewsItem[] = []
    for (const item of parsedData) {
      newsItems.push({
        title: item.title || '',
        source: item.source || '',
        date: item.date || '',
        content: item.content || '',
        url: item.url || '',
        html: item.html || ''
      })
    }

    return { success: true, data: newsItems }
  } catch (error) {
    return { success: false, data: [], error: `解析失败: ${error}` }
  }
}

坑 3:iPhone 屏幕内容区域尺寸调死人

问题 :iPhone 的 SVG 外框有边框宽度,内容区域要正好卡在屏幕里面,不能超出圆角,也不能留太多白边。我调了 N 次 padding 和 border-radius。

解决:最终试出来的参数:

scss 复制代码
.iphone-screen {
  padding-left: 23px;   // 左边框宽度
  padding-right: 22px;  // 右边框宽度(右边比左边少 1px,因为 SVG 不对称)

  .screen-content {
    border-bottom-right-radius: 49px;
    border-bottom-left-radius: 51px;  // 左右圆角也不一样大!
  }
}

这个真的是像素级调试,每次改完都要重启应用看效果,烦死了。

坑 4:关闭按钮和拖动区域重叠

问题:iPhone 顶部的刘海区域要支持拖动窗口,但右上角又要放关闭按钮。如果拖动区域盖住了关闭按钮,就点不到关闭;如果关闭按钮在拖动区域上面,又拖不动窗口。

解决 :用 z-index 分层,并且把拖动区域做窄一点:

scss 复制代码
.drag-area {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 280px;  // 只覆盖中间部分
  height: 40px;
  z-index: 20;
  -webkit-app-region: drag;
}

.close-btn {
  position: absolute;
  top: 0;
  right: 0;
  width: 30px;
  height: 30px;
  z-index: 30;  // 比拖动区域高
  cursor: pointer;
}

拖动区域只覆盖顶部中间(刘海位置),关闭按钮放在右上角,两者不重叠。

坑 5:Prompt 调了好几个小时

问题:AI 生成的 HTML 样式总是不符合预期,有时候卡片太宽占满屏幕,有时候颜色太花哨,有时候又忘记加按钮。

解决 :在 prompt.ts 里写了一个超详细的 Prompt,包括:

  1. 明确的角色设定:"你是一个专业的新闻内容生成助手"
  2. 详细的设计规范:颜色、字体、间距、阴影都给出具体数值
  3. 完整的 HTML 示例:直接给一个样板代码让 AI 参考
  4. 强调约束条件:移动端优先、内联样式、不要占满屏幕等

Prompt 工程真的是个体力活,改了十几次才稳定。

坑 6:详情窗口关闭后再打不开

问题 :一开始我用 win.close() 关闭详情窗口,但再次点击新闻时,窗口对象还在内存里,只是隐藏了,再次 show() 会报错。

解决 :改用 win.destroy() 彻底销毁窗口,每次打开详情都创建新实例:

typescript 复制代码
ipcMain.on('close-current-window', (event) => {
  const webContents = event.sender
  const win = BrowserWindow.fromWebContents(webContents)
  if (win) {
    win.destroy()  // 彻底销毁,不是 close()
  }
})

项目总结与展望

已完成的功能

✅ 智能新闻搜索与生成 ✅ 精美的 HTML 自动渲染 ✅ iPhone 模拟器展示效果 ✅ 多窗口管理(主窗口 + 详情窗口) ✅ 跨平台支持(Windows/macOS/Linux)

技术收获

这次做下来,学到了不少东西:

  1. Electron 开发流程:主进程/渲染进程分工、IPC 通信、窗口管理,一开始搞不清谁干啥,后来就顺手了
  2. AI Prompt 工程:调Prompt调到想砸键盘,改了十几次才稳定,终于知道怎么让AI乖乖返回JSON了
  3. Vue3 + TypeScript:组合式API用起来挺爽的,类型安全确实能少踩坑
  4. 桌面端 UI 设计:无边框窗口、自定义标题栏、拖动区域,这些细节调起来真费劲

未来可以做的优化

🔲 缓存机制 :避免重复搜索相同主题 🔲 导出功能 :支持导出为 PDF 或图片 🔲 历史记录 :保存生成过的新闻 🔲 自定义样式 :让用户选择不同的 HTML 模板 🔲 多语言支持:英文新闻生成


写在最后

这个项目从构思到完成,大概花了一个周末的时间。

说实话,最耗时的不是写代码,而是调试 Prompt------让 AI 稳定地返回符合要求的 JSON 和 HTML,真的需要反复尝试。调到想砸键盘,改了十几次才稳定。

但看到最终效果的那一刻,值了!

输入"量子计算最新进展",点击生成,过一段时间后,一个精美的 iPhone 界面弹出,里面是最新的量子计算新闻,排版精美、信息完整。

图:完整的新闻生成和展示流程

虽然过程有点折腾,但做出来之后确实挺爽的。


源码与使用

项目已开源,欢迎 Star 和 PR:

bash 复制代码
# 克隆项目
git clone https://github.com/black542684/news-html.git
cd news-html

# 安装依赖
yarn

# 配置 OpenAI API Key
cp .env.example .env
# 编辑 .env 文件,填入你的 API Key

# 启动开发模式
yarn dev

# 打包构建
yarn build:win    # Windows
yarn build:mac    # macOS
yarn build:linux  # Linux

感谢阅读!如果觉得有用,欢迎点赞、收藏、转发~

有任何问题,欢迎在评论区留言交流!


相关推荐
sell-cloud8 小时前
Prompt Engineering要凉了?Fable 5.5能“猜“意图,但银行正在封杀AI Agent——企业AI的真正护城河是什么
人工智能·prompt
java1234_小锋8 小时前
【技术专题】HTML5 网页技术 - HTML5 Canvas 基础以及动画入门
前端·html·html5
Navicat中国10 小时前
Navicat 18 内置 AI 助手让数据库开发从繁琐操作迈向智能协作
大数据·人工智能·数据库开发
4SAPI10 小时前
多模型请求路由怎么设计?成本、速度和能力如何平衡
人工智能·python·gateway·php
魔乐社区10 小时前
翼芯・智算测试与适配优化专区,让国产算力“选得准、跑得通、用得好“
人工智能·开源·大模型
知了学历孙老师11 小时前
小自考的校考和统考,出题方和难度差在哪
java·大数据·前端
XCDoris12 小时前
郴州logo设计机构
大数据·人工智能·python
Goboy12 小时前
一个人带三个Agent,我用全新TRAE从0到1搭了个资讯周刊站
人工智能·trae
俊哥V12 小时前
每日 AI 研究简报 · 2026-10-19
人工智能·ai
AI_AGENT_DEV_AI13 小时前
AI英语教培智能体的开发
人工智能