Oinone引入搜索引擎(增强模型)

场景描述

在碰到大数据量并且需要全文检索的场景,我们在分布式架构中基本会架设ElasticSearch来作为一个常规解决方案。在oinone体系中增强模型就是应对这类场景,其背后也是整合了ElasticSearch;

使用前你应该

  • 了解ElasticSearch,包括不限于:Index(索引)、分词、Node(节点)、Document(文档)、Shards(分片) & Replicas(副本)。参考官方网站:https://www.elastic.co/cn/
  • 有一个可用的ElasticSearch环境(本地项目能引用到)

前置约束

增强模型增量依赖数据变更实时消息,因此确保项目的event是开启的,mq配置正确。

项目引入搜索步骤

1、boot工程加入相关依赖包

  • boot工程需要指定ES客户端包版本,不指定版本会隐性依赖顶层spring-boot依赖管理指定的低版本
  • boot工程加入pamris-channel的工程依赖
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>8.4.1</version>
</dependency>
<dependency>
    <groupId>jakarta.json</groupId>
    <artifactId>jakarta.json-api</artifactId>
    <version>2.1.1</version>
</dependency>

<dependency>
    <groupId>pro.shushi.pamirs.core</groupId>
    <artifactId>pamirs-sql-record-core</artifactId>
</dependency>
<dependency>
    <groupId>pro.shushi.pamirs.core</groupId>
    <artifactId>pamirs-channel-core</artifactId>
</dependency>

2、api工程加入相关依赖包

在XXX-api中增加入pamirs-channel-api的依赖

<dependency>
    <groupId>pro.shushi.pamirs.core</groupId>
    <artifactId>pamirs-channel-api</artifactId>
</dependency>

3、yml文件配置

在pamirs-demo-boot的application-dev.yml文件中增加配置pamirs.boot.modules增加channel,即在启动模块中增加channel模块。同时注意es的配置,是否跟es的服务一致

pamirs:
  record:
    sql:
      #改成自己本地路径(或服务器路径)
      store: /Users/oinone/record
  boot:
    modules:
      - channel
      ## 确保也安装了sql_record
      - sql_record
  channel:
    packages:
      # 增强模型扫描包配置
      - com.xxx.xxx
  elastic:
    url: 127.0.0.1:9200

4、项目的模块增加模块依赖

XXXModule增加对ChannelModule的依赖

@Module(dependencies = {ChannelModule.MODULE_MODULE})

5、增加增强模型(举例)

package pro.shushi.pamirs.demo.api.enhance;

import pro.shushi.pamirs.channel.enmu.IncrementEnum;
import pro.shushi.pamirs.channel.meta.Enhance;
import pro.shushi.pamirs.channel.meta.EnhanceModel;
import pro.shushi.pamirs.demo.api.model.ShardingModel;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.enmu.ModelTypeEnum;

@Model(displayName = "测试EnhanceModel")
@Model.model(ShardingModelEnhance.MODEL_MODEL)
@Model.Advanced(type = ModelTypeEnum.PROXY, inherited = {EnhanceModel.MODEL_MODEL})
@Enhance(shards = "3", replicas = "1", reAlias = true,increment= IncrementEnum.OPEN)
public class ShardingModelEnhance extends ShardingModel {
    public static final String MODEL_MODEL="demo.ShardingModelEnhance";
}

6、重启系统看效果

1、进入【传输增强模型】应用,访问增强模型列表我们会发现一条记录,并点击【全量同步】初始化ES,并全量dump数据
Oinone引入搜索引擎(增强模型)

2、再次回到Demo应用,进入增强模型页面,可以正常访问并进增删改查操作
Oinone引入搜索引擎(增强模型)

个性化dump逻辑

通常dump逻辑是有个性化需求,那么我们可以重写模型的synchronize方法,函数重写特性在“面向对象-继承与多态”部分中已经有详细介绍。

重写ShardingModelEnhance模型的synchronize方法

重写后,如果针对老数据记录需要把新增的字段都自动填充,可以进入【传输增强模型】应用,访问增强模型列表,找到对应的记录并点击【全量同步】

package pro.shushi.pamirs.demo.api.enhance;

