Skip to content

大屏开发技巧整理

此次项目大屏的需求是需要嵌入管理页面中,并且需要放大缩小时两边不能留白,即因不能等比缩放,所以不能使用市面上使用比较多的transform: scale的方法,后来使用vw方案,效果不是很理想,最终使用百分比以及弹性盒子方案来做整体框架布局,为了不影响使用,需要在页面变形前加入滚动条,上下滚动的话在各个子模块中使用滚动,不在全局使用

自适应方案

  • 鉴于以上需求,并且在页面缩小的时候不影响使用效果,我们在最外层盒子上面添加如下样式
vue
<template>
    <div class="data_screen_container">
        <div class="scale_box"></div>
    </div>
</template>

<style lang="scss" scoped>
.data_screen_container {
    width: 100%;
    height: 100%;
    overflow-x: auto;
    overflow-y: hidden;  // 全局禁用y轴滚动条

    .scale_box {
        background: url("./images/bg.png") top no-repeat;  // 背景图
        background-size: 100% 100%;
        min-width: 1380px;  // 页面缩小时不影响布局
    } 
}
</style>
<template>
    <div class="data_screen_container">
        <div class="scale_box"></div>
    </div>
</template>

<style lang="scss" scoped>
.data_screen_container {
    width: 100%;
    height: 100%;
    overflow-x: auto;
    overflow-y: hidden;  // 全局禁用y轴滚动条

    .scale_box {
        background: url("./images/bg.png") top no-repeat;  // 背景图
        background-size: 100% 100%;
        min-width: 1380px;  // 页面缩小时不影响布局
    } 
}
</style>
  • 在内容主体上我们使用上下结构,先设置两个盒子的宽高
  • 主体内容的宽度我们可以用flex也预先定义好
vue
<template>
    <div class="scale_box">
        <div class="data_screen_header"></div>
        <div class="data_screen_main">
            <div class="data_screen_lf"></div>
            <div class="data_screen_ct"></div>
            <div class="data_screen_rg"></div>
        </div>
    </div>
</template>

<style lang="scss" scoped>
$topHeight: 92px;  // 头部高度
.scale_box {
    .data_screen_header {
        position: relative;  // 里面的内容需要绝对定位
        width: 100%;
        max-height: $topHeight;
        overflow: hidden;
    }

    .data_screen_main {
        display: flex;  // flex弹性盒子布局
        justify-content: space-between;  // 子div撑开并且左右对齐
        width: 99%;  // 两边需要留点白
        margin: .3vw auto 0;  // 上下需要留点白,并且水平居中
        height: calc(100% - $topHeight - .3vw * 2);  // 按照百分比来计算,除去留白部分的主题内容
        z-index: 1;

        .data_screen_lf, .data_screen_rg {
            width: 26%;  // 左右两块宽度
        }

        .data_screen_ct {
            flex: 1;  // 刨去左右两边,中间自适应
        }
    }
}
</style>
<template>
    <div class="scale_box">
        <div class="data_screen_header"></div>
        <div class="data_screen_main">
            <div class="data_screen_lf"></div>
            <div class="data_screen_ct"></div>
            <div class="data_screen_rg"></div>
        </div>
    </div>
</template>

<style lang="scss" scoped>
$topHeight: 92px;  // 头部高度
.scale_box {
    .data_screen_header {
        position: relative;  // 里面的内容需要绝对定位
        width: 100%;
        max-height: $topHeight;
        overflow: hidden;
    }

    .data_screen_main {
        display: flex;  // flex弹性盒子布局
        justify-content: space-between;  // 子div撑开并且左右对齐
        width: 99%;  // 两边需要留点白
        margin: .3vw auto 0;  // 上下需要留点白,并且水平居中
        height: calc(100% - $topHeight - .3vw * 2);  // 按照百分比来计算,除去留白部分的主题内容
        z-index: 1;

        .data_screen_lf, .data_screen_rg {
            width: 26%;  // 左右两块宽度
        }

        .data_screen_ct {
            flex: 1;  // 刨去左右两边,中间自适应
        }
    }
}
</style>
  • 主体区域的宽度和高度已经设置好了,接下来我们设置主题内容块样式
  • 主体内容有三裂,横向布局使用flex,纵向为100%,我们按情况进行切割
  • 主体内容高度为100%,但我们需要在页面缩小到形变的时候,加入滚动条,并且滚动条需要被隐藏
vue
<template>
    <div class="data_screen_main">
        <div class="data_screen_lf">
            <div class="scroll_auto">
                <div class="dt_scrn_item"></div>
                <div class="dt_scrn_item"></div>
                <div class="dt_scrn_item"></div>
            </div>
        </div>
        <div class="data_screen_ct">
            <div class="scroll_auto">
                <div class="dsren_ct_top">
                    <div class="dt_scrn_item"></div>
                    <div class="dt_scrn_item"></div>
                </div>
                <div class="dsren_ct_bot">
                    <div class="dt_scrn_item"></div>
                    <div class="dt_scrn_item"></div>
                </div>
            </div>
        </div>
        <div class="data_screen_rg">
            <div class="scroll_auto">
                <div class="dt_scrn_item"></div>
                <div class="dt_scrn_item"></div>
                <div class="dt_scrn_item"></div>
            </div>
        </div>
    </div>
</template>

<style lang="scss" scoped>
$marginBetween: 8px;

.data_screen_main {
    // 三纵列的公共样式
    .data_screen_lf, .data_screen_ct, .data_screen_rg {
        overflow: auto;  // 需要滚动
        scrollbar-width: none;  // 并且隐藏滚动条

        .scroll_auto {
            width: 100%;
            height: 100%;
            min-height: 678px;  // 滚动区域
            overflow: hidden;
        }
    }

    // 主体区域的宽度,上面已经使用flex设置好了,这边就不作赘述了
    // 这里主要展示内部怎么分层

    // 左右两边样式 - 因为是对称的,所以两边应该是相同的
    .data_screen_lf, .data_screen_rg {
        top: -48px;  // 按照需求,左右两边需要向上提
        height: calc(100% + 48px);  // 向上提之后,高度增加了,需要在100%的高度基础上增加

        // 接下来设置左右两边内部一个个子模块的高度,宽度则是100%
        .dt_scrn_item {
            width: 100%;
            height: calc(100% / 3);  // 高度为z了精确,我们直接进行计算,三等分
            overflow: hidden;
        }
    }

    // 中间样式
    .data_screen_ct {
        // 中间需要留白,为了留白距离一样,我们设置变量
        margin: 0 $marginBetween;

        // 先设置中间上下两个盒子的高度,第一个盒子要高一些
        .dsren_ct_top {
            height: 63%;
            margin-bottom: $marginBetween;
            overflow: hidden;
        }
        .dsren_ct_bot {
            // 第二个盒子直接用百分比进行计算,并且减去留白
            height: calc(100% - 63% - $marginBetween);
            overflow: hidden;
        }

        // 至此中间的两个盒子宽高都填充完成了,接下来就是内部子模块的填充

        // 上下两个盒子,第一个盒子和第二个盒子需要上下对齐,即它们的高度相同,所以需要同时进行设置
        .dsren_ct_top, .dsren_ct_bot {
            display: flex;
            width: 100%;  // 高度默认是100%

            .dt_scrn_item {
                // 两个盒子的第一列
                &:first-child {
                    width: 65%;
                    margin-right: $marginBetween;
                }

                // 两个盒子的第二列
                &:last-child {
                    flex: 1;  // 刨去第一列和留白,flex会自动进行填充
                }
            }
        }
    }
}
</style>
<template>
    <div class="data_screen_main">
        <div class="data_screen_lf">
            <div class="scroll_auto">
                <div class="dt_scrn_item"></div>
                <div class="dt_scrn_item"></div>
                <div class="dt_scrn_item"></div>
            </div>
        </div>
        <div class="data_screen_ct">
            <div class="scroll_auto">
                <div class="dsren_ct_top">
                    <div class="dt_scrn_item"></div>
                    <div class="dt_scrn_item"></div>
                </div>
                <div class="dsren_ct_bot">
                    <div class="dt_scrn_item"></div>
                    <div class="dt_scrn_item"></div>
                </div>
            </div>
        </div>
        <div class="data_screen_rg">
            <div class="scroll_auto">
                <div class="dt_scrn_item"></div>
                <div class="dt_scrn_item"></div>
                <div class="dt_scrn_item"></div>
            </div>
        </div>
    </div>
</template>

<style lang="scss" scoped>
$marginBetween: 8px;

.data_screen_main {
    // 三纵列的公共样式
    .data_screen_lf, .data_screen_ct, .data_screen_rg {
        overflow: auto;  // 需要滚动
        scrollbar-width: none;  // 并且隐藏滚动条

        .scroll_auto {
            width: 100%;
            height: 100%;
            min-height: 678px;  // 滚动区域
            overflow: hidden;
        }
    }

    // 主体区域的宽度,上面已经使用flex设置好了,这边就不作赘述了
    // 这里主要展示内部怎么分层

    // 左右两边样式 - 因为是对称的,所以两边应该是相同的
    .data_screen_lf, .data_screen_rg {
        top: -48px;  // 按照需求,左右两边需要向上提
        height: calc(100% + 48px);  // 向上提之后,高度增加了,需要在100%的高度基础上增加

        // 接下来设置左右两边内部一个个子模块的高度,宽度则是100%
        .dt_scrn_item {
            width: 100%;
            height: calc(100% / 3);  // 高度为z了精确,我们直接进行计算,三等分
            overflow: hidden;
        }
    }

    // 中间样式
    .data_screen_ct {
        // 中间需要留白,为了留白距离一样,我们设置变量
        margin: 0 $marginBetween;

        // 先设置中间上下两个盒子的高度,第一个盒子要高一些
        .dsren_ct_top {
            height: 63%;
            margin-bottom: $marginBetween;
            overflow: hidden;
        }
        .dsren_ct_bot {
            // 第二个盒子直接用百分比进行计算,并且减去留白
            height: calc(100% - 63% - $marginBetween);
            overflow: hidden;
        }

        // 至此中间的两个盒子宽高都填充完成了,接下来就是内部子模块的填充

        // 上下两个盒子,第一个盒子和第二个盒子需要上下对齐,即它们的高度相同,所以需要同时进行设置
        .dsren_ct_top, .dsren_ct_bot {
            display: flex;
            width: 100%;  // 高度默认是100%

            .dt_scrn_item {
                // 两个盒子的第一列
                &:first-child {
                    width: 65%;
                    margin-right: $marginBetween;
                }

                // 两个盒子的第二列
                &:last-child {
                    flex: 1;  // 刨去第一列和留白,flex会自动进行填充
                }
            }
        }
    }
}
</style>
  • 至此,整个页面的整体布局就完成了,这也是我们此次大屏自适应的核心方案

