[Front-End-Engineering] Ausführliche Erläuterung von vite (2) - Vue3 Family Barrel + TS zum Aufbau des Post-Management-Systems

Installieren Sie das Basispaket

npm create vite@latest
# 这里选择的是Vue+Typescript的组合
cd vue-admin
npm install

# 先安装基础包
npm install vue-router@4
npm i pinia
npm i axios
npm install sass --save-dev
npm install element-plus --save
npm install @element-plus/icons-vue
npm install -D unplugin-vue-components unplugin-auto-import
npm i eslint -D

# 提交规范
npm i lint-staged husky  --save-dev
npm install @commitlint/cli @commitlint/config-conventional -D

Code-Spezifikation

npm init @eslint/config

Es wird eine Reihe von Eingabeaufforderungen geben. Die Auswahlmöglichkeiten sind wie folgt:

Need to install the following packages:
  @eslint/create-config
Ok to proceed? (y)
√ How would you like to use ESLint? · style       
√ What type of modules does your project use? · esm
√ Which framework does your project use? · vue
√ Does your project use TypeScript? · No / Yes
√ Where does your code run? · browser
√ How would you like to define a style for your project? · guide
√ Which style guide do you want to follow? · standard-with-typescript
√ What format do you want your config file to be in? · JavaScript
Checking peerDependencies of eslint-config-standard-with-typescript@latest
The config that you've selected requires the following dependencies:

eslint-plugin-vue@latest eslint-config-standard-with-typescript@latest @typescript-eslint/eslint-plugin@^5.50.0 eslint@^8.0.1 eslint-plugin-import@^2.25.2 eslint-plugin-n@^15.0.0 eslint-plugin-promise@^6.0.0 typescript@*
√ Would you like to install them now? · No / Yes
√ Which package manager do you want to use? · npm
Installing eslint-plugin-vue@latest, eslint-config-standard-with-typescript@latest, @typescript-eslint/eslint-plugin@^5.50.0, eslint@^8.0.1, eslint-plugin-import@^2.25.2, eslint-plugin-n@^15.0.0, eslint-plugin-promise@^6.0.0, typescript@*

Im Projekt wird eine Datei generiert .eslintrc.cjs. Anschließend wird das Skript zur Überprüfung konfiguriert:

"lint": "eslint src/**/*.{js,jsx,vue,ts,tsx} --fix"

Beim Ausführen wurde jedoch ein Fehler gemeldet. Da mein derzeit unterstützter "typescript": "^5.1.3",Versionsbereich : ist und ich daher ein Downgrade durchführen muss: , gibt es bei der Konfiguration von eslint viele Probleme, und die Lösung wird direkt bereitgestellt:@typescript-eslint/typescript-estreets=3.3.1 <5.1.0[email protected]

Die erste besteht darin, Folgendes zu ändern .eslintrc.cjs:

module.exports = {
    
    
  env: {
    
    
    browser: true,
    es2021: true
  },
  extends: [
    'plugin:vue/vue3-essential',
    'standard-with-typescript'
  ],
  parser: "vue-eslint-parser",
  overrides: [
  ],
  parserOptions: {
    
    
    ecmaVersion: 'latest',
    sourceType: 'module',
    project: ["./tsconfig.json"],
    parser: "@typescript-eslint/parser",
    extraFileExtensions: ['.vue']
  },
  plugins: [
    'vue'
  ],
  rules: {
    
    
    'space-before-function-paren': [2, {
    
    
      anonymous: 'always',
      named: 'never',
      asyncArrow: 'always'
    }],
    'vue/multi-word-component-names': 0,
    "space-before-function-paren": 0,
    "@typescript-eslint/consistent-type-assertions": 0,
    "@typescript-eslint/ban-types": [
      "error",
      {
    
    
        "extendDefaults": true,
        "types": {
    
    
          "{}": false
        }
      }
    ]
  }
}

Fügen Sie eine Kommentarzeile hinzu , um vite-env.d.tsdie Prüfung zu ignorieren:

// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference types="vite/client" />

Bezüglich eslintder bei der Konfiguration aufgetretenen Probleme können Sie sich für weitere Einzelheiten auf das Schreiben dieses großen Mannes beziehen: Eslint: Eslint (Standardregeln) zum vue3-Projekt hinzufügen

Commit-Spezifikation

git init

Fügen Sie den folgenden Code hinzu package.jsonund rufen Sie damit eslint und stylelint auf, um den Code im temporären Speicherbereich zu überprüfen

"lint-staged": {
    
    
    "*.{vue,js}": [
      "npm run lint"
    ]
  }

implementieren:

npm pkg set scripts.postinstall="husky install"
# 等同于执行npm i,执行过程中会生成.husky文件夹
npm run postinstall

npx husky add .husky/pre-commit "npm lint"
git add .husky/pre-commit

git commitAuf diese Weise wird es automatisch ausgeführt, wenn wir es ausführen npm lint.

Es ist peinlich. Während des laufenden Prozesses wurde ein Fehler gemeldet, dass der Knoten kein interner oder externer Befehl ist. Es gibt kein Problem, es liegt wahrscheinlich am NVM-Tool, daher wurde Voltanode -v später geändert , um die Versionskontrolle des Knotens durchzuführen.

npx husky add .husky/commit-msg "npx --no -- commitlint --edit $1"

Neu commitlint.config.cjs:

module.exports = {
    
    
  extends: ['@commitlint/config-conventional'],
  rules: {
    
    
    'type-enum': [2, 'always', [
      'feat', // 新增功能
      'update', // 更新功能
      'ui', // 样式改动
      'fix', // 修复功能bug
      'merge', // 合并分支
      'refactor', // 重构功能
      'perf', // 性能优化
      'revert', // 回退提交
      'style', // 不影响程序逻辑的代码修改(修改空白字符,格式缩进,补全缺失的分号等)
      'build', // 修改项目构建工具(例如 glup,webpack,rollup 的配置等)的提交
      'docs', // 文档新增、改动
      'test', // 增加测试、修改测试
      'chore' // 不修改src或者test的其余修改,例如构建过程或辅助工具的变动
    ]],
    'scope-empty': [0],
    // 'scope-empty': [2, 'never'], 作用域不为空
    'scope-case': [0],
    'subject-full-stop': [0],
    'subject-case': [0]
  }
}

Ändern tsconfig.json:

"include": [
  //...
  "commitlint.config.cjs"
 ],

Ändern .eslintrc.cjs:

