Skip to content

首页

mock搭建

1 新建数据json文件

src/mock/data.json

json文件默认就是对外暴漏

json

2 新建mockjs

src/mock/index.ts

ts
# 安装 mockjs
npm install mockjs

// 引入mock
// 忽略ts类型校验
// @ts-ignore 
import Mock from 'mockjs'

import data from './date.json'

// 创建首页模块接口
// mock()函数第一个参数是首页的接口地址
// 第二个参数是返回的数据对象
Mock.mock('/mock/home',{
    code:200,
    data:data
})

3 入口文件引入执行

main.ts

ts
import './mock'

4 二次封装axios

src/utils/requestMock.ts

ts
import axios, { type AxiosResponse } from 'axios';
import { ElMessage, ElMessageBox } from 'element-plus';
import pinia from '@/stores/index';
import { useUserInfoStore } from '../stores/userInfo';

/* 定义response对象的data接口 */
interface ResponseData<T> {
	code: number;
	data: T;
	message: string;
}

// 配置新建一个 axios 实例
const service = axios.create({
	baseURL: "/mock", // 请求路由设置为mock
	timeout: 50000,
});

// 添加请求拦截器
service.interceptors.request.use(
	(config:any) => {
		// 获取用户小仓库(在组件外使用,需要将大仓库传入作为参数)
		const userInfo = useUserInfoStore(pinia)
		const token = userInfo.token
		//请求头携带token:才可以获取用户信息
        userInfo.token && (config.headers.token = token);
		// config.headers.token = token
		// 返回请求配置项
		return config;
	}
);

// 添加响应拦截器
service.interceptors.response.use(
	/* 约束一下response */
	async (response: AxiosResponse<ResponseData<any>>) => {
		// 对响应数据做点什么:简化数据
		const res = response.data;
		if (res.code !== 20000 && res.code !== 200) { /* 成功数据的code值为20000/200 */
			// 统一的错误提示
			ElMessage({
				message: (typeof res.data == 'string' && res.data) || res.message || 'Error',
				type: 'error',
				duration: 5 * 1000
			})

			// `token` 过期或者账号已在别处登录
			if (response.status === 401) {
				const storeUserInfo = useUserInfoStore(pinia)
				await storeUserInfo.reset()
				window.location.href = '/' // 去登录页
				ElMessageBox.alert('你已被登出,请重新登录', '提示', {})
					.then(() => { })
					.catch(() => { })
			}
			return Promise.reject(service.interceptors.response);
		} else {
			return res.data; /* 返回成功响应数据中的data属性数据 */
		}
	},
	(error) => {
		// 对响应错误做点什么
		if (error.message.indexOf('timeout') != -1) {
			ElMessage.error('网络超时');
		} else if (error.message == 'Network Error') {
			ElMessage.error('网络连接错误');
		} else {
			if (error.response.data) ElMessage.error(error.response.statusText);
			else ElMessage.error('接口路径找不到');
		}
		return Promise.reject(error);
	}
);

export default service;

5 封装首页接口api

src/api/home/index.ts

ts
// 导入requestMock对象
import requestMock from '@/utils/requestMock';
//首页数据报表接口地址
export const reqCharts = () => requestMock.get("/home");

6 在首页组件中发送请求获取数据

src/views/home/index.vue

ts
<template>
	<div class="home">
		<Top  />
		<Middle  />
		<Bottom />
	</div>
</template>

<script lang="ts">
export default {
	name: 'Home',
}
</script>
<script lang="ts" setup>
import { useUserInfoStore } from '@/stores/userInfo'
import Middle from '@/views/home/midChart/index.vue'
import Top from '@/views/home/topChart/index.vue'
import Bottom from '@/views/home/bottomChart/index.vue'
//引入请求函数
import { reqCharts } from "@/api/home"
import { ref, onMounted } from "vue"
// 存储首页数据可视化的数据对象
const chartObj = ref({})
const userInfoStore = useUserInfoStore()
//组件挂载完毕:获取首页需要数据
onMounted(() => {
  	getData()
})
//获取首页全部的数据方法
const getData = async() => {
    const result = await reqCharts()
    //存储数据
    chartObj.value = result
}

</script>

<style scoped>

</style>

首页静态页面

1 首页拆分上中下组件

顶部组件

src/views/home/chartTop/index.vue

vue
<template>
    <div>top</div>
</template>

<script setup lang='ts'>
 
</script>

<style scoped lang="less">
</style>

