vue3学习

距离vue2学习已经一年度了,现在开始vue3的学习。

一、webpack

(1)创建列表隔行变色项目及webpack使用:

新建项目空白目录,并运行npm init -y命令,初始化包管理配置文件package.json;

新建src源代码目录,新建src -> index.html首页和src -> index.js脚本文件,初始化首页基本的结构

运行npm install jquery -s命令,安装jQuery,通过ES6模块化的方式导入jQuery,实现列表隔行变色效果。其中ul-li快速创建:ul>li{这是第$个li}*9回车。

webpack可以帮助用户将兼容性代码转为非兼容性代码

在代码文件根目录执行命令:

bash 复制代码
cnpm i webpack-dev-server webpack-cli webpack -D

(2)webpack使用

①在项目根目录创建名为webpack.config.js的webpack配置文件,并初始化如下的配置文件:

javascript 复制代码
module.exports = {
  mode: 'development' // mode 用来制定构建模式,可选值有development 和 production
}

②在package.json的scripts节点下,添加dev脚本如下:

XML 复制代码
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack" 
  },

③在终端中运行npm run dev命令,启动webpack进行项目的打包构建。会生成dist文件夹(里面是main.js),index.html引入的js替换为../dist/main.js,这样html就可以正常运行了。

(3)webpack.config.js 文件的作用

webpack.config.js 是 webpack的配置文件。webpack在真正开始打包构建之前,会先读取这个配置文件,从而基于给定的配置,对项目进行打包。

注意:由于webpack是基于node.js开发出来的打包工具,因此在它的配置文件中,支持使用node.js 相关的语法和模块进行webpack的个性化配置。

(4)webpack中的默认约定

》在webpack中有如下的默认约定:

①默认的打包入口文件为src -> index.js ②默认的输出文件路径为dist -> main.js

注意:可以在webpack.config.js中修改打包的默认约定

》自定义打包的入口与出口

在webpack.config.js配置文件中,通过entry节点指定打包的入口。通过output节点指定打包的出口。示例代码如下:

javascript 复制代码
const path = require('path')
module.exports = {
  mode: 'development', // mode 用来制定构建模式,可选值有development 和 production
  entry: path.join(__dirname, './src/index.js'), // 打包入口文件的路径
  output: {
    path: path.join(__dirname, './dist'), //输出文件的存放路径
    filename: 'bundle.js' // 输出文件的名称
  }
}

(5)webpack插件的作用

通过安装和配置第三方的插件,可以拓展webpack的能力,从而让webpack用起来更方便。最常用的webpack插件有如下两个:

①webpack-dev-server

●类似于node.js阶段用到的nodemon工具

●每当修改了 源代码, webpack会自动进行项目的打包和构建,即package.json的scripts节点的dev值改为"webpack serve"

②html-webpack-plugin

●webpack 中的HTML插件( 类似于于一个模板引擎插件)

●可以通过此插件自定制index.html页面的内容

bash 复制代码
cnpm i html-webpack-plugin -D

配置方式:

效果如下:

javascript 复制代码
const path = require('path')
// 导入插件,得到构造函数
const HtmlPlugin = require('html-webpack-plugin')
// 创建插件的实例对象
const htmlPlugin = new HtmlPlugin({
  template: './src/index.html',
  filename: './index.html'
})

module.exports = {
  mode: 'development', // mode 用来制定构建模式,可选值有development 和 production
  entry: path.join(__dirname, './src/index.js'), // 打包入口文件的路径
  output: {
    path: path.join(__dirname, './dist'), //输出文件的存放路径
    filename: 'bundle.js' // 输出文件的名称
  },
  plugins: [htmlPlugin] // 挂载插件的实例对象
}

注意:通过HTML插件复制到项目根目录中的index.html页面,也被放到了内存中;HTML插件在生成的index.html页面的底部,自动注入了打包的bundle.js 文件。

(6)devServer节点

在webpack.config.js配置文件中,可以通过devServer节点对webpack-dev-server插件进行更多的配置,open(初次打包之后,直接打开浏览器)、host(实时打包所用的主机地址)、port(d端口号)

