跳转至

Body区域划分

一、预览

body区域准备划分两部分,左侧做出文件管理器页面,右侧是编辑区域,对于Markdown编辑来说,可以平分做预览区域。

二、实现思路

应用界面分为以下几个层次:

Text Only
1
2
3
4
5
6
7
8
App.vue(根组件)
├── WelcomeScreen.vue(欢迎界面,初始显示)
└── BaiZeNotes.vue(主应用界面)
    ├── 标题栏(title-bar,支持拖动、最小化/最大化/关闭)
    ├── 菜单栏(menu-bar,MenuBar.vue)
    ├── 工作区域(workspace-area,WorkSpace.vue)
    ├── 状态栏(status-bar,StatusBar.vue)
    └── 对话框组件(dialogs)

工作区域划分三部分:

  • 左侧导航栏(navi-tab):固定宽度,放置图标,包含文件资源管理器、文章大纲、绘图工具、PlantUML、Mermaid 等入口
  • 中间资源管理区域(resource-manager):宽度可调节,根据左侧导航选择显示不同内容
    • 文件资源管理器:文件列表 tree
    • 文章大纲:Markdown-toc
  • 右侧工作区域:根据导航切换不同容器
    • Markdown 编辑器(MarkdownContainer)
    • 插件工具(PluginTools)
    • Hemy 工具(HemyTools)
    • HTML 查看(ShowHtmlView)
    • PDF 编辑器(ShowPdfEditor)

状态栏显示:文件绝对路径、文件类型、编码、文件大小、保存状态等信息

三、实现

3.1 App.vue

App.vue 是根组件,负责在欢迎界面和主应用界面之间切换:

App.vue 参考

Vue
<template>
    <div id="app-root">
        <!-- 欢迎界面 -->
        <WelcomeScreen 
            v-if="showWelcome"
            @openFile="handleOpenFile"
            @openFolder="handleOpenFolder"
            @openRecentFolder="handleOpenRecentFolder"
        />
        <!-- 主应用界面 -->
        <BaiZeNotes v-else />
    </div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue'
import WelcomeScreen from './components/WelcomeScreen.vue'
import BaiZeNotes from './components/BaiZeNotes.vue'

const showWelcome = ref(true)

// 处理打开文件/文件夹,通过 IPC 通知主进程
function handleOpenFile() {
    window.electron.ipcRenderer.send('baize:notes:welcome:open-file')
}
function handleOpenFolder() {
    window.electron.ipcRenderer.send('baize:notes:welcome:open-directory')
}
</script>

3.2 BaiZeNotes.vue

BaiZeNotes.vue 是主应用组件,包含标题栏、菜单栏、工作区域、状态栏和所有对话框:

BaiZeNotes.vue 结构参考

Vue
<template>
    <div id="editor-container" :style="containerStyles">
        <!-- 标题栏区域,高度30px,支持拖动移动窗口 -->
        <div v-show="electronMenu" id="title-bar" class="title-bar"
            @mousedown="onTitleBarMouseDown"
            @dblclick="onTitleBarDblClick">
            <div class="title-left">
                <img :src="logoSrc" alt="Baize Logo" width="24" height="24">
                <span class="title-text">白泽笔记 - Markdown Editor</span>
            </div>
            <div class="window-controls">
                <button class="window-btn minimize" @click="minimizeWindow"></button>
                <button class="window-btn maximize" @click="maximizeWindow"></button>
                <button class="window-btn close" @click="closeWindow"></button>
            </div>
        </div>
        <!-- 菜单栏区域 -->
        <div v-show="electronMenu" id="menu-bar" class="menu-bar"><MenuBar /></div>
        <!-- 工作区域 -->
        <div id="workspace-area" class="workspace-area"><WorkSpace /></div>
        <!-- 状态栏 -->
        <div id="status-bar" class="status-bar" :style="statusBarStyles"><StatusBar /></div>
        <!-- 对话框组件(主题设置、字体选择、编辑器设置等) -->
        <BaiZeDialogs.ThemeSettingDialog ... />
        <BaiZeDialogs.EditorSettingDialog ... />
        <!-- ... 其他对话框 -->
    </div>
</template>

3.3 WorkSpace.vue

WorkSpace.vue 是工作区域,分为左侧导航、资源管理器、右侧工作容器,支持多种容器切换:

WorkSpace.vue 参考

