|
|
@@ -0,0 +1,835 @@
|
|
|
+<template>
|
|
|
+ <div>
|
|
|
+ <layout>
|
|
|
+ <layout-header>
|
|
|
+ <div class="top-action-bar no-print">
|
|
|
+ <el-form class="demo-form-inline">
|
|
|
+ <el-form-item>
|
|
|
+ <div class="action-buttons">
|
|
|
+ <!-- 打印按钮 -->
|
|
|
+ <el-button type="primary" @click="printCertificate" style="margin: 5px">
|
|
|
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" style="margin-right: 5px">
|
|
|
+ <path d="M5 1a2 2 0 0 0-2 2v1h10V3a2 2 0 0 0-2-2H5zm6 8H5a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1z"/>
|
|
|
+ <path d="M0 7a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2h-1v-2a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v2H2a2 2 0 0 1-2-2V7zm2.5 1a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1z"/>
|
|
|
+ </svg>
|
|
|
+ 打印证书
|
|
|
+ </el-button>
|
|
|
+
|
|
|
+ <!-- 编辑按钮 -->
|
|
|
+ <el-button type="primary" @click="toggleEditMode" style="margin: 5px">
|
|
|
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" style="margin-right: 5px">
|
|
|
+ <path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
|
|
|
+ </svg>
|
|
|
+ {{ isEditing ? '完成编辑' : '编辑证书' }}
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+ </layout-header>
|
|
|
+
|
|
|
+ <layout>
|
|
|
+ <!-- 左侧树状图区域 -->
|
|
|
+ <layout-sider :resize-directions="['right']" :width="300" v-if="showTree">
|
|
|
+ <div class="tree-section">
|
|
|
+ <div class="tree-header">
|
|
|
+ <h3>学生选择</h3>
|
|
|
+ </div>
|
|
|
+ <div class="tree-wrapper">
|
|
|
+ <el-tree
|
|
|
+ ref="treeRef"
|
|
|
+ :data="treeData"
|
|
|
+ node-key="id"
|
|
|
+ :props="defaultProps"
|
|
|
+ @node-click="handleNodeClick"
|
|
|
+ highlight-current
|
|
|
+ :default-expand-all="true"
|
|
|
+ >
|
|
|
+ <template #default="{ node, data }">
|
|
|
+ <span class="tree-node">
|
|
|
+ <span>{{ data.name }}</span>
|
|
|
+ <span v-if="data.college" class="tree-college">{{ data.college }}</span>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-tree>
|
|
|
+ </div>
|
|
|
+ <div class="selected-info" v-if="selectedStudent">
|
|
|
+ 已选择:{{ selectedStudent.name }} ({{ selectedStudent.college }})
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </layout-sider>
|
|
|
+
|
|
|
+ <!-- 右侧证书区域 -->
|
|
|
+ <layout-content>
|
|
|
+ <div class="certificate-content">
|
|
|
+ <div class="cert-wrapper" ref="certificateWrapper">
|
|
|
+ <!-- 学生证书样式 -->
|
|
|
+ <div class="student-certificate">
|
|
|
+ <!-- 证书背景图片 -->
|
|
|
+ <div class="certificate-background">
|
|
|
+ <img src="http://20.0.16.87:9094/uploads/certificate/student.png" alt="学生证书背景" class="bg-image">
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 可编辑字段 -->
|
|
|
+ <div class="certificate-fields">
|
|
|
+ <!-- Name -->
|
|
|
+ <div class="field-container name-container">
|
|
|
+ <span
|
|
|
+ v-if="!isEditing || editingField !== 'name'"
|
|
|
+ class="field name-field"
|
|
|
+ @click="startEditing('name')"
|
|
|
+ :style="{ width: nameWidth }"
|
|
|
+ ref="nameRef"
|
|
|
+ >
|
|
|
+ {{ editableData.name }}
|
|
|
+ </span>
|
|
|
+ <input
|
|
|
+ v-else
|
|
|
+ v-model="editableData.name"
|
|
|
+ class="field-input name-input"
|
|
|
+ type="text"
|
|
|
+ @blur="blurField"
|
|
|
+ @keyup.enter="blurField"
|
|
|
+ ref="nameInput"
|
|
|
+ :style="{ width: nameInputWidth }"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- College -->
|
|
|
+ <div class="field-container college-container">
|
|
|
+ <span
|
|
|
+ v-if="!isEditing || editingField !== 'college'"
|
|
|
+ class="field college-field"
|
|
|
+ @click="startEditing('college')"
|
|
|
+ :style="{ width: collegeWidth }"
|
|
|
+ ref="collegeRef"
|
|
|
+ >
|
|
|
+ {{ editableData.college }}
|
|
|
+ </span>
|
|
|
+ <input
|
|
|
+ v-else
|
|
|
+ v-model="editableData.college"
|
|
|
+ class="field-input college-input"
|
|
|
+ type="text"
|
|
|
+ @blur="blurField"
|
|
|
+ @keyup.enter="blurField"
|
|
|
+ ref="collegeInput"
|
|
|
+ :style="{ width: collegeInputWidth }"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Department -->
|
|
|
+ <div class="field-container department-container">
|
|
|
+ <span
|
|
|
+ v-if="!isEditing || editingField !== 'department'"
|
|
|
+ class="field department-field"
|
|
|
+ @click="startEditing('department')"
|
|
|
+ :style="{ width: departmentWidth }"
|
|
|
+ ref="departmentRef"
|
|
|
+ >
|
|
|
+ {{ editableData.department }}
|
|
|
+ </span>
|
|
|
+ <input
|
|
|
+ v-else
|
|
|
+ v-model="editableData.department"
|
|
|
+ class="field-input department-input"
|
|
|
+ type="text"
|
|
|
+ @blur="blurField"
|
|
|
+ @keyup.enter="blurField"
|
|
|
+ ref="departmentInput"
|
|
|
+ :style="{ width: departmentInputWidth }"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </layout-content>
|
|
|
+ </layout>
|
|
|
+ </layout>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, reactive, computed, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
|
|
|
+import { Layout, LayoutSider, LayoutContent, LayoutHeader } from '@arco-design/web-vue'
|
|
|
+import { ElTree, ElButton, ElForm, ElFormItem } from 'element-plus'
|
|
|
+import { Get_studentPrintFields } from '@/api/mes/job'
|
|
|
+
|
|
|
+// 树状图相关
|
|
|
+const showTree = ref(true)
|
|
|
+const treeRef = ref(null)
|
|
|
+const treeData = ref([])
|
|
|
+const selectedStudent = ref(null) // 存储单个选中的节点
|
|
|
+const defaultProps = {
|
|
|
+ label: 'name'
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// 证书数据
|
|
|
+const props = defineProps({
|
|
|
+ certificateData: {
|
|
|
+ type: Object,
|
|
|
+ default: () => ({
|
|
|
+ name: '陈冰婕',
|
|
|
+ college: '艺术设计学院',
|
|
|
+ department: '视传2401'
|
|
|
+ })
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 响应式数据
|
|
|
+const certificateWrapper = ref(null)
|
|
|
+const isEditing = ref(false)
|
|
|
+const editingField = ref(null)
|
|
|
+
|
|
|
+// 宽度相关
|
|
|
+const nameWidth = ref('200px')
|
|
|
+const collegeWidth = ref('200px')
|
|
|
+const departmentWidth = ref('200px')
|
|
|
+const nameInputWidth = ref('200px')
|
|
|
+const collegeInputWidth = ref('200px')
|
|
|
+const departmentInputWidth = ref('200px')
|
|
|
+
|
|
|
+// 可编辑数据
|
|
|
+const editableData = ref({
|
|
|
+ name: props.certificateData.name,
|
|
|
+ college: props.certificateData.college,
|
|
|
+ department: props.certificateData.department
|
|
|
+})
|
|
|
+
|
|
|
+// 添加监听器,确保当 props.certificateData 变化时更新显示
|
|
|
+watch(() => props.certificateData, (newData) => {
|
|
|
+ console.log('🔔 父组件数据更新:', newData)
|
|
|
+ // 更新本地响应式数据
|
|
|
+ Object.assign(editableData.value, newData)
|
|
|
+ // 强制更新一次字段宽度
|
|
|
+ nextTick(() => {
|
|
|
+ adjustFieldWidths()
|
|
|
+ })
|
|
|
+}, { deep: true })
|
|
|
+
|
|
|
+// 获取打印数据字段
|
|
|
+const getPrintDataFields = async () => {
|
|
|
+ try {
|
|
|
+ const response = await Get_studentPrintFields({})
|
|
|
+ if (response.code === 0) {
|
|
|
+ // 转换接口数据为树状图数据,处理字段名和ID
|
|
|
+ treeData.value = response.data.map(item => ({
|
|
|
+ id: item.$treeNodeId,
|
|
|
+ name: item.Name,
|
|
|
+ college: item.College,
|
|
|
+ department: item.Department
|
|
|
+ }))
|
|
|
+ console.log('转换后的数据:', treeData.value)
|
|
|
+ } else {
|
|
|
+ console.error('获取打印数据字段失败:', response.msg)
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取数据失败:', error)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 处理树状图节点点击 - 单选逻辑
|
|
|
+const handleNodeClick = (nodeData) => {
|
|
|
+ selectedStudent.value = nodeData
|
|
|
+ console.log('✅ 选中节点:', nodeData)
|
|
|
+ updateCertificateFromStudent(nodeData)
|
|
|
+}
|
|
|
+
|
|
|
+// 更新证书数据方法
|
|
|
+const updateCertificateFromStudent = (studentData) => {
|
|
|
+ console.log('从选中学生更新证书数据:', studentData)
|
|
|
+
|
|
|
+ // 创建一个完整的更新对象,包含所有字段
|
|
|
+ const updatedData = {
|
|
|
+ name: studentData.name || '陈冰婕',
|
|
|
+ college: studentData.college || '艺术设计学院',
|
|
|
+ department: studentData.department || '视传2401'
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('📤 要更新的数据:', updatedData)
|
|
|
+
|
|
|
+ // 直接更新本地响应式数据
|
|
|
+ Object.assign(editableData.value, updatedData)
|
|
|
+
|
|
|
+ // 发送更新到父组件
|
|
|
+ emit('update:certificate-data', updatedData)
|
|
|
+
|
|
|
+ // 强制重新计算并显示
|
|
|
+ nextTick(() => {
|
|
|
+ console.log('当前显示的数据:', {
|
|
|
+ 姓名: editableData.value.name,
|
|
|
+ 学院: editableData.value.college,
|
|
|
+ 系部: editableData.value.department
|
|
|
+ })
|
|
|
+ adjustFieldWidths()
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 调整字段宽度
|
|
|
+const adjustFieldWidths = () => {
|
|
|
+ const adjustField = (fieldRef, minWidth = 100) => {
|
|
|
+ const element = certificateWrapper.value?.querySelector(`[ref="${fieldRef}"]`);
|
|
|
+ if (element) {
|
|
|
+ const textLength = element.textContent?.length || 0;
|
|
|
+ const newWidth = Math.max(minWidth, textLength * 12);
|
|
|
+ return newWidth + 'px';
|
|
|
+ }
|
|
|
+ return minWidth + 'px';
|
|
|
+ }
|
|
|
+
|
|
|
+ nameWidth.value = adjustField('nameRef', 200);
|
|
|
+ collegeWidth.value = adjustField('collegeRef', 200);
|
|
|
+ departmentWidth.value = adjustField('departmentRef', 200);
|
|
|
+}
|
|
|
+
|
|
|
+const adjustInputWidth = (fieldName) => {
|
|
|
+ const minWidthMap = {
|
|
|
+ name: 200,
|
|
|
+ college: 200,
|
|
|
+ department: 200
|
|
|
+ }
|
|
|
+
|
|
|
+ const widthPropMap = {
|
|
|
+ name: 'nameInputWidth',
|
|
|
+ college: 'collegeInputWidth',
|
|
|
+ department: 'departmentInputWidth'
|
|
|
+ }
|
|
|
+
|
|
|
+ const value = editableData.value[fieldName]
|
|
|
+ const minWidth = minWidthMap[fieldName] || 100
|
|
|
+ const textLength = value ? value.length : 0
|
|
|
+ const newWidth = Math.max(minWidth, textLength * 12 + 20)
|
|
|
+
|
|
|
+ const propName = widthPropMap[fieldName]
|
|
|
+ if (propName) {
|
|
|
+ if (fieldName === 'name') nameInputWidth.value = newWidth + 'px'
|
|
|
+ else if (fieldName === 'college') collegeInputWidth.value = newWidth + 'px'
|
|
|
+ else if (fieldName === 'department') departmentInputWidth.value = newWidth + 'px'
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const toggleEditMode = () => {
|
|
|
+ if (isEditing.value) {
|
|
|
+ saveAllChanges();
|
|
|
+ isEditing.value = false;
|
|
|
+ editingField.value = null;
|
|
|
+
|
|
|
+ nextTick(() => {
|
|
|
+ adjustFieldWidths();
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ isEditing.value = true;
|
|
|
+ editingField.value = null;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const startEditing = (fieldName) => {
|
|
|
+ if (!isEditing.value) {
|
|
|
+ isEditing.value = true;
|
|
|
+ }
|
|
|
+ editingField.value = fieldName;
|
|
|
+
|
|
|
+ nextTick(() => {
|
|
|
+ const inputRefMap = {
|
|
|
+ name: 'nameInput',
|
|
|
+ college: 'collegeInput',
|
|
|
+ department: 'departmentInput'
|
|
|
+ };
|
|
|
+
|
|
|
+ const refName = inputRefMap[fieldName];
|
|
|
+ if (refName) {
|
|
|
+ const element = certificateWrapper.value?.querySelector(`[ref="${refName}"]`);
|
|
|
+ if (element) {
|
|
|
+ element.focus();
|
|
|
+ element.select();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+const blurField = () => {
|
|
|
+ editingField.value = null
|
|
|
+}
|
|
|
+
|
|
|
+const saveAllChanges = () => {
|
|
|
+ emit('update:certificate-data', {
|
|
|
+ name: editableData.value.name,
|
|
|
+ college: editableData.value.college,
|
|
|
+ department: editableData.value.department
|
|
|
+ });
|
|
|
+
|
|
|
+ emit('certificate-updated', {
|
|
|
+ data: { ...editableData.value }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+// 打印证书方法
|
|
|
+const printCertificate = async () => {
|
|
|
+ // 如果正在编辑,先保存再打印
|
|
|
+ if (isEditing.value) {
|
|
|
+ await saveAllChanges();
|
|
|
+ isEditing.value = false;
|
|
|
+ editingField.value = null;
|
|
|
+ await nextTick();
|
|
|
+ }
|
|
|
+
|
|
|
+ const originalBodyStyle = document.body.style.cssText;
|
|
|
+ const originalBodyClass = document.body.className;
|
|
|
+ const originalHtmlStyle = document.documentElement.style.cssText;
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 创建打印结构
|
|
|
+ const iframe = document.createElement('iframe');
|
|
|
+ iframe.style.position = 'fixed';
|
|
|
+ iframe.style.right = '0';
|
|
|
+ iframe.style.bottom = '0';
|
|
|
+ iframe.style.width = '0';
|
|
|
+ iframe.style.height = '0';
|
|
|
+ iframe.style.border = 'none';
|
|
|
+ iframe.style.visibility = 'hidden';
|
|
|
+ document.body.appendChild(iframe);
|
|
|
+
|
|
|
+ await new Promise(resolve => {
|
|
|
+ iframe.onload = resolve;
|
|
|
+ iframe.src = 'about:blank';
|
|
|
+ });
|
|
|
+
|
|
|
+ const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
|
|
|
+ iframeDoc.open();
|
|
|
+
|
|
|
+ // 获取当前数据
|
|
|
+ const currentData = editableData.value;
|
|
|
+
|
|
|
+ // 学生证书打印配置
|
|
|
+ const printConfig = {
|
|
|
+ fields: {
|
|
|
+ name: {
|
|
|
+ top: 315,
|
|
|
+ left: 340,
|
|
|
+ width: 200
|
|
|
+ },
|
|
|
+ college: {
|
|
|
+ top: 400,
|
|
|
+ left: 150,
|
|
|
+ width: 200
|
|
|
+ },
|
|
|
+ department: {
|
|
|
+ top: 400,
|
|
|
+ left: 520,
|
|
|
+ width: 200
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ iframeDoc.write(`
|
|
|
+<!DOCTYPE html>
|
|
|
+<html>
|
|
|
+<head>
|
|
|
+ <meta charset="UTF-8">
|
|
|
+ <title>学生证书打印</title>
|
|
|
+ <style>
|
|
|
+ @page {
|
|
|
+ size: A4 landscape;
|
|
|
+ margin: 0;
|
|
|
+ }
|
|
|
+ body {
|
|
|
+ margin: 0;
|
|
|
+ padding: 0;
|
|
|
+ background: #fff;
|
|
|
+ -webkit-print-color-adjust: exact;
|
|
|
+ print-color-adjust: exact;
|
|
|
+ overflow: hidden;
|
|
|
+ font-family: 'Source Han Sans CN', Arial, sans-serif;
|
|
|
+ }
|
|
|
+
|
|
|
+ .cert-print-container {
|
|
|
+ width: 297mm;
|
|
|
+ height: 210mm;
|
|
|
+ position: relative;
|
|
|
+ margin: 0 auto;
|
|
|
+ background: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .student-cert-print {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ position: relative;
|
|
|
+ padding: 40px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ .field-section-print {
|
|
|
+ position: absolute;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .field-value-print {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: normal;
|
|
|
+ }
|
|
|
+
|
|
|
+ .name-field-print {
|
|
|
+ top: ${printConfig.fields.name.top}px;
|
|
|
+ left: ${printConfig.fields.name.left}px;
|
|
|
+ width: ${printConfig.fields.name.width}px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .name-field-print .field-value-print {
|
|
|
+ font-size: 30px;
|
|
|
+ font-weight: 300;
|
|
|
+ letter-spacing: 12px;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .college-field-print {
|
|
|
+ top: ${printConfig.fields.college.top}px;
|
|
|
+ left: ${printConfig.fields.college.left}px;
|
|
|
+ width: ${printConfig.fields.college.width}px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .department-field-print {
|
|
|
+ top: ${printConfig.fields.department.top}px;
|
|
|
+ left: ${printConfig.fields.department.left}px;
|
|
|
+ width: ${printConfig.fields.department.width}px;
|
|
|
+ }
|
|
|
+
|
|
|
+ * {
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+ </style>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+ <div class="cert-print-container">
|
|
|
+ <div class="student-cert-print">
|
|
|
+ <!-- 只打印需要的字段,不包含背景和头部 -->
|
|
|
+
|
|
|
+ <!-- 姓名 -->
|
|
|
+ <div class="field-section-print name-field-print">
|
|
|
+ <div class="field-value-print">${currentData.name}</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 学院 -->
|
|
|
+ <div class="field-section-print college-field-print">
|
|
|
+ <div class="field-value-print">${currentData.college}</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 系部 -->
|
|
|
+ <div class="field-section-print department-field-print">
|
|
|
+ <div class="field-value-print">${currentData.department}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</body>
|
|
|
+</html>
|
|
|
+`);
|
|
|
+ iframeDoc.close();
|
|
|
+
|
|
|
+ // 等待iframe渲染完成后执行打印
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
+
|
|
|
+ // 打印调试信息
|
|
|
+ console.log('打印字段位置信息:', {
|
|
|
+ 姓名: currentData.name,
|
|
|
+ 学院: currentData.college,
|
|
|
+ 系部: currentData.department
|
|
|
+ });
|
|
|
+
|
|
|
+ iframe.contentWindow.print();
|
|
|
+
|
|
|
+ // 打印后移除iframe
|
|
|
+ setTimeout(() => {
|
|
|
+ document.body.removeChild(iframe);
|
|
|
+ // 恢复原页面样式
|
|
|
+ document.body.style.cssText = originalBodyStyle;
|
|
|
+ document.body.className = originalBodyClass;
|
|
|
+ document.documentElement.style.cssText = originalHtmlStyle;
|
|
|
+ }, 1000);
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('打印失败:', error);
|
|
|
+ // 异常时恢复样式
|
|
|
+ document.body.style.cssText = originalBodyStyle;
|
|
|
+ document.body.className = originalBodyClass;
|
|
|
+ document.documentElement.style.cssText = originalHtmlStyle;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+// 定义emit事件
|
|
|
+const emit = defineEmits(['update:certificate-data', 'certificate-updated'])
|
|
|
+
|
|
|
+// 生命周期
|
|
|
+onMounted(() => {
|
|
|
+ adjustFieldWidths()
|
|
|
+ window.addEventListener('resize', adjustFieldWidths)
|
|
|
+ getPrintDataFields()
|
|
|
+})
|
|
|
+
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ window.removeEventListener('resize', adjustFieldWidths)
|
|
|
+})
|
|
|
+
|
|
|
+// 监听器
|
|
|
+watch(() => editableData.value.name, () => {
|
|
|
+ nextTick(() => {
|
|
|
+ adjustFieldWidths()
|
|
|
+ adjustInputWidth('name')
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+watch(() => editableData.value.college, () => {
|
|
|
+ nextTick(() => {
|
|
|
+ adjustFieldWidths()
|
|
|
+ adjustInputWidth('college')
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+watch(() => editableData.value.department, () => {
|
|
|
+ nextTick(() => {
|
|
|
+ adjustFieldWidths()
|
|
|
+ adjustInputWidth('department')
|
|
|
+ })
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* 基本样式 */
|
|
|
+.certificate-content {
|
|
|
+ padding: 0;
|
|
|
+ background-color: #f9f9f9;
|
|
|
+ min-height: calc(100vh - 100px);
|
|
|
+ font-family: "SimSun", "思源黑体", "STSong", serif;
|
|
|
+ font-size: 15pt;
|
|
|
+ font-weight: normal;
|
|
|
+}
|
|
|
+
|
|
|
+/* 顶部操作栏 */
|
|
|
+.top-action-bar {
|
|
|
+ padding: 10px 20px;
|
|
|
+ background: #fff;
|
|
|
+ border-bottom: 1px solid #e8e8e8;
|
|
|
+}
|
|
|
+
|
|
|
+.action-buttons {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+}
|
|
|
+
|
|
|
+/* 树状图区域 */
|
|
|
+.tree-section {
|
|
|
+ height: 100%;
|
|
|
+ padding: 10px;
|
|
|
+ background: #fff;
|
|
|
+ border-right: 1px solid #e8e8e8;
|
|
|
+}
|
|
|
+
|
|
|
+.tree-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 15px;
|
|
|
+ padding-bottom: 10px;
|
|
|
+ border-bottom: 1px solid #e4e7ed;
|
|
|
+}
|
|
|
+
|
|
|
+.tree-header h3 {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 16px;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+
|
|
|
+.tree-wrapper {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ max-height: calc(100vh - 200px);
|
|
|
+}
|
|
|
+
|
|
|
+.tree-node {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.tree-college {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+ margin-left: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.selected-info {
|
|
|
+ margin-top: 15px;
|
|
|
+ padding: 10px;
|
|
|
+ background: #f5f7fa;
|
|
|
+ border-radius: 4px;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #606266;
|
|
|
+}
|
|
|
+
|
|
|
+/* 证书容器 */
|
|
|
+.certificate-content {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0px;
|
|
|
+}
|
|
|
+
|
|
|
+.cert-wrapper {
|
|
|
+ width: 297mm;
|
|
|
+ height: 210mm;
|
|
|
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
|
|
+ background: #fff;
|
|
|
+ margin: 0 auto;
|
|
|
+ position: relative;
|
|
|
+ padding: 0;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+/* 学生证书样式 */
|
|
|
+.student-certificate {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ position: relative;
|
|
|
+ padding: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.certificate-background {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ z-index: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.bg-image {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+}
|
|
|
+
|
|
|
+.certificate-fields {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ z-index: 2;
|
|
|
+ padding: 60px;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.field-container {
|
|
|
+ position: absolute;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+/* 字段定位 - 适应横向证书 */
|
|
|
+.name-container {
|
|
|
+ top: 370px;
|
|
|
+ left: 350px;
|
|
|
+ width: 200px;
|
|
|
+}
|
|
|
+
|
|
|
+.college-container {
|
|
|
+ top: 440px;
|
|
|
+ left: 160px;
|
|
|
+ width: 200px;
|
|
|
+}
|
|
|
+
|
|
|
+.department-container {
|
|
|
+ top: 440px;
|
|
|
+ left: 530px;
|
|
|
+ width: 200px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 字段样式 */
|
|
|
+.field {
|
|
|
+ display: inline-block;
|
|
|
+ border: 2px solid transparent;
|
|
|
+ text-align: center;
|
|
|
+ box-sizing: border-box;
|
|
|
+ vertical-align: middle;
|
|
|
+ position: relative;
|
|
|
+ min-height: 30px;
|
|
|
+ line-height: 30px;
|
|
|
+ padding: 0 10px;
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 300;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.name-field {
|
|
|
+ font-size: 30px;
|
|
|
+ font-weight: 300;
|
|
|
+ letter-spacing: 12px;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.field:hover {
|
|
|
+ background: rgba(0, 102, 204, 0.1);
|
|
|
+ border-bottom-color: #0066cc;
|
|
|
+}
|
|
|
+
|
|
|
+.field-input {
|
|
|
+ display: inline-block;
|
|
|
+ border: 2px solid #0066cc;
|
|
|
+ background: rgba(255, 255, 255, 0.9);
|
|
|
+ text-align: center;
|
|
|
+ box-sizing: border-box;
|
|
|
+ vertical-align: middle;
|
|
|
+ position: relative;
|
|
|
+ min-height: 30px;
|
|
|
+ line-height: 30px;
|
|
|
+ padding: 0 10px;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+ outline: none;
|
|
|
+ border-radius: 4px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.field-input:focus {
|
|
|
+ background: rgba(0, 102, 204, 0.1);
|
|
|
+ box-shadow: 0 0 0 2px rgba(0, 102, 204, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式设计 */
|
|
|
+@media (max-width: 1200px) {
|
|
|
+ .cert-wrapper {
|
|
|
+ width: 95%;
|
|
|
+ padding: 30px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .student-certificate {
|
|
|
+ padding: 40px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .field, .field-input {
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .cert-wrapper {
|
|
|
+ width: 100%;
|
|
|
+ padding: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .student-certificate {
|
|
|
+ padding: 30px;
|
|
|
+ background-image: none;
|
|
|
+ border: 1px solid #e8e8e8;
|
|
|
+ }
|
|
|
+
|
|
|
+ .field, .field-input {
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|