中间组件

src/views/home/chartMiddle/index.vue

vue
<template>
    <div>mid</div>
</template>

<script setup lang='ts'>
 
</script>

<style scoped lang="less">
</style>

底部组件

src/views/home/chartBottom/index.vue

vue
<template>
    <div>bottom</div>
</template>

<script setup lang='ts'>
 
</script>

<style scoped lang="less">
</style>

2 首页组件中引入

src/views/home/index.vue

vue
<template>
	<div class="home">
		<Top  />
		<Middle  />
		<Bottom />
	</div>
</template>

<script lang="ts">
export default {
	name: 'Home',
}
</script>
<script lang="ts" setup>
import { useUserInfoStore } from '@/stores/userInfo'
import Middle from '@/views/home/midChart/index.vue'
import Top from '@/views/home/topChart/index.vue'
import Bottom from '@/views/home/bottomChart/index.vue'
//引入请求函数
import { reqCharts } from "@/api/home"
import { ref, onMounted } from "vue"
// 存储首页数据可视化的数据对象
const chartObj = ref({})
const userInfoStore = useUserInfoStore()
//组件挂载完毕:获取首页需要数据
onMounted(() => {
  	getData()
})
//获取首页全部的数据方法
const getData = async() => {
    const result = await reqCharts()
    //存储数据
    chartObj.value = result
}

</script>

<style scoped>

</style>

顶部的组件

1 顶部组件再进行拆分为组件

顶部组件的图表组件模版

src/views/home/chartTop/card/index.vue

vue
<template>
    <el-card class="container">
        <!-- 设置插槽1 -->
        <div class="header_title">
            <slot name="header"></slot>
        </div>

        <!-- 设置插槽2 -->
        <div class="number">
            <slot name="number"></slot>
        </div>

        <!-- 设置插槽3 -->
        <div class="charts">
            <slot name="charts"></slot>
        </div>

        <!-- 设置插槽4 -->
        <div class="bottom">
            <slot name="bottom"></slot>
        </div>
    </el-card>
</template>

<script setup lang='ts'>

</script>

<style scoped lang="scss">
// 定义颜色
$color: yellowgreen;

.container {
    .header_title {
        margin: 10px 0px;
        font-weight: 900;
    }

    .number {
        // 字体缩进
        text-indent: 10px;
        font-weight: 800;
        // 动画 动画名 动画时间 动画速度 动画延时时间 执行次数
        animation: donghua .5s linear 0s 1;
    }

    .charts {
        height: 80px;
        border-bottom: 1px dashed yellowgreen;
        display: flex;
        // 主轴上的伸缩方式 居中
        justify-content: center;
        // 侧轴上的伸缩方式 居中
        align-items: center;
        font-size: 16px;
    }

    .bottom {
        margin-top: 10px;
    }
}

//定义动画
@keyframes donghua {
    from {
        // 变换类型为 旋转 0度
        transform: rotate(0deg);
    }

    to {
        // 变换类型为 旋转 360度
        transform: rotate(360deg);
    }
}
</style>

2 父组件引用子组件

src/views/home/chartTop/index.vue

vue
# 引用四次子组件


# 让子组件在父组件中横向排列
# 方式一:使用flex布局,让子组件横向排列,在外层标签设置类名top,里层标签设置item
	.top{
		display:flex
	}
	.top .item{
		width:20%
	}
# 方式二:element栅格系统,每一列设置距离 :gutter="20"
	<el-row :gutter="20">
        <el-col :span='6'>1</el-col>
        <el-col :span='6'>2</el-col>
        <el-col :span='6'>3</el-col>
        <el-col :span='6'>4</el-col>
	</el-row>
vue
<template>
    <!-- 使用el栅格系统, 间隔为20px -->
    <el-row :gutter="20">
        <!-- 第一个图表 -->
        <el-col :span="6">
            <Card>
                <!-- 插槽1 -->
                <template #header>
                    <div></div>
                </template>
                <!-- 插槽2 -->
                <template #number>
                    <div></div>
                </template>
            </Card>
        </el-col>
        <!-- 第二个图表 -->
        <el-col :span="6">123</el-col>
        <!-- 第三个图表 -->
        <el-col :span="6">123</el-col>
        <!-- 第四个图表 -->
        <el-col :span="6">123</el-col>

    </el-row>
</template>

<script setup lang='ts'>
import Card from "./card/index.vue";

</script>

<style scoped lang="less">
</style>

