美文网首页
Vue3.0 Vite + windicss + ...实战项目

Vue3.0 Vite + windicss + ...实战项目

作者: coderhzc | 来源:发表于2022-10-12 22:31 被阅读0次

十九.如何动态添加后端返回的菜单实现动态路由的添加呢?

1. router/index.js 文件
import { createRouter, createWebHashHistory } from "vue-router";

import Admin from "~/Layout/admin.vue";
import Index from "~/pages/index.vue";
import Login from "~/pages/Login/login.vue";
import NotFound from "~/pages/NotFound/404.vue";
import GoodList from "~/pages/Goods/List.vue";
import CategoryList from "~/pages/Category/ListCategory.vue";
// const routes = [
//   {
//     path: "/",
//     component: Admin,
//     children: [
//       {
//         path: '/',
//         component: Index,
//         meta: {
//           title: "后台首页"
//         }
//       },

//       {
//         path: '/goods/list',
//         component: GoodList,
//         meta: {
//           title: "商品管理"
//         }
//       },
//       {
//         path: '/category/list',
//         component: CategoryList,
//         meta: {
//           title: "分类管理"
//         }
//       }
//     ]
//   },

//   {
//     path: "/login",
//     component: Login,
//     meta: {
//       title: "登录"
//     }
//   },
//   {
//     path: "/:pathMatch(.*)*",
//     name: "NotFound",
//     component: NotFound,
//     meta: {
//       title: "404-NotFound"
//     }
//   },
// ];

// 这个是公共路由 所有用户共享
const routes = [
  {
    path: "/",
    name: "admin",
    component: Admin,
  },

  {
    path: "/login",
    name: "login",
    component: Login,
    meta: {
      title: "登录",
    },
  },
  {
    path: "/:pathMatch(.*)*",
    name: "NotFound",
    component: NotFound,
    meta: {
      title: "404-NotFound",
    },
  },
];
// 1. 动态路由,用于匹配菜单动态添加路由
const aysncRoutes = [
  {
    path: "/",
    component: Index,
    name: "/",
    meta: {
      title: "后台首页",
    },
  },

  {
    path: "/goods/list",
    component: GoodList,
    name: "/goods/list",
    meta: {
      title: "商品管理",
    },
  },
  {
    path: "/category/list",
    component: CategoryList,
    name: "/category/list",
    meta: {
      title: "分类管理",
    },
  },
];

export const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

//  2. 动态添加路由的方法
export const addRoutes = (menus) => {
  console.log(menus);
  // 是否有新的路由
  let hasNewRoutes = false;
  const findAndAddRoutesByMenus = (arr) => {
    arr.forEach((e) => {
      let item = aysncRoutes.find((o) => o.path == e.frontpath);
      // item: 是否有item; router.hasRoute():检查路由是否存在
      // router.hasRoute(): 传递一个name,所以要在路由中写上name名称
      if (item && !router.hasRoute(item.path)) { // !router.hasRoute(item.path): 没有注册过路由
        // addRoute():参数一: 父级路由的name值,参数二:满足条件的对象 
        router.addRoute("admin", item);
        hasNewRoutes = true;
      }
      // e: 是一个对象 
      if (e.child && e.child.length > 0) {
        findAndAddRoutesByMenus(e.child);
      }
    });
  };

  findAndAddRoutesByMenus(menus);

  return hasNewRoutes;
  // console.log(router.getRoutes());
};

2. 
// 专门处理权限相关的代码

// 1. 引入路由实例
import { router, addRoutes } from "~/router";
import { getToken } from "~/commonCookie/Auth";
import { Toast, showFullLoading, hideFullLoading } from "~/commonCookie/utils";
import store from "./store";

// 前置路由守卫
router.beforeEach(async (to, from, next) => {
  // 只要路由发生了变化 要实现loading效果
  showFullLoading();

  const token = getToken();
  if (!token && to.path !== "/login") {
    Toast("请先登录", "error");
    return next({ path: "/login" });
  }
  // 防止重复登录
  if (token && to.path == "/login") {
    Toast("请务重复登录", "error");
    return next({ path: from.path ? from.path : "/" });
  }

  let hasNewRoutes = false;
  // 如果用户登录了 ,自动获取用户信息, 并储存在vuex中,
  // 这种是为了防止 第一次可以获取到数据, 但是在页面刷新以后数据会丢失
  if (token) {
    const res = await store.dispatch("getInfo");
    // console.log(res);

    // 动态添加路由 hasNewRoutes:返回值是Boolean当有新的路由的时候 值为true
    hasNewRoutes = addRoutes(res.menus);
  }

  // 设置页面标题
  // console.log(to.meta.title); // 显示的是当前路由的title
  let title = to.meta?.title + "--振楚后台系统";
  document.title = title;
  console.log(to);
  // 防止路由刷新页面消失,hasNewRoutes:返回值是Boolean当有新的路由的时候 值为true
  hasNewRoutes ? next(to.fullPath) : next();
});

