EasyExcel文件读取、生成excel、合并指定单元格

目录

1、添加EasyExcel依赖

2、测试类、读取本地excel、写入本地excel

2.1 实体类

  2.1.1 用户实体类-User

  2.1.2 用户实体类-Log 

 2.1.3 UserExcelListener

3、实际的业务处理控制器

4、读取合并数据分类

5、 导出excel合并单元格

 5.1 要导出的数据2022.xls文件内容

5.2 执行代码调试导入文件 

5.3 合并指定列的工具类

5.4 导出的实体类

5.5 实际的控制层调用代码


1、添加EasyExcel依赖

<!--alibaba easyexcel-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.2.0-beta2</version> <!--1.1.2-beta5-->
        </dependency>

2、测试类、读取本地excel、写入本地excel

package com.shucha.deveiface.biz.test;

import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;

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

/**
 * @author tqf
 * @Description
 * @Version 1.0
 * @since 2022-01-18 14:15
 */
@Slf4j
public class ExcelRead {
    public static void main1(String[] args) {
        String userPath = "D:/123.xls";
        String logPath = "D:/log.xls";
        EasyExcel.read(userPath, User.class, new UserExcelListener()).sheet().doRead();
        EasyExcel.read(logPath, Log.class, new UserExcelListener()).sheet().doRead();
    }
    public static void main(String[] args) {
        String fileName = "D:/log.xls";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish
        List<Log> list = EasyExcel.read(fileName).head(Log.class).sheet().doReadSync();
        for (Log data : list) {
            log.info("读取到数据:{}", JSON.toJSONString(data));
        }
        // String fileName1 = addFile("D:/", "test.xls");
        EasyExcel.write("D:/test.xls", Log.class).sheet().doWrite(list);
        // 这里 也可以不指定class,返回一个list,然后读取第一个sheet 同步读取会自动finish
        String path = "D:/123.xls";
        List<Map<Integer, String>> listMap = EasyExcel.read(path).sheet().doReadSync();
        for (Map<Integer, String> data : listMap) {
            // 返回每条数据的键值对 表示所在的列 和所在列的值
            log.info("读取到数据:{}", JSON.toJSONString(data));
        }
    }
    public static String addFile(String filePath, String fileName){
        File file = new File(filePath + fileName);
        if (!file.exists()) {
            file.mkdirs();// 能创建多级目录
        }
        return file.getPath();
    }


}

2.1 实体类

  2.1.1 用户实体类-User

package com.shucha.deveiface.biz.test;

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

/**
 * @author tqf
 * @Description
 * @Version 1.0
 * @since 2022-01-18 14:09
 */
@Data
public class User {

    /**
     * 姓名
     */
    @ExcelProperty(index = 0)
    private String name;

    /**
     * 年龄
     */
    @ExcelProperty(index = 1)
    private String age;
}

  2.1.2 用户实体类-Log 

package com.shucha.deveiface.biz.test;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import lombok.Data;

/**
 * @author tqf
 * @Description
 * @Version 1.0
 * @since 2022-01-18 15:03
 */
@Data

public class Log {

    /**
     * 日志名称
     */
    @ColumnWidth(20)
    @ExcelProperty(value = "日志名称", index = 0)
    // @ExcelProperty(value = {"资源基本信息", "序号"},index = 0)
    private String logName;

    /**
     * 日志时间
     */
    @ColumnWidth(20)
    @ExcelProperty(value = "日志时间", index = 1)
    @DateTimeFormat("yyyy-MM-dd HH:mm:ss")
    private String date;

    /**
     * 字段名
     */
    @ColumnWidth(20)
    @ExcelProperty(value = "字段名", index = 2)
    private String fieldName;

    /**
     * 字段描述
     */
    @ColumnWidth(20)
    @ExcelProperty(value = "字段描述", index = 3)
    private String fieldDesc;

    /**
     * 字段值
     */
    @ColumnWidth(20)
    @ExcelProperty(value = "字段值", index = 4)
    private String fieldValue;
}

 2.1.3 UserExcelListener

package com.shucha.deveiface.biz.test;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;

