挑战一周完成Vue3项目Day5:数据大屏+菜单权限+按钮权限

news2024/11/24 1:05:37

一、数据大屏

国内echarts镜像站:ISQQW.COM x ECharts 文档(国内同步镜像) - 配置项

echarts图表集:echarts图表集

1.数据大屏适配问题解决

(1)vw与vh单位解决适配问题

vw/vh:新增单位,要求ie8
比如box内部有两个自适应的盒子top和bottom,他们的宽和高都是随着父容器的变化而变化.将box的宽和高设为视图的宽高100vw,100vh.

那么top和bottom的宽高就要计算一个vw是多少。比如100vw=1920px,那么1vw=19.2px,5.2vw=100px.宽就是5.2vw。

缺点:需要计算子容器的宽高;子容器里面的文字不能进行缩放。

.box{
            width: 100vw;
            height: 100vh;
            background-color: blue;
        }
        .top{
            width: 5.2vw;
            height: 9.26vh;
            background-color: black;
            margin-bottom: 2.6vw;
        }
        .bottom{
            width: 5.2vw;
            height: 9.26vh;
            background-color: white;
        }

(2)css3:scale缩放实现

需要计算出缩放比例(返回的比例需要判断用ww还是wh,原则是根据小的这样不会破坏比例),根据比例放大缩小。

缺点:会留白

   <style>
      * {
        margin: 0;
        padding: 0;
      }
      .container {
        width: 100vw;
        height: 100vh;
        background: url(./bg.png) no-repeat;
        background-size: cover;
      }
      .box {
        position: fixed;
        width: 1920px;
        height: 1080px;
        background-color: red;
        transform-origin: left top;
        left: 50%;
        top: 50%;
      }
      .top {
        width: 100px;
        height: 100px;
        background-color: hotpink;
        margin-left: 50px;
      }
      .bottom {
        width: 100px;
        height: 100px;
        background-color: skyblue;
        margin-left: 50px;
        margin-top: 50px;
      }
    </style>
<script>
  // 控制数据大屏缩放
  let box = document.querySelector('.box')
  box.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 = () => {
      box.style.transform = `scale(${getScale()}) translate(-50%,-50%)`
  }
</script>

2.screen

(1)写好静态,引入image,screen/images

(2)将子组件封装再引入

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>

3.顶部组件Top

(1)pnpm i moment

(2)关于时间动态展示》先存储当前时间》组件挂载完毕利用定时器,每隔一秒刷新时间》展示时间

(3)跳转首页

src/views/screen/components/top/index.vue

<template>
    <div class="top">
        <div class="left">
            <span class="lbtn" @click="goHome">首页</span>
        </div>
        <div class="center">
            <div class="title">智慧旅游可视化大数据平台</div>
        </div>
        <div class="right">
            <span class="rbtn">统计报告</span>
            <span class="time">当前时间:{{ time }}</span>
        </div>
    </div>
</template>

<script setup lang="ts">
//@ts-ignore
import moment from 'moment';
//点击首页按钮回到首页
import { useRouter } from 'vue-router';
import { ref, onMounted, onBeforeUnmount } from 'vue';
//获取路由器对象
let $router = useRouter();

//存储当前时间
let time = ref(moment().format('YYYY年MM月DD日 hh:mm:ss'));
let timer = ref(0);
//按钮的点击回调
const goHome = () => {
    $router.push('/home')
}
//组件挂载完毕更新当前的事件
onMounted(() => {
    timer.value = setInterval(() => {
        time.value = moment().format('YYYY年MM月DD日 hh:mm:ss');
    }, 1000);
});
// 销毁定时器
onBeforeUnmount(() => {
    clearInterval(timer.value);
})
</script>

<style scoped lang="scss">
.top {
    width: 100%;
    height: 40px;
    display: flex;

    .left {
        flex: 1.5;
        background: url(../../images/dataScreen-header-left-bg.png) no-repeat;
        background-size: cover;

        .lbtn {
            width: 150px;
            height: 40px;
            float: right;
            background: url(../../images/dataScreen-header-btn-bg-l.png) no-repeat;
            background-size: 100% 100%;
            text-align: center;
            line-height: 40px;
            color: #29fcff;
            font-size: 20px;
        }
    }

    .center {
        flex: 2;

        .title {
            width: 100%;
            height: 74px;
            background: url(../../images/dataScreen-header-center-bg.png) no-repeat;
            background-size: 100% 100%;
            text-align: center;
            line-height: 74px;
            color: #29fcff;
            font-size: 30px;
        }
    }

    .right {
        flex: 1.5;
        background: url(../../images/dataScreen-header-left-bg.png) no-repeat;
        background-size: cover;
        display: flex;
        justify-content: space-between;
        align-items: center;

        .rbtn {
            width: 150px;
            height: 40px;
            background: url(../../images/dataScreen-header-btn-bg-r.png) no-repeat;
            background-size: 100% 100%;
            text-align: center;
            line-height: 40px;
            color: #29fcff;
        }

        .time {
            color: #29fcff;
            font-size: 20px;
        }


    }
}
</style>

