自用vue3,已标记慎看(日常更新)

认识Vue3

1. Vue2 选项式 API vs Vue3 组合式API



特点:

  1. 代码量变少
  2. 分散式维护变成集中式维护

2. Vue3的优势

使用create-vue搭建Vue3项目

1. 认识create-vue

create-vue是Vue官方新的脚手架工具,底层切换到了 vite (下一代前端工具链),为开发提供极速响应

2. 使用create-vue创建项目 新电脑创建vue3项目

前置条件 - 已安装16.0或更高版本的Node.js
powershell中输入node -v

执行如下命令,这一指令将会安装并执行 create-vue

npm init vue@latest

项目名字:vue3-demo
除了ESLint剩下都No

创建成功后cd 到 vue3-demo

3.安装依赖

npm install

4.启动项目

npm run dev

熟悉项目和关键文件

  1. vite.config.js - 项目的配置文件 基于vite的配置

  2. package.json - 项目包文件 核心依赖项变成了Vue3.x 和 vite

  3. main.js - 入口文件 createApp函数创建应用实例
    //new Vue() 创建一个应用实例 => createApp()
    // createRouter() createStore()
    // 将创建实例进行了封装,保证每个实例的独立封闭性

  4. app.vue - 根组件 SFC单文件组件 script - template - style
    变化一: 脚本 script 和 模板 template 顺序调整
    变化二: 模板 template 不再要求唯一根元素
    变化三: 脚本 script 添加 setup 标识支持组合式 API

  5. index.html - 单页入口 提供 id 为 app 的挂载点

Vue2变Vue3需要替换的插件: 新电脑

禁用Vetur 安装volar(Vue Language Eeature)

组合式API - setup选项

vue2 vue3区别:
回忆:之前 vue 文件内容架子是 template + script + style(scoped) 结构 js 样式
如今是 script(set up) + template + style(scoped) js 结构 样式

  之前组件使用方法:导入注册 + 使用
  如今: 导入 + 使用
  import ... from + template中使用 

 vue2中template要求唯一根元素
 vue3中不要求(header , main..)

1. setup选项的写法和执行时机

加上setup 允许在script中直接编写组合式API

写法


执行时机

在beforeCreate钩子之前执行 非常早

2. setup中写代码的特点 setup用法

在setup函数中写的数据和方法需要在末尾以对象的方式return,才能给模版使用
定义+导出+使用
定义
数据形式:const 名 = 值
函数形式:const 名 = () =>{ }
导出 (在script后加setup可以省略此步)
return{名,名…}
使用
{{名}}…


3.

script标签添加 setup标记,不需要再写导出语句,默认会添加导出语句


组合式API - reactive和ref函数

1. reactive 响应的对象(obj)

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

vue3中实现 响应式 对象类型数据 的方法,
应用:比如点击按钮加1,没有reactive进行包起来就不能对数字加1

步骤:从vue导入reactive + reactive包对象 + 使用




2. ref (常用 更强大) 响应的 简单类型 或 复杂类型

接收 简单类型 或者 对象类型 的数据传入并返回一个响应式的对象

注意:1. js中访问数据,需要.value
2.template中,.value不用加(帮我们扒了一层)

步骤:导入ref + ref包数据 + 数据.value使用(js中,template不加)




3. reactive 对比 ref

  1. 都是用来生成响应式数据
  2. 不同点
    1. reactive不能处理简单类型的数据
    2. ref参数类型支持更好,但是必须通过.value做访问修改
    3. ref函数内部的实现依赖于reactive函数
  3. 在实际工作中的推荐
    推荐使用ref函数,减少记忆负担,小兔鲜项目都使用ref

组合式API - computed 计算属性函数写法

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

计算函数格式:

const 名 =computed( () =>{
return 计算公式
} )

步骤: 导入ref和computed + 定义计算函数用computed包起来 + 使用函数名

过滤数组案例 筛选大于二的



组合式API - watch 监听写成函数写法

侦听一个或者多个数据的变化,数据变化时执行回调函数,俩个额外参数 immediate控制立刻执行,deep开启深度侦听

步骤:导入ref,watch + 写事件函数(比如点击改名字) + 监听改前后的名字watch
watch( const的值,(新,老) => { } )
watch( [const的值1,const值2],(新,老) => { },{ immediate/deep:true } )

1. 侦听单个简单数据

watch 单个数据监听 格式

watch( (监听的值ref对象"无.value",(新值,旧值)) => {

} )


2. 侦听多个简单数据 无非多个数组包const

侦听多个数据(多个count),第一个参数改写成数组的写法

多个格式:

watch括号里边: 值是对象的话写对象不用加.velue
[值1,值2],(新,老)

<script setup>
  // 1. 导入watch
  import { ref, watch } from 'vue'
  const count = ref(0)  两个都是简单的
  const name = ref('cp')

  // 2. 调用watch 侦听变化 
  watch([count, name], (newValue,oldValue)=>{
    console.log(`count或者name变化了${oldValue},${newValue}`)
  })
</script>

3. immediate 一进页面立即执行一次 无非多个immediate:true

使用方法:在watch函数后面多加一个对象,写上immediate:true

执行单次案例后面加上immediate:true 一进页面控制台会显示 0 undefined

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


4. deep 复杂类型(obj系列)用 监听整个obj

通过watch监听的ref对象默认是 浅层侦听 的,

直接修改嵌套的对象属性不会触发回调执行,需要开启deep




5.精确监听对象中的某个属性(补充第四点)

写法:obj可变具体对象名,key可变具体键值对
watch(
()=>obj.value.key,
(新,老)=>console.log(新,老)
)

组合式API - 生命周期函数

1. 选项式对比组合式

Vue3的生命周期API(选项式 VS 组合式)

选项式API 组合式API

beforeCreate/created setup
创建响应式数据/
发送初始化渲染请求 在setup里直接请求+调用

beforeMount onBeforeMount
数据已经渲染完成

mounted onMounted
操作dom 渲染之后才可以(操作dom:修改数据或者方法…)

beforeUpdate onBeforeUpdate