(7)loader

在实际开发过程中,webpack默认只能打包处理以.js后缀名结尾的模块。其他非.js后缀名结尾的模块,webpack 默认处理不了,需要调用loader加载器才可以正常打包,否则会报错!

7.1 处理css的loader

在index.js里面导入css:import './css/index/css'

①:npm i style-loader css-loader -D,安装css的loader;②在webpack.config.js的module->rules数组里面添加规则,其中test表示匹配的文件烈性,use表示要调用的loader,loader顺序不能错。

javascript 复制代码
module: {
    rules: [{ test: /\.css$/, use: ['style-loader', 'css-loader'] }]
  }

7.2 处理less的loader

①:运行npm i less-loader less -D命令;②在webpack.config.js的moudle->rules数组添加规则:

bash 复制代码
rules: [
      { test: /\.css$/, use: ['style-loader', 'css-loader'] },
      { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }
    ]

其中安装的less是less-loader的内置依赖项。

7.3 打包处理样式表中与url相关的文件

在webpack5中url-loader、file-loader已经弃用。

二、vue3简介

1. vue3的优势

更容易维护:组合式API,更好的TypeScript支持;更快的速度:重写diff算法,模版变异优化,更高效的组件初始化;更小的提价:良好的TreeShaking,按需引入;更优的数据响应式:Proxy

2. 认识create-vue

creat-vue是Vue官方新的脚手架工具,底层切换到了vite(下一代构建工具)。

前提环境条件:node 版本高于16.0。创建一个vue应用:npm init vue@lastest,这一指令将会安装并执行Create-vue。安装慢的话需要用到:npm 使用快速的安装源(nrm)npm 使用快速的安装源(nrm) - 编程教程

按照绿字的提示输入命令,这样就可以打开一个vue3的项目了。使用vscode打开该项目,查看项目结构和目录。

vue2项目的main.js使用new vue()创建一个应用实例,vue3使用createApp() / createRouter() createStore(),将创建实例进行了封装,保证每个实例的独立封闭性,mount设置挂载点,#app(id魏app的盒子,即index.html里面id为app的div)。vue3需要vscode安装volar插件,具体如下:

3. 组合式api

3.1 setup

3.1.1、setup选项的执行时机:beforeCreate钩子之前自动执行

3.1.2.、setup写代码的特点:定义数据+函数然后以对象方式return

3.1.3、<script setup>解决了什么问题?经过语法糖的封装更简单的使用组合式API

3.1.4、setup中的this还指向组件实例吗?指向undefined

3.2 reactive和ref函数

推荐:声明数据,统一使用ref。

3.2.1 reactive()

作用:接受对象函数类型数据的参数传入并返回一个响应式的对象

步骤:从vue包导入reactive函数;在<script setup>中执行reactive函数并传入类型为对象的初始值,并使变量接收返回的值。

3.2.2 ref()

作用:接受简单类型或复杂类型,返回一个响应式的对象,本质上是在原有传入数据的基础上,外层包了一层对象,包成了复杂类型。

注意点:script访问数据,需要通过.value;template里面不需要.value

3.3、computed计算属性函数

计算属性基本思想和Vue2的完全一致,组合式API下的计算属性只是修改了写法。

核心步骤:1.导入computed函数;2.执行函数在回调参数中return基于响应式数据做计算的值,用变量接收。

注意:(1)计算属性中不应该有"副作用",比如异步请求/修改dom;(2)避免直接修改计算属性的值,计算属性应该是只读的,特殊情况可以配置get set,参见响应式 API:核心 | Vue.js

3.4、watch

3.4.1 侦听单个数据

(1)导入watch函数;(2)执行watch函数传入要侦听的响应式数据(ref对象 )和回调函数。

3.4.2 侦听多个数据

同时侦听多个响应式数据的变化,不管哪个数据变化都要触发回调。

3.4.3 immediate

说明:在侦听器创建时立即触发回调,响应式数据变化之后继续执行回调。