4.左侧三个子组件

4.1游客统计组件tourist

(1)pnpm i echarts

(2)pnpm i echarts-liquidfill水球图组件

Apache ECharts

GitHub - ecomfe/echarts-liquidfill: Liquid Fill Chart for Apache ECharts

src/views/screen/components/tourist/index.vue 

<template>
    <div class="box">
        <div class="top">
            <p class="title">实时游客统计</p>
            <p class="bg"></p>
            <p class="right">可预约总量<span>99999</span>人</p>
        </div>
        <div class="number">
            <span v-for="(item, index) in people" :key="index">{{ item }}</span>
        </div>
        <!-- 盒子将来echarts展示图形图标的节点 -->
        <div class="charts" ref="charts">123</div>
    </div>
</template>

<script setup lang="ts">
import 'echarts-liquidfill'
import * as echarts from 'echarts';
import { ref, onMounted } from 'vue';
let people = ref('215908人');

//水球图拓展插件

//获取节点
let charts = ref();
onMounted(() => {
    //获取echarts类的实例
    let mycharts = echarts.init(charts.value);
    //设置实例的配置项
    mycharts.setOption({
        //标题组件
        title: {
            text: '水球图'
        },
        //x|y轴组件
        xAxis: {},
        yAxis: {},
        //系列:决定你展示什么样的图形图标
        series: {
            type: 'liquidFill',//系列
            data: [0.6, 0.4, 0.2],//展示的数据
            waveAnimation: true,//动画
            animationDuration: 3,
            animationDurationUpdate: 0,
            radius: '100%',//半径
            outline: {//外层边框颜色设置
                show: true,
                borderDistance: 8,
                itemStyle: {
                    color: 'skyblue',
                    borderColor: '#294D99',
                    borderWidth: 8,
                    shadowBlur: 20,
                    shadowColor: 'rgba(0, 0, 0, 0.25)'
                }
            },
        },
        //布局组件
        grid: {
            left: 0,
            right: 0,
            top: 0,
            bottom: 0
        }

    })
})
</script>

<style scoped lang="scss">
.box {
    background: url(../../images/dataScreen-main-lb.png) no-repeat;
    background-size: 100% 100%;
    margin-top: 10px;

    .top {
        margin-left: 20px;

        .title {
            color: white;
            font-size: 20px;

        }

        .bg {
            width: 68px;
            height: 7px;
            background: url(../../images/dataScreen-title.png) no-repeat;
            background-size: 100% 100%;
            margin-top: 10px;
        }

        .right {
            float: right;
            color: white;
            font-size: 20px;

            span {
                color: yellowgreen;
            }
        }
    }

    .number {
        padding: 10px;
        margin-top: 30px;
        display: flex;


        span {
            flex: 1;
            height: 40px;
            text-align: center;
            line-height: 40px;
            background: url(../../images/total.png) no-repeat;
            background-size: 100% 100%;
            color: #29fcff;
        }
    }

    .charts {
        width: 100%;
        height: 270px;
    }
}
</style>

 4.2Sex组件

(1)基础柱状图 - 柱状图 - 常用图表类型 - 应用篇 - 使用手册 - Apache ECharts

(2)柱状图 的使用

 src/views/screen/components/sex/index.vue

<template>
  <div class="box1">
    <div class="title">
      <p>男女比例</p>
      <img src="../../images/dataScreen-title.png" alt="">
    </div>
    <div class="sex">
      <div class="man">
        <img src="../../images/man.png" alt="">
      </div>
      <div class="women">
        <img src="../../images/woman.png" alt="">
      </div>

    </div>
    <div class="rate">
      <p>男士58%</p>
      <p>女士42%</p>
    </div>
    <div class="charts" ref='charts'></div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import * as echarts from 'echarts';
//获取图形图标的DOM节点
let charts = ref();
onMounted(() => {
  //初始化echarts实例
  let mycharts = echarts.init(charts.value);
  //设置配置项
  mycharts.setOption({
    //组件标题
    title: {
      text: '男女比例',//主标题
      textStyle: {//主标题颜色
        color: 'skyblue'
      },
      left: '40%'
    },
    //x|y
    xAxis: {
      show: false,
      min: 0,
      max: 100
    },
    yAxis: {
      show: false,
      type: 'category'
    },
    series: [
      {
        type: 'bar',
        data: [58],
        barWidth: 20,
        z: 100,
        itemStyle: {
          color: 'skyblue',
          borderRadius: 20
        }
      }
      ,
      {
        type: 'bar',
        data: [100],
        barWidth: 20,
        //调整女士柱条位置
        barGap: '-100%',
        itemStyle: {
          color: 'pink',
          borderRadius: 20
        }
      }
    ],
    grid: {
      left: 0,
      top: 0,
      right: 0,
      bottom: 0
    }
  });
})


</script>

<style scoped lang="scss">
.box1 {
  width: 100%;
  height: 100%;
  background: url(../../images/dataScreen-main-cb.png) no-repeat;
  background-size: 100% 100%;
  margin: 20px 0px;

  .title {
    margin-left: 20px;

    p {
      color: white;
      font-size: 20px;
    }
  }

  .sex {
    display: flex;
    justify-content: center;

    .man {
      margin: 20px;
      width: 111px;
      height: 115px;
      background: url(../../images/man-bg.png) no-repeat;
      display: flex;
      justify-content: center;
      align-items: center;
    }

    .women {
      margin: 20px;
      width: 111px;
      height: 115px;
      background: url(../../images/woman-bg.png) no-repeat;
      display: flex;
      justify-content: center;
      align-items: center;
    }

  }

  .rate {
    display: flex;
    justify-content: space-between;
    color: white;
  }

  .charts {
    height: 100px;
  }
}
</style>

4.3Age组件

 (1)饼图

圆环图 - 饼图 - 常用图表类型 - 应用篇 - 使用手册 - Apache ECharts

 src/views/screen/components/age/index.vue

<template>
    <div class="box2">
        <div class="title">
            <p>年龄比例</p>
            <img src="../../images/dataScreen-title.png" alt="">
        </div>
        <!-- 图形图标的容器 -->
        <div class="charts" ref="charts"></div>
    </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
//引入echarts
import * as echarts from 'echarts';
let charts = ref();
//组件挂载完毕初始化图形图标
onMounted(() => {
    let mychart = echarts.init(charts.value);
    //设置配置项
    let option = {
        tooltip: {
            trigger: 'item'
        },
        legend: {
            right: 30,
            top: 40,
            orient: 'vertical',//图例组件方向的设置
            textStyle: {
                color: 'white',
                fontSize: 14
            }
        },
        series: [
            {
                name: 'Access From',
                type: 'pie',
                radius: ['40%', '70%'],
                avoidLabelOverlap: false,
                itemStyle: {
                    borderRadius: 10,
                    borderColor: '#fff',
                    borderWidth: 2
                },
                label: {
                    show: true,
                    position: 'inside',
                    color:'white'
                },

                labelLine: {
                    show: false
                },
                data: [
                    { value: 1048, name: '军事' },
                    { value: 735, name: '新闻' },
                    { value: 580, name: '直播' },
                    { value: 484, name: '娱乐' },
                    { value: 300, name: '财经' }
                ]
            }
        ],
        //调整图形图标的位置
        grid: {
            left: 0,
            top: 0,
            right: 0,
            bottom: 0
        }
    };
    mychart.setOption(option);
});
</script>

<style scoped lang="scss">
.box2 {
    width: 100%;
    height: 100%;
    background: url(../../images/dataScreen-main-cb.png) no-repeat;
    background-size: 100% 100%;

    .title {
        margin-left: 20px;

        p {
            color: white;
            font-size: 20px;
        }
    }

    .charts {
        height: 260px;
    }

}
</style>

5.中间两个子组件

5.1Map组件中国地图

(1)ISQQW.COM x ECharts 文档(国内同步镜像) - 配置项

(2)阿里云网站提供中国地图数据

http://datav.aliyun.com/portal/school/atlas/area_selector

将数据放入map/china.json

注册中国地图》引入json数据》onMounted设置配置项》地图组件

  src/views/screen/components/map/index.vue

<template>
    <div class="box4" ref="map">
        我是地图组件
    </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import * as echarts from 'echarts';
