一、创建项目
1.pnpm create vue
执行cd Vue3-big-event-admin、pnpm install
运行项目用pnpm dev命令
2.ESlint配置代码风格
在.eslintrc.cjs文件中module.exports中添加以下属性:
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.json中修改:
eslint:规范
prettier:美观
3.基于husky的代码检查工作流
husky 是一个 git hooks 工具 ( git的钩子工具,可以在特定时机执行特定的命令 )
打开bash终端
初始化:终端中输入git init
在终端中输入
pnpm dlx husky-init && pnpm install
则会在项目中自动生成一个.husky文件夹
将.husky/pre-commit中npm test改为pnpm lint(我也不知道为啥要改)(这一步不重要,反正之后又会将pnpm lint改为pnpm lint-staged)
如果在终端中输入pnpm lint检查代码,这个是全量检查,有耗时问题、历史问题
lint-staged 配置:
pnpm i lint-staged -D
配置package.json
{
// ... 省略 ...
"lint-staged": {
"*.{js,ts,vue}": [
"eslint --fix"
]
}
}
{
"scripts": {
// ... 省略 ...
"lint-staged": "lint-staged"
}
}
将.husky/pre-commit中pnpm lint改为pnpm lint-staged
pnpm lint-staged只会检查暂存区新添加的代码,而不是像pnpm lint一样全量检查
4.目录调整
该删删该加加(api文件夹、utils文件夹(vant使用))
5.安装sass预处理器支持
6.使用element plus
(1)装包
# 选择一个你喜欢的包管理器
# NPM
$ npm install element-plus --save
# Yarn
$ yarn add element-plus
# pnpm
$ pnpm install element-plus
(2)按需导入(自动导入)
npm install -D unplugin-vue-components unplugin-auto-import
在vite.config.js中引入下列代码
// vite.config.ts
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'
export default defineConfig({
// ...
plugins: [
// ...
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
})
就可以直接使用element-plus中的组件了
另外,在component中定义的组件也可以直接使用
如果要使用图标,还要执行以下命令安装图标库:
pnpm i @element-plus/icons-vue
并在需要用的组件里导入:
import { User, Lock } from '@element-plus/icons-vue'
7.安装pinia持久化插件
8.实现pinia独立维护和仓库统一导出
独立维护:将关于仓库的核心代码全写在stores/index.js中
统一导出:将各个模块全导入到index中,并在index中导出,这样做的好处是,在其他组件中使用数据时,不管是哪个仓库的数据,都是从stores/index中导入
原本的stores文件夹:
现在的:
stores/index.js:
import { createPinia } from 'pinia' //创建状态管理库
import persist from 'pinia-plugin-persistedstate' //实现数据持久化
const pinia = createPinia().use(persist)
export default pinia
// import { useUserStore } from '@/stores/modules/user'
// export { useUserStore }
// import { useCountStore } from '@/stores/modules/count'
// export { useCountStore }
// 上面可以被下列替代掉
export * from '@/stores/modules/user'
export * from '@/stores/modules/counter'
main.js:
//···
import pinia from '@/stores/index'
const app = createApp(App)
app.use(pinia)
//···
modules/use.js:
import { defineStore } from 'pinia' //创建仓库
import { ref } from 'vue'
export const useUserStore = defineStore('user', () => {
const token = ref('Bear abcdefg')
const setToken = (newToken) => {
token.value = newToken
}
return {
token,
setToken
}
})
9.axios配置:创建axios实例、请求拦截器、响应拦截器
创建文件utils/request.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
这个项目具体的配置代码:
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 }
二、一些知识
1.关于路由
(1)创建路由
createRouter:创建路由
createWebHistory:默认,history模式,没有#
createWebHashHistory:hash模式,有#
import { createRouter, createWebHistory } from 'vue-router'
//createRouter({})对应的是vue2中的new vue()
const router = createRouter({
//createWebHistory:默认,history模式,没有#
//也有可选项createWebHashHistory
//括号中的内容默认是'/',表示的是路由前面加的东西,就是vite.config.js中的base配置项
history: createWebHistory(import.meta.env.BASE_URL),
routes: []
})
export default router
(3)route和router
在vue3的CompositionApi中
<script setup>
import { useRouter, useRoute } from 'vue-router'
//跳转页面等操作
const router = useRouter()
//路由信息
const route = useRoute()
const goList = () => {
// 路由跳转
router.push('list')
}
console.log(route)
</script>
三.项目实践
1.一些注意点
(1)关于api下的文件内部函数的命名应遵循一定的规则,如userRegisterService表示api/user.js文件中关于注册的请求。
(2)eslintrc.cjs 中声明全局变量名, 解决 ElMessage 报错问题
不然会因为ElMessage未声明但使用了而报错
module.exports = {
...
globals: {
ElMessage: 'readonly',
ElMessageBox: 'readonly',
ElLoading: 'readonly'
}
}
2.关于<el-form>的使用&注册登录功能
正则中:\S表示非空字符
/焦虑啊焦虑啊,不在焦虑中爆发,就在焦虑中灭亡/
(1)嵌套关系
<el-form>//这里写form表单中所有项双向绑定到的数据和校验规则是什么
<el-form-item>//这里写这一项的校验规则是哪个
<el-input>//这里写这一项的绑定的数据是哪个
</el-input>
</el-form-item>
</el-form>
(2)具体使用
<script setup>
const formModel = ref({
username: ''
})
const rules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 5, max: 10, message: '用户名必须是5-10位的字符', trigger: 'blur' },
{
pattern: /^\S{6,15}$/,
message: '密码必须是6-15位的非空字符',
trigger: 'blur'
},
{
validator: (rule, value, callback) => {
// 注意这里的函数,有三个参数
if (value !== formModel.value.password) {
callback(new Error('两次输入密码不一致!'))
} else {
callback()
}
},
trigger: 'blur'
}
]
}
const form=ref(null)
</script>
<el-form
:model="formModel"
:rules="rules"
ref="form"
>
<el-form-item prop="username">
<el-input
v-model="formModel.username"
:prefix-icon="User"
placeholder="请输入用户名"
></el-input>
</el-form-item>
</el-form>
(3)校验
非空、长度、正则、自定义
(4)预校验
<el-button
@click="register"
>
注册
</el-button>
const register = async () => {
// 这个方法是validate组件内部的方法,要在组件以外使用,就必须在组件内部声明。
// 而在element - plus官网中可查明validate方法是可以在外部使用的,并且是异步的,所以要使用async&await
await form.value.validate()
console.log('开始注册请求')
}
(5)登录
登录和注册绑定同一份数据和同一个校验规则,所以在切换登录或注册时,要重置绑定的数据。登录成功之后将token存在仓库中,并通过插件实现持久化。
3.<el-menu>&<el-menu-item>
(1)el-menu
有几个属性:active-text-color、background-color、:default-active="$route.path"、text-color
(2)el-menu-item
属性有:index
(3)el-sub-menu二级菜单
用具名插槽配置菜单名
(4)示例代码
<el-menu
active-text-color="#ffd04b"// 选中时文字颜色
background-color="#232323"// 背景色
:default-active="$route.path"// 默认(刚进入页面时选中的是哪一项)
text-color="#fff"// 未选中时文字颜色
router
>
<el-menu-item index="/article/manage">// <!--index值就表示选中时跳转至的路由-->
<el-icon>
<Promotion />
</el-icon>
<span>文章管理</span>
</el-menu-item>
<el-sub-menu index="/user">
<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-sub-menu>
</el-menu>
4.全局前置守卫
任何页面被访问之前要通过这里
在router/index.js文件中
语法:
router.beforeEach((to, from) => {
const userStore = useUserStore()
if (!userStore.token && to.path !== '/login') return '/login'
// 返回值:
// 1.没有返回(相当于返回undefined)或true:放行
// 2.返回false:回到之前的页面
// 3.返回路径或者对象:跳转至相应页面
// 如:'/loign'或{name:'login'}
})
5.下拉菜单
大概结构:
<el-dropdown @command="onCommand">
<!--这里是下拉之前展示出来的内容-->
<span>
下拉菜单
<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>
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}`)
}
}
用法示例:
<el-dropdown placement="bottom-end">
<span class="el-dropdown__box">
<el-avatar :src="userStore.user.user_pic || 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>