eec icon indicating copy to clipboard operation
eec copied to clipboard

【功能增强】状态转换成中文

Open xiaoxpai opened this issue 2 years ago • 14 comments

需求:审批有很多状态是数字类型的,转成对应的中文名称导出

类似:

看了一下easyexcel在实体注解中存在注解且可以自定义 例如:

public class ConverterData {
   public @interface ExcelProperty {

    /**
     * The name of the sheet header.
     *
     * <p>
     * write: It automatically merges when you have more than one head
     * 工作表标题的名称。
     * write:当有多个头时,自动合并
     * read:当有多个头时,取最后一个
     * 返回:sheet header 的名称
     * <p>
     * read: When you have multiple heads, take the last one
     *
     * @return The name of the sheet header
     */
    String[] value() default {""};

    /**
     * Index of column
     *
     * Read or write it on the index of column, If it's equal to -1, it's sorted by Java class.
     *
     * priority: index &gt; order &gt; default sort
     *
     * @return Index of column
     */
    int index() default -1;

    /**
     * Defines the sort order for an column.
     *
     * priority: index &gt; order &gt; default sort
     *
     * @return Order of column
     */
    int order() default Integer.MAX_VALUE;

    /**
     * Force the current field to use this converter.
     *
     * @return Converter
     */
    Class<? extends Converter<?>> converter() default AutoConverter.class;

    /**
     *
     * default @see com.alibaba.excel.util.TypeUtil if default is not meet you can set format
     *
     * @return Format string
     * @deprecated please use {@link com.alibaba.excel.annotation.format.DateTimeFormat}
     */
    @Deprecated
    String format() default "";
}
}

目前eec版本不支持

public @interface ExcelColumn {
    String value() default "";

    boolean share() default false;

    HeaderComment comment() default @HeaderComment;

    String format() default "";

    boolean wrapText() default false;

    int colIndex() default -1;

    double maxWidth() default -1.0;

    boolean hide() default false;
}

xiaoxpai avatar Oct 16 '23 07:10 xiaoxpai

EEC有两种方式实现此类需求,一时使用ConversionProcessor,另一种是实现自定义ICellValueAndStyle,可以对任意类型进行转换。

  1. 指定表头并设置Conversion
 String[] typeName = { "待提交", "审核中", "一审", "二审", "终审" };
new Workbook()
    .addSheet(new ListSheet<>(new Column("申请单", "id")
        , new Column("抬头", "title")
        , new Column("审核状态", "status", int.class,  n -> typeName[(int) n])) )// 将状态0,1,2,3,4转为"待提交", "审核中", "一审", "二审", "终审"
    .writeTo(Paths.get("./"));
  1. 自定义ICellValueAndStyle
new Workbook()
    .addSheet(new ListSheet<>("请款审核").setCellValueAndStyle(new XMLCellValueAndStyle() {
        @Override
        public void setCellValue(int row, Cell cell, Object e, Column hc, Class<?> clazz, boolean hasProcessor) {
            // 对审核状态特殊处理
            if ("status".equals(hc.key)) {
                cell.setSv(typeName[(int) e]);
            } else super.setCellValue(row, cell, e, hc, clazz, hasProcessor);
        }
    })).writeTo(Paths.get("./"));

wangguanquan avatar Oct 16 '23 07:10 wangguanquan

