xh's blog
文章
分类
标签
时间线
文章
分类
标签
时间线
  • 实习周总结一

实习周总结一

本周按照计划的那样,进行了基本的git学习以及一些Echart的实操。同时修复了一些项目的代码具体的修复想法都写在了之前的文件里了,这里不再赘述。同时也尝试了熟悉了VueX的代码。

项目代码学习

对于项目代码的阅览,我主要包括动态路由,VueX中的commit,以及&&运算符的巧用的学习

Dynamic Router

动态路由是一种根据用户权限动态控制前端页面访问权限的技术方案。其核心原理是将后端返回的用户菜单权限数据与前端预定义的路由配置进行映射和匹配。

具体的实现流程是:用户登录成功后,前端根据其权限标识(如角色或权限列表)从后端接口获取对应的菜单数据。随后,前端通过一个转换函数(如 filterAsyncRoutes)将这些菜单数据加工成符合 Vue Router 规范的路由配置对象。这个过程通常涉及组件的动态解析(例如使用 () => import(...)实现懒加载)和路由层级的递归处理。生成的有效路由配置会通过 router.addRoute(Vue Router 4)或 router.addRoutes(Vue Router 3)方法动态注入到路由实例中。同时,侧边栏导航菜单会根据处理后的权限数据,利用递归组件或 v-for指令动态渲染,确保用户界面与其权限实时同步。

整个机制依赖于全局路由守卫(如 router.beforeEach)进行权限校验拦截,形成了“数据驱动UI”安全访问控制体系。

比如这个项目就在全局路由守卫中进行了token的验证以及动态路由的更新。

其中resortDynamicRoutes(...)是核心函数,它根据后端返回的用户菜单列表store.state.user.menus,在一个完整的路由模板 (backendStaticRoutes) 中筛选并排序出当前用户有权限访问的路由,然后添加到 dynamicRoutes中。这实现了基于权限的动态路由加载,

resortDynamicRoutes()的核心工作流程,是将后端菜单结构(menu)与前端预定义的路由模backendStaticRoutes)进行匹配、筛选和组装,最终生成用户有权限访问的动态路由列表。

export function resortDynamicRoutes(
  dynamicRoutes: RouteRecordRaw[], // 处理后的动态路由
  menu: any[], // 后台返回的用户菜单项
  backendStaticRoutes?: RouteRecordRaw[], // 后台静态路由
  beforePush?: Function // 路由加入前的回调
) {
  if (!menu.length) {
    console.warn('menu is empty')
    return
  }

  // 处理后台菜单
  menu.forEach((m) => {
    const asyncRoute = backendStaticRoutes?.find((r) => r.meta?.id === m.id)

    if (asyncRoute) {
      const dynamicRoute = cloneDeep(asyncRoute)
      dynamicRoute.children = undefined

      // 必定都有 meta,但 ts 限制,用 if 处理
      // 显示的菜单名以后台菜单为准
      if (dynamicRoute.meta && m.menuName) {
        dynamicRoute.meta.title = m.menuName
      }

      if (beforePush) {
        beforePush(dynamicRoute, m)
      }

      dynamicRoutes.push(dynamicRoute)

      // 递归处理后台子菜单
      if (m.childList && m.childList.length) {
        dynamicRoute.children = []
        resortDynamicRoutes(dynamicRoute.children, m.childList, asyncRoute.children, beforePush)
      }
    }
  })
}

函数接收四个参数,共同决定了动态路由的生成逻辑

  • dynamicRoutes: RouteRecordRaw[]:一个引用类型参数,用于收集和存放所有处理完成后、有权限访问的路由对象。函数内部通过 dynamicRoutes.push填充此数组。
  • menu: any[]:后端返回的用户菜单列表,定义了当前用户有权访问的菜单结构。它是生成路由的“骨架”或“白名单”。
  • backendStaticRoutes?: RouteRecordRaw[](可选):前端预定义的完整路由模板库。可以理解为所有可用路由的“零件库”。函数会从其中挑选出 menu中允许的路由。
  • beforePush?: Function(可选):一个回调函数,允许在路由被添加到 dynamicRoutes之前,对其进行最后的自定义修改(例如,设置特定的 meta信息)。