3 父组件发送请求获取数据

父组件 src/views/home/chartTop/index.vue

ts
# 组件挂载完成后,发请求获取数据(在生命周期函数内,不要写await和async,定义在外边然后调用)

# 存储数据
	const chartObj = ref({})
# 把数据传到仓库,或者使用prop
vue
<template>
	<div class="home">
		<Top  :data="chartObj"/>
		<Middle  />
		<Bottom />
	</div>
</template>

<script lang="ts">
export default {
	name: 'Home',
}
</script>
<script lang="ts" setup>
import { useUserInfoStore } from '@/stores/userInfo'
import Middle from '@/views/home/midChart/index.vue'
import Top from '@/views/home/topChart/index.vue'
import Bottom from '@/views/home/bottomChart/index.vue'
//引入请求函数
import { reqCharts } from "@/api/home"
import { ref, onMounted } from "vue"
// 存储首页数据可视化的数据对象
const chartObj = ref({})
const userInfoStore = useUserInfoStore()
//组件挂载完毕:获取首页需要数据
onMounted(() => {
  	getData()
})
//获取首页全部的数据方法
const getData = async() => {
    const result = await reqCharts()
    //存储数据
    chartObj.value = result
}

</script>



<style scoped>

</style>

4 使用插槽 父组件给子组件传值

子组件 src/views/home/chartTop/card/index.vue

ts
# 子组件设置插槽占个坑位

父组件 src/views/home/chartTop/index.vue

ts


# 第四个卡片组件
	# 在子组件对应插槽处填充内容,并传参
	# 在子组件charts插槽处填充一个进度条组件
    	# 定义一个进度条组件 src/views/home/chartTop/progress/index.vue
			# 使用vue-echarts组件作为进度条
				# 设置x轴
                	# x轴隐藏
                    # x轴设置最小值和最大值
                # 设置y轴
                	# y轴隐藏
                # 系列
                	# 设置类型
                    # 设置数据
                    # 设置柱状图宽度
                    # 设置柱条背景颜色
                    # 设置柱条背景显示
                    # 设置柱状图的文本标签
                    	# 显示
                    	# 设置文本位置
                        # 设置标签自定义内容
                        # 设置文字的颜色
                # 布局
                	上下左右都为0

第一个图表卡片

ts
# 第一个卡片组件

    # 在子组件对应插槽处填充内容,并传参

    # 设置样式
        使用flex居中
        设置字体动画 设置动画 关键帧的使用

    # 阿里的图标不需要下载到本地,直接使用
vue
<template>
    <!-- 使用el栅格系统, 间隔为20px -->
    <el-row :gutter="20">
        <!-- 第一个图表 -->
        <el-col :span="6">
            <Card>
                <!-- 插槽1 -->
                <template #header>
                    <div>{{ data["//1"] }}</div>
                </template>
                <!-- 插槽2 -->
                <template #number>
                    <div>{{ data.salesToday }}</div>
                </template>
                <!-- 插槽3 -->
                <template #charts>
                    <div style="line-height: 30px">
                        日同比: {{ data.salesGrowthLastDay}}
                    </div>
                    <div style="line-height: 30px">
                        月同比: {{ data.salesGrowthLastMonth}}
                    </div>
                </template>
                <template #bottom>
                    <div>昨日销售额:{{ data.salesLastDay }}</div>
                </template>
            </Card>
        </el-col>
        <!-- 第二个图表 -->
        <el-col :span="6">123</el-col>
        <!-- 第三个图表 -->
        <el-col :span="6">123</el-col>
        <!-- 第四个图表 -->
        <el-col :span="6">123</el-col>

    </el-row>
</template>

<script setup lang='ts'>
import Card from "./card/index.vue";

// 获取Home组件传来的数据
defineProps(["data"]);

</script>

<style scoped lang="less">
</style>

第二个图表卡片

ts
# 第二个卡片组件
	# 在子组件对应插槽处填充内容,并传参
	# 在子组件charts插槽处填充一个折线图组件
		# 定义一个折线图组件 src/views/home/chartTop/line/index.vue
			# 安装echarts插件
			# 导包,设置一个有高度的容器,初始化echarts实例(在onMounted调用),
			# 设置Echarts配置项
				# 折线图使用echarts中的onresize事件,只有在视口发生变化的时候才会自适应,展开和收缩菜单栏,视口没有发生变化,不会产生自适应
				# 折线图拿到数据时,才渲染页面 v-if="data.xxx",解决初次渲染无数据,会报错