project: ["./tsconfig.json", "./commitlint.config.cjs"],
git add .
# 失败
git commit -m "commit校验"
# 成功
git commit -m "feat: commit校验"

Pfad-Alias ​​festlegen

import {
    
     defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

const resolve = (dist) => path.resolve(__dirname, dist)

export default defineConfig({
    
    
  plugins: [vue()],
  resolve: {
    
    
    alias: {
    
    
      '@': resolve('src')
    },
    // 顺便把可以省略的后缀配置一下,在vite中不支持省略.vue
    extensions: [".js", ".ts", ".tsx", ".jsx"]
  }
})

Geändert tsconfig.jsonund hinzugefügt:

"compilerOptions": {
    
    
    // ...
    "baseUrl": ".",
    "paths": {
    
    
      "@/*": ["src/*"]
    }
  },

Stil zurücksetzen

assetsErstellen Sie einen neuen Ordner unter styles/reset.css:

/**
 * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)
 * http://cssreset.com
 */
 
 html, body, div, span, applet, object, iframe,
 h1, h2, h3, h4, h5, h6, p, blockquote, pre,
 a, abbr, acronym, address, big, cite, code,
 del, dfn, em, img, ins, kbd, q, s, samp,
 small, strike, strong, sub, sup, tt, var,
 b, u, i, center,
 dl, dt, dd, ol, ul, li,
 fieldset, form, label, legend,
 table, caption, tbody, tfoot, thead, tr, th, td,
 article, aside, canvas, details, embed, 
 figure, figcaption, footer, header, hgroup, 
 menu, nav, output, ruby, section, summary,
 time, mark, audio, video{
    
    
   margin: 0;
   padding: 0;
   border: 0;
   font-size: 100%;
   font: inherit;
   font-weight: normal;
   vertical-align: baseline;
 }
 /* HTML5 display-role reset for older browsers */
 article, aside, details, figcaption, figure, 
 footer, header, hgroup, menu, nav, section{
    
    
   display: block;
 }
 ol, ul, li{
    
    
   list-style: none;
 }
 blockquote, q{
    
    
   quotes: none;
 }
 blockquote:before, blockquote:after,
 q:before, q:after{
    
    
   content: '';
   content: none;
 }
 table{
    
    
   border-collapse: collapse;
   border-spacing: 0;
 }
  
 /* custom */
 a{
    
    
   color: #7e8c8d;
   text-decoration: none;
   backface-visibility: hidden;
   -webkit-backface-visibility: hidden;
 }
 ::-webkit-scrollbar{
    
    
   width: 5px;
   height: 5px;
 }
 ::-webkit-scrollbar-track-piece{
    
    
   background-color: rgba(0, 0, 0, 0.2);
   border-radius: 6px;
   -webkit-border-radius: 6px;
 }
 ::-webkit-scrollbar-thumb:vertical{
    
    
   height: 5px;
   background-color: rgba(125, 125, 125, 0.7);
   border-radius: 6px;
   -webkit-border-radius: 6px;
 }
 ::-webkit-scrollbar-thumb:horizontal{
    
    
   width: 5px;
   background-color: rgba(125, 125, 125, 0.7);
   border-radius: 6px;
   -webkit-border-radius: 6px;
 }
 html, body{
    
    
   width: 100%;
   font-family: "Arial", "Microsoft YaHei", "黑体", "宋体", "微软雅黑", sans-serif;
 }
 body{
    
    
   line-height: 1;
   -webkit-text-size-adjust: none;
   -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
 }
 html{
    
    
   overflow-y: scroll;
 }
  
 /*清除浮动*/
 .clearfix:before,
 .clearfix:after{
    
    
   content: " ";
   display: inline-block;
   height: 0;
   clear: both;
   visibility: hidden;
 }
 .clearfix{
    
    
   *zoom: 1;
 }
  
 /*隐藏*/
 .dn{
    
    
   display: none;
 }
 

Verwenden Sie Scss

Vite bietet integrierte Unterstützung für , .scss, .sassund Dateien . Es ist nicht notwendig , dafür spezielle Plugins zu installieren , es müssen jedoch die entsprechenden Präprozessor-Abhängigkeiten installiert werden..less.styl.stylusVite

Im Allgemeinen werden wir im Projekt einige Themenfarben definieren:

// variable.scss
$font-color-gray:rgb(147,147,147);

Oder einige gekapselte Sammlungsstile:

// mixins.scss
@mixin line-clamp($lines) {
    
    
  word-break: break-all;
  display: -webkit-box;
  overflow: hidden;
  text-overflow: ellipsis;
  -webkit-line-clamp: $lines;
  -webkit-box-orient: vertical;
}

@mixin ellipsis() {
    
    
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}


Dann vite.config.jskonfigurieren wir in:

css: {
    
    
    preprocessorOptions: {
    
    
      scss: {
    
    
        additionalData: '@import "@/assets/styles/variable.scss";@import "@/assets/styles/mixins.scss";'
      }
    }
  }

Löschen Sie das Verzeichnis style.cssund erstellen Sie ein neues styles/common.scss:

// common.scss
@import url('./reset.css');

Dann main.tsimportieren Sie in:

import '@/assets/styles/common.scss'

Auf diese Weise wird der globale Stil initialisiert. Testen Sie als Nächstes, ob die Summe funktioniert, variable.scssund mixins.scssändern Sie sie HelloWorld.vue, um festzustellen, ob sie grau ist und die beiden Zeilen weggelassen werden:

<script setup lang="ts">
</script>

<template>
  <div class="box">没错,这里要使用浏览器的获取媒体设备的 api 来拿到摄像头的视频流,设置到 video 上,然后对 video 做下镜像反转,加点模糊就好了。</div>
</template>

<style scoped lang="scss">
.box {
      
      
  color: $font-color-gray;
  height: 40px;
  line-height: 20px;
  width: 200px;
  @include line-clamp(2);
}
</style>

Routing konfigurieren

Um nun das Routing zu konfigurieren, hoffe ich, dieses Ergebnis zu erhalten:

- 登录页
- 带菜单栏的框架
  - 主页
  - 人员管理
    - 客户管理
    - 员工管理
- 404

Erstellen Sie also die folgenden Dateien:

Fügen Sie hier eine Bildbeschreibung ein

Bei diesem Prozess werden wir auf mehrere Probleme stoßen:

  • Beim Schreiben von Routen führen wir Komponenten ein, die .vueSuffixe haben müssen;
  • import xxx from '@/xxx'Es wird ein Fehler gemeldet, da Sie den Summenwert nicht wie oben beschrieben tsconfg.jsoneingestellt haben.baseUrlpaths

Da layoutUnterrouten in der Datei verschachtelt werden sollen, layoutmuss Folgendes hinzugefügt werden router-view:

<template>
  <div>布局</div>
  <router-view></router-view>
</template>

Andere Dateien müssen einfach so geschrieben werden:

<template>
  <p>主页</p>
</template>

Neuer routerOrdner:

- router
  -hooks # 后期做登录校验和鉴权用的
  - routes
    - index.ts # 总输出文件
    - others.ts # 不需要layout这一层的路由均可以放在这里
    - person.ts # 人员管理模块
  - index.ts   # 总输出文件

Im Folgenden finden Sie den Inhalt jeder Datei:

// person.ts
export default [
  {
    
    
    path: '/person',
    name: 'Person',
    meta: {
    
     title: '人员管理' },
    redirect: '/person/customer',
    children: [
      {
    
    
        path: '/person/customer',
        name: 'PersonCustomer',
        meta: {
    
     title: '客户管理' },
        component: () => import('@/views/person/customer/index.vue')
      },
      {
    
    
        path: '/person/staff',
        name: 'PersonStaff',
        meta: {
    
     title: '员工管理' },
        component: () => import('@/views/person/staff/index.vue')
      }
    ]
  }
];

// others.ts
export default [
  {
    
    
    path: '/login',
    name: 'Login',
    meta: {
    
     title: '登录' },
    component: () => import('@/views/login/index.vue')
  }
];

// router/routes/index.ts
import Layout from '@/views/layout/index.vue';
import personRoutes from './person';
import otherRoutes from './others';

export default [
  {
    
    
    path: '/',
    name: 'Layout',
    component: Layout,
    children: [
      {
    
    
        path: '/',
        name: 'Index',
        meta: {
    
     title: '主页' },
        component: () => import('@/views/index/index.vue')
      },

      ...personRoutes,
    ]
  },
  ...otherRoutes,
  {
    
    
    path: '/404',
    name: 'NotFound',
    meta: {
    
     title: '404' },
    component: () => import('@/views/404/index.vue')
  },
  {
    
    
    path: "/:pathMatch(.*)",
    redirect: "/404",
    name:'ErrorPage',
    meta: {
    
     title: '' },
  }
];

Legen Sie zunächst den Ordner beiseite hooksund schreiben Sie einfach index.ts:

// router/index.ts
import routes from "./routes";
export default routes;

Erstellen Sie einen neuen src/plugins/index.ts. Wenn wir den Inhalt zuvor registriert haben, haben wir ihn direkt in main.ts abgelegt, was nicht einfach zu warten ist, daher werden wir ihn in Zukunft hier einheitlich bereitstellen:

// src/plugins/index.ts
import {
    
     createRouter, createWebHashHistory } from 'vue-router';
import routes from '@/router/index';

export default (app: any) => {
    
    
  // 注册路由
  const router = createRouter({
    
    
    history: createWebHashHistory(),
    routes
  })

  app.use(router);
}

Vergessen Sie nicht, Folgendes zu ändern App.vue:

<template>
  <router-view></router-view>
</template>
import {
    
     createApp } from 'vue'
import '@/assets/styles/common.scss'
import App from './App.vue'
import installPlugins from '@/plugins';

const app = createApp(App);
installPlugins(app);
app.mount('#app')

Dies ermöglicht das Testen von:

http://127.0.0.1:5173/#/
http://127.0.0.1:5173/#/login
http://127.0.0.1:5173/#/person
http://127.0.0.1:5173/#/person/customer
http://127.0.0.1:5173/#/person/staff

Wenn während des Konfigurationsprozesses ein Fehler gefunden wird: Das Modul „ xxx.vue“ oder seine entsprechende Typdeklaration kann nicht gefunden werden, dann vite-env.d.tsfügen Sie Folgendes hinzu:

declare module '*.vue' {
    
    
  import type {
    
     DefineComponent } from 'vue';
  const vueComponent: DefineComponent<{
    
    }, {
    
    }, any>;
  export default vueComponent;
} 

Verwenden Sie Element Plus

Wenn Sie verwenden Volar, geben Sie den globalen Komponententyp tsconfig.jsonüber im an compilerOptions.type.

// tsconfig.json
{
    
    
  "compilerOptions": {
    
    
    // ...
    // 然而这个配置在后期打包的时候报错了...
    "types": ["element-plus/global"]
  }
}

Hier wird die Methode des On-Demand-Imports übernommen. Wenn Sie das Volumen nicht verfolgen, können Sie den vollständigen Import verwenden:

// vite.config.ts
import {
    
     defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

// 新增
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import {
    
     ElementPlusResolver } from 'unplugin-vue-components/resolvers'

const resolve = (dist) => path.resolve(__dirname, dist)

export default defineConfig({
    
    
  plugins: [
    vue(),
    // 新增
    AutoImport({
    
    
      resolvers: [ElementPlusResolver()]
    }),
    // 新增
    Components({
    
    
      resolvers: [ElementPlusResolver()]
    })
  ],
  resolve: {
    
    
    alias: {
    
    
      '@': resolve('./src')
    },
    extensions: [".js", ".ts", ".tsx", ".jsx"]
  },
  css: {
    
    
    preprocessorOptions: {
    
    
      scss: {
    
    
        additionalData: '@import "@/assets/styles/variable.scss";@import "@/assets/styles/mixins.scss";'
      }
    }
  }
})


element plusKomponenten wie das chinesische Datum sind standardmäßig auf Englisch, daher ändern wir die Komponenten auf Chinesisch:

Fügen Sie hier eine Bildbeschreibung ein

Ändern App.vue:

<script setup lang="ts">
import locale from 'element-plus/lib/locale/lang/zh-cn'
</script>

<template>
  <el-config-provider :locale="locale">
    <router-view></router-view>
  </el-config-provider>
</template>

Dies wird auf Chinesisch angezeigt.

Fügen Sie hier eine Bildbeschreibung ein

Zu den beiden eingeführten Plug-Ins finden Sie hier eine Erklärung:

  • unplugin-vue-componentsWird verwendet, um die in der Vue-Vorlage verwendeten Komponenten automatisch zu identifizieren und bei Bedarf automatisch zu importieren und zu registrieren.
  • unplugin-auto-importvite、webpackHäufig verwendete Konfigurationsbibliotheken können bei Bedarf automatisch in andere Umgebungen importiert werden . APIBeispielsweise ist keine manuelle Arbeit erforderlich , sodass wir sie konfigurieren und einige API-Einführungen löschen können:Vuerefimport
export default defineConfig({
    
    
  plugins: [
    // ...
    AutoImport({
    
    
      imports: [
        'vue',
        'vue-router',
        'pinia'
      ],
      eslintrc: {
    
    
        enabled: true,
        filepath: './.eslintrc-auto-import.json',
        globalsPropValue: true
      },
      resolvers: [ElementPlusResolver()]
    }),
    // ...
  ],
})

Nachdem das Speichern wirksam geworden ist, auto-imports.d.tswird der Inhalt automatisch ausgefüllt und .eslintrc-auto-import.jsondie Konfiguration der globalen Variablen eslint wird im Stammverzeichnis des Projekts generiert.

Dann ändern tsconfg.jsonund .eslintrc.cjs:

// tsconfg.json
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "commitlint.config.cjs", "auto-imports.d.ts"],

// .eslintrc.cjs
project: ["./tsconfig.json", "./commitlint.config.cjs", './.eslintrc-auto-import.json'],

Ignorieren Sie auto-imports.d.tsdie ESLint-Validierung

# .eslintignore
auto-imports.d.ts

Hier müssen Sie aufpassen:

  1. Nicht alle APIs , wie z. B. Vue-Router, createRouterwerden nicht importiert. Informationen zu bestimmten APIs, die automatisch importiert werden können, finden Sie unter unplugin-auto-import/src/presets
  2. Wenn Sie nach dem Generieren der Datei .eslintrc-auto-import.jsonkeine Konfiguration hinzufügen müssen , wird empfohlen, diese enabled: trueauf festzulegen false, andernfalls wird diese Datei jedes Mal generiert.

Nach der Konfiguration können Sie einige Referenzen auf der Seite löschen und feststellen, dass kein Problem vorliegt.

<script lang='ts' setup>
// import { storeToRefs } from 'pinia'
// import { useRouter } from 'vue-router'
// ...
</script>

Testen Sie die Komponente:

<template>
  <p><el-button>测试</el-button></p>
</template>

Dadurch wird die Schaltfläche auf der Seite angezeigt.

Das Prinzip des automatischen On-Demand-Imports besteht darin, <template>die bei der Identifizierung verwendeten Komponenten automatisch zu importieren. Ähnlich wie bei ElMessagedieser Art von Komponenten, die Methoden in JS direkt aufrufen, erkennt das Plug-in den automatischen Import nicht und schließt ihn nicht ab, sodass Sie ihn immer noch manuell importieren müssen (es wird empfohlen, die vollständige Stildatei bei Bedarf zu importieren, um solche Grenzprobleme zu vermeiden):

Ändern Sie vite-env.d.ts, andernfalls wird bei der Einführung in ts ein Fehler gemeldet element plus:

declare module "element-plus";

plugins/element-plus.tsProbieren Sie es in klein und mittel aus:

import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import {
    
     ElLoading, ElMessage, ElMessageBox, ElNotification } from 'element-plus'
import 'element-plus/theme-chalk/index.css'

const options = {
    
    
  size: 'small',
  zIndex: 3000
}

const components = [
  ElLoading,
  ElMessage,
  ElMessageBox, 
  ElNotification
]

export default function install (app: any): void {
    
    
  for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    
    
    app.component(key, component)
  }

  components.forEach((component) => {
    
    
    app.use(component, options)
  })
}


