当前位置: 首页 > news >正文

小说写作中的时间轴管理:基于 Vue 3 的事序图技术实现

📊 小说写作中的时间轴管理:基于 Vue 3 的事序图技术实现

💡 本文深入探讨了在小说写作软件中实现类似甘特图的事序图功能的技术方案,包括时间轴可视化、事件管理、拖拽交互等核心功能的完整实现,为开发者提供一套完整的时间轴管理解决方案。

📋 目录

  • 项目背景
  • 技术架构设计
  • 核心功能实现
  • 拖拽交互实现
  • 数据持久化方案
  • 性能优化策略
  • 技术亮点总结
  • 总结与展望

🎯 项目背景

在小说创作过程中,作者需要管理复杂的时间线和事件序列。传统的时间线管理工具往往过于复杂,不适合小说创作场景。因此,我们在 51mazi 中设计了一个专门的事序图功能,类似甘特图但更贴近小说创作需求。

📊 事序图功能展示

事序图

可视化事序图管理 - 直观展示事件时间轴和进度

✨ 核心功能特性

  • 🕐 时间轴可视化: 基于时间单元格的可视化事件展示
  • 📝 事件管理: 支持创建、编辑、删除事件,包含简介、详情、进度等信息
  • 🖱️ 拖拽调整: 直观的拖拽操作调整事件时间位置
  • 📈 进度跟踪: 事件进度条显示,支持0-100%进度管理
  • 📚 多事序图: 支持创建多个独立的事序图,满足不同章节或故事线需求
  • 💾 数据持久化: 本地文件存储,确保数据安全

🏗️ 技术架构设计

核心技术栈

  • Vue 3.5.13: 渐进式 JavaScript 框架
  • Element Plus 2.10.1: 企业级 UI 组件库
  • Electron 35.0.3: 跨平台桌面应用框架

系统架构设计

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   渲染进程      │    │   主进程        │    │   文件系统      │
│   (Vue 3)       │◄──►│   (Node.js)     │◄──►│   (JSON存储)    │
└─────────────────┘    └─────────────────┘    └─────────────────┘

📁 组件结构设计

// src/renderer/src/views/EventsSequence.vue
<template><LayoutTool :title="`事序图 - ${bookName}`"><!-- 事序图列表 --><div class="sequence-charts"><div v-for="chart in sequenceCharts" :key="chart.id" class="sequence-chart"><!-- 甘特图表格 --><div class="gantt-table"><!-- 表格头部 --><div class="table-header">...</div><!-- 表格主体 --><div class="table-body"><div class="table-left">...</div><div class="table-right">...</div></div></div></div></div></LayoutTool>
</template>

🔧 核心功能实现

1. 数据结构设计

// 事序图数据结构
const sequenceCharts = ref([{id: 'chart-1',title: '第一章事件',cellCount: 100,events: [{id: 'event-1',index: 1,introduction: '事件简介',detail: '事件详细描述',progress: 50,startTime: 1,endTime: 5,color: '#409EFF'}]}
])

2. 时间轴渲染实现

// 时间轴单元格渲染
<div class="right-header"><div v-for="i in chart.cellCount || 100" :key="i" class="time-cell">{{ i }}</div>
</div>// 背景网格渲染
<div class="grid-background"><divv-for="rowIndex in chart.events.length":key="`row-${rowIndex}`"class="grid-row"><divv-for="colIndex in chart.cellCount || 100":key="`cell-${rowIndex}-${colIndex}`"class="grid-cell"></div></div>
</div>

3. 事件条样式计算

// 事件条样式计算
const getEventBarStyle = (event) => {if (!event.startTime || !event.endTime) return {}const left = (event.startTime - 1) * 40 // 40px 单元格宽度const width = (event.endTime - event.startTime + 1) * 40return {left: `${left}px`,width: `${width}px`,backgroundColor: event.color || '#409EFF'}
}

4. 进度条可视化

