数据大屏
功能展示
先安上下分
下 buttom 又分为左中右
而在左中右又分为上中下,或者上下
代码实现
-
在project\src\views\screen\images引入images
-
安装相关组件
# 安装时间组件(用于动态显示当前的时间) pnpm i moment # 安装 echarts pnpm i echarts # 安装echarts 扩展插件之水球图 pnpm i echarts-liquidfill
-
封装对应的组件 (代码见码云)
- project\src\views\screen\components\age\index.vue
- project\src\views\screen\components\couter\index.vue
- project\src\views\screen\components\line\index.vue
- project\src\views\screen\components\map\china.json
- project\src\views\screen\components\map\index.vue
- project\src\views\screen\components\rank\index.vue
- project\src\views\screen\components\sex\index.vue
- project\src\views\screen\components\top\index.vue
- project\src\views\screen\components\tourist\index.vue
- project\src\views\screen\components\year\index.vue
- 在大屏的index.vue中引入project\src\views\screen\index.vue
<template>
<div class="container">
<!-- 数据大屏展示内容区域 -->
<div class="screen" ref="screen">
<!-- 数据大屏顶部 -->
<div class="top">
<Top />
</div>
<div class="bottom">
<div class="left">
<Tourist class="tourist"></Tourist>
<Sex class="sex"></Sex>
<Age class="age"></Age>
</div>
<div class="center">
<Map class="map"></Map>
<Line class="line"></Line>
</div>
<div class="right">
<Rank class="rank"></Rank>
<Year class="year"></Year>
<Counter class="count"></Counter>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
//引入顶部的子组件
import Top from './components/top/index.vue';
//引入左侧三个子组件
import Tourist from './components/tourist/index.vue';
import Sex from './components/sex/index.vue';
import Age from './components/age/index.vue'
//引入中间两个子组件
import Map from './components/map/index.vue';
import Line from './components/line/index.vue';
//引入右侧三个子组件
import Rank from './components/rank/index.vue';
import Year from './components/year/index.vue';
import Counter from './components/couter/index.vue'
//获取数据大屏展示内容盒子的DOM元素
let screen = ref();
onMounted(() => {
screen.value.style.transform = `scale(${getScale()}) translate(-50%,-50%)`
});
//定义大屏缩放比例
function getScale(w = 1920, h = 1080) {
const ww = window.innerWidth / w;
const wh = window.innerHeight / h;
return ww < wh ? ww : wh;
}
//监听视口变化
window.onresize = () => {
screen.value.style.transform = `scale(${getScale()}) translate(-50%,-50%)`
}
</script>
<style scoped lang="scss">
.container {
width: 100vw;
height: 100vh;
background: url(./images/bg.png) no-repeat;
background-size: cover;
.screen {
position: fixed;
width: 1920px;
height: 1080px;
left: 50%;
top: 50%;
transform-origin: left top;
.top {
width: 100%;
height: 40px;
}
.bottom {
display: flex;
.right {
flex: 1;
display: flex;
flex-direction: column;
margin-left: 40px;
.rank {
flex: 1.5;
}
.year {
flex: 1;
}
.count {
flex: 1;
}
}
.left {
flex: 1;
height: 1040px;
display: flex;
flex-direction: column;
.tourist {
flex: 1.2;
}
.sex {
flex: 1;
}
.age {
flex: 1;
}
}
.center {
flex: 1.5;
display: flex;
flex-direction: column;
.map {
flex: 4;
}
.line {
flex: 1;
}
}
}
}
}
</style>
权限控制
-
功能分析
菜单的权限: 超级管理员账号:admin atguigu123 拥有全部的菜单、按钮的权限 飞行员账号 硅谷333 111111 不包含权限管理模块、按钮的权限并非全部按钮 同一个项目:不同人(职位是不一样的,他能访问到的菜单、按钮的权限是不一样的) 一、目前整个项目一共多少个路由!!! login(登录页面)、 404(404一级路由)、 任意路由、 首页(/home)、 数据大屏、 权限管理(三个子路由) 商品管理模块(四个子路由) 1.1开发菜单权限 ---第一步:拆分路由 静态(常量)路由:大家都可以拥有的路由 login、首页、数据大屏、404 异步路由:不同的身份有的有这个路由、有的没有 权限管理(三个子路由) 商品管理模块(四个子路由) 任意路由:任意路由 1.2菜单权限开发思路 目前咱们的项目:任意用户访问大家能看见的、能操作的菜单与按钮都是一样的(大家注册的路由都是一样的) -
安装 插件
// 可以操作js对象,主要用于深拷贝对象,解决权限递归遍历时将数据覆盖,从而导致账号切换时候权限不对的问题 pnpm i lodash -
路由拆分
//对外暴露配置路由(常量路由):全部用户都可以访问到的路由 export const constantRoute = [ { //登录 path: '/login', component: () => import('@/views/login/index.vue'), name: 'login', meta: { title: '登录',//菜单标题 hidden: true,//代表路由标题在菜单中是否隐藏 true:隐藏 false:不隐藏 icon: "Promotion",//菜单文字左侧的图标,支持element-plus全部图标 } } , { //登录成功以后展示数据的路由 path: '/', component: () => import('@/layout/index.vue'), name: 'layout', meta: { title: '', hidden: false, icon: '' }, redirect: '/home', children: [ { path: '/home', component: () => import('@/views/home/index.vue'), meta: { title: '首页', hidden: false, icon: 'HomeFilled' } } ] }, { //404 path: '/404', component: () => import('@/views/404/index.vue'), name: '404', meta: { title: '404', hidden: true, icon: 'DocumentDelete' } }, { path: '/screen', component: () => import('@/views/screen/index.vue'), name: 'Screen', meta: { hidden: false, title: '数据大屏', icon: 'Platform' } }] //异步路由 export const asnycRoute = [ { path: '/acl', component: () => import('@/layout/index.vue'), name: 'Acl', meta: { title: '权限管理', icon: 'Lock' }, redirect: '/acl/user', children: [ { path: '/acl/user', component: () => import('@/views/acl/user/index.vue'), name: 'User', meta: { title: '用户管理', icon: 'User' } }, { path: '/acl/role', component: () => import('@/views/acl/role/index.vue'), name: 'Role', meta: { title: '角色管理', icon: 'UserFilled' } }, { path: '/acl/permission', component: () => import('@/views/acl/permission/index.vue'), name: 'Permission', meta: { title: '菜单管理', icon: 'Monitor' } } ] } , { path: '/product', component: () => import('@/layout/index.vue'), name: 'Product', meta: { title: '商品管理', icon: 'Goods', }, redirect: '/product/trademark', children: [ { path: '/product/trademark', component: () => import('@/views/product/trademark/index.vue'), name: "Trademark", meta: { title: '品牌管理', icon: 'ShoppingCartFull', } }, { path: '/product/attr', component: () => import('@/views/product/attr/index.vue'), name: "Attr", meta: { title: '属性管理', icon: 'ChromeFilled', } }, { path: '/product/spu', component: () => import('@/views/product/spu/index.vue'), name: "Spu", meta: { title: 'SPU管理', icon: 'Calendar', } }, { path: '/product/sku', component: () => import('@/views/product/sku/index.vue'), name: "Sku", meta: { title: 'SKU管理', icon: 'Orange', } }, ] } ] //任意路由 export const anyRoute = { //任意路由 path: '/:pathMatch(.*)*', redirect: '/404', name: 'Any', meta: { title: '任意路由', hidden: true, icon: 'DataLine' } } -
修改userStore 中引入的路由,和与异步路由过略合并
//创建用户相关的小仓库 import { defineStore } from 'pinia'; //引入接口 import { reqLogin, reqUserInfo, reqLogout } from '@/api/user'; import type { loginFormData, loginResponseData, userInfoReponseData } from "@/api/user/type"; import type { UserState } from './types/type'; //引入操作本地存储的工具方法 import { SET_TOKEN, GET_TOKEN, REMOVE_TOKEN } from '@/utils/token'; //引入路由(常量路由) import { constantRoute, asnycRoute, anyRoute } from '@/router/routes'; //引入深拷贝方法 //@ts-ignore import cloneDeep from 'lodash/cloneDeep' import router from '@/router'; //用于过滤当前用户需要展示的异步路由 function filterAsyncRoute(asnycRoute: any, routes: any) { return asnycRoute.filter((item: any) => { if (routes.includes(item.name)) { if (item.children && item.children.length > 0) { //硅谷333账号:product\trademark\attr\sku item.children = filterAsyncRoute(item.children, routes); } return true; } }) } //创建用户小仓库 let useUserStore = defineStore('User', { //小仓库存储数据地方 state: (): UserState => { return { token: GET_TOKEN(),//用户唯一标识token menuRoutes: constantRoute,//仓库存储生成菜单需要数组(路由) username: '', avatar: '', //存储当前用户是否包含某一个按钮 buttons:[], } }, //异步|逻辑的地方 actions: { //用户登录的方法 async userLogin(data: loginFormData) { //登录请求 let result: loginResponseData = await reqLogin(data); //登录请求:成功200->token //登录请求:失败201->登录失败错误的信息 if (result.code == 200) { //pinia仓库存储一下token //由于pinia|vuex存储数据其实利用js对象 this.token = (result.data as string); //本地存储持久化存储一份 SET_TOKEN((result.data as string)); //能保证当前async函数返回一个成功的promise return 'ok'; } else { return Promise.reject(new Error(result.data)); } }, //获取用户信息方法 async userInfo() { //获取用户信息进行存储仓库当中[用户头像、名字] let result: userInfoReponseData = await reqUserInfo(); //如果获取用户信息成功,存储一下用户信息 if (result.code == 200) { this.username = result.data.name; this.avatar = result.data.avatar; this.buttons = result.data.buttons; //计算当前用户需要展示的异步路由 let userAsyncRoute = filterAsyncRoute(cloneDeep(asnycRoute), result.data.routes); //菜单需要的数据整理完毕 this.menuRoutes = [...constantRoute, ...userAsyncRoute, anyRoute]; //目前路由器管理的只有常量路由:用户计算完毕异步路由、任意路由动态追加 [...userAsyncRoute, anyRoute].forEach((route: any) => { router.addRoute(route); }); return 'ok'; } else { return Promise.reject(new Error(result.message)); } }, //退出登录 async userLogout() { //退出登录请求 let result: any = await reqLogout(); if (result.code == 200) { //目前没有mock接口:退出登录接口(通知服务器本地用户唯一标识失效) this.token = ''; this.username = ''; this.avatar = ''; REMOVE_TOKEN(); return 'ok'; } else { return Promise.reject(new Error(result.message)); } } }, getters: { } }) //对外暴露获取小仓库方法 export default useUserStore; -
404页面搭建
<template> <div class="box"> <img src="../../assets/images/error_images/404.png" alt=""> <button @click="goHome">首页</button> </div> </template> <script setup lang="ts"> import {useRouter} from 'vue-router'; let $router = useRouter(); const goHome = ()=>{ $router.push('/home') } </script> <style scoped lang="scss"> .box{ width: 100vw; height: 100vh; background: yellowgreen; display: flex; justify-content: center; img{ width: 800px; height: 400px; } button{ width: 50px; height: 50px; } } </style> -
按钮权限控制
将当前用户的按钮标识储存到userStore 仓库,
定义自定义子令并在main.ts 注册
project\src\directive\has.ts
import pinia from '@/store';
import useUserStore from '@/store/modules/user';
let userStore =useUserStore(pinia)
export const isHasButton = (app: any) => {
//获取对应的用户仓库
//全局自定义指令:实现按钮的权限
app.directive('has', {
//代表使用这个全局自定义指令的DOM|组件挂载完毕的时候会执行一次
mounted(el:any,options:any) {
//自定义指令右侧的数值:如果在用户信息buttons数组当中没有
//从DOM树上干掉
if(!userStore.buttons.includes(options.value)){
el.parentNode.removeChild(el);
}
},
})
}
注册指令
............................
// 注册模板路由
app.use(router);
// 注册仓库
app.use(pinia);
//引入自定义指令文件 +++++++++++++
import { isHasButton } from '@/directive/has';
// +++++++++++++++++++++
isHasButton(app);
// 将应用挂载到挂载点
app.mount('#app')
使用指令
project\src\views\product\trademark\index.vue
<template>
<div>
<el-card class="box-card">
<!-- 卡片顶部添加品牌按钮 ++++++++++++++ v-has="`btn.Trademark.add`" -->
<el-button type="primary" size="default" icon="Plus" @click="addTrademark" v-has="`btn.Trademark.add`">添加品牌</el-button>
<!-- 表格组件:用于展示已有得平台数据 -->
<!-- table:---border:可以设置表格纵向是否有边框
table-column:---label:某一个列表 ---width:设置这列宽度 ---align:设置这一列对齐方式
-->
<el-table style="margin:10px 0px" border :data="trademarkArr">
.......................
在按钮显示的地方判断当前用户的所有的
项目上线
pnpm run build