基于SpringBoot + Vue3的在线笔记系统

基于Spring Boot + Vue3 的 在线笔记系统的设计与实现

1. 绪论

1.1 研究背景

​ 近几年来,随着信息技术的飞速发展,计算机普及率越来越高,人们对于信息存储和管理的需求也在不断增加。传统的笔记方式已经无法满足人们对于高效、便捷、安全的信息管理需求。因此,随着Web技术的发展,在线笔记系统逐渐成为了一个研究的热点。

​ 同时,笔记作为入门学习过程中不可缺失的一部分,也表现出了与时代和技术相适应的新趋向。在工作中,人们需要记录会议内容、任务清单、项目进度等信息,以便于沟通和协作。在学习中,学生需要记录课堂笔记、读书笔记、研究心得等信息,以便于巩固知识和提高学习效果。借助于在线笔记系统能够满足这些需求,提供便捷的记录和整理方式,提高工作效率和学习效果。

​ 人们需要有效地管理个人知识,以便于快速查找、整理和使用信息。在线笔记系统能够帮助人们构建自己的知识库,对信息进行分类、标签、摘要等操作,提高个人知识管理的效率。

1.2 研究目的

​ 本文旨在基于Spring Boot + Vue框架对系统进行研究和开发,结合实际情况使系统提供一种高效的信息管理方式,使用户能够快速记录、整理和查找信息的在线笔记系统。

​ Web 应用的前端开发分为三个层次,分别为 视图层、业务逻辑层、数据层,其中视图层由 Naive UI 提供的组件进行编写。业务逻辑层和数据曾使用Vue组件对用户的行为和数据进行处理和双向绑定。

​ 设计前端页面,实现用户查看笔记、小记、回收站、个人信息界面以及暗色模式切换功能。

​ 通过开发在线笔记系统,可以使得用户摆脱了传统笔记无法跨平台、跨设备使用的限制。无论用户使用哪种设备,都可以随时访问和编辑自己的笔记内容,确保信息的一致性和可访问性。 本文旨在相关领域提供参考价值,同时为在线笔记系统应用程序的开发提供一定的实践指导建议。

1.3 研究意义

​ 为了方便人们的知识存储,在线笔记不仅满足传统笔记记录的要求,而且可以作为一个存储空间来存储文本信息,并同步到服务器。在面对浩如烟海的信息时,在线笔记系统可以作为人类的外脑,及时记录零碎的信息,释放大脑空间。同时,人脑中存储的知识会随着时间的推移逐渐淡化,而在线笔记把文本数据存储到服务器中,信息可不受外界干扰长期保存,并能保证其真实性。这有利于知识资源的继承和发展,对大学生的个人知识管理具有重大意义。

2 项目技术介绍

2.1 前端技术

2.1.1 Vue

Vue 框架是基于 JavaScript 所开发的前端框架,可用于构建用户界面。它是一个渐进式框架,并提供了响应式数据绑定的概念,将数据和DOM元素建立了关联。当数据发生改变时,相应的DOM元素也会发生变化,这种机制可使得开发者更加方便的开发和维护用户界面。

Vue 提供了组件化开发的能力,可将多个页面拆分为独立的组件。每个组件都拥有自己的模板、逻辑以及样式,可在不同的组件中重复使用。

Vue 拥有丰富的生态环境,其中不仅包括官方库、更有社区库和第三方库。这使得选用 Vue 开发能够快速地构建功能丰富的应用,提高开发效率。

2.1.2 Naive UI

Naive UI 是一个基于 Vue3 的组件库,旨在为开发者提供一套简洁、易用且功能丰富的 UI 组件。它拥有丰富的主题、字体和图标设置,可使得开发者根据项目需求轻松调整 UI 样式。

2.1.3 Pinia

Pinia 是一个用于 Vue3 的状态管理库,它使用 TypeScript ,可以在编译时捕获许多常见的错误,并且提供了强类型的状态管理,避免了潜在的错误和调试困难。

​ 它采用了响应式的数据管理机制,只更新发生变化的状态,从而获得了更高的性能和更好的用户体验

​ 它的出现极大简化了 VueX 的使用,是Vue3 的新的状态管理工具。

2.1.4 CkEditor5

CKEditor 5 是一个功能丰富且可定制的富文本编辑器,它提供了丰富的编辑功能和灵活的插件系统,采用了模块化的架构,将编辑器的功能划分为多个独立的模块。这使得开发人员可以根据项目需求来选择和加载所需的功能模块,从而减少资源消耗和编辑器的体积。

​ 它提供了现代化的用户界面,具有直观和易于使用的文本编辑体验。得益于它支持所见即所得编辑模式,使用户可以直接在编辑器中预览和编辑文本内容。

2.1.5 GSAP

GSAP 是一个功能强大的 JavaScript 动画库,用于创建高性能和流畅的动画效果。它提供了丰富的动画功能和易于使用的 API,并且兼容各种主流的浏览器。它封装了浏览器之间的差异,以确保在不同的浏览器上获得一致的动画效果。

2.1.6 tsParticles

tsParticles 是一个使用 TypeScript 编写的轻量级粒子动画库,用于创建各种炫酷的粒子效果,它不仅提供了丰富的配置选项,更可以自定义粒子的形状、颜色、大小、速度等属性,并且对主流浏览器使用了优化算法和硬件加速技术,提高了动画性能和响应速度。

2.1.7 Cropper

Cropper 是一个基于 HTML5 的开源图片裁剪框,它提供了简单易学的 APIDOM 元素,并且兼容了主流浏览器,方便开发者实现图片的裁剪、缩放、旋转和镜像等操作。

2.2 后端技术

2.2.1 SpringBoot

Spring Boot 是基于 Spring 所开发的一款开源框架,其设计目的是简化新 Spring 应用的初始搭建以及开发过程。它充分利用了 JavaConfig 的配置模式以及 “约定优于配置” 的理念,使得基于 Spring MVC 的 Web 应用开发变得更加简单。

Spring Boot 不仅支持多种嵌入式 web 服务器 ,并且会根据项目中的依赖来自动配置相关组件,可以十分轻松的跟 Spring 生态系统中的组件进行集成,如 Spring DataSpring Security,在项目开发完毕后可以被单独打包成 JAR 包,不需要外部容器来运行。

