人员登记,pdf生成器

ln_ry20250512
dshclm 3 weeks ago
parent e51ee4d9b7
commit b9c213ddf4

@ -81,6 +81,48 @@
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<!-- PDFBox 核心库 -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.27</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 字体处理库 -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>fontbox</artifactId>
<version>2.0.27</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 使用SLF4J替代commons-logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.36</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.36</version>
</dependency>
</dependencies>

@ -0,0 +1,85 @@
package com.ruoyi.web.controller.pdf.controller;
import com.ruoyi.web.controller.pdf.service.PdfService;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/pdf")
public class PdfController {
private final PdfService pdfService;
public PdfController(PdfService pdfService) {
this.pdfService = pdfService;
}
@PostMapping(value = "/fill", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<byte[]> fillPdfForm(
@RequestParam(required = false) String name,
@RequestParam(required = false) String sex,
@RequestParam(required = false) String nationa,
@RequestParam(required = false) String formerName,
@RequestParam(required = false) String nationality,
@RequestParam(required = false) String maritalStatus,
@RequestParam(required = false) String political,
@RequestParam(required = false) String phone,
@RequestParam(required = false) String cerno,
@RequestParam(required = false) String address,
@RequestParam(required = false) String registeredAuthority,
@RequestParam(required = false) String permanentAddress,
@RequestParam(required = false) String residentBureau,
@RequestParam(required = false) String positionCapacity,
@RequestParam(required = false) String smPost,
@RequestParam(required = false) String smGrade,
@RequestParam(required = false) MultipartFile photo) throws Exception {
// 构建表单数据
Map<String, String> formData = new HashMap<>();
if (name != null) formData.put("姓名", name);
if (sex != null) formData.put("性别", sex);
if (nationa != null) formData.put("国籍", nationa);
if (formerName != null) formData.put("曾用名", formerName);
if (nationality != null) formData.put("民族", nationality);
if (maritalStatus != null) formData.put("婚姻状况", maritalStatus);
if (political != null) formData.put("政治面貌", political);
if (phone != null) formData.put("联系方式", phone);
if (cerno != null) formData.put("身份证号", cerno);
if (address != null) formData.put("户籍地址", address);
if (registeredAuthority != null) formData.put("户籍地公安机关", registeredAuthority);
if (permanentAddress != null) formData.put("常住地址", permanentAddress);
if (residentBureau != null) formData.put("常住地公安机关", residentBureau);
if (positionCapacity != null) formData.put("单位及职务职称", positionCapacity);
if (smPost != null) formData.put("已(拟)任涉密岗位", smPost);
if (smGrade != null) formData.put("涉密等级", smGrade);
// 构建图片数据
Map<String, MultipartFile> imageData = new HashMap<>();
if (photo != null && !photo.isEmpty()) {
imageData.put("照片", photo);
}
// 生成PDF
ByteArrayOutputStream outputStream = pdfService.fillForm(formData, imageData);
// 返回PDF文件
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_PDF);
headers.setContentDispositionFormData("attachment", "output.pdf");
return ResponseEntity.ok()
.headers(headers)
.body(outputStream.toByteArray());
}
}

@ -0,0 +1,13 @@
package com.ruoyi.web.controller.pdf.dto;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import java.util.HashMap;
import java.util.Map;
@Data
public class PdfFormRequest {
private Map<String, String> formData = new HashMap<>();
private Map<String, MultipartFile> imageData = new HashMap<>();
}