// plugins/index.ts
export default (app: any) => {
    
    
  // ...

  // 注册element-plus
  installElementPlus(app);
}

Mach mal einen Test:

<template>
  <p><el-button @click="onTest">测试</el-button></p>
</template>

<script setup lang="ts">
const onTest = () => {
      
      
  ElLoading.service({
      
       fullscreen: true });
}
</script>

globaleEigenschaften

Gemäß der vorherigen Gewohnheit loadingist der Aufruf von definitiv nicht die obige Methode, sondern wird auf der globalen Seite gemountet Vue.prototype. In diesem Projekt verwenden wir jedoch den On-Demand-Import, und in Vue3hat sich die Schreibweise geändert. Vielleicht möchten Sie so schreiben:

app.config.globalProperties.$loading = ElLoading;
app.config.globalProperties.$message = ElMessage;
app.config.globalProperties.$msgBox = ElMessageBox;
app.config.globalProperties.$notification = ElNotification;

Dann im Prozess der Verwendung:

<script setup lang="ts">
const instance = getCurrentInstance()

onMounted(() => {
    
    
  instance.proxy.$message.success('setup - getCurrentInstance() 成功使用')
  // 也可以使用 appContext
  console.log(instance.appContext.config.globalProperties.$message === instance.proxy.$message) // true
})
</script>