2.2.2 Mybatis-Flex

MyBatis-Flex 是一个基于 MyBatis 框架的扩展,它提供了更灵活的查询和结果映射功能。它简化复杂的SQL查询和结果处理,使得开发者可以更轻松地与数据库进行交互。

2.3 存储技术

2.3.1 Redis

Redis是一款高性能的内存数据存储服务器,通常用于缓存、消息队列、排行榜、计数器等场景。它支持多种数据结构,如字符串、哈希、列表、集合、有序集合等。Redis的服务器端和客户端采用单线程模型,但其内部通过异步 I/O 和事件驱动机制实现了高并发处理。

2.3.2 MySQL

MySQL 是一个关系型数据库管理系统,它是开源的,体积小、速度快、成本低,尤其是开放源码这一特点,使得许多中小型网站为了降低网站总体拥有成本而选择了MySQL作为网站数据库。MySQL将数据分别存储在不同的表中,这种存储方式增加了速度并提高了灵活性。此外,MySQL使用的SQL语言是用于访问数据库的最常用标准化语言。

3 系统的开发平台以及运行环境

3.1 软件开发环境

软件的开发环境如表 3-1 所示:

​ 表 3 - 1 软件环境表

序号 名称 版本 备注
1 Windows 11 操作系统
2 JDK 8 开发工具包
3 Redis 6 缓存
4 MySQL 5.7 数据库

3.2 硬件开发环境

硬件环境如表 3-2 所示:

​ 表 3 - 2 硬件环境表

序号 名称 备注
1 处理器 i5 - 9300H
2 宏基 16G RAM

3.3 开发工具

开发工具如表 3-3 所示:

​ 表 3 - 3 开发工具表

序号 名称 版本 备注
1 IntellJ IDEA 2023 3.4 集成编译器
2 Maven 3.9.6 项目管理工具
3 Another Redis Desktop Manager 1.6.1.0 数据库管理工具
4 Navicat 16 数据库管理工具

4 项目需求分析

4.1 业务分析

4.1.1 全局暗色模式切换

为了使用户在使用在线笔记系统中有更好的书写体验,减少暗光环境下用户使用系统屏幕对眼睛的伤害,通过 Naive-UI 提供的组件进行暗色模式的实现,同时在实现该功能中,考虑到用户存在会同时打开多个标签页进行笔记或小记的编写,应保证一个标签页进行模式切换后,其他标签页也会进行模式的切换。

4.1.2 图片上传裁剪框

用户在使用笔记系统的过程中,会对个性化头像产生需求,基于 Cropper 提供的图片裁剪框功能来实现用户对头像的自定义,并且能对头像图片进行旋转、镜像、放大、缩小、裁剪等操作。

​ 图 4.1 头像上传裁剪框图

4.1.3 邮箱注册账号

用户在注册时,需要提供自己邮箱账号进行注册,系统应向该邮箱发送一个包含注册验证码内容的电子邮箱,同时将垓验证码存储到 Redis 中用户收到验证码后,应在输入框中填写验证码,在提交时前端进行验证码长度的校验,校验无误后再发送给后端,后端则访问 Redis 数据库再次进行校验,确认无误后,在数据库中新建用户的基本信息。

4.1.4 个人信息

用户在点击自己的头像后,网页将弹出一个 提醒框 来展示用户的部分信息,如 邮箱、昵称、性别、等级、出生日期、注册时间,其中昵称跟性别以及出生日期允许用户自行进行更改,并且在点击保存按钮之后,页面的头像元素会实时被更替为用户上传的图像。

4.1.5 回收站

用户在删除笔记或小记以后,当数据库中笔记或小记所处的状态值为 0 时,MySQL视图会根据状态筛选出符合条件的笔记或小记字段加入到该视图中,回收站的列表会根据该视图来进行获取,当用户勾选任意的被删除的笔记或小记,点击批量恢复后,MySQL中对应的字段值应改回1。

4.1.6 最近操作

用户在对笔记或小记进行编辑后,会使改笔记或小记对应数据库存储的 update_time 字段发生改变,MySQL数据库会根据该字段对应现实中的日期来进行排列,只要在七天以内,会在首页对该笔记或小记进行一个缩略展示,当用户点击该笔记或小记时,会跳装到对应的界面并展示它们。

5 项目设计与实现

5.1 数据库设计

5.1.1 数据库设计思想

在线笔记系统的用户信息、以及笔记和小记的内容的存储是以 MySQL 数据库所建立起来的,对回收站模块和最近使用模块进行设计运用到了 MySQL 的视图功能,并且根据关系图实现了部分表的增、删、改、查功能。

在设计表时,应遵循数据规范原则,确保表中的数据不会出现冗余和不一致的问题,并且应当选择合适的数据类型来存储数据,避免浪费存储空间和影响服务器性能。

在对经常需要查询或使用的数据的字段应当加上合适的索引,用以提高查询性能,同时应当避免过多的索引,因为过多的索引会增加读写操作的负担,并且会降低操作性能。

当需要对已有的多个表进行查询,并将结合进行整合,需要合理使用 MySQL 自带的视图功能,它是基于一个或多个表的查询结果集合,具有与表相同的结构,但实际并不存储数据,能简化对应的 SQL 语句操作,并且能提高数据的保密性和安全性,并有数据独立性的特点。

在后端对数据库进行增、删、改操作时,应当使用 MySQL 的事务功能,事务是一组 SQL 语句的结合,当它进行执行时,会产生两种结果,全部执行成功或回滚到初始状态,这样有利于数据的完整性和一致性。

5.1.2 数据库表设计

通过对在线笔记系统功能的分析,可以确认这个数据库应该包含一下内容:

  1. 用户表:用户可以通过邮箱账号和密码来登录系统,并且能更改自己的部分信息

  2. 笔记表:通过编号来绑定用户,里面存储用户笔记的标题、内容、创建时间和更新时间以及部分状态

  3. 小记表:同样通过编号来绑定用户,里面存储用户小记的标题、代办事项、创建时间和更新时间以及部分状态

  4. 用户日志表: 里面包含对用户操作的记录,应有事件描述、操作时间、事件编号、用户的编号

  5. 文件日志表:包含用户对笔记或小记的操作记录,包括操作事件、事件编号、文件编号、文件类型

    ​ 图 5.1.2 数据库模型