@ -0,0 +1,177 @@
package com.ruoyi.web.controller.pdf.service;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDTextField;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Map;
@Service
public class PdfService {
// 定义最大和最小字体大小,用于动态调整
private static final float MAX_FONT_SIZE = 14;
private static final float MIN_FONT_SIZE = 8;
// 需要特殊处理的字段列表
private static final String[] AUTO_WRAP_FIELDS = {
"常住地址", "户籍地址", "户籍地公安机关", "常住地公安机关"
};
public ByteArrayOutputStream fillForm(Map<String, String> formData,
Map<String, MultipartFile> imageData) throws IOException {
// 禁用系统字体扫描,只使用自定义字体
System.setProperty("org.apache.pdfbox.rendering.UseSystemFonts", "false");
// 提高字体解析警告的日志级别
System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog");
System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.fontbox", "ERROR");
PDDocument document = null;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
// 加载模板PDF
ClassLoader classLoader = getClass().getClassLoader();
File templateFile = new File(classLoader.getResource("static/file/pdf/smryscb.pdf").getFile());
document = PDDocument.load(templateFile);
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
if (acroForm == null) {
throw new IOException("PDF不包含表单域");
}
// 加载中文字体
PDType0Font font = PDType0Font.load(document,
new File(classLoader.getResource("static/file/pdf/STSONG.TTF").getFile()));
// 设置需要更新表单外观
acroForm.setNeedAppearances(true);
// 填充文本表单
for (Map.Entry<String, String> entry : formData.entrySet()) {
String fieldName = entry.getKey();
String fieldValue = entry.getValue();
PDField field = acroForm.getField(fieldName);
if (field != null) {
// 设置表单域为不可编辑
field.setReadOnly(true);
// 设置表单域不高亮显示
// for (PDAnnotationWidget widget : field.getWidgets()) {
// widget.setHighlightMode(PDAnnotationWidget.HIGHLIGHT_MODE_NONE);
// }
if (fieldValue != null &&!fieldValue.isEmpty()) {
field.setValue(fieldValue);
if (field instanceof PDTextField) {
PDTextField textField = (PDTextField) field;
if (isAutoWrapField(fieldName)) {
textField.setMultiline(true);
float fontSize = calculateFontSize(fieldValue);
textField.setDefaultAppearance("/" + font.getName() + " " + fontSize + " Tf 0 g");
} else {
textField.setDefaultAppearance("/" + font.getName() + " 12 Tf 0 g");
}
}
} else {
// 无内容时,也设置默认外观
if (field instanceof PDTextField) {
((PDTextField) field).setDefaultAppearance("/" + font.getName() + " 12 Tf 0 g");
}
}
}
}
// 插入图片
insertImages(document, acroForm, imageData);
// 保存到输出流
document.save(outputStream);
} catch (Exception e) {
e.printStackTrace(); // 打印异常信息,方便调试
throw new IOException("处理PDF时出错", e);
} finally {
if (document != null) {
document.close();
}
}
return outputStream;
}
private void insertImages(PDDocument document, PDAcroForm acroForm,
Map<String, MultipartFile> imageData) throws IOException {
for (Map.Entry<String, MultipartFile> entry : imageData.entrySet()) {
String fieldName = entry.getKey();
MultipartFile file = entry.getValue();
PDField refField = acroForm.getField(fieldName);
// 设置表单域为不可编辑
if (refField != null) {
refField.setReadOnly(true);
}
if (refField != null &&!file.isEmpty()) {
// 假设图片插入到第一页,你可以根据实际情况修改
PDPage page = document.getPage(0);
// 手动指定图片插入位置
float x = 435; // 可以根据实际情况调整2
float y = 620; // 可以根据实际情况调整
PDImageXObject image = null;
PDPageContentStream contentStream = null;
try {
// 从上传的文件创建图片对象
image = PDImageXObject.createFromByteArray(document, file.getBytes(), file.getOriginalFilename());
// 计算图片尺寸(按比例缩放)
float imageWidth = image.getWidth();
float imageHeight = image.getHeight();
float maxWidth = 100;
float maxHeight = 100;
float scale = Math.min(maxWidth / imageWidth, maxHeight / imageHeight);
imageWidth *= scale;
imageHeight *= scale;
// 在指定位置绘制图片
contentStream = new PDPageContentStream(
document, page, PDPageContentStream.AppendMode.APPEND, true);
contentStream.drawImage(image, x, y, imageWidth, imageHeight);
} finally {
if (contentStream != null) {
contentStream.close();
}
}
}
}
}
// 判断是否是需要自动换行的字段
private boolean isAutoWrapField(String fieldName) {
for (String autoWrapField : AUTO_WRAP_FIELDS) {
if (autoWrapField.equals(fieldName)) {
return true;
}
}
return false;
}
// 计算适应字段的字体大小(仅用于需要特殊处理的字段)
private float calculateFontSize(String fieldValue) {
int textLength = fieldValue.length();
float fontSize = 10;
if (textLength > 30) {
fontSize = Math.max(MIN_FONT_SIZE, 10 - ((textLength - 30) / 5));
}
return fontSize;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

@ -62,7 +62,7 @@
url: prefix + "/list",
createUrl: prefix + "/add",
updateUrl: prefix + "/edit/{id}",
detailUrl:prefix + "/detail/{id}",
detailUrl:prefix + "/editFile/{id}",
removeUrl: prefix + "/remove",
modalName: "涉密人员上岗",
type: 0,

@ -0,0 +1,523 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="include :: header('材料上传')"/>
<th:block th:include="include :: select2-css"/>
<th:block th:include="include :: jasny-bootstrap-css"/>
<link href="/file/pdf/test.css" rel="stylesheet">
<script src="/file/pdf/tailwindcss.js"></script>
<!-- Tailwind配置 -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#3b82f6',
secondary: '#64748b',
accent: '#0ea5e9',
neutral: '#f1f5f9',
'neutral-dark': '#334155',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.shadow-soft {
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
}
.transition-custom {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
/*.scale-hover {*/
/* transition: transform 0.2s ease-in-out;*/
/*}*/
/*.scale-hover:hover {*/
/* transform: scale(1.02);*/
/*}*/
}
.text-sm {
font-size: 1.4rem;
}
.text-base {
font-size: 1.4rem;
}
.text-xs {
font-size: 0.85rem;
}
.py-2 {
padding-top: 0.8rem;
padding-bottom: 0.8rem;
}
.space-y-3{
margin-bottom: 1.5rem;
}
</style>
</head>
<body class="bg-gray-50 font-sans">
<div class="main-content min-h-screen flex flex-col" id="app">
<main class="flex-grow container mx-auto px-4 py-8" style="max-width: 100%;">
<div class="max-w-10xl mx-auto" style="width: 100%;">
<div class="bg-white rounded-xl shadow-soft p-6 mb-8 scale-hover">
<form class="form-horizontal space-y-6" id="form-apply-edit" th:object="${applyInfoList}" @submit.prevent="submitForm">
<input name="applyId" type="hidden" th:field="*{applyId}"/>
<h4 class="form-header h4">基本信息</h4>
<!-- 文本字段 -->
<div class="grid md:grid-cols-3 gap-6">
<!-- 第一行前两列:姓名、性别 -->
<div class="md:col-span-1 space-y-3">
<!-- 姓名 -->
<div class="space-y-3">
<label for="name" class="block text-sm font-medium text-gray-700">姓名</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
<i class="fa-solid fa-user"></i>
</span>
<input type="text" id="name" v-model="formData.name" class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg" placeholder="请输入姓名">
</div>
</div>
<!-- 性别 -->
<div class="space-y-3">
<label for="sex" class="block text-sm font-medium text-gray-700">性别</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
<i class="fa-solid fa-venus-mars"></i>
</span>
<input type="text" id="sex" v-model="formData.sex" class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg" placeholder="请输入性别">
</div>
</div>
</div>
<!-- 第二行前两列:国籍、曾用名 -->
<div class="md:col-span-1 space-y-3">
<!-- 国籍 -->
<div class="space-y-3">
<label for="nationa" class="block text-sm font-medium text-gray-700">国籍</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
<i class="fa-solid fa-globe"></i>
</span>
<input type="text" id="nationa" v-model="formData.nationa" class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg" placeholder="请输入国籍">
</div>
</div>
<!-- 曾用名 -->
<div class="space-y-3">
<label for="formerName" class="block text-sm font-medium text-gray-700">曾用名</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
<i class="fa-solid fa-user-tag"></i>
</span>
<input type="text" id="formerName" v-model="formData.formerName" class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg" placeholder="请输入曾用名">
</div>
</div>
</div>
<!-- 第一行第三列:图片上传区域(固定位置,跨两行) -->
<div class="md:col-span-1 md:row-span-2 space-y-3 relative">
<label class="block text-sm font-medium text-gray-700">一寸照片</label>
<div class="mt-1 h-64 border-2 border-gray-300 rounded-lg shadow-soft transition-all duration-200" style="width: 141px;height: 195px">
<!-- 上传区域(未选中时显示) -->
<div v-if="!selectedFile" class="h-full flex justify-center items-center border-dashed cursor-pointer hover:border-primary">
<div class="text-center">
<i class="fa-solid fa-cloud-upload text-4xl text-gray-400 mb-2"></i>
<label for="photo" class="cursor-pointer bg-white rounded-md font-medium text-primary hover:text-primary/80">
<span>上传照片</span>
<input id="photo" name="photo" type="file" accept="image/jpeg,image/png" class="sr-only" @change="handleFileUpload">
</label>
<p class="text-xs text-gray-500">
JPG, PNG (最大 2MB)
</p>
</div>
</div>
<!-- 预览区域(选中后显示) -->
<div v-else class="h-full flex flex-col items-center justify-center p-3">
<img :src="previewUrl" alt="预览图" class="max-h-full max-w-full object-cover rounded-lg mb-2" style="max-height: 200px;">
<button type="button" @click="clearFile" class="text-xs text-red-500 hover:text-red-700">
<i class="fa-solid fa-times-circle mr-1"></i> 移除
</button>
</div>
</div>
</div>
<div class="space-y-3">
<label for="nationality" class="block text-sm font-medium text-gray-700">民族</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
<i class="fa-solid fa-people-group"></i>
</span>
<input type="text" id="nationality" v-model="formData.nationality"
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200"
placeholder="请输入民族">
</div>
</div>
<div class="space-y-3">
<label for="maritalStatus" class="block text-sm font-medium text-gray-700">婚姻状况</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
<i class="fa-solid fa-ring"></i>
</span>
<input type="text" id="maritalStatus" v-model="formData.maritalStatus"
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200"
placeholder="请输入婚姻状况">
</div>
</div>
<div class="space-y-3">
<label for="political" class="block text-sm font-medium text-gray-700">政治面貌</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
<i class="fa-solid fa-user-tie"></i>
</span>
<input type="text" id="political" v-model="formData.political"
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200"
placeholder="请输入政治面貌">
</div>
</div>
<div class="space-y-3">
<label for="phone" class="block text-sm font-medium text-gray-700">联系方式</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
<i class="fa-solid fa-phone"></i>
</span>
<input type="text" id="phone" v-model="formData.phone"
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200"
placeholder="请输入联系方式">
</div>
</div>
<div class="space-y-3">
<label for="cerno" class="block text-sm font-medium text-gray-700">身份证号</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
<i class="fa-solid fa-id-card"></i>
</span>
<input type="text" id="cerno" v-model="formData.cerno"
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200"
placeholder="请输入身份证号">
</div>
</div>
<div class="space-y-3">
<label for="address" class="block text-sm font-medium text-gray-700">户籍地址</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
<i class="fa-solid fa-house-chimney"></i>
</span>
<input type="text" id="address" v-model="formData.address"
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200"
placeholder="请输入户籍地址">
</div>
</div>
<div class="space-y-3">
<label for="registeredAuthority" class="block text-sm font-medium text-gray-700">户籍地公安机关</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
<i class="fa-solid fa-badge-shield"></i>
</span>
<input type="text" id="registeredAuthority" v-model="formData.registeredAuthority"
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200"
placeholder="请输入户籍地公安机关">
</div>
</div>
<div class="space-y-3">
<label for="permanentAddress" class="block text-sm font-medium text-gray-700">常住地址</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
<i class="fa-solid fa-house"></i>
</span>
<input type="text" id="permanentAddress" v-model="formData.permanentAddress"
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200"
placeholder="请输入常住地址">
</div>
</div>
<div class="space-y-3">
<label for="residentBureau" class="block text-sm font-medium text-gray-700">常住地公安机关</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
<i class="fa-solid fa-badge-shield"></i>
</span>
<input type="text" id="residentBureau" v-model="formData.residentBureau"
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200"
placeholder="请输入常住地公安机关">
</div>
</div>
<div class="space-y-3">
<label for="positionCapacity" class="block text-sm font-medium text-gray-700">单位及职务职称</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
<i class="fa-solid fa-building-user"></i>
</span>
<input type="text" id="positionCapacity" v-model="formData.positionCapacity"
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200"
placeholder="请输入单位及职务职称">
</div>
</div>
<div class="space-y-3">
<label for="smPost" class="block text-sm font-medium text-gray-700">已(拟)任涉密岗位</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
<i class="fa-solid fa-shield-halved"></i>
</span>
<input type="text" id="smPost" v-model="formData.smPost"
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200"
placeholder="请输入已(拟)任涉密岗位">
</div>
</div>
<div class="space-y-3">
<label for="smGrade" class="block text-sm font-medium text-gray-700">涉密等级</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
<i class="fa-solid fa-lock"></i>
</span>
<input type="text" id="smGrade" v-model="formData.smGrade"
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200"
placeholder="请输入涉密等级">
</div>
</div>
</div>
<!-- 按钮 -->
<div class="row">
<button type="submit" class="group relative w-full max-w-xs flex justify-center py-3 px-6 border border-transparent text-base font-medium rounded-lg text-white bg-primary hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary transition-all duration-300">
<span class="absolute left-0 inset-y-0 flex items-center pl-3" style="margin-right: 5px;">
<i class="fa-solid fa-file-pdf text-white transform group-hover:translate-x-0.5 transition-transform duration-200"></i>
</span>
生成PDF文件
</button>
</div>
</form>
<h4 class="form-header h4">文件上传</h4>
<div class="row">
<div class="col-sm-offset-5 col-sm-10" style="display: flex;">
<button type="button" class="btn btn-sm btn-primary" onclick="submitHandler()"><i class="fa fa-check"></i>提 交</button>&nbsp;
<button type="button" class="btn btn-sm btn-danger" onclick="closeItem()"><i class="fa fa-reply-all"></i>关 闭
</button>
</div>
</div>
</div>
<!-- 结果区域 -->
<div v-if="showResult" class="bg-white rounded-xl shadow-soft p-6 mb-8 transform hover:shadow-lg transition-all duration-300">
<div class="flex items-start">
<div class="flex-shrink-0 bg-green-100 p-3 rounded-lg">
<i class="fa-solid fa-check text-green-500 text-xl"></i>
</div>
<div class="ml-4">
<h3 class="text-lg font-medium text-gray-800">PDF生成成功</h3>
<p class="mt-1 text-sm text-gray-600">您的PDF表单已成功填写可以点击下方按钮下载。</p>
<div class="mt-4 flex flex-wrap gap-3">
<a :href="pdfUrl" download="smryscb.pdf" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-primary hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary transition-colors duration-200">
<i class="fa-solid fa-download mr-2"></i> 下载PDF
</a>
<button @click="showResult = false" class="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md shadow-sm text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary transition-colors duration-200">
<i class="fa-solid fa-times mr-2"></i> 关闭
</button>
</div>
</div>
</div>
</div>
<!-- 加载中 -->
<div v-if="isLoading" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div class="bg-white rounded-xl p-6 shadow-lg max-w-md w-full">
<div class="flex items-center">
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary mr-4"></div>
<div>
<h3 class="text-lg font-medium text-gray-800">正在处理</h3>
<p class="text-sm text-gray-500 mt-1">请稍候系统正在生成您的PDF文件...</p>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<th:block th:include="include :: footer"/>
<th:block th:include="include :: select2-js"/>
<th:block th:include="include :: jasny-bootstrap-js"/>
<script type="text/javascript" th:inline="javascript">
var prefix = ctx + "system/applyList";
function submitHandler() {
if ($.validate.form()) {
var data = $("#form-apply-edit").serializeArray();
$.operate.saveTab(prefix + "/edit", data);
}
}
new Vue({
el: '#app',
data: {
formData: {
name: '',
sex: '',
nationa: '',
formerName: '',
nationality: '',
maritalStatus: '',
political: '',
phone: '',
cerno: '',
address: '',
registeredAuthority: '',
permanentAddress: '',
residentBureau: '',
positionCapacity: '',
smPost: '',
smGrade: '',
photoUrl: '',
},
selectedFile: null,
isLoading: false,
showResult: false,
pdfUrl: '',
},
computed: {
previewUrl() {
return this.selectedFile ? window.URL.createObjectURL(this.selectedFile) : '';
}
},
methods: {
handleFileUpload(event) {
let file = event.target.files[0];
if (file) {
// 检查文件类型和大小
let fileSize = file.size / 1024 / 1024; // MB
if (fileSize > 2) {
alert('文件大小不能超过2MB');
return;
}
let fileType = file.type;
if (!fileType.startsWith('image/')) {
alert('请上传图片文件');
return;
}
// 验证图片格式
let validFormats = ['image/jpeg', 'image/png'];
if (!validFormats.includes(fileType)) {
alert('请上传JPG或PNG格式的图片');
return;
}
// 验证图片尺寸是否接近一寸照片 (2.5cm x 3.5cm, 约295x413像素)
let img = new Image();
img.src = window.URL.createObjectURL(file);
img.onload = () => {
let width = img.naturalWidth;
let height = img.naturalHeight;
let ratio = width / height;
// 检查宽高比是否接近一寸照片的比例 (约0.714)
if (Math.abs(ratio - 0.714) > 0.1) {
alert('建议上传一寸照片尺寸 (约295x413像素)');
}
this.selectedFile = file;
};
img.onerror = () => {
alert('无法加载图片,请重新选择');
};
}
},
clearFile() {
this.selectedFile = null;
document.getElementById('photo').value = '';
},
submitForm() {
// 简单验证
if (!this.formData.name || !this.formData.sex || !this.formData.nationa || !this.formData.nationality || !this.formData.maritalStatus || !this.formData.political || !this.formData.phone || !this.formData.cerno || !this.formData.address || !this.formData.registeredAuthority || !this.formData.permanentAddress || !this.formData.residentBureau || !this.formData.positionCapacity || !this.formData.smPost || !this.formData.smGrade) {
alert('请填写所有必填字段');
return;
}
// 检查是否上传了照片
if (!this.selectedFile) {
alert('请上传一寸照片');
return;
}
// 准备表单数据
const formData = new FormData();
if (this.formData.name) formData.append("name", this.formData.name);
if (this.formData.sex) formData.append("sex", this.formData.sex);
if (this.formData.nationa) formData.append("nationa", this.formData.nationa);
if (this.formData.formerName) formData.append("formerName", this.formData.formerName);
if (this.formData.nationality) formData.append("nationality", this.formData.nationality);
if (this.formData.maritalStatus) formData.append("maritalStatus", this.formData.maritalStatus);
if (this.formData.political) formData.append("political", this.formData.political);
if (this.formData.phone) formData.append("phone", this.formData.phone);
if (this.formData.cerno) formData.append("cerno", this.formData.cerno);
if (this.formData.address) formData.append("address", this.formData.address);
if (this.formData.registeredAuthority) formData.append("registeredAuthority", this.formData.registeredAuthority);
if (this.formData.permanentAddress) formData.append("permanentAddress", this.formData.permanentAddress);
if (this.formData.residentBureau) formData.append("residentBureau", this.formData.residentBureau);
if (this.formData.positionCapacity) formData.append("positionCapacity", this.formData.positionCapacity);
if (this.formData.smPost) formData.append("smPost", this.formData.smPost);
if (this.formData.smGrade) formData.append("smGrade", this.formData.smGrade);
formData.append('photo', this.selectedFile);
// 显示加载状态
this.isLoading = true;
// 发送请求
axios.post('/api/pdf/fill', formData, {
responseType: 'blob'
})
.then(response => {
// 创建下载URL
const blob = new Blob([response.data], { type: 'application/pdf' });
this.pdfUrl = window.URL.createObjectURL(blob);
// 隐藏加载状态,显示结果
this.isLoading = false;
this.showResult = true;
})
.catch(error => {
console.error('Error:', error);
this.isLoading = false;
alert('生成PDF时出错请重试');
});
}
},
mounted() {
// 从后端获取转义后的 JSON 字符串
if ([[${applyInfoList}]]){
this.formData = {...[[${applyInfoList}]]}
// 获取图片并转换为 Blob
fetch(this.formData.photoUrl)
.then(response => response.blob())
.then(blob => {
// 此时已获取二进制文件Blob
console.log('Blob 类型:', blob);
this.selectedFile = blob;
})
.catch(error => console.error('处理图片时出错:', error));
console.log(this.formData)
}
}
});
//图片上传
$('#applyUrlId').on('change.bs.fileinput ', function (e) {
// 处理自己的业务
var file = e.target.files[0];
var data = new FormData();
data.append("file", file);
$.ajax({
type: "POST",
url: ctx + "common/upload",
data: data,
cache: false,
contentType: false,
processData: false,
dataType: 'json',
success: function (result) {
if (result.code == web_status.SUCCESS) {
$("#photoUrl").val(result.url);
} else {
$.modal.alertError(result.msg);
}
},
error: function (error) {
$.modal.alertWarning("图片上传失败。");
}
});
});
</script>
</body>
</html>
Loading…
Cancel
Save