Nach Konsultation der offiziellen Dokumente gibt es jedoch keine getCurrentInstancesolche Methode, die wahrscheinlich nicht den Spezifikationen entspricht. Daher habe ich die Injektion der globalen Methode übernommen provide/inject.

App.vue::

<script setup lang="ts">
import locale from 'element-plus/lib/locale/lang/zh-cn'
import {
      
       ElLoading, ElMessage, ElMessageBox, ElNotification } from 'element-plus'

provide('$loading', ElLoading)
provide('$message', ElMessage)
provide('$messagebox', ElMessageBox)
provide('$notification', ElNotification)
</script>

<template>
  <el-config-provider :locale="locale">
    <router-view></router-view>
  </el-config-provider>
</template>

Ändern element-plus.ts:

import {
    
     App } from 'vue'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
// import { ElLoading, ElMessage, ElMessageBox, ElNotification } from 'element-plus'
import 'element-plus/theme-chalk/index.css'

// const options = {
    
    
//   size: 'small',
//   zIndex: 3000
// }

// const components = [
//   ElLoading,
//   ElMessage,
//   ElMessageBox, 
//   ElNotification
// ]

export default function install (app: App): void {
    
    
  for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    
    
    app.component(key, component)
  }

  // components.forEach((component) => {
    
    
  //   app.use(component, options)
  // })
}

Hinzugefügt src/types/global.d.ts:

import {
    
     ElLoading, ElMessage, ElMessageBox, ElNotification } from 'element-plus'

export interface ComponentCustomProperties {
    
    
  $message: typeof ElMessage
  $msgBox: typeof ElMessageBox
  $loading: typeof ElLoading
  $notification: typeof ElNotification
}


prüfen:

<template>
  <p><el-button @click="onTest">测试</el-button></p>
</template>