单独封装一个折线图组件

src/views/home/topChart/line/index.vue

vue
<template>
    <!-- 容器:具有一定高度 -->
    <div class="box" ref="line">专门开发折线图</div>
</template>

<script setup lang='ts'>
// 导包
import * as echarts from "echarts";

import { ref, onMounted } from "vue";
// 获取divDOM元素
const line = ref();
// 获取父组件传值
const dataLine = defineProps(["data"]);

// 挂载完成后执行
onMounted(() => {
    initLine();
})
//初始化echarts实例
const initLine = () => {
    // 初始化实例
    const mycharts = echarts.init(line.value);
    //设置配置项
    mycharts.setOption({
        // x轴
        xAxis: {
            // 均匀分布
            type: "category",
            //两个不留白
            boundaryGap: false,
            //隐藏x轴
            show: false,
            //指示器
            axisPointer: {
                //显示指示器
                show: true,
                //指示器的类型
                type: "line",
            },
        },
        yAxis: {
            //隐藏y轴
            show: false,
        },
        series: {
            // 类型为折线图
            type: "line",
            data: dataLine.data,
            //圆滑曲线
            smooth: true,
            //区域填充颜色
            areaStyle: {
                color: "skyblue",
            },
            //拐点隐藏
            symbol: "none",
        },
        //调整布局
        grid: {
            left: 0,
            right: 0,
            bottom: 0,
            top: 0,
            show: true,
            backgroundColor: "rgba(10,120,90,.1)",
        },
        //提示框组件
        tooltip: {
            //显示提示款在容器内
            confine: true,
            backgroundColor: "rgba(10,120,90,.1)",
        },
    });

    //图形图标自适应
    window.onresize = function () {
        //图标的自适应,使用实例的方法
        mycharts.resize();
    };
};
</script>

<style scoped>
.box {
    width: 100%;
    height: 100%;
}
</style>

父组件传递参数

src/views/home/topChart/index.vue

vue
<!-- 第二个图表 -->
        <el-col :span="6">
            <Card>
                <template #header>
                    <div>{{ data["//2"] }}</div>
                </template>
                <template #number>
                    <div>
                        {{ data.orderToday }}
                    </div>
                </template>
                <template #bottom>
                    <div>昨日订单{{ data.orderLastDay }}</div>
                </template>
                <template #charts>
                    <!-- 当数据存在时,再渲染数据,避免假报错 -->
                    <Line :data="data.orderTrend" v-if="data.orderTrend" />
                </template>
            </Card>
        </el-col>

第三个图表卡片

ts
# 第三个卡片组件
	# 在子组件对应插槽处填充内容,并传参
	# 在子组件charts插槽处填充一个柱状图组件
		# 定义一个柱状图组件 src/views/home/chartTop/bar/index.vue
			# 使用vue-echarts插件解决自适应问题

单独封装一个柱状图组件

src/views/home/topChart/bar/index.vue

vue
<template>
    <div class="bar">
        <!-- autoresize:自适应 -->
        <!-- 通过option属性设置配置项 -->
        <v-chart :option="getOption()" autoresize></v-chart>
    </div>
</template>

<script setup lang="ts">
const getOption = () => {
    return {
        xAxis: {
            // x轴均匀分布
            type: "category",
            // x轴不显示
            show: false,
        },
        yAxis: {
            // y轴不显示
            show: false,
        },
        series: {
            // 类型为柱状图
            type: "bar",
            // 传递数据
            data: barData.data
        },
        grid: {
            // 设置布局
            left: 0,
            right: 0,
            top: 0,
            bottom: 0,
        },
        //提示框组件
        tooltip: {
            // 设置指示器的类型为 十字准星
            axisPointer: {
                type: "cross",
            },
        },
    };

};

const barData = defineProps(["data"]);
</script>

<style scoped>
.bar {
    width: 100%;
    height: 100%;
}
</style>

父组件传递参数

src/views/home/topChart/index.vue

vue
<!-- 第三个图表 -->
        <el-col :span="6">
            <Card>
                <template #header>
                    <div>{{ data["//3"] }}</div>
                </template>
                <template #number>
                    <div>
                        {{ data.orderUser }}
                    </div>
                </template>
                <template #bottom>
                    <div>退货率:{{ data.returnRate }}</div>
                </template>
                <template #charts>
                    <Bar :data="data.orderUserTrend" />
                </template>
            </Card>
        </el-col>

