This commit is contained in:
2026-04-01 09:08:01 +08:00
parent 68cf01dd6f
commit 371b3eb67a
6 changed files with 1136 additions and 98 deletions

View File

@ -0,0 +1,63 @@
import request from '@/utils/request'
export function listBoardScreen(query) {
return request({
url: '/mes/board/screen/list',
method: 'get',
params: query
})
}
export function getBoardScreen(screenId) {
return request({
url: '/mes/board/screen/' + screenId,
method: 'get'
})
}
export function addBoardScreen(data) {
return request({
url: '/mes/board/screen',
method: 'post',
data
})
}
export function updateBoardScreen(data) {
return request({
url: '/mes/board/screen',
method: 'put',
data
})
}
export function delBoardScreen(screenId) {
return request({
url: '/mes/board/screen/' + screenId,
method: 'delete'
})
}
export function getBoardScreenConfig(screenId) {
return request({
url: '/mes/board/screen/config',
method: 'get',
params: { screenId }
})
}
export function saveBoardScreenConfig(data) {
return request({
url: '/mes/board/screen/config',
method: 'post',
data
})
}
export function resetBoardScreenConfig(screenId) {
return request({
url: '/mes/board/screen/config',
method: 'delete',
params: { screenId }
})
}

View File

@ -0,0 +1,9 @@
import request from '@/utils/request'
export function getWorkshopBoardData(screenCode) {
return request({
url: '/mes/board/workshop/data',
method: 'get',
params: { screenCode }
})
}

View File

@ -0,0 +1,130 @@
import request from '@/utils/request'
const DEFAULT_SCREEN_CODE = 'WORKSHOP_BOARD_DEFAULT'
function createEmptyScreenBindingConfig(screenCode = DEFAULT_SCREEN_CODE) {
return {
screenCode,
workshopId: null,
workshopCode: '',
workshopName: '',
updatedAt: '',
basicBindings: [],
machineConfigs: []
}
}
function toUiBinding(binding) {
return {
bindingId: binding.bindingId,
screenCode: binding.screenCode,
workshopId: binding.workshopId,
workshopCode: binding.workshopCode,
workshopName: binding.workshopName,
sourceType: binding.sourceType || 'FIXED',
pointId: binding.pointId,
pointCode: binding.pointCode || '',
pointName: binding.pointName || '',
fixedValue: binding.fixedValue || '',
defaultValue: binding.defaultValue || '',
displayUnit: binding.displayUnit || '',
precisionDigit: binding.precisionDigit,
sortNum: binding.sortNum,
statusClass: binding.statusClass || 'is-running',
enableFlag: binding.enableFlag || 'Y',
key: binding.bindingKey,
label: binding.bindingName
}
}
function toUiConfig(data) {
const emptyConfig = createEmptyScreenBindingConfig(data.screenCode || DEFAULT_SCREEN_CODE)
return {
...emptyConfig,
screenCode: data.screenCode || emptyConfig.screenCode,
workshopId: data.workshopId === undefined ? emptyConfig.workshopId : data.workshopId,
workshopCode: data.workshopCode || emptyConfig.workshopCode,
workshopName: data.workshopName || emptyConfig.workshopName,
updatedAt: data.updatedAt || emptyConfig.updatedAt,
basicBindings: (data.basicBindings || []).map(toUiBinding),
machineConfigs: (data.machineConfigs || []).map(item => ({
id: item.id,
statusClass: item.statusClass || 'is-running',
bindings: (item.bindings || []).map(toUiBinding)
}))
}
}
function toApiBinding(binding, scope, machineId, statusClass) {
return {
bindingId: binding.bindingId,
screenCode: binding.screenCode,
workshopId: binding.workshopId,
workshopCode: binding.workshopCode,
workshopName: binding.workshopName,
bindingScope: scope,
machineId,
bindingKey: binding.key,
bindingName: binding.label,
sourceType: binding.sourceType,
pointId: binding.pointId,
pointCode: binding.pointCode,
pointName: binding.pointName,
fixedValue: binding.fixedValue,
defaultValue: binding.defaultValue,
displayUnit: binding.displayUnit,
precisionDigit: binding.precisionDigit,
statusClass: statusClass || binding.statusClass,
sortNum: binding.sortNum,
enableFlag: binding.enableFlag,
remark: binding.remark
}
}
function toApiConfig(config) {
return {
screenCode: config.screenCode,
workshopId: config.workshopId,
workshopCode: config.workshopCode,
workshopName: config.workshopName,
basicBindings: (config.basicBindings || []).map(item => toApiBinding(item, 'BASIC', 0, item.statusClass)),
machineConfigs: (config.machineConfigs || []).map(machine => ({
id: machine.id,
statusClass: machine.statusClass,
bindings: (machine.bindings || []).map(item => toApiBinding(item, 'MACHINE', machine.id, machine.statusClass))
}))
}
}
export function getScreenBindingConfig(screenCode) {
return request({
url: '/mes/md/screenbinding/config',
method: 'get',
params: { screenCode }
}).then(response => ({
...response,
data: toUiConfig(response.data || {})
}))
}
export function saveScreenBindingConfig(data) {
return request({
url: '/mes/md/screenbinding/config',
method: 'post',
data: toApiConfig(data)
}).then(response => ({
...response,
data: toUiConfig(response.data || {})
}))
}
export function resetScreenBindingConfig(screenCode) {
return request({
url: '/mes/md/screenbinding/config',
method: 'delete',
params: { screenCode }
}).then(response => ({
...response,
data: toUiConfig(response.data || {})
}))
}