updated onUpdated

beforeDestroy onBeforeUnmount

destroyed onUnmounted

2. 生命周期函数基本使用

  1. 导入生命周期函数
  2. 执行生命周期函数,传入回调
<scirpt setup>
import { onMounted } from 'vue'
onMounted(()=>{
  // 自定义逻辑
})
</script>

3. 执行多次

生命周期函数执行多次的时候,会按照顺序依次执行

<scirpt setup>
import { onMounted } from 'vue'
onMounted(()=>{
  // 自定义逻辑
})

onMounted(()=>{
  // 自定义逻辑
})
</script>

组合式API - 父子通信

1. 父传子数据 const props = defineProps({子:String})

1.父准备数据 const 父 =ref(‘李白’)
2.在子标签上加属性的 传值 :子=“父”
3. 子中props接收数据 vue3中不能写props:[]
借助编译器宏 defineProps()
const props = defineProps({
子:String
})
4. 渲染即可
{{ 子 }} 无需props.子

2. 子传父想改

例子:孩子花钱
子组件内部
1.写点击事件,@click=“spend()”
2.定义emit
(需要 e m i t 触发事件,给父发送消息通知 , 以前是 t h i s . emit触发事件,给父发送消息通知,以前是this. emit触发事件,给父发送消息通知,以前是this.emit,现在没有,要先自借助编译器宏)
const emit = defineEmits([‘wantSpend’])
3.写方法
const spend=()=>{
emit(‘wantSpend’,5)
}

父组件中
const money=ref(100)
1.给子组件标签通过@绑定事件 此时’5’传了过来
@wantSpend =dieGive
2.爹把参数放到方法里改
const dieGive=(newValue)=>{
money.value = newValue
}

组合式API - 模版引用 ref获取dom别组件的数据或方法

概念:通过 ref标识 获取真实的
dom对象 或者 组件实例对象(一个组件获取另一个组件的数据或者方法)

1. 基本使用 dom对象

案例:点击获取焦点 获取真实的 dom对象

实现步骤:

  1. 调用ref函数生成一个ref对象
    import ref from ‘vue’
    const 绑定属性名 =ref(null)
  1. 标签处通过ref标识绑定ref对象
    ref = ‘属性名’
    3.注册点击事件及方法
    @click=‘focuFn’
    const focuFn=()=>{
    属性名.value.focus()
    }

2. defineExpose 组件实例对象(一个组件获取另一个组件的数据或者方法) 拿其他组件的数据/属性

默认情况下在

案例:点击’获取组件数据’时控制台显示子组件数据
步骤:1.父中ref个属性名
2.子组件标签上ref=“属性名”
3.注册点击事件及方法

 @click='getState()'
 const getState=()={
  console.log(属性名.value.数据名/方法名)
 }

子:4.利用编译器宏把允许被外部访问的数据或方法放进去
数据:const count=999
const sayHi=()=>{
console.log(‘你好’)
}
打开权限允许访问:
difineExpose({
count,
sayHi
})

组合式API - provide和inject 父传数据给儿、孙

1. 作用和场景

顶层组件向任意的底层组件传递数据和方法,实现跨层组件通信

2. 跨层传递普通数据

实现步骤
0. 父导入 provide 或子孙导入 inject 函数

  1. 顶层通过 provide 提供数据
    provide(‘属性’,值) :值 无.value
  1. 底层通过 inject 接收数据
    const 子属性 = inject(‘属性’)

3.渲染
{{ 子属性 }}

注:不是自己的组件不能直接改,想改看 ##3

3. 跨层传递响应式数据 控制权还在爹(在爹传方法)

案例:爹的500传下去儿想改

在调用provide函数时,第二个参数设置为ref对象
newCount实际为底层的数据参数
顶层:
provide(‘爹方法名’,(newCount)=>{
爹数据.value = newCount
})
底层:
count 儿方法名 = inject(‘爹方法名’)

@click = ‘changeState’
const changeState=()=>{
儿方法名(参数)
}

4. 跨层传递方法 电脑进度

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

案例:父给儿、孙传数据 关键词:provide inject

  1. 父导入 provide 或子孙导入 inject 函数
  2. 顶层通过 provide 提供数据
    provide(‘属性’,值)
  1. 底层通过 inject 接收数据
    const 子属性 = inject(‘属性’)

3.渲染
{{ 子属性 }} */

Vue3.3 新特性-defineOptions 想给组件起名字defineOption({ name:‘’ })

背景说明:

但是用了


为了解决这一问题,引入了 defineProps 与 defineEmits 这两个宏。但这只解决了 props 与 emits 这两个属性。

如果我们要定义组件的 name 或其他自定义的属性,还是得回到最原始的用法——再添加一个普通的

这样就会存在两个


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

Vue3.3新特性-defineModel 快速实现双向绑定

在Vue3中,自定义组件上使用v-model, 相当于传递一个modelValue属性,同时触发 update:modelValue 事件

我们需要先定义 props,再定义 emits 。其中有许多重复的代码。如果需要修改此值,还需要手动调用 emit 函数。

于是乎 defineModel 诞生了。

步骤: vite文件配 defineModel:true + A组件v-model=“值” + (B组件中导入 + const 属性=defineModel() )

  • B使用’属性’

生效需要配置 vite.config.js

import { fileURLToPath, URL } from 'node:url'

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

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

Vue3 状态管理 - Pinia

1. 什么是Pinia

Pinia 是 Vue 的专属的最新状态管理库 ,是 Vuex 状态管理工具的替代品

2. 手动添加Pinia到Vue项目

后面在实际开发项目的时候,Pinia可以在项目创建时自动添加,现在我们初次学习,从零开始:

  1. 使用 Vite 创建一个空的 Vue3项目
npm init vite@latest

名字:vue3-pinia-demo
eslint yes,其他全NO
+
cd 到项目中
+
安装依赖: npm i
+
启动项目:npm run dev

之前vuex库中有 5个核心概念:state数据 mutations方法 action异步实现 getters计算 modules小库
如今是 3 个 : state action getters

  1. 按照官方文档安装 pinia 到项目中
    使用pinia: (新电脑)

