This commit is contained in:
zlwu committed 2026-01-09 10:24:37 +08:00
commit 451332d117
15 files changed
+976 -1

No files matched your search

@@ -0,0 +1,75 @@
package org.dromara.cw.controller;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.core.domain.R;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.cw.domain.vo.CwBudgetExecutionImportVo;
import org.dromara.cw.listener.CwBudgetExecutionImportListener;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.http.MediaType;
import java.io.InputStream;
/**
* 预算执行导入 Controller
*/
@Slf4j
@RestController
@RequestMapping("/cw/budgetExecutionImport")
public class CwBudgetExecutionImportController extends BaseController {
/**
* 导入数据
*
* @param file 导入文件
*/
@Log(title = "预算执行导入", businessType = BusinessType.IMPORT)
@SaCheckPermission("cw:budgetExecutionImport:import")
@PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<Void> importData(@RequestPart("file") MultipartFile file) throws Exception {
try (InputStream is = file.getInputStream()) {
ExcelUtil.importExcel(is, CwBudgetExecutionImportVo.class, new CwBudgetExecutionImportListener());
return R.ok();
} catch (Exception e) {
log.error("导入失败", e);
throw e;
}
}
/**
* 获取导入模板(导出空模板)
*/
@SaCheckPermission("cw:budgetExecutionImport:import")
@PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) {
ExcelUtil.exportExcel(java.util.List.of(), "预算执行导入模板", CwBudgetExecutionImportVo.class, response);
}
/**
* 查询导入批次分页列表
*/
@SaCheckPermission("cw:budgetExecutionImport:list")
@GetMapping("/batch/list")
public org.dromara.common.mybatis.core.page.TableDataInfo<?> batchList(org.dromara.common.mybatis.core.page.PageQuery pageQuery) {
var service = org.dromara.common.core.utils.SpringUtils.getBean(org.dromara.cw.service.ICwBudgetExecutionImportService.class);
return service.queryBatchPageList(pageQuery);
}
/**
* 查询导入明细分页列表
*/
@SaCheckPermission("cw:budgetExecutionImport:list")
@GetMapping("/item/list")
public org.dromara.common.mybatis.core.page.TableDataInfo<?> itemList(@RequestParam("batchId") Long batchId, org.dromara.common.mybatis.core.page.PageQuery pageQuery) {
var service = org.dromara.common.core.utils.SpringUtils.getBean(org.dromara.cw.service.ICwBudgetExecutionImportService.class);
return service.queryItemPageList(batchId, pageQuery);
}
}
@@ -0,0 +1,58 @@
package org.dromara.cw.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import java.math.BigDecimal;
import java.util.Date;
/**
* 预算执行导入批次对象 cw_budget_execution_import_batch
*
* @author ruoyi
* @date 2026-01-05
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("cw_budget_execution_import_batch")
public class CwBudgetExecutionImportBatch extends BaseEntity {
private static final long serialVersionUID = 1L;
@TableId(value = "id")
private Long id;
private String batchNo;
private String fileName;
private Integer totalCount;
private Integer successCount;
private Integer failedCount;
private Long importBy;
private Date importTime;
private String status;
private String remark;
private BigDecimal totalAmount;
private Long createBy;
private Date createTime;
private Long updateBy;
private Date updateTime;
}
@@ -0,0 +1,100 @@
package org.dromara.cw.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import java.math.BigDecimal;
import java.util.Date;
/**
* 预算执行导入明细对象 cw_budget_execution_import_item
*
* @author ruoyi
* @date 2026-01-05
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("cw_budget_execution_import_item")
public class CwBudgetExecutionImportItem extends BaseEntity {
private static final long serialVersionUID = 1L;
@TableId(value = "id")
private Long id;
private Long batchId;
private Integer rowNo;
private String serialNo;
private String purpose;
private BigDecimal amount;
private String deptPaymentCategory;
private String funcSubject;
private String fundNature;
private String indicatorSource;
private String paymentType;
private String payeeBank;
private String payee;
private String payeeAccount;
private String projectType;
private String project;
private java.sql.Date receiptDate;
private String localIndicatorDocNo;
private String budgetSource;
private String indicatorId;
@TableField("ext_field_1")
private String extField1;
@TableField("ext_field_2")
private String extField2;
@TableField("ext_field_3")
private String extField3;
@TableField("ext_field_4")
private String extField4;
@TableField("ext_field_5")
private String extField5;
private String otherFields;
private String rawData;
private String importStatus;
private String errorMessage;
private Long createBy;
private Date createTime;
private Date updateTime;
private Long updateBy;
}
@@ -0,0 +1,43 @@
package org.dromara.cw.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* Excel 导入映射 VO
*/
@Data
public class CwBudgetExecutionImportVo {
private String serialNo;
private String purpose;
private BigDecimal amount;
private String deptPaymentCategory;
private String funcSubject;
private String fundNature;
private String indicatorSource;
private String paymentType;
private String payeeBank;
private String payee;
private String payeeAccount;
private String projectType;
private String project;
/**
* 收到回单的时间,使用 String 避免 Excel 解析严格类型导致异常,
* 在 Listener 中做灵活解析为 Date
*/
private String receiptDate;
private String localIndicatorDocNo;
private String budgetSource;
private String indicatorId;
private String extField1;
private String extField2;
private String extField3;
private String extField4;
private String extField5;
private String otherFields;
// 额外字段:原始行号由监听器填充
private Integer rowNo;
}
@@ -0,0 +1,139 @@
package org.dromara.cw.listener;
import cn.idev.excel.context.AnalysisContext;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.excel.core.ExcelResult;
import org.dromara.common.excel.core.DefaultExcelListener;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.cw.domain.CwBudgetExecutionImportItem;
import org.dromara.cw.domain.CwBudgetExecutionImportBatch;
import org.dromara.cw.domain.vo.CwBudgetExecutionImportVo;
import org.dromara.cw.service.ICwBudgetExecutionImportService;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 预算执行导入监听器
*/
@Slf4j
public class CwBudgetExecutionImportListener extends DefaultExcelListener<CwBudgetExecutionImportVo> {
private final ICwBudgetExecutionImportService importService;
private final Long operUserId;
private final List<CwBudgetExecutionImportItem> buffer = new ArrayList<>();
public CwBudgetExecutionImportListener() {
super(true);
this.importService = SpringUtils.getBean(ICwBudgetExecutionImportService.class);
this.operUserId = LoginHelper.getUserId();
}
@Override
public void invoke(CwBudgetExecutionImportVo data, AnalysisContext context) {
try {
CwBudgetExecutionImportItem item = new CwBudgetExecutionImportItem();
// 尽量从解析上下文获取原始 Excel 行号(更可靠),否则使用 VO 中的 rowNo
try {
int rowIndex = context.readRowHolder().getRowIndex(); // 0-based
item.setRowNo(rowIndex + 1);
} catch (Exception ex) {
// fallback
item.setRowNo(data.getRowNo());
}
item.setSerialNo(data.getSerialNo());
item.setPurpose(data.getPurpose());
item.setAmount(data.getAmount());
item.setDeptPaymentCategory(data.getDeptPaymentCategory());
item.setFuncSubject(data.getFuncSubject());
item.setFundNature(data.getFundNature());
item.setIndicatorSource(data.getIndicatorSource());
item.setPaymentType(data.getPaymentType());
item.setPayeeBank(data.getPayeeBank());
item.setPayee(data.getPayee());
item.setPayeeAccount(data.getPayeeAccount());
item.setProjectType(data.getProjectType());
item.setProject(data.getProject());
// 灵活解析回单时间(Excel 中可能为字符串或日期格式不一致)
try {
if (data.getReceiptDate() != null) {
// receiptDate 在 VO 中为 String(已改),尝试解析为 Date
java.util.Date parsed = DateUtils.parseDate(data.getReceiptDate());
if (parsed != null) {
item.setReceiptDate(new java.sql.Date(parsed.getTime()));
} else {
// 解析失败时记录为 null,并在明细中标记错误以便人工排查
item.setReceiptDate(null);
item.setImportStatus("fail");
item.setErrorMessage("回单时间解析失败: " + data.getReceiptDate());
// 将该行仍加入buffer以便入库和后续查看;可以选择跳过或计为失败
}
}
} catch (Exception e) {
item.setReceiptDate(null);
item.setImportStatus("fail");
item.setErrorMessage("回单时间解析异常: " + e.getMessage());
}
item.setLocalIndicatorDocNo(data.getLocalIndicatorDocNo());
item.setBudgetSource(data.getBudgetSource());
item.setIndicatorId(data.getIndicatorId());
item.setExtField1(data.getExtField1());
item.setExtField2(data.getExtField2());
item.setExtField3(data.getExtField3());
item.setExtField4(data.getExtField4());
item.setExtField5(data.getExtField5());
item.setOtherFields(data.getOtherFields());
item.setImportStatus("pending");
item.setCreateBy(operUserId);
item.setCreateTime(new Date());
buffer.add(item);
} catch (Exception e) {
log.error("导入行处理异常", e);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
log.info("Excel 解析完成,准备写入数据库,行数={}", buffer.size());
if (!buffer.isEmpty()) {
// 创建批次
CwBudgetExecutionImportBatch batch = new CwBudgetExecutionImportBatch();
batch.setBatchNo(String.valueOf(System.currentTimeMillis()));
batch.setFileName("excel_import");
batch.setTotalCount(buffer.size());
batch.setSuccessCount(0);
batch.setFailedCount(0);
batch.setImportBy(operUserId);
batch.setImportTime(new Date());
batch.setStatus("running");
batch.setCreateBy(operUserId);
batch.setCreateTime(new Date());
Long batchId = importService.insertBatch(batch);
// 赋值 batchId 并批量插入
BigDecimal totalAmount = BigDecimal.ZERO;
for (int i = 0; i < buffer.size(); i++) {
CwBudgetExecutionImportItem it = buffer.get(i);
it.setBatchId(batchId);
// 确保 rowNo 非空(数据库约束),若为空则用解析顺序作为行号
if (it.getRowNo() == null) {
it.setRowNo(i + 1);
}
totalAmount = totalAmount.add(it.getAmount() == null ? BigDecimal.ZERO : it.getAmount());
}
importService.batchInsertItems(buffer);
// 更新批次汇总
importService.updateBatchSummary(batchId, buffer.size(), 0, totalAmount);
log.info("导入完成,batchId={}, totalAmount={}, rows={}", batchId, totalAmount, buffer.size());
}
}
// 使用 DefaultExcelListener 提供的默认实现
}
@@ -0,0 +1,14 @@
package org.dromara.cw.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.cw.domain.CwBudgetExecutionImportBatch;
/**
* 批次 Mapper
*/
public interface CwBudgetExecutionImportBatchMapper extends BaseMapperPlus<CwBudgetExecutionImportBatch, CwBudgetExecutionImportBatch> {
}
@@ -0,0 +1,17 @@
package org.dromara.cw.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.cw.domain.CwBudgetExecutionImportItem;
import java.util.List;
/**
* 明细 Mapper
*/
public interface CwBudgetExecutionImportItemMapper extends BaseMapperPlus<CwBudgetExecutionImportItem, CwBudgetExecutionImportItem> {
int batchInsert(List<CwBudgetExecutionImportItem> list);
}
@@ -0,0 +1,24 @@
package org.dromara.cw.service;
import org.dromara.cw.domain.CwBudgetExecutionImportBatch;
import org.dromara.cw.domain.CwBudgetExecutionImportItem;
import java.math.BigDecimal;
import java.util.List;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
public interface ICwBudgetExecutionImportService {
Long insertBatch(CwBudgetExecutionImportBatch batch);
Boolean batchInsertItems(List<CwBudgetExecutionImportItem> items);
Boolean updateBatchSummary(Long batchId, int successCount, int failedCount, BigDecimal totalAmount);
TableDataInfo<CwBudgetExecutionImportBatch> queryBatchPageList(PageQuery pageQuery);
TableDataInfo<CwBudgetExecutionImportItem> queryItemPageList(Long batchId, PageQuery pageQuery);
}
@@ -0,0 +1,89 @@
package org.dromara.cw.service.impl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.dromara.cw.domain.CwBudgetExecutionImportBatch;
import org.dromara.cw.domain.CwBudgetExecutionImportItem;
import org.dromara.cw.mapper.CwBudgetExecutionImportBatchMapper;
import org.dromara.cw.mapper.CwBudgetExecutionImportItemMapper;
import org.dromara.cw.service.ICwBudgetExecutionImportService;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
/**
* 导入服务实现
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class CwBudgetExecutionImportServiceImpl implements ICwBudgetExecutionImportService {
private final CwBudgetExecutionImportBatchMapper batchMapper;
private final CwBudgetExecutionImportItemMapper itemMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Long insertBatch(CwBudgetExecutionImportBatch batch) {
batch.setCreateTime(new Date());
batch.setUpdateTime(new Date());
int r = batchMapper.insert(batch);
if (r > 0) {
return batch.getId();
}
return null;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean batchInsertItems(List<CwBudgetExecutionImportItem> items) {
if (items == null || items.isEmpty()) {
return false;
}
// 避免依赖外部 XML 的 batchInsert 映射,在这里使用 MyBatis-Plus 的通用 insert 批量处理
int count = 0;
for (CwBudgetExecutionImportItem item : items) {
int r = itemMapper.insert(item);
if (r > 0) {
count++;
}
}
return count == items.size();
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateBatchSummary(Long batchId, int successCount, int failedCount, BigDecimal totalAmount) {
CwBudgetExecutionImportBatch update = new CwBudgetExecutionImportBatch();
update.setId(batchId);
update.setSuccessCount(successCount);
update.setFailedCount(failedCount);
update.setTotalCount(successCount + failedCount);
update.setTotalAmount(totalAmount);
update.setUpdateTime(new Date());
int r = batchMapper.updateById(update);
return r > 0;
}
@Override
public org.dromara.common.mybatis.core.page.TableDataInfo<CwBudgetExecutionImportBatch> queryBatchPageList(org.dromara.common.mybatis.core.page.PageQuery pageQuery) {
Page<CwBudgetExecutionImportBatch> page = pageQuery.<CwBudgetExecutionImportBatch>build();
var pageResult = batchMapper.selectPage(page, null);
return org.dromara.common.mybatis.core.page.TableDataInfo.build(pageResult);
}
@Override
public org.dromara.common.mybatis.core.page.TableDataInfo<CwBudgetExecutionImportItem> queryItemPageList(Long batchId, org.dromara.common.mybatis.core.page.PageQuery pageQuery) {
Page<CwBudgetExecutionImportItem> page = pageQuery.<CwBudgetExecutionImportItem>build();
var lqw = com.baomidou.mybatisplus.core.toolkit.Wrappers.<CwBudgetExecutionImportItem>lambdaQuery().eq(CwBudgetExecutionImportItem::getBatchId, batchId);
var pageResult = itemMapper.selectPage(page, lqw);
log.info("查询导入明细: batchId={}, pageNum={}, pageSize={}, result total={}", batchId, page.getCurrent(), page.getSize(), pageResult.getTotal());
return org.dromara.common.mybatis.core.page.TableDataInfo.build(pageResult);
}
}
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.cw.mapper.CwBudgetExecutionImportItemMapper">
<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO cw_budget_execution_import_item
(id, batch_id, row_no, serial_no, purpose, amount, dept_payment_category, func_subject,
fund_nature, indicator_source, payment_type, payee_bank, payee, payee_account,
project_type, project, receipt_date, local_indicator_doc_no, budget_source, indicator_id,
ext_field_1, ext_field_2, ext_field_3, ext_field_4, ext_field_5,
other_fields, raw_data, import_status, error_message, create_by, create_time, update_time, update_by)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.id}, #{item.batchId}, #{item.rowNo}, #{item.serialNo}, #{item.purpose}, #{item.amount},
#{item.deptPaymentCategory}, #{item.funcSubject}, #{item.fundNature}, #{item.indicatorSource},
#{item.paymentType}, #{item.payeeBank}, #{item.payee}, #{item.payeeAccount},
#{item.projectType}, #{item.project}, #{item.receiptDate}, #{item.localIndicatorDocNo},
#{item.budgetSource}, #{item.indicatorId}, #{item.extField1}, #{item.extField2},
#{item.extField3}, #{item.extField4}, #{item.extField5},
#{item.otherFields}, #{item.rawData}, #{item.importStatus}, #{item.errorMessage},
#{item.createBy}, #{item.createTime}, #{item.updateTime}, #{item.updateBy})
</foreach>
</insert>
</mapper>
+71
View File
@@ -0,0 +1,71 @@
剩余乱码字段列表
================================================================================
rs_overseas_application.serial_number
rs_overseas_application.gender
rs_overseas_application.birth_date
rs_overseas_application.photo
rs_overseas_application.id_number
rs_overseas_application.political_status
rs_overseas_application.registered_address
rs_overseas_application.department
rs_overseas_application.professional_title
rs_overseas_application.administrative_level
rs_overseas_application.personnel_category
rs_overseas_application.registration_unit
rs_overseas_application.unit_leader_signature
rs_overseas_application.office_phone
rs_overseas_application.mobile_phone
rs_overseas_application.document_name
rs_overseas_application.document_number
rs_overseas_application.destination_country
rs_overseas_application.exit_reason
rs_overseas_application.visit_relationship
rs_overseas_application.remarks
rs_overseas_application.emergency_contact
rs_overseas_application.signature
rs_overseas_application.stamped_registration_file_path
rs_overseas_application.stamped_registration_file_name
rs_overseas_application.stamped_file_upload_time
rs_overseas_application.stamped_file_upload_status
rs_overseas_application.stamped_file_oss_id
rs_overseas_application.party_retention_file_path
rs_overseas_application.party_retention_file_name
rs_overseas_application.party_retention_upload_time
rs_overseas_application.party_retention_upload_status
rs_overseas_application.party_retention_file_oss_id
rs_overseas_application.attachment_count
rs_overseas_application.approval_status
rs_overseas_application.current_approver_id
rs_overseas_application.approval_level
rs_overseas_application.attachment_ids
rs_overseas_application_attachment.attachment_id
rs_overseas_application_attachment.upload_time
rs_overseas_itinerary_change.change_id
rs_overseas_registration.registration_id
rs_overseas_registration.application_id
rs_overseas_registration.name
rs_overseas_registration.gender
rs_overseas_registration.birth_date
rs_overseas_registration.photo
rs_overseas_registration.id_number
rs_overseas_registration.political_status
rs_overseas_registration.registered_address
rs_overseas_registration.department
rs_overseas_registration.position
rs_overseas_registration.administrative_level
rs_overseas_registration.personnel_category
rs_overseas_registration.status
rs_overseas_registration.registration_time
rs_overseas_registration.remarks
rs_overseas_registration_history.history_id
rs_overseas_registration_history.registration_id
rs_overseas_registration_history.actual_departure_date
rs_overseas_registration_history.actual_return_date
rs_overseas_registration_history.actual_duration_days
rs_overseas_registration_history.actual_destination
rs_overseas_registration_history.completion_status
rs_overseas_registration_history.completion_notes
rs_overseas_registration_history.evidence_files
rs_overseas_registration_history.actual_cost
rs_overseas_registration_history.cost_notes
+54
View File
@@ -0,0 +1,54 @@
import request from '@/utils/request';
/**
* 导入预算执行数据
* @param file File
*/
export const importBudgetExecution = (file: File) => {
const formData = new FormData();
formData.append('file', file);
return request({
url: '/cw/budgetExecutionImport/importData',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
});
};
/**
* 下载导入模板
*/
export const downloadBudgetExecutionTemplate = () => {
return request({
url: '/cw/budgetExecutionImport/importTemplate',
method: 'post',
responseType: 'blob'
});
};
/**
* 查询批次分页
*/
export const listImportBatches = (params?: any) => {
return request({
url: '/cw/budgetExecutionImport/batch/list',
method: 'get',
params
});
};
/**
* 查询明细分页
*/
export const listImportItems = (batchId: number, params?: any) => {
return request({
url: '/cw/budgetExecutionImport/item/list',
method: 'get',
params: { batchId, ...params }
});
};
@@ -0,0 +1,7 @@
<script>
import Comp from './budgetExecutionImport/index.vue';
export default Comp;
</script>
@@ -0,0 +1,254 @@
<template>
<div>
<div class="page-actions" style="margin-bottom: 12px;">
<el-button type="primary" @click="triggerFileInput">导入</el-button>
</div>
<!-- Hidden native file input fallback to ensure upload works reliably -->
<input ref="fileInputRef" type="file" accept=".xlsx,.xls" style="display:none" @change="handleLocalFileChange" />
<import-dialog
v-if="false"
:visible="importDialogVisible"
:uploadUrl="importUrl"
:templateUrl="templateUrl"
@update:visible="importDialogVisible = $event"
@success="onImportSuccess"
/>
<el-card>
<div style="margin-bottom: 8px;">
<span>已选择批次:</span>
<el-select v-model="selectedBatchId" placeholder="请选择批次" @change="onBatchChange" style="min-width: 220px;">
<el-option
v-for="b in batches"
:key="b.id"
:label="b.batchNo + ' (' + b.createTime + ') - 合计:' + (b.totalAmount || 0)"
:value="String(b.id)"
/>
</el-select>
</div>
<!-- debug info removed -->
<div style="margin-bottom: 8px; font-weight: 600;">
总合计金额:{{ totalAmountDisplay }}
</div>
<div class="table-wrapper">
<el-table :data="items" border style="width: 100%; min-width: 1400px;">
<el-table-column prop="rowNo" label="序号" width="80" />
<el-table-column prop="serialNo" label="序号(原表)" width="120" />
<el-table-column prop="purpose" label="用途" min-width="180" />
<el-table-column prop="amount" label="金额(元)" width="140" />
<el-table-column prop="deptPaymentCategory" label="部门支出款项分类" min-width="160" />
<el-table-column prop="funcSubject" label="支出功能分类科目" min-width="160" />
<el-table-column prop="fundNature" label="资金性质" width="120" />
<el-table-column prop="indicatorSource" label="指标来源" min-width="140" />
<el-table-column prop="paymentType" label="支付类型" width="120" />
<el-table-column prop="payeeBank" label="收款人开户银行" min-width="180" />
<el-table-column prop="payee" label="收款人" width="160" />
<el-table-column prop="payeeAccount" label="收款账号" width="180" />
<el-table-column prop="projectType" label="项目类型" min-width="160" />
<el-table-column prop="project" label="项目" min-width="200" />
<el-table-column prop="receiptDate" label="回单时间" width="140" />
<el-table-column prop="localIndicatorDocNo" label="本级指标文号" min-width="160" />
<el-table-column prop="budgetSource" label="预算来源" min-width="140" />
<el-table-column prop="indicatorId" label="指标ID" width="160" />
<el-table-column prop="extField1" label="冗余字段1" width="140" />
<el-table-column prop="extField2" label="冗余字段2" width="140" />
<el-table-column prop="extField3" label="冗余字段3" width="140" />
<el-table-column prop="extField4" label="冗余字段4" width="140" />
<el-table-column prop="extField5" label="冗余字段5" width="140" />
<el-table-column prop="otherFields" label="扩展字段" min-width="200" />
</el-table>
</div>
<div style="margin-top: 12px; text-align: right;">
<el-pagination
background
layout="prev, pager, next, jumper"
:total="itemsTotal"
:page-size="pageSize"
:current-page.sync="pageNum"
@current-change="fetchItems"
/>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, computed, watch, nextTick } from 'vue';
import ImportDialog from '@/components/ImportDialog/index.vue';
import { listImportBatches, listImportItems, downloadBudgetExecutionTemplate } from '@/api/cw/budgetExecutionImport';
import { ElMessage } from 'element-plus';
import { importBudgetExecution } from '@/api/cw/budgetExecutionImport';
const importDialogVisible = ref(false);
const importUrl = import.meta.env.VITE_APP_BASE_API + '/cw/budgetExecutionImport/importData';
const templateUrl = import.meta.env.VITE_APP_BASE_API + '/cw/budgetExecutionImport/importTemplate';
const batches = ref<any[]>([]);
const selectedBatchId = ref<string | null>(null);
const items = ref<any[]>([]);
const itemsTotal = ref(0);
const pageNum = ref(1);
const pageSize = ref(20);
const totalAmountDisplay = computed(() => {
const b = batches.value.find((x) => String(x.id) === selectedBatchId.value);
return b ? (b.totalAmount || 0) : 0;
});
const importUrlLocal = importUrl;
const openImport = () => {
importDialogVisible.value = true;
};
const downloadTemplate = async () => {
try {
await downloadBudgetExecutionTemplate();
// browser will handle download
} catch (e) {
ElMessage.error('模板下载失败');
}
};
const fileInputRef = ref<HTMLInputElement | null>(null);
const triggerFileInput = () => {
fileInputRef.value?.click();
};
const handleLocalFileChange = async (e: Event) => {
const input = e.target as HTMLInputElement;
const file = input.files?.[0];
if (!file) {
ElMessage.warning('请先选择文件');
return;
}
// simple client-side validation
const allowedTypes = ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-excel'];
if (file.size / 1024 / 1024 > 10) {
ElMessage.error('文件大小不能超过 10MB');
input.value = '';
return;
}
const formData = new FormData();
formData.append('file', file);
try {
// 使用对象形式调用以兼容不同环境下的实现细节,避免 createMessage 内部错误
ElMessage({ message: '正在导入,请稍候...', type: 'info', duration: 0 });
const res: any = await importBudgetExecution(file);
ElMessage.closeAll();
if (res && res.code === 200) {
ElMessage.success('导入成功');
fetchBatches();
if (selectedBatchId.value) {
fetchItems();
}
} else {
ElMessage.error(res?.msg || '导入失败');
}
} catch (err) {
ElMessage.closeAll();
console.error(err);
ElMessage.error('导入失败,请检查文件格式或网络');
} finally {
input.value = '';
}
};
const fetchBatches = async () => {
try {
const res: any = await listImportBatches({ pageNum: 1, pageSize: 100 });
console.debug('budgetExecutionImport - batch list raw res:', res);
let rows = [];
if (res) {
// response can be { code, msg, data: { rows } } or { total, rows, code }
rows = res.data?.rows ?? res.rows ?? res.data ?? [];
}
batches.value = Array.isArray(rows) ? rows : [];
console.debug('budgetExecutionImport - fetched batches:', batches.value);
} catch (e) {
console.error('fetchBatches error', e);
batches.value = [];
}
};
// 监听 batches 变化:如果未选择批次则自动选中第一个并加载明细
watch(
() => batches.value,
async (val) => {
if (val && val.length > 0 && !selectedBatchId.value) {
// 等待 DOM 更新再触发
selectedBatchId.value = String(val[0].id);
await nextTick();
console.debug('budgetExecutionImport - auto selected batchId:', selectedBatchId.value);
fetchItems(1);
}
},
{ immediate: true }
);
// no debugInfo in production
const fetchItems = async (page = pageNum.value) => {
try {
if (!selectedBatchId.value) {
items.value = [];
itemsTotal.value = 0;
return;
}
// Important: pass batchId as string to avoid JS Number precision loss for long IDs
const res: any = await listImportItems(selectedBatchId.value as string, { pageNum: page, pageSize: pageSize.value });
console.debug('budgetExecutionImport - item list raw res:', res);
let rows: any[] = [];
let totalCount = 0;
if (res) {
// support multiple formats: { code, data:{ rows, total } } or { total, rows, code }
rows = res.data?.rows ?? res.rows ?? res.data ?? [];
totalCount = res.data?.total ?? res.total ?? (Array.isArray(rows) ? rows.length : 0);
}
items.value = Array.isArray(rows) ? rows : [];
itemsTotal.value = totalCount || 0;
} catch (e) {
console.error('fetchItems error', e);
items.value = [];
itemsTotal.value = 0;
}
};
const onBatchChange = () => {
pageNum.value = 1;
fetchItems(1);
};
const onImportSuccess = () => {
importDialogVisible.value = false;
fetchBatches();
if (selectedBatchId.value) {
fetchItems();
}
ElMessage.success('导入成功');
};
onMounted(() => {
fetchBatches();
});
defineExpose({});
</script>
<style scoped>
.page-actions {
display: flex;
gap: 8px;
}
.table-wrapper {
overflow-x: auto;
}
</style>
+1 -1
View File
@@ -1,5 +1,5 @@
import vue from '@vitejs/plugin-vue';
import vueDevTools from 'vite-plugin-vue-devtools';
import vueDevTools from 'vite-plugin-vue-devtools'; // 临时禁用以解决localStorage兼容性问题
import createUnoCss from './unocss';
import createAutoImport from './auto-import';