135 lines
4.3 KiB
Vue
135 lines
4.3 KiB
Vue
|
|
<template>
|
|||
|
|
<div class="gantt-chart-wrapper">
|
|||
|
|
<div ref="ganttContainer" class="gantt-container"></div>
|
|||
|
|
<div class="gantt-info">
|
|||
|
|
<el-card class="task-table-card" shadow="never" style="margin-bottom: 16px;">
|
|||
|
|
<div slot="header">生产任务列表</div>
|
|||
|
|
<el-table :data="tasks" size="mini" stripe>
|
|||
|
|
<el-table-column prop="remark" label="任务名称" min-width="120" />
|
|||
|
|
<el-table-column prop="productId" label="产品ID" width="100" />
|
|||
|
|
<el-table-column prop="startDate" label="开始时间" width="140">
|
|||
|
|
<template slot-scope="scope">{{ formatDate(scope.row.startDate) }}</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column prop="endDate" label="结束时间" width="140">
|
|||
|
|
<template slot-scope="scope">{{ formatDate(scope.row.endDate) }}</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column prop="quantity" label="数量" width="80" />
|
|||
|
|
<el-table-column prop="orderId" label="订单编号" width="120" />
|
|||
|
|
</el-table>
|
|||
|
|
</el-card>
|
|||
|
|
<el-card class="order-table-card" shadow="never">
|
|||
|
|
<div slot="header">相关订单信息</div>
|
|||
|
|
<el-table :data="orders" size="mini" stripe>
|
|||
|
|
<el-table-column prop="orderCode" label="订单编号" width="120" />
|
|||
|
|
<el-table-column prop="salesManager" label="负责人" width="120" />
|
|||
|
|
<el-table-column prop="customerName" label="客户" width="120" />
|
|||
|
|
<el-table-column prop="orderStatus" label="状态" width="100" />
|
|||
|
|
</el-table>
|
|||
|
|
</el-card>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
import Gantt from 'frappe-gantt';
|
|||
|
|
const colorClasses = [
|
|||
|
|
'gantt-color-1', 'gantt-color-2', 'gantt-color-3', 'gantt-color-4', 'gantt-color-5',
|
|||
|
|
'gantt-color-6', 'gantt-color-7', 'gantt-color-8', 'gantt-color-9', 'gantt-color-10'
|
|||
|
|
];
|
|||
|
|
function getColorClass(lineId, orderId, idx) {
|
|||
|
|
// 先按产线分色,同产线下不同订单再细分
|
|||
|
|
if (!lineId) return colorClasses[idx % colorClasses.length];
|
|||
|
|
const base = Math.abs(Number(lineId)) % colorClasses.length;
|
|||
|
|
if (!orderId) return colorClasses[base];
|
|||
|
|
return colorClasses[(base + Math.abs(Number(orderId)) % colorClasses.length) % colorClasses.length];
|
|||
|
|
}
|
|||
|
|
export default {
|
|||
|
|
name: 'GanttChart',
|
|||
|
|
props: {
|
|||
|
|
tasks: {
|
|||
|
|
type: Array,
|
|||
|
|
default: () => []
|
|||
|
|
},
|
|||
|
|
orders: {
|
|||
|
|
type: Array,
|
|||
|
|
default: () => []
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
watch: {
|
|||
|
|
tasks: {
|
|||
|
|
handler() {
|
|||
|
|
this.renderGantt();
|
|||
|
|
},
|
|||
|
|
deep: true
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
mounted() {
|
|||
|
|
this.renderGantt();
|
|||
|
|
},
|
|||
|
|
methods: {
|
|||
|
|
renderGantt() {
|
|||
|
|
if (!this.$refs.ganttContainer) return;
|
|||
|
|
this.$refs.ganttContainer.innerHTML = '';
|
|||
|
|
if (!this.tasks || this.tasks.length === 0) return;
|
|||
|
|
const data = this.tasks.map((item, idx) => ({
|
|||
|
|
id: String(item.detailId || idx),
|
|||
|
|
name: item.remark || `任务${idx+1}`,
|
|||
|
|
start: item.startDate ? this.formatDate(item.startDate) : '',
|
|||
|
|
end: item.endDate ? this.formatDate(item.endDate) : '',
|
|||
|
|
progress: 100,
|
|||
|
|
custom_class: getColorClass(item.lineId, item.orderId, idx),
|
|||
|
|
}));
|
|||
|
|
// 调试:打印data,确认custom_class
|
|||
|
|
console.log('Gantt data:', data);
|
|||
|
|
new Gantt(this.$refs.ganttContainer, data, {
|
|||
|
|
view_mode: 'Month',
|
|||
|
|
language: 'zh',
|
|||
|
|
custom_popup_html: null
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
formatDate(val) {
|
|||
|
|
if (!val) return '';
|
|||
|
|
const d = typeof val === 'string' ? new Date(val) : val;
|
|||
|
|
return d.toISOString().slice(0, 10);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.gantt-chart-wrapper {
|
|||
|
|
width: 100%;
|
|||
|
|
min-height: 400px;
|
|||
|
|
}
|
|||
|
|
.gantt-container {
|
|||
|
|
width: 100%;
|
|||
|
|
min-height: 220px;
|
|||
|
|
height: auto;
|
|||
|
|
overflow: visible;
|
|||
|
|
position: relative;
|
|||
|
|
}
|
|||
|
|
.gantt-container svg {
|
|||
|
|
display: block;
|
|||
|
|
width: 100% !important;
|
|||
|
|
height: auto !important;
|
|||
|
|
}
|
|||
|
|
.gantt-info {
|
|||
|
|
margin-top: 8px;
|
|||
|
|
}
|
|||
|
|
.order-info {
|
|||
|
|
margin-bottom: 8px;
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
|
|||
|
|
<style>
|
|||
|
|
.bar.gantt-color-1 { fill: #409EFF !important; }
|
|||
|
|
.bar.gantt-color-2 { fill: #67C23A !important; }
|
|||
|
|
.bar.gantt-color-3 { fill: #E6A23C !important; }
|
|||
|
|
.bar.gantt-color-4 { fill: #F56C6C !important; }
|
|||
|
|
.bar.gantt-color-5 { fill: #909399 !important; }
|
|||
|
|
.bar.gantt-color-6 { fill: #13C2C2 !important; }
|
|||
|
|
.bar.gantt-color-7 { fill: #B37FEB !important; }
|
|||
|
|
.bar.gantt-color-8 { fill: #FF85C0 !important; }
|
|||
|
|
.bar.gantt-color-9 { fill: #36CBCB !important; }
|
|||
|
|
.bar.gantt-color-10 { fill: #FFC53D !important; }
|
|||
|
|
</style>
|