<script setup lang="ts">
const $loading = inject('$loading') as any 
const onTest = () => {
      
      
  $loading.service({
      
      
    lock: true,
    text: 'Loading',
  })
}
</script>

Sehen Sie sich einige gängige Ts-Schreibmethoden an

Der Entwicklungsserver und der Packager tsführen nur Syntax-Escapes für die Dateien ohne Typprüfung durch und stellen so sicher, dass viteder Entwicklungsserver tsbei der Verwendung blitzschnell bleibt.

Hier sind einige gängige Beispiele:

<!-- 对props的类型声明和默认值 -->
<script setup lang="ts">
interface Props {
      
      
  msg?: string
  labels?: string[]
}
const props = withDefaults(defineProps<Props>(), {
      
      
   msg: 'hello',
   labels: () => ['one', 'two']
})
</script>


<!-- 另一种方式 -->
<script setup lang="ts">
interface Book {
      
      
  title?: string
  author: string
}
const props = defineProps({
      
      
  book: Object as PropType<Book>
})
</script>


<!-- 对emits进行声明 -->
<script setup lang="ts">
// 1.运行时
const emit = defineEmits(['change', 'update'])

// 2.基于类型
const emit = defineEmits<{
      
      
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()
</script>


<!-- 为computed指定返回类型 --->
<script setup lang="ts">
const double = computed<number>(() => {
      
       /** return number */ })
</script>

<!-- 为函数参数标注类型 -->
<script setup lang="ts">
const onClick = (e: Event) => {
      
      
  console.log((event.target as HTMLInputElement).value)
}
</script>

<!-- project和inject 然而我很少用到 -->
<script setup lang="ts">
import {
      
       provide, inject } from 'vue'
import type {
      
       InjectionKey } from 'vue'

const key = Symbol() as InjectionKey<string>
provide(key, 'foo')

const foo = inject<string>('foo', 'bar')
</script>

<!-- 为模板引用标注类型 -->
<script setup lang="ts">
import {
      
       ref, onMounted } from 'vue'
const el = ref<HTMLInputElement | null>(null)

onMounted(() => {
      
      
  el.value?.focus()
})
</script>

<template>
  <input ref="el" />
</template>


<!-- 为组件模板引用标注类型 -->
<script setup lang="ts">
import MyModal from './MyModal.vue'

const modal = ref<InstanceType<typeof MyModal> | null>(null)

const openModal = () => {
      
      
  modal.value?.open()
}
</script>

Statische Anmeldeseite

Komponenten werden auf der Anmeldeseite verwendet el-input. Im Allgemeinen ist die Häufigkeit der Postverwaltung für diese Art von Formularkomponente immer noch sehr hoch, sodass sie tendenziell neu verpackt und wiederverwendet wird. Im Allgemeinen werden die vorderen und hinteren Räume optimiert und zerlegt textarea:

<script lang='ts' setup>
import {
      
       computed } from 'vue';
const emits = defineEmits<{
      
      
  (e: 'update:value', value: string): void;
  (e: 'blur'): void;
  (e: 'focus'): void;
  (e: 'change', value: string): void;
  (e: 'clear'): void;
}>()

const props = defineProps({
      
      
  type: {
      
      
    type: String,
    default: 'string'
  },
  value: {
      
      
    type: [String, Number],
    default: '',
    required: true
  },
  maxlength: [String, Number],
  minlength: [String, Number],
  placeholder: {
      
      
    type: String,
    default: '请输入',
  },
  clearable: {
      
      
    type: Boolean,
    default: true
  },
  showPassword: {
      
      
    type: Boolean,
    default: false
  },
  disabled: {
      
      
    type: Boolean,
    default: false
  },
  prefixIcon: {
      
      
    type: String,
    default: ''
  },
  suffixIcon: {
      
      
    type: String,
    default: ''
  },
  inputStyle: [String, Object],
  showWordLimit: {
      
      
    type: Boolean,
    default: true
  },
  rows: Number,
  autosize: [Boolean, Object]
})

const input = computed({
      
      
  get(){
      
      
    console.log(props.value)
    return props.value;
  },

  set(val: any){
      
      
    if(typeof val === 'string') {
      
      
      val = val ? val.trim() : val;
    }
    emits('update:value', val);
  }
})

const onFocus = () => {
      
      
  emits('focus');
}

const onBlur = () => {
      
      
  emits('blur');
}

const onClear = () => {
      
      
  emits('clear');
}

const onChange = (val: string) => {
      
      
  emits('change', val);
}

</script>

<template>
  <el-input
    v-if="type === 'textarea'"
    v-model="input"
    :rows="rows"
    type="textarea"
    :placeholder="placeholder"
    :maxlength="maxlength"
    :minlength="minlength"
    :show-word-limit="showWordLimit"
    :disabled="disabled"
    :prefixIcon="prefixIcon"
    :suffixIcon="suffixIcon"
    :autosize="autosize"
    :inputStyle="inputStyle"
    @focus="onFocus"
    @blur="onBlur"
    @change="onChange"
  />
  <el-input 
    v-else
    v-model="input" 
    :type="type"
    :placeholder="placeholder"
    :maxlength="maxlength"
    :minlength="minlength"
    :clearable="clearable"
    :showPassword="showPassword"
    :disabled="disabled"
    :prefixIcon="prefixIcon"
    :suffixIcon="suffixIcon"
    :inputStyle="inputStyle"
    @focus="onFocus"
    @blur="onBlur"
    @clear="onClear"
    @change="onChange" />
    
</template>

<style scoped lang='scss'>

</style>

plugins/components.tsRegistrieren Sie sich weltweit unter

import type {
    
     Component } from 'vue'
import ArInput from '@/components/form/input/index.vue';

const componentObj: {
    
    [propName: string]: Component} = {
    
    
  ArInput
};

export default function install(app: any) {
    
    
  Object.keys(componentObj).forEach((key) => {
    
    
    app.component(key, componentObj[key])
  })
}

Denken Sie plugins/index.tsdaran, Folgendes hinzuzufügen:

import installComponents from './components'

export default (app: any) => {
    
     
  // ...

  // 注册自定义组件
  installComponents(app);
}

login.vueQuellcode:

<script lang="ts" setup>
import type {
      
       FormInstance, FormRules } from 'element-plus'
import {
      
       ref, reactive } from 'vue'


const formRef = ref<FormInstance>();
const form = reactive({
      
      
  name: '',
  password: ''
})
const rules = ref<FormRules>({
      
      
  name: [
    {
      
       required: true, message: '请输入账号', trigger: 'blur' },
    {
      
       min: 8, max: 12, message: '账号长度为8-12', trigger: 'blur' },
  ],
  password: [
  {
      
       required: true, message: '请输入密码', trigger: 'blur' },
  ]
})

const onSubmit = async() => {
      
      
  await formRef.value.validate((valid, fields) => {
      
      
    if (valid) {
      
      
      console.log('submit!')
    } else {
      
      
      console.log('error submit!', fields)
    }
  })
}

</script>

<template>
  <div class="login">
    <div class="login-inner">
      <el-form ref="formRef" :model="form" :rules="rules">
        <el-form-item label="账号" prop="name" required>
          <ArInput v-model:value="form.name" prefix-icon="User" />
        </el-form-item>
        <el-form-item label="密码" prop="password" required>
          <ArInput v-model:value="form.password" type="password" prefix-icon="Lock" />
        </el-form-item>
        <el-button type="primary" class="login-btn" @click="onSubmit">登录</el-button>
      </el-form>
    </div>
  </div>
</template>

<style lang="scss" scoped>
.login {
      
      
  width: 100vw;
  height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  background-color: #ccc;

  &-inner{
      
      
    margin-top: 20%;
    width: 300px;
    padding: 32px;
    background-color: #fff;
    border-radius: 4px;
  }

  &-btn {
      
      
    width: 100%;
  }
}
</style>

Der endgültige Seiteneffekt ist:

Fügen Sie hier eine Bildbeschreibung ein

Umgebungsvariable

Bevor Sie die Seitenübermittlungsaktion abschließen, lösen Sie zunächst das Problem der Umgebungsvariablen. Im Testdienst, in der Vorabversion oder in der Produktion haben wir immer einige unterschiedliche Variablen, daher müssen wir die Umgebung unterscheiden

Erstellen Sie einen neuen envOrdner und fügen Sie drei oder vier Dateien hinzu:

.env                # 所有情况下都会加载
.env.development     # 开发环境
.env.release         # 预发布环境
.env.production      # 正服环境

Zum Beispiel:

# .env.development 
VITE_ENV = devalopment
# 请求接口
VITE_API_URL = https://api.vvhan.com/testapi/saorao

Auf diese Weise können Variablen in verschiedenen Umgebungen festgelegt werden. Anschließend ändern wir den Skriptbefehl, um die Umgebung zu unterscheiden:

"scripts": {
    
    
    "watch": "vite",
    "watch:release": "vite --mode release",
    "watch:production": "vite --mode production",
    "build:development": "vue-tsc && vite build --mode development",
    "build:release": "vue-tsc && vite build --mode release",
    "build:production": "vue-tsc && vite build --mode production",
    // ...
  },

Erstellen Sie eine neue Datei im Stammverzeichnis build/utils.ts:

// Read all environment variable configuration files to process.env
export function wrapperEnv(envConf: Recordable) {
    
    
  const result: any = {
    
    }

  for (const envName of Object.keys(envConf)) {
    
    
    let realName = envConf[envName].replace(/\\n/g, '\n')
    realName =
      realName === 'true' ? true : realName === 'false' ? false : realName

    result[envName] = realName
    if (typeof realName === 'string') {
    
    
      process.env[envName] = realName
    } else if (typeof realName === 'object') {
    
    
      process.env[envName] = JSON.stringify(realName)
    }
  }
  return result
}

Ändern Sie dann vite.config.jsdie von uns definierten Variablen und fügen Sie sie ein:

import {
    
     defineConfig, loadEnv, UserConfig, ConfigEnv  } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import {
    
     ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import {
    
     wrapperEnv } from './build/utils'

const resolve = (dist) => path.resolve(__dirname, dist)

// https://vitejs.dev/config/
export default ({
    
     command, mode }: ConfigEnv): UserConfig => {
    
    
  const env = loadEnv(mode, './env')
  wrapperEnv(env)

  return {
    
    
    plugins: [
      vue(),
      AutoImport({
    
    
        imports: [
          'vue',
          'vue-router',
          'pinia'
        ],
        eslintrc: {
    
    
          enabled: false,
          filepath: './.eslintrc-auto-import.json',
          globalsPropValue: true
        },
        resolvers: [ElementPlusResolver()]
      }),
      Components({
    
    
        resolvers: [ElementPlusResolver()]
      })
    ],
    resolve: {
    
    
      alias: {
    
    
        '@': resolve('./src')
      },
      extensions: [".js", ".ts", ".tsx", ".jsx"]
    },
    css: {
    
    
      preprocessorOptions: {
    
    
        scss: {
    
    
          additionalData: '@import "@/assets/styles/variable.scss";@import "@/assets/styles/mixins.scss";'
        }
      }
    }
  }
}

Auf diese Weise können wir axioses im folgenden Paket verwenden. Oh ja, es könnte ein kleiner TS-Fehler vorliegen, ändern Sie tsconfig.node.json:

"include": ["vite.config.ts", "build**/*.ts"]

Axios-Paket

Neu src/utils/request.ts:

import axios, {
    
     AxiosInstance, InternalAxiosRequestConfig } from 'axios'
import {
    
     ElMessage } from 'element-plus'

const request: AxiosInstance | any = axios.create({
    
    
  timeout: 100000,
  headers: {
    
    
    post: {
    
    
      'Content-Type': 'application/x-www-form-urlencoded'
    }
  },
  withCredentials: true
});

request.interceptors.request.use((config: InternalAxiosRequestConfig) => {
    
    
  let token = localStorage.getItem("token");
  if (token && token !== '') {
    
    
    config.headers['Authorization'] = token;
  }

  // 获取环境变量!!!
  const projectUrlPrefix = import.meta.env.VITE_API_URL;
  // 这样更支持多域名接口的情况
  if (config && config.url && !/^(http(|s):\/\/)|^\/\//.test(config.url)) {
    
    
    config.url = projectUrlPrefix + config.url;
  }
  return config;
});

request.interceptors.response.use((res: any) => {
    
    
  if (res.data.status && res.data.status !== 200) {
    
    
    ElMessage.error(res.data.msg || '请求失败,请稍后重试')
    return Promise.reject(res.data)
  }
  // 如果这里是登录信息过期,那么应该给个弹窗提示什么的,最后都应该重定向到登录页面
  return res.data
}, (error: any) => {
    
    
  console.log(`%c 接口异常 `, 'background-color:orange;color: #FFF;border-radius: 4px;', error);
})

export default request;
export const $get = (url: string, params = {
    
    }) => {
    
    
  return request.get(url, {
    
    
    params
  })
}
export const $post = (url: string, params = {
    
    }) => {
    
    
  return request.post(url, params)
}

Gehen Sie zur Anmeldeseite und machen Sie einen Test:

<script lang="ts" setup>
const $post = inject('$post') as any
// ...

const onSubmit = async () => {
  // ...
  const res = await $post('/login', form)
  // ...
}
</script >

Testen Sie die Domänennamenpräfixe in verschiedenen Umgebungen. Wenn sie unterschiedlich sind, ist die Konfiguration erfolgreich ~

https://api.vvhan.com/testapi/saorao/login
https://api.vvhan.com/releaseapi/saorao/login
https://api.vvhan.com/api/saorao/login

Gesamtanordnung

Fügen Sie hier eine Bildbeschreibung ein

  • Konvertieren Sie den Pfad im Routing-Modul in das links angezeigte Menü.
  • Der obige Teil enthält Benutzerinformationen und kann beendet werden.
  • Beim Wechseln der Routen wird eine Registerkarte ähnlich einem Browser angezeigt. Sie können auf die Registerkarte klicken, um zu wechseln, oder die aktuelle Seite schließen.
  • Abschließend wird der Hauptinhalt der Seite angezeigt.

Schauen Sie sich zunächst die Menükomponente an:

<script lang='ts' setup>
import {
    
     ref, computed, watch } from 'vue';
import {
    
     useRoute, useRouter } from 'vue-router'
import {
    
     storeToRefs } from 'pinia'
import routes from '@/router/index';
import {
    
     IRouterItem } from '@/types/menu'
import {
    
     useTagViewsStore } from '@/store/tagViews';

const route = useRoute();
const router = useRouter();
const isCollapse = ref(false);
const defaultActive = ref('0')
const tagViewsStore = useTagViewsStore();
const {
    
     visitedViews } = storeToRefs(tagViewsStore);

// 1. 从声明的路由中获取当前显示的菜单栏(还可以过滤一些不是菜单栏的页面)
const currentMenu = computed(() => {
    
    
  if(routes && routes.length) {
    
    
    const routesArr: any = routes.filter((route) => route.name === 'Layout');
    if(routesArr && routesArr[0] && routesArr[0].children){
    
    
      const res = routesArr[0].children as IRouterItem[];
      return res;
    }else {
    
    
      return [];
    }
  }else {
    
    
    return []
  }
});

// 获取路由对应的菜单下标(如果一打开是客户列表页,则高亮客户列表)
const currentMenuToObj = computed(() => {
    
    
  const routes = currentMenu.value;
  if(routes && routes.length) {
    
    
    let obj: {
    
    [key: string]: any} = {
    
    };
    for(let i = 0; i < routes.length; i++) {
    
    
      const item = routes[i];
      if(item.children) {
    
    
        for(let j = 0; j< item.children.length; j++) {
    
    
          const subItem = item.children[j];
          obj[subItem.path] = {
    
    
            index: `${
      
      i}-${
      
      j}`,
            item: subItem
          };
        }
      }else {
    
    
        obj[item.path] = {
    
    
          index: '' + i,
          item
        }; 
      }
    }

    return obj;
  }else {
    
    
    return {
    
    };
  }
})

// 监听路由获取当前高亮的值,store的使用在后面详细说一下
watch(
  () => route.path,
  (val: string) => {
    
    
    if(!visitedViews.value.length) {
    
    
      const item = {
    
    
        path: '/',
        name: 'Index',
        meta: {
    
     title: '主页' },
      }
      tagViewsStore.addVisitedViews(item)
      tagViewsStore.setActivitedView(item)
    }
    if(val) {
    
    
      const obj = currentMenuToObj.value[val];
      defaultActive.value = obj.index;
      tagViewsStore.addVisitedViews(obj.item)
      tagViewsStore.setActivitedView(obj.item)
    }
  }, {
    
    
    immediate: true
  }
)

// 点击菜单栏进行跳转
const onToPage = (item: IRouterItem) => {
    
    
  if(route.path === item.path) return;
  tagViewsStore.addVisitedViews(item)
  tagViewsStore.setActivitedView(item)
  router.push(item.path)
}

</script>

<template>
  <div class="menu">
    <div class="menu-logo">Logo</div>
    <div class="menu-main">
      <el-menu
        :default-active="defaultActive"
        :collapse="isCollapse"
        background-color="#191919"
        text-color="#7e7e7e"
        active-text-color="#ffffff"
      >
        <template v-for="(item, index) in currentMenu" :key="index">
          <el-menu-item :index="'' + index" v-if="!item.children || !item.children.length" @click="onToPage(item)">
            <template #title>{
    
    {
    
     item.meta.title  }}</template>
          </el-menu-item>
          <el-sub-menu :index="'' + index" v-else>
            <template #title>
              <span>{
    
    {
    
     item.meta.title  }}</span>
            </template>
            <el-menu-item v-for="(subItem, subIndex) in item.children" :key="`${index}-${subIndex}`" :index="`${index}-${subIndex}`" @click="onToPage(subItem)">{
    
    {
    
     subItem.meta.title  }}</el-menu-item>
          </el-sub-menu>
        </template>
      </el-menu>
    </div>
  </div>
</template>

<style scoped lang='scss'>
.menu {
    
    
  height: 100vh;
  max-width: 280px;
  background-color: #191919;
  
  &-logo {
    
    
    height: 60px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
  }

  &-main {
    
    
    height: calc(100vh - 100px);
  }
}
</style>

<style lang="scss">
.menu .el-menu {
    
    
  border: none !important;
}
</style>

Als nächstes folgen die Informationen in der oberen Leiste:

<script lang='ts' setup>

</script>

<template>
  <div class="nav">
    <div class="nav-left"></div>
    <div class="nav-right">
      <div class="nav-right-item">
        <el-dropdown>
          <span class="el-dropdown-link">
            您好,XXX
          </span>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item>退出登录</el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
    </div>
  </div>
</template>

<style scoped lang='scss'>
.nav {
      
      
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 16px;
  height: 60px;
  border-bottom: solid 1px var(--el-menu-border-color);
  background-color: #fff;

  &-right{
      
      
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: flex-end;

    &-item {
      
      
      cursor: pointer;
    }
  }
}
</style>

Als nächstes folgt die Entwicklung der Navigationsleiste tab. Hier verwenden wir Pinia für die Statusverwaltung. Nach der Installation plugins/index.tsregistrieren wir uns zunächst in:

import {
    
     createPinia } from 'pinia';

// ...
// 注册store (建议这个放在所有注册的首位,方便其他插件可能会用到它)
app.use(createPinia());

Stellungnahme zur Navigation tab, storein src/store/tagViews.ts:

import {
    
     defineStore } from 'pinia';
import {
    
     IRouterItem } from '@/types/menu'

export const useTagViewsStore = defineStore('tagViews', {
    
    
  state: () => {
    
    
    return {
    
    
      // 访问过的页面
      visitedViews: [] as IRouterItem[],
      // 当前访问的页面
      activitedView: {
    
    } as IRouterItem
    }
  },

  actions: {
    
    
    // 新增页面
    addVisitedViews(view: IRouterItem){
    
    
      const item = this.visitedViews.find((item) => item.path === view.path)
      if(item) return;
      this.visitedViews.push(view);
    },
    
    // 删除页面
    deleteVisitedViews(index: number) {
    
    
      this.visitedViews.splice(index, 1);
    },
    
    // 高亮某个页面
    setActivitedView(view: IRouterItem) {
    
    
      this.activitedView = view
    }
  }
})

tagViewsDer Quellcode der Komponente lautet wie folgt:

<script lang='ts' setup>
import {
      
       storeToRefs } from 'pinia';
import {
      
       useRouter } from 'vue-router'
import {
      
       useTagViewsStore } from '@/store/tagViews';
import {
      
       IRouterItem } from '@/types/menu'

const router = useRouter();
const tagViewsStore = useTagViewsStore();
const {
      
       visitedViews, activitedView } = storeToRefs(tagViewsStore);

// 关闭页面
const onDel = (item: IRouterItem) => {
      
      
  const index = visitedViews.value.findIndex((view) => view.path === item.path);
  if(index === -1) return;

  tagViewsStore.deleteVisitedViews(index);
  if(item.path === activitedView.value.path) {
      
      
    const obj = visitedViews.value[index - 1]
    tagViewsStore.setActivitedView(obj);
    router.push(obj.path)
  }
}

// 切换页面
const onChange = (item: IRouterItem) => {
      
      
  tagViewsStore.setActivitedView(item)
  router.push(item.path)
}

</script>

<template>
  <el-scrollbar class="tags-scrollbar">
    <div class="tags">
      <div v-for="item in visitedViews" :key="item.path" :class="['tags-item', { active: activitedView.path === item.path }]" @click="onChange(item)" >
        <span class="tags-item-title">{
   
   { item.meta ? item.meta.title : '' }}</span>
        <el-icon v-if="item.path !== '/'" @click.stop="onDel(item)"><Close /></el-icon>
      </div>
    </div>
  </el-scrollbar>
</template>

<style scoped lang='scss'>
.tags-scrollbar {
      
      
  height: 30px;
  overflow: hidden;
}
.tags {
      
      
  display: flex;
  background: #f3f3f3;
  border: 1px solid #f2f2f2;
  border-right: none;
  margin: -1px 0 0 -1px;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  &-item {
      
      
    display: flex;
    align-items: center;
    position: relative;
    cursor: pointer;
    height: 26px;
    line-height: 26px;
    border: 1px solid #d8dce5;
    color: #495060;
    background: #fff;
    padding: 0 8px;
    font-size: 12px;
    margin-left: 5px;
    margin-top: 4px;

    &-title {
      
      
      margin-right: 4px;
    }

    &:first-of-type {
      
      
      margin-left: 5px;
    }
    &:last-of-type {
      
      
      margin-right: 5px;
    }
    &.active {
      
      
      background-color: #e25050;
      color: #fff;
      border-color: #e25050;
      &::before {
      
      
        content: '';
        background: #fff;
        display: inline-block;
        width: 8px;
        height: 8px;
        border-radius: 50%;
        position: relative;
        margin-right: 2px;
      }
    }
  }
}
</style>

piniaDas Nutzungsverhältnis vuexist viel einfacher, der größte Unterschied besteht darin, dass * mutations* nicht mehr existiert.

Routenabfangen

Im Allgemeinen aktualisieren wir nach der Anmeldung piniadie Informationen über den Benutzer auf der Website und einige Informationen werden verschlüsselt und auf der Website gespeichert localstorage. Für Benutzer, die nicht angemeldet sind, müssen wir ihren Zugriff auf das System abfangen und auf die Anmeldeseite umleiten. (In tatsächlichen Projekten muss auch das Abfangen von Seitenberechtigungen berücksichtigt werden.)

// router/hooks/index.ts
import type {
    
     Router } from 'vue-router'
import {
    
     USERINFO } from '@/constants/localstorage'

const routerHook = (router: Router) => {
    
    
  router.beforeEach(to => {
    
    
    if(to.path === '/login') {
    
    
      // 可以做一些清空登录信息的操作, 比如跟pinia相关的等操作
      localStorage.removeItem(USERINFO);
      return true;
    }else{
    
    
      // 在这里可以判断用户是否登录,跳转的某个页面是否有权限,这里只是粗略写一下
      const info = localStorage.getItem(USERINFO);
      if(!info) {
    
    
        return {
    
     name: 'Login' }
      }
    } 
  })
}

export default routerHook;


import {
    
     createRouter, createWebHashHistory } from 'vue-router'
import routes from './routes'
import routerHook from './hooks/index'

// 注册路由
const router = createRouter({
    
    
  history: createWebHashHistory(),
  routes
})

routerHook(router)

export default router

Ändern Sie die Anmeldeseite und simulieren Sie sie:

const onSubmit = async () => {
    
    
  await formRef.value.validate(async (valid: boolean, fields: {
     
     [key: string]: any}) => {
    
    
    if (valid) {
    
    
      // 一般这种情况下,localStorage中存储的信息不能太重要,且需要加密,还应该更新pinia中的用户信息
      const info = {
    
    
        name: 'Armouy'
      }
      localStorage.setItem(USERINFO, JSON.stringify(info))
      router.push('/')
    } else {
    
    
      console.log('error submit!', fields)
    }
  })
}

Wenn Sie nicht angemeldet sind, werden Sie zur Anmeldeseite weitergeleitet.

Pack

npm run build:production
npm run preview

Referenzlink


Wenn es Fehler gibt, weisen Sie sie bitte darauf hin. Vielen Dank fürs Lesen ~

Ich denke du magst

Origin blog.csdn.net/qq_34086980/article/details/131371511
Empfohlen
Rangfolge