5.1.2.1 用户表

用户表如表 5.1.2 所示

​ 用户表 5.1.2

字段名 数据类型 长度 是否可为空 备注
id int 11 用户编号
email varchar 255 用户邮箱
password varchar 255 用户密码
nickname varchar 255 用户昵称
head_pic varchar 1000 用户头像 【存储URL地址】
level int 11 用户等级 【0:普通会员 1:超级会员】
time datatime 用户注册时间
sex int 1 性别【0:女生,1:男生】
birthday date 1 用户生日
status int 11 状态【0:锁定,1:正常】
5.1.2.2 笔记表

笔记表如表 5.1.3 所示

​ 笔记表 5.1.3

字段名 数据类型 长度 是否可为空 备注
id int 11 笔记编号
title varchar 255 笔记标题
body longtext 笔记内容
content longtext 笔记完整内容
time datatime 笔记创建时间
update_time datatime 笔记最后修改时间
u_id int 11 笔记所属用户编号【外键:对应用户表-id】
top int 1 笔记置顶状态【1:置顶,2:不置顶】
status varchar 2 笔记状态【-1:彻底被删除,0:被删除,1:正常】
type int 11 文件类型【1:笔记,2:小记】
5.1.2.3 小记表

小记表如表 5.1.4 所示

​ 笔记表 5.1.4

字段名 数据类型 长度 是否可为空 备注
id int 11 小记编号
title varchar 255 小记标题
tags longtext 小记标签
content longtext 小记内容
u_id int 11 小记所属用户编号 【外键:对应用户表-id】
finished int 11 小记完成状态 【0:未完成,1:已完成】
time datetime 小记创建时间
update_time datatime 小记最后修改时间
top int 1 小记置顶状态【1:置顶,2:不置顶】
status varchar 2 小记状态【-1:彻底被删除,0:被删除,1:正常】
type int 11 文件类型【1:笔记,2:小记】
5.1.2.4 用户日志表

用户日志表如表 5.1.5 所示

​ 用户日志表 5.1.5

字段名 数据类型 长度 是否可为空 备注
id int 11 用户日志编号
desc varchar 255 用户日志描述
time datetime 用户日志创建时间
event varchar 255 用户日志事件
u_id int 11 用户日志所属用户编号 【外键:对应用户表-id】
5.1.2.5 文件日志表

文件日志表如表 5.1.6 所示

​ 用户日志表 5.1.6

字段名 数据类型 长度 是否可为空 备注
id int 11 主键
time datetime 文件日志创建时间
event varchar 255 文件日志事件
desc varchar 255 文件日志描述
u_id int 11 文件日志所属用户编号 【外键:对应用户表-id】
f_id int 11 文件编号
f_type int 11 文件类型【1:笔记,2:小记,3: …】

5.1.3 数据库视图设计

为了减少数据库对服务器性能的占用,以及提高查询 SQL 语句查询效率,在已有的表的基础上可以抽离出两个视图,用于完成在线笔记系统的最近使用和垃圾回收站功能。

5.1.3.1 回收站视图

通过上述的笔记表和小记表可以发现,它们有一个共同的字段名 status ,这个字段名记录当前笔记或小记所处的状态,通过该字段,可以筛选出已被删除的笔记或小记,通过 SQL语句的WHERE条件同时对笔记表和小记表的状态字段进行判断,可以完成生成新的回收站视图

1
2
3
4
5
# 状态为删除状态的笔记和小记
CREATE OR REPLACE VIEW z_file_dumpster AS
SELECT `id`, `title`, `update_time`, `u_id`, `type` FROM `z_note` WHERE `status` = 0
UNION ALL
SELECT `id`, `title`, `update_time`, `u_id`, `type` FROM `z_thing` WHERE `status` = 0

回收站视图如表 5.1.7所示

​ 表 5.1.7 回收站视图

字段名 数据类型 长度 是否可为空 备注
id int 11 文件编号
title datetime 文件标题
update_time varchar 255 文件更新时间
u_id varchar 255 文件所属的用户ID 【外键:对应用户表-id】
type int 11 文件类型【1:笔记,2:小记,3: …】
5.1.3.2 最近操作视图

为了实现最近使用模块的展示,在已有的笔记表和小记表上抽离共用的字段,如id, titleupdate_timeu_idtype 字段来查询出一个新的视图,这个视图可以用于对实体类形成对应关系,可以减少数据库的内存占用。

1
2
3
4
5
6
7
8
9
10
11
# 一周以内的操作的文件
CREATE OR REPLACE VIEW z_file_recently_use AS
SELECT
`id`, `title`, `update_time`, `u_id`, `type`
FROM `z_note`
WHERE `status` = 1 AND `update_time` BETWEEN NOW() + INTERVAL -7 DAY AND NOW()
UNION ALL
SELECT
`id`, `title`, `update_time`, `u_id`, `type`
FROM `z_thing`
WHERE `status` = 1 AND `update_time` BETWEEN NOW() + INTERVAL -7 DAY AND NOW()

从上述 SQL 语句可以看出,借助于 MySQL 自带的日期函数 NOW 和 区间值运算符 BETWEEN , 由 NOW 获取的当前系统时间来,同时用 INTERVAL 关键字来表示时间间隔,最后判断是否处于一周以内的操作文件。

最近操作视图如表 5.1.8 所示

​ 表5.1.8 最近操作视图

字段名 数据类型 长度 是否可为空 备注
id int 11 文件编号
title datetime 文件标题
update_time varchar 255 文件更新时间
u_id varchar 255 文件所属的用户ID 【外键:对应用户表-id】
type int 11 文件类型【1:笔记,2:小记,3: …】

5.2 系统设计

5.2.1 系统设计理念

在对在线笔记系统设计前,需要明确软件体系结构思想,对具有相似功能或重复功能的组件进行复用,例如 在后端中笔记和小记列表的展示,这部分功能是高度相似,可以直接在原有的代码进行修改并结合场景做适应性修改,并可以得到实现功能的模块。在前端中也会出现这个现象,例如编辑小记和添加小记模块会弹出相似度高度重复的模态框,这个组件也可以做适应性修改来实现对应的功能。

在对系统进行开发时,必须遵循低耦合,高内聚的设计理念,用以增加模块的可移植性和可复用性,尽可能的减少对在类内部对其他类进行调用,将模块的功能尽量进行单一化处理,当模块的功能比较简单时,它供其他模块调用的机会就会少,从而减少耦合化。

5.2.2 系统体系结构风格

不断成熟的Web技术为这次开发选用了 浏览/服务器风格(B/S)风格,得益于浏览器强大的脚本语言,为原本需要复杂的专用软件才能实现的强大功能,如今用浏览器就可以实现,这一定程度上节约了开发成本。

在 B/S 结构中,除去数据库需要部署到专用服务器外,软件以网页的方式存放到Web服务器上,用户需要运行这个软件,只需要在客户端的浏览器中输入对应的网址,通过调用 Web 服务器上的软件来操作数据库服务器中的数据,从而完成响应的数据处理工作,最后通过浏览器将结果展示给用户,由此可见在 B/S 结构风格中,系统的软件的安装、修改、维护全会在幅度短短中进行处理,用户仅需要一个浏览器就能使用全部的模块。

同时 B/S 结构也有不足之处,比如说采用B/S体系结构的软件系统,在查询数据时,它的响应数据会远远低于C/S体系结构系统,并且由于B/S的体系结构的系统扩展能力较差,它的安全性也难以得到控制。

5.2.3 系统整体设计

系统整体分为三个层次:应用头、左侧应用工具栏、主页面。

  1. 应用头:软件名称、登录、主题、消息、用户头像(用户菜单列表及分割线)

  2. 左侧应用主工具栏:新建、搜索、主页、笔记、小记、回收站、关于

  3. 主页面:对应的功能页面,比如 笔记界面、回收站页面

    ​ 图 5.3 系统整体设计图

    在线笔记图

5.3 系统实现

5.3.1 应用头部容器模块实现

在线笔记的应用头部(MainTopToolBar)由软件名称、登录后的用户头像、消息图标、主题切换按钮 、登录按钮组成,为了保证整个软件整个风格统一,这些小模块借助于Naive UI 的组件进行实现,并且在实际开发过程中,未登录状态下显示登录按钮,用户头像及其个人中心和分割线不显示,登录状态下则相反。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<template>
<!-- 使用 align 让间距组件内的元素垂直居中,设置高度100% 让间距组件充满整个父元素-->
<n-space justify="space-between" align="center" style="height: 100%;">
<n-text>在线笔记</n-text>
<n-space align="center" :wrap-item="false">
<!--头像-->
<n-popover v-model:show="userMenuShow" trigger="click" width="260px" content-style="padding:10px">
<template #trigger>
<n-button circle :bordered="false">
<n-avatar round v-if="user_id !== null" :src="head_image"/>
</n-button>
</template>
<n-thing :title="nickName">
<!--头像-->
<template #avatar>
<n-avatar size="large" round :src="head_image" style="position: relative;top: 3px"/>
</template>
<!--简介-->
<template #description>
<n-space align="center">
<n-tag size="small" :bordered="false" :type="levelInfo.theme">{{ levelInfo.text }}</n-tag>
<n-text depth="3">2099-12-31 到期</n-text>
</n-space>
</template>
<!--分割线-->
<template #default>
<n-divider style="margin: 5px auto"/>
<!--菜单-->
<n-menu id="user-head-menu" :options="userMenu" :on-update-value="clickUserMenu"/>
</template>

</n-thing>
</n-popover>

<!--分割线-->
<n-divider v-if="user_id !== null" vertical/>
<!--消息-->
<n-badge dot processing type="success" :offset="[-8,4]">
<n-button circle tertiary>
<n-icon size="18px" :component="NotificationsOutlined"></n-icon>
</n-button>
</n-badge>

<!--主题按钮-->
<n-button circle tertiary @click="changeTheme(!isDarkTheme)">
<n-icon size="18px" :component="theme.icon"/>
</n-button>
<!--登录按钮-->
<n-button v-if="user_id === null" tertiary type="primary" @click="changeLoginModalShowStatus(true)">登录
</n-button>
</n-space>
</n-space>
<!-- 用户基本信息 -->
<user-basic-info-drawer ref="userBasicInfoRef"/>
</template>

从上述代码可以看到,除了用户基本信息显示以外,整个代码用了一个 n-space 组件进行包围,它是由 Naive UI 提供的一个组件,用于实现页面元素之间的间距,组件额外提供了 alignjustify 分别用于控制在 n-space 内的组件排列方式,考虑到头部应用栏的元素会被分散到两端,这里才用 space-between (两端对齐)对元素进行分散排布。

为了保证用户在登录后,登录按钮和分割线会被隐藏,这里从 PiniaUserstore 中引入了用户登录信息,它会对登录后的用户信息进行一个全局的共享,并且在 UserStore 中对一下默认的 null 值进行了对应处理,例如用户在未上传头像的情况下,会使用 computed 函数对用户的头像地址进行判断,如果未空则会给用户一个默认的头像地址。

​ 图5.3 应用头部容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import {defineStore} from 'pinia'
import {ref, computed, watch} from 'vue'
// 关于登录数据的数据共享
export const useUserStore = defineStore(
"user",
() => {
const token = ref(null) // 用户登录的 token值
const id = ref(null) // 编号
const nickname = ref(null) //昵称
const email = ref(null) //邮箱
const headPic = ref(null) //头像
const level = ref(null) //等级
const time = ref(null) //注册时间
const sex = ref(null) //性别 【1:男 0:女】
const birthday = ref(null) //出生日期

// 用户头像地址
const head_image = computed(() => {
// 如果用户暂无头像,则使用默认头像,否则使用自己的头像
return headPic.value ? headPic.value : "https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg"
})

// 用户昵称
const nickName = computed(() => {
// 如果用户暂无昵称,则使用默认昵称,否则使用自己的昵称
return nickname.value ? nickname.value : "暂未设置昵称"
})

// 用户等级
const levelInfo = computed(() => {
// 如果用户暂无昵称,则使用默认昵称,否则使用自己的昵称
return !!level.value ? {theme: 'error', text: "超级会员"} : {theme: 'default', text: "会员"}
})

/**
* 设置用户信息
* @param userToken 用户的tokean
* @param user {Object} 用户信息
* @param user id {Number} //昵称
* @param user email {String} //邮箱
* @param user nickname {String} //昵称
* @param user headPic {String} //头像
* @param user level {Number} //等级
* @param user time {String} //注册时间
* @param user sex {String} //性别
* @param user birthday {String} //出生日期
*/
const setUserInfo = (userToken, user) => {
token.value = userToken
setUserBasicInfo(user)
}
/**
* 设置用户信息
* @param user {Object} 用户信息
* @param user id {Number} //昵称
* @param user email {String} //邮箱
* @param user nickname {String} //昵称
* @param user headPic {String} //头像
* @param user level {Number} //等级
* @param user time {String} //注册时间
* @param user sex {String} //性别
* @param user birthday {String} //出生日期
*/
const setUserBasicInfo = (user) => {
id.value = user.id
email.value = user.email
nickname.value = user.nickname
headPic.value = user.headPic
level.value = user.level
time.value = user.time
sex.value = user.sex
birthday.value = user.birthday
}

/**
* 重置用户信息
*/
const resetUserInfo = () => {
token.value = null
}

watch(
() => token.value,
newData => {
if (newData === null) {
id.value = null
email.value = null
nickname.value = null
headPic.value = null
level.value = null
time.value = null
sex.value = null
birthday.value = null
}
}
)
return {
token,
id,
email,
nickname,
nickName,
headPic,
level,
levelInfo,
time,
head_image,
sex,
birthday,
setUserInfo,
resetUserInfo,
setUserBasicInfo
}
},
{
persist: {
storage: localStorage, //存储到本地
},
},
)

基于这个 store 就可以来使用 v-ifv-show 对头部应用栏的模块的显示进行控制,登录按钮和分割线会在 user.id 为空的状态下不会进行展示。

5.3.2 全局暗色模切换

为了对在线笔记系统实现全局主题切换,Naive UI 提供了 n-config-provider 组件来进行主题切换,同时通过 Pinia 将 数据共享到整个组件进行使用,当一个组件的页面进行主题切换时,会改变所有组件的主题显示,最后通过 pinia-plugin-persist 插件来对响应式数据 isDarkTheme 进行持久化存储。以方便用户在下次访问时会保持之前的主题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import {defineStore} from 'pinia'
import {ref, computed} from "vue";
import {darkTheme} from "naive-ui";
import {LightbulbOutlined, DarkModeRound} from '@vicons/material'
// 关于主题的全局状态
export const useThemeStore = defineStore("theme", () => {
// 是否是暗系主题
const isDarkTheme = ref(false)
// 暗系主题-灯泡 亮系主题-月牙
const theme = computed(() => {
if (isDarkTheme.value) {
// 暗系主题
return {
name: darkTheme,
icon: LightbulbOutlined
}
} else {
// 亮系主题
return {
name: null,
icon: DarkModeRound
}
}
})
/**
* 更改主题
* @param dark {Boolean} 是否是暗系主题
*/
const changeTheme = dark => {
isDarkTheme.value = dark
}
return {isDarkTheme, theme, changeTheme}
}, {
persist: {
storage: localStorage, //本地存储
paths: ["isDarkTheme"], // 将isDarkTheme 持久化保存
}
}
)

​ 图5.4 暗色主题模式下的页面展示图

5.3.3 个人信息和图片头像裁剪框实现

对于用户个人信息的展示,为了避免耦合化,根据系统设计理念来把它单独封装成了一个组件,为了避免用户填写表单数据错误,需要对用户填写的数据进行校验,这个规则对昵称的长度进行校验,昵称必须在两个字符和六个字符之间,不在这个区间系统会提示错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// 表单验证规则
const formRules = {
nickname: {
trigger: ['input', 'blur'],
message: '昵称需保证到 2-6 字符之间',
validator: (rule, value) => {
return !!value && value !== '' && value.length >= 2 && value.length <= 6;
}
}
}

<template>
<!-- 用户基本信息抽屉 -->
<n-drawer v-model:show="active" :width="400" @after-leave="updateFormItem = false">
<n-drawer-content closable title="用户基本信息">
<!-- 用户头像 -->
<n-space justify="center">
<label>
<n-avatar round :size="120" :src=head_image></n-avatar>
<input ref="fileInputRef" type="file" style="display: none"
accept="image/jpeg, images/jpg, image/png, image/gif"
@change="selectImageFile">
</label>
</n-space>

<!-- 用户信息表单 -->
<n-form ref="formRef" label-placement="left" label-width="auto" :show-require-mark="false" :model="formValue"
:rules="formRules"
style="margin-top: 16px">

<n-form-item label="邮箱:">
<n-text>{{ email }}</n-text>
</n-form-item>

<n-form-item label="昵称:" path="nickname">
<n-text v-if="!updateFormItem" v-bind="nicknameText.props">{{ nicknameText.text }}</n-text>
<n-input v-else v-model:value="formValue.nickname" show-count maxlength="6"></n-input>
</n-form-item>
<n-form-item label="性别:">
<n-text v-if="!updateFormItem">{{ !!formValue.sex ? '男' : '女' }}</n-text>
<n-radio-group v-else v-model:value="formValue.sex">
<n-space>
<n-radio :value="0"></n-radio>
<n-radio :value="1"></n-radio>
</n-space>
</n-radio-group>
</n-form-item>
<n-form-item label="等级:">
<n-tag :bordered="false" :type="levelInfo.theme">{{ levelInfo.text }}</n-tag>
</n-form-item>
<n-form-item label="出生日期:">
<n-text v-if="!updateFormItem" v-bind="birthdayText.props">{{ birthdayText.text }}</n-text>
<n-date-picker
v-else
type="date"
v-model:formatted-value="formValue.birthday"
value-format="yyyy-MM-dd"
:is-date-disabled="disablePreviousDate"
/>
</n-form-item>
<n-form-item label="注册时间:">
<n-text>{{ time }}</n-text>
</n-form-item>

