### 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 }) ```