纯vue实现笔记系统

前言

最近研究了一个笔记记录系统,然后突然想到一个问题,我该如何才能只用前端就实现笔记的记录系统?经过这两天的研究将其做出来了,接下来将分享实现的过程

✨✨✨✨✨✨✨✨✨✨

项目演示

在我的项目中,是可以适配移动端,以及电脑端的,跟着编写代码在两个地方运行完全没有问题,首先我以移动端的页面进行演示

🎈🎈🎈🎈🎈🎈🎈🎈

打开项目会看到保存的笔记

❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️

点击右上角的+号可以新增编辑笔记

🎈🎈🎈🎈🎈🎈🎈🎈🎈🎈🎈

进行编辑,可添加文字,图片,表格

❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️

点击完成可以保存笔记

🎈🎈🎈🎈🎈🎈🎈🎈🎈🎈🎈

当新建多个笔记可以筛选

点右上角放大镜筛选


❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️

演示电脑版样式

🏷️🏷️🏷️🏷️🏷️🏷️🏷️🏷️🏷️🏷️🏷️

实现功能

在我的项目实现了基础的笔记记录功能主要有如下

  1. 笔记展示
  2. 笔记编辑
  3. 笔记新增
  4. 笔记筛选
  5. 笔记修改
  6. 笔记添加文本,图片,表格
  7. 样式适配
  8. 笔记回退,还原
  9. 文本样式修改,富文本

🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶🔶

使用框架

  1. element-ui
  2. quill富文本
  3. 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>
        &nbsp;
        <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实现笔记记录系统的全部逻辑了

相关推荐
云边有个稻草人23 分钟前
【优选算法】—复写零(双指针算法)
笔记·算法·双指针算法
一个处女座的程序猿O(∩_∩)O1 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink4 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者6 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-6 小时前
验证码机制
前端·后端
燃先生._.7 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖8 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235248 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
冷眼看人间恩怨9 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget