一、Pinia
(一)手动添加Piaia到Vue项目
1.安装Pinia:使用包管理器进行安装,在项目目录下运行 npm install pinia 或 yarn add pinia ,为项目引入Pinia状态管理库。
2.创建Pinia实例:在项目的JavaScript代码中,通常在 main.js 里创建Pinia实例。先从 pinia 库中导入 createPinia 函数,再调用该函数生成Pinia实例。这一实例用于管理整个项目的状态。
3.挂载Pinia实例到Vue应用:创建好Pinia实例后,通过 app.use(pinia) 方法将其挂载到Vue应用上,确保整个Vue项目都能使用Pinia进行状态管理。
4.使用注意事项:Pinia主要用于在Vue项目中进行状态管理,它能集中管理组件间共享的数据,提升开发效率和代码的可维护性。在使用时,要注意正确配置和引入相关依赖,按照规范的方式创建和使用状态、getters、actions等。
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'// 创建Pinia实例
const pinia = createPinia()
const app = createApp(App)// 挂载Pinia实例到Vue应用
app.use(pinia)
app.mount('#app')
(二)Pinia基本语法
1.在Pinia中,通过 defineStore 函数来定义store。 defineStore 接收两个参数,第一个是store的唯一ID,第二个是一个配置对象。
import { defineStore } from 'pinia'// 使用defineStore定义一个名为counterStore的store
const useCounterStore = defineStore('counter', {state: () => {return {count: 0}},getters: {doubleCount: (state) => state.count * 2},actions: {increment() {this.count++}}
})
2.在组件中使用定义好的store,首先要引入对应的 useStore 函数,然后调用该函数获取store实例,进而访问和修改store中的状态、调用getters和actions。
<template><div><p>Count: {{ counterStore.count }}</p><p>Double Count: {{ counterStore.doubleCount }}</p><button @click="counterStore.increment">Increment</button></div>
</template><script setup>
import { useCounterStore } from './stores/counterStore.js'const counterStore = useCounterStore()
</script>
3.state 是一个函数,返回一个对象,这个对象中的属性就是store的状态。状态是响应式的,当状态发生变化时,依赖它的组件会自动更新。
Getters(计算属性)
getters 用于对store中的状态进行计算和处理,类似于Vue组件中的计算属性。它接收 state 作为参数,可以返回一个计算后的值。
Actions(操作)
actions 用于定义可以修改状态的方法,也可以进行异步操作。在 actions 中可以通过 this 访问store的状态和其他方法。
(三)Pinia-action异步写法
1.action不仅能同步修改状态,还可处理异步任务。通过在action中使用 async/await 语法,能让代码以更简洁、直观的方式处理异步操作,避免复杂的回调地狱。
2.与同步action的区别:同步action即时执行并修改状态,而异步action在异步操作完成后才更新状态。并且,异步action需处理异步操作可能出现的错误,增强代码的健壮性。
3.错误处理:使用 try...catch 块捕获异步操作中的错误,可在捕获错误后,根据不同错误类型进行相应处理
import { defineStore } from 'pinia'
// 模拟一个异步请求函数
const fetchData = () => {return new Promise((resolve, reject) => {setTimeout(() => {const success = Math.random() > 0.5;if (success) {resolve({ data: '异步请求成功的数据' });} else {reject(new Error('异步请求失败'));}}, 1000);});
}const useAsyncStore = defineStore('asyncStore', {state: () => ({asyncData: null,error: null}),actions: {async fetchAsyncData() {try {const response = await fetchData();this.asyncData = response.data;} catch (error) {this.error = error.message;}}}
});
在组件中使用这个带有异步action的store:
<template><div><button @click="fetchAsyncData">获取异步数据</button><div v-if="asyncData">数据: {{ asyncData }}</div><div v-if="error" style="color: red">错误: {{ error }}</div></div>
</template><script setup>
import { useAsyncStore } from './stores/asyncStore.js';
const asyncStore = useAsyncStore();
const fetchAsyncData = async () => {await asyncStore.fetchAsyncData();
};
</script>
(四)Pinia-storeToRefs方法
1. storeToRefs 是Pinia提供的工具函数,用于将store中的状态转换为响应式引用(refs)。在解构store状态时,能保持状态的响应性,避免丢失响应式导致组件无法随状态变化自动更新。
2.原理:它会遍历store的状态对象,为每个属性创建一个对应的ref对象,将这些ref对象包装在一个新对象中返回。在组件中解构这个返回对象,就能得到与store状态关联且保持响应性的ref。
3.与直接解构对比:直接解构store状态,如 const { count } = counterStore , count 失去响应性;而使用 storeToRefs ,即 const { count } = storeToRefs(counterStore) , count 是响应式的,组件能感知其变化并重新渲染。
// 定义store
import { defineStore } from 'pinia'
const useCounterStore = defineStore('counter', {state: () => ({count: 0}),actions: {increment() {this.count++}}
})<!-- 组件中使用storeToRefs -->
<template><div><p>Count: {{ count }}</p><button @click="counterStore.increment">Increment</button></div>
</template><script setup>
import { useCounterStore, storeToRefs } from './stores/counterStore.js'
const counterStore = useCounterStore()
const { count } = storeToRefs(counterStore)
</script>
(五)Pinia持久化
1.Pinia持久化是指将Pinia store中的状态数据保存下来,在页面刷新、关闭浏览器再打开等情况下,数据依然存在,不会丢失。这样可以提升用户体验,保持应用状态的连贯性。
2.实现方式:通常借助插件来实现,如 pinia-plugin-persistedstate 插件。该插件能将store状态存储在浏览器的本地存储(Local Storage)、会话存储(Session Storage)或其他存储机制中。
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import App from './App.vue'const pinia = createPinia()
// 使用插件
pinia.use(piniaPluginPersistedstate)const app = createApp(App)
app.use(pinia)
app.mount('#app')
import { defineStore } from 'pinia'const useCounterStore = defineStore('counter', {state: () => ({count: 0}),// 开启持久化,默认存储在本地存储persist: true
})
二、Vue3项目大事件
(一)项目介绍和pnpm创建项目
1.pnpm是一种快速、节省磁盘空间的包管理器。相比npm和yarn,它利用硬链接和内容寻址存储机制,能更高效地管理项目依赖,减少磁盘占用,提升安装速度,并且在多项目开发时能共享依赖,降低维护成本。
2.使用pnpm创建项目:需先确保已安装pnpm,可在官网下载对应安装包完成安装。在命令行中,使用 pnpm create vue@latest 命令来创建Vue项目。创建过程中,会提示设置项目名称、选择包管理器(此时默认选择pnpm)、是否使用TypeScript、是否安装Vue Router和Pinia等,可根据项目需求进行选择。
3.创建Vue项目:在命令行输入以下命令:
bash
pnpm create vue@latest
4.根据提示依次输入:
项目名称(例如 big-events-project )。
选择包管理器时,默认选择 pnpm 。
对于是否使用TypeScript、是否安装Vue Router和Pinia等问题,按需求输入 y 或 n 进行选择。
例如,若希望项目使用TypeScript且安装Vue Router和Pinia,依次选择 y 确认。 安装完成后,进入项目目录:
bash
cd big-events-project
(二)ESlint配合Prettier完成代码风格配置
ESlint与Prettier的作用
1. ESlint:主要用于发现并报告JavaScript代码中的问题,确保代码符合一定的规范和最佳实践,还能检测潜在的错误,提升代码质量。
2.Prettier:专注于代码格式化,统一代码风格,自动调整代码的排版,如缩进、换行、空格等,使团队成员编写的代码格式一致,增强代码可读性。
3.两者配合的优势:ESlint侧重代码质量和规范检查,Prettier负责格式化代码,两者结合能在保证代码质量的同时,让代码拥有统一美观的风格,减少因代码风格不一致引发的冲突,提高开发效率。
配置步骤
1.安装相关依赖:包括ESlint、Prettier以及它们的Vue插件和相关配置文件。
2.初始化ESlint配置:使用命令生成基础配置文件,根据项目需求修改规则。
3.配置Prettier:创建配置文件,设置格式化规则,让其与ESlint协同工作。
4.解决冲突:由于两者对代码的处理方式有重叠,需配置插件解决潜在冲突,确保代码检查和格式化顺利进行。
5.安装依赖:在项目根目录下执行命令:
bash
pnpm add eslint eslint-plugin-vue @vue/eslint-config-prettier @vue/eslint-config-standard prettier eslint-plugin-prettier eslint-plugin-standard -D
初始化ESlint配置:运行命令生成配置文件,按提示选择配置选项:
bash
npx eslint --init
创建Prettier配置文件:在项目根目录创建 .prettierrc.json 文件,添加如下配置:
json
{"semi": true,"singleQuote": true,"trailingComma": "es5"
}
配置ESlint与Prettier协同工作:在 .eslintrc.js 文件中,添加或修改配置:
module.exports = {// 其他配置项...extends: ['plugin:vue/vue3-essential', '@vue/standard', '@vue/prettier']
};
(三)基于husky的代码检查工作流程
1.husky是一个Git钩子工具,它允许开发者在Git操作(如commit、push等)的特定阶段执行自定义脚本。在Vue3大事件项目中,利用husky可实现自动化的代码检查,保证提交到仓库的代码质量,避免低质量代码进入版本库。
2.基于husky的代码检查工作流原理:在项目中配置husky后,当执行Git操作时,husky会检测是否有对应的钩子脚本。若有,就执行该脚本,例如在 pre-commit 钩子中,可以运行ESlint等代码检查工具,检查暂存区的代码是否符合规范,若不符合则阻止提交,开发者需修改代码后再次提交。
配置步骤
1.安装husky:在项目中使用包管理器安装husky,它会在项目中创建必要的文件和目录结构,用于管理钩子脚本。
2.的钩子文件模板。
3.添加钩子脚本:在husky生成的钩子文件中,添加实际执行代码检查的脚本命令,通常是运行ESlint检查暂存区文件的命令。
安装husky:在项目根目录下,使用pnpm(若项目使用pnpm管理依赖)安装husky,命令如下:
bash
pnpm add husky -D
激活husky:运行以下命令激活husky,它会在项目根目录下创建 .husky 目录,并在其中生成一些初始文件:
bash
npx husky install
添加 pre-commit 钩子脚本:在 .husky 目录下找到 pre-commit 文件(若不存在可手动创建),添加以下内容:
bash
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"pnpm lint
假设项目中 pnpm lint 命令已配置为运行ESlint进行代码检查。这样,每次执行 git commit 时,husky会先运行 pnpm lint 命令检查代码,若代码检查不通过,提交会被阻止。
(四)调整目录
如果在某个组件(如 Home.vue )中原来引用 user.js 的方式是 import { getUserInfo } from '@/user.js'; ,移动文件后需要更新为 import { getUserInfo } from '@/api/user.js';
<!-- Home.vue -->
<template><div><!-- 组件内容 --></div>
</template><script setup>
// 更新前引用路径
// import { getUserInfo } from '@/user.js';
// 更新后引用路径
import { getUserInfo } from '@/api/user.js';
// 后续使用getUserInfo函数的代码
</script>
(五)VueRouter4路由语法解析
1.基本概念:VueRouter4是Vue.js官方的路由管理器,用于构建单页面应用(SPA)的路由系统。它能让页面在不刷新的情况下实现不同视图间的切换,提升用户体验。在Vue3大事件项目里,借助VueRouter4实现页面导航、组件展示切换等功能。
路由定义与配置
1. 创建路由实例:通过 createRouter 函数创建路由实例,该函数接收一个配置对象,包含路由模式(如 history 模式用于去除URL中的 # )、路由规则数组等关键信息。
2.定义路由规则:在路由规则数组中,每个对象代表一条路由规则。包含 path (路由路径)、 name (路由名称,方便引用)、 component (对应路径要渲染的组件)等属性。例如,定义首页路由, path 设为 / , component 指定为首页组件。
路由导航
1.声明式导航:在模板中使用 <router-link> 组件进行导航。通过设置 to 属性指定目标路由的路径或名称,点击该组件会触发路由切换,如 <router-link to="/home">首页</router-link> 。
2.编程式导航:在JavaScript代码中使用 router.push 或 router.replace 方法进行导航。 router.push 会向历史记录中添加一条新记录, router.replace 则替换当前历史记录,常用于登录成功后跳转到特定页面等场景。
动态路由
在路由路径中使用冒号( : )定义动态参数。比如 /user/:id ,其中 :id 就是动态参数,可用于根据不同用户ID展示对应信息。在组件中通过 $route.params 获取动态参数的值。
嵌套路由
用于构建具有层级结构的页面布局,在父路由的 children 属性中定义子路由规则。每个子路由同样包含 path 、 name 、 component 等属性,子路由的路径是相对于父路由的。
代码示例
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import UserDetail from '@/views/UserDetail.vue'const router = createRouter({history: createWebHistory(),routes: [{path: '/',name: 'Home',component: Home},{path: '/user/:id',name: 'UserDetail',component: UserDetail},{path: '/parent',name: 'Parent',component: () => import('@/views/Parent.vue'),children: [{path: 'child',name: 'Child',component: () => import('@/views/Child.vue')}]}]
})export default router
<!-- 在模板中使用声明式导航 -->
<template><div><router-link to="/">首页</router-link><router-link to="/user/123">用户详情</router-link><router-link to="/parent/child">子页面</router-link><router-view></router-view></div>
</template>
// 在组件中使用编程式导航
import { useRouter } from 'vue-router'export default {setup() {const router = useRouter()const goToUserDetail = () => {router.push('/user/456')}return {goToUserDetail}}
}
// 在UserDetail.vue组件中获取动态参数
import { useRoute } from 'vue-router'export default {setup() {const route = useRoute()const userId = route.params.idreturn {userId}}
}
<!-- 在组件模板中使用编程式导航对应的方法 -->
<template><button @click="goToUserDetail">跳转到用户详情</button>
</template>
(六)ElementPlus按需引入
ElementPlus是基于Vue 3的桌面端组件库。在项目中按需引入,而非全部引入,能有效减少项目体积,提升加载速度。按需引入借助 unplugin-vue-components 和 unplugin-auto-import 插件实现,它们可自动导入所需组件和函数,避免手动逐个引入的繁琐操作。
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
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: [vue(),AutoImport({resolvers: [ElementPlusResolver()]}),Components({resolvers: [ElementPlusResolver()]})]
})
(七)Pinia构建用户仓库和持久化
使用Pinia构建用户仓库,用于集中管理与用户相关的状态数据,如用户登录信息、权限等。通过持久化存储用户仓库数据,可在页面刷新或关闭后重新打开时,保留用户状态,提升用户体验。持久化借助 pinia-plugin-persistedstate 插件实现。
import { defineStore } from 'pinia'const useUserStore = defineStore('user', {state: () => ({userInfo: null,token: ''}),actions: {setUserInfo(info) {this.userInfo = info},setToken(token) {this.token = token}},persist: true
})export default useUserStore
在组件中使用用户仓库:在组件的 <script setup> 中
import { useUserStore } from '@/stores/user.js'const userStore = useUserStore()
const login = () => {const newUserInfo = { name: 'test', age: 18 }const newToken = '123456'userStore.setUserInfo(newUserInfo)userStore.setToken(newToken)
}
(八)数据交互--请求工具设计
// 创建request.js文件
import axios from 'axios'// 创建axios实例
const request = axios.create({baseURL: 'https://api.example.com', // 基础URLtimeout: 5000 // 超时时间
})// 请求拦截器
request.interceptors.request.use(config => {// 在发送请求前做些什么,如添加tokenconst token = localStorage.getItem('token')if (token) {config.headers['Authorization'] = `Bearer ${token}`}return config
}, error => {return Promise.reject(error)
})// 响应拦截器
request.interceptors.response.use(response => {return response.data
}, error => {// 处理响应错误,如统一错误提示console.error('请求错误', error)return Promise.reject(error)
})export default request
(九)路由的设计和配置
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import Login from '@/views/Login.vue'
import Dashboard from '@/views/Dashboard.vue'const router = createRouter({history: createWebHistory(),routes: [{path: '/',name: 'Home',component: Home},{path: '/login',name: 'Login',component: Login},{path: '/dashboard',name: 'Dashboard',component: Dashboard,meta: { requiresAuth: true } // 需要登录才能访问}]
})// 全局前置守卫
router.beforeEach((to, from, next) => {const isLoggedIn = localStorage.getItem('token')if (to.meta.requiresAuth &&!isLoggedIn) {next('/login')} else {next()}
})export default router
三、注册登录
(一)静态结构--基本切换
搭建注册登录页面的静态结构,使用HTML和CSS构建页面布局,包含用户名、密码输入框,注册/登录按钮等元素。利用Vue的动态组件和状态管理实现注册和登录页面的基本切换功能,通过一个标志变量控制显示哪个页面,增强用户操作的便捷性。
<template><div><button @click="showLogin = true">登录</button><button @click="showLogin = false">注册</button><component :is="showLogin? 'LoginComponent' : 'RegisterComponent'"></component></div>
</template><script setup>
import LoginComponent from '@/components/LoginComponent.vue'
import RegisterComponent from '@/components/RegisterComponent.vue'
import { ref } from 'vue'const showLogin = ref(true)
</script>
<!-- LoginComponent.vue -->
<template><div><h2>登录</h2><input type="text" placeholder="用户名"><input type="password" placeholder="密码"><button>登录</button></div>
</template>
<!-- RegisterComponent.vue -->
<template><div><h2>注册</h2><input type="text" placeholder="用户名"><input type="password" placeholder="密码"><input type="password" placeholder="确认密码"><button>注册</button></div>
</template>
(二)表单校验
为确保用户输入数据的准确性和合法性,对注册登录表单进行校验。使用 vee-validate 库,定义校验规则,如用户名长度、密码强度、确认密码一致性等。在表单提交时,根据校验结果决定是否允许提交,同时在页面上实时显示校验错误信息,引导用户正确输入。
# 安装vee-validate
npm install vee-validate@next
<template><div><h2>注册</h2><Form @submit="onSubmit"><Field name="username" :rules="['required', 'alpha_num', 'between:3,20']"><template #default="{ field }"><input v-bind="field" type="text" placeholder="用户名"><ErrorMessage name="username" /></template></Field><Field name="password" :rules="['required', 'length:6,20']"><template #default="{ field }"><input v-bind="field" type="password" placeholder="密码"><ErrorMessage name="password" /></template></Field><Field name="confirmPassword" :rules="['required', 'confirmed:password']"><template #default="{ field }"><input v-bind="field" type="password" placeholder="确认密码"><ErrorMessage name="confirmPassword" /></template></Field><button type="submit">注册</button></Form></div>
</template><script setup>
import { Form, Field, ErrorMessage } from 'vee-validate'
import { ref } from 'vue'const onSubmit = (values) => {console.log('提交的数据', values)
}
</script>
四、注册预校验--封装api实现注册功能
在注册功能中,进行预校验,如检查用户名是否已存在,提高注册的成功率。封装注册API,将注册请求的逻辑封装在一个函数中,方便在不同组件中调用。在注册时,调用封装的API,处理注册成功或失败的情况,如保存用户信息、提示用户注册结果。
// api/user.js
import request from '@/utils/request'export const register = (data) => {return request.post('/register', data)
}<template><div><h2>注册</h2><Form @submit="handleSubmit"><Field name="username" :rules="['required', 'alpha_num', 'between:3,20']"><template #default="{ field }"><input v-bind="field" type="text" placeholder="用户名"><ErrorMessage name="username" /></template></Field><Field name="password" :rules="['required', 'length:6,20']"><template #default="{ field }"><input v-bind="field" type="password" placeholder="密码"><ErrorMessage name="password" /></template></Field><Field name="confirmPassword" :rules="['required', 'confirmed:password']"><template #default="{ field }"><input v-bind="field" type="password" placeholder="确认密码"><ErrorMessage name="confirmPassword" /></template></Field><button type="submit">注册</button></Form></div>
</template><script setup>
import { Form, Field, ErrorMessage } from 'vee-validate'
import { register } from '@/api/user.js'
import { ref } from 'vue'
import { useRouter } from 'vue-router'const router = useRouter()
const handleSubmit = async (values) => {try {const response = await register(values)if (response.success) {// 注册成功,保存用户信息,跳转到首页localStorage.setItem('token', response.data.token)router.push('/')} else {console.error('注册失败', response.message)}} catch (error) {console.error('注册请求错误', error)}
}
</script>
五、登录校验和登录请求
登录校验用于确保用户输入的登录信息准确无误,常见校验包括用户名或邮箱格式、密码长度等。登录请求则是将用户输入信息发送到后端服务器进行验证,验证通过后获取相关授权信息(如token),实现用户登录功能。校验和请求功能需协同,校验不通过时阻止请求发送,保障数据交互准确性。
<template><div><h2>登录</h2><form @submit.prevent="handleLogin"><label for="username">用户名:</label><input type="text" id="username" v-model="formData.username" required><label for="password">密码:</label><input type="password" id="password" v-model="formData.password" required minlength="6"><button type="submit">登录</button></form></div>
</template><script setup>
import { ref } from 'vue';
import { login } from '@/api/user.js';const formData = ref({username: '',password: ''
});const handleLogin = async () => {if (!formData.value.username ||!formData.value.password) {alert('用户名和密码不能为空');return;}try {const response = await login(formData.value);if (response.success) {localStorage.setItem('token', response.data.token);// 登录成功,跳转到首页} else {alert('登录失败,请检查用户名和密码');}} catch (error) {console.error('登录请求出错', error);}
};
</script>// api/user.js
import request from '@/utils/request';export const login = (data) => {return request.post('/login', data);
};
六、首页
(一)layout架子分析和登录访问拦截
首页 layout 架子分析旨在确定首页整体布局结构,包括导航栏、侧边栏、内容区域等组件的划分与布局方式。登录访问拦截是利用路由守卫机制,判断用户是否登录。未登录用户尝试访问首页等受保护页面时,将其重定向到登录页面,保障页面访问安全性,防止非法访问。
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from '@/views/Home.vue';
import Login from '@/views/Login.vue';const router = createRouter({history: createWebHistory(),routes: [{path: '/',name: 'Home',component: Home,meta: { requiresAuth: true }},{path: '/login',name: 'Login',component: Login}]
});router.beforeEach((to, from, next) => {const token = localStorage.getItem('token');if (to.meta.requiresAuth &&!token) {next('/login');} else {next();}
});export default router;<!-- Home.vue -->
<template><div class="home-layout"><header>导航栏</header><aside>侧边栏</aside><main>内容区域</main></div>
</template><script setup>
// 首页组件逻辑
</script><style scoped>
.home-layout {display: flex;
}
header {width: 100%;height: 60px;background-color: #333;color: white;text-align: center;
}
aside {width: 200px;background-color: #f0f0f0;
}
main {flex: 1;background-color: #fff;
}
</style>
(二)用户基本信息渲染和退出
用户基本信息渲染是从存储(如本地存储、后端接口获取)中读取用户相关信息,如用户名、头像等,并在首页展示。退出功能则是清除用户登录状态相关信息(如token),使用户登出系统,同时更新页面状态,如隐藏用户信息展示区域、跳转到登录页面等
<template><div class="home-layout"><header><div v-if="userInfo"><span>{{ userInfo.username }}</span><button @click="handleLogout">退出</button></div></header><aside>侧边栏</aside><main>内容区域</main></div>
</template><script setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router';const userInfo = ref(JSON.parse(localStorage.getItem('userInfo')));
const router = useRouter();const handleLogout = () => {localStorage.removeItem('token');localStorage.removeItem('userInfo');userInfo.value = null;router.push('/login');
};
</script>