6.1 文件与导入导出(改)

导入导出在一定程度上是企业级软件和效率工具(office工具)的桥梁

文件的上传下载以及业务数据的导入导出是企业级软件一个比较常规的需求,甚至是巨量的需求。业务有管理需要一般都伴随有导入导出需求,导入导出在一定程度上是企业级软件和效率工具(office工具)的桥梁。oinone的文件模块就提供了通用的导入导出实现方案,以简单、一致、可扩展为目标,简单是快速入门,一致是用户操作感知一致、可扩展是满足用户最大化的自定义需求。

下图为文件导入导出的实现示意图,大家可以做一个整体了解

6.1 文件与导入导出(改)

图6-1-1 文件导入导出实现示意图

一、基础能力

准备工作

  1. pamirs-demo-api的pom文件中引入pamirs-file2-api包依赖
        <dependency>
            <groupId>pro.shushi.pamirs.core</groupId>
            <artifactId>pamirs-file2-api</artifactId>
        </dependency>

图6-1-2 引入pamirs-file2-api包依赖

  1. DemoModule增加对FileModule的依赖
@Module(dependencies = {FileModule.MODULE_MODULE})

图6-1-3 DemoModule增加对FileModule的依赖

  1. pamirs-demo-boot的pom文件中引入pamirs-file2-core包依赖
        <dependency>
            <groupId>pro.shushi.pamirs.core</groupId>
            <artifactId>pamirs-file2-core</artifactId>
        </dependency>

图6-1-4 启动工程引入pamirs-file2-core包依赖

  1. pamirs-demo-boot的application-dev.yml文件中增加配置pamirs.boot.modules增加file,即在启动模块中增加file模块
pamirs:
    boot:
    modules:
        - file

图6-1-5 pamirs-demo-boot的application-dev.yml文件中增加配置

  1. pamirs-demo-boot的application-dev.yml文件中增加oss的配置。更多有关文件相关配置详见4.1.1【模块之yml文件结构详解】一文
cdn:
    oss:
    name: 阿里云
    type: OSS
    bucket: demo
    uploadUrl: #换成自己的oss上传服务地址
    downUrl: #换成自己的oss下载服务地址
    accessKeyId: #阿里云oss的accessKeyId
    accessKeySecret: #阿里云oss的accessKeySecret
    mairDir: upload/demo #换成自己的目录
    validTime: 360000
    timeout: 60000
    active: true
    referer: www.shushi.pro

图6-1-6 application-dev.yml文件中增加oss的配置

  1. 其他文件系统支持

a. 文件系统类型

类型 服务
OSS 阿里云
UPYUN 又拍云
MINIO Minio
HUAWEI_OBS 华为云
LOCAL 本地文件存储

表6-1-1 支持的文件系统类型

b. OSS配置示例

ⅰ. 华为云OBS

cdn:
  oss:
    name: 华为云
    type: HUAWEI_OBS
    bucket: pamirs #(根据实际情况修改)
    uploadUrl: obs.cn-east-2.myhuaweicloud.com
    downloadUrl: obs.cn-east-2.myhuaweicloud.com
    accessKeyId: 你的accessKeyId
    accessKeySecret: 你的accessKeySecret
    # 根据实际情况修改
    mainDir: upload/
    validTime: 3600000
    timeout: 600000
    active: true
    allowedOrigin: http://192.168.95.31:8888,https://xxxx.xxxxx.com
    referer:

图6-1-7 OBS的配置说明

ⅱ. MINIO

cdn:
  oss:
    name: minio
    type: MINIO
    bucket: pamirs #(根据实际情况修改)
    uploadUrl: http://192.168.243.6:32190 #(根据实际情况修改)
    downloadUrl: http://192.168.243.6:9000 #(根据实际情况修改)
    accessKeyId: 你的accessKeyId
    accessKeySecret: 你的accessKeySecret
    # 根据实际情况修改
    mainDir: upload/
    validTime: 3600000
    timeout: 600000
    active: true
    referer:
    localFolderUrl:

图6-1-8 MINIO的配置说明

ⅲ. 又拍云

cdn:
  oss:
    name: 又拍云
    type: UPYUN
    bucket: pamirs #(根据实际情况修改)
    uploadUrl: v0.api.upyun.com
    downloadUrl: v0.api.upyun.com
    accessKeyId: 你的accessKeyId
    accessKeySecret: 你的accessKeySecret
    # 根据实际情况修改
    mainDir: upload/
    validTime: 3600000
    timeout: 600000
    active: true
    referer: 

图6-1-9 又拍云的配置说明

ⅳ. 本地文件存储

cdn:
  oss:
    name: 本地文件NG系统
    type: LOCAL
    bucket: pamirs #(根据实际情况修改)
    # uploadUrl 这个是Oinone后端服务地址和端口
    uploadUrl: http://127.0.0.1:8091
    # downloadUrl前端地址,即直接映射在nginx的静态资源的路径和端口
    downloadUrl: http://127.0.0.1:8081
    validTime: 3600000
    timeout: 600000
    active: true
    referer:
    # 本地Nginx静态资源目录
    localFolderUrl: /Users/oinone/nginx/html/designer/static

图6-1-10 本地文件的配置说明