import pro.shushi.pamirs.channel.enmu.IncrementEnum;
import pro.shushi.pamirs.channel.meta.Enhance;
import pro.shushi.pamirs.channel.meta.EnhanceModel;
import pro.shushi.pamirs.demo.api.model.ShardingModel;
import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.enmu.FunctionTypeEnum;
import pro.shushi.pamirs.meta.enmu.ModelTypeEnum;

import java.util.List;

@Model(displayName = "测试EnhanceModel")
@Model.model(ShardingModelEnhance.MODEL_MODEL)
@Model.Advanced(type = ModelTypeEnum.PROXY, inherited = {EnhanceModel.MODEL_MODEL})
@Enhance(shards = "3", replicas = "1", reAlias = true,increment= IncrementEnum.OPEN)
public class ShardingModelEnhance extends ShardingModel {
    public static final String MODEL_MODEL="demo.ShardingModelEnhance";

    @Field(displayName = "nick")
    private String nick;

    @Function.Advanced(displayName = "同步数据", type = FunctionTypeEnum.UPDATE)
    @Function(summary = "数据同步函数")
    public List<ShardingModelEnhance> synchronize(List<ShardingModelEnhance> data) {
        for(ShardingModelEnhance shardingModelEnhance:data){
            shardingModelEnhance.setNick(shardingModelEnhance.getName());
        }
        return data;
    }
}

给搜索增加个性化逻辑

如果我们需要在查询方法中增加逻辑,在前面的教程中一般是重写queryPage函数,但对于增强模型我们需要重写的是search函数。

个性化search函数

@Function(
        summary = "搜索函数",
        openLevel = {FunctionOpenEnum.LOCAL, FunctionOpenEnum.REMOTE, FunctionOpenEnum.API}
)
@pro.shushi.pamirs.meta.annotation.Function.Advanced(
        type = {FunctionTypeEnum.QUERY},
        category = FunctionCategoryEnum.QUERY_PAGE,
        managed = true
)
public  Pagination<ShardingModelEnhance> search(Pagination<ShardingModelEnhance> page, IWrapper<ShardingModelEnhance> queryWrapper) {
    System.out.println("您的个性化搜索逻辑");
    // return ((IElasticRetrieve) CommonApiFactory.getApi(IElasticRetrieve.class)).search(page, queryWrapper);
    return ((ElasticSearchApi) CommonApiFactory.getApi(ElasticSearchApi.class)).search(page, queryWrapper);
}

