4.25 Echarts和一些外卖项目

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)
       }
  }
  ​
  ​

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值