一文学会easyexcel导出数据,多sheet页、字典【附带源码】

一、前言

​ 项目开发过程中,免不了需要数据导出功能,常见的导出工具包有poi,easypoi,easyexcel,它们各有优缺点,简要来说:

  • poi:功能强大,使用起来相对复杂,大数据时可能导致内存溢出
  • easypoi:基于poi实现,功能强大,简单易用,大数据时可能导致内存溢出,小数据量时推荐
  • easyexcel:基于poi实现,性能更优,大数据量时推荐
  • 本文只介绍easyexcel,并介绍常见问题比如字典、样式的解决方案

二、业务流程

​ 介绍数据导出业务流程之前,先梳理下数据导出,什么是数据导出,为什么需要数据导出?

​ 我们都知道数据存储在数据库中,数据库中的数据可以通过前端页面或者APP呈现给使用者,其实通过excel将数据导出也是一种呈现方式,而且可以通过excel对数据做更复杂的处理。

​ 前面说了,excel导出和前端页面、APP一样都是一种呈现方式,所以前端页面和APP在将数据呈现给用户的过程中遇到的问题,像属性转换(数据库存储的name,要呈现给使用者名称)、字典转换(数据库存储的1/0,要呈现给使用者启用/停用)等等问题,excel导出一样会遇到。下面介绍下数据呈现必须要经过业务流程

  1. 获取需要导出的数据集合
  2. 数据属性和自然语言映射关系,将数据对象的属性转为使用者可以理解的自然语言
  3. 数据字典值和自然语言映射关系,将属性的字典值转为使用者可以理解的自然语言(非字典值,是什么值就呈现什么值)
  4. 数据样式和自然语言样式映射关系,将数据样式转为使用者可以理解的自然语言样式
  5. 设置表格样式
  6. 将数据集合按照上述映射关系和表格样式,写入到excel中
  7. 用户下载excel

三、实现

1、引入easyexcel、fastjson、lombok包

<!--easy excel-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>4.0.3</version>
</dependency>
<!--fastjson-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.46</version>
</dependency>
<!--工具-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.0</version>
</dependency>

2、创建Json工具类

package com.yu.demo.tools;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.type.TypeReference;

import java.lang.reflect.Type;
import java.util.Map;

/**
 * JSON工具类
 *
 * @author admin
 */
public abstract class JsonUtil {
    private JsonUtil() {
    }

    public final static Type MAP_INTEGER_STRING = new TypeReference<Map<Integer, String>>() {
    }.getType();

    /**
     * json串转Map(Map的value类型一致时使用)
     *
     * @param jsonString json串
     * @return 对象
     */
    public static <K, V> Map<K, V> json2Map(String jsonString, Type type) {
        return JSON.parseObject(jsonString, type);
    }

}

3、创建自定义字典转换注解

package com.yu.demo.tools;

import java.lang.annotation.*;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DictSource {
    /**
     * 字典类型主键
     */
    String dictTypeId() default "";

    /**
     * 字典内容json串
     */
    String dictContentJson() default "";

}

4、创建字典转换实现类

package com.yu.demo.tools;

import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import org.apache.poi.util.StringUtil;


import java.lang.reflect.Field;
import java.util.Map;


public class IntegerDictConverter implements Converter<Integer> {

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    @Override
    public WriteCellData<?> convertToExcelData(Integer value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
        //属性值为空时,直接返回
        if (value == null) {
            //为空时的处理,与前端展示保持一致即可
            return new WriteCellData<>("");
        }
        //获取添加@ExcelProperty注解且converter = IntegerDictConverter.class的属性
        Field field = contentProperty.getField();
        //获取该属性的DictConverter注解信息
        DictSource dictSource = field.getAnnotation(DictSource.class);
        //配置了converter = IntegerDictConverter.class的属性,但是没有添加DictSource注解的直接返回
        if (dictSource == null) {
            return new WriteCellData<>(String.valueOf(value));
        }
        //获取配置的dictTypeId
        String dictTypeId = dictSource.dictTypeId();
        //获取配置的dictContentJson
        String dictContentJson = dictSource.dictContentJson();
        //判断dictTypeId是否为空
        boolean nullDictType = StringUtil.isBlank(dictTypeId);
        //判断nullDictContentJson是否为空
        boolean nullDictContentJson = StringUtil.isBlank(dictContentJson);
        //字典配置都为空时,将属性值转为字符串直接返回
        if (nullDictType && nullDictContentJson) {
            return new WriteCellData<>(String.valueOf(value));
        }
        //优先使用dictTypeId处理转换
        if (!nullDictType) {
            //通过dictTypeId获取字典内容集合:List<DictContent> dictContents = dictContentService.listByDictTypeId(dictTypeId);//主键是数值的,将dictTypeId转为数值
            //遍历字典内容,匹配属性值与字典值:value.equals(dictContent.getValue())
            //匹配成功后获取字典名称返回:return new WriteCellData<>(dictContent.getName());
            //如果没有匹配成功使用dictContentJson处理转换
        }
        if (!nullDictContentJson) {
            Map<Integer, String> dictContentMap = JsonUtil.json2Map(dictContentJson, JsonUtil.MAP_INTEGER_STRING);
            String cnName = dictContentMap.get(value);
            if (StringUtil.isNotBlank(cnName)) {
                return new WriteCellData<>(cnName);
            }
        }
        //没有转换成功时使用默认属性值
        return new WriteCellData<>(String.valueOf(value));
    }
}

