实现背景:之前电子证书的实现是后端实现的,主要采用GD库技术,在底图上添加文字水印和图片水印实现的。这里采用前端技术实现电子证书的呈现以及点击证书下载,优点是:后端给前端传递的是一组数据,不需要传证书的图片,交互所需数据流大大减少了。后端不需要生成证书,就不需要额外开辟存储证书的空间,当用户量很大时,节省开支。
前端技术栈:vite+vue3+typescript+elementPlus
证书查询首页实现,代码如下:
html
<template>
<el-row class="header">
<el-col :span="24">
<el-text>电子证书查询系统</el-text>
</el-col>
</el-row>
<el-row class="main">
<el-col :span="24">
<el-card style="max-width: 680px" shadow="always">
<template #header>
<div class="card-header">
<span>证书查询系统</span>
</div>
</template>
<el-form
ref="ruleFormRef"
:model="ruleForm"
:rules="rules"
label-width="auto"
class="demo-ruleForm"
:size="formSize"
:label-position="labelPosition"
status-icon
>
<el-form-item label="姓 名" prop="name">
<el-input v-model="ruleForm.name" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="身份证号" prop="idNo">
<el-input v-model="ruleForm.idNo" placeholder="请输入身份证号" />
</el-form-item>
<el-form-item label="证书编号" prop="certificateNo">
<el-input v-model="ruleForm.certificateNo" placeholder="请输入证书编号" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm(ruleFormRef)"> 查询 </el-button>
</el-form-item>
</el-form>
</el-card>
</el-col>
</el-row>
</template>
<script lang="ts" setup name="CertificateIndex">
import { reactive, ref } from 'vue'
import type { ComponentSize, FormInstance, FormRules, FormProps } from 'element-plus'
import { ElMessage } from 'element-plus'
import { createItem } from '../services/crudService'
import { useRouter } from 'vue-router'
const router = useRouter()
interface RuleForm {
name: string
idNo: string
certificateNo: string
}
const formSize = ref<ComponentSize>('large')
const labelPosition = ref<FormProps['labelPosition']>('left')
const ruleFormRef = ref<FormInstance>()
const ruleForm = reactive<RuleForm>({
name: '',
idNo: '',
certificateNo: ''
})
const rules = reactive<FormRules<RuleForm>>({
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
idNo: [{ required: true, message: '请输入身份证号', trigger: 'blur' }],
certificateNo: [{ required: true, message: '请输入证书编号', trigger: 'blur' }]
})
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
// Validate the form
await formEl.validate()
// If validation passes, call createItem with the form data
const { data } = await createItem(ruleForm)
if (!data.id) {
ElMessage({
message: '暂无此人相关证书!',
type: 'warning'
})
return
}
router.push({ name: 'CertificateDetail', query: { data: JSON.stringify(data) } })
}
</script>
<style scoped style="scss">
.header {
background-color: #1174c2;
width: 100%;
height: 50px;
.el-col {
text-align: center;
vertical-align: center;
padding: 0.5rem 0;
.el-text {
font-size: 1.5rem;
color: #fff;
}
}
}
.main {
margin-top: 100px;
.el-col {
.el-card {
margin: 0 auto;
.card-header {
text-align: center;
vertical-align: center;
font-size: 1.5rem;
background-color: #1174c2;
color: #fff;
width: 100%;
padding: 0.8rem 0;
}
.el-form {
.el-form-item {
margin: 2rem auto;
}
.el-button {
font-size: 1.5rem;
padding: 1.5rem 0;
width: 100%;
background-color: #1174c2;
}
}
}
}
}
</style>
证书查询首页实现,效果呈现如下:
电子证书查询结果实现,代码如下:
html
<template>
<div class="main">
<div
class="card-header p-2 w-full bg-[#1174c2] text-[#fff] text-center text-xl fixed top-0 left-0 w-full z-50"
>
<span>电子证书查询结果</span>
</div>
<el-card shadow="always" class="mt-20">
<div class="content" ref="contentToCapture">
<div class="logo w-28 h-10 mt-4"></div>
<div class="text-center mt-20 mb-6 text-lg dirBlod font-bold">内部审核员证书</div>
<div class="mb-4 main">
<img
:src="crossOriginImageSrc"
alt="Cross-origin image"
style="width: 88px; height: 118px"
fit="cover"
/>
<div class="text-base mt-6">{{ form.name }}</div>
</div>
<div class="id text-base mb-4 dirBlod text-center">ID: {{ form.idNo }}</div>
<div class="text text-base">
<div class="mb-4 dirBlod text-center">兹证明其参加了 {{ form.course }}</div>
<div class="ml-4 dirBlod">内部审核员培训课程并经考核合格,特发此证。</div>
</div>
<div class="footer mt-20">
<div class="text-xs">
<div class="dirBlod leading-6">发证日期 {{ form.authorizationDate }}</div>
<div class="dirBlod leading-6">编号 {{ form.certificateNo }}</div>
<div class="dirBlod leading-6">查询 {{ form.url }}</div>
</div>
<div class="text-base dirBlod gz-bg">
<div class="gz-bg-img"></div>
xx教育培训有限公司
</div>
</div>
</div>
<div @click="captureAndDownload" class="text-center mt-5 text-blue-600 cursor-pointer">
证书下载
</div>
</el-card>
</div>
</template>
<script lang="ts" setup name="CertificateDetail">
import { ref, reactive, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import html2canvas from 'html2canvas'
import { saveAs } from 'file-saver'
// 后端基本路径
const url = '/dev-api'
const route = useRoute()
const form = reactive(JSON.parse(route.query.data as string))
const crossOriginImageSrc = ref(url + form.path) // 示例跨域图片
const contentToCapture = ref<HTMLDivElement>()
async function captureAndDownload() {
if (!contentToCapture.value) return
try {
const canvas = await html2canvas(contentToCapture.value, {
useCORS: true // 允许跨域请求
})
const imgDataUrl = canvas.toDataURL('image/png')
const uniqueBlobUrl = URL.createObjectURL(
new Blob([await fetch(imgDataUrl).then((res) => res.blob())], { type: 'image/png' })
)
saveAs(uniqueBlobUrl, 'screenshot.png')
} catch (error) {
console.error('Error capturing screenshot:', error)
}
}
</script>
<style scoped lang="scss">
.main {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.card-header {
height: 50px;
}
.el-card {
margin-top: 60px;
margin-bottom: 60px;
width: 620px;
}
.content {
position: relative;
background: url(@/assets/images/bg.png) no-repeat;
background-size: 100% 100%;
height: 880px;
padding: 106px;
font-family: 'dirBlod', sans-serif;
.logo {
background: url(@/assets/images/logo.png) no-repeat;
background-size: 100% 100%;
}
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
}
.dirBlod {
font-family: 'dirBlod', sans-serif;
}
.gz-bg {
position: relative;
.gz-bg-img {
position: absolute;
top: -280%;
left: 20%;
width: 120px;
height: 120px;
background: url(@/assets/images/seal.png) no-repeat;
background-size: 100% 100%;
}
}
</style>
电子证书查询结果实现,效果呈现如下:
小结:
1、节省了存储电子证书图片的空间;
2、后端负责数据,前端负责呈现,实现更加灵活