//引入中国地图的JSON数据
import chinaJSON from './china.json'
//获取DOM元素
let map = ref();
//注册中国地图
echarts.registerMap('china', chinaJSON as any)
onMounted(() => {
    let mychart = echarts.init(map.value);
    //设置配置项
    mychart.setOption({
        //地图组件
        geo: {
            map: 'china',//中国地图
            roam: true,//鼠标缩放的效果
            //地图的位置调试
            left: 150,
            top: 150,
            right: 150,
            zoom:1.2,
            bottom: 0,
            //地图上的文字的设置
            label: {
                show: true,//文字显示出来
                color: 'white',
                fontSize: 14
            },

            itemStyle: {
                //每一个多边形的样式
                color: {
                    type: 'linear',
                    x: 0,
                    y: 0,
                    x2: 0,
                    y2: 1,
                    colorStops: [{
                        offset: 0, color: 'red' // 0% 处的颜色
                    }, {
                        offset: 1, color: 'blue' // 100% 处的颜色
                    }],
                    global: false // 缺省为 false
                },
                opacity: .8
            },
            //地图高亮的效果
            emphasis: {
                itemStyle: {
                    color: 'red'
                },
                label: {
                    fontSize: 40
                }
            }
        },
        //布局位置
        grid: {
            left: 0,
            top: 0,
            right: 0,
            bottom: 0
        },
        series: [
            {
                type: 'lines',//航线的系列
                data: [
                    {
                        coords: [
                            [116.405285, 39.904989],  // 起点
                            [119.306239, 26.075302]   // 终点

                        ],
                        // 统一的样式设置
                        lineStyle: {
                            color: 'orange',
                            width: 5
                        }
                    },
                    {
                        coords: [
                            [116.405285, 39.904989],  // 起点
                            [114.298572,30.584355]   // 终点

                        ],
                        // 统一的样式设置
                        lineStyle: {
                            color: 'yellow',
                            width: 5
                        }
                    }
                ],
                //开启动画特效
                effect: {
                    show: true,
                    symbol: 'arrow',
                    color: 'black',
                    symbolSize: 10
                }
            }
        ]
    })

});
</script>

<style scoped></style>

 5.2Line组件游客趋势折线图

 src/views/screen/components/line/index.vue

<template>
    <div class="box5">
        <div class="title">
            <p>未来七天游客数量趋势图</p>
            <img src="../../images/dataScreen-title.png" alt="">
        </div>
        <div class="charts" ref='line'></div>
    </div>
</template>

<script setup lang="ts">
import * as echarts from 'echarts';
import { ref, onMounted } from 'vue';
//获取图形图标的节点
let line = ref();
onMounted(() => {
    let mycharts = echarts.init(line.value);
    //设置配置项
    mycharts.setOption({
        //标题组件
        title: {
            text: '访问量'
        },
        //x|y轴
        xAxis: {
            type: 'category',
            //两侧不留白
            boundaryGap: false,
            //分割线不要
            splitLine: {
                show: false
            },
            data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
            //轴线的设置
            axisLine: {
                show: true
            },
            //刻度
            axisTick: {
                show: true
            }
        },
        yAxis: {
            splitLine: {
                show: false
            },
            //轴线的设置
            axisLine: {
                show: true
            },
            //刻度
            axisTick: {
                show: true
            }
        },
        grid: {
            left: 40,
            top: 0,
            right: 20,
            bottom: 20
        },
        //系列
        series: [
            {
                type: 'line',
                data: [120, 1240, 66, 2299, 321, 890, 1200],
                //平滑曲线的设置
                smooth: true,
                //区域填充样式
                areaStyle: {
                    color: {
                        type: 'linear',
                        x: 0,
                        y: 0,
                        x2: 0,
                        y2: 1,
                        colorStops: [{
                            offset: 0, color: 'red' // 0% 处的颜色
                        }, {
                            offset: 1, color: 'blue' // 100% 处的颜色
                        }],
                        global: false // 缺省为 false
                    }
                }
            }
        ]
    })
})
</script>

<style scoped lang="scss">
.box5 {
    width: 100%;
    height: 100%;
    background: url(../../images/dataScreen-main-cb.png) no-repeat;
    background-size: 100% 100%;
    margin: 0px 20px;

    .title {
        margin-left: 10px;

        p {
            color: white;
            font-size: 20px;
        }
    }

    .charts {
        height: calc(100% - 40px);
    }

}
</style>

6.右侧三个子组件

6.1Rank组件

 src/views/screen/components/rank/index.vue

<template>
    <div class="box6">
        <div class="title">
            <p>热门景区排行</p>
            <img src="../../images/dataScreen-title.png" alt="">
        </div>
        <!-- 图形图标的容器 -->
        <div class="charts" ref='charts'></div>
    </div>
</template>

