1
0

8 Коммитууд cf68dbf411 ... 93a328fad6

Эзэн SHA1 Мессеж Огноо
  alphaH 93a328fad6 fix: 导出列名调整 - 订单号改支付宝订单号、创建时间改转账时间 2 долоо хоног өмнө
  alphaH 6ff32c48c1 fix: 时间筛选结束日期补23:59:59含当天全天(修复漏掉结束日数据); 导出新增收款方账号列、去掉商户订单号 2 долоо хоног өмнө
  alphaH 4f9473ed09 feat: 转账记录下载报表按钮接入导出接口(跟随表单筛选),清理旧导出死代码 2 долоо хоног өмнө
  alphaH 513395fed5 feat: 转账记录筛选表单增加时间范围选择器,控件加宽,创建时间列格式化为东8 2 долоо хоног өмнө
  alphaH f08c99ef3a feat: 转账导出API参数对齐表单筛选(6条件全可选),transferList支持时间筛选 2 долоо хоног өмнө
  alphaH d691dbd171 feat: 转账导出接口对齐表单筛选条件,导出列加备注/失败原因,时间格式东8,类型翻译补齐 2 долоо хоног өмнө
  alphaH e190048604 feat: 转账列表支持时间范围筛选 2 долоо хоног өмнө
  alphaH 60e796dc11 fix: 批量转账模板字段必填标注与页面校验对齐(备注必填/对公省市支行联行号必填) 2 долоо хоног өмнө

+ 7 - 2
frontend/src/api/module_payment/account.ts