</n-form>

<template #footer>
<n-space>
<n-button
type="success"
v-show="updateFormItem"
:disabled="!showUpdateBtn"
@click="toUpdateBasicInfo">
更新
</n-button>
<n-button v-bind="editBtnObj.props" @click="clickEditOrCancelActiveBtn(!updateFormItem)">
{{ editBtnObj.text }}
</n-button>
</n-space>

</template>

</n-drawer-content>
</n-drawer>

<!-- 裁剪图像的窗口 -->
<cropper-window ref="cropperRef" title="头像上传" @cut="uploadHeadPic"/>
</template>

​ 图 5.5 用户基本信息图

对于头像上传裁剪框,使用 Cropper 来实现,这个组件提供了一个方法用来生成裁剪框对象,在这个对象的方法中传入 element 元素和 options 裁剪选项后,就会生成对应的裁剪框,将头像上传后会将图像文件转化为 base64 位地址来进行存储。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
// 裁剪框的窗口显示状态
const show = ref(false)

// 选中图像的地址
const picSrc = ref(null)

/**
* 显示图像裁剪框
* @param imgURL 图像base64地址
*/
const showCropperWindow = (imgURL) => {
if (imgURL) {
// 清楚上一次裁剪的结果
resultData.blobData = null
resultData.dataURL = null
// 图像元素显示选中图像文件 (base64 地址)
picSrc.value = imgURL
// 显示头像上传的窗口
show.value = true
// 视图更新以后,初始化Cropper
nextTick(() => {
initCropper()
})
} else {
// 关闭头像上传窗口
show.value = false
}

}

/**
* 获取裁剪区域的画布对象
*/

const getCroppedCanvas = () => {
// 获取裁剪区域的画布数据
const croppedCanvas = cropper.getCroppedCanvas({
width: 120, // 画布宽度 120
height: 120 // 画布高度 120
});
// 将 canvas 转成 64位地址值
resultData.dataURL = croppedCanvas.toDataURL("image/png");
// 将 canvas 转成二进制的数据
croppedCanvas.toBlob(blog => {
resultData.blobData = blog
// 通知父组件触发了裁剪操作
emits('cut', resultData)
})
}

// cropper 实例
let cropper = null;

// 初始化 cropper
const initCropper = () => {
// 裁决区的原始图像元素
const image = document.getElementById('image');
// cropper 存在 则销毁
cropper?.destroy()
// 创建 cropper 实例
cropper = new Cropper(image, {
viewMode: 1, // 视图模式,裁决框不允许在图像外
dragMode: 'move', // 拖拽模式
aspectRatio: 1, // 裁决横宽比
autoCropArea: 0.5, // 画布高度的百分之五十
cropBoxResizable: false, // 不允许更改裁剪框的尺寸
toggleDragModeOnDblclick: false, // 禁止双击切换拖拽模式
preview: '.img-preview' // 裁剪框的预览效果
})
}

<template>
<!-- 头像上传窗口 -->
<n-modal v-model:show="show" preset="card" closable segmented title="头像上传" :mask-closable="false"
:close-on-esc="false"
style="width: 800px;height: 560px"
content-style="height: 0">
<template #default>
<!-- 裁剪框图像元素 -->
<n-space justify="space-between" :size="24" :wrap-item="false" style="height: 100%">
<div style="height: 100%;width: calc(100% - 200px)">
<img id="image" :src="picSrc" style="max-height: 100%;max-width: 100%">
</div>
<!-- 裁剪区域的预览图效果区 -->
<n-space vertical justify="center" :size="24" :wrap-item="false" style="width: 170px">
<!-- 120 -->
<n-space vertical align="center">
<div class="img-preview image-preview-120"></div>
<n-text>120 X 120 像素</n-text>
</n-space>
<!-- 40 -->
<n-space vertical align="center">
<div class="img-preview image-preview-40"></div>
<n-text>40 X 40 像素</n-text>
</n-space>
<!-- 36 -->
<n-space vertical align="center">
<div class="img-preview image-preview-36"></div>
<n-text>36 X 36 像素</n-text>
</n-space>

</n-space>
</n-space>
</template>
<template #action>
<n-space justify="center" :size="60">
<!-- 对裁剪图像功能按钮(缩放,选择,翻转,重置) -->
<n-button-group>
<!-- 放大 -->
<n-tooltip>
<template #trigger>
<n-button circle @click="cropper.zoom(0.1)">
<template #icon>
<n-icon :component="ZoomInRound"/>
</template>
</n-button>
</template>
放大
</n-tooltip>
<!-- 缩小 -->
<n-tooltip>
<template #trigger>
<n-button circle @click="cropper.zoom(-0.1)">
<template #icon>
<n-icon :component="ZoomOutRound"/>
</template>
</n-button>
</template>
缩小
</n-tooltip>
<!-- 顺时针旋转 -->
<n-tooltip>
<template #trigger>
<n-button circle @click="cropper.rotate(45)">
<template #icon>
<n-icon :component="RotateRightRound"/>
</template>
</n-button>
</template>
顺时针旋转45°
</n-tooltip>
<!-- 逆时针旋转 -->
<n-tooltip>
<template #trigger>
<n-button circle @click="cropper.rotate(-45)">
<template #icon>
<n-icon :component="RotateLeftRound"/>
</template>
</n-button>
</template>
逆时针旋转45°
</n-tooltip>
<!-- 上下翻转 -->
<n-tooltip>
<template #trigger>
<n-button circle @click="cropper.scaleY(y = -y)">
<template #icon>
<n-icon :component="SwapVertRound"/>
</template>
</n-button>
</template>
上下翻转
</n-tooltip>
<!-- 左右翻转 -->
<n-tooltip>
<template #trigger>
<n-button circle @click="cropper.scaleX(x = -x)">
<template #icon>
<n-icon :component="SwapHorizRound"/>
</template>
</n-button>
</template>
左右翻转
</n-tooltip>
<!-- 重置 -->
<n-tooltip>
<template #trigger>
<n-button circle @click="cropper.reset()">
<template #icon>
<n-icon :component="AutorenewRound"/>
</template>
</n-button>
</template>
重置
</n-tooltip>
</n-button-group>
<!-- 取消裁剪-确定裁剪 -->
<n-button-group>
<n-button @click="show = false">取消</n-button>
<n-button @click="getCroppedCanvas">
<template #icon>
<n-icon :component="ContentCutRound"></n-icon>
</template>
确定
</n-button>
</n-button-group>
</n-space>
</template>
</n-modal>
</template>

