feat: migrate tray management module
- add V2 tray management page for planning production monitoring - add tray management API, route, and i18n entries - update migration task list status for tray management
This commit is contained in:
@@ -0,0 +1,562 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template #header>
|
||||
<div class="search-bar">
|
||||
<el-form ref="searchForm" :inline="true" :model="search" size="mini">
|
||||
<el-form-item :label="$t(key('tray_code'))" prop="tray">
|
||||
<el-input
|
||||
v-model="search.tray"
|
||||
prefix-icon="el-icon-search"
|
||||
:placeholder="$t(key('tray_code_placeholder'))"
|
||||
clearable
|
||||
style="width:220px"
|
||||
@keyup.enter.native="onSearch"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" :disabled="loading" @click="onSearch">
|
||||
{{ $t(key('query')) }}
|
||||
</el-button>
|
||||
<el-button icon="el-icon-refresh" :disabled="loading" @click="onReset">
|
||||
{{ $t(key('reset')) }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-loading="loading" class="tray-management">
|
||||
<el-empty v-if="!hasTrayData" :description="$t(key('enter_tray_to_query'))" />
|
||||
<el-container v-else class="tray-layout">
|
||||
<el-aside width="260px" class="process-aside">
|
||||
<el-menu :default-active="activeProcessIndex" @select="selectProcess">
|
||||
<el-menu-item
|
||||
v-for="(item, index) in dateLog"
|
||||
:key="index"
|
||||
:index="String(index)"
|
||||
>
|
||||
<div class="process-item">
|
||||
<p class="process-title">
|
||||
{{ item.flow_process_code || item.name }}
|
||||
<span v-if="Number(item.idx) === Number(trayData.process_idx) + 1">
|
||||
[{{ $t(key('current_process')) }}]
|
||||
</span>
|
||||
</p>
|
||||
<p>{{ $t(key('start_time')) }}: {{ item.beginTime }}</p>
|
||||
<p>{{ $t(key('end_time')) }}: {{ item.endTime }}</p>
|
||||
<p>{{ $t(key('device_code')) }}: {{ item.device_code }}</p>
|
||||
</div>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
|
||||
<el-container>
|
||||
<el-header height="auto" class="tray-header">
|
||||
<el-form label-width="90px" size="small">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="6">
|
||||
<el-form-item :label="$t(key('batch'))">
|
||||
<el-input :value="trayData.batch" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item :label="$t(key('lot'))">
|
||||
<el-input :value="trayData.lot" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item :label="$t(key('input_battery_count'))">
|
||||
<el-input :value="trayData.actual_input_battery_count" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item :label="$t(key('input_time'))">
|
||||
<el-input :value="trayData.create_time" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="6">
|
||||
<el-form-item :label="$t(key('previous_process'))">
|
||||
<el-input :value="trayData.process_code" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item :label="$t(key('current_process'))">
|
||||
<el-input :value="trayData.next_process_code" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item :label="$t(key('status'))">
|
||||
<el-input :value="trayData.process_code ? $t(key('activated')) : ''" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item :label="$t(key('tray_ng'))">
|
||||
<el-input
|
||||
:value="trayData.ngclassname"
|
||||
:class="{ 'is-ng': Number(trayData.is_ng) === 1 }"
|
||||
disabled
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-header>
|
||||
|
||||
<el-main class="tray-main">
|
||||
<div class="actions">
|
||||
<el-button v-if="auth.stop" type="danger" size="mini" @click="handleStopTray">
|
||||
{{ $t(key('stop_tray')) }}
|
||||
</el-button>
|
||||
<el-button v-if="auth.ALLNG" type="warning" size="mini" @click="handleNgTray">
|
||||
{{ $t(key('tray_ng')) }}
|
||||
</el-button>
|
||||
<el-button v-if="auth.FX" type="primary" size="mini" @click="handleRangeFx">
|
||||
{{ $t(key('capacity_sorting')) }}
|
||||
</el-button>
|
||||
<el-button v-if="auth.TRAY_NG" type="danger" size="mini" @click="handleCleanTrayNg">
|
||||
{{ $t(key('clear_tray_ng')) }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="tray-grid-wrap">
|
||||
<div class="tray-row tray-head">
|
||||
<div :class="breachClass('left_top')" />
|
||||
<div class="tray-cols">
|
||||
<span v-for="item in trayCol" :key="item">{{ item }}</span>
|
||||
</div>
|
||||
<div :class="breachClass('right_top')" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="(row, rowIndex) in trayRows"
|
||||
:key="rowIndex"
|
||||
class="tray-row"
|
||||
:style="{ height: cellHeight + 'px' }"
|
||||
>
|
||||
<div class="row-label">{{ rowIndex + 1 }}</div>
|
||||
<div class="tray-cells">
|
||||
<div
|
||||
v-for="idx in row"
|
||||
:key="idx"
|
||||
class="tray-cell"
|
||||
:style="{ background: batteryBackground(batteryInfo[idx - 1]) }"
|
||||
@click="openBattery(idx)"
|
||||
>
|
||||
<span class="cell-index">{{ idx }}</span>
|
||||
<span v-if="batteryInfo[idx - 1] && batteryInfo[idx - 1].classname" class="cell-class">
|
||||
{{ batteryInfo[idx - 1].classname }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-label">{{ rowIndex + 1 }}</div>
|
||||
</div>
|
||||
|
||||
<div class="tray-row tray-head">
|
||||
<div :class="breachClass('left_bottom')" />
|
||||
<div class="tray-cols">
|
||||
<span v-for="item in trayCol" :key="item">{{ item }}</span>
|
||||
</div>
|
||||
<div :class="breachClass('right_bottom')" />
|
||||
</div>
|
||||
</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</div>
|
||||
|
||||
<el-dialog :title="batteryDialogTitle" :visible.sync="batteryDialogVisible" width="420px">
|
||||
<el-descriptions :column="1" border size="mini">
|
||||
<el-descriptions-item :label="$t(key('battery_id'))">
|
||||
{{ selectedBattery.battery_id }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t(key('grade'))">
|
||||
{{ selectedBattery.classname }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t(key('activation_status'))">
|
||||
{{ Number(selectedBattery.active) === 1 ? $t(key('activated')) : $t(key('not_activated')) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<span slot="footer">
|
||||
<el-button size="mini" @click="batteryDialogVisible = false">{{ $t(key('cancel')) }}</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { i18nMixin } from '@/composables/useI18n'
|
||||
import {
|
||||
getTrayManageInfo,
|
||||
changeFlowProcess,
|
||||
trayNg,
|
||||
rangeFx,
|
||||
cleanTrayNg
|
||||
} from '@/api/planning-production/tray-management'
|
||||
import { sendWorkerman } from '@/api/production-master-data/workerman'
|
||||
|
||||
export default {
|
||||
name: 'planning-production-tray-management',
|
||||
mixins: [i18nMixin('page.planning_production.production_monitoring.tray_management')],
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
search: {
|
||||
tray: this.$route.query.tray || ''
|
||||
},
|
||||
trayData: {},
|
||||
auth: {
|
||||
stop: false,
|
||||
ALLNG: false,
|
||||
FX: false,
|
||||
TRAY_NG: false
|
||||
},
|
||||
batteryDialogVisible: false,
|
||||
batteryDialogTitle: '',
|
||||
selectedBattery: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasTrayData () {
|
||||
return !!(this.trayData && this.trayData.tray)
|
||||
},
|
||||
dateLog () {
|
||||
const value = this.trayData.date_log
|
||||
if (Array.isArray(value)) return value
|
||||
if (!value) return []
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
},
|
||||
trayFormat () {
|
||||
return this.trayData.tary_format_data || {}
|
||||
},
|
||||
trayCol () {
|
||||
return Number(this.trayFormat.tray_col || 14)
|
||||
},
|
||||
trayRow () {
|
||||
return Number(this.trayFormat.tray_row || 14)
|
||||
},
|
||||
breach () {
|
||||
return this.trayFormat.breach || 'left_bottom'
|
||||
},
|
||||
dataBreach () {
|
||||
return this.trayFormat.data_breach || 'left_top'
|
||||
},
|
||||
direction () {
|
||||
return this.trayFormat.direction || 'horizontal'
|
||||
},
|
||||
shape () {
|
||||
return this.trayFormat.shape || 'NU'
|
||||
},
|
||||
cellHeight () {
|
||||
return Number(this.trayFormat.cellheight || 35)
|
||||
},
|
||||
batteryInfo () {
|
||||
return this.trayData.battery_info || []
|
||||
},
|
||||
activeProcessIndex () {
|
||||
return String(Number(this.trayData.process_idx || 0) + 1)
|
||||
},
|
||||
trayRows () {
|
||||
const rows = []
|
||||
for (let x = 0; x < this.trayRow; x++) {
|
||||
const row = []
|
||||
for (let y = 0; y < this.trayCol; y++) {
|
||||
let idx
|
||||
if (this.direction === 'vertical') {
|
||||
idx = x + 1 + this.trayRow * y
|
||||
if (this.shape !== 'NU' && y % 2 === 1) {
|
||||
idx = this.trayRow - x + this.trayRow * y
|
||||
}
|
||||
} else {
|
||||
idx = this.trayCol * x + y + 1
|
||||
}
|
||||
row.push(idx)
|
||||
}
|
||||
if (this.direction === 'horizontal' && this.shape !== 'NU' && x % 2 === 1) {
|
||||
row.reverse()
|
||||
}
|
||||
rows.push(row)
|
||||
}
|
||||
|
||||
if (this.dataBreach === 'left_bottom') {
|
||||
rows.reverse()
|
||||
} else if (this.dataBreach === 'right_top') {
|
||||
rows.forEach(row => row.reverse())
|
||||
} else if (this.dataBreach === 'right_bottom') {
|
||||
rows.reverse()
|
||||
rows.forEach(row => row.reverse())
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route.query.tray' (tray) {
|
||||
this.search.tray = tray || ''
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.initAuth()
|
||||
if (this.search.tray) this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
initAuth () {
|
||||
const can = key => (this.$permission ? this.$permission(key) : true)
|
||||
this.auth.stop = can('/produce/monitor/trayManage/stop')
|
||||
this.auth.ALLNG = can('/produce/monitor/trayManage/ALLNG')
|
||||
this.auth.FX = can('/produce/monitor/trayManage/FX')
|
||||
this.auth.TRAY_NG = can('/produce/monitor/trayManage/TRAY_NG')
|
||||
},
|
||||
fetchData () {
|
||||
if (!this.search.tray) {
|
||||
this.trayData = {}
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
getTrayManageInfo({ ...this.search })
|
||||
.then(res => {
|
||||
const payload = res && res.data ? res.data : res
|
||||
this.trayData = payload && payload.data ? payload.data : (payload || {})
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
onSearch () {
|
||||
this.fetchData()
|
||||
},
|
||||
onReset () {
|
||||
this.$refs.searchForm.resetFields()
|
||||
this.trayData = {}
|
||||
},
|
||||
refreshTray () {
|
||||
this.fetchData()
|
||||
},
|
||||
confirmAction (message, handler) {
|
||||
this.$confirm(message, this.$t(this.key('prompt')), {
|
||||
confirmButtonText: this.$t(this.key('confirm')),
|
||||
cancelButtonText: this.$t(this.key('cancel')),
|
||||
type: 'warning'
|
||||
})
|
||||
.then(handler)
|
||||
.catch(() => {
|
||||
this.$message.info(this.$t(this.key('operation_cancelled')))
|
||||
})
|
||||
},
|
||||
handleStopTray () {
|
||||
this.confirmAction(this.$t(this.key('confirm_stop_tray')), () => {
|
||||
return sendWorkerman({
|
||||
sendData: {
|
||||
action: 'set_tray_inactivity',
|
||||
param: { tray: this.trayData.tray }
|
||||
}
|
||||
}).then(() => {
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
this.refreshTray()
|
||||
})
|
||||
})
|
||||
},
|
||||
handleNgTray () {
|
||||
this.confirmAction(this.$t(this.key('confirm_full_tray_ng')), () => {
|
||||
return trayNg({ tray: this.trayData.tray }).then(() => {
|
||||
this.$message.success(this.$t(this.key('full_tray_ng_success')))
|
||||
this.refreshTray()
|
||||
})
|
||||
})
|
||||
},
|
||||
handleCleanTrayNg () {
|
||||
this.confirmAction(this.$t(this.key('confirm_clear_tray_ng')), () => {
|
||||
return cleanTrayNg({
|
||||
lot: this.trayData.lot,
|
||||
subbatch: this.trayData.subbatch,
|
||||
tray: this.trayData.tray,
|
||||
next_process_code: this.trayData.next_process_code
|
||||
}).then(() => {
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
this.refreshTray()
|
||||
})
|
||||
})
|
||||
},
|
||||
handleRangeFx () {
|
||||
this.confirmAction(this.$t(this.key('confirm_capacity_resort')), () => {
|
||||
return rangeFx({
|
||||
lot: this.trayData.lot,
|
||||
subbatch: this.trayData.subbatch,
|
||||
tray: this.trayData.tray
|
||||
}).then(() => {
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
this.refreshTray()
|
||||
})
|
||||
})
|
||||
},
|
||||
selectProcess (index) {
|
||||
const currentIndex = Number(this.trayData.process_idx || 0) + 1
|
||||
if (Number(index) === currentIndex) return
|
||||
if (Number(index) === 0) {
|
||||
this.$message.info(this.$t(this.key('cannot_adjust_to_initial_process')))
|
||||
return
|
||||
}
|
||||
this.confirmAction(this.$t(this.key('confirm_adjust_tray_process')), () => {
|
||||
const prev = this.dateLog[Number(index) - 1] || {}
|
||||
const next = this.dateLog[Number(index)] || {}
|
||||
return changeFlowProcess({
|
||||
variable: 1,
|
||||
lot: this.trayData.lot,
|
||||
subbatch: this.trayData.subbatch,
|
||||
tray: this.trayData.tray,
|
||||
process_code: prev.flow_process_code || '',
|
||||
process_idx: prev.idx || 0,
|
||||
next_process_code: next.flow_process_code || '',
|
||||
clear_ng: false
|
||||
}).then(() => {
|
||||
this.$message.success(this.$t(this.key('adjust_process_success')))
|
||||
this.refreshTray()
|
||||
})
|
||||
})
|
||||
},
|
||||
batteryBackground (item = {}) {
|
||||
if (item && item.battery_id) {
|
||||
if (Number(item.active) === 1) {
|
||||
if (item.class === 'NG') return '#F56C6C'
|
||||
if (item.class === 'RC') return 'yellowgreen'
|
||||
return '#317894'
|
||||
}
|
||||
return '#908D8D'
|
||||
}
|
||||
return '#F5F7FA'
|
||||
},
|
||||
openBattery (idx) {
|
||||
const battery = this.batteryInfo[idx - 1]
|
||||
if (!battery || !battery.battery_id) return
|
||||
this.selectedBattery = battery
|
||||
this.batteryDialogTitle = this.$t(this.key('channel_battery_info'), { index: idx })
|
||||
this.batteryDialogVisible = true
|
||||
},
|
||||
breachClass (position) {
|
||||
return this.breach === position ? `tray-breach tray-breach--${position}` : 'tray-corner'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search-bar {
|
||||
margin-bottom: -18px;
|
||||
}
|
||||
.tray-management,
|
||||
.tray-layout {
|
||||
height: 100%;
|
||||
}
|
||||
.process-aside {
|
||||
background: #eef1f6;
|
||||
overflow: auto;
|
||||
}
|
||||
.process-item {
|
||||
line-height: 22px;
|
||||
font-size: 12px;
|
||||
white-space: normal;
|
||||
}
|
||||
.process-item p {
|
||||
margin: 0;
|
||||
}
|
||||
.process-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.tray-header {
|
||||
padding: 10px 12px 0;
|
||||
}
|
||||
.tray-header ::v-deep .el-form-item {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.tray-header ::v-deep .is-ng .el-input__inner {
|
||||
color: #F56C6C;
|
||||
}
|
||||
.tray-main {
|
||||
padding: 10px 12px;
|
||||
overflow: auto;
|
||||
}
|
||||
.actions {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.tray-grid-wrap {
|
||||
width: 100%;
|
||||
min-width: 760px;
|
||||
border-top: 1px solid #ddd;
|
||||
border-left: 1px solid #ddd;
|
||||
}
|
||||
.tray-row {
|
||||
display: flex;
|
||||
}
|
||||
.tray-head {
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
}
|
||||
.tray-corner,
|
||||
.tray-breach {
|
||||
width: 26px;
|
||||
flex: 0 0 26px;
|
||||
border-right: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
background: #fff;
|
||||
}
|
||||
.tray-breach {
|
||||
background: #303133;
|
||||
}
|
||||
.tray-cols,
|
||||
.tray-cells {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--tray-col), 1fr);
|
||||
}
|
||||
.tray-cols {
|
||||
display: flex;
|
||||
}
|
||||
.tray-cols span {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
border-right: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
background: #fafafa;
|
||||
}
|
||||
.row-label {
|
||||
width: 26px;
|
||||
flex: 0 0 26px;
|
||||
text-align: center;
|
||||
border-right: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
background: #fafafa;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.tray-cells {
|
||||
grid-template-columns: repeat(auto-fit, minmax(24px, 1fr));
|
||||
}
|
||||
.tray-cell {
|
||||
position: relative;
|
||||
border-right: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 24px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.cell-index {
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
}
|
||||
.cell-class {
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user