// 进度条宽度计算
const getEventProgressWidth = (event) => {const progress = typeof event.progress === 'number' ? event.progress : Number(event.progress) || 0const clamped = Math.max(0, Math.min(100, progress))return `${clamped}%`
}
/* 进度条样式 */
.event-progress {position: absolute;inset: 0;background: rgba(255, 255, 255, 0.22);width: 0%;transition: width 0.3s ease;pointer-events: none;background-image: repeating-linear-gradient(45deg,transparent,transparent 4px,rgba(255, 255, 255, 0.1) 4px,rgba(255, 255, 255, 0.1) 8px);animation: progressStripes 1s linear infinite;
}@keyframes progressStripes {0% { background-position: 0 0; }100% { background-position: 8px 8px; }
}

🖱️ 拖拽交互实现

1. 拖拽状态管理

// 拖拽相关状态
const draggingEvent = ref(null)
const dragStartX = ref(0)
const dragStartLeft = ref(0)
const isDragging = ref(false)
const hasMovedWhileMouseDown = ref(false)
const isPointerDown = ref(false)
let clickSuppressTimer = null

2. 拖拽开始处理

const startDrag = (event, eventData) => {event.preventDefault()draggingEvent.value = eventDatadragStartX.value = event.clientXdragStartLeft.value = (eventData.startTime - 1) * 40hasMovedWhileMouseDown.value = falseisPointerDown.value = true// 添加全局鼠标事件监听document.addEventListener('mousemove', handleDrag)document.addEventListener('mouseup', stopDrag)// 设置拖拽状态isDragging.value = true// 添加拖拽样式document.body.style.cursor = 'grabbing'document.body.style.userSelect = 'none'document.body.classList.add('dragging')
}

3. 拖拽移动处理

const handleDrag = (event) => {if (!isDragging.value || !draggingEvent.value) returnconst deltaX = event.clientX - dragStartX.valueif (Math.abs(deltaX) > 3) {hasMovedWhileMouseDown.value = true}const newLeft = dragStartLeft.value + deltaX// 计算新的时间位置(基于40px单元格宽度)const newStartTime = Math.round(newLeft / 40) + 1// 边界检查const chart = sequenceCharts.value.find((c) => c.events.includes(draggingEvent.value))if (chart) {const eventDuration = draggingEvent.value.endTime - draggingEvent.value.startTime + 1const maxStartTime = chart.cellCount - eventDuration + 1// 限制在有效范围内const clampedStartTime = Math.max(1, Math.min(newStartTime, maxStartTime))const clampedEndTime = clampedStartTime + eventDuration - 1// 更新事件时间draggingEvent.value.startTime = clampedStartTimedraggingEvent.value.endTime = clampedEndTime}
}

4. 拖拽结束处理

const stopDrag = async () => {if (!isDragging.value || !draggingEvent.value) return// 显示拖拽结果const chart = sequenceCharts.value.find((c) => c.events.includes(draggingEvent.value))if (chart) {if (hasMovedWhileMouseDown.value) {ElMessage.success(`事件"${draggingEvent.value.introduction}"已移动到时间 ${draggingEvent.value.startTime}-${draggingEvent.value.endTime}`)await saveSequenceCharts()// 拖动结束后,短暂抑制 mouseup 触发编辑if (clickSuppressTimer) clearTimeout(clickSuppressTimer)clickSuppressTimer = setTimeout(() => {hasMovedWhileMouseDown.value = falseclickSuppressTimer = null}, 120)}}// 清理拖拽状态isDragging.value = falsedraggingEvent.value = null// 移除全局事件监听document.removeEventListener('mousemove', handleDrag)document.removeEventListener('mouseup', stopDrag)// 恢复样式document.body.style.cursor = ''document.body.style.userSelect = ''document.body.classList.remove('dragging')isPointerDown.value = false
}

5. 点击与拖拽冲突解决

