admin-vue3/src/views/mes/pro/task/components/GanttChart.vue

274 lines
6.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!-- dhtmlx-gantt Vue 3 封装 -->
<!-- TODO @芋艿更深度的注释 -->
<template>
<div ref="ganttContainer" :style="{ width: '100%', height: height + 'px' }"></div>
</template>
<script setup lang="ts">
import { gantt } from 'dhtmlx-gantt'
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
// TODO @AI更全的注释
defineOptions({ name: 'GanttChart' })
const props = withDefaults(
defineProps<{
tasks: any[] // 甘特图任务数据
readonly?: boolean // 是否只读
height?: number // 甘特图高度
}>(),
{
tasks: () => [],
readonly: false,
height: 350
}
)
const emit = defineEmits<{
'task-update': [task: any]
'task-click': [id: string | number]
}>()
const ganttContainer = ref<HTMLElement>()
const ganttInited = ref(false)
/** 初始化甘特图配置 */
const initGantt = () => {
if (!ganttContainer.value) return
// 中文 locale
gantt.locale = {
date: {
month_full: [
'一月',
'二月',
'三月',
'四月',
'五月',
'六月',
'七月',
'八月',
'九月',
'十月',
'十一月',
'十二月'
],
month_short: [
'1月',
'2月',
'3月',
'4月',
'5月',
'6月',
'7月',
'8月',
'9月',
'10月',
'11月',
'12月'
],
day_full: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
day_short: ['日', '一', '二', '三', '四', '五', '六']
},
// TODO @AI这里有 linter 报错;
labels: {
new_task: '新任务',
icon_save: '保存',
icon_cancel: '取消',
icon_details: '详情',
icon_edit: '编辑',
icon_delete: '删除',
confirm_closing: '',
confirm_deleting: '确认删除?',
section_description: '描述',
section_time: '时间',
section_type: '类型',
column_wbs: 'WBS',
column_text: '任务名称',
column_start_date: '开始时间',
column_duration: '时长',
column_add: '',
link: '关联',
confirm_link_deleting: '将被删除',
link_start: '(开始)',
link_end: '(结束)',
type_task: '任务',
type_project: '项目',
type_milestone: '里程碑',
minutes: '分钟',
hours: '小时',
days: '天',
weeks: '周',
months: '月',
years: '年'
}
}
// 基础配置
gantt.config.date_format = '%Y-%m-%d %H:%i:%s'
gantt.config.duration_unit = 'hour'
gantt.config.duration_step = 8 // 1工作日 = 8小时
gantt.config.row_height = 36
gantt.config.bar_height = 24
gantt.config.fit_tasks = true
gantt.config.auto_scheduling = false
gantt.config.drag_links = false
gantt.config.details_on_create = false
gantt.config.details_on_dblclick = false
gantt.config.show_progress = true
// 只读模式
if (props.readonly) {
gantt.config.drag_move = false
gantt.config.drag_resize = false
gantt.config.drag_progress = false
} else {
gantt.config.drag_move = true
gantt.config.drag_resize = true
gantt.config.drag_progress = false
}
// 时间刻度:周 > 日
gantt.config.scales = [
{ unit: 'week', step: 1, format: '%Y年 第%W周' },
{ unit: 'day', step: 1, format: '%m/%d %D' }
]
// 左侧列配置
gantt.config.columns = [
{ name: 'text', label: '任务名称', tree: true, width: 200, resize: true },
{ name: 'start_date', label: '开始时间', align: 'center', width: 100 },
{ name: 'duration', label: '时长(天)', align: 'center', width: 60 }
]
// 今天标记线
gantt.plugins({ marker: true })
gantt.addMarker({
start_date: new Date(),
css: 'today',
text: '今天'
})
// 任务颜色模板
gantt.templates.task_class = (_start: any, _end: any, task: any) => {
if (task.type === gantt.config.types.project) return 'gantt-project-bar'
return ''
}
gantt.templates.task_cell_class = () => ''
gantt.templates.task_row_class = () => ''
// 事件监听
if (!props.readonly) {
gantt.attachEvent('onAfterTaskDrag', (id: string | number) => {
const task = gantt.getTask(id)
emit('task-update', {
id: task.taskId || id,
startTime: task.start_date,
endTime: task.end_date,
// TODO @AI这里有linter报错
duration: task.duration / 8 // 转回工作日
})
})
}
gantt.attachEvent('onTaskClick', (id: string | number) => {
emit('task-click', id)
return true
})
gantt.init(ganttContainer.value)
ganttInited.value = true
}
/** 转换数据为甘特图格式 */
const convertToGanttData = (tasks: any[]) => {
const data: any[] = []
// 按工单分组
const workOrderMap = new Map<number, any[]>()
tasks.forEach((task) => {
const woId = task.workOrderId
if (!workOrderMap.has(woId)) {
workOrderMap.set(woId, [])
}
workOrderMap.get(woId)!.push(task)
})
workOrderMap.forEach((woTasks, woId) => {
const firstTask = woTasks[0]
// 工单作为 project 行
data.push({
id: 'wo_' + woId,
text: firstTask.workOrderCode + ' ' + firstTask.workOrderName,
type: gantt.config.types.project,
open: true
})
// 任务行
woTasks.forEach((t) => {
data.push({
id: 'task_' + t.id,
taskId: t.id,
text: t.name,
start_date: t.startTime ? new Date(t.startTime) : new Date(),
duration: (t.duration || 1) * 8, // 工作日→小时
parent: 'wo_' + woId,
progress: t.quantity > 0 ? t.producedQuantity / t.quantity : 0,
color: t.colorCode || '#00AEF3'
})
})
})
return { data, links: [] }
}
/** 加载数据到甘特图 */
const loadData = (tasks: any[]) => {
if (!ganttInited.value) return
gantt.clearAll()
const ganttData = convertToGanttData(tasks)
gantt.parse(ganttData)
}
// 监听 tasks 变化
watch(
() => props.tasks,
(val) => {
if (val && ganttInited.value) {
loadData(val)
}
},
{ deep: true }
)
onMounted(() => {
initGantt()
if (props.tasks?.length) {
loadData(props.tasks)
}
})
onBeforeUnmount(() => {
if (ganttInited.value) {
gantt.clearAll()
}
})
defineExpose({ loadData })
</script>
<style>
/* 今天标记线 */
.gantt_marker.today {
background-color: #ff4444;
opacity: 0.4;
}
.gantt_marker.today .gantt_marker_content {
color: #ff4444;
font-size: 12px;
}
/* 工单project行样式 */
.gantt-project-bar .gantt_task_progress {
background: #7b68ee;
}
</style>