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

@@ -1,6 +1,8 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<div class="report-tab-bar"> <el-empty v-if="noLineType" description="请通过产线页面进入" />
<template v-else>
<div class="report-tab-bar">
<div class="report-tabs"> <div class="report-tabs">
<span v-for="r in tabs" :key="r.reportId" <span v-for="r in tabs" :key="r.reportId"
:class="['report-tab', { active: activeReport && activeReport.reportId === r.reportId }]" :class="['report-tab', { active: activeReport && activeReport.reportId === r.reportId }]"
@@ -18,14 +20,18 @@
<el-tag size="mini" style="margin-left:6px">{{ lineName(activeReport) }}</el-tag> <el-tag size="mini" style="margin-left:6px">{{ lineName(activeReport) }}</el-tag>
<span class="entry-meta">{{ parseTime(activeReport.reportDate,'{y}-{m}-{d}') }} 投入{{ activeReport.inputWeight }}t 产出{{ activeReport.outputWeight }}t</span> <span class="entry-meta">{{ parseTime(activeReport.reportDate,'{y}-{m}-{d}') }} 投入{{ activeReport.inputWeight }}t 产出{{ activeReport.outputWeight }}t</span>
<el-button type="primary" size="mini" style="float:right;margin-left:8px" @click="saveGrid" :loading="saving">保存</el-button> <el-button type="primary" size="mini" style="float:right;margin-left:8px" @click="saveGrid" :loading="saving">保存</el-button>
<el-button size="mini" style="float:right" @click="openColCfg">列配置</el-button> <el-button size="mini" style="float:right;margin-left:8px" @click="openColCfg">列配置</el-button>
<span style="float:right;margin-right:12px;font-size:12px;color:#606266;display:flex;align-items:center">
<span style="margin-right:4px">{{ inputMode ? '录入' : '查看' }}</span>
<el-switch v-model="inputMode" size="small" />
</span>
</div> </div>
<el-alert :title="'已配置'+allCols.length+'个列'" type="info" :closable="false" show-icon style="margin-bottom:8px" /> <el-alert :title="'已配置'+allCols.length+'个列'" type="info" :closable="false" show-icon style="margin-bottom:8px" />
<el-table v-loading="gridLoading" :data="gridRows" border stripe size="mini" style="width:100%" :header-cell-style="headerStyle"> <el-table v-loading="gridLoading" :data="gridRows" border stripe size="mini" style="width:100%" :header-cell-style="headerStyle" :key="'tbl-'+inputMode">
<el-table-column label="日期" width="135" fixed> <el-table-column label="日期" width="135" fixed>
<template slot-scope="s"><el-date-picker v-model="s.row.detailDate" type="date" value-format="yyyy-MM-dd" size="mini" style="width:124px" @change="sortGrid" /></template> <template slot-scope="s"><el-date-picker v-model="s.row.detailDate" type="date" value-format="yyyy-MM-dd" size="mini" style="width:124px" @change="sortGrid" /></template>
</el-table-column> </el-table-column>
<template v-for="col in allCols"> <template v-for="col in displayCols">
<el-table-column v-if="col.$type==='detail' && !col.isShift" :key="'d'+col.itemId" :label="col.itemName+(col.unit?'('+col.unit+')':'')" width="105" align="center"> <el-table-column v-if="col.$type==='detail' && !col.isShift" :key="'d'+col.itemId" :label="col.itemName+(col.unit?'('+col.unit+')':'')" width="105" align="center">
<template slot-scope="s"> <template slot-scope="s">
<el-input v-model="s.row['q'+col.itemId]" size="mini" @input="recalcAll"> <el-input v-model="s.row['q'+col.itemId]" size="mini" @input="recalcAll">
@@ -214,8 +220,13 @@
<el-table-column label="报表标题" prop="reportTitle" /> <el-table-column label="报表标题" prop="reportTitle" />
<el-table-column label="日期" width="120"><template slot-scope="s">{{ parseTime(s.row.reportDate,'{y}-{m}-{d}') }}</template></el-table-column> <el-table-column label="日期" width="120"><template slot-scope="s">{{ parseTime(s.row.reportDate,'{y}-{m}-{d}') }}</template></el-table-column>
<el-table-column label="产线" width="70"><template slot-scope="s">{{ lineName(s.row) }}</template></el-table-column> <el-table-column label="产线" width="70"><template slot-scope="s">{{ lineName(s.row) }}</template></el-table-column>
<el-table-column label="投入(t)" prop="inputWeight" width="80" /> <el-table-column label="操作" width="180" align="center">
<el-table-column label="产出(t)" prop="outputWeight" width="80" /> <template slot-scope="s">
<el-button size="mini" type="text" style="padding:0" @click="editRp(s.row)">修改</el-button>
<el-button size="mini" type="text" style="padding:0" @click="copyInlineRp(s.row)">复制</el-button>
<el-button size="mini" type="text" style="padding:0;color:#f56c6c" @click="delRp(s.row)">删除</el-button>
</template>
</el-table-column>
</el-table> </el-table>
<pagination v-show="total>0" :total="total" :page.sync="q.pageNum" :limit.sync="q.pageSize" @pagination="getList" /> <pagination v-show="total>0" :total="total" :page.sync="q.pageNum" :limit.sync="q.pageSize" @pagination="getList" />
@@ -223,9 +234,6 @@
<el-form ref="rpf" :model="rpForm" :rules="{reportTitle:[{required:true,message:'请输入',trigger:'blur'}]}" label-width="100px"> <el-form ref="rpf" :model="rpForm" :rules="{reportTitle:[{required:true,message:'请输入',trigger:'blur'}]}" label-width="100px">
<el-form-item label="报表标题" prop="reportTitle"><el-input v-model="rpForm.reportTitle" /></el-form-item> <el-form-item label="报表标题" prop="reportTitle"><el-input v-model="rpForm.reportTitle" /></el-form-item>
<el-form-item label="报表日期" prop="reportDate"><el-date-picker v-model="rpForm.reportDate" type="date" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item> <el-form-item label="报表日期" prop="reportDate"><el-date-picker v-model="rpForm.reportDate" type="date" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item>
<el-form-item label="产线" prop="lineType"><el-select v-model="rpForm.lineType" style="width:100%" placeholder="请选择产线"><el-option v-for="ln in lineOptions" :key="ln.lineId" :label="ln.lineName" :value="ln.lineId" /></el-select></el-form-item>
<el-form-item label="投入量(吨)" prop="inputWeight"><el-input-number v-model="rpForm.inputWeight" :precision="2" :min="0" style="width:100%" /></el-form-item>
<el-form-item label="产出量(吨)" prop="outputWeight"><el-input-number v-model="rpForm.outputWeight" :precision="2" :min="0" style="width:100%" /></el-form-item>
<el-form-item label="备注" prop="remark"><el-input v-model="rpForm.remark" type="textarea" /></el-form-item> <el-form-item label="备注" prop="remark"><el-input v-model="rpForm.remark" type="textarea" /></el-form-item>
</el-form> </el-form>
<div slot="footer"><el-button :loading="rpBtnLoading" type="primary" @click="submitRp"> </el-button><el-button @click="rpOpen=false"> </el-button></div> <div slot="footer"><el-button :loading="rpBtnLoading" type="primary" @click="submitRp"> </el-button><el-button @click="rpOpen=false"> </el-button></div>
@@ -242,7 +250,9 @@
<el-button @click="copyRpOpen=false"> </el-button> <el-button @click="copyRpOpen=false"> </el-button>
</div> </div>
</el-dialog> </el-dialog>
</el-dialog> </el-dialog>
</template>
</div>
</div> </div>
</template> </template>
@@ -352,7 +362,10 @@ export default {
copyCfgOpen: false, copyReports: [], copySrc: null, copyCfgOpen: false, copyReports: [], copySrc: null,
configOpen: false, configOpen: false,
autoLoading: {}, autoLoading: {},
lineOptions: [] lineOptions: [],
lineType: null,
noLineType: false,
inputMode: false
} }
}, },
computed: { computed: {
@@ -360,6 +373,10 @@ export default {
const used = new Set(this.allCols.filter(c => c.$type === 'detail').map(c => String(c.itemId))) const used = new Set(this.allCols.filter(c => c.$type === 'detail').map(c => String(c.itemId)))
return this.allItems.filter(i => !used.has(String(i.itemId))) return this.allItems.filter(i => !used.has(String(i.itemId)))
}, },
displayCols() {
if (!this.inputMode) return this.allCols
return [...this.allCols.filter(c => c.$type === 'detail'), ...this.allCols.filter(c => c.$type === 'metric')]
},
headerStyle() { headerStyle() {
return ({ column }) => { return ({ column }) => {
if (!column || !column.label) return {} if (!column || !column.label) return {}
@@ -373,14 +390,33 @@ export default {
} }
}, },
watch: { configOpen(v) { if (!v) this.rpOpen = false } }, watch: { configOpen(v) { if (!v) this.rpOpen = false } },
created() { this.getTabList(); this.loadItems(); this.loadLines() }, created() {
const lineType = this.$route.query.lineType
if (lineType) {
this.lineType = lineType
this.getTabList()
this.loadItems()
this.loadLines()
} else {
this.noLineType = true
}
},
methods: { methods: {
/* report */ /* report */
getList() { this.loading = true; listProdReport(this.q).then(r=>{this.list=r.rows;this.total=r.total}).finally(()=>this.loading=false) }, getList() {
getTabList() { listProdReport({ pageNum: 1, pageSize: 9999 }).then(r => { this.tabs = r.rows || [] }) }, this.loading = true
const params = { ...this.q }
if (this.lineType) params.lineType = this.lineType
listProdReport(params).then(r=>{this.list=r.rows;this.total=r.total}).finally(()=>this.loading=false)
},
getTabList() {
const params = { pageNum: 1, pageSize: 9999 }
if (this.lineType) params.lineType = this.lineType
listProdReport(params).then(r => { this.tabs = r.rows || [] })
},
search() { this.q.pageNum = 1; this.getList() }, search() { this.q.pageNum = 1; this.getList() },
resetQ() { this.resetForm("qf"); this.search() }, resetQ() { this.resetForm("qf"); this.search() },
addRp() { this.rpForm = {}; this.rpTitle = "新增"; this.rpOpen = true }, addRp() { this.rpForm = { lineType: this.lineType || undefined }; this.rpTitle = "新增"; this.rpOpen = true },
editRp(row) { editRp(row) {
const id = (row&&row.reportId)||this.selIds[0]; if(!id)return const id = (row&&row.reportId)||this.selIds[0]; if(!id)return
getProdReport(id).then(r=>{ getProdReport(id).then(r=>{
@@ -414,6 +450,10 @@ export default {
this.copyRpForm = { reportId: this.selIds[0], reportTitle: (row ? row.reportTitle : '') + '-副本', reportDate: row ? row.reportDate : undefined } this.copyRpForm = { reportId: this.selIds[0], reportTitle: (row ? row.reportTitle : '') + '-副本', reportDate: row ? row.reportDate : undefined }
this.copyRpOpen = true this.copyRpOpen = true
}, },
copyInlineRp(row) {
this.copyRpForm = { reportId: row.reportId, reportTitle: row.reportTitle + '-副本', reportDate: row.reportDate }
this.copyRpOpen = true
},
async doCopyRp() { async doCopyRp() {
const sid = this.copyRpForm.reportId const sid = this.copyRpForm.reportId
if (!sid) return if (!sid) return
@@ -619,7 +659,6 @@ export default {
}, },
evalF(f) { const s = f.replace(/[^0-9+\-*/.()\s]/g,''); if(!s) return null; try { const r = new Function('return ('+s+')')(); return isFinite(r)?Math.round(r*10000)/10000:null } catch(e){ return null } }, evalF(f) { const s = f.replace(/[^0-9+\-*/.()\s]/g,''); if(!s) return null; try { const r = new Function('return ('+s+')')(); return isFinite(r)?Math.round(r*10000)/10000:null } catch(e){ return null } },
sortGrid() { this.gridRows.sort((a,b)=>{if(!a.detailDate)return 1;if(!b.detailDate)return -1;return a.detailDate.localeCompare(b.detailDate)}) }, sortGrid() { this.gridRows.sort((a,b)=>{if(!a.detailDate)return 1;if(!b.detailDate)return -1;return a.detailDate.localeCompare(b.detailDate)}) },
async saveGrid() { async saveGrid() {
const rid = this.activeReport.reportId; if (!rid) return; this.saving = true const rid = this.activeReport.reportId; if (!rid) return; this.saving = true
try { try {

View File

@@ -33,6 +33,11 @@
@keyup.enter.native="handleQuery" @keyup.enter.native="handleQuery"
/> />
</el-form-item> </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-form-item label="收货单位" prop="consignee">
<el-input <el-input
v-model="queryParams.consignee" v-model="queryParams.consignee"
@@ -114,6 +119,7 @@
<el-table-column label="执行标准" align="center" prop="standard" /> <el-table-column label="执行标准" align="center" prop="standard" />
<el-table-column label="收货单位" align="center" prop="consignee" /> <el-table-column label="收货单位" align="center" prop="consignee" />
<el-table-column label="生产厂家" align="center" prop="manufacturer" /> <el-table-column label="生产厂家" align="center" prop="manufacturer" />
<el-table-column label="质保书类型" align="center" prop="certificateType" />
<el-table-column label="签发日期" align="center" prop="issueDate"> <el-table-column label="签发日期" align="center" prop="issueDate">
<template #default="scope"> <template #default="scope">
{{ parseTime(scope.row.issueDate, '{y}-{m}-{d}') }} {{ parseTime(scope.row.issueDate, '{y}-{m}-{d}') }}
@@ -186,6 +192,11 @@
placeholder="请选择签发日期"> placeholder="请选择签发日期">
</el-date-picker> </el-date-picker>
</el-form-item> </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-form-item label="质保证明说明" prop="note">
<el-input v-model="form.note" type="textarea" placeholder="请输入内容" auto-height /> <el-input v-model="form.note" type="textarea" placeholder="请输入内容" auto-height />
</el-form-item> </el-form-item>
@@ -227,23 +238,7 @@
:template-type="selectedTemplateType" :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> </div>
</template> </template>
@@ -251,7 +246,6 @@
import { listCertificate, getCertificate, delCertificate, addCertificate, updateCertificate } from "@/api/mes/qc/certificate"; import { listCertificate, getCertificate, delCertificate, addCertificate, updateCertificate } from "@/api/mes/qc/certificate";
import { listCertificateItem } from "@/api/mes/qc/certificateItem"; import { listCertificateItem } from "@/api/mes/qc/certificateItem";
import CertificatePrintPreview from "./components/CertificatePrintPreview.vue"; import CertificatePrintPreview from "./components/CertificatePrintPreview.vue";
import { templateOptions } from "./components/templates";
import { print as printPdf, downloadPdf } from "./lib/printUtils"; import { print as printPdf, downloadPdf } from "./lib/printUtils";
export default { export default {
@@ -261,7 +255,6 @@ export default {
}, },
data() { data() {
return { return {
templateOptions,
buttonLoading: false, buttonLoading: false,
loading: true, loading: true,
ids: [], ids: [],
@@ -280,10 +273,13 @@ export default {
printComponentVisible: false, printComponentVisible: false,
printCertificateData: {}, printCertificateData: {},
printItemsData: [], printItemsData: [],
templateDialogVisible: false,
selectedTemplateType: 'chromium', selectedTemplateType: 'chromium',
templateActionType: 'print', certificateTypeOptions: [
templateDialogResolve: null, { value: '冷硬', label: '冷硬' },
{ value: '镀锌', label: '镀锌' },
{ value: '镀铬', label: '镀铬' },
{ value: '原料', label: '原料' }
],
queryParams: { queryParams: {
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
@@ -291,6 +287,7 @@ export default {
contractNo: undefined, contractNo: undefined,
productName: undefined, productName: undefined,
standard: undefined, standard: undefined,
certificateType: undefined,
consignee: undefined, consignee: undefined,
manufacturer: undefined, manufacturer: undefined,
}, },
@@ -312,6 +309,10 @@ export default {
}; };
}, },
created() { created() {
const routeType = this.$route.query.certificateType;
if (routeType) {
this.queryParams.certificateType = routeType;
}
this.getList(); this.getList();
}, },
methods: { methods: {
@@ -326,6 +327,15 @@ export default {
getActualStatus(status) { getActualStatus(status) {
return status || 'PENDING'; return status || 'PENDING';
}, },
getTemplateType(certificateType) {
const map = {
'冷硬': 'coldHard',
'镀锌': 'galvanizing',
'镀铬': 'chromium',
'原料': 'rawMaterial'
};
return map[certificateType] || 'chromium';
},
/** 查询质量证明书主列表 */ /** 查询质量证明书主列表 */
getList() { getList() {
this.loading = true; this.loading = true;
@@ -351,6 +361,7 @@ export default {
consignee: undefined, consignee: undefined,
manufacturer: undefined, manufacturer: undefined,
issueDate: 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', 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.', 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, delFlag: undefined,
@@ -380,6 +391,10 @@ export default {
/** 新增按钮操作 */ /** 新增按钮操作 */
handleAdd() { handleAdd() {
this.reset(); this.reset();
const routeType = this.$route.query.certificateType;
if (routeType) {
this.form.certificateType = routeType;
}
this.open = true; this.open = true;
this.title = "添加质量证明书主"; this.title = "添加质量证明书主";
}, },
@@ -475,10 +490,6 @@ export default {
}, },
/** 预览按钮操作 */ /** 预览按钮操作 */
async handlePreview(row) { async handlePreview(row) {
this.templateActionType = 'preview';
const confirmed = await this.showTemplateDialog();
if (!confirmed) return;
const certificateId = row.certificateId; const certificateId = row.certificateId;
this.loading = true; this.loading = true;
try { try {
@@ -489,6 +500,7 @@ export default {
this.previewData = certRes.data; this.previewData = certRes.data;
this.previewItems = itemsRes.rows || []; this.previewItems = itemsRes.rows || [];
this.previewTitle = `预览 - ${certRes.data.certificateNo}`; this.previewTitle = `预览 - ${certRes.data.certificateNo}`;
this.selectedTemplateType = this.getTemplateType(certRes.data.certificateType);
this.previewVisible = true; this.previewVisible = true;
} catch (error) { } catch (error) {
this.$message.error('获取数据失败'); this.$message.error('获取数据失败');
@@ -498,10 +510,6 @@ export default {
}, },
/** 打印按钮操作 */ /** 打印按钮操作 */
async handlePrint(row) { async handlePrint(row) {
this.templateActionType = 'print';
const confirmed = await this.showTemplateDialog();
if (!confirmed) return;
const certificateId = row.certificateId; const certificateId = row.certificateId;
await this.preparePrintComponent(certificateId); await this.preparePrintComponent(certificateId);
await this.$nextTick(); await this.$nextTick();
@@ -544,6 +552,7 @@ export default {
]); ]);
this.printCertificateData = certRes.data; this.printCertificateData = certRes.data;
this.printItemsData = itemsRes.rows || []; this.printItemsData = itemsRes.rows || [];
this.selectedTemplateType = this.getTemplateType(certRes.data.certificateType);
this.printComponentVisible = true; this.printComponentVisible = true;
await this.$nextTick(); await this.$nextTick();
} catch (error) { } catch (error) {
@@ -552,19 +561,6 @@ export default {
this.loading = false; 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> </script>
@@ -586,45 +582,5 @@ export default {
font-weight: 500; 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> </style>

View File

@@ -1,8 +1,13 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<div class="tab-container"> <div class="tab-container">
<div class="select-button" @click="openCertificateDialog"> <div class="filter-group">
<i class="el-icon-setting"></i> <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>
<div class="custom-tabs"> <div class="custom-tabs">
<div class="tab-nav"> <div class="tab-nav">
@@ -84,6 +89,13 @@
<el-input v-model="currentCertificateInfo.manufacturer" @change="handleCertificateInfoChange" /> <el-input v-model="currentCertificateInfo.manufacturer" @change="handleCertificateInfoChange" />
</el-form-item> </el-form-item>
</el-col> </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-row>
</el-form> </el-form>
@@ -397,6 +409,11 @@
<el-date-picker v-model="certificateForm.issueDate" value-format="yyyy-MM-dd HH:mm:ss" type="date" <el-date-picker v-model="certificateForm.issueDate" value-format="yyyy-MM-dd HH:mm:ss" type="date"
placeholder="请选择签发日期" /> placeholder="请选择签发日期" />
</el-form-item> </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-form-item label="质保证明说明" prop="note">
<el-input v-model="certificateForm.note" type="textarea" placeholder="请输入内容" auto-height /> <el-input v-model="certificateForm.note" type="textarea" placeholder="请输入内容" auto-height />
</el-form-item> </el-form-item>
@@ -417,23 +434,7 @@
<CertificatePrintPreview ref="certificatePrint" v-show="false" :certificate="printCertificateData" <CertificatePrintPreview ref="certificatePrint" v-show="false" :certificate="printCertificateData"
:items="printItemsData" :template-type="selectedTemplateType" /> :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> </div>
</template> </template>
@@ -445,7 +446,6 @@ import { listChemicalItem } from "@/api/mes/qc/chemicalItem";
import { listPhysicalItem } from "@/api/mes/qc/physicalItem"; import { listPhysicalItem } from "@/api/mes/qc/physicalItem";
import CoilSelector from "@/components/CoilSelector/index.vue"; import CoilSelector from "@/components/CoilSelector/index.vue";
import CertificatePrintPreview from "./components/CertificatePrintPreview.vue"; import CertificatePrintPreview from "./components/CertificatePrintPreview.vue";
import { templateOptions } from "./components/templates";
import { print as printPdf } from "./lib/printUtils"; import { print as printPdf } from "./lib/printUtils";
export default { export default {
@@ -461,7 +461,13 @@ export default {
}, },
data() { data() {
return { return {
templateOptions, certificateTypeOptions: [
{ value: '冷硬', label: '冷硬' },
{ value: '镀锌', label: '镀锌' },
{ value: '镀铬', label: '镀铬' },
{ value: '原料', label: '原料' }
],
certificateTypeFilter: undefined,
buttonLoading: false, buttonLoading: false,
loading: false, loading: false,
certificateLoading: false, certificateLoading: false,
@@ -480,9 +486,7 @@ export default {
printComponentVisible: false, printComponentVisible: false,
printCertificateData: {}, printCertificateData: {},
printItemsData: [], printItemsData: [],
templateDialogVisible: false,
selectedTemplateType: 'chromium', selectedTemplateType: 'chromium',
templateDialogResolve: null,
certificateForm: { certificateForm: {
certificateNo: '', certificateNo: '',
contractNo: '', contractNo: '',
@@ -491,6 +495,7 @@ export default {
consignee: '', consignee: '',
manufacturer: '', manufacturer: '',
issueDate: '', issueDate: '',
certificateType: undefined,
remark: '' remark: ''
}, },
coilLoading: false, coilLoading: false,
@@ -508,7 +513,8 @@ export default {
pageSize: 10, pageSize: 10,
certificateNo: undefined, certificateNo: undefined,
contractNo: undefined, contractNo: undefined,
productName: undefined productName: undefined,
certificateType: undefined
}, },
coilQueryParams: { coilQueryParams: {
pageNum: 1, pageNum: 1,
@@ -540,6 +546,15 @@ export default {
.replace('{i}', minute.toString().padStart(2, '0')) .replace('{i}', minute.toString().padStart(2, '0'))
.replace('{s}', second.toString().padStart(2, '0')); .replace('{s}', second.toString().padStart(2, '0'));
}, },
getTemplateType(certificateType) {
const map = {
'冷硬': 'coldHard',
'镀锌': 'galvanizing',
'镀铬': 'chromium',
'原料': 'rawMaterial'
};
return map[certificateType] || 'chromium';
},
getCertificateList() { getCertificateList() {
this.certificateLoading = true; this.certificateLoading = true;
listCertificate(this.certificateQueryParams).then(response => { listCertificate(this.certificateQueryParams).then(response => {
@@ -559,8 +574,15 @@ export default {
pageSize: 10, pageSize: 10,
certificateNo: undefined, certificateNo: undefined,
contractNo: 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(); this.getCertificateList();
}, },
handleTabClick(certificate) { handleTabClick(certificate) {
@@ -730,6 +752,7 @@ export default {
consignee: '', consignee: '',
manufacturer: '', manufacturer: '',
issueDate: '', 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', 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.', 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() { async handlePrint() {
const confirmed = await this.showTemplateDialog(); this.selectedTemplateType = this.getTemplateType(this.currentCertificateInfo.certificateType);
if (!confirmed) return;
this.printCertificateData = this.currentCertificateInfo; this.printCertificateData = this.currentCertificateInfo;
this.printItemsData = this.certificateItemList || []; this.printItemsData = this.certificateItemList || [];
this.$nextTick(() => { await this.$nextTick();
const el = this.$refs.certificatePrint.$refs.certificateContent; const el = this.$refs.certificatePrint.$refs.certificateContent;
console.log(el); printPdf(el);
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> </script>
@@ -858,27 +864,49 @@ export default {
margin-bottom: 20px; margin-bottom: 20px;
} }
.select-button { .filter-group {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px;
padding: 10px 16px;
border: 1px solid #e4e7ed; border: 1px solid #e4e7ed;
border-radius: 4px; 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; cursor: pointer;
transition: all 0.3s ease; transition: all 0.3s ease;
background-color: #f9f9f9; background-color: #f9f9f9;
color: #606266; color: #409eff;
font-size: 14px; font-size: 14px;
user-select: none; user-select: none;
margin-right: 10px; border-right: 1px solid #e4e7ed;
color: #409eff;
} }
.select-button:hover { .filter-group .select-button:hover {
background-color: #ecf5ff; 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 { .custom-tabs {
@@ -1021,45 +1049,5 @@ export default {
padding: 0; 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> </style>

View File

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