commit()的作用

​ 在Vue项目中,特别是在使用Vuex进行状态管理时,commit()方法扮演着至关重要的角色。它的核心作用是唯一地、以同步方式修改Vuex Store中的状态(state),从而确保所有状态的变化都是可预测和可追踪的,是改变 Vuex 状态的唯一途径。

Vuex

Vuex是Vue.js的官方状态管理库,用于集中管理应用中所有组件的共享状态,确保状态变更的可预测性。其核心在于一个包含五大关键概念的store(仓库):State负责存储初始状态数据;Mutations是唯一能同步修改State的方法,通过commit触发;Actions用于处理异步操作或复杂逻辑,通过dispatch触发,内部再commit给Mutations;Getters类似于计算属性,用于派生基于State的新数据;Modules则允许将复杂的Store分割成多个模块便于维护。典型的数据流是:组件dispatch一个Action,Action执行异步任务后commit一个Mutation,Mutation最终同步更新State,进而触发视图重新渲染。

在创建了store后,可以通过this.$store访问store实例:

  • 访问state:使用计算属性返回state。
  • 提交mutation:使用this.$store.commit('mutationName')。
  • 分发action:使用this.$store.dispatch('actionName')。
  • 访问getter:使用this.$store.getters.getterName。

&&符号

router.hasRoute(name) && router.removeRoute(name) 代码巧妙地利用了 &&运算符的短路特性,

它会先计算左侧的表达式的值,如果这个值可以转换为 false(如 false、0、""、null、undefined、NaN),那么整个表达式的结果就已经确定为这个假值,右侧的表达式将不会被计算或执行

这段代名出现在 resetRouter()函数中,因此这段代码表示如果没有这个路由,就不用进行右侧的删除函数,如果有这个路由,就进行删除。&&符号的应用很好的简化了代码。

Git的使用

Git分为暂存区,本地仓库和远程仓库

大概流程就是从远程仓库拉取代码后进行修改,修改后的代码文件可放在暂存区,然后可以将暂存区的文件commit到本地仓库,本地仓库在与远程的最新代码合并后可以进行对远程的提交

首先拉取远程仓库的内容到本地

git clone <仓库地址或ssh地址>

本地修改了之后将修改的文件放入暂存区

git add <文件名>

git add .     //将当前目录所有文件放入暂存区

然后可将暂存区的文件提交到本地仓库

git commit -m "提交理由"

最后推送到远端仓库

git pull  //更新最新代码

git push //推送到远端仓库

如果想要撤销已经推送到远程仓库的内容,需要先保证你的提交没有被人拉取

安全撤销的方式:

  1. 查看提交历史:git log --online
  2. 找到你想撤销的那次提交的哈希值
  3. 执行撤销命令git revert <那次提交的哈希值>
  4. wq 保存退出(涉及一点vim操作)
  5. 将一个撤销你之前更改的新提交推送到远程仓库 git push

如果想撤销撤销操作,一样的找hash值操作即可

Echart入门

基本的安装

npm install echarts --save

使用方面一般都是子组件进行画图,父组件引入并传数据给子组件

父组件引入子组件并传值

<template>
  <div>
    <!-- <BarChart :chart-data="salesData" /> -->
     <ZheX :chart-data="salesData" />
    <div class="update-btn">
        <button @click="updateData" >更新数据</button>
    </div>
    
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
// import BarChart from './BarChart.vue';
import ZheX from './ZheX.vue';

const salesData = ref([15, 25, 18, 32, 21, 28]);

const updateData = () => {
  salesData.value = salesData.value.map(() => 
    Math.floor(Math.random() * 40) + 5
  );
};
</script>

