从零构建桌面写作软件的书籍管理系统: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 #书籍管理 #桌面应用 #前端开发 #状态管理 #用户体验


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

相关推荐
20262 小时前
13.2 ssr基本原理,构建步骤
前端·vue.js
前端开发爱好者2 小时前
首个「完整级」WebSocket 调试神器来了!
前端·javascript·vue.js
黑幕困兽3 小时前
vue 项目给输入框增加trim()方法
vue.js
王者鳜錸4 小时前
VUE+SPRINGBOOT从0-1打造前后端-前后台系统-文章详情、评论、点赞
前端·vue.js·spring boot
一大树4 小时前
Vue 3 中 `ref` 的“浅监听”行为解析:是误解还是真相?
前端·vue.js
海天胜景4 小时前
vue3 el-select 加载内容后 触发事件
前端·javascript·vue.js
掘金015 小时前
Vue3+Element Plus实现动态条件字段联动校验
前端·vue.js·前端框架
蒙面人5 小时前
拖动组件 vue-draggable-next 跨组件和clone问题
vue.js
蓝爱人5 小时前
vue3接收SSE流数据进行实时渲染日志
前端·javascript·vue.js