|
|
<template> <div class="picking-board-screen"> <!-- 装饰背景 --> <div class="bg-decoration"> <div class="decoration-line line-1"></div> <div class="decoration-line line-2"></div> <div class="decoration-line line-3"></div> <div class="decoration-circle circle-1"></div> <div class="decoration-circle circle-2"></div> </div>
<!-- 顶部标题栏 --> <div class="screen-header"> <!-- CCL Logo --> <div class="header-logo"> <img src="~@/assets/img/cclbai.png" alt="CCL Logo" class="logo-img"> </div>
<div class="header-decoration left"></div> <div class="header-center"> <div class="title-glow"></div> <h1 class="screen-title">人工拣选实时看板</h1> <div class="title-subtitle">Manual Picking Real-time Dashboard</div> </div> <div class="header-decoration right"></div> <div class="header-time"> <div class="time-icon"></div> <div class="time-text">{{ currentTime }}</div> </div> </div>
<!-- 主内容区 --> <div class="screen-content"> <!-- 工单卡片 --> <div class="picking-panel" v-for="(order, index) in workOrders" :key="order.workOrderNo" > <!-- 面板标题 --> <div class="panel-title-bar"> <div class="title-left"> <div class="title-icon"></div> <div class="title-text"> <span class="title-main">人工拣选{{ index + 1 }}</span> <span class="title-divider">|</span> <span class="title-sub">工单号码: <strong>{{ order.workOrderNo }}</strong></span> <span class="title-divider">|</span> <span class="title-sub">产品名称: <strong>{{ order.materialName }}</strong></span> <span class="title-divider"></span> <span class="title-sub">工单时间: <strong>{{ order.workOrderTime }}</strong></span> </div> </div><!-- <div class="title-right"> <div class="progress-display"> <div class="progress-numbers"> <span class="num-current">{{ order.completedCount }}</span> <span class="num-divider">/</span> <span class="num-total">{{ order.totalCount }}</span> </div> <div class="progress-bar-container"> <div class="progress-bar-bg"> <div class="progress-bar-fill" :style="{ width: (order.completedCount / order.totalCount * 100) + '%' }" ></div> </div> <div class="progress-percent">{{ Math.round(order.completedCount / order.totalCount * 100) }}%</div> </div> </div> </div>--> </div>
<!-- 数据表格 --> <div class="panel-table"> <table class="data-table"> <thead> <tr> <th style="width: 10px;">No.</th> <th style="width: 110px;">拣选托盘码</th> <th style="width: 180px;">拣选物料名称</th> <th style="width: 120px;">RFID</th> <th style="width: 130px;">状态</th> <th style="width: 80px;">存放位置</th> </tr> </thead> <tbody> <tr v-for="(item, idx) in order.details" :key="idx"> <td class="text-center">{{ idx + 1 }}</td> <td class="text-center">{{ item.pickingBatchNo }}</td> <td class="text-center">{{ item.pickingMaterialName }}</td> <td class="text-right">{{ item.rfidBarcode }}</td> <td class="text-center"> <span :class="['status-badge', getStatusClass(item.status)]"> {{ item.status }} </span> </td> <td class="text-center">{{ item.storageLocation }}</td> </tr> </tbody> </table> </div> </div> </div>
<!-- 底部装饰 --> <div class="screen-footer"> <div class="footer-line"></div> </div> </div></template>
<script>import {manualPicking} from '@/api/dashboard/dashboard.js'
export default { name: 'PickingBoard',
data() { return { currentTime: '', timeInterval: null, dataInterval: null,
// 模拟工单数据
workOrders: [ { workOrderNo: '', materialName: '', workOrderTime: '', completedCount: '', totalCount: '', details: [] }, { workOrderNo: '', materialName: '', workOrderTime: '', completedCount: '', totalCount: '', details: [] } ] } },
mounted() { this.updateTime() this.timeInterval = setInterval(() => { this.updateTime() }, 1000)
// 首次加载数据
this.getDataList()
// 每10秒刷新一次数据
this.dataInterval = setInterval(() => { this.getDataList() }, 10000) },
beforeDestroy() { if (this.timeInterval) { clearInterval(this.timeInterval) } if (this.dataInterval) { clearInterval(this.dataInterval) } },
methods: { // 获取数据列表
getDataList() { manualPicking({}).then(({data}) => { if (data && data.code === 200) { console.log('获取数据成功:', data.data) // TODO: 处理返回的数据,覆盖而非追加,避免内存累积
// if (data.data) {
// this.leftPanelList = data.data.leftPanelList || []
// this.rightPanelList = data.data.rightPanelList || []
// }
} }).catch(error => { console.error('获取数据失败:', error) }) }, /** * 更新当前时间 */ updateTime() { const now = new Date() const year = now.getFullYear() const month = String(now.getMonth() + 1).padStart(2, '0') const day = String(now.getDate()).padStart(2, '0') const hours = String(now.getHours()).padStart(2, '0') const minutes = String(now.getMinutes()).padStart(2, '0') const seconds = String(now.getSeconds()).padStart(2, '0') const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'] const weekDay = weekDays[now.getDay()]
this.currentTime = `${year}-${month}-${day} ${weekDay} ${hours}:${minutes}:${seconds}` },
/** * 根据状态获取样式类名 */ getStatusClass(status) { const statusMap = { '完成': 'status-success', '分拣中': 'status-warning', '等待分拣': 'status-pending' } return statusMap[status] || 'status-pending' } }}</script>
<style scoped lang="scss">/* ===== 整体容器 ===== */.picking-board-screen { width: 100vw; height: 100vh; background: linear-gradient(135deg, #5f8cc3 0%, #749cc8 100%); position: relative; overflow: hidden; font-family: 'Microsoft YaHei', 'PingFang SC', Arial, sans-serif;}
/* ===== 装饰背景 ===== */.bg-decoration { display: none;}
/* ===== 顶部标题区 ===== */.screen-header { position: relative; height: 60px; display: flex; align-items: center; justify-content: center; padding: 0 40px; z-index: 10; border-bottom: 2px solid rgba(23, 179, 163, 0.4); background: linear-gradient(180deg, rgba(23, 179, 163, 0.08) 0%, transparent 100%);}
/* CCL Logo */.header-logo { position: absolute; left: 20px; top: 50%; transform: translateY(-50%); z-index: 20;}
.logo-img { height: 40px; width: auto; filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.3)); transition: all 0.3s ease;
&:hover { filter: drop-shadow(0 4px 12px rgba(23, 179, 163, 0.5)); transform: scale(1.05); }}
.header-decoration { display: none;}
.header-center { position: relative; text-align: center;}
.title-glow { display: none;}
.screen-title { position: relative; font-size: 28px; font-weight: bold; color: #ffffff; margin: 0; letter-spacing: 3px; text-shadow: 0 0 20px rgba(23, 179, 163, 0.5);}
.title-subtitle { font-size: 10px; color: rgba(255, 255, 255, 0.8); letter-spacing: 2px; margin-top: 3px; font-family: Arial, sans-serif; text-transform: uppercase;}
.header-time { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); display: flex; align-items: center; gap: 12px; background: rgba(23, 179, 163, 0.15); padding: 12px 20px; border-radius: 8px; border: 1px solid rgba(23, 179, 163, 0.4); backdrop-filter: blur(10px);}
.time-icon { font-size: 14px; color: #17B3A3;}
.time-text { font-size: 16px; color: #ffffff; font-family: 'Consolas', 'Courier New', monospace; font-weight: 500; letter-spacing: 1px;}
/* ===== 主内容区 ===== */.screen-content { position: relative; z-index: 1; padding: 10px 5px; height: calc(100vh - 60px); overflow-y: auto; display: flex; gap: 30px;
&::-webkit-scrollbar { width: 6px; }
&::-webkit-scrollbar-track { background: rgba(23, 179, 163, 0.1); }
&::-webkit-scrollbar-thumb { background: rgba(23, 179, 163, 0.5); border-radius: 3px;
&:hover { background: rgba(23, 179, 163, 0.7); } }}
/* ===== 拣选面板 ===== */.picking-panel { margin-left: 5px; flex: 1; background: rgba(70, 90, 120, 0.9); backdrop-filter: blur(10px); border: 1px solid rgba(23, 179, 163, 0.5); border-radius: 12px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.1); overflow: hidden; transition: all 0.3s ease; display: flex; flex-direction: column;
&:hover { border-color: rgba(23, 179, 163, 0.5); box-shadow: 0 12px 48px rgba(0, 0, 0, 0.5), 0 0 30px rgba(23, 179, 163, 0.2); transform: translateY(-2px); }}
/* ===== 面板标题栏 ===== */.panel-title-bar { background: linear-gradient(135deg, rgba(23, 179, 163, 0.3) 0%, rgba(23, 179, 163, 0.15) 100%); border-bottom: 1px solid rgba(23, 179, 163, 0.3); padding: 10px 16px; display: flex; justify-content: space-between; align-items: center;}
.title-left { display: flex; align-items: center; gap: 15px; flex: 1;}
.title-icon { font-size: 16px; color: #64D8CB;}
.title-text { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;}
.title-main { font-size: 18px; font-weight: bold; color: #ffffff; text-shadow: 0 0 10px rgba(100, 216, 203, 0.5);}
.title-divider { color: rgba(255, 255, 255, 0.4); font-size: 11px;}
.title-sub { font-size: 16px; color: rgba(255, 255, 255, 0.8);
strong { color: #ffffff; font-weight: 600; font-family: 'Consolas', monospace; }}
.title-right { min-width: 280px;}
/* ===== 进度显示 ===== */.progress-display { display: flex; align-items: center; gap: 15px;}
.progress-numbers { font-size: 18px; font-weight: bold; font-family: 'Arial', sans-serif; white-space: nowrap;
.num-current { color: #FFD54F; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); }
.num-divider { color: rgba(255, 255, 255, 0.7); margin: 0 3px; }
.num-total { color: #ffffff; }}
.progress-bar-container { flex: 1; display: flex; align-items: center; gap: 6px;}
.progress-bar-bg { flex: 1; height: 6px; background: rgba(0, 0, 0, 0.3); border-radius: 3px; overflow: hidden; box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);}
.progress-bar-fill { height: 100%; background: linear-gradient(90deg, #17B3A3, #64D8CB); border-radius: 3px; transition: width 0.5s ease; box-shadow: 0 0 15px rgba(23, 179, 163, 0.6); position: relative;}
.progress-percent { font-size: 11px; color: #64D8CB; font-weight: bold; min-width: 38px; text-align: right; text-shadow: 0 0 10px rgba(100, 216, 203, 0.5);}
/* ===== 数据表格 ===== */.panel-table { padding: 5px 5px; flex: 1; overflow-y: auto; &::-webkit-scrollbar { width: 4px; }
&::-webkit-scrollbar-track { background: rgba(23, 179, 163, 0.05); }
&::-webkit-scrollbar-thumb { background: rgba(23, 179, 163, 0.3); border-radius: 2px;
&:hover { background: rgba(23, 179, 163, 0.5); } }}
.data-table { width: 100%; border-collapse: separate; border-spacing: 0;
thead { tr { background: linear-gradient(135deg, rgba(23, 179, 163, 0.25) 0%, rgba(23, 179, 163, 0.15) 100%); }
th { padding: 8px 2px; color: #fcfdfd; font-size: 10px; font-weight: bold; text-align: center; border-bottom: 2px solid rgba(23, 179, 163, 0.4); text-shadow: 0 0 10px rgba(100, 216, 203, 0.5); white-space: nowrap;
&:first-child { border-top-left-radius: 8px; }
&:last-child { border-top-right-radius: 8px; } } }
tbody { tr { background: rgba(60, 80, 105, 0.6); transition: all 0.3s ease;
&:nth-child(even) { background: rgba(70, 90, 115, 0.7); }
&:hover { background: rgba(23, 179, 163, 0.15); box-shadow: 0 4px 12px rgba(23, 179, 163, 0.2); }
&:last-child { td:first-child { border-bottom-left-radius: 8px; }
td:last-child { border-bottom-right-radius: 8px; } } }
td { padding: 7px 2px; color: rgba(255, 255, 255, 0.9); font-size: 12px; border-bottom: 1px solid rgba(23, 179, 163, 0.15);
&.text-center { text-align: center; }
&.text-left { text-align: left; }
&.text-right { text-align: right; }
} }}
/* ===== 状态徽章 ===== */.status-badge { display: inline-block; padding: 3px 8px; border-radius: 12px; font-size: 10px; font-weight: bold; text-align: center; min-width: 60px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
&.status-success { background: linear-gradient(135deg, #10b981, #34d399); color: #ffffff; box-shadow: 0 0 15px rgba(16, 185, 129, 0.5); }
&.status-warning { background: linear-gradient(135deg, #f59e0b, #fbbf24); color: #ffffff; box-shadow: 0 0 15px rgba(245, 158, 11, 0.5); }
&.status-pending { background: linear-gradient(135deg, #6b7280, #9ca3af); color: #ffffff; box-shadow: 0 0 15px rgba(107, 114, 128, 0.5); }}
/* ===== 底部装饰 ===== */.screen-footer { display: none;}
/* ===== 响应式适配 ===== */@media screen and (max-width: 1600px) { .screen-title { font-size: 26px; letter-spacing: 3px; }
.title-main { font-size: 18px; }
.title-sub { font-size: 16px; }
.progress-numbers { font-size: 16px; }}
@media screen and (min-width: 2560px) { .screen-title { font-size: 32px; }
.panel-title-bar { padding: 12px 20px; }
.title-main { font-size: 18px; }
.data-table { thead th { font-size: 12px; padding: 9px 5px; }
tbody td { font-size: 14px; padding: 8px 12px; } }}</style>
|