From 9a69e37bc4385220152652fc5474f65edaef8160 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A2=81=E5=AE=87=E5=A5=87?= Date: Sun, 17 Aug 2025 03:37:46 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/xkt/StoreOrderController.java | 54 ++- .../xkt/vo/order/StoreOrderPageQueryVO.java | 105 +++++ .../xkt/vo/order/StoreOrderQueryVO.java | 7 +- .../META-INF/excel/order_export.xlsx | Bin 0 -> 10401 bytes .../ruoyi/common/utils/poi/ExcelColumn.java | 25 ++ .../common/utils/poi/ExcelMergeRegion.java | 23 + .../common/utils/poi/ExcelTemplateUtil.java | 416 ++++++++++++++++++ .../xkt/dto/order/StoreOrderExportDTO.java | 38 ++ .../ruoyi/xkt/service/IStoreOrderService.java | 15 +- .../service/impl/StoreOrderServiceImpl.java | 74 +++- 10 files changed, 738 insertions(+), 19 deletions(-) create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/order/StoreOrderPageQueryVO.java create mode 100644 ruoyi-admin/src/main/resources/META-INF/excel/order_export.xlsx create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelColumn.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelMergeRegion.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelTemplateUtil.java create mode 100644 xkt/src/main/java/com/ruoyi/xkt/dto/order/StoreOrderExportDTO.java diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/StoreOrderController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/StoreOrderController.java index 026f1f6c1..fbc74d681 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/StoreOrderController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/StoreOrderController.java @@ -4,8 +4,10 @@ import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson2.JSON; import com.github.pagehelper.Page; +import com.github.pagehelper.PageHelper; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.core.controller.XktBaseController; @@ -17,6 +19,7 @@ import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.desensitization.DesensitizationUtil; +import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.framework.notice.fs.FsNotice; import com.ruoyi.web.controller.xkt.vo.IdVO; import com.ruoyi.web.controller.xkt.vo.express.ExpressShippingLabelVO; @@ -29,12 +32,14 @@ import com.ruoyi.xkt.dto.express.ExpressInterceptReqDTO; import com.ruoyi.xkt.dto.express.ExpressShippingLabelDTO; import com.ruoyi.xkt.dto.order.*; import com.ruoyi.xkt.enums.EExpressStatus; +import com.ruoyi.xkt.enums.EOrderStatus; import com.ruoyi.xkt.enums.EPayChannel; import com.ruoyi.xkt.enums.EPayPage; import com.ruoyi.xkt.manager.ExpressManager; import com.ruoyi.xkt.manager.PaymentManager; import com.ruoyi.xkt.service.IExpressService; import com.ruoyi.xkt.service.IStoreOrderService; +import com.ruoyi.xkt.service.IStoreService; import io.jsonwebtoken.lang.Assert; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -44,7 +49,10 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; +import java.io.IOException; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -63,6 +71,8 @@ public class StoreOrderController extends XktBaseController { @Autowired private IExpressService expressService; @Autowired + private IStoreService storeService; + @Autowired private List paymentManagers; @Autowired private FsNotice fsNotice; @@ -162,7 +172,7 @@ public class StoreOrderController extends XktBaseController { @ApiOperation(value = "订单分页查询") @PostMapping("/page") @ResponseHeader - public R> page(@Validated @RequestBody StoreOrderQueryVO vo) { + public R> page(@Validated @RequestBody StoreOrderPageQueryVO vo) { StoreOrderQueryDTO queryDTO = BeanUtil.toBean(vo, StoreOrderQueryDTO.class); if (1 == vo.getSrcPage()) { queryDTO.setOrderUserId(SecurityUtils.getUserId()); @@ -174,12 +184,50 @@ public class StoreOrderController extends XktBaseController { } queryDTO.setStoreId(storeId); } - Page pageDTO = storeOrderService.page(queryDTO); + Page pageDTO = PageHelper.startPage(queryDTO.getPageNum(), queryDTO.getPageSize()); + storeOrderService.listPageItem(queryDTO); return success(PageVO.of(pageDTO, StoreOrderPageItemVO.class)); } + @PreAuthorize("@ss.hasAnyRoles('store')||@ss.hasSupplierSubRole()") + @ApiOperation(value = "导出订单") + @PostMapping("/export") + @ResponseHeader + public void export(@Validated @RequestBody StoreOrderQueryVO vo, HttpServletResponse response) { + StoreOrderQueryDTO queryDTO = BeanUtil.toBean(vo, StoreOrderQueryDTO.class); + Long storeId = SecurityUtils.getStoreId(); + queryDTO.setStoreId(storeId); + String storeName = storeService.getStoreNameByIds(Collections.singletonList(storeId)).get(storeId); + String title = StrUtil.emptyIfNull(storeName).concat("代发订单"); + try (ServletOutputStream os = response.getOutputStream()) { + FileUtils.setAttachmentResponseHeader(response, title + ".xlsx"); + storeOrderService.exportOrder(queryDTO, null, title, os); + } catch (IOException e) { + logger.error("导出异常", e); + } + } + + @PreAuthorize("@ss.hasAnyRoles('store')||@ss.hasSupplierSubRole()") + @ApiOperation(value = "导出备货单") + @PostMapping("/exportPendingShipment") + @ResponseHeader + public void exportPendingShipment(@Validated @RequestBody StoreOrderQueryVO vo, HttpServletResponse response) { + StoreOrderQueryDTO queryDTO = BeanUtil.toBean(vo, StoreOrderQueryDTO.class); + Long storeId = SecurityUtils.getStoreId(); + queryDTO.setStoreId(storeId); + queryDTO.setOrderStatus(EOrderStatus.PENDING_SHIPMENT.getValue()); + String storeName = storeService.getStoreNameByIds(Collections.singletonList(storeId)).get(storeId); + String title = StrUtil.emptyIfNull(storeName).concat("代发备货单"); + try (ServletOutputStream os = response.getOutputStream()) { + FileUtils.setAttachmentResponseHeader(response, title + ".xlsx"); + storeOrderService.exportOrder(queryDTO, EOrderStatus.PENDING_SHIPMENT, title, os); + } catch (IOException e) { + logger.error("导出异常", e); + } + } + @PreAuthorize("@ss.hasAnyRoles('store,seller,agent')||@ss.hasSupplierSubRole()") - @ApiOperation(value = "订单物流信息") + @ApiOperation(value = "订单统计信息") @GetMapping(value = "/count/{srcPage}") public R count(@PathVariable("srcPage") Integer srcPage) { StoreOrderCountQueryDTO queryDTO = new StoreOrderCountQueryDTO(); diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/order/StoreOrderPageQueryVO.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/order/StoreOrderPageQueryVO.java new file mode 100644 index 000000000..2bca52284 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/order/StoreOrderPageQueryVO.java @@ -0,0 +1,105 @@ +package com.ruoyi.web.controller.xkt.vo.order; + +import com.ruoyi.web.controller.xkt.vo.BasePageVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import javax.validation.constraints.NotNull; +import java.util.Date; +import java.util.List; + +/** + * @author liangyq + * @date 2025-04-14 12:54 + */ +@ApiModel +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class StoreOrderPageQueryVO extends BasePageVO { + + @ApiModelProperty(value = "订单ID集合") + private List storeOrderIds; + /** + * 档口ID + */ + @ApiModelProperty(value = "档口ID") + private Long storeId; +// /** +// * 下单用户ID +// */ +// @ApiModelProperty(value = "下单用户ID") +// private Long orderUserId; + /** + * 订单号(模糊) + */ + @ApiModelProperty(value = "订单号(模糊)") + private String orderNo; + + @ApiModelProperty(value = "售后订单原订单号") + private String originOrderNo; + /** + * 订单类型[1:销售订单 2:退货订单] + */ + @ApiModelProperty(value = "订单类型[1:销售订单 2:退货订单]") + private Integer orderType; + /** + * 订单状态(1开头为销售订单状态,2开头为退货订单状态)[10:已取消 11:待付款 12:待发货 13:已发货 14:已完成 21:售后中 22:售后拒绝 23:平台介入 24:售后完成] + */ + @ApiModelProperty(value = "订单状态(1开头为销售订单状态,2开头为退货订单状态)[10:已取消 11:待付款 12:待发货 13:已发货 14:已完成 21:售后中 22:售后拒绝 23:平台介入 24:售后完成]") + private Integer orderStatus; + /** + * 收货人-名称(模糊) + */ + @ApiModelProperty(value = "收货人-名称(模糊)") + private String destinationContactName; + /** + * 收货人-电话(模糊) + */ + @ApiModelProperty(value = "收货人-电话(模糊)") + private String destinationContactPhoneNumber; + /** + * 发货方式[1:货其再发 2:有货先发] + */ + @ApiModelProperty(value = "发货方式[1:货其再发 2:有货先发]") + private Integer deliveryType; + /** + * 下单开始时间 + */ + @ApiModelProperty(value = "下单开始时间") + private Date orderTimeBegin; + /** + * 下单结束时间 + */ + @ApiModelProperty(value = "下单结束时间") + private Date orderTimeEnd; + /** + * 支付开始时间 + */ + @ApiModelProperty(value = "支付开始时间") + private Date payTimeBegin; + /** + * 支付结束时间 + */ + @ApiModelProperty(value = "支付结束时间") + private Date payTimeEnd; + + /** + * 物流运单号(模糊) + */ + @ApiModelProperty(value = "物流运单号(模糊)") + private String expressWaybillNo; + /** + * 商品货号(模糊) + */ + @ApiModelProperty(value = "商品货号(模糊)") + private String prodArtNum; + + @NotNull(message = "来源页面不能为空") + @ApiModelProperty(value = "来源页面[1:卖家订单列表 2:档口/平台订单列表]", required = true) + private Integer srcPage; + +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/order/StoreOrderQueryVO.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/order/StoreOrderQueryVO.java index 85303c8ee..c9e4a0ef9 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/order/StoreOrderQueryVO.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/order/StoreOrderQueryVO.java @@ -1,11 +1,8 @@ package com.ruoyi.web.controller.xkt.vo.order; -import com.ruoyi.web.controller.xkt.vo.BasePageVO; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; import javax.validation.constraints.NotNull; import java.util.Date; @@ -17,9 +14,7 @@ import java.util.List; */ @ApiModel @Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class StoreOrderQueryVO extends BasePageVO { +public class StoreOrderQueryVO { @ApiModelProperty(value = "订单ID集合") private List storeOrderIds; diff --git a/ruoyi-admin/src/main/resources/META-INF/excel/order_export.xlsx b/ruoyi-admin/src/main/resources/META-INF/excel/order_export.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..b1ce7705874ce8fc53d859391f2c14b5c5f6c7e3 GIT binary patch literal 10401 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 literal 0 HcmV?d00001 diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelColumn.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelColumn.java new file mode 100644 index 000000000..d25d8e571 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelColumn.java @@ -0,0 +1,25 @@ +package com.ruoyi.common.utils.poi; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author LH + * @date 2021-04-23 13:30:54 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface ExcelColumn { + /** + * 表头 + */ + String head() default ""; + + /** + * 排序 + */ + int index() default -1; + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelMergeRegion.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelMergeRegion.java new file mode 100644 index 000000000..5da7d6f05 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelMergeRegion.java @@ -0,0 +1,23 @@ +package com.ruoyi.common.utils.poi; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author liangyq + * @date 2025-08-16 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ExcelMergeRegion { + + private Integer firstRow; + + private Integer lastRow; + + private Integer firstCol; + + private Integer lastCol; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelTemplateUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelTemplateUtil.java new file mode 100644 index 000000000..56d8212f5 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelTemplateUtil.java @@ -0,0 +1,416 @@ +package com.ruoyi.common.utils.poi; + + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.resource.ClassPathResource; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddress; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author LH + * @date 2019/3/20 13:27 + **/ +@SuppressWarnings("all") +public class ExcelTemplateUtil { + + private static final Logger logger = LoggerFactory.getLogger(ExcelUtil.class); + + private static final String HIDE = "$hide$"; + + private static final String COMMON_REGEX = "^(.*)\\$\\{(.+)\\}(.*)$"; + /** + * for 循环表达式 + */ + private static final String FOREACH_REGEX = "^\\$\\{(.+\\[.+\\])\\}$"; + /** + * for 循环表达式中的字段 + */ + private static final String FOREACH_REGEX_FIELD_NAME = "^\\$\\{(.+)\\[.+\\]\\}$"; + private static final String ARRAY_REGEX = "^.+\\[(.+)\\]$"; + + /** + * 导出excel + * + * @param data 数据源 + * @param template 模板输入流 + * @param out excel 输出 + * @throws Exception + */ + public static void export(T data, InputStream template, OutputStream out) throws Exception { + export(data, template, out, null); + } + + /** + * 使用4个参数进行导出 + * + * @param data 数据源 + * @param template 模板输入流 + * @param out 输出流 + * @param datePattern 数据格式 + * @throws Exception + */ + public static void export(T data, InputStream template, OutputStream out, String datePattern) throws Exception { + export(data, template, out, datePattern, 0, null); + } + + + /** + * 导出excel (用于导出不规则数据) + * 支持表达式: ${name}, ${list[name]}, 表达式全部在模板中配置 + * ${name} : 数据源中属性名存在name + * ${list[name]} : 数据源对象中存在属性list(List),并且列表对象中存在属性name + * + * @param data 数据源 + * @param template 模板输入流 + * @param out 输出 + * @param datePattern 日期格式化样式 + * @param sheetNumber sheet编号 + * @param mergeRegions 合并单元格 + * @throws Exception + */ + public static void export(T data, InputStream template, OutputStream out, String datePattern, Integer sheetNumber, List mergeRegions) throws Exception { + Workbook workbook = WorkbookFactory.create(template); + Sheet sheet = workbook.getSheetAt(sheetNumber); + + Row row; + Cell cell; + boolean foreach; + String curListFieldName = null; + for (int rowIndex = 0; rowIndex <= sheet.getLastRowNum(); rowIndex++) { + row = sheet.getRow(rowIndex); + if (row == null) { + continue; + } + foreach = false; + + List expressions = new ArrayList<>(); + Map styleMap = new ConcurrentHashMap<>(20); + for (int clumnINdex = 0; clumnINdex <= row.getLastCellNum(); clumnINdex++) { + cell = row.getCell(clumnINdex); + if (cell == null) { + if (foreach) { + expressions.add(""); + } else { + continue; + } + } + String cellValue = getCellText(cell); + if (cellValue.matches(FOREACH_REGEX)) { + if (!foreach) { + foreach = true; + clumnINdex = -1; + continue; + } + String expression = cellValue.replaceAll(FOREACH_REGEX, "$1"); + curListFieldName = cellValue.replaceAll(FOREACH_REGEX_FIELD_NAME, "$1"); + expressions.add(expression); + styleMap.put(expression, cell.getCellStyle()); + } else if (cellValue.matches(COMMON_REGEX)) { + String fieldName = cellValue.replaceAll(COMMON_REGEX, "$2"); + String fieldValue = getFieldStringValue(data, fieldName); + String newValue = cellValue.replaceAll(COMMON_REGEX, "$1" + fieldValue + "$3"); + setCellValue(cell, newValue, datePattern); + if (foreach) { + expressions.add(newValue); + } + } else { + if (foreach) { + expressions.add(cellValue); + } + } + } + // 处理for循环表达式 + if (expressions != null && !expressions.isEmpty()) { + int index = 0; + List list = getFieldListValue(data, curListFieldName); + if (list == null || list.isEmpty()) { + sheet.removeRow(row); + continue; + } + // 把表达式行到最后一行的内容往后移动list 大小的行数 + if (list.size() - 1 != 0 && rowIndex + 1 <= sheet.getLastRowNum()) { + sheet.shiftRows(rowIndex + 1, sheet.getLastRowNum(), list.size() - 1); + } + String expression; + Object item; + Cell cur; + for (int k = 0; k < list.size(); k++) { + item = list.get(k); + index++; + row = sheet.getRow(rowIndex++); + if (row == null) { + row = sheet.createRow(rowIndex - 1); + } + for (int i = 0; i < expressions.size(); i++) { + cur = row.getCell(i); + if (cur == null) { + cur = row.createCell(i); + } + expression = expressions.get(i); + if (expression.matches(ARRAY_REGEX)) { + // items[waybillNo] -> wayBillNo -> item.waybillNo + cur.setCellStyle(styleMap.get(expression)); + expression = expression.replaceAll(ARRAY_REGEX, "$1"); + if (expression.equals("INDEX")) { + setCellValue(cur, index, datePattern); + } else { + setCellValue(cur, getFieldValue(item, expression), datePattern); + } + } else { + setCellValue(cur, expression, datePattern); + } + } + } + rowIndex--; + } + } + if (CollUtil.isNotEmpty(mergeRegions)) { + for (ExcelMergeRegion mergeRegion : mergeRegions) { + CellRangeAddress cellAddresses = new CellRangeAddress(mergeRegion.getFirstRow(), + mergeRegion.getLastRow(), mergeRegion.getFirstCol(), mergeRegion.getLastCol()); + sheet.addMergedRegion(cellAddresses); + } + } + workbook.write(out); + } + + + /** + * Excel 导出 + * 目标源对象必须使用 @ExcelColumn注解标记属性 例如 @ExcelColumn(index = 0, head = "运单号码") + * index: 显示列的顺序, head: excle表头 + * + * @param data 数据源列表 + * @param out 目标流 + * @param + */ + public static void export(List data, OutputStream out) { + export(data, out, null, false, null); + } + + public static void export(List data, OutputStream out, String datePattern) { + export(data, out, datePattern, false, null); + } + + public static void export(List data, OutputStream out, boolean xlsx) { + export(data, out, null, true, null); + } + + public static void export(List data, OutputStream out, List excludes) { + export(data, out, null, false, excludes); + } + + /** + * Excel 导出 + * + * @param data 数据源 + * @param out 输入目标 + * @param datePattern 日期 + * @param xlsx 文件格式是否为xlsx + * @param excludes 不需要导出的列 + * @param
+     *                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            {@code
+     */
+    public static  void export(List data, OutputStream out, String datePattern, boolean xlsx,
+                                  List excludes) {
+        if (data == null || data.isEmpty()) {
+            throw new IllegalArgumentException("数据不能为空");
+        }
+        if (datePattern == null) {
+            datePattern = "yyyy-MM-dd HH:mm:ss";
+        }
+        Class dataCls = data.get(0).getClass();
+        Field[] fields = dataCls.getDeclaredFields();
+        List columns = new ArrayList<>();
+
+        for (Field field : fields) {
+            ExcelColumn annotation = field.getAnnotation(ExcelColumn.class);
+            if (annotation != null) {
+                field.setAccessible(true);
+                // 排除列
+                if (excludes != null && excludes.contains(field.getName())) {
+                    continue;
+                }
+                Object value = getFieldValue(field, data.get(0));
+                columns.add(new Column(field, annotation.index(), annotation.head()));
+            }
+        }
+        columns.sort((o1, o2) -> Integer.compare(o1.getIndex(), o2.getIndex()));
+
+        Workbook workbook;
+        try {
+            workbook = WorkbookFactory.create(xlsx);
+        } catch (Exception e) {
+            throw new IllegalStateException(e);
+        }
+        Sheet sheet = workbook.createSheet();
+        int rowIndex = 0;
+        int cellIndex = 0;
+        Row headRow = sheet.createRow(rowIndex++);
+        for (Column column : columns) {
+            Cell cell = headRow.createCell(cellIndex++);
+            setCellValue(cell, column.getHead());
+        }
+        Row curRow;
+        for (T cur : data) {
+            curRow = sheet.createRow(rowIndex++);
+            Cell cell;
+            cellIndex = 0;
+            for (Column column : columns) {
+                cell = curRow.createCell(cellIndex++);
+                setCellValue(cell, getFieldValue(column.getField(), cur), datePattern);
+            }
+        }
+        try {
+            workbook.write(out);
+        } catch (IOException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    public static InputStream getTemplate(String templateName) {
+        String path = "META-INF/excel/" + templateName;
+        return new ClassPathResource(path).getStream();
+    }
+
+    static class Column {
+        private Field field;
+        private int index;
+        private String head;
+
+        public Column(Field field, int index, String head) {
+            this.field = field;
+            this.index = index;
+            this.head = head;
+        }
+
+        public Field getField() {
+            return field;
+        }
+
+        public void setField(Field field) {
+            this.field = field;
+        }
+
+        public int getIndex() {
+            return index;
+        }
+
+        public void setIndex(int index) {
+            this.index = index;
+        }
+
+        public String getHead() {
+            return head;
+        }
+
+        public void setHead(String head) {
+            this.head = head;
+        }
+    }
+
+    private static Object getFieldValue(Field field, Object data) {
+        field.setAccessible(true);
+        try {
+            return field.get(data);
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+
+    private static  Object getFieldValue(T data, String fieldName) {
+        try {
+            if (fieldName.equals("list") && data instanceof List) {
+                return data;
+            }
+            if (data instanceof Map) {
+                return ((Map) data).get(fieldName);
+            }
+            Field field = data.getClass().getDeclaredField(fieldName);
+            field.setAccessible(true);
+            return field.get(data);
+        } catch (NoSuchFieldException e) {
+            throw new IllegalArgumentException("无字段[" + fieldName + "]");
+        } catch (IllegalAccessException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    private static  String getFieldStringValue(T data, String fieldName) {
+        Object value = getFieldValue(data, fieldName);
+        return value == null ? "" : value.toString();
+    }
+
+    private static  List getFieldListValue(T data, String fieldName) {
+        Object value = getFieldValue(data, fieldName);
+        if (value == null) {
+            return null;
+        }
+        if (!(value instanceof List)) {
+            throw new IllegalArgumentException("字段[" + fieldName + "]非List类型");
+        }
+        return (List) value;
+    }
+
+    private static String getCellText(Cell cell) {
+        if (cell == null) {
+            return "";
+        }
+        CellType cellType = cell.getCellType();
+
+        if (CellType.STRING.equals(cellType)) {
+            return cell.getStringCellValue();
+        } else if (CellType.NUMERIC.equals(cellType)) {
+            return String.valueOf(cell.getNumericCellValue());
+        } else {
+            return "";
+        }
+    }
+
+
+    private static void setCellValue(Cell cell, Object value) {
+        setCellValue(cell, value, null);
+    }
+
+    private static void setCellValue(Cell cell, Object value, String datePattern) {
+        if (value == null) {
+            value = "";
+        }
+        if (value instanceof Double) {
+            cell.setCellValue((Double) value);
+        } else if (value instanceof String) {
+            cell.setCellValue((String) value);
+        } else if (value instanceof BigDecimal) {
+            BigDecimal val = (BigDecimal) value;
+            cell.setCellValue(val.doubleValue());
+        } else if (value instanceof Date) {
+            if (datePattern == null) {
+                datePattern = "yyyy-MM-dd HH:mm:ss";
+            }
+            Date date = (Date) value;
+            String formatValue = new SimpleDateFormat(datePattern).format(date);
+            cell.setCellValue(formatValue);
+        } else if (value instanceof Number) {
+            cell.setCellValue(Double.valueOf(value.toString()));
+        }
+    }
+
+}
+
+
diff --git a/xkt/src/main/java/com/ruoyi/xkt/dto/order/StoreOrderExportDTO.java b/xkt/src/main/java/com/ruoyi/xkt/dto/order/StoreOrderExportDTO.java
new file mode 100644
index 000000000..07b9941a7
--- /dev/null
+++ b/xkt/src/main/java/com/ruoyi/xkt/dto/order/StoreOrderExportDTO.java
@@ -0,0 +1,38 @@
+package com.ruoyi.xkt.dto.order;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author liangyq
+ * @date 2025-08-17
+ */
+@Data
+public class StoreOrderExportDTO {
+
+    private String title;
+
+    private List items;
+
+    @Data
+    public static class Item {
+
+        private Integer seqNo;
+
+        private String orderNo;
+
+        private String createTime;
+
+        private String prodArtNum;
+
+        private String colorName;
+
+        private Integer size;
+
+        private Integer goodsQuantity;
+
+        private String detailStatus;
+
+    }
+}
diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/IStoreOrderService.java b/xkt/src/main/java/com/ruoyi/xkt/service/IStoreOrderService.java
index bf9bc91ee..f88673cbe 100644
--- a/xkt/src/main/java/com/ruoyi/xkt/service/IStoreOrderService.java
+++ b/xkt/src/main/java/com/ruoyi/xkt/service/IStoreOrderService.java
@@ -1,13 +1,14 @@
 package com.ruoyi.xkt.service;
 
-import com.github.pagehelper.Page;
 import com.ruoyi.xkt.domain.StoreOrder;
 import com.ruoyi.xkt.dto.express.ExpressShippingLabelDTO;
 import com.ruoyi.xkt.dto.express.ExpressTrackDTO;
 import com.ruoyi.xkt.dto.order.*;
+import com.ruoyi.xkt.enums.EOrderStatus;
 import com.ruoyi.xkt.enums.EPayChannel;
 import com.ruoyi.xkt.enums.EPayPage;
 
+import java.io.OutputStream;
 import java.math.BigDecimal;
 import java.util.Collection;
 import java.util.Date;
@@ -92,7 +93,17 @@ public interface IStoreOrderService {
      * @param queryDTO
      * @return
      */
-    Page page(StoreOrderQueryDTO queryDTO);
+    List listPageItem(StoreOrderQueryDTO queryDTO);
+
+    /**
+     * 导出订单
+     *
+     * @param queryDTO
+     * @param detailStatus
+     * @param title
+     * @param os
+     */
+    void exportOrder(StoreOrderQueryDTO queryDTO, EOrderStatus detailStatus, String title, OutputStream os);
 
     /**
      * 订单统计
diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/impl/StoreOrderServiceImpl.java b/xkt/src/main/java/com/ruoyi/xkt/service/impl/StoreOrderServiceImpl.java
index cb0a2b9dc..b3411250f 100644
--- a/xkt/src/main/java/com/ruoyi/xkt/service/impl/StoreOrderServiceImpl.java
+++ b/xkt/src/main/java/com/ruoyi/xkt/service/impl/StoreOrderServiceImpl.java
@@ -11,7 +11,6 @@ import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.NumberUtil;
 import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
-import com.github.pagehelper.Page;
 import com.github.pagehelper.PageHelper;
 import com.ruoyi.common.constant.Constants;
 import com.ruoyi.common.core.domain.SimpleEntity;
@@ -20,6 +19,8 @@ import com.ruoyi.common.core.domain.entity.SysUser;
 import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.common.utils.bean.BeanValidators;
+import com.ruoyi.common.utils.poi.ExcelMergeRegion;
+import com.ruoyi.common.utils.poi.ExcelTemplateUtil;
 import com.ruoyi.common.utils.spring.SpringUtils;
 import com.ruoyi.system.mapper.SysUserMapper;
 import com.ruoyi.xkt.domain.*;
@@ -40,6 +41,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.io.OutputStream;
 import java.math.BigDecimal;
 import java.util.*;
 import java.util.function.Function;
@@ -549,12 +551,10 @@ public class StoreOrderServiceImpl implements IStoreOrderService {
     }
 
     @Override
-    public Page page(StoreOrderQueryDTO queryDTO) {
-        Page page = PageHelper.startPage(queryDTO.getPageNum(), queryDTO.getPageSize(),
-                "so.create_time DESC");
-        storeOrderMapper.listStoreOrderPageItem(queryDTO);
-        if (CollUtil.isNotEmpty(page.getResult())) {
-            List list = page.getResult();
+    public List listPageItem(StoreOrderQueryDTO queryDTO) {
+        PageHelper.orderBy("so.create_time DESC");
+        List list = storeOrderMapper.listStoreOrderPageItem(queryDTO);
+        if (CollUtil.isNotEmpty(list)) {
             Set soIds = list.stream().map(StoreOrderPageItemDTO::getId).collect(Collectors.toSet());
             List orderDetailList = storeOrderDetailMapper.listInfoByStoreOrderIds(soIds);
             List spIds = orderDetailList.stream().map(StoreOrderDetailInfoDTO::getStoreProdId).distinct()
@@ -593,7 +593,65 @@ public class StoreOrderServiceImpl implements IStoreOrderService {
                 order.setExpressWaybillNoInfos(new ArrayList<>(expressWaybillNoInfos));
             }
         }
-        return page;
+        return list;
+    }
+
+    @Override
+    public void exportOrder(StoreOrderQueryDTO queryDTO, EOrderStatus detailStatus, String title, OutputStream os) {
+        PageHelper.orderBy("so.create_time DESC");
+        List pageItems = storeOrderMapper.listStoreOrderPageItem(queryDTO);
+        Map> orderDetailGroup;
+        if (CollUtil.isNotEmpty(pageItems)) {
+            Set soIds = pageItems.stream().map(StoreOrderPageItemDTO::getId).collect(Collectors.toSet());
+            List orderDetailList = storeOrderDetailMapper.listInfoByStoreOrderIds(soIds);
+            orderDetailGroup = orderDetailList
+                    .stream()
+                    .collect(Collectors.groupingBy(StoreOrderDetailDTO::getStoreOrderId));
+        } else {
+            orderDetailGroup = MapUtil.empty();
+        }
+        StoreOrderExportDTO exportDTO = new StoreOrderExportDTO();
+        exportDTO.setTitle(title);
+        List items = new ArrayList<>();
+        exportDTO.setItems(items);
+        List mergeRegions = new ArrayList<>();
+        int seqNo = 1;
+        int lineNo = 1;
+        for (StoreOrderPageItemDTO pageItem : pageItems) {
+            List details = orderDetailGroup.get(pageItem.getId()).stream().filter(o -> {
+                if (detailStatus != null) {
+                    return detailStatus.getValue().equals(o.getDetailStatus());
+                }
+                return true;
+            }).collect(Collectors.toList());
+            int size = details.size();
+            if (size > 1) {
+                mergeRegions.add(new ExcelMergeRegion(lineNo + 1, lineNo + size, 0, 0));
+                mergeRegions.add(new ExcelMergeRegion(lineNo + 1, lineNo + size, 1, 1));
+                mergeRegions.add(new ExcelMergeRegion(lineNo + 1, lineNo + size, 2, 2));
+            }
+            for (StoreOrderDetailInfoDTO detail : details) {
+                StoreOrderExportDTO.Item item = new StoreOrderExportDTO.Item();
+                item.setSeqNo(seqNo);
+                item.setOrderNo(pageItem.getOrderNo());
+                item.setCreateTime(DateUtil.formatDateTime(pageItem.getCreateTime()));
+                item.setProdArtNum(detail.getProdArtNum());
+                item.setColorName(detail.getColorName());
+                item.setSize(detail.getSize());
+                item.setGoodsQuantity(detail.getGoodsQuantity());
+                item.setDetailStatus(EOrderStatus.of(detail.getDetailStatus()).getLabel());
+                items.add(item);
+                lineNo++;
+            }
+            seqNo++;
+        }
+        try {
+            ExcelTemplateUtil.export(exportDTO, ExcelTemplateUtil.getTemplate("order_export.xlsx"),
+                    os, null, 0, mergeRegions);
+        } catch (Exception e) {
+            log.error("订单导出异常", e);
+            throw new ServiceException("订单导出失败");
+        }
     }
 
     @Override