View File

@ -3,11 +3,11 @@
<div class="board-header"> <div class="board-header">
<div class="header-cell"> <div class="header-cell">
<div class="header-label">看板编号</div> <div class="header-label">看板编号</div>
<div class="header-value">HY-CJKB-001</div> <div class="header-value">{{ header.screenCode }}</div>
</div> </div>
<div class="header-cell"> <div class="header-cell">
<div class="header-label">管理责任人</div> <div class="header-label">管理责任人</div>
<div class="header-value">张主管</div> <div class="header-value">{{ header.ownerName }}</div>
</div> </div>
<div class="header-cell"> <div class="header-cell">
<div class="header-label">更新时间</div> <div class="header-label">更新时间</div>
@ -290,99 +290,62 @@
</template> </template>
<script> <script>
function createMachine(id, name, model, statusText, statusClass, mode, runtime, output, workDate, batchNo, planQty, progress, productModel, color, remark, nextModel) { import { getWorkshopBoardData } from '@/api/mes/board/workshop'
import { listRepair } from '@/api/mes/dv/repair'
import { listCheckplan } from '@/api/mes/dv/checkplan'
const DEFAULT_SCREEN_CODE = 'WORKSHOP_BOARD_DEFAULT'
function createEmptyWorkshopBoardData() {
return { return {
id, header: {},
name, summary: {},
model, faultList: [],
statusText, maintenanceList: [],
statusClass, machineList: [],
mode, detailRealtime: [],
runtime, detailSetting: [],
output, detailProduction: []
workDate,
batchNo,
planQty,
progress,
productModel,
color,
remark,
nextModel
} }
} }
export default { export default {
name: 'WorkshopBoard', name: 'WorkshopBoard',
data() {
const boardData = createEmptyWorkshopBoardData()
return {
detailVisible: false,
boardTimer: null,
updateTime: '',
previousTitle: '',
activeMachine: {},
header: boardData.header,
faultTableRows: 8,
planTableRows: 8,
summary: boardData.summary,
faultList: boardData.faultList,
maintenanceList: boardData.maintenanceList,
machineList: boardData.machineList,
detailRealtime: boardData.detailRealtime,
detailSetting: boardData.detailSetting,
detailProduction: boardData.detailProduction
}
},
computed: { computed: {
orderedMachineList() { orderedMachineList() {
return [...this.machineList].sort((a, b) => a.id - b.id) return [...this.machineList].sort((a, b) => a.id - b.id)
} },
}, workshopMachineCodeSet() {
data() { return new Set(
return { (this.machineList || [])
detailVisible: false, .map(item => this.normalizeMachineCode(item && item.name))
updateTime: '', .filter(Boolean)
clockTimer: null, )
previousTitle: '',
activeMachine: {},
faultTableRows: 8,
planTableRows: 8,
summary: {
total: 17,
online: 15,
running: 15,
stop: 2,
startRate: '88.2%'
},
faultList: [
{ deviceNo: '8#', faultTime: '09:25', faultDesc: '模温异常', owner: '李工', recoverTime: '10:10' },
{ deviceNo: '16#', faultTime: '11:40', faultDesc: '液压压力波动', owner: '王工', recoverTime: '13:00' },
{ deviceNo: '5#', faultTime: '13:18', faultDesc: '送料传感器告警', owner: '赵工', recoverTime: '13:45' }
],
maintenanceList: [
{ deviceNo: '2#', project: '保养润滑系统', planTime: '2026-03-23 16:00', status: '待执行' },
{ deviceNo: '11#', project: '校验温控模块', planTime: '2026-03-23 18:00', status: '待执行' },
{ deviceNo: '14#', project: '更换过滤组件', planTime: '2026-03-24 09:00', status: '已排期' }
],
machineList: [
createMachine(6, '6#', 'MA2500/1000G', '状态', 'is-running', '运行', '13小时', '600个', '2026.3.16-3.18', '25110914', '5000个', '30%', '23W 中间盖', '7037C 灰', '重点订单', '159BB'),
createMachine(12, '12#', 'MA1600/540G', '状态', 'is-running', '运行', '8小时', '305个', '2026.3.23-3.25', '25110953', '2800个', '27%', '中盖件', '灰色', '正常生产', 'J20R'),
createMachine(5, '5#', 'MA2500 2S/1000', '状态', 'is-running', '运行', '6小时', '210个', '2026.3.22-3.24', '25110937', '3600个', '19%', '卡扣件', '7035 灰', '待维修', '32QR'),
createMachine(11, '11#', 'MA3600/2250G', '状态', 'is-running', '运行', '15小时', '720个', '2026.3.23-3.25', '25110950', '6500个', '33%', '大型壳体', '深蓝', '计划保养', 'LT9X'),
createMachine(17, '17#', 'MA1600/540G', '状态', 'is-running', '运行', '12小时', '510个', '2026.3.23-3.25', '25110961', '3600个', '53%', '面框件', '冷白', '正常生产', 'AA17'),
createMachine(4, '4#', 'MA2500/1000G', '状态', 'is-running', '运行', '10小时', '488个', '2026.3.21-3.22', '25110931', '4500个', '61%', '28W 外壳', '米白', '正常生产', '26WA'),
createMachine(10, '10#', 'MA2500/1000G', '状态', 'is-running', '运行', '13小时', '610个', '2026.3.23-3.24', '25110946', '5100个', '44%', '挡板件', '黑色', '正常生产', 'M22A'),
createMachine(16, '16#', '570', '状态', 'is-running', '运行', '5小时', '190个', '2026.3.23-3.25', '25110959', '2400个', '18%', '小型卡件', '浅灰', '液压检修', 'Q30E'),
createMachine(3, '3#', 'MA2500/1000G', '状态', 'is-running', '运行', '8小时', '360个', '2026.3.20-3.22', '25110925', '3000个', '58%', '17W 支架', '银色', '等待换模', '90KT'),
createMachine(9, '9#', 'MA2500/1000G', '状态', 'is-running', '运行', '14小时', '680个', '2026.3.23-3.24', '25110945', '5400个', '52%', '导流板', '银灰', '正常生产', 'C88L'),
createMachine(15, '15#', 'MA1600/540G', '状态', 'is-running', '运行', '11小时', '460个', '2026.3.23-3.25', '25110957', '3500个', '41%', '转接件', '深灰', '正常生产', 'UV06'),
createMachine(2, '2#', 'MA2500/1000G', '状态', 'is-running', '运行', '11小时', '520个', '2026.3.18-3.20', '25110918', '4200个', '42%', '26W 面板', '黑色', '夜班优先', '188AX'),
createMachine(8, '8#', 'MA2500/1000G', '状态', 'is-running', '运行', '9小时', '430个', '2026.3.22-3.24', '25110941', '4600个', '36%', '封边件', '白色', '温控波动', 'B71S'),
createMachine(14, '14#', 'MA1600/540G', '状态', 'is-running', '运行', '10小时', '395个', '2026.3.23-3.25', '25110955', '3200个', '38%', '扣合件', '亮黑', '正常生产', 'R81N'),
createMachine(1, '1#', 'MA2500/1000G', '状态', 'is-running', '运行', '13小时', '600个', '2026.3.16-3.18', '25110914', '5000个', '30%', '23W 中间盖', '7037C 灰', '重点订单', '159BB'),
createMachine(7, '7#', 'MA2500/1000G', '状态', 'is-running', '运行', '4小时', '120个', '2026.3.22-3.24', '25110940', '3000个', '12%', '结构件', '深灰', '人工调试', 'X09M'),
createMachine(13, '13#', 'MA1600/540G', '状态', 'is-running', '运行', '7小时', '280个', '2026.3.23-3.25', '25110954', '2600个', '31%', '面罩件', '暖白', '待料', 'PK11')
],
detailRealtime: [
{ name: '温度一段A15', current: '186℃', currentTime: '14:20:15', target: '185℃', targetTime: '14:05:00', orderValue: '188℃', orderTime: '08:30:00' },
{ name: '温度二段A16', current: '192℃', currentTime: '14:20:15', target: '190℃', targetTime: '14:05:00', orderValue: '192℃', orderTime: '08:30:00' },
{ name: '射胶压力A243', current: '12.3MPa', currentTime: '14:20:10', target: '12.0MPa', targetTime: '14:00:00', orderValue: '12.5MPa', orderTime: '08:30:00' }
],
detailSetting: [
{ name: '开模行程A060', setting: '320mm', settingTime: '13:15:00', orderValue: '325mm', orderTime: '08:30:00' },
{ name: '锁模压力A061', setting: '86%', settingTime: '13:15:00', orderValue: '85%', orderTime: '08:30:00' },
{ name: '保压时间A062', setting: '4.5s', settingTime: '13:15:00', orderValue: '4.8s', orderTime: '08:30:00' }
],
detailProduction: [
{ name: '上模循环时A37', value: '28.6s', collectTime: '14:20:11' },
{ name: '一模产出数', value: '2', collectTime: '14:20:11' },
{ name: '累计良品数', value: '598', collectTime: '14:20:11' }
]
} }
}, },
created() { created() {
this.refreshClock() this.loadBoardData()
this.clockTimer = setInterval(this.refreshClock, 1000) this.boardTimer = setInterval(this.loadBoardData, 30000)
this.activeMachine = this.machineList[0] this.activeMachine = this.machineList[0]
}, },
mounted() { mounted() {
@ -390,13 +353,111 @@ export default {
this.tryEnterFullscreen() this.tryEnterFullscreen()
}, },
beforeDestroy() { beforeDestroy() {
if (this.clockTimer) { if (this.boardTimer) {
clearInterval(this.clockTimer) clearInterval(this.boardTimer)
this.clockTimer = null this.boardTimer = null
} }
this.restoreTitle() this.restoreTitle()
}, },
methods: { methods: {
loadBoardData() {
const screenCode = this.$route && this.$route.query && this.$route.query.screenCode
? this.$route.query.screenCode
: DEFAULT_SCREEN_CODE
getWorkshopBoardData(screenCode).then(response => {
this.applyBoardData(response.data)
this.loadFaultList()
this.loadMaintenanceList()
}).catch(() => {
this.applyBoardData(createEmptyWorkshopBoardData())
this.faultList = []
this.maintenanceList = []
})
},
loadFaultList() {
if (!this.workshopMachineCodeSet.size) {
this.faultList = []
return
}
listRepair({
pageNum: 1,
pageSize: this.faultTableRows * 5
}).then(response => {
const rows = (response && response.rows) || []
this.faultList = rows
.filter(item => this.isWorkshopMachine(item && item.machineryCode))
.slice(0, this.faultTableRows)
.map(item => ({
deviceNo: item.machineryCode || '--',
faultTime: item.requireDate || '--',
faultDesc: item.repairName || item.remark || '--',
owner: item.acceptedName || item.acceptedBy || '--',
recoverTime: item.finishDate || '--'
}))
}).catch(() => {
this.faultList = []
})
},
loadMaintenanceList() {
if (!this.workshopMachineCodeSet.size) {
this.maintenanceList = []
return
}
listCheckplan({
pageNum: 1,
pageSize: this.planTableRows * 5
}).then(response => {
const rows = (response && response.rows) || []
this.maintenanceList = rows
.filter(item => this.hasWorkshopMachine(item && item.machineryCodes))
.slice(0, this.planTableRows)
.map(item => ({
deviceNo: item.machineryCodes || '--',
project: item.planName || '--',
planTime: this.formatPlanTime(item),
status: item.status || '--'
}))
}).catch(() => {
this.maintenanceList = []
})
},
normalizeMachineCode(code) {
return code ? String(code).trim().toUpperCase() : ''
},
isWorkshopMachine(code) {
return this.workshopMachineCodeSet.has(this.normalizeMachineCode(code))
},
hasWorkshopMachine(codes) {
return String(codes || '')
.split(',')
.map(code => this.normalizeMachineCode(code))
.filter(Boolean)
.some(code => this.workshopMachineCodeSet.has(code))
},
formatPlanTime(item) {
const startDate = item && item.startDate ? item.startDate : ''
const endDate = item && item.endDate ? item.endDate : ''
if (startDate && endDate) {
return `${startDate} ~ ${endDate}`
}
return startDate || endDate || '--'
},
applyBoardData(boardData) {
const normalizedBoardData = boardData || createEmptyWorkshopBoardData()
this.header = normalizedBoardData.header || {}
this.summary = normalizedBoardData.summary || {}
this.machineList = normalizedBoardData.machineList || []
this.detailRealtime = normalizedBoardData.detailRealtime || []
this.detailSetting = normalizedBoardData.detailSetting || []
this.detailProduction = normalizedBoardData.detailProduction || []
this.refreshClock()
if (!this.activeMachine || !this.activeMachine.id) {
this.activeMachine = this.machineList[0] || {}
} else {
const currentMachine = this.machineList.find(item => item.id === this.activeMachine.id)
this.activeMachine = currentMachine || this.machineList[0] || {}
}
},
applyDisplayTitle() { applyDisplayTitle() {
const mode = this.$route && this.$route.query ? this.$route.query.displayMode : '' const mode = this.$route && this.$route.query ? this.$route.query.displayMode : ''
if (mode !== 'fullscreen') { if (mode !== 'fullscreen') {

View File

@ -36,9 +36,6 @@
<el-col :span="1.5"> <el-col :span="1.5">
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete" v-hasPermi="['mes:md:measurepoint:remove']">删除</el-button> <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete" v-hasPermi="['mes:md:measurepoint:remove']">删除</el-button>
</el-col> </el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-refresh" size="mini" @click="refreshLatest" v-hasPermi="['mes:md:measurepoint:query']">刷新最新值</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row> </el-row>
@ -53,12 +50,6 @@
<el-table-column label="车间" align="center" prop="workshopName" min-width="120" /> <el-table-column label="车间" align="center" prop="workshopName" min-width="120" />
<el-table-column label="设备编码" align="center" prop="deviceCode" min-width="120" /> <el-table-column label="设备编码" align="center" prop="deviceCode" min-width="120" />
<el-table-column label="节点编码" align="center" prop="nodeCode" min-width="120" /> <el-table-column label="节点编码" align="center" prop="nodeCode" min-width="120" />
<el-table-column label="最新值" align="center" min-width="120">
<template slot-scope="scope">
<span>{{ formatLatestValue(scope.row) }}</span>
</template>
</el-table-column>
<el-table-column label="最新时间" align="center" prop="latestTime" min-width="180" :show-overflow-tooltip="true" />
<el-table-column label="单位" align="center" prop="unit" width="80" /> <el-table-column label="单位" align="center" prop="unit" width="80" />
<el-table-column label="字段" align="center" prop="fieldKey" min-width="120" /> <el-table-column label="字段" align="center" prop="fieldKey" min-width="120" />
<el-table-column label="启用" align="center" prop="enableFlag" width="80"> <el-table-column label="启用" align="center" prop="enableFlag" width="80">
@ -371,9 +362,6 @@ export default {
this.open = false this.open = false
this.reset() this.reset()
}, },
refreshLatest() {
this.getList()
},
formatLatestValue(row) { formatLatestValue(row) {
if (!row || row.latestValue === null || row.latestValue === undefined || row.latestValue === '') { if (!row || row.latestValue === null || row.latestValue === undefined || row.latestValue === '') {
return '-' return '-'

View File

@ -0,0 +1,787 @@
<template>
<div class="app-container board-screen-page">
<el-form :model="queryParams" size="small" :inline="true" label-width="80px">
<el-form-item label="看板编码">
<el-input v-model="queryParams.screenCode" placeholder="请输入看板编码" clearable style="width: 180px" />
</el-form-item>
<el-form-item label="责任人">
<el-input v-model="queryParams.ownerName" placeholder="请输入责任人" clearable style="width: 180px" />
</el-form-item>
<el-form-item label="所属车间">
<el-select v-model="queryParams.workshopId" placeholder="请选择车间" clearable filterable style="width: 180px">
<el-option v-for="item in workshopOptions" :key="item.workshopId" :label="item.workshopName" :value="item.workshopId" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="getScreenList">查询</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
<el-button type="success" plain icon="el-icon-plus" size="mini" @click="openScreenDialog()">新增看板</el-button>
</el-form-item>
</el-form>
<el-row :gutter="16">
<el-col :span="9">
<el-card shadow="never" class="panel-card">
<div slot="header" class="card-header">
<span>看板列表</span>
</div>
<el-table
v-loading="screenLoading"
:data="screenList"
border
height="620"
highlight-current-row
row-key="screenId"
@current-change="handleCurrentScreenChange"
>
<el-table-column label="看板编码" prop="screenCode" min-width="140" />
<el-table-column label="车间" prop="workshopName" min-width="120" show-overflow-tooltip />
<el-table-column label="责任人" prop="ownerName" min-width="100" />
<el-table-column label="设备数" align="center" prop="deviceTotal" width="80" />
<el-table-column label="操作" align="center" width="150" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="mini" @click.stop="openScreenDialog(scope.row)">编辑</el-button>
<el-button type="text" size="mini" @click.stop="removeScreen(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
<el-col :span="15">
<el-card shadow="never" class="panel-card">
<div slot="header" class="card-header">
<div>
<div class="screen-title">{{ currentScreen ? currentScreen.screenCode : '请选择看板' }}</div>
<div class="screen-subtitle">{{ currentScreenSummary }}</div>
</div>
<div v-if="currentScreen">
<el-button type="primary" plain size="mini" icon="el-icon-plus" @click="openDeviceDialog">添加设备</el-button>
<el-button type="warning" plain size="mini" icon="el-icon-delete" @click="resetConfig">清空绑定</el-button>
<el-button type="primary" plain size="mini" icon="el-icon-monitor" @click="openBoard">打开看板</el-button>
</div>
</div>
<div v-if="!currentScreen" class="empty-block">
请选择左侧看板开始配置设备和点位绑定
</div>
<template v-else>
<el-collapse v-model="activeMachines">
<el-collapse-item
v-for="machine in machineConfigs"
:key="machine.machineCode"
:name="machine.machineCode"
>
<template slot="title">
<div class="machine-title-row">
<span>{{ machine.machineCode }}</span>
<span class="machine-name">{{ machine.machineName || '--' }}</span>
<el-button type="text" size="mini" class="machine-remove" @click.stop="removeMachine(machine.machineCode)">移除设备</el-button>
</div>
</template>
<el-table :data="machine.bindings" border>
<el-table-column label="字段名称" prop="fieldName" min-width="140" />
<el-table-column label="字段键" prop="bindingField" min-width="140" />
<el-table-column label="当前点位" min-width="220">
<template slot-scope="scope">
{{ getPointSummary(scope.row) }}
</template>
</el-table-column>
<el-table-column label="预览值" min-width="160">
<template slot-scope="scope">
{{ getPreviewValue(scope.row) }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="120">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openBindingEditor(machine, scope.row)">配置点位</el-button>
</template>
</el-table-column>
</el-table>
</el-collapse-item>
</el-collapse>
</template>
</el-card>
</el-col>
</el-row>
<el-dialog :title="screenDialogTitle" :visible.sync="screenDialogOpen" width="640px" append-to-body>
<el-form ref="screenForm" :model="screenForm" :rules="screenRules" label-width="96px">
<el-form-item label="看板编码" prop="screenCode">
<el-input v-model="screenForm.screenCode" placeholder="请输入看板编码" :disabled="!!screenForm.screenId" />
</el-form-item>
<el-form-item label="责任人" prop="ownerName">
<el-input v-model="screenForm.ownerName" placeholder="请输入责任人" />
</el-form-item>
<el-form-item label="所属车间" prop="workshopId">
<el-select v-model="screenForm.workshopId" placeholder="请选择车间" filterable style="width: 100%" @change="handleWorkshopChange">
<el-option v-for="item in workshopOptions" :key="item.workshopId" :label="item.workshopName" :value="item.workshopId" />
</el-select>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="screenForm.remark" type="textarea" :rows="3" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitScreenForm"> </el-button>
<el-button @click="screenDialogOpen = false"> </el-button>
</div>
</el-dialog>
<el-dialog title="添加设备" :visible.sync="deviceDialogOpen" width="1000px" append-to-body>
<el-form :model="deviceQuery" size="small" :inline="true" label-width="80px">
<el-form-item label="设备编码">
<el-input v-model="deviceQuery.machineryCode" placeholder="请输入设备编码,支持模糊搜索" clearable />
</el-form-item>
<el-form-item label="设备名称">
<el-input v-model="deviceQuery.machineryName" placeholder="请输入设备名称,支持模糊搜索" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="getDeviceList">查询</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetDeviceQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="deviceLoading"
:data="deviceList"
border
height="420"
@selection-change="handleDeviceSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column label="设备编码" prop="machineryCode" min-width="140" />
<el-table-column label="设备名称" prop="machineryName" min-width="160" />
<el-table-column label="设备型号" prop="machinerySpec" min-width="160" />
<el-table-column label="所属车间" prop="workshopName" min-width="140" />
</el-table>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="appendSelectedDevices"> </el-button>
<el-button @click="deviceDialogOpen = false"> </el-button>
</div>
</el-dialog>
<el-dialog title="配置点位" :visible.sync="editorOpen" width="760px" append-to-body>
<el-form :model="editForm" label-width="96px">
<el-row>
<el-col :span="12">
<el-form-item label="设备编码">
<el-input :value="editMachine.machineCode" readonly />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="字段名称">
<el-input :value="editForm.fieldName" readonly />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="18">
<el-form-item label="测量点">
<el-input :value="getPointSummary(editForm)" readonly placeholder="请选择测量点" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label-width="0">
<el-button icon="el-icon-search" @click="openPointDialog">选择点位</el-button>
<el-button type="text" @click="clearPoint">清空</el-button>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="当前预览">
<el-input :value="getPreviewValue(editForm)" readonly />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitBindingEdit"> </el-button>
<el-button @click="editorOpen = false"> </el-button>
</div>
</el-dialog>
<el-dialog title="选择测量点" :visible.sync="pointDialogOpen" width="1000px" append-to-body>
<el-form :model="pointQuery" size="small" :inline="true" label-width="84px">
<el-form-item label="测量点编码">
<el-input v-model="pointQuery.pointCode" placeholder="请输入测量点编码" clearable />
</el-form-item>
<el-form-item label="测量点名称">
<el-input v-model="pointQuery.pointName" placeholder="请输入测量点名称" clearable />
</el-form-item>
<el-form-item label="设备编码">
<el-input v-model="pointQuery.deviceCode" placeholder="请输入设备编码" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="getPointList">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetPointQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table v-loading="pointLoading" :data="pointList" border height="420">
<el-table-column label="测量点编码" prop="pointCode" min-width="130" />
<el-table-column label="测量点名称" prop="pointName" min-width="160" />
<el-table-column label="设备编码" prop="deviceCode" min-width="120" />
<el-table-column label="最新值" min-width="140">
<template slot-scope="scope">
{{ formatPointLatest(scope.row) }}
</template>
</el-table-column>
<el-table-column label="最新时间" prop="latestTime" min-width="170" />
<el-table-column label="操作" align="center" width="100">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="selectPoint(scope.row)">选择</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script>
import { listMeasurePoint, getMeasurePointLatest } from '@/api/mes/md/measurepoint'
import { listAllWorkshop } from '@/api/mes/md/workshop'
import { listMachinery } from '@/api/mes/dv/machinery'
import {
addBoardScreen,
delBoardScreen,
getBoardScreenConfig,
listBoardScreen,
resetBoardScreenConfig,
saveBoardScreenConfig,
updateBoardScreen
} from '@/api/mes/board/screen'
const FIELD_OPTIONS = [
{ bindingField: 'device_status', fieldName: '设备状态' },
{ bindingField: 'run_mode', fieldName: '运行模式' },
{ bindingField: 'today_run_time', fieldName: '今日开机时长' },
{ bindingField: 'today_piece_count', fieldName: '今日计件数' },
{ bindingField: 'batch_code', fieldName: '批次号' }
]
function buildFieldMap() {
return FIELD_OPTIONS.reduce((result, item) => {
result[item.bindingField] = item.fieldName
return result
}, {})
}
const FIELD_NAME_MAP = buildFieldMap()
function createBindingSkeleton(machine, field) {
return {
bindingId: null,
screenId: null,
screenCode: '',
machineryId: machine.machineryId,
machineryCode: machine.machineryCode,
bindingField: field.bindingField,
fieldName: field.fieldName,
pointId: null,
pointCode: '',
pointName: '',
sortNum: FIELD_OPTIONS.findIndex(item => item.bindingField === field.bindingField) + 1,
enableFlag: 'Y'
}
}
function createMachineConfig(machine) {
return {
machineryId: machine.machineryId,
machineCode: machine.machineryCode,
machineName: machine.machineryName || '',
bindings: FIELD_OPTIONS.map(field => createBindingSkeleton(machine, field))
}
}
function groupBindings(bindings, deviceNameMap) {
const grouped = {}
;(bindings || []).forEach(item => {
const machineCode = item.machineryCode
if (!machineCode) {
return
}
if (!grouped[machineCode]) {
grouped[machineCode] = createMachineConfig({
machineryId: item.machineryId,
machineryCode: item.machineryCode,
machineryName: deviceNameMap[item.machineryCode] || ''
})
}
const target = grouped[machineCode].bindings.find(binding => binding.bindingField === item.bindingField)
if (target) {
Object.assign(target, item, {
fieldName: FIELD_NAME_MAP[item.bindingField] || item.bindingField
})
}
})
return Object.keys(grouped).sort().map(key => grouped[key])
}
function collectPointCodes(machineConfigs) {
const pointCodeMap = {}
;(machineConfigs || []).forEach(machine => {
(machine.bindings || []).forEach(binding => {
if (binding.pointCode) {
pointCodeMap[binding.pointCode] = true
}
})
})
return Object.keys(pointCodeMap)
}
export default {
name: 'ScreenBinding',
data() {
return {
screenLoading: false,
saveLoading: false,
pointLoading: false,
deviceLoading: false,
screenDialogOpen: false,
deviceDialogOpen: false,
editorOpen: false,
pointDialogOpen: false,
workshopOptions: [],
screenList: [],
pointList: [],
deviceList: [],
selectedDeviceRows: [],
currentScreen: null,
machineConfigs: [],
activeMachines: [],
latestMap: {},
deviceNameMap: {},
queryParams: {
screenCode: '',
ownerName: '',
workshopId: null
},
screenForm: {
screenId: null,
screenCode: '',
ownerName: '',
workshopId: null,
workshopCode: '',
workshopName: '',
remark: ''
},
screenRules: {
screenCode: [{ required: true, message: '看板编码不能为空', trigger: 'blur' }],
ownerName: [{ required: true, message: '责任人不能为空', trigger: 'blur' }],
workshopId: [{ required: true, message: '所属车间不能为空', trigger: 'change' }]
},
deviceQuery: {
pageNum: 1,
pageSize: 200,
machineryCode: '',
machineryName: ''
},
pointQuery: {
pageNum: 1,
pageSize: 200,
pointCode: '',
pointName: '',
deviceCode: '',
enableFlag: 'Y'
},
editMachine: {},
editForm: {}
}
},
computed: {
currentScreenSummary() {
if (!this.currentScreen) {
return ''
}
const workshopName = this.currentScreen.workshopName || '--'
const ownerName = this.currentScreen.ownerName || '--'
return `车间:${workshopName},责任人:${ownerName}`
},
screenDialogTitle() {
return this.screenForm.screenId ? '编辑看板' : '新增看板'
}
},
created() {
this.getWorkshopOptions()
this.getDeviceList()
this.getScreenList()
},
methods: {
getWorkshopOptions() {
listAllWorkshop().then(response => {
this.workshopOptions = Array.isArray(response) ? response : (response.data || [])
})
},
getScreenList() {
this.screenLoading = true
listBoardScreen(this.queryParams).then(response => {
this.screenList = response.rows || []
if (!this.screenList.length) {
this.currentScreen = null
this.machineConfigs = []
this.activeMachines = []
return
}
const nextCurrent = this.currentScreen
? this.screenList.find(item => item.screenId === this.currentScreen.screenId)
: this.screenList[0]
this.handleCurrentScreenChange(nextCurrent)
}).finally(() => {
this.screenLoading = false
})
},
resetQuery() {
this.queryParams = {
screenCode: '',
ownerName: '',
workshopId: null
}
this.getScreenList()
},
handleCurrentScreenChange(row) {
if (!row || (this.currentScreen && this.currentScreen.screenId === row.screenId && this.machineConfigs.length)) {
this.currentScreen = row || null
return
}
this.currentScreen = row
this.loadCurrentConfig()
},
loadCurrentConfig() {
if (!this.currentScreen) {
return
}
getBoardScreenConfig(this.currentScreen.screenId).then(response => {
const bindings = response.data || []
this.machineConfigs = groupBindings(bindings, this.deviceNameMap)
this.activeMachines = this.machineConfigs.map(item => item.machineCode)
this.refreshPreview()
})
},
openScreenDialog(row) {
if (this.$refs.screenForm) {
this.$refs.screenForm.resetFields()
}
if (row) {
this.screenForm = { ...row }
} else {
this.screenForm = {
screenId: null,
screenCode: '',
ownerName: '',
workshopId: null,
workshopCode: '',
workshopName: '',
remark: ''
}
}
this.screenDialogOpen = true
},
handleWorkshopChange(workshopId) {
const workshop = this.workshopOptions.find(item => item.workshopId === workshopId)
this.screenForm.workshopCode = workshop ? workshop.workshopCode : ''
this.screenForm.workshopName = workshop ? workshop.workshopName : ''
},
submitScreenForm() {
this.$refs.screenForm.validate(valid => {
if (!valid) {
return
}
const request = this.screenForm.screenId ? updateBoardScreen(this.screenForm) : addBoardScreen(this.screenForm)
request.then(() => {
this.screenDialogOpen = false
this.$modal.msgSuccess(this.screenForm.screenId ? '修改成功' : '新增成功')
this.getScreenList()
})
})
},
removeScreen(row) {
this.$modal.confirm(`是否确认删除看板 ${row.screenCode}`).then(() => {
return delBoardScreen(row.screenId)
}).then(() => {
this.$modal.msgSuccess('删除成功')
this.getScreenList()
}).catch(() => {})
},
getDeviceList() {
this.deviceLoading = true
listMachinery(this.deviceQuery).then(response => {
this.deviceList = response.rows || []
;(response.rows || []).forEach(item => {
this.deviceNameMap[item.machineryCode] = item.machineryName
})
}).finally(() => {
this.deviceLoading = false
})
},
resetDeviceQuery() {
this.deviceQuery.machineryCode = ''
this.deviceQuery.machineryName = ''
this.getDeviceList()
},
openDeviceDialog() {
this.selectedDeviceRows = []
this.deviceDialogOpen = true
this.getDeviceList()
},
handleDeviceSelectionChange(rows) {
this.selectedDeviceRows = rows || []
},
appendSelectedDevices() {
if (!this.selectedDeviceRows.length) {
this.$modal.msgWarning('请先选择设备')
return
}
const codeMap = {}
let changed = false
this.machineConfigs.forEach(item => {
codeMap[item.machineCode] = true
})
this.selectedDeviceRows.forEach(item => {
if (codeMap[item.machineryCode]) {
return
}
this.deviceNameMap[item.machineryCode] = item.machineryName
this.machineConfigs.push(createMachineConfig(item))
changed = true
})
if (!changed) {
this.$modal.msgWarning('所选设备已存在')
return
}
this.machineConfigs.sort((a, b) => a.machineCode.localeCompare(b.machineCode))
this.activeMachines = this.machineConfigs.map(item => item.machineCode)
this.persistConfig({
successMessage: '设备添加成功',
onSuccess: () => {
this.deviceDialogOpen = false
}
})
},
removeMachine(machineCode) {
this.machineConfigs = this.machineConfigs.filter(item => item.machineCode !== machineCode)
this.activeMachines = this.machineConfigs.map(item => item.machineCode)
this.persistConfig({
successMessage: '设备移除成功'
})
},
openBindingEditor(machine, binding) {
this.editMachine = { ...machine }
this.editForm = JSON.parse(JSON.stringify(binding))
this.editorOpen = true
},
openPointDialog() {
this.pointDialogOpen = true
this.getPointList()
},
getPointList() {
this.pointLoading = true
listMeasurePoint(this.pointQuery).then(response => {
this.pointList = response.rows || []
}).finally(() => {
this.pointLoading = false
})
},
resetPointQuery() {
this.pointQuery.pointCode = ''
this.pointQuery.pointName = ''
this.pointQuery.deviceCode = ''
this.getPointList()
},
selectPoint(row) {
this.editForm.pointId = row.pointId
this.editForm.pointCode = row.pointCode
this.editForm.pointName = row.pointName
this.pointDialogOpen = false
},
clearPoint() {
this.editForm.pointId = null
this.editForm.pointCode = ''
this.editForm.pointName = ''
},
submitBindingEdit() {
const machine = this.machineConfigs.find(item => item.machineCode === this.editMachine.machineCode)
if (!machine) {
return
}
const target = machine.bindings.find(item => item.bindingField === this.editForm.bindingField)
if (target) {
Object.assign(target, this.editForm)
}
this.editorOpen = false
this.refreshPreview()
this.persistConfig({
successMessage: '点位配置已保存'
})
},
persistConfig({ successMessage, onSuccess } = {}) {
if (!this.currentScreen) {
return
}
const bindings = []
this.machineConfigs.forEach(machine => {
machine.bindings.forEach(binding => {
bindings.push({
bindingId: binding.bindingId,
screenId: this.currentScreen.screenId,
screenCode: this.currentScreen.screenCode,
machineryId: machine.machineryId,
machineryCode: machine.machineCode,
bindingField: binding.bindingField,
pointId: binding.pointId,
pointCode: binding.pointCode || '',
pointName: binding.pointName || '',
sortNum: binding.sortNum,
enableFlag: 'Y'
})
})
})
this.saveLoading = true
saveBoardScreenConfig({
screenId: this.currentScreen.screenId,
screenCode: this.currentScreen.screenCode,
bindings
}).then(response => {
this.machineConfigs = groupBindings(response.data || [], this.deviceNameMap)
this.activeMachines = this.machineConfigs.map(item => item.machineCode)
this.refreshPreview()
if (typeof onSuccess === 'function') {
onSuccess()
}
this.$modal.msgSuccess(successMessage || response.msg || '保存成功')
this.getScreenList()
}).finally(() => {
this.saveLoading = false
})
},
resetConfig() {
if (!this.currentScreen) {
return
}
this.$modal.confirm('是否确认清空当前看板的设备绑定和点位配置?').then(() => {
return resetBoardScreenConfig(this.currentScreen.screenId)
}).then(() => {
this.machineConfigs = []
this.activeMachines = []
this.latestMap = {}
this.$modal.msgSuccess('已清空绑定配置')
this.getScreenList()
}).catch(() => {})
},
refreshPreview() {
const pointCodes = collectPointCodes(this.machineConfigs)
if (!pointCodes.length) {
this.latestMap = {}
return
}
Promise.all(pointCodes.map(pointCode => {
return getMeasurePointLatest(pointCode).then(response => {
const payload = response && response.data !== undefined ? response.data : response
return {
pointCode,
value: payload.latestValue !== undefined ? payload.latestValue : payload.value,
latestTime: payload.latestTime || payload.collectTime || payload.time || ''
}
}).catch(() => ({
pointCode,
value: '',
latestTime: ''
}))
})).then(list => {
this.latestMap = list.reduce((result, item) => {
result[item.pointCode] = item
return result
}, {})
})
},
getPointSummary(binding) {
if (!binding.pointCode) {
return '未配置测量点'
}
return `${binding.pointCode} / ${binding.pointName || '--'}`
},
getPreviewValue(binding) {
if (!binding.pointCode) {
return '--'
}
const latest = this.latestMap[binding.pointCode] || {}
const value = latest.value
return value === undefined || value === null || value === '' ? '--' : value
},
formatPointLatest(row) {
const value = row.latestValue === undefined || row.latestValue === null || row.latestValue === '' ? '--' : row.latestValue
if (!row.unit || value === '--') {
return value
}
return `${value}${row.unit}`
},
openBoard() {
if (!this.currentScreen) {
return
}
const routeData = this.$router.resolve({
path: '/board/workshop',
query: {
screenCode: this.currentScreen.screenCode
}
})
window.open(routeData.href, '_blank')
}
}
}
</script>
<style scoped>
.board-screen-page {
min-width: 1200px;
}
.panel-card {
min-height: 680px;
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.screen-title {
font-size: 18px;
font-weight: 600;
color: #303133;
}
.screen-subtitle {
margin-top: 6px;
font-size: 13px;
color: #909399;
}
.empty-block {
min-height: 520px;
display: flex;
align-items: center;
justify-content: center;
color: #909399;
font-size: 14px;
}
.machine-title-row {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
}
.machine-name {
color: #909399;
}
.machine-remove {
margin-left: auto;
margin-right: 16px;
}
</style>