第四个图表卡片

单独封装一个进度条组件

src/views/home/topChart/bar/index.vue

vue
<template>
    <div class="container">
        <!-- 自适应宽度 -->
        <v-chart autoresize :option="getOption()"></v-chart>
    </div>
</template>

<script setup lang="ts">
import * as echarts from 'echarts/core'
// 导入水球图
import "echarts-liquidfill";
// 进度条的配置
// const getOption = () => {
//   return {
//       xAxis:{
//          //最大数值与最小数值
//          min:0,
//          max:100,
// 不显示
//          show:false
//       },
//       yAxis:{
//         //在y轴均匀分布
//         type:'category',
//         show:false

//       },
//       series:{
//         type:'bar',
//         data:[50],
//         //柱条的宽度
//         barWidth:10,
//         //设置柱条的背景颜色
//         showBackground:true,
//         backgroundStyle:{
//             color:'#ccc'
//         },
//         //文本标签
//         label:{
//             show:true,
//             //文字的位置
//             position:'right',
//             //内容自定义
//             formatter:'|',
//             color:'#5470c6'
//         }
//       },
//       grid:{
//         left:0,
//         top:0,
//         bottom:0,
//         right:0
//     },
//   };
// };
const value = 0.5;
//水球图的配置项
const getOption = () => {
    return {
        series: {
            type: "liquidFill",
            data: [0.6, 0.55, 0.4, 0.25],
            radius: "90%",
            label: {
                fontSize: 12,
            },
            outline: {
                show: false,
            },
            shape: "pin",
        },
    };
};
</script>
<style scoped>
.container {
    width: 100%;
    height: 100%;
}
</style>

父组件传递参数

src/views/home/topChart/index.vue

ts
<!-- 第四个图表 -->
        <el-col :span="6">
            <Card>
                <template #header>
                    <div>{{ data["//4"] }}</div>
                </template>
                <template #number>
                    <div>{{ data.usersTotal }}</div>
                </template>
                <template #bottom>
                    <div style="display:flex;justify-content:space-between">
                        <div>月同比:{{ data.userGrowthLastDay }}</div>
                        <div>日同比:{{ data.userGrowthLastMonth }}</div>
                    </div>
                </template>
                <template #charts>
                    <Watter/>
                </template>
            </Card>
        </el-col>

中间的组件

静态搭建

使用 element-plus中的Tabs组件

ts
# 使用 element-plus中的Tabs组件搭建静态

# 使用深度选择器调整Tabs组件的样式
	::v-deep(){
		
	}

# DatePicker组件(element-plus)
	设置组件的中文
	设置组件to
# 把时间,标签定位到右侧
	设置span的左右宽度,
	设置颜色
# 使用栅格系统,把图标和排名左右分开
	
# 日历组件

日历组件

ts
# 当点击 今天 时 日历组件选择今天
	定义一个函数
		向ref响应式数据中添加今天的数组 
        	方式一:使用JS原生的Date	
        		dateArr.value = new Date().getFullYear()
			方式二:使用moments插件
            	
            方式三:使用dayjs插件
            	安装:npm install dayjs
                使用(api):
                	获取当前时间
                    本周的第一天,本周的最后一天(一般是外国人使用的)
					手动设置周的第一天
                    本月的第一天,本月的最后一天
                    
# 定义收集日历的ref响应式数据
	const dateArr = ref()
# 使用value-format属性把Date格式转为需要的字符串格式

# 使用v-model双向绑定

moment插件

moment和dayjs是一样的

ts
npm install moment
引入:import moment from 'moment'
取值:(当前时间)
moment(value).format("YYYY-MM-DD") // 时间格式

dayjs插件

https://dayjs.fenxianglu.cn/category/parse.html#utc

ts
安装:
	npm install dayjs
引入:(在组件中)
	import dayjs from 'dayjs'
取值(当前时间)
    dayjs() // 返回一个对象
    dayjs().format('')
    // 默认是当地时间
    dayjs().format("YYYY-MM-DD") //2019-03-06

中间柱状图

ts
# 在栅格系统的标签内写柱状图
	# 使用 v-chart 绘制柱状图
    	# 柱状图配置项
        	# 设置标题
            	标题位置
                文字颜色
            # 设置x轴
            	均匀分配
                x轴底部文字设置
                设置x轴显示内容
            # 设置y轴
            	配置y轴轴线
                配置y轴刻度
            # 系列
            	设置类型
                设置数据
                设置柱条颜色
                文本标签的显示
            # 布局
            	上下左右
            # 提示框
            	触发时机:
            	指示器:设置类型
                