5、创建数据对象类

package com.yu.demo.web.easyexcel.entity;

import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.converters.date.DateStringConverter;
import com.yu.demo.web.easyexcel.component.DictSource;
import com.yu.demo.web.easyexcel.component.IntegerDictConverter;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.util.Date;

@Setter
@Getter
@ToString
//类上添加@ExcelIgnoreUnannotated时,属性没有@ExcelProperty注解时不导出
//类上未添加@ExcelIgnoreUnannotated,属性没有@ExcelProperty注解时也导出
@ExcelIgnoreUnannotated
public class User {

    /**
     * 名称
     */
    @ExcelProperty("名称")
    private String name;
    /**
     * 密码
     * 类添加@ExcelIgnoreUnannotated,属性未添加@ExcelProperty,不导出
     */
    private String password;
    /**
     * 生日
     * 日期样式处理
     * 1.使用@DateTimeFormat设置导出样式
     * 2.使用DateStringConverter处理导出
     */
    @DateTimeFormat("yyyy-MM-dd HH:mm:ss")
    @ExcelProperty(value = "生日", converter = DateStringConverter.class)
    private Date birthday;
    /**
     * 性别
     * 字典转换处理
     */
    @ColumnWidth(7)//指定列宽度,优先级高于LongestMatchColumnWidthStyleStrategy
    @ExcelProperty(value = "性别", converter = IntegerDictConverter.class)
    @DictSource(dictContentJson = "{0:'女',1:'男',2:'保密'}")
    private Integer sex;

}

6、创建多sheet页封装对象

package com.yu.demo.tools;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.util.List;


/**
 * excel导入导出数据对象
 */
@Setter
@Getter
@ToString
public class SheetEntity<T> {

    /**
     * sheet页名称(导出参数)
     * 可以为空,为空时,单sheet页没有名称,多sheet页序号为名称
     */
    private String sheetName;
    /**
     * 数据类型(导入导出参数)
     */
    private Class<T> head;
    /**
     * 数据(导出参数)
     */
    private List<T> data;

}

7、创建Excel导出工具类

​ 导出的数据有如下三种及其说明

  • 通过全路径文件名导出,easyexcel通过全路径文件名创建文件,将数据写入文件,当路径不存在时报错,适合场景:一次导出,多次下载
  • 通过文件导出,将数据写入文件,当路径不存在报错,适合场景:一次导出,多次下载
  • 通过输出流导出,将数据写入输出流,适合场景:导出一次下载一次
package com.yu.demo.tools;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.util.StringUtil;

import java.io.File;
import java.io.OutputStream;
import java.util.List;

/**
 * excel导入导出工具类(easyExcel实现)
 * easyPoi:并发量和数据量都不大时推荐,定制化的导出支持非常的丰富
 * easyExcel:高并发、大数据量时推荐
 */
public abstract class ExcelUtil {
    // 设置居中对齐的样式
    private static final WriteCellStyle CONTENT_WRITE_CELL_STYLE;
    private static final WriteHandler HORIZONTAL_CELL_STYLE_STRATEGY;

