纯vue实现笔记系统

news2024/9/27 23:31:27

前言

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

✨✨✨✨✨✨✨✨✨✨

项目演示

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

🎈🎈🎈🎈🎈🎈🎈🎈

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

在这里插入图片描述

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

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

在这里插入图片描述
🎈🎈🎈🎈🎈🎈🎈🎈🎈🎈🎈

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

在这里插入图片描述
❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️

点击完成可以保存笔记

在这里插入图片描述
🎈🎈🎈🎈🎈🎈🎈🎈🎈🎈🎈

当新建多个笔记可以筛选

点右上角放大镜筛选

在这里插入图片描述


在这里插入图片描述
❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️

演示电脑版样式

在这里插入图片描述

在这里插入图片描述
🏷️🏷️🏷️🏷️🏷️🏷️🏷️🏷️🏷️🏷️🏷️

实现功能

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

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

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

使用框架

  1. element-ui
  2. quill富文本
  3. indexDB

逻辑实现

项目搭建

使用指令

vue create 项目名

或者

vue ui

打开创建项目的ui界面

创建vue2项目

ps:如果提示不是内部命令信息提示,就需要安装vue组件
命令行输入:
npm install -g vue
npm install -g @vue/cli

✒️✒️✒️✒️✒️✒️✒️✒️✒️✒️✒️✒️✒️

依赖安装

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

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

<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文件

代码如下:

<template>
  <keep-alive>
    <div class="editor-container">
      <div class="quillEditor"></div>
    </div>
  </keep-alive>
</template>
<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>
<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工具类

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

<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>
<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>

<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

<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>
<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>

<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


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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PCIe prefix总结

这周研究了一下spec中关于prefix的部分&#xff0c;在此做一个总结&#xff0c;欢迎大家指正补充。 TLP Prefix基本介绍 • TLP 第 0 字节的 Fmt [2:0] 字段值为 100b 表示当前 DW 为 TLP Prefix 。 • TLP Prefix 分为两大类&#xff1a; Local 和 End-End &#xff0c;其中…

深入理解归并排序

目录 一、概念 二、递归版实现 三、非递归实现 三、文件归并排序 小结 一、概念 归并排序&#xff08;Merge sort&#xff09;是建立在归并操作上的一种有效的排序算法&#xff0c;该算法是采用分治法&#xff08;Divide and Conquer&#xff09;的一个非常典型的应用。将…

海外新闻稿发布:企业如何充分利用数字化媒体进行

在全球数字化进程加速的时代&#xff0c;企业要在激烈的国际市场中脱颖而出&#xff0c;利用数字化媒体进行海外新闻稿发布是一个不可或缺的战略。精确的策略和有效的执行能够帮助企业获得更高的曝光度和市场份额。以下将从多个角度探讨如何充分利用数字化媒体进行海外新闻稿发…

万亿生成式AI市场,商汤迎来“长坡厚雪”

AI掀起了全球科技玩家的军备竞赛&#xff0c;然而声浪越强噪音越多&#xff0c;这个领域的混乱程度也变得远超以往。就连刚刚公布财报的英伟达&#xff0c;市场也没有买账&#xff0c;因为担心AI驱动的增长高峰已过&#xff0c;接下来&#xff0c;下游会更看重实际成果。 “囤…

【电子数据取证】微信8.0.50版本数据库解密

文章关键词&#xff1a;电子数据取证、手机取证、微信取证、数据库解密 通过对8.0.50这一特定版本的分析&#xff0c;我们期望揭示软件迭代背后的逻辑思考&#xff0c;以及安全策略的演进方向。这不单纯是对技术细节的揭秘&#xff0c;更是一次关于未来通信安全趋势的展望&…

在Linux中如何安装JDK

一、卸载JDK &#xff08;可以不删除&#xff0c;直接安装新的JDK&#xff0c;然后修改环境变量&#xff09; 1.1卸载使用yum安装的jdk 1.1.1卸载系统预安装的JDK 使用命令&#xff1a;yum list installed |grep java 注意&#xff1a;该命令只能查看使用yum命令安装的jav…

python模块和包的区别有哪些

模块&#xff1a;就是.py文件&#xff0c;里面定义了一些函数和变量&#xff0c;需要的时候就可以导入这些模块。 包&#xff1a;在模块之上的概念&#xff0c;为了方便管理而将文件进行打包。包目录下第一个文件便是 __init__.py&#xff0c;然后是一些模块文件和子目录&…

pytorch 均方误差损失函数

均方误差损失函数主要用于回归问题。它计算预测值与真实值之间差的平方&#xff0c;然后取平均值。这个损失函数通过惩罚大的误差&#xff0c;使得模型在训练时更加注重减少较大的偏差。 import torch import torch.nn as nn# 创建预测值和实际值张量 predicted torch.tensor(…

