迁移物料监控模块
Some checks failed
Release pipeline / publish (push) Has been cancelled
Release pipeline / Always run job (push) Has been cancelled

- 新增计划与生产物料监控 V2 页面

- 新增 WIP 物料监控接口、旧路径路由和中英文文案

- 更新迁移任务列表中的物料监控状态
This commit is contained in:
sheng
2026-06-22 16:12:59 +08:00
parent 6bb2d1285c
commit 9863bf1113
6 changed files with 457 additions and 3 deletions

View File

@@ -0,0 +1,353 @@
<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('batch_no'))" prop="batch">
<el-input
v-model="search.batch"
:placeholder="$t(key('enter_batch_no'))"
clearable
style="width:200px"
@keyup.enter.native="onSearch"
/>
</el-form-item>
<el-form-item :label="$t(key('semi_finished_id'))" prop="item_id">
<el-input
v-model="search.item_id"
:placeholder="$t(key('enter_semi_finished_id'))"
clearable
style="width:200px"
@keyup.enter.native="onSearch"
/>
</el-form-item>
<el-form-item :label="$t(key('workingsubclass'))" prop="workingsubclass">
<el-select
v-model="search.workingsubclass"
:placeholder="$t(key('select_workingsubclass'))"
clearable
filterable
style="width:200px"
@focus="loadBatchOptions"
>
<el-option
v-for="item in processOptions"
:key="item.code || item.name"
:label="item.name || item.code"
:value="item.code || item.name"
/>
</el-select>
</el-form-item>
<el-form-item :label="$t(key('finish_time'))" prop="start_time">
<el-date-picker
v-model="search.start_time"
type="datetimerange"
range-separator="-"
:start-placeholder="$t(key('start_time'))"
:end-placeholder="$t(key('end_time'))"
value-format="yyyy-MM-dd HH:mm:ss"
style="width:330px"
/>
</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>
<page-table
:columns="columns"
:data="tableData"
:loading="loading"
:toolbar-buttons="toolbarButtons"
:row-buttons="[]"
:pagination="pagination"
:table-attrs="{ size: 'mini', rowKey: 'id', highlightCurrentRow: true }"
auto-height
@page-change="onPageChange"
>
<template #col-last_status="{ row }">
<el-tag :type="Number(row.last_status) === 0 ? 'danger' : 'success'" size="mini">
{{ Number(row.last_status) === 0 ? $t(key('finished')) : $t(key('status_unused')) }}
</el-tag>
</template>
<template #empty>
<el-empty :description="$t('暂无数据')" :image-size="80" />
</template>
</page-table>
<el-dialog :title="$t(key('input_semi_product_data'))" :visible.sync="dialogVisible" width="520px">
<el-form ref="form" :model="form" :rules="rules" label-width="130px" size="mini">
<el-form-item :label="$t(key('batch_id'))" prop="batch_id">
<el-select
v-model="form.batch_id"
:placeholder="$t(key('select_batch_id'))"
clearable
filterable
style="width:100%"
@focus="loadBatchOptions"
@change="onBatchChange"
>
<el-option
v-for="item in batchOptions"
:key="item.id"
:label="item.batch"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="$t(key('workingsubclass'))" prop="process_code">
<el-select
v-model="form.process_code"
:placeholder="$t(key('select_workingsubclass'))"
clearable
filterable
style="width:100%"
:disabled="!form.batch_id"
>
<el-option
v-for="item in selectedBatchProcesses"
:key="item.code || item.name"
:label="item.name || item.code"
:value="item.code || item.name"
/>
</el-select>
</el-form-item>
<el-form-item :label="$t(key('device_code'))" prop="device_code">
<el-select
v-model="form.device_code"
:placeholder="$t(key('select_device_code'))"
clearable
filterable
style="width:100%"
@focus="loadDeviceOptions"
>
<el-option
v-for="item in deviceOptions"
:key="item.code || item.device_code"
:label="item.name || item.device_name || item.code || item.device_code"
:value="item.code || item.device_code"
/>
</el-select>
</el-form-item>
<el-form-item :label="$t(key('output_quantity'))" prop="item_quantity">
<el-input-number v-model="form.item_quantity" :min="1" style="width:100%" />
</el-form-item>
<el-form-item :label="$t(key('output_date'))" prop="start_time">
<el-date-picker
v-model="form.start_time"
type="date"
value-format="yyyy-MM-dd"
style="width:100%"
:placeholder="$t(key('output_date'))"
/>
</el-form-item>
</el-form>
<span slot="footer">
<el-button size="mini" @click="dialogVisible = false">{{ $t(key('cancel')) }}</el-button>
<el-button size="mini" type="primary" :loading="submitting" @click="submitForm">
{{ $t(key('confirm')) }}
</el-button>
</span>
</el-dialog>
</d2-container>
</template>
<script>
import { useTableColumns } from '@/composables/useTableColumns'
import { i18nMixin } from '@/composables/useI18n'
import PageTable from '@/components/page-table'
import { getBatchAll } from '@/api/planning-production/batch-list'
import { getDeviceAll } from '@/api/planning-production/equipment-monitoring'
import { createWipData, getWipDataList } from '@/api/planning-production/material-monitoring'
export default {
name: 'planning-production-material-monitoring',
components: { PageTable },
mixins: [i18nMixin('page.planning_production.production_monitoring.material_monitoring')],
data () {
return {
loading: false,
submitting: false,
dialogVisible: false,
search: {
batch: '',
item_id: '',
workingsubclass: '',
start_time: []
},
tableData: [],
pagination: {
current: 1,
size: 10,
total: 0
},
form: {
batch_id: '',
process_code: '',
device_code: '',
item_quantity: undefined,
start_time: ''
},
batchOptions: [],
deviceOptions: []
}
},
computed: {
columns () {
return useTableColumns([
{ prop: 'batch', label: this.key('batch_no'), minWidth: 150, showOverflowTooltip: true },
{ prop: 'item_id', label: this.key('semi_finished_id'), minWidth: 160, showOverflowTooltip: true },
{ prop: 'item_code', label: this.key('semi_product_code'), minWidth: 170, showOverflowTooltip: true },
{ prop: 'item_quantity', label: this.key('output_quantity'), minWidth: 120 },
{ prop: 'workingsubclass', label: this.key('workingsubclass'), minWidth: 150, showOverflowTooltip: true },
{ prop: 'device_code', label: this.key('production_device'), minWidth: 150, showOverflowTooltip: true },
{ prop: 'last_status', label: this.key('last_status'), width: 120, slot: 'last_status' },
{ prop: 'finish_time', label: this.key('finish_time'), minWidth: 170, showOverflowTooltip: true }
], {
selectionWidth: 0,
indexWidth: 55
})
},
toolbarButtons () {
return [
{
key: 'create',
label: this.key('input_semi_product_data'),
type: 'primary',
icon: 'el-icon-plus',
size: 'mini',
onClick: this.openDialog
}
]
},
rules () {
return {
batch_id: [{ required: true, message: this.$t(this.key('select_batch_id')), trigger: 'change' }],
process_code: [{ required: true, message: this.$t(this.key('select_workingsubclass')), trigger: 'change' }],
device_code: [{ required: true, message: this.$t(this.key('select_device_code')), trigger: 'change' }],
item_quantity: [{ required: true, message: this.$t(this.key('enter_output_quantity')), trigger: 'blur' }]
}
},
selectedBatch () {
return this.batchOptions.find(item => String(item.id) === String(this.form.batch_id)) || {}
},
selectedBatchProcesses () {
return Array.isArray(this.selectedBatch.process) ? this.selectedBatch.process : []
},
processOptions () {
const map = new Map()
this.batchOptions.forEach(batch => {
const processes = Array.isArray(batch.process) ? batch.process : []
processes.forEach(item => {
const value = item.code || item.name
if (value && !map.has(value)) map.set(value, item)
})
})
return Array.from(map.values())
}
},
created () {
this.fetchData()
},
methods: {
responseData (res) {
return res && res.data ? res.data : (res || {})
},
searchParams () {
const params = {
batch: this.search.batch || undefined,
item_id: this.search.item_id || undefined,
workingsubclass: this.search.workingsubclass || undefined
}
if (Array.isArray(this.search.start_time) && this.search.start_time.length === 2) {
params.start_time = this.search.start_time[0]
params.end_time = this.search.start_time[1]
}
return params
},
async fetchData () {
this.loading = true
try {
const res = await getWipDataList({
...this.searchParams(),
page_no: this.pagination.current,
page_size: this.pagination.size
})
const payload = this.responseData(res)
this.tableData = Array.isArray(payload.data) ? payload.data : []
this.pagination.total = Number(payload.count || 0)
} finally {
this.loading = false
}
},
onSearch () {
this.pagination.current = 1
this.fetchData()
},
onReset () {
this.search = {
batch: '',
item_id: '',
workingsubclass: '',
start_time: []
}
this.onSearch()
},
onPageChange (pagination) {
this.pagination = pagination
this.fetchData()
},
openDialog () {
this.dialogVisible = true
this.form = {
batch_id: '',
process_code: '',
device_code: '',
item_quantity: undefined,
start_time: ''
}
this.$nextTick(() => {
if (this.$refs.form) this.$refs.form.clearValidate()
})
this.loadBatchOptions()
this.loadDeviceOptions()
},
async loadBatchOptions () {
if (this.batchOptions.length) return
const res = await getBatchAll({})
const data = Array.isArray(res) ? res : (res && res.data) || []
this.batchOptions = Array.isArray(data) ? data : []
},
async loadDeviceOptions () {
if (this.deviceOptions.length) return
const res = await getDeviceAll({})
const payload = this.responseData(res)
this.deviceOptions = Array.isArray(payload.data) ? payload.data : []
},
onBatchChange () {
this.form.process_code = ''
},
submitForm () {
this.$refs.form.validate(async valid => {
if (!valid) return
this.submitting = true
try {
await createWipData({ ...this.form })
this.$message.success(this.$t(this.key('operation_success')))
this.dialogVisible = false
this.fetchData()
} finally {
this.submitting = false
}
})
}
}
}
</script>