纵向排列的dom如何进行自适应

  • 页面里面还有一个点的样式需要单独拉出来讲一下,纵向排列的有序列表,如何在盒子中进行等分排列,并且页面不管如何缩放都是等分排列的
  • 这一点困扰了我很久,我把我捣鼓出来的解决方案分享一下
vue
<template>
    <div class="data_dot_list">
        <ul>
            <li v-for=(item in dataDotList) :key="item.id">
                <dl>
                    <!-- 这里是一个小图标 -->
                    <dt></dt>
                    <dd>
                        <span>xxxx</span>
                    </dd>
                </dl>
            </li>
            <li>
                <dl>
                    <dt></dt>
                    <dd>
                        <span>xxxx</span>
                    </dd>
                </dl>
            </li>
            <li>
                <dl>
                    <dt></dt>
                    <dd>
                        <span>xxxx</span>
                    </dd>
                </dl>
            </li>
        </ul>
    </div>
</template>

<script setup>
const dataDotList = ref([])
</script>

<style lang="scss" scoped>
// 我们默认data_dot_list是一个100%
.data_dot_list {
    height: 100%;

    ul {
        display: flex;
        flex-direction: column;  // 纵向排列
        // 设置文本内容的位置
        width: 80%;
        margin: 2.6% auto 0;

        // 设置每个子项文本的高度
        li {
            position: relative;
            // height: calc(100% / 8 - .6%);
            // 或者动态获取
            height: calc(100% / v-bind("dataDotList.length") - .6%);

            // ......
            // 其他就是简单的样式,不作赘述了
        }
    }
}
</style>
<template>
    <div class="data_dot_list">
        <ul>
            <li v-for=(item in dataDotList) :key="item.id">
                <dl>
                    <!-- 这里是一个小图标 -->
                    <dt></dt>
                    <dd>
                        <span>xxxx</span>
                    </dd>
                </dl>
            </li>
            <li>
                <dl>
                    <dt></dt>
                    <dd>
                        <span>xxxx</span>
                    </dd>
                </dl>
            </li>
            <li>
                <dl>
                    <dt></dt>
                    <dd>
                        <span>xxxx</span>
                    </dd>
                </dl>
            </li>
        </ul>
    </div>
</template>

<script setup>
const dataDotList = ref([])
</script>

<style lang="scss" scoped>
// 我们默认data_dot_list是一个100%
.data_dot_list {
    height: 100%;

    ul {
        display: flex;
        flex-direction: column;  // 纵向排列
        // 设置文本内容的位置
        width: 80%;
        margin: 2.6% auto 0;

        // 设置每个子项文本的高度
        li {
            position: relative;
            // height: calc(100% / 8 - .6%);
            // 或者动态获取
            height: calc(100% / v-bind("dataDotList.length") - .6%);

            // ......
            // 其他就是简单的样式,不作赘述了
        }
    }
}
</style>

遇到渐变色时,如果使用transition过渡属性

  • 页面上我们需要做一个平行四边形效果,背景色渐变,并且需要过渡效果,但是在渐变的时候transition失效,于是我使用了另外一种方案尽量避开渐变和transition的冲突
vue
<template>
<ul class="btn_list" @click="btnListLink">
    <li>
        <div class="box"><b></b></div>
        <span>aaaa</span>
    </li>
    <li>
        <div class="box"><b></b></div>
        <span>bbbb</span>
    </li>
    <li>
        <div class="box"><b></b></div>
        <span>cccc</span>
    </li>
    <li>
        <div class="box"><b></b></div>
        <span>dddd</span>
    </li>
    <li>
        <div class="box"><b></b></div>
        <span>eeee</span>
    </li>
</ul>
</template>