# 通过响应式数据控制销售额和访问量的切换
	# 在标题内容使用三元表达式处理显示内容
    # 数据也使用三元表达式处理显示那组数据

排名组件

ts
# 静态搭建 展示排名
	使用ul li结构
    设置样式
    	设置flex布局
        	主轴 一左一右
            侧轴 居中
            动态设置前三个li标签设置样式
            	:class={activeName = "index<3"}
				.activeName{
                    
                }
# 渲染数据
	使用v-if设置展示哪一个

中间的组件

src/views/home/chartMid/index.vue

ts
<template>
    <el-card style="margin: 10px 0px">
        <!-- 头部 -->
        <div class="header">
            <!-- 选项卡:左侧 -->
            <el-tabs class="demo-tabs" v-model="active">
                <el-tab-pane label="销售额" name="sale"></el-tab-pane>
                <el-tab-pane label="访问量" name="visite"></el-tab-pane>
            </el-tabs>
            <!-- 右侧:时间选择 -->
            <div class="left">
                <span @click="setToday">今日</span>
                <span @click="setWeek">本周</span>
                <span @click="setMonth">本月</span>
                <span @click="setYear">本年</span>

                <el-date-picker type="daterange" range-separator="-" start-placeholder="入住时间" end-placeholder="离开时间"
                    size="small" value-format="YYYY-MM-DD" v-model="dateArr"/>
            </div>
        </div>
        <!-- 中间内容 -->
        <el-row :gutter="10">
            <!--柱状图-->
            <el-col :span="18">
                <v-chart :option="getBar()" style="height: 300px"></v-chart>
            </el-col>
            <!-- 排行 -->
            <el-col :span="6">
                <h1>门店{{ active == "sale" ? "销售额" : "访问量" }}排名</h1>
                <ul class="sort" v-if="active == 'sale'">
                    <li v-for="(item, index) in data.orderRank" :key="index">
                        <span :class="{ activeName: index < 3 }">{{ item.no }}</span>
                        <span>{{ item.name }}</span>
                        <span>{{ item.count }}</span>
                    </li>
                </ul>
                <ul class="sort" v-if="active == 'visite'">
                    <li v-for="(item, index) in data.userRank" :key="index">
                        <span :class="{ activeName: index < 3 }">{{ item.no }}</span>
                        <span>{{ item.name }}</span>
                        <span>{{ item.count }}</span>
                    </li>
                </ul>
            </el-col>
        </el-row>
    </el-card>
</template>

<script setup lang='ts'>
import { ref } from "vue";
const active = ref("sale");
//收集日历数据
const dateArr = ref<any>([]);
//处理时间的插件dayjs
import dayjs from "dayjs";
//接受首页全部数据
const props = defineProps(["data"]);
//点击今日按钮回调
const setToday = () => {
    const start = dayjs().format("YYYY-MM-DD");
    const end = dayjs().format("YYYY-MM-DD");
    dateArr.value = [start, end];
}
//点击本周
const setWeek = () => {
    // 通过查看dayjs的文档得知 dayjs().day(1) 获取本周第一天
    const start = dayjs().day(1).format("YYYY-MM-DD");
    const end = dayjs().day(7).format("YYYY-MM-DD");
    dateArr.value = [start, end];
}
//本月
const setMonth = () => {
    const start = dayjs().startOf("month").format("YYYY-MM-DD");
    const end = dayjs().endOf("month").format("YYYY-MM-DD");
    dateArr.value = [start, end];
}
//本年
const setYear = () => {
    const start = dayjs().startOf("year").format("YYYY-MM-DD");
    const end = dayjs().endOf("year").format("YYYY-MM-DD");
    dateArr.value = [start, end];
}
//柱状图的配置项
const getBar = () => {
    return {
        title: {
            // 动态判断
            text: active.value == "sale" ? "销售额" : "访问量",
            left: "center",
            textStyle: {
                color: "yellowgreen",
            },
        },
        xAxis: {
            type: "category",
            //x轴底部的文字设置
            data: props.data.userFullYearAxis,
        },
        yAxis: {
            //轴线
            axisLine: {
                show: true,
            },
            //刻度
            axisTick: {
                show: true,
            },
        },
        series: {
            type: "bar",
            // 动态显示数据
            data: active.value == "sale" ? props.data.orderFullYear : props.data.userFullYear,
            //柱条的颜色
            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
                },
            },
            //文本标签
            label: {
                show: true,
            },
        },
        grid: {
            left: 30,
            bottom: 30,
            right: 0,
        },
        //提示框组件
        tooltip: {
            trigger: "axis", // 触发时机
            axisPointer: { // 设置指示器
                type: "shadow",
            },
        },
    };
};
</script>

