Files
klp-oa/klp-ui/src/views/wms/anneal/overview/index.vue

337 lines
11 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>
<div class="app-container">
<el-row :gutter="12" class="summary-row">
<el-col :xs="24" :sm="12" :md="6">
<div class="summary-card">
<div class="summary-title">当前计划数</div>
<div class="summary-value">{{ overview.totalPlanCount || 0 }}</div>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="summary-card">
<div class="summary-title">退火炉(忙碌/全部)</div>
<div class="summary-value">{{ overview.furnaceBusyCount || 0 }}/{{ overview.furnaceTotal || 0 }}</div>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="summary-card">
<div class="summary-title">待退火钢卷</div>
<div class="summary-value">{{ overview.pendingCoilCount || 0 }}</div>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="summary-card">
<div class="summary-title">今日已完成</div>
<div class="summary-value">
计划 {{ overview.todayDonePlanCount || 0 }} | 钢卷 {{ overview.todayDoneCoilCount || 0 }}
</div>
</div>
</el-col>
</el-row>
<el-card shadow="never" class="panel">
<div slot="header" class="panel-title">退火炉状态</div>
<div class="furnace-grid">
<div v-for="item in overview.furnaces" :key="item.furnaceId" class="furnace-card">
<div class="furnace-header">
<svg-icon icon-class="furnace" :class="item.busyFlag === 1 ? 'furnace-busy' : 'furnace-idle'" />
<div>
<div class="furnace-name">{{ item.furnaceName }}</div>
<div class="furnace-code">{{ item.furnaceCode }}</div>
</div>
</div>
<div class="furnace-body">
<div class="furnace-line">状态<span :class="item.busyFlag === 1 ? 'busy-text' : 'idle-text'">{{ item.busyFlag === 1 ? '忙碌' : '空闲' }}</span></div>
<div v-if="item.busyFlag === 1" class="furnace-line">
计划{{ item.currentPlanNo || '-' }}
</div>
<div v-if="item.busyFlag === 1" class="furnace-line">
当前钢卷{{ item.coilCount || 0 }}
</div>
<div v-if="item.busyFlag === 1" class="furnace-line">
预计剩余{{ formatCountdown(item.planEndTime) }}
</div>
<div v-else class="furnace-line">待入炉计划{{ planQueueCount(item.furnaceId) }}</div>
</div>
</div>
</div>
</el-card>
<el-card shadow="never" class="panel">
<div slot="header" class="panel-title">计划队列</div>
<el-table :data="overview.planQueue" v-loading="loading">
<el-table-column label="计划号" prop="planNo" align="center" />
<el-table-column label="目标炉子" prop="targetFurnaceName" align="center" />
<el-table-column label="状态" prop="status" align="center">
<template slot-scope="scope">
<el-tag :type="statusTag(scope.row.status)">{{ statusLabel(scope.row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="计划时间" prop="planStartTime" align="center" width="160">
<template slot-scope="scope">
{{ parseTime(scope.row.planStartTime, '{y}-{m}-{d} {h}:{i}') }}
</template>
</el-table-column>
<el-table-column label="钢卷数" prop="coilCount" align="center" />
<el-table-column label="操作" align="center" width="160">
<template slot-scope="scope">
<el-button v-if="scope.row.status === 0" size="mini" type="primary" @click="handleInFurnace(scope.row)">入炉</el-button>
<el-button v-if="scope.row.status === 2" size="mini" type="success" @click="openCompleteDialog(scope.row)">完成</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog title="完成退火" :visible.sync="completeOpen" width="720px" append-to-body>
<div class="complete-tip">请为每条钢卷分配实际库位未分配将无法完成</div>
<el-table :data="completeCoils" v-loading="completeLoading" height="360px">
<el-table-column label="入场钢卷号" prop="enterCoilNo" align="center" />
<el-table-column label="实际库位" align="center" width="240">
<template slot-scope="scope">
<el-select v-model="scope.row.actualWarehouseId" placeholder="请选择" filterable>
<el-option v-for="item in actualWarehouseOptions" :key="item.actualWarehouseId" :label="item.name" :value="item.actualWarehouseId" />
</el-select>
</template>
</el-table-column>
</el-table>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitComplete"> </el-button>
<el-button @click="completeOpen = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getAnnealOverview } from "@/api/wms/annealOverview";
import { inFurnace, completeAnnealPlan, listAnnealPlanCoils } from "@/api/wms/annealPlan";
import { listActualWarehouse } from "@/api/wms/actualWarehouse";
export default {
name: "AnnealOverview",
data() {
return {
loading: true,
overview: {
furnaces: [],
planQueue: []
},
timer: null,
completeOpen: false,
completeLoading: false,
completeCoils: [],
completePlanId: null,
actualWarehouseOptions: []
};
},
created() {
this.fetchOverview();
this.startTimer();
this.loadActualWarehouses();
},
beforeDestroy() {
this.stopTimer();
},
methods: {
fetchOverview() {
this.loading = true;
getAnnealOverview().then(response => {
this.overview = response.data || { furnaces: [], planQueue: [] };
this.loading = false;
});
},
startTimer() {
this.stopTimer();
this.timer = setInterval(() => {
this.$forceUpdate();
}, 1000);
},
stopTimer() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
},
loadActualWarehouses() {
listActualWarehouse({ pageNum: 1, pageSize: 999, actualWarehouseType: 2, isEnabled: 1 }).then(response => {
this.actualWarehouseOptions = response.rows || [];
});
},
openCompleteDialog(row) {
this.completePlanId = row.planId;
this.completeOpen = true;
this.completeLoading = true;
listAnnealPlanCoils(row.planId).then(response => {
this.completeCoils = (response.data || []).map(item => ({
coilId: item.coilId,
enterCoilNo: item.enterCoilNo,
actualWarehouseId: null
}));
this.completeLoading = false;
}).catch(() => {
this.completeLoading = false;
});
},
submitComplete() {
const missing = this.completeCoils.filter(item => !item.actualWarehouseId);
if (missing.length > 0) {
this.$message.warning('请先为所有钢卷分配实际库位');
return;
}
this.completeLoading = true;
completeAnnealPlan({
planId: this.completePlanId,
locations: this.completeCoils.map(item => ({
coilId: item.coilId,
actualWarehouseId: item.actualWarehouseId
}))
}).then(() => {
this.$message.success('已完成');
this.completeOpen = false;
this.fetchOverview();
}).finally(() => {
this.completeLoading = false;
});
},
planQueueCount(furnaceId) {
if (!this.overview.planQueue) {
return 0;
}
return this.overview.planQueue.filter(item => item.targetFurnaceId === furnaceId && item.status !== 2).length;
},
statusLabel(status) {
const map = { 0: '草稿', 1: '已下发', 2: '执行中', 3: '已完成', 4: '已取消' };
return map[status] || '-';
},
statusTag(status) {
const map = { 0: 'info', 1: 'warning', 2: 'success', 3: 'success', 4: 'danger' };
return map[status] || 'info';
},
formatCountdown(endTime) {
if (!endTime) return '-';
const end = new Date(endTime).getTime();
const now = Date.now();
let diff = Math.max(0, end - now);
const hours = Math.floor(diff / (1000 * 60 * 60));
diff %= 1000 * 60 * 60;
const minutes = Math.floor(diff / (1000 * 60));
diff %= 1000 * 60;
const seconds = Math.floor(diff / 1000);
return `${hours}小时${minutes}${seconds}`;
},
handleInFurnace(row) {
const furnace = this.overview.furnaces.find(item => item.furnaceId === row.targetFurnaceId);
if (furnace && furnace.busyFlag === 1) {
this.$message.warning(`炉子正忙:计划${furnace.currentPlanNo || ''},钢卷${furnace.coilCount || 0}`);
return;
}
this.$confirm('确定入炉该计划吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return inFurnace({ planId: row.planId });
}).then(() => {
this.$message.success('入炉成功');
this.fetchOverview();
});
}
}
};
</script>
<style scoped>
.summary-row {
margin-bottom: 12px;
}
.summary-card {
border: 1px solid #f0f2f5;
border-radius: 6px;
padding: 12px 16px;
height: 90px;
display: flex;
flex-direction: column;
justify-content: center;
background: #ffffff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
}
.summary-title {
font-size: 13px;
color: #909399;
}
.summary-value {
margin-top: 8px;
font-size: 18px;
font-weight: 600;
color: #303133;
}
.panel {
margin-bottom: 16px;
border: 1px solid #f0f2f5;
background: #ffffff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
}
.panel-title {
font-weight: 500;
color: #606266;
}
.furnace-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 12px;
}
.furnace-card {
border: 1px solid #f0f2f5;
border-radius: 8px;
padding: 12px;
background: #ffffff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
}
.furnace-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.furnace-name {
font-weight: 600;
color: #303133;
}
.furnace-code {
font-size: 12px;
color: #909399;
}
.furnace-body {
font-size: 13px;
color: #606266;
}
.furnace-line {
margin-bottom: 4px;
}
.furnace-idle {
font-size: 28px;
color: #303133;
}
.furnace-busy {
font-size: 28px;
color: #f56c6c;
}
.busy-text {
color: #f56c6c;
}
.idle-text {
color: #67c23a;
}
:deep(.el-table th),
:deep(.el-table td) {
border-bottom: 1px solid #f0f2f5;
}
:deep(.el-table::before) {
background-color: transparent;
}
:deep(.el-card__header) {
border-bottom: 1px solid #f0f2f5;
background: #ffffff;
}
</style>