从零构建桌面写作软件的书籍管理系统:Electron + Vue 3 实战指南

📚 从零构建桌面写作软件的书籍管理系统:Electron + Vue 3 实战指南

💡 本文深入探讨了基于 Electron + Vue 3 技术栈的桌面写作软件中书籍管理系统的设计与实现,涵盖了书籍的创建、编辑、删除等核心功能的完整技术方案,为开发者提供一套完整的书籍管理解决方案。

📋 目录

🎯 项目背景

51mazi 是一款专为小说创作者设计的桌面写作软件,其核心功能之一就是完善的书籍管理系统。作者需要一个直观、高效的书籍管理界面来组织和管理自己的创作项目,包括书籍的创建、编辑、删除以及元数据管理等功能。

📖 书籍管理界面

直观的书籍管理界面 - 支持创建、编辑、删除等操作

✨ 功能特性

  • 📝 书籍创建: 支持多种类型书籍创建
  • ✏️ 书籍编辑: 实时编辑书籍信息和元数据
  • 🗑️ 书籍删除: 安全删除确认机制
  • 📊 数据统计: 字数统计和更新记录
  • 🎨 界面美观: 书籍卡片式展示
  • 🔄 实时同步: 状态管理和数据同步

🏗️ 技术架构概览

核心技术栈

  • Electron 35.0.3: 跨平台桌面应用框架
  • Vue 3.5.13: 渐进式 JavaScript 框架
  • Element Plus 2.10.1: 企业级 UI 组件库
  • Pinia 3.0.1: Vue 3 官方推荐的状态管理库

系统架构设计

scss 复制代码
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   渲染进程      │    │   主进程        │    │   文件系统      │
│   (Vue 3)      │◄──►│   (Node.js)     │◄──►│   (本地存储)    │
└─────────────────┘    └─────────────────┘    └─────────────────┘

📁 项目目录结构

bash 复制代码
51mazi/
├── src/
│   ├── main/           # Electron 主进程
│   │   └── index.js    # 主进程入口文件
│   ├── preload/        # 预加载脚本
│   │   └── index.js    # IPC 通信接口
│   └── renderer/       # 渲染进程 (Vue 应用)
│       ├── src/
│       │   ├── components/    # 组件库
│       │   │   ├── Bookshelf.vue    # 书籍列表组件
│       │   │   └── Book.vue         # 书籍卡片组件
│       │   ├── views/         # 页面视图
│       │   ├── stores/        # 状态管理
│       │   │   └── index.js   # Pinia 状态管理
│       │   ├── service/       # 服务层
│       │   │   └── books.js   # 书籍相关 API
│       │   └── utils/         # 工具函数
│       └── assets/            # 静态资源

🔧 书籍管理核心功能实现

1. 📊 书籍数据结构设计

每本书籍包含以下核心信息:

javascript 复制代码
const bookData = {
  id: 'unique_id',           // 唯一标识
  name: '书籍名称',           // 书名
  type: 'novel',             // 类型
  typeName: '小说',          // 类型名称
  targetCount: 100000,       // 目标字数
  intro: '书籍简介',         // 简介
  createdAt: '2024-01-01',  // 创建时间
  updatedAt: '2024-01-01',  // 更新时间
  totalWords: 50000          // 当前字数
}

💡 完整数据结构请查看 : src/renderer/src/components/Bookshelf.vue

2. 🗂️ 主进程文件操作层

在主进程中实现文件系统操作,确保数据持久化:

javascript 复制代码
// src/main/index.js
import { ipcMain } from 'electron'
import fs from 'fs'
import { join } from 'path'