// 全局后置守卫
router.afterEach((to, from) => {
  // 当路由加载完成以后 关闭全局的进度条
  hideFullLoading();
});

二十. 封装一个完整版的标签导航

1. FTagListHooks.js
import { ref } from "vue";
import { useRoute, onBeforeRouteUpdate, useRouter } from "vue-router";
import { useCookies } from "@vueuse/integrations/useCookies";
export const useTabList = () => {
  const route = useRoute();
  const router = useRouter();
  const cookie = useCookies();
  const activeTab = ref(route.path);
  const tabList = ref([
    {
      title: "后台首页",
      path: "/",
    },
  ]);
  const changeTab = (t) => {
    console.log(t);
    activeTab.value = t;
    router.push(t);
  };
  const removeTab = (t) => {
    console.log(t); // 拿到path值
    let tabs = tabList.value;
    let a = activeTab.value;
    if (a == t) {
      tabs.forEach((tab, index) => {
        if (tab.path == t) {
          const nextTab = tabs[index + 1] || tabs[index - 1];
          if (nextTab) {
            a = nextTab.path; // 点击删除,匹配到当前上一个或者写一个的路径
          }
        }
      });
    }

    activeTab.value = a;

    tabList.value = tabList.value.filter((tab) => tab.path != t);

    cookie.set("tabList", tabList.value);
  };
  // 监听当前路由改变
  onBeforeRouteUpdate((to, from) => {
    // console.log(to);
    // console.log(from);
    activeTab.value = to.path;
    addTab({
      title: to.meta.title,
      path: to.path,
    });
  });

  const addTab = (tabObj) => {
    let noTab = tabList.value.findIndex((t) => t.path == tabObj.path) == -1; // 代表此时循环是数据是没有这个tab
    if (noTab) {
      tabList.value.push(tabObj);
    }
    cookie.set("tabList", tabList.value);
  };

  // 初始化标签导航列表,当你在刷新浏览器的时候 添加的tab标签没有了
  const initTabList = () => {
    let tabs = cookie.get("tabList");
    if (tabs) {
      tabList.value = tabs;
    }
  };

  initTabList();

  // 关闭标签
  const handleClode = (c) => {
    // closeOther
    // closeAll
    if (c == "closeAll") {
      // 切换到首页
      activeTab.value = "/";
      // 过滤只剩下首页
      tabList.value = [
        {
          title: "后台首页",
          path: "/",
        },
      ];
    } else if (c == "closeOther") {
      tabList.value = tabList.value.filter(
        (tab) => tab.path == "/" || tab.path == activeTab.value
      );
    }

    cookie.set("tabList", tabList.value);
  };

  return {
    activeTab,
    tabList,
    changeTab,
    removeTab,
    handleClode
  }
};

2. FTagList.vue
<template>
  <div class="f-tag-list" :style="{ left: $store.state.asideWidth }">
    <el-tabs v-model="activeTab" type="card" class="flex-1" @tab-remove="removeTab" @tab-change="changeTab"
      style="min-width: 100px">
      <el-tab-pane v-for="item in tabList" :closable="item.path != '/'" :key="item.path" :label="item.title"
        :name="item.path">
      </el-tab-pane>
    </el-tabs>

    <span class="tag-btn-class">
      <el-dropdown @command="handleClode">
        <span class="el-dropdown-link">
          <el-icon>
            <arrow-down />
          </el-icon>
        </span>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item command="closeOther">关闭其他</el-dropdown-item>
            <el-dropdown-item command="closeAll">全部关闭</el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </span>
  </div>
  <div style="height: 44px"></div>
</template>
<script setup>
import { useTabList } from "./CpnHook/FTagListHooks.js";

const {
  activeTab,
  tabList,
  changeTab,
  removeTab,
  handleClode
} = useTabList()
</script>
<style>
.f-tag-list {
  @apply fixed bg-gray-100 flex items-center px-2;
  top: 64px;
  right: 0;
  height: 44px;
  z-index: 100;
}

.tag-btn-class {
  @apply bg-white rounded ml-auto flex items-center justify-center;
  height: 32px;
  width: 32px;
}

.el-tabs__header {
  margin-bottom: 0px !important;
  border: 0 !important;
}

.el-tabs__nav {
  border: 0 !important;
}

.el-tabs__item {
  border: 0 !important;
  height: 32px;
  line-height: 32px;
  margin-top: 4px;
  @apply bg-white mx-1 rounded;
}

.is-disabled {
  cursor: not-allowed !important;
  @apply text-gray-300 !important;
}
</style>


实际截图

image.png

二十一. 利用keep-alive 实现 全局 页面缓存

image.png

二十二.设置全局过渡动画

image.png

二十三.使用 animate.style 库来做动画效果