<script setup lang="ts">
import * as echarts from 'echarts';
import { ref, onMounted } from 'vue';
//获取DOM节点
let charts = ref();
//组件挂载完毕
onMounted(() => {
    //一个容器可以同时展示多种类型的图形图标
    let mychart = echarts.init(charts.value);
    //设置配置项
    mychart.setOption({
        //标题组件
        title: {
            //主标题
            text: '景区排行',
            link: 'http://www.baidu.com',
            //标题的位置
            left: '50%',
            //主标题文字样式
            textStyle: {
                color: 'yellowgreen',
                fontSize: 20
            },
            //子标题
            subtext: "各大景区排行",
            //子标题的样式
            subtextStyle: {
                color: 'yellowgreen',
                fontSize: 16
            }
        },
        //x|y轴组件
        xAxis: {
            type: 'category',//图形图标在x轴均匀分布展示
        },
        yAxis: {},
        //布局组件
        grid: {
            left: 20,
            bottom: 20,
            right: 20
        },
        //系列:决定显示图形图标是哪一种的
        series: [
            {
                type: 'bar',
                data: [10, 20, 30, 40, 50, 60, 70],
                //柱状图的:图形上的文本标签,
                label: {
                    show: true,
                    //文字的位置
                    position: 'insideTop',
                    //文字颜色
                    color: 'yellowgreen'
                },
                //是否显示背景颜色
                showBackground: true,
                backgroundStyle: {
                    //底部背景的颜色
                    color: {
                        type: 'linear',
                        x: 0,
                        y: 0,
                        x2: 0,
                        y2: 1,
                        colorStops: [{
                            offset: 0, color: 'black' // 0% 处的颜色
                        }, {
                            offset: 1, color: 'blue' // 100% 处的颜色
                        }],
                        global: false // 缺省为 false
                    }
                },
                //柱条的样式
                itemStyle: {
                    borderRadius:[10, 10, 0, 0],
                    //柱条颜色
                    color:function(data:any){
                        //给每一个柱条这是背景颜色
                        let arr =['red','orange','yellowgreen','green','purple','hotpink','skyblue']
                         return arr[data.dataIndex];
                    }
                }
            },
            {
                type:'line',
                data:[10,20,30,40,50,60,90],
                smooth:true,//平滑曲线
            },
            {
                type: 'bar',
                data: [10, 20, 30, 40, 50, 60, 70],
                //柱状图的:图形上的文本标签,
                label: {
                    show: true,
                    //文字的位置
                    position: 'insideTop',
                    //文字颜色
                    color: 'yellowgreen'
                },
                //是否显示背景颜色
                showBackground: true,
                backgroundStyle: {
                    //底部背景的颜色
                    color: {
                        type: 'linear',
                        x: 0,
                        y: 0,
                        x2: 0,
                        y2: 1,
                        colorStops: [{
                            offset: 0, color: 'black' // 0% 处的颜色
                        }, {
                            offset: 1, color: 'blue' // 100% 处的颜色
                        }],
                        global: false // 缺省为 false
                    }
                },
                //柱条的样式
                itemStyle: {
                    borderRadius:[10, 10, 0, 0],
                    //柱条颜色
                    color:function(data:any){
                        //给每一个柱条这是背景颜色
                        let arr =['red','orange','yellowgreen','green','purple','hotpink','skyblue']
                         return arr[data.dataIndex];
                    }
                }
            },
        ],
        tooltip:{
            backgroundColor:'rgba(50,50,50,0.7)'
        }
    })
})
</script>

<style scoped lang="scss">
.box6 {
    width: 100%;
    height: 100%;
    background: url(../../images/dataScreen-main-cb.png) no-repeat;
    background-size: 100% 100%;
    margin: 20px 0px;

    .title {
        margin-left: 5px;

        p {
            color: white;
            font-size: 20px;
        }
    }

    .charts {
        height: calc(100% - 30px);
    }
}
</style>

6.2Year组件

 src/views/screen/components/year/index.vue

<template>
  <div class="box7">
    <div class="title">
      <p>年度游客量对比</p>
      <img src="../../images/dataScreen-title.png" alt="">
    </div>
    <div class="charts" ref="charts"></div>
  </div>
</template>

<script setup lang="ts">
import * as echarts from 'echarts';
import { ref, onMounted } from 'vue';
//获取DOM节点
let charts = ref();
//组件挂载完毕
onMounted(() => {
  //一个容器可以同时展示多种类型的图形图标
  let mychart = echarts.init(charts.value);
  //设置配置项
  mychart.setOption({
    title: {
      text: '散点图',
      left: '40%',
      textStyle: {
        color: 'white'
      }
    },
    xAxis: {
      type: 'category',
      show: true,
    },
    yAxis: {
      show: false
    },
    grid: {
      left: 20,
      top: 20,
      right: 0,
      bottom: 20
    },
    series: {
      type: 'scatter',
      data: [33, 88, 21, 9, 88, 234, 113, 1231, 674, 3, 88, 33, 21, 888, 3332, 313, 123, 5, 657, 7],
      //标记图形设置
      symbol: 'diamond',
      symbolSize: 16,
      //图文标签
      label: {
        show: true,
        position: 'top',
        color: 'red'
      },
      //散点图标记的颜色
      itemStyle: {
        color: {
          type: 'linear',
          x: 0,
          y: 0,
          x2: 0,
          y2: 1,
          colorStops: [{
            offset: 0, color: 'red' // 0% 处的颜色
          }, {
            offset: 1, color: 'blue' // 100% 处的颜色
          }],
          global: false // 缺省为 false
        }
      }
    }

  })
})
</script>

