Files
im-uniapp/pages/workbench/reportSchedule/gantt.vue
2025-07-16 11:25:16 +08:00

250 lines
6.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="gantt-container">
<scroll-view scroll-x class="gantt-scroll-x">
<view class="gantt-table">
<!-- 时间轴头部 -->
<view class="gantt-header-row">
<view class="gantt-header-cell gantt-project-col">项目</view>
<view class="gantt-header-cell gantt-date-col" v-for="d in dateList" :key="d">{{ d.slice(5) }}</view>
</view>
<!-- 任务条 -->
<view v-for="(task, idx) in tasks" :key="idx" class="gantt-row">
<view class="gantt-project-col gantt-task-label">{{ task.projectName }}</view>
<view class="gantt-bar-area" :style="{ minWidth: (dateList.length * cellWidth) + 'rpx' }">
<view
class="gantt-bar"
:style="getBarStyle(task)"
@click="showDetail(task)"
>
<text class="gantt-bar-text">{{ task.scheduleName }}{{ task.headerName }}</text>
</view>
</view>
</view>
</view>
</scroll-view>
<!-- 详情弹窗 -->
<uni-popup ref="detailPopup" type="center" :mask-click="true">
<view class="detail-popup-content" v-if="currentTask">
<view class="detail-title">排产详情</view>
<view class="detail-row"><text class="detail-label">项目</text>{{ currentTask.projectName }}</view>
<view class="detail-row"><text class="detail-label">排产名称</text>{{ currentTask.scheduleName }}</view>
<view class="detail-row"><text class="detail-label">负责人</text>{{ currentTask.headerName }}</view>
<view class="detail-row"><text class="detail-label">开始日期</text>{{ currentTask.startDate }}</view>
<view class="detail-row"><text class="detail-label">结束日期</text>{{ currentTask.endDate }}</view>
<view class="detail-row"><text class="detail-label">备注</text>{{ currentTask.remark || '-' }}</view>
<view class="detail-btns">
<button class="detail-btn" @click="closeDetail">关闭</button>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import uniPopup from '@/uni_modules/uni-popup/components/uni-popup/uni-popup.vue'
function dateDiffInDays(a, b) {
return Math.floor((new Date(a) - new Date(b)) / (1000 * 60 * 60 * 24));
}
export default {
components: { uniPopup },
props: {
chartData: {
type: Object,
required: true
}
},
data() {
return {
currentTask: null,
cellWidth: 80 // 每个日期格子的宽度,单位 rpx
}
},
computed: {
tasks() {
if (!this.chartData || !this.chartData.series || !this.chartData.series[0] || !this.chartData.series[0].data) return []
return this.chartData.series[0].data
},
minDate() {
if (!this.tasks.length) return null
const min = this.tasks.reduce((min, t) => t.startDate < min ? t.startDate : min, this.tasks[0].startDate)
const d = new Date(min)
d.setDate(d.getDate() - 2)
return d.toISOString().slice(0, 10)
},
maxDate() {
if (!this.tasks.length) return null
const max = this.tasks.reduce((max, t) => t.endDate > max ? t.endDate : max, this.tasks[0].endDate)
const d = new Date(max)
d.setDate(d.getDate() + 2)
return d.toISOString().slice(0, 10)
},
dateList() {
if (!this.minDate || !this.maxDate) return []
const res = []
let cur = new Date(this.minDate)
const end = new Date(this.maxDate)
while (cur <= end) {
res.push(cur.toISOString().slice(0, 10))
cur.setDate(cur.getDate() + 1)
}
return res
}
},
methods: {
getBarStyle(task) {
if (!this.minDate || !this.maxDate) return {}
const startOffset = dateDiffInDays(task.startDate, this.minDate)
const duration = dateDiffInDays(task.endDate, task.startDate) + 1
const left = startOffset * this.cellWidth + 'rpx'
const width = duration * this.cellWidth + 'rpx'
return {
left,
width,
position: 'absolute',
height: '32rpx',
background: '#2979ff',
borderRadius: '8rpx',
color: '#fff',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '24rpx',
padding: '0 12rpx',
boxSizing: 'border-box',
minWidth: '60rpx',
zIndex: 2
}
},
showDetail(task) {
this.currentTask = task
this.$refs.detailPopup.open('center')
},
closeDetail() {
this.$refs.detailPopup.close()
this.currentTask = null
}
}
}
</script>
<style scoped>
.gantt-container {
padding: 24rpx;
min-height: 100vh;
}
.gantt-scroll-x {
width: 100%;
overflow-x: auto;
}
.gantt-table {
min-width: 800rpx;
}
.gantt-header-row {
display: flex;
align-items: center;
border-radius: 8rpx 8rpx 0 0;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.03);
margin-bottom: 8rpx;
}
.gantt-header-cell {
text-align: center;
font-size: 24rpx;
color: #888;
padding: 0 8rpx;
flex-shrink: 0;
}
.gantt-project-col {
width: 200rpx;
min-width: 200rpx;
text-align: right;
font-weight: bold;
color: #2979ff;
background: #f0f7ff;
border-right: 1rpx solid #e0e0e0;
padding-right: 12rpx;
}
.gantt-date-col {
width: 80rpx;
min-width: 80rpx;
}
.gantt-row {
display: flex;
align-items: center;
min-height: 64rpx;
position: relative;
}
.gantt-task-label {
font-size: 24rpx;
color: #2979ff;
padding-right: 12rpx;
}
.gantt-bar-area {
flex: 1;
position: relative;
height: 64rpx;
background: transparent;
/* min-width 由内联 style 动态设置 */
}
.gantt-bar {
position: absolute;
top: 16rpx;
height: 40rpx;
background: #2979ff;
border-radius: 8rpx;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
padding: 0 12rpx;
min-width: 60rpx;
z-index: 2;
box-shadow: 0 2rpx 8rpx rgba(41,121,255,0.08);
cursor: pointer;
}
.gantt-bar-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* 详情弹窗样式 */
.detail-popup-content {
background: #fff;
border-radius: 16rpx;
padding: 40rpx 32rpx 24rpx 32rpx;
min-width: 480rpx;
max-width: 90vw;
}
.detail-title {
font-size: 32rpx;
font-weight: bold;
color: #2979ff;
margin-bottom: 32rpx;
text-align: center;
}
.detail-row {
font-size: 28rpx;
color: #333;
margin-bottom: 18rpx;
display: flex;
align-items: flex-start;
}
.detail-label {
color: #888;
min-width: 120rpx;
display: inline-block;
}
.detail-btns {
margin-top: 32rpx;
text-align: center;
}
.detail-btn {
background: #2979ff;
color: #fff;
border: none;
border-radius: 8rpx;
padding: 16rpx 48rpx;
font-size: 28rpx;
}
</style>