文章目录
一.项目介绍和相关配置
1.技术栈
- 使用Vue3组件式API
- 使用Pinia进行持久化处理
- 使用Element-plus作为组件库(提供表单校验,表格处理和组件封装等功能)
- 使用升级的包管理工具pnpm
- 使用ESLint+Prettier作为代码规范
- 使用husky(Git hook工具)作为代码提交的规范校验
- 使用VueRouter4作为路由设置和请求模块的设计
- 尝试使用AI(deepseek或chatGPT),向其提出明确的,描述准确的功能需求,让其返回可为我们所用的代码
2.项目介绍
在线演示:
https://fe-bigevent-web.itheima.net/login
接口文档:
https://apifox.com/apidoc/shared
基地址:
http://big-event-vue-apit.itheima.net/
视频地址:
https://www.bilibili.com/video/BV1HV4y1a7n4
3.安装pnpm并用其创建vue3项目
安装命令:npm install -g pnpm
创建项目:pnpm create vue
配置:
项目名:big-event-admin
包名:同上
是否添加TS:NO
是否添加JSX:No
是否添加VueRouter作为单页面组件的路由:Yes
是否添加:pinia:Yes
单元测试Vitest:No
测试的解决方案:No
是否添加ESLInt:Yes
是否添加Perttier:Yes//专门美化代码的格式化工具
踩坑:遇到了cmd或powershell无法正常显示无法显示配置界面的问题
可能原因是:由于版本兼容问题不能显示交互式界面
当前的解决办法是:先照着视频盲配,后续再解决
4.ESLint的配置代码风格和Prettier
- 说明:
ESLint在配置文件.eslintrc.cjs
中配置相关代码,负责对不规范代码报错的
perttier是专注代码格式化的插件,使代码更美观,是负责美化代码的
- 先把在vscode应用商店中下载的prettier插件禁用,再启用ESLint,然后在settings.json中设置:
//当保存时.,eslint自动修复错误:
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
//保存代码,不自动格式化
"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 解构的校验
// 💡 添加未定义变量错误提示,create-vue@3.6.3 关闭,这里加上是为了支持下一个章节演示。
'no-undef': 'error'
}
踩坑:我的Vue3项目中并不自动生成.eslintrc.cjs
文件
原因是这个项目太老了,现在创建vue3项目自动生成的是eslint.config.js
文件,配置如下:
//eslint.config.js
import { defineConfig, globalIgnores } from 'eslint/config'
import globals from 'globals'
import js from '@eslint/js'
import pluginVue from 'eslint-plugin-vue'
import pluginOxlint from 'eslint-plugin-oxlint'
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
export default defineConfig([
{
name: 'app/files-to-lint',
files: ['**/*.{js,mjs,jsx,vue}'],
},
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
// 基础配置
{
languageOptions: {
globals: {
...globals.browser,
},
},
},
// 自定义规则
{
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'
}
},
js.configs.recommended,
...pluginVue.configs['flat/essential'],
...pluginOxlint.configs['flat/recommended'],
skipFormatting,
])
5.基于husky配置代码检查工作流
什么是husky?
基于git的hook工具,理解为git里面的钩子,能在特性时机执行特定命令
场景需求
提交代码到仓库之前做一下检查,确保提交的代码都是规范的
做法
- 在项目根目录打开gitBash,初始化仓库:
git init
- 下载husky插件:
pnpm dlx husky-init$$pnpm install
- 修改
.husky/pre-commit
文件如下:
把:npm test
改为:pnpm lint
==>lint命令是默认在package.json中配置好的,一次性对项目中所有的文件进行校验,并自动修复不规范代码
但是这样的每次都对全局代码全部校验,效率很低也浪费性能,
后续应该优化为只对暂存区代码进行校验
如何优化为只对暂存区校验?
- 安装小插件lint-staged包:
pnpm i lint-staged -D
- 在package.json中配置lint-staged命令:
//package.json
{
"name": "my-big-event-admin",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore",
"lint:eslint": "eslint . --fix",
"lint-staged": "lint-staged",
"lint": "run-s lint:*",
"format": "prettier --write src/",
"prepare": "husky install"
},
"dependencies": {
"pinia": "^3.0.1",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
},
"devDependencies": {
"@eslint/js": "^9.22.0",
"@vitejs/plugin-vue": "^5.2.3",
"@vue/eslint-config-prettier": "^10.2.0",
"eslint": "^9.22.0",
"eslint-plugin-oxlint": "^0.16.0",
"eslint-plugin-vue": "~10.0.0",
"globals": "^16.0.0",
"husky": "^8.0.0",
"npm-run-all2": "^7.0.2",
"oxlint": "^0.16.0",
"prettier": "3.5.3",
"vite": "^6.2.4",
"vite-plugin-vue-devtools": "^7.7.2"
},
"lint-staged": {
"*.{js,ts,vue}": [
"eslint --fix"
]
}
}
- 最后再修改
.husky/pre-commit
文件
#npm init---不要了
#pnpm lint---不要了
pnpm lint-staged
6.调整Vue3项目的目录
除了代码规范,目录也同样需要规范,因为默认生成的目录结构不满足项目需求,需做一些自定义改动:
- 删除一些初始化的默认文件
assets,views,components,store等下的子文件(如:counter.js)
重置router/index.js中的routes数组(清空路由配置)
- 新增我们需要的目录结构
创建api(请求模块相关的封装),utils(工具函数相关的封装),这两个目录
- 拷贝全局样式和图片,安装预处理器
静态资源放到assets (没有静态文件)
下载sass:git bash:pnpm add sass -D (可以下载)
并在main.js中引入:import '@/assets/main.scss'(没有main.scss文件,不导入了)
- 初始化重要文件如下
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')
7.VueRouter4对比VueRouter2代码风格上有哪些不同?
vue-router4路由代码解析
之前的:
import VueRouter from "vue-router"
//初始化:vue-router3.x和vue2
const router=new VueRouter({
mode:'history',//去掉"#"
route:[]
})
export default router
现在的:
//按需导入
import {{createRouter,createWebHistory}} from 'vue-router'
//初始化:vue-router4.x和vue3
const router=new({
history:createWebHistory(import.meta.env.BASE_URL),//配置模式(可选值:createWebHistory,createHashHistory)
route:[]//配置路由规则
})
export default router
路由跳转有哪些变化?
template中,仍然兼容之前写法:$router.push('/')
script中,由于没有this,不能写成this.$router.push('/'),
所以要引入useRouter(),写法如下:
import {useRouter,useRoute} from 'vue-router'
const router=useRouter()//获取路由信息对象router
然后:router.push('/')
如何获取路由参数?
const route = useRoute()//获取当前路由参数数组route
8.引入element-plus组件库
这是element ui的升级版,可以用于vue3项目中
安装ElementPlus
- 安装命令:
pnpm add element-plus
安装插件unplugin-vue-components
和unplugin-auto-import
实现自动导入
- 安装命令:
npm install -D unplugin-vue-components unplugin-auto-import
- 在
vite.config.,js
中粘贴配置代码如下
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
AutoImport({
resolvers: [ElementPlusResolver()]
}),
Components({
resolvers: [ElementPlusResolver()]
})
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})
- 然后重启项目即可:
pnpm dev
- 效果:可以在项目的任意位置使用element-plus的组件,不需要import,直接用:
<el-button type="primary">Primary</el-button>
9.使用Pinia构建一个仓库并实现持久化
Pinia是Vuex的替代,在多组件共享数据的使用场景中用到,区别是取消了mutations和modules
9.1.创建一个用户仓库store/user.js
- 由于创建项目时选择了pinia,因此在main.js中自动配置好了,可以直接使用
//main.js
import {createPinia} from "pinia"
app.use(createPinia)
- 新建
store/user.js
import { defineStore } from 'pinia'//导入定义模块的方法
import { ref } from 'vue'
//用户模块需求:token,setToken,removeToken
export const useUserStore = defineStore('big-user', () => {
//定义数据
const token = ref('')
const setToken = (newToken) => {
token.value = newToken
}
const removeToken = () => {
token.value = null
}
return {
token,
setToken,
removeToken
}
})
- 在页面中调用
<script setup>
// 按需导入
import { useUserStore } from './stores/user';
// 调用,拿到仓库对象
const useStore=useUserStore();
</script>
<template>
<router-view></router-view>
<div>
<h3>{{useStore.token}}</h3>
<el-button @click='useStore.setToken("HELLO VUE3")'>登录</el-button>
<el-button @click='useStore.removeToken()' >退出</el-button>
</div>
</template>
此时数据,还不是持久化的,需要进一步优化
9.2.持久化处理
- 安装插件:`pnpm add pinia-plugin-persistedstate -D
- `在main.js中导入
import persist from 'pinia-plugin-persistedstate'
app.use(createPinia().use(persist))
- 配置stores/user.js的第三个参数
import { defineStore } from 'pinia'//导入定义模块的方法
import { ref } from 'vue'
//用户模块需求:token,setToken,removeToken
export const useUserStore = defineStore('big-user', () => {
//定义数据
const token = ref('')
const setToken = (newToken) => {
token.value = newToken
}
const removeToken = () => {
token.value = null
}
return {
token,
setToken,
removeToken
}
}, {//第三个参数
persist: true
})
- 效果:
9.3.pinia代码的统一管理
- 新建
stores/index.js
并把main.js中pinia相关代码提取出来
//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 { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import pinia from '@/stores/index'
const app = createApp(App)
app.use(router)
app.use(pinia)
app.mount('#app')
- 配置仓库的统一导入
//假设App.vue导入并使用了两个子模块
import {useUserStore} from "@/stores/modules/user"
import {useCounterStore} from "@/stores/modules/counter"
const useStore=useUserStore()
const countStore=useCounterStore()
/*如何实现仓库统一导出?
最终目的是写成import {useUserStore,useCounterStore} from "@/stores"
做法:用store/index.js作为唯一出口*/
//store/index.js
import { useUserStore } from './user'
export { useUserStore }
import { useCounterStore } from './counter'
export { useCounterStore }
//把上面的按需导入和导出优化如下:(导入直接不用写了)
export * from "./user"
export * from "./counter"
- 实现仓库的统一导出后的配置和调用代码如下:
//App.vue
import {useUserStore,useCounterStore} from "@/stores"//不用分开按需导入了
const useStore=useUserStore()
const countStore=useCounterStore()
<div>
<h3>{{useStore.token}}</h3>
<h3>{{countStore.count}}</h3>
<el-button @click='useStore.setToken("HELLO VUE3")'>登录</el-button>
<el-button @click='useStore.removeToken()' >退出</el-button>
<el-button @click='countStore.addCount()' >点击</el-button>
</div>
//store/index.js
export * from "./user"
export * from "./counter"
10.请求工具的设计(vue-router4风格)
把创建axios实例和请求拦截器,响应拦截器放在一个文件中(request.js
),并配置基地址,超时时间和携带token和业务失败处理,获取核心响应数据,401处理
- 安装axios:
pnpm add axios
- 新建
utils/request.js
,并复制官网代码然后配置如下:
import { useUserStore } from '@/stores/user'
import axios from 'axios'
import router from '@/router'
import { ElMessage } from 'element-plus'
//1.配置基地址
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) {//2.配置token
config.headers.Authorization = userStore.token
}
return config
},
(err) => Promise.reject(err)
)
// 响应拦截器:接收返回数据之前做些什么
instance.interceptors.response.use(
(res) => {
if (res.data.code === 0) {//4.获取核心响应数据
return res
}
//3.处理业务失败:给出错误提示并抛出错误
ElMessage({ message: res.data.message || '服务异常', type: 'error' })
return Promise.reject(res.data)
},
(err) => {
ElMessage({ message: err.response.data.message || '服务异常', type: 'error' })
console.log(err)
//5.错误的特殊情况:处理401错误(权限不足,token过期==>拦截)
if (err.response?.status === 401) {
router.push('/login')
}
return Promise.reject(err)
}
)
export default instance// 导出实例
export { baseURL }//导出基地址
二.创建路由文件和配置路由规则
项目主要由2个一级路由页面:
- 首页LayoutContainer
- 注册登录页LoginPage
组成,其中,首页里面嵌套了5个二级路由页面:
- 频道管理ActicleChannel
- 文章管理ArticleManage
- 个人详情UserProfile
- 更换头像UserAvatar
- 重置密码UserPassword
创建路由文件
配置路由规则
//router/index.js
//把首页下的文章管理页http://localhost:5173/article/manage设置为真正的首页
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
component: () => import('../views/layout/LayoutContainer.vue'),
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')
}, {
path: '/',
redirect: '/article/manage',
}
]
},
{
path: '/login',
name: 'login',
component: () => import('../views/login/LoginPage.vue')
}
]
})
export default router
*需要在App.vue和LayoutContainer.vue中写入<router-view></router-view>