### Install Project Dependencies
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-1.md
Installs all necessary packages for the project. Run this command in the project's root directory.
```bash
npm install
```
--------------------------------
### Build for Windows
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-1.md
Compiles and packages the application for Windows deployment. This command generates an executable installer for Windows users.
```bash
npm run build:win
```
--------------------------------
### Start Development Server
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-1.md
Launches the development server for rapid prototyping and testing. This command enables hot module replacement and other development-friendly features.
```bash
npm run dev
```
--------------------------------
### Canvas Tool Usage Pattern
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/src/renderer/src/composables/map/STATUS.md
Demonstrates how to import and initialize canvas composables and tools within a Vue setup function.
```javascript
// 1. 导入 composables
import { useCanvasState } from '@renderer/composables/map/useCanvasState'
import { usePencilTool } from '@renderer/composables/map/tools/usePencilTool'
// 2. 在 setup 中使用
const canvasState = useCanvasState()
const pencilTool = usePencilTool({ ... })
// 3. 在事件处理中调用
function handleMouseDown(e) {
if (tool.value === 'pencil') {
pencilTool.onMouseDown(getCanvasPos(e))
}
}
```
--------------------------------
### MapDesign.vue Usage Example
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/src/renderer/src/composables/map/README.md
Demonstrates how to import and utilize various composables for canvas state management, coordinate transformation, rendering, and tool functionalities within the MapDesign.vue component. Requires setup of refs for canvas and editor container, and state management for color, size, and opacity.
```javascript
import { useCanvasState } from '@renderer/composables/map/useCanvasState'
import { useCoordinate } from '@renderer/composables/map/useCoordinate'
import { useHistory, HistoryManager } from '@renderer/composables/map/useHistory'
import { useElements } from '@renderer/composables/map/useElements'
import { useRender } from '@renderer/composables/map/useRender'
import { usePencilTool } from '@renderer/composables/map/tools/usePencilTool'
import { useEraserTool } from '@renderer/composables/map/tools/useEraserTool'
// ... 其他工具
export default {
setup() {
// 基础状态
const canvasRef = ref(null)
const editorContainerRef = ref(null)
const canvasState = useCanvasState()
const elements = useElements()
const { history } = useHistory(canvasRef)
// 坐标转换
const { getCanvasPos } = useCoordinate(
canvasRef,
editorContainerRef,
canvasState.scale,
canvasState.scrollX,
canvasState.scrollY
)
// 渲染
const renderFunctions = useRender(canvasRef, canvasState.scale)
// 工具
const pencilTool = usePencilTool({
canvasRef,
elements,
history,
renderCanvas,
color,
size,
opacity
})
const eraserTool = useEraserTool({
canvasRef,
history,
size
})
// 根据当前工具调用对应的方法
function handleCanvasMouseDown(e) {
const pos = getCanvasPos(e)
if (tool.value === 'pencil') {
pencilTool.onMouseDown(pos)
} else if (tool.value === 'eraser') {
eraserTool.onMouseDown(pos)
}
// ... 其他工具
}
// ...
}
}
```
--------------------------------
### Setup Function with Composables in Vue
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/src/renderer/src/composables/map/EXAMPLE_USAGE.md
Initializes refs and utilizes various composables within the setup function to manage canvas state, elements, history, coordinates, rendering, and tool interactions. This structure separates concerns and improves code organization.
```javascript
export default {
setup() {
// 基础 refs
const canvasRef = ref(null)
const editorContainerRef = ref(null)
const tool = ref('pencil')
const color = ref('#222222')
const size = ref(5)
const opacity = ref(100)
const backgroundColor = ref('#ffffff')
// 基础 composables
const canvasState = useCanvasState()
const elements = useElements()
const { history } = useHistory(canvasRef)
// 坐标转换
const { getCanvasPos } = useCoordinate(
canvasRef,
editorContainerRef,
canvasState.scale,
canvasState.scrollX,
canvasState.scrollY
)
// 渲染函数
const renderFunctions = useRender(canvasRef, canvasState.scale)
// 画布管理
const { renderCanvas, canvasWrapStyle } = useCanvas(
canvasRef,
editorContainerRef,
canvasState,
elements,
{
currentFreeDrawPath: elements.currentFreeDrawPath,
currentShape: elements.currentShape,
drawingActive: ref(false) // 需要从工具中获取
},
renderFunctions,
backgroundColor,
tool,
undefined, // selectedElementIds - 待实现
undefined, // isSelecting - 待实现
undefined, // selectionStart - 待实现
undefined // selectionEnd - 待实现
)
// 工具 composables
const pencilTool = usePencilTool({
canvasRef,
elements,
history,
renderCanvas,
color,
size,
opacity
})
const eraserTool = useEraserTool({
canvasRef,
history,
size
})
// 事件处理
function handleCanvasMouseDown(e) {
const pos = getCanvasPos(e)
if (tool.value === 'pencil') {
pencilTool.onMouseDown(pos)
} else if (tool.value === 'eraser') {
eraserTool.onMouseDown(pos)
}
// ... 其他工具
}
function handleCanvasMouseMove(e) {
const pos = getCanvasPos(e)
if (tool.value === 'pencil' && pencilTool.drawingActive.value) {
pencilTool.onMouseMove(pos)
} else if (tool.value === 'eraser' && eraserTool.drawingActive.value) {
eraserTool.onMouseMove(pos)
}
// ... 其他工具
}
function handleCanvasMouseUp(e) {
if (tool.value === 'pencil') {
pencilTool.onMouseUp()
} else if (tool.value === 'eraser') {
eraserTool.onMouseUp()
}
// ... 其他工具
}
return {
canvasRef,
editorContainerRef,
canvasWrapStyle,
handleCanvasMouseDown,
handleCanvasMouseMove,
handleCanvasMouseUp
}
}
}
```
--------------------------------
### Load and Get Editor Settings
Source: https://context7.com/xiaoshengxianjun/51mazi/llms.txt
Loads editor settings from the Pinia store and then accesses them. This allows for retrieving previously saved configurations like font family and size.
```javascript
// 编辑器设置
await editorStore.loadEditorSettings()
console.log(editorStore.editorSettings)
// { fontFamily: 'SimHei', fontSize: '16px', lineHeight: '1.6', ... }
```
--------------------------------
### Get Available Novel Sources
Source: https://context7.com/xiaoshengxianjun/51mazi/llms.txt
Fetches a list of available book sources that can be used for searching and downloading novels. This is the first step before performing any novel-related operations.
```javascript
// 获取可用书源列表
const sources = await window.electron.novelGetSources()
// 返回: [{ id: 'xbiqugu', name: '香书小说' }, { id: 'shuhaige', name: '书海阁小说网' }, ...]
```
--------------------------------
### IPC Handler for AI Cover Generation in Electron Main Process
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-15-tongyiwanxiang-electron.md
This code snippet demonstrates the main process logic for handling the 'tongyiwanxiang:generate-cover' IPC channel. It orchestrates calling the Tongyiwanxiang service to get an image URL, downloading the image, and saving it to the book's directory with a sequential naming convention (e.g., ai_cover1.png).
```javascript
// 1. Call Tongyiwanxiang to get image URL
const imageUrl = await tongyiwanxiangService.generateCover({ prompt, size, negativePrompt })
// 2. Download the image
const res = await fetch(imageUrl)
const buf = Buffer.from(await res.arrayBuffer())
// 3. Save to book root directory: ai_cover1.png, ai_cover2.png ...
const existing = fs.readdirSync(bookPath).filter((f) => /^ai_cover\d+\.png$/i.test(f))
const nextNum = existing.length === 0 ? 1 : Math.max(...existing.map(parseNumber)) + 1
const fileName = `ai_cover${nextNum}.png`
const coverPath = join(bookPath, fileName)
fs.writeFileSync(coverPath, buf)
return { success: true, localPath: coverPath }
```
--------------------------------
### Implement Real-time Typing Statistics with Pinia
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-2-tiptap.md
This Pinia store module tracks typing activity, calculating current word count, typing speed per minute, and per hour. It starts a timer when content is set and updates statistics accordingly. Ensure the `editor` store is initialized before use.
```javascript
// src/renderer/src/stores/editor.js
export const useEditorStore = defineStore('editor', () => {
const content = ref('')
const typingStartTime = ref(null)
const initialWordCount = ref(0)
const typingSpeed = ref({
perMinute: 0,
perHour: 0
})
// 计算当前字数
const chapterWords = computed(() => {
return content.value.length
})
// 开始计时
function startTypingTimer() {
if (!typingStartTime.value) {
typingStartTime.value = Date.now()
initialWordCount.value = chapterWords.value
}
}
// 更新码字速度
function updateTypingSpeed() {
if (!typingStartTime.value) return
const now = Date.now()
const timeElapsed = (now - typingStartTime.value) / 1000
const wordsTyped = chapterWords.value - initialWordCount.value
if (timeElapsed > 0) {
typingSpeed.value = {
perMinute: Math.round((wordsTyped / timeElapsed) * 60),
perHour: Math.round((wordsTyped / timeElapsed) * 3600)
}
}
}
// 设置内容时触发统计
function setContent(newContent) {
content.value = newContent
startTypingTimer()
updateTypingSpeed()
}
return {
content,
chapterWords,
typingSpeed,
setContent,
resetTypingTimer
}
})
```
--------------------------------
### Handling Character Encoding (UTF-8 and GBK)
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-19-novel-download.md
Provides a code example for decoding response bodies using `iconv-lite` for GBK encoded content, and `TextDecoder` for UTF-8. This is crucial for correctly parsing text from sources that do not use UTF-8.
```javascript
const buf = await res.arrayBuffer()
const bytes = new Uint8Array(buf)
if (encoding && encoding.toLowerCase() !== 'utf-8') {
return iconv.decode(Buffer.from(bytes), encoding)
}
return new TextDecoder('utf-8').decode(bytes)
```
--------------------------------
### Path Data Structure Example
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-12-pencil-tool.md
Defines the structure for storing path data, including points, color, stroke width, opacity, and a unique ID. This structure facilitates serialization and full recovery of path elements.
```javascript
{
type: 'freedraw',
points: [
{ x: 100, y: 200 },
{ x: 105, y: 205 },
// ... 更多点
],
color: '#222222',
strokeWidth: 5,
opacity: 100,
id: '1234567890'
}
```
--------------------------------
### Generate Names with AI Service (JavaScript)
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-13-deepseek-integration.md
Frontend function to call an AI service for generating names. Includes error handling and fallback to local generation. Requires setup of AI service and local generation fallback.
```javascript
// src/renderer/src/components/RandomName.vue
async function generateNamesWithAIService() {
generating.value = true
try {
const result = await generateNamesWithAI({
type: type.value,
surname: surname.value,
gender: gender.value,
count: 24
})
if (result.success && result.names.length > 0) {
names.value = result.names
ElMessage.success(`AI 生成了 ${result.names.length} 个名字`)
} else {
// 降级到本地生成
generateNamesLocal()
}
} catch (error) {
ElMessage.error('AI 起名失败,使用本地生成')
generateNamesLocal()
} finally {
generating.value = false
}
}
```
--------------------------------
### Constructing AI Cover Prompt in JavaScript
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-16-ai-cover-novel.md
This JavaScript snippet demonstrates how to dynamically construct a prompt for an AI image generation API by concatenating the book title, author's pen name, and user-defined style requirements. It ensures that essential information like the book title and author name are explicitly included in the prompt to guide the AI.
```javascript
const titlePart = `封面上必须清晰、醒目地显示书名《${bookName}》,书名需易于辨认。`
const authorPart = penName ? `封面上必须清晰显示作者笔名:${penName}。` : ''
const stylePart = userPrompt ? `风格与画面要求:${userPrompt}` : '封面风格为小说封面,美观大气。'
const fullPrompt = `${titlePart}${authorPart}${stylePart}`
await generateAICover({ prompt: fullPrompt, size, bookName, negativePrompt })
```
--------------------------------
### Build Project for Different Operating Systems
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-1-juejin.md
Commands to build the project for production deployment on Windows, macOS, and Linux.
```bash
# Windows
npm run build:win
```
```bash
# macOS
npm run build:mac
```
```bash
# Linux
npm run build:linux
```
--------------------------------
### Implementing Fetch with Timeout
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-19-novel-download.md
Demonstrates how to use `fetch` with an `AbortController` to implement request timeouts in the main process. This prevents the application from hanging if a book source is unresponsive. It also shows basic header configuration to mimic browser requests.
```javascript
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS)
const res = await fetch(url, { signal: controller.signal, redirect: 'follow', headers })
// ...
clearTimeout(timeoutId)
```
--------------------------------
### Get Editor Word Count
Source: https://context7.com/xiaoshengxianjun/51mazi/llms.txt
Retrieves the word count of the editor's content from the Pinia store, excluding whitespace and newline characters.
```javascript
// 获取字数统计(排除空格换行等格式字符)
console.log(editorStore.contentWordCount) // 12345
```
--------------------------------
### Project Directory Structure
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-1-juejin.md
The file structure for the Electron and Vue 3 application.
```bash
51mazi/
├── src/
│ ├── main/ # Electron 主进程
│ ├── preload/ # 预加载脚本
│ └── renderer/ # 渲染进程 (Vue 应用)
│ ├── src/
│ │ ├── components/ # 组件库
│ │ │ ├── EditorPanel.vue # 编辑器面板
│ │ │ ├── MapDesign.vue # 地图设计
│ │ │ ├── RelationshipDesign.vue # 关系图谱
│ │ │ └── ...
│ │ ├── views/ # 页面视图
│ │ ├── stores/ # 状态管理
│ │ ├── router/ # 路由配置
│ │ └── utils/ # 工具函数
│ └── assets/ # 静态资源
├── package.json
└── electron.vite.config.mjs
```
--------------------------------
### Initialize Resource Management Tool
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-9-map-design-overview.md
Initializes the resource management composable with required canvas and state dependencies.
```javascript
// 资源工具
const resourceTool = useResourceTool({
canvasRef,
elements,
history,
renderCanvas,
getCanvasPos
})
```
--------------------------------
### Build for Linux
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-1.md
Compiles and packages the application for Linux deployment. This command generates an executable suitable for Linux distributions.
```bash
npm run build:linux
```
--------------------------------
### Track User Behavior and Performance Metrics
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-6-events-sequence-ux.md
Initializes objects to store user interaction counts and system performance latency metrics.
```javascript
// 用户操作统计
const userBehavior = {
dragOperations: 0,
clickOperations: 0,
formSubmissions: 0,
errors: 0
}
// 性能指标监控
const performanceMetrics = {
renderTime: 0,
dragLatency: 0,
saveTime: 0
}
```
--------------------------------
### Fetch and Get Book Total Words
Source: https://context7.com/xiaoshengxianjun/51mazi/llms.txt
Fetches the total word count for a specific book and then accesses it from the Pinia store. This is useful for tracking progress or statistics for a book.
```javascript
// 管理书籍总字数
await editorStore.fetchBookTotalWords('我的小说')
console.log(editorStore.bookTotalWords) // 56789
```
--------------------------------
### Get Novel Chapter List
Source: https://context7.com/xiaoshengxianjun/51mazi/llms.txt
Retrieves the list of chapters for a given book URL from a specific source. Returns a success status and an array of chapters, each with a title and URL.
```javascript
// 获取章节目录
const chapterList = await window.electron.novelGetChapterList({
bookUrl: 'http://www.xbiqugu.la/book/12345/',
sourceId: 'xbiqugu'
})
// 返回: { success: true, chapters: [{ title: '第一章 陨落的天才', url: '...' }, ...] }
```
--------------------------------
### Implement Keyboard Shortcuts
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-3-canvas.md
Configuration and registration of keyboard shortcuts for various actions like tool selection, undo, and zoom. Uses a keydown event listener on the document.
```javascript
// 快捷键配置
const keyboardShortcuts = {
'KeyP': () => selectTool('pencil'),
'KeyE': () => selectTool('eraser'),
'Control+z': () => undo(),
'Equal': () => zoomIn(),
'Minus': () => zoomOut()
}
// 注册快捷键
onMounted(() => {
document.addEventListener('keydown', (event) => {
const key = getKeyCombination(event)
const handler = keyboardShortcuts[key]
if (handler) {
event.preventDefault()
handler()
}
})
})
```
--------------------------------
### Get Full Chapter Text in TipTap
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-18-ai-polish.md
Retrieves the entire text content of the chapter from the TipTap editor using the getText method. Includes a check for empty content.
```javascript
const fullText = editor.getText();
if (fullText === '') {
// Prompt user that chapter is empty
}
```
--------------------------------
### Build for macOS
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-1.md
Compiles and packages the application for macOS deployment. This command generates a .dmg file for macOS users.
```bash
npm run build:mac
```
--------------------------------
### Get Selected Text in TipTap
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-18-ai-polish.md
Retrieves the selected text from the TipTap editor state using the 'from' and 'to' properties of the selection. Handles cases where only a cursor is present.
```javascript
const { from, to } = editor.state.selection;
if (from === to) {
// Prompt user to select text
} else {
const selectedText = state.doc.textBetween(from, to, '\n');
// Call AI polish function with selectedText
}
```
--------------------------------
### Initialize TipTap Editor with Extensions
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-1.md
Configures the TipTap rich text editor with essential extensions like StarterKit, Bold, Italic, and custom extensions for Tab insertion and collapsible content. It also includes an onUpdate handler for content changes and debounced auto-saving.
```javascript
import { Editor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import Bold from '@tiptap/extension-bold'
import Italic from '@tiptap/extension-italic'
import TextAlign from '@tiptap/extension-text-align'
const editor = new Editor({
extensions: [
StarterKit,
Bold,
Italic,
TextAlign.configure({ types: ['heading', 'paragraph'] }),
TabInsert, // 自定义 Tab 键扩展
Collapsible // 自定义折叠扩展
],
content: editorStore.content,
onUpdate: ({ editor }) => {
const content = editor.getText()
editorStore.setContent(content)
// 防抖自动保存
if (saveTimer) clearTimeout(saveTimer)
saveTimer = setTimeout(() => {
autoSaveContent()
}, 1000)
}
})
```
--------------------------------
### Handle Drag-and-Drop DOM Events
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-8-paragraph-drag.md
Manages the drag lifecycle including initialization, preview setup, and drop execution. Requires a draggingPos variable to track the source node position.
```javascript
handleDOMEvents: {
// 鼠标按下:初始化拖拽
mousedown: (view, event) => {
const target = event.target
if (!target.classList.contains('note-outline-drag-handle')) return false
const pos = Number(target.dataset.pos || -1)
if (pos < 0) return false
// 选中整个段落节点
const node = state.doc.nodeAt(pos)
const tr = state.tr.setSelection(NodeSelection.create(state.doc, pos))
view.dispatch(tr)
// 记录拖拽起点
draggingPos = pos
return true
},
// 拖拽开始:设置拖拽预览
dragstart: (view, event) => {
const target = event.target
if (!target.classList.contains('note-outline-drag-handle')) return false
const pos = Number(target.dataset.pos || -1)
const nodeDom = view.nodeDOM(pos)
// 克隆节点作为拖拽预览
const clone = nodeDom.cloneNode(true)
clone.style.position = 'fixed'
clone.style.pointerEvents = 'none'
clone.style.top = '-10000px'
// ... 设置样式
// 设置拖拽预览图
event.dataTransfer.setDragImage(clone, 8, 8)
return true
},
// 拖拽悬停:允许放置
dragover: (view, event) => {
if (draggingPos != null) {
event.preventDefault()
event.dataTransfer.dropEffect = 'move'
return true
}
return false
},
// 放置:执行移动
drop: (view, event) => {
if (draggingPos == null) return false
event.preventDefault()
moveParagraphAtPoint(view, event.clientX, event.clientY)
draggingPos = null
return true
}
}
```
--------------------------------
### Initialize Rendering System
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-9-map-design-overview.md
Initializes the rendering functions for a canvas reference.
```javascript
// 渲染函数
const renderFunctions = useRender(canvasRef)
```
--------------------------------
### Pencil Tool for Smooth Drawing
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-3-canvas.md
Implements a PencilTool class that uses Bezier curves for smooth drawing on a canvas context. Requires at least three points to start drawing curves.
```javascript
// 铅笔工具平滑绘制
class PencilTool {
constructor(ctx) {
this.ctx = ctx
this.points = []
}
move(x, y) {
this.points.push({ x, y })
// 平滑曲线绘制
if (this.points.length > 2) {
const lastPoint = this.points[this.points.length - 1]
const prevPoint = this.points[this.points.length - 2]
const prevPrevPoint = this.points[this.points.length - 3]
const cp1x = prevPoint.x + (lastPoint.x - prevPrevPoint.x) / 6
const cp1y = prevPoint.y + (lastPoint.y - prevPrevPoint.y) / 6
const cp2x = lastPoint.x - (lastPoint.x - prevPoint.x) / 6
const cp2y = lastPoint.y - (lastPoint.y - prevPoint.y) / 6
this.ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, lastPoint.x, lastPoint.y)
}
this.ctx.stroke()
}
}
```
--------------------------------
### DeepSeek Service Implementation for Prompt Engineering
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-21-ai-scene-image.md
This refers to the service implementation for interacting with the DeepSeek API, specifically for the `sceneVisualPromptFromExcerpt` function. This function is used to refine text excerpts into prompts suitable for image generation.
```javascript
deepseekService.sceneVisualPromptFromExcerpt
```
--------------------------------
### Implement Color Picker UI with Element Plus Popover
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-7-text-highlight.md
Uses Element Plus's Popover component to create a UI for selecting highlight colors. Requires Vue and Element Plus setup.
```vue
```
--------------------------------
### TipTap 依赖配置
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-2-tiptap.md
列出项目所需的 TipTap 相关 npm 依赖包。
```javascript
// package.json 依赖
{
"@tiptap/vue-3": "^2.12.0",
"@tiptap/starter-kit": "^2.12.0",
"@tiptap/extension-bold": "^2.12.0",
"@tiptap/extension-italic": "^2.12.0",
"@tiptap/extension-text-align": "^2.12.0"
}
```
--------------------------------
### 项目目录结构
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-4-book-management.md
展示 Electron 项目的组织结构,包含主进程、预加载脚本和渲染进程。
```text
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/ # 静态资源
```
--------------------------------
### Handle Dragging Logic in Sequence Charts
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-5-events-sequence-technical.md
Calculates new start and end times for an event during drag operations, ensuring it stays within chart boundaries. Requires `isDragging`, `draggingEvent`, `dragStartX`, `dragStartLeft`, `hasMovedWhileMouseDown`, `sequenceCharts` to be defined.
```javascript
const handleDrag = (event) => {
if (!isDragging.value || !draggingEvent.value) return
const deltaX = event.clientX - dragStartX.value
if (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 + 1
const maxStartTime = chart.cellCount - eventDuration + 1
// 限制在有效范围内
const clampedStartTime = Math.max(1, Math.min(newStartTime, maxStartTime))
const clampedEndTime = clampedStartTime + eventDuration - 1
// 更新事件时间
draggingEvent.value.startTime = clampedStartTime
draggingEvent.value.endTime = clampedEndTime
}
}
```
--------------------------------
### 渲染进程服务层 API 封装
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-4-book-management.md
封装渲染进程中的书籍管理 API,通过 window.electron 接口调用主进程功能。
```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
}
```
--------------------------------
### DeepSeek API Chat Request
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-18-ai-polish.md
Example of making a chat completion request to the DeepSeek API, including system prompts, user messages, temperature, max tokens, and request ID. The system prompt emphasizes the role of a professional editor and the desired output format.
```javascript
this.chat({
messages: [
{ role: 'system', content: 'You are a professional Chinese writing editor. Polish the full chapter text, optimizing expression and grammar while maintaining the original meaning and paragraph structure. Output only the polished text, with no explanations or extraneous text.' },
{ role: 'user', content: text }
],
temperature: 0.5,
max_tokens: 8000,
requestId
});
```
--------------------------------
### Initialize Pencil Tool
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-9-map-design-overview.md
Initializes the pencil tool with necessary canvas and state references.
```javascript
const pencilTool = usePencilTool({
canvasRef,
elements,
history,
renderCanvas,
color,
size,
opacity
})
```
--------------------------------
### Implement Keyboard Shortcut System
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-9-map-design-overview.md
Handles tool switching, undo/redo operations, and temporary mode toggling via keyboard events.
```javascript
// 快捷键配置
function handleKeyDown(e) {
// 工具快捷键
switch (e.key.toLowerCase()) {
case 'v': onToolChange('select'); break
case 'h': onToolChange('move'); break
case 'p': onToolChange('pencil'); break
case 'e': onToolChange('eraser'); break
case 's': onToolChange('shape'); break
case 't': onToolChange('text'); break
case 'b': onToolChange('bucket'); break
case 'r': onToolChange('resource'); break
}
// 撤销/重做
if ((e.ctrlKey || e.metaKey) && e.key === 'z') {
handleUndo()
}
// 空格键:临时切换到移动模式
if (e.key === ' ' && tool.value !== 'move') {
spaceKeyPressed.value = true
}
}
```
--------------------------------
### Resource Management Implementation
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-3-canvas.md
Defines a list of resources with their names and URLs, and implements a function to handle resource dragging onto a canvas.
```javascript
// 资源管理核心实现
const resources = [
{ name: '点', url: '/src/assets/images/point.png' },
{ name: '五角星', url: '/src/assets/images/star.png' },
{ name: '山', url: '/src/assets/images/mountain.png' },
{ name: '森林', url: '/src/assets/images/forest.png' },
{ name: '宫殿', url: '/src/assets/images/palace.png' },
{ name: '城市', url: '/src/assets/images/city.png' }
]
// 资源拖拽功能
function onResourceMouseDown(resource, event) {
const img = new Image()
img.src = resource.url
img.onload = () => {
const ctx = canvasRef.value.getContext('2d')
const rect = canvasRef.value.getBoundingClientRect()
const x = (event.clientX - rect.left) / scale.value
const y = (event.clientY - rect.top) / scale.value
ctx.drawImage(img, x - img.width / 2, y - img.height / 2)
}
}
```
--------------------------------
### Initialize Select Tool
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-9-map-design-overview.md
Initializes the selection tool for managing element manipulation.
```javascript
const selectTool = useSelectTool({
elements,
history,
renderCanvas,
selectedElementIds,
canvasState,
canvasCursor
})
```
--------------------------------
### Flood Fill with Recursion (Illustrative)
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-10-bucket-tool.md
Illustrates a recursive approach to flood fill, highlighting the potential for call stack overflow errors with large areas.
```javascript
// ❌ 递归实现(可能导致调用栈溢出)
function floodFillRecursive(x, y) {
// ... 递归调用
floodFillRecursive(x + 1, y)
floodFillRecursive(x - 1, y)
// ...
}
```
--------------------------------
### Implement Touch Device Support
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-3-canvas.md
Handles touch events (touchstart, touchmove, touchend) for drawing on touch-enabled devices. Prevents default behavior and uses touch coordinates for drawing actions.
```javascript
// 触摸事件支持
function handleTouchStart(event) {
event.preventDefault()
const touch = event.touches[0]
startDraw({ clientX: touch.clientX, clientY: touch.clientY })
}
function handleTouchMove(event) {
event.preventDefault()
const touch = event.touches[0]
drawing({ clientX: touch.clientX, clientY: touch.clientY })
}
// 注册触摸事件
onMounted(() => {
const canvas = canvasRef.value
canvas.addEventListener('touchstart', handleTouchStart, { passive: false })
canvas.addEventListener('touchmove', handleTouchMove, { passive: false })
canvas.addEventListener('touchend', endDraw, { passive: false })
})
```
--------------------------------
### Constructing AI Image Prompts
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-17-ai-character-image.md
This logic combines a fixed subject constraint, style keywords, user-provided character descriptions, and pose information into a single prompt string.
```javascript
// 结构:竖版全身 + 风格 + 形象描述 + 姿态
const parts = ['竖版全身人物立绘,清晰完整的人物']
if (styleKey) {
const styleOpt = styleOptions.find((o) => o.value === styleKey)
if (styleOpt?.prompt) parts.push(styleOpt.prompt)
}
parts.push('。人物形象:')
parts.push(form.value.prompt.trim()) // 用户填写的形象描述
if (poseKey && poseLabel) parts.push(',' + poseLabel)
return parts.join('')
```
--------------------------------
### Iconfont Component (Class Method)
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/src/renderer/src/assets/icons/README.md
Demonstrates how to use the IconFont component for displaying icons with customizable size, color, and event handling.
```APIDOC
## IconFont Component (Class Method)
### Description
Use the `IconFont` component to easily integrate icons into your Vue application. It supports basic usage, custom sizing, coloring, and event listeners.
### Method
Vue Component
### Endpoint
N/A
### Parameters
#### Path Parameters
None
#### Query Parameters
None
#### Request Body
None
### Request Example
```vue
```
### Response
#### Success Response (200)
N/A (Component rendering)
#### Response Example
N/A
```
--------------------------------
### Check Image Readiness
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-10-bucket-tool.md
Verify if an image is fully loaded and valid before processing.
```javascript
if (img.complete && img.naturalWidth > 0) {
handleImageReady()
}
```
--------------------------------
### JavaScript 拖拽开始和过程反馈
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-6-events-sequence-ux.md
处理拖拽事件的开始和过程。`startDrag`函数用于设置全局样式和事件状态,`handleDrag`函数计算新位置并提供边界检查的视觉反馈。
```javascript
// 拖拽开始 - 视觉反馈
const startDrag = (event, eventData) => {
// 添加拖拽样式
document.body.style.cursor = 'grabbing'
document.body.style.userSelect = 'none'
document.body.classList.add('dragging')
// 事件条高亮
eventData.dragging = true
}
// 拖拽过程 - 实时反馈
const handleDrag = (event) => {
// 计算新位置
const newLeft = dragStartLeft.value + deltaX
const newStartTime = Math.round(newLeft / 40) + 1
// 边界检查反馈
if (newStartTime < 1 || newStartTime > maxStartTime) {
// 边界限制视觉反馈
eventBar.style.opacity = '0.5'
} else {
eventBar.style.opacity = '1'
}
}
```
--------------------------------
### Direct CSS Class Usage
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/src/renderer/src/assets/icons/README.md
Shows how to use Iconfont icons directly by applying CSS classes to `` elements.
```APIDOC
## Direct CSS Class Usage
### Description
Icons can be used directly by applying the `iconfont` class along with specific icon name classes to `` elements. This method also supports various style variants and animations.
### Method
HTML Element with CSS Classes
### Endpoint
N/A
### Parameters
#### Path Parameters
None
#### Query Parameters
None
#### Request Body
None
### Request Example
```vue
```
### Response
#### Success Response (200)
N/A (HTML rendering)
#### Response Example
N/A
```
--------------------------------
### Initialize Shape Tool
Source: https://github.com/xiaoshengxianjun/51mazi/blob/main/blog/blog-post-9-map-design-overview.md
Initializes the shape tool for drawing various geometric elements.
```javascript
const shapeTool = useShapeTool({
canvasRef,
elements,
history,
renderCanvas,
color,
size,
opacity
})
```