基于IndexDB+md-editor-v3实现的简单的文章书写小系统

基于IndexDB+md-editor-v3实现的简单的文章书写小系统

    • 文章说明
    • 核心代码
    • 效果展示
    • 源码下载

文章说明

采用vue3 + IndexDB 实现的个人仓库系统,采用markdown书写文章,并将文章信息存储在IndexDB数据库中,通过JavaScript原生自带的分词API进行文章的搜索

核心代码

采用SpringBoot简单搭建了一个图片服务器,之前一直想通过前台实现图片的上传和下载,但是采用vue的代理我试了很久,都实现不了;还是采用后台服务器技术来的简洁方便。

前台就采用md-editor-v3进行文章的书写和阅读,然后自己简单的采用基于分词和计数的方式来实现了一个简单的搜索引擎效果;目前只是作为一个示例,供学习使用。

图片服务器核心代码

package com.boot.controller;import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import java.io.File;import static com.boot.config.WebConfig.imageDir;@RestController
@CrossOrigin(origins = "*", maxAge = 3600)
@RequestMapping("/image")
public class ImageController {@PostMapping("/upload")public void upload(@RequestBody MultipartFile file, @RequestParam String id) throws Exception {File dir = new File(imageDir + id);if (!dir.exists()) {if (!dir.mkdirs()) {System.out.println("图片文件存放文件夹," + imageDir + id + "创建失败");}}file.transferTo(new File(imageDir + id + File.separator + file.getOriginalFilename()));}
}