3.4.4 deep:true

说明:深度监视,默认watch进行的是浅层监视;const ref1 = ref(简单类型)可以直接监视;const ref2 = ref(复杂类型)监视不到复杂类型内部数据的变化。

如果不开启deep的前提,要精确侦听对象的某个属性,如下写法:

3.4.5 总结

(1)作为watch函数的第一个参数,ref对象要添加.value吗?不要,第一个参数就是传ref 对象;

(2)watch只能侦听单个数据吗?单个或者多个;

(3)不开启deep,直接监视复杂类型,修改属性能触发回调吗?不能,默认是浅层侦听。

(4)不开启deep,精确侦听对象的某个属性?可以把第一个参数写成函数的写法,返回要监听的具体属性。

3.5、生命周期函数

比如:

3.6、父子通信

3.6.1 父传子

基本思想:父组件给子组件绑定属性,子组件内部通过props选项接收。

子组件由于写了setup,所以无法直接配置 props选项,所以此处需要借助于"编译器宏"函数接收子组件传递的数据;对于props传递过来的数据,模板中可以直接使用。

3.6.2 子传父

基本思想:父组件中给子组件标签通过@绑定事件;子组件内部通过emit方法触发事件。

3.6.3 总结
3.6.3.1 父传子

(1)父传子的过程中通过什么方式接收props?

defineProps({属性名:类型})

(2)setup语法糖中如何使用父组件传过来的数据?

const props = defineProps({属性名:类型}) props.xxx

3.6.3.2 子传父

(1)子传父的过程中通过什么方式得到emit方法?

defineEmits(['事件名称])

(2)怎么触发事件

emit('自定义事件名',参数)

3.7、模板引用

通过ref标识获取真实的dom对象或者组件实例对象。

通过ref函数生成一个ref对象,名称要和模版元素ref一致;通过ref标识绑定ref对象到标签。

默认情况下在<script setup>语法糖下组件内部的属性和方法是不开放给父组件访问的,可以通过defineExpose编译宏指定哪些属性和方法允许访问。

总结:

获取模板引用的时机是什么?组件挂载完毕

defineExpose编译宏的作用是什么?显式暴露组件内部的属性和方法

3.8、provide和inject

跨层传递普通数据:1.顶层组件通过provide函数提供数据;2.底层组件通过inject函数获取数据。

跨层传递响应式数据:在调用provide函数时,第二个参数设置为ref对象。

跨层传递方法:顶层组件可以向底层组件传递方法,底层组件调用方法修改顶层组件中的数据

4、vue3.3新特性

背景说明:

有<script setup>之前,如果要定义props,emits 可以轻而易举地添加一个与setup平级的属性。但是用了<script setup>后,就没法这么干了setup属性已经没有了,自然无法添加与其平级的属性。

为了解决这一问题,引入了defineProps 与defineEmits 这两个宏。但这只解决了props与emits这两个属性。如果我们要定义组件的name或其他自定义的属性,还是得回到最原始的用法------再添加一个普通的<script>标签。这样就会存在两个<script>标签。让人无法接受。

所以在Vue 3.3中新引入了defineOptions宏。顾名思义,主要是用来定义Options API的选项。可以用defineOptions定义任意的选项,props, emits, expose, slots 除外(因为这些可以使用defineXXX来做到)

4.1、defineOptions

4.2、defineModel

在Vue3中,自定义组件上使用v-model,相当于传递一个modelvalue属性,同时触发 update:modelvalue 事件。我们需要先定义props,再定义emits。其中有许多重复的代码。如果需要修改此值,还需要手动调用emit 函数。

如果使用defineModel,代码如下,需要修改vite.config.js,开启defineModel,重启:

5、Pinia

Pinia使Vue3的官方状态管理工具,是Vuex的替代品。

(1)提供更加简单的API(去掉了mutation )

(2)提供符合,组合式风格的API(和Vue3新语法统一)

(3)去掉了modules的概念,每一个store都是一个独立的模块

(4)配合TypeScript更加友好,提供可靠的类型推断

5.1、使用

按照官方文档安装pinia到项目。Pinia | Pinia

5.1.1 开始
javascript 复制代码
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia).mount('#app')
5.1.2 定义store
javascript 复制代码
// @/store/counter.js
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)

  const increment = () => {
    count.value++
  }
  const decrement = () => {
    count.value--
  }

  const double = computed(() => count.value * 2)
  const msg = ref('hello pinia')
  return { count, msg, double, increment, decrement }
})
javascript 复制代码
// @/store/channels.js 支持异步
import { defineStore } from 'pinia'
import { ref } from 'vue'
import axios from 'axios'

