vue3 外卖管理系统
-
Echarts 使用
-
账号模块交互
-
添加账号
-
个人中心的头像上传联动
-
修改密码
-
1.Echarts 使用
-
引入Echarts
pnpm install echarts
-
在项目中准备 dom节点 必须有宽高
<div class="chart" ref="echartDom"> </div>
-
实例化 echarts 对象 echarts.init(dom节点)
-
ecahrts实例对象.setOption(图形的配置对象)
import * as echarts from 'echarts' import {ref,onMounted} from 'vue' const echartDom = ref(null); //vue2:创建 create 挂载 mount 更新update 销毁 destory //vue3 创建 setup 挂载 onBeforeMount onMounted 更新 onBeforeUpdate 销毁 onUnmount const draw = () =>{ const myEchart = echarts.init(echartDom) const option = { } myEchart.setOption(option) } onMounted(()=>{ //生成echarts 图形 draw() })
-
home.vue
<template> <div class="home"> <div class="chart" ref="chart"></div> </div> </template> <script setup> import * as echarts from 'echarts' import { ref, onMounted } from 'vue' import dayjs from 'dayjs' const chart = ref(null) const draw = () => { const myEchart = echarts.init(chart.value); const option = { title: { text: 'Stacked Line' }, tooltip: { trigger: 'axis' }, legend: { data: ['Email', 'Union Ads', 'Video Ads', 'Direct', 'Search Engine'] }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, toolbox: { feature: { saveAsImage: {} } }, xAxis: { type: 'category', boundaryGap: false, data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] }, yAxis: { type: 'value' }, series: [ { name: 'Email', type: 'line', stack: 'Total', data: [120, 132, 101, 134, 90, 230, 210] }, { name: 'Union Ads', type: 'line', stack: 'Total', data: [220, 182, 191, 234, 290, 330, 310] }, { name: 'Video Ads', type: 'line', stack: 'Total', data: [150, 232, 201, 154, 190, 330, 410] }, { name: 'Direct', type: 'line', stack: 'Total', data: [320, 332, 301, 334, 390, 330, 320] }, { name: 'Search Engine', type: 'line', stack: 'Total', color:'#000', data: [820, 932, 901, 934, 1290, 1330, 1320] } ] }; myEchart.setOption(option) } onMounted(() => { draw() }) </script> <style scoped lang="scss"> .home { width: 100%; height: 100%; } .chart { width: 100%; height: 100%; } </style>
-
echarts 图 自适应 页面的宽高变化
通过监听页面的 宽高变化 改变
echarts 实例对象.resize() 可以自适应变化
import * as echarts from 'echarts' import { ref, onMounted } from 'vue' import dayjs from 'dayjs' // 获取dom const chart = ref(null) //echarts 实例对象 let myEchart = null; const draw = () => { myEchart = echarts.init(chart.value); const option = { title: { text: 'Stacked Line' }, tooltip: { trigger: 'axis' }, legend: { data: ['Email', 'Union Ads', 'Video Ads', 'Direct', 'Search Engine'] }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, toolbox: { feature: { saveAsImage: {} } }, xAxis: { type: 'category', boundaryGap: false, data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] }, yAxis: { type: 'value' }, series: [ { name: 'Email', type: 'line', stack: 'Total', data: [120, 132, 101, 134, 90, 230, 210] }, { name: 'Union Ads', type: 'line', stack: 'Total', data: [220, 182, 191, 234, 290, 330, 310] }, { name: 'Video Ads', type: 'line', stack: 'Total', data: [150, 232, 201, 154, 190, 330, 410] }, { name: 'Direct', type: 'line', stack: 'Total', data: [320, 332, 301, 334, 390, 330, 320] }, { name: 'Search Engine', type: 'line', stack: 'Total', color:'#000', data: [820, 932, 901, 934, 1290, 1330, 1320] } ] }; myEchart.setOption(option) } onMounted(() => { draw() }) // 监听页面的宽高变化 window.addEventListener('resize', () => { console.log('宽高发生了改变'); myEchart.resize() })
-
问题: 其他页面 仍然在监听 window resize 事件 并不断触发这个事件
解决思路:
在页面组件 被销毁的瞬间
onBeforeUnmount
销毁前 在自己的页面销毁的瞬间就执行了【推荐使用】
onUnmounted
销毁后 在自己的页面组件 已经销毁后(在其他的组件页面中) 进行执行需要将 window事件 进行取消监听
需要将 定时器 延时器 进行清除定时器
// 监听页面的宽高变化 const xxx = () => { console.log('宽高发生了改变'); myEchart.resize() } window.addEventListener('resize',xxx ) // 销毁页面 取消监听window事件 onBeforeUnmount(()=>{ window.removeEventListener('resize',xxx) })
2.个人中心操作
<template> <div> <h1>个人中心</h1> <div v-if="accountInfoData"> <div class="item"> <div class="text"> 管理员ID: </div> <div class="value"> {{ accountInfoData.id }} </div> </div> <div class="item"> <div class="text"> 账号: </div> <div class="value"> {{ accountInfoData.account }} </div> </div> <div class="item"> <div class="text"> 用户组: </div> <div class="value"> {{ accountInfoData.userGroup }} </div> </div> <div class="item"> <div class="text"> 创建时间: </div> <div class="value"> {{ accountInfoData.ctime }} </div> </div> <div class="avatar"> 管理员头像: </div> <el-upload class="avatar-uploader" :action="serverUrl+'/users/avatar_upload?id='+accountInfoData.id" :show-file-list="false" :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload" > <img v-if="accountInfoData.imgUrl" :src="serverUrl+accountInfoData.imgUrl" class="avatar" /> <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon> </el-upload> </div> </div> </template> <script setup> import {accountInfo} from '@/api/account' import {ref} from 'vue' import {serverUrl} from '@/utils/common' import { ElMessage } from 'element-plus' import { Plus } from '@element-plus/icons-vue' const accountInfoData = ref(null); const getAccountInfo = async () => { let {id} = JSON.parse(sessionStorage.getItem('user')); const res = await accountInfo({id}); accountInfoData.value = res.accountInfo } getAccountInfo() //上传之前 判断 检测用户上传的文件是否合法 // 合法 true // 不合法 false 并 提示用户文件的问题 const beforeAvatarUpload = (file) => { console.log(file); // 判断文件类型必须是 图片 if(!file.type.includes('image')){ ElMessage.error('文件类型必须是图片'); return false; } if(file.size >1*1024*1024){ ElMessage.error('文件大小不能超过 1M'); return false; } return true ; } // 修改用户头像成功后 // 重新获取用户的信息 const handleAvatarSuccess = () => { getAccountInfo() } </script>
3.个人中心 头像上传 和 顶部组件联动
头像上传成功后,刷新页面 useRouter().go(0) history.go(0) window.reload()
1.通过多组件 共享一个状态 pinia
在个人中心 页面修改了这个状态
在顶部组件 监听这个状态的变化,如果发生改变,顶部组件重新获取用户最新信息即可
通过多组件 共享用户的基本信息 状态 pinia
在个人中心 修改了头像,修改pinia中的用户信息
顶部组件 实时获取 用户状态 (登录页 也需要先修改一次用户信息)
刷新会丢失 用户数据 ---(pinia 数据持久化)
-
center.vue
const accountInfoData = ref(null); const store = userStore() const getAccountInfo = async () => { let {id} = JSON.parse(sessionStorage.getItem('user')); const res = await accountInfo({id}); accountInfoData.value = res.accountInfo } getAccountInfo() //上传之前 判断 检测用户上传的文件是否合法 // 合法 true // 不合法 false 并 提示用户文件的问题 const beforeAvatarUpload = (file) => { // 判断文件类型必须是 图片 if(!file.type.includes('image')){ ElMessage.error('文件类型必须是图片'); return false; } if(file.size >1*1024*1024){ ElMessage.error('文件大小不能超过 1M'); return false; } return true ; } // 修改用户头像成功后 // 重新获取用户的信息 const handleAvatarSuccess = () => { // router.go(0) getAccountInfo() // 修改poinia中状态 表示我已经修改了头像 store.changeUserInfoState() }
-
pinia userStore.js
import { defineStore } from 'pinia' //defineStore 函数 //参数1:模块名 保证整个状态管理中的唯一 //参数2:对象 包含核心配置 export const userStore = defineStore('user', { state: () => { return { userInfo:{ }, userInfoState:true, } }, actions: { changeUserInfo(userInfo) { this.userInfo = {...userInfo}; }, changeUserInfoState(){ this.userInfoState = !this.userInfoState; } } })
-
header.vue 顶部组件
import {ref,computed,watch} from 'vue' import {userStore} from '@/stores/userStore' const store = userStore() // 获取用户信息 const getAccountInfo =async ()=>{ let res = await accountInfo({id:user.id}) userInfo.value = res.accountInfo; } getAccountInfo() // 监听 pinia中的数据 userInfoState 是否发生改变 // 如果改变 就重新获取用户信息--重新渲染用户最新头像 watch(()=>store.userInfoState,(newVal,oldVal)=>{ getAccountInfo() })
4.表单 一致性验证
在请求接口之前,通过formData.newPwd 和 formData.newPwd1 判断是否相等,如果相等就调用接口,如果不等 提示用户两次密码不一致
在表单自定义校验中 可以通过 formref获取其他的输入框的值 然后在做判断
<template> <div> <h1> 修改密码 </h1> <div class="form"> <el-form :model="formData" :rules="rules" ref="formRef"> <el-form-item label="原密码" prop="oldPwd"> <el-input v-model="formData.oldPwd" @blur="checkPwd"></el-input> </el-form-item> <el-form-item label="新密码" prop="newPwd"> <el-input type="password" v-model="formData.newPwd"></el-input> </el-form-item> <el-form-item label="确认新密码" prop="newPwd1"> <el-input type="password" v-model="formData.newPwd1"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="addBtn">立即修改</el-button> </el-form-item> </el-form> </div> </div> </template> <script setup> import {reactive,ref} from 'vue' import {accountEditPwd,accountCheckPwd} from '@/api/account' import { ElMessage } from 'element-plus' import {useRouter} from 'vue-router' const formRef = ref(null) const router = useRouter() const {id} = JSON.parse(sessionStorage.getItem('user')) const formData = reactive({ newPwd:'', oldPwd:'', newPwd1:'', id:id }) const validatePassword1 = (rule,value,callback) => { //确认密码 是否 新密码一直 如果一致成功 不一致提示用户 if(value !== formData.newPwd){ callback(new Error('两次密码不一致')) }else{ callback() } } const rules = { oldPwd:[ {required:true,message:'请输入原密码',trigger:'blur'}, ], newPwd:[ {required:true,message:'请输入新密码',trigger:'blur'}, ], newPwd1:[ {required:true,message:'请输入确认密码',trigger:'blur'}, { validator: validatePassword1, trigger: 'blur' } ] } const addBtn = () => { formRef.value.validate((valid) => { if (valid) { accountEditPwd(formData).then(res => { if(res.code === 0){ ElMessage({ message: '修改密码成功,请重新登录', type: 'success', }) sessionStorage.clear(); router.push('/login') } }) } else { ElMessage({ message: '请输入正确的信息', type: 'error', }) return false; } }); } // 判断原密码是否输入正确 const checkPwd = async() => { console.log(formData.oldPwd); let data = { id:formData.id, oldPwd:formData.oldPwd } let {code,msg} = await accountCheckPwd(data) if(code ==0){ ElMessage.success(msg) }else{ ElMessage.error(msg) } }