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.

427 lines
9.8 KiB
Vue

<template>
<div class="schedule-card-view" :class="{ 'fullscreen-mode': isFullscreen }">
<div class="schedule-toolbar">
<div class="schedule-legend">
<div class="legend-item">
<div class="legend-color" style="background: #5dade2"></div>
<span>{{ t('GanttChart.CardView.legendScheduled') }}</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #52c41a"></div>
<span>{{ t('GanttChart.CardView.legendMerged') }}</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #faad14"></div>
<span>{{ t('GanttChart.CardView.legendPaused') }}</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #ff7875"></div>
<span>{{ t('GanttChart.CardView.legendPendingStorage') }}</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #b37feb"></div>
<span>{{ t('GanttChart.CardView.legendStored') }}</span>
</div>
</div>
<div class="toolbar-actions">
<el-button circle @click="emit('refresh')">
<el-icon><Refresh /></el-icon>
</el-button>
<el-button circle @click="toggleFullscreen">
<el-icon><FullScreen /></el-icon>
</el-button>
</div>
</div>
<div class="device-cards-container">
<div v-for="device in scheduleList" :key="device.deviceId" class="device-card">
<div class="device-header">
<div class="device-info">
<div class="device-name">{{ device.deviceName }}</div>
<div class="device-code">{{ device.deviceCode }}</div>
</div>
<div class="device-stats">
<div class="stat-item">
<span class="stat-label">{{ t('GanttChart.CardView.statPlanCount') }}</span>
<span class="stat-value">{{ device.plans?.length || 0 }}</span>
</div>
<div class="stat-item">
<span class="stat-label">{{ t('GanttChart.CardView.statCapacity') }}</span>
<span class="stat-value">{{ device.ratedCapacity || '-' }}</span>
</div>
</div>
</div>
<div class="plans-container">
<div
v-for="plan in device.plans"
:key="plan.planId"
class="plan-card"
:style="{ borderLeftColor: getPlanStatusColor(plan.planStatus) }"
>
<div class="plan-header">
<span class="plan-status-badge" :style="{ backgroundColor: getPlanStatusColor(plan.planStatus) }">
{{ getPlanStatusLabel(plan.planStatus) }}
</span>
<span class="plan-code">{{ plan.planCode }}</span>
</div>
<div class="plan-content">
<div class="plan-row">
<span class="plan-label">{{ t('GanttChart.CardView.planCodeLabel') }}</span>
<span class="plan-value">{{ plan.taskCode }}</span>
</div>
<div class="plan-row">
<span class="plan-label">{{ t('GanttChart.CardView.productLabel') }}</span>
<span class="plan-value">{{ plan.productCode }} / {{ plan.productName }}</span>
</div>
<div class="plan-row">
<span class="plan-label">{{ t('GanttChart.CardView.planNumberLabel') }}</span>
<span class="plan-value">{{ plan.planNumber }}</span>
</div>
<div class="plan-row">
<span class="plan-label">{{ t('GanttChart.CardView.deliveryDateLabel') }}</span>
<span class="plan-value">{{ plan.deliveryDateStr || '-' }}</span>
</div>
<div class="plan-row">
<span class="plan-label">{{ t('GanttChart.CardView.startLabel') }}</span>
<span class="plan-value">{{ plan.planStartTimeStr }}</span>
</div>
<div class="plan-row">
<span class="plan-label">{{ t('GanttChart.CardView.endLabel') }}</span>
<span class="plan-value">{{ plan.planEndTimeStr }}</span>
</div>
</div>
</div>
</div>
<div v-if="!device.plans || device.plans.length === 0" class="empty-state">
<el-empty :description="t('GanttChart.CardView.emptyDescription')" :image-size="40" />
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { FullScreen, Refresh } from '@element-plus/icons-vue'
import { useI18n } from '@/hooks/web/useI18n'
defineOptions({ name: 'ScheduleCardView' })
const { t } = useI18n()
type UnifiedPlan = {
planId: number
planCode: string
taskCode: string
productCode: string
productName: string
planNumber: number
planStartTimeStr: string
planEndTimeStr: string
deliveryDateStr?: string
planStatus: number
sourceType: 'HISTORY'
}
type UnifiedDevice = {
deviceId: string | number
deviceName: string
deviceCode: string
ratedCapacity?: number | string
plans: UnifiedPlan[]
}
withDefaults(
defineProps<{
scheduleList: UnifiedDevice[]
}>(),
{
scheduleList: () => []
}
)
const emit = defineEmits(['refresh'])
const isFullscreen = ref(false)
const toggleFullscreen = () => {
isFullscreen.value = !isFullscreen.value
if (isFullscreen.value) {
document.body.style.overflow = 'hidden'
} else {
document.body.style.overflow = ''
}
}
onBeforeUnmount(() => {
if (isFullscreen.value) {
document.body.style.overflow = ''
}
})
const PLAN_STATUS_I18N_MAP: Record<number, string> = {
1: 'GanttChart.CardView.statusScheduled',
3: 'GanttChart.CardView.statusMerged',
5: 'GanttChart.CardView.statusPaused',
8: 'GanttChart.CardView.statusPendingStorage',
9: 'GanttChart.CardView.statusStored'
}
const planStatusMap: Record<number, { color: string }> = {
1: { color: '#5dade2' },
3: { color: '#52c41a' },
5: { color: '#faad14' },
8: { color: '#ff7875' },
9: { color: '#b37feb' }
}
const getPlanStatusLabel = (status: number) => {
const i18nKey = PLAN_STATUS_I18N_MAP[status]
return i18nKey ? t(i18nKey) : t('GanttChart.CardView.statusUnknown')
}
const getPlanStatusColor = (status: number) => {
return planStatusMap[status]?.color || '#999999'
}
</script>
<style scoped>
.schedule-card-view {
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
background: #fff;
}
.schedule-card-view.fullscreen-mode {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
border-radius: 0;
}
.schedule-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: #f5f7fa;
border-bottom: 1px solid #e4e7eb;
flex-shrink: 0;
}
.schedule-legend {
display: flex;
gap: 24px;
flex-wrap: wrap;
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: #666;
}
.legend-color {
width: 12px;
height: 12px;
border-radius: 2px;
}
.toolbar-actions {
display: flex;
gap: 8px;
}
.device-cards-container {
flex: 1;
overflow-x: auto;
overflow-y: hidden;
padding: 16px;
display: flex;
gap: 16px;
align-items: flex-start;
scroll-behavior: smooth;
}
.device-cards-container::-webkit-scrollbar {
height: 6px;
}
.device-cards-container::-webkit-scrollbar-track {
background: #f5f7fa;
}
.device-cards-container::-webkit-scrollbar-thumb {
background: #d9d9d9;
border-radius: 3px;
}
.device-cards-container::-webkit-scrollbar-thumb:hover {
background: #bfbfbf;
}
.device-card {
background: #fff;
border: 1px solid #e4e7eb;
border-radius: 4px;
overflow: hidden;
display: flex;
flex-direction: column;
flex-shrink: 0;
width: 340px;
max-height: 100%;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
}
.device-header {
padding: 12px 16px;
border-bottom: 1px solid #e4e7eb;
background: #fafbfc;
}
.device-info {
margin-bottom: 8px;
}
.device-name {
font-size: 14px;
font-weight: 600;
color: #1f2937;
margin-bottom: 2px;
}
.device-code {
font-size: 12px;
color: #999;
}
.device-stats {
display: flex;
gap: 16px;
}
.stat-item {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
}
.stat-label {
color: #666;
}
.stat-value {
font-weight: 600;
color: #1f2937;
}
.plans-container {
padding: 12px;
overflow-y: auto;
flex: 1;
max-height: calc(100vh - 200px);
scrollbar-width: thin;
scrollbar-color: transparent transparent;
}
.plans-container:hover {
scrollbar-color: rgba(144, 147, 153, 0.3) transparent;
}
.plans-container::-webkit-scrollbar {
width: 6px;
}
.plans-container::-webkit-scrollbar-thumb {
background: transparent;
border-radius: 3px;
}
.plans-container:hover::-webkit-scrollbar-thumb {
background: rgba(144, 147, 153, 0.3);
}
.plans-container::-webkit-scrollbar-track {
background: transparent;
}
.plan-card {
background: #fff;
border: 1px solid #e4e7eb;
border-left: 3px solid;
border-radius: 2px;
padding: 10px;
margin-bottom: 8px;
font-size: 12px;
}
.plan-card:last-child {
margin-bottom: 0;
}
.plan-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.plan-status-badge {
padding: 2px 6px;
border-radius: 2px;
color: #fff;
font-size: 11px;
font-weight: 600;
white-space: nowrap;
flex-shrink: 0;
}
.plan-code {
font-weight: 600;
color: #1f2937;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.plan-content {
display: flex;
flex-direction: column;
gap: 4px;
}
.plan-row {
display: flex;
gap: 4px;
line-height: 1.4;
}
.plan-label {
color: #999;
flex-shrink: 0;
}
.plan-value {
color: #1f2937;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
}
.empty-state {
padding: 20px;
text-align: center;
}
</style>