2026-05-16 17:23:20 +08:00
< template >
< div class = "app-container" >
< el-form :model = "queryParams" ref = "queryForm" size = "small" :inline = "true" v-show = "showSearch" label-width="68px" >
< el-form-item label = "证明书号" prop = "certificateNo" >
< el-input
v - model = "queryParams.certificateNo"
placeholder = "请输入证明书号"
clearable
@ keyup . enter . native = "handleQuery"
/ >
< / el-form-item >
< el-form-item label = "合同号" prop = "contractNo" >
< el-input
v - model = "queryParams.contractNo"
placeholder = "请输入合同号"
clearable
@ keyup . enter . native = "handleQuery"
/ >
< / el-form-item >
< el-form-item label = "产品名称" prop = "productName" >
< el-input
v - model = "queryParams.productName"
placeholder = "请输入产品名称"
clearable
@ keyup . enter . native = "handleQuery"
/ >
< / el-form-item >
< el-form-item label = "执行标准" prop = "standard" >
< el-input
v - model = "queryParams.standard"
placeholder = "请输入执行标准"
clearable
@ keyup . enter . native = "handleQuery"
/ >
< / el-form-item >
< el-form-item label = "收货单位" prop = "consignee" >
< el-input
v - model = "queryParams.consignee"
placeholder = "请输入收货单位"
clearable
@ keyup . enter . native = "handleQuery"
/ >
< / el-form-item >
< el-form-item label = "生产厂家" prop = "manufacturer" >
< el-input
v - model = "queryParams.manufacturer"
placeholder = "请输入生产厂家"
clearable
@ keyup . enter . native = "handleQuery"
/ >
< / el-form-item >
2026-05-18 17:54:12 +08:00
< el-form-item label = "审批状态" prop = "approveStatus" >
< el-select v-model = "queryParams.approveStatus" placeholder="请选择审批状态" clearable >
< el-option label = "待审批" value = "PENDING" / >
< el-option label = "审批中" value = "APPROVING" / >
< el-option label = "已通过" value = "PASSED" / >
< el-option label = "已驳回" value = "REJECTED" / >
< / el-select >
< / el-form-item >
2026-05-16 17:23:20 +08:00
< el-form-item >
< el-button type = "primary" icon = "el-icon-search" size = "mini" @click ="handleQuery" > 搜索 < / el -button >
< el-button icon = "el-icon-refresh" size = "mini" @click ="resetQuery" > 重置 < / el -button >
< / el-form-item >
< / el-form >
< el-row :gutter = "10" class = "mb8" >
< el-col :span = "1.5" >
< el-button
type = "primary"
plain
icon = "el-icon-plus"
size = "mini"
@ click = "handleAdd"
> 新增 < / el-button >
< / el-col >
< el-col :span = "1.5" >
< el-button
type = "success"
plain
icon = "el-icon-edit"
size = "mini"
: disabled = "single"
@ click = "handleUpdate"
> 修改 < / el-button >
< / el-col >
< el-col :span = "1.5" >
< el-button
type = "danger"
plain
icon = "el-icon-delete"
size = "mini"
: disabled = "multiple"
@ click = "handleDelete"
> 删除 < / el-button >
< / el-col >
< right-toolbar :showSearch.sync = "showSearch" @queryTable ="getList" > < / right -toolbar >
< / el-row >
2026-05-18 17:54:12 +08:00
< el-table
v - loading = "loading"
: data = "certificateList"
@ selection - change = "handleSelectionChange"
border
class = "certificate-table"
>
< el-table-column type = "selection" width = "55" align = "center" / >
< el-table-column label = "证明书号" align = "center" prop = "certificateNo" >
< template # default = "scope" >
< span class = "cert-no" > { { scope . row . certificateNo || '-' } } < / span >
< / template >
< / el-table-column >
< el-table-column label = "合同号" align = "center" prop = "contractNo" / >
< el-table-column label = "产品名称" align = "center" prop = "productName" / >
< 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 = "issueDate" >
< template # default = "scope" >
{ { parseTime ( scope . row . issueDate , '{y}-{m}-{d}' ) } }
< / template >
< / el-table-column >
< el-table-column label = "签发状态" align = "center" prop = "approveStatus" width = "180" >
< template # default = "scope" >
< el-tag :type = "getStatusType(scope.row.approveStatus)" size = "small" >
{ { getStatusText ( scope . row . approveStatus ) } }
< / el-tag >
< / template >
< / el-table-column >
< el-table-column label = "审批信息" align = "center" width = "220" >
< template # default = "scope" >
< div v-if = "(scope.row.approveStatus || 'PENDING') === 'PASSED' || (scope.row.approveStatus || 'PENDING') === 'REJECTED'" >
< div > 审批人 : { { scope . row . approveBy || '-' } } < / div >
< div > 审批时间 : { { parseTime ( scope . row . approveTime , '{y}-{m}-{d} {h}:{i}:{s}' ) } } < / div >
2026-05-16 17:23:20 +08:00
< / div >
2026-05-18 17:54:12 +08:00
< span v-else > - < / span >
< / template >
< / el-table-column >
< el-table-column label = "操作" align = "center" width = "220" >
< template # default = "scope" >
< el-button size = "mini" type = "text" icon = "el-icon-edit" @click ="handleUpdate(scope.row)" : disabled = "(scope.row.approveStatus || 'PENDING') === 'APPROVING' || (scope.row.approveStatus || 'PENDING') === 'PASSED'" > 修改 < / el-button >
< el-button size = "mini" type = "text" icon = "el-icon-delete" @click ="handleDelete(scope.row)" > 删除 < / el -button >
< el-button size = "mini" type = "text" icon = "el-icon-view" @click ="handlePreview(scope.row)" > 预览 < / el -button >
< el-button size = "mini" type = "text" icon = "el-icon-printer" @click ="handlePrint(scope.row)" > 打印 < / el -button >
< el-button v-if = "(scope.row.approveStatus || 'PENDING') === 'PENDING' || (scope.row.approveStatus || 'PENDING') === 'REJECTED'" size="mini" type="text" icon="el-icon-upload" @click="handleSubmit(scope.row)" > 提交审批 < / el -button >
< el-button v-hasPermi = "['qc:certificate:approve']" v-if="(scope.row.approveStatus || 'PENDING') === 'APPROVING'" size="mini" type="text" icon="el-icon-check" @click="handleApprove(scope.row, 'PASSED')" > 通过 < / el -button >
< el-button v-hasPermi = "['qc:certificate:approve']" v-if="(scope.row.approveStatus || 'PENDING') === 'APPROVING'" size="mini" type="text" icon="el-icon-close" @click="handleApprove(scope.row, 'REJECTED')" > 驳回 < / el -button >
< / template >
< / el-table-column >
< / el-table >
2026-05-16 17:23:20 +08:00
< pagination
v - show = "total>0"
: total = "total"
: page . sync = "queryParams.pageNum"
: limit . sync = "queryParams.pageSize"
@ pagination = "getList"
/ >
<!-- 添加或修改质量证明书主对话框 -- >
< el-dialog :title = "title" :visible.sync = "open" width = "800px" append -to -body >
< el-form ref = "form" :model = "form" :rules = "rules" label -width = " 80px " >
< el-form-item label = "证明书号" prop = "certificateNo" >
< el-input v-model = "form.certificateNo" placeholder="请输入证明书号" / >
< / el-form-item >
< el-form-item label = "合同号" prop = "contractNo" >
< el-input v-model = "form.contractNo" placeholder="请输入合同号" / >
< / el-form-item >
< el-form-item label = "产品名称" prop = "productName" >
< el-input v-model = "form.productName" placeholder="请输入产品名称" / >
< / el-form-item >
< el-form-item label = "执行标准" prop = "standard" >
< el-input v-model = "form.standard" placeholder="请输入执行标准" / >
< / el-form-item >
< el-form-item label = "收货单位" prop = "consignee" >
< el-input v-model = "form.consignee" placeholder="请输入收货单位" / >
< / el-form-item >
< el-form-item label = "生产厂家" prop = "manufacturer" >
< el-input v-model = "form.manufacturer" placeholder="请输入生产厂家" / >
< / el-form-item >
< el-form-item label = "签发日期" prop = "issueDate" >
< el-date-picker clearable
v - model = "form.issueDate"
type = "datetime"
value - format = "yyyy-MM-dd HH:mm:ss"
placeholder = "请选择签发日期" >
< / el-date-picker >
< / el-form-item >
< el-form-item label = "质保证明说明" prop = "note" >
< el-input v-model = "form.note" type="textarea" placeholder="请输入内容" auto -height / >
< / el-form-item >
< el-form-item label = "备注" prop = "remark" >
< el-input v-model = "form.remark" type="textarea" placeholder="请输入内容" auto -height / >
< / el-form-item >
< / el-form >
< div slot = "footer" class = "dialog-footer" >
< el-button :loading = "buttonLoading" type = "primary" @click ="submitForm" > 确 定 < / el -button >
< el-button @click ="cancel" > 取 消 < / el -button >
< / div >
< / el-dialog >
<!-- 预览对话框 -- >
< el-dialog :title = "previewTitle" :visible.sync = "previewVisible" width = "1200px" append -to -body fullscreen >
< div class = "preview-content-wrapper" >
< CertificatePrintPreview
ref = "printComponent"
: visible = "true"
: preview = "true"
: certificate = "previewData"
2026-06-02 13:22:58 +08:00
: items = "previewItems"
: template - type = "selectedTemplateType"
2026-05-16 17:23:20 +08:00
/ >
< / div >
< div slot = "footer" class = "dialog-footer" >
< el-button type = "primary" @click ="handleExportPdfFromPreview" > 导出PDF < / el -button >
< el-button type = "success" @click ="handlePrintFromPreview" > 打印 < / el -button >
< el-button @click ="previewVisible = false" > 关闭 < / el -button >
< / div >
< / el-dialog >
<!-- 隐藏的打印组件 -- >
< CertificatePrintPreview
v - show = "false"
ref = "hiddenPrintComponent"
: certificate = "printCertificateData"
2026-06-02 13:22:58 +08:00
: items = "printItemsData"
: template - type = "selectedTemplateType"
2026-05-16 17:23:20 +08:00
/ >
2026-06-02 13:22:58 +08:00
<!-- 模板选择对话框 -- >
< 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 >
2026-05-16 17:23:20 +08:00
< / div >
< / template >
< script >
import { listCertificate , getCertificate , delCertificate , addCertificate , updateCertificate } from "@/api/mes/qc/certificate" ;
import { listCertificateItem } from "@/api/mes/qc/certificateItem" ;
import CertificatePrintPreview from "./components/CertificatePrintPreview.vue" ;
2026-06-02 13:22:58 +08:00
import { templateOptions } from "./components/templates" ;
2026-05-16 17:23:20 +08:00
import { print as printPdf , downloadPdf } from "./lib/printUtils" ;
export default {
name : "Certificate" ,
components : {
CertificatePrintPreview
} ,
data ( ) {
return {
2026-06-02 13:22:58 +08:00
templateOptions ,
2026-05-16 17:23:20 +08:00
buttonLoading : false ,
loading : true ,
ids : [ ] ,
selectedIds : [ ] ,
single : true ,
multiple : true ,
showSearch : true ,
total : 0 ,
certificateList : [ ] ,
title : "" ,
open : false ,
previewVisible : false ,
previewTitle : "" ,
previewData : { } ,
previewItems : [ ] ,
printComponentVisible : false ,
printCertificateData : { } ,
printItemsData : [ ] ,
2026-06-02 13:22:58 +08:00
templateDialogVisible : false ,
selectedTemplateType : 'chromium' ,
templateActionType : 'print' ,
templateDialogResolve : null ,
2026-05-16 17:23:20 +08:00
queryParams : {
pageNum : 1 ,
pageSize : 10 ,
certificateNo : undefined ,
contractNo : undefined ,
productName : undefined ,
standard : undefined ,
consignee : undefined ,
manufacturer : undefined ,
} ,
form : { } ,
rules : {
2026-05-18 17:54:12 +08:00
} ,
statusMap : {
'PENDING' : '待提交' ,
'APPROVING' : '审批中' ,
'PASSED' : '已通过' ,
'REJECTED' : '已驳回'
} ,
statusTypeMap : {
'PENDING' : 'info' ,
'APPROVING' : 'warning' ,
'PASSED' : 'success' ,
'REJECTED' : 'danger'
2026-05-16 17:23:20 +08:00
}
} ;
} ,
created ( ) {
this . getList ( ) ;
} ,
methods : {
2026-05-18 17:54:12 +08:00
getStatusText ( status ) {
const actualStatus = status || 'PENDING' ;
return this . statusMap [ actualStatus ] || '未知' ;
} ,
getStatusType ( status ) {
const actualStatus = status || 'PENDING' ;
return this . statusTypeMap [ actualStatus ] || 'info' ;
} ,
getActualStatus ( status ) {
return status || 'PENDING' ;
} ,
2026-05-16 17:23:20 +08:00
/** 查询质量证明书主列表 */
getList ( ) {
this . loading = true ;
listCertificate ( this . queryParams ) . then ( response => {
this . certificateList = response . rows ;
this . total = response . total ;
this . loading = false ;
} ) ;
} ,
// 取消按钮
cancel ( ) {
this . open = false ;
this . reset ( ) ;
} ,
// 表单重置
reset ( ) {
this . form = {
certificateId : undefined ,
certificateNo : undefined ,
contractNo : undefined ,
productName : undefined ,
standard : undefined ,
consignee : undefined ,
manufacturer : undefined ,
issueDate : 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 shouldn’ tbe regarded as delivery conditions. \n 3.盖章后生效。The quality certificate willcome into force with a valid stamp.' ,
delFlag : undefined ,
createTime : undefined ,
createBy : undefined ,
updateTime : undefined ,
updateBy : undefined
} ;
this . resetForm ( "form" ) ;
} ,
/** 搜索按钮操作 */
handleQuery ( ) {
this . queryParams . pageNum = 1 ;
this . getList ( ) ;
} ,
/** 重置按钮操作 */
resetQuery ( ) {
this . resetForm ( "queryForm" ) ;
this . handleQuery ( ) ;
} ,
2026-05-18 17:54:12 +08:00
handleSelectionChange ( val ) {
this . selectedIds = val . map ( item => item . certificateId ) ;
2026-05-16 17:23:20 +08:00
this . ids = [ ... this . selectedIds ] ;
this . single = this . selectedIds . length !== 1 ;
this . multiple = this . selectedIds . length === 0 ;
} ,
/** 新增按钮操作 */
handleAdd ( ) {
this . reset ( ) ;
this . open = true ;
this . title = "添加质量证明书主" ;
} ,
/** 修改按钮操作 */
handleUpdate ( row ) {
this . loading = true ;
this . reset ( ) ;
const certificateId = row . certificateId || this . ids
getCertificate ( certificateId ) . then ( response => {
this . loading = false ;
this . form = response . data ;
this . open = true ;
this . title = "修改质量证明书主" ;
} ) ;
} ,
/** 提交按钮 */
submitForm ( ) {
this . $refs [ "form" ] . validate ( valid => {
if ( valid ) {
this . buttonLoading = true ;
if ( this . form . certificateId != null ) {
updateCertificate ( this . form ) . then ( response => {
this . $modal . msgSuccess ( "修改成功" ) ;
this . open = false ;
this . getList ( ) ;
} ) . finally ( ( ) => {
this . buttonLoading = false ;
} ) ;
} else {
2026-05-18 17:54:12 +08:00
const newForm = { ... this . form , approveStatus : 'PENDING' } ;
addCertificate ( newForm ) . then ( response => {
2026-05-16 17:23:20 +08:00
this . $modal . msgSuccess ( "新增成功" ) ;
this . open = false ;
this . getList ( ) ;
} ) . finally ( ( ) => {
this . buttonLoading = false ;
} ) ;
}
}
} ) ;
} ,
2026-05-18 17:54:12 +08:00
/** 提交审批 */
handleSubmit ( row ) {
this . $modal . confirm ( '确认提交审批?' ) . then ( ( ) => {
this . loading = true ;
const params = {
certificateId : row . certificateId ,
approveStatus : 'APPROVING'
} ;
updateCertificate ( params ) . then ( response => {
this . $modal . msgSuccess ( "提交成功" ) ;
this . getList ( ) ;
} ) . catch ( ( ) => {
} ) . finally ( ( ) => {
this . loading = false ;
} ) ;
} ) ;
} ,
/** 审批操作 */
handleApprove ( row , status ) {
const statusText = status === 'PASSED' ? '通过' : '驳回' ;
this . $modal . confirm ( ` 确认 ${ statusText } 该审批? ` ) . then ( ( ) => {
this . loading = true ;
const params = {
certificateId : row . certificateId ,
approveStatus : status ,
approveBy : this . $store . getters . name ,
approveTime : this . parseTime ( new Date ( ) , '{y}-{m}-{d} {h}:{i}:{s}' )
} ;
updateCertificate ( params ) . then ( response => {
this . $modal . msgSuccess ( ` ${ statusText } 成功 ` ) ;
this . getList ( ) ;
} ) . catch ( ( ) => {
} ) . finally ( ( ) => {
this . loading = false ;
} ) ;
} ) ;
} ,
2026-05-16 17:23:20 +08:00
/** 删除按钮操作 */
handleDelete ( row ) {
const certificateIds = row . certificateId || this . ids ;
this . $modal . confirm ( '是否确认删除质量证明书主编号为"' + certificateIds + '"的数据项?' ) . then ( ( ) => {
this . loading = true ;
return delCertificate ( certificateIds ) ;
} ) . then ( ( ) => {
this . loading = false ;
this . getList ( ) ;
this . $modal . msgSuccess ( "删除成功" ) ;
} ) . catch ( ( ) => {
} ) . finally ( ( ) => {
this . loading = false ;
} ) ;
} ,
/** 预览按钮操作 */
async handlePreview ( row ) {
2026-06-02 13:22:58 +08:00
this . templateActionType = 'preview' ;
const confirmed = await this . showTemplateDialog ( ) ;
if ( ! confirmed ) return ;
2026-05-16 17:23:20 +08:00
const certificateId = row . certificateId ;
this . loading = true ;
try {
const [ certRes , itemsRes ] = await Promise . all ( [
getCertificate ( certificateId ) ,
listCertificateItem ( { certificateId , pageSize : 100 } )
] ) ;
this . previewData = certRes . data ;
this . previewItems = itemsRes . rows || [ ] ;
this . previewTitle = ` 预览 - ${ certRes . data . certificateNo } ` ;
this . previewVisible = true ;
} catch ( error ) {
this . $message . error ( '获取数据失败' ) ;
} finally {
this . loading = false ;
}
} ,
/** 打印按钮操作 */
async handlePrint ( row ) {
2026-06-02 13:22:58 +08:00
this . templateActionType = 'print' ;
const confirmed = await this . showTemplateDialog ( ) ;
if ( ! confirmed ) return ;
2026-05-16 17:23:20 +08:00
const certificateId = row . certificateId ;
await this . preparePrintComponent ( certificateId ) ;
await this . $nextTick ( ) ;
const el = this . $refs . hiddenPrintComponent . $refs . certificateContent ;
await printPdf ( el ) ;
} ,
/** 从预览打印 */
async handlePrintFromPreview ( ) {
await this . $nextTick ( ) ;
const el = this . $refs . printComponent . $refs . certificateContent ;
await printPdf ( el ) ;
} ,
/** 导出PDF按钮操作 */
async handleExportPdf ( ) {
if ( this . ids . length !== 1 ) {
this . $message . warning ( '请选择一条质保书进行导出' ) ;
return ;
}
await this . preparePrintComponent ( this . ids [ 0 ] ) ;
await this . $nextTick ( ) ;
const fileName = ` ${ this . printCertificateData . certificateNo || '质量证明书' } _ ${ new Date ( ) . getTime ( ) } .pdf ` ;
const el = this . $refs . hiddenPrintComponent . $refs . certificateContent ;
await downloadPdf ( el , fileName ) ;
this . $message . success ( '导出成功' ) ;
} ,
/** 从预览导出PDF */
async handleExportPdfFromPreview ( ) {
await this . $nextTick ( ) ;
const fileName = ` ${ this . previewData . certificateNo || '质量证明书' } _ ${ new Date ( ) . getTime ( ) } .pdf ` ;
const el = this . $refs . printComponent . $refs . certificateContent ;
await downloadPdf ( el , fileName ) ;
this . $message . success ( '导出成功' ) ;
} ,
async preparePrintComponent ( certificateId ) {
this . loading = true ;
try {
const [ certRes , itemsRes ] = await Promise . all ( [
getCertificate ( certificateId ) ,
listCertificateItem ( { certificateId , pageSize : 100 } )
] ) ;
this . printCertificateData = certRes . data ;
this . printItemsData = itemsRes . rows || [ ] ;
this . printComponentVisible = true ;
await this . $nextTick ( ) ;
} catch ( error ) {
this . $message . error ( '获取数据失败' ) ;
} finally {
this . loading = false ;
}
2026-06-02 13:22:58 +08:00
} ,
showTemplateDialog ( ) {
return new Promise ( ( resolve ) => {
this . templateDialogResolve = resolve ;
this . templateDialogVisible = true ;
} ) ;
} ,
confirmTemplate ( ) {
this . templateDialogVisible = false ;
if ( this . templateDialogResolve ) {
this . templateDialogResolve ( true ) ;
this . templateDialogResolve = null ;
}
2026-05-16 17:23:20 +08:00
}
}
} ;
< / script >
< style scoped >
. preview - content - wrapper {
display : flex ;
justify - content : center ;
padding : 20 px ;
}
2026-05-18 17:54:12 +08:00
. certificate - table {
width : 100 % ;
2026-05-16 17:23:20 +08:00
}
2026-05-18 17:54:12 +08:00
. cert - no {
2026-05-16 17:23:20 +08:00
font - family : "Courier New" , monospace ;
2026-05-18 17:54:12 +08:00
color : # 1 f79b9 ;
2026-05-16 17:23:20 +08:00
font - weight : 500 ;
}
2026-06-02 13:22:58 +08:00
. template - selection {
padding : 20 px ;
}
. template - radio - group {
display : flex ;
flex - direction : column ;
gap : 15 px ;
}
. template - radio {
display : flex ;
align - items : center ;
margin : 0 ;
padding : 15 px ;
border : 1 px solid # e4e7ed ;
border - radius : 8 px ;
transition : all 0.3 s ease ;
}
. template - radio : hover {
border - color : # 409 eff ;
background - color : # ecf5ff ;
}
. template - radio . is - checked {
border - color : # 409 eff ;
background - color : # ecf5ff ;
}
. template - option {
display : flex ;
align - items : center ;
gap : 10 px ;
font - size : 14 px ;
}
. template - option i {
font - size : 20 px ;
color : # 409 eff ;
}
2026-05-16 17:23:20 +08:00
< / style >