欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Electron中如何使用SQLite存儲筆記

 更新時間:2022年11月29日 09:53:55   作者:夜焱辰  
這篇文章主要為大家介紹了Electron中如何使用SQLite存儲筆記示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

前言

上一篇,我們使用 remirror 實現(xiàn)了一個簡單的 markdown 編輯器。接下來,我們要學習如何去存儲這些筆記。

當然了,你也可以選擇不使用數(shù)據(jù)庫,不過若是你以后需要將該應(yīng)用上架到 mac Apple Store ,就需要考慮這個了。因為上架 mac 應(yīng)用需要啟用 sandbox,當你第一次訪問筆記中的媒體文件時,都要打開選擇文件的彈窗,通過讓用戶主動選擇來授權(quán)訪問沙箱外的媒體文件。不過,如果你的媒體文件在第一次選擇插入文檔時復制到 sandbox 中,以后訪問優(yōu)先從沙箱容器中讀取,那是不需要授權(quán)的。雖然我也可以這么做,但這里考慮到后面的功能,還是選擇使用數(shù)據(jù)庫,當需要導出筆記時再從數(shù)據(jù)庫中導出。

數(shù)據(jù)庫的選擇

Electron 應(yīng)用中常使用的數(shù)據(jù)庫是 SQLiteIndexedDB,IndexedDB 是在前端網(wǎng)頁中去操作。有的文章里說 IndexedDB 的性能會比 SQLite 更好,大家看實際場景去選擇使用。大多數(shù)桌面應(yīng)用或者 App 需要使用數(shù)據(jù)庫的時候一般都是用 SQLite。

npm 上有兩個最常用的 sqlite3 庫,一是 better-sqlite3 ,一是 node-sqlite ,兩種各有特點。前者是同步的 api ,執(zhí)行速度快,后者是異步 api ,執(zhí)行速度相對慢一點。值得注意的是,后者的編譯支持 arm 機器,而且由于出的比較早,和其他庫配合使用很方便。

安裝

安裝 node-sqlite

// 倉庫名是 node-sqlite, package 名是 sqlite3
yarn add sqlite3

借助 Knex.js 簡化數(shù)據(jù)庫操作

Knex.js是為Postgres,MSSQL,MySQL,MariaDB,SQLite3,Oracle和Amazon Redshift設(shè)計的 SQL 查詢構(gòu)建器

安裝 Knex.js

yarn add knex

創(chuàng)建表

現(xiàn)在,我們要開始設(shè)計數(shù)據(jù)庫結(jié)構(gòu)了。我們大概需要 3 張表,筆記本表,筆記表,還有一個媒體文件表。sqlite 支持 blob 數(shù)據(jù)類型,所以你也可以把媒體文件的二進制數(shù)據(jù)存到數(shù)據(jù)庫中。這里我們就簡單的記個 id ,把媒體文件存到沙箱內(nèi)。

我們確定一下三張表的表名,notebooks, notes, media, 然后看一下該如何使用 Knex.js 創(chuàng)建表

import { app } from "electron";
import knex, { Knex } from "knex";
import { join } from "path";
import { injectable } from "inversify";
@injectable()
export class LocalDB {
  declare db: Knex;
  async init() {
    this.db = knex({
      client: "sqlite",
      useNullAsDefault: true,
      connection: {
        filename: join(app.getPath("userData"), "local.db"),
      },
    });
    // 新建表
    await this.sync();
  }
  async sync() {
    // notebooks
    await this.db.schema.hasTable("notebooks").then((exist) => {
      if (exist) return;
      return this.db.schema.createTable("notebooks", (table) => {
        table.bigIncrements("id", { primaryKey: true });
        table.string("name");
        table.timestamps(true, true);
      });
    });
    // notes
    await this.db.schema.hasTable("notes").then((exist) => {
      if (exist) return;
      return this.db.schema.createTable("notes", (table) => {
        table.bigIncrements("id", { primaryKey: true });
        table.string("name");
        table.text("content");
        table.bigInteger("notebook_id");
        table.timestamps(true, true);
      });
    });
    // media
    await this.db.schema.hasTable("media").then((exist) => {
      if (exist) return;
      return this.db.schema.createTable("media", (table) => {
        table.bigIncrements("id", { primaryKey: true });
        table.string("name");
        table.string("local_path"); // 本地實際路徑
        table.string("sandbox_path"); // 沙盒中的地址
        table.bigInteger("note_id");
        table.timestamps(true, true);
      });
    });
  }
}

這里我用了一個 IOC 庫 inversify, 后面遇到 injectable、inject、ioc.get等寫法都是和這個有關(guān),這里我就不多介紹了,具體用法可以看文檔或其他文章。

注意:三張表中,notemedia 都一個外鍵,這里我簡化了,并沒有用 api 去創(chuàng)建。