export const useChannelStore = defineStore('channel', () => {
  // 声明数据
  const channelList = ref([])
  // 声明操作数据的方法
  const getList = async () => {
    const {
      data: { data }
    } = await axios.get('http://geek.itheima.net/V1_0/channels')
    channelList.value = data.channels
    console.log(data.channels)
  }
  // 声明getters相关

  return { channelList, getList }
})
5.1.3 使用store
html 复制代码
<!-- App.vue,子组件可忽略 -->
<script setup>
import Son1 from '@/components/SonView1.vue'
import Son2 from '@/components/SonView2.vue'

import { useCounterStore } from '@/store/counter'
const counterStore = useCounterStore()
import { useChannelStore } from '@/store/channel.js'
const channelStore = useChannelStore()
</script>

<template>
  <div>
    <h3>APP.vue根组件 -{{ counterStore.count }} -{{ counterStore.msg }} - {{ counterStore.double }}</h3>
    <Son1 />
    <Son2 />
    <hr />

    <button type="button" @click="channelStore.getList">channel</button>
    <ul>
      <li v-for="item in channelStore.channelList" :key="item.id">{{ item.name }}</li>
    </ul>
  </div>
</template>

<style scoped></style>

另一种写法, 为了从 store 中提取属性时保持其响应性,你需要使用 storeToRefs()。它将为每一个响应式属性创建引用。当你只使用 store 的状态而不调用任何 action 时,它会非常有用。请注意,你可以直接从 store 中解构 action,因为它们也被绑定到 store 上。

javascript 复制代码
// 官网代码
<script setup>
import { storeToRefs } from 'pinia'
const store = useCounterStore()
// `name` 和 `doubleCount` 是响应式的 ref
// 同时通过插件添加的属性也会被提取为 ref
// 并且会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性
const { name, doubleCount } = storeToRefs(store)
// 作为 action 的 increment 可以直接解构
const { increment } = store
</script>
5.1.4 pinia持久化插件

官方文档:快速开始 | pinia-plugin-persistedstate

(1)安装插件:npm i pinia-plugin-persistedstate

(2)main.js使用:

javascript 复制代码
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia.use(persist)).mount('#app')

(3)store仓库中,pereist:true开启

5.1.5 总结

(1)Pinia是用来做什么的?新一代的状态管理工具,替代vuex

(2)Pinia中还需要mutation吗?不需要,action既支持同步也支持异步

(3)Pinia如何实现getter?computed计算属性函数

(4)Pinia产生的Store如何解构赋值数据保持响应式?storeToRefs

(5)Pinia 如何快速实现持久化?pinia-plugin-persistedstate

6. 项目:大事件管理系统

简介:使用vue3 compositionAPI,Pinia/Pinia持久化处理,Element Plus(表单校验、表格处理、组件封装),pnpm包管理升级,Eslint+prettier更规范的配置,husky(Git hooks工具)代码提交之前进行校验,球球模块设置,VueRouter4路由设计,AI大模型开发一整个项目模块(掌握最新的开发方式)

6.1 pnpm包管理器

一些优势:比同类工具快2倍左右、节省磁盘空间... https://www.pnpm.cn/

安装方式::npm install -g pnpm 创建项目:pnpm create vue。注意不要在磁盘根目录创建项目,否则可能报权限不足。

6.2 Eslint配置代码风格

配置文件.eslintrc.cjs

