Vue3 大事件管理系统

pnpm 包管理器-创建项目

优势:

比同类工具快两倍左右,节省磁盘空间

安装方式:npm install -g pnpm

创建项目:pnpm create vue

Eslint 配置代码风格

配置文件:.eslint.sjs

1. prettier风格配置

  • 单引号
  • 无分号
  • 每行字符至多80字符
  • 不加对象|数组最后逗号
  • 换行符号不限制(win mac 不一致)
js 复制代码
// ESlint插件 + Vscode配置 实现自动格式化修复
"editor.codeActionsOnSave": {
    "source.fixAll": true
},
"editor.formatOnSave": false,

Eslint关注于规范,不符合规范会报错

prettier关注美观度

2. vue组件名称多单词组成(忽略index.vue)

3. props解构(关闭)

js 复制代码
  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 解构的校验
    // 💡 添加未定义变量错误提示,create-vue@3.6.3 关闭,这里加上是为了支持下一个章节演示。
    'no-undef': 'error'
  }
  • 禁用格式化插件prettier format on save关闭

  • 装Eslint插件,并配置保存时自动修复

配置代码检查工作流

  1. 初始化git仓库,执行git init即可
  2. 初始化husky工具配置, typicode.github.io/husky/
js 复制代码
pnpm dlx husky-init && pnpm install 

暂存区 eslint校验

  1. 安装lint-staged 包 pnpm i lint-staged-D
  2. packagejson配置lint-staged命令
js 复制代码
{
  // ... 省略 ...
  "lint-staged": {
    "*.{js,ts,vue}": [
      "eslint --fix"
    ]
  }
}


{
  "scripts": {
    // ... 省略 ...
    "lint-staged": "lint-staged"
  }
}
  1. .husky/pre-commit 文件修改
js 复制代码
pnpm lint-staged

目录调整

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

  • 删除初始化的默认文件
  • 修改剩余代码内容
  • 新增调整我们需要的目录结构
  • 拷贝初始化资源文件,安装预处理器插件
  1. 删除文件

  2. 修改内容

src/router/index.js

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

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

export default router

src/App.vue

jsx 复制代码
<script setup></script>

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

<style scoped></style>

src/main.js

jsx 复制代码
import { createApp } from 'vue'
import { createPinia } from 'pinia'

import App from './App.vue'
import router from './router'

const app = createApp(App)

app.use(createPinia())
app.use(router)
app.mount('#app')
  1. 新增需要目录 api 和 utils

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

jsx 复制代码
import '@/assets/main.scss'
  • 安装 sass 依赖
jsx 复制代码
pnpm add sass -D

vue-router4 路由解析

路由初始化

  1. 创建路由实例由createRouter实现
  2. 路由模式
  • history 模式使用 createWebHistory()
  • hash模式使用createWebHashHistory()
  • 参数是基础路径,默认/ 基础代码解析
js 复制代码
import { createRouter, createWebHistory } from 'vue-router'

// createRouter 创建路由实例,===> new VueRouter()
// 1. history模式: createWebHistory()   http://xxx/user
// 2. hash模式: createWebHashHistory()  http://xxx/#/user

// vite 的配置 import.meta.env.BASE_URL 是路由的基准地址,默认是 '/'
// https://vitejs.dev/guide/build.html#public-base-path

// 如果将来你部署的域名路径是:http://xxx/my-path/user
// vite.config.ts  添加配置  base: my-path,路由这就会加上 my-path 前缀了

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

export default router

import.meta.env.BASE_URL 是Vite 环境变量:cn.vitejs.dev/guide/env-a...

总结:

引入Element Plus 组件库

按需引入Element Plus

  1. 安装 pnpm add element-plus
  2. 配置按需导入

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

  1. 直接使用组件

步骤

  1. 安装插件
js 复制代码
pnpm add -D unplugin-vue-components unplugin-auto-import
  1. 然后把下列代码插入到你的 ViteWebpack 的配置文件中
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. 直接使用
js 复制代码
<template>
  <div>
    <el-button type="primary">Primary</el-button>
    <el-button type="success">Success</el-button>
    <el-button type="info">Info</el-button>
    <el-button type="warning">Warning</el-button>
    <el-button type="danger">Danger</el-button>
    ...
  </div>
</template>