安装pinia:
yarn add pinia 或者 npm install pinia
+
创建pinia实例 main.js
import {createPinia} from ‘pinia’

const pinia=createPinia() // 创建pinia实例
const app=createApp(App) // 创建根实例
app.use(pinia).mount(‘#app’) //pinia插件的安装配置,视图的挂载

3. Pinia基础使用 AK47 vue官网-生态-pinia-定义store vue3库 相当于xuex

案例:计数器案例

  1. 建store文件
    src/store/库名.js

  2. 导入defineStore + 定义store + 输出数据/方法
    import { defineStore } from ‘pinia’
    import { ref } from ‘vue’

//定义Store
// defineStore(仓库的唯一标识仓库名,()=<{…}>)

export const 组件中导入名 = defineStore(‘库名’,()=>{
//其他配置 选项式/组合式(常用)
const count = ref(0) //定义数据 ref就是state属性

const addCount =()=>{
     count.value++                // 普通函数 + 异步 写法
} 

//计算属性写法:
const 名 =computed( ()=>count.value*2 )

return { count,adedCount,名 }        //输出数据或方法

})

  1. 组件 导入’1中的组件中导入名’ + 接收’组件中导入名’ + 使用store
    import {导入名} from ‘@/src/store/库名’

const 接收名 = 导入名()

{{ 接收名.count }}
@click=“接收名.库内方法名addCount”

4. getters实现

Pinia中的 getters 直接使用 computed函数 进行模拟, 组件中需要使用需要把 getters return出去
如上3

5. action异步实现

方式:异步action函数的写法和组件中获取异步数据的写法完全一致

  • 接口地址:http://geek.itheima.net/v1_0/channels

  • 请求方式:get

  • 请求参数:无

需求:在Pinia中获取频道列表数据并把数据渲染App组件的模板中

安装axios: yarn add axios 或 npm i axios

重新启动项目:yarn dev 或 npm run dev

1.建文件 + 导入defineStore + 定义Store + 输出数据/方法

import {defineStore} from ‘pinia’
import {ref} from ‘vue’

export const 组件中导入名 = defineStore(‘库名’,()=>{
//声明数据
const 空数组 = ref([])

//声明操作数据的方法
 const 请求方法 = async ()=>{
      //支持异步
      const res=await axios({       //可以解构res: {data:{data}}
        method:'get',
        url:'http://geek.itheima.net/v1_0/channels'
      })
      console.log(res)
      空数组.value=res.data.data.channels        //channel是控制台得到的数组
 }

//声明getters相关

return{
数据,方法
}
})

2.组件中: 导入’1中组件中导入名’ + 接收’组件中导入名’ + 点击事件

import {组件中导入名} from ‘@/src/store/库名.js’

const 接收名 = 组件中导入名()

@click=“接收名.库内方法名” //此时控制台有数据

v-for渲染:

  • {{ item.name }}

6. storeToRefs解构工具函数 使用store时 解构数据的问题:破坏响应性

数据较多时解构会比较方便

使用storeToRefs函数可以辅助保持数据(state + getter)的响应式解构
组件中解构库中的数据:

   import {storeToRefs} from 'pinia'
   import {组件中导入名} from '@/src/store/库名.js'

   const 接收名 = 组件中导入名()

   const {库中数据} = storeToRefs(接收名)   //数据需要解构,方法不需要
   const 库中方法名 =  接收名

    {{库中数据}}        //此时不需要 接收名.库中数据
    @click="方法"  

7. Pinia的调试

Vue官方的 dev-tools 调试工具 对 Pinia直接支持,可以直接进行调试
检查 - vue - App(Pinia)

8. Pinia持久化插件 新电脑 安装插件直接持久化贼猛

官方文档:https://prazdevs.github.io/pinia-plugin-persistedstate/zh/

  1. 安装插件 pinia-plugin-persistedstate
npm i pinia-plugin-persistedstate
  1. 使用 main.js 导入插件 + .use
import persist from 'pinia-plugin-persistedstate'
...
app.use(createPinia().use(persist))
  1. 配置 store/counter.js
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'

export const useCounterStore = defineStore('counter', () => {
  ...
  return {
    count,
    doubleCount,
    increment
  }
}, {
  persist: true            //开启当前模块的持久化
})
  1. 其他配置,看官网文档即可
    {
    persist: {
    key:‘默认store.$id’,
    storage:‘默认localStorage’,
    paths:[‘默认全部数据持久化’],
    }
    }

后台数据管理系统 - 项目架构设计 AK47

在线演示:https://fe-bigevent-web.itheima.net/login
接口地址
接口文档: https://apifox.com/apidoc/shared-26c67aee-0233-4d23-aab7-08448fdf95ff/api-93850835

接口根路径: http://big-event-vue-api-t.itheima.net

本项目技术栈 本项目技术栈基于
ES6、
vue3、 CompositionAPI
pinia、 库,持久化
vue-router 、
vite 、axios 和
element-plus (表单校验,表格处理,组件封装)

新东西:
pnpm 包管理升级 (创建项目快)
Eslint + prettier 更规范的配置
husky (Git hooks工具) 代码提交前进行校验的工具

请求模块设计
VueRouter4 路由设计

AI 大模型开发一整个项目模块 (掌握最新的开发方式)

项目页面介绍

登录页(注册) + 文章分类 + 文章管理 + 个人中心

pnpm 包管理器 - 创建项目 新电脑

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

安装方式: 在磁盘的某个文件安装

npm install -g pnpm

创建vue3项目:

pnpm create vue

name: vue3-big-event-admin
选项Yes的有: VueRouter Pinia ESLint Prettier

安装依赖:

pnpm install

启动pnpm项目

pnpm dev

拓展pnpm 常用命令
安装axios

pnpm add axios            -D   依赖冲突加-D

移除axios

pnpm remove axios

ESLint & prettier 配置代码风格 新电脑

环境同步:

  1. 安装了插件 ESlint,开启保存自动修复
  2. 禁用了插件 Prettier,并关闭保存自动格式化
// ESlint插件 + Vscode配置 实现自动格式化修复
"editor.codeActionsOnSave": {
    "source.fixAll": true
},
"editor.formatOnSave": false,

配置文件 .eslintrc.cjs

  1. prettier 风格配置 https://prettier.io

    1. 单引号
    2. 不使用分号
    3. 每行宽度至多80字符
    4. 不加对象|数组最后逗号
    5. 换行符号不限制(win mac 不一致)
  2. vue组件名称多单词组成(忽略index.vue)

  3. props解构(关闭)

添加插件ESLint

改设置文件(vscode左下角setting + 右上角打开设置)

添加以下配置

//ESLint插件 + Vscode配置 实现自动格式化修复
"editor.codeActionsOnSave": {
    "source.fixAll": true
},
// 关闭保存自动格式化
"editor.formatOnSave":false

改目录中.eslintrc.cjs文件

  rules: {
    'prettier/prettier': [
      'warn',
      {
        singleQuote: true, // 单引号
        semi: false, // 无分号
        printWidth: 80, // 每行宽度至多80字符
        trailingComma: 'none', // 不加对象|数组最后逗号
        endOfLine: 'auto' // 换行符号不限制(win mac 不一致)
      }
    ],
    'vue/multi-word-component-names': [
      'warn',
      {
        ignores: ['index'] // vue组件名称多单词组成(忽略index.vue)
      }
    ],
    'vue/no-setup-props-destructure': ['off'], // 关闭 props 解构的校验
    //  添加未定义变量错误提示,[email protected] 关闭,这里加上是为了支持下一个章节演示。
    'no-undef': 'error'
  }

基于husky的提交代码前检查工作流 新电脑

husky 是一个 git hooks 工具 ( git的钩子工具,可以在特定时机执行特定的命令 )
没有git就去pc.qq下载 以下命令全是git bash终端

husky 配置

  1. git初始化 git init

  2. 初始化 husky 工具配置 https://typicode.github.io/husky/

pnpm dlx husky-init && pnpm install
  1. 修改 .husky/pre-commit 文件
pnpm lint       暂时的,以下面的为准

**问题:**pnpm lint默认进行的是全量检查,耗时问题,历史问题。
暂存区新添加的代码的 eslint 校验
lint-staged 配置

  1. 安装
pnpm i lint-staged -D
  1. 配置 package.json
{
  
  "lint-staged": {
    "*.{js,ts,vue}": [
      "eslint --fix"
    ]
  }
}

scripts配置项后面加
{
  "scripts": {
    "lint-staged": "lint-staged"
  }
}
  1. 修改 .husky/pre-commit 文件
pnpm lint-staged

调整项目目录

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

- 删除初始化的默认文件

以下只留文件夹:
assets,components,stores,views

- 修改剩余代码内容

src/router/index.js 的routes:[]内清空
App.vue中只留空架子
src/main.js中导入css文件删掉

- 新增调整我们需要的目录结构

utils 工具函数
api 请求封装

- 拷贝初始化资源文件,安装预处理器插件

将项目需要的全局样式 和 图片文件,复制到 assets 文件夹中, 并将全局样式在main.js中引入

import '@/assets/main.scss'
  • 安装 sass 依赖
pnpm add sass -D

VueRouter4 路由代码解析 点击跳转页面

// createRouter 创建路由实例,===> new VueRouter()
// 1. history模式: createWebHistory()  常用   
// 2. hash模式: createWebHashHistory()  
区别有无#

vue2 与 vue3 点击跳转不同:
如果 @click="$router.push('/路径path')"  生效
但是,@click="tiaoZhuan"
const tiaoZhuan =()=>{
    this.$router.push('/路径path')   不成立
}

vue3 CompositionAPI中获取路由

import { useRouter,useRoute } from 'vue-router'
1. 获取路由对象 router useRouter
 const router = useRouter()
 //点击跳转
 const 方法=()=>{
   router.push('/路径path')
 }
2. 获取路由参数 route useRoute   点击时携带的参数
const route = useRoute()


import.meta.env.BASE_URL 是路径前缀,可在vite文件配置 base:'/前缀路径'   (pluugins同级)
// vite 的配置 import.meta.env.BASE_URL 是路由的基准地址,默认是 ’/‘
// https://vitejs.dev/guide/build.html#public-base-path    vite官网说明

基础代码解析

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
history:createWebHistory(import.meta.env.BASE_URL),
  routes: []
})