    static {
        CONTENT_WRITE_CELL_STYLE = new WriteCellStyle();
        //水平居中
        CONTENT_WRITE_CELL_STYLE.setHorizontalAlignment(HorizontalAlignment.CENTER);
        //垂直居中
        CONTENT_WRITE_CELL_STYLE.setVerticalAlignment(VerticalAlignment.CENTER);
        HORIZONTAL_CELL_STYLE_STRATEGY = new HorizontalCellStyleStrategy(null, CONTENT_WRITE_CELL_STYLE);
    }

    private ExcelUtil() {
    }


    /**
     * 使用EasyExcel导出
     *
     * @param fullFileName 文件路径+文件名+后缀(文件已存在时覆盖)
     * @param sheetName    sheet名称(为空时使用默认值0)
     * @param head         数据类型(为空时没有表头,只有数据)
     * @param exportData   需要导出的数据(为空时,没有数据)
     */
    public static void exportByEasyExcel(String fullFileName, String sheetName, Class<?> head, List<?> exportData) {
        File targetFile = new File(fullFileName);
        // 判断文件父目录是否存在
        if (!targetFile.getParentFile().exists()) {
            boolean mkdirResult = targetFile.getParentFile().mkdirs();
            if (!mkdirResult) {
                return;
            }
        }
        ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(targetFile, head);
        if (fullFileName.endsWith(ExcelTypeEnum.XLS.getValue())) {
            excelWriterBuilder.excelType(ExcelTypeEnum.XLS);
        } else if (fullFileName.endsWith(ExcelTypeEnum.CSV.getValue())) {
            excelWriterBuilder.excelType(ExcelTypeEnum.CSV);
        } else {
            excelWriterBuilder.excelType(ExcelTypeEnum.XLSX);
        }
        excelWriterBuilder
                //设置列按最大长度调整
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                //设置水平垂直居中
                .registerWriteHandler(HORIZONTAL_CELL_STYLE_STRATEGY)
                .sheet(sheetName)
                .doWrite(exportData);
    }

    /**
     * 使用EasyExcel导出
     *
     * @param outputStream 输出流
     * @param sheetName    sheet名称(为空时使用默认值0)
     * @param head         数据类型(为空时没有表头,只有数据)
     * @param exportData   需要导出的数据(为空时,没有数据)
     */
    public static void exportByEasyExcel(OutputStream outputStream, ExcelTypeEnum excelType, String sheetName, Class<?> head, List<?> exportData) {
        EasyExcel.write(outputStream, head)
                .excelType(excelType)
                //设置列按最大长度调整,非线程安全,每次都需要new
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                //设置水平垂直居中
                .registerWriteHandler(HORIZONTAL_CELL_STYLE_STRATEGY)
                .sheet(sheetName)
                .doWrite(exportData);
    }

    /**
     * 使用EasyExcel导出多sheet页数据
     *
     * @param outputStream  输出流
     * @param sheetEntities 导出数据对象集合
     */
    public static void exportByEasyExcel(OutputStream outputStream, ExcelTypeEnum excelType, List<SheetEntity<?>> sheetEntities) {
        ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(outputStream).excelType(excelType);
        writeSheets(excelWriterBuilder, sheetEntities);
    }

    private static void writeSheets(ExcelWriterBuilder excelWriterBuilder, List<SheetEntity<?>> sheetEntities) {
        excelWriterBuilder
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                .registerWriteHandler(HORIZONTAL_CELL_STYLE_STRATEGY);
        ExcelWriter excelWriter = excelWriterBuilder.build();
        for (int i = 0; i < sheetEntities.size(); i++) {
            SheetEntity<?> sheetEntity = sheetEntities.get(i);
            Class<?> head = sheetEntity.getHead();
            List<?> exportData = sheetEntity.getData();
            String sheetName = StringUtil.isBlank(sheetEntity.getSheetName()) ? String.valueOf(i + 1) : sheetEntity.getSheetName();
            WriteSheet writeSheet = EasyExcel.writerSheet(i + 1, sheetName).head(head).build();
            excelWriter.write(exportData, writeSheet);
        }
        excelWriter.finish();
    }

}

8、创建测试类

package com.yu.demo.web.easyexcel.web;

import com.alibaba.excel.support.ExcelTypeEnum;
import com.yu.demo.web.easyexcel.entity.SheetEntity;
import com.yu.demo.web.easyexcel.entity.User;
import com.yu.demo.web.easyexcel.util.ExcelUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@RestController
@RequestMapping("user")
public class UserController {
    @Value("${download.path}")
    private String filePath;
    private List<User> users;
    private List<SheetEntity<?>> sheetEntities;