个性化search函数示例

    @Override
    @SuppressWarnings({"rawtypes"})
    public <T> Pagination<T> search(Pagination<T> page, IWrapper<T> queryWrapper) {
        String modelModel = queryWrapper.getModel();
        if (null == modelModel || modelModel.isEmpty()) {
            return page;
        }
        ModelConfig modelCfg = PamirsSession.getContext().getModelConfig(modelModel);
        if (null == modelCfg) {
            return page;
        }
        String rsql = queryWrapper.getOriginRsql();
        if (StringUtils.isBlank(rsql)) {
            rsql = "id>0";
        }
        BoolQuery.Builder queryBuilder = ElasticRSQLHelper.parseRSQL(modelCfg, rsql);
        TermQuery isDeletedTerm = QueryBuilders.term()
                .queryName(IS_DELETED)
                .field(IS_DELETED).value(0)
                .build();
        BoolQuery.Builder builder = QueryBuilders.bool().must(new Query(queryBuilder.build()));
        builder.must(new Query(isDeletedTerm));
        String alias = IndexNaming.aliasByModel(modelModel);
        Query query = new Query(builder.build());
        log.info("{}", query);

        List<Order> orders = Optional.ofNullable(page.getSort()).map(Sort::getOrders).orElse(new ArrayList<>());
        int currentPage = Optional.ofNullable(page.getCurrentPage()).orElse(1);
        Long size = Optional.ofNullable(page.getSize()).orElse(10L);
        int pageSize = size.intValue();
        List<SortOptions> sortOptions = new ArrayList<>();
        if (CollectionUtils.isEmpty(orders)) {
            orders.add(new Order(SortDirectionEnum.DESC, ID));
            orders.add(new Order(SortDirectionEnum.DESC, CREATE_DATE));
        }
        for (Order order : orders) {
            sortOptions.add(new SortOptions.Builder()
                    .field(SortOptionsBuilders.field()
                            .field(order.getField())
                            .order(SortDirectionEnum.DESC.equals(order.getDirection()) ? SortOrder.Desc : SortOrder.Asc)
                            .build())
                    .build());
        }

        SearchRequest request = new SearchRequest.Builder()
                .index(alias)
                .from((currentPage - 1) * pageSize)
                .size(pageSize)
                .sort(sortOptions)
                .query(query)
                .highlight(_builder ->
                        _builder.numberOfFragments(4)
                                .fragmentSize(50)
                                .type(HighlighterType.Unified)
                                .fields("name", HighlightField.of(_fieldBuilder -> _fieldBuilder.preTags(ElasticsearchConstant.HIGH_LIGHT_PREFIX).postTags(ElasticsearchConstant.HIGH_LIGHT_POSTFIX)))
                                .fields("documentNo", HighlightField.of(_fieldBuilder -> _fieldBuilder.preTags(ElasticsearchConstant.HIGH_LIGHT_PREFIX).postTags(ElasticsearchConstant.HIGH_LIGHT_POSTFIX)))
                                .fields("keywords", HighlightField.of(_fieldBuilder -> _fieldBuilder.preTags(ElasticsearchConstant.HIGH_LIGHT_PREFIX).postTags(ElasticsearchConstant.HIGH_LIGHT_POSTFIX))))
                .build();

        SearchResponse<HashMap> response = null;
        try {
            log.info("ES搜索请求参数:{}", request.toString());
            response = elasticsearchClient.search(request, HashMap.class);
        } catch (ElasticsearchException e) {
            log.error("索引异常", e);
            PamirsSession.getMessageHub()
                    .msg(Message.init()
                            .setLevel(InformationLevelEnum.WARN)
                            .msg("索引异常"));
            return page;
        } catch (IOException e) {
            log.error("ElasticSearch运行状态异常", e);
            PamirsSession.getMessageHub()
                    .msg(Message.init()
                            .setLevel(InformationLevelEnum.WARN)
                            .msg("ElasticSearch运行状态异常"));
            return page;
        }

        if (null == response || response.timedOut()) {
            return page;
        }

        HitsMetadata<HashMap> hits = response.hits();
        if (null == hits) {
            return page;
        }

        TotalHits totalHits = hits.total();
        long total = Optional.ofNullable(totalHits).map(TotalHits::value).orElse(0L);

        List<HashMap> dataMapList = Optional.of(hits)
                .map(HitsMetadata<HashMap>::hits)
                .map(hitsMap ->{
                    hitsMap.stream().forEach(highlightForEach -> {
                        highlightForEach.highlight().forEach((key, value) -> {
                            if(highlightForEach.source().containsKey(key)){
                                highlightForEach.source().put(key,value.get(0));
                            }
                        });

                    });
                    return hitsMap;
                })
                .map(List::stream)
                .orElse(Stream.empty())
                .map(Hit::source)
                .collect(Collectors.toList());

        List<T> context = persistenceDataConverter.out(modelModel, dataMapList);

        page.setSize(size);
        page.setTotalElements(total);
        page.setContent(context);
        log.info("ES搜索请求参数返回total,{}", total);
        return page;
    }

Oinone社区 作者:望闲原创文章,如若转载,请注明出处:https://doc.oinone.top/backend/7235.html

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

Like (0)
望闲's avatar望闲数式管理员
Previous 2024年5月14日 pm5:10
Next 2024年5月14日 pm10:15

