前言
最近研究了一个笔记记录系统,然后突然想到一个问题,我该如何才能只用前端就实现笔记的记录系统?经过这两天的研究将其做出来了,接下来将分享实现的过程
✨✨✨✨✨✨✨✨✨✨
项目演示
在我的项目中,是可以适配移动端,以及电脑端的,跟着编写代码在两个地方运行完全没有问题,首先我以移动端的页面进行演示
🎈🎈🎈🎈🎈🎈🎈🎈
打开项目会看到保存的笔记
❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️
点击右上角的+号可以新增编辑笔记
🎈🎈🎈🎈🎈🎈🎈🎈🎈🎈🎈
进行编辑,可添加文字,图片,表格
❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️
点击完成可以保存笔记
🎈🎈🎈🎈🎈🎈🎈🎈🎈🎈🎈
当新建多个笔记可以筛选
点右上角放大镜筛选
❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️
演示电脑版样式
🏷️🏷️🏷️🏷️🏷️🏷️🏷️🏷️🏷️🏷️🏷️
实现功能
在我的项目实现了基础的笔记记录功能主要有如下
- 笔记展示
- 笔记编辑
- 笔记新增
- 笔记筛选
- 笔记修改
- 笔记添加文本,图片,表格
- 样式适配
- 笔记回退,还原
- 文本样式修改,富文本
🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶
使用框架
- element-ui
- quill富文本
- indexDB
逻辑实现
项目搭建
使用指令
bash
vue create 项目名
或者
bash
vue ui
打开创建项目的ui界面
创建vue2
项目
ps:如果提示不是内部命令信息提示,就需要安装vue组件
命令行输入:
npm install -g vue
npm install -g @vue/cli
✒️✒️✒️✒️✒️✒️✒️✒️✒️✒️✒️✒️✒️
依赖安装
bash
npm i element-ui S
npm i quill
npm i quill-better-table
npm i quill-delta
npm i quill-table
npm i quill-table-ui
npm i vue-router@3.0.1
main.js
javascript
import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import 'quill/dist/quill.snow.css'
import router from "./router/index"
Vue.config.productionTip = false
Vue.use(ElementUI)
new Vue({
router,
render: h => h(App),
}).$mount('#app')
App.vue
html
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
position: absolute;
width: 100%;
height: 100%;
overflow-y: hidden;
}
</style>
🎧🎧🎧🎧🎧🎧🎧🎧🎧🎧🎧🎧
新建quill封装组件
建立一个
QuillEditor.vue
文件
代码如下:
html
<template>
<keep-alive>
<div class="editor-container">
<div class="quillEditor"></div>
</div>
</keep-alive>
</template>
javascript
<script>
import Quill from 'quill'
import 'quill/dist/quill.snow.css'
const titleConfig = {
'ql-bold': '加粗',
'ql-color': '颜色',
'ql-font': '字体',
'ql-code': '插入代码',
'ql-italic': '斜体',
'ql-link': '添加链接',
'ql-background': '颜色',
'ql-size': '字体大小',
'ql-strike': '删除线',
'ql-script': '上标/下标',
'ql-underline': '下划线',
'ql-blockquote': '引用',
'ql-header': '标题',
'ql-indent': '缩进',
'ql-list': '列表',
'ql-align': '文本对齐',
'ql-direction': '文本方向',
'ql-code-block': '代码块',
'ql-formula': '公式',
'ql-image': '图片',
'ql-video': '视频',
'ql-clean': '清除字体样式',
'ql-upload': '文件',
'ql-table': '插入表格',
'ql-table-insert-row': '插入行',
'ql-table-insert-column': '插入列',
'ql-table-delete-row': '删除行',
'ql-table-delete-column': '删除列'
}
export default {
name: 'quillEditor',
props: {
value: Object
},
data() {
return {
quill: null,
options: {
theme: 'snow',
modules: {
toolbar: {
container: [
['bold', 'italic', 'underline', 'strike'],
[{header: 1}, {header: 2}],
[{list: 'ordered'}, {list: 'bullet'}],
[{indent: '-1'}, {indent: '+1'}],
[{color: []}, {background: []}],
[{font: []}],
[{align: []}],
['clean'],
[
{table: 'TD'},
{'table-insert-row': 'TIR'},
{'table-insert-column': 'TIC'},
{'table-delete-row': 'TDR'},
{'table-delete-column': 'TDC'}
]
],
handlers: {
table: function (val) {
console.log(val)
this.quill.getModule('table').insertTable(2, 3)
},
'table-insert-row': function () {
this.quill.getModule('table').insertRowBelow()
},
'table-insert-column': function () {
this.quill.getModule('table').insertColumnRight()
},
'table-delete-row': function () {
this.quill.getModule('table').deleteRow()
},
'table-delete-column': function () {
this.quill.getModule('table').deleteColumn()
}
}
},
table: true
},
placeholder: ''
}
}
},
methods: {
addQuillTitle() {
const oToolBar = document.querySelector('.ql-toolbar')
const aButton = oToolBar.querySelectorAll('button')
const aSelect = oToolBar.querySelectorAll('select')
aButton.forEach(function (item) {
if (item.className === 'ql-script') {
item.value === 'sub' ? (item.title = '下标') : (item.title = '上标')
} else if (item.className === 'ql-indent') {
item.value === '+1' ? (item.title = '向右缩进') : (item.title = '向左缩进')
} else {
item.title = titleConfig[item.classList[0]]
}
})
aSelect.forEach(function (item) {
item.parentNode.title = titleConfig[item.classList[0]]
})
},
getContentData() {
return this.quill.getContents()
},
getQuillInstance() {
return this.quill;
}
},
mounted() {
const dom = this.$el.querySelector('.quillEditor')
this.quill = new Quill(dom, this.options)
this.quill.on('text-change', () => {
this.$emit('contentData', this.quill.root.innerHTML)
})
this.$el.querySelector(
'.ql-table-insert-row'
).innerHTML = `<svg t="1591862376726" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6306" width="18" height="200"><path d="M500.8 604.779L267.307 371.392l-45.227 45.27 278.741 278.613L779.307 416.66l-45.248-45.248z" p-id="6307"></path></svg>`
this.$el.querySelector(
'.ql-table-insert-column'
).innerHTML = `<svg t="1591862238963" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6509" width="18" height="200"><path d="M593.450667 512.128L360.064 278.613333l45.290667-45.226666 278.613333 278.762666L405.333333 790.613333l-45.226666-45.269333z" p-id="6510"></path></svg>`
this.$el.querySelector(
'.ql-table-delete-row'
).innerHTML = `<svg t="1591862253524" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6632" width="18" height="200"><path d="M500.8 461.909333L267.306667 695.296l-45.226667-45.269333 278.741333-278.613334L779.306667 650.026667l-45.248 45.226666z" p-id="6633"></path></svg>`
this.$el.querySelector(
'.ql-table-delete-column'
).innerHTML = `<svg t="1591862261059" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6755" width="18" height="200"><path d="M641.28 278.613333l-45.226667-45.226666-278.634666 278.762666 278.613333 278.485334 45.248-45.269334-233.365333-233.237333z" p-id="6756"></path></svg>`
this.addQuillTitle()
},
activated() {
this.quill.setContents({})
}
}
</script>
css
<style scoped>
.quillEditor {
width: auto; /* 允许宽度根据内容自适应 */
display: inline-block; /* 让宽度根据内容自适应 */
}
.quillEditor .ql-editor {
width: 100%; /* 确保编辑器内容区域宽度为 100% */
}
.quillEditor .ql-editor table {
width: 100%; /* 确保表格宽度自适应容器宽度 */
}
.quillEditor .ql-editor table {
width: max-content; /* 根据表格内容的宽度来调整宽度 */
}
/* 模态框背景 */
.image-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
/* 放大图片样式 */
.large-image {
max-width: 90%;
max-height: 90%;
}
/* 关闭按钮样式 */
.close-button {
position: absolute;
top: 10px;
right: 10px;
font-size: 24px;
color: white;
cursor: pointer;
}
</style>
✏️✏️✏️✏️✏️✏️✏️✏️✏️✏️✏️✏️✏️
新建indexDB.js工具类
javascript
export default class IndexedDBUtil {
constructor(dbName, storeName) {
this.dbName = dbName;
this.storeName = storeName;
this.db = null;
this.initDB();
}
// 打开并初始化数据库
initDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, 1);
request.onupgradeneeded = event => {
this.db = event.target.result;
if (!this.db.objectStoreNames.contains(this.storeName)) {
const objectStore = this.db.createObjectStore(this.storeName, { keyPath: 'id', autoIncrement: true });
objectStore.createIndex('name', 'name', { unique: false });
}
};
request.onsuccess = event => {
this.db = event.target.result;
resolve();
};
request.onerror = event => {
console.error('打开数据库失败:', event.target.error);
reject(event.target.error);
};
});
}
// 增加记录
add(data) {
return this.initDB().then(() => {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readwrite');
const objectStore = transaction.objectStore(this.storeName);
const request = objectStore.add(data);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
});
}
// 根据 ID 获取记录
getById(id) {
return this.initDB().then(() => {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName]);
const objectStore = transaction.objectStore(this.storeName);
const request = objectStore.get(id);
request.onsuccess = () => {
if (request.result) {
resolve(request.result);
} else {
console.warn(`未找到 ID 为 ${id} 的记录`); // 输出警告信息
resolve(null); // No result found
}
};
request.onerror = () => {
console.error('获取记录失败:', request.error); // 输出错误信息
reject(request.error);
};
});
});
}
// 获取所有记录
getAll() {
return this.initDB().then(() => {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName]);
const objectStore = transaction.objectStore(this.storeName);
const request = objectStore.getAll();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
});
}
// 根据 ID 更新记录
update(data) {
return this.initDB().then(() => {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readwrite');
const objectStore = transaction.objectStore(this.storeName);
const request = objectStore.put(data);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
});
}
// 根据 ID 删除记录
delete(id) {
return this.initDB().then(() => {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readwrite');
const objectStore = transaction.objectStore(this.storeName);
const request = objectStore.delete(id);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
});
}
// 清空对象存储中的所有记录
clearAll() {
return this.initDB().then(() => {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readwrite');
const objectStore = transaction.objectStore(this.storeName);
const request = objectStore.clear();
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
});
}
// 删除整个数据库
deleteDatabase() {
return new Promise((resolve, reject) => {
const request = indexedDB.deleteDatabase(this.dbName);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
request.onblocked = () => console.warn('删除数据库请求被阻塞');
});
}
}
🔔🔔🔔🔔🔔🔔🔔🔔🔔🔔🔔🔔
新建笔记入口
新建文件Note.vue
html
<template>
<div class="note-page">
<!-- 上部分 -->
<div class="header">
<div class="title">简易笔记</div>
<div class="actions">
<el-button icon="el-icon-search" circle @click="showSearchDialog = true"></el-button>
<el-button icon="el-icon-plus" circle @click="addNote"></el-button>
</div>
</div>
<!-- 中间内容区 -->
<div class="content">
<div v-if="filteredNotes.length != 0">
<el-card v-for="note in filteredNotes" :key="note.id" class="note-card">
<div class="note-content">
<div class="note-main" @click="editNote(note)">
<h3 class="note-title">{{ note.title }}</h3>
<p class="note-summary">{{ note.summary }}</p>
<p class="note-time">{{ note.date }}</p>
</div>
<div class="note-delete">
<el-button type="danger" icon="el-icon-delete" @click="deleteNote(note)" class="delete-button"></el-button>
</div>
</div>
</el-card>
</div>
<div v-else class="note-no-content">
暂未新建笔记
</div>
</div>
<!-- 下部分菜单栏 -->
<div class="footer">
<el-menu mode="horizontal" class="footer-menu" :default-active="currentMenuItem">
<el-menu-item index="1">笔记</el-menu-item>
<el-menu-item index="2">我的</el-menu-item>
</el-menu>
</div>
<!-- 弹出层 -->
<el-dialog
title="检索笔记"
:visible.sync="showSearchDialog"
width="100%"
:fullscreen="false"
class="search-dialog"
:before-close="handleClose"
>
<div class="dialog-content">
<div class="input-container">
<el-input
v-model="searchQuery"
placeholder="请输入标题关键字"
clearable
@input="filterNotes"
></el-input>
</div>
<div class="button-container">
<el-button type="primary" @click="showSearchDialog = false">确认</el-button>
</div>
</div>
</el-dialog>
</div>
</template>
javascript
<script>
import IndexedDBUtil from "@/utils/IndexDB";
export default {
name: 'noteApp',
data() {
return {
currentMenuItem: '1',
notes: [],
showSearchDialog: false,
searchQuery: '',
filteredNotes: [],
dbUtil: null, // IndexedDB 工具类实例
};
},
created() {
this.dbUtil = new IndexedDBUtil('NotesDatabase', 'NotesStore');
this.getAllNote()
},
methods: {
filterNotes() {
if (this.searchQuery.trim() === '') {
this.filteredNotes = this.notes;
} else {
const query = this.searchQuery.toLowerCase();
this.filteredNotes = this.notes.filter(note =>
note.title.toLowerCase().includes(query)
);
}
},
safeSubstring(str, start, end) {
// 检查起始位置和结束位置是否有效
if (start < 0 || end < 0 || start >= str.length) {
return ''; // 返回空字符串
}
// 如果结束位置超出字符串长度,调整结束位置
if (end > str.length) {
end = str.length;
}
return str.substring(start, end);
},
deleteNote(note) {
this.$confirm('确认删除?', '确认信息', {
type: 'warning',
confirmButtonText: '删除',
cancelButtonText: '取消',
center: true
})
.then(() => {
this.deleteNoteById(note)
this.getAllNote();
})
.catch(action => {
console.log("取消删除", action)
});
console.log('笔记', note)
},
deleteNoteById(note) {
console.log('需要删除笔记:', note)
this.dbUtil.delete(note.id)
},
handleClose(done) {
this.showSearchDialog = false;
done();
},
addNote() {
this.$router.push({ path: "/noteAdd2" })
},
getAllNote() {
this.dbUtil.getAll().then(result => {
console.log('所有数据', result);
if (result) {
for (let i = 0; i < result.length; i ++) {
let obj = JSON.parse(result[i].delta)
if (obj && obj.insert) {
result[i].summary = this.safeSubstring(obj.insert, 0, 10)
}
}
this.notes = result
}
}).catch(error => {
console.error('获取所有数据失败', error);
});
},
editNote(note) {
this.$router.push({ path: '/noteAdd2', query: { id: note.id } });
// this.$router.push({
// path: "/noteAdd2",
// params: {
// id: note.id
// }
// })
}
},
watch: {
notes: 'filterNotes',
searchQuery: 'filterNotes',
},
mounted() {
this.filteredNotes = this.notes; // 初始化时显示所有笔记
},
};
</script>
css
<style scoped>
.note-page {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden; /* 避免整个页面滚动 */
}
.header, .footer {
background-color: #f5f5f5;
padding: 10px;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.1);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
.title {
font-size: 18px;
font-weight: bold;
}
.actions el-button {
margin-left: 10px;
}
.content {
flex: 1;
padding: 10px;
overflow-y: auto; /* 允许内部滚动 */
}
.note-card {
margin-bottom: 10px;
}
.note-content {
display: flex;
justify-content: space-between;
align-items: center;
}
note-no-content {
font-weight: bold;
}
.note-main {
flex: 0 0 80%; /* 主内容区占80%宽度 */
}
.note-delete {
flex: 0 0 20%; /* 删除区占20%宽度 */
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
}
.delete-button {
width: 60px; /* 根据需要调整按钮大小 */
height: 40px; /* 根据需要调整按钮大小 */
}
.note-title {
font-size: 16px;
font-weight: bold;
}
.note-summary {
margin: 5px 0;
}
.note-time {
font-size: 12px;
color: #888;
}
.footer {
background-color: #fff;
padding: 10px;
}
.footer-menu {
text-align: center;
}
.footer-menu .el-menu-item {
line-height: 40px;
}
/* 弹出层内部容器样式 */
.search-dialog .dialog-content {
display: flex; /* 使用Flex布局 */
align-items: center; /* 垂直居中 */
}
/* 输入框样式 */
.search-dialog .input-container {
flex: 0 0 80%; /* 输入框占80%宽度 */
}
/* 按钮样式 */
.search-dialog .button-container {
flex: 0 0 20%; /* 按钮占20%宽度 */
}
/* 确保按钮填满其容器宽度 */
.search-dialog .el-button {
width: 100%;
}
</style>
🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁
新建笔记编辑文件
新建NoteAdd.vue
html
<template>
<div class="note-add-page">
<!-- 上部分 -->
<div class="header">
<el-button class="back-button" icon="el-icon-arrow-left" @click="goBack"></el-button>
<div class="title">编辑笔记</div>
<el-button class="finish-button" type="primary" @click="finishNote" :disabled="noteTitle.length == 0">完成</el-button>
</div>
<!-- 中间部分编辑区 -->
<div class="editor-container">
<el-input
v-model="noteTitle"
placeholder="请输入标题"
class="title-input"
></el-input>
<div class="content-editor">
<QuillEditor class='quillEditorChild' ref='quillEditor' v-model="contentArea" @contentData="change($event)"></QuillEditor>
</div>
</div>
<!-- 下部分区 -->
<div class="bottom-toolbar">
<el-button icon="el-icon-back" @click="undo" class="toolbar-button"></el-button>
<el-button icon="el-icon-right" @click="redo" class="toolbar-button"></el-button>
<el-button
icon="el-icon-plus"
@click="toggleAddMenu"
class="toolbar-button add-button"
ref="addButton"
></el-button>
</div>
<!-- 弹出层 -->
<el-dialog
:visible.sync="addMenuVisible"
width="80vw"
:show-close="false"
custom-class="custom-add-menu"
:modal="false"
>
<div class="add-menu-scroll-container">
<div class="add-menu-content">
<!-- 图片-->
<div class="add-menu-item">
<div class="icon-container">
<el-button @click="insertImage" class="icon-button">
<i class="el-icon-picture"></i>
</el-button>
</div>
<div class="text-container">插入图片</div>
</div>
<div class="add-menu-item">
<div class="icon-container">
<el-button class="icon-button">
<i class="el-icon-microphone"></i>
</el-button>
</div>
<div class="text-container">占位功能1</div>
</div>
<div class="add-menu-item">
<div class="icon-container">
<el-button class="icon-button">
<i class="el-icon-tickets"></i>
</el-button>
</div>
<div class="text-container">占位功能2</div>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
javascript
<script>
import QuillEditor from "@/components/QuillEditor";
import Quill from "quill";
import IndexedDBUtil from "@/utils/IndexDB";
export default {
name: 'noteAdd',
components: {
QuillEditor
},
data() {
return {
noteId: 0,
noteTitle: '',
contentArea: null,
addMenuVisible: false,
dbUtil: null, // IndexedDB 工具类实例
};
},
mounted() {
this.loadEditNote()
},
created() {
this.dbUtil = new IndexedDBUtil('NotesDatabase', 'NotesStore');
this.noteId = Number(this.$route.query.id)
console.log('noteId', this.noteId)
},
methods: {
goBack() {
this.$router.push('/');
},
undo() {
const quill = this.$refs.quillEditor.getQuillInstance();
if (quill) {
quill.history.undo();
}
},
redo() {
const quill = this.$refs.quillEditor.getQuillInstance();
if (quill) {
quill.history.redo();
}
},
formattedDate() {
const year = new Date().getFullYear();
const month = String(new Date().getMonth() + 1).padStart(2, '0'); // 月份从 0 开始,所以需要 +1
const day = String(new Date().getDate()).padStart(2, '0'); // 日期是从 1 开始的
return `${year}-${month}-${day}`; // 返回格式化后的日期
},
async finishNote() {
const quill = this.$refs.quillEditor.getQuillInstance();
// 获取 Delta 格式内容
var delta = quill.getContents();
// 获取 HTML 格式内容
var html = quill.root.innerHTML;
if (this.noteId) {
var updateNote = {
id: this.noteId,
title: this.noteTitle,
delta: JSON.stringify(delta),
html: html,
date: this.formattedDate()
};
try {
const result = await this.dbUtil.update(updateNote);
console.log('笔记已更新', result)
} catch (error) {
console.error('更新笔记失败:', error)
}
} else {
var insertNote = {
title: this.noteTitle,
delta: JSON.stringify(delta),
html: html,
date: this.formattedDate()
};
console.log(insertNote)
try {
// 保存 note 对象到 IndexedDB
const result = await this.dbUtil.add(insertNote);
console.log('笔记已保存:', result);
} catch (error) {
console.error('保存笔记失败:', error);
}
}
this.goBack()
},
toggleAddMenu() {
this.addMenuVisible = !this.addMenuVisible;
},
insertImage() {
const quill = this.$refs.quillEditor.getQuillInstance();
const range = quill.getSelection();
let index = range ? range.index : quill.getLength();
// 创建一个隐藏的文件选择对话框
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*'; // 只允许选择图片文件
input.addEventListener('change', (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
const imageURL = e.target.result;
quill.insertEmbed(index, 'image', imageURL, Quill.sources.USER);
};
reader.readAsDataURL(file); // 读取文件内容作为 data URL
}
});
input.click(); // 模拟点击文件选择对话框
this.addMenuVisible = false;
},
change(event) {
console.log('更新值:', event)
},
loadContent(content, fileName) {
this.noteTitle = fileName.replace('.json','')
const quill = this.$refs.quillEditor.getQuillInstance();
if (fileName.endsWith('.html') || fileName.endsWith('.htm')) {
quill.root.innerHTML = content;
} else if (fileName.endsWith('.json')) {
let delta = null;
if (content) {
delta = JSON.parse(content);
}
quill.setContents(delta);
}
},
loadEditNote() {
if (!this.noteId) {
return
}
this.dbUtil.getById(this.noteId).then(res => {
if (res) {
this.loadContent(res.delta, res.title + ".json")
} else {
this.$message({
message: '笔记加载失败,未能获取到笔记',
type: 'warning'
});
}
})
},
},
};
</script>
css
<style scoped>
.note-add-page {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #fff;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
background-color: #f5f5f5;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.1);
height: 10%;
}
.title {
flex: 1;
text-align: center;
font-size: 18px;
font-weight: bold;
}
.finish-button {
margin-left: 10px;
}
.editor-container {
flex: 1;
display: flex;
flex-direction: column;
padding: 10px;
overflow-y: auto;
height: 80%;
}
.title-input {
margin-bottom: 10px;
}
.content-editor {
flex: 1;
}
.quill-editor {
height: 100%;
}
.bottom-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
background-color: #f5f5f5;
box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.1);
height: 10%;
}
.add-menu-scroll-container {
display: flex;
overflow-x: auto;
white-space: nowrap;
padding: 10px;
}
.add-menu-content {
display: flex;
align-items: center;
}
.add-menu-item {
display: flex;
flex-direction: column;
align-items: center;
margin: 0 10px; /* 水平间距 */
}
.icon-container {
margin-bottom: 4px; /* 图标和文字之间的间距 */
}
.text-container {
text-align: center;
}
/* 自定义弹出层样式 */
.custom-add-menu .el-dialog__header {
display: none; /* 隐藏标题栏 */
}
.custom-add-menu .el-dialog__body {
padding: 0; /* 去掉默认内边距 */
}
.custom-add-menu .el-dialog {
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
margin-top: -10px; /* 根据需要调整与按钮的距离 */
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); /* 添加阴影效果 */
}
.table-controls {
position: absolute;
z-index: 10;
background: white;
border: 1px solid #ddd;
padding: 10px;
border-radius: 5px;
}
.table-control-button {
margin: 5px;
}
</style>
🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷🔷
新建router文件
建立一个router文件夹,在其中建立index.js
javascript
import Vue from "vue"
import Router from 'vue-router'
import Note from "@/components/Note";
import NoteAdd from "@/components/NoteAdd";
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
redirect: 'note'
},
{
path: "/note",
name: "Note",
component: Note
},
{
path: "/noteAdd",
name: "NoteAdd",
component: NoteAdd
},
]
})
结语
如上就是纯vue实现笔记记录系统的全部逻辑了