5.3.4 邮箱注册账号

用户在首次进入系统时,需要使用个人邮箱来注册账号,为了保证用户填写的邮箱正确有效以及邮箱收到的验证码跟收到验证码邮箱的一致性,需要从前端和后端来进行双重校验,如果有一次校验不成功,则注册账号失败。

前端会分别进行以下步骤:

  1. 验证注册的邮箱和验证码是否未填写、条款复选框是否未勾选

  2. 验证注册的邮箱和接收验证码的邮箱是否一致

  3. 发送注册请求,禁用注册按钮,注册请求处理结束,解除禁用的注册按钮

  4. 注册请求成功时,跳转至注册成功窗口,注册请求失败时,显示注册失败通知

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    // 注册表单值
    const registerFormValue = ref({
    email: '',
    vc: '',
    trim: false
    })

    // 注册表单验证规则
    const registerFormRules = {
    //邮箱规则
    email: [
    {
    required: true,
    message: "请输入邮箱号码",
    trigger: ["input", "blur"] // 在输入或失去焦点的情况下
    },
    {
    key: 'mail',
    message: "请正确输入邮箱格式",
    trigger: ["input", "blur"], // 在输入或失去焦点的情况下
    validator: (rule, value) => {
    return /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(value)
    }
    }
    ],
    //验证码规则
    vc: {
    required: true,
    message: "请输入验证码",
    trigger: ["input", "blur"] // 在输入或失去焦点的情况下
    },
    // 复选框规则
    trim: {
    message: "请认真阅读本系统的条款与协议",
    trigger: "change",
    validator: (rule, value) => {
    return value
    }
    }
    }

    后端则会在前端发送去请求之后做出以下步骤:

    1. 验证注册的邮箱、验证码、查询验证码的关键词是否未空

    2. 验证注册的邮箱和接收验证码的邮箱是否一致

    3. 查看验证码是否已失效

    4. 匹配验证码是否正确

    5. 调用邮箱注册业务

    6. 判断邮箱是否已被注册

    7. 新增一个用户记录,密码随机加密(MD5方式)处理

    8. 新增用户日志(注册)

    9. 采用邮箱的方式,发送初始密码到注册邮箱中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    /**
    * 注册(邮箱)
    * 请求地址: http://localhost:180981/wic-notes/user/register/email
    * 请求方式: POST
    *
    * @param email 邮箱号
    * @param vc 验证码
    * @param vcKey 验证码查询关键词
    * @return 响应数据 {user,userToken}
    */
    @PostMapping("/register/email") //
    public void registerAccountByEmail(
    @NotBlankEmail String email,
    @NotBlank(message = ValidateMessage.VC_IS_NOT_EMPTY) String vc,
    @NotBlank(message = ValidateMessage.PARAM_WRONG) String vcKey
    ) {
    // 判断注册的邮箱号是否为发送验证码的邮箱
    String vc_email = vcKey.split(":")[1];
    if (!CharSequenceUtil.equals(email, vc_email))
    throw ValidateParamException.errorValidate(ValidateMessage.EMAIL_CHANGE);

    // 从redis中获取真实的验证码
    String vcTokenValue = redisTemplate.opsForValue().get(vcKey);

    // 判断验证码是否已失效
    if (Validator.isEmpty(vcTokenValue))
    throw ValidateParamException.errorValidate(ValidateMessage.VC_INVALID);

    // 判断验证码是否准确
    if (!CharSequenceUtil.equals(vcTokenValue, vc))
    throw ValidateParamException.errorValidate(ValidateMessage.VC_ERROR);

    // 调用邮箱注册账号业务
    userService.registerAccountByEmail(email);
    }

后端对参数的校验大多数情况下使用了Spring 自带的 Validated 注解进行校验,少部分使用了自定义注解来进行校验,当用户分别通过前端和后端完成注册以后后,会弹出窗口提示注册成功,并把账号的初始密码发送到用户的邮箱中,用户可以凭此初始密码来进行登录。

5.3.5 回收站

用户对笔记或小记进行删除后,会将处于删除状态的小记或笔记在回收站界面展示,用于用户方便对删除的小记或笔记进行批量彻底删除和批量恢复,基于之前的系统设计理念,在对数据库已有的表进行查询,来生成回收站所对应的视图,来编写对应的实体类。

在视图与后端 pojo 包中的实体类形成映射关系后,接下来前端发送请求来对视图中的字段进行展示,对于这次视图数据的展示,采用了 Naive UI 中的 n-data-table 组件来进行显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<n-layout content-style="padding: 25px">
<!-- 标题、批量操作按钮-->
<n-space justify="space-between" align="center" style="margin-bottom: 20px">
<h3 style="margin: 0">回收站</h3>
<n-space>
<n-button ghost type="success" @click="restoreFilesBatch">批量恢复</n-button>
<n-button ghost type="error" @click="clickBatchDeleteBtn">批量彻底删除</n-button>
</n-space>
</n-space>
<!-- 表格-->
<n-data-table
striped
flex-height
:columns="columns"
:data="data"
:pagination="pagination"
v-model:checked-row-keys="rowChecked"
@update:page="rowChecked = []"
style="height: calc(100% - 34px - 20px)"
/>
</n-layout>
<delete-remind-dialog @delete="getFiles" @remove="removeRowChecked"/>
</template>

5.3.6 最近操作

为了方便用户操作,改善用户的使用体验, 该页面会作为首页展示用户最近操作过的笔记和小记,当用户点击展示的卡片会快速跳转到笔记或小记的编辑页,实现这个功能需要根据最近操作视图来进行实现。