@@ -188,6 +188,8 @@ export const AccountAPI = {
     status?: string;
     tenant_id?: number;
     enterprise_id?: string;
+    start_time?: string;
+    end_time?: string;
   }) {
     return request<ApiResponse<TransferListResp>>({
       url: `${API_PATH}/transfer`,
@@ -228,9 +230,12 @@ export const AccountAPI = {
   },
 
   exportTransferReport(params: {
-    start_time: string;
-    end_time: string;
+    out_biz_no?: string;
+    status?: string;
+    tenant_id?: number;
     enterprise_id?: string;
+    start_time?: string;
+    end_time?: string;
   }) {
     return request({
       url: `${API_PATH}/transfer/export`,

+ 70 - 166
frontend/src/views/module_payment/account/index.vue

@@ -285,23 +285,25 @@
             <template #header>
               <div class="card-header">
                 <span>转账记录</span>
-                <!-- <el-button
+                <el-button
                   type="primary"
                   icon="Download"
                   class="ml-auto"
-                  @click="showDownloadMenu = true"
+                  :loading="transferExportLoading"
+                  v-hasPerm="['module_payment:account:transfer:list']"
+                  @click="handleExportTransfer"
                 >
                   下载报表
-                </el-button> -->
+                </el-button>
               </div>
             </template>
             <div class="mb-4">
               <el-form :inline="true" :model="transferSearchForm">
                 <el-form-item label="订单号">
-                  <el-input v-model="transferSearchForm.out_biz_no" placeholder="请输入订单号" clearable />
+                  <el-input v-model="transferSearchForm.out_biz_no" placeholder="请输入订单号" clearable style="width: 220px" />
                 </el-form-item>
                 <el-form-item label="状态">
-                  <el-select v-model="transferSearchForm.status" placeholder="请选择状态" clearable>
+                  <el-select v-model="transferSearchForm.status" placeholder="请选择状态" clearable style="width: 180px">
                     <el-option label="处理中" value="DEALING" />
                     <el-option label="成功" value="SUCCESS" />
                     <el-option label="失败" value="FAIL" />
@@ -309,15 +311,27 @@
                   </el-select>
                 </el-form-item>
                 <el-form-item v-if="is_platform_user" label="租户">
-                  <el-select v-model="transferSearchForm.tenant_id" placeholder="选择租户" clearable filterable>
+                  <el-select v-model="transferSearchForm.tenant_id" placeholder="选择租户" clearable filterable style="width: 180px">
                     <el-option v-for="t in allTenantData" :key="t.id" :label="t.name" :value="t.id" />
                   </el-select>
                 </el-form-item>
                 <el-form-item label="企业">
-                  <el-select v-model="transferSearchForm.enterprise_id" placeholder="选择企业" clearable filterable>
+                  <el-select v-model="transferSearchForm.enterprise_id" placeholder="选择企业" clearable filterable style="width: 180px">
                     <el-option v-for="e in enterpriseStore.getEnterpriseList" :key="e.enterprise_id" :label="e.name" :value="e.enterprise_id" />
                   </el-select>
                 </el-form-item>
+                <el-form-item label="时间范围">
+                  <el-date-picker
+                    v-model="transferSearchForm.dateRange"
+                    type="daterange"
+                    range-separator="至"
+                    start-placeholder="开始日期"
+                    end-placeholder="结束日期"
+                    value-format="YYYY-MM-DD"
+                    style="width: 280px"
+                    @change="handleTransferDateChange"
+                  />
+                </el-form-item>
                 <el-form-item>
                   <el-button type="primary" @click="handleTransferSearch">查询</el-button>
                   <el-button @click="handleTransferSearchReset">重置</el-button>
@@ -350,7 +364,11 @@
                 </template>
               </el-table-column>
               <el-table-column prop="order_title" label="转账标题" min-width="150" />
-              <el-table-column prop="created_time" label="创建时间" min-width="160" />
+              <el-table-column prop="created_time" label="创建时间" min-width="160">
+                <template #default="{ row }">
+                  {{ row.created_time ? dayjs(row.created_time).format("YYYY-MM-DD HH:mm:ss") : "" }}
+                </template>
+              </el-table-column>
               <el-table-column label="操作" width="160" fixed="right">
                 <template #default="{ row }">
                   <el-button type="primary" link @click="handleViewTransferDetail(row.out_biz_no)">
@@ -725,43 +743,6 @@
         <el-button type="primary" :loading="withdrawLoading" @click="handleWithdraw">确认提现</el-button>
       </template>
     </el-dialog>
-
-    <!-- 下载报表弹窗 -->
-    <el-dialog title="下载转账报表" v-model="showDownloadMenu" width="500px" :close-on-click-modal="false">
-      <div class="download-options">
-        <div class="option-group">
-          <p class="option-title">快捷时间范围</p>
-          <div class="option-buttons">
-            <el-button
-              v-for="option in quickOptions"
-              :key="option.value"
-              :type="selectedQuickOption === option.value ? 'primary' : ''"
-              @click="selectedQuickOption = option.value"
-            >
-              {{ option.label }}
-            </el-button>
-          </div>
-        </div>
-        <div class="option-group mt-4">
-          <p class="option-title">自定义时间范围</p>
-          <el-date-picker
-            v-model="customDateRange"
-            type="daterange"
-            range-separator="至"
-            start-placeholder="开始日期"
-            end-placeholder="结束日期"
-            class="w-full"
-            @change="handleCustomDateChange"
-          />
-        </div>
-      </div>
-      <template #footer>
-        <el-button @click="showDownloadMenu = false">取消</el-button>
-        <el-button type="primary" @click="handleDownload" :loading="downloadLoading">
-          下载报表
-        </el-button>
-      </template>
-    </el-dialog>
   </div>
 </template>
 
@@ -783,6 +764,7 @@ import { Refresh, Loading, Plus, QuestionFilled } from "@element-plus/icons-vue"
 import { ElMessage, ElMessageBox } from "element-plus";
 import type { FormInstance, FormRules } from "element-plus";
 import * as ExcelJS from "exceljs";
+import dayjs from "dayjs";
 
 const enterpriseStore = useEnterpriseStore();
 const userStore = useUserStore();
@@ -799,18 +781,7 @@ const depositLoading = ref(false);
 const transferLoading = ref(false);
 const consumeLoading = ref(false);
 const transferListLoading = ref(false);
-const downloadLoading = ref(false);
-
-const showDownloadMenu = ref(false);
-const selectedQuickOption = ref("today");
-const customDateRange = ref<Date[] | null>(null);
-
-const quickOptions = [
-  { label: "当天", value: "today" },
-  { label: "近3天", value: "3days" },
-  { label: "近15天", value: "15days" },
-  { label: "近30天", value: "30days" },
-];
+const transferExportLoading = ref(false);
 
 const authorizeFormRef = ref<FormInstance>();
 const createFormRef = ref<FormInstance>();
@@ -1160,6 +1131,9 @@ const transferSearchForm = reactive({
   status: "",
   tenant_id: undefined as number | undefined,
   enterprise_id: undefined as string | undefined,
+  dateRange: null as string[] | null,
+  start_time: undefined as string | undefined,
+  end_time: undefined as string | undefined,
 });
 
 const allTenantData = ref<TenantTable[]>([]);
@@ -1249,18 +1223,18 @@ function downloadTemplate() {
 
   // 设置表头
   worksheet.columns = [
-    { header: "转账金额", key: "amount", width: 15 },
-    { header: "转账标题", key: "order_title", width: 20 },
-    { header: "收款方姓名", key: "payee_name", width: 15 },
-    { header: "收款方账号类型", key: "payee_type", width: 15 },
-    { header: "收款方账号", key: "payee_account", width: 30 },
-    { header: "银行账户类型(1:公司账户,2:个人账户)", key: "bank_type", width: 30 },
-    { header: "银行名称(公账户必填)", key: "bank_name", width: 20 },
-    { header: "开户省份(必填)", key: "bank_province", width: 15 },
-    { header: "开户城市(必填)", key: "bank_city", width: 15 },
-    { header: "支行名称(必填)", key: "bank_branch", width: 20 },
-    { header: "联行号(必填)", key: "bank_code", width: 15 },
-    { header: "备注(必填)", key: "remark", width: 20 },
+    { header: "转账金额(必填)", key: "amount", width: 15 },
+    { header: "转账标题(必填)", key: "order_title", width: 20 },
+    { header: "收款方姓名(必填)", key: "payee_name", width: 15 },
+    { header: "收款方账号类型(必填:支付宝/银行卡)", key: "payee_type", width: 20 },
+    { header: "收款方账号(必填)", key: "payee_account", width: 30 },
+    { header: "银行账户类型(1:公司账户,2:个人账户;银行卡必填)", key: "bank_type", width: 30 },
+    { header: "银行名称(公账户必填)", key: "bank_name", width: 20 },
+    { header: "开户省份(对公账户必填)", key: "bank_province", width: 15 },
+    { header: "开户城市(对公账户必填)", key: "bank_city", width: 15 },
+    { header: "支行名称(对公账户必填)", key: "bank_branch", width: 20 },
+    { header: "联行号(对公账户必填)", key: "bank_code", width: 15 },
+    { header: "备注(必填)", key: "remark", width: 20 },
   ];
 
   // 添加示例数据
@@ -1759,11 +1733,24 @@ async function handleTransferSearch() {
   await fetchTransferList();
 }
 
+function handleTransferDateChange() {
+  if (transferSearchForm.dateRange && transferSearchForm.dateRange.length === 2) {
+    transferSearchForm.start_time = (transferSearchForm.dateRange as any)[0];
+    transferSearchForm.end_time = (transferSearchForm.dateRange as any)[1];
+  } else {
+    transferSearchForm.start_time = undefined;
+    transferSearchForm.end_time = undefined;
+  }
+}
+
 function handleTransferSearchReset() {
   transferSearchForm.out_biz_no = "";
   transferSearchForm.status = "";
   transferSearchForm.tenant_id = undefined;
   transferSearchForm.enterprise_id = undefined;
+  transferSearchForm.dateRange = null;
+  transferSearchForm.start_time = undefined;
+  transferSearchForm.end_time = undefined;
   handleTransferSearch();
 }
 
@@ -1777,6 +1764,8 @@ async function fetchTransferList() {
       status: transferSearchForm.status || undefined,
       tenant_id: transferSearchForm.tenant_id || undefined,
       enterprise_id: transferSearchForm.enterprise_id || undefined,
+      start_time: transferSearchForm.start_time || undefined,
+      end_time: transferSearchForm.end_time || undefined,
     });
     transferList.value = res.data.data?.items || res.data.data?.list || [];
     transferPage.total = res.data.data.total || 0;
@@ -2046,101 +2035,33 @@ onMounted(async() => {
   }
 });
 
-async function handleDownload() {
-  downloadLoading.value = true;
+async function handleExportTransfer() {
+  transferExportLoading.value = true;
   try {
-    let start_time: string;
-    let end_time: string;
-
-    if (selectedQuickOption.value !== "custom") {
-      const range = calculateTimeRange(selectedQuickOption.value);
-      start_time = range.start_time;
-      end_time = range.end_time;
-    } else {
-      if (!customDateRange.value || customDateRange.value.length !== 2) {
-        ElMessage.warning("请选择时间范围");
-        return;
-      }
-      const [start, end] = customDateRange.value;
-      start_time = formatDateTime(start, "start");
-      end_time = formatDateTime(end, "end");
-    }
-
     const res = await AccountAPI.exportTransferReport({
-      start_time,
-      end_time,
-      enterprise_id: currentEnterpriseId.value || undefined,
+      out_biz_no: transferSearchForm.out_biz_no || undefined,
+      status: transferSearchForm.status || undefined,
+      tenant_id: transferSearchForm.tenant_id || undefined,
+      enterprise_id: transferSearchForm.enterprise_id || undefined,
+      start_time: transferSearchForm.start_time || undefined,
+      end_time: transferSearchForm.end_time || undefined,
     });
-
     const blob = new Blob([res.data], {
       type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
     });
     const url = window.URL.createObjectURL(blob);
     const a = document.createElement("a");
     a.href = url;
-    a.download = `转账报表_${start_time}_${end_time}.xlsx`;
+    a.download = "转账记录.xlsx";
     document.body.appendChild(a);
     a.click();
     document.body.removeChild(a);
     window.URL.revokeObjectURL(url);
-
-    ElMessage.success("报表下载成功");
-    showDownloadMenu.value = false;
-    selectedQuickOption.value = "today";
-    customDateRange.value = null;
+    ElMessage.success("导出成功");
   } catch (error) {
-    ElMessage.error("下载失败,请稍后重试");
+    ElMessage.error("导出失败,请稍后重试");
   } finally {
-    downloadLoading.value = false;
-  }
-}
-
-function calculateTimeRange(type: string): { start_time: string; end_time: string } {
-  const now = new Date();
-  const start = new Date();
-
-  switch (type) {
-    case "today":
-      start.setHours(0, 0, 0, 0);
-      break;
-    case "3days":
-      start.setDate(now.getDate() - 3);
-      start.setHours(0, 0, 0, 0);
-      break;
-    case "15days":
-      start.setDate(now.getDate() - 15);
-      start.setHours(0, 0, 0, 0);
-      break;
-    case "30days":
-      start.setDate(now.getDate() - 30);
-      start.setHours(0, 0, 0, 0);
-      break;
-  }
-
-  const end = new Date(now);
-  end.setHours(23, 59, 59, 999);
-
-  return {
-    start_time: formatDateTime(start),
-    end_time: formatDateTime(end),
-  };
-}
-
-function formatDateTime(date: Date, type: string = "start"): string {
-  const year = date.getFullYear();
-  const month = String(date.getMonth() + 1).padStart(2, "0");
-  const day = String(date.getDate()).padStart(2, "0");
-  
-  if (type === "start") {
-    return `${year}-${month}-${day} 00:00:00`;
-  } else {
-    return `${year}-${month}-${day} 23:59:59`;
-  }
-}
-
-function handleCustomDateChange() {
-  if (customDateRange.value && customDateRange.value.length === 2) {
-    selectedQuickOption.value = "custom";
+    transferExportLoading.value = false;
   }
 }
 
@@ -2235,21 +2156,4 @@ function handleCustomDateChange() {
     }
   }
 }
-
-.download-options {
-  .option-group {
-    .option-title {
-      font-size: 14px;
-      font-weight: 500;
-      color: #606266;
-      margin-bottom: 12px;
-    }
-
-    .option-buttons {
-      display: flex;
-      flex-wrap: wrap;
-      gap: 8px;
-    }
-  }
-}
 </style>

+ 11 - 6
java/src/main/java/com/payment/platform/module/payment/account/controller/AccountController.java

@@ -79,8 +79,10 @@ public class AccountController {
             @RequestParam(name = "page_no", defaultValue = "1") int pageNo, @RequestParam(name = "page_size", defaultValue = "20") int pageSize,
             @RequestParam(name = "out_biz_no", required = false) String outBizNo, @RequestParam(required = false) String status,
             @RequestParam(name = "tenant_id", required = false) Long tenantId,
-            @RequestParam(name = "enterprise_id", required = false) String enterpriseId) {
-        return Result.ok(accountService.transferList(pageNo, pageSize, outBizNo, status, tenantId, enterpriseId));
+            @RequestParam(name = "enterprise_id", required = false) String enterpriseId,
+            @RequestParam(name = "start_time", required = false) String startTime,
+            @RequestParam(name = "end_time", required = false) String endTime) {
+        return Result.ok(accountService.transferList(pageNo, pageSize, outBizNo, status, tenantId, enterpriseId, startTime, endTime));
     }
 
     @PreAuthorize("@perm.hasAny('module_payment:account:transfer:list')")
@@ -237,14 +239,17 @@ public class AccountController {
     @PreAuthorize("@perm.hasAny('module_payment:account:transfer:list')")
     @GetMapping("/transfer/export")
     public void transferExport(
-            @RequestParam(name = "start_time") String startTime,
-            @RequestParam(name = "end_time") String endTime,
+            @RequestParam(name = "out_biz_no", required = false) String outBizNo,
+            @RequestParam(name = "status", required = false) String status,
+            @RequestParam(name = "tenant_id", required = false) Long tenantId,
             @RequestParam(name = "enterprise_id", required = false) String enterpriseId,
+            @RequestParam(name = "start_time", required = false) String startTime,
+            @RequestParam(name = "end_time", required = false) String endTime,
             HttpServletResponse response) throws IOException {
-        byte[] bytes = accountService.transferExport(startTime, endTime, enterpriseId);
+        byte[] bytes = accountService.transferExport(outBizNo, status, tenantId, enterpriseId, startTime, endTime);
         response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
         response.setHeader("Content-Disposition",
-                "attachment; filename=transfer_report_" + startTime + "_" + endTime + ".xlsx");
+                "attachment; filename=transfer_report.xlsx");
         response.getOutputStream().write(bytes);
     }
 

+ 37 - 20
java/src/main/java/com/payment/platform/module/payment/account/service/AccountService.java

@@ -37,6 +37,7 @@ import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.OffsetDateTime;
+import java.time.ZoneId;
 import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
 import org.springframework.data.redis.core.RedisTemplate;
@@ -188,12 +189,14 @@ public class AccountService {
     }
 
     public PageResult<TransferVO> transferList(int pageNo, int pageSize, String outBizNo, String status,
-            Long tenantId, String enterpriseId) {
+            Long tenantId, String enterpriseId, String startTime, String endTime) {
         var w = new LambdaQueryWrapper<TransferEntity>().orderByDesc(TransferEntity::getId);
         if (outBizNo != null && !outBizNo.isBlank()) w.eq(TransferEntity::getOutBizNo, outBizNo);
         if (status != null && !status.isBlank()) w.eq(TransferEntity::getStatus, status);
         if (tenantId != null) w.eq(TransferEntity::getTenantId, tenantId);
         if (enterpriseId != null && !enterpriseId.isBlank()) w.eq(TransferEntity::getEnterpriseId, enterpriseId);
+        if (startTime != null && !startTime.isBlank()) w.ge(TransferEntity::getCreatedTime, parseDateTime(startTime));
+        if (endTime != null && !endTime.isBlank()) w.le(TransferEntity::getCreatedTime, parseDateTime(endTime, true));
         var p = new Page<TransferEntity>(pageNo, pageSize);
         var r = transferMapper.selectPage(p, w);
         List<TransferVO> items = r.getRecords().stream().map(this::transferVO).toList();
@@ -210,7 +213,7 @@ public class AccountService {
         if (consumeType != null && !consumeType.isBlank()) w.eq(PayBillEntity::getConsumeType, consumeType);
         if (status != null && !status.isBlank()) w.eq(PayBillEntity::getStatus, status);
         if (startTime != null && !startTime.isBlank()) w.ge(PayBillEntity::getGmtBizCreate, parseDateTime(startTime));
-        if (endTime != null && !endTime.isBlank()) w.le(PayBillEntity::getGmtBizCreate, parseDateTime(endTime));
+        if (endTime != null && !endTime.isBlank()) w.le(PayBillEntity::getGmtBizCreate, parseDateTime(endTime, true));
         var p = new Page<PayBillEntity>(pageNo, pageSize);
         var r = payBillMapper.selectPage(p, w);
         List<ConsumeVO> items = r.getRecords().stream().map(this::consumeVO).toList();
@@ -292,17 +295,16 @@ public class AccountService {
      *
      * Python 对应: transfer_export_service (L753-821) 使用 ExcelUtil 导出 Excel
      */
-    public byte[] transferExport(String startTime, String endTime, String enterpriseId) {
+    public byte[] transferExport(String outBizNo, String status, Long tenantId, String enterpriseId,
+            String startTime, String endTime) {
         try {
-            OffsetDateTime start = parseDateTime(startTime);
-            OffsetDateTime end = parseDateTime(endTime);
-
             LambdaQueryWrapper<TransferEntity> w = new LambdaQueryWrapper<>();
-            w.ge(TransferEntity::getCreatedTime, start)
-             .le(TransferEntity::getCreatedTime, end);
-            if (enterpriseId != null && !enterpriseId.isBlank()) {
-                w.eq(TransferEntity::getEnterpriseId, enterpriseId);
-            }
+            if (outBizNo != null && !outBizNo.isBlank()) w.eq(TransferEntity::getOutBizNo, outBizNo);
+            if (status != null && !status.isBlank()) w.eq(TransferEntity::getStatus, status);
+            if (tenantId != null) w.eq(TransferEntity::getTenantId, tenantId);
+            if (enterpriseId != null && !enterpriseId.isBlank()) w.eq(TransferEntity::getEnterpriseId, enterpriseId);
+            if (startTime != null && !startTime.isBlank()) w.ge(TransferEntity::getCreatedTime, parseDateTime(startTime));
+            if (endTime != null && !endTime.isBlank()) w.le(TransferEntity::getCreatedTime, parseDateTime(endTime, true));
             w.orderByDesc(TransferEntity::getId);
 
             List<TransferEntity> records = transferMapper.selectList(w);
@@ -314,53 +316,64 @@ public class AccountService {
                     "REFUND", "退票");
 
             Map<String, String> payeeTypeMap = Map.of(
+                    "ALIPAY_USER_ID", "支付宝账户",
+                    "ALIPAY_LOGON_ID", "支付宝账户",
                     "ALIPAY_ACCOUNT", "支付宝账户",
+                    "BANKCARD_ACCOUNT", "银行卡",
                     "BANK_CARD", "银行卡");
 
             ObjectMapper om = new ObjectMapper();
             List<Map<String, Object>> listData = new ArrayList<>();
+            DateTimeFormatter timeFmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 
             int seq = 1;
             for (TransferEntity r : records) {
                 Map<String, Object> row = new LinkedHashMap<>();
                 row.put("序号", seq++);
-                row.put("订单号", r.getOutBizNo() != null ? r.getOutBizNo() : "");
-                row.put("商户订单号", r.getOrderNo() != null ? r.getOrderNo() : "");
+                row.put("支付宝订单号", r.getOutBizNo() != null ? r.getOutBizNo() : "");
                 row.put("金额(元)", r.getAmount() != null ? r.getAmount().toPlainString() : "0");
                 row.put("状态", statusMap.getOrDefault(r.getStatus(), r.getStatus() != null ? r.getStatus() : ""));
                 row.put("转账标题", r.getOrderTitle() != null ? r.getOrderTitle() : "");
-                row.put("创建时间", r.getCreatedTime() != null ? r.getCreatedTime().toString() : "");
+                row.put("转账时间", r.getCreatedTime() != null
+                        ? r.getCreatedTime().atZoneSameInstant(ZoneId.of("Asia/Shanghai")).format(timeFmt) : "");
 
                 // 解析 payeeInfo JSON
                 String name = "";
                 String identityType = "";
+                String identity = "";
                 if (r.getPayeeInfo() != null && !r.getPayeeInfo().isBlank()) {
                     try {
                         @SuppressWarnings("unchecked")
                         Map<String, Object> payeeInfo = om.readValue(r.getPayeeInfo(), Map.class);
                         name = String.valueOf(payeeInfo.getOrDefault("name", ""));
                         identityType = String.valueOf(payeeInfo.getOrDefault("identity_type", ""));
+                        identity = String.valueOf(payeeInfo.getOrDefault("identity", ""));
                     } catch (Exception ignored) {
                         // ignore invalid JSON
                     }
                 }
                 row.put("收款方姓名", name);
+                row.put("收款方账号", identity);
                 row.put("收款方类型", payeeTypeMap.getOrDefault(identityType, identityType));
+                row.put("备注", r.getRemark() != null ? r.getRemark() : "");
+                row.put("失败原因", r.getErrorMsg() != null ? r.getErrorMsg() : "");
                 listData.add(row);
             }
 
             Map<String, String> mappingDict = new LinkedHashMap<>();
             mappingDict.put("序号", "序号");
-            mappingDict.put("订单号", "订单号");
-            mappingDict.put("商户订单号", "商户订单号");
+            mappingDict.put("支付宝订单号", "支付宝订单号");
             mappingDict.put("金额(元)", "金额(元)");
             mappingDict.put("收款方姓名", "收款方姓名");
+            mappingDict.put("收款方账号", "收款方账号");
             mappingDict.put("收款方类型", "收款方类型");
             mappingDict.put("状态", "状态");
             mappingDict.put("转账标题", "转账标题");
-            mappingDict.put("创建时间", "创建时间");
+            mappingDict.put("转账时间", "转账时间");
+            mappingDict.put("备注", "备注");
+            mappingDict.put("失败原因", "失败原因");
 
-            log.info("导出转账记录报表: {} -> {}, {} 条", startTime, endTime, listData.size());
+            log.info("导出转账记录报表: {} 条", listData.size());
             return ExcelUtil.exportToExcel(listData, mappingDict);
         } catch (Exception e) {
             log.error("导出转账记录失败", e);
@@ -369,9 +382,13 @@ public class AccountService {
     }
 
     private OffsetDateTime parseDateTime(String dt) {
+        return parseDateTime(dt, false);
+    }
+
+    private OffsetDateTime parseDateTime(String dt, boolean endOfDay) {
         if (dt == null || dt.isBlank()) return OffsetDateTime.now().minusDays(30);
-        // 补全时间部分
-        if (dt.length() == 10) dt += " 00:00:00";
+        // 补全时间部分: 起始日期按当天零点, 结束日期按当天 23:59:59(含当天全天)
+        if (dt.length() == 10) dt += endOfDay ? " 23:59:59" : " 00:00:00";
         try {
             return LocalDateTime.parse(dt, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
                     .atOffset(ZoneOffset.ofHours(8));