第三种是在Java Bean中增加一个获取文本的方法并在方法上添加@ExcelColumn注解,如果不希望被JSON序列化还需要加上@JsonIgnore ··· @ExcelColumn public String getStatusTxt() { return "审核中"; // 根据status的值返回文件 } ···

wangguanquan avatar Oct 16 '23 08:10 wangguanquan

EEC有两种方式实现此类需求,一时使用ConversionProcessor,另一种是实现自定义ICellValueAndStyle,可以对任意类型进行转换。

  1. 指定表头并设置Conversion
 String[] typeName = { "待提交", "审核中", "一审", "二审", "终审" };
new Workbook()
    .addSheet(new ListSheet<>(new Column("申请单", "id")
        , new Column("抬头", "title")
        , new Column("审核状态", "status", int.class,  n -> typeName[(int) n])) )// 将状态0,1,2,3,4转为"待提交", "审核中", "一审", "二审", "终审"
    .writeTo(Paths.get("./"));
  1. 自定义ICellValueAndStyle
new Workbook()
    .addSheet(new ListSheet<>("请款审核").setCellValueAndStyle(new XMLCellValueAndStyle() {
        @Override
        public void setCellValue(int row, Cell cell, Object e, Column hc, Class<?> clazz, boolean hasProcessor) {
            // 对审核状态特殊处理
            if ("status".equals(hc.key)) {
                cell.setSv(typeName[(int) e]);
            } else super.setCellValue(row, cell, e, hc, clazz, hasProcessor);
        }
    })).writeTo(Paths.get("./"));

首先,感谢您有时间回复提出的问题 ,我经测试第一、第二种测试 确实可以实现,但是可能不是很友好,如果有同样业务,代码会冗余,就是假设有5个类似业务,就需要写5份类似的代码

xiaoxpai avatar Oct 19 '23 01:10 xiaoxpai

如果习惯使用注解可以参考WIKI自定义注解,只需要在createColumn方里下添加Converter转换即可,具体的Converter实现类需要实现ConversionProcessor接口

示例代码如下

// 复制wiki中的代码然后在createColumn中添加如下代码,放在`5. 列宽`下面即可

// 6. Converter
Class<? extends Converter<?>> clazz = ec.converter();
if (!AutoConverter.class.isAssignableFrom(clazz) && ConversionProcessor.class.isAssignableFrom(clazz)) {
    try {
        column.setProcessor((ConversionProcessor) clazz.newInstance());
    } catch (InstantiationException | IllegalAccessException e) {
        e.printStackTrace();
    }
}

这是Converter代码,需要实现ConversionProcessor接口并实现转换

public class StatusConverter implements com.alibaba.excel.converters.Converter<Integer>, ConversionProcessor {
    static final String[] typeName = { "待提交", "审核中", "一审", "二审", "终审" };

    @Override
    public Object conversion(Object v) {
        if (v == null) return null;
        int n = Integer.parseInt(v.toString());
        return n >= 0 && n < typeName.length ? typeName[n] : null;
    }
}

使用以上代码就可以实现自定义Converter注解

测试代码如下

// 测试对象
public static class Entry {
    @com.alibaba.excel.annotation.ExcelProperty("审核编号")
    private String no;

    @com.alibaba.excel.annotation.ExcelProperty(value = "审核状态", converter = StatusConverter.class) // <- 这里指定StatusConverter
    private Integer status;

    public static List<Entry > randomTestData() {
        List<Entry > list = new ArrayList<>(10);
        for (int i = 0; i < 10; i++) {
            Entry e = new Entry ();
            e.no = getRandomString();
            e.status = random.nextInt(4);
            list.add(e);
        }
        return list;
    }
}

// 导出代码不变
new Workbook().addSheet(new EasyExcelSupportListSheet<>(Entry.randomTestData())).writeTo(Paths.get("d:/自定义转换器.xlsx"));

你可以使用自定义注解完全替换掉EEC的注解,这样就不需要切换现有实体中的注解了

wangguanquan avatar Oct 19 '23 04:10 wangguanquan

如果习惯使用注解可以参考WIKI自定义注解,只需要在createColumn方里下添加Converter转换即可,具体的Converter实现类需要实现ConversionProcessor接口

示例代码如下

// 复制wiki中的代码然后在createColumn中添加如下代码,放在`5. 列宽`下面即可

// 6. Converter
Class<? extends Converter<?>> clazz = ec.converter();
if (!AutoConverter.class.isAssignableFrom(clazz) && ConversionProcessor.class.isAssignableFrom(clazz)) {
    try {
        column.setProcessor((ConversionProcessor) clazz.newInstance());
    } catch (InstantiationException | IllegalAccessException e) {
        e.printStackTrace();
    }
}

这是Converter代码,需要实现ConversionProcessor接口并实现转换

public class StatusConverter implements com.alibaba.excel.converters.Converter<Integer>, ConversionProcessor {
    static final String[] typeName = { "待提交", "审核中", "一审", "二审", "终审" };

    @Override
    public Object conversion(Object v) {
        if (v == null) return null;
        int n = Integer.parseInt(v.toString());
        return n >= 0 && n < typeName.length ? typeName[n] : null;
    }
}

使用以上代码就可以实现自定义Converter注解

测试代码如下

// 测试对象
public static class Entry {
    @com.alibaba.excel.annotation.ExcelProperty("审核编号")
    private String no;

    @com.alibaba.excel.annotation.ExcelProperty(value = "审核状态", converter = StatusConverter.class) // <- 这里指定StatusConverter
    private Integer status;

    public static List<Entry > randomTestData() {
        List<Entry > list = new ArrayList<>(10);
        for (int i = 0; i < 10; i++) {
            Entry e = new Entry ();
            e.no = getRandomString();
            e.status = random.nextInt(4);
            list.add(e);
        }
        return list;
    }
}

// 导出代码不变
new Workbook().addSheet(new EasyExcelSupportListSheet<>(Entry.randomTestData())).writeTo(Paths.get("d:/自定义转换器.xlsx"));

你可以使用自定义注解完全替换掉EEC的注解,这样就不需要切换现有实体中的注解了

EasyExcelSupportListSheet这个类 , 不是eec的内部类吗, 我这边没有这个类,是版本的问题吗

image

xiaoxpai avatar Oct 21 '23 01:10 xiaoxpai

是一个自定义类并不是eec的代码,复制wiki中的代码新建一个任意名的类即可

在 2023年10月19日,09:34,xiaoxpai @.***> 写道:

EEC有两种方式实现此类需求,一时使用ConversionProcessor,另一种是实现自定义ICellValueAndStyle,可以对任意类型进行转换。

指定表头并设置Conversion String[] typeName = { "待提交", "审核中", "一审", "二审", "终审" }; new Workbook() .addSheet(new ListSheet<>(new Column("申请单", "id") , new Column("抬头", "title") , new Column("审核状态", "status", int.class, n -> typeName[(int) n])) )// 将状态0,1,2,3,4转为"待提交", "审核中", "一审", "二审", "终审" .writeTo(Paths.get("./")); 自定义ICellValueAndStyle new Workbook() .addSheet(new ListSheet<>("请款审核").setCellValueAndStyle(new XMLCellValueAndStyle() { @Override public void setCellValue(int row, Cell cell, Object e, Column hc, Class<?> clazz, boolean hasProcessor) { // 对审核状态特殊处理 if ("status".equals(hc.key)) { cell.setSv(typeName[(int) e]); } else super.setCellValue(row, cell, e, hc, clazz, hasProcessor); } })).writeTo(Paths.get("./")); 首先,感谢您有时间回复提出的问题 ,我经测试第一、第二种测试 确实可以实现,但是可能不是很友好,如果有同样业务,代码会冗余,就是假设有5个类似业务,就需要写5份类似的代码

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.

wangguanquan avatar Oct 21 '23 02:10 wangguanquan

很抱歉,由于工作原因,没有及时回复,还是非常感谢您的回复

我根据您的步骤,复制wiki中的代码新建一个 类 但实际上有一个构造器的问题,

step1: 新建了一个类,

public class SupportEasyExcelSheet   {

    public static class EasyExcelSupportListSheet<T> extends ListSheet<T> {


        /**
         * 过滤不需要导出的字段
         *
         * @param ao {@link T}对象定义的所有{@link java.lang.reflect.Field}和 {@link java.lang.reflect.Method}
         * @return true: 忽略该字段(该字段不导出)
         */
        @Override
        protected boolean ignoreColumn(AccessibleObject ao) {
            // Easyexcel使用注解com.alibaba.excel.annotation.ExcelIgnore来标记忽略字段
            return ao.getAnnotation(ExcelIgnore.class) != null;
        }

        /**
         * 创建列
         *
         * @param ao {@link T}对象定义的所有{@link java.lang.reflect.Field}和 {@link java.lang.reflect.Method}
         * @return 列定义,可以返回null,表示忽略该字段
         */
        @Override
        protected ListSheet.EntryColumn createColumn(AccessibleObject ao) {
            // 1. 过滤掉需要忽略的字段
            if (ignoreColumn(ao)) return null;

            ao.setAccessible(true);
            // 2. Easyexcel使用com.alibaba.excel.annotation.ExcelProperty标记,这里可以替换为任意的定义的注解,当然需要包含一些列头的信息
            ExcelProperty ec = ao.getAnnotation(ExcelProperty.class);
            if (ec != null) {
                ListSheet.EntryColumn column = new ListSheet.EntryColumn(ec.value()[0], EMPTY);
            /*
             Easyexcel格式化有3种方式
             一是放在{@code ExcelProperty#format} 注解
             二是使用DateTimeFormat和NumberFormat注解
             所以这里要兼容这3种方式

             EEC统一使用NumFmt
             */
                DateTimeFormat dateTimeFormat = ao.getAnnotation(DateTimeFormat.class);
                NumberFormat numberFormat = ao.getAnnotation(NumberFormat.class);
                // 3. 格式化(支持日期和数字)
                if (isNotEmpty(ec.format())) {
                    column.setNumFmt(ec.format());
                } else if (dateTimeFormat != null) {
                    column.setNumFmt(dateTimeFormat.value());
                } else if (numberFormat != null) {
                    column.setNumFmt(numberFormat.value());
                }
                // 4. 列位置
                if (ec.index() > -1) {
                    column.setColIndex(ec.index());
                }
                // 5. 列宽
                ColumnWidth columnWidth = ao.getAnnotation(ColumnWidth.class);
                if (columnWidth != null && columnWidth.value() > 0) {
                    column.width = columnWidth.value();
                }
                // 6. Converter
                Class<? extends Converter<?>> clazz = ec.converter();
                if (!AutoConverter.class.isAssignableFrom(clazz) && ConversionProcessor.class.isAssignableFrom(clazz)) {
                    try {
                        column.setProcessor((ConversionProcessor) clazz.newInstance());
                    } catch (InstantiationException | IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
                // TODO 其它属性

                return column;
            }
            return null;
        }
    }
}

step2 测试类

image

xiaoxpai avatar Oct 24 '23 08:10 xiaoxpai

只需要重载所有父类构造器即可,那部分代码属于基础代码无特殊意义所以wiki里没有写。比如SupportEasyExcelSheet(Column...headers) { super(headers); }

wiki已更新

wangguanquan avatar Oct 24 '23 09:10 wangguanquan

新建java文件EasyExcelSupportListSheet.java,内容如下。支持多行表头,format和Convert转换

import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.format.NumberFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.converters.AutoConverter;
import com.alibaba.excel.converters.Converter;
import org.ttzero.excel.processor.ConversionProcessor;

import java.lang.reflect.AccessibleObject;
import java.util.List;

import static org.ttzero.excel.util.StringUtil.isNotEmpty;

/**
 * 支持EasyExcel注解
 *
 * @param <T>
 */
public class EasyExcelSupportListSheet<T> extends ListSheet<T> {
    public EasyExcelSupportListSheet() {
    }

    public EasyExcelSupportListSheet(String name) {
        super(name);
    }

    public EasyExcelSupportListSheet(Column... columns) {
        super(columns);
    }

    public EasyExcelSupportListSheet(String name, Column... columns) {
        super(name, columns);
    }

    public EasyExcelSupportListSheet(String name, WaterMark waterMark, Column... columns) {
        super(name, waterMark, columns);
    }

    public EasyExcelSupportListSheet(List<T> data) {
        super(data);
    }

    public EasyExcelSupportListSheet(String name, List<T> data) {
        super(name, data);
    }

    public EasyExcelSupportListSheet(List<T> data, Column... columns) {
        super(data, columns);
    }

    public EasyExcelSupportListSheet(String name, List<T> data, Column... columns) {
        super(name, data, columns);
    }

    public EasyExcelSupportListSheet(List<T> data, WaterMark waterMark, Column... columns) {
        super(data, waterMark, columns);
    }

    public EasyExcelSupportListSheet(String name, List<T> data, WaterMark waterMark, Column... columns) {
        super(name, data, waterMark, columns);
    }

    /**
     * 过滤不需要导出的字段
     *
     * @param ao {@link T}对象定义的所有{@link java.lang.reflect.Field}和 {@link java.lang.reflect.Method}
     * @return true: 忽略该字段(该字段不导出)
     */
    @Override
    protected boolean ignoreColumn(AccessibleObject ao) {
        // Easyexcel使用注解com.alibaba.excel.annotation.ExcelIgnore来标记忽略字段
        // 可以是任意注解比如JsonIgnore也不能导出
        return ao.getAnnotation(ExcelIgnore.class) != null;
    }

    /**
     * 创建列
     *
     * @param ao {@link T}对象定义的所有{@link java.lang.reflect.Field}和 {@link java.lang.reflect.Method}
     * @return 列定义,可以返回null,表示忽略该字段
     */
    @Override
    protected ListSheet.EntryColumn createColumn(AccessibleObject ao) {
        // 1. 过滤掉需要忽略的字段
        if (ignoreColumn(ao)) return null;

        ao.setAccessible(true);
        // 2. Easyexcel使用com.alibaba.excel.annotation.ExcelProperty标记,这里可以替换为任意的定义的注解,当然需要包含一些列头的信息
        ExcelProperty ec = ao.getAnnotation(ExcelProperty.class);
        if (ec != null) {
            String[] columnNames = ec.value();
            ListSheet.EntryColumn column = null;
            // 多行表头
            if (columnNames.length > 1) {
                for (String cn : columnNames) {
                    ListSheet.EntryColumn col = new ListSheet.EntryColumn(cn);
                    if (column == null) {
                        column = col;
                    } else {
                        column.addSubColumn(col);
                    }
                }
            }
            // 单行表头
            else if (columnNames.length == 1) {
                column = new ListSheet.EntryColumn(columnNames[0]);
            } else {
                column = new ListSheet.EntryColumn("");
            }
            /*
             Easyexcel格式化有3种方式
             一是放在{@code ExcelProperty#format} 注解
             二是使用DateTimeFormat和NumberFormat注解
             所以这里要兼容这3种方式

             EEC统一使用NumFmt
             */
            DateTimeFormat dateTimeFormat = ao.getAnnotation(DateTimeFormat.class);
            NumberFormat numberFormat = ao.getAnnotation(NumberFormat.class);
            // 3. 格式化(支持日期和数字)
            if (isNotEmpty(ec.format())) {
                column.setNumFmt(ec.format());
            } else if (dateTimeFormat != null) {
                column.setNumFmt(dateTimeFormat.value());
            } else if (numberFormat != null) {
                column.setNumFmt(numberFormat.value());
            }
            // 4. 列位置
            if (ec.index() > -1) {
                column.setColIndex(ec.index());
            }
            // 5. 列宽
            ColumnWidth columnWidth = ao.getAnnotation(ColumnWidth.class);
            if (columnWidth != null && columnWidth.value() > 0) {
                column.width = columnWidth.value();
            }
            // 6. Convert
            Class<? extends Converter<?>> clazz = ec.converter();
            if (!AutoConverter.class.isAssignableFrom(clazz) && ConversionProcessor.class.isAssignableFrom(clazz)) {
                try {
                    column.setProcessor((ConversionProcessor) clazz.newInstance());
                } catch (InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }

            // TODO 其它属性

            return column;
        }
        return null;
    }
}

wangguanquan avatar Oct 24 '23 12:10 wangguanquan

首先表示一下感谢,很幸运能够得到您的帮助,祝愿您在事业上持续取得成功

自己代码测试之后,可以实现,这里留个脚印,希望可以帮助到更多的人🎃😀

...
    @com.alibaba.excel.annotation.ExcelProperty(value = "审核状态", converter = StatusConverter.class) // <- 这里指定StatusConverter
    private Integer status;

export

 ...

  new Workbook()
            .addSheet(new EasyExcelSupportListSheet(mc)).writeTo(Paths.get("e:/excel"));

image

xiaoxpai avatar Oct 30 '23 07:10 xiaoxpai

后续会在下一个release把这个功能加进去吗,如果您有业务时间的话

xiaoxpai avatar Oct 30 '23 07:10 xiaoxpai

可以考虑,比起注解更推荐使用指定表头,后者没有侵入性并且与实体解耦。 这个只是建议,实际开的时候主要考虑业务现有框架和开发规范。

另外,像状态为类枚举值可以设置字符串共享和单元格居中显示。

在EasyExcelSupportListSheet.java文件的TODO 其它属性之后添加代码

// TODO 其它属性
// EEC的动态样式
StyleProcessor<?> styleProcessor = getDesignStyle(ao.getAnnotation(StyleDesign.class));
if (styleProcessor != null) {
    for (Column col : column.toArray()) {
        col.setStyleProcessor(styleProcessor);
    }
}

这样就可以使用EEC的动态样式了

/**
 * 单元格居中显示,无论值是多少都适用
 */
public class AlignCenter implements StyleProcessor<Object> {
    @Override public int build(Object o, int style, Styles sst) {
        return sst.modifyHorizontal(style, Horizontals.CENTER);
    }
}

/**
 * 驳回状态整行标红
 */
public class StatusRejectStyle implements StyleProcessor<Entry> {
    Fill readFill = new Fill(PatternType.solid, Color.RED);
    @Override public int build(Entry o, int style, Styles sst) {
        // 审核状态为9:驳回
        if (o.getStatus().equals(9)) {
            return sst.modifyFill(style, readFill); 
        }
        return style;
    }
}


// 导出实体
@StyleDesign(using = StatusRejectStyle.class) // <- 作用于对象,影响整行样式
public static class Entry {
    @com.alibaba.excel.annotation.ExcelProperty("名称")
    private String no;

    @StyleDesign(using = AlignCenter.class) // <- 作用于单个字段,影响单列样式
    @com.alibaba.excel.annotation.ExcelProperty(value = "审核状态", converter = StatusConverter.class)
    private Integer status;
}

EEC有较好的扩展性,熟悉之后你也可以根据自己的需求和开发习惯进行扩展

wangguanquan avatar Oct 30 '23 10:10 wangguanquan

非常感激您所花费的时间和精力来理解我的问题并提供解决方案🤝🤝🤝

测试有效,感谢提供一个新特性,这里留个脚印,希望可以帮助到更多的人🎃😀

image

xiaoxpai avatar Oct 31 '23 01:10 xiaoxpai

v0.5.12 已支持双向转换 动态转换

@ExcelColumn(converter = StatusConvert.class) // <- 指定转换类
private int status;

// 转换器实现
public static class StatusConvert implements Converter<Integer> {
    final String[] statusDesc = { "未开始", "进行中", "完结", "中止" };

    /**
     * Excel读取的文本转为状态码
     * 
     * @param v Excel原始值
     * @return 状态码
     */
    @Override
    public Integer reversion(String v) {
        for (int i = 0; i < statusDesc.length; i++) {
            if (statusDesc[i].equals(v)) {
                return i;
            }
        }
        return null;
    }

    /**
     * 状态码转为文本
     * 
     * @param v 原始值
     * @return 状态说明
     */
    @Override
    public Object conversion(Object v) {
        return v != null ? statusDesc[(int) v] : null;
    }
}

wangguanquan avatar Nov 27 '23 02:11 wangguanquan