(1)prettier风格配置:https://pretties.io。禁用prettier code format插件,安装Eslint插件, 修改.eslintrc.cjs:

javascript 复制代码
module.exports = {
 ...
  rules: {
    // 禁用格式化插件,prettier format on save,
    // 安装Eslint插件,并配置保存时自动修复。
    // prettier关注代码美观度
    'prettier/prettier': [
      'warn',
      {
        singleQuote: true, // 单引号
        semi: false, // 无分号
        printWidth: 80, // 每行宽度至多80字符
        trailingComma: 'none', // 不加对象|数组最后逗号
        endOfLine: 'auto' // 换行符号不限制(win mac 不一致)
      }
    ],
    // eslint关注规范
    'vue/multi-word-component-names': [
      'warn',
      {
        ignores: ['index'] // vue组件名称多单词组成(忽略index.vue)
      }
    ],
    'vue/no-setup-props-destructure': ['off'], // 关闭 props 解构的校验
    // 💡 添加未定义变量错误提示,create-vue@3.6.3 关闭,这里加上是为了支持下一个章节演示。
    'no-undef': 'error'
  }
}

(2)修改vscode的setting.json:

javascript 复制代码
...
"editor.formatOnSave": false, // #值设置为true时,每次保存的时候自动格式化;
"editor.codeActionsOnSave": {
   "source.fixAll": true
},
...

6.3 提交前做代码检查

6.3.1 husky

husky是一个git hooks工具( git的钩子工具,可以在特定时机执行特定的命令)

(1)初始化git仓库,执行git init即可。

(2)初始化husky工具配置,执行pnpm dlx husky-init && pnpm install 即可、https://typicode.github.io/husky/

(3)修改.husky/pre-commit文件。

javascript 复制代码
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

# npm test
pnpm lint

pnpm lint检验的是全部代码。

6.3.2 暂存区eslint校验
  1. 安装lint-staged包:pnpm i lint-staged -D

  2. package.json配置lint-staged命令

javascript 复制代码
```jsx
{
  // ... 省略 ...
  "lint-staged": {
    "*.{js,ts,vue}": [
      "eslint --fix"
    ]
  }
}
{
  "scripts": {
    // ... 省略 ...
    "lint-staged": "lint-staged"
  }
}
```
  1. .husky/pre-commit文件修改
javascript 复制代码
```jsx
pnpm lint-staged
```

6.4 目录调整

默认生成的目录结构不满足我们的开发需求,所以这里需要做一些自定义改动。主要是以下工作:

1.删除一些初始化的默认文件;主要是components、assets、router/index.js

2.修改剩余代码内容;主要是App.vue

3.新增调整我们需要的目录结构;比如api文件夹

4.拷贝全局样式和图片,安装预处理器支持:pnpm add sass -D

6.5 Vue-Router4 路由代码解析

6.5.1 路由初始化

以上为Vue2 => Vue3的router变化

(1)创建路由实例由createRouter 实现

(2)路由模式:history模式使用createwebHistory();hash模式使用createWebHashHistory();参数是基础路径,默认/

javascript 复制代码
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

// createRouter创建路由实例
// 配置history模式
// 1. history模式:createwebHistory:地址栏不带#
// 2. hash模式: createwebHashHistory:地址栏带#
// vite环境变量:import.meta.env.BASE_URL
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: []
})

export default router

修改import.meta.env.BASE_URL需要在vite.config.js添加base参数,参看:环境变量和模式 | Vite 官方中文文档

javascript 复制代码
<script setup>
import { useRoute, useRouter } from 'vue-router'
const router = useRouter()
const route = useRoute()

const goList = () => {
  router.push('/list')
  console.log(router, route)
}
</script>

<template>
  <div>APP</div>
  <button @click="$router.push('/home')">跳首页</button>
  <button @click="goList">跳列表页</button>
</template>

<style scoped></style>

一种是直接在html里面使用$router.push(),一种是在script里面用useRouter函数。

6.6 按需引入Element Plus

6.6.1 官网地址