<style scoped lang="scss">
// 设置el-table 元素的颜色
::v-deep(.el-tabs__item) {
    color: yellowgreen;
}

// 设置激活的颜色
::v-deep(.el-tabs__active-bar) {
    background: yellowgreen !important;
}

// 头部的样式
.header {
    // 相对定位
    position: relative;

    .left {
        // 绝对定位
        position: absolute;
        right: 0;
        top: 0;

        span {
            margin: 0px 5px;
            font-size: 14px;
            color: yellowgreen;
        }
    }
}

// 排行的样式
.sort {
    // 清除默认样式
    list-style: none;

    li {
        display: flex;
        // 主轴方向 两端
        justify-content: space-between;
        align-items: center;
        margin: 10px 0px;

        span:nth-child(1) {
            display: inline-block;
            width: 15px;
            height: 15px
        }

        .activeName {
            color: white;
            background: black;
            // 设置为圆
            border-radius: 50%;
            text-align: center;
            // 设置行高
            line-height: 15px
        }
    }
}
</style>

父组件传值

src/views/home/index.vue

vue
<template>
	<div class="home">
		<Top  :data="chartObj"/>
		<Middle  :data="chartObj"/>
		<Bottom />
	</div>
</template>

底部的组件

ts
# 使用栅格系统把卡片设置为左右两个
	左侧card组件的设置
    	使用card组件的header处插槽填充内容
        	设置card组件header处的内容和样式
            	左侧使用div包裹文字
            	右侧使用dropdown下拉菜单组件
                设置flex布局,一左一右
				使用深度选择器修改字体的颜色
            设置左侧card的内容
            	使用雷达图组件(echart官网获取配置项)
ts
# 右侧card组件的设置
    设置card组件header处的内容和样式
    	div文字
        单选按钮组件 radio组件
        	设置样式和弹性布局
            设置响应式数据 收集单选按钮value
    设置左侧card的内容
        使用饼图组件(echart官网获取配置项)
			设置视觉引导线
           	设置文本标签
            饼图半径
            标题组件
            	主标题
                	text:通过radio的状态三元表达式展示不同的内容
                子标题
                调整标题的位置
                	left,top
            饼图高亮效果
            	放大效果
           	设置品类的数组,用于饼图的数据渲染
            设置商品的数组,用于饼图的数据渲染
            通过radio数据状态,三元表达式控制哪个数组数据渲染
			配置图例的位置和布局 legend
# 在v-echarts组件中绑定鼠标进入事件
	<v-echart @mouseover="handle"></v-echart>
	@mouseover
    设置回调函数 handle
		通过Echarts实例的方法setOption()设置新的配置项,修改标题

底部的组件

src/views/home/chartBottom/index.vue

vue
<template>
    <el-row :gutter="10">
        <!-- 左侧卡片 -->
        <el-col :span="14">
            <el-card>
                <!-- 设置element card组件中的header插槽内容 -->
                <template #header>
                    <div class="top">
                        <span>关键字搜索</span>
                        <!-- 下拉框组件 -->
                        <el-dropdown>
                            <!-- 下拉框标题 -->
                            <span class="el-dropdown-link">城市选择器
                                <el-icon class="el-icon--right"><arrow-down /></el-icon>
                            </span>
                            <!-- 下拉框内容 -->
                            <template #dropdown>
                                <el-dropdown-menu>
                                    <el-dropdown-item>北京</el-dropdown-item>
                                    <el-dropdown-item>上海 </el-dropdown-item>
                                </el-dropdown-menu>
                            </template>
                        </el-dropdown>
                    </div>
                </template>
                <!-- 设置身体内容 -->
                <v-chart class="leida" :option="getRadar()" style="height: 300px"></v-chart>
            </el-card>
        </el-col>
        <!-- 右侧卡片 -->
        <el-col :span="10">
            <el-card>
                <!-- 卡片头部 -->
                <template #header>
                    <div class="top">
                        <div>分类销售</div>
                        <!-- 单选按钮 -->
                        <el-radio-group size="small" v-model="radio">
                            <el-radio-button value="品类" label="品类"/>
                            <el-radio-button value="商品" label="商品"/>
                        </el-radio-group>
                    </div>
                </template>
                <!-- 展示饼图 设置鼠标事件-->
                <v-chart ref="charts" style="height: 300px" :option="getPie()" @mouseover="handler"></v-chart>
            </el-card>
        </el-col>
    </el-row>