// 创建书籍
ipcMain.handle('create-book', async (event, bookInfo) => {
  const safeName = bookInfo.name.replace(/[\\/:*?"<>|]/g, '_')
  const booksDir = store.get('booksDir')
  const bookPath = join(booksDir, safeName)
  
  // 创建书籍目录结构
  if (!fs.existsSync(bookPath)) {
    fs.mkdirSync(bookPath)
  }
  
  // 写入元数据文件
  const meta = {
    ...bookInfo,
    createdAt: new Date().toLocaleString(),
    updatedAt: new Date().toLocaleString()
  }
  fs.writeFileSync(join(bookPath, 'mazi.json'), JSON.stringify(meta, null, 2))
  
  // 创建默认目录结构
  const textPath = join(bookPath, '正文')
  const notesPath = join(bookPath, '笔记')
  fs.mkdirSync(textPath, { recursive: true })
  fs.mkdirSync(notesPath, { recursive: true })
  
  return true
})

// 删除书籍
ipcMain.handle('delete-book', async (event, { name }) => {
  const booksDir = store.get('booksDir')
  const bookPath = join(booksDir, name)
  if (fs.existsSync(bookPath)) {
    fs.rmSync(bookPath, { recursive: true })
    return true
  }
  return false
})

// 编辑书籍
ipcMain.handle('edit-book', async (event, bookInfo) => {
  const booksDir = store.get('booksDir')
  const bookPath = join(booksDir, bookInfo.name)
  if (fs.existsSync(bookPath)) {
    const metaPath = join(bookPath, 'mazi.json')
    const existingMeta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'))
    const mergedMeta = { ...existingMeta, ...bookInfo }
    fs.writeFileSync(metaPath, JSON.stringify(mergedMeta, null, 2))
    return true
  }
  return false
})

💡 完整主进程代码请查看 : src/main/index.js

3. 🔌 渲染进程服务层

在渲染进程中封装 API 调用,提供统一的接口:

javascript 复制代码
// src/renderer/src/service/books.js
export function createBook(bookInfo) {
  return window.electron.createBook(bookInfo)
}

export function updateBook(bookInfo) {
  return window.electron.editBook(bookInfo)
}

export async function deleteBook(name) {
  const dir = await getBookDir()
  return window.electron.deleteBook(dir, name)
}

export async function readBooksDir() {
  const mainStore = useMainStore()
  const dir = await getBookDir()
  if (!dir) return []
  const books = await window.electron.readBooksDir(dir)
  mainStore.setBooks(books)
  return books
}

💡 完整服务层代码请查看 : src/renderer/src/service/books.js

4. 🎨 用户界面组件设计

4.1 📚 书籍列表组件 (Bookshelf.vue)
vue 复制代码
<template>
  <div class="bookshelf">
    <!-- 顶部操作栏 -->
    <div class="top-bar">
      <el-button type="primary" @click="handleNewBook">
        <el-icon><Plus /></el-icon>
        新建书籍
      </el-button>
    </div>

    <!-- 书籍列表 -->
    <div class="books-box">
      <Book
        v-for="book in books"
        :key="book.id"
        :name="book.name"
        :type="book.type"
        :type-name="book.typeName"
        :total-words="book.totalWords"
        :updated-at="book.updatedAt"
        @on-open="onOpen(book)"
        @on-edit="onEdit(book)"
        @on-delete="onDelete(book)"
      />
    </div>
  </div>
</template>

💡 完整书籍列表组件代码请查看 : src/renderer/src/components/Bookshelf.vue

4.2 📖 书籍卡片组件 (Book.vue)
vue 复制代码
<template>
  <div class="book" @click="emit('onOpen')" @contextmenu.prevent="showMenu($event)">
    <div class="spine"></div>
    <div class="cover-bg">
      <div class="title-block">
        <div class="vertical-title">{{ name }}</div>
      </div>
    </div>
    <div class="info">
      <div class="type">{{ typeName }}</div>
      <div class="stats">
        <div class="word-count">字数:{{ totalWords }}</div>
        <div class="update-time">更新:{{ updatedAt }}</div>
      </div>
    </div>
  </div>
</template>

💡 完整书籍卡片组件代码请查看 : src/renderer/src/components/Book.vue

5. 🗃️ 状态管理设计

使用 Pinia 进行全局状态管理:

javascript 复制代码
// src/renderer/src/stores/index.js
import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useMainStore = defineStore('main', () => {
  const books = ref([])
  
  function setBooks(newBooks) {
    books.value = newBooks
  }
  
  function addBook(book) {
    books.value.push(book)
  }
  
  function removeBook(bookId) {
    const index = books.value.findIndex(book => book.id === bookId)
    if (index > -1) {
      books.value.splice(index, 1)
    }
  }
  
  return {
    books,
    setBooks,
    addBook,
    removeBook
  }
})

💡 完整状态管理代码请查看 : src/renderer/src/stores/index.js

⚙️ 核心功能实现细节

1. 📝 书籍创建流程

javascript 复制代码
async function handleConfirm() {
  formRef.value.validate(async (valid) => {
    if (valid) {
      // 校验同名书籍
      const exists = books.value.some(
        (b) => b.name === form.value.name && (!isEdit.value || b.id !== editBookId.value)
      )
      if (exists) {
        ElMessage.error('已存在同名书籍,不能重复创建!')
        return
      }
      
      const randomId = Date.now().toString() + Math.floor(Math.random() * 10000).toString()
      const bookData = {
        id: randomId,
        name: form.value.name,
        type: form.value.type,
        typeName: BOOK_TYPES.find((item) => item.value === form.value.type)?.label,
        targetCount: form.value.targetCount,
        intro: form.value.intro
      }
      
      await createBook(bookData)
      dialogVisible.value = false
      await readBooksDir()
    }
  })
}

💡 完整创建流程代码请查看 : src/renderer/src/components/Bookshelf.vue

2. ✏️ 书籍编辑功能

javascript 复制代码
function onEdit(book) {
  isEdit.value = true
  editBookId.value = book.id
  dialogVisible.value = true
  form.value.name = book.name
  form.value.type = book.type
  form.value.targetCount = book.targetCount
  form.value.intro = book.intro
}

💡 完整编辑功能代码请查看 : src/renderer/src/components/Bookshelf.vue

3. 🗑️ 书籍删除确认

javascript 复制代码
async function onDelete(book) {
  try {
    await ElMessageBox.confirm(`确定要删除《${book.name}》吗?此操作不可恢复!`, '删除确认', {
      confirmButtonText: '删除',
      cancelButtonText: '取消',
      type: 'warning'
    })
    await deleteBook(book.name)
    ElMessage.success('删除成功')
    await readBooksDir()
  } catch (e) {
    // 用户取消删除
    console.log(e)
  }
}

💡 完整删除功能代码请查看 : src/renderer/src/components/Bookshelf.vue

🎨 用户体验优化

1. 🖱️ 右键菜单支持

javascript 复制代码
function showMenu(e) {
  menuX.value = e.clientX
  menuY.value = e.clientY
  menuVisible.value = true
  document.addEventListener('click', hideMenu)
}

function hideMenu() {
  menuVisible.value = false
  document.removeEventListener('click', hideMenu)
}

💡 完整右键菜单代码请查看 : src/renderer/src/components/Book.vue

2. ✅ 表单验证

javascript 复制代码
const rules = ref({
  name: [{ required: true, message: '请输入书籍名称', trigger: 'blur' }],
  type: [{ required: true, message: '请选择类型', trigger: 'blur' }],
  targetCount: [{ required: true, message: '请输入目标字数', trigger: 'blur' }],
  intro: [{ required: true, message: '请输入简介', trigger: 'blur' }]
})

💡 完整表单验证代码请查看 : src/renderer/src/components/Bookshelf.vue

3. 💬 错误处理与用户反馈

javascript 复制代码
// 创建成功提示
ElMessage.success('创建成功')

// 删除确认
await ElMessageBox.confirm(`确定要删除《${book.name}》吗?此操作不可恢复!`, '删除确认', {
  confirmButtonText: '删除',
  cancelButtonText: '取消',
  type: 'warning'
})

💡 完整错误处理代码请查看 : src/renderer/src/components/Bookshelf.vue

⚡ 技术亮点总结

1. 🔄 跨进程通信设计

  • 使用 Electron 的 IPC 机制实现主进程与渲染进程的安全通信
  • 通过 contextBridge 暴露安全的 API 接口

2. 🗂️ 文件系统管理

  • 自动创建标准化的书籍目录结构
  • 元数据 JSON 文件存储,便于扩展和维护
  • 文件名安全处理,避免特殊字符冲突

3. 🗃️ 状态管理优化

  • 使用 Pinia 实现响应式状态管理
  • 统一的数据流,确保 UI 与数据同步

4. 🎨 用户体验设计

  • 直观的书籍卡片展示
  • 右键菜单快速操作
  • 完善的表单验证和错误提示

🔮 扩展性考虑

1. 📚 书籍类型扩展

javascript 复制代码
const BOOK_TYPES = [
  { value: 'novel', label: '小说' },
  { value: 'essay', label: '散文' },
  { value: 'poetry', label: '诗歌' },
  { value: 'script', label: '剧本' }
]

💡 完整书籍类型配置请查看 : src/renderer/src/constants/config.js

2. 📊 元数据扩展

javascript 复制代码
const bookMeta = {
  // 基础信息
  id: 'unique_id',
  name: '书籍名称',
  type: 'novel',
  
  // 扩展信息
  tags: ['标签1', '标签2'],
  status: 'writing', // writing, completed, paused
  coverImage: 'cover.jpg',
  wordCountGoal: 100000,
  
  // 统计信息
  currentWordCount: 50000,
  chaptersCount: 10,
  lastModified: '2024-01-01'
}

💡 完整元数据结构请查看 : src/renderer/src/components/Bookshelf.vue

📝 总结与展望

通过 Electron + Vue 3 技术栈,我们成功构建了一个功能完善、用户体验优秀的书籍管理系统。该系统不仅满足了基本的 CRUD 操作需求,还在用户体验、数据安全、扩展性等方面进行了深度优化。

🎯 关键成功因素

  • 🏗️ 架构清晰: 主进程负责文件操作,渲染进程负责 UI 交互
  • 🔒 数据安全: 通过 IPC 机制确保跨进程通信的安全性
  • 🎨 用户体验: 直观的界面设计和流畅的操作体验
  • 🔧 可维护性: 模块化的代码结构和统一的状态管理

🚀 技术价值

  • 跨平台支持: 基于 Electron 实现 Windows、macOS、Linux 全平台支持
  • 高性能: 使用 Vue 3 的 Composition API 和 Pinia 状态管理
  • 可扩展: 模块化的组件设计和清晰的代码结构
  • 用户友好: 完善的错误处理和用户反馈机制

这个书籍管理系统为整个写作软件奠定了坚实的基础,为后续的功能扩展提供了良好的架构支持。


📚 相关链接

🏷️ 标签

#Electron #Vue3 #书籍管理 #桌面应用 #前端开发 #状态管理 #用户体验


💡 如果这篇文章对你有帮助,请给个 ⭐️ 支持一下!

相关推荐
持续前行6 小时前
vscode 中找settings.json 配置
前端·javascript·vue.js
JosieBook6 小时前
【Vue】11 Vue技术——Vue 中的事件处理详解
前端·javascript·vue.js
安逸点6 小时前
Vue项目中使用xlsx库解析Excel文件
vue.js
一只小阿乐6 小时前
vue 改变查询参数的值
前端·javascript·vue.js·路由·router·网文·未花中文网
小酒星小杜7 小时前
在AI时代下,技术人应该学会构建自己的反Demo地狱系统
前端·vue.js·ai编程
Code知行合壹7 小时前
Pinia入门
vue.js
今天也要晒太阳4737 小时前
element表单和vxe表单联动校验的实现
vue.js
依赖_赖8 小时前
前端实现token无感刷新
前端·javascript·vue.js
hhcccchh9 小时前
学习vue第十三天 Vue3组件深入指南:组件的艺术与科学
javascript·vue.js·学习
zhengxianyi5159 小时前
ruoyi-vue-pro本地环境搭建(超级详细,带异常处理)
前端·vue.js·前后端分离·ruoyi-vue-pro