Service

數(shù)據(jù)庫表創(chuàng)建完了,接下來我們?yōu)楸淼牟僮鲗懴嚓P(guān)服務(wù),這一塊我是參考傳統(tǒng)后端 api 的設(shè)計去寫的,有 Service(數(shù)據(jù)庫) 和 Controller(業(yè)務(wù)),以 Notebook 為例:

import { inject, injectable } from "inversify";
import { LocalDB } from "../db";
interface NotebookModel {
  id: number;
  name: string;
  create_at?: string | null;
  update_at?: string | null;
}
@injectable()
export class NotebooksService {
  name = "notebooks";
  constructor(@inject(LocalDB) public localDB: LocalDB) {}
  async create(data: { name: string }) {
    return await this.localDB.db.table(this.name).insert(data);
  }
  async get(id: number) {
    return await this.localDB.db
      .table<NotebookModel>(this.name)
      .select("*")
      .where("id", "=", id)
      .first();
  }
  async delete(id: number) {
    return await this.localDB.db.table(this.name).where("id", "=", id).delete();
  }
  async update(data: { id: number; name: string }) {
    return await this.localDB.db
      .table(this.name)
      .where("id", "=", data.id)
      .update({ name: data.name });
  }
  async getAll() {
    return await this.localDB.db.table<NotebookModel>(this.name).select("*");
  }
}

Service 只負責數(shù)據(jù)庫的連接和表中數(shù)據(jù)的增刪改查。

Controller

Controller 可以通過接入 Service 操作數(shù)據(jù)庫,并做一些業(yè)務(wù)上的工作。

import { inject, injectable } from "inversify";
import { NotebooksService } from "../services/notebooks.service";
import { NotesService } from "../services/notes.service";
@injectable()
export class NotebooksController {
  constructor(
    @inject(NotebooksService) public service: NotebooksService,
    @inject(NotesService) public notesService: NotesService
  ) {}
  async create(name: string) {
    await this.service.create({
      name,
    });
  }
  async delete(id: number) {
    const row = await this.service.get(id);
    if (row) {
      const notes = await this.notesService.getByNotebookId(id);
      if (notes.length) throw Error("delete failed");
      await this.service.delete(id);
    }
  }
  async update(data: { id: number; name: string }) {
    return await this.service.update(data);
  }
  async getAll() {
    return await this.service.getAll();
  }
}

業(yè)務(wù)

如何創(chuàng)建筆記本?

我們先來實現(xiàn)創(chuàng)建筆記本,之后的刪除筆記本,更新筆記本名稱等等,依葫蘆畫瓢就行。我們在界面上添加一個創(chuàng)建按鈕。

點擊后就會出現(xiàn)這樣一個彈窗,這里 UI 庫我是用的 antd 做的。

看一下這個彈窗部分的邏輯

import { Modal, Form, Input } from "antd";
import React, { forwardRef, useImperativeHandle, useState } from "react";
interface CreateNotebookModalProps {
  onCreateNotebook: (name: string) => Promise<void>;
}
export interface CreateNotebookModalRef {
  setVisible: (visible: boolean) => void;
}
export const CreateNotebookModal = forwardRef<
  CreateNotebookModalRef,
  CreateNotebookModalProps
>((props, ref) => {
  const [modalVisible, setMoalVisible] = useState(false);
  const [form] = Form.useForm();
  const handleOk = () => {
    form.validateFields().then(async (values) => {
      await props.onCreateNotebook(values.name);
      setMoalVisible(false);
    });
  };
  useImperativeHandle(ref, (): CreateNotebookModalRef => {
    return {
      setVisible: setMoalVisible,
    };
  });
  return (
    <Modal
      visible={modalVisible}
      title="創(chuàng)建筆記本"
      onCancel={() => setMoalVisible(false)}
      onOk={handleOk}
      cancelText="取消"
      okText="確定"
      destroyOnClose
    >
      <Form form={form}>
        <Form.Item
          label="筆記本名稱"
          name="name"
          rules={[
            {
              required: true,
              message: "請?zhí)顚懨Q",
            },
            {
              whitespace: true,
              message: "禁止使用空格",
            },
            { min: 1, max: 100, message: "字符長度請保持在 1-100 之間" },
          ]}
        >
          <Input />
        </Form.Item>
      </Form>
    </Modal>
  );
});

外部提供的 onCreateNotebook 的實現(xiàn):

const handleCreateNotebook = async (name: string) => {
    await window.Bridge?.createNotebook(name);
    const data = await window.Bridge?.getNotebooks();
    if (data) {
      setNotebooks(data);
    }
};

上面出現(xiàn)的 Bridge 是我在第一篇中講的 preload.js 提供的對象,它可以幫我們和 electron 主進程通信。

