Vue3大事件管理系统

项目介绍

  1. 大事件项目介绍
    在线演示:fe-bigevent-web.itheima.net/login

  2. 接口文档:apifox.com/apidoc/shar…

  3. 基地址:big-event-vue-api-t.itheima.net

pnpm包管理器 – 创建项目

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

  • 安装方式:npm install -g pnpm

  • 创建项目:pnpm create vue

  • 安装依赖👇(三种安装方式)
    Vue3大事件管理系统

这个不要往根目录建,会报权限不够的错

Vue3大事件管理系统

接着cd打开——然后dev运行

Eslint配置代码风格

两者一般配合使用

Eslint 注重规范,不符合规范会报错

prettier 注重代码美观

  1. prettier 配置风格 prettier.io
    • 单引号
    • 无分号
    • 每行字符至多80字符
    • 不加对象|数组最后逗号
    • 换行符号不限制(win mac 不一致)
  2. vue组件名称多单词组成(忽略index.vue)
  3. props解构(关闭)
    • 解构不会丢失响应式

在.eslintrc.cjs后添加配置👇

prettier专注于代码的美观度(格式化工具)

  1. 禁用格式化插件prettier format on save关闭
  2. 安装Eslint插件,并配置保存时自动修复
  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'
  }

设置settings里:

     //当保存时,eslint自动帮我们修复错误
    "editor.codeActionsOnSave": {
        "source.fixAll":true
    },
      //保存代码,不自动格式化
    "editor.formatOnSave": false

基于husky的代码检查工作流

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

提交仓库 做代码检查的

husky配置

首先打开git bash终端

  1. 初始化git仓库,执行git init即可
pnpm dlx husky-init && pnpm install 
  1. 初始化husky工具配置, 执行 pnpm dlx husky-init && pnpm install 即可typicode.github.io/husky/
  2. 修改.husky/pre-commit文件
npm test //删掉这个
pnpm lint // 换成这个,一次性对整个项目中所有文件进行校验,并自动修复
  1. 然后需要校验,就在bash终端pnpm lint即可
  2. 提交git add .
  3. git commit -m '初始化提交'
    • 如果有错误,这里提交就会报错

但是问题是:

  • pnpm lint是全量检查,耗时问题,历史问题
  • 所以还需要学习 暂存区eslint校验👇

暂存区eslint校验

相当于只对自己写的代码进行校验 ( 解决”历史问题” 也就是只对新添加的代码进行校验,原来就有的不会)

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

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

目录调整

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

主要是以下工作:

  1. 删除一些初始化的默认文件
  2. 删改剩余代码内容
  3. 新增调整我们需要的目录结构
  4. 拷贝全局样式和图片,安装预处理器支持
    • 安装sass预处理器
    • pnpm add sass -D

  1. 删除文件

  2. 修改内容

src/router/index.js

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

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

export default router

src/App.vue

<script setup></script>

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

<style scoped></style>

src/main.js

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 中引入
import '@/assets/main.scss'
  • 这里引入还需要再bash终端再安装一个依赖
pnpm add sass -D
  1. 然后陪你pnpm dev重新启动即可

vue-router4 路由解析

跟原来相比,变成了按需导入

  1. 创建路由实例由createRouter实现

  2. 路由模式

    • history 模式使用 createWebHistory()
    • hash模式使用createWebHashHistory()
    • 参数是基础路径,默认/ 基础代码解析

Vue3大事件管理系统
index.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


  • 跳转路由操作

Vue3大事件管理系统
app.vue

<script setup>
// 在 Vue3 CompositionAPI中
// 1. 获取路由对象 router useRouter
//    const router = useRouter()
// 2. 获取路由参数 route  useRoute
//    const route = useRoute()
import { useRoute,useRouter } from 'vue-router'
const router = useRouter() // 获取路由,一个大的路由信息对象
const route = useRoute() // 路由参数
const goList = () => {
	router.push('/list')
  console.log(router, route)
}

</script>
<template>
    <button @click="golist">跳列表页</button>
</template>
  • import.meta.env.BASE_URL 是Vite 环境变量:cn.vitejs.dev/guide/env-a…
    • 写了这个后需要在vite.config.js里配,写一个配置项base:'/自己写'

总之就是👇:

  • 创建一个路由实例,路由模式为history模式
  • 基础地址为vite.config.js中的base配置的值
  • 默认为/,环境变量地址为👆
    Vue3大事件管理系统

引入 Element Plus 组件库

按需引入Element Plus

项目不是很复杂,所以直接按需引入就可以

  1. 安装 pnpm add element-plus

  2. 配置按需导入