根据之前对最近操作设计,可以得知它默认的字段会根据距离当前时间最近的顺序来进行排序,基于这个特性可以方便前端发送请求后,对数据的展示不用进行额外的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- 最近记录 -->
<n-card :bordered="false" style="height: calc(100% - 140px - 50px - 74px);"
content-style="height: calc(100% - 72.69PX)">
<template #header>
<h3 style="margin: 0">最近记录</h3>
</template>
<template #default>
<n-scrollbar style="height: 100%;">
<n-space>
<!-- 文件卡片 -->
<file-card
v-for="file in files"
:key="file.id + ':' + file.type"
:id="file.id"
:title="file.title"
:type="file.type"
:time="file.updateTime"
/>
</n-space>
</n-scrollbar>
</template>
</n-card>

6 系统测试

6.1 测试环境

测试环境是指用于执行软件测试的硬件和软件环境。一个良好的测试环境是保证测试工作有效进行的重要因素之一。

硬件环境:

  1. CPU: Intel i5-13600KF
  2. RAM: 32G DDR5

软件环境:

  1. ApiPost 7
  2. Postman 9

6.2 功能测试

在对软件的整体开发完成以后,需要验证软件系统的各项功能是否按规格要求正常运行,功能测试包括功能验证、错误处理测试、用户体验测试和兼容性测试,现在对在线笔记系统功能进行测试后,编写测试用例。

测试用例编号 功能点 用例说明 前置条件 输入 执行步骤 预期结果 重要程度 执行用例测试结果
WIC-NOTE-N001 注册功能 注册界面正确性显示 界面显示文字和按钮文字显示正确,按钮齐全,控件整齐 通过
WIC-NOTE-N002 注册功能 获取邮箱验证码有效性验证 勾选遵循协议 邮箱账号 邮箱账号收到注册验证码,按钮进入60秒冷却 通过
WIC-NOTE-N003 注册功能 注册账号成功 获取过邮箱验证码之后,勾选遵循协议 邮箱账号以及邮箱验证码 点击注册按钮 注册成功,并展示注册成功卡片 通过
WIC-NOTE-N004 登录功能 登录账号 该账号信息存在到数据库中 测试邮箱账号以及账号密码 点击登录按钮 登录成功,进入系统 通过
WIC-NOTE-N005 登录功能 密码隐秘性验证 登录页面正常显示 密码: 123456 输入数据 密码不明文显示 通过
WIC-NOTE-N006 最近操作 最近操作页显示 账号登录过后 最近操作页正常显示 通过
WIC-NOTE-N007 最近操作 最近操作卡片跳装 账号登录过后 点击最近编辑过的笔记或小记卡片 成功跳转到对应笔记和小记的编辑页面 通过
WIC-NOTE-N008 笔记 笔记编辑 账号登录后,且创建过笔记 点击笔记列表的该笔记标题 成功进入到笔记的编辑页面,并可以对笔记进行编辑 通过
WIC-NOTE-N009 小记 创建小记 账号登录后 点击创建小记按钮 成功弹出创建小记窗口,并可对创建的小记内容进行编辑 通过

6.3 测试结果总结

  1. 功能测试总结
  • 测试覆盖率 100%
  • 测试用例执行结果:通过率95%
  • 功能缺陷情况:共发现6处功能缺陷
  1. 性能测试总结
  • 吞吐量: 平均每秒处理请求数为1000
  • 响应时间: 平均响应时间为500ms
  • 并发用户数:系统在1000并发用户下仍能正常运行
  1. 兼容性测试总结
  • 浏览器兼容性:系统在Chrome、Firefox、Safari等主流浏览器上均能正常运行

  • 操作系统兼容性:系统在Windows、MacOS、Linux等操作系统上均能正常运行

  1. 总体测试总结:
  • 测试全面性:系统测试覆盖了功能、性能、安全、兼容性、易用性等多个方面
  • 测试效果:系统测试发现并解决了多个问题,系统质量得到提升

7 结论

7.1 总结

在开发系统过程中,对于页面的开发需要保持各页面的风格统一,这对于没有产品原型图的开发工作来说,是极为困难的,因为整个系统使用到了同一套 UI 组件才使得整个系统页面体验起来不会那么割裂,但是仍然存在不足的地方,比如说 在笔记编辑页对笔记进行编辑时,切换到暗色主题,笔记编辑栏的工具元素不会因为主题切换而变色,而这个问题出现在实现富文本编辑器功能的 CkEditor5 不是 Naive UI 中的组件,未能给这个组件进行切换处理,这是一个十分影响用户体验的地方。

整个开发中设计到不同的组件的使用,需要查询该组件官网的开发文档来学习对应的API来提高开发效率,并且在开发的过程是避免不了对页面的调试的,Vue 官方针对 debug 工作设计了一款浏览器插件 Vue devolment,可以非常方便的进行页面调试、网络请求查看等。

并且在对在线笔记系统开发前,需要对目标用户进行调研,了解用户对于笔记软件的需求,例如用户对于笔记的创建限制、会员等级、实时同步等方面的关注点。并对如今已经存在的笔记系统进行考察,了解当前存在的笔记软件的优点以及缺点作进一步总结。

7.2 未来展望

在未来的发展中,考虑到现在一份文档需要团队来协作完成,可以在此基础上给笔记加入团队协作功能,使多人可以同时编辑和分享笔记,提高团队工作效率,并且随着现在 AI 技术不断的发展,在线笔记系统也可以考虑引入机器学习和人工智能技术,为用户提供个性化的笔记推荐和智能分类功能,提升用户体验。

作为 Web 端的应用程序,未能对手机端进行适配也是此次开发中未能解决的一个痛点,现如今用户拥有的智能设备日益增多,需要笔记软件来支持跨平台的同步功能,使用户可以在不同的设备上实时访问和编辑笔记,并提供自动备份功能,确保数据安全。

在引入人工智能技术以后,可以不断优化搜索算法,提高搜索结果的准确性和速度,同时引入推送服务,可以为用户推荐相关的笔记内容,以此为基础上,将笔记系统打造成一个社交分享与交流平台,用户可以在平台上分享、交流笔记、建立用户社区。

8 致谢

学生朽木,承蒙师恩

9 参考文献

[1]聂玉峰,朱倩.计算机应用基础[M].科学出版社,2018.