vue2.X 中使用 echarts5.4.0实现项目进度甘特图
效果图:
左侧
都是名称
,上面
是时间
,当中的内容是日志内容
- 组件:
gantt.vue
<template>
<div id="main" style="width: 100%; height: 100%"></div>
</template>
<script>
import * as echarts from "echarts";
export default {
name: "Gantt",
props: {
baseDate: {
type: String,
default: "",
},
ganttData: {
type: Array,
default: () => [],
},
roomData: {
type: Array,
default: () => [],
},
},
data() {
return {
minHours: '08:00',
maxHours: '24:00',
// colors: ['#5c2223','#346c9c', '#525288', '#87723e', '#d1c2d3','#f07c82', '#835e1d', '#d99156', '#954416', '#ee8055', '#126e82', '#61649f', '#a7a8bd']
};
},
created() {},
mounted() {
this.myEcharts();
},
watch: {
ganttData(newVal) {
this.myEcharts();
},
},
methods: {
getTimes() {
let baseDate = `${this.baseDate}`;
// 获取日志的最早和最晚时间,这样防止两边出现空白,比如00:00-09:00;
this.$http.post(`/sys/task/times`, {taskDate: baseDate}).then(({ data: res }) => {
if (res.code !== 0) {
return this.$message.error(res.msg)
}
this.minHours = res.data.minTime
this.maxHours = res.data.maxTime
}).catch(() => {})
},
myEcharts() {
this.getTimes();
// 基于准备好的dom,初始化echarts实例
const container = document.getElementById("main");
this.$echarts.init(container).dispose();
var myChart = this.$echarts.init(container);
// 用于随机颜色
var colors= ['#8dddfa','#f98e72', '#f7b84f', '#a872f9', '#d6a9d1','#a7e56d', '#ff73c7', '#d6a9d1', '#b1e7fb', '#d3b3af', '#2859b1', '#1f6cb0']
//let min = `${this.$moment().format("YYYY-MM-DD")} 00:00:00`;
//let max1 = `${this.$moment().add(1, "day").format("YYYY-MM-DD")} 00:00:00`;
// 指定图表的配置项和数据
var option = {
color: "#0A8BFF",
backgroundColor: "#fff",
title: {},
tooltip: {
// enterable: true,
trigger: "item",
show: true,
// axisPointer: { // 坐标轴指示器,坐标轴触发有效
// type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
// },
// alwaysShowContent: true,
hideDelay: 100,
backgroundColor: "rgba(255,255,255,1)", // 背景颜色(此时为默认色)
borderRadius: 5, // 边框圆角
confine: true,
textStyle: {
color: "#000"
},
// 悬浮的时候展示对应的数据
formatter: function (params) {
for(var i = 0; i < params.data.value.length; i++) {
var content = (
params.data.value[i].content +
// "<br/>" +
// (params.data.value[i].status === "1" ? '<span style="color:#4dc394;">已完成</span>' : '<span style="color:#e5835b;">进行中</span>') +
"<br/>" +
params.data.value[i].stime +
" - " +
params.data.value[i].etime
);
// 作用:鼠标悬浮在内容,出现弹窗显示内容详情,这里限制了宽,以防止宽度过长
var tooltipHtml = '<div style="width:fit-content; max-width: 500px; white-space: wrap;">'+content+'</div>';
return tooltipHtml;
//return content;
}
},
},
legend: {
// left: '90px',
top: "1%",
itemWidth: 16,
itemHeight: 16,
show: true,
// selectedMode: false, // 图例设为不可点击
textStyle: {
color: "rgba(0, 0, 0, 0.45)",
fontSize: 14,
},
},
grid: {
// 绘图网格
left: "2%",
right: "3%",
top: "10%",
bottom: "10%",
containLabel: true,
},
xAxis: {
type: "time",
position: "top",
// interval: 3600 * 1000, // 以一个小时递增
// 以一小时的时间递增
minInterval: 3600 * 1000 ,
maxInterval: 3600 * 1000 ,
min: `${this.baseDate} ` + this.minHours,
max: `${this.baseDate} ` + this.maxHours,
//max:`${this.baseDate} 24:00`,
//max: `${this.baseDate} 19:00`, // 设置最大时间为18点
//min:`${this.baseDate} 00:00`, //将data里最小时间的整点时间设为min,否则min会以data里面的min为开始进行整点递增
//min: `${this.baseDate} 08:00`, // 将data里最小时间的整点时间设为min,否则min会以data里面的min为开始进行整点递增
axisLabel: {
// 设置最后一个数据显示
showMaxLabel: true,
formatter: function (value, index) {
var data = new Date(value);
var hours = data.getHours();
var minutes = data.getMinutes();
if ((index !== 0 && hours === 0) || index === 25){
return "23:59"
} else {
//return hours + ":00";
if (minutes === 0) {
return hours + ":00";
} else {
return hours + ":" + minutes;
}
}
},
textStyle: {
color: "rgba(0,0,0,0.65)", // 更改坐标轴文字颜色
fontSize: 14, // 更改坐标轴文字大小
},
},
axisLine: {
lineStyle: {
color: "#e5e5e5",
},
onZero: false,
},
splitLine: {
show: true,
lineStyle: {
type: "dashed",
},
},
},
// dataZoom: [
// // 给x轴设置滚动条
// {
// type: 'slider',
// show: true,
// yAxisIndex: [0, 1],
// left: '96%',
// start: 1,
// end: 100,
// fiterMode: 'filter'
// },
// {
// type: 'inside',
// fiterMode: 'filter',
// yAxisIndex: [0, 1],
// start: 1,
// end: 100
// }
// ],
yAxis: {
inverse: true, // 是否反转
type: "category",
axisTick:{
show: true //不显示坐标轴刻度线
},
splitLine: { //网格线
"show": true
},
axisLine: {
show: true,
lineStyle: {
color: "#e5e5e5",
},
},
data: this.roomData,
axisLabel: {
textStyle: {
color: "rgba(0, 0, 0, 0.65)", // 刻度颜色
fontSize: 14, // 刻度大小
},
},
},
series: [
{
type: "custom",
clickable: false,
renderItem: function (params, api) {
// 开发者自定义的图形元素渲染逻辑,是通过书写 renderItem 函数实现的
var categoryIndex = api.value(0).index; // 这里使用 api.value(0) 取出当前 dataItem 中第一个维度的数值。
var start = api.coord([api.value(0).startTime, categoryIndex]); // 这里使用 api.coord(...) 将数值在当前坐标系中转换成为屏幕上的点的像素值。
var end = api.coord([api.value(0).endTime, categoryIndex]);
var height = 26;
return {
type: "rect", // 表示这个图形元素是矩形。还可以是 'rect', 'circle', 'sector', 'polygon' 等等。
shape: echarts.graphic.clipRectByRect(
{
// 矩形的位置和大小。
x: start[0],
y: start[1] - height / 2,
width: end[0] - start[0],
height: 27,
},
{
// 当前坐标系的包围盒。
x: params.coordSys.x,
y: params.coordSys.y,
width: params.coordSys.width,
height: params.coordSys.height,
}
),
style: api.style(),
};
},
label: {
normal: {
show: true,
position: "insideBottom",
formatter: function (params) {
//return params.value[0].content;
let value = params.value[0].content;
if (!value) return "";
if (value.length > 6) {
return value.slice(0, 6) + "...";
}
return value;
},
textStyle: {
align: "center",
fontSize: 14,
fontWeight: "400",
lineHeight: "20",
},
},
},
encode: {
x: 1, // data 中『维度1』对应到 X 轴
y: 0, // 把"维度0"映射到 Y 轴。
},
itemStyle: {
normal: {
color: function (params) {
const randomIndex = Math.floor(Math.random() * colors.length);
return colors[randomIndex];
},
},
},
// dataZoom: [
// {
// show: true,
// realtime: true,
// start: 0,
// end: 50
// }
// ],
data: this.ganttData,
},
],
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
window.onresize = function () {
myChart.resize();
};
// myChart.getZr().on("mousemove", (param) => {
// var pointInPixel = [param.offsetX, param.offsetY];
// if (myChart.containPixel("grid", pointInPixel)) {
// // 若鼠标滑过区域位置在当前图表范围内 鼠标设置为小手
// // myChart.getZr().setBackgroundColor('red')
// myChart.getZr().setCursorStyle("pointer");
// } else {
// myChart.getZr().setCursorStyle("default");
// }
// });
// 任意位置点击事件----注册双击
// myChart.getZr().on("click", (params) => {
// if (!params.target) {
// // 点击在了空白处,做些什么。
// const point = [params.offsetX, params.offsetY];
// if (myChart.containPixel("grid", point)) {
// // 获取被点击的点在y轴上的索引
// const idxArr = myChart.convertFromPixel({ seriesIndex: 0 }, point);
// const xValue = new Date(+idxArr[0]).getHours();
// const yValue = idxArr[1];
// const sendData = [xValue, yValue];
// this.$emit("getInfoCallback", sendData);
// }
// }
// });
// // 图例点击事件-返回数据给父组件---单击事件
// myChart.on("click", (params) => {
// this.$emit("getInfoCallback", params.data.value);
// });
},
},
computed: {},
};
</script>
<style scoped lang="less">
</style>
js文件
import Gantt from './src/gantt'
Gantt.install = function (Vue) {
Vue.component(Gantt.name, Gantt)
}
export default Gantt
- main.js中组件引用组件
- log页面
<template>
<div class="appointment">
<div class="a-gantt">
<el-row style="padding: 12px 10px; background-color: #fff">
<el-col :span="12" align="left" style="font-weight: 700">
日志
</el-col>
<el-col :span="12" align="right">
<el-date-picker
v-model="baseDate"
type="date"
@change="handleSelect"
placeholder="选择日期"
value-format="yyyy-MM-dd"
>
</el-date-picker>
</el-col>
</el-row>
</div>
<div class="f-gantt">
<Gantt
:baseDate="baseDate"
ref="gantt"
:ganttData="ganttData"
@getInfoCallback="getGanttInfo"
:roomData="roomData"
></Gantt>
</div>
<!-- 新增编辑框 -->
<!-- <el-dialog :title="formTitle" :visible.sync="dialogVisible" width="30%">
<el-form :model="form" :label-width="formLabelWidth">
<el-form-item label="会议室">
<el-input
disabled
v-model="usernameData[form.index]"
autocomplete="off"
></el-input>
</el-form-item>
<el-form-item label="内容">
<el-input v-model="form.content" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="时间" :label-width="formLabelWidth">
<el-date-picker
v-model="form.startTime"
type="datetime"
placeholder="选择日期"
value-format="yyyy-MM-dd HH:mm"
>
</el-date-picker>
-
<el-date-picker
v-model="form.endTime"
type="datetime"
placeholder="选择日期"
value-format="yyyy-MM-dd HH:mm"
>
</el-date-picker>
</el-form-item>
<el-form-item label="状态" :label-width="formLabelWidth">
<el-select v-model="form.status" style="width: 100%">
<el-option label="进行中" value="0"></el-option>
<el-option label="已完成" value="1"></el-option>
</el-select>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="saveData">确 定</el-button>
</span>
</el-dialog> -->
</div>
</template>
<script>
export default {
data() {
return {
baseDate: `${new Date().getFullYear()}-${
new Date().getMonth() + 1
}-${new Date().getDate()}`,
roomData: [],
ganttData: [],
dialogVisible: false,
formLabelWidth: "120px",
formTitle: "",
form: {
id: "",
index: "",
content: "",
endTime: "",
status: "",
startTime: "",
},
};
},
created() {
this.getDataList()
// this.ganttData = [
// {
// value: [
// {
// index: 1,
// roomName: "会议室二",
// RoomId: "123",
// id: "333",
// startTime: '2023-09-05 08:28', //`${this.baseDate} 8:28`,
// endTime: '2023-09-05 09:28', // `${this.baseDate} 9:28`,
// status: "1",
// content: "睡觉",
// }
// ],
// },
// {
// value: [
// {
// index: 1,
// roomName: "会议室二",
// RoomId: "123",
// id: "333",
// startTime: '2023-09-05 12:28', //`${this.baseDate} 8:28`,
// endTime: '2023-09-05 15:28', // `${this.baseDate} 9:28`,
// status: "1",
// content: "工作",
// },
// ]
// },
// {
// value: [
// {
// index: 0,
// roomName: "会议室一",
// RoomId: "2234",
// id: "444",
// startTime: `2023-09-05 10:28`,
// endTime: `2023-09-05 12:28`,
// status: "0",
// content: "吃饭",
// },
// ],
// },
// {
// value: [
// {
// index: 0,
// roomName: "会议室一",
// RoomId: "123",
// id: "333",
// startTime: '2023-09-05 13:28', //`${this.baseDate} 8:28`,
// endTime: '2023-09-05 15:28', // `${this.baseDate} 9:28`,
// status: "1",
// content: "工作111",
// },
// ]
// }
// ];
//this.roomData = ['会议室一', '会议室二', '会议室三', '会议室四'];
},
mounted() {
this.getDataList();
},
methods: {
handleSelect() {
this.getDataList()
this.$refs.gantt.myEcharts();
},
getDataList() {
this.$http.post(`/sys/task/ganteLog`, {taskDate: this.baseDate}).then(({ data: res }) => {
if (res.code !== 0) {
return this.$message.error(res.msg)
}
this.roomData = res.data.usernameList
this.ganttData = res.data.data
}).catch(() => {})
},
getGanttInfo(data) {
this.dialogVisible = true;
// 根据data的长度判断是新增还是编辑
// 新增
if (data.length === 2) {
this.formTitle = "新增";
this.form = this.$options.data().form;
this.$set(this.form, "index", data[1]);
} else {
this.formTitle = "修改";
this.form = data[0];
}
},
saveData() {
if (this.formTitle === "修改") {
this.ganttData = this.ganttData.filter((item) => {
return item.value[0].id !== this.form.id;
});
} else {
this.$set(this.form, "status", "0");
}
const obj = Object.assign({}, this.form);
this.ganttData.push({ value: [obj] });
this.$refs.gantt.myEcharts();
this.dialogVisible = false;
},
},
};
</script>
<style scoped>
/* .a-gantt {
position: absolute;
top: 0;
height: 60px;
width: 100%;
box-sizing: border-box;
text-align: center;
} */
/* .appointment {
position: relative;
height: 100%;
overflow-y: hidden;
border: 1px solid #ddd;
color: #0f1419;
box-sizing: border-box;
} */
.f-gantt {
position: absolute;
bottom: 10px;
top: 80px;
width: 100%;
/*height: 600px;*/
box-sizing: border-box;
}
</style>
注意数据结构