设计 | Element Plus

6.6.2 步骤
javascript 复制代码
// 1. 安装
pnpm install element-plus

// 2. 插件
pnpm add -D unplugin-vue-components unplugin-auto-import

// 3.修改vite.config.js的引入项以及plugins参数
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      resolvers: [ElementPlusResolver()]
    }),
    Components({
      resolvers: [ElementPlusResolver()]
    })
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

// 4. 重启服务,button直接改成el-button即可,无须导入。
 <el-button @click="$router.push('/home')">跳首页</el-button>
 <el-button @click="goList">跳列表页</el-button>

默认components 下的文件也会被自动注册。

6.7 pinia构建用户仓库和持久化

6.7.1 示例目标:
6.7.2 步骤

(1)新建stores仓库

javascript 复制代码
// stores/user.js
import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useUserStore = defineStore(
  'big-user',
  () => {
    const token = ref('')
    const setToken = (newToken) => {
      token.value = newToken
    }
    const removeToken = () => {
      token.value = ''
    }

    return {
      token,
      setToken,
      removeToken
    }
  },
  {
    persist: true // 持久化
  }
)

(2)安装持久化插件

javascript 复制代码
pnpm add pinia-plugin-persistedstate -D

(3)使用main.js

```jsx

import persist from 'pinia-plugin-persistedstate'

...

app.use(createPinia().use(persist))

```

(4)配置 stores/user.js

添加persist参数(最终看上面的代码):

...

{ persist: true // 持久化 }

...

6.7.3 pinia仓库统一管理

(1)pinia独立维护:由 stores 统一维护,在 stores/index.js 中完成 pinia 初始化,交付 main.js 使用

(2)仓库统一导出:由 stores/index.js 统一导出,导入路径统一 `./stores`,而且仓库维护在 stores/modules 中。

新建stores/index.js

javascript 复制代码
// stores/index.js
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'

const pinia = createPinia().use(persist)

pinia.use(persist)

export default pinia

export * from './modules/user'
export * from './modules/counter'

修改main.js

javascript 复制代码
// main.js
import { createApp } from 'vue'

import pinia from './stores/index.js'
import App from './App.vue'
import router from './router'
import '@/assets/main.scss'

const app = createApp(App)

app.use(pinia)
app.use(router)

app.mount('#app')

修改app.vue

javascript 复制代码
<script setup>
...
import { useUserStore, useCounterStore } from '@/stores'
...
const userStore = useUserStore()
const counterStore = useCounterStore()
</script>

6.8 数据交互-请求工具设计

使用 axios 来请求后端接口, 一般都会对 axios 进行一些配置 (比如: 配置基础地址等),一般项目开发中, 都会对 axios 进行基本的二次封装, 单独封装到一个模块中, 便于使用。

新建 `utils/request.js` 封装 axios 模块,利用 axios.create 创建一个自定义的 axios 来使用,axios中文文档|axios中文网 | axios

javascript 复制代码
import { useUserStore } from '@/stores'
import axios from 'axios'
import router from '@/router'
import { ElMessage } from 'element-plus'

const baseURL = 'http://big-event-vue-api-t.itheima.net'

const instance = axios.create({
  // TODO 1. 基础地址,超时时间
  baseURL,
  timeout: 100000
})
// 请求拦截器
instance.interceptors.request.use(
  (config) => {
    // TODO 2. 携带token
    const userStore = useUserStore()
    if (userStore.token) {
      config.headers.Authorization = userStore.token // 根据实际使用修改
    }
    return config
  },
  (err) => Promise.reject(err)
)
// 响应拦截器
instance.interceptors.response.use(
  (res) => {
    // TODO 3. 处理业务失败
    // TODO 4. 摘取核心响应数据
    if (res.data.code === 0) {
      // 根据实际使用修改
      return res
    }
    ElMessage({ message: res.data.message || '服务异常', type: 'error' })
    return Promise.reject(res.data)
  },
  (err) => {
    ElMessage({
      message: err.response.data.message || '服务异常',
      type: 'error'
    })
    console.log(err)
    // TODO 5. 处理401错误 权限不足或token过期
    if (err.response?.status === 401) {
      router.push('/login')
    }
    return Promise.reject(err)
  }
)

export default instance
export { baseURL }

6.9 整体路由设计

6.9.1 实现目标
  • 完成整体路由规划【搞清楚要做几个页面,它们分别在哪个路由下面,怎么跳转的.....】

  • 通过观察, 点击左侧导航, 右侧区域在切换, 那右侧区域内容一直在变, 那这个地方就是一个路由的出口

  • 我们需要搭建嵌套路由

  • 把项目中所有用到的组件及路由表, 约定下来。

该项目的路由结构如下:

javascript 复制代码
import { createRouter, createWebHistory } from 'vue-router'

// createRouter创建路由实例
// 配置history模式
// 1. history模式:createwebHistory:地址栏不带#
// 2. hash模式: createwebHashHistory:地址栏带#
// vite环境变量:import.meta.env.BASE_URL
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    { path: '/login', component: () => import('@/views/login/loginPage.vue') },
    {
      path: '/',
      component: () => import('@/views/layout/LayoutContainer.vue'),
      redirect: '/article/manage',
      children: [
        {
          path: '/article/manage',
          component: () => import('@/views/article/ArticleManage.vue')
        },
        {
          path: '/article/channel',
          component: () => import('@/views/article/ArticleChannel.vue')
        },
        {
          path: '/user/profile',
          component: () => import('@/views/user/UserProfile.vue')
        },
        {
          path: '/user/avatar',
          component: () => import('@/views/user/UserAvatar.vue')
        },
        {
          path: '/user/password',
          component: () => import('@/views/user/UserPassword.vue')
        }
      ]
    }
  ]
})