使用 animate.style 库来做动画效果

image.png

二十四.如何实现数字滚动 动画效果

gsap库的地址

1. 要是用一个第三方的gsap库安装命令: npm i gsap

2. 封装一个数字滚动组件 CountTo.vue组件
<template>
  {{ d.num.toFixed(0) }}
</template>

<script setup>
import gsap from 'gsap';
import { reactive, watch } from 'vue';

// 接收父组件传递过来的数据
const props = defineProps({
  value: {
    type: Number,
    default: 0
  }
})

const d = reactive({
  num: 0
})

const AnimateTovalue = () => {
  /**
   * 参数一: 需要变化的那个对象,目标对象,
   * 参数二: 对象
   *        {
   *         // 动画秒数 
   *          duration: 0.5
   *         // 当前内部的num: props.value 是父组件传递过来的数据
   *          num:props.value
   * 此时他会从0 到父组件传递过来的数据 使用0.5s 的过渡效果
   *       
   *  
   *        }
   * 
   * **/
  gsap.to(d, {
    duration: 3,
    num: props.value
  })
}
AnimateTovalue();

// 监听props.value中的值只要有变化的话, 就重新执行一下动画
watch(() => props.value, () => AnimateTovalue())
</script>

<style>

</style>

3. 在页面使用
// 3.1 引入
import CountTo from "~/components/CountTo/CountTo.vue"

// 3.2 标签上使用
 <CountTo :value="item.value"></CountTo>
image.png

二十五.对Echarts图标封装

1. 安装Eacharts:  cnpm install echarts --save

2. 封装一个Eacharts.vue文件
<template>
  <el-card shadow="never" class="mt-4">
    <template #header>
      <div class="flex justify-between">
        <span class="text-sm">订单统计</span>
        <div>
          <el-check-tag :checked="item.value === currentValue" v-model="currentValue" v-for="(item,index) in TagOptions"
            class="mr-2" :key="index" @change="chartClick(item.value)">{{ item.text }}
          </el-check-tag>
        </div>
      </div>
    </template>

    <div id="chart" style="width:100%;height:300px;" ref="el"></div>
  </el-card>
</template>

<script setup>
import * as echarts from 'echarts';
import { ref, onMounted, onBeforeUnmount } from "vue";
import { getStatistics3 } from "~/api/Index/index.js";
// 当拖动缩小浏览器的窗口时对窗口的监听
import { useResizeObserver } from '@vueuse/core';

const currentValue = ref("week")
const TagOptions = [
  {
    text: "近1个月",
    value: "month"
  },
  {
    text: "近1周",
    value: "week"
  },
  {
    text: "近24小时",
    value: "hour"
  }
]
// Check Tag 选中事件
const chartClick = (type) => {
  currentValue.value = type;

  getList();
}
var myChart = null;
onMounted(() => {
  var chartDom = document.getElementById('chart');
  myChart = echarts.init(chartDom);

  getList();// 在onMounted中获取数据
})


// 获取图表的数据方法
const getList = () => {
  let option = {
    xAxis: {
      type: 'category',
      data: []
    },
    yAxis: {
      type: 'value'
    },
    series: [
      {
        data: [],
        type: 'bar',
        showBackground: true,
        backgroundStyle: {
          color: 'rgba(180, 180, 180, 0.2)'
        }
      }
    ]
  };

  // 调接口请求图标数据
  myChart.showLoading()
  getStatistics3(currentValue.value).then(({ x, y }) => {
    // console.log(res, "resresresres");
    option.xAxis.data = x
    option.series[0].data = y
    option && myChart.setOption(option);
  }).finally(() => {
    myChart.hideLoading()
  })

}

// 当页面即将销毁的时候 要销毁图表
onBeforeUnmount(() => {
  /**
   *   官方: 在图表容器被销毁之后 调用echartsInstance.dispose 销毁实例,在图表容器重新被添加再次调用echarts.init初始化
   *  
   *   为啥要调用echarts.dispose(): 销毁实例释放资源,避免内存泄漏
   * **/
  if (myChart) echarts.dispose(myChart);
})

// 优化: 当拖动缩小浏览器的窗口时对窗口的监听
const el = ref(null)
useResizeObserver(el, (entries) => {
  // console.log(el); // 可以监听 对应 浏览器拖动
  myChart.resize(); // 重新计算图表的大小
})

</script>

<style>

</style>

3. 在需要引用的页面中使用
import CheckEChart from "~/components/Compone/ComponentIndexEacharts.vue";
<el-row :gutter="20">
    <el-col :span="12">
        <CheckEChart />
      </el-col>
      <el-col :span="12"></el-col>
 </el-row>

实际截图:

image.png

相关文章

网友评论

      本文标题:Vue3.0 Vite + windicss + ...实战项目

      本文链接:https://www.haomeiwen.com/subject/qmxxzrtx.html