    @PostConstruct
    public void init() {
        users = new ArrayList<>(5);
        for (int i = 0; i < 5; i++) {
            User user = new User();
            user.setName(i + "号用户");
            user.setPassword(String.valueOf(i * 1000));
            user.setBirthday(new Date());
            user.setSex(i % 3);
            users.add(user);
        }
        sheetEntities = new ArrayList<>(2);
        for (int i = 0; i < 2; i++) {
            SheetEntity<User> sheetEntity = new SheetEntity<>();
            sheetEntity.setSheetName(i + "号sheet");
            sheetEntity.setHead(User.class);
            sheetEntity.setData(users);
            sheetEntities.add(sheetEntity);
        }
    }

    /**
     * 单sheet页通过全路径文件名导出测试接口(也可以通过文件流导出)
     * 返回文件名,前端通过web路径+文件名下载文件
     */
    @GetMapping("/filePath")
    public String filePath() {
        String fileName = "用户.xlsx";
        String fullFileName = filePath + fileName;
        ExcelUtil.exportByEasyExcel(fullFileName, "用户", User.class, users);
        return fileName;
    }

    /**
     * 多sheet页通过文件流导出(也可以通过全路径文件名导出)
     */
    @GetMapping("/download")
    public void download(HttpServletResponse response) throws IOException {
        String fileName = "用户";
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        String encodeFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=*=utf-8''" + encodeFileName + ExcelTypeEnum.XLSX.getValue());
        ExcelUtil.exportByEasyExcel(response.getOutputStream(), ExcelTypeEnum.XLSX, sheetEntities);
    }
}

9、application.yml中添加文件下载路径配置

download:
  #配置文件下载路径
  path: C:\Users\Administrator\Desktop
spring:
  web:
    resources:
      static-locations:
        #注册文件下载路径
        - file:${download.path}
        #系统默认配置
        - classpath:/META-INF/resources/
        - classpath:/resources/
        - classpath:/static/
        - classpath:/public/

四、接口测试

1、启用项目

image-20241025110748439

2、使用全路径文件名方式导出

  • 访问接口地址:http://localhost:8080/user/filePath

image-20241025111027053

image-20241025111701665

3、使用文件流方式导出

  • 访问接口地址:http://localhost:8080/user/download

image-20241025111851215

五、总结

  1. 使用Entity对象作为关系映射的载体,使用@ExcelProperty注解映射属性名称,并可以指定转换器、序号等信息;使用@DateTimeFormat注解和指定转换器设置时间格式;使用自定义注解@DictSource注解和指定转换器转换字典值
  2. 使用@ColumnWidth或其同目录下的其他注解、WriteHandler设置Excel样式
  3. 使用全路径文件名、文件、文件流作为数据导出的载体导出数据。
  4. SpringBoot集成easyexcel数据导出案例下载