相关推荐

  • 非存储字段搜索,适应灵活的搜索场景

    1、非存储字段搜索 1.1 描述 通常根据本模型之外的信息作为搜索条件时,通常会把 这些字段放在代理模型上。这类场景我们称之为 非存储字段搜索 1.2 场景一 非存储字段为基本的String。1.代码定义:非存储字段为基本的包装数据类型 @Field(displayName = "确认密码", store = NullableBoolEnum.FALSE) private String confirmPassword; 2.设计器拖拽:列表中需要有退拽这个字段,字段是否隐藏的逻辑自身业务是否需要决定。3.页面通过非存储字段为基本的包装数据类型进行搜索时。会拼在 queryWrapper 的属性queryData中,queryData为Map,key为字段名,value为搜索值。4.后台逻辑处理代码示例: Map<String, Object> queryData = queryWrapper.getQueryData(); if (null != queryData) { Object productIdObj = queryData.get(PRODUCT_ID); if (Objects.nonNull(productIdObj)) { String productId = productIdObj.toString(); queryWrapper.lambda().eq(MesProduceOrderProxy::getProductId, productId); } } 1.3 场景二 非存储字段为非存储对象。 定义 为非存储的 @Field(displayName = "款", store = NullableBoolEnum.FALSE) @Field.many2one @Field.Relation(store = false) private MesProduct product; 2.页面在搜索栏拖拽非存储字段作为搜索条件 3.后台逻辑处理代码示例: try { if (null != queryData && !queryData.isEmpty()) { List<Long> detailId = null; BasicSupplier supplier = JsonUtils.parseMap2Object((Map<String, Object>) queryData.get(supplierField), BasicSupplier.class); MesProduct product = JsonUtils.parseMap2Object((Map<String, Object>) queryData.get(productField), MesProduct.class); MesMaterial material = JsonUtils.parseMap2Object((Map<String, Object>) queryData.get(materialField), MesMaterial.class); if (supplier != null) { detailId = bomService.queryBomDetailIdBySupplierId(supplier.getId()); if (CollectionUtils.isEmpty(detailId)) { detailId.add(-1L); } } if (product != null) { List<Long> produceOrderId = produceOrderService.queryProductOrderIdByProductIds(product.getId()); if (CollectionUtils.isNotEmpty(produceOrderId)) { queryWrapper.lambda().in(MesProduceBomSizes::getProduceOrderId, produceOrderId); } } if (material != null) { //找出两个bom列表的并集 List<Long> materBomDetailId = bomService.queryBomDetailIdByMaterialId(material.getId()); if (CollectionUtils.isNotEmpty(detailId)) { detailId = detailId.stream().filter(materBomDetailId::contains).collect(Collectors.toList()); } else { detailId = new ArrayList<>(); if (CollectionUtils.isEmpty(materBomDetailId)) { detailId.add(-1L); } else { detailId.addAll(materBomDetailId); } } } if (CollectionUtils.isNotEmpty(detailId)) { queryWrapper.lambda().in(MesProduceBomSizes::getProductBomId, detailId); } } } catch (Exception e) { log.error("queryData处理异常", e); } 注意:…

    2024年2月20日
    1.7K00
  • 函数之触发与定时配置和示例

    异步任务总体介绍 函数的触发和定时在很多场景中会用到,也是一个oinone的基础能力。比如我们的流程产品中在定义流程触发时就会让用户选择模型触发还是时间触发,就是用到了函数的触发与定时能力。 触发任务TriggerTaskAction 触发任务的创建,使用sql-record模块监听mysql的binlog事件,通过rocketmq发送变更数据消息,收到MQ消息后,创建TriggerAutoTask。 触发任务的执行,使用TBSchedule拉取触发任务后,执行相应函数。 项目中引入依赖 1、项目的API工程引入依赖pamirs-core-trigger模块 <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-trigger-api</artifactId> </dependency> 2、DemoModule在模块依赖定义中增加@Module(dependencies={TriggerModule.MODULE_MODULE}) @Component @Module( name = DemoModule.MODULE_NAME, displayName = "oinoneDemo工程", version = "1.0.0", dependencies = {ModuleConstants.MODULE_BASE, CommonModule.MODULE_MODULE, UserModule.MODULE_MODULE, TriggerModule.MODULE_MODULE} ) @Module.module(DemoModule.MODULE_MODULE) @Module.Advanced(selfBuilt = true, application = true) @UxHomepage(PetShopProxy.MODEL_MODEL) public class DemoModule implements PamirsModule { ……其他代码 } 3、项目的boot工程引入依赖 <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-trigger-core</artifactId> </dependency> <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-trigger-bridge-tbschedule</artifactId> </dependency> <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-sql-record-core</artifactId> </dependency> yml文件修改(applcation-xxx.yml) a. 修改pamris.event.enabled和pamris.event.schedule.enabled为trueb. pamirs_boot_modules增加启动模块:trigger、sql_record pamirs: record: sql: #改成自己路径 store: /opt/pamirs/logs … event: enabled: true schedule: enabled: true rocket-mq: namesrv-addr: 127.0.0.1:9876 boot: init: true sync: true modules: – base -…… – trigger – sql_record -…… 新建触发任务 新建PetTalentTrigger类,当PetTalent模型的数据记录被新建时触发系统做一些事情 package pro.shushi.pamirs.demo.core.trigger; import pro.shushi.pamirs.demo.api.model.PetTalent; import pro.shushi.pamirs.meta.annotation.Fun; import pro.shushi.pamirs.meta.annotation.Function; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import pro.shushi.pamirs.trigger.annotation.Trigger; import pro.shushi.pamirs.trigger.enmu.TriggerConditionEnum; @Fun(PetTalent.MODEL_MODEL) @Slf4j public class PetTalentTrigger { @Function @Trigger(displayName = “PetTalent创建时触发”,name = “PetTalent#Trigger#onCreate”,condition = TriggerConditionEnum.ON_CREATE) public PetTalent onCreate(PetTalent data){ log.info(data.getName() + “,被创建”); //可以增加逻辑 return data; } } 定时任务 定时任务是一种非常常见的模式,这里就不介绍概念了,直接进入示例环节 新建PetTalentAutoTask实现ScheduleAction getInterfaceName()需要跟taskAction.setExecuteNamespace定义保持一致,都是函数的命名空间 taskAction.setExecuteFun("execute");跟执行函数名“execute”一致 TaskType需配置为CYCLE_SCHEDULE_NO_TRANSACTION_TASK,把定时任务的schedule线程分开,要不然有一个时间长的任务会导致普通异步或触发任务全部延时。 package pro.shushi.pamirs.demo.core.task; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import pro.shushi.pamirs.core.common.enmu.TimeUnitEnum; import pro.shushi.pamirs.demo.api.model.PetTalent; import pro.shushi.pamirs.meta.annotation.Fun; import pro.shushi.pamirs.meta.annotation.Function; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import pro.shushi.pamirs.meta.domain.fun.FunctionDefinition; import pro.shushi.pamirs.middleware.schedule.api.ScheduleAction; import pro.shushi.pamirs.middleware.schedule.common.Result; import pro.shushi.pamirs.middleware.schedule.domain.ScheduleItem; import pro.shushi.pamirs.middleware.schedule.eunmeration.TaskType; import pro.shushi.pamirs.trigger.enmu.TriggerTimeAnchorEnum; import pro.shushi.pamirs.trigger.model.ScheduleTaskAction; import pro.shushi.pamirs.trigger.service.ScheduleTaskActionService; @Slf4j @Component @Fun(PetTalent.MODEL_MODEL) public class PetTalentAutoTask implements…

    2024年5月25日
    1.7K00
  • 自定义用户中心菜单

    使用扩展点实现用户中心菜单替换 1. 工程中引起pamirs-user-api <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-user-api</artifactId> </dependency> 2. 实现TopBarUserBlockAction的后置扩展 实现HookAfter后置扩展接口 @Hook(model = {TopBarUserBlock.MODEL_MODEL}, fun = {"construct"})添加Hook注解注明是TopBarUserBlock模型的construct函数的后置扩展。 增加用户中心菜单 @Component @Order(1) @SPI.Service public class MyTopBarActionExt implements TopBarActionExtendApi { public void edit(List<TopBarAction> list) { list.add(new TopBarAction("top_demo", "top.Teacher", "uiView9a0caf1d574a42c9847a057a0c4a4ad1", ActionTypeEnum.VIEW, 1) .setDisplayName("商品管理") .setIcon("oinone-gongzuotai") ); } } 实现效果 替换原有的用户中心菜单 替换原有的菜单跳转 @Component public class DemoTopBarUserBlockDataHookAfter implements HookAfter { @Override @Hook(model = {TopBarUserBlock.MODEL_MODEL}, fun = {"construct"}) public Object run(Function function, Object ret) { if (ret == null) { return null; } TopBarUserBlock result = null; if (ret instanceof Object[]) { Object[] rets = (Object[]) ((Object[]) ret); if (rets.length == 1) { result = (TopBarUserBlock) rets[0]; //例:替换用户中心:修改密码菜单 //使用name和model查询出模型的ViewAction替换修改密码ViewAction ViewAction demoViewAction = PamirsSession.getContext().getExtendCache(ActionCacheApi.class).get(Dog.MODEL_MODEL, "changePassword"); //设置菜单的icon Map<String, Object> attributes = Optional.ofNullable(demoViewAction.getAttributes()).orElse(new HashMap<>()); attributes.put("icon", "oinone-xiugaimima"); demoViewAction.setAttributes(attributes); //UserViewAction第0个是修改密码ViewAction,使用自定义的ViewAction就可以实现替换 result.getUserViewAction().set(0, demoViewAction); } } else { result = (TopBarUserBlock) ret; } return result; } } 使用@UxRouteButton方式新增ViewAction,更多新增Action方式详见:Action的类型 @Model.model(Dog.MODEL_MODEL) @Component @UxRouteButton( action = @UxAction(name = "changePassword", displayName = "修改密码"), value = @UxRoute(model = Dog.MODEL_MODEL, openType = ActionTargetEnum.DIALOG)) public class DogAction { } 替换原有的个人设置头像跳转 修改点击头像绑定的跳转逻辑 @Order(10) @Component @SPI.Service public class DemoTopBarUserBlockDataApi implements TopBarUserBlockDataApi { @Override public TopBarUserBlock extendData(TopBarUserBlock data) { //例如增加一个菜单, PamirsDemo.MODEL_MODEL: 模型。 MenuuiMenu31f22466735a4abe8e0544b428ed88ac:viewAction的name。 Action demoViewAction =…

    2024年7月4日
    2.0K00
  • 【Excel导入/导出】多Sheet导入导出示例(4.7.x及以上版本)

    代码示例 示例仅供参考 点击下载代码示例 业务场景 准备工作:两个模型,物料(Material)和物料类别(MaterialCategory)。目标:在一个Excel模板中同时导入和导出两个模型的数据。 Material模型 @Model.model(Material.MODEL_MODEL) @Model.Advanced(unique = {"code"}) @Model(displayName = "物料", labelFields = {"name"}) public class Material extends IdModel { private static final long serialVersionUID = -2594216864389636135L; public static final String MODEL_MODEL = "maas.Material"; @Field.String @Field(displayName = "物料编码", required = true) private String code; @Field.String @Field(displayName = "物料名称", required = true) private String name; } MaterialCategory模型 @Model.model(MaterialCategory.MODEL_MODEL) @Model.Advanced(unique = {"code"}) @Model(displayName = "物料类别", labelFields = {"name"}) public class MaterialCategory extends IdModel { private static final long serialVersionUID = 6300896634558908349L; public static final String MODEL_MODEL = "maas.MaterialCategory"; @Field.String @Field(displayName = "类别编码", required = true) private String code; @Field.String @Field(displayName = "类别名称", required = true) private String name; } 模板定义 MaterialTemplate @Component public class MaterialTemplate implements ExcelTemplateInit { public static final String TEMPLATE_NAME = "materialTemplate"; @Override public List<ExcelWorkbookDefinition> generator() { WorkbookDefinitionBuilder builder = WorkbookDefinitionBuilder.newInstance(Material.MODEL_MODEL, TEMPLATE_NAME) .setDisplayName("物料和物料类别") .setEachImport(Boolean.FALSE);//设置importData的入参为 (ExcelImportContext importContext, List<MaterialCategory> data),如入参是单个对象,请删除setEachImport(Boolean.FALSE) createMaterialSheet(builder); createMaterialCategorySheet(builder); return Collections.singletonList(builder.build()); } private static void createMaterialSheet(WorkbookDefinitionBuilder builder) { builder.createSheet().setName("物料") .createBlock(Material.MODEL_MODEL, ExcelAnalysisTypeEnum.FIXED_HEADER, ExcelDirectionEnum.HORIZONTAL, "A1:B2") .createHeader().setStyleBuilder(ExcelHelper.createDefaultStyle()).setIsConfig(Boolean.TRUE) .createCell().setField("code").setAutoSizeColumn(Boolean.TRUE).and() .createCell().setField("name").setAutoSizeColumn(Boolean.TRUE).and() .and() .createHeader().setStyleBuilder(ExcelHelper.createDefaultStyle(v -> v.setBold(Boolean.TRUE)).setHorizontalAlignment(ExcelHorizontalAlignmentEnum.CENTER)) .createCell().setValue("物料编码").and() .createCell().setValue("物料名称"); } private static void createMaterialCategorySheet(WorkbookDefinitionBuilder builder) { builder.createSheet().setName("物料类别") .createBlock(MaterialCategory.MODEL_MODEL, ExcelAnalysisTypeEnum.FIXED_HEADER, ExcelDirectionEnum.HORIZONTAL, "A1:B2") .createHeader().setStyleBuilder(ExcelHelper.createDefaultStyle()).setIsConfig(Boolean.TRUE) .createCell().setField("code").setAutoSizeColumn(Boolean.TRUE).and()…

    2024年4月23日
    1.4K00
  • 同一行操作跳转到不同的视图(动态表单)

    背景 实际项目中,存在这样的场景:同一列表中的数据是泛化的数据集合,即数据来源于不同的模型;行操作需要根据来源去向不同的目标页。 如下图,「提报」操作需根据「报表类型」去向不同的表单。 并支持目标弹窗标题和弹框大小的配置。 解决思路 每行记录需要跳转到不同的模型不同视图,增加一个配置页面用于维护源模型和目标模型的调整动作关系; 返回数据的时候,同时返回自定义的动作。 前端自定义实现如上面图例中的「填报」,从返回数据中获取ViewAction并做对应的跳转。 具体步骤 [后端] 建立模型和视图的关系设置的模型 1、创建 模型和视图的关系设置的模型,用于配置列表模型和各记录即目标模型的视图关系 import pro.shushi.oinone.examples.simple.api.proxy.system.SimpleModel; import pro.shushi.oinone.examples.simple.api.proxy.system.SimpleModule; import pro.shushi.pamirs.boot.base.enmu.ActionTargetEnum; import pro.shushi.pamirs.boot.base.model.View; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.base.IdModel; import pro.shushi.pamirs.meta.enmu.ViewTypeEnum; /** * 模型和视图的关系设置 * ModelRelViewSetting */ @Model.model(ModelRelViewSetting.MODEL_MODEL) @Model(displayName = "模型和视图的关系设置", summary = "模型和视图的关系设置") @Model.Advanced(unique = {"oModel,model,target,viewType,viewName"}) public class ModelRelViewSetting extends IdModel { public static final String MODEL_MODEL = "examples.custom.ModelRelViewSetting"; @Field.many2one @Field(displayName = "模块") @Field.Relation(relationFields = {"module"}, referenceFields = {"module"}) private SimpleModule moduleDef; @Field.String @Field(displayName = "模块编码", invisible = true) private String module; @Field.many2one @Field(displayName = "源模型") @Field.Relation(relationFields = {"oModel"}, referenceFields = {"model"}) private SimpleModel originModel; @Field.String @Field(displayName = "源模型编码", invisible = true) private String oModel; @Field.many2one @Field(displayName = "目标模型") @Field.Relation(relationFields = {"model"}, referenceFields = {"model"}) private SimpleModel targetModel; @Field.String @Field(displayName = "目标模型编码", invisible = true) private String model; @Field.Enum @Field(displayName = "视图类型") private ViewTypeEnum viewType; @Field.Enum @Field(displayName = "打开方式", required = true) private ActionTargetEnum target; @Field.String @Field(displayName = "动作名称", invisible = true) private String name; @Field.many2one @Field.Relation(relationFields = {"model", "viewName"}, referenceFields = {"model", "name"}, domain = "systemSource=='UI'") @Field(displayName = "绑定页面", summary = "绑定页面") private View view; @Field.String @Field(displayName = "视图/页面", invisible…

    2025年2月19日
    1.3K00

Leave a Reply

Please Login to Comment