<template>
|
<div class="task-monitor">
|
<div class="page-header">
|
<h2>任务监控</h2>
|
<div class="header-actions">
|
<el-button type="primary" @click="refreshData">刷新数据</el-button>
|
<el-button type="warning" @click="simulatePLCSignal">模拟PLC入库信号</el-button>
|
<el-button type="success" @click="simulateStackerComplete">模拟堆垛机完成</el-button>
|
</div>
|
</div>
|
|
<!-- 任务状态卡片 -->
|
<div class="status-cards">
|
<el-row :gutter="20">
|
<el-col :span="6">
|
<el-card class="status-card">
|
<div class="status-content">
|
<div class="status-number">{{ statistics.totalTasks }}</div>
|
<div class="status-label">总任务数</div>
|
</div>
|
</el-card>
|
</el-col>
|
<el-col :span="6">
|
<el-card class="status-card">
|
<div class="status-content">
|
<div class="status-number">{{ statistics.newTasks }}</div>
|
<div class="status-label">新建任务</div>
|
</div>
|
</el-card>
|
</el-col>
|
<el-col :span="6">
|
<el-card class="status-card">
|
<div class="status-content">
|
<div class="status-number">{{ statistics.finishedTasks }}</div>
|
<div class="status-label">已完成任务</div>
|
</div>
|
</el-card>
|
</el-col>
|
<el-col :span="6">
|
<el-card class="status-card">
|
<div class="status-content">
|
<div class="status-number">{{ statistics.inboundTasks }}</div>
|
<div class="status-label">入库任务</div>
|
</div>
|
</el-card>
|
</el-col>
|
</el-row>
|
</div>
|
|
<!-- 任务列表 -->
|
<el-card class="task-list-card">
|
<div slot="header" class="card-header">
|
<span>任务列表</span>
|
<div class="filter-controls">
|
<el-select v-model="filterState" placeholder="任务状态" @change="filterTasks">
|
<el-option label="全部" value=""></el-option>
|
<el-option label="新建" value="100"></el-option>
|
<el-option label="完成" value="1000"></el-option>
|
</el-select>
|
<el-select v-model="filterType" placeholder="任务类型" @change="filterTasks">
|
<el-option label="全部" value=""></el-option>
|
<el-option label="入库" value="500"></el-option>
|
<el-option label="出库" value="100"></el-option>
|
</el-select>
|
</div>
|
</div>
|
|
<el-table :data="filteredTasks" style="width: 100%" v-loading="loading">
|
<el-table-column prop="taskNum" label="任务号" width="100"></el-table-column>
|
<el-table-column prop="taskType" label="任务类型" width="100">
|
<template slot-scope="scope">
|
<el-tag :type="scope.row.taskType === 500 ? 'success' : 'warning'">
|
{{ scope.row.taskType === 500 ? '入库' : '出库' }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="taskState" label="任务状态" width="100">
|
<template slot-scope="scope">
|
<el-tag :type="scope.row.taskState === 100 ? 'info' : 'success'">
|
{{ scope.row.taskState === 100 ? '新建' : '完成' }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="palletCode" label="托盘编号" width="120"></el-table-column>
|
<el-table-column prop="deviceCode" label="设备编号" width="120"></el-table-column>
|
<el-table-column prop="sourceAddress" label="起始地址" width="120"></el-table-column>
|
<el-table-column prop="targetAddress" label="目标地址" width="120"></el-table-column>
|
<el-table-column prop="currentAddress" label="当前位置" width="120"></el-table-column>
|
<el-table-column prop="dispatchertime" label="下发时间" width="160">
|
<template slot-scope="scope">
|
{{ formatDate(scope.row.dispatchertime) }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="wmsId" label="WMS任务ID" width="100"></el-table-column>
|
<el-table-column label="操作" width="120">
|
<template slot-scope="scope">
|
<el-button size="mini" @click="viewTaskDetail(scope.row)">详情</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
</el-card>
|
|
<!-- 任务执行明细 -->
|
<el-card class="task-detail-card" v-if="selectedTask">
|
<div slot="header" class="card-header">
|
<span>任务执行明细 - 任务号: {{ selectedTask.taskNum }}</span>
|
<el-button size="small" @click="selectedTask = null">关闭</el-button>
|
</div>
|
|
<el-timeline>
|
<el-timeline-item
|
v-for="(detail, index) in taskDetails"
|
:key="index"
|
:timestamp="formatDate(detail.createDate)"
|
:type="getTimelineItemType(detail)">
|
<h4>{{ detail.description }}</h4>
|
<p>状态: {{ getTaskStatusText(detail.taskState) }}</p>
|
<p>当前位置: {{ detail.currentAddress }}</p>
|
<p>下一位置: {{ detail.nextAddress }}</p>
|
<p v-if="detail.remark">备注: {{ detail.remark }}</p>
|
</el-timeline-item>
|
</el-timeline>
|
</el-card>
|
|
<!-- 模拟PLC信号对话框 -->
|
<el-dialog title="模拟PLC入库信号" :visible.sync="plcSignalDialog" width="500px">
|
<el-form :model="plcSignal" label-width="120px">
|
<el-form-item label="托盘编号">
|
<el-input v-model="plcSignal.palletCode"></el-input>
|
</el-form-item>
|
<el-form-item label="物料编号">
|
<el-input v-model="plcSignal.materialCode"></el-input>
|
</el-form-item>
|
<el-form-item label="数量">
|
<el-input-number v-model="plcSignal.quantity" :min="1"></el-input-number>
|
</el-form-item>
|
</el-form>
|
<div slot="footer" class="dialog-footer">
|
<el-button @click="plcSignalDialog = false">取消</el-button>
|
<el-button type="primary" @click="sendPLCSignal">发送信号</el-button>
|
</div>
|
</el-dialog>
|
|
<!-- 模拟堆垛机完成对话框 -->
|
<el-dialog title="模拟堆垛机完成信号" :visible.sync="stackerCompleteDialog" width="500px">
|
<el-form :model="stackerComplete" label-width="120px">
|
<el-form-item label="任务号">
|
<el-select v-model="stackerComplete.taskNum" placeholder="选择任务号">
|
<el-option
|
v-for="task in activeTasks"
|
:key="task.taskNum"
|
:label="`${task.taskNum} - ${task.palletCode}`"
|
:value="task.taskNum">
|
</el-option>
|
</el-select>
|
</el-form-item>
|
<el-form-item label="托盘编号">
|
<el-input v-model="stackerComplete.palletCode" disabled></el-input>
|
</el-form-item>
|
<el-form-item label="当前位置">
|
<el-input v-model="stackerComplete.currentAddress"></el-input>
|
</el-form-item>
|
</el-form>
|
<div slot="footer" class="dialog-footer">
|
<el-button @click="stackerCompleteDialog = false">取消</el-button>
|
<el-button type="primary" @click="sendStackerCompleteSignal">发送信号</el-button>
|
</div>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script>
|
import axios from 'axios'
|
|
export default {
|
name: 'TaskMonitor',
|
data() {
|
return {
|
loading: false,
|
tasks: [],
|
filteredTasks: [],
|
filterState: '',
|
filterType: '',
|
selectedTask: null,
|
taskDetails: [],
|
plcSignalDialog: false,
|
stackerCompleteDialog: false,
|
statistics: {
|
totalTasks: 0,
|
newTasks: 0,
|
finishedTasks: 0,
|
inboundTasks: 0
|
},
|
plcSignal: {
|
palletCode: '',
|
materialCode: '',
|
quantity: 1
|
},
|
stackerComplete: {
|
taskNum: null,
|
palletCode: '',
|
currentAddress: ''
|
}
|
}
|
},
|
computed: {
|
activeTasks() {
|
return this.tasks.filter(t => t.taskState === 100)
|
}
|
},
|
watch: {
|
'stackerComplete.taskNum'(val) {
|
if (val) {
|
const task = this.tasks.find(t => t.taskNum === val)
|
if (task) {
|
this.stackerComplete.palletCode = task.palletCode
|
this.stackerComplete.currentAddress = task.targetAddress
|
}
|
}
|
}
|
},
|
mounted() {
|
this.loadTasks()
|
// 每15秒自动刷新数据
|
setInterval(() => {
|
this.loadTasks()
|
}, 15000)
|
},
|
methods: {
|
async loadTasks() {
|
this.loading = true
|
try {
|
const response = await axios.get('/api/TaskInfo/GetTaskList')
|
if (response.data.status) {
|
this.tasks = response.data.data || []
|
this.filteredTasks = [...this.tasks]
|
this.calculateStatistics()
|
|
// 如果有选中的任务,刷新任务详情
|
if (this.selectedTask) {
|
this.viewTaskDetail(this.selectedTask)
|
}
|
}
|
} catch (error) {
|
this.$message.error('加载任务列表失败')
|
} finally {
|
this.loading = false
|
}
|
},
|
calculateStatistics() {
|
this.statistics.totalTasks = this.tasks.length
|
this.statistics.newTasks = this.tasks.filter(t => t.taskState === 100).length
|
this.statistics.finishedTasks = this.tasks.filter(t => t.taskState === 1000).length
|
this.statistics.inboundTasks = this.tasks.filter(t => t.taskType === 500).length
|
},
|
filterTasks() {
|
this.filteredTasks = this.tasks.filter(task => {
|
const stateMatch = !this.filterState || task.taskState.toString() === this.filterState
|
const typeMatch = !this.filterType || task.taskType.toString() === this.filterType
|
return stateMatch && typeMatch
|
})
|
},
|
refreshData() {
|
this.loadTasks()
|
this.$message.success('数据已刷新')
|
},
|
async viewTaskDetail(task) {
|
this.selectedTask = task
|
try {
|
// 这里应该调用获取任务明细的API
|
// 模拟数据
|
this.taskDetails = [
|
{
|
taskId: task.taskId,
|
taskNum: task.taskNum,
|
taskState: 100,
|
currentAddress: task.sourceAddress,
|
nextAddress: task.targetAddress,
|
description: '任务创建',
|
remark: '任务开始执行',
|
createDate: task.dispatchertime
|
}
|
]
|
|
if (task.taskState === 1000) {
|
this.taskDetails.push({
|
taskId: task.taskId,
|
taskNum: task.taskNum,
|
taskState: 1000,
|
currentAddress: task.targetAddress,
|
nextAddress: task.targetAddress,
|
description: '任务完成',
|
remark: '堆垛机任务执行完成',
|
createDate: new Date(task.dispatchertime).getTime() + 60000 // 模拟1分钟后完成
|
})
|
}
|
} catch (error) {
|
this.$message.error('获取任务明细失败')
|
}
|
},
|
simulatePLCSignal() {
|
this.plcSignal = {
|
palletCode: `PLT${new Date().getTime().toString().substr(-6)}`,
|
materialCode: `MAT${new Date().getTime().toString().substr(-6)}`,
|
quantity: 1
|
}
|
this.plcSignalDialog = true
|
},
|
async sendPLCSignal() {
|
try {
|
// 这里应该调用模拟PLC信号的API
|
this.$message.success('PLC入库信号已发送')
|
this.plcSignalDialog = false
|
|
// 延迟2秒后刷新数据,模拟信号处理时间
|
setTimeout(() => {
|
this.loadTasks()
|
}, 2000)
|
} catch (error) {
|
this.$message.error('发送PLC信号失败')
|
}
|
},
|
simulateStackerComplete() {
|
if (this.activeTasks.length === 0) {
|
this.$message.warning('没有可完成的任务')
|
return
|
}
|
|
this.stackerComplete = {
|
taskNum: null,
|
palletCode: '',
|
currentAddress: ''
|
}
|
this.stackerCompleteDialog = true
|
},
|
async sendStackerCompleteSignal() {
|
if (!this.stackerComplete.taskNum) {
|
this.$message.warning('请选择要完成的任务')
|
return
|
}
|
|
try {
|
// 这里应该调用模拟堆垛机完成信号的API
|
this.$message.success('堆垛机完成信号已发送')
|
this.stackerCompleteDialog = false
|
|
// 延迟2秒后刷新数据,模拟信号处理时间
|
setTimeout(() => {
|
this.loadTasks()
|
}, 2000)
|
} catch (error) {
|
this.$message.error('发送堆垛机完成信号失败')
|
}
|
},
|
formatDate(dateString) {
|
if (!dateString) return '-'
|
return new Date(dateString).toLocaleString()
|
},
|
getTaskStatusText(status) {
|
switch (status) {
|
case 100: return '新建'
|
case 1000: return '完成'
|
default: return `状态${status}`
|
}
|
},
|
getTimelineItemType(detail) {
|
switch (detail.taskState) {
|
case 100: return 'primary'
|
case 1000: return 'success'
|
default: return 'info'
|
}
|
}
|
}
|
}
|
</script>
|
|
<style scoped>
|
.task-monitor {
|
padding: 20px;
|
}
|
|
.page-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 20px;
|
}
|
|
.status-cards {
|
margin-bottom: 20px;
|
}
|
|
.status-card {
|
text-align: center;
|
}
|
|
.status-content {
|
padding: 20px;
|
}
|
|
.status-number {
|
font-size: 32px;
|
font-weight: bold;
|
color: #409EFF;
|
margin-bottom: 8px;
|
}
|
|
.status-label {
|
font-size: 14px;
|
color: #666;
|
}
|
|
.task-list-card {
|
margin-bottom: 20px;
|
}
|
|
.task-detail-card {
|
margin-bottom: 20px;
|
}
|
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
.filter-controls {
|
display: flex;
|
gap: 10px;
|
}
|
</style>
|