<style lang="scss" scoped>
// 整个盒子我们设置绝对定位,不过这里和我们要说的点没有关系
ul.btn_list {
    position: absolute;
    top: 7px;
    left: 10px;
    z-index: 1;
    cursor: pointer;

    // 我们使用li来作为每个需要添加效果的盒子,并设置相对定位
    li {
        position: relative;
        display: inline-block;  // 横向排列
        width: 88px;
        height: 26px;
        cursor: pointer;

        &:not(:last-child) {
            margin-right: 10px;  // 刨去最后一个,给每个盒子设置间距
        }

        // 上下两个大盒子,一个是用来作效果的,一个用来放文字
        .box, span {
            position: absolute;
            top: 0;
            left: 0;
            min-width: 88px
        }

        // 这里开始效果展示 - 核心代码
        // 这个box就是平行四边形
        .box {
            display: block;
            width: 100%;
            height: 100%;
            transform: skew(-20deg);  // 旋转 - 平行四边形核心代码
            // transform: skew(23deg);
            background: linear-gradient(to left, #1F4F8A, #0f275a);
            border-radius: 5px;

            // 平行四边形里面的小盒子用来展示鼠标移入的渐变效果
            // 默认是隐藏,即透明度为0
            // 鼠标移入实际上是移到b标签这个位置
            b {
                display: block;
                width: 100%;
                height: 100%;
                background: linear-gradient(to right, #1F4F8A, #0f275a);
                opacity: 0;
                transition: .5s linear;
                border-radius: 5px;
            }

            // + 表示兄弟属性,这里的文字也需要在鼠标移入的时候变亮
            &+span {
                display: block;
                left: 51%;
                top: 50%;
                transform: translate(-50%, -50%);
                font-family: $mainFontFamily;
                font-size: 16px;
                color: #4ABEFE;
                line-height: 100%;
                text-align: center;
                opacity: .8;
                transition: .8s linear;
            }
        }

        // 在鼠标移入box的时候触发hover效果
        // 并且需要作用在box的兄弟元素span和b上
        &:hover {
            .box {
                &+span,
                    b {
                    opacity: 1;
                    transition: .8s linear;
                }
            }
        }
    }
}
</style>

<script setup>
// 使用冒泡的方式捕获dom并设置dom下的所有事件
const btnListLink = (e) => {
  const { target } = e
  const { innerText } = target
  // console.log(innerText)

  switch (innerText) {
    case 'aaaa':
      console.log('aaaa')
      break
    case 'bbbb':
      console.log('bbbb')
      break
    case 'cccc':
      console.log('cccc')
      break
    case 'dddd':
      console.log('dddd')
      break
    case 'eeee':
      console.log('eeee')
      break
  }
}
</script>
<template>
<ul class="btn_list" @click="btnListLink">
    <li>
        <div class="box"><b></b></div>
        <span>aaaa</span>
    </li>
    <li>
        <div class="box"><b></b></div>
        <span>bbbb</span>
    </li>
    <li>
        <div class="box"><b></b></div>
        <span>cccc</span>
    </li>
    <li>
        <div class="box"><b></b></div>
        <span>dddd</span>
    </li>
    <li>
        <div class="box"><b></b></div>
        <span>eeee</span>
    </li>
</ul>
</template>

<style lang="scss" scoped>
// 整个盒子我们设置绝对定位,不过这里和我们要说的点没有关系
ul.btn_list {
    position: absolute;
    top: 7px;
    left: 10px;
    z-index: 1;
    cursor: pointer;

    // 我们使用li来作为每个需要添加效果的盒子,并设置相对定位
    li {
        position: relative;
        display: inline-block;  // 横向排列
        width: 88px;
        height: 26px;
        cursor: pointer;

        &:not(:last-child) {
            margin-right: 10px;  // 刨去最后一个,给每个盒子设置间距
        }

        // 上下两个大盒子,一个是用来作效果的,一个用来放文字
        .box, span {
            position: absolute;
            top: 0;
            left: 0;
            min-width: 88px
        }

        // 这里开始效果展示 - 核心代码
        // 这个box就是平行四边形
        .box {
            display: block;
            width: 100%;
            height: 100%;
            transform: skew(-20deg);  // 旋转 - 平行四边形核心代码
            // transform: skew(23deg);
            background: linear-gradient(to left, #1F4F8A, #0f275a);
            border-radius: 5px;

            // 平行四边形里面的小盒子用来展示鼠标移入的渐变效果
            // 默认是隐藏,即透明度为0
            // 鼠标移入实际上是移到b标签这个位置
            b {
                display: block;
                width: 100%;
                height: 100%;
                background: linear-gradient(to right, #1F4F8A, #0f275a);
                opacity: 0;
                transition: .5s linear;
                border-radius: 5px;
            }

            // + 表示兄弟属性,这里的文字也需要在鼠标移入的时候变亮
            &+span {
                display: block;
                left: 51%;
                top: 50%;
                transform: translate(-50%, -50%);
                font-family: $mainFontFamily;
                font-size: 16px;
                color: #4ABEFE;
                line-height: 100%;
                text-align: center;
                opacity: .8;
                transition: .8s linear;
            }
        }

        // 在鼠标移入box的时候触发hover效果
        // 并且需要作用在box的兄弟元素span和b上
        &:hover {
            .box {
                &+span,
                    b {
                    opacity: 1;
                    transition: .8s linear;
                }
            }
        }
    }
}
</style>

<script setup>
// 使用冒泡的方式捕获dom并设置dom下的所有事件
const btnListLink = (e) => {
  const { target } = e
  const { innerText } = target
  // console.log(innerText)

  switch (innerText) {
    case 'aaaa':
      console.log('aaaa')
      break
    case 'bbbb':
      console.log('bbbb')
      break
    case 'cccc':
      console.log('cccc')
      break
    case 'dddd':
      console.log('dddd')
      break
    case 'eeee':
      console.log('eeee')
      break
  }
}
</script>
  • 以上就完成了在渐变色遇上过度效果时,如何共存的问题,主要就是使用加一个div上去用来显示鼠标移入后的效果,默认为透明,然后鼠标移入的时候,改变透明度,这样就可以实现渐变色和过度效果共存了
  • 同时为了方便,直接使用冒泡去捕获btn_list盒子下的所有dom,并为其设置事件

监听dom以到达自适应效果

怎么做到若伊框架侧边栏展开或收起时,大屏页面自适应

  • 侧栏收起,框架是使用cookie去控制open变量,又是通过open变量去控制openSidebar和hideSidebar,所以我们可以监听openSidebar和hideSidebar的变化,从而实现自适应
js
// 初始化子页面图表
const resetAllChart = () => {
  setTimeout(() => {
    nextTick(() => {
      refLeftPage.value && refLeftPage.value.resetChart()
      refCenterPage.value && refCenterPage.value.resetChart()
      refRightPage.value && refRightPage.value.resetChart()
    })
  }, 500)
}

// 监听dom变化,在展开或者收起的时候重新渲染图表
// 创建Observer实例,并指定回调函数
const observer = new MutationObserver(mutations => {
    mutations.forEach(mutations => {
        // 处理DOM变化
        resetAllChart()
    });
});

// 需要被监听的dom
const targetNode = document.querySelector('.app-wrapper');
// observer.observe(targetNode, { childList: true, subtree: true, attributes: true, characterData: true });

// 如果dom存在,则监听dom的某几个属性
if (targetNode) {
    observer.observe(targetNode, { attributes: true, attributeFilter: ['class'] });
}
// 初始化子页面图表
const resetAllChart = () => {
  setTimeout(() => {
    nextTick(() => {
      refLeftPage.value && refLeftPage.value.resetChart()
      refCenterPage.value && refCenterPage.value.resetChart()
      refRightPage.value && refRightPage.value.resetChart()
    })
  }, 500)
}

// 监听dom变化,在展开或者收起的时候重新渲染图表
// 创建Observer实例,并指定回调函数
const observer = new MutationObserver(mutations => {
    mutations.forEach(mutations => {
        // 处理DOM变化
        resetAllChart()
    });
});

// 需要被监听的dom
const targetNode = document.querySelector('.app-wrapper');
// observer.observe(targetNode, { childList: true, subtree: true, attributes: true, characterData: true });

// 如果dom存在,则监听dom的某几个属性
if (targetNode) {
    observer.observe(targetNode, { attributes: true, attributeFilter: ['class'] });
}

echarts封装,新的取值方式

  • vue3种echart的封装使用方式大体和之前相同,但里面随着页面的缩小放大,echarts需要重新渲染的功能之前使用mix的方式写的,vue3中我们来重新弄一下
  • 这里将echart组件整个粘贴过来了,后面组件主要是在option里面作了修改,大体结构都是一样的,其他echart组件都可以参考本节

修改之后的echarts封装

  • 在页面使用时,echarts子组件的渲染会在父组件之前,即先渲染子组件,再渲染父组件
  • 这样导致了,每次echarts里面是没有数据的,同时数据传递是异步的,就更加影响组件的渲染了
  • 梳理一下,我们不做任何操作,页面逻辑是这样的 子组件渲染(数据默认为空) => 父组件渲染 => 异步获取接口数据
  • 但是我们需要 父组件渲染 => 异步获取接口数据 => 子组件渲染
  • 所以我们需要设置监听从父组件传到子组件的数据,在数据加载完成之后再进行渲染
  • 有两种方式,一种是设置flag,在数据请求完成之后,flag为true,再进行渲染图表,但这种方案在echart组件比较多的时候比较麻烦
  • 我们使用第二种,监听,但监听有一个弊端,echart组件是一直存在的,就是数据为空的时候会进行一次加载,所以在echarts里面需要进行一次非空判断
  • 注: 里面还包含了轮播代码
vue
<template>
  <section ref="refChart" class="chart_wrap" :class="className" :style="{ height: height, width: width }"></section>
</template>

<script setup>
import { onMounted, onBeforeUnmount, ref, watch, nextTick } from 'vue'
import * as echarts from 'echarts'
import { myDebounce } from '@/utils/index'

/**
 * 父组件参数
 */
const props = defineProps({
  className: {
    type: String,
    default: 'chart'
  },
  width: {
    type: String,
    default: '100%'
  },
  height: {
    type: String,
    default: '100%'
  },
  chartFontColor: {
    type: String,
    default: '#000'
  },
  autoResize: {
    type: Boolean,
    default: true
  },
  chartData: {
    type: Object,
    required: true
  },
  txtFontSize: {
    type: Number,
    default: 15
  }
})

/**
 * 定义变量
 */
let myChart = null // 图表
const refChart = ref(null) // 图表ref

let changePieInterval = null

const chartConfig = {
  barWidth: '10',
  textStyle: {
    color: '#fff',
    fontSize: 10.5,
  },
  lineStyle: {
    color: 'rgba(255, 255, 255, .6)', // 设置横坐标线颜色
    // width: 2, // 设置横坐标线宽度
  }
}

/**
 * 监听数据
 */
// setoption解构传参用这种监听
/* watch(
  props.chartData,
  (val) => {
    setOption(val)
  },
  { deep: true }
) */
// 重新修改传值方式之后,监听需要修改
watch(() => props.chartData, val => {
  setOption(val)
})

/**
 * 方法
 */
/**
 * 工具方法
 */
const setProxyData = (proxyData) => JSON.parse(JSON.stringify(proxyData))

/**
 * 图表相关
 */
// 销毁图表
const destroyChart = (next) => {
  if (myChart) {
    myChart.dispose()
    myChart = null

    if (next) {
      next()
    }
  }
}
// 重置图表
const resetChart = () => {
  // console.log("初始化图表", myChart)

  destroyChart(() => {
    // 重新启动图表
    initChart()
  })
}
// 初始化图表
const initChart = () => {
    myChart = echarts.init(refChart.value, 'macarons')
    setOption(props.chartData);

    // 使用防抖减少渲染次数
    // 在页面放大或缩小时,重新渲染echart
    window.addEventListener('resize', myDebounce(() => {
        resetChart()
    }));
}

// 轮播
const chartAuto = (option) => {
  if (JSON.stringify(props.chartData) === '{}') {
    return
  }

  let intervaltime = 3000

  if (changePieInterval) {
    clearInterval(changePieInterval)
  }

  let currentIndex = -1; // 当前高亮图形在饼图数据中的下标
  changePieInterval = setInterval(selectChart, intervaltime); // 设置自动切换高亮图形的定时器

  // 取消所有高亮并高亮当前图形
  function highlightChart() {
    if (!myChart) {
      return
    }

    for (var idx in option.series[0].data) {
      // 遍历饼图数据,取消所有图形的高亮效果
      myChart.dispatchAction({
        type: 'showTip',
        seriesIndex: 0,
        dataIndex: idx
      });
    }
    // 高亮当前图形
    myChart.dispatchAction({
      type: 'showTip',
      seriesIndex: 0,
      dataIndex: currentIndex
    });
  }

  // 用户鼠标悬浮到某一图形时,停止自动切换并高亮鼠标悬浮的图形
  myChart.on('mouseover', (params) => {
    if (changePieInterval) {
      clearInterval(changePieInterval);
    }
    currentIndex = params.dataIndex;
    highlightChart();
  });

  // 用户鼠标移出时,重新开始自动切换
  myChart.on('mouseout', (params) => {
    if (changePieInterval) {
      clearInterval(changePieInterval);
    }
    changePieInterval = setInterval(selectChart, intervaltime);
  });

  // 高亮效果切换到下一个图形
  function selectChart() {
    if (option.series[0].data) {
      var dataLen = option.series[0].data.length;
      currentIndex = (currentIndex + 1) % dataLen;
      highlightChart();
    }
  }
}

// 设置图表
const setOption = (chartData) => {
  if (!chartData) {
    return
  }

  // ----------------------------  图表配置开始
  const option = {
    title: {
      text: '单位(分)',
      top: "2.5%",
      right: '2%',
      textStyle: {
        color: '#fff',
        fontSize: 10,
      }
    },
    tooltip: {
      trigger: 'axis',
      textStyle: {
        color: '#000',
        fontSize: 11,
      },
      axisPointer: {
        type: 'cross',
        crossStyle: {
          color: '#fff'
        }
      }
    },
    grid: {
      left: '3%',
      right: '4%',
      bottom: '5%',
      height: '83%',
      containLabel: true
    },
    xAxis: {
      type: 'category',
      offset: 18,
      axisTick: {
        show: false,  // 隐藏刻度线
      },
      axisLine: {
        lineStyle: chartConfig.lineStyle
      },
      axisLabel: {
        padding: [0, 0, 0, -11],
        interval: 0, // 横轴信息全部显示
        rotate: 30,
        ...chartConfig.textStyle,
        align: 'left',
      },
      data: chartData.map(item => item.name),  // 对象数组使用map转换成name的数组传入
    },
    yAxis: {
      type: 'value',
      splitLine: {
        show: false,
      },
      axisTick: {
        show: false,  // 隐藏刻度线
      },
      axisLine: {
        show: true, // 显示y轴线
        lineStyle: chartConfig.lineStyle
      },
      axisLabel: {
        color: '#fff',
        fontSize: 10,
      }
    },
    series: [
      {
        name: '健康度',
        type: 'line',
        barWidth: chartConfig.barWidth,
        smooth: true,
        showSymbol: false, // 在 tooltip hover 的时候显示
        itemStyle: {
          color: 'rgba(82, 217, 95, 0.8)',
        },
        lineStyle: {
          color: '#52D95F',
          width: 2, // 设置线宽
        },
        areaStyle: {
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: 'rgba(82, 217, 95, 0.3)' },
            { offset: 1, color: 'rgba(0,211,252,0)' },
          ]),
        },
        data: chartData.map(item => item.healthLevel),
      },
    ]
  }
  // ----------------------------  图表配置结束

  // 绘制图表
  myChart.setOption(option)
  // 轮播
  chartAuto(option)
}

/**
 * 生命周期
 */
onMounted(() => {
  nextTick(() => {
    initChart() // 初始化图表
  })
})
onBeforeUnmount(() => {
  destroyChart() // 销毁图表
})

/**
 * 暴露方法
 */
defineExpose({
  resetChart
})
</script>
<template>
  <section ref="refChart" class="chart_wrap" :class="className" :style="{ height: height, width: width }"></section>
</template>

<script setup>
import { onMounted, onBeforeUnmount, ref, watch, nextTick } from 'vue'
import * as echarts from 'echarts'
import { myDebounce } from '@/utils/index'

/**
 * 父组件参数
 */
const props = defineProps({
  className: {
    type: String,
    default: 'chart'
  },
  width: {
    type: String,
    default: '100%'
  },
  height: {
    type: String,
    default: '100%'
  },
  chartFontColor: {
    type: String,
    default: '#000'
  },
  autoResize: {
    type: Boolean,
    default: true
  },
  chartData: {
    type: Object,
    required: true
  },
  txtFontSize: {
    type: Number,
    default: 15
  }
})

/**
 * 定义变量
 */
let myChart = null // 图表
const refChart = ref(null) // 图表ref

let changePieInterval = null

const chartConfig = {
  barWidth: '10',
  textStyle: {
    color: '#fff',
    fontSize: 10.5,
  },
  lineStyle: {
    color: 'rgba(255, 255, 255, .6)', // 设置横坐标线颜色
    // width: 2, // 设置横坐标线宽度
  }
}

/**
 * 监听数据
 */
// setoption解构传参用这种监听
/* watch(
  props.chartData,
  (val) => {
    setOption(val)
  },
  { deep: true }
) */
// 重新修改传值方式之后,监听需要修改
watch(() => props.chartData, val => {
  setOption(val)
})

/**
 * 方法
 */
/**
 * 工具方法
 */
const setProxyData = (proxyData) => JSON.parse(JSON.stringify(proxyData))

/**
 * 图表相关
 */
// 销毁图表
const destroyChart = (next) => {
  if (myChart) {
    myChart.dispose()
    myChart = null

    if (next) {
      next()
    }
  }
}
// 重置图表
const resetChart = () => {
  // console.log("初始化图表", myChart)

  destroyChart(() => {
    // 重新启动图表
    initChart()
  })
}
// 初始化图表
const initChart = () => {
    myChart = echarts.init(refChart.value, 'macarons')
    setOption(props.chartData);

    // 使用防抖减少渲染次数
    // 在页面放大或缩小时,重新渲染echart
    window.addEventListener('resize', myDebounce(() => {
        resetChart()
    }));
}

// 轮播
const chartAuto = (option) => {
  if (JSON.stringify(props.chartData) === '{}') {
    return
  }

  let intervaltime = 3000

  if (changePieInterval) {
    clearInterval(changePieInterval)
  }

  let currentIndex = -1; // 当前高亮图形在饼图数据中的下标
  changePieInterval = setInterval(selectChart, intervaltime); // 设置自动切换高亮图形的定时器

  // 取消所有高亮并高亮当前图形
  function highlightChart() {
    if (!myChart) {
      return
    }

    for (var idx in option.series[0].data) {
      // 遍历饼图数据,取消所有图形的高亮效果
      myChart.dispatchAction({
        type: 'showTip',
        seriesIndex: 0,
        dataIndex: idx
      });
    }
    // 高亮当前图形
    myChart.dispatchAction({
      type: 'showTip',
      seriesIndex: 0,
      dataIndex: currentIndex
    });
  }

  // 用户鼠标悬浮到某一图形时,停止自动切换并高亮鼠标悬浮的图形
  myChart.on('mouseover', (params) => {
    if (changePieInterval) {
      clearInterval(changePieInterval);
    }
    currentIndex = params.dataIndex;
    highlightChart();
  });

  // 用户鼠标移出时,重新开始自动切换
  myChart.on('mouseout', (params) => {
    if (changePieInterval) {
      clearInterval(changePieInterval);
    }
    changePieInterval = setInterval(selectChart, intervaltime);
  });

  // 高亮效果切换到下一个图形
  function selectChart() {
    if (option.series[0].data) {
      var dataLen = option.series[0].data.length;
      currentIndex = (currentIndex + 1) % dataLen;
      highlightChart();
    }
  }
}

// 设置图表
const setOption = (chartData) => {
  if (!chartData) {
    return
  }

  // ----------------------------  图表配置开始
  const option = {
    title: {
      text: '单位(分)',
      top: "2.5%",
      right: '2%',
      textStyle: {
        color: '#fff',
        fontSize: 10,
      }
    },
    tooltip: {
      trigger: 'axis',
      textStyle: {
        color: '#000',
        fontSize: 11,
      },
      axisPointer: {
        type: 'cross',
        crossStyle: {
          color: '#fff'
        }
      }
    },
    grid: {
      left: '3%',
      right: '4%',
      bottom: '5%',
      height: '83%',
      containLabel: true
    },
    xAxis: {
      type: 'category',
      offset: 18,
      axisTick: {
        show: false,  // 隐藏刻度线
      },
      axisLine: {
        lineStyle: chartConfig.lineStyle
      },
      axisLabel: {
        padding: [0, 0, 0, -11],
        interval: 0, // 横轴信息全部显示
        rotate: 30,
        ...chartConfig.textStyle,
        align: 'left',
      },
      data: chartData.map(item => item.name),  // 对象数组使用map转换成name的数组传入
    },
    yAxis: {
      type: 'value',
      splitLine: {
        show: false,
      },
      axisTick: {
        show: false,  // 隐藏刻度线
      },
      axisLine: {
        show: true, // 显示y轴线
        lineStyle: chartConfig.lineStyle
      },
      axisLabel: {
        color: '#fff',
        fontSize: 10,
      }
    },
    series: [
      {
        name: '健康度',
        type: 'line',
        barWidth: chartConfig.barWidth,
        smooth: true,
        showSymbol: false, // 在 tooltip hover 的时候显示
        itemStyle: {
          color: 'rgba(82, 217, 95, 0.8)',
        },
        lineStyle: {
          color: '#52D95F',
          width: 2, // 设置线宽
        },
        areaStyle: {
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: 'rgba(82, 217, 95, 0.3)' },
            { offset: 1, color: 'rgba(0,211,252,0)' },
          ]),
        },
        data: chartData.map(item => item.healthLevel),
      },
    ]
  }
  // ----------------------------  图表配置结束

  // 绘制图表
  myChart.setOption(option)
  // 轮播
  chartAuto(option)
}