接來寫,我們具體看一下 preload 和 主進程部分的實現(xiàn):

// preload.ts
import { contextBridge, ipcRenderer, MessageBoxOptions } from "electron";
contextBridge.exposeInMainWorld("Bridge", {
  showMessage: (options: MessageBoxOptions) => {
    ipcRenderer.invoke("showMessage", options);
  },
  createNotebook: (name: string) => {
    return ipcRenderer.invoke("createNotebook", name);
  },
  getNotebooks: () => {
    return ipcRenderer.invoke("getNotebooks");
  },
});

實際還是用 ipcRenderer 去通信,但是這種方式更好

// main.ts
import { ipcMain } from "electron"
ipcMain.handle("createNotebook", async (e, name: string) => {
  return await ioc.get(NotebooksController).create(name);
});
ipcMain.handle("getNotebooks", async () => {
  return await ioc.get(NotebooksController).getAll();
});

總結(jié)

最后,我們來看一下這部分的完整交互:

這一篇,我們主要學習了如何在 Elctron 使用 SQLite 數(shù)據(jù)庫,并且簡單完成了 CRUD 中的 C。相關(guān)代碼在 Github 上,感興趣的同學可以自行查看。

以上就是詳解Electron中如何使用SQLite存儲筆記的詳細內(nèi)容,更多關(guān)于Electron SQLite存儲筆記的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • vue 圖片裁剪上傳組件的實現(xiàn)

    vue 圖片裁剪上傳組件的實現(xiàn)

    這篇文章主要介紹了vue 圖片裁剪上傳組件的實現(xiàn),幫助大家更好的理解和使用vue框架,感興趣的朋友可以了解下
    2020-11-11
  • Vue vxe-table使用問題收錄小結(jié)

    Vue vxe-table使用問題收錄小結(jié)

    這篇文章主要為大家介紹了Vue vxe-table使用問題收錄小結(jié),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09
  • Vue實現(xiàn)倒計時小功能

    Vue實現(xiàn)倒計時小功能

    這篇文章主要為大家詳細介紹了Vue實現(xiàn)倒計時小功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • 關(guān)于el-table表格組件中插槽scope.row的使用方式

    關(guān)于el-table表格組件中插槽scope.row的使用方式

    這篇文章主要介紹了關(guān)于el-table表格組件中插槽scope.row的使用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • vue實現(xiàn)動態(tài)顯示與隱藏底部導航的方法分析

    vue實現(xiàn)動態(tài)顯示與隱藏底部導航的方法分析

    這篇文章主要介紹了vue實現(xiàn)動態(tài)顯示與隱藏底部導航的方法,結(jié)合實例形式分析了vue.js針對導航隱藏與顯示的路由配置、事件監(jiān)聽等相關(guān)操作技巧,需要的朋友可以參考下
    2019-02-02
  • Vue加載json文件的方法簡單示例

    Vue加載json文件的方法簡單示例

    這篇文章主要介紹了Vue加載json文件的方法,結(jié)合實例形式分析了vue.js針對json文件的加載及數(shù)據(jù)讀取等相關(guān)操作技巧,需要的朋友可以參考下
    2019-01-01
  • Vue CLI 3搭建vue+vuex最全分析(推薦)

    Vue CLI 3搭建vue+vuex最全分析(推薦)

    這篇文章主要介紹了Vue CLI 3搭建vue+vuex最全分析(推薦),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-09-09
  • 在Vue中實現(xiàn)地圖熱點展示與交互的方法詳解(如熱力圖)

    在Vue中實現(xiàn)地圖熱點展示與交互的方法詳解(如熱力圖)

    隨著大數(shù)據(jù)和可視化技術(shù)的發(fā)展,地圖熱點展示越來越受到人們的關(guān)注,在Vue應(yīng)用中,我們通常需要實現(xiàn)地圖熱點的展示和交互,以便更好地呈現(xiàn)數(shù)據(jù)和分析結(jié)果,本文將介紹在Vue中如何進行地圖熱點展示與交互,包括熱力圖、點聚合等
    2023-07-07
  • Vue2.0設(shè)置全局樣式(less/sass和css)

    Vue2.0設(shè)置全局樣式(less/sass和css)

    這篇文章主要為大家詳細介紹了Vue2.0設(shè)置全局樣式(less/sass和css),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • vue3繼承并擴展三方組件完成二次封裝的示例詳解

    vue3繼承并擴展三方組件完成二次封裝的示例詳解

    這篇文章主要介紹了vue3繼承并擴展三方組件完成二次封裝,文章使用naiveui的Input組件進行舉例,elementPlus或者其他組件庫同理,并通過代碼示例講解的非常詳細,需要的朋友可以參考下
    2024-03-03

最新評論