export default router

引入 element-ui 组件库 新电脑 gitbash终端

官方文档: https://element-plus.org/zh-CN/

  • 安装
pnpm add element-plus

自动按需:

  1. 安装插件
pnpm add -D unplugin-vue-components unplugin-auto-import
  1. 然后把下列代码插入到你的 ViteWebpack 的配置文件中 vite.js
...
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    AutoImport({
      resolvers: [ElementPlusResolver()]
    }),
    Components({
      resolvers: [ElementPlusResolver()]
    })
  ]
})

  1. 直接使用

**彩蛋:**默认 components 下的文件也会被自动注册~不用import导入,直接 <组件>

Pinia - 构建用户user仓库 和 持久化 新电脑

pinia官方文档:https://prazdevs.github.io/pinia-plugin-persistedstate/zh/

1. 安装插件 pinia-plugin-persistedstate

创建项目时yes pinia就不用

pnpm add pinia-plugin-persistedstate -D

2. 使用 main.js 创建项目时yes pinia就不用

import persist from 'pinia-plugin-persistedstate'
...
app.use(createPinia().use(persist))

3. 配置 stores/user.js

1.导入‘定义库’和ref(响应数据) +
2.export定义模块 +
3.return数据/方法 +
4.persist持久化

import { defineStore } from 'pinia'
import { ref } from 'vue'