/**
 * 生命周期
 */
onMounted(() => {
  nextTick(() => {
    initChart() // 初始化图表
  })
})
onBeforeUnmount(() => {
  destroyChart() // 销毁图表
})

/**
 * 暴露方法
 */
defineExpose({
  resetChart
})
</script>

在页面中使用

  • 在之前的封装中,是字段名和值分开传入,每次都需要将对象数组重组传入,我们现在直接将对象数组传入echart组件中,用map的形式将数组传入
  • 具体代码见上
vue
<right-chart-1 ref="refRightChart1" :chart-data="rightChart1Data" />

<script setup>
import RightChart1 from '../components/chart/RightChart1.vue';

const refRightChart1 = ref(null)
const rightChart1Data = ref([])

// 获取接口数据
const getRightChart1Data = async () => {
  // 获取接口数据接口
  const resDataData = await apiCommon(screenApi.xxxxxx);

  // 数据处理
  // ......

  // 假数据 - 使用对象数组
  rightChart1Data.value = [
    { name: '慈溪市', value: 85 },
    { name: '余姚市', value: 66 },
    { name: '海曙区', value: 88 },
    { name: '江北区', value: 78 },
    { name: '镇海区', value: 70 },
    { name: '奉化区', value: 12 },
    { name: '鄞州区', value: 72 },
    { name: '北仑区', value: 33 },
    { name: '宁海县', value: 95 },
    { name: '象山县', value: 75 },
  ]
}
</script>
<right-chart-1 ref="refRightChart1" :chart-data="rightChart1Data" />

<script setup>
import RightChart1 from '../components/chart/RightChart1.vue';

const refRightChart1 = ref(null)
const rightChart1Data = ref([])

// 获取接口数据
const getRightChart1Data = async () => {
  // 获取接口数据接口
  const resDataData = await apiCommon(screenApi.xxxxxx);

  // 数据处理
  // ......

  // 假数据 - 使用对象数组
  rightChart1Data.value = [
    { name: '慈溪市', value: 85 },
    { name: '余姚市', value: 66 },
    { name: '海曙区', value: 88 },
    { name: '江北区', value: 78 },
    { name: '镇海区', value: 70 },
    { name: '奉化区', value: 12 },
    { name: '鄞州区', value: 72 },
    { name: '北仑区', value: 33 },
    { name: '宁海县', value: 95 },
    { name: '象山县', value: 75 },
  ]
}
</script>

代码中使用的防抖

js
export const myDebounce = (fn, delay = 500) => {
  // timer是在闭包中的 => 下面的if(timer)
  // 这样不会被外界轻易拿到 => 即不对外暴露
  // 我们在外面使用不需要关心
  // 同时也是在debounce的作用域中
  // 闭包的使用场景:函数当做返回值或者参数传入
  let timer = null;

  // 函数作为返回值,这就形成闭包了
  return function () {
    // 这里面的timer需要在它定义的作用域往上寻找
    if (timer) {
      clearTimeout(timer)
    }

    timer = setTimeout(() => {
      // 触发change事件
      // 第一个参数是改变this指向
      // 第二个参数是获取所有的参数
      // apply第二个参数开始,只接收数组
      // fn函数在执行的时候,argument传进来
      // debounce返回的函数可能会传进来一些参数
      // 面试使用fn()也没问题
      // fn()
      fn.apply(this, arguments)

      // 清空定时器
      timer = null
    }, delay)
  }
}
export const myDebounce = (fn, delay = 500) => {
  // timer是在闭包中的 => 下面的if(timer)
  // 这样不会被外界轻易拿到 => 即不对外暴露
  // 我们在外面使用不需要关心
  // 同时也是在debounce的作用域中
  // 闭包的使用场景:函数当做返回值或者参数传入
  let timer = null;

  // 函数作为返回值,这就形成闭包了
  return function () {
    // 这里面的timer需要在它定义的作用域往上寻找
    if (timer) {
      clearTimeout(timer)
    }

    timer = setTimeout(() => {
      // 触发change事件
      // 第一个参数是改变this指向
      // 第二个参数是获取所有的参数
      // apply第二个参数开始,只接收数组
      // fn函数在执行的时候,argument传进来
      // debounce返回的函数可能会传进来一些参数
      // 面试使用fn()也没问题
      // fn()
      fn.apply(this, arguments)

      // 清空定时器
      timer = null
    }, delay)
  }
}