彩蛋:默认 components 下的文件也会被自动注册

Pinia - 构建用户仓库 和 持久化

官方文档:

prazdevs.github.io/pinia-plugi...

步骤:

  1. 安装插件 pinia-plugin-persistedstate
js 复制代码
pnpm add pinia-plugin-persistedstate -D
  1. 使用 main.js
js 复制代码
import persist from 'pinia-plugin-persistedstate'
...
app.use(createPinia().use(persist))
  1. 配置 stores/user.js
jsx 复制代码
import { defineStore } from 'pinia'
import { ref } from 'vue'

// 用户模块
export const useUserStore = defineStore(
  'big-user',
  () => {
    const token = ref('') // 定义 token
    const setToken = (t) => (token.value = t) // 设置 token

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

Pinia - 配置仓库统一管理

pinia 独立维护

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

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

仓库 统一导出

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

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

数据交互 - 请求工具设计

创建 axios 实例

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

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

步骤

  1. 安装 axios
csharp 复制代码
pnpm add axios
  1. 新建 utils/request.js 封装 axios 模块

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

    www.axios-js.com/zh-cn/docs/...

js 复制代码
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

完成 axios 基本配置

jsx 复制代码
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) => {
    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({ message: res.data.message || '服务异常', type: 'error' })
    return Promise.reject(res.data)
  },
  (err) => {
    ElMessage({ message: err.response.data.message || '服务异常', type: 'error' })
    console.log(err)
    if (err.response?.status === 401) {
      router.push('/login')
    }
    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 二级路由

登录注册页面【element-plus 表单 & 表单校验】

功能需求

  1. 注册登录 静态结构&基本切换
  2. 诸恶功能(校验+注册)
  3. 登录功能(校验+登录+存token)

登录注册

  1. 安装 element-plus 图标库
js 复制代码
pnpm i @element-plus/icons-vue
  1. 静态结构准备
js 复制代码
<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref } from 'vue'
const isRegister = ref(true)
</script>

<template>

  <el-row class="login-page">
    <el-col :span="12" class="bg"></el-col>
    <el-col :span="6" :offset="3" class="form">
    //注册相关表单
      <el-form ref="form" size="large" autocomplete="off" v-if="isRegister">
        <el-form-item>
          <h1>注册</h1>
        </el-form-item>
        <el-form-item>
          <el-input :prefix-icon="User" placeholder="请输入用户名"></el-input>
        </el-form-item>
        <el-form-item>
          <el-input
            :prefix-icon="Lock"
            type="password"
            placeholder="请输入密码"
          ></el-input>
        </el-form-item>
        <el-form-item>
          <el-input
            :prefix-icon="Lock"
            type="password"
            placeholder="请输入再次密码"
          ></el-input>
        </el-form-item>
        <el-form-item>
          <el-button class="button" type="primary" auto-insert-space>
            注册
          </el-button>
        </el-form-item>
        <el-form-item class="flex">
          <el-link type="info" :underline="false" @click="isRegister = false">
            ← 返回
          </el-link>
        </el-form-item>
      </el-form>
      <el-form ref="form" size="large" autocomplete="off" v-else>
        <el-form-item>
          <h1>登录</h1>
        </el-form-item>
        <el-form-item>
          <el-input :prefix-icon="User" placeholder="请输入用户名"></el-input>
        </el-form-item>
        <el-form-item>
          <el-input
            name="password"
            :prefix-icon="Lock"
            type="password"
            placeholder="请输入密码"
          ></el-input>
        </el-form-item>
        <el-form-item class="flex">
          <div class="flex">
            <el-checkbox>记住我</el-checkbox>
            <el-link type="primary" :underline="false">忘记密码?</el-link>
          </div>
        </el-form-item>
        <el-form-item>
          <el-button class="button" type="primary" auto-insert-space
            >登录</el-button
          >
        </el-form-item>
        <el-form-item class="flex">
          <el-link type="info" :underline="false" @click="isRegister = true">
            注册 →
          </el-link>
        </el-form-item>
      </el-form>
    </el-col>
  </el-row>
</template>

<style lang="scss" scoped>
.login-page {
  height: 100vh;
  background-color: #fff;
  .bg {
    background: url('@/assets/logo2.png') no-repeat 60% center / 240px auto,
      url('@/assets/login_bg.jpg') no-repeat center / cover;
    border-radius: 0 20px 20px 0;
  }
  .form {
    display: flex;
    flex-direction: column;
    justify-content: center;
    user-select: none;
    .title {
      margin: 0 auto;
    }
    .button {
      width: 100%;
    }
    .flex {
      width: 100%;
      display: flex;
      justify-content: space-between;
    }
  }
}
</style>

注:

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

  • el-col表示列:span="12" 代表在一行中占12份(50%)

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

  • offset="3" 表示在一行中,左侧margin份数

  • el-form 整个表单组件
  • el-form-item 表单的一行(一个表单域)
  • 表单域彼此独立 互不影响
  • el-input表单元素(输入框)

注册功能

【需求】注册页面基本校验
  1. 用户名非空,长度校验5-10位
  2. 密码非空,长度校验6-15位
  3. 再次输入密码,非空,长度校验6-15位

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

校验相关
  • el-form => :model="ruleForm" 绑定的整个form的数据对象{xxx,xxx,xxx}
  • el-form => :rules="rules" 绑定的整个rules规则对象{xxx,xxx,xxx}
  • 表单元素 =>v-model="ruleForm,xxx" 给表单元素绑定form的子属性
  • el-model-item-props配置生效的是哪个校验规则
具体如下:
1. model 属性绑定 form 数据对象
js 复制代码
//整个用于提交的form数据对象
const formModel = ref({
  username: '',
  password: '',
  repassword: ''
})

<el-form :model="formModel" >
2. v-model 绑定 form 数据对象的子属性
js 复制代码
<el-input
  v-model="formModel.username"
  :prefix-icon="User"
  placeholder="请输入用户名"
></el-input>
... 
(其他两个也要绑定)
3. rules 配置校验规则
js 复制代码
<el-form :rules="rules" >
 //整个表单的校验规则   
const rules = {
  username: [
    { required: true, message: '请输入用户名', trigger: 'blur' },
    { min: 5, max: 10, message: '用户名必须是5-10位的字符', trigger: 'blur' }
  ],
  password: [
    { required: true, message: '请输入密码', trigger: 'blur' },
    {
      pattern: /^\S{6,15}$/,
      message: '密码必须是6-15位的非空字符',
      trigger: 'blur'
    }
  ],
  repassword: [
    { required: true, message: '请再次输入密码', trigger: 'blur' },
    {
      pattern: /^\S{6,15}$/,
      message: '密码必须是6-15的非空字符',
      trigger: 'blur'
    },
    {
      validator: (rule, value, callback) => {
      //判度value和当前form中收集的password是否一致
        if (value !== formModel.value.password) {
          callback(new Error('两次输入密码不一致!'))
        } else {
          callback()
        }
      },
      trigger: 'blur'
    }
  ]
}

效果如下:

整个表单的校验规则
  • 非空校验:required:true message 消息提示 trigger触发校验时机 blur change

  • 长度校验 min:xx max:xx

  • 正则校验 pattern:正则规则

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

    • validator : (rule,value,callback)
    • rule 当前校验相关的信息
    • value 当前校验表单元素目前的表单值
    • callback 无论成功还是失败,都需要callback回调
      • callback() 校验成功
      • callback(new Error(错误信息)) 校验失败
4. prop 绑定校验规则
js 复制代码
<el-form-item prop="username">
  <el-input
    v-model="formModel.username"
    :prefix-icon="User"
    placeholder="请输入用户名"
  ></el-input>
</el-form-item>
... 
(其他两个也要绑定prop)

注册前的预校验

需求:点击注册按钮,注册之前,需要先校验

1. 通过 ref 获取到 表单组件
js 复制代码
const form = ref()

<el-form ref="form">
2. 注册之前进行校验
js 复制代码
<el-button
  @click="register"
  class="button"
  type="primary"
  auto-insert-space
>
  注册
</el-button>

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

封装 api 实现注册功能

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

1. 新建 api/user.js 封装
js 复制代码
import request from '@/utils/request'

export const userRegisterService = ({ username, password, repassword }) =>
  request.post('/api/reg', { username, password, repassword })
2. 页面中调用
jsx 复制代码
const register = async () => {
  await form.value.validate()
  await userRegisterService(formModel.value)
  ElMessage.success('注册成功')
  // 切换到登录
  isRegister.value = false
}
3. eslintrc 中声明全局变量名, 解决 ElMessage 报错问题
jsx 复制代码
module.exports = {
  ...
  globals: {
    ElMessage: 'readonly',
    ElMessageBox: 'readonly',
    ElLoading: 'readonly'
  }
}

效果如下:

登录功能

实现登录校验

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

  1. 用户名不能为空,用户名必须是5-10位的字符,失去焦点 和 修改内容时触发校验
  2. 密码不能为空,密码必须是6-15位的字符,失去焦点 和 修改内容时触发校验
操作步骤:
  1. model 属性绑定 form 数据对象,直接绑定之前提供好的数据对象即可
js 复制代码
<el-form :model="formModel" >
  1. rules 配置校验规则,共用注册的规则即可
js 复制代码
<el-form :rules="rules" >
  1. v-model 绑定 form 数据对象的子属性
js 复制代码
<el-input
  v-model="formModel.username"
  :prefix-icon="User"
  placeholder="请输入用户名"
></el-input>

<el-input
  v-model="formModel.password"
  name="password"
  :prefix-icon="Lock"
  type="password"
  placeholder="请输入密码"
></el-input>
  1. prop 绑定校验规则
js 复制代码
<el-form-item prop="username">
  <el-input
    v-model="formModel.username"
    :prefix-icon="User"
    placeholder="请输入用户名"
  ></el-input>
</el-form-item>
... 
  1. 切换的时候重置
js 复制代码
watch(isRegister, () => {
  formModel.value = {
    username: '',
    password: '',
    repassword: ''
  }
})

预校验

请求参数:

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

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

【需求说明2】登录功能

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

实现步骤:

  1. 注册事件,进行登录前的预校验 (获取到组件调用方法)
js 复制代码
<el-form ref="form">
    
const login = async () => {
  await form.value.validate()
  console.log('开始登录')
}
  1. 封装接口 API
js 复制代码
export const userLoginService = ({ username, password }) =>
  request.post('api/login', { username, password })
  1. 调用方法将 token 存入 pinia 并 自动持久化本地
js 复制代码
const userStore = useUserStore()
const router = useRouter()
const login = async () => {
  await form.value.validate()
  const res = await userLoginService(formModel.value)
  userStore.setToken(res.data.token)
  ElMessage.success('登录成功')
  //跳转到本地
  router.push('/')
}

效果如下:

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

基本架子拆解

架子组件列表:

el-container

  • el-aside 左侧

    • el-menu 左侧边栏菜单
  • el-container 右侧

    • el-header 右侧头部
      • el-dropdown
    • el-main 右侧主体
      • router-view

代码

html 复制代码
<script setup>
import {
  Management,
  Promotion,
  UserFilled,
  User,
  Crop,
  EditPen,
  SwitchButton,
  CaretBottom
} from '@element-plus/icons-vue'
import avatar from '@/assets/default.png'
</script>

<template>
  <el-container class="layout-container">
    <el-aside width="200px">
      <div class="el-aside__logo"></div>
      <el-menu
        active-text-color="#ffd04b"
        background-color="#232323"
        :default-active="$route.path"
        text-color="#fff"
        router
      >
        <el-menu-item index="/article/channel">
          <el-icon><Management /></el-icon>
          <span>文章分类</span>
        </el-menu-item>
        <el-menu-item index="/article/manage">
          <el-icon><Promotion /></el-icon>
          <span>文章管理</span>
        </el-menu-item>
        <el-sub-menu index="/user">
        //多级菜单的标题-具名插槽title
          <template #title>
            <el-icon><UserFilled /></el-icon>
            <span>个人中心</span>
          </template>
          
          //展开的内容-默认插槽
          <el-menu-item index="/user/profile">
            <el-icon><User /></el-icon>
            <span>基本资料</span>
          </el-menu-item>
          <el-menu-item index="/user/avatar">
            <el-icon><Crop /></el-icon>
            <span>更换头像</span>
          </el-menu-item>
          <el-menu-item index="/user/password">
            <el-icon><EditPen /></el-icon>
            <span>重置密码</span>
          </el-menu-item>
        </el-sub-menu>
      </el-menu>
    </el-aside>
    <el-container>
      <el-header>
        <div>黑马程序员:<strong>小帅鹏</strong></div>
        <el-dropdown placement="bottom-end">
          <span class="el-dropdown__box">
            <el-avatar :src="avatar" />
            <el-icon><CaretBottom /></el-icon>
          </span>
          <template #dropdown>
            <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>
          </template>
        </el-dropdown>
      </el-header>
      <el-main>
        <router-view></router-view>
      </el-main>
      <el-footer>大事件 ©2023 Created by 黑马程序员</el-footer>
    </el-container>
  </el-container>
</template>

<style lang="scss" scoped>
.layout-container {
  height: 100vh;
  .el-aside {
    background-color: #232323;
    &__logo {
      height: 120px;
      background: url('@/assets/logo.png') no-repeat center / 120px auto;
    }
    .el-menu {
      border-right: none;
    }
  }
  .el-header {
    background-color: #fff;
    display: flex;
    align-items: center;
    justify-content: space-between;
    .el-dropdown__box {
      display: flex;
      align-items: center;
      .el-icon {
        color: #999;
        margin-left: 10px;
      }

      &:active,
      &:focus {
        outline: none;
      }
    }
  }
  .el-footer {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
    color: #666;
  }
}
</style>

注:

  • el-menu 整个菜单

  • active-text-color:激活时文字颜色

  • :default-active="$route.path" 配置默认高亮的菜单项

  • router router选项开启,el-menu-item 的index就是点击跳转的路径

el-menu-item 菜单项

index="/article/channel"配置的是访问的跳转路径,配合default-active值实现高亮

登陆访问拦截 =>默认是放行的

根据返回值决定,是放行还是拦截

返回值:

  1. undefined / 直接放行

  2. false拦回from的地址是页面

  3. 具体路径或路径对象 拦截到对应的地址

    • '/lohin'{ name:'login'}

如果没有token,且访问的是非登录页,拦截到登录,其他情况正常放行

登录访问拦截

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

jsx 复制代码
// 登录访问拦截
router.beforeEach((to) => {
  const userStore = useUserStore()
  if (!userStore.token && to.path !== '/login') return '/login'
})

用户基本信息获取&渲染

请求参数

步骤如下:

1. api/user.js封装接口
js 复制代码
export const userGetInfoService = () => request.get('/my/userinfo')
2. stores/modules/user.js 定义数据
js 复制代码
const user = ref({})
const getUser = async () => {
  const res = await userGetInfoService() // 请求获取数据
  user.value = res.data.data
}
3. layout/LayoutContainer页面中调用
js 复制代码
import { useUserStore } from '@/stores'
const userStore = useUserStore()
onMounted(() => {
  userStore.getUser()
})
4. 动态渲染
js 复制代码
<div>
  黑马程序员:<strong>{{ userStore.user.nickname || userStore.user.username }}</strong>
</div>

<el-avatar :src="userStore.user.user_pic || avatar" />

退出功能 [element-plus 确认框]

1. 注册点击事件

js 复制代码
<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>

2. 添加退出功能

js 复制代码
const onCommand = async (command) => {
  if (command === 'logout') {
    await ElMessageBox.confirm('你确认退出大事件吗?', '温馨提示', {
      type: 'warning',
      confirmButtonText: '确认',
      cancelButtonText: '取消'
    })
    //清楚本地的数据(token+user)
    userStore.removeToken()
    userStore.setUser({})
    router.push(`/login`)
  } else {
  //跳转操作
    router.push(`/user/${command}`)
  }
}

3. pinia user.js 模块 提供 setUser 方法

js 复制代码
const setUser = (obj) => (user.value = obj)
相关推荐
喵叔哟5 分钟前
重构代码之取消临时字段
java·前端·重构
还是大剑师兰特1 小时前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
王解1 小时前
【深度解析】CSS工程化全攻略(1)
前端·css
一只小白菜~1 小时前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
方才coding1 小时前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
man20171 小时前
【2024最新】基于springboot+vue的闲一品交易平台lw+ppt
vue.js·spring boot·后端
阿征学IT1 小时前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓1 小时前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
丶21361 小时前
【WEB】深入理解 CORS(跨域资源共享):原理、配置与常见问题
前端·架构·web
发现你走远了1 小时前
『VUE』25. 组件事件与v-model(详细图文注释)
前端·javascript·vue.js