Vue
<template>
    <!-- 左侧导航栏,固定宽度 -->
    <div id="left-navi" class="navi-tab" :style="{ width: naviTabWidth, float: 'left' }">
        <NaviTab position="left" @update:navi:tab="onSwitchRightNaviTab" />
    </div>
    <!-- 中间资源管理区域,宽度可调节 -->
    <div v-show="isShowResourceMgrArea" id="resource-manager" class="resource-manager"
        :style="{ width: resMgrWidth }">
        <ResManager :navi-show="naviResManagerShow" />
    </div>
    <!-- 宽度调节条 -->
    <div id="resizer-main" class="resizer-main" :style="{ left: resizerLeft }"
        @mousedown="startCursorPosition($event)"></div>
    <!-- 右侧工作区域,根据导航切换不同容器 -->
    <div v-show="isShowMdContainer" id="md-container" class="md-container"
        :style="{ width: workAreaWidth, marginRight: naviTabWidth }">
        <MdContainer :md-container-width="workAreaWidth" />
    </div>
    <div v-show="isShowPluginsContainer" id="plugin-containers" ...>
        <PluginTools :plugins-area-width="workAreaWidth" />
    </div>
    <div v-show="isShowToolsContainer" id="tool-containers" ...>
        <HemyTools :tools-area-width="workAreaWidth" />
    </div>
    <div v-show="isShowHtmlContainer" id="html-containers" ...>
        <ShowHtmlView :html-area-width="workAreaWidth" />
    </div>
    <div v-show="isShowPdfContainer" id="pdf-containers" ...>
        <ShowPdfEditor :pds-area-width="workAreaWidth" />
    </div>
    <!-- 最右侧导航栏 -->
    <div id="right-navi" class="navi-tab" :style="{ width: naviTabWidth, float: 'right' }">
        <NaviTab position="right" @update:navi:tab="onSwitchLeftNaviTab" />
    </div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
import EventBus from "@renderer/common/event_bus/event-bus"
import NaviTab from "@renderer/components/WorkSpaceArea/NaviTab.vue"
import ResManager from "@renderer/components/ResourceManager/ResourceManager.vue"
import MdContainer from "@renderer/components/Markdown/MarkdownContainer.vue"
import PluginTools from "@renderer/components/PluginTools/PluginTools.vue"
import HemyTools from "@renderer/components/HemyTools/HemyTools.vue"
import ShowHtmlView from "@renderer/components/HemyTools/ShowHtmlVIew.vue"
import ShowPdfEditor from "@renderer/components/HemyTools/ShowPdfEditor.vue"
// ...
</script>

利用 ref 特性完成窗口的动态变化,在窗口被拖动或者最大化最小化时,窗口大小随着刷新。各容器通过 v-show 控制显示,切换时保留组件状态。

3.4 MarkdownContainer.vue

Markdown 编辑区域,分为编辑器工具栏、编辑器和预览区域:

Vue
1
2
3
4
5
6
7
8
<template>
  <div id="md-edit-tools-bar" class="md-edit-tools-bar">
    <MdEditTools :tool-bar-width="props.mdContainerWidth" />
  </div>
  <div id="md-edit-component" class="md-edit-component">
    <MdEditComp :editor-preview-width="props.mdContainerWidth" />
  </div>
</template>

3.5 MarkdownEditComponent.vue

编辑区域和预览区域,使用 Monaco Editor 编辑,markdown-it 实时渲染预览:

Vue
<template>
  <div id="md-edit-component" class="md-edit-component" :style="mdEditComponetStyle">
    <MdMonacoEdit v-model="markdownEditorCode" :code="initialCodeContent"
        @update:code="handleMarkdownCodeUpdate" />
  </div>
  <div id="resizer-md" class="resizer-md"></div>
  <div id="md-preview" class="md-preview" :style="mdPreviewComponentStyle">
    <MdPreview :code="markdownEditorCode" />
  </div>
</template>

中间有分割条,可以鼠标拖动调整编辑区和预览区的显示比例。视图菜单支持三种模式切换:编辑模式、预览模式、编辑/预览模式。

3.6 MarkdownPreviewComponent.vue

预览区域,使用 markdown-it 及其插件链进行渲染,支持防抖优化:

MarkdownPreviewComponent.vue 渲染管线

TypeScript
import MarkdownIt from 'markdown-it'
import highlightjs from 'markdown-it-highlightjs'
import { full as emoji } from 'markdown-it-emoji'
import plantuml from 'markdown-it-plantuml'

const md = MarkdownIt({
    html: true, xhtmlOut: true, linkify: true,
    langPrefix: 'language-', breaks: true, typographer: false
})
    .use(highlightjs, { inline: true, hljs: hljs, ... })
    .use(plantuml)
    .use(emoji)

// 监听编辑器内容变化,带防抖优化
watch(() => props.editorContent, () => {
    if (renderDebounceTimer) clearTimeout(renderDebounceTimer)
    renderDebounceTimer = setTimeout(() => {
        updateMarkdownPreRender()
        renderDebounceTimer = null
    }, 150)  // 150ms 防抖延迟
}, { immediate: true })

四、效果