<template>
|
<div class="intelligent-scheduling">
|
<!-- 搜索表单 -->
|
<el-card class="search-form-card" shadow="never">
|
<el-form :inline="true" :model="searchParams" class="search-form">
|
<el-form-item label="时间范围">
|
<el-date-picker
|
v-model="searchParams.dateRange"
|
type="daterange"
|
range-separator="至"
|
start-placeholder="开始日期"
|
end-placeholder="结束日期"
|
:shortcuts="dateShortcuts"
|
/>
|
</el-form-item>
|
<!-- <el-form-item label="煤种">
|
<el-input v-model="searchParams.coalType" placeholder="请输入煤种" clearable />
|
</el-form-item>
|
<el-form-item label="排产状态">
|
<el-select v-model="searchParams.status" placeholder="请选择排产状态" clearable style="width: 200px">
|
<el-option label="全部" value="" />
|
<el-option label="待排产" value="pending" />
|
<el-option label="排产中" value="processing" />
|
<el-option label="已完成" value="completed" />
|
</el-select>
|
</el-form-item> -->
|
<el-form-item>
|
<el-button type="primary" @click="handleSearch" :loading="loading">查询</el-button>
|
<el-button @click="handleReset">重置</el-button>
|
<!-- <el-button type="success" @click="handleRandomScheduling">随机排产</el-button> -->
|
</el-form-item>
|
</el-form>
|
</el-card>
|
|
<!-- 统计卡片 -->
|
<div class="stats-cards">
|
<div class="stat-card">
|
<div class="card-icon">
|
<i class="el-icon-s-order" />
|
</div>
|
<div class="card-content">
|
<div class="card-title">总排产量</div>
|
<div class="card-value">{{ statisticsData.totalOutput.toFixed(2) }} 吨</div>
|
</div>
|
</div>
|
|
<div class="stat-card">
|
<div class="card-icon">
|
<i class="el-icon-check" />
|
</div>
|
<div class="card-content">
|
<div class="card-title">已完成排产</div>
|
<div class="card-value">{{ statisticsData.completedScheduling }} 吨</div>
|
</div>
|
</div>
|
|
<div class="stat-card">
|
<div class="card-icon">
|
<i class="el-icon-time" />
|
</div>
|
<div class="card-content">
|
<div class="card-title">待排产</div>
|
<div class="card-value">{{ statisticsData.pendingScheduling }} 吨</div>
|
</div>
|
</div>
|
|
<div class="stat-card">
|
<div class="card-icon">
|
<i class="el-icon-warning" />
|
</div>
|
<div class="card-content">
|
<div class="card-title">库存预警</div>
|
<div class="card-value">{{ statisticsData.inventoryWarning }} 项</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 库存原料列表 -->
|
<el-card class="stock-materials-card" shadow="never">
|
<template #header>
|
<div class="card-header">
|
<span>库存原料</span>
|
</div>
|
</template>
|
<el-table v-loading="loading" :data="stockMaterials" style="width: 100%">
|
<el-table-column prop="id" label="序号" width="80" type="index" align="center" />
|
<el-table-column prop="type" label="煤料类型">
|
<template #default="scope">
|
<span>{{ scope.row.type === 1 ? '成品' : scope.row.type === 2 ? '原料' : '-' }}</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="coalId" label="煤种" />
|
<el-table-column prop="inventoryQuantity" label="当前库存"></el-table-column>
|
<el-table-column prop="pendingReplenishment" label="待补库"></el-table-column>
|
<el-table-column prop="unit" label="单位" />
|
<el-table-column prop="updateTime" label="更新时间" width="180" />
|
</el-table>
|
</el-card>
|
|
<!-- 排产结果列表 -->
|
<el-card class="scheduling-results-card" shadow="never">
|
<template #header>
|
<div class="card-header">
|
<span>排产结果</span>
|
</div>
|
</template>
|
<el-table v-loading="loading" :data="schedulingResults" style="width: 100%">
|
<el-table-column prop="id" label="序号" width="60" type="index"/>
|
<el-table-column prop="schedulingNum" label="排产数量" width="100">
|
<template #default="scope">{{ scope.row.schedulingNum ? scope.row.schedulingNum.toFixed(2) : '0.00' }}</template>
|
</el-table-column>
|
<el-table-column prop="successNum" label="入库数量" width="100">
|
<template #default="scope">{{ scope.row.successNum ? scope.row.successNum.toFixed(2) : '0.00' }}</template>
|
</el-table-column>
|
<el-table-column prop="type" label="煤料类型" width="100">
|
<template #default="scope">
|
<span>{{ scope.row.type === 1 ? '成品' : scope.row.type === 2 ? '原料' : '-' }}</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="status" label="状态" width="100">
|
<template #default="scope">
|
<el-tag
|
:type="scope.row.status === 3 ? 'success' : scope.row.status === 2 ? 'warning' : 'info'"
|
size="small"
|
>
|
{{ scope.row.status === 3 ? '已报工' : scope.row.status === 2 ? '生产中' : '待生产' }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="process" label="工序" width="120" />
|
<el-table-column prop="unit" label="单位" width="80" />
|
<el-table-column prop="workHours" label="工时定额" width="100">
|
<template #default="scope">{{ scope.row.workHours ? scope.row.workHours.toFixed(2) : '0.00' }}</template>
|
</el-table-column>
|
<el-table-column prop="schedulingDate" label="排产日期" width="180" />
|
<el-table-column prop="schedulingUserName" label="排产人" width="120" />
|
<el-table-column prop="createTime" label="创建时间" width="180" />
|
<el-table-column prop="updateTime" label="更新时间" width="180" />
|
</el-table>
|
</el-card>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, onMounted } from 'vue'
|
import { ElMessage } from 'element-plus'
|
import { getProductionSchedulingInventoryList, getProductionSchedulingStatistics, getProductionSchedulingStatisticsList } from '@/api/productionScheduling'
|
|
// 搜索参数
|
const searchParams = ref({
|
dateRange: [],
|
coalType: '',
|
status: ''
|
})
|
|
// 日期快捷选项
|
const dateShortcuts = [
|
{
|
text: '今天',
|
value: () => {
|
const end = new Date()
|
const start = new Date()
|
return [start, end]
|
}
|
},
|
{
|
text: '昨天',
|
value: () => {
|
const end = new Date()
|
const start = new Date()
|
start.setTime(start.getTime() - 3600 * 1000 * 24)
|
return [start, end]
|
}
|
},
|
{
|
text: '近7天',
|
value: () => {
|
const end = new Date()
|
const start = new Date()
|
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
|
return [start, end]
|
}
|
},
|
{
|
text: '近30天',
|
value: () => {
|
const end = new Date()
|
const start = new Date()
|
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
|
return [start, end]
|
}
|
},
|
{
|
text: '本月',
|
value: () => {
|
const end = new Date()
|
const start = new Date(end.getFullYear(), end.getMonth(), 1)
|
return [start, end]
|
}
|
}
|
]
|
|
// 库存原料数据
|
const stockMaterials = ref([])
|
|
// 排产结果数据
|
const schedulingResults = ref([])
|
|
// 加载状态
|
const loading = ref(false)
|
|
// 统计数据
|
const statisticsData = ref({
|
totalOutput: 0,
|
completedScheduling: 0,
|
pendingScheduling: 0,
|
inventoryWarning: 0
|
})
|
|
// 生成排产编码
|
const generateSchedulingCode = () => {
|
const date = new Date()
|
const year = date.getFullYear()
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
const day = String(date.getDate()).padStart(2, '0')
|
const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0')
|
return `PS${year}${month}${day}${random}`
|
}
|
|
// 随机排产
|
const handleRandomScheduling = async () => {
|
try {
|
loading.value = true
|
|
// 模拟API请求延迟
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
// 获取有库存的原料
|
const availableMaterials = stockMaterials.value.filter(item => item.currentStock > 0)
|
|
if (availableMaterials.length === 0) {
|
ElMessage.warning('没有可用的库存原料')
|
return
|
}
|
|
// 随机选择原料
|
const randomMaterial = availableMaterials[Math.floor(Math.random() * availableMaterials.length)]
|
|
// 随机生成排产数量(不超过库存的50%)
|
const maxQuantity = randomMaterial.currentStock * 0.5
|
const quantity = Math.max(50, Math.random() * maxQuantity) // 至少排产50吨
|
|
// 随机选择生产线
|
const productionLines = ['生产线1', '生产线2', '生产线3']
|
const randomLine = productionLines[Math.floor(Math.random() * productionLines.length)]
|
|
// 生成新的排产计划
|
const newScheduling = {
|
code: generateSchedulingCode(),
|
productionLine: randomLine,
|
coalType: randomMaterial.coalType,
|
quantity: quantity,
|
scheduleTime: new Date().toLocaleString('zh-CN'),
|
status: 'pending',
|
remark: '随机排产生成'
|
}
|
|
// 添加到排产结果列表
|
schedulingResults.value.unshift(newScheduling)
|
|
// 更新库存数量
|
randomMaterial.currentStock -= quantity
|
|
ElMessage.success('随机排产成功')
|
} catch (error) {
|
console.error('随机排产失败:', error)
|
ElMessage.error('随机排产失败,请稍后重试')
|
} finally {
|
loading.value = false
|
}
|
}
|
|
// 获取库存原料列表
|
const getStockMaterials = async () => {
|
try {
|
loading.value = true
|
|
// 构建请求参数
|
const params = {
|
dto: {
|
entryDateStart: searchParams.value.dateRange && searchParams.value.dateRange[0] ?
|
searchParams.value.dateRange[0].toISOString().split('T')[0] : '',
|
entryDateEnd: searchParams.value.dateRange && searchParams.value.dateRange[1] ?
|
searchParams.value.dateRange[1].toISOString().split('T')[0] : ''
|
}
|
}
|
|
// 调用接口获取数据
|
const response = await getProductionSchedulingInventoryList(params)
|
|
if (response.code === 200 && response.data) {
|
// 处理返回的数据,将接口字段映射到表格需要的字段
|
stockMaterials.value = response.data
|
}
|
|
} catch (error) {
|
console.error('获取库存原料失败:', error)
|
} finally {
|
loading.value = false
|
}
|
}
|
|
// 获取统计数据
|
const getProductionStatistics = async () => {
|
try {
|
// 构建请求参数
|
const params = {
|
dto: {
|
entryDateStart: searchParams.value.dateRange && searchParams.value.dateRange[0] ?
|
searchParams.value.dateRange[0].toISOString().split('T')[0] : '',
|
entryDateEnd: searchParams.value.dateRange && searchParams.value.dateRange[1] ?
|
searchParams.value.dateRange[1].toISOString().split('T')[0] : ''
|
}
|
}
|
|
// 调用接口获取数据
|
const response = await getProductionSchedulingStatistics(params)
|
|
if (response.code === 200 && response.data) {
|
statisticsData.value = response.data
|
}
|
|
} catch (error) {
|
console.error('获取统计数据失败:', error)
|
}
|
}
|
|
// 获取排产结果列表
|
const getSchedulingResults = async () => {
|
try {
|
// 构建请求参数
|
const params = {
|
dto: {
|
entryDateStart: searchParams.value.dateRange && searchParams.value.dateRange[0] ?
|
searchParams.value.dateRange[0].toISOString().split('T')[0] : '',
|
entryDateEnd: searchParams.value.dateRange && searchParams.value.dateRange[1] ?
|
searchParams.value.dateRange[1].toISOString().split('T')[0] : ''
|
}
|
}
|
|
// 调用接口获取数据
|
const response = await getProductionSchedulingStatisticsList(params)
|
|
if (response.code === 200 && response.data) {
|
schedulingResults.value = response.data
|
}
|
|
} catch (error) {
|
console.error('获取排产结果失败:', error)
|
}
|
}
|
|
// 查询数据
|
const handleSearch = async () => {
|
await getStockMaterials()
|
getProductionStatistics()
|
getSchedulingResults()
|
}
|
|
// 重置搜索参数
|
const handleReset = () => {
|
searchParams.value = {
|
dateRange: [],
|
coalType: '',
|
status: ''
|
}
|
handleSearch()
|
}
|
|
// 初始化页面
|
onMounted(() => {
|
// 默认查询近7天数据
|
const end = new Date()
|
const start = new Date()
|
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
|
searchParams.value.dateRange = [start, end]
|
|
// 初始加载数据
|
handleSearch()
|
})
|
</script>
|
|
<style scoped>
|
.intelligent-scheduling {
|
padding: 20px;
|
}
|
|
.page-header {
|
margin-bottom: 20px;
|
}
|
|
.page-header h2 {
|
font-size: 18px;
|
font-weight: 600;
|
color: #303133;
|
}
|
|
.search-form-card {
|
margin-bottom: 20px;
|
}
|
|
.search-form {
|
display: flex;
|
align-items: center;
|
flex-wrap: wrap;
|
}
|
|
.stats-cards {
|
display: grid;
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
gap: 16px;
|
margin-bottom: 24px;
|
}
|
|
.stat-card {
|
display: flex;
|
padding: 20px;
|
background: #fff;
|
border-radius: 8px;
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
transition: transform 0.3s, box-shadow 0.3s;
|
}
|
|
.stat-card:hover {
|
transform: translateY(-2px);
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
}
|
|
.card-icon {
|
width: 48px;
|
height: 48px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
border-radius: 50%;
|
font-size: 24px;
|
margin-right: 16px;
|
}
|
|
.stat-card:nth-child(1) .card-icon {
|
background: #ECF5FF;
|
color: #409EFF;
|
}
|
|
.stat-card:nth-child(2) .card-icon {
|
background: #F6FFED;
|
color: #52C41A;
|
}
|
|
.stat-card:nth-child(3) .card-icon {
|
background: #FFF7E6;
|
color: #FAAD14;
|
}
|
|
.stat-card:nth-child(4) .card-icon {
|
background: #FFF1F0;
|
color: #F5222D;
|
}
|
|
.card-content {
|
flex: 1;
|
}
|
|
.card-title {
|
font-size: 14px;
|
color: #606266;
|
margin-bottom: 8px;
|
}
|
|
.card-value {
|
font-size: 24px;
|
font-weight: 600;
|
color: #303133;
|
}
|
|
.stock-materials-card,
|
.scheduling-results-card {
|
margin-bottom: 24px;
|
}
|
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 16px;
|
border-bottom: 1px solid #EBEEF5;
|
}
|
|
.card-header span {
|
font-weight: 600;
|
color: #303133;
|
}
|
|
.stock-warning {
|
color: #F5222D;
|
font-weight: 500;
|
}
|
|
/* 响应式布局 */
|
@media (max-width: 768px) {
|
.intelligent-scheduling {
|
padding: 10px;
|
}
|
|
.stats-cards {
|
grid-template-columns: 1fr;
|
}
|
|
.search-form {
|
flex-direction: column;
|
align-items: stretch;
|
}
|
|
.search-form .el-form-item {
|
margin-right: 0;
|
margin-bottom: 10px;
|
}
|
}
|
</style>
|