前言
? 最近肝了幾天寫了一個用于管理和編輯我們小團隊的一個md檔案的網頁版typora,在過去我們的知識歸納都是各自使用typora編輯,編輯后每周定時提交檔案,由一個人匯總后發布到csdn等平臺,
? 但是這樣做在檔案出現問題后發布的文章如果需要持續更新的話無法讓團隊中的其他人看到,且使用typora保存的圖片都是本地的,配置圖床也沒有那么方便,所以干脆花點時間整理了一下需求,想著做一個網頁協作版的typora,有一個基本的賬號機制,一個檔案可以大家一起編輯,但是不是協作檔案那種同時編輯,而是一個人在編輯的時候將該檔案鎖定,并提示有其他人正在編輯中,
? 整理了大概的需求就開錘了!由于是一個小專案并且最近個人正在從vue2轉向vue3+ts,就用這個專案來踩踩坑,最終實作的效果如下圖


頁面框架構思
通過初步的需求分析,在除了注冊和登錄外的主要頁面就是一個類似typora的頁面,分析如下:
左側欄:
頂部的tabs標簽欄,用于切換檔案目錄和大綱,在檔案目錄標簽內有一個頂部搜索框,一個檔案管理的樹狀串列,底部欄有一個在根目錄添加檔案或檔案的按鈕以及用戶的用戶名顯示,滑鼠右鍵點擊檔案夾可以編輯檔案或檔案夾的狀態,



頂部欄:
展示了當前選中的檔案,以及檔案的創建者和屬性,頂部右側是一個開始編輯的按鈕,當其他人在編輯時將進入disable狀態,并且按鈕文案變更為xx用戶正在編輯中

編輯器:
中間是編輯器的主要頁面,有預覽狀態和編輯狀態,編輯狀態隱藏左邊欄
-
技術選型
前端
vue@3.2.x及相關版本全家桶 + vite@2.x + ts + element-plus +tailwindcss
后端
node.js + koa@2.x + sequelize + pm2
資料庫
mysql
markdown編輯器
v-md-editor
ide工具
vscode(主要使用插件:volar,Tailwind CSS IntelliSense,TypeScript Extension Pack)
…
框架搭建
生成目錄框架
首先我們要創建一個專案,根據vite官方檔案使用以下命令創建一個專案,根據命令列提示我們選擇vue+ts的框架
yarn create vite
再根據我們的技術選型以及相應的官方檔案將整體目錄創建好
如下圖:

檔案目錄框架是vue專案中非常典型的一種,router和store分別對應vue-router和vuex的功能模塊,style是全域樣式,utils是全域方法,views和componets是頁面和通用組件,每一個views中都有自己對應的components檔案夾和一個入口檔案index.vue
安裝tailwindcss
根據官方檔案安裝指定依賴
yarn add -D tailwindcss@npm:@tailwindcss/postcss7-compat @tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
初始化tailwind組態檔
npx tailwindcss init -p
使用vscode建議安裝Tailwind CSS IntelliSense插件,在寫class時先敲一個空格就可以出現tailwindcss的樣式代碼提示,如下圖

安裝element-plus并修改主題色
yarn add element-plus
在src目錄創建檔案element-variables.scss,寫入如下代碼
@use "sass:math";
@use "sass:map";
$--colors: () !default;
$--colors: map.deep-merge(('white': #ffffff,
'black': #000000,
'primary': ('base': teal,
),
'success': ('base': #67c23a,
),
'warning': ('base': #e6a23c,
),
'danger': ('base': #f56c6c,
),
'error': ('base': #f56c6c,
),
'info': ('base': #909399,
),
),
$--colors);
$--font-path: 'element-plus/theme-chalk/fonts';
@import "element-plus/theme-chalk/src/index.scss"
安裝v-md-editor
yarn add @kangc/v-md-editor@next
安裝vuex和vue-router
yarn add vue-router@4 vuex@next --save
yarn add vue-router@4 vuex@next --save
其中vuex的模塊化,型別化,持久化引入我在另一篇文章中有寫
vue3+vuex的型別化和模塊引入
安裝axios并封裝http請求
yarn add axios
http請求的封裝參照這篇文章
vue3+ts+axios請求封裝使用
在這個專案中,我并沒有將所有的api請求單獨封裝,因為各個api的復用程度不高,而且傳遞的引數也比較簡單,個人認為沒有封裝的必要
引入所有依賴
除了以上幾個依賴,其他的都沒有什么特殊點,可以直接在看檔案安裝,最終的main.ts如下
import { createApp } from "vue"
import App from "./App.vue"
import { store, key } from "./store"
import router from "./router"
import "element-plus/theme-chalk/display.css"
import "font-awesome/css/font-awesome.min.css"
import "./element-variables.scss"
import "tailwindcss/tailwind.css"
import "./style/global.scss"
import "@kangc/v-md-editor/lib/style/base-editor.css"
import "@kangc/v-md-editor/lib/theme/style/vuepress.css"
import VueMarkdownEditor from "@kangc/v-md-editor"
import vuepressTheme from "@kangc/v-md-editor/lib/theme/vuepress.js"
import Prism from "prismjs"
import createLineNumbertPlugin from "@kangc/v-md-editor/lib/plugins/line-number/index"
VueMarkdownEditor.use(vuepressTheme, {
Prism,
})
VueMarkdownEditor.use(createLineNumbertPlugin())
const app = createApp(App)
app.use(store, key)
app.use(router)
app.use(VueMarkdownEditor)
app.mount("#app")
頁面實作
頁面的實作代碼比較多,大家可以直接去gitee看原始碼,主要講一下開發的思路
登錄和注冊頁面
頁面的目錄如下,三個主要頁面,分別是登錄頁面,注冊頁面和檔案頁面

