当前位置: 首页 > news >正文

甘特图Vue3 | 原生绘制

甘特图

vue3加原生三件套绘制,轻量且易扩展

在这里插入图片描述

源代码甘特图组件

<template><div class="gantt-container"><div class="gantt-table-wrapper"><table class="gantt-table"><thead><tr><th rowspan="2" style="width: 3%;">序号</th><th rowspan="2" style="width: 8%;">编号</th><th rowspan="2" style="width: 5%;">申请单位</th><th rowspan="2" style="width: 10%;">检修主题</th><th rowspan="2" style="width: 6%;">开始时间</th><th rowspan="2" style="width: 6%;">结束时间</th><th rowspan="2" style="width: 4%;">工期(天)</th><th :colspan="daysInMonth" class="month-header">{{ currentYear }}年{{ currentMonth }}月</th></tr><tr><th v-for="day in daysInMonth" :key="day" style="min-width: 20px; white-space: nowrap;">{{ day }}</th></tr></thead><tbody><tr v-for="(task, index) in tableData" :key="task.id" :title="`#工作内容\n${task['工作内容']}`"><td>{{ index + 1 }}</td><td>{{ task.bianhao }}</td><td>{{ task['申请单位'] }}</td><td :title="task.name" class="ellipsis">{{ task.name }}</td><td>{{ formatDate(task.start) }}</td><td>{{ formatDate(task.end) }}</td><td>{{ calculateDuration(task.start, task.end) }}</td><tdv-for="day in daysInMonth":key="day":class="{'highlight': isDateInRange(day, task.start, task.end),'first-day': isFirstDay(day, task.start),'last-day': isLastDay(day, task.end)}":style="{ color: (isFirstDay(day, task.start) || isLastDay(day, task.end)) ? 'white' : '' }">{{isFirstDay(day, task.start) ? formatDateMD(task.start) : (isLastDay(day, task.end) ? formatDateMD(task.end) : '')}}</td></tr></tbody></table></div></div>
</template><script setup>
import {computed, defineProps} from 'vue';
import {dayjs} from 'element-plus'
import * as XLSX from "xlsx-js-style";const props = defineProps({tableData: {type: Array,required: true},yearMonth: {type: String,required: true}
});// 当前月份处理
const currentYear = computed(() => parseInt(props.yearMonth.split('-')[0]));
const currentMonth = computed(() => parseInt(props.yearMonth.split('-')[1]));
const daysInMonth = computed(() => dayjs(`${currentYear.value}-${currentMonth.value}`).daysInMonth());// 日期格式化
const formatDate = (date) => dayjs(date).format('YYYY-MM-DD');
//日前格式化M-D
const formatDateMD = (date) => dayjs(date).format('M-D');
//计算工期,结束时间减去(day)开始时间+1
const calculateDuration = (start, end) => {const startDate = dayjs(start);const endDate = dayjs(end);return endDate.diff(startDate, 'day') + 1;
};// 判断日期是否在范围内
const isDateInRange = (day, start, end) => {const yearMonth = props.yearMonth.split('-');const currentDate = new Date(yearMonth[0], yearMonth[1] - 1, day);const startDate = new Date(start);const endDate = new Date(end);// 设置时间为0时0分0秒currentDate.setHours(0, 0, 0, 0);startDate.setHours(0, 0, 0, 0);endDate.setHours(0, 0, 0, 0);return currentDate >= startDate && currentDate <= endDate;
};// 判断是否是任务的第一天
const isFirstDay = (day, start) => {const startDate = dayjs(start);return startDate.date() === day && startDate.month() + 1 === currentMonth.value;
};// 判断是否是任务的最后一天
const isLastDay = (day, end) => {const endDate = dayjs(end);return endDate.date() === day && endDate.month() + 1 === currentMonth.value;
};
const exportToExcel = () => {const title = `甘特图${currentYear.value}${currentMonth.value}`const ws = XLSX.utils.aoa_to_sheet([[{v: title, t: "s", s: {alignment: {vertical: 'center', horizontal: 'center'}, font: {sz: 16, bold: true}}}],['序号', '编号', '申请单位', '检修主题', '开始时间', '结束时间', '工期(天)', ...Array.from({length: daysInMonth.value}, (_, i) => i + 1)],...props.tableData.map((task, index) => [index + 1,task.bianhao,task['申请单位'],task.name,formatDate(task.start),formatDate(task.end),calculateDuration(task.start, task.end),...Array.from({length: daysInMonth.value}, (_, day) =>isDateInRange(day + 1, task.start, task.end)? {v: isFirstDay(day + 1, task.start) ? formatDateMD(task.start) : (isLastDay(day + 1, task.end) ? formatDateMD(task.end) : ''),t: "s",s: {fill: {fgColor: {rgb: "0A938E"}},font: {color: {rgb: "FFFFFF"}},border: {top: {style: "thin"}, bottom: {style: "thin"}}}} : '')])]);const wb = XLSX.utils.book_new();ws['!cols'] = [{wch: 5}, // 序号{wch: 18}, // 编号{wch: 10}, // 申请单位{wch: 15}, // 检修主题{wch: 12}, // 开始时间{wch: 12}, // 结束时间{wch: 8},  // 工期(天)];ws["!merges"] = [{s: {r: 0, c: 0}, e: {r: 0, c: 6 + daysInMonth.value}},];for (let i = 8; i < 8 + daysInMonth.value; i++) {ws['!cols'].push({wch: 4});}XLSX.utils.book_append_sheet(wb, ws, '甘特图');XLSX.writeFile(wb, `甘特图${props.yearMonth}.xlsx`);
}
//暴露导出excel方法
defineExpose({exportToExcel
})
</script><style scoped>
.gantt-container {width: 100%;overflow-x: auto;
}.gantt-table {width: 100%;border-collapse: collapse;table-layout: fixed;
}.gantt-table th,
.gantt-table td {border: 1px solid #ddd;text-align: center;
}.month-header {background-color: #f2f2f2;font-weight: bold;
}.highlight {background-color: #0A938E;position: relative;padding: 10px 0;
}.highlight.first-day::before {position: absolute;left: 2px;color: white;
}.highlight.last-day::after {position: absolute;right: 2px;color: white;
}tbody tr:nth-child(even) {background-color: #f9f9f9;
}tbody tr:hover {background-color: #f1f1f1;
}.ellipsis {white-space: nowrap;overflow: hidden;text-overflow: ellipsis;
}
</style>

使用甘特图示例,二次封装成弹窗dialog

<template><el-dialogv-model="dialogVisible"title="月度甘特图-流转中"width="90%"draggable><div class="wh-100 container"><div class="query-box"><label style="font-weight: bold">编号</label><el-input v-model="queryParams.bianhao" size="small" clearable style="width: 150px;margin: 0 10px"placeholder="请输入编号"></el-input><label style="font-weight: bold">检修主题</label><el-input v-model="queryParams.topic" size="small" clearable style="width: 150px;margin: 0 10px"placeholder="请输入检修主题"></el-input><label style="font-weight: bold">工作内容</label><el-input v-model="queryParams.content" size="small" clearable style="width: 150px;margin: 0 10px"placeholder="请输入工作内容"></el-input><label style="font-weight: bold">申请单位</label><el-input v-model="queryParams.applyUnit" size="small" clearable style="width: 150px;margin: 0 10px"placeholder="请输入申请单位"></el-input><label style="font-weight: bold">审核处室</label><el-select v-model="queryParams.checkDep" placeholder="请选择" size="small" clearable style="width: 120px;margin: 0 10px"><el-optionv-for="item in dicCheckDepOption":key="item":label="item":value="item"/></el-select><label style="font-weight: bold">年月</label><el-date-pickerv-model="queryParams.month"type="month"@change="getList"value-format="YYYY-MM"style="width: 120px;margin: 0 10px"placeholder="选择年月"/><el-button type="primary" size="small" :icon="Search" @click="getList" :loading="loading">搜索</el-button><el-button type="primary" size="small" :icon="Download" @click="ExportExcelGantt" :loading="loadingDown">导出excel</el-button></div><div class="query-table"><gantt ref="ganttRef" v-loading="loading" :table-data="tableDataComputed" :year-month="queryParams.month"/></div></div></el-dialog>
</template><script setup>
import {computed, ref} from "vue";
import {monthlzz} from "@/api/monthFlow.js";
import gantt from "@/components/Gantt/index.vue"
import {Download, Search} from "@element-plus/icons-vue";
import {dayjs} from 'element-plus'
import {dicCheckDepOption} from "@/utils/dicc.js";
// 弹窗显示控制
const dialogVisible = ref(false)
const ganttRef = ref()const queryParams = ref({bianhao: '',applyUnit: '',checkDep:'',current: 1,size: 100,content: '',month: dayjs().add(1, 'month').format("YYYY-MM"),topic: '',total: 0
})
const loading = ref(false)
const loadingDown = ref(false)
const tableData = ref([])
//查询
const getList = (() => {loading.value = truemonthlzz(queryParams.value).then(res => {if (res.data) {tableData.value = res.data.recordsqueryParams.value.total = res.data.totalloading.value = false}})
})
getList()
// 打开弹窗
const openGanttDialog = () => {dialogVisible.value = truegetList() // 打开时自动查询
}const ExportExcelGantt = () => {loadingDown.value = trueganttRef.value.exportToExcel()loadingDown.value = false
}
const tableDataComputed = computed(() => {return tableData.value.map((item) => {return {id: item['business_id'],start: item['开工时间'],end: item['完工时间'],name: item['检修主题'],bianhao: item['编号'], ...item}})
});// 暴露方法给父组件
defineExpose({openGanttDialog
})
</script><style scoped>
.container {height: 600px;.query-box {display: flex;align-items: center;background-color: white;padding-left: 10px;width: 100%;height: 50px;}.query-table {width: 100%;height: calc(100% - 100px);overflow-y: scroll;}.query-page {display: flex;width: 100%;height: 50px;padding-left: 10px;background-color: #F1F4F6;}
}
</style>

前端导出甘特图为excel基于xlsx-js-style

第一步:安装xlsx-js-style

npm install xlsx-js-style

第二步:绘制表格

xlsx-js-style官网仓库xlsx-js-style
使用文档SheetJS 中文网

源代码

const exportToExcel = () => {const title = `甘特图${currentYear.value}${currentMonth.value}`const ws = XLSX.utils.aoa_to_sheet([[{v: title, t: "s", s: {alignment: {vertical: 'center', horizontal: 'center'}, font: {sz: 16, bold: true}}}],['序号', '编号', '申请单位', '检修主题', '开始时间', '结束时间', '工期(天)', ...Array.from({length: daysInMonth.value}, (_, i) => i + 1)],...props.tableData.map((task, index) => [index + 1,task.bianhao,task['申请单位'],task.name,formatDate(task.start),formatDate(task.end),calculateDuration(task.start, task.end),...Array.from({length: daysInMonth.value}, (_, day) =>isDateInRange(day + 1, task.start, task.end)? {v: isFirstDay(day + 1, task.start) ? formatDateMD(task.start) : (isLastDay(day + 1, task.end) ? formatDateMD(task.end) : ''),t: "s",s: {fill: {fgColor: {rgb: "0A938E"}},font: {color: {rgb: "FFFFFF"}},border: {top: {style: "thin"}, bottom: {style: "thin"}}}} : '')])]);const wb = XLSX.utils.book_new();ws['!cols'] = [{wch: 5}, // 序号{wch: 18}, // 编号{wch: 10}, // 申请单位{wch: 15}, // 检修主题{wch: 12}, // 开始时间{wch: 12}, // 结束时间{wch: 8},  // 工期(天)];ws["!merges"] = [{s: {r: 0, c: 0}, e: {r: 0, c: 6 + daysInMonth.value}},];for (let i = 8; i < 8 + daysInMonth.value; i++) {ws['!cols'].push({wch: 4});}XLSX.utils.book_append_sheet(wb, ws, '甘特图');XLSX.writeFile(wb, `甘特图${props.yearMonth}.xlsx`);
}

相关文章:

  • leetcode 69和367
  • 构造函数体赋值和初始化列表
  • 面试题:在1亿个数据中取前10个最大的数据(Java实现)
  • 【数据结构】Map与Set结构详解
  • 开源交易所源码,交易所开发
  • 时序数据库IoTDB构建的能源电力解决方案
  • 无人设备遥控之调度自动化技术篇
  • 从岗位依附到能力生态:AI革命下“什么叫就业”的重构与价值
  • Python3(8) 字符串
  • 使用HYPRE库并行装配IJ稀疏矩阵指南: 矩阵预分配和重复利用
  • 数据集-目标检测系列- F35 战斗机 检测数据集 F35 plane >> DataBall
  • 数据分析之技术干货业务价值​​ powerquery 分组排序后取TOP
  • Code Splitting 分包策略
  • 【网络原理】从零开始深入理解TCP的各项特性和机制.(一)
  • 立錡科技优化 HDD、LPDDR、SoC 供电的高性能降压转换器
  • Python实现技能记录系统
  • 【华为OD机试真题】428、连续字母长度 | 机试真题+思路参考+代码解析(E卷)(C++)
  • Browser-Use WebUI:让AI自动使用浏览器帮你查询信息执行任务
  • StableDiffusionPipeline原理解读——引导尺度是如何调整噪声残差的
  • 【C语言经典算法实战】:从“移动距离”问题看矩阵坐标计算
  • 贝壳:网传“深圳贝壳内部通知”不实
  • 上海体育消费节将从5月持续至11月,推出运动装备商品促销活动
  • 湖南永州公安全面推行“项目警官制”,为重点项目建设护航
  • 爱奇艺要转型做微剧?龚宇:是误解,微剧是增量业务,要提高投资回报效益
  • 特斯拉一季度净利下滑七成,马斯克表态将继续倡导关税下调
  • 对话地铁读书人|媒体人Echo:读书使人远离“班味”