<think>好的,我现在需要帮助用户解决使用EasyExcel导出包含61个sheet的Excel文件的问题。首先,用户提到了参考引用中的代码片段,其中有个方法exportSystemExcel,但具体实现需要自己编写。我需要先回忆EasyExcel的基本用法,特别是sheet导出的方法。 EasyExcel通常通过次调用write方法并指定不同的sheet名称来创建sheet。每个sheet对应数据列表。对于61个sheet,可能需要循环创建,每次循环生成sheet,并写入对应的数据。但是需要注意,大量sheet导出时内存占用可能会比较高,所以需要考虑分批次处理或者使用缓存机制。 接下来,我应该检查用户的需求是否涉及大数据量。用户提到的是“大规模Excel文件”,所以除了sheet数量,可能每个sheet数据量也较大。这时候需要确保使用EasyExcel的写入特性,比如重复利用头信息、使用模板或者分查询数据,避免次性加载所有数据到内存中。 然后,想到EasyExcel的文档中关于次写入sheet的部分,通常示例代码是次调用write方法,每次传入不同的sheet名称和数据。例如: ```java ExcelWriter excelWriter = EasyExcel.write(fileName).build(); for (int i = 0; i < 61; i++) { WriteSheet writeSheet = EasyExcel.writerSheet(i, "Sheet" + i).head(head).build(); excelWriter.write(dataList.get(i), writeSheet); } excelWriter.finish(); ``` 这样可以循环创建sheet。每个sheet数据可以来自不同的数据源,或者分批次查询。但用户需要导出的是大规模数据,可能需要每个sheet数据也是分查询的,避免内存溢出。 另外,需要考虑性能问题。61个sheet如果每个都包含大量数据,可能会导致导出时间较长或者内存不足。这时候建议使用分批次查询数据,并在每次写入时及时清空缓存。例如,对于每个sheet,分读取数据,逐写入,而不是次性加载所有数据。 此外,可能还需要设置参数来优化性能,比如设置autoCloseStream为true,使用默认的ExcleType(xlsx格式),以及调整内存参数,如使用SXSSF模式,不过EasyExcel本身已经做了优化,可能不需要额外设置。 用户可能遇到的另个问题是sheet名称的重复或长度限制,需要确保每个sheet的名称唯且符合Excel的命名规则,比如长度不超过31字符,不含特殊字符等。 总结步骤应该是: 1. 创建ExcelWriter对象。 2. 循环61次,每次创建个WriteSheet,指定名称和表头。 3. 每次循环时,获取对应sheet数据,可能分查询。 4. 将数据写入对应的sheet。 5. 最后关闭ExcelWriter。 同时,需要处理可能的异常,比如IO异常,确保流正确关闭。在导出完成后,要调用finish方法,并关闭资源。 可能还需要考虑并发问题,但如果是单线程导出的话,可能不需要处理。另外,如果数据量极大,可能需要使用临时文件或者磁盘缓存,但EasyExcel默认已经处理了这些情况。 最后,给出示例代码,并提醒用户注意内存管理和性能优化,比如分查询数据,避免次性加载所有数据。同时,检查sheet名称的有效性,确保导出成功。</think>使用 EasyExcel 导出包含 61 个工作表的大规模 Excel 文件时,需通过循环创建个 `WriteSheet` 对象并分批次写入数据。以下是实现步骤与示例代码: --- ### **实现步骤** 1. **初始化 ExcelWriter** 使用 `EasyExcel.write()` 创建 `ExcelWriter` 对象,指定输出流或文件路径。 2. **循环创建 Sheet** 通过循环生成 61 个 `WriteSheet` 对象,每个 Sheet 需设置**唯名称**和**表头结构**。注意:Sheet 名称长度需 ≤31 字符,且不可重复[^1]。 3. **分批次写入数据** 针对每个 Sheet数据,建议通过分查询(如数据库分)逐批读取数据,避免次性加载全部数据到内存。 4. **关闭资源** 最后调用 `excelWriter.finish()` 确保数据刷入文件并释放资源。 --- ### **示例代码** ```java public void exportSystemExcel(HttpServletResponse response) throws IOException { // 1. 初始化 ExcelWriter String fileName = "大规模数据导出.xlsx"; ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()) .autoCloseStream(true) // 自动关闭输出流 .build(); // 2. 循环创建 61 个 Sheet for (int sheetIndex = 0; sheetIndex < 61; sheetIndex++) { // 定义表头(根据业务调整) List<List<String>> head = Arrays.asList( Arrays.asList("字段1"), Arrays.asList("字段2") ); // 创建 WriteSheet 对象 WriteSheet writeSheet = EasyExcel.writerSheet(sheetIndex, "Sheet" + sheetIndex) .head(head) .build(); // 3. 分查询数据并写入(示例仅演示单次写入,实际需循环分) List<YourDataModel> data = fetchDataByPage(sheetIndex, pageSize); // 自定义数据获取方法 excelWriter.write(data, writeSheet); } // 4. 关闭资源 excelWriter.finish(); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")); } ``` --- ### **关键优化点** 1. **内存控制** - 每个 Sheet数据通过 `fetchDataByPage()` 分查询,避免全量加载。 - EasyExcel 默认使用磁盘缓存(SXSSF 模式),无需手动配置。 2. **性能提升** - 使用 `autoCloseStream(true)` 自动关闭输出流。 - 若数据量极大,可对每个 Sheet 单独分写入: ```java for (int page = 0; ; page++) { List<YourDataModel> chunk = fetchDataByPage(sheetIndex, page, pageSize); if (chunk.isEmpty()) break; excelWriter.write(chunk, writeSheet); } ``` 3. **异常处理** 添加 `try-catch` 块确保 `excelWriter.finish()` 始终执行,防止流未关闭。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

那你为何对我三笑留情

感谢支持,沉淀自己,帮助他人!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值