注冊和登錄比較簡單,主要是element中 ElForm, ElFormItem 兩個組件的應用,可以直接在源代碼中查看
編輯器頁面
編輯器頁面中有三個組件,分別是對應頁面框架的三個部分,由于這三個組件之間的需要相互呼叫資料和方法耦合程度很高,如果使用傳統的組件傳值會非常麻煩,我們可以利用vuex來非常方便的進行資料的使用和修改
在store檔案夾中,有五個資料模塊,對應著頁面中各個位置的資料

拿其中的fileTree.ts模塊來看,這個模塊里的方法和資料不論是頂部欄還是左側欄都會頻繁用到,這就節省了很多父子,兄弟組件之間的互動,
import { Module } from "vuex"
import { RootState } from "../index"
import http from "@/utils/http"
import { FlatToTree } from "@/utils/format"
import type { FileItemType } from "@/views/home/components/LeftBar/components/FileTree/type"
const state = {
data: [] as Array<FileItemType>,//檔案樹的資料
flag: true,//左側欄是否展開
treeExpandedArr: [] as Array<string>,//檔案樹需要展開的節點陣列
}
export type FileTreeState = typeof state
export const store: Module<FileTreeState, RootState> = {
namespaced: true,
state,
mutations: {
//更新展開樹節點的快取資料
changeTreeExpandedArr(
state: FileTreeState,
TreeExpandedArr: FileTreeState["treeExpandedArr"]
) {
state.treeExpandedArr = TreeExpandviteedArr
},
//切換左側樹狀檔案夾的展開
switchFileTree(state: FileTreeState, flag: boolean) {
state.flag = flag
},
},
actions: {
//獲取檔案目錄并從扁平轉化為樹狀
async getFile({ state }) {
const res = await http.get<Array<FileItemType>>("/file/getFile")
if (res.code === 1) {
state.data = FlatToTree(res.data as Array<FileItemType>, "parentId")
}
},
},
}
websocket實作
之所以需要使用到websocket,是因為如果有人開始編輯檔案,而我們這里并不知道檔案已經在編輯狀態了,就會導致兩個人同時編輯后提交,后提交的覆寫了先提交的資料,因此我們希望前端可以在其他人開始編輯的時候實時接受到,并讓其他用戶不能編輯,
檔案被占用時檔案樹會如下圖所示
3248178d4a4a4c6833eab8.png)
開始編輯按鈕也會無法點擊

websocket的封裝
通過判斷收到的訊息中 handle 欄位進行檔案的占用或釋放的處理,heartbeat方法是用于建立心跳,在通訊因意外斷開后可以及時的重連
type MessageType = {
handle: "occupy" | "release"
userId: number
fileId: number
name: string
}
export const websocket = {
ws: null as WebSocket | null,
status: false,
userId: 0,
url: "",
connect(url: string, userId: number) {
const ws = new WebSocket(url)
ws.onopen = () => {
ws.send(JSON.stringify({ userId }))
}
ws.onmessage = this.onmessage
ws.onclose = this.onclose
ws.onerror = this.onerror
this.status = true
this.ws = ws
this.userId = userId
this.url = url
},
onmessage(e: any) {
if ((e.data as MessageType | "pong") === "pong") {
this.status = true
} else {
websocket.messageCallback(JSON.parse(e.data))
}
},
messageCallback(e: MessageType) {},
onerror() {
websocket.status = false
},
onclose() {
websocket.status = false
},
heartbeat() {
setInterval(() => {
if (this.status) {
try {
;(this.ws as WebSocket).send("ping")
} catch {
this.status = false
this.connect(this.url, this.userId)
}
} else {
this.connect(this.url, this.userId)
}
}, 3000)
},
}
注意點
在上面的代碼中,我們可以發現,onerror和onclose方法中寫入的并不是this.status = false 而是 websocket.status = false,這是因為我們將這兩個方法傳給了ws物件,當觸發這個方法的時候this指向的就是ws物件而不是我們websocket 這個物件了,同理onmessage也是一樣,想要觸發回呼函式就得修改呼叫的物件
websocket的呼叫
websocket.connect("ws://websocketUrl", store.state.user.id)
websocket.heartbeat()
websocket.messageCallback = async (e) => {
//收到訊息后執行的回呼方法
}
暫時先寫到這里,后面將會持續更新,前后端的原始碼也在整理準備開源啦!歡迎關注插眼!
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/304894.html
標籤:其他
上一篇:http用戶登錄
下一篇:05gcc/g++和gdb使用
