Vue3 動態 Web title

Vue 動態 Title

每個網頁都會有WebTitle ,在Vue 的專案內必須要全局中才能獲取到,
在App.vue 內必須使用

當打開網頁Home時塞入localStorage.setItem(‘pageClass’,’pageHome’),
綁定body document.querySelector(‘body’) => 將 localStorage.getItem(‘pageClass’)設定屬性setAttribute 到body的Class,且將網頁主題名稱塞入titleTag

  • Pinia全局綁定title 與 切換時改變title
  • 綁定body 設定屬性 setAttribute class 改變body class
  • 監控 watchEffect 改變
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    import { defineStore } from 'pinia';

    export const usePageStore = defineStore('page', () => {

    const setPageClass = (pageClass:string) => {
    document.querySelector('body')?.setAttribute('class', pageClass);
    }
    const getPageClass=()=>{
    let pageClass:any | string =localStorage.getItem('pageClass');
    switch (pageClass) {
    case pageClass:
    document.querySelector('body')?.setAttribute('class', pageClass);
    break
    }
    }
    const changeTitleTag = (title: string) => {
    const titleTag = document.querySelector('title');

    if (titleTag != null) {
    titleTag.innerText = '採購系統'+' | '+title;
    }
    }

    return{
    setPageClass,getPageClass,changeTitleTag
    }
    })

    引入頁面
    首頁頁面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <template>
    <div>{{ title }}</div>
    </template>

    <script setup lang="ts">
    import { ref, onMounted,watchEffect } from 'vue'
    import { usePageStore } from '../stores/titleTag'

    const storePageTag = usePageStore()
    const {getPageClass,changeTitleTag,setPageClass} = storePageTag
    const title = ref<string>('首頁')
    const pageClass = ref<string>('homePage')
    watchEffect(() => {
    getPageClass();
    setPageClass(pageClass.value)
    })
    onMounted(() => {
    changeTitleTag(title.value);
    });
    </script>

    登入頁面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <template>
    <div>{{ title }}</div>
    </template>

    <script setup lang="ts">
    import { ref, onMounted,watchEffect } from 'vue'
    import { usePageStore } from '../stores/titleTag'
    const storePageTag = usePageStore()
    const {getPageClass,changeTitleTag,setPageClass} = storePageTag
    const title = ref<string>('請先登入')
    const pageClass = ref<string>('loginPage')
    watchEffect(() => {
    getPageClass();
    setPageClass(pageClass.value)
    })
    onMounted(() => {
    changeTitleTag(title.value);

    });
    </script>

    Node express 權限管理

    權限管理

    資料表設計
    • 使用者資料表: `users` 使用者,上面綁角色role_id,每個使用者都有一種角色
    • 角色資料表: `roles` 角色,用來設定權限用
    • 模組資料表: `module_permissions` 模組權限,用來綁定底下功能權限
    • 功能資料表: `func_permissions` 功能權限,用來綁底層的權限
    • 權限資料表: `permissions` 用來綁func_permissions與permission_types的表
    • 權限型別資料表: `permission_types` 權限分類,可自行定義分類,如:查看、新增、編輯、刪除…等
    使用者 => (一對多) 角色 => (一對多) 模組 => (一對多) 功能 => (一對多) 權限 => (多對一) 權限型別

    根據使用者角色獲得頁面檢視權限

    1
    2
    3
    4
    SELECT users.id, users.uId, users.userName, users.userEmail, users.userPassword, users.userPhone, users.roleId, users.token ,modulePermissions.modulePermissionId,modulePermissions.modulePermissionName,modulePermissions.pageLink 
    FROM `users`
    INNER JOIN `modulePermissions`
    ON users.uId=modulePermissions.roleId WHERE users.userName =?

    bcryptjs密碼加密與驗證

    安裝bcryptjs bcryptjs
    密碼加密,此套件的加密是不可逆的,所以沒有辦法從加密後的結果回推原始密碼,相對安全性提高非常多

    1
    2
    npm install bcryptjs --save

    加密後的 bcrypt 分為四個部分:

    • Bcrypt
      該字串為 UTF-8 編碼,並且包含一個終止符
    • Round
      (回合數)每增加一次就加倍雜湊次數,預設10次
    • Salt
      (加鹽)128 bits 22個字元
    • Hash
      (雜湊)138 bits 31個字元

    Node express MySql 註冊與登入

    註冊

    因爲註冊使用到bcryptjs密碼加密功能,
    bcrypt加密語法

    1
    2
    3
    4
    5
    6
    //引入bcryptjs密碼加密
    const bcrypt = require('bcryptjs')

    const saltRounds = 10;
    const hashPassword = bcrypt.hashSync(密碼, saltRounds)

    models.users.js

    1
    INSERT INTO `users` (column1, column2, column3...) SET (value1, value2, value3...)

    token

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //bcryptjs密碼加密
    const bcrypt = require('bcryptjs')
    //載入 jsonwebtoken
    const jwt = require('jsonwebtoken')
    // 建立使用者
    var User = function(newUser){
    this.uId = newUser.uId;
    this.userName = newUser.userName;
    this.userEmail = newUser.userEmail;
    this.userPassword = bcrypt.hashSync(newUser.userPassword, 3);//bcrypt.hashSync(newUser.userPassword, 12)
    this.userPhone= newUser.userPhone;
    this.roleId= newUser.roleId;
    this.token = jwt.sign({_id: newUser.uId ,username: newUser.userName,email: newUser.userEmail}, 'RESTFULAPI',{expiresIn: "24h"}).toString();//jwt
    this.createdAt= newUser.createdAt;
    this.updatedAt= newUser.updatedAt;
    this.deletedAt= newUser.deletedAt;
    }
    User.create = function (newUser, callback) {
    db.query('INSERT INTO users SET ?', newUser, callback);
    };
    module.exports = User;

    登入

    尋找帳號與密碼驗證 => 內連結的模組資料表的資料內容 (取出modulePermissionId與modulePermissionName 並且新增到menu )
    使用資料庫語法:

    1
    2
    3
    4

    SELECT 顯示欄位 FROM `資料表1` INNER JOIN `資料表2` ON 資料表1uId=資料表2roleId WHERE 資料表1.欄位名 ='';
    //例如
    SELECT users.id, users.uId, users.userName, users.userEmail, users.userPassword, users.userPhone, users.roleId, users.token ,modulePermissions.modulePermissionId,modulePermissions.modulePermissionName FROM `users` INNER JOIN `modulePermissions` ON users.uId=modulePermissions.roleId WHERE users.userName =?;

    登入時必須帳號登入時驗證密碼(bcrypt.compareSync(密碼, 密碼加密))

    bcrypt驗證語法

    1
    bcrypt.compareSync(密碼, 密碼加密)

    models.users.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    'use strict';

    var db = require('../config/database');
    //bcryptjs密碼加密
    const bcrypt = require('bcryptjs')
    //載入 jsonwebtoken
    const jwt = require('jsonwebtoken')

    User.userName = (userName, result) =>
    {
    let sql = 'SELECT users.id, users.uId, users.userName, users.userEmail, users.userPassword, users.userPhone, users.roleId, users.token ,modulePermissions.modulePermissionId,modulePermissions.modulePermissionName FROM `users` INNER JOIN `modulePermissions` ON users.uId=modulePermissions.roleId WHERE users.userName =?';
    db.query(sql, userName, (err, row, fields) =>
    {
    if (err) result(err, null,);
    result(null, row);
    });
    };

    controllers.users.js

    • bcrypt.hashSync(密碼, SaltRound)
    • bcrypt.compareSync(密碼,加密密碼)
    加密後的 bcrypt 分為四個部分:
    • Bcrypt
      該字串為 UTF-8 編碼,並且包含一個終止符
    • Round
      (回合數)每增加一次就加倍雜湊次數,預設10次
    • Salt
      (加鹽)128 bits 22個字元
    • Hash
      (雜湊)138 bits 31個字元
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    const User = require('../models/users');

    exports.login = async (req, res, next) =>
    {
    /*
    #swagger.summary='登入',
    #swagger.description = '' */

    /* #swagger.parameters['body'] = {
    in: 'body',
    description: '',
    required: true,
    schema: { $ref: "#/definitions/Login" }
    } */

    /* #swagger.security = [{
    "apiKeyAuth": []
    }] */

    const userName = req.body.userName;
    const userPassword = req.body.userPassword;

    try {
    //檢查是否有userName 帳號
    User.userName(userName, (error, item) =>
    {
    if (item.length != 0 && bcrypt.compareSync(userPassword, item[0].userPassword) ) {
    // console.log('登入item', item)
    console.log('登入驗證密碼', bcrypt.compareSync(userPassword, item[0].userPassword))
    let menu = [];
    // forEach
    item.forEach(el => {
    let query = {
    modulePermissionId: el.modulePermissionId,
    modulePermissionName: el.modulePermissionName,
    }
    menu.push(query)
    });
    let loginValue = {
    userName: item[0].userName,
    token: item[0].token,
    menu: menu,
    }
    return res.status(200).json(loginValue);
    } else {
    return res.status(400).send('沒有userName')
    }
    })
    }
    catch (error) {
    return res.status(500).json({message: error.message})
    }
    }
    routes/users.js
    1
    2
    3
    4
    5
    var express = require('express');
    var router = express.Router();
    const userController = require('../controllers/users');

    router.post('/login', userController.login);

    MySql 目錄

    AUTO INCREMENT 欄位會自動遞增資料行的值

    AUTO_INCREMENT 欄位會自動遞增資料行的值

    AUTO INCREMENT 欄位會自動遞增資料行的值,因為每次新增資料時欄位值都會自動遞增也就是說 AUTO INCREMENT 欄位值會是唯一的,該欄位用途就像是一個識別碼或流水號,而 AUTO INCREMENT 常與 Primary Key 一起搭配使用。
    將 users 資料表中的 id 欄位 AUTO INCREMENT 欄位會自動遞增資料行的值,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    CREATE TABLE `users` ( 
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    uId INT NOT NULL UNIQUE,
    userName VARCHAR(30) NOT NULL,
    userEmail VARCHAR(50) NOT NULL,
    userPassword VARCHAR(30) NOT NULL,
    userPhone VARCHAR(10) NOT NULL,
    roleId INT NOT NULL,
    createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    deletedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
    );
    CREATE TABLE `users` (
    id INT NOT NULL AUTO_INCREMENT,
    uId INT NOT NULL,
    userName VARCHAR(50) NOT NULL,
    userEmail VARCHAR(30)NOT NULL,
    userPassword VARCHAR(30) NOT NULL,
    userPhone VARCHAR(10) NOT NULL,
    roleId INT NOT NULL,
    createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    deletedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ,
    PRIMARY KEY (id),
    UNIQUE (uId)
    );
    參考:AUTO INCREMENT 欄位 (SQL AUTO INCREMENT column)

    PRIMARY KEY 主鍵限制 與 AUTO INCREMENT 欄位會自動遞增資料行的值

    PRIMARY KEY 主鍵限制 (SQL PRIMARY KEY Constraint)

    PRIMARY KEY 用來保證欄位在資料表中的唯一性,主鍵欄位中的每一筆資料在資料表中都必需是獨一無二的。
    PRIMARY KEY 有點類似 UNIQUE 加上 NOT NULL。

    一個資料表中只能有一個 PRIMARY KEY,但是可以有多個 UNIQUE。
    將 users 資料表中的 id 欄位設為主鍵:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    CREATE TABLE `users` ( 
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    uId INT NOT NULL UNIQUE,
    userName VARCHAR(30) NOT NULL,
    userEmail VARCHAR(50) NOT NULL,
    userPassword VARCHAR(30) NOT NULL,
    userPhone VARCHAR(10) NOT NULL,
    roleId INT NOT NULL,
    createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    deletedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
    );
    CREATE TABLE `users` (
    id INT NOT NULL AUTO_INCREMENT,
    uId INT NOT NULL,
    userName VARCHAR(50) NOT NULL,
    userEmail VARCHAR(30)NOT NULL,
    userPassword VARCHAR(30) NOT NULL,
    userPhone VARCHAR(10) NOT NULL,
    roleId INT NOT NULL,
    createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    deletedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ,
    PRIMARY KEY (id),
    UNIQUE (uId)
    );

    參考:PRIMARY KEY 主鍵限制 (SQL PRIMARY KEY Constraint)

    AUTO_INCREMENT 欄位會自動遞增資料行的值

    AUTO INCREMENT 欄位會自動遞增資料行的值,因為每次新增資料時欄位值都會自動遞增也就是說 AUTO INCREMENT 欄位值會是唯一的,該欄位用途就像是一個識別碼或流水號,而 AUTO INCREMENT 常與 Primary Key 一起搭配使用。
    將 users 資料表中的 id 欄位 AUTO INCREMENT 欄位會自動遞增資料行的值,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    CREATE TABLE `users` ( 
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    uId INT NOT NULL UNIQUE,
    userName VARCHAR(30) NOT NULL,
    userEmail VARCHAR(50) NOT NULL,
    userPassword VARCHAR(30) NOT NULL,
    userPhone VARCHAR(10) NOT NULL,
    roleId INT NOT NULL,
    createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    deletedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
    );
    CREATE TABLE `users` (
    id INT NOT NULL AUTO_INCREMENT,
    uId INT NOT NULL,
    userName VARCHAR(50) NOT NULL,
    userEmail VARCHAR(30)NOT NULL,
    userPassword VARCHAR(30) NOT NULL,
    userPhone VARCHAR(10) NOT NULL,
    roleId INT NOT NULL,
    createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    deletedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ,
    PRIMARY KEY (id),
    UNIQUE (uId)
    );
    參考:AUTO INCREMENT 欄位 (SQL AUTO INCREMENT column)

    建立一個新資料庫

    SQL 語句不區分大小寫

    1
    CREATE DATABASE 新資料庫名稱;

    參考:CREATE DATABASE 語法(Syntax)

    創建table

    1
    2
    3
    4
    5
    6
    CREATE TABLE 資料表名稱 (
    欄位column_name1 欄位屬性data_type,
    欄位column_name2 欄位屬性data_type,
    欄位column_name3 欄位屬性data_type,
    ···
    );
    例如:建立資料表table 名稱 users
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    CREATE TABLE `users` ( 
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    uId INT NOT NULL UNIQUE,
    userName VARCHAR(30) NOT NULL,
    userEmail VARCHAR(50) NOT NULL,
    userPassword VARCHAR(30) NOT NULL,
    userPhone VARCHAR(10) NOT NULL,
    roleId INT NOT NULL,
    createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    deletedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
    );
    CREATE TABLE `users` (
    id INT NOT NULL AUTO_INCREMENT,
    uId INT NOT NULL,
    userName VARCHAR(50) NOT NULL,
    userEmail VARCHAR(30)NOT NULL,
    userPassword VARCHAR(30) NOT NULL,
    userPhone VARCHAR(10) NOT NULL,
    roleId INT NOT NULL,
    createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    deletedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ,
    PRIMARY KEY (id),
    UNIQUE (uId)
    );
    參考:CREATE TABLE 是我們在資料庫中用來建立一個新資料表的語法

    新增資料至某資料表: INSERT INTO 是用來新增資料至某資料表 (table)

    1
    2
    3
    4
    5
    INSERT INTO table_name (column1, column2, column3...)
    VALUES (value1, value2, value3...);
    or
    INSERT INTO table_name
    VALUES (value1, value2, value3...);
    例如:新增資料至users資料表
    1
    INSERT INTO `users`(`uId`, `userName`, `userEmail`, `userPassword`, `userPhone`, `roleId`, `createdAt`, `updatedAt`, `deletedAt`) VALUES ('1735223063','laraHuang','laraHuang@gmail.com','Lara123456','0910123456','1735223063','2024-12-26 22:24:23','2024-12-26 22:24:23','2024-12-26 22:24:23')

    參考:INSERT INTO 是用來新增資料至某資料表 (table)

    TypeScript type 與interface 區別

    Type

    1
    2
    3
    4
    5
    type Point = {
    x: number;
    y: number;
    };
    type SetPoint = (x: number, y: number) => void;

    Interface

    1
    2
    3
    4
    5
    6
    7
    interface Point {
    x: number;
    y: number;
    }
    interface SetPoint {
    (x: number, y: number): void;
    }

    Interface都可允许拓展(extends)

    Interface extends Type
    1
    2
    3
    4
    type Person{
    name:string
    }
    interface Student extends Person { stuNo: number }
    Interface extends Interface
    1
    2
    3
    4
    interface Person{
    name:string
    }
    interface Student extends Person { stuNo: number }
    type extends type
    1
    2
    3
    4
    type Person{
    name:string
    }
    type Student = Person & { stuNo: number }
    type extends Interface
    1
    2
    3
    4
    interface Person{
    name:string
    }
    type Student = Person & { stuNo: number }
    實現 implements
    類別可以實作interface以及type(除聯合類型外)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    interface ICat{
    setName(name:string): void;
    }
    class Cat implements ICat{
    setName(name:string):void{
    // todo
    }
    }
    // type
    type ICat = {
    setName(name:string): void;
    }
    class Cat implements ICat{
    setName(name:string):void{
    // todo
    }
    }
    type 無法對聯合類型Person進行實現
    無法對聯合類型Person進行實現
    error: A class can only implement an object type or intersection of object types with statically known members.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    type Person = { name: string; } | { setName(name:string): void };
    // 無法對聯合類型Person進行實現
    // error: A class can only implement an object type or intersection of object types with statically known members.
    class Student implements Person {
    name= "Lara";
    setName(name:string):void{
    // todo
    }
    }

    type 可以宣告基本型別別名,聯合型別類型,元組等型別,而 interface 不行

    type可以宣告基本型別別名,聯合型別類型,元組等型別

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 基本類型别名
    type Name = string

    // 聯合類型
    interface Dog {
    wong();
    }
    interface Cat {
    miao();
    }

    type Pet = Dog | Cat

    // 具体定義數組每個位置的類型
    type PetList = [Dog, Pet]

    type 語句中還可以使用 typeof 獲取實例的 類型進行赋值

    1
    2
    3
    // 當你想獲取一個變量的類型時,使用 typeof
    let div = document.createElement('div');
    type B = typeof div
    1
    2
    3
    4
    5
    6
    7
    type StringOrNumber = string | number;  
    type Text = string | { text: string };
    type NameLookup = Dictionary<string, Person>;
    type Callback<T> = (data: T) => void;
    type Pair<T> = [T, T];
    type Coordinates = Pair<number>;
    type Tree<T> = T | { left: Tree<T>, right: Tree<T> };

    interface 可以聲明合併而 type 不行

    interface 能够聲明合併

    1
    2
    3
    4
    5
    6
    7
    8
    interface User {
    name: string
    age: number
    }

    interface User {
    sex: string
    }