// 用户模块    useUserStore  是组件内导入名
export const useUserStore = defineStore('big-user',() => {
    const token = ref('') // 定义 token
    const setToken = (newToken) => {    
        token.value=newToken
    }// 设置 token

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

4.使用库内数据/方法 导入‘组件内导入名’ + 接受名=‘组件内导入名’() + 使用/渲染

import {useUserStore} from '@/stores/库名.js'

count userStore = useUserStore()

{{ userStore.数据}}
@click="userStore.setToken('参数')"

Pinia - 配置仓库统一管理 解决路径较长防写错 新电脑

pinia 独立维护 防止路径写错

- 现在:初始化代码在 main.js 中,仓库代码在 stores 中,代码分散职能不单一

- 优化:由 stores 统一维护,在 stores/index.js 中完成 pinia 初始化,交付 main.js 使用
步骤:

main.js 中 pinia 相关的放到 stores/index.js
import { createPinia } from 'pinia'
import persist...

app.use(createPinia().use(persist))        
  // createPinia().use(persist)放到stores/index.js
创建stores/index.js,加内容并导出
import { createPinia } from 'pinia'
import persist...

const pinia=createPinia()
pinia.use(persist)

export default pinia
main.js中导入pinia

import pinia from ‘@/stores/index’

app.use(pinia)

重新启动项目

pnpm dev

仓库 统一导出 某个组件下想调用两个库

- 现在:使用一个仓库 import { useUserStore } from ./stores/user.js 不同仓库路径不一致

- 优化:由 stores/index.js 统一导出,导入路径统一 ./stores,而且仓库维护在 stores/modules 中

import {useUserStore} from '@/stores/modules/user'
import {useCountStore} from '@/stores/modules/counter'

理想型:
import {useUserStore,useCountStore} from '@/stores'

实现:index.js中添加
export * from './modules/user'
export * from './modules/counter'

数据交互 - 请求工具设计 axios封装 新电脑

1. 创建 axios 实例

们会使用 axios 来请求后端接口, 一般都会对 axios 进行一些配置 (比如: 配置基础地址等)

一般项目开发中, 都会对 axios 进行基本的二次封装, 单独封装到一个模块中, 便于使用

  1. 安装 axios
pnpm add axios
  1. 新建 utils/request.js 封装 axios 模块

    利用 axios.create 创建一个自定义的 axios 来使用

    http://www.axios-js.com/zh-cn/docs/#axios-create-config
    创建实例:

const instance = axios.create({
  baseURL: 'https://some-domain.com/api/',
  timeout: 1000,
  headers: {'X-Custom-Header': 'foobar'}
});
import axios from 'axios'

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

const instance = axios.create({
  // TODO 1. 基础地址,超时时间
})

instance.interceptors.request.use(
  (config) => {
    // TODO 2. 携带token
    return config
  },
  (err) => Promise.reject(err)
)

instance.interceptors.response.use(
  (res) => {
    // TODO 3. 处理业务失败
    // TODO 4. 摘取核心响应数据
    return res
  },
  (err) => {
    // TODO 5. 处理401错误
    return Promise.reject(err)
  }
)

export default instance

2. 完成 axios 基本配置 请求库前提 @/utils/request

import { useUserStore } from '@/stores/user'
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({
  baseURL,
  timeout: 100000
})

instance.interceptors.request.use(
  (config) => {
    // 在发送请求之前做些什么,用库的token验证
    const userStore = useUserStore()
    if (userStore.token) {
      config.headers.Authorization = userStore.token
    }
    return config
  },
  // 对请求错误做些什么
  (err) => Promise.reject(err)
)

instance.interceptors.response.use(
  (res) => {
    // 对响应数据做点什么
    if (res.data.code === 0) {
      return res
    }
    ElMessage.error(res.data.message || '服务异常')
    return Promise.reject(res.data)
  },
  (err) => {
    // 对响应错误做点什么
    //错误的特殊情况=>401权限不足 或 token过期 =>拦截到登录
    if (err.response?.status === 401) {
      router.push('./login')
    }
    //错误的默认情况
    ElMessage.error(err.response.data.message || '服务异常')
    return Promise.reject(err)
  }
)

export default instance
export { baseURL }

首页整体路由设计

实现目标:

  • 完成整体路由规划【搞清楚要做几个页面,它们分别在哪个路由下面,怎么跳转的…】
  • 通过观察, 点击左侧导航, 右侧区域在切换, 那右侧区域内容一直在变, 那这个地方就是一个路由的出口
  • 我们需要搭建嵌套路由

目标:

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

约定路由规则

path 文件 功能 组件名 路由级别
/login views/login/LoginPage.vue 登录&注册 LoginPage 一级路由
/ views/layout/LayoutContainer.vue 布局架子 LayoutContainer 一级路由
├─ /article/manage views/article/ArticleManage.vue 文章管理 ArticleManage 二级路由
├─ /article/channel views/article/ArticleChannel.vue 频道管理 ArticleChannel 二级路由
├─ /user/profile views/user/UserProfile.vue 个人详情 UserProfile 二级路由
├─ /user/avatar views/user/UserAvatar.vue 更换头像 UserAvatar 二级路由
├─ /user/password views/user/UserPassword.vue 重置密码 UserPassword 二级路由

明确了路由规则,可以全部配完,也可以边写边配。

配置一级路由

@/views下新建以及每个路由文件(文件名全小写),
且在每个路由文件下再创建一个主核心文件index.vue(export default里加name,名字如LayoutIndex)

@/router/index.js里添加:
import (可现命名)组件名 from ‘@/views/.vue文件所在文件,可以省略.vue文件不写’(二级路由要写)
{path:‘设置地址栏路径比如/rearch’,component:上面import的组件名} */

二级路由配置:创文件 + 配路由 + 配出口

配置二级路由(即导航部分)
创建文件
首页的文件下新建四个二级路由文件 因为是首页四个导航的二级路由,所以是首页的chidren
views/layout/xxx.vue
+
配路由
router/index.js的layout后边加上children:[{path:‘/xxx’,component:组件名 },{ }…]
+
配出口(2个:App.vue + chidren所在组件)
在配置chidren的组件上加 布局架子路由组件加

重定向 默认页面

配路由处多配一项
{path:‘/’,redirect:‘/默认页面的路径’}

404 用户搜索不存在的路径 找不到网页

登录注册页面 [element-plus 表单 & 表单校验]

注册登录 静态结构 & 基本切换(注册页与登录页)

网页布局: 左边图片 + 右边登录信息

  1. 安装 element-plus 图标库
pnpm i @element-plus/icons-vue
  1. 静态结构准备

/* el-row表示一行,一行分成24分 
   el-col表示列 
      (1):span="12"  代表在一行中占用12分,也就是网页的一半 
      (2):span="6"   表示在一行中,占6份 
      (3):span="3"   代表在一行中,左侧margin份数:离左侧有多少 

       el-form 整个表单组件
       el-form-item 表单的一行
       h1、el-input 表单元素(标题,输入框)

       :prefix-icon=""   element-plus的图标

       接口文档:   https://apifox.com/apidoc/shared-26c67aee-0233-4d23-aab7-08448fdf95ff/api-93850835

【需求】注册页面基本校验

1. 用户名非空,长度校验5-10位
2. 密码非空,长度校验6-15位
3. 再次输入密码,非空,长度校验6-15位

【进阶】再次输入密码需要自定义校验规则,和密码框值一致(可选)

注意:配规则

form标签:
  1.:model="对象"   
  2.:rules="规则对象名"
imput处:
  3.v-model="对象.接口变量"
每一个填写的form-item处:  
  4.prop="规则名"

13小点总结:
按照官网所写,一个表单数据绑定一个变量(name),
如果出现多个数据这么写会非常麻烦,
所以把他们方到同一个对象中,
这些变量名都需要看接口文档,以便后期提交

将来 :model="对象名" + v-model="对象名.变量"

24小店总结:
:rules='规则对象名' 和 prop="规则名"  对应 
规则写法:
const 规则对象名 ={
    规则名:[{ 未输入时的规则 },{ 输入后的规则 }]
}

自定义校验(常用于再次输入密码):官网有
一个属性加在上面的规则对象里:validitor:(rule,value,callback)=>{写函数}

*/



注册功能

实现注册校验

接口文档: https://apifox.com/apidoc/shared-26c67aee-0233-4d23-aab7-08448fdf95ff/api-93850835

【需求】注册页面基本校验

  1. 用户名非空,长度校验5-10位
  2. 密码非空,长度校验6-15位 pattern:*/S{正则}$\
  3. 再次输入密码,非空,长度校验6-15位

【进阶】再次输入密码需要自定义校验规则,和密码框值一致(可选)

注意:

form标签:
1.:model=“对象”
2.:rules=“规则对象名”
imput处:
3.v-model=“对象.接口变量”
每一个填写的form-item处:
4.prop=“规则名”

13小点总结:
按照官网所写,一个表单数据绑定一个变量(name),
如果出现多个数据这么写会非常麻烦,
所以把他们方到同一个对象中,
这些变量名都需要看接口文档,以便后期提交

将来 :model=“对象名” + v-model=“对象名.变量”

24小店总结:
:rules=‘规则对象名’ 和 prop=“规则名” 对应
规则写法:
const 规则对象名 ={
规则名:[{ 未输入时的规则 },{ 输入后的规则 }]
}

自定义校验(常用于再次输入密码):官网有
一个属性加在上面的规则对象里:validitor:(rule,value,callback)=>{写函数}

 repassword:[{},{}, 第一第二个对象与password相同
   {
    validitor:(rule,value,callback)=>{
         <!-- 判断value与from中的password值是否相同 -->
      if (value === formModel.password.value){
        callback()
      }else{
        callback(new Error('您输入的密码不一致'))
      }
    }trigger:'blur' /* 失焦校验 */
   }
 ] 
  1. model 属性绑定 form 数据对象 加在表单外层

这些变量要到接口文档获取(将来要提交后台)

const formModel = ref({
  username: '',
  password: '',
  repassword: ''
})

    
  1. rules 配置校验规则 加在表单外层
    要基于1 2点 :model的变量 配的规则
          
    
const rules = {
  username: [
      /* 非空校验:未输入时的东西,校验提示,啥时候校验 */
    { required: true, message: '请输入用户名', trigger: 'blur' },
    { min: 5, max: 10, message: '用户名必须是5-10位的字符', trigger: 'blur' }
    /* 字符长度最短5,最长10,提示,输入完(焦点离开)校验 */
  ],
  password: [
    { required: true, message: '请输入密码', trigger: 'blur' },
    {
     
      pattern: /^\S{6,15}$/,     /*密码属性patten,值需要写正则表达式: /*\S{ 开始,结束 }$/  */
      message: '密码必须是6-15位的非空字符',
      trigger: 'blur'    
    }
  ],
  repassword: [
    { required: true, message: '请再次输入密码', trigger: 'blur' },
    {
      pattern: /^\S{6,15}$/,
      message: '密码必须是6-15的非空字符',
      trigger: 'blur'
    },
    {    /* 二次校验属于自定义校验,看element-plus官网 */
      validator: (rule, value, callback) => {
        if (value !== formModel.value.password) {
          callback(new Error('两次输入密码不一致!'))
        } else {
          callback()
        }
      },
      trigger: 'blur'
    }
  ]
}
  1. v-model 绑定 form 数据对象的子属性 写在每个表单填写处

... 
(其他两个也要绑定)
  1. prop 绑定校验规则

  

... 
(其他两个也要绑定prop)

注册成功前的预校验 解决注册时未填写但是点注册的提示 再输入的validate()方法通过就发请求

注册成功之前,先进行校验,校验成功 -》 请求 ,校验失败-》自动提示

官网Form Exposes暴露出去的方法 官网-表单-右侧FormExpose
so在注册的el-from中获取表单组件

const 获取名 = ref(null) + 标签ref=“获取名”
点击注册前校验
@click=“zhuC”
const zhuC=()=>{
await 获取名.value.validate()
console.log(‘开始注册请求’)
}
需求:点击注册按钮,注册之前,需要先校验(信息校验通过后才可以点击)

  1. 通过 ref 获取到 表单组件
const form = ref()


  1. 注册之前进行校验

  注册


const register = async () => {
  await form.value.validate()
  console.log('开始注册请求')
}

封装 api 实现注册功能

user.js中导入request请求+导出方法 + 组件内导入+调用方法

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

  1. 新建 api/user.js 封装
    导入请求前提 + 导出方法即可
import request from '@/utils/request'

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

参数解构(第一个解构是咱的,第二个是接口需要的)+
{
return request.post(‘/api/reg’, { username, password, repassword })
}
简写如上

  1. 注册登录页面中调用
import {userRegisterService} from '@/api/user.js'

const register = async () => {
  await form.value.validate()
  await userRegisterService(formModel.value)
  
  //Feedback反馈组件 - Massage消息提示
  ElMessage.success('注册成功')
  // 切换到登录
  isRegister.value = false
}
  1. eslintrc 中声明全局变量名, 解决 ElMessage 未导入的报错问题 新电脑 无需导入组件即可直接使用
    导入element-plus组件
module.exports = {
  ...
  globals: {
    ElMessage: 'readonly',/* 值是全局的意思,以后需要的组件都需要写进来就不会报错 */
    ElMessageBox: 'readonly',
    ElLoading: 'readonly'
  }
}

注册总结 : 静态 + 校验 + 注册前的预处理 + 发注册请求 + 跳转登录(改布尔值) AK47

登录功能

实现登录校验

【需求说明】给输入框添加表单校验

  1. 用户名不能为空,用户名必须是5-10位的字符,失去焦点 和 修改内容时触发校验
  2. 密码不能为空,密码必须是6-15位的字符,失去焦点 和 修改内容时触发校验

操作步骤: 可共用属性 方法

  1. model 属性绑定 form 数据对象,直接绑定之前提供好的数据对象即可

  1. rules 配置校验规则,共用注册的规则即可

  1. v-model 绑定 form 数据对象的子属性



  1. prop 绑定校验规则

  

... 
  1. 切换的时候重置账号密码
    监视这个切换值,变化了咋样
    将上面的三绑数据 对象 清空
watch(isRegister, () => {
  formModel.value = {
    username: '',
    password: '',
    repassword: ''
  }
})

登录前的预校验 & 登录成功

【需求说明1】登录之前的预校验

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

【需求说明2】登录功能

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

【测试账号】

  • 登录的测试账号: shuaipeng

  • 登录测试密码: 123456

PS: 每天账号会重置,如果被重置了,可以去注册页,注册一个新号

实现步骤:

  1. 注册事件,进行登录前的预校验 (获取到组件调用方法)

    
const login = async () => {
  await form.value.validate()
  console.log('开始登录')
}
  1. 封装接口 API api/user.js文件

export const userLoginService = ({ username, password }) =>
  request.post('api/login', { username, password })
  1. 调用库 以及调用库内方法将 token 存入 pinia 并 自动持久化本地
import { userLoginService } from '@/api/user.js'
import { useUserStore } from '@/stores/modules/user.js'

const userStore = useUserStore()
const router = useRouter()
const login = async () => {
  await form.value.validate()
  const res = await userLoginService(formModel.value)
  console.log(res) /* 此时发现接口会返回token值,调进user库,用那里的方法 */
  userStore.setToken(res.data.token)
  ElMessage.success('登录成功')
  router.push('/')
}

登录总结AK47
共用校验属性/规则 +
切换时清空表单(watch监听切换的值) +
注册前的预处理 +
发注册请求(携带请求返回来的token放到api/user.js)+
跳转到"/"页

首页 layout 架子 [element-plus 菜单组件]

基本架子拆解

架子组件列表:

el-container

  • el-aside 左侧
    • el-menu 左侧边栏菜单
      ·
  • el-container 右侧
    • el-header 右侧头部
      • el-dropdown
    • el-main 右侧主体
      • router-view

/* 
   el-menu 整个菜单组件
     active-text-color="#ffd04b"  点击时字体颜色

     :default-active="$route.path"   配置默认高亮的菜单项
     router     router选项开启,下面el-menu-item 的index 是点击跳转的路径(相当于vant组件的to)
  
     item 的index 是点击跳转的路径

  

  
    
      
      
        
          
          文章分类
        
        
          
          文章管理
        
        
          
          
            
            基本资料
          
          
            
            更换头像
          
          
            
            重置密码
          
        
      
    
    
      
        
黑马程序员:小帅鹏
大事件 ©2023 Created by 黑马程序员

登录访问拦截 不登录输网址进不来 未完成

未登录自动跳转登录页 172
router/index里加

需求:只有登录页,可以未授权的时候访问,其他所有页面,都需要先登录再访问
vueRouter 前置守卫
根据 有无router+去的是不是除了登录页的页面 来判断,true就去登录页

import { useUserStore } from '@/stores'

// 登录访问拦截  =>默认直接放行
//根据返回值决定,是放行还是拦截
//返回值:
//1. undefined/true 直接放行
//2. false 拦回from的地址页面
//3. 具体路径 或 路径对象 拦截到对应的地址
 //          '/login'  或 {name:'login'}     

router.beforeEach((to) => {
  const userStore = useUserStore()
  /* 无token且去的是非登录页,拦截到登录 */
  if (!userStore.token && to.path !== '/login') return '/login'
})