/**
 * @author tqf
 * @Description
 * @Version 1.0
 * @since 2022-01-18 14:14
 */
@Slf4j
public class UserExcelListener<T> extends AnalysisEventListener<T> {

    /**
     * 批处理阈值
     */
    private static final int BATCH_COUNT = 2;
    List<T> list = new ArrayList<>();

    @Override
    public void invoke(T t, AnalysisContext analysisContext) {
        log.info("解析到一条数据:{}", JSON.toJSONString(t));
        list.add(t);
        if (list.size() >= BATCH_COUNT) {
            saveData();
            list.clear();
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        saveData();
        log.info("所有数据解析完成!");
    }

    private void saveData(){
        log.info("{}条数据,开始存储数据库!", list.size());
        log.info("存储数据库成功!");
    }
}

3、实际的业务处理控制器

package com.shucha.deveiface.web.controller;

import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
import com.sdy.common.utils.StringUtil;
import com.shucha.deveiface.biz.test.Log;
import com.shucha.deveiface.biz.test.User;
import com.shucha.deveiface.biz.test.UserExcelListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.List;

/**
 * @author tqf
 * @Description
 * @Version 1.0
 * @since 2022-01-18 14:32
 */
@Slf4j
@RestController
@SuppressWarnings("unchecked")
@RequestMapping("/excel")
public class TestController {

    /**
     * 测试上传excel文件读取文件内容
     * @param file
     * @throws IOException
     */
    @PostMapping("/readExcel")
    public void readExcel(MultipartFile file) throws IOException {
        EasyExcel.read(file.getInputStream(), User.class, new UserExcelListener()).sheet().doRead();
        List<User> list = EasyExcel.read(file.getInputStream()).head(User.class).sheet().doReadSync();
        // System.out.println(list);
    }

