fix:付款单详情添加导出PDF和打印功能,付款单列表导出添加应付单信息

dev_1.0.2
jiangpeng 2026-04-24 15:38:14 +08:00
parent 4bdbc1daf7
commit c2bbeb7cdc
4 changed files with 288 additions and 21 deletions

View File

@ -12,6 +12,11 @@
<div class="dialog-body" v-if="detail" ref="paymentDrawer">
<div class="section">
<el-divider content-position="left">采购-付款单</el-divider>
<div class="section-actions">
<el-button type="primary" size="mini" :loading="pdfExporting" @click="handleExportPDF">PDF</el-button>
<el-button size="mini" @click="handlePrint"></el-button>
</div>
<div ref="pdfExportArea">
<div class="details-container">
<el-row :gutter="20">
<el-col :span="8">
@ -94,6 +99,8 @@
<el-col :span="24">
<div class="detail-item">特别说明: {{ detail.remark }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<div class="detail-item">
<span style="margin-right: 10px">附件:</span>
@ -101,7 +108,7 @@
</div>
</el-col>
</el-row>
<el-row :gutter="20" v-if="detail.fileList == null || detail.fileList.length < 10">
<el-row :gutter="20" class="export-exclude" v-if="detail.fileList == null || detail.fileList.length < 10">
<el-col :span="24">
<div class="detail-item">
<span style="margin-right: 10px">附件补充:</span>
@ -112,7 +119,6 @@
</el-col>
</el-row>
</div>
</div>
<div class="section">
<el-divider content-position="left">采购-应付单</el-divider>
@ -143,6 +149,8 @@
<el-divider content-position="left">流转意见</el-divider>
<flow-opinion-timeline :logs="approveLogs" />
</div>
</div>
</div>
</div>
<edit-form :visible.sync="payableVisible" :data="selectedPayableRow" :z-index="2000" @close="payableVisible = false" />
</el-drawer>
@ -153,6 +161,7 @@ import FileUpload from "@/components/FileUpload";
import EditForm from "@/views/finance/payable/components/EditForm.vue";
import FlowOpinionTimeline from "@/views/finance/components/FlowOpinionTimeline.vue";
import { listCompletedFlows } from "@/api/flow";
import { exportElementToPDF } from "@/views/approve/finance/pdfUtils";
export default {
name: "DetailDrawer",
@ -180,6 +189,7 @@ export default {
approveLogs: [],
approveLogsTimer: null,
approveLogsLoading: false,
pdfExporting: false,
};
},
beforeDestroy() {
@ -276,6 +286,114 @@ export default {
this.approveLogsLoading = false;
});
},
async handleExportPDF() {
if (!this.$refs.pdfExportArea) {
this.$modal.msgWarning("未找到可导出的内容");
return;
}
this.pdfExporting = true;
let exportWrapper = null;
try {
await this.$nextTick();
const sourceEl = this.$refs.pdfExportArea;
const sourceWidth = Math.ceil(sourceEl.getBoundingClientRect().width) || 1000;
//
exportWrapper = document.createElement("div");
exportWrapper.style.position = "fixed";
exportWrapper.style.left = "-10000px";
exportWrapper.style.top = "0";
exportWrapper.style.width = `${sourceWidth}px`;
exportWrapper.style.background = "#F8F5F0";
exportWrapper.style.overflow = "visible";
exportWrapper.style.zIndex = "-1";
exportWrapper.innerHTML = sourceEl.outerHTML;
document.body.appendChild(exportWrapper);
await new Promise(resolve => setTimeout(resolve, 60));
const exportTarget = exportWrapper.firstElementChild || exportWrapper;
exportTarget.querySelectorAll(".export-exclude").forEach(node => node.remove());
const fileName = `付款单详情-${this.detail && this.detail.paymentBillCode ? this.detail.paymentBillCode : ""}.pdf`;
await exportElementToPDF(exportTarget, fileName);
this.$modal.msgSuccess("PDF导出成功");
} catch (error) {
console.error("PDF导出失败:", error);
this.$modal.msgError("PDF导出失败请稍后重试");
} finally {
if (exportWrapper && exportWrapper.parentNode) {
exportWrapper.parentNode.removeChild(exportWrapper);
}
this.pdfExporting = false;
}
},
handlePrint() {
const exportArea = this.$refs.pdfExportArea;
if (!exportArea) {
this.$modal.msgWarning("未找到可打印的内容");
return;
}
const printWindow = window.open("", "_blank");
if (!printWindow) {
this.$modal.msgError("打印窗口被拦截,请允许弹窗后重试");
return;
}
const styleContent = Array.from(document.querySelectorAll("style, link[rel='stylesheet']"))
.map(node => node.outerHTML)
.join("");
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>付款单详情打印</title>
${styleContent}
<style>
@page { margin: 8mm; size: auto; }
html, body {
margin: 0;
padding: 0;
height: auto;
overflow: visible;
}
.print-root {
width: 100%;
transform: scale(0.97);
transform-origin: top left;
page-break-after: avoid;
break-after: avoid-page;
}
.print-root .detail-item:last-child {
margin-bottom: 0 !important;
}
.export-exclude {
display: none !important;
}
@media print {
* {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
</style>
</head>
<body>
<div class="print-root">${exportArea.outerHTML}</div>
</body>
</html>
`;
printWindow.document.open();
printWindow.document.write(html);
printWindow.document.close();
printWindow.focus();
printWindow.onload = () => {
printWindow.print();
printWindow.close();
};
}
},
};
</script>
@ -307,6 +425,12 @@ export default {
margin-bottom: 20px;
}
.section-actions {
display: flex;
justify-content: flex-end;
margin-bottom: 10px;
}
.red-text{
color: red;
}

View File

@ -1,29 +1,27 @@
package com.ruoyi.sip.controller;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.config.flow.ProcessConfig;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.sip.domain.OmsFinAttachment;
import com.ruoyi.sip.domain.OmsPaymentBill;
import com.ruoyi.sip.flowable.service.TodoService;
import com.ruoyi.sip.service.IOmsFinAttachmentService;
import com.ruoyi.sip.service.IOmsPaymentBillService;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.sip.domain.OmsPaymentBill;
import com.ruoyi.sip.service.IOmsPaymentBillService;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;
import org.springframework.web.multipart.MultipartFile;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Controller
*
@ -113,12 +111,7 @@ public class OmsPaymentBillController extends BaseController
@ResponseBody
public AjaxResult export(OmsPaymentBill omsPaymentBill)
{
List<OmsPaymentBill> list = omsPaymentBillService.selectOmsPaymentBillList(omsPaymentBill);
ExcelUtil<OmsPaymentBill> util = new ExcelUtil<OmsPaymentBill>(OmsPaymentBill.class);
todoService.fillApproveNode(list,
Arrays.asList(processConfig.getDefinition().getFinancePayment(), processConfig.getDefinition().getFinanceRefund())
, OmsPaymentBill::getPaymentBillCode, (a, b) -> a.setApproveNode(b.get(a.getPaymentBillCode())));
return util.exportExcel(list, "采购付款单数据");
return omsPaymentBillService.exportExcelWithPayableDetails(omsPaymentBill);
}
/**

View File

@ -66,6 +66,8 @@ public interface IOmsPaymentBillService
*/
public int deleteOmsPaymentBillById(Long id);
AjaxResult exportExcelWithPayableDetails(OmsPaymentBill omsPaymentBill);
PaymentBillDetailDTO query(Long id);
AjaxResult returnPaymentBill(Long id);

View File

@ -1,5 +1,6 @@
package com.ruoyi.sip.service.impl;
import com.alibaba.excel.EasyExcel;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
@ -17,9 +18,11 @@ import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.enums.ApproveStatusEnum;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.DictUtils;
import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.common.utils.file.FileUploadUtils;
import com.ruoyi.common.utils.mail.TemplateMailUtil;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.sip.domain.*;
import com.ruoyi.sip.domain.dto.PaymentBillDetailDTO;
import com.ruoyi.sip.domain.dto.PaymentBillPayableDetailDTO;
@ -29,6 +32,7 @@ import com.ruoyi.sip.flowable.domain.Todo;
import com.ruoyi.sip.flowable.service.TodoCommonTemplate;
import com.ruoyi.sip.flowable.service.TodoService;
import com.ruoyi.sip.service.*;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.ManagementService;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Autowired;
@ -45,6 +49,7 @@ import org.springframework.web.multipart.MultipartFile;
* @author ruoyi
* @date 2025-10-22
*/
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class OmsPaymentBillServiceImpl implements IOmsPaymentBillService , TodoCommonTemplate
@ -108,6 +113,149 @@ public class OmsPaymentBillServiceImpl implements IOmsPaymentBillService , TodoC
return omsPaymentBillMapper.selectOmsPaymentBillList(omsPaymentBill);
}
@Override
public AjaxResult exportExcelWithPayableDetails(OmsPaymentBill omsPaymentBill) {
List<OmsPaymentBill> list = omsPaymentBillMapper.selectOmsPaymentBillList(omsPaymentBill);
todoService.fillApproveNode(list,
Arrays.asList(processConfig.getDefinition().getFinancePayment(), processConfig.getDefinition().getFinanceRefund())
, OmsPaymentBill::getPaymentBillCode, (a, b) -> a.setApproveNode(b.get(a.getPaymentBillCode())));
List<List<PaymentBillPayableDetailDTO>> payableDetailsGroup = new ArrayList<>();
int maxPayableCount = 0;
for (OmsPaymentBill bill : list) {
List<PaymentBillPayableDetailDTO> payableDetails = Collections.emptyList();
if (bill.getId() != null) {
PaymentBillDetailDTO detailDTO = this.query(bill.getId());
if (detailDTO != null && detailDTO.getPayableDetails() != null) {
payableDetails = detailDTO.getPayableDetails();
}
}
payableDetailsGroup.add(payableDetails);
maxPayableCount = Math.max(maxPayableCount, payableDetails.size());
}
List<List<String>> header = buildExportHeader(maxPayableCount);
List<List<Object>> data = buildExportData(list, payableDetailsGroup, maxPayableCount);
try {
ExcelUtil<OmsPaymentBill> util = new ExcelUtil<>(OmsPaymentBill.class);
String fileName = util.encodingFilename("采购付款单数据");
String filePath = util.getAbsoluteFile(fileName);
EasyExcel.write(filePath).head(header).sheet("采购付款单数据").doWrite(data);
return AjaxResult.success(fileName);
} catch (Exception e) {
log.error("导出采购付款单失败", e);
return AjaxResult.error("导出采购付款单失败:" + e.getMessage());
}
}
private List<List<String>> buildExportHeader(int maxPayableCount) {
List<List<String>> header = new ArrayList<>();
Collections.addAll(header,
Collections.singletonList("项目编号"),
Collections.singletonList("项目名称"),
Collections.singletonList("付款单编号"),
Collections.singletonList("备注"),
Collections.singletonList("预计付款时间"),
Collections.singletonList("制造商名称"),
Collections.singletonList("合同编号"),
Collections.singletonList("含税总价"),
Collections.singletonList("未税总价"),
Collections.singletonList("税额"),
Collections.singletonList("实际付款时间"),
Collections.singletonList("付款状态"),
Collections.singletonList("审批状态"),
Collections.singletonList("审批节点"),
Collections.singletonList("审批时间"),
Collections.singletonList("支付方式"),
Collections.singletonList("应付单编号"),
Collections.singletonList("账户名称"),
Collections.singletonList("银行账号"),
Collections.singletonList("银行开户行"),
Collections.singletonList("银行行号")
);
for (int i = 1; i <= maxPayableCount; i++) {
header.add(Collections.singletonList("应付单" + i + "-项目编号"));
header.add(Collections.singletonList("应付单" + i + "-项目名称"));
header.add(Collections.singletonList("应付单" + i + "-采购应付单编号"));
header.add(Collections.singletonList("应付单" + i + "-含税总价"));
header.add(Collections.singletonList("应付单" + i + "-本次付款金额"));
header.add(Collections.singletonList("应付单" + i + "-本次付款比例"));
}
return header;
}
private List<List<Object>> buildExportData(List<OmsPaymentBill> list,
List<List<PaymentBillPayableDetailDTO>> payableDetailsGroup,
int maxPayableCount) {
List<List<Object>> data = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
OmsPaymentBill bill = list.get(i);
List<PaymentBillPayableDetailDTO> payableDetails = payableDetailsGroup.get(i);
List<Object> row = new ArrayList<>();
Collections.addAll(row,
bill.getProjectCode(),
bill.getProjectName(),
bill.getPaymentBillCode(),
resolvePaymentBillTypeDesc(bill.getPaymentBillType()),
formatDateTime(bill.getPaymentTime()),
bill.getVendorName(),
bill.getOrderCode(),
bill.getTotalPriceWithTax(),
bill.getTotalPriceWithoutTax(),
bill.getTaxAmount(),
formatDateTime(bill.getActualPaymentTime()),
DictUtils.getDictLabel("payment_status", bill.getPaymentStatus()),
DictUtils.getDictLabel("approve_status", bill.getApproveStatus()),
bill.getApproveNode(),
formatDateTime(bill.getApproveTime()),
DictUtils.getDictLabel("payment_method", bill.getPaymentMethod()),
bill.getPayableBillCode(),
bill.getPayName(),
bill.getPayBankNumber(),
bill.getPayBankOpenAddress(),
bill.getBankNumber()
);
for (int index = 0; index < maxPayableCount; index++) {
if (index < payableDetails.size()) {
PaymentBillPayableDetailDTO detail = payableDetails.get(index);
row.add(detail.getProjectCode());
row.add(detail.getProjectName());
row.add(detail.getPayableBillCode());
row.add(detail.getTotalPriceWithTax());
row.add(detail.getPaymentAmount());
row.add(detail.getPaymentRate());
} else {
row.add("");
row.add("");
row.add("");
row.add("");
row.add("");
row.add("");
}
}
data.add(row);
}
return data;
}
private String formatDateTime(Date date) {
return date == null ? "" : DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", date);
}
private String resolvePaymentBillTypeDesc(String paymentBillType) {
if (OmsPaymentBill.PaymentBillTypeEnum.FROM_PAYABLE.getCode().equals(paymentBillType)) {
return OmsPaymentBill.PaymentBillTypeEnum.FROM_PAYABLE.getDesc();
}
if (OmsPaymentBill.PaymentBillTypeEnum.PRE_PAYMENT.getCode().equals(paymentBillType)) {
return OmsPaymentBill.PaymentBillTypeEnum.PRE_PAYMENT.getDesc();
}
if (OmsPaymentBill.PaymentBillTypeEnum.REFUND.getCode().equals(paymentBillType)) {
return OmsPaymentBill.PaymentBillTypeEnum.REFUND.getDesc();
}
return paymentBillType;
}
/**
*
*