...
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. 直接使用组件(在.vue文件中,如果是js文件仍需再导入一下)(配置完后就可以在项目的任意位置去使用组件了,不用额外导入什么了

而且components下面的组件也可以直接用,当标签用

<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>

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

Vue3大事件管理系统
官方文档:prazdevs.github.io/pinia-plugi…

步骤:

  1. bash终端 安装 持久化 插件 pinia-plugin-persistedstate
pnpm add pinia-plugin-persistedstate -D
  1. 安装完插件后就可以在main.js中使用
import persist from 'pinia-plugin-persistedstate'
...
app.use(createPinia().use(persist))
  1. 配置 stores/user.js
import { defineStore } from 'pinia'
import { ref } from 'vue'

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

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

Pinia仓库统一管理

pinia 独立维护

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

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

    • 需要将main.js当中跟pinia相关的内容进行提取(直接剪切)
    • 提取到stores下面的index.js中

stores下面的index.js

import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'

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

export default pinia //导出(这里导出后,就可以在main.js里导入了)

// 完整写法👇
// import { useUserStore } from './modules/user'
// export { useUserStore }

// import { useCountStore } from './modules/counter'
// export { useCountStore }

// 简化版👇在这里暴露出module里的方法
export * from './modules/user'
export * from './modules/counter'

main.js

import pinia from '@/stores/index'
app.use(pinia)

仓库 统一导出

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

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

index.js

....
// 完整写法👇
// import { useUserStore } from './modules/user'
// export { useUserStore }

// import { useCountStore } from './modules/counter'
// export { useCountStore }

// 简化版👇在这里暴露出module里的方法
export * from './modules/user'
export * from './modules/counter'

counter.js

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

// 数字计数器模块
export const useCountStore = defineStore('big-count', () => {
  const count = ref(100)
  const add = (n) => {
    count.value += n
  }
  return {
    count,
    add
  }
})

App.vue

<script setup>
...
import { useRoute,useRouter } from 'vue-router'
import { useUserStore, useCountStore } from '@/stores'👈
...

const userStore = useUserStore()
const countStore = useCountStore()
</script>

<template>
  <div>
    woshiApp
    <p>{{ userStore.token }}</p>
    <el-button @click="userStore.setToken('Bearer uiasfghdukashfk')">
      登录
    </el-button>
    <el-button @click="userStore.removeToken()">退出</el-button>

    <hr />
    {{ countStore.count }}👈
    <el-button @click="countStore.add(2)">加法</el-button>👈
  </div>
</template>

<style scoped></style>

数据交互——请求工具设计

Vue3大事件管理系统

创建 axios 实例

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

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

步骤

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

    利用 axios.create 创建一个自定义的 axios 来使用 www.axios-js.com/zh-cn/docs/…

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

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

// 请求拦截器
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错误
    // 错误的特殊情况 => 401说明权限不足 或 token过期 => 所以这里需要拦截到登录
    ...
    
    // 错误的默认情况 => 只要给提示就行
    ...
    return Promise.reject(err)
  }
)

export default instance

完整

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 }

整体路由设计

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

比如分析——>登录和注册的页面,采用的是切组件,而不是切路由(直接v-if切换的)

分析路由规则:

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

Vue3大事件管理系统

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 二级路由

Vue3大事件管理系统

新建完组件后,配规则

router/index.js

...
 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')
        }
      ]
    }
  ]
  ...

<router-view></router-view>

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

功能需求说明

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

Vue3大事件管理系统

步骤

app.vue只需要留一个路由出口即可

<script setup></script>

<template>
  <div>
    <!-- app.vue只需要留一个路由出口即可 -->
    <router-view></router-view>
  </div>
</template>

<style scoped></style>

登录页

  1. 安装 element-plus 图标库(在powershell终端上安装)
pnpm i @element-plus/icons-vue
  1. 静态结构准备
<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref } from 'vue'
const isRegister = ref(false)
</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去具体分所占多少份
    • offset为左外边距margin-left
  • el-form 整个表单元素
    • el-form-item 表单的一行(也就是一个表单域)
      • 表单域彼此独立 互不影响
      • el-input 表单元素(输入框)
      • :prefix-icon="..." 配的是图标(上面已经安装过图标库插件了)
        Vue3大事件管理系统

注册页

…需求

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

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

…校验

四个部分缺一不可

Vue3大事件管理系统

  • 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 配置生效的是哪个校验规则


整个表单的校验规则

  • 非空校验:required:true message 消息提示 trigger触发校验时机 blur change

  • 长度校验 min:xx max:xx

  • 正则校验 pattern:正则规则, \S 非空字符

  • 自定义校验 validator =>自己写逻辑校验(校验函数)(一般用于密码的二次验证)

    • validator : (rule,value,callback)

    • rule 当前校验相关的信息

    • value 当前校验表单元素 目前的表单值

    • callback 无论成功还是失败,都需要callback回调

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

四个部分

1.model 属性绑定 form 数据对象

//整个用于提交的form数据对象
const formModel = ref({
username: '',
password: '',
repassword: ''
})
<el-form :model="formModel" >
  1. rules 配置校验规则
// 整个表单的校验规则
//要跟上面form那的字段统一
//规则这里是数组,表示可以配置很多规则
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'
}
]
}
    1. v-model 和 prop 绑定
<el-form-item prop="username">
<el-input
v-model="formModel.username"
:prefix-icon="User"
placeholder="请输入用户名"
></el-input>
</el-form-item>
... 
(其他两个同理)

…点击注册前的预校验

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

Vue3大事件管理系统

  1. 通过 ref 获取到 表单组件(想校验,那就先拿到form)
const form = ref()
<el-form ref="form">
  1. 注册之前进行校验
<el-button
@click="register"
class="button"
type="primary"
auto-insert-space
>
注册
</el-button>
//为什么用异步,因为validate返回的是promise
const register = async () => {
await form.value.validate()
console.log('开始注册请求')
}

…封装 api 实现注册功能

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

  1. 新建 api/user.js 封装
import request from '@/utils/request'
//注册接口
//直接解构了
export const userRegisterService = ({ username, password, repassword }) =>
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)
ElMessage.success('注册成功')
// 切换到登录
isRegister.value = false
}
  1. eslintrc 中声明全局变量名, 解决 ElMessage 报错问题(无需校验,直接使用)
  • 为什么会报错
  • 因为虽然你之前装了按需导入的插件,但是vscode的eslint不知道,所以虽然可以正常使用,但是会在写代码的页面报错
module.exports = {
...
globals: {
ElMessage: 'readonly',
ElMessageBox: 'readonly',
ElLoading: 'readonly'
}
}

原文链接:https://juejin.cn/post/7334311273339912207 作者:phy_winter

(0)
上一篇 2024年2月17日 上午11:04
下一篇 2024年2月17日 上午11:14

相关推荐

发表回复

登录后才能评论