文件上传

文件上传下载是常用功能,也是导入导出功能的基础。接下的例子,我们来看看Oinone是如何玩转文件上传的功能的

@Field.String
@Field(displayName = "图片" ,multi = true)
@UxForm.FieldWidget(@UxWidget(widget = "Upload"))
private List<String> imgUrls;

Step1 修改PetTalent模型

为PetTalent模型增加一个字段imgUrls ,用于保存上传后的文件地址。同时定义Form视图的时候改字段的前端展示widget为“Upload”

<field data="imgUrls" widget="Upload"/>

Step2 修改PetTalent的Form视图

因为前面教程中为PetTalent的Form视图写了自定义视图,所以这里需要手工加上字段配置,不然展示不出来,同时也要加上widget="Upload"的配置,因为自定义视图不会再去读字段上定义的UxWidget,字段上的UxWidget只对默认视图生效。

Step3 重启看效果

image.png

默认导入导出功能

因为前面我们在Demo的启动工程中加入对pamirs-file2-core的jar包依赖同时启动模块列表中也增加了file模块,所以所有的默认表格页面都出现了默认的导入导出按钮。

image.png

但是我们发现宠物达人的表格就没有出现导入导出按钮,首先我们自定义了表格视图,而且在学习自定义Action的时候修改了表格视图的Action的填充方式为手工指定。

image.png

Step1 修改PetTalent的Table视图

修改PetTalent的自定义Table视图,把表格视图的Action的填充方式改为自动填充,或者根据平台默认前端动作增加导入、导出两个前端动作

<template slot="actions" autoFill="true">
  <!--        <action actionType="client" name="demo.doNothing" label="第一个自定义Action" />-->
  <!--        <action name="delete" label="删除" />-->
  <!--        <action name="redirectCreatePage" label="创建" />-->
</template>
<template slot="actions" autoFill="true">
  <action actionType="client" name="demo.doNothing" label="第一个自定义Action" />
  <action name="delete" label="删除" />
  <action name="redirectCreatePage" label="创建" />
<!--   <action name="$$internal_GotoListExportDialog" label="导出" />
  <action name="$$internal_GotoListImportDialog" label="导入" /> -->
    <action name="internalGotoListExportDialog" label="导出" />
    <action name="internalGotoListImportDialog" label="导入" />
</template>

Step2 重启看效果

image.png

Step3 注意点

  1. 系统生成的导入模版以默认的Form视图为准

  2. 系统生成的导出模版以默认的Table视图为准

  3. 有自定义导入导出模版后,默认模版会失效

自定义导入导出模版与逻辑

数据的导出(举例一)

  1. 通常我们只需定义一个导出模版

  2. 默认导出方法是调用模型的queryPage函数(即在日常开发中我们经常会自定义模型的queryPage函数),为了跟页面查询效果保持一致,默认导出方式实现中手工生效了hook,所以类似数据权限的hook都会生效,与页面查询逻辑一致。

  3. 如果不一致可以实现ExcelExportFetchDataExtPoint扩展点来完成,在举例二中介绍

Step1 新建PetTalentExportTemplate

新建PetTalentExportTemplate类实现ExcelTemplateInit接口。

  1. 实现ExcelTemplateInit接口,系统会自动调用generator方法,并注册该方法返回的模版定义列表,例子中定义了一个导出模版“宠物达人导出”

  2. 关联对象为集合,petShops[*].shopName

  3. 关联对象为模型,creater.name

package pro.shushi.pamirs.demo.core.init.exports;

import org.springframework.stereotype.Component;
import pro.shushi.pamirs.demo.api.model.PetTalent;
import pro.shushi.pamirs.file.api.enmu.ExcelTemplateTypeEnum;
import pro.shushi.pamirs.file.api.model.ExcelWorkbookDefinition;
import pro.shushi.pamirs.file.api.util.ExcelHelper;
import pro.shushi.pamirs.file.api.util.ExcelTemplateInit;

import java.util.Collections;
import java.util.List;

@Component
public class PetTalentExportTemplate implements ExcelTemplateInit {
    public static final String TEMPLATE_NAME ="宠物达人导出";

    @Override
    public List<ExcelWorkbookDefinition> generator() {
        //可以返回多个模版,导出的时候页面上由用户选择导出模版
        return Collections.singletonList(
                ExcelHelper.fixedHeader(PetTalent.MODEL_MODEL,TEMPLATE_NAME)
                        .createBlock(TEMPLATE_NAME,PetTalent.MODEL_MODEL)
                        .setType(ExcelTemplateTypeEnum.EXPORT)
                        .addColumn("name","名称")
                        .addColumn("annualIncome","年收入")
                        .addColumn("petShops[*].shopName","店铺名")
                        .addColumn("creater.name","创建者")
                        .build());
    }

}

Step2 重启看效果

  1. 点击【宠物达人】菜单进入宠物达人列表页,选择记录点击【导出】按钮。导出规则为

a. 如果勾选列表记录行,则只导出勾选记录

b. 如果没有勾选,则根据查询条件进行导出

image.png

  1. 在导出弹出框中选择导出模版,点击【导出】按钮提示成功,则表示导出任务被正常创建