export default router
6.9.2 登录注册
6.9.2.1 使用element-plus编写模型

(1)结构相关

el-row表示一行,一行分成24份

el-col表示列

(1.1) :span="12" 代表在一行中,占12份 (50%)

(1.2) :span="6" 表示在一行中,占6份 (25%)

(1.3) :offset="3" 代表在一行中,左侧margin份数

el-form 整个表单组件

el-form-item 表单的一行 (一个表单域)

el-input 表单元素(输入框)

(2)校验相关

(2.1) el-form => :model="ruleForm" 绑定的整个form的数据对象 { xxx, xxx, xxx }

(2.2) el-form => :rules="rules" 绑定的整个rules规则对象 { xxx, xxx, xxx }

(2.3) 表单元素 => v-model="ruleForm.xxx" 给表单元素,绑定form的子属性

(2.4) el-form-item => prop配置生效的是哪个校验规则 (和rules中的字段要对应)

6.9.2.2 script编写规则

(1)整个表单的校验规则

(3.1)非空校验 required: true message消息提示, trigger触发校验的时机 blur change

(3.2)长度校验 min:xx, max: xx

(3.3)正则校验 pattern: 正则规则 \S 非空字符

(3.4)自定义校验 => 自己写逻辑校验 (校验函数)

(2) validator: (rule, value, callback)

(4.1) rule 当前校验规则相关的信息

(4.2) value 所校验的表单元素目前的表单值

(4.3) callback 无论成功还是失败,都需要 callback 回调

  • callback() 校验成功

  • callback(new Error(错误信息)) 校验失败

6.9.2.3 注册功能

封装注册api,进行注册,注册成功切换到登录

(1)新建 api/user.js 封装

javascript 复制代码
import request from '@/utils/request'

export const userRegisterService = ({ username, password, repassword }) =>
  request.post('/api/reg', { username, password, repassword })

(2)页面中调用

javascript 复制代码
const register = async () => {
  await form.value.validate()
  await userRegisterService(formModel.value)
  ElMessage.success('注册成功')
  // 切换到登录
  isRegister.value = false
}

(3)eslintrc 中声明全局变量名, 解决 ElMessage 报错问题