<style scoped lang="scss">
.box7 {
  width: 100%;
  height: 100%;
  background: url(../../images/dataScreen-main-cb.png) no-repeat;
  background-size: 100% 100%;
  margin: 20px 0px;

  .title {
    p {
      color: white;
      font-size: 18px;
    }
  }

  .charts {
    height: calc(100% - 30px);
  }

}
</style>

6.3Couter组件

 src/views/screen/components/couter/index.vue

<template>
  <div class="box8">
    <div class="title">
      <p>数据统计</p>
      <img src="../../images/dataScreen-title.png" alt="">
    </div>
    <div class="charts" ref="charts"></div>
  </div>
</template>
  
<script setup lang="ts">
import * as echarts from 'echarts';
import { ref, onMounted } from 'vue';
//获取DOM节点
let charts = ref();
//组件挂载完毕
onMounted(() => {
  //一个容器可以同时展示多种类型的图形图标
  let mychart = echarts.init(charts.value);
  let option = {
    title: {
      text: '游客消费统计',
      textStyle:{
        color:'white'
      }
    },
    radar: {
      // shape: 'circle',
      indicator: [
        { name: '消费', max: 6500 },
        { name: '好感', max: 16000 },
        { name: '出行', max: 30000 },
        { name: '小吃', max: 38000 },
        { name: '爱好', max: 52000 },
        { name: '景点', max: 25000 }
      ]
    },
    series: [
      {
        name: 'Budget vs spending',
        type: 'radar',
        data: [
          {
            value: [4200, 3000, 20000, 35000, 50000, 18000],
            name: '购物'
          },
          {
            value: [5000, 14000, 28000, 26000, 42000, 21000],
            name: '吃饭'
          }
        ]
      }
    ]
  };
  //设置配置项
  mychart.setOption(option)
})
</script>
  
<style scoped lang="scss">
.box8 {
  width: 100%;
  height: 100%;
  background: url(../../images/dataScreen-main-cb.png) no-repeat;
  background-size: 100% 100%;
  margin-top: 20px;

  .title {
    p {
      color: white;
      font-size: 18px;
    }
  }

  .charts {
    height: calc(100% - 30px);
  }

}
</style>

效果图 

二、菜单权限

1. 路由拆分 

将项目路由拆分为:

  • 静态路由:login、404、home、screen
  • 异步路由:权限管理(包含三个子路由)、商品管理(包含四个子路由)
  • 任意路由:任意路由

src/router/routes.ts

// 对外暴露配置路由(常量路由):全部用户都可以访问到的路由
export const constantRoute = [
  {
    // 登录
    path: '/login',
    component: () => import('@/views/login/index.vue'),
    name: 'login',
    meta: {
      title: '登录',
      hidden: true,
      icon: 'Moon',
    },
  },
  {
    // 登录成功以后展示数据的路由
    path: '/',
    component: () => import('@/layout/index.vue'),
    name: 'layout',
    meta: {
      title: '',
      hidden: false,
      icon: '', // 菜单文字左侧的图标,支持element-plus全部图标
    },
    redirect: '/home',
    children: [
      {
        path: '/home',
        component: () => import('@/views/home/index.vue'),
        meta: {
          title: '首页',
          hidden: false,
          icon: 'Sunny',
        },
      },
    ],
  },
  {
    // 404
    path: '/404',
    component: () => import('@/views/404/index.vue'),
    name: '404',
    meta: {
      title: '404',
      hidden: true,
      icon: 'Moon',
    },
  },
  {
    path: '/screen',
    component: () => import('@/views/screen/index.vue'),
    name: 'Screen',
    meta: {
      title: '数据大屏',
      hidden: false,
      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: 'Moon',
    },
  }


 2 . 菜单权限业务实现 

src/store/modules/user.ts

//创建用户相关的小仓库
import { defineStore } from 'pinia'
//引入接口
import { reqLogin, reqUserInfo, reqLogout } from '@/api/user'
import type {
  loginFormData,
  loginResponseData,
  userInfoResponseData,
} 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-expect-error
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
    }
  })
}