用户基本信息获取&渲染 已完成

封装接口(api/user)-发请求(user的)
export const 方法名=()=>request.method(‘url’)
+
pinia库内
导入请求库(api/user)
+
定义空对象(响应式 ref({}) )
+
定义个方法,调用请求方法
(async await 请求方法) ,const res接收
+
存到空对象 ( 对象名.value=res.具体数组 )
+
对象和请求方法暴露出去(return{})
+
组件中 layout/LayoutContainer
导入pinia+接收 user库
(const 自用名=组件内导入名)
+
onMounted(()=>{})钩子内调用库内方法名
+
渲染(自用名.对象名.属性)

  1. api/user.js封装接口
export const userGetInfoService = () => request.get('/my/userinfo')
  1. stores/modules/user.js 定义数据
const user = ref({})
const getUser = async () => {
  const res = await userGetInfoService() // 请求获取数据
  user.value = res.data.data
}
  1. layout/LayoutContainer页面中调用
import { useUserStore } from '@/stores'
const userStore = useUserStore()
/*mounted                 onMounted
操作dom  渲染之后才可以(操作dom:修改数据或者方法...)  */
onMounted(() => {
  userStore.getUser()
})
  1. 动态渲染
    可以先去演示网址修改再自己演示
优先前面名字 黑马程序员:{{ userStore.user.nickname || userStore.user.username }}

