feat: 多个页面优化与功能增强

1. 磨削页面:添加操作人权限控制,自动填充上次硬度值,移除字典值字段
2. 质保书页面:新增质保书类型筛选与表单字段,移除模板选择弹窗
3. 成本综合页面:添加产线校验、录入/查看切换、表格列控制与快捷操作
4. 质保书条目页面:新增类型筛选与表单字段,移除模板选择弹窗
This commit is contained in:
2026-06-04 10:26:09 +08:00
parent b4fbb8dfc8
commit 31d8d1ee16
4 changed files with 187 additions and 204 deletions

View File

@@ -33,6 +33,11 @@
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="质保书类型" prop="certificateType">
<el-select v-model="queryParams.certificateType" placeholder="请选择质保书类型" clearable>
<el-option v-for="item in certificateTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="收货单位" prop="consignee">
<el-input
v-model="queryParams.consignee"
@@ -114,6 +119,7 @@
<el-table-column label="执行标准" align="center" prop="standard" />
<el-table-column label="收货单位" align="center" prop="consignee" />
<el-table-column label="生产厂家" align="center" prop="manufacturer" />
<el-table-column label="质保书类型" align="center" prop="certificateType" />
<el-table-column label="签发日期" align="center" prop="issueDate">
<template #default="scope">
{{ parseTime(scope.row.issueDate, '{y}-{m}-{d}') }}
@@ -186,6 +192,11 @@
placeholder="请选择签发日期">
</el-date-picker>
</el-form-item>
<el-form-item label="质保书类型" prop="certificateType">
<el-select v-model="form.certificateType" placeholder="请选择质保书类型">
<el-option v-for="item in certificateTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="质保证明说明" prop="note">
<el-input v-model="form.note" type="textarea" placeholder="请输入内容" auto-height />
</el-form-item>
@@ -227,23 +238,7 @@
:template-type="selectedTemplateType"
/>
<!-- 模板选择对话框 -->
<el-dialog title="选择质保书模板" :visible.sync="templateDialogVisible" width="500px" append-to-body>
<div class="template-selection">
<el-radio-group v-model="selectedTemplateType" class="template-radio-group">
<el-radio v-for="option in templateOptions" :key="option.value" :label="option.value" class="template-radio">
<div class="template-option">
<i class="el-icon-document"></i>
<span>{{ option.label }}</span>
</div>
</el-radio>
</el-radio-group>
</div>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="confirmTemplate"> </el-button>
<el-button @click="templateDialogVisible = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
@@ -251,7 +246,6 @@
import { listCertificate, getCertificate, delCertificate, addCertificate, updateCertificate } from "@/api/mes/qc/certificate";
import { listCertificateItem } from "@/api/mes/qc/certificateItem";
import CertificatePrintPreview from "./components/CertificatePrintPreview.vue";
import { templateOptions } from "./components/templates";
import { print as printPdf, downloadPdf } from "./lib/printUtils";
export default {
@@ -261,7 +255,6 @@ export default {
},
data() {
return {
templateOptions,
buttonLoading: false,
loading: true,
ids: [],
@@ -280,10 +273,13 @@ export default {
printComponentVisible: false,
printCertificateData: {},
printItemsData: [],
templateDialogVisible: false,
selectedTemplateType: 'chromium',
templateActionType: 'print',
templateDialogResolve: null,
certificateTypeOptions: [
{ value: '冷硬', label: '冷硬' },
{ value: '镀锌', label: '镀锌' },
{ value: '镀铬', label: '镀铬' },
{ value: '原料', label: '原料' }
],
queryParams: {
pageNum: 1,
pageSize: 10,
@@ -291,6 +287,7 @@ export default {
contractNo: undefined,
productName: undefined,
standard: undefined,
certificateType: undefined,
consignee: undefined,
manufacturer: undefined,
},
@@ -312,6 +309,10 @@ export default {
};
},
created() {
const routeType = this.$route.query.certificateType;
if (routeType) {
this.queryParams.certificateType = routeType;
}
this.getList();
},
methods: {
@@ -326,6 +327,15 @@ export default {
getActualStatus(status) {
return status || 'PENDING';
},
getTemplateType(certificateType) {
const map = {
'冷硬': 'coldHard',
'镀锌': 'galvanizing',
'镀铬': 'chromium',
'原料': 'rawMaterial'
};
return map[certificateType] || 'chromium';
},
/** 查询质量证明书主列表 */
getList() {
this.loading = true;
@@ -351,6 +361,7 @@ export default {
consignee: undefined,
manufacturer: undefined,
issueDate: undefined,
certificateType: undefined,
note: 'D.T=Denu_Test \t T.S=Tensile Strength \t D=弯心直径MandrelDiameter \n Strength \t G.L=拉伸标距GaugeLength \t EL=Percentage Elongation After Fracture',
remark: '1.本产品经检验满足订货标准要求。The material has been tested with satisfactory resultsin accordance with the speciicatication \n 2.本质量证明书中空白项目均不作为交货条件。The blank items shouldntbe regarded as delivery conditions. \n 3.盖章后生效。The quality certificate willcome into force with a valid stamp.',
delFlag: undefined,
@@ -380,6 +391,10 @@ export default {
/** 新增按钮操作 */
handleAdd() {
this.reset();
const routeType = this.$route.query.certificateType;
if (routeType) {
this.form.certificateType = routeType;
}
this.open = true;
this.title = "添加质量证明书主";
},
@@ -475,10 +490,6 @@ export default {
},
/** 预览按钮操作 */
async handlePreview(row) {
this.templateActionType = 'preview';
const confirmed = await this.showTemplateDialog();
if (!confirmed) return;
const certificateId = row.certificateId;
this.loading = true;
try {
@@ -489,6 +500,7 @@ export default {
this.previewData = certRes.data;
this.previewItems = itemsRes.rows || [];
this.previewTitle = `预览 - ${certRes.data.certificateNo}`;
this.selectedTemplateType = this.getTemplateType(certRes.data.certificateType);
this.previewVisible = true;
} catch (error) {
this.$message.error('获取数据失败');
@@ -498,10 +510,6 @@ export default {
},
/** 打印按钮操作 */
async handlePrint(row) {
this.templateActionType = 'print';
const confirmed = await this.showTemplateDialog();
if (!confirmed) return;
const certificateId = row.certificateId;
await this.preparePrintComponent(certificateId);
await this.$nextTick();
@@ -544,6 +552,7 @@ export default {
]);
this.printCertificateData = certRes.data;
this.printItemsData = itemsRes.rows || [];
this.selectedTemplateType = this.getTemplateType(certRes.data.certificateType);
this.printComponentVisible = true;
await this.$nextTick();
} catch (error) {
@@ -552,19 +561,6 @@ export default {
this.loading = false;
}
},
showTemplateDialog() {
return new Promise((resolve) => {
this.templateDialogResolve = resolve;
this.templateDialogVisible = true;
});
},
confirmTemplate() {
this.templateDialogVisible = false;
if (this.templateDialogResolve) {
this.templateDialogResolve(true);
this.templateDialogResolve = null;
}
}
}
};
</script>
@@ -586,45 +582,5 @@ export default {
font-weight: 500;
}
.template-selection {
padding: 20px;
}
.template-radio-group {
display: flex;
flex-direction: column;
gap: 15px;
}
.template-radio {
display: flex;
align-items: center;
margin: 0;
padding: 15px;
border: 1px solid #e4e7ed;
border-radius: 8px;
transition: all 0.3s ease;
}
.template-radio:hover {
border-color: #409eff;
background-color: #ecf5ff;
}
.template-radio.is-checked {
border-color: #409eff;
background-color: #ecf5ff;
}
.template-option {
display: flex;
align-items: center;
gap: 10px;
font-size: 14px;
}
.template-option i {
font-size: 20px;
color: #409eff;
}
</style>

View File

@@ -1,8 +1,13 @@
<template>
<div class="app-container">
<div class="tab-container">
<div class="select-button" @click="openCertificateDialog">
<i class="el-icon-setting"></i>
<div class="filter-group">
<div class="select-button" @click="openCertificateDialog">
<i class="el-icon-setting"></i>
</div>
<el-select v-model="certificateTypeFilter" placeholder="质保书类型" clearable @change="handleCertificateTypeFilterChange">
<el-option v-for="item in certificateTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="custom-tabs">
<div class="tab-nav">
@@ -84,6 +89,13 @@
<el-input v-model="currentCertificateInfo.manufacturer" @change="handleCertificateInfoChange" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="质保书类型">
<el-select v-model="currentCertificateInfo.certificateType" @change="handleCertificateInfoChange" style="width:100%">
<el-option v-for="item in certificateTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
@@ -397,6 +409,11 @@
<el-date-picker v-model="certificateForm.issueDate" value-format="yyyy-MM-dd HH:mm:ss" type="date"
placeholder="请选择签发日期" />
</el-form-item>
<el-form-item label="质保书类型" prop="certificateType">
<el-select v-model="certificateForm.certificateType" placeholder="请选择质保书类型" style="width:100%">
<el-option v-for="item in certificateTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="质保证明说明" prop="note">
<el-input v-model="certificateForm.note" type="textarea" placeholder="请输入内容" auto-height />
</el-form-item>
@@ -417,23 +434,7 @@
<CertificatePrintPreview ref="certificatePrint" v-show="false" :certificate="printCertificateData"
:items="printItemsData" :template-type="selectedTemplateType" />
<!-- 模板选择对话框 -->
<el-dialog title="选择质保书模板" :visible.sync="templateDialogVisible" width="500px" append-to-body>
<div class="template-selection">
<el-radio-group v-model="selectedTemplateType" class="template-radio-group">
<el-radio v-for="option in templateOptions" :key="option.value" :label="option.value" class="template-radio">
<div class="template-option">
<i class="el-icon-document"></i>
<span>{{ option.label }}</span>
</div>
</el-radio>
</el-radio-group>
</div>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="confirmTemplate"> </el-button>
<el-button @click="templateDialogVisible = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
@@ -445,7 +446,6 @@ import { listChemicalItem } from "@/api/mes/qc/chemicalItem";
import { listPhysicalItem } from "@/api/mes/qc/physicalItem";
import CoilSelector from "@/components/CoilSelector/index.vue";
import CertificatePrintPreview from "./components/CertificatePrintPreview.vue";
import { templateOptions } from "./components/templates";
import { print as printPdf } from "./lib/printUtils";
export default {
@@ -461,7 +461,13 @@ export default {
},
data() {
return {
templateOptions,
certificateTypeOptions: [
{ value: '冷硬', label: '冷硬' },
{ value: '镀锌', label: '镀锌' },
{ value: '镀铬', label: '镀铬' },
{ value: '原料', label: '原料' }
],
certificateTypeFilter: undefined,
buttonLoading: false,
loading: false,
certificateLoading: false,
@@ -480,9 +486,7 @@ export default {
printComponentVisible: false,
printCertificateData: {},
printItemsData: [],
templateDialogVisible: false,
selectedTemplateType: 'chromium',
templateDialogResolve: null,
certificateForm: {
certificateNo: '',
contractNo: '',
@@ -491,6 +495,7 @@ export default {
consignee: '',
manufacturer: '',
issueDate: '',
certificateType: undefined,
remark: ''
},
coilLoading: false,
@@ -508,7 +513,8 @@ export default {
pageSize: 10,
certificateNo: undefined,
contractNo: undefined,
productName: undefined
productName: undefined,
certificateType: undefined
},
coilQueryParams: {
pageNum: 1,
@@ -540,6 +546,15 @@ export default {
.replace('{i}', minute.toString().padStart(2, '0'))
.replace('{s}', second.toString().padStart(2, '0'));
},
getTemplateType(certificateType) {
const map = {
'冷硬': 'coldHard',
'镀锌': 'galvanizing',
'镀铬': 'chromium',
'原料': 'rawMaterial'
};
return map[certificateType] || 'chromium';
},
getCertificateList() {
this.certificateLoading = true;
listCertificate(this.certificateQueryParams).then(response => {
@@ -559,8 +574,15 @@ export default {
pageSize: 10,
certificateNo: undefined,
contractNo: undefined,
productName: undefined
productName: undefined,
certificateType: undefined
};
this.certificateTypeFilter = undefined;
this.getCertificateList();
},
handleCertificateTypeFilterChange(val) {
this.certificateQueryParams.certificateType = val || undefined;
this.certificateQueryParams.pageNum = 1;
this.getCertificateList();
},
handleTabClick(certificate) {
@@ -730,6 +752,7 @@ export default {
consignee: '',
manufacturer: '',
issueDate: '',
certificateType: this.certificateTypeFilter || undefined,
note: 'D.T=Denu_Test \t T.S=Tensile Strength \t D=弯心直径MandrelDiameter \n Strength \t G.L=拉伸标距GaugeLength \t EL=Percentage Elongation After Fracture',
remark: '1.本产品经检验满足订货标准要求。The material has been tested with satisfactory resultsin accordance with the speciicatication \n 2.本质量证明书中空白项目均不作为交货条件。The blank items shouldntbe regarded as delivery conditions. \n 3.盖章后生效。The quality certificate willcome into force with a valid stamp.',
};
@@ -823,30 +846,13 @@ export default {
});
},
async handlePrint() {
const confirmed = await this.showTemplateDialog();
if (!confirmed) return;
this.selectedTemplateType = this.getTemplateType(this.currentCertificateInfo.certificateType);
this.printCertificateData = this.currentCertificateInfo;
this.printItemsData = this.certificateItemList || [];
this.$nextTick(() => {
const el = this.$refs.certificatePrint.$refs.certificateContent;
console.log(el);
printPdf(el);
});
await this.$nextTick();
const el = this.$refs.certificatePrint.$refs.certificateContent;
printPdf(el);
},
showTemplateDialog() {
return new Promise((resolve) => {
this.templateDialogResolve = resolve;
this.templateDialogVisible = true;
});
},
confirmTemplate() {
this.templateDialogVisible = false;
if (this.templateDialogResolve) {
this.templateDialogResolve(true);
this.templateDialogResolve = null;
}
}
}
};
</script>
@@ -858,27 +864,49 @@ export default {
margin-bottom: 20px;
}
.select-button {
.filter-group {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
border: 1px solid #e4e7ed;
border-radius: 4px;
overflow: hidden;
margin-right: 10px;
background-color: #f9f9f9;
}
.filter-group:hover {
border-color: #c6e2ff;
}
.filter-group .select-button {
display: flex;
align-items: center;
padding: 10px 12px;
cursor: pointer;
transition: all 0.3s ease;
background-color: #f9f9f9;
color: #606266;
color: #409eff;
font-size: 14px;
user-select: none;
margin-right: 10px;
color: #409eff;
border-right: 1px solid #e4e7ed;
}
.select-button:hover {
.filter-group .select-button:hover {
background-color: #ecf5ff;
border-color: #c6e2ff;
color: #409eff;
}
.filter-group .el-select {
width: 140px;
}
.filter-group .el-select >>> .el-input__inner {
border: none;
background: transparent;
border-radius: 0;
}
.filter-group .el-select >>> .el-input__suffix {
right: 8px;
}
.custom-tabs {
@@ -1021,45 +1049,5 @@ export default {
padding: 0;
}
.template-selection {
padding: 20px;
}
.template-radio-group {
display: flex;
flex-direction: column;
gap: 15px;
}
.template-radio {
display: flex;
align-items: center;
margin: 0;
padding: 15px;
border: 1px solid #e4e7ed;
border-radius: 8px;
transition: all 0.3s ease;
}
.template-radio:hover {
border-color: #409eff;
background-color: #ecf5ff;
}
.template-radio.is-checked {
border-color: #409eff;
background-color: #ecf5ff;
}
.template-option {
display: flex;
align-items: center;
gap: 10px;
font-size: 14px;
}
.template-option i {
font-size: 20px;
color: #409eff;
}
</style>

View File

@@ -109,7 +109,7 @@
<div slot="header" class="card-header">
<span class="card-title"><i class="el-icon-document" /> 磨削台账</span>
<span style="margin-left:auto;display:flex;gap:8px">
<el-button size="mini" type="text" icon="el-icon-setting" @click="openOperatorDict">操作人维护</el-button>
<el-button v-hasPermi="['system:dict:list']" size="mini" type="text" icon="el-icon-setting" @click="openOperatorDict">操作人维护</el-button>
<el-button
type="primary"
size="mini"
@@ -233,7 +233,6 @@
<el-input
v-if="isEditing(row)"
v-model.number="editRow.hardness"
type="number"
size="mini"
style="width:64px"
/>
@@ -366,9 +365,7 @@
<el-form-item label="姓名" prop="dictLabel">
<el-input v-model="operatorForm.dictLabel" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="值" prop="dictValue">
<el-input v-model="operatorForm.dictValue" placeholder="请输入值" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="operatorForm.status">
<el-radio label="0">在职</el-radio>
@@ -532,8 +529,7 @@ export default {
operatorFormTitle: '',
operatorForm: {},
operatorFormRules: {
dictLabel: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
dictValue: [{ required: true, message: '值不能为空', trigger: 'blur' }]
dictLabel: [{ required: true, message: '姓名不能为空', trigger: 'blur' }]
}
}
},
@@ -670,6 +666,10 @@ export default {
const pad = n => String(n).padStart(2, '0')
const grindTime = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ` +
`${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
const lastHardness = this.grindList
.filter(r => r.hardness != null)
.sort((a, b) => new Date(b.grindTime) - new Date(a.grindTime))
.map(r => r.hardness)[0]
this.editRow = {
__isNew: true,
rollId: this.selectedRollId,
@@ -679,7 +679,7 @@ export default {
diaAfter: undefined,
rollShape: '平',
flawResult: '合格',
hardness: undefined,
hardness: lastHardness,
operator: this.currentUserName,
remark: undefined
}
@@ -827,7 +827,7 @@ export default {
}).finally(() => { this.operatorDictLoading = false })
},
handleAddOperator() {
this.operatorForm = { dictType: 'mes_roll_operator', dictLabel: '', dictValue: '', status: '0' }
this.operatorForm = { dictType: 'mes_roll_operator', dictLabel: '', status: '0' }
this.operatorFormTitle = '新增操作人'
this.$nextTick(() => { this.operatorFormOpen = true })
},
@@ -848,7 +848,7 @@ export default {
submitOperatorForm() {
this.$refs.operatorForm.validate(valid => {
if (!valid) return
const form = { ...this.operatorForm }
const form = { ...this.operatorForm, dictValue: this.operatorForm.dictLabel }
const api = form.dictCode ? updateData : addData
api(form).then(() => {
this.$modal.msgSuccess(form.dictCode ? '修改成功' : '新增成功')