    /**
     * 下载excel文件
     * @param response
     * @throws IOException
     */
    @GetMapping("/downLoadResItem")
    public void downLoadResItem(HttpServletResponse response) throws IOException {
        String fileName = "D:/log.xls";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish
        List<Log> list = EasyExcel.read(fileName).head(Log.class).sheet().doReadSync();

        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        String fileName1 = URLEncoder.encode("生成的excel文件", "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName1 + ".xls");
        ServletOutputStream output = response.getOutputStream();
        // ExcelWriter writer = new ExcelWriter(output, ExcelTypeEnum.XLS, true);
        //Sheet sheet = new Sheet(1, 0, ResItemExcelVo.class);
        // sheet.setSheetName("第一页");

        EasyExcel.write(response.getOutputStream(), Log.class)
                .sheet("sheet1")
                .doWrite(list);
        // writer.finish();
        output.flush();
    }

    /**
     * 生成的excel,写入多个sheet
     * @param response
     * @throws IOException
     */
    @GetMapping("/downLoadTwo")
    public void downLoadTwo(HttpServletResponse response) throws IOException {
        String fileName = "D:/log.xls";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish
        List<Log> logList = EasyExcel.read(fileName).head(Log.class).sheet().doReadSync();
        // 读取用户信息excel
        String userPath = "D:/123.xls";
        List<User> userList = EasyExcel.read(userPath).head(User.class).doReadAllSync();

        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        String fileName1 = URLEncoder.encode("生成多个sheet文件", "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName1 + ".xls");
        ServletOutputStream output = response.getOutputStream();

        ExcelWriter excelWriter = EasyExcel.write(output).build();
        //这里 需要指定写用哪个class去写
        WriteSheet writeSheet = EasyExcel.writerSheet(0, "用户信息").head(User.class).build();
        excelWriter.write(userList, writeSheet);
        writeSheet = EasyExcel.writerSheet(1, "查询日志信息").head(Log.class).build();
        excelWriter.write(logList, writeSheet);
        //千万别忘记finish 会帮忙关闭流
        excelWriter.finish();
        output.flush();
    }
}

4、读取合并数据分类

日志名称日志时间字段名字段描述字段值
查询用户信息2021-01-11userName用户名tqf_123
age年龄25
sex性别(0-男,1-女,2-未知)0
获取日志列表2021-01-12logName日志名称查询
logTime日志时间2021-10-01 14:25:14
logUser查询人姓名王杰
根据ID查询详情2021-01-13total总数25
查询最新数据2021-01-15time时间2021-10-01 14:25:18
total数据条数35

public static void main(String[] args) {
        String fileName = "D:/log.xls";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish
        List<Log> list = EasyExcel.read(fileName).head(Log.class).sheet().doReadSync();
        for (Log data : list) {
            log.info("读取到数据:{}", JSON.toJSONString(data));
        }
        List<LogDTO> dtoList = new ArrayList<>();
        int i = 0;
        for (Log data : list) {
            // 判断日志名称不为空就是主数据
            LogDTO dto = new LogDTO();
            i++;
            if(StringUtil.isNotBlank(data.getLogName())) {
                dto.setLogName(data.getLogName());
                dto.setDate(data.getDate());
                // 获取子类
                List<Log> sonDataList = new ArrayList<>();
                Log sonLog = new Log();
                sonLog.setFieldName(data.getFieldName());
                sonLog.setFieldDesc(data.getFieldDesc());
                sonLog.setFieldValue(data.getFieldValue());
                sonDataList.add(sonLog);
                for (int j = i;j<list.size();j++) {
                    if(StringUtil.isNotBlank(list.get(j).getLogName())) {
                        break;
                    }
                    if(StringUtil.isBlank(list.get(j).getLogName())) {
                        Log sonLog1 = new Log();
                        sonLog1.setFieldName(list.get(j).getFieldName());
                        sonLog1.setFieldDesc(list.get(j).getFieldDesc());
                        sonLog1.setFieldValue(list.get(j).getFieldValue());
                        sonDataList.add(sonLog1);
                    }
                }
                dto.setSonDataList(sonDataList);
                dtoList.add(dto);
            }
        }
        System.out.println(JsonUtil.toJson(dtoList));
    }

 读取的数据结构:

[{
	"logName": "查询用户信息",
	"date": "2021-01-11",
	"sonDataList": [{
		"logName": null,
		"date": null,
		"fieldName": "userName",
		"fieldDesc": "用户名",
		"fieldValue": "tqf_123"
	}, {
		"logName": null,
		"date": null,
		"fieldName": "age",
		"fieldDesc": "年龄",
		"fieldValue": "25"
	}, {
		"logName": null,
		"date": null,
		"fieldName": "sex",
		"fieldDesc": "性别(0-男,1-女,2-未知)",
		"fieldValue": "0"
	}]
}, {
	"logName": "获取日志列表",
	"date": "2021-01-12",
	"sonDataList": [{
		"logName": null,
		"date": null,
		"fieldName": "logName",
		"fieldDesc": "日志名称",
		"fieldValue": "查询"
	}, {
		"logName": null,
		"date": null,
		"fieldName": "logTime",
		"fieldDesc": "日志时间",
		"fieldValue": "2021-10-01 14:25:14"
	}, {
		"logName": null,
		"date": null,
		"fieldName": "logUser",
		"fieldDesc": "查询人姓名",
		"fieldValue": "王杰"
	}]
}, {
	"logName": "根据ID查询详情",
	"date": "2021-01-13",
	"sonDataList": [{
		"logName": null,
		"date": null,
		"fieldName": "total",
		"fieldDesc": "总数",
		"fieldValue": "25"
	}]
}, {
	"logName": "查询最新数据",
	"date": "2021-01-15",
	"sonDataList": [{
		"logName": null,
		"date": null,
		"fieldName": "time",
		"fieldDesc": "时间",
		"fieldValue": "2021-10-01 14:25:18"
	}, {
		"logName": null,
		"date": null,
		"fieldName": "total",
		"fieldDesc": "数据条数",
		"fieldValue": "35"
	}]
}]
LogDTO实体类
package com.shucha.deveiface.biz.test;

import lombok.Data;

import java.util.List;

/**
 * @author tqf
 * @Description
 * @Version 1.0
 * @since 2022-01-25 09:57
 */
@Data
public class LogDTO {

    /**
     * 日志名称
     */
    private String logName;

    /**
     * 日志时间
     */
    private String date;

    /**
     * 对应的子类列表数据
     */
    private List<Log> sonDataList;
}

5、 导出excel合并单元格

 5.1 要导出的数据2022.xls文件内容

日志名称日志时间字段名字段描述字段值
根据ID查询详情2021-01-13total总数25
根据ID查询详情2021-01-13total总数25
根据ID查询详情2021-01-13total总数25
获取日志列表2021-01-12logName日志名称查询
获取日志列表2021-01-12logTime日志时间2021-10-01 14:25:14
获取日志列表2021-01-12logUser查询人姓名王杰
查询用户信息2021-01-11userName用户名tqf_123
查询用户信息2021-01-11age年龄25
查询用户信息2021-01-11sex性别(0-男,1-女,2-未知)0

5.2 执行代码调试导入文件 

public void mergeExcel(){
        String fileName = "D:/2022.xls";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish
        List<Log> list = EasyExcel.read(fileName).head(Log.class).sheet().doReadSync();
        //需要合并的列
        int[] mergeColumeIndex = {0,1};
        // 从第二行后开始合并
        int mergeRowIndex = 1;
        EasyExcel.write("D:/test.xls", Log.class).sheet().registerWriteHandler(new ExcelFillCellMergeStrategy(mergeRowIndex, mergeColumeIndex)).doWrite(list);
    }

导出的excel文件格式 

日志名称日志时间字段名字段描述字段值
根据ID查询详情2021-01-13total总数25
total总数25
total总数25
获取日志列表2021-01-12logName日志名称查询
logTime日志时间2021-10-01 14:25:14
logUser查询人姓名王杰
查询用户信息2021-01-11userName用户名tqf_123
age年龄25
sex性别(0-男,1-女,2-未知)0

5.3 合并指定列的工具类

package com.shucha.deveiface.biz.test;

import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import lombok.Data;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;

import java.util.List;

/**
 * 单元格合并
 *
 * @author Jamin
 * @date 2020/11/9 11:35
 */
@Data
public class ExcelFillCellMergeStrategy implements CellWriteHandler {
    /**
     * 合并字段的下标
     */
    private int[] mergeColumnIndex;
    /**
     * 合并几行
     */
    private int mergeRowIndex;

    public ExcelFillCellMergeStrategy() {
    }

    public ExcelFillCellMergeStrategy(int mergeRowIndex, int[] mergeColumnIndex) {
        this.mergeRowIndex = mergeRowIndex;
        this.mergeColumnIndex = mergeColumnIndex;
    }

    @Override
    public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,
                                 Head head, Integer integer, Integer integer1, Boolean aBoolean) {

    }

    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell,
                                Head head, Integer integer, Boolean aBoolean) {

    }