退出功能 [element-plus 下拉框+确认框] 下拉选项:跳转 和 退出 一步到位 已完成运行无效

自做错误点: 未注意弹窗的promise 加上async awiat, 忘记删除用户信息, 判断command === 'logout’时未加引号

退出(判断是否退出,给弹窗(MessageBox),退出时删除本地token值,user的id名,头像…)、跳转时(退出到登录页,跳转到其他对应页面)

官网 - dropdown下拉菜单 - 指令事件
官网 - MessageBox消息弹框 - 确认消息(有写promise返回,记得async await)

组件中:
用官网方法监听command属性做出行为
@command=“onCommand”
+
导入+接收 useRouter
import { useRouter } from ‘vue-router’
const router = useRouter()
+
导入+接收 user库
import { 组件中导入名 } from ‘@/stores/user.js’
const 自用名 = 组件中导入名()
+
定义方法
const 方法名= async(command)=>{
if(command === 退出command值){

    弹窗给提示
    自用名.删token的方法()
    自用名.删user信息的方法()
    router.push('登录的路径')
  }else{
     router.push(`.../${command}`)
  }

}
+
pinia库里
const 删token的方法=()=>{
token.value=‘’
}
const 删user信息的方法=()=>{
user.value= {}
}
return{
删token的方法,删user信息的方法
}

  1. 注册点击事件


