首页
mock搭建
1 新建数据json文件
src/mock/data.json
json文件默认就是对外暴漏
2 新建mockjs
src/mock/index.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
import './mock'
4 二次封装axios
src/utils/requestMock.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
// 导入requestMock对象
import requestMock from '@/utils/requestMock';
//首页数据报表接口地址
export const reqCharts = () => requestMock.get("/home");
6 在首页组件中发送请求获取数据
src/views/home/index.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/index.vue
<template>
<div>top</div>
</template>
<script setup lang='ts'>
</script>
<style scoped lang="less">
</style>
中间组件
src/views/home/chartMiddle/index.vue
<template>
<div>mid</div>
</template>
<script setup lang='ts'>
</script>
<style scoped lang="less">
</style>
底部组件
src/views/home/chartBottom/index.vue
<template>
<div>bottom</div>
</template>
<script setup lang='ts'>
</script>
<style scoped lang="less">
</style>
2 首页组件中引入
src/views/home/index.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
<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
# 引用四次子组件
# 让子组件在父组件中横向排列
# 方式一:使用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>
<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
# 组件挂载完成后,发请求获取数据(在生命周期函数内,不要写await和async,定义在外边然后调用)
# 存储数据
const chartObj = ref({})
# 把数据传到仓库,或者使用prop
<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
# 子组件设置插槽占个坑位
父组件 src/views/home/chartTop/index.vue
# 第四个卡片组件
# 在子组件对应插槽处填充内容,并传参
# 在子组件charts插槽处填充一个进度条组件
# 定义一个进度条组件 src/views/home/chartTop/progress/index.vue
# 使用vue-echarts组件作为进度条
# 设置x轴
# x轴隐藏
# x轴设置最小值和最大值
# 设置y轴
# y轴隐藏
# 系列
# 设置类型
# 设置数据
# 设置柱状图宽度
# 设置柱条背景颜色
# 设置柱条背景显示
# 设置柱状图的文本标签
# 显示
# 设置文本位置
# 设置标签自定义内容
# 设置文字的颜色
# 布局
上下左右都为0
第一个图表卡片
# 第一个卡片组件
# 在子组件对应插槽处填充内容,并传参
# 设置样式
使用flex居中
设置字体动画 设置动画 关键帧的使用
# 阿里的图标不需要下载到本地,直接使用
<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>
第二个图表卡片
# 第二个卡片组件
# 在子组件对应插槽处填充内容,并传参
# 在子组件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
<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
<!-- 第二个图表 -->
<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>
第三个图表卡片
# 第三个卡片组件
# 在子组件对应插槽处填充内容,并传参
# 在子组件charts插槽处填充一个柱状图组件
# 定义一个柱状图组件 src/views/home/chartTop/bar/index.vue
# 使用vue-echarts插件解决自适应问题
单独封装一个柱状图组件
src/views/home/topChart/bar/index.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
<!-- 第三个图表 -->
<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
<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
<!-- 第四个图表 -->
<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组件
# 使用 element-plus中的Tabs组件搭建静态
# 使用深度选择器调整Tabs组件的样式
::v-deep(){
}
# DatePicker组件(element-plus)
设置组件的中文
设置组件to
# 把时间,标签定位到右侧
设置span的左右宽度,
设置颜色
# 使用栅格系统,把图标和排名左右分开
# 日历组件
日历组件
# 当点击 今天 时 日历组件选择今天
定义一个函数
向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是一样的
npm install moment
引入:import moment from 'moment'
取值:(当前时间)
moment(value).format("YYYY-MM-DD") // 时间格式
dayjs插件
https://dayjs.fenxianglu.cn/category/parse.html#utc
安装:
npm install dayjs
引入:(在组件中)
import dayjs from 'dayjs'
取值(当前时间)
dayjs() // 返回一个对象
dayjs().format('')
// 默认是当地时间
dayjs().format("YYYY-MM-DD") //2019-03-06
中间柱状图
# 在栅格系统的标签内写柱状图
# 使用 v-chart 绘制柱状图
# 柱状图配置项
# 设置标题
标题位置
文字颜色
# 设置x轴
均匀分配
x轴底部文字设置
设置x轴显示内容
# 设置y轴
配置y轴轴线
配置y轴刻度
# 系列
设置类型
设置数据
设置柱条颜色
文本标签的显示
# 布局
上下左右
# 提示框
触发时机:
指示器:设置类型
# 通过响应式数据控制销售额和访问量的切换
# 在标题内容使用三元表达式处理显示内容
# 数据也使用三元表达式处理显示那组数据
排名组件
# 静态搭建 展示排名
使用ul li结构
设置样式
设置flex布局
主轴 一左一右
侧轴 居中
动态设置前三个li标签设置样式
:class={activeName = "index<3"}
.activeName{
}
# 渲染数据
使用v-if设置展示哪一个
中间的组件
src/views/home/chartMid/index.vue
<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
<template>
<div class="home">
<Top :data="chartObj"/>
<Middle :data="chartObj"/>
<Bottom />
</div>
</template>
底部的组件
# 使用栅格系统把卡片设置为左右两个
左侧card组件的设置
使用card组件的header处插槽填充内容
设置card组件header处的内容和样式
左侧使用div包裹文字
右侧使用dropdown下拉菜单组件
设置flex布局,一左一右
使用深度选择器修改字体的颜色
设置左侧card的内容
使用雷达图组件(echart官网获取配置项)
# 右侧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
<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
<template>
<div class="home">
<Top :data="chartObj"/>
<Middle :data="chartObj"/>
<Bottom />
</div>
</template>