<style>
.update-btn{
    padding-top: 20px;
}
</style>

子组件通过接收的prop数据参数进行图标绘制

<template>
   <div class="chart-container">
        <h2>销售数据统计</h2>
        <div ref="chartRef" class="chart"></div>
    </div>
</template>

<script setup lang='ts'>
import { ref, onMounted, onUnmounted, watch } from 'vue';
import * as echarts from 'echarts';
import type { ECharts, EChartsOption } from 'echarts';

// 使用ref获取DOM元素
const chartRef = ref<HTMLDivElement>();
let myChart: ECharts | null = null;

// 定义props类型 
interface ChartProps {
    chartData: number[];
}

const props = withDefaults(defineProps<ChartProps>(), {
    chartData: () => [5, 20, 36, 10, 10, 20]
});


const getOption = (): EChartsOption => ({
    title: {
        text: '月度销售数据',
        left: 'left',
        textStyle: {
            color: '#333',
            fontSize: 16
        }
    },
    // 鼠标悬停提示框
    tooltip: {
        trigger: 'axis',
        axisPointer: {
            type: 'cross' //悬停显示的类型
        },
        formatter: '<strong>商品{b}</strong><br/>{a0}: {c0}件<br/>{a1}: {c1}件<br/>{a2}: {c2}件'
        // b0对应 series配置项中的 name属性的第一个
        // c0对应 对应 series.data中指定位置的具体数值的第一个
        // a0对应 ​对应 xAxis.data中指定位置的数据,通常表示横轴的标签的第一个
    },
    // 图例部分
    legend: {
        data: ['销量','买量','余量'],
        left: 'right', //对齐方式
        top: '5%'  //离顶部的距离
    },
 	// 配置ECharts图表的网格(grid)系统,决定了直角坐标系(例如常见的折线图、柱状图)的绘图区域在容器中的位置和大小
    grid: {
        left: '3%',
        right: '4%',
        bottom: '3%',
        containLabel: true
        //containLabel配置​​网格区域是否包含坐标轴的​​刻度标签
    },

    // 坐标轴配置
    xAxis: {
    type: 'category',
    data: ['A', 'B', 'C','D','E','F'],
  },
  yAxis: {
    type:'value'
  },
  series: [
    {
      name: '销量',
      data: props.chartData,
      type: 'line',
      color:'red',
    },
    {
        name:'余量',
        data:[25, 20, 50, 30, 55, 10],
        type:"bar",
        color:'pink',
        barWidth:'50%'
    },
    {
        name:"买量",
        data:[23,12,63,32,52,33],
        type:'line'
    }
  ]
});

//初始化函数
const initChart = () => {
    if (!chartRef.value) return;
    myChart = echarts.init(chartRef.value);
    myChart.setOption(getOption());
};
// 处理窗口 resize 事件
const handleResize = () => {
    myChart?.resize();
};

onMounted(() => {
    // 确保DOM已挂载后初始化图表
    setTimeout(() => {
        initChart();
        window.addEventListener('resize', handleResize);
    }, 0);
});


// 监听数据变化
watch(() => props.chartData, (newData) => {
    if (myChart) {
        myChart.setOption({
            series: [{
                data: newData
            }]
        });
    }
});

onUnmounted(() => {
    // 清理资源,销毁实例避免内存泄漏
    window.removeEventListener('resize', handleResize);
    myChart?.dispose();
});
</script>

<style scoped>
。。。略
</style>

其中,getOption用于对图表各类参数进行配置,

initChart函数用于初始化图表,在onMounted的时候调用这个初始化函数。

onUnmounted时记得清理资源,销毁实例避免内存泄漏

这里是对我个人之前的图书管理系统加了一个子菜单专门用来演示我的各种demo,因为本身这个项目已经做好了Vue3和ElmentPlus的环境。具体Echart的绘制如图,可以看到我还进行了ElementPlus的水印组件的使用:

最近更新:: 2025/12/2 11:54
Contributors: ksldnasx