纯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实现笔记记录系统的全部逻辑了

相关推荐
卸任9 小时前
Vibe coding工具使用心得
前端·ai编程
brien910 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance10 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M10 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写10 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李10 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易10 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马10 小时前
前沿视界
前端
kyriewen10 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易11 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员