You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
759 lines
22 KiB
Vue
759 lines
22 KiB
Vue
<template>
|
|
<div class="schedule-preview-wrap">
|
|
<div class="gantt-main-area">
|
|
<div class="schedule-status-legend">
|
|
<div v-for="item in sortedPlanStatusList" :key="item.key" class="legend-item"
|
|
:style="{ borderLeftColor: item.color }">
|
|
<span class="legend-color" :style="{ backgroundColor: item.color }"></span>
|
|
<span class="legend-label">{{ item.label }}</span>
|
|
</div>
|
|
</div>
|
|
<div ref="ganttContainerRef" class="schedule-gantt-container" :style="{ height }"></div>
|
|
</div>
|
|
|
|
<div class="schedule-detail-panel">
|
|
<div class="schedule-detail-title">{{ t('GanttChart.GanttPanel.detailTitle') }}</div>
|
|
<template v-if="activePreviewDevice">
|
|
<el-descriptions :column="1" border size="small">
|
|
<el-descriptions-item :label="t('GanttChart.GanttPanel.deviceNameLabel')">{{ activePreviewDevice.deviceName
|
|
}}</el-descriptions-item>
|
|
<el-descriptions-item :label="t('GanttChart.GanttPanel.deviceIdLabel')">{{ activePreviewDevice.deviceId
|
|
}}</el-descriptions-item>
|
|
<el-descriptions-item :label="t('GanttChart.GanttPanel.capacityLabel')">{{ activePreviewDevice.ratedCapacity
|
|
?? '-' }}</el-descriptions-item>
|
|
<el-descriptions-item :label="t('GanttChart.GanttPanel.planCountLabel')">{{ activePreviewDevice.plans?.length
|
|
?? 0 }}</el-descriptions-item>
|
|
</el-descriptions>
|
|
<div class="schedule-plan-list-title">{{ t('GanttChart.GanttPanel.planDetailTitle') }}</div>
|
|
<div class="schedule-plan-list">
|
|
<div v-for="(plan, index) in activePreviewTask ? [activePreviewTask] : (activePreviewDevice?.plans ?? [])"
|
|
:key="`${activePreviewDevice?.deviceId}-${plan.planId}-${index}`"
|
|
:class="['schedule-plan-item', { 'schedule-plan-item-active': plan.sourceType === 'CURRENT' }]">
|
|
<div class="schedule-plan-item-head">
|
|
<span class="schedule-plan-item-title">{{ plan.productCode ?? '-' }} / {{ plan.productName ?? '-'
|
|
}}</span>
|
|
<span v-if="plan.planStatus && PLAN_STATUS_COLOR_MAP[plan.planStatus]" class="plan-status-tag"
|
|
:style="{ backgroundColor: PLAN_STATUS_COLOR_MAP[plan.planStatus].color }">
|
|
{{ PLAN_STATUS_COLOR_MAP[plan.planStatus].label }}
|
|
</span>
|
|
</div>
|
|
<div>{{ t('GanttChart.GanttPanel.planCodeColon') }}{{ plan.taskCode ?? '-' }}</div>
|
|
<div>{{ t('GanttChart.GanttPanel.planNumberColon') }}{{ plan.planNumber ?? '-' }}</div>
|
|
<div>{{ t('GanttChart.GanttPanel.deliveryDateColon') }}{{ plan.deliveryDateStr ?? '-' }}</div>
|
|
<div>{{ t('GanttChart.GanttPanel.startColon') }}{{ plan.planStartTimeStr || '-' }}</div>
|
|
<div>{{ t('GanttChart.GanttPanel.endColon') }}{{ plan.planEndTimeStr || '-' }}</div>
|
|
<div>{{ t('GanttChart.GanttPanel.latestStartColon') }}{{ plan.latestStartTimeStr || '-' }}</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<el-empty v-else :description="t('GanttChart.GanttPanel.emptyDescription')" :image-size="80" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import dayjs from 'dayjs'
|
|
import { gantt } from 'dhtmlx-gantt'
|
|
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
|
|
import { useI18n } from '@/hooks/web/useI18n'
|
|
|
|
defineOptions({ name: 'ScheduleGanttPanel' })
|
|
|
|
const { t } = useI18n()
|
|
|
|
// 计划状态颜色映射表
|
|
const PLAN_STATUS_COLOR_MAP = {
|
|
1: { label: t('GanttChart.GanttPanel.statusScheduled'), color: '#409eff', textColor: '#ffffff', sort: 1 },
|
|
8: { label: t('GanttChart.GanttPanel.statusStarted'), color: '#67c23a', textColor: '#ffffff', sort: 2 },
|
|
3: { label: t('GanttChart.GanttPanel.statusPaused'), color: '#e6a23c', textColor: '#ffffff', sort: 3 },
|
|
4: { label: t('GanttChart.GanttPanel.statusPendingStorage'), color: '#f56c6c', textColor: '#ffffff', sort: 4 },
|
|
5: { label: t('GanttChart.GanttPanel.statusStored'), color: '#8e7cc3', textColor: '#ffffff', sort: 5 },
|
|
}
|
|
|
|
const sortedPlanStatusList = Object.entries(PLAN_STATUS_COLOR_MAP)
|
|
.map(([key, val]) => ({ key: Number(key), ...val }))
|
|
.sort((a, b) => a.sort - b.sort)
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
scheduleList: any[]
|
|
editable?: boolean
|
|
height?: string
|
|
}>(),
|
|
{
|
|
editable: false,
|
|
height: '800px'
|
|
}
|
|
)
|
|
|
|
const ganttContainerRef = ref<HTMLDivElement>()
|
|
const activePreviewDevice = ref<any>()
|
|
const activePreviewTask = ref<any>()
|
|
const ganttEventIds = ref<string[]>([])
|
|
|
|
const previewScheduleList = computed(() => (Array.isArray(props.scheduleList) ? props.scheduleList : []))
|
|
|
|
const getGlobalDateRange = (scheduleList: any[]) => {
|
|
const allPlans = scheduleList.flatMap((item: any) => item?.plans ?? [])
|
|
const starts = allPlans.map((item: any) => dayjs(item?.planStartTimeStr).valueOf()).filter((item: number) => Number.isFinite(item))
|
|
const ends = allPlans.map((item: any) => dayjs(item?.planEndTimeStr).valueOf()).filter((item: number) => Number.isFinite(item))
|
|
if (!starts.length || !ends.length) {
|
|
const now = Date.now()
|
|
return {
|
|
start: now,
|
|
end: now + 7 * 24 * 60 * 60 * 1000
|
|
}
|
|
}
|
|
return {
|
|
start: Math.min(...starts),
|
|
end: Math.max(...ends)
|
|
}
|
|
}
|
|
|
|
const formatGanttDate = (value: unknown) => {
|
|
const date = dayjs(value)
|
|
if (!date.isValid()) return undefined
|
|
return date.format('YYYY-MM-DD HH:mm')
|
|
}
|
|
|
|
const buildPreviewGanttData = (scheduleList: any[]) => {
|
|
const tasks: any[] = []
|
|
|
|
scheduleList.forEach((device: any) => {
|
|
const plans = (device?.plans ?? []).map((plan: any) => ({
|
|
...plan,
|
|
_start: dayjs(plan?.planStartTimeStr),
|
|
_end: dayjs(plan?.planEndTimeStr)
|
|
}))
|
|
const validPlans = plans
|
|
.filter((plan: any) => plan._start.isValid() && plan._end.isValid())
|
|
.sort((a: any, b: any) => a._start.valueOf() - b._start.valueOf())
|
|
|
|
if (!validPlans.length) return
|
|
|
|
const allStarts = validPlans.map((p: any) => p._start.valueOf())
|
|
const allEnds = validPlans.map((p: any) => p._end.valueOf())
|
|
const earliestStart = dayjs(Math.min(...allStarts))
|
|
const latestEnd = dayjs(Math.max(...allEnds))
|
|
const totalDays = Math.max(latestEnd.endOf('day').diff(earliestStart.startOf('day'), 'day') + 1, 1)
|
|
|
|
const deviceTaskId = `device-${device.deviceId}`
|
|
tasks.push({
|
|
id: deviceTaskId,
|
|
text: device.deviceName ?? '-',
|
|
start_date: formatGanttDate(earliestStart),
|
|
end_date: formatGanttDate(latestEnd),
|
|
duration: totalDays,
|
|
parent: 0,
|
|
progress: 0,
|
|
readonly: true,
|
|
deviceName: device.deviceName ?? '-',
|
|
totalDays,
|
|
_deviceData: device,
|
|
_validPlans: validPlans
|
|
})
|
|
})
|
|
|
|
return { data: tasks, links: [] }
|
|
}
|
|
|
|
const destroyGantt = () => {
|
|
ganttEventIds.value.forEach((eventId) => gantt.detachEvent(eventId))
|
|
ganttEventIds.value = []
|
|
gantt.clearAll()
|
|
}
|
|
|
|
const formatTooltipDateTime = (value: unknown) => {
|
|
const date = dayjs(value)
|
|
if (!date.isValid()) return '-'
|
|
return date.format('YYYY-MM-DD HH:mm:ss')
|
|
}
|
|
const clearCustomPlanBars = () => {
|
|
if (!ganttContainerRef.value) return
|
|
const existing = ganttContainerRef.value.querySelectorAll('.custom-plan-bar')
|
|
existing.forEach((el) => el.remove())
|
|
}
|
|
|
|
const renderCustomPlanBars = () => {
|
|
clearCustomPlanBars()
|
|
if (!ganttContainerRef.value) return
|
|
|
|
const taskRows = ganttContainerRef.value.querySelectorAll('.gantt_task_row')
|
|
taskRows.forEach((row: Element) => {
|
|
const taskId = row.getAttribute('task_id')
|
|
if (!taskId) return
|
|
const task = gantt.getTask(taskId)
|
|
if (!task?._validPlans) return
|
|
|
|
const plans = task._validPlans as any[]
|
|
const rowHeight = gantt.config.row_height
|
|
const barHeight = Math.min(gantt.config.bar_height, rowHeight - 8)
|
|
const barTop = (rowHeight - barHeight) / 2
|
|
|
|
plans.forEach((plan: any) => {
|
|
const planStart = dayjs(plan.planStartTimeStr)
|
|
const planEnd = dayjs(plan.planEndTimeStr)
|
|
if (!planStart.isValid() || !planEnd.isValid()) return
|
|
|
|
const leftPos = gantt.posFromDate(planStart.toDate())
|
|
const rightPos = gantt.posFromDate(planEnd.toDate())
|
|
const width = Math.max(rightPos - leftPos, 4)
|
|
|
|
const bar = document.createElement('div')
|
|
bar.className = 'custom-plan-bar'
|
|
bar.style.cssText = `
|
|
position: absolute;
|
|
left: ${leftPos}px;
|
|
top: ${barTop}px;
|
|
width: ${width}px;
|
|
height: ${barHeight}px;
|
|
border-radius: 10px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
cursor: pointer;
|
|
z-index: 2;
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 0 8px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
color: #ffffff;
|
|
text-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
text-overflow: ellipsis;
|
|
box-sizing: border-box;
|
|
`
|
|
|
|
const planStatus = plan.planStatus
|
|
const statusColors: Record<number, string> = {
|
|
1: '#3b82f6',
|
|
8: '#10b981',
|
|
3: '#f59e0b',
|
|
4: '#f56c6c',
|
|
5: '#8e7cc3'
|
|
}
|
|
bar.style.backgroundColor = statusColors[planStatus] || '#6b7280'
|
|
|
|
bar.textContent = `${plan.productCode ?? '-'} / ${plan.productName ?? '-'}`
|
|
|
|
bar.setAttribute('data-plan-id', String(plan.planId ?? ''))
|
|
bar.setAttribute('data-device-id', String(task._deviceData?.deviceId ?? ''))
|
|
|
|
bar.addEventListener('click', (e) => {
|
|
e.stopPropagation()
|
|
activePreviewTask.value = plan
|
|
activePreviewDevice.value = task._deviceData
|
|
})
|
|
|
|
bar.addEventListener('mouseenter', (e) => {
|
|
const tooltipsExt = (gantt.ext as any)?.tooltips
|
|
const tooltip = tooltipsExt?.tooltip
|
|
if (!tooltip) return
|
|
const html = buildPlanBarTooltipHtml(plan, task._deviceData)
|
|
tooltip.setContent(html)
|
|
tooltip.show(e)
|
|
const node = tooltip.getNode?.()
|
|
if (node) {
|
|
node.style.display = 'block'
|
|
node.style.visibility = 'visible'
|
|
node.style.opacity = '1'
|
|
node.style.zIndex = '10000'
|
|
node.style.pointerEvents = 'none'
|
|
}
|
|
})
|
|
|
|
bar.addEventListener('mouseleave', () => {
|
|
const tooltipsExt = (gantt.ext as any)?.tooltips
|
|
const tooltip = tooltipsExt?.tooltip
|
|
if (tooltip) {
|
|
tooltip.hide()
|
|
}
|
|
})
|
|
|
|
row.appendChild(bar)
|
|
})
|
|
})
|
|
}
|
|
|
|
const buildPlanBarTooltipHtml = (plan: any, device: any) => {
|
|
return `
|
|
<div><b>${t('GanttChart.GanttPanel.tooltipTaskDetail')}</b></div>
|
|
<div>${t('GanttChart.GanttPanel.tooltipDevice')}${device?.deviceName ?? '-'}</div>
|
|
<div>${t('GanttChart.GanttPanel.tooltipTaskCode')}${plan.taskCode ?? '-'}</div>
|
|
<div>${t('GanttChart.GanttPanel.tooltipProduct')}${plan.productCode ?? '-'} / ${plan.productName ?? '-'}</div>
|
|
<div>${t('GanttChart.GanttPanel.tooltipPlanNumber')}${plan.planNumber ?? '-'}</div>
|
|
<div>${t('GanttChart.GanttPanel.tooltipStart')}${formatTooltipDateTime(plan.planStartTimeStr)}</div>
|
|
<div>${t('GanttChart.GanttPanel.tooltipEnd')}${formatTooltipDateTime(plan.planEndTimeStr)}</div>
|
|
<div>${t('GanttChart.GanttPanel.tooltipLatestStart')}${formatTooltipDateTime(plan.latestStartTimeStr)}</div>
|
|
`
|
|
}
|
|
|
|
const initGanttPreview = () => {
|
|
if (!ganttContainerRef.value) return
|
|
destroyGantt()
|
|
clearCustomPlanBars()
|
|
if (!previewScheduleList.value.length) {
|
|
activePreviewDevice.value = undefined
|
|
activePreviewTask.value = undefined
|
|
return
|
|
}
|
|
|
|
gantt.plugins({ tooltip: true })
|
|
|
|
gantt.config.tooltip_timeout = 0
|
|
gantt.templates.tooltip_text = () => ''
|
|
|
|
gantt.config.readonly = true
|
|
gantt.config.drag_move = false
|
|
gantt.config.drag_links = false
|
|
gantt.config.drag_progress = false
|
|
gantt.config.drag_resize = false
|
|
gantt.config.order_branch = false
|
|
gantt.config.order_branch_free = false
|
|
gantt.config.details_on_dblclick = false
|
|
gantt.config.show_progress = false
|
|
|
|
gantt.config.row_height = 48
|
|
gantt.config.scale_height = 70
|
|
gantt.config.xml_date = '%Y-%m-%d %H:%i'
|
|
gantt.config.task_height = 36
|
|
gantt.config.min_column_width = 80
|
|
gantt.config.column_width = 90
|
|
gantt.config.bar_height = 34
|
|
gantt.config.resize_step = 15
|
|
|
|
gantt.config.columns = [
|
|
{
|
|
name: 'text',
|
|
label: t('GanttChart.GanttPanel.columnDeviceName'),
|
|
tree: false,
|
|
width: '*',
|
|
min_width: 160,
|
|
template: (task: any) => task.deviceName ?? task.text ?? '-'
|
|
},
|
|
{
|
|
name: 'duration',
|
|
label: t('GanttChart.GanttPanel.columnDays'),
|
|
align: 'center',
|
|
width: 80,
|
|
template: (task: any) => String(task.totalDays ?? task.duration ?? 0)
|
|
}
|
|
]
|
|
|
|
gantt.config.scales = [
|
|
{ unit: 'month', step: 1, format: (date) => dayjs(date).format(t('GanttChart.GanttPanel.scaleMonthFormat')) },
|
|
{ unit: 'day', step: 1, format: (date) => dayjs(date).format('MM-DD') }
|
|
]
|
|
|
|
gantt.templates.task_class = (_start, _end, task: any) => {
|
|
if (task._validPlans) return 'schedule-device-row'
|
|
return ''
|
|
}
|
|
|
|
const globalRange = getGlobalDateRange(previewScheduleList.value)
|
|
gantt.config.start_date = dayjs(globalRange.start).startOf('day').toDate()
|
|
gantt.config.end_date = dayjs(globalRange.end).endOf('day').toDate()
|
|
|
|
gantt.init(ganttContainerRef.value)
|
|
|
|
const ganttData = buildPreviewGanttData(previewScheduleList.value)
|
|
gantt.parse(ganttData)
|
|
|
|
if (ganttData.data.length) {
|
|
activePreviewDevice.value = ganttData.data[0]._deviceData
|
|
const today = dayjs().startOf('day').toDate()
|
|
const pos = gantt.posFromDate(today)
|
|
const scrollState = gantt.getScrollState()
|
|
if (scrollState && pos >= 0) {
|
|
const halfWidth = scrollState.inner_width / 3
|
|
gantt.scrollTo(Math.max(0, pos - halfWidth), 0)
|
|
} else {
|
|
gantt.showDate(today)
|
|
}
|
|
}
|
|
|
|
const clickEventId = gantt.attachEvent('onTaskClick', (id) => {
|
|
const task = gantt.getTask(id)
|
|
if (task?._deviceData) {
|
|
activePreviewTask.value = undefined
|
|
activePreviewDevice.value = task._deviceData
|
|
}
|
|
return true
|
|
})
|
|
ganttEventIds.value.push(clickEventId)
|
|
|
|
nextTick(() => {
|
|
renderCustomPlanBars()
|
|
})
|
|
|
|
const renderEventId = gantt.attachEvent('onGanttRender', () => {
|
|
nextTick(() => {
|
|
renderCustomPlanBars()
|
|
})
|
|
})
|
|
ganttEventIds.value.push(renderEventId)
|
|
}
|
|
|
|
onMounted(async () => {
|
|
await nextTick()
|
|
initGanttPreview()
|
|
})
|
|
|
|
watch(
|
|
() => props.scheduleList,
|
|
async () => {
|
|
await nextTick()
|
|
initGanttPreview()
|
|
},
|
|
{ deep: true }
|
|
)
|
|
|
|
watch(
|
|
() => props.editable,
|
|
async () => {
|
|
await nextTick()
|
|
initGanttPreview()
|
|
}
|
|
)
|
|
|
|
onBeforeUnmount(() => {
|
|
destroyGantt()
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.schedule-preview-wrap {
|
|
display: flex;
|
|
width: 100%;
|
|
height: 100%;
|
|
gap: 12px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.gantt-main-area {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.schedule-gantt-container {
|
|
min-width: 0;
|
|
border: 1px solid var(--el-border-color);
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.schedule-detail-panel {
|
|
width: 280px;
|
|
flex: 0 0 280px;
|
|
}
|
|
|
|
.schedule-detail-title {
|
|
margin-bottom: 10px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.schedule-status-legend {
|
|
display: flex;
|
|
margin-bottom: 12px;
|
|
padding: 8px;
|
|
background: var(--el-fill-color-light);
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.legend-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 4px 8px;
|
|
border-left: 3px solid;
|
|
font-size: 12px;
|
|
margin-right: 20px
|
|
}
|
|
|
|
.legend-color {
|
|
display: inline-block;
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 2px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.legend-label {
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.schedule-plan-list-title {
|
|
margin-top: 14px;
|
|
margin-bottom: 10px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.schedule-plan-list {
|
|
overflow: auto;
|
|
max-height: 55vh;
|
|
border: 1px solid var(--el-border-color);
|
|
border-radius: 8px;
|
|
padding: 8px;
|
|
}
|
|
|
|
.schedule-plan-item {
|
|
padding: 8px;
|
|
border-radius: 8px;
|
|
background: var(--el-fill-color-light);
|
|
font-size: 14px;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.schedule-plan-item-head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.schedule-plan-item-title {
|
|
font-weight: 600;
|
|
color: var(--el-text-color-primary);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.plan-status-tag {
|
|
display: inline-block;
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
font-size: 10px;
|
|
color: #ffffff;
|
|
font-weight: 600;
|
|
white-space: nowrap;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.schedule-plan-item-active {
|
|
border: 1px solid var(--el-color-success-light-5);
|
|
background: var(--el-color-success-light-9);
|
|
}
|
|
|
|
.schedule-plan-item+.schedule-plan-item {
|
|
margin-top: 8px;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_grid_data .gantt_row) {
|
|
font-weight: 600;
|
|
background: linear-gradient(180deg, #f8fafc 0%, #f0f4f8 100%);
|
|
border-bottom: 1px solid #e2e8f0;
|
|
height: 48px;
|
|
display: flex;
|
|
align-items: flex-end;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_grid_data .gantt_row:nth-child(even)) {
|
|
background: linear-gradient(180deg, #ffffff 0%, #f9fafb 100%);
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_grid_data .gantt_row:hover) {
|
|
background: linear-gradient(180deg, #f3f4f6 0%, #e5e7eb 100%) !important;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_grid_data .gantt_cell) {
|
|
text-overflow: clip;
|
|
font-size: 13px;
|
|
color: #4b5563;
|
|
border-right: 1px solid #e5e7eb;
|
|
padding: 0 12px;
|
|
line-height: 36px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: flex-start;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_scale_line) {
|
|
border-bottom: 2px solid #d1d5db;
|
|
background: linear-gradient(180deg, #ffffff 0%, #f9fafb 100%);
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_scale_line:last-child) {
|
|
margin-bottom: 0;
|
|
border-bottom: 1px solid #e5e7eb;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_scale_cell) {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: #374151;
|
|
border-right: 1px solid #e5e7eb;
|
|
padding: 0 8px;
|
|
text-align: center;
|
|
background: linear-gradient(180deg, #ffffff 0%, #f9fafb 100%);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 35px;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_scale_line:last-child .gantt_scale_cell) {
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: #6b7280;
|
|
background: linear-gradient(180deg, #f9fafb 0%, #f3f4f6 100%);
|
|
border-bottom: 1px solid #e5e7eb;
|
|
height: 35px;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_task_bg) {
|
|
background: #ffffff;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_task_bg .gantt_task_cell) {
|
|
border-right: 1px solid #f3f4f6;
|
|
border-bottom: 1px solid #f3f4f6;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_task_bg .gantt_task_row:nth-child(even)) {
|
|
background: #f9fafb;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_task_bg .gantt_task_row:hover) {
|
|
background: #f3f4f6 !important;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_task_line) {
|
|
border-radius: 10px;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
border: none;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_task_content) {
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
color: #ffffff;
|
|
padding: 0 12px;
|
|
line-height: 32px;
|
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-device-row) {
|
|
visibility: hidden;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task) {
|
|
background: #67c23a;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task .gantt_task_content) {
|
|
color: #ffffff;
|
|
}
|
|
|
|
/* 已排产状态 - 蓝色 */
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task-status-1) {
|
|
background: #3b82f6 !important;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task-status-1 .gantt_task_content) {
|
|
color: #ffffff !important;
|
|
}
|
|
|
|
/* 已开工状态 - 绿色 */
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task-status-8) {
|
|
background: #10b981 !important;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task-status-8 .gantt_task_content) {
|
|
color: #ffffff !important;
|
|
}
|
|
|
|
/* 暂停状态 - 橙色 */
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task-status-3) {
|
|
background: #f59e0b !important;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task-status-3 .gantt_task_content) {
|
|
color: #ffffff !important;
|
|
}
|
|
|
|
/* 待入库状态 - 红色 */
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task-status-4) {
|
|
background: #ef4444 !important;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task-status-4 .gantt_task_content) {
|
|
color: #ffffff !important;
|
|
}
|
|
|
|
/* 已入库状态 - 紫色 */
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task-status-5) {
|
|
background: #8b5cf6 !important;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task-status-5 .gantt_task_content) {
|
|
color: #ffffff !important;
|
|
}
|
|
|
|
/* 其他状态 - 深灰 */
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task-status-2) {
|
|
background: #4b5563 !important;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task-status-2 .gantt_task_content) {
|
|
color: #ffffff !important;
|
|
}
|
|
|
|
/* 默认状态 */
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task-default) {
|
|
background: #6b7280;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task-default .gantt_task_content) {
|
|
color: #ffffff;
|
|
}
|
|
|
|
/* 历史任务 */
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task-history) {
|
|
background: #9ca3af;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.schedule-gantt-container :deep(.gantt_task_line.schedule-plan-task-history .gantt_task_content) {
|
|
color: #ffffff;
|
|
}
|
|
|
|
:deep(.gantt_tooltip) {
|
|
z-index: 5000 !important;
|
|
background: rgba(255, 255, 255, 0.95) !important;
|
|
border: 1px solid #e5e7eb !important;
|
|
border-radius: 8px !important;
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important;
|
|
padding: 12px !important;
|
|
font-size: 13px !important;
|
|
line-height: 1.5 !important;
|
|
}
|
|
|
|
:deep(.gantt_tooltip) div {
|
|
margin-bottom: 6px !important;
|
|
}
|
|
|
|
:deep(.gantt_tooltip) div:last-child {
|
|
margin-bottom: 0 !important;
|
|
}
|
|
|
|
:deep(.gantt_tree_content) {
|
|
line-height: 50px;
|
|
}
|
|
|
|
:deep(.gantt_tree_icon.gantt_close:before) {
|
|
margin-top: 5px;
|
|
}
|
|
</style>
|