二级修改

This commit is contained in:
2026-01-05 14:57:22 +08:00
parent 4480cfdfcb
commit c11805aff1
2 changed files with 200 additions and 12 deletions

View File

@@ -17,6 +17,50 @@
</el-button> </el-button>
</div> </div>
<!-- 计划队列 -->
<div v-loading="planQueueLoading" class="plan-queue-section">
<div class="section-header">
<i class="el-icon-s-order"></i>
<span>生产队列</span>
<el-badge :value="sortedPlanQueue.length" class="tab-badge" style="margin-left: 10px;" />
</div>
<div class="plan-list">
<div
v-for="plan in sortedPlanQueue"
:key="plan.id"
class="plan-item"
:class="{
'plan-item-producing': plan.status === 'PRODUCING',
'plan-item-ready': plan.status === 'READY',
'plan-item-new': plan.status === 'NEW'
}"
>
<div class="plan-status">
<span class="status-dot" :class="{
'status-producing': plan.status === 'PRODUCING',
'status-ready': plan.status === 'READY',
'status-new': plan.status === 'NEW'
}"></span>
<el-tag
:type="plan.status === 'PRODUCING' ? 'success' :
plan.status === 'READY' ? 'primary' : 'info'"
size="mini"
>
{{ plan.status === 'PRODUCING' ? '生产中' :
plan.status === 'READY' ? '就绪' : '新建' }}
</el-tag>
</div>
<div class="plan-content">
<span class="plan-no">{{ plan.planid }}</span>
<span class="coil-no">{{ plan.coilid }}</span>
</div>
</div>
<div v-if="sortedPlanQueue.length === 0 && !planQueueLoading" class="empty-text">
暂无生产计划
</div>
</div>
</div>
<!-- 卡片列表 --> <!-- 卡片列表 -->
<div v-loading="loading" class="card-grid-container"> <div v-loading="loading" class="card-grid-container">
<el-row :gutter="20"> <el-row :gutter="20">
@@ -60,10 +104,19 @@
type="primary" type="primary"
size="mini" size="mini"
icon="el-icon-s-promotion" icon="el-icon-s-promotion"
@click="handleSend(setup)" @click="handleSendCurrent(setup)"
:loading="setup.sending" :loading="setup.sending"
> >
下发 当前计划下发
</el-button>
<el-button
type="success"
size="mini"
icon="el-icon-right"
@click="handleSendNext(setup)"
:loading="setup.sendingNext"
>
下一计划下发
</el-button> </el-button>
</div> </div>
</div> </div>
@@ -103,6 +156,7 @@
import { listSetup } from '@/api/business/setup' import { listSetup } from '@/api/business/setup'
import { createSendJob, executeSendJob } from '@/api/l2/sendJob' import { createSendJob, executeSendJob } from '@/api/l2/sendJob'
import { getLastSuccess } from '@/api/l2/sendTemplate' import { getLastSuccess } from '@/api/l2/sendTemplate'
import { listPlan } from '@/api/l2/plan'
// 传动字段定义(中文界面,贴合工业场景) // 传动字段定义(中文界面,贴合工业场景)
const DRIVE_FIELDS = [ const DRIVE_FIELDS = [
@@ -165,6 +219,8 @@ export default {
loading: false, loading: false,
lastSuccess: null, lastSuccess: null,
setups: [], setups: [],
planQueueLoading: false,
planQueue: [],
driveFields: DRIVE_FIELDS, driveFields: DRIVE_FIELDS,
driveAddress: { ...DRIVE_ADDRESS } driveAddress: { ...DRIVE_ADDRESS }
} }
@@ -172,8 +228,53 @@ export default {
created() { created() {
this.reload() this.reload()
}, },
computed: {
// 生产队列(不含已完成),按优先级排序:生产中 > 就绪 > 新建
sortedPlanQueue() {
const statusPriority = { PRODUCING: 1, READY: 2, NEW: 3 }
return (this.planQueue || []).slice().sort((a, b) => {
const pa = statusPriority[a.status] || 999
const pb = statusPriority[b.status] || 999
return pa - pb
})
},
// 下一计划队列中第一个非生产中计划READY/NEW若没有则取队列第一个
nextPlan() {
const list = this.sortedPlanQueue
if (!list.length) return null
const next = list.find(p => p.status !== 'PRODUCING')
return next || list[0]
}
},
methods: { methods: {
// 获取生产计划队列
async getPlanQueue() {
this.planQueueLoading = true
try {
// 查询状态为 PRODUCING, READY, NEW 的计划
const res = await listPlan({
status: 'PRODUCING,READY,NEW',
pageSize: 100, // 获取足够多的计划
pageNum: 1
})
this.planQueue = res.data || []
} catch (e) {
console.error('获取计划队列失败:', e)
this.$message.error('获取计划队列失败')
} finally {
this.planQueueLoading = false
}
},
async reload() { async reload() {
// 同时加载计划队列和设置
await Promise.all([
this.getPlanQueue(),
this.loadSetups()
])
},
async loadSetups() {
this.loading = true this.loading = true
try { try {
// 1. 获取传动模块上次成功下发数据 // 1. 获取传动模块上次成功下发数据
@@ -204,7 +305,8 @@ export default {
return { return {
...s, ...s,
params, params,
sending: false sending: false,
sendingNext: false
} }
}) })
} catch (e) { } catch (e) {
@@ -242,7 +344,7 @@ export default {
return new Date(t).toLocaleString() return new Date(t).toLocaleString()
}, },
handleSend(setup) { handleSendCurrent(setup) {
this.$confirm( this.$confirm(
`确认要下发【${setup.coilid || '-'}】钢卷的传动参数吗?`, `确认要下发【${setup.coilid || '-'}】钢卷的传动参数吗?`,
'提示', '提示',
@@ -254,29 +356,53 @@ export default {
).then(() => this.doSend(setup)).catch(() => {}) ).then(() => this.doSend(setup)).catch(() => {})
}, },
async doSend(setup) { handleSendNext(setup) {
setup.sending = true if (!this.nextPlan) {
this.$message.warning('暂无下一计划')
return
}
const plan = this.nextPlan
this.$confirm(
`确认要按下一计划【${plan.coilid || '-'}】下发传动参数吗?`,
'提示',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => this.doSendNextPlan(setup, plan)).catch(() => {})
},
async doSend(setup, plan) {
const isNextPlan = !!plan
if (isNextPlan) {
setup.sendingNext = true
} else {
setup.sending = true
}
try { try {
const items = this.driveFields.map(f => ({ const items = this.driveFields.map(f => ({
paramCode: f.key, paramCode: f.key,
address: this.driveAddress[f.key], address: this.driveAddress[f.key],
valueRaw: String(setup.params[f.key] || ''), valueRaw: String(setup.params[f.key] || ''),
setTime: new Date() setTime: new Date()
})).filter(it => !!it.address) // 过滤无OPC地址的项 })).filter(it => !!it.address)
if (!items.length) { if (!items.length) {
this.$message.warning('OPC地址未配置无可下发内容') this.$message.warning('OPC地址未配置无可下发内容')
return return
} }
const bizKey = isNextPlan ? plan.coilid : setup.coilid
const dto = { const dto = {
deviceName: 'CGL_LINE_1', deviceName: 'CGL_LINE_1',
bizKey: setup.coilid, bizKey: bizKey,
groups: [ groups: [
{ {
groupNo: 1, groupNo: 1,
groupType: 'DRIVE', groupType: 'DRIVE',
groupName: `传动参数_${setup.coilid || ''}`, groupName: `传动参数_${bizKey || ''}`,
items items
} }
] ]
@@ -294,8 +420,16 @@ export default {
console.error(e) console.error(e)
this.$message.error(e.message || '下发失败') this.$message.error(e.message || '下发失败')
} finally { } finally {
setup.sending = false if (isNextPlan) {
setup.sendingNext = false
} else {
setup.sending = false
}
} }
},
doSendNextPlan(setup, plan) {
this.doSend(setup, plan)
} }
} }
} }
@@ -303,6 +437,60 @@ export default {
<style scoped> <style scoped>
.toolbar { margin-bottom: 20px; display:flex; flex-wrap:wrap; gap:8px; align-items:center; } .toolbar { margin-bottom: 20px; display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
.plan-queue-section {
margin-bottom: 20px;
background: #ffffff;
border: 1px solid #dcdfe6;
border-radius: 4px;
}
.section-header {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 15px;
background: #f5f7fa;
border-bottom: 1px solid #e4e7ed;
font-weight: 600;
color: #303133;
}
.plan-list {
padding: 10px;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.plan-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
background: #ffffff;
border: 1px solid #e4e7ed;
border-left: 3px solid #409eff;
border-radius: 4px;
min-width: 220px;
}
.plan-item-producing { border-left-color: #67c23a; }
.plan-item-ready { border-left-color: #409eff; }
.plan-item-new { border-left-color: #909399; }
.plan-status { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.plan-content { display: flex; flex-direction: column; gap: 2px; }
.plan-no { font-weight: 600; color: #409eff; }
.coil-no { font-size: 12px; color: #606266; }
.status-dot { width: 10px; height: 10px; border-radius: 50%; }
.status-producing { background: #67c23a; box-shadow: 0 0 6px rgba(103, 194, 58, 0.6); }
.status-ready { background: #409eff; box-shadow: 0 0 6px rgba(64, 158, 255, 0.5); }
.status-new { background: #909399; box-shadow: 0 0 6px rgba(144, 147, 153, 0.4); }
.empty-text { padding: 10px; color: #909399; }
.card-grid-container { min-height: 300px; } .card-grid-container { min-height: 300px; }
.card-col { margin-bottom: 20px; } .card-col { margin-bottom: 20px; }
.parameter-card .card-header { display:flex; justify-content:space-between; align-items:center; } .parameter-card .card-header { display:flex; justify-content:space-between; align-items:center; }
@@ -310,7 +498,7 @@ export default {
.card-title-row { margin-bottom: 4px; } .card-title-row { margin-bottom: 4px; }
.card-title { font-weight: 600; font-size: 16px; } .card-title { font-weight: 600; font-size: 16px; }
.card-subtitle { font-size: 12px; color: #909399; display: flex; gap: 12px; } .card-subtitle { font-size: 12px; color: #909399; display: flex; gap: 12px; }
.header-right { flex-shrink: 0; margin-left: 16px; } .header-right { flex-shrink: 0; margin-left: 16px; display: flex; gap: 8px; align-items: center; }
.last-send-time { font-size: 12px; color:#909399; margin-right:16px; } .last-send-time { font-size: 12px; color:#909399; margin-right:16px; }
.empty-data { margin-top: 20px; } .empty-data { margin-top: 20px; }
</style> </style>

View File

@@ -177,7 +177,7 @@ export default {
templateItems() { templateItems() {
if (!this.template || !Array.isArray(this.template.items)) return [] if (!this.template || !Array.isArray(this.template.items)) return []
return [...this.template.items] return [...this.template.items]
.filter(i => i.enabled === undefined || i.enabled === 1) .filter(i => i.enabled === undefined || i.enabled === 1 || i.enabled === null)
.sort((a, b) => (a.itemNo || 0) - (b.itemNo || 0)) .sort((a, b) => (a.itemNo || 0) - (b.itemNo || 0))
}, },