image.png

  1. 切换换模块,进入【文件】应用,点击【导出任务】菜单查看导出任务情况。分别对应三种情况:

a. Table视图中带来条件但查询结果没有数据,则会报错

b. Table视图中带来条件或无条件但查询结果有数据,则会成功

c. Table视图中勾选了数据行,则只导出勾选数据行对应数据

image.png

数据的导出(举例二)

如果您有特殊需求,请实现ExcelExportFetchDataExtPoint扩展点来完成,用这种模式请自行控制数据权限,小心小心再小心。因为数据权限是通过hook机制来完成控制,在面向切面-拦截器一文中有说明拦截器只有在前端请求时才会默认生效,如果后端代码自行调用需要生效hook机制需要根据函数之元位指令一文手动去生效。

下面例子中为了复用了ExcelExportSameQueryPageTemplate的逻辑主要是hook的机制,ExcelExportSameQueryPageTemplate手工生效了hook,例子中继承了ExcelExportSameQueryPageTemplate类,只做返回结果的后置处理

Step1 修改PetTalentExportTemplate

package pro.shushi.pamirs.demo.core.init.exports;

import org.springframework.stereotype.Component;
import pro.shushi.pamirs.demo.api.model.PetTalent;
import pro.shushi.pamirs.file.api.context.ExcelDefinitionContext;
import pro.shushi.pamirs.file.api.enmu.ExcelTemplateTypeEnum;
import pro.shushi.pamirs.file.api.extpoint.ExcelExportFetchDataExtPoint;
import pro.shushi.pamirs.file.api.extpoint.impl.ExcelExportSameQueryPageTemplate;
import pro.shushi.pamirs.file.api.model.ExcelExportTask;
import pro.shushi.pamirs.file.api.model.ExcelWorkbookDefinition;
import pro.shushi.pamirs.file.api.util.ExcelHelper;
import pro.shushi.pamirs.file.api.util.ExcelTemplateInit;
import pro.shushi.pamirs.meta.annotation.ExtPoint;

import java.util.Collections;
import java.util.List;

@Component
public class PetTalentExportTemplate extends ExcelExportSameQueryPageTemplate implements ExcelTemplateInit , ExcelExportFetchDataExtPoint {
    public static final String TEMPLATE_NAME ="宠物达人导出";

    @Override
    public List<ExcelWorkbookDefinition> generator() {
        //可以返回多个模版,导出的时候页面上由用户选择导出模版
        return Collections.singletonList(
                ExcelHelper.fixedHeader(PetTalent.MODEL_MODEL,TEMPLATE_NAME)
                        .createBlock(TEMPLATE_NAME,PetTalent.MODEL_MODEL)
                        .setType(ExcelTemplateTypeEnum.EXPORT)
                        .addColumn("name","名称")
                        .addColumn("annualIncome","年收入")
                        .addColumn("petShops[*].shopName","店铺名")
                        .addColumn("creater.name","创建者")
                        .build());
    }

    //表达式里的常量需要使用" "包括
    @Override
    @ExtPoint.Implement(expression = "context.model == " + PetTalent.MODEL_MODEL+" && context.name == " +TEMPLATE_NAME+"" )
    public List<Object> fetchExportData(ExcelExportTask exportTask, ExcelDefinitionContext context) {
        //复用父类的权限控制等
        List<Object> result =  super.fetchExportData(exportTask,context);
        //对petTalents做一些操作
        List<PetTalent>  petTalents =  (List<PetTalent>)result.get(0);
        for(PetTalent petTalent:petTalents){
            petTalent.setName(petTalent.getName()+":被修改过");
        }
        return result;
    }
}

Step2 重启看效果

相同的操作,发现导出文件内容被特定修改过

image.png

数据的导入(举例一)

Step1 创建PetTalentImportTemplate

新建PetTalentImportTemplate类实现ExcelImportDataExtPoint和ExcelTemplateInit接口。

  1. 实现ExcelTemplateInit接口,系统会自动调用generator方法,并注册该方法返回的模版定义列表,例子中定义了一个导入模版“宠物达人导入”

a. 通过ExcelCellDefinition自定义单元可选值,并在addColumn时绑定如:.addColumn("petTalentSex",sexExcelCellDefinition)

b. 通过addUnique,设置导入时的唯一key,相同key同时相连行会进行合并操作

  1. 实现ExcelImportDataExtPoint的importData扩展点,实现自定义的导入逻辑

