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 000000000..b1ce77058 Binary files /dev/null and b/ruoyi-admin/src/main/resources/META-INF/excel/order_export.xlsx differ 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