    @Override
    public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
                                       CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) {

    }

    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
                                 List<CellData> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {
        //当前行
        int curRowIndex = cell.getRowIndex();
        //当前列
        int curColIndex = cell.getColumnIndex();

        if (curRowIndex > mergeRowIndex) {
            for (int i = 0; i < mergeColumnIndex.length; i++) {
                if (curColIndex == mergeColumnIndex[i]) {
                    mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex);
                    break;
                }
            }
        }
    }

    private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {
        //获取当前行的当前列的数据和上一行的当前列列数据,通过上一行数据是否相同进行合并
        Object curData = cell.getCellTypeEnum() == CellType.STRING ? cell.getStringCellValue() :
                cell.getNumericCellValue();
        Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);
        Object preData = preCell.getCellTypeEnum() == CellType.STRING ? preCell.getStringCellValue() :
                preCell.getNumericCellValue();
        // 比较当前行的第一列的单元格与上一行是否相同,相同合并当前单元格与上一行
        //
        if (curData.equals(preData)) {
            Sheet sheet = writeSheetHolder.getSheet();
            List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
            boolean isMerged = false;
            for (int i = 0; i < mergeRegions.size() && !isMerged; i++) {
                CellRangeAddress cellRangeAddr = mergeRegions.get(i);
                // 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元
                if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) {
                    sheet.removeMergedRegion(i);
                    cellRangeAddr.setLastRow(curRowIndex);
                    sheet.addMergedRegion(cellRangeAddr);
                    isMerged = true;
                }
            }
            // 若上一个单元格未被合并,则新增合并单元
            if (!isMerged) {
                CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex,
                        curColIndex);
                sheet.addMergedRegion(cellRangeAddress);
            }
        }
    }
}