进行全屏时遇到的一些问题

因为大屏是作为子页面的,有时候有菜单栏,有时候没有,有时候菜单栏收起,有时候展开,有4种情况

  • 全屏代码 - 常规的全屏按钮
js
// falg为当前的全屏状态
export const setFullScreen = (falg) => {
  if (!falg) {
    // 全屏
    if (document.documentElement.requestFullscreen) {
      document.documentElement.requestFullscreen();
    } else if (document.documentElement.mozRequestFullScreen) {
      document.documentElement.mozRequestFullScreen();
    } else if (document.documentElement.webkitRequestFullscreen) {
      document.documentElement.webkitRequestFullscreen();
    } else if (element.msRequestFullscreen) {
      document.documentElement.msRequestFullscreen();
    }
  } else {
    // 退出全屏
    if (document.exitFullscreen) {
      document.exitFullscreen();
    } else if (document.mozCancelFullScreen) {
      document.mozCancelFullScreen();
    } else if (document.webkitExitFullscreen) {
      document.webkitExitFullscreen();
    } else if (document.msExitFullscreen) {
      document.msExitFullscreen();
    }
  }
}

// 使用
// api相关
import { setFullScreen } from "@/utils/index.js";
<button class="title" @click="setFullScreen(isFullscreen)">{{ isFullscreen ? '退出全屏' : '全屏' }}<button>
// falg为当前的全屏状态
export const setFullScreen = (falg) => {
  if (!falg) {
    // 全屏
    if (document.documentElement.requestFullscreen) {
      document.documentElement.requestFullscreen();
    } else if (document.documentElement.mozRequestFullScreen) {
      document.documentElement.mozRequestFullScreen();
    } else if (document.documentElement.webkitRequestFullscreen) {
      document.documentElement.webkitRequestFullscreen();
    } else if (element.msRequestFullscreen) {
      document.documentElement.msRequestFullscreen();
    }
  } else {
    // 退出全屏
    if (document.exitFullscreen) {
      document.exitFullscreen();
    } else if (document.mozCancelFullScreen) {
      document.mozCancelFullScreen();
    } else if (document.webkitExitFullscreen) {
      document.webkitExitFullscreen();
    } else if (document.msExitFullscreen) {
      document.msExitFullscreen();
    }
  }
}

// 使用
// api相关
import { setFullScreen } from "@/utils/index.js";
<button class="title" @click="setFullScreen(isFullscreen)">{{ isFullscreen ? '退出全屏' : '全屏' }}<button>
  • 上面是根据状态来进行全屏操作的,所以需要监听全屏状态的变化,来修改flag状态,上面那个方法才能生效
  • 如果在全屏的状态,点击跳转到单独的大屏页面,页面是重新加载了,但状态值还没有发生改变,所以要在页面一加载的时候就判断当前的全屏状态
js
// 判断是否全屏方法封装
const getIsFullScreenStatus = (into, out) => {
  if (document.fullscreenElement || document.webkitIsFullScreen || document.mozFullScreen) {
    isFullscreen.value = true

    if (into) { into() }
  } else {
    isFullscreen.value = false

    if (out) { out() }
  }
}

// 监听全屏状态修改标识
const setFullScreenFlag = (e) => {
  getIsFullScreenStatus() // 页面一加载就先判断是否全屏

  // 监听是否全屏事件
  window.addEventListener('fullscreenchange', () => {
    // 可以写一些进出全屏的回调
    getIsFullScreenStatus(() => {
        // console.log('进入全屏模式');
    }, () => {
        // console.log('退出全屏模式');
    })
  });
}

const initPage = ()=> {
    setFullScreenFlag() // 监听全屏状态修改标识
}
// 判断是否全屏方法封装
const getIsFullScreenStatus = (into, out) => {
  if (document.fullscreenElement || document.webkitIsFullScreen || document.mozFullScreen) {
    isFullscreen.value = true

    if (into) { into() }
  } else {
    isFullscreen.value = false

    if (out) { out() }
  }
}

// 监听全屏状态修改标识
const setFullScreenFlag = (e) => {
  getIsFullScreenStatus() // 页面一加载就先判断是否全屏

  // 监听是否全屏事件
  window.addEventListener('fullscreenchange', () => {
    // 可以写一些进出全屏的回调
    getIsFullScreenStatus(() => {
        // console.log('进入全屏模式');
    }, () => {
        // console.log('退出全屏模式');
    })
  });
}

const initPage = ()=> {
    setFullScreenFlag() // 监听全屏状态修改标识
}

echarts三维地图是怎么形成的

  • 这里贴出三维地图的echart配置
vue
<center-chart-1 ref="refCenterChart1" :chart-data="centerChart1Data" />

<script setup>
const centerChart1Data.value = [
    {
      name: "aaaa",
      data1: 99, 
    },
    {
      name: "bbbb",
      data1: 85,
    },
    {
      name: "cccc",
      data1: 88,
    },
];
</script>

<section ref="refChart"></section>

<script setup>
import nbGeoJSON from "./GEOJSON/nbGeoJSON.json";
// 初始化图表
const initChart = () => {
  myChart = echarts.init(refChart.value);
  echarts.registerMap("ningbo", nbGeoJSON);
  setOption(props.chartData);
};

const option = {
    tooltip: {
        show: true,
        textStyle: {
            fontSize: 13,
            color: "#fff",
        },
        trigger: "item",
        backgroundColor: "rgba(0,0,0,0.3)",
        borderColor: 'rgba(74,190,254,0.5)',
        borderWidth: 2.5,
        formatter: (params) => {
            let { data } = params;

            let str = `
                <div class="chart_tooltip">
                    <h3>${data.name}</h3>
                    <ul>
                        <li>
                            <dl>
                                <dt>xxxxxx:</dt>
                                <dd>${data.data1}</dd>
                            </dl>  
                        </li>  
                    </ul>
                </div>
            `;

            return str;
        },
    },
    series: [
        {
            type: 'map3D', // 系列类型
            map: "ningbo",
            data: chartData,
            label: {
                // 标签的相关设置
                show: true, // (地图上的城市名称)是否显示标签 [ default: false ]
                // 标签的字体样式
                color: '#fff', // 地图初始化区域字体颜色
                fontSize: 11, // 字体大小
                opacity: .8, // 字体透明度
            },
            itemStyle: {
                color: '#0b3eb3', // 地图板块的颜色
                borderWidth: 0.5, // (地图板块间的分隔线)图形描边的宽度。加上描边后可以更清晰的区分每个区域   [ default: 0 ]
                borderColor: 'rgba(0, 178, 255, 1)' // 图形描边的颜色。[ default: #333 ]
            },
            emphasis: {
                label: {
                    show: true, // 是否显示高亮
                    color: '#fff', // 高亮文字颜色
                    fontSize: 15,
                },
                itemStyle: {
                    color: '#00ade0', // 地图高亮颜色
                    borderWidth: 10, // 分界线wdith
                    borderColor: '#00ade0' // 分界线颜色
                }
            },
            shading: 'realistic',
            groundPlane: {
                // 地面可以让整个组件有个“摆放”的地方,从而使整个场景看起来更真实,更有模型感。
                show: false, // 是否显示地面。[ default: false ]
                color: '#0C264D' // 地面颜色。[ default: '#aaa' ]
            },
            shading: 'realistic', // 三维地理坐标系组件中三维图形的着色效果,echarts-gl 中支持下面三种着色方式:
            realisticMaterial: {
                // detailTexture: 'https://cdn.polyhaven.com/asset_img/primary/kloppenheim_06_puresky.png?height=780', // 纹理贴图
                detailTexture: mapbg, // 纹理贴图
                textureTiling: 1, // 纹理平铺,1是拉伸,数字表示纹理平铺次数
                roughness: .8, // 材质粗糙度,0完全光滑,1完全粗糙
                metalness: 0, // 0材质是非金属 ,1金属
            }, // 真实感材质相关的配置项,在 shading 为'realistic'时有效。
            light: {
                // 光照相关的设置。在 shading 为 'color' 的时候无效。  光照的设置会影响到组件以及组件所在坐标系上的所有图表。合理的光照设置能够让整个场景的明暗变得更丰富,更有层次。
                main: {
                    color: '#ffe',
                    intensity: 1.1,
                    shadow: true,
                    shadowQuality: 'high',
                    alpha: 25,
                    beta: 40
                },
                ambient: {
                    // 全局的环境光设置。
                    color: '#fff', // 环境光的颜色。[ default: #fff ]
                    intensity: 1 // 环境光的强度。[ default: 0.2 ]
                }
            },
            viewControl: {
                // 用于鼠标的旋转,缩放等视角控制。
                projection: 'orthographic', // 投影方式,默认为透视投影'perspective',也支持设置为正交投影'orthographic'。
                autoRotate: false, // 是否开启视角绕物体的自动旋转查看。[ default: false ]
                autoRotateDirection: 'ccw', // 物体自传的方向。默认是 'cw' 也就是从上往下看是顺时针方向,也可以取 'ccw',既从上往下看为逆时针方向。
                autoRotateSpeed: 10, // 物体自传的速度。单位为角度 / 秒,默认为10 ,也就是36秒转一圈。
                autoRotateAfterStill: 3, // 在鼠标静止操作后恢复自动旋转的时间间隔。在开启 autoRotate 后有效。[ default: 3 ]
                damping: 0, // 鼠标进行旋转,缩放等操作时的迟滞因子,在大于等于 1 的时候鼠标在停止操作后,视角仍会因为一定的惯性继续运动(旋转和缩放)。[ default: 0.8 ]
                rotateSensitivity: 1, // 旋转操作的灵敏度,值越大越灵敏。支持使用数组分别设置横向和纵向的旋转灵敏度。默认为1, 设置为0后无法旋转。	rotateSensitivity: [1, 0]——只能横向旋转; rotateSensitivity: [0, 1]——只能纵向旋转。
                zoomSensitivity: 1, // 缩放操作的灵敏度,值越大越灵敏。默认为1,设置为0后无法缩放。
                panSensitivity: 1, // 平移操作的灵敏度,值越大越灵敏。默认为1,设置为0后无法平移。支持使用数组分别设置横向和纵向的平移灵敏度
                panMouseButton: 'middle', // 平移操作使用的鼠标按键,支持:'left' 鼠标左键(默认);'middle' 鼠标中键 ;'right' 鼠标右键(注意:如果设置为鼠标右键则会阻止默认的右键菜单。)
                rotateMouseButton: 'left', // 旋转操作使用的鼠标按键,支持:'left' 鼠标左键;'middle' 鼠标中键(默认);'right' 鼠标右键(注意:如果设置为鼠标右键则会阻止默认的右键菜单。)

                alpha: 23, // 视角绕 x 轴,即上下旋转的角度。配合 beta 可以控制视角的方向。[ default: 40 ]
                beta: 40, // 视角绕 y 轴,即左右旋转的角度。[ default: 0 ]
                minAlpha: 5, // 上下旋转的最小 alpha 值。即视角能旋转到达最上面的角度。[ default: 5 ]
                maxAlpha: 50, // 上下旋转的最大 alpha 值。即视角能旋转到达最下面的角度。[ default: 90 ]
                minBeta: -360, // 左右旋转的最小 beta 值。即视角能旋转到达最左的角度。[ default: -80 ]
                maxBeta: 360, // 左右旋转的最大 beta 值。即视角能旋转到达最右的角度。[ default: 80 ]

                center: [-3, 3, -1.5], // 视角中心点,旋转也会围绕这个中心点旋转,默认为[0,0,0]。

                animation: true, // 是否开启动画。[ default: true ]
                animationDurationUpdate: 1000, // 过渡动画的时长。[ default: 1000 ]
                animationEasingUpdate: 'cubicInOut', // 过渡动画的缓动效果。[ default: cubicInOut ]


                // 缩放大小,数值越大,地图越小
                zoomSensitivity: 0.5
            },
        },
    ],
}
</script>
<center-chart-1 ref="refCenterChart1" :chart-data="centerChart1Data" />