a. 声明扩展点的生效规则:@ExtPoint.Implement(expression = "importContext.definitionContext.model == \" + PetTalent.MODEL_MODEL+"\ && importContext.definitionContext.name == \" +TEMPLATE_NAME+"\" ) 。可以用表达式的上下文就是方法入参

b. 记录错误:importTask.addTaskMessage(TaskMessageLevelEnum.ERROR,"达人名称不能为空")。

c. importData方法返回true,代表继续下一行的导入。返回false则表示中断导入

package pro.shushi.pamirs.demo.core.init.imports;

import com.alibaba.fastjson.JSON;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import pro.shushi.pamirs.demo.api.enumeration.PetTalentSexEnum;
import pro.shushi.pamirs.demo.api.model.PetShop;
import pro.shushi.pamirs.demo.api.model.PetTalent;
import pro.shushi.pamirs.file.api.context.ExcelImportContext;
import pro.shushi.pamirs.file.api.enmu.ExcelTemplateTypeEnum;
import pro.shushi.pamirs.file.api.enmu.ExcelValueTypeEnum;
import pro.shushi.pamirs.file.api.enmu.TaskMessageLevelEnum;
import pro.shushi.pamirs.file.api.extpoint.AbstractExcelImportDataExtPointImpl;
import pro.shushi.pamirs.file.api.extpoint.ExcelImportDataExtPoint;
import pro.shushi.pamirs.file.api.model.ExcelCellDefinition;
import pro.shushi.pamirs.file.api.model.ExcelImportTask;
import pro.shushi.pamirs.file.api.model.ExcelWorkbookDefinition;
import pro.shushi.pamirs.file.api.util.ExcelHelper;
import pro.shushi.pamirs.file.api.util.ExcelTemplateInit;
import pro.shushi.pamirs.framework.connectors.data.sql.Pops;
import pro.shushi.pamirs.framework.connectors.data.tx.transaction.Tx;
import pro.shushi.pamirs.meta.annotation.ExtPoint;
import pro.shushi.pamirs.meta.api.dto.config.TxConfig;
import pro.shushi.pamirs.meta.api.dto.wrapper.IWrapper;

import java.util.*;

@Component
public class PetTalentImportTemplate extends AbstractExcelImportDataExtPointImpl<PetTalent> implements ExcelTemplateInit , ExcelImportDataExtPoint<PetTalent> {
    public static final String TEMPLATE_NAME ="宠物达人导入";

    @Override
    public List<ExcelWorkbookDefinition> generator() {
        //自定义定义Excel单元格,设置单元可选值
        Map<String,String> sexType = new HashMap<>();
        sexType.put(String.valueOf(PetTalentSexEnum.FEMAL.value()),PetTalentSexEnum.FEMAL.displayName());
        sexType.put(String.valueOf(PetTalentSexEnum.MAN.value()),PetTalentSexEnum.MAN.displayName());
        ExcelCellDefinition sexExcelCellDefinition = new ExcelCellDefinition();
        sexExcelCellDefinition.setType(ExcelValueTypeEnum.ENUMERATION).setValue("性别").setFormat(JSON.toJSONString(sexType));

        //可以返回多个模版,导出的时候页面上由用户选择导出模版
        return Collections.singletonList(
                ExcelHelper.fixedHeader(PetTalent.MODEL_MODEL,TEMPLATE_NAME)
                        .createBlock(TEMPLATE_NAME,PetTalent.MODEL_MODEL)
                        .setType(ExcelTemplateTypeEnum.IMPORT)
                        //相同key,相连行会进行合并操作
                        .addUnique(PetTalent.MODEL_MODEL,"name")
                        .addColumn("name","名称")
                        .addColumn("annualIncome","年收入")
                        .addColumn("petShops[*].shopName","店铺名")
                        .addColumn("petTalentSex",sexExcelCellDefinition)
                        .build());
    }

    //表达式里的常量需要使用" "包括
    @Override
    @ExtPoint.Implement(expression = "importContext.definitionContext.model == \"" + PetTalent.MODEL_MODEL+"\" && importContext.definitionContext.name == \"" +TEMPLATE_NAME+"\"" )
    public Boolean importData(ExcelImportContext importContext, PetTalent data) {
        ExcelImportTask importTask = importContext.getImportTask();
        if(StringUtils.isBlank(data.getName())){
            //返回true表示继续读下一行。返回false表示中断
            importTask.addTaskMessage(TaskMessageLevelEnum.ERROR,"达人名称不能为空");
            return Boolean.TRUE;
        }
        if(CollectionUtils.isNotEmpty(data.getPetShops())){
            List<String > petShopNams = new ArrayList<>();
            for(PetShop petShop:data.getPetShops()){
                petShopNams.add(petShop.getShopName());
            }
            //FIXME 抽到Service中去,散落的查询逻辑不好维护。
            IWrapper<PetShop> queryWrapper = Pops.<PetShop>lambdaQuery().from(PetShop.MODEL_MODEL).in(PetShop::getShopName, petShopNams);
            List<PetShop> shops = new PetShop().queryList(queryWrapper);
            data.setPetShops(shops);
        }
        Tx.build(new TxConfig().setPropagation(Propagation.REQUIRED.value())).executeWithoutResult(status -> {
            data.create();
            data.fieldSave(PetTalent::getPetShops);
        });
        return Boolean.TRUE;
    }

}

Step2 重启看效果

  1. 点击【宠物达人】菜单进入宠物达人列表页面,点击【导入】。

  2. 在导入弹出框中选择导入模版后点击下载模版,并提按模版填写数据。

  3. 在导入弹出框中点击【点击上传】按钮,上传填写好的文件,并点击【导入】按钮

image.png

image.png

  1. 列表页增加对应数据行,点击详情可以看到所有相关数据

image.png

这个例子中用了ExcelHelper这个简单工具类来定义模版,基本能满足日常需求。复杂的可以直接用WorkbookDefinitionBuilder来完成。

导入导出的更多配置项

基础设计

以POI和EasyExcel共有的Excel处理能力为基础进行设计。

  1. 以Workbook为核心的模板定义

  2. 支持多个Sheet定义。

  3. 支持【固定表头】形式的模板定义。(陆续支持【固定格式】和【混合格式】)

  4. 支持三个维度的定义方式:表头定义(列定义),行定义,单元格定义。

  5. 支持单元格样式、字体样式等Excel样式定义。

  6. 支持合并单元格。

  7. 支持数字、字符串、时间、日期、公式等Excel的数据处理能力。

  8. 支持直接存数据库和获取导入数据两种方式。

Excel功能

模板定义,使用数据初始化或页面操作的方式创建Excel模板。

导出任务的创建,页面选择导出模板、导出条件后,点击确定后,创建导出任务。导出方式为:异步,在导出任务页面查看导出任务,并允许多次下载。

导入任务的创建,页面选择模板,并点击下载后,获取对应Excel模板。按照指定规则(平台默认规则或业务自定义规则)填写完成后,上传Excel文件进行异步导入操作。

Excel工作簿(ExcelWorkbookDefinition)

属性 类型 名称 必填 默认值 说明
name String 名称 Excel工作簿的定义名称
filename String 文件名 导出时使用的文件名,不指定则默认使用名称作为文件名
version Enum Office版本 AUTO OfficeVersionEnum
sheetList List<ExcelSheetDefinition>(JSONArray) 工作表定义列表 对多个Sheet进行定义

Excel工作表(ExcelSheetDefinition)

属性 类型 名称 必填 默认值 说明
name String 工作表名称 指定工作表名称,当未指定工作表名称时,默认使用模型的显示名称,未绑定模型生成时,默认使用「Sheet + ${index}」作为工作表名称
blockDefinitionList List<ExcelBlockDefinition> 区块列表 至少一个区块
mergeRangeList List<ExcelCellRangeDefinition> 单元格合并范围 工作表的合并范围相对于A1单元格行列索引,绝对定义
uniqueDefinitions List<ExcelUniqueDefinition> 唯一定义 全局模型唯一定义,当区块中未定义时,使用该定义

Excel区块(ExcelBlockDefinition)

属性 类型 名称 必填 默认值 说明
designRegion ExcelCellRangeDefinition 设计区域 设计区域是区块的唯一标识,不允许重叠。定义时应示意扩展方向,如下图所示。
analysisType Enum 解析类型 FIXED_HEADER ExcelAnalysisTypeEnum
usingCascadingStyle Boolean 是否使用层叠样式 false 将样式覆盖变为样式层叠;单个工作表有效;优先级顺序为:列样式 < 行样式 < 单元格样式三个属性共同定义一个Sheet的样式
headerList List<ExcelHeaderDefinition> 表头定义 【固定表头】类型下,至少一行,不显示的表头行使用配置行定义。
rowList List<ExcelRowDefinition> 行定义 【固定格式】类型下,至少一行。
bindingModel String 绑定模型 绑定任意模型,允许使用类的全限定名。
mergeRangeList List<ExcelCellRangeDefinition> 单元格合并范围 区块内的单元格合并范围相对与区块起始行列索引。当合并范围指定到最后一行的表头定义时,属于相对定义,即扩展时会自动填充合并样式。否则与工作表合并相同。
uniqueDefinitions List<ExcelUniqueDefinition> 唯一定义 默认使用模型定义的unique属性

Excel行(ExcelRowDefinition)

属性 类型 名称 必填 默认值 说明
cellList List<ExcelCellDefinition> 单元格列表
style ExcelStyleDefinition 样式 行样式

Excel表头(ExcelHeaderDefinition继承ExcelRowDefinition)

属性 类型 名称 必填 默认值 说明
cellList List<ExcelCellDefinition> 单元格列表
style ExcelStyleDefinition 样式 行样式
direction Enum 排列方向 HORIZONTAL ExcelDirectionEnum
selectRange ExcelCellRangeDefinition 应用范围 仅起始属性有效
isConfig Boolean 是否配置表头
autoSizeColumn Boolean 自动列宽 true 多行表头需使用配置表头进行定义
isFrozen Boolean 是否冻结 Excel冻结功能

Excel单元格范围(ExcelCellRangeDefinition)

属性 类型 名称 必填 默认值 说明
beginRowIndex Integer 起始行索引
endRowIndex Integer 结束行索引
beginColumnIndex Integer 起始列索引
endColumnIndex Integer 结束列索引
fixedBeginRowIndex Boolean 固定起始行索引
fixedEndRowIndex Boolean 固定结束行索引
fixedBeginColumnIndex Boolean 固定起始列索引
fixedEndColumnIndex Boolean 固定结束列索引

Excel单元格(ExcelCellDefinition)

属性 类型 名称 必填 默认值 说明
field String 属性 当且仅当单元格在表头中有效;多行表头需使用配置表头进行定义;多个配置表头定义属性时,仅首个属性定义生效;
value String
type Enum 值类型 STRING ExcelValueTypeEnum
formatContextString String(JSONObject) 格式化上下文 针对不同类型的值设置不同的参数内容(类型待定)
isStatic Boolean 是否是静态值 false 静态值在数据解析时将使用配置值,不读取单元格的值
style ExcelStyleDefinition 样式 单元格样式

Excel单元格样式(ExcelStyleDefinition)

属性 类型 名称 必填 默认值 说明
horizontalAlignment Enum 水平对齐 GENERAL ExcelHorizontalAlignmentEnum
verticalAlignment Enum 垂直对齐 ExcelVerticalAlignmentEnum
fillBorderStyle Enum 全边框样式 NONE ExcelBorderStyleEnum
topBorderStyle Enum 上边框样式 ExcelBorderStyleEnum
rightBorderStyle Enum 右边框样式 ExcelBorderStyleEnum
bottomBorderStyle Enum 下边框样式 ExcelBorderStyleEnum
leftBorderStyle Enum 左边框样式 ExcelBorderStyleEnum
wrapText Boolean 是否自动换行 true
typefaceDefinition ExcelTypefaceDefinition 字体
width Integer 首列样式中有效
height Integer 首列样式中有效

Excel字体(ExcelTypefaceDefinition)

属性 类型 名称 必填 默认值 说明
typeface Enum 字体 ExcelTypefaceEnum

注意事项

如果是在分布式模式下,启动工程中没有加入File模块,则需要手工调用ExcelTemplateInitHelper .init方法

Oinone社区 作者:史, 昂原创文章,如若转载,请注明出处:https://doc.oinone.top/oio4/9325.html

访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验

(0)
史, 昂的头像史, 昂数式管理员
上一篇 2024年5月23日 am8:13
下一篇 2024年5月23日

相关推荐

  • 3.5.2.2 构建View的Template

    我们在很多时候需要自定义模型的管理页面,而不是直接使用默认页面,比如字段的展示与隐藏,Action是否在这个页面上出现,搜索条件自定义等等,那么本章节带您一起学习如何自定义View的Template。 自定义View的Template 在使用默认layout的情况下,我们来做几个自定义视图Template,并把文件放到指定目录下。 图3-5-2-14 自定义View的Template 第一个Tabel Step1 自定义PetTalent的列表 我们先通过数据库查看默认页面定义,找到base_view表,过滤条件设置为model =\’demo.PetTalent\’,我们就看到该模型下对应的所有view,这些是系统根据该模型的ViewAction对应生成的默认视图,找到类型为【表格(type = TABLE)】的记录,查看template字段。 图3-5-2-15 base_view表查看template字段 <view name="tableView" cols="1" type="TABLE" enableSequence="false"> <template slot="actions" autoFill="true"/> <template slot="rowActions" autoFill="true"/> <template slot="fields"> <field invisible="true" data="id" label="ID" readonly="true"/> <field data="name" label="达人"/> <field data="dataStatus" label="数据状态"> <options> <option name="DRAFT" displayName="草稿" value="DRAFT" state="ACTIVE"/> <option name="NOT_ENABLED" displayName="未启用" value="NOT_ENABLED" state="ACTIVE"/> <option name="ENABLED" displayName="已启用" value="ENABLED" state="ACTIVE"/> <option name="DISABLED" displayName="已禁用" value="DISABLED" state="ACTIVE"/> </options> </field> <field data="createDate" label="创建时间" readonly="true"/> <field data="writeDate" label="更新时间" readonly="true"/> <field data="createUid" label="创建人id"/> <field data="writeUid" label="更新人id"/> </template> <template slot="search" autoFill="true" cols="4"/> </view> 图3-5-2-16 base_view表查看template字段 对比view的template定义与页面差异,从页面上看跟view的定义少了,创建人id和更新人id。因为这两个字段元数据定义里invisible属性。 a. 当XML里面没有配置,则用元数据覆盖了。 b. 当XML里面配置了,则不会用元数据覆盖了。 在下一步中我们只要view的DSL中给这两个字段加上invisible="false"就可以展示出来了 图3-5-2-17 查看列表展示 图3-5-2-18 invisible属性 新建pet_talent_table.xml文件放到对应的pamirs/views/demo_core/template目录下,内容如下 a. 对比默认视图,在自定义视图时需要额外增加属性model="demo.PetTalent" b. name设置为"tableView",系统重启后会替换掉base_view表中model为"demo.PetTalent",name为"tableView",type为"TABLE"的数据记录。 ⅰ. name不同的但type相同,且viewAction没有指定时,根据优先级priority进行选择。小伙伴可以尝试修改name="tableView1",并设置priority为1,默认生成的优先级为10,越小越优先。 ccreateUid和writeUid字段,增加invisible="false"的属性定义 <view name="tableView" model="demo.PetTalent" cols="1" type="TABLE" enableSequence="false"> <template slot="actions" autoFill="true"/> <template slot="rowActions" autoFill="true"/> <template slot="fields"> <field invisible="true" data="id" label="ID" readonly="true"/> <field data="name" label="达人"/> <field data="dataStatus" label="数据状态"> <options> <option name="DRAFT" displayName="草稿" value="DRAFT" state="ACTIVE"/> <option name="NOT_ENABLED" displayName="未启用" value="NOT_ENABLED" state="ACTIVE"/> <option name="ENABLED" displayName="已启用" value="ENABLED" state="ACTIVE"/> <option name="DISABLED" displayName="已禁用" value="DISABLED" state="ACTIVE"/> </options> </field> <field data="createDate" label="创建时间" readonly="true"/> <field data="writeDate" label="更新时间" readonly="true"/> <field data="createUid" label="创建人id" invisible="false"/> <field data="writeUid" label="更新人id" invisible="false"/> </template> <template slot="search" autoFill="true" cols="4"/> </view> 图3-5-2-19 增加invisible=”false”的属性定义 Step2 重启应用看效果 图3-5-2-20 示例效果 第一个Form Step1 自定义PetTalent的编辑页…

    2024年5月23日
    84400
  • 3.3.10 字段类型之关系描述的特殊场景

    在3.3.9【字段类型之关系与引用】一文中已经描述了各种关系字段的常规写法,还有一些特殊场景如:关系映射中存在常量,或者M2M中间表是大于两个字段构成。 举例说明关系字段-高级用法 场景描述 PetTalent模型增加talentType字段,PetItem与PetTalent的多对多关系增加talentType(达人类型),PetItemRelPetTalent 中间表维护petItemId、petTalentId以及talentType,PetDogItem和PetCatItem分别重写petTalents 字段,关系中增加常量描述。示意图如下 实际操作步骤: Step1 新增 TalentTypeEnum package pro.shushi.pamirs.demo.api.enumeration; import pro.shushi.pamirs.meta.annotation.Dict; import pro.shushi.pamirs.meta.common.enmu.BaseEnum; @Dict(dictionary = TalentTypeEnum.DICTIONARY,displayName = "达人类型") public class TalentTypeEnum extends BaseEnum<TalentTypeEnum,Integer> { public static final String DICTIONARY ="demo.TalentTypeEnum"; public final static TalentTypeEnum DOG =create("DOG",1,"狗达人","狗达人"); public final static TalentTypeEnum CAT =create("CAT",2,"猫达人","猫达人"); } Step2 PetTalent模型增加talentType字段 package pro.shushi.pamirs.demo.api.model; import pro.shushi.pamirs.demo.api.enumeration.TalentTypeEnum; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; @Model.model(PetTalent.MODEL_MODEL) @Model(displayName = "宠物达人",summary="宠物达人",labelFields ={"name"}) public class PetTalent extends AbstractDemoIdModel{ public static final String MODEL_MODEL="demo.PetTalent"; @Field(displayName = "达人") private String name; @Field(displayName = "达人类型") private TalentTypeEnum talentType; } Step3 修改PetItem的petTalents字段,在关系描述中增加talentType(达人类型) @Field.many2many(relationFields = {"petItemId"},referenceFields = {"petTalentId","talentType"},through = "PetItemRelPetTalent") @Field.Relation(relationFields = {"id"}, referenceFields = {"id","talentType"}) @Field(displayName = "推荐达人",summary = "推荐该商品的达人们") private List<PetTalent> petTalents; Step4 PetDogItem增加petTalents字段,重写父类PetItem的关系描述 talentType配置为常量,填入枚举的值 增加domain描述用户页面选择的时候自动过滤出特定类型的达人,RSQL用枚举的name @Field.many2many(relationFields = {"petItemId"},referenceFields = {"petTalentId","talentType"},through = "PetItemRelPetTalent") @Field.Relation(relationFields = {"id"}, referenceFields = {"id","talentType"}) @Field(displayName = "推荐达人",summary = "推荐该商品的达人们") private List<PetTalent> petTalents; Step5 PetCatItem增加petTalents字段,重写父类PetItem的关系描述 talentType配置为常量,填入枚举的值 增加domain描述用户页面选择的时候自动过滤出特定类型的达人,RSQL用枚举的name @Field(displayName = "推荐达人") @Field.many2many( through = "PetItemRelPetTalent", relationFields = {"petItemId"}, referenceFields = {"petTalentId","talentType"} ) @Field.Relation(relationFields = {"id"}, referenceFields = {"id", "#2#"}, domain = " talentType == CAT") private List<PetTalent> petTalents; Step6 清除中间表demo_core_pet_item_rel_pet_talent的数据记录 清除PetItem与PetTalent的多对多中间表demo_core_pet_item_rel_pet_talent的数据记录 Step7 重启看效果 修改达人记录,选择不同达人类型 PetItem、PetCatItem、PetDogItem不同的交互页面

    2024年5月23日
    1.4K00
  • 4.1.20 框架之Session

    在日常开发中,我们经常需要把一些通用的信息放入程序执行的上下文中,以便业务开发人员快速获取。那么oinone的PamirsSession就是来解决此类问题的。 一、PamirsSession介绍 在oinone的体系中PamirsSession是执行上下文的承载,您能从中获取业务基础信息、指令信息、元数据信息、环境信息、请求参数,以及前后端MessageHub等。在前面的学习过程中我们已经多次接触到了如何使用PamirsSession: 在4.1.19【框架之网关协议-后端占位符】一文中,使用PamirsSession.getUserId()来获取当前登入用户Id,诸如此类的业务基础信息; 在4.1.18【框架之网关协议-variables变量】一文中,使用PamirsSession.getRequestVariables()得到PamirsRequestVariables对象,进而获取前端请求的相关信息; 在4.1.5【模型之持久层配置】一文中,使用PamirsSession.directive(),来操作元位指令系统,进而影响执行策略; 在4.1.13【Action之校验】、3.4.1【构建第一个Function】等文章中,都用到PamirsSession.getMessageHub()来设置返回消息。 二、构建模块自身Session(举例) 不同的应用场景对PamirsSession的诉求是不一样的,这个时候我们就可以去扩展PamirsSession来达到我们的目的 构建模块自身Session的步骤 构建自身特有的数据结构XSessionData 对XSessionData进行线程级缓存封装 利用Hook机制初始化XSessionData并放到ThreadLocal中 定义自身XSessionApi 实现XSessionApi接口、SessionClearApi。在请求结束时会调用SessionClearApi的clear方法 定义XSession继承PamirsSession 扩展PamirsSession的经典案例设计图 图4-1-20-1 扩展PamirsSession的经典案例设计图 构建Demo应用自身Session 下面的例子为给Session放入当前登陆用户 Step1 新建DemoSessionData类 构建自身特有的数据结构DemoSessionData,增加一个模型为PamirsUser的字段user,DemoSessionData用Data注解,注意要用Oinone平台提供的@Data package pro.shushi.pamirs.demo.core.session; import pro.shushi.pamirs.meta.annotation.fun.Data; import pro.shushi.pamirs.user.api.model.PamirsUser; @Data public class DemoSessionData { private PamirsUser user; } 图4-1-20-2 新建DemoSessionData类 Step2 新建DemoSessionCache 对DemoSessionData进行线程级缓存封装 package pro.shushi.pamirs.demo.core.session; import pro.shushi.pamirs.meta.api.CommonApiFactory; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.user.api.model.PamirsUser; import pro.shushi.pamirs.user.api.service.UserService; public class DemoSessionCache { private static final ThreadLocal<DemoSessionData> BIZ_DATA_THREAD_LOCAL = new ThreadLocal<>(); public static PamirsUser getUser(){ return BIZ_DATA_THREAD_LOCAL.get()==null?null:BIZ_DATA_THREAD_LOCAL.get().getUser(); } public static void init(){ if(getUser()!=null){ return ; } Long uid = PamirsSession.getUserId(); if(uid == null){ return; } PamirsUser user = CommonApiFactory.getApi(UserService.class).queryById(uid); if(user!=null){ DemoSessionData demoSessionData = new DemoSessionData(); demoSessionData.setUser(user); BIZ_DATA_THREAD_LOCAL.set(demoSessionData); } } public static void clear(){ BIZ_DATA_THREAD_LOCAL.remove(); } } 图4-1-20-3 对DemoSessionData进行线程级缓存封装 Step3 新建DemoSessionHook 利用Hook机制,调用DemoSessionCache的init方法初始化DemoSessionData并放到ThreadLocal中。 @Hook(module= DemoModule.MODULE_MODULE), 规定只有增对DemoModule模块访问的请求该拦截器才会生效,不然其他模块的请求都会被DemoSessionHook拦截。 package pro.shushi.pamirs.demo.core.hook; import org.springframework.stereotype.Component; import pro.shushi.pamirs.demo.api.DemoModule; import pro.shushi.pamirs.demo.core.session.DemoSessionCache; import pro.shushi.pamirs.meta.annotation.Hook; import pro.shushi.pamirs.meta.api.core.faas.HookBefore; import pro.shushi.pamirs.meta.api.dto.fun.Function; @Component public class DemoSessionHook implements HookBefore { @Override @Hook(priority = 1,module = DemoModule.MODULE_MODULE) public Object run(Function function, Object… args) { DemoSessionCache.init(); return function; } } 图4-1-20-4 新建DemoSessionHook Step4 新建DemoSessionApi package pro.shushi.pamirs.demo.core.session; import pro.shushi.pamirs.meta.api.CommonApi; import pro.shushi.pamirs.user.api.model.PamirsUser; public interface DemoSessionApi extends CommonApi { PamirsUser getUser(); } 图4-1-20-5 新建DemoSessionApi Step5…

    2024年5月23日
    96100
  • 用户中心

    1. 创建用户 进入用户中心应用,在用户列表中点击创建。 填写表单中的必填信息。 若未设置昵称,则右上角头像右侧展示名称。若设置了昵称,则右上角头像右侧展示昵称。 是否激活账号选择是,选择否时用户登录会显示“未找到首页”。 角色分组中,选择创建的用户的角色,默认选择了超级管理员(包含所有权限)。 点击确定,用户创建完成。 用户登录时可用登录账号/邮箱/手机号登录。 2. 用户相关操作 表格页中包含常规的搜索、批量删除功能。 冻结:当将“是否有效”状态为“是”时展示,将用户“是否有效”修改为“否”。 解冻:当将“是否有效”状态为“否”时展示,将用户“是否有效”修改为“是”。 修改:进入用户信息修改页面,“编码、登录账号、注册时间”只读。 重置密码:点击后在弹窗“账号确认”中输入账号,点击重置密码后,展示新密码。

    2024年6月20日
    1.6K00

Leave a Reply

登录后才能评论