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.minTimethis.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-pickerv-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-inputdisabledv-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-pickerv-model="form.startTime"type="datetime"placeholder="选择日期"value-format="yyyy-MM-dd HH:mm"></el-date-picker>-<el-date-pickerv-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.usernameListthis.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>
注意数据结构