/* command属性要跟配路由时的path最后一项相同 */

  基本资料
  更换头像
  重置密码
  退出登录

  1. 添加退出功能 就两种功能:退出、跳转
    退出时:

本地的用户token 和 用户信息清除 库内方法
跳到登录页

import { 组件中导入名 } from '@/stores/user.js'
import { useRouter } from 'vue-router'
  const router = useRouter()

const onCommand = async (command) => {
  if (command === 'logout') {
    /* 消息弹出框  加await:官网原文:在这里我们返回了一个 Promise 来处理后续响应。  有promise返回不想加下面 需要awiat */
    await ElMessageBox.confirm('你确认退出大事件吗?', '温馨提示', {
      type: 'warning',      /* 警告 */
      confirmButtonText: '确认',  /* 绑定了router.push */
      cancelButtonText: '取消'
    })
    userStore.removeToken()    
    userStore.setUser({})
    router.push(`/login`)
  } else {
    router.push(`/user/${command}`)
  }
}
  1. pinia user.js 模块 提供 setUser 方法
const 删token的方法=()=>{
   token.value=''
 }
 const 删user信息的方法=()=>{
   user.value= {}
 }
 return{
   删token的方法,删user信息的方法
 }

文章分类页面 - [element-plus 表格]

一个页面中穿插 组件 和 插槽,具名插槽会有按钮 ,默认插槽会有表格、分页
方法使用: el-card + el-button + defineProps + slot(name=名 定个位 + template/div#名 里面写内容 )

基本架子 - PageContainer

  1. 基本结构样式,用到了 el-card 组件



  1. 考虑到多个页面复用,封装成组件
    • props 定制标题 组件父传子
    • 默认插槽 default 定制内容主体
    • 具名插槽 extra 定制头部右侧额外的按钮





  1. 页面中直接使用测试
    ( unplugin-vue-components 会自动注册组件,全局啥都不用,局部要导入(import…))
  • 文章分类测试:

  • 文章管理测试:

文章分类渲染 某个路由页面渲染表格

因为关于分类的请求并不多,可以不用pinia

api库
封装接口(api/article)-发请求(获取文章分类的)
imprt request from ‘@/utils/request.js’
export const 请求方法名=()=>request.method(‘url’)
+
组件中
+
导入ref, 导入api模块
import { ref } from ‘vue’
import { 请求方法名 } from ‘@/api/article’
+
定义 响应式 空数组(因为获取数据一般都是数组包对象)
const 数组名 = ref([])
+
定义方法,调用 请求方法,定义res接收 ,并在下面直接调用
const 方法名 = async () =>{
const res = await 请求方法名()
数组名.value = res.data.具体
}
方法名()
+
添加表格 直接 实现渲染
表头: Table表格-基础表格
序号列: Table表格-单选(添加序号 type=“index”)
拿数据编辑删除: Table表格-自定义列模板
按钮及图标: Button 按钮-基础用法
+
添加加载效果
默认关闭,请求得数据前一直loading,得到数据后就取消加载 (关开关)
官网 - Loading加载 - 区域加载 - el-table标签处v-loading=“布尔值”
const loading=ref(true)
const 方法名 = async () =>{
loading.value=true
const res = await 请求方法名()
数组名.value = res.data.具体
loading.value=false
}
+
请求无数据处理
官网 - Empty 空状态 - 基础用法
el-table里边的下面加上


以下是表格制作:

使用到官网-Table表格-基础表格

   <template>
    <el-table :data="tableData" style="width: 100%">       通过:data="tableData" 注入数据
      <el-table-column prop="date" label="Date" width="180" /> 每一列,lable是列名(第一列..)
      <el-table-column prop="name" label="Name" width="180" />
      <el-table-column prop="address" label="Address" />
    </el-table>
  </template>
  <script lang="ts" setup>
  /* 上面通过prop="属性" 渲染 */
  const tableData = [
    {
      date: '2016-05-03',
      name: 'Tom',
      address: 'No. 189, Grove St, Los Angeles',
    }
  ]
  </script>

使用到官网-Table表格-单选(添加序号 在需要的列标签加 type=“index”)

使用到官网-Table表格-自定义列模板 (操作数据)

想操作每行的数据:
在里添加template属性+默认插槽,得到一个对象,里边有row,$index…