<script setup>
const centerChart1Data.value = [
    {
      name: "aaaa",
      data1: 99, 
    },
    {
      name: "bbbb",
      data1: 85,
    },
    {
      name: "cccc",
      data1: 88,
    },
];
</script>

<section ref="refChart"></section>

<script setup>
import nbGeoJSON from "./GEOJSON/nbGeoJSON.json";
// 初始化图表
const initChart = () => {
  myChart = echarts.init(refChart.value);
  echarts.registerMap("ningbo", nbGeoJSON);
  setOption(props.chartData);
};

const option = {
    tooltip: {
        show: true,
        textStyle: {
            fontSize: 13,
            color: "#fff",
        },
        trigger: "item",
        backgroundColor: "rgba(0,0,0,0.3)",
        borderColor: 'rgba(74,190,254,0.5)',
        borderWidth: 2.5,
        formatter: (params) => {
            let { data } = params;

            let str = `
                <div class="chart_tooltip">
                    <h3>${data.name}</h3>
                    <ul>
                        <li>
                            <dl>
                                <dt>xxxxxx:</dt>
                                <dd>${data.data1}</dd>
                            </dl>  
                        </li>  
                    </ul>
                </div>
            `;

            return str;
        },
    },
    series: [
        {
            type: 'map3D', // 系列类型
            map: "ningbo",
            data: chartData,
            label: {
                // 标签的相关设置
                show: true, // (地图上的城市名称)是否显示标签 [ default: false ]
                // 标签的字体样式
                color: '#fff', // 地图初始化区域字体颜色
                fontSize: 11, // 字体大小
                opacity: .8, // 字体透明度
            },
            itemStyle: {
                color: '#0b3eb3', // 地图板块的颜色
                borderWidth: 0.5, // (地图板块间的分隔线)图形描边的宽度。加上描边后可以更清晰的区分每个区域   [ default: 0 ]
                borderColor: 'rgba(0, 178, 255, 1)' // 图形描边的颜色。[ default: #333 ]
            },
            emphasis: {
                label: {
                    show: true, // 是否显示高亮
                    color: '#fff', // 高亮文字颜色
                    fontSize: 15,
                },
                itemStyle: {
                    color: '#00ade0', // 地图高亮颜色
                    borderWidth: 10, // 分界线wdith
                    borderColor: '#00ade0' // 分界线颜色
                }
            },
            shading: 'realistic',
            groundPlane: {
                // 地面可以让整个组件有个“摆放”的地方,从而使整个场景看起来更真实,更有模型感。
                show: false, // 是否显示地面。[ default: false ]
                color: '#0C264D' // 地面颜色。[ default: '#aaa' ]
            },
            shading: 'realistic', // 三维地理坐标系组件中三维图形的着色效果,echarts-gl 中支持下面三种着色方式:
            realisticMaterial: {
                // detailTexture: 'https://cdn.polyhaven.com/asset_img/primary/kloppenheim_06_puresky.png?height=780', // 纹理贴图
                detailTexture: mapbg, // 纹理贴图
                textureTiling: 1, // 纹理平铺,1是拉伸,数字表示纹理平铺次数
                roughness: .8, // 材质粗糙度,0完全光滑,1完全粗糙
                metalness: 0, // 0材质是非金属 ,1金属
            }, // 真实感材质相关的配置项,在 shading 为'realistic'时有效。
            light: {
                // 光照相关的设置。在 shading 为 'color' 的时候无效。  光照的设置会影响到组件以及组件所在坐标系上的所有图表。合理的光照设置能够让整个场景的明暗变得更丰富,更有层次。
                main: {
                    color: '#ffe',
                    intensity: 1.1,
                    shadow: true,
                    shadowQuality: 'high',
                    alpha: 25,
                    beta: 40
                },
                ambient: {
                    // 全局的环境光设置。
                    color: '#fff', // 环境光的颜色。[ default: #fff ]
                    intensity: 1 // 环境光的强度。[ default: 0.2 ]
                }
            },
            viewControl: {
                // 用于鼠标的旋转,缩放等视角控制。
                projection: 'orthographic', // 投影方式,默认为透视投影'perspective',也支持设置为正交投影'orthographic'。
                autoRotate: false, // 是否开启视角绕物体的自动旋转查看。[ default: false ]
                autoRotateDirection: 'ccw', // 物体自传的方向。默认是 'cw' 也就是从上往下看是顺时针方向,也可以取 'ccw',既从上往下看为逆时针方向。
                autoRotateSpeed: 10, // 物体自传的速度。单位为角度 / 秒,默认为10 ,也就是36秒转一圈。
                autoRotateAfterStill: 3, // 在鼠标静止操作后恢复自动旋转的时间间隔。在开启 autoRotate 后有效。[ default: 3 ]
                damping: 0, // 鼠标进行旋转,缩放等操作时的迟滞因子,在大于等于 1 的时候鼠标在停止操作后,视角仍会因为一定的惯性继续运动(旋转和缩放)。[ default: 0.8 ]
                rotateSensitivity: 1, // 旋转操作的灵敏度,值越大越灵敏。支持使用数组分别设置横向和纵向的旋转灵敏度。默认为1, 设置为0后无法旋转。	rotateSensitivity: [1, 0]——只能横向旋转; rotateSensitivity: [0, 1]——只能纵向旋转。
                zoomSensitivity: 1, // 缩放操作的灵敏度,值越大越灵敏。默认为1,设置为0后无法缩放。
                panSensitivity: 1, // 平移操作的灵敏度,值越大越灵敏。默认为1,设置为0后无法平移。支持使用数组分别设置横向和纵向的平移灵敏度
                panMouseButton: 'middle', // 平移操作使用的鼠标按键,支持:'left' 鼠标左键(默认);'middle' 鼠标中键 ;'right' 鼠标右键(注意:如果设置为鼠标右键则会阻止默认的右键菜单。)
                rotateMouseButton: 'left', // 旋转操作使用的鼠标按键,支持:'left' 鼠标左键;'middle' 鼠标中键(默认);'right' 鼠标右键(注意:如果设置为鼠标右键则会阻止默认的右键菜单。)

                alpha: 23, // 视角绕 x 轴,即上下旋转的角度。配合 beta 可以控制视角的方向。[ default: 40 ]
                beta: 40, // 视角绕 y 轴,即左右旋转的角度。[ default: 0 ]
                minAlpha: 5, // 上下旋转的最小 alpha 值。即视角能旋转到达最上面的角度。[ default: 5 ]
                maxAlpha: 50, // 上下旋转的最大 alpha 值。即视角能旋转到达最下面的角度。[ default: 90 ]
                minBeta: -360, // 左右旋转的最小 beta 值。即视角能旋转到达最左的角度。[ default: -80 ]
                maxBeta: 360, // 左右旋转的最大 beta 值。即视角能旋转到达最右的角度。[ default: 80 ]

                center: [-3, 3, -1.5], // 视角中心点,旋转也会围绕这个中心点旋转,默认为[0,0,0]。

                animation: true, // 是否开启动画。[ default: true ]
                animationDurationUpdate: 1000, // 过渡动画的时长。[ default: 1000 ]
                animationEasingUpdate: 'cubicInOut', // 过渡动画的缓动效果。[ default: cubicInOut ]


                // 缩放大小,数值越大,地图越小
                zoomSensitivity: 0.5
            },
        },
    ],
}
</script>

如何让eacharts饼图颜色渐变

  • 如果要单独设置饼图数据的颜色,需要在series的data属性里面单独作配置
vue
<section ref="refChart"></section>