</template>

<script setup lang='ts'>
import { ArrowDown } from "@element-plus/icons-vue";
import { ref } from "vue";
// 元素ref对象
const radio = ref("品类");
const charts = ref();

// 右侧数据
//品类的数据
const arr1 = [{ name: '华为', value: 90 }, { name: 'oppo', value: 30 }, { name: 'vivo', value: 60 }, { name: '小米', value: 99 }];
//商品的数据
const arr2 = [{ name: '华为1', value: 190 }, { name: 'oppo1', value: 130 }, { name: 'vivo1', value: 160 }, { name: '小米1', value: 199 }]

//雷达图配置项
const getRadar = () => {
    return {
        title: {
            text: "王者荣耀",
        },
        // 设置图例
        legend: {
            data: ["男生", "女生"],
            right: 0,
            // 纵向排列
            orient: "vertical",
        },
        // 雷达结构
        radar: {
            indicator: [
                { name: "生存", max: 10000 },
                { name: "发育", max: 10000 },
                { name: "猥琐", max: 10000 },
                { name: "别浪", max: 10000 },
                { name: "我们会赢", max: 10000 },
                { name: "坚持就是胜利", max: 10000 },
            ],
            splitNumber: 5, // 将雷达图分成5层
        },
        series: [
            {
                name: "Budget vs spending",
                type: "radar",
                // 雷达数据
                data: [
                    {
                        value: [4200, 3000, 700, 400, 2000, 6000],
                        name: "男生",
                    },
                    {
                        value: [5000, 4000, 8000, 6000, 7000, 3000],
                        name: "女生",
                    },
                ],
            },
        ],
    };
};

//饼图的配置项
const getPie = () => {
    return {
        // 提示框
        tooltip: {
            trigger: "item", // 触发时机
        },
        // 图例
        legend: {
            right: "0",
            orient: 'vertical' // 设置纵向
        },
        series: [
            {
                type: "pie", // 类型为饼图
                //饼图半径
                radius: ["30%", "60%"],
                avoidLabelOverlap: false, // 标签放在中心位置

                itemStyle: { // 图形的样式
                    borderRadius: 10, // 图形的边角
                    borderColor: "#fff", // 描边的颜色
                    borderWidth: 2, // 描边的宽度
                },
                //设置标签标题
                label: {
                    show: true,
                },
                //高亮效果
                emphasis: {
                    label: { //高亮显示label标签
                        show: true,
                        fontSize: 20, // 设置视觉引导线字体大小
                        fontWeight: "bold", // 设置字体宽度
                    },
                },
                //视觉引导线
                labelLine: {
                    show: true,
                },
                //数据
                data: radio.value == '品类' ? arr1 : arr2,

            },
        ],
        //标题组件
        title: {
            //主标题
            text: radio.value == '品类' ? arr1[0].name : arr2[0].name, //主标题
            subtext: radio.value == '品类' ? arr1[0].value : arr2[0].value, //副标题
            //标题的位置
            left: 'center',
            top: 'center'
        }
    };
};

// 鼠标进入事件 params 默认是饼图的元素对象
const handler = (params: any) => {
    // 获取 饼图 实例对象,并使用实例对象上的 setOption方法
    // 重新设置 label的 内容 为 当前鼠标选中的 饼图元素对象 
    charts.value.setOption({
        title: { // 设置标题
            text: params.name, // 设置主标题
            subtext: params.value // 设置副标题
        }
    })

}
</script>

<style scoped lang="scss">
.top {
    display: flex;
    justify-content: space-between;
    color: yellowgreen;
    align-items: center;
}

.el-dropdown-link {
    cursor: pointer;
    color: yellowgreen;
    display: flex;
    align-items: center;
}

::v-deep(.el-dropdown) {
    color: yellowgreen;
}

.leida {
    height: 300px;
}

</style>

父组件传值

src/views/home/index.vue

vue
<template>
	<div class="home">
		<Top  :data="chartObj"/>
		<Middle  :data="chartObj"/>
		<Bottom />
	</div>
</template>