vite+vue3+typescript+elementPlus前端实现电子证书查询系统

实现背景:之前电子证书的实现是后端实现的,主要采用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="姓&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;名" 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、后端负责数据,前端负责呈现,实现更加灵活

相关推荐
Ticnix3 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人3 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl3 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人4 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼4 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端
布列瑟农的星空4 小时前
前端都能看懂的Rust入门教程(三)——控制流语句
前端·后端·rust
Mr Xu_4 小时前
Vue 3 中计算属性的最佳实践:提升可读性、可维护性与性能
前端·javascript
jerrywus4 小时前
我写了个 Claude Code Skill,再也不用手动切图传 COS 了
前端·agent·claude
玖月晴空4 小时前
探索关于Spec 和Skills 的一些实战运用-Kiro篇
前端·aigc·代码规范