Spring Boot 中的 “依赖管理和自动配置” 详解透彻到底(附+详细代码流程)

1. 如何理解 “ 约定优于配置 ” 约定优于配置&#xff08;Convention over Configuration / CoC&#xff09;,又称约定编程&#xff0c;是一种软件设计规范&#xff0c;本质上是对系统&#xff0c;类库或框架中一些东西。 一个大众化合理的默认值&#xff08;缺省值&#xff0…

VSCode+Keil协同开发之Keil Assistant

VSCodeKeil协同开发之Keil Assistant 目录 VSCodeKeil协同开发之Keil Assistant1. 效果展示2. Keil Assistant简介3. Keil Assistant功能特性4. 部署步骤4.1. 1.部署准备4.2. 2.安装Keil Assistant插件4.3. 3.配置Keil Assistant插件 5. Keil Assistant使用6. 总结 大家在单片机…

java 使用网易邮箱发送邮件

java 使用网易邮箱发送邮件 准备条件 网易邮箱账号开通邮箱的POP3/SMPT服务&#xff0c;申请授权码 引入工具包 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.26</version></de…

深入浅出Entity-Component-System:重塑游戏开发的未来

引言 在游戏开发领域&#xff0c;架构设计往往决定了项目的成败。随着游戏规模和复杂度的不断增加&#xff0c;传统的面向对象编程(OOP)模式逐渐显露出其局限性。而ECS(Entity-Component-System)架构作为一种新兴的设计模式&#xff0c;正在彻底改变游戏开发的方式。本文将深入…

C# 安卓开发1(基于MAUI框架)

1&#xff1a;什么是 .NET MAUI&#xff1f;这里参考一下官方文档&#xff0c;因为解释的更详细&#xff0c;点击这里&#xff1b; 2&#xff1a;开发工具VS2022&#xff0c;下载下图开发框架 3: 安装完成后创建新项目,选择net6.0长期支持(创建文件的存放路径不要有中文): 4:…

FPGA速度优化

速度优化 文章目录 速度优化前言一、时序优化1.1 减少关键路径上的时序1.1.1 关键路径重组1.1.2 解决扇出问题1.1.3 路径上插入寄存器1.1.4 寄存器平衡1.1.5 并行结构1.1.6 消除代码优先级 总结 前言 速度优化&#xff0c;主要就是设计时序进行优化 吞吐量&#xff1a;每个时…

bbr 随机 phase 的麻烦与 inflight 守恒算法的动机

bbr 有个要点&#xff0c;要把 probebw 的 phase 错开&#xff1a; static void bbr_reset_probe_bw_mode(struct sock *sk) {struct bbr *bbr inet_csk_ca(sk);bbr->mode BBR_PROBE_BW;bbr->cycle_idx CYCLE_LEN - 1 - prandom_u32_max(bbr_cycle_rand);bbr_advance…

炒现货黄金白银通用的技术

要在现货黄金、现货白银等市场&#xff0c;甚至是股票、期货等其他以市场走势为分析对象的市场&#xff0c;我们都需要熟练的掌握一些交易的技术&#xff0c;这些技术可以为我们入场交易打好基础&#xff0c;让我们获得比随机交易更高一点的概率。下面我们就来讨论一下炒现货黄…

关于el-table的show-summary,合计栏不显示以及保留两位小数问题

<el-tableref"table1"v-loading"loading":data"":stripe"true"height"600"show-summary:summary-method"getSummaries":show-overflow-tooltip"true">...</el-table>合计部分不显示的问题 …

安全升级:Docker部署Redis,启用密码验证

1.在自己选定的目录中创建文件夹 在redis文件夹里面创建&#xff1a;data文件夹和conf文件夹&#xff08;文件夹名称随意&#xff09; 2.在conf文件夹中创建redis.conf文件&#xff1a; vim redis.conf 2.1.redis.conf里面编写内容可以根据官网&#xff08;Index of /releases…

Threejs之OrbitControls轨道控制器

本文目录 前言一、Orbitcontrols&#xff08;轨道控制器&#xff09;1.1 基础使用1.2 代码演示 二、效果展示 前言 Orbitcontrols&#xff08;轨道控制器&#xff09;可以使得相机围绕目标进行轨道运动。 一、Orbitcontrols&#xff08;轨道控制器&#xff09; 1.1 基础使用 C…

Hreflang 和 SEO:新手完整指南

每天&#xff0c;数以百万计的法国用户访问像 Amazon.com 这样的全球网站。虽然 Amazon.com 的官方页面是英文的&#xff0c;但用户仍然可以看到法语的文本和产品描述。这是因为亚马逊的全球网站有针对法国的本地化版本&#xff0c;确保所有法国用户都可以自动看到法语的网站内…