<script setup>
// 设置图表
const setOption = (chartData) => {
  if (!chartData) {
    return
  }

  // ----------------------------  图表配置开始
  const option = {
    tooltip: {
      trigger: 'item',
      textStyle: {
        color: '#000',
        fontSize: 11,
      },
      formatter(val) {
        const { name } = val
        let curData = null
        let sum = 0

        chartData.forEach(item => {
          if (item.name === name)
            curData = item

          sum += item.value
        })

        return `${curData.name}:${curData.value}个 (${parseFloat(curData.value / sum * 100).toFixed(2)}%)`
      }
    },
    title: {
      text: '基站个数',
      right: "24.2%",
      top: "16%",
      textAlign: "center",
      show: true,
      textStyle: {
        color: "#fff",
        fontSize: 12,
        align: "center",
        fontWeight: 800,
      },
    },
    legend: {
      show: true,
      orient: 'vertical',
      itemWidth: 11,
      itemHeight: 11,
      icon: "rect",
      top: '32%',
      right: '8%',
      padding: [0, 0, 0, 0],
      height: '100%',
      itemGap: 18,       // 设置图例项之间的间隔
      textStyle: {
        color: '#fff',
        fontSize: '11.5px'
      },
      tooltip: {
        show: false
      },
      formatter: name => {
        let curData = null
        let sum = 0
        let avage = 0

        chartData.forEach(item => {
          if (item.name === name)
            curData = item

          sum += item.value
        })

        if (sum !== 0) {
          avage = parseFloat(curData.value / sum * 100).toFixed(2)
        }

        return `${curData.name}:${curData.value} 个 (${avage}%)`
      }
    },
    series: [
      {
        type: 'pie',
        radius: ['49%', '81.5%'],
        center: ['28.5%', '51.5%'],
        data: chartData,  // 在这里使用数据
        labelLine: {
          show: false
        },
        label: {
          show: false,
          position: 'center',
          formatter(val) {
            const { name } = val
            let curData = null

            chartData.forEach(item => {
              if (item.name === name) {
                curData = item
              }
            })

            return `{title|${name}} \n\n {num|${curData.value}个}`
          }
        },
        emphasis: {
          label: {
            show: true,
            rich: {
              // 标题
              title: {
                color: "#fff",
                fontSize: 16,
                fontWeight: 800,
              },
              num: {
                color: "#4ABEFE",
                fontSize: 22,
                fontWeight: 900,
              },
            }
          }
        },
      }
    ]
  }
  // ----------------------------  图表配置结束

  // 绘制图表
  myChart.setOption(option)
  // 轮播
  chartAuto(option)
}

// 对外供出渐变色方法
const setColor = (color1, color2) => {
  return new echarts.graphic.LinearGradient(0, 0, 0, 1, [
    { offset: 0, color: color1 },
    { offset: 1, color: color2 }
  ])
}

/**
 * 生命周期
 */
onMounted(() => {
  nextTick(() => {
    initChart() // 初始化图表
  })
})

onBeforeUnmount(() => {
  destroyChart() // 销毁图表
})

/**
 * 暴露方法
 */
defineExpose({
  resetChart,
  setColor
})
</script>
<section ref="refChart"></section>

<script setup>
// 设置图表
const setOption = (chartData) => {
  if (!chartData) {
    return
  }

  // ----------------------------  图表配置开始
  const option = {
    tooltip: {
      trigger: 'item',
      textStyle: {
        color: '#000',
        fontSize: 11,
      },
      formatter(val) {
        const { name } = val
        let curData = null
        let sum = 0

        chartData.forEach(item => {
          if (item.name === name)
            curData = item

          sum += item.value
        })

        return `${curData.name}:${curData.value}个 (${parseFloat(curData.value / sum * 100).toFixed(2)}%)`
      }
    },
    title: {
      text: '基站个数',
      right: "24.2%",
      top: "16%",
      textAlign: "center",
      show: true,
      textStyle: {
        color: "#fff",
        fontSize: 12,
        align: "center",
        fontWeight: 800,
      },
    },
    legend: {
      show: true,
      orient: 'vertical',
      itemWidth: 11,
      itemHeight: 11,
      icon: "rect",
      top: '32%',
      right: '8%',
      padding: [0, 0, 0, 0],
      height: '100%',
      itemGap: 18,       // 设置图例项之间的间隔
      textStyle: {
        color: '#fff',
        fontSize: '11.5px'
      },
      tooltip: {
        show: false
      },
      formatter: name => {
        let curData = null
        let sum = 0
        let avage = 0

        chartData.forEach(item => {
          if (item.name === name)
            curData = item

          sum += item.value
        })

        if (sum !== 0) {
          avage = parseFloat(curData.value / sum * 100).toFixed(2)
        }

        return `${curData.name}:${curData.value} 个 (${avage}%)`
      }
    },
    series: [
      {
        type: 'pie',
        radius: ['49%', '81.5%'],
        center: ['28.5%', '51.5%'],
        data: chartData,  // 在这里使用数据
        labelLine: {
          show: false
        },
        label: {
          show: false,
          position: 'center',
          formatter(val) {
            const { name } = val
            let curData = null

            chartData.forEach(item => {
              if (item.name === name) {
                curData = item
              }
            })

            return `{title|${name}} \n\n {num|${curData.value}个}`
          }
        },
        emphasis: {
          label: {
            show: true,
            rich: {
              // 标题
              title: {
                color: "#fff",
                fontSize: 16,
                fontWeight: 800,
              },
              num: {
                color: "#4ABEFE",
                fontSize: 22,
                fontWeight: 900,
              },
            }
          }
        },
      }
    ]
  }
  // ----------------------------  图表配置结束

  // 绘制图表
  myChart.setOption(option)
  // 轮播
  chartAuto(option)
}

// 对外供出渐变色方法
const setColor = (color1, color2) => {
  return new echarts.graphic.LinearGradient(0, 0, 0, 1, [
    { offset: 0, color: color1 },
    { offset: 1, color: color2 }
  ])
}

/**
 * 生命周期
 */
onMounted(() => {
  nextTick(() => {
    initChart() // 初始化图表
  })
})

onBeforeUnmount(() => {
  destroyChart() // 销毁图表
})

/**
 * 暴露方法
 */
defineExpose({
  resetChart,
  setColor
})
</script>
  • 使用
vue
<left-chart-2 ref="refLeftChart2" :chart-data="leftChart2Data" />

<script setup>
const refLeftChart2 = ref(null)
const leftChart2Data = ref([])

// 接口数据
const getLeftChart2Data = async () => {
  // 获取接口数据接口
  const resDataData = await apiCommon(screenApi.getDistributionInfo);

  const opacity = '.35'

  // const colorArr = ['#D65755', '#8B6BE9', '#F0D18C', '#4389F6', '#EE9D73']
  const colorArr = [
    refLeftChart2.value.setColor('#D65755', `rgba(214, 87, 85, ${opacity})`),
    refLeftChart2.value.setColor('#8B6BE9', `rgba(139, 107, 233, ${opacity})`),
    refLeftChart2.value.setColor('#F0D18C', `rgba(240, 107, 140, ${opacity})`),
    refLeftChart2.value.setColor('#4389F6', `rgba(67, 137, 246, ${opacity})`),
    refLeftChart2.value.setColor('#EE9D73', `rgba(238, 157, 115, ${opacity})`)
  ]

  // 数据处理
  leftChart2Data.value = resDataData.data.map((item, index) => {
    return {
      name: item.section,
      value: item.stationCount,
      itemStyle: { color: colorArr[index] }
    }
  })

  // 假数据 -- 数据处理已完成,使用接口数据,假数据注释即可
  leftChart2Data.value = [
    {
      name: '90-100分', value: 2, itemStyle: {
        color: refLeftChart2.value.setColor('#D65755', `rgba(214, 87, 85, ${opacity})`),
      }
    },
    {
      name: '80-90分', value: 3, itemStyle: {
        color: refLeftChart2.value.setColor('#8B6BE9', `rgba(139, 107, 233, ${opacity})`),
      }
    },
    {
      name: '70-80分', value: 4, itemStyle: {
        color: refLeftChart2.value.setColor('#F0D18C', `rgba(240, 107, 140, ${opacity})`),
      }
    },
    {
      name: '60-70分', value: 10, itemStyle: {
        color: refLeftChart2.value.setColor('#4389F6', `rgba(67, 137, 246, ${opacity})`),
      }
    },
    {
      name: '60分以下', value: 3, itemStyle: {
        color: refLeftChart2.value.setColor('#EE9D73', `rgba(238, 157, 115, ${opacity})`)
      }
    },
  ]
}

// 初始化图表数据
const initChartData = () => {
  getLeftChart2Data()
}
</script>
<left-chart-2 ref="refLeftChart2" :chart-data="leftChart2Data" />

<script setup>
const refLeftChart2 = ref(null)
const leftChart2Data = ref([])

// 接口数据
const getLeftChart2Data = async () => {
  // 获取接口数据接口
  const resDataData = await apiCommon(screenApi.getDistributionInfo);

  const opacity = '.35'

  // const colorArr = ['#D65755', '#8B6BE9', '#F0D18C', '#4389F6', '#EE9D73']
  const colorArr = [
    refLeftChart2.value.setColor('#D65755', `rgba(214, 87, 85, ${opacity})`),
    refLeftChart2.value.setColor('#8B6BE9', `rgba(139, 107, 233, ${opacity})`),
    refLeftChart2.value.setColor('#F0D18C', `rgba(240, 107, 140, ${opacity})`),
    refLeftChart2.value.setColor('#4389F6', `rgba(67, 137, 246, ${opacity})`),
    refLeftChart2.value.setColor('#EE9D73', `rgba(238, 157, 115, ${opacity})`)
  ]

  // 数据处理
  leftChart2Data.value = resDataData.data.map((item, index) => {
    return {
      name: item.section,
      value: item.stationCount,
      itemStyle: { color: colorArr[index] }
    }
  })

  // 假数据 -- 数据处理已完成,使用接口数据,假数据注释即可
  leftChart2Data.value = [
    {
      name: '90-100分', value: 2, itemStyle: {
        color: refLeftChart2.value.setColor('#D65755', `rgba(214, 87, 85, ${opacity})`),
      }
    },
    {
      name: '80-90分', value: 3, itemStyle: {
        color: refLeftChart2.value.setColor('#8B6BE9', `rgba(139, 107, 233, ${opacity})`),
      }
    },
    {
      name: '70-80分', value: 4, itemStyle: {
        color: refLeftChart2.value.setColor('#F0D18C', `rgba(240, 107, 140, ${opacity})`),
      }
    },
    {
      name: '60-70分', value: 10, itemStyle: {
        color: refLeftChart2.value.setColor('#4389F6', `rgba(67, 137, 246, ${opacity})`),
      }
    },
    {
      name: '60分以下', value: 3, itemStyle: {
        color: refLeftChart2.value.setColor('#EE9D73', `rgba(238, 157, 115, ${opacity})`)
      }
    },
  ]
}

