455 lines
15 KiB
Vue
455 lines
15 KiB
Vue
|
|
<template>
|
||
|
|
<d2-container>
|
||
|
|
<template #header>
|
||
|
|
<div class="search-bar">
|
||
|
|
<el-form :inline="true" size="mini">
|
||
|
|
<el-form-item :label="$t(key('tray_no'))">
|
||
|
|
<el-input
|
||
|
|
v-model="search.tray"
|
||
|
|
:placeholder="$t(key('enter_tray_no'))"
|
||
|
|
clearable
|
||
|
|
style="width:220px"
|
||
|
|
@keyup.enter.native="onSearch"
|
||
|
|
/>
|
||
|
|
</el-form-item>
|
||
|
|
<el-form-item :label="$t(key('batch_no'))">
|
||
|
|
<el-input
|
||
|
|
v-model="search.batch"
|
||
|
|
:placeholder="$t(key('enter_batch_no'))"
|
||
|
|
clearable
|
||
|
|
style="width:220px"
|
||
|
|
@keyup.enter.native="onSearch"
|
||
|
|
/>
|
||
|
|
</el-form-item>
|
||
|
|
<el-form-item :label="$t(key('status'))">
|
||
|
|
<el-select
|
||
|
|
v-model="search.active"
|
||
|
|
:placeholder="$t(key('select_status'))"
|
||
|
|
clearable
|
||
|
|
style="width:180px"
|
||
|
|
>
|
||
|
|
<el-option :label="$t(key('stop'))" value="0" />
|
||
|
|
<el-option :label="$t(key('activated'))" value="1" />
|
||
|
|
</el-select>
|
||
|
|
</el-form-item>
|
||
|
|
<el-form-item :label="$t(key('login_time'))">
|
||
|
|
<el-date-picker
|
||
|
|
v-model="search.create_time"
|
||
|
|
type="datetimerange"
|
||
|
|
value-format="yyyy-MM-dd HH:mm:ss"
|
||
|
|
range-separator="-"
|
||
|
|
:start-placeholder="$t(key('start_time'))"
|
||
|
|
:end-placeholder="$t(key('end_time'))"
|
||
|
|
style="width:330px"
|
||
|
|
/>
|
||
|
|
</el-form-item>
|
||
|
|
<el-form-item>
|
||
|
|
<el-button type="primary" icon="el-icon-search" @click="onSearch">
|
||
|
|
{{ $t(key('search')) }}
|
||
|
|
</el-button>
|
||
|
|
<el-button icon="el-icon-refresh" @click="onReset">
|
||
|
|
{{ $t(key('reset')) }}
|
||
|
|
</el-button>
|
||
|
|
</el-form-item>
|
||
|
|
</el-form>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<page-table
|
||
|
|
:columns="columns"
|
||
|
|
:data="tableData"
|
||
|
|
:loading="loading"
|
||
|
|
:toolbar-buttons="[]"
|
||
|
|
:row-buttons="rowButtons"
|
||
|
|
:pagination="pagination"
|
||
|
|
:help-text="$t(ckey('help'))"
|
||
|
|
auto-height
|
||
|
|
@page-change="onPageChange"
|
||
|
|
>
|
||
|
|
<template #col-status="{ row }">
|
||
|
|
<el-tag v-if="row.active == 1" type="success">{{ $t(key('activated')) }}</el-tag>
|
||
|
|
<el-tag v-else type="danger">{{ $t(key('stop')) }}</el-tag>
|
||
|
|
</template>
|
||
|
|
</page-table>
|
||
|
|
|
||
|
|
<el-drawer
|
||
|
|
:visible.sync="trayDrawerVisible"
|
||
|
|
:with-header="false"
|
||
|
|
size="100%"
|
||
|
|
append-to-body
|
||
|
|
>
|
||
|
|
<div class="drawer-header">
|
||
|
|
<el-page-header @back="trayDrawerVisible = false" :content="trayDrawerTitle" />
|
||
|
|
</div>
|
||
|
|
<el-row>
|
||
|
|
<el-col :span="trayTimeline.length ? 6 : 0" v-if="trayTimeline.length">
|
||
|
|
<div class="details-left">
|
||
|
|
<el-timeline class="timeline">
|
||
|
|
<el-timeline-item v-for="(item, index) in trayTimeline" :key="index">
|
||
|
|
<p>
|
||
|
|
{{ $t(key('process')) }}: {{ item.process_name }}
|
||
|
|
<span v-if="item.is_next_process_code == 1" class="current-process">
|
||
|
|
{{ $t(key('current_process')) }}
|
||
|
|
</span>
|
||
|
|
</p>
|
||
|
|
<el-card>
|
||
|
|
<p>{{ $t(key('start_time')) }}: {{ item.beginTime }}</p>
|
||
|
|
<p>{{ $t(key('end_time')) }}: {{ item.endTime }}</p>
|
||
|
|
<p>{{ $t(key('device_number')) }}: {{ item.device_code }}</p>
|
||
|
|
</el-card>
|
||
|
|
</el-timeline-item>
|
||
|
|
</el-timeline>
|
||
|
|
</div>
|
||
|
|
</el-col>
|
||
|
|
<el-col :span="trayTimeline.length ? 18 : 24">
|
||
|
|
<div class="drawer-main">
|
||
|
|
<el-form :inline="true" size="mini">
|
||
|
|
<el-form-item :label="$t(key('battery_barcode'))">
|
||
|
|
<el-input
|
||
|
|
v-model="traySearch"
|
||
|
|
:placeholder="$t(key('please_enter_battery_barcode'))"
|
||
|
|
clearable
|
||
|
|
style="width:320px"
|
||
|
|
/>
|
||
|
|
</el-form-item>
|
||
|
|
</el-form>
|
||
|
|
<el-table :data="filteredTrayDetails" border height="calc(100vh - 160px)">
|
||
|
|
<el-table-column type="index" width="60" :label="$t(key('serial_number'))" />
|
||
|
|
<el-table-column min-width="160" prop="battery_id" :label="$t(key('battery_barcode'))">
|
||
|
|
<template #default="{ row }">
|
||
|
|
<span class="link-text" @click="openBatteryDetails(row)">{{ row.battery_id }}</span>
|
||
|
|
</template>
|
||
|
|
</el-table-column>
|
||
|
|
<el-table-column min-width="140" prop="batch" :label="$t(key('batch_no'))" />
|
||
|
|
<el-table-column min-width="140" prop="tray" :label="$t(key('tray_no'))" />
|
||
|
|
<el-table-column min-width="120" prop="lot" :label="$t(key('lot'))" />
|
||
|
|
<el-table-column min-width="130" prop="active" :label="$t(key('activation_status'))">
|
||
|
|
<template #default="{ row }">
|
||
|
|
<el-tag :type="row.active == 1 ? 'success' : 'warning'">
|
||
|
|
{{ row.active == 1 ? $t(key('activated')) : $t(key('stop')) }}
|
||
|
|
</el-tag>
|
||
|
|
</template>
|
||
|
|
</el-table-column>
|
||
|
|
</el-table>
|
||
|
|
</div>
|
||
|
|
</el-col>
|
||
|
|
</el-row>
|
||
|
|
</el-drawer>
|
||
|
|
|
||
|
|
<el-drawer
|
||
|
|
:visible.sync="batteryDrawerVisible"
|
||
|
|
:with-header="false"
|
||
|
|
size="100%"
|
||
|
|
append-to-body
|
||
|
|
>
|
||
|
|
<div class="drawer-header">
|
||
|
|
<el-page-header @back="batteryDrawerVisible = false" :content="batteryDrawerTitle" />
|
||
|
|
</div>
|
||
|
|
<el-row v-loading="batteryLoading">
|
||
|
|
<el-col :span="batteryTimeline.length ? 6 : 0" v-if="batteryTimeline.length">
|
||
|
|
<div class="details-left">
|
||
|
|
<el-timeline class="timeline">
|
||
|
|
<el-timeline-item
|
||
|
|
v-for="(item, index) in batteryTimeline"
|
||
|
|
:key="index"
|
||
|
|
:icon="item.is_select === '1' ? 'el-icon-check' : ''"
|
||
|
|
:type="item.is_select === '1' ? 'success' : ''"
|
||
|
|
:color="item.is_select === '1' ? '#67C23A' : ''"
|
||
|
|
@click.native="selectBatteryTimeline(index)"
|
||
|
|
>
|
||
|
|
<p>
|
||
|
|
{{ $t(key('process')) }}: {{ item.process_name }}
|
||
|
|
<span v-if="item.is_next_process_code == 1" class="current-process">
|
||
|
|
{{ $t(key('current_process')) }}
|
||
|
|
</span>
|
||
|
|
</p>
|
||
|
|
<el-card :class="{ selected: item.is_select === '1' }">
|
||
|
|
<p>{{ $t(key('start_time')) }}: {{ item.beginTime }}</p>
|
||
|
|
<p>{{ $t(key('end_time')) }}: {{ item.endTime }}</p>
|
||
|
|
<p>{{ $t(key('device_number')) }}: {{ item.device_code }}</p>
|
||
|
|
</el-card>
|
||
|
|
</el-timeline-item>
|
||
|
|
</el-timeline>
|
||
|
|
</div>
|
||
|
|
</el-col>
|
||
|
|
<el-col :span="batteryTimeline.length ? 18 : 24">
|
||
|
|
<div class="drawer-main">
|
||
|
|
<el-form :inline="true" size="mini">
|
||
|
|
<el-form-item :label="$t(key('project_name'))">
|
||
|
|
<el-input
|
||
|
|
v-model="batterySearch"
|
||
|
|
:placeholder="$t(key('enter_project_name_search'))"
|
||
|
|
clearable
|
||
|
|
style="width:320px"
|
||
|
|
/>
|
||
|
|
</el-form-item>
|
||
|
|
</el-form>
|
||
|
|
<el-table :data="filteredBatteryDetails" border height="calc(100vh - 160px)">
|
||
|
|
<el-table-column type="index" width="60" :label="$t(key('serial_number'))" />
|
||
|
|
<el-table-column min-width="180" prop="name" :label="$t(key('project_name'))" />
|
||
|
|
<el-table-column min-width="220" prop="content" :label="$t(key('content'))" />
|
||
|
|
</el-table>
|
||
|
|
</div>
|
||
|
|
</el-col>
|
||
|
|
</el-row>
|
||
|
|
</el-drawer>
|
||
|
|
</d2-container>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
import { useTableColumns } from '@/composables/useTableColumns'
|
||
|
|
import { useTableButtons } from '@/composables/useTableButtons'
|
||
|
|
import { i18nMixin } from '@/composables/useI18n'
|
||
|
|
import { confirmMixin } from '@/composables/useConfirmHandle'
|
||
|
|
import {
|
||
|
|
getBatchTrayList,
|
||
|
|
trayUnbinding,
|
||
|
|
getBatteryParam
|
||
|
|
} from '@/api/planning-production/batch-tray'
|
||
|
|
import { sendWorkerman } from '@/api/production-master-data/workerman'
|
||
|
|
import PageTable from '@/components/page-table'
|
||
|
|
|
||
|
|
export default {
|
||
|
|
name: 'planning-production-tray-tracking',
|
||
|
|
components: { PageTable },
|
||
|
|
mixins: [i18nMixin('page.planning_production.batch_management.tray_tracking'), confirmMixin],
|
||
|
|
data () {
|
||
|
|
return {
|
||
|
|
loading: false,
|
||
|
|
tableData: [],
|
||
|
|
pagination: { current: 1, size: 10, total: 0 },
|
||
|
|
search: {
|
||
|
|
tray: '',
|
||
|
|
batch: '',
|
||
|
|
active: '',
|
||
|
|
create_time: []
|
||
|
|
},
|
||
|
|
columns: [],
|
||
|
|
rowButtons: [],
|
||
|
|
trayDrawerVisible: false,
|
||
|
|
trayDrawerTitle: '',
|
||
|
|
trayTimeline: [],
|
||
|
|
trayDetailData: [],
|
||
|
|
traySearch: '',
|
||
|
|
batteryDrawerVisible: false,
|
||
|
|
batteryDrawerTitle: '',
|
||
|
|
batteryLoading: false,
|
||
|
|
batteryTimeline: [],
|
||
|
|
batteryDetailData: [],
|
||
|
|
batterySearch: ''
|
||
|
|
}
|
||
|
|
},
|
||
|
|
computed: {
|
||
|
|
filteredTrayDetails () {
|
||
|
|
const keyword = String(this.traySearch || '').toLowerCase()
|
||
|
|
if (!keyword) return this.trayDetailData
|
||
|
|
return this.trayDetailData.filter(row => String(row.battery_id || '').toLowerCase().includes(keyword))
|
||
|
|
},
|
||
|
|
filteredBatteryDetails () {
|
||
|
|
const keyword = String(this.batterySearch || '').toLowerCase()
|
||
|
|
if (!keyword) return this.batteryDetailData
|
||
|
|
return this.batteryDetailData.filter(row => String(row.name || '').toLowerCase().includes(keyword))
|
||
|
|
}
|
||
|
|
},
|
||
|
|
created () {
|
||
|
|
this.search.tray = this.$route.params.tray || ''
|
||
|
|
this.search.batch = this.$route.params.batch || ''
|
||
|
|
this.columns = useTableColumns([
|
||
|
|
{ prop: 'tray', label: this.key('tray_no'), minWidth: 140 },
|
||
|
|
{ prop: 'lot', label: this.key('lot'), minWidth: 120 },
|
||
|
|
{ prop: 'status', label: this.key('status'), slot: 'status', minWidth: 110 },
|
||
|
|
{ prop: 'batch', label: this.key('batch_no'), minWidth: 140 },
|
||
|
|
{ prop: 'flow_name', label: this.key('flow_name'), minWidth: 160 },
|
||
|
|
{ prop: 'next_process_code', label: this.key('next_process_code'), minWidth: 140 },
|
||
|
|
{ prop: 'actual_input_battery_count', label: this.key('loaded_battery_qty'), minWidth: 150 },
|
||
|
|
{ prop: 'create_time', label: this.key('login_time'), minWidth: 170 },
|
||
|
|
{ prop: '_actions', label: this.key('actions'), width: 220, fixed: 'right' }
|
||
|
|
], { selectionWidth: 0 })
|
||
|
|
const btns = useTableButtons({
|
||
|
|
row: [
|
||
|
|
{
|
||
|
|
key: 'detail',
|
||
|
|
label: this.key('detail'),
|
||
|
|
icon: 'el-icon-position',
|
||
|
|
auth: '/planning_production/production_batch_management/batch_tray/trace',
|
||
|
|
onClick: this.openTrayDetails
|
||
|
|
},
|
||
|
|
{
|
||
|
|
key: 'unbind',
|
||
|
|
label: this.key('unbind'),
|
||
|
|
icon: 'el-icon-unlock',
|
||
|
|
auth: '/planning_production/production_batch_management/batch_tray/unbinding',
|
||
|
|
cssStyle: { color: '#E6A23C', marginRight: '10px', cursor: 'pointer' },
|
||
|
|
onClick: this.handleTrayUnbinding
|
||
|
|
},
|
||
|
|
{
|
||
|
|
key: 'stop',
|
||
|
|
label: this.key('stop'),
|
||
|
|
icon: 'el-icon-close',
|
||
|
|
color: 'danger',
|
||
|
|
auth: '/planning_production/production_batch_management/batch_tray/inactivity',
|
||
|
|
onClick: this.handleTrayInactivity
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}, this.$permission)
|
||
|
|
this.rowButtons = btns.rowButtons
|
||
|
|
this.fetchData()
|
||
|
|
},
|
||
|
|
methods: {
|
||
|
|
normalizeListResponse (res) {
|
||
|
|
const data = Array.isArray(res) ? res : (res && res.data) || []
|
||
|
|
if (Array.isArray(data)) return { list: data, total: data.length }
|
||
|
|
return {
|
||
|
|
list: data.data || [],
|
||
|
|
total: data.count || 0
|
||
|
|
}
|
||
|
|
},
|
||
|
|
async fetchData () {
|
||
|
|
this.loading = true
|
||
|
|
try {
|
||
|
|
const res = await getBatchTrayList({
|
||
|
|
...this.search,
|
||
|
|
page_no: this.pagination.current,
|
||
|
|
page_size: this.pagination.size
|
||
|
|
})
|
||
|
|
const { list, total } = this.normalizeListResponse(res)
|
||
|
|
this.tableData = list
|
||
|
|
this.pagination.total = total
|
||
|
|
} finally {
|
||
|
|
this.loading = false
|
||
|
|
}
|
||
|
|
},
|
||
|
|
onSearch () {
|
||
|
|
this.pagination.current = 1
|
||
|
|
this.fetchData()
|
||
|
|
},
|
||
|
|
onReset () {
|
||
|
|
this.search = { tray: '', batch: '', active: '', create_time: [] }
|
||
|
|
this.pagination.current = 1
|
||
|
|
this.fetchData()
|
||
|
|
},
|
||
|
|
onPageChange (page) {
|
||
|
|
this.pagination.current = page.current
|
||
|
|
this.pagination.size = page.size
|
||
|
|
this.fetchData()
|
||
|
|
},
|
||
|
|
openTrayDetails (row) {
|
||
|
|
this.trayDrawerTitle = `【${row.tray || ''}】${this.$t(this.key('tray_details'))}`
|
||
|
|
this.trayTimeline = row.date_log || []
|
||
|
|
this.trayDetailData = row.trayDetailsData || []
|
||
|
|
this.traySearch = ''
|
||
|
|
this.trayDrawerVisible = true
|
||
|
|
},
|
||
|
|
async openBatteryDetails (row) {
|
||
|
|
this.batteryDrawerTitle = `【${row.battery_id || ''}】${this.$t(this.key('battery_details'))}`
|
||
|
|
this.batteryDrawerVisible = true
|
||
|
|
this.batteryLoading = true
|
||
|
|
this.batterySearch = ''
|
||
|
|
try {
|
||
|
|
const res = await getBatteryParam({
|
||
|
|
subbatch: row.subbatch,
|
||
|
|
batch: row.batch,
|
||
|
|
tray: row.tray,
|
||
|
|
lot: row.lot,
|
||
|
|
battery_id: row.battery_id
|
||
|
|
})
|
||
|
|
const data = Array.isArray(res) ? res : (res && res.data) || []
|
||
|
|
this.batteryTimeline = data.map((item, index) => ({
|
||
|
|
...item,
|
||
|
|
is_select: index === 0 ? '1' : '0'
|
||
|
|
}))
|
||
|
|
this.batteryDetailData = this.batteryTimeline[0] ? (this.batteryTimeline[0].batteryDetailsData || []) : []
|
||
|
|
} finally {
|
||
|
|
this.batteryLoading = false
|
||
|
|
}
|
||
|
|
},
|
||
|
|
selectBatteryTimeline (index) {
|
||
|
|
this.batteryTimeline = this.batteryTimeline.map((item, itemIndex) => ({
|
||
|
|
...item,
|
||
|
|
is_select: itemIndex === index ? '1' : '0'
|
||
|
|
}))
|
||
|
|
this.batteryDetailData = this.batteryTimeline[index]
|
||
|
|
? (this.batteryTimeline[index].batteryDetailsData || [])
|
||
|
|
: []
|
||
|
|
},
|
||
|
|
async handleTrayUnbinding (row) {
|
||
|
|
await this.$confirmAction(
|
||
|
|
{
|
||
|
|
message: this.key('tray_unbind_confirm'),
|
||
|
|
title: this.key('prompt')
|
||
|
|
},
|
||
|
|
() => trayUnbinding({ batch: row.batch, tray: row.tray })
|
||
|
|
)
|
||
|
|
this.$message.success(this.$t(this.key('operation_success')))
|
||
|
|
this.pagination.current = Math.min(
|
||
|
|
this.pagination.current,
|
||
|
|
Math.ceil((this.pagination.total - 1) / this.pagination.size) || 1
|
||
|
|
)
|
||
|
|
this.fetchData()
|
||
|
|
},
|
||
|
|
async handleTrayInactivity (row) {
|
||
|
|
await this.$confirmAction(
|
||
|
|
{
|
||
|
|
message: this.key('tray_stop_confirm'),
|
||
|
|
title: this.key('prompt')
|
||
|
|
},
|
||
|
|
() => sendWorkerman({
|
||
|
|
sendData: {
|
||
|
|
action: 'set_tray_inactivity',
|
||
|
|
param: {
|
||
|
|
batch: row.batch,
|
||
|
|
tray: row.tray
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
)
|
||
|
|
this.$message.success(this.$t(this.key('operation_success')))
|
||
|
|
this.fetchData()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style scoped>
|
||
|
|
.search-bar {
|
||
|
|
padding: 10px 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/deep/ .el-form-item--mini.el-form-item {
|
||
|
|
margin-bottom: 4px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.drawer-header {
|
||
|
|
padding: 24px;
|
||
|
|
border-bottom: 1px solid #dcdfe6;
|
||
|
|
}
|
||
|
|
|
||
|
|
.details-left {
|
||
|
|
height: calc(100vh - 73px);
|
||
|
|
overflow-y: auto;
|
||
|
|
background: #f2f3f5;
|
||
|
|
}
|
||
|
|
|
||
|
|
.timeline {
|
||
|
|
padding-top: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.drawer-main {
|
||
|
|
padding: 20px 20px 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.current-process {
|
||
|
|
color: #f56c6c;
|
||
|
|
}
|
||
|
|
|
||
|
|
.link-text {
|
||
|
|
color: #409eff;
|
||
|
|
cursor: pointer;
|
||
|
|
}
|
||
|
|
|
||
|
|
.selected {
|
||
|
|
color: #fff;
|
||
|
|
background-color: #67c23a;
|
||
|
|
}
|
||
|
|
</style>
|