5.4 导出的实体类

package com.shucha.deveiface.biz.test;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import lombok.Data;

/**
 * @author tqf
 * @Description
 * @Version 1.0
 * @since 2022-01-18 15:03
 */
@Data

public class Log {

    /**
     * 日志名称
     */
    @ColumnWidth(20)
    @ExcelProperty(value = "日志名称", index = 0)
    // @ExcelProperty(value = {"资源基本信息", "序号"},index = 0)
    private String logName;

    /**
     * 日志时间
     */
    @ColumnWidth(20)
    @ExcelProperty(value = "日志时间", index = 1)
    @DateTimeFormat("yyyy-MM-dd HH:mm:ss")
    private String date;

    /**
     * 字段名
     */
    @ColumnWidth(20)
    @ExcelProperty(value = "字段名", index = 2)
    private String fieldName;

    /**
     * 字段描述
     */
    @ColumnWidth(20)
    @ExcelProperty(value = "字段描述", index = 3)
    private String fieldDesc;

    /**
     * 字段值
     */
    @ColumnWidth(20)
    @ExcelProperty(value = "字段值", index = 4)
    private String fieldValue;
}

5.5 实际的控制层调用代码

/**
     * 下载合并单元格
     * @param response
     * @throws IOException
     */
    @GetMapping("/downLoadhb")
    public void downLoadhb(HttpServletResponse response) throws IOException {
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        String fileName1 = URLEncoder.encode("生成的excel文件,合并单元格", "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName1 + ".xls");
        ServletOutputStream output = response.getOutputStream();
        // ExcelWriter writer = new ExcelWriter(output, ExcelTypeEnum.XLS, true);
        // Sheet sheet = new Sheet(1, 0, ResItemExcelVo.class);
        // sheet.setSheetName("第一页");

        String fileName = "D:/2022.xls";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish
        List<Log> list = EasyExcel.read(fileName).head(Log.class).sheet().doReadSync();
        for (Log data : list) {
            log.info("读取到数据:{}", JSON.toJSONString(data));
        }
        List<LogDTO> dtoList = new ArrayList<>();
        int i = 0;
        for (Log data : list) {
            // 判断日志名称不为空就是主数据
            LogDTO dto = new LogDTO();
            i++;
            if(StringUtil.isNotBlank(data.getLogName())) {
                dto.setLogName(data.getLogName());
                dto.setDate(data.getDate());
                // 获取子类
                List<Log> sonDataList = new ArrayList<>();
                Log sonLog = new Log();
                sonLog.setFieldName(data.getFieldName());
                sonLog.setFieldDesc(data.getFieldDesc());
                sonLog.setFieldValue(data.getFieldValue());
                sonDataList.add(sonLog);
                for (int j = i;j<list.size();j++) {
                    if(StringUtil.isNotBlank(list.get(j).getLogName())) {
                        break;
                    }
                    if(StringUtil.isBlank(list.get(j).getLogName())) {
                        Log sonLog1 = new Log();
                        sonLog1.setFieldName(list.get(j).getFieldName());
                        sonLog1.setFieldDesc(list.get(j).getFieldDesc());
                        sonLog1.setFieldValue(list.get(j).getFieldValue());
                        sonDataList.add(sonLog1);
                    }
                }
                dto.setSonDataList(sonDataList);
                dtoList.add(dto);
            }
        }
        //需要合并的列
        int[] mergeColumeIndex = {0,1};
        // 从第二行后开始合并
        int mergeRowIndex = 1;
        EasyExcel.write(response.getOutputStream(),Log.class).sheet().registerWriteHandler(new ExcelFillCellMergeStrategy(mergeRowIndex, mergeColumeIndex)).doWrite(list);
        output.flush();
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

码奴生来只知道前进~

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值