// mouseup 时决定是否打开弹框(仅未发生拖动)
const onEventBarMouseUp = (chartId, event) => {if (hasMovedWhileMouseDown.value) {// 刚刚发生过拖动,不打开return}openEventEditor(chartId, event)
}

💾 数据持久化方案

1. 主进程文件操作

// src/main/index.js
const { ipcMain } = require('electron')
const fs = require('fs').promises
const path = require('path')// 读取事序图数据
ipcMain.handle('read-sequence-charts', async (event, bookId) => {try {const bookDir = path.join(booksDirectory, bookId)const filePath = path.join(bookDir, 'sequence-charts.json')// 确保目录存在await fs.mkdir(bookDir, { recursive: true })const data = await fs.readFile(filePath, 'utf8')return JSON.parse(data)} catch (error) {console.error('读取事序图数据失败:', error)return []}
})// 写入事序图数据
ipcMain.handle('write-sequence-charts', async (event, bookId, data) => {try {const bookDir = path.join(booksDirectory, bookId)const filePath = path.join(bookDir, 'sequence-charts.json')// 确保目录存在await fs.mkdir(bookDir, { recursive: true })await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8')return { success: true }} catch (error) {console.error('保存事序图数据失败:', error)return { success: false, error: error.message }}
})

2. 预加载脚本暴露API

// src/preload/index.js
const { contextBridge, ipcRenderer } = require('electron')contextBridge.exposeInMainWorld('electron', {// 事序图相关APIreadSequenceCharts: (bookId) => ipcRenderer.invoke('read-sequence-charts', bookId),writeSequenceCharts: (bookId, data) => ipcRenderer.invoke('write-sequence-charts', bookId, data)
})

3. 渲染进程数据管理

// 加载事序图数据
const loadSequenceCharts = async () => {try {const data = await window.electron.readSequenceCharts(currentBookId.value)sequenceCharts.value = data || []} catch (error) {console.error('加载事序图数据失败:', error)ElMessage.error('加载事序图数据失败')}
}// 保存事序图数据
const saveSequenceCharts = async () => {try {const result = await window.electron.writeSequenceCharts(currentBookId.value, sequenceCharts.value)if (!result.success) {throw new Error(result.error)}} catch (error) {console.error('保存事序图数据失败:', error)ElMessage.error('保存事序图数据失败')}
}// 监听数据变化自动保存
watch(sequenceCharts, saveSequenceCharts, { deep: true })

⚡ 性能优化策略

1. 虚拟滚动优化

// 对于大量事件的情况,可以考虑虚拟滚动
const visibleEvents = computed(() => {const start = scrollTop.value / itemHeightconst end = start + visibleCount.valuereturn sequenceCharts.value.slice(start, end)
})

2. 事件防抖处理

// 拖拽过程中的防抖处理
const debouncedSave = debounce(saveSequenceCharts, 300)// 在拖拽过程中使用防抖保存
const handleDrag = (event) => {// ... 拖拽逻辑debouncedSave()
}

3. 内存优化

// 组件卸载时清理定时器
onUnmounted(() => {if (clickSuppressTimer) {clearTimeout(clickSuppressTimer)}// 清理全局事件监听document.removeEventListener('mousemove', handleDrag)document.removeEventListener('mouseup', stopDrag)
})

🎯 技术亮点总结

1. 创新的交互设计

  • 拖拽与点击分离: 通过 mouseup 事件和位移检测,完美解决拖拽与点击的冲突
  • 视觉反馈: 拖拽时的视觉状态变化,提升用户体验
  • 边界限制: 智能的边界检查,防止事件拖拽到无效位置

2. 高性能渲染

  • 分层渲染: 背景网格、事件条分层渲染,提升性能
  • CSS动画: 使用CSS动画实现进度条效果,性能更优
  • 响应式设计: 基于Vue 3的响应式系统,数据变化自动更新UI

3. 数据持久化

  • 本地存储: 基于文件系统的本地存储,数据安全可靠
  • 自动保存: 数据变化自动保存,防止数据丢失
  • 错误处理: 完善的错误处理机制,提升系统稳定性

🔮 总结与展望

本文详细介绍了在小说写作软件中实现事序图功能的技术方案。通过Vue 3的响应式系统、Element Plus的UI组件、以及精心设计的交互逻辑,我们实现了一个功能完善、用户体验优秀的时间轴管理工具。

技术优势

  • 高性能: 基于Vue 3的响应式系统,渲染性能优秀
  • 易扩展: 模块化设计,易于功能扩展
  • 用户友好: 直观的拖拽交互,操作简单
  • 数据安全: 本地文件存储,数据安全可靠

未来优化方向

  • 🔮 实时协作: 支持多用户实时协作编辑
  • 🔮 模板系统: 提供预设的事件模板
  • 🔮 导出功能: 支持导出为图片或PDF格式
  • 🔮 AI辅助: 集成AI功能,智能推荐事件安排

通过这套技术方案,我们为小说创作者提供了一个强大而直观的时间轴管理工具,大大提升了创作效率和体验。


📚 相关链接

  • 项目地址: GitHub - 51mazi,给个 Star 哦~
  • 技术栈: Electron + Vue 3 + Element Plus
  • 关键词: 事序图、甘特图、时间轴管理、拖拽交互、Vue 3

🏷️ 标签

#Vue3 #ElementPlus #事序图 #甘特图 #时间轴管理 #拖拽交互 #小说写作 #前端开发


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


文章转载自:

http://Oh3dAWFr.rLksq.cn
http://9SkFOpVm.rLksq.cn
http://pIeAF4Hz.rLksq.cn
http://vPiwtJ3i.rLksq.cn
http://hVgmNAOE.rLksq.cn
http://5wTAWqrf.rLksq.cn
http://pU3tA4ov.rLksq.cn
http://t9l5qba1.rLksq.cn
http://nhcMBxdA.rLksq.cn
http://4iFc8MkN.rLksq.cn
http://1BUxTzP6.rLksq.cn
http://05WnUJmb.rLksq.cn
http://8YBDXWIF.rLksq.cn
http://PIJlA6Qr.rLksq.cn
http://GDJDgtid.rLksq.cn
http://c6tkU2tB.rLksq.cn
http://56RUSszf.rLksq.cn
http://1XmlhmqO.rLksq.cn
http://sUPQ6rLu.rLksq.cn
http://zdmHb7dS.rLksq.cn
http://rHARzOOJ.rLksq.cn
http://r1Xmc0Qv.rLksq.cn
http://yf658JOk.rLksq.cn
http://2oc6OxC1.rLksq.cn
http://5AeHEQWi.rLksq.cn
http://QwADi9PB.rLksq.cn
http://CuocqiIl.rLksq.cn
http://6vmbUXm6.rLksq.cn
http://bf5JCz8r.rLksq.cn
http://I3x6s3ip.rLksq.cn
http://www.dtcms.com/a/377139.html

相关文章:

  • 计算机视觉与深度学习 | 计算机视觉中线特征提取与匹配算法综述
  • DAPP智能合约系统:技术解析与实现指南
  • AutoTrack-IR-DR200仿真导航实验详解:为高校打造的机器人学习实践平台
  • [模块教学]VK16K33_8×16LED矩阵屏的驱动以及技术文档,矩阵屏, 详细配置说明
  • BMT-370:开启智能楼宇通信新时代
  • stm32中 中断和事件的区别
  • Android开发入门系列教程
  • CSS 权重(优先级规则)
  • 快速搭建open-webui
  • Qt 信号-槽函数(signal - slot)
  • 机器学习算法之Boosting
  • Ubuntu 20.04手动安装.NET 8 SDK
  • NSGA-II多目标优化算法:原理、应用与实现
  • 盼之代售 最新版 decode__1174
  • maven , mvn 运行 项目
  • WPF常见问题清单
  • Devops-Hi Git
  • Maven多环境配置指南:用Profile实现开发/测试/生产环境无缝切换
  • python常量变量运算符
  • JDBC接口
  • 图形基础算法:如何将点与带曲线边的多边形位置关系算法做稳定
  • 深圳南柯电子|EMC干扰问题整改:患者安全优先的零风险操作方案
  • Java全栈开发面试实战:从基础到微服务的完整技术栈解析
  • 关于发布生成式人工智能服务已备案信息的公告(2025年7月至8月)
  • 深度学习基本模块:ConvTranspose1D 一维转置卷积层
  • Flink Agents:基于Apache Flink的事件驱动AI智能体框架
  • JavaSSM框架-MyBatis 框架(四)
  • 网络编程基础知识总结:Socket与TCP通信
  • 《TCP协议基础》
  • 1234567