//创建用户小仓库
const useUserStore = defineStore('User', {
  //小仓库存储数据地方
  state: (): UserState => {
    return {
      token: GET_TOKEN(), //用户唯一标识token
      menuRoutes: constantRoute, //仓库存储生成菜单需要数组(路由)
      username: '',
      avatar: '',
      //存储当前用户是否包含某一个按钮
      buttons: [],
    }
  },
  //异步|逻辑的地方
  actions: {
    //用户登录的方法
    async userLogin(data: loginFormData) {
      //登录请求
      const 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() {
      //获取用户信息进行存储仓库当中[用户头像、名字]
      const result: userInfoResponseData = await reqUserInfo()
      //如果获取用户信息成功,存储一下用户信息
      if (result.code == 200) {
        this.username = result.data.name
        this.avatar = result.data.avatar
        this.buttons = result.data.buttons
        //计算当前用户需要展示的异步路由
        const 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() {
      //退出登录请求
      const 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

刷新的时候是异步路由,有可能获取到用户信息,异步路由还没有加载完毕,出现空白的效果!!

解决方法:在全局前置守卫中,获取用户信息后改成next({...to})

next() :直接放行(这种写法会导致刷新产生空白的效果)
next({...to}):等待路由加载完毕再放行

 src/permission.ts

三、按钮权限

1.用户按钮权限信息存储

src/store/modules/user.ts 

......
state: (): UserState => {
    return {
      ......
      //存储当前用户是否包含某一个按钮
      buttons: [],
    }
......
async userInfo() {
      ......
      // 如果获取信息成功,存储下用户信息
      if (result.code === 200) {
        ......
        this.buttons = result.data.buttons
        ......
      }

2.定义全局自定义指令

src/directive/has.ts 

import pinia from "@/store"
import useUserStore from "@/store/modules/user"
const 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)
            }
        },
    })
}

 在main.ts文件中引入自定义指令文件

// 引入自定义指令文件
import { isHasButton } from '@/directive/has'
isHasButton(app)

3.使用自定义指令配置按钮权限

此处以trademark作为例子,项目中其他按钮的权限都需要配置

src/views/product/trademark/index.vue

<el-button type="primary" size="default" icon="Plus" @click="addTrademark" v-has="'btn.Trademark.add'">

完结!后续还会复习项目总结一波!也会在做一个项目,有需要的可以关注!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1640842.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

顺序循环队列--c语言实现

#include <stdio.h> #include <stdlib.h> #include <stdbool.h>#define MAX_SIZE 100 // 假设队列的最大长度为100// 队列结构体 typedef struct {int data[MAX_SIZE]; // 存储队列元素的数组int front; // 队头指针int rear; // 队尾指针 } SeqQueue;// 初…

AI学习指南-专栏前言

前言 人工智能AI作为当下最火热的技术之一&#xff0c;我们每个人似乎都被动的置身于这场科技变革中&#xff0c;AI越来越多的出现在我们的身边&#xff0c;也总能听到这样的言论&#xff0c;例如“N年后AI会取代某某行业”&#xff0c;AI出现之后&#xff0c;好像大多数人都陷…

【研发日记】Matlab/Simulink避坑指南(十一)——Delay周期Bug

文章目录 前言 背景介绍 问题描述 分析排查 解决方案 总结归纳 前言 见《研发日记&#xff0c;Matlab/Simulink避坑指南(六)——字节分割Bug》 见《研发日记&#xff0c;Matlab/Simulink避坑指南(七)——数据溢出钳位Bug》 见《研发日记&#xff0c;Matlab/Simulink避坑指…

图像分割模型实际分隔效果测试(Clipseg + GroundingDINO + Yoloworld)

✨背景 对于设计的工作流来说&#xff0c;智能图像分割也是一个很重要的课题&#xff0c;尤其是像照片换脸、照片高清写真等等的工作流&#xff0c;可能要多次把人物的脸部或者手部抠图出来进行合成&#xff0c;工作流如果可以直接智能分割图像并合成&#xff0c;就可以避免自…

20240503安装HEVC解码器播放H265格式的8K视频

20240503安装HEVC解码器播放H265格式的8K视频 2024/5/3 9:55 缘起&#xff1a;由于youtube支持8K视频了&#xff0c;想尝尝鲜&#xff01; 主摄像头当然是选择SONY的【夜摄/弱光场景】&#xff0c;根据优选&#xff0c;小米&#xff08;MI&#xff09;13Ultra 最佳了。 在开始播…

设计模式动态代理

什么是设计模式? 一个问题通常有n种解法&#xff0c;其中肯定有一种解法是最优的&#xff0c;这个最优的解法被人总结出来了&#xff0c;称之为设计模式。 设计模式有20多种&#xff0c;对应20多种软件开发中会遇到的问题。 关于设计模式的学习&#xff0c;主要学什么&#…

项目管理-项目范围管理2/2

项目管理&#xff1a;每天进步一点点~ 活到老&#xff0c;学到老 ヾ(◍∇◍)&#xff89;&#xff9e; 何时学习都不晚&#xff0c;加油 项目范围管理&#xff0c;过程 6个&#xff0c;包括“规收定 创确控”&#xff1a; 规划范围管理收集需求定义范围创建WBS确认范围控制…

JS 笔记9 认识JavaScript

相关内容&#xff1a;JS对象、属性、常用事件处理过程、运算符、if...else、for、…… <script type"text/javascript"></script> type属性用来指定MIME(Multipurpose Internet Mail Extension)类型&#xff0c;主要是告诉浏览器目前使用的是哪一种Scri…

先电2.4的openstack搭建

先电2.4版本的openstack&#xff0c;前期虚拟机部署参考上一篇2.2版本&#xff0c;基本步骤是一样的&#xff0c;准备两个镜像文件CentOS-7.5-x86_64-DVD-1804.iso&#xff0c;XianDian-IaaS-V2.4.iso [rootcontroller ~]# cat /etc/sysconfig/network-scripts/ifcfg-eno16777…

【软件测试】软件测试基础

1. 需求1.1. 需求的概念1.2. 为什么要有需求1.3. 测试人员眼中的需求 2. 测试用例2.1. 为什么需要测试用例2.2. 什么是测试用例2.3. 一个简单的测试用例 3. 软件测试的整体流程4. bug4.1. 如何描述一个bug4.2. bug的级别4.3. bug生命周期 1. 需求 1.1. 需求的概念 简单的来说…

如何在iOS设备(iPhone,iPad等)上恢复丢失的照片

如果你像现代90%的人一样拥有智能手机&#xff0c;那么你很可能使用口袋里的微型电脑拍摄大部分&#xff08;如果不是全部&#xff09;照片&#xff0c;而不是标准的傻瓜相机或数码单反相机。 像任何数字设备一样&#xff0c;存储和保存这些照片可能是一个变化无常的过程&…

nvm 切换 Node 版本失败

创建vue3项目时&#xff0c;需要切换到更高版本的 node&#xff0c;于是使用 nvm (node 包版本管理工具)进行版本切换 切换版本时&#xff0c;显示成功&#xff0c;但再次查看当前 node 版本时&#xff0c;发现没切换过来。 解决方法&#xff1a; where node 查看node的安装…

spring高级篇(七)

1、异常处理 在DispatcherServlet中&#xff0c;doDispatch(HttpServletRequest request, HttpServletResponse response) 方法用于进行任务处理&#xff1a; 在捕获到异常后没有立刻进行处理&#xff0c;而是先用一个局部变量dispatchException进行记录&#xff0c;然后统一由…

找不到msvcr110.dll的多种解决方法,轻松解决dll问题

在日常使用计算机的时候&#xff0c;突然提示&#xff1a;“由于找不到msvcr110.dll&#xff0c;无法继续执行代码”。这个错误通常发生在运行某些程序时&#xff0c;系统无法找到所需的动态链接库文件。这个问题可能会给用户带来困扰&#xff0c;但是不用担心&#xff0c;下面…

ReentrantReadWriteLock(可重入读写锁)源码解读与使用

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java源码解读-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 目录 1. 前言 2. 读写锁是什么 3. ReentrantReadWriteLock是什么 4. 源码解…

每日OJ题_贪心算法二⑥_力扣409. 最长回文串

目录 力扣409. 最长回文串 解析代码 力扣409. 最长回文串 409. 最长回文串 难度 简单 给定一个包含大写字母和小写字母的字符串 s &#xff0c;返回 通过这些字母构造成的 最长的回文串 。 在构造过程中&#xff0c;请注意 区分大小写 。比如 "Aa" 不能当做一个…

spring框架学习记录(1)

前半个月一直在应付期中考试&#xff0c;快被折磨似了orz 文章目录 SpringIoC(Inversion of Control) 控制反转与DI(Dependency Injection)依赖注入bean相关bean配置bean实例化bean的生命周期 依赖注入相关依赖注入方式依赖自动装配 容器创建容器获取bean Spring IoC(Inversi…

场景文本检测识别学习 day08(无监督的Loss Function、代理任务)

无监督的Loss Function&#xff08;无监督的目标函数&#xff09; 根据有无标签&#xff0c;可以将模型的学习方法分为&#xff1a;无监督、有监督两种。而自监督是无监督的一种无监督的目标函数可以分为以下几种&#xff1a; 生成式网络的做法&#xff0c;衡量模型的输出和固…

网络安全审计

一、什么叫网络安全审计 网络安全审计是按照一定的安全策略&#xff0c;利用记录、系统活动和用户活动等信息&#xff0c;检查、审查和检验操作时间的环境及活动&#xff0c;从而发现系统漏洞、入侵行为或改善系统性能的过程&#xff0c;它是提高系统安全性的重要手段。 系统…

巨人网络发布2023年年报:全力拥抱AI浪潮,开启游戏产业新篇章

易采游戏网5月3日消息&#xff0c;国内知名游戏公司巨人网络发布了其2023年度财务报告&#xff0c;报告显示&#xff0c;公司在过去一年中积极拥抱AI技术&#xff0c;实现了业绩的稳步增长&#xff0c;为游戏产业带来了新的活力与机遇。 在报告中&#xff0c;巨人网络详细阐述了…