配置类;采用路径映射,简单的对图片进行存储和预览;registry.addResourceHandler(“/image/**”).addResourceLocations(“file:” + imageDir);

package com.boot.config;import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.io.File;@Configuration
public class WebConfig implements WebMvcConfigurer {public static String imageDir;@Value("${image.dir}")public void setImageDir(String imageDir) {WebConfig.imageDir = imageDir;File file = new File(imageDir);if (!file.exists()) {if (file.mkdirs()) {System.out.println("图片文件夹," + imageDir + "创建成功");} else {System.out.println("图片文件夹," + imageDir + "创建失败");System.exit(0);}}}@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/image/**").addResourceLocations("file:" + imageDir);}
}

文章书写页面代码

<script setup>
import {onBeforeMount, reactive, watch} from "vue";
import {MdEditor} from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
import {baseUrl, message, splitText, uploadImage} from "@/util";
import {onBeforeRouteLeave, useRoute, useRouter} from "vue-router";
import {openArticleLog, openContentWordLog, openTitleWordLog} from "@/util/config";
import {dbOperation} from "@/util/dbOperation";
import {ElLoading} from "element-plus";const data = reactive({text: "",visible: false,title: "",id: "",modFlag: false,
});watch(() => data.text, () => {data.modFlag = true;
});const route = useRoute();onBeforeMount(async () => {data.id = route.query.id;if (data.id) {await openArticleLog();const res = await dbOperation.getDataByField("id", Number(data.id));const article = res.data[0];data.title = article.article_title;data.text = article.article_content;}window.onbeforeunload = function () {if (data.modFlag) {write();}}
});onBeforeRouteLeave(() => {if (data.modFlag) {write();return true;}
});async function onUploadImg(files, callback) {for (let i = 0; i < files.length; i++) {const imagePath = await uploadImage(files[i]);callback([baseUrl + "/image/" + imagePath]);}message("图片添加成功", "success");
}function onSave() {data.visible = true;
}const router = useRouter();async function write() {data.modFlag = false;const loadingInstance = ElLoading.service({background: "rgba(0, 0, 0, 0.7)",text: "正在保存中,请稍候"});if (data.title === "") {data.title = "草稿";}if (data.id) {await openArticleLog();await dbOperation.update({id: Number(data.id),article_title: data.title,article_content: data.text,create_time: new Date(),});} else {await openArticleLog();await dbOperation.add([{article_title: data.title,article_content: data.text,create_time: new Date(),}]);const newArticle = await dbOperation.getLimitOrderDescData("id", "1");if (newArticle.data.length > 0) {data.id = newArticle.data[0].id;}}await dealTitleWord();await dealContentWord();data.visible = false;loadingInstance.close();message("文章保存成功", "success");await router.push({path: "/index",});
}async function dealTitleWord() {const titleWords = splitText(data.title);for (let i = 0; i < titleWords.length; i++) {const segment = titleWords[i].segment;await openTitleWordLog();const titleWordRes = await dbOperation.getDataByField("title_word", segment);if (titleWordRes.data.length > 0) {const titleWord = titleWordRes.data[0];const article_id_list = titleWord.article_id_list;if (article_id_list.indexOf(data.id) === -1) {article_id_list.push(data.id);await dbOperation.update({id: titleWord.id,title_word: segment,article_id_list: article_id_list,create_time: new Date(),});}} else {await dbOperation.add([{title_word: segment,article_id_list: [data.id],create_time: new Date(),}]);}}
}async function dealContentWord() {const contentWords = splitText(data.text);for (let i = 0; i < contentWords.length; i++) {const segment = contentWords[i].segment;await openContentWordLog();const contentWordRes = await dbOperation.getDataByField("content_word", segment);if (contentWordRes.data.length > 0) {const contentWord = contentWordRes.data[0];const article_id_list = contentWord.article_id_list;if (article_id_list.indexOf(data.id) === -1) {article_id_list.push(data.id);await dbOperation.update({id: contentWord.id,content_word: segment,article_id_list: article_id_list,create_time: new Date(),});}} else {await dbOperation.add([{content_word: segment,article_id_list: [data.id],create_time: new Date(),}]);}}
}
</script><template><MdEditor v-model="data.text" :toolbarsExclude="['github']" style="height: 100%; width: 100%"@onSave="onSave" @on-upload-img="onUploadImg"/><el-dialog v-model="data.visible" title="文章标题" width="80%"><el-input v-model="data.title" :rows="3" resize="none" type="textarea"/><template #footer><el-button @click="data.visible = false">关闭</el-button><el-button type="primary" @click="write">保存</el-button></template></el-dialog>
</template><style lang="scss" scoped></style>

文章搜索页面代码

<script setup>
import {onBeforeMount, reactive} from "vue";
import {confirm, message, splitText} from "@/util";
import {useRouter} from "vue-router";
import {CONTENT_RATE, openArticleLog, openContentWordLog, openTitleWordLog, TITLE_RATE} from "@/util/config";
import {dbOperation} from "@/util/dbOperation";const data = reactive({searchInput: "",articleList: [],
});onBeforeMount(() => {initData();
});async function initData() {await openArticleLog();const res = await dbOperation.getLimitOrderDescData("create_time", 10);data.articleList = res.data;
}const router = useRouter();function preview(item) {router.push({path: "/article",query: {id: item.id,},});
}function edit(item) {router.push({path: "/write",query: {id: item.id,},});
}function deleteArticle(item) {confirm("确认删除该文章吗?", async () => {await openArticleLog();await dbOperation.delete([Number(item.id)]);message("文章删除成功", "success");await initData();});
}async function search() {if (data.searchInput.trim().length === 0) {initData();return;}if (data.searchInput.length > 20) {message("搜索词最长20个字符", "warning");return;}const searchWords = splitText(data.searchInput);const titleList = [];for (let i = 0; i < searchWords.length; i++) {const segment = searchWords[i].segment;await openTitleWordLog();const titleWordRes = await dbOperation.getDataByField("title_word", segment);if (titleWordRes.data.length > 0) {titleList.push(titleWordRes.data[0].article_id_list);}}const contentList = [];for (let i = 0; i < searchWords.length; i++) {const segment = searchWords[i].segment;await openContentWordLog();const contentWordRes = await dbOperation.getDataByField("content_word", segment);if (contentWordRes.data.length > 0) {contentList.push(contentWordRes.data[0].article_id_list);}}const articleIdMap = {};for (let i = 0; i < titleList.length; i++) {const article_id_list = titleList[i];for (let j = 0; j < article_id_list.length; j++) {if (!articleIdMap[article_id_list[j]]) {articleIdMap[article_id_list[j]] = TITLE_RATE;} else {articleIdMap[article_id_list[j]] += TITLE_RATE;}}}for (let i = 0; i < contentList.length; i++) {const article_id_list = contentList[i];for (let j = 0; j < article_id_list.length; j++) {if (!articleIdMap[article_id_list[j]]) {articleIdMap[article_id_list[j]] = CONTENT_RATE;} else {articleIdMap[article_id_list[j]] += CONTENT_RATE;}}}const articleIdList = [];Object.keys(articleIdMap).forEach(function (key) {articleIdList.push({articleId: key,count: articleIdMap[key],});});articleIdList.sort(function (o1, o2) {return o2.count - o1.count;});const articleList = [];for (let i = 0; i < articleIdList.length; i++) {await openArticleLog();const articleRes = await dbOperation.getDataByField("id", Number(articleIdList[i].articleId));if (articleRes.data.length > 0) {articleList.push(articleRes.data[0]);}}data.articleList = articleList;
}
</script><template><div style="width: 100%; height: 100%; padding: 1rem"><el-row style="margin-bottom: 1rem; justify-content: center"><el-input v-model="data.searchInput" placeholder="请输入搜索" size="large" style="width: 30rem" @change="search"/></el-row><template v-for="item in data.articleList" :key="item.id"><el-card shadow="hover" style="width: 100%; margin-bottom: 1rem"><h3>{{ item.article_title }}</h3><p style="float: right; font-size: 0.8rem">{{ item.create_time }}</p><el-row style="margin-top: 1rem"><el-button type="primary" @click="preview(item)" class="btn">查看</el-button><el-button type="info" @click="edit(item)" class="btn">编辑</el-button><el-button type="danger" @click="deleteArticle(item)" class="btn">删除</el-button></el-row></el-card></template></div>
</template><style lang="scss" scoped>
.btn {width: 4rem;height: 2rem;line-height: 2rem;
}
</style>

在书写了一些简单的小项目后,感到indexDB这个数据库还是有它的优点的,使用便捷,部署方便。我也简单的书写了一个工具类,之前一直都是采用回调函数的方式,在层次较深时逻辑很不清晰,采用Promise的形式,使用起来更加方便一些

import {message} from "@/util/index";class DbOperation {request = undefined;db = undefined;dbName = undefined;tableName = undefined;fieldList = undefined;init(dbName, tableList) {const request = window.indexedDB.open(dbName);request.onsuccess = function (event) {dbOperation.db = event.target["result"];};request.onupgradeneeded = function (event) {dbOperation.db = event.target.result;for (let i = 0; i < tableList.length; i++) {createTable(tableList[i].tableName, tableList[i].fieldList);}};function createTable(tableName, fieldList) {if (!dbOperation.db.objectStoreNames.contains(tableName)) {const objectStore = dbOperation.db.createObjectStore(tableName, {keyPath: "id",autoIncrement: true});for (let i = 0; i < fieldList.length; i++) {objectStore.createIndex(fieldList[i], fieldList[i]);}}}}open(dbName, tableName, fieldList) {return new Promise((resolve) => {dbOperation.dbName = dbName;dbOperation.tableName = tableName;dbOperation.fieldList = fieldList;const request = window.indexedDB.open(dbName);dbOperation.request = request;request.onsuccess = function (event) {dbOperation.db = event.target["result"];resolve();};});}getObjectStore() {const transaction = dbOperation.db.transaction(dbOperation.tableName, "readwrite");return transaction.objectStore(dbOperation.tableName);}add(dataList) {return new Promise((resolve) => {if (dbOperation.dbName === undefined) {message("数据库还未打开", "warning");return;}const transaction = dbOperation.db.transaction(dbOperation.tableName, "readwrite");const objectStore = transaction.objectStore(dbOperation.tableName);for (let i = 0; i < dataList.length; i++) {objectStore.add(dataList[i]);}transaction.oncomplete = () => {resolve();};});}update(newData) {return new Promise((resolve) => {if (dbOperation.dbName === undefined) {message("数据库还未打开", "warning");return;}const objectStore = dbOperation.getObjectStore();const men = objectStore.put(newData);men.onsuccess = function () {resolve();};});}delete(idValueList) {return new Promise((resolve) => {if (dbOperation.dbName === undefined) {message("数据库还未打开", "warning");return;}const transaction = dbOperation.db.transaction(dbOperation.tableName, "readwrite");const objectStore = transaction.objectStore(dbOperation.tableName);for (let i = 0; i < idValueList.length; i++) {objectStore.delete(idValueList[i]);}transaction.oncomplete = () => {resolve();};});}getAllData() {return new Promise((resolve) => {if (dbOperation.dbName === undefined) {message("数据库还未打开", "warning");return;}const objectStore = dbOperation.getObjectStore();const men = objectStore.openCursor();const data = [];men.onsuccess = function (event) {const row = event.target["result"];if (row) {data.push(row.value);row.continue();} else {resolve({data: data,});}};});}getLimitOrderDescData(fieldName, limit) {return new Promise((resolve) => {if (dbOperation.dbName === undefined) {message("数据库还未打开", "warning");return;}const objectStore = dbOperation.getObjectStore().index(fieldName);const men = objectStore.openCursor(null, "prev");const data = [];men.onsuccess = function (event) {const row = event.target["result"];if (row) {if (data.length >= limit) {resolve({data: data,});return;}data.push(row.value);row.continue();} else {resolve({data: data,});}};});}getDataByField(fieldName, fieldValue) {return new Promise((resolve) => {if (dbOperation.dbName === undefined) {message("数据库还未打开", "warning");return;}const objectStore = dbOperation.getObjectStore().index(fieldName);const men = objectStore.openCursor(IDBKeyRange.only(fieldValue));const data = [];men.onsuccess = function (event) {const row = event.target["result"];if (row) {data.push(row.value);row.continue();} else {resolve({data: data,});}};});}
}export const dbOperation = new DbOperation();

效果展示

搜索页面
在这里插入图片描述

书写页面
在这里插入图片描述

阅读页面
在这里插入图片描述

编辑页面
在这里插入图片描述

采用electron打包为桌面应用
在这里插入图片描述

源码下载

冰冰一号的个人仓库系统

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/419953.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32——串口通信(发送/接收数据与中断函数应用)

文章目录 通信&#xff1a;串口通信简介&#xff1a;1.双工/单工&#xff1a;2.同步/异步&#xff1a;3.电平&#xff1a;电平标准&#xff1a; 串口参数以及数据帧时序&#xff1a;数据帧&#xff1a;1.波特率和比特率&#xff1a;例&#xff1a;无校验&#xff0c;1位停止位 …

【WebGIS实例】(16)GeoServer 自定义样式 - 渲染矢量数据

1. 前言 本篇博客将会分享一系列的 GeoServer 样式&#xff0c;通过这些样式预先在服务端完成数据渲染&#xff0c;让前端应用更便捷的加载数据服务。 2. 面矢量 示例数据&#xff1a; {type: FeatureCollection,features: [{type: Feature,properties: {分类字段: 字段一…

数据权限的设计与实现系列6——前端筛选器组件Everright-filter使用探索

linear 功能探索 最终我们是需要使用 API 的方式&#xff0c;调用后端服务拉取数据填充筛选器组件&#xff0c;不过在探索阶段&#xff0c;直接用 API 方式&#xff0c;就需要构造 mock 数据&#xff0c;比较麻烦&#xff0c;因此先使用 Function 方式来进行功能验证。 组件初…

代理模式详解

1.基本介绍 代理模式&#xff1a;为一个对象提供一个替身&#xff0c;以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能;被代理的对象可以是远程对象、创建开销大的对象或需要安全控制…

记录:uniapp直播的弹幕的样式修改与发送弹幕会自动滚动到底部两个技巧

1、在直播页面的弹幕评论中&#xff0c;我们希望的样式是&#xff1a; 观众名字&#xff1a;评论 而且颜色有所区分&#xff0c;并在同一行显示 2、我们希望在发弹幕的时候可以回自动滚动到自己发的内容那里 一&#xff1a;弹幕样式修改 因为是小白&#xff0c;前端对于样式这…

苹果手机照片被删除?如何通过不同的方法来恢复照片

手机已经成为我们生活中不可或缺的一部分&#xff0c;它不仅仅是通讯工具&#xff0c;更是我们记录生活点滴的重要工具之一。然而&#xff0c;正如其他任何设备一样&#xff0c;iPhone上存储的照片有时也会不小心被删除或丢失。 别担心&#xff0c;即使你误删了重要的照片&…

重头开始嵌入式第三十四天(数据库二)

sqlite3的一些补充 目录 sqlite3的一些补充 1.事物 2.连接&#xff0c;联合 3.触发器 4.子查询 1.事物 数据库事务是数据库管理系统执行过程中的一个逻辑单位&#xff0c;它由一系列对数据库的操作组成。 一、事务的特性 1. 原子性&#xff08;Atomicity&#xff09…

Linux:目录及文件管理

目录及文件管理 cd的命令使用 . 当前目录 .. 父目录&#xff08;上一层&#xff09; ~ 表示家目录 家目录&#xff1a;专门存放用户个性化信息的目录 ~user&#xff1a;用户user的家目录 /root: 是Linux管理员的家目录 /home: 存放所有普通用户的家目录]# cd ~root #去…

大模型LLM算法工程师技术面试指南

大模型LLM算法工程师技术面试指南 AI大模型全套学习资料 “最先掌握AI的人&#xff0c;将会比较晚掌握AI的人有竞争优势”。 这句话&#xff0c;放在计算机、互联网、移动互联网的开局时期&#xff0c;都是一样的道理。 我在一线互联网企业工作十余年里&#xff0c;指导过不少…

Java异常类

目录 Java异常类 Java中的异常体系 抛出异常 处理异常 处理异常的两种方式 try...catch和throws的区别 finally关键字 抛出异常注意事项 自定义异常类 Java异常类 Java中的异常体系 在Java中&#xff0c;异常类的父类为Throwable类&#xff0c;在Throwable下&#x…

记一次高版本view-design的组件迁移到自身项目的低版本

背景 npm i -S view-design当前老项目使用view-design这个组件库&#xff0c;但是当我们去官网查看该组件库最新版本&#xff0c;竟然发现没有博主想用的image/ImagePreivew这两个基础组件 说实话&#xff0c;有点离谱了哈&#xff01;&#xff01; 自己造轮子&#xff1f; …

数据结构基本知识

一、什么是数据结构 1.1、组织存储数据 ---------》内存&#xff08;存储&#xff09; 1.2、研究目的 如何存储数据&#xff08;变量&#xff0c;数组....)程序数据结构算法 1.3、常见保存数据的方法 数组&#xff1a;保存自己的数据指针&#xff1a;是间接访问已经存在的…

分库分表核心理念

文章目录 分库&#xff0c;分表&#xff0c;分库分表什么时候分库&#xff1f;什么时候分表&#xff1f;什么时候既分库又分表&#xff1f;横向拆分 & 纵向拆分 分表算法Range 范围Hash 取模一致性 Hash斐波那契散列 严格雪崩标准&#xff08;SAC&#xff09;订单分库分表实…

【880高数】高等数学一刷错题整理

第一章 函数、极限、连续 2024.8.11日 1. 2. 3. 4. 5. 2024.8.12日 1. 2. 3. 4. 5. 6. 7. 8. 2024.8.13日 1. 2. 3. 4. 2024.8.14日 1. 2. 3. 4. 5. 第二章 一元函数微分学及其应用 2024.8.15日 1. 2. 3. 4. 5. 6. 2024.8.16日 1. 2. 3. 4. 5. 2024.8.17日 1. 2. 3. 4…

个人简历 (自己设计的)

欢迎大家来观看。 代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" co…

相亲交友系统背后的科技力量:智能匹配的秘密

随着互联网技术的发展&#xff0c;相亲交友系统已经成为许多人寻找另一半的重要工具。这些相亲交友系统不仅仅是一个简单的社交平台&#xff0c;它们背后隐藏着强大的科技力量&#xff0c;尤其是智能匹配技术&#xff0c;使得用户能够更加高效地找到适合自己的伴侣。 相亲交友…

信息学奥赛初赛天天练-87-NOIP2014普及组-完善程序-矩阵、子矩阵、最大子矩阵和、前缀和、打擂台求最大值

1 完善程序 最大子矩阵和 给出 m行 n列的整数矩阵&#xff0c;求最大的子矩阵和(子矩阵不能为空)。 输入第一行包含两个整数 m和 n&#xff0c;即矩阵的行数和列数。之后 m行&#xff0c;每行 n个整数&#xff0c;描述整个矩阵。程序最终输出最大的子矩阵和。 &#xff08;最…

C语言俄罗斯方块(VS2022版)

C语言俄罗斯方块 演示视频一、前置知识1.Win32 API 的使用2.宽字符的使用 二、封装核心数据与框架介绍三、核心操作介绍旋转操作检测操作水平检测竖直检测代码化简 四、源码展示在 tetris.h 中&#xff1a;在 tetris.c 中&#xff1a;在 test.c 中&#xff1a; 以下代码环境为 …

码上进阶_刷题模块测试_用例设计

码上进阶_刷题模块测试_用例设计 系统概述&#xff1a; 码上进阶是为程序员专门打造的交流平台&#xff0c;采用主流的微服务框架和C端技术栈作为技术基础。在这个平台上&#xff0c;程序员 可以通过刷题、练习和模拟面试来提升自己的面试能力。 功能测试&#xff1a; 登录…

SpringBoot OAuth2自定义登陆/授权页

背景 5 月份的时候&#xff0c;我实践并整理了一篇博客&#xff1a;SpringBoot搭建OAuth2&#xff0c;该博客完成之后&#xff0c;很长一段时间里我都有种意犹未尽的感觉。诚然&#xff0c;我把OAuth2搭起来了&#xff0c;各种场景的用例也跑通了&#xff0c;甚至源码也看了&am…