Files
klp-oa/klp-ui/src/views/mes/qc/inspectionSample/index.vue
砂糖 f197462b11 refactor(mes/qc): 重构质检模块接口与页面,迁移is模块业务到qc模块
1.  新增qc模块下检验主、检验明细、拉伸试验相关接口文件
2.  删除原is模块下的检验任务、样品库存、检验委托单接口与页面文件
3.  调整待办页面的检验任务标签页引用路径
4.  优化质检模板页面的检查项相关交互逻辑
2026-06-13 15:51:15 +08:00

530 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="app-container">
<div v-loading="filterLoading" style="margin-bottom: 16px; min-height: 50px;">
<div style="display: flex; align-items: center; margin-bottom: 6px;">
<span style="font-size: 13px; color: #909399; white-space: nowrap; margin-right: 12px; width: 68px; text-align: right;">所属单位</span>
<el-radio-group v-model="selectedCompany" size="small" @change="handleCompanyChange">
<el-radio-button label="">全部</el-radio-button>
<el-radio-button v-for="company in companyOptions" :key="company" :label="company">{{ company }}</el-radio-button>
</el-radio-group>
</div>
<div style="display: flex; align-items: center; margin-bottom: 6px;">
<span style="font-size: 13px; color: #909399; white-space: nowrap; margin-right: 12px; width: 68px; text-align: right;">方案名称</span>
<el-radio-group v-model="selectedScheme" size="small" @change="handleSchemeChange">
<el-radio-button label="">全部</el-radio-button>
<el-radio-button v-for="scheme in schemeOptions" :key="scheme" :label="scheme">{{ scheme }}</el-radio-button>
</el-radio-group>
</div>
<div style="display: flex; align-items: center;">
<span style="font-size: 13px; color: #909399; white-space: nowrap; margin-right: 12px; width: 68px; text-align: right;">检验日期</span>
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd HH:mm:ss"
size="small"
style="width: 340px;"
@change="handleDateRangeChange"
/>
</div>
</div>
<div v-if="currentTemplate">
<div class="card-header">
<h3>{{ currentTemplate.templateName }}</h3>
<div>
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAddMain">新增</el-button>
<el-button type="info" plain icon="el-icon-refresh" size="mini" @click="getMainList">刷新</el-button>
</div>
</div>
<el-table v-loading="mainLoading" :data="mainList" border stripe class="dynamic-table" fit max-height="calc(100vh - 260px)">
<el-table-column label="日期" align="center" width="150" fixed="left">
<template slot-scope="scope">
<el-date-picker
v-model="scope.row.inspectionDate"
type="date"
value-format="yyyy-MM-dd HH:mm:ss"
size="mini"
class="inspect-input"
style="width: 100%;"
/>
</template>
</el-table-column>
<el-table-column label="样品名称" align="center" width="130" min-width="130">
<template slot-scope="scope">
<el-input v-model="scope.row.sampleName" size="mini" class="inspect-input" />
</template>
</el-table-column>
<el-table-column label="样品编号" align="center" width="130" min-width="130">
<template slot-scope="scope">
<el-input v-model="scope.row.sampleNo" size="mini" class="inspect-input" />
</template>
</el-table-column>
<el-table-column label="来样编号" align="center" width="130" min-width="130">
<template slot-scope="scope">
<el-input v-model="scope.row.batchNo" size="mini" class="inspect-input" />
</template>
</el-table-column>
<el-table-column label="分析项目" align="center">
<el-table-column
v-for="checkItem in checkItems"
:key="checkItem.itemId"
:label="checkItem.itemName"
align="center"
min-width="130"
>
<el-table-column :label="checkItem.unit || ''" align="center" min-width="120">
<template slot-scope="scope">
<el-input
v-if="scope.row._items[checkItem.itemName]"
v-model="scope.row._items[checkItem.itemName].itemValue"
size="mini"
class="inspect-input"
/>
<span v-else>-</span>
</template>
</el-table-column>
</el-table-column>
</el-table-column>
<el-table-column label="备注" align="center" width="150">
<template slot-scope="scope">
<el-input v-model="scope.row.remark" size="mini" class="inspect-input" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="120" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="mini" :loading="scope.row._saving" @click="handleSaveMain(scope.row)">保存</el-button>
<el-button type="text" size="mini" style="color: #f56c6c;" @click="handleDeleteMain(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="mainTotal > 0"
:total="mainTotal"
:page.sync="mainQueryParams.pageNum"
:limit.sync="mainQueryParams.pageSize"
@pagination="getMainList"
/>
</div>
<el-empty v-else description="请先选择所属单位和检验方案" />
<el-dialog :title="'新增检验记录'" :visible.sync="addMainDialogVisible" width="600px" append-to-body>
<el-form ref="addMainForm" :model="addMainFormData" label-width="100px">
<el-form-item label="样品名称" prop="sampleName">
<el-input v-model="addMainFormData.sampleName" placeholder="请输入样品名称" />
</el-form-item>
<el-form-item label="样品编号" prop="sampleNo">
<el-input v-model="addMainFormData.sampleNo" placeholder="请输入样品编号" />
</el-form-item>
<el-form-item label="来样编号" prop="batchNo">
<el-input v-model="addMainFormData.batchNo" placeholder="请输入来样编号" />
</el-form-item>
<el-form-item label="检验日期" prop="inspectionDate">
<el-date-picker v-model="addMainFormData.inspectionDate" type="date" value-format="yyyy-MM-dd HH:mm:ss" placeholder="请选择检验日期" style="width:100%" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="addMainFormData.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="addMainDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitAddMain">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listInspectionMain, addInspectionMain, updateInspectionMain, delInspectionMain } from '@/api/mes/qc/inspectionMain'
import { listInspectionDetail, addInspectionDetail, updateInspectionDetail } from '@/api/mes/qc/inspectionDetail'
import { listInspectionItemTemplate, getInfoByInspectionItem } from '@/api/mes/qc/inspectionItemTemplate'
export default {
name: 'InspectionSample',
data() {
return {
filterLoading: false,
mainLoading: false,
addMainDialogVisible: false,
templateList: [],
cascadeData: {},
companyOptions: [],
schemeOptions: [],
selectedCompany: '',
selectedScheme: '',
currentTemplate: null,
checkItems: [],
mainList: [],
mainTotal: 0,
mainQueryParams: {
pageNum: 1,
pageSize: 1000
},
dateRange: [],
addMainFormData: {
sampleName: '',
sampleNo: '',
batchNo: '',
inspectionDate: '',
remark: ''
}
}
},
created() {
this.initDateRange()
this.loadCascadeData()
},
methods: {
initDateRange() {
const now = new Date()
const year = now.getFullYear()
const month = now.getMonth()
const start = new Date(year, month, 1)
const end = new Date(year, month + 1, 0, 23, 59, 59)
const fmt = d => {
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${day} 00:00:00`
}
const fmtEnd = d => {
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${day} 23:59:59`
}
this.dateRange = [fmt(start), fmtEnd(end)]
},
parseTime(time, pattern) {
if (!time) return ''
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
const date = new Date(time)
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
return format.replace('{y}', year)
.replace('{m}', month.toString().padStart(2, '0'))
.replace('{d}', day.toString().padStart(2, '0'))
.replace('{h}', hour.toString().padStart(2, '0'))
.replace('{i}', minute.toString().padStart(2, '0'))
.replace('{s}', second.toString().padStart(2, '0'))
},
loadCascadeData() {
this.filterLoading = true
listInspectionItemTemplate({ pageNum: 1, pageSize: 9999 }).then(response => {
const templates = response.rows || response.data || []
this.templateList = templates
const cascade = {}
templates.forEach(t => {
const company = t.templateUnit || '未分类'
if (!cascade[company]) {
cascade[company] = []
}
if (t.templateName && !cascade[company].includes(t.templateName)) {
cascade[company].push(t.templateName)
}
})
this.cascadeData = cascade
this.companyOptions = Object.keys(cascade).sort()
const allSchemes = new Set()
Object.values(cascade).forEach(schemes => {
schemes.forEach(s => allSchemes.add(s))
})
this.schemeOptions = [...allSchemes].sort()
}).finally(() => {
this.filterLoading = false
})
},
handleCompanyChange() {
this.selectedScheme = ''
if (this.selectedCompany) {
this.schemeOptions = this.cascadeData[this.selectedCompany] || []
} else {
const allSchemes = new Set()
Object.values(this.cascadeData).forEach(schemes => {
schemes.forEach(s => allSchemes.add(s))
})
this.schemeOptions = [...allSchemes].sort()
}
this.currentTemplate = null
this.mainList = []
},
handleSchemeChange() {
if (!this.selectedScheme) {
this.currentTemplate = null
this.mainList = []
return
}
let template
if (this.selectedCompany) {
template = this.templateList.find(t =>
t.templateName === this.selectedScheme && (t.templateUnit || '未分类') === this.selectedCompany
)
} else {
template = this.templateList.find(t => t.templateName === this.selectedScheme)
}
if (template) {
this.currentTemplate = template
this.mainQueryParams.pageNum = 1
this.loadCheckItems(template)
this.getMainList()
}
},
handleDateRangeChange() {
this.mainQueryParams.pageNum = 1
this.getMainList()
},
loadCheckItems(template) {
if (!template.inspectionItem) {
this.checkItems = []
return
}
getInfoByInspectionItem(template.inspectionItem).then(response => {
this.checkItems = response.data || []
}).catch(() => {
this.checkItems = []
})
},
getMainList() {
if (!this.currentTemplate) return
this.mainLoading = true
const params = {
...this.mainQueryParams,
templateId: this.currentTemplate.templateId
}
if (this.dateRange && this.dateRange.length === 2) {
params.inspectionDateStart = this.dateRange[0]
params.inspectionDateEnd = this.dateRange[1]
}
listInspectionMain(params).then(response => {
this.mainList = (response.rows || []).map(main => {
const itemMap = {}
if (this.checkItems.length > 0) {
this.checkItems.forEach(item => {
itemMap[item.itemName] = {
detailId: undefined,
mainId: main.mainId,
itemName: item.itemName,
itemValue: '',
itemUnit: item.unit,
upperLimit: item.targetUpper,
lowerLimit: item.targetLower,
rangeDesc: ''
}
})
}
return {
...main,
_items: itemMap,
_originalItems: JSON.parse(JSON.stringify(itemMap))
}
})
this.mainTotal = response.total || 0
this.mainLoading = false
this.loadMainDetails()
}).catch(() => {
this.mainLoading = false
})
},
loadMainDetails() {
if (this.mainList.length === 0) return
const promises = this.mainList.map(main => {
return listInspectionDetail({ mainId: main.mainId, pageNum: 1, pageSize: 9999 }).then(response => {
const details = response.rows || []
details.forEach(detail => {
if (main._items[detail.itemName]) {
main._items[detail.itemName] = {
detailId: detail.detailId,
mainId: detail.mainId,
itemName: detail.itemName,
itemValue: detail.itemValue || '',
itemUnit: detail.itemUnit || main._items[detail.itemName].itemUnit,
upperLimit: detail.upperLimit,
lowerLimit: detail.lowerLimit,
rangeDesc: detail.rangeDesc
}
}
})
main._originalItems = JSON.parse(JSON.stringify(main._items))
}).catch(() => {})
})
return Promise.all(promises)
},
handleAddMain() {
this.addMainFormData = {
sampleName: '',
sampleNo: '',
batchNo: '',
inspectionDate: '',
remark: ''
}
this.addMainDialogVisible = true
},
submitAddMain() {
const data = {
...this.addMainFormData,
templateId: this.currentTemplate.templateId
}
addInspectionMain(data).then(() => {
this.$message.success('新增成功')
this.addMainDialogVisible = false
this.getMainList()
}).catch(() => {
this.$message.error('新增失败')
})
},
handleSaveMain(row) {
row._saving = true
updateInspectionMain({
mainId: row.mainId,
sampleName: row.sampleName,
sampleNo: row.sampleNo,
batchNo: row.batchNo,
inspectionDate: row.inspectionDate,
remark: row.remark
}).then(() => {
const items = this.checkItems.map(checkItem => {
const itemData = row._items[checkItem.itemName]
return itemData || { itemName: checkItem.itemName }
})
const promises = items.map(item => {
const data = {
mainId: row.mainId,
itemName: item.itemName,
itemValue: item.itemValue,
itemUnit: item.itemUnit,
upperLimit: item.upperLimit,
lowerLimit: item.lowerLimit
}
if (item.detailId) {
data.detailId = item.detailId
return updateInspectionDetail(data)
} else if (item.itemValue) {
return addInspectionDetail(data)
}
return Promise.resolve()
})
return Promise.all(promises)
}).then(() => {
this.$message.success('保存成功')
return listInspectionDetail({ mainId: row.mainId, pageNum: 1, pageSize: 9999 })
}).then(response => {
const details = response.rows || []
if (this.checkItems.length > 0) {
this.checkItems.forEach(item => {
const found = details.find(d => d.itemName === item.itemName)
if (found) {
row._items[item.itemName] = {
detailId: found.detailId,
mainId: found.mainId,
itemName: found.itemName,
itemValue: found.itemValue || '',
itemUnit: found.itemUnit || item.unit,
upperLimit: found.upperLimit,
lowerLimit: found.lowerLimit,
rangeDesc: found.rangeDesc
}
}
})
}
row._originalItems = JSON.parse(JSON.stringify(row._items))
row._saving = false
}).catch(() => {
this.$message.error('保存失败')
row._saving = false
})
},
handleDeleteMain(row) {
this.$confirm('是否确认删除该检验记录?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delInspectionMain(row.mainId).then(() => {
this.$message.success('删除成功')
this.getMainList()
}).catch(() => {
this.$message.error('删除失败')
})
})
}
}
}
</script>
<style scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.card-header h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.dynamic-table {
width: 100%;
overflow-x: auto;
}
::v-deep .dynamic-table .el-table__header-wrapper {
background-color: #f0f5ff;
}
::v-deep .dynamic-table .el-table__header-wrapper th {
background-color: #f0f5ff;
}
::v-deep .inspect-input .el-input__inner {
border: 1px solid #e4e7ed;
border-radius: 2px;
text-align: center;
background-color: #fff;
padding: 0 4px;
height: 24px;
line-height: 24px;
font-size: 13px;
}
::v-deep .inspect-input.is-focus .el-input__inner {
border-color: #409eff;
}
::v-deep .dynamic-table .el-table__cell {
padding: 2px 0;
}
::v-deep .dynamic-table .el-table__header-wrapper th {
padding: 4px 0;
font-size: 13px;
}
::v-deep .dynamic-table .el-table__body-wrapper {
overflow-x: auto;
}
</style>