// 初始化图表数据
const initChartData = () => {
  getLeftChart2Data()
}
</script>

echarts轮播是怎么实现的

js
let changePieInterval = null

// 轮播
const chartAuto = (option) => {
  if (JSON.stringify(props.chartData) === '{}') {
    return
  }

  let intervaltime = 3000

  if (changePieInterval) {
    clearInterval(changePieInterval)
  }

  let currentIndex = -1; // 当前高亮图形在饼图数据中的下标
  changePieInterval = setInterval(selectChart, intervaltime); // 设置自动切换高亮图形的定时器

  // 取消所有高亮并高亮当前图形
  const highlightChart = ()=> {
    if (!myChart) {
      return
    }

    for (var idx in option.series[0].data) {
      // 遍历饼图数据,取消所有图形的高亮效果
      myChart.dispatchAction({
        type: 'showTip',
        seriesIndex: 0,
        dataIndex: idx
      });
    }
    // 高亮当前图形
    myChart.dispatchAction({
      type: 'showTip',
      seriesIndex: 0,
      dataIndex: currentIndex
    });
  }

  // 用户鼠标悬浮到某一图形时,停止自动切换并高亮鼠标悬浮的图形
  myChart.on('mouseover', (params) => {
    if (changePieInterval) {
      clearInterval(changePieInterval);
    }
    currentIndex = params.dataIndex;
    highlightChart();
  });

  // 用户鼠标移出时,重新开始自动切换
  myChart.on('mouseout', (params) => {
    if (changePieInterval) {
      clearInterval(changePieInterval);
    }
    changePieInterval = setInterval(selectChart, intervaltime);
  });

  // 高亮效果切换到下一个图形
  const selectChart = ()=> {
    if (option.series[0].data) {
      var dataLen = option.series[0].data.length;
      currentIndex = (currentIndex + 1) % dataLen;
      highlightChart();
    }
  }
}
let changePieInterval = null

// 轮播
const chartAuto = (option) => {
  if (JSON.stringify(props.chartData) === '{}') {
    return
  }

  let intervaltime = 3000

  if (changePieInterval) {
    clearInterval(changePieInterval)
  }

  let currentIndex = -1; // 当前高亮图形在饼图数据中的下标
  changePieInterval = setInterval(selectChart, intervaltime); // 设置自动切换高亮图形的定时器

  // 取消所有高亮并高亮当前图形
  const highlightChart = ()=> {
    if (!myChart) {
      return
    }

    for (var idx in option.series[0].data) {
      // 遍历饼图数据,取消所有图形的高亮效果
      myChart.dispatchAction({
        type: 'showTip',
        seriesIndex: 0,
        dataIndex: idx
      });
    }
    // 高亮当前图形
    myChart.dispatchAction({
      type: 'showTip',
      seriesIndex: 0,
      dataIndex: currentIndex
    });
  }

  // 用户鼠标悬浮到某一图形时,停止自动切换并高亮鼠标悬浮的图形
  myChart.on('mouseover', (params) => {
    if (changePieInterval) {
      clearInterval(changePieInterval);
    }
    currentIndex = params.dataIndex;
    highlightChart();
  });

  // 用户鼠标移出时,重新开始自动切换
  myChart.on('mouseout', (params) => {
    if (changePieInterval) {
      clearInterval(changePieInterval);
    }
    changePieInterval = setInterval(selectChart, intervaltime);
  });

  // 高亮效果切换到下一个图形
  const selectChart = ()=> {
    if (option.series[0].data) {
      var dataLen = option.series[0].data.length;
      currentIndex = (currentIndex + 1) % dataLen;
      highlightChart();
    }
  }
}

如何让雷达图好看一些

js
const chartConfig = {
  barWidth: '12',
  textStyle: {
    color: '#fff',
    fontSize: 10.5,
  },
  lineStyle: {
    color: 'rgba(255, 255, 255, .6)', // 设置横坐标线颜色
    // width: 2, // 设置横坐标线宽度
  }
}

const indicator = computed(() => {
  if (JSON.stringify(props.chartData) === '[]') {
    return [  //配置各个维度的最大值
      { name: '', max: 0 },
    ]
  }
  return props.chartData.map(item => {
    return {
      name: item.name,
      max: 100
    }
  })
})

// ----------------------------  图表配置开始
const option = {
    tooltip: {
      trigger: 'item',
      textStyle: {
        color: '#000',
        fontSize: 11,
      },
    },
    legend: {
      itemWidth: 9,
      itemHeight: 9,
      icon: "rect",
      textStyle: {
        color: "#fff",
        fontSize: 10
      },
      left: 'left',
      top: '3.5%',
      left: '3.1%',
    },
    radar: {
      center: ['50%', '57.5%'],
      radius: '60%',
      splitNumber: 5,
      shape: 'circle', // 设置为圆形
      indicator: indicator.value,
      axisLine: {
        show: true,
        lineStyle: {
          color: 'rgba(255,255,255,0.6)',
          type: 'dashed'
        }
      },
      splitLine: {
        show: true,
        lineStyle: {
          color: 'rgba(255,255,255,0.5)',
          type: 'dashed'
        }
      },
      splitArea: {
        show: true,
        areaStyle: {
          color: ['rgba(17,85,231,.3)', 'transparent']
        }
      }
    },
    series: [
      {
        type: 'radar',
        animation: true,
        data: [{
          name: "当日各维度评分(分)",
          value: chartData.map(item => item.value),
          areaStyle: {
            // 填充区颜色
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: 'rgba(22, 62, 112, .8)' },
              { offset: 1, color: 'rgb(74,190,254,.6)' },
            ])
          },
          // 线条样式
          lineStyle: {
            color: 'rgb(74,194,254,.5)',
            width: 1.5
          },
          symbol: 'circle', // 拐点形状,rect=矩形,circle=圆形
          // 拐点的大小
          symbolSize: 5,
          // 小圆点(拐点)设置为白色
          itemStyle: {
            color: '#4abefe'
          },
        }]
      },
    ]
}
// ----------------------------  图表配置结束
const chartConfig = {
  barWidth: '12',
  textStyle: {
    color: '#fff',
    fontSize: 10.5,
  },
  lineStyle: {
    color: 'rgba(255, 255, 255, .6)', // 设置横坐标线颜色
    // width: 2, // 设置横坐标线宽度
  }
}

const indicator = computed(() => {
  if (JSON.stringify(props.chartData) === '[]') {
    return [  //配置各个维度的最大值
      { name: '', max: 0 },
    ]
  }
  return props.chartData.map(item => {
    return {
      name: item.name,
      max: 100
    }
  })
})

// ----------------------------  图表配置开始
const option = {
    tooltip: {
      trigger: 'item',
      textStyle: {
        color: '#000',
        fontSize: 11,
      },
    },
    legend: {
      itemWidth: 9,
      itemHeight: 9,
      icon: "rect",
      textStyle: {
        color: "#fff",
        fontSize: 10
      },
      left: 'left',
      top: '3.5%',
      left: '3.1%',
    },
    radar: {
      center: ['50%', '57.5%'],
      radius: '60%',
      splitNumber: 5,
      shape: 'circle', // 设置为圆形
      indicator: indicator.value,
      axisLine: {
        show: true,
        lineStyle: {
          color: 'rgba(255,255,255,0.6)',
          type: 'dashed'
        }
      },
      splitLine: {
        show: true,
        lineStyle: {
          color: 'rgba(255,255,255,0.5)',
          type: 'dashed'
        }
      },
      splitArea: {
        show: true,
        areaStyle: {
          color: ['rgba(17,85,231,.3)', 'transparent']
        }
      }
    },
    series: [
      {
        type: 'radar',
        animation: true,
        data: [{
          name: "当日各维度评分(分)",
          value: chartData.map(item => item.value),
          areaStyle: {
            // 填充区颜色
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: 'rgba(22, 62, 112, .8)' },
              { offset: 1, color: 'rgb(74,190,254,.6)' },
            ])
          },
          // 线条样式
          lineStyle: {
            color: 'rgb(74,194,254,.5)',
            width: 1.5
          },
          symbol: 'circle', // 拐点形状,rect=矩形,circle=圆形
          // 拐点的大小
          symbolSize: 5,
          // 小圆点(拐点)设置为白色
          itemStyle: {
            color: '#4abefe'
          },
        }]
      },
    ]
}
// ----------------------------  图表配置结束

如何自动点击按钮,并让他们一直循环

js
const AutoClickMenu = () => {
  const btnList = document.querySelectorAll(".btn_list li")
  let loopTime = 5000

  const clickTool = (i) => {
    btnList[i].click()
    btnList.forEach((item) => {
      item.classList.remove("current")

      btnList[i].classList.add("current")
    })
  }

  for (let i = 0; i < btnList.length; i++) {
    //立即执行函数
    (() => {
      setTimeout(() => {
        clickTool(i)
        if (btnList.length === i + 1) {
          setTimeout(() => {
            AutoClickMenu()
          }, loopTime);
        }
      }, loopTime * i)
    })(i)
  }
}
const AutoClickMenu = () => {
  const btnList = document.querySelectorAll(".btn_list li")
  let loopTime = 5000

  const clickTool = (i) => {
    btnList[i].click()
    btnList.forEach((item) => {
      item.classList.remove("current")

      btnList[i].classList.add("current")
    })
  }

  for (let i = 0; i < btnList.length; i++) {
    //立即执行函数
    (() => {
      setTimeout(() => {
        clickTool(i)
        if (btnList.length === i + 1) {
          setTimeout(() => {
            AutoClickMenu()
          }, loopTime);
        }
      }, loopTime * i)
    })(i)
  }
}