html 复制代码
module.exports = {
  ...
  globals: {
    ElMessage: 'readonly',
    ElMessageBox: 'readonly',
    ElLoading: 'readonly'
  }
}
6.9.2.4 登录前的预校验

(1)登录请求之前,需要对用户的输入内容,进行校验,校验通过才发送请求。

(2)封装登录API,点击按钮发送登录请求,登录成功存储token,存入pinia 和 持久化本地storage,跳转到首页,给提示

html 复制代码
...
import { useUserStore } from '@/stores/'
import { useRouter } from 'vue-router'
...

...
const userStore = useUserStore()
const router = useRouter()
...
6.9.3 登录访问拦截

只有登录页,可以未授权的时候访问,其他所有页面,都需要先登录再访问

javascript 复制代码
// 登录访问拦截 => 默认是直接放行的
// 根据返回值决定,是放行还是拦截
// 返回值:
// 1. undefined / true 直接放行
// 2. false 拦回from的地址页面
// 3. 具体路径或路径对象,拦截到对应的地址
// return '/login' 或 {name: 'login'}
router.beforeEach((to) => {
  const userStore = useUserStore()
  if (!userStore.token && to.path !== '/login') return '/login'
})
6.9.4 用户基本信息获取&渲染

(1)`api/user.js`封装接口

javascript 复制代码
export const userGetInfoService = () => request.get('/my/userinfo')

(2)stores/modules/user.js 定义数据

javascript 复制代码
const user = ref({})
const getUser = async () => {
  const res = await userGetInfoService() // 请求获取数据
  user.value = res.data.data
}

(3)`layout/LayoutContainer`页面中调用

javascript 复制代码
import { useUserStore } from '@/stores'
const userStore = useUserStore()
onMounted(() => {
  userStore.getUser()
})
6.9.5 退出功能 [element-plus 确认框]
html 复制代码
<!-- 注册点击事件 -->
<el-dropdown placement="bottom-end" @command="onCommand">

<el-dropdown-menu>
  <el-dropdown-item command="profile" :icon="User">基本资料</el-dropdown-item>
  <el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item>
  <el-dropdown-item command="password" :icon="EditPen">重置密码</el-dropdown-item>
  <el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item>
</el-dropdown-menu>
javascript 复制代码
// 添加退出功能
const onCommand = async (command) => {
  if (command === 'logout') {
    await ElMessageBox.confirm('你确认退出大事件吗?', '温馨提示', {
      type: 'warning',
      confirmButtonText: '确认',
      cancelButtonText: '取消'
    })
    userStore.removeToken()
    userStore.setUser({})
    router.push(`/login`)
  } else {
    router.push(`/user/${command}`)
  }
}
javascript 复制代码
// pinia  user.js 模块 提供 setUser 方法
const setUser = (obj) => {user.value = obj}
相关推荐
禁默19 分钟前
第四届计算机、信息工程与电子材料国际学术会议 (CTIEEM 2024)
学习
非常规定义M3 小时前
Day30_0.1基础学习MATLAB学习小技巧总结(30)——优化工具箱及
开发语言·人工智能·学习·算法·数学建模·matlab
王俊山IT4 小时前
C++学习笔记----7、使用类与对象获得高性能(二)---- 理解对象生命周期(7)
开发语言·c++·笔记·学习
OEC小胖胖5 小时前
MyBatis系统学习(四)——MyBatis的关联映射和缓存机制
java·后端·学习·缓存·mybatis·web
达不溜方7 小时前
智谱清影 CogVideoX-2b:深度解析部署流程与高效使用指南
学习·创业创新·学习方法
limengshi1383928 小时前
通信工程学习:什么是NFVI网络功能虚拟化基础设施层
网络·学习·信息与通信
Jessaminee8 小时前
linux 学习 03
linux·windows·学习
YOLO数据集工作室8 小时前
Python人工智能学习路线
人工智能·python·学习
写代码的老教授8 小时前
用Python实现运筹学——Day 0: 学习计划
学习
zhangrelay8 小时前
机器人相关知识的本身和价值
笔记·学习·持续学习