同一行操作跳转到不同的视图(动态表单)

背景

实际项目中,存在这样的场景:同一列表中的数据是泛化的数据集合,即数据来源于不同的模型;行操作需要根据来源去向不同的目标页。 如下图,「提报」操作需根据「报表类型」去向不同的表单。

并支持目标弹窗标题和弹框大小的配置。

解决思路

每行记录需要跳转到不同的模型不同视图,增加一个配置页面用于维护源模型和目标模型的调整动作关系; 返回数据的时候,同时返回自定义的动作。

image-20250218170109190

前端自定义实现如上面图例中的「填报」,从返回数据中获取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 = true)
    private String viewName;

    @Field.String
    @Field(displayName = "页面标题")
    private String title;

    @Field.String
    @Field(displayName = "显示名称")
    private String displayName;

}

注意:未避免对系统模块和模型的影响ModelRelViewSetting.java中引用的模块和模型使用项目中代理处理的对象。

1)示例中心模型SimpleModel

import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.domain.model.ModelDefinition;
import pro.shushi.pamirs.meta.enmu.ModelTypeEnum;

@Base
@Model.model(SimpleModel.MODEL_MODEL)
@Model(displayName = "示例中心模型", labelFields = "displayName")
@Model.Advanced(type = ModelTypeEnum.PROXY)
public class SimpleModel extends ModelDefinition {

    public static final String MODEL_MODEL = "examples.system.SimpleModel";

}

2)示例中心模块SimpleModule

import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.annotation.sys.Base;
import pro.shushi.pamirs.meta.domain.module.ModuleDefinition;
import pro.shushi.pamirs.meta.enmu.ModelTypeEnum;

@Base
@Model.model(SimpleModule.MODEL_MODEL)
@Model(displayName = "示例中心模块", labelFields = "displayName")
@Model.Advanced(type = ModelTypeEnum.PROXY)
public class SimpleModule extends ModuleDefinition {

    public static final String MODEL_MODEL = "examples.system.SimpleModule";

}

3)动态页面菜单

@UxMenus
public class DemoMenus implements ViewActionConstants {

    @UxMenu("动态页面配置")
    @UxRoute(ModelRelViewSetting.MODEL_MODEL)
    class ModelRelViewSettingMenu {
    }
}
2、模型和视图的关系设置动作重新,创建按钮元数据等
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import pro.shushi.oinone.examples.simple.api.model.custom.config.ModelRelViewSetting;
import pro.shushi.oinone.examples.simple.core.action.helper.ModelDataHelper;
import pro.shushi.oinone.examples.simple.core.util.UUIDUtils;
import pro.shushi.pamirs.boot.base.enmu.ActionTypeEnum;
import pro.shushi.pamirs.boot.base.model.ViewAction;
import pro.shushi.pamirs.boot.base.ux.cache.api.ActionCacheApi;
import pro.shushi.pamirs.boot.base.ux.cache.api.ModelActionsCacheApi;
import pro.shushi.pamirs.meta.annotation.Action;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j;
import pro.shushi.pamirs.meta.api.session.PamirsSession;
import pro.shushi.pamirs.meta.constant.FunctionConstants;
import pro.shushi.pamirs.meta.domain.ModelData;
import pro.shushi.pamirs.meta.domain.module.ModuleDefinition;
import pro.shushi.pamirs.meta.enmu.ActionContextTypeEnum;
import pro.shushi.pamirs.meta.enmu.FunctionTypeEnum;
import pro.shushi.pamirs.meta.enmu.SystemSourceEnum;
import pro.shushi.pamirs.meta.enmu.ViewTypeEnum;

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

@Slf4j
@Component
@Model.model(ModelRelViewSetting.MODEL_MODEL)
public class ModelRelViewSettingAction {

    @Transactional
    @Action.Advanced(name = FunctionConstants.create, managed = true, invisible = "!IS_BLANK(activeRecord.id)")
    @Action(displayName = "确定", summary = "创建", bindingType = ViewTypeEnum.FORM)
    @Function(name = FunctionConstants.create)
    @Function.fun(FunctionConstants.create)
    public ModelRelViewSetting create(ModelRelViewSetting data) {

        ViewAction viewAction = buildAction(data);
        viewAction.construct();
        viewAction.create();

        data.setName(viewAction.getName());
        data.create();

        //写入modelData/刷新缓存 等
        actionMetaAddHandler(viewAction);

        return data;
    }

    @Transactional
    @Action.Advanced(name = FunctionConstants.update, managed = true, invisible = "IS_BLANK(activeRecord.id)")
    @Action(displayName = "更新", summary = "修改", bindingType = ViewTypeEnum.FORM)
    @Function(name = FunctionConstants.update)
    @Function.fun(FunctionConstants.update)
    public ModelRelViewSetting update(ModelRelViewSetting data) {

        ViewAction viewAction = buildAction(data);
        viewAction.updateById();
        data.updateById();

        //写入modelData/刷新缓存 等
        actionMetaAddHandler(viewAction);

        return data;
    }

    @Function.Advanced(type = FunctionTypeEnum.DELETE)
    @Function.fun(FunctionConstants.deleteWithFieldBatch)
    @Function(name = FunctionConstants.delete)
    @Action(displayName = "删除", contextType = ActionContextTypeEnum.SINGLE_AND_BATCH)
    public List<ModelRelViewSetting> delete(List<ModelRelViewSetting> dataList) {
        dataList.forEach(data -> {
            data.deleteById();

            ViewAction viewAction = new ViewAction().setModel(data.getOModel()).setName(data.getName());
            viewAction.deleteById();

            actionMetaDelHandler(viewAction);
        });

        return dataList;
    }

    private static ViewAction buildAction(ModelRelViewSetting data) {
        ViewAction viewAction = null;
        if (data.getId()==null) {
            viewAction = new ViewAction();
            viewAction.setName(UUIDUtils.generateUniqueKey("uiView"));
            viewAction.setActionType(ActionTypeEnum.VIEW);
            viewAction.setPriority(999);
            viewAction.setSys(false);
            viewAction.setSystemSource(SystemSourceEnum.UI);
            viewAction.setContextType(ActionContextTypeEnum.SINGLE);
            viewAction.setBindingType(Collections.singletonList(ViewTypeEnum.TABLE));
        } else {
            viewAction = new ViewAction().setModel(data.getModel()).setName(data.getName()).queryOne();
            if (viewAction == null){
                data = data.queryById();
                viewAction = new ViewAction().setModel(data.getOModel()).setName(data.getName()).queryOne();
            }
        }
        viewAction.setTitle(data.getTitle());
        viewAction.setDisplayName(data.getDisplayName());
        viewAction.setLabel(data.getDisplayName());
        viewAction.setResModel(data.getModel());
        viewAction.setModule(data.getModule());
        ModuleDefinition moduleDef = PamirsSession.getContext().getModule(data.getModule());
        viewAction.setModuleDefinition(moduleDef);
        if (moduleDef!=null) {
            viewAction.setModuleName(moduleDef.getName());
        }
        viewAction.setViewType(data.getViewType());
        viewAction.setTarget(data.getTarget());
        viewAction.setResView(data.getView());
        viewAction.setResViewName(data.getViewName());
        //viewAction.setOptionViewTypes();
        //viewAction.setOptionViewList();
        viewAction.setModelDefinition(data.getTargetModel());
        viewAction.setModel(data.getModel());
        //viewAction.setBindingView();
        //viewAction.setBindingViewName(data.getViewName());
        return viewAction;
    }

    public void actionMetaAddHandler(pro.shushi.pamirs.boot.base.model.Action action) {
        //处理元数据注册
        ModelData modelData = ModelDataHelper.convert(action);
        modelData.construct();
        modelData.createOrUpdate();

        if (log.isInfoEnabled()) {
            log.info("开始写入动作缓存,id:{},model:{},name:{}", action.getId(), action.getModel(), action.getName());
        }

        pro.shushi.pamirs.boot.base.model.Action finalAction = action;
        PamirsSession.getContext().putExtendCacheEntity(ActionCacheApi.class, (cacheApi) -> {
            cacheApi.put(finalAction.getSign(), finalAction);
        });

        PamirsSession.getContext().putExtendCacheEntity(ModelActionsCacheApi.class, (cacheApi) -> {
            String model = finalAction.getModel();
            //新建一个列表,全部处理完毕后再覆盖
            List<pro.shushi.pamirs.boot.base.model.Action> cacheActions = cacheApi.get(model);
            List<pro.shushi.pamirs.boot.base.model.Action> modelActions = new ArrayList<>();
            if (CollectionUtils.isNotEmpty(cacheActions)) {
                modelActions.addAll(cacheActions);
            }
            modelActions.stream().filter(i -> finalAction.getSign().equals(i.getSign())).findFirst().ifPresent(modelActions::remove);
            modelActions.add(finalAction);

            cacheApi.put(model, modelActions);
        });
    }

    public void actionMetaDelHandler(pro.shushi.pamirs.boot.base.model.Action action) {
        //处理元数据注册
        ModelData modelData = ModelDataHelper.convert(action);
        modelData.deleteByUnique();

        if (log.isInfoEnabled()) {
            log.info("开始删除动作缓存,id:{},model:{},name:{}", action.getId(), action.getModel(), action.getName());
        }

        pro.shushi.pamirs.boot.base.model.Action finalAction = action;
        PamirsSession.getContext().putExtendCacheEntity(ActionCacheApi.class, (cacheApi) -> {
            cacheApi.remove(finalAction.getModel(), finalAction.getName());
        });

        PamirsSession.getContext().putExtendCacheEntity(ModelActionsCacheApi.class, (cacheApi) -> {
            String model = finalAction.getModel();
            //新建一个列表,全部处理完毕后再覆盖
            List<pro.shushi.pamirs.boot.base.model.Action> cacheActions = cacheApi.get(model);
            List<pro.shushi.pamirs.boot.base.model.Action> modelActions = new ArrayList<>();
            if (CollectionUtils.isNotEmpty(cacheActions)) {
                modelActions.addAll(cacheActions);
            }
            modelActions.stream().filter(i -> finalAction.getSign().equals(i.getSign())).findFirst().ifPresent(modelActions::remove);
            modelActions.remove(finalAction);

            cacheApi.put(model, modelActions);
        });
    }

}

至此,已经写好模型跳转另一个模型的配置逻辑了。下面使用可视化的方式绑定

模型和视图的关系设置的模型数据操作

点击创建,添加源模型跳转目标模型的逻辑

image-20250218170518116

[后端] 行操作对应的模型增强

1、行操作对应的模型增加目标模型和上下文扩展

import pro.shushi.oinone.examples.simple.api.model.custom.config.ModelRelViewSetting;
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.NullableBoolEnum;

import java.util.List;

/**
 * @Author: shushi
 * @Description:
 */
@Model.model(CustomTaskCenter.MODEL_MODEL)
@Model(displayName = "任务中心")
public class CustomTaskCenter extends IdModel {

    public static final String MODEL_MODEL = "examples.biz.CustomTaskCenter";

    // 其他业务字段(忽略)

    //////////////////////////////////////////////////////
    // 页面自定义跳转使用
    @Field.String(size = 50)
    @Field(displayName = "目标模型", summary = "自定义动作的目标模型")
    private String targetModel;

    @Field.one2many
    @Field.Relation(store = false)
    @Field(displayName = "上下文扩展字段", store = NullableBoolEnum.FALSE, invisible = true)
    private List<ModelRelViewSetting> customViewAction;

}

2、行操作对应的模型重写queryPage,获取行记录的上下文扩展

import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;
import pro.shushi.oinone.examples.simple.api.model.custom.biz.CustomTaskCenter;
import pro.shushi.oinone.examples.simple.api.model.custom.config.ModelRelViewSetting;
import pro.shushi.pamirs.framework.connectors.data.sql.Pops;
import pro.shushi.pamirs.framework.connectors.data.sql.query.QueryWrapper;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j;
import pro.shushi.pamirs.meta.api.dto.condition.Pagination;
import pro.shushi.pamirs.meta.common.util.ListUtils;
import pro.shushi.pamirs.meta.constant.FunctionConstants;
import pro.shushi.pamirs.meta.enmu.FunctionOpenEnum;
import pro.shushi.pamirs.meta.enmu.FunctionTypeEnum;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@Component
@Model.model(CustomTaskCenter.MODEL_MODEL)
public class CustomTaskCenterAction {

    @Function.Advanced(type = FunctionTypeEnum.QUERY)
    @Function.fun(FunctionConstants.queryPage)
    @Function(openLevel = {FunctionOpenEnum.API})
    public Pagination<CustomTaskCenter> queryPage(Pagination<CustomTaskCenter> page, QueryWrapper<CustomTaskCenter> queryWrapper) {
        page = new CustomTaskCenter().queryPage(page, queryWrapper);
        if (CollectionUtils.isNotEmpty(page.getContent())) {
            computeCustomViewAction(page.getContent());
        }

        return page;
    }

    private void computeCustomViewAction(List<CustomTaskCenter> taskCenters) {
        List<String> models = ListUtils.transform(taskCenters, CustomTaskCenter::getTargetModel);
        List<ModelRelViewSetting> customViewActions = new ModelRelViewSetting().queryList(Pops.<ModelRelViewSetting>lambdaQuery().setBatchSize(-1)
                .from(ModelRelViewSetting.MODEL_MODEL).in(ModelRelViewSetting::getModel, models));

        if (CollectionUtils.isEmpty(customViewActions)) {
            return;
        }

        Map<String/**model*/, List<ModelRelViewSetting>> modelRelViewSettingMap = customViewActions.stream().collect(Collectors.groupingBy(ModelRelViewSetting::getModel));
        taskCenters.forEach(business -> {
            List<ModelRelViewSetting> modelRelViewSettingList = modelRelViewSettingMap.get(business.getTargetModel());
            business.setCustomViewAction(modelRelViewSettingList);
        });
    }

}

[界面设计器] 行操作对应列表配置

1、业务数据的列表增加「上下文扩展字段」,并设置隐藏和配置透出字段,参考下面的截图

image-20250219151302564

image-20250219151319818

2、配置操作按钮,根据业务情况拖跳转动作如「详情」或者「填报」等,拖拽跳转动作时,开启「保留动作」这样可以填写API名称。

[注意]:设计器跳转动作(需动态路由的)配置的「页面打开方式」 和 模型视图列表中的「页面打开方式」必须一致,否则会报错:未配置弹窗视图

[权限] 自定义动作权限扩展和配置

1、权限扩展,自定义动作的权限解析到权限树上

import org.apache.commons.collections4.CollectionUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import pro.shushi.oinone.examples.simple.api.model.custom.config.ModelRelViewSetting;
import pro.shushi.pamirs.auth.api.entity.node.ActionPermissionNode;
import pro.shushi.pamirs.auth.api.entity.node.PermissionNode;
import pro.shushi.pamirs.auth.api.extend.PermissionNodeLoadExtendApi;
import pro.shushi.pamirs.auth.api.helper.AuthNodeHelper;
import pro.shushi.pamirs.boot.base.enmu.ActionTypeEnum;
import pro.shushi.pamirs.boot.base.model.Action;
import pro.shushi.pamirs.boot.base.model.ViewAction;
import pro.shushi.pamirs.core.common.TranslateUtils;
import pro.shushi.pamirs.core.common.query.QueryActions;
import pro.shushi.pamirs.meta.common.spi.SPI;

import java.util.List;

/**
 * 自定义动作 加载扩展
 *
 * @author shushi
 */
@Component
@Order(80)
@SPI.Service("CustomViewActionPermissionNodeLoadExtend")
public class CustomViewActionPermissionNodeLoadExtend implements PermissionNodeLoadExtendApi {

    @Override
    public void buildRootPermissions(List<PermissionNode> nodes) {
        List<ModelRelViewSetting> settings = new ModelRelViewSetting().queryList();
        if (CollectionUtils.isEmpty(settings)) {
            return;
        }

        QueryActions<ViewAction> queryActions = new QueryActions<>(ActionTypeEnum.VIEW);
        settings.stream().forEach(setting -> {
            queryActions.add(setting.getModel(), setting.getName());
        });
        List<ViewAction> viewActions = queryActions.query();
        if (CollectionUtils.isEmpty(viewActions)) {
            return;
        }

        PermissionNode root = createTopBarNode();
        createActionNodes(viewActions, root);
        if (!root.getNodes().isEmpty()) {
            nodes.add(0, root);
        }
    }

    private void addNode(PermissionNode node, PermissionNode target) {
        if (target == null) {
            return;
        }
        node.getNodes().add(target);
    }

    private PermissionNode createTopBarNode() {
        return AuthNodeHelper.createNode("ExamplesCustomViewAction", TranslateUtils.translateValues("XX动作权限"));
    }

    private ActionPermissionNode createActionNode(Action action, PermissionNode parentNode, ViewAction viewAction) {
        ActionPermissionNode node = AuthNodeHelper.createActionNode(viewAction.getModule(), action, parentNode);
        if (node == null) {
            return null;
        }
        node.setPath("/" +viewAction.getModel() + "/" +viewAction.getName());
        return node;
    }

    private void createActionNodes(List<ViewAction> viewActions, PermissionNode parentNode) {
        for (int i = 0; i < viewActions.size(); i++) {
            ViewAction action = viewActions.get(i);
            addNode(parentNode, createActionNode(action, parentNode, action));
        }
    }

}

上面的权限扩展后自动会将自定义的动作加载到权限树中,后面可以安装正常的权限配置进行

image-20250218171137362

使用步骤

  1. 添加模型和视图的关系

    image-20250219152421792

  2. 添加一条业务数据,绑定目标模型编码

    image-20250219152703749

  3. 查看页面可以看到上下文参数已经有数据了,查看接口返回可以看到具体的viewAction数据。再配合前端代码实现跳转。

    image-20250219152825868

[前端] 前端根据列表模型和API名称自定义动作
import {
    ActionContextType,
    ActionType,
    ActionWidget,
    DialogViewActionWidget,
    executeViewAction,
    RouterViewActionWidget,
    RuntimeViewAction,
    SPI,
    ViewType,
    Widget
  } from '@kunlun/dependencies';
  import CustomViewActionVue from './CustomViewAction.vue';

  @SPI.ClassFactory(
    ActionWidget.Token({
      model: 'demo.CooperationCenter', // 需要更换为对应页面model
      name: 'uiViewaee878c066d4490195246f62add8ffff' // 从页面debug中找同名name按钮
    })
  )
  export class VieDynamicActionWidget extends RouterViewActionWidget {
    /**
     * 重写按钮的点击事件
     */
    protected clickAction(): Promise<void> {
      const activeRecord = this.activeRecords?.[0] || {};
      // 构建上下文
      const context = this.buildContext(activeRecord);

      // 执行当前动作
      executeViewAction(
        {
          ...this.action,
          context
        },
        undefined,
        undefined,
        activeRecord
      );
      return null as any;
    }

    @Widget.Reactive()
    public get action() {
      // console.log('---1',this.activeRecords?.[0].customViewAction);
      // customViewAction 约定的数据名称
      const list = (this.activeRecords?.[0].customViewAction || []) as RuntimeViewAction[];
      const item =
        list.find((item) => {
          return item.viewType == ViewType.Form;
        }) || ({} as RuntimeViewAction);
      return {
        ...item,
        contextType: ActionContextType.Single,
        actionType: ActionType.View,
        sessionPath: `/${item.model}/${item.name}`,
        context: { id: this.activeRecords?.[0].id }
      };
    }
    // 注: 目标页面的加载函数需配置为 queryOne queryOne
  }

Oinone社区 作者:yexiu原创文章,如若转载,请注明出处:https://doc.oinone.top/dai-ma-shi-jian/20495.html

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

(0)
yexiuyexiu
上一篇 2025年2月17日 am11:43
下一篇 2025年2月22日 pm2:17

相关推荐

  • Excel添加水印功能

    实现ExcelWriteHandlerExtendApi接口从而实现对Excel增加复杂功能的操作。如添加水印。具体实现请自行百度 /** * 根据上下文判断是否执行 * * @param context Excel定义上下文 * @return 是否执行该扩展 */ boolean match(ExcelDefinitionContext context); /** * 构建出一个WriteWorkbook对象,即一个工作簿对象,对应的是一个Excel文件; * * @param builder 可用于设置inMemory=true实现复杂功能(如添加水印) */ default void extendBuilder(ExcelWriterBuilder builder) { } 例:Excel添加水印 本例参考文章:Java使用EasyExcel导出添加水印 实现ExcelWriteHandlerExtendApi接口,并添加@Component注解 添加依赖包 <!– eaysexcel –> <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.3.2</version> </dependency> <!– poi 添加水印 –> <dependency> <groupId>org.apache.poi</groupId> <artifactId>ooxml-schemas</artifactId> <version>1.4</version> </dependency> <!– 使用了hutool的工具类 –> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.20</version> </dependency> package pro.shushi.pamirs.top.core.temp; import cn.hutool.core.img.ImgUtil; import com.alibaba.excel.write.builder.ExcelWriterBuilder; import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder; import org.apache.poi.openxml4j.opc.PackagePartName; import org.apache.poi.openxml4j.opc.PackageRelationship; import org.apache.poi.openxml4j.opc.TargetMode; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.xssf.usermodel.XSSFPictureData; import org.apache.poi.xssf.usermodel.XSSFRelation; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.stereotype.Component; import pro.shushi.pamirs.file.api.context.ExcelDefinitionContext; import pro.shushi.pamirs.file.api.easyexcel.ExcelWriteHandlerExtendApi; import java.awt.*; import java.awt.image.BufferedImage; @Component public class CustomWaterMarkHandler implements ExcelWriteHandlerExtendApi { private final WaterMark watermark; public CustomWaterMarkHandler() { this.watermark = new WaterMark().setContent("ABC"); } @Override public void extendBuilder(ExcelWriterBuilder builder) { builder.inMemory(true); } @Override public boolean match(ExcelDefinitionContext context) { return DemoTemplate.TEMPLATE_NAME.equals(context.getName()); } @Override public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { try { BufferedImage bufferedImage = createWatermarkImage(); setWaterMarkToExcel((XSSFWorkbook) writeWorkbookHolder.getWorkbook(), bufferedImage); } catch (Exception e) { throw new RuntimeException("添加水印出错",e); } } private BufferedImage createWatermarkImage() { final Font font = watermark.getFont(); final int width = watermark.getWidth(); final int height = watermark.getHeight(); String[] textArray…

    2024年9月6日
    00
  • 标品实施:从标品构建到定制(扩展)包的开发

    总体描述 Oinone有一个非常重要的特性:通过平台承载标准化产品(标品)。针对不同客户的个性化需求,不再直接修改标准产品代码,而是以扩展包的形式进行扩展和定制化,通过继承和重写标准产品的能力来满足客户需求。 本文讲解述怎么通过标品构建扩展工程的过程。 构建标品 按照Oinone的规范构建标品工程 构建扩展包 在定制模块中指定上游模块 上游依赖模块upstreams,模块定义如下: @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Module { // 显示名称 @AliasFor("displayName") String value() default ""; // 依赖模块名列表 String[] dependencies() default ModuleConstants.MODULE_BASE; // 上游模块名列表 String[] upstreams() default {}; …… 扩展模块示例 @Component @Module( name = SecondModule.MODULE_NAME, displayName = "DEMO扩展", version = "1.0.0", // 指定上游模块(标品模块,可以为多个) upstreams = DemoModule.MODULE_MODULE, priority = 1, dependencies = {ModuleConstants.MODULE_BASE, CommonModule.MODULE_MODULE, UserModule.MODULE_MODULE, AuthModule.MODULE_MODULE, BusinessModule.MODULE_MODULE, // 上游模块(标品模块,可以为多个) DemoModule.MODULE_MODULE, } ) @Module.module(SecondModule.MODULE_MODULE) @Module.Advanced(selfBuilt = true, application = true) @UxHomepage(@UxRoute(model = WorkRecord.MODEL_MODEL)) public class SecondModule implements PamirsModule { public static final String MODULE_MODULE = "demo_core_ext"; public static final String MODULE_NAME = "DemoCoreExt"; @Override public String[] packagePrefix() { return new String[]{ "pro.shushi.pamirs.second" }; } } application.yml配置文件 pamirs: boot: modules: ….. – demo_core // 加标准工程 – demo_core_ext maven配置 父工程依赖 <dependencyManagement> <dependencies> ….. <dependency> <groupId>pro.shushi.pamirs.demo</groupId> <artifactId>pamirs-demo-api</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency> <dependency> <groupId>pro.shushi.pamirs.demo</groupId> <artifactId>pamirs-demo-core</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency> ….. </dependencies> </dependencyManagement> api子工程加入依赖 <dependency> <groupId>pro.shushi.pamirs.demo</groupId> <artifactId>pamirs-demo-api</artifactId> </dependency> boot子工程加入依赖 <dependency> <groupId>pro.shushi.pamirs.demo</groupId> <artifactId>pamirs-demo-core</artifactId> </dependency> 数据库设置 base数据库要跟标品工程一致 注意事项 标品工程的第三方依赖,在扩展工程都要有,否则启动会报错 扩展模块功能开发 菜单扩展 1、可以按需隐藏标品的菜单; 2、可以根据扩展包的实际情况增加菜单; 模型扩展 1、扩展包可继承标品已有模型; 2、扩展包可根据实际情况新增自有模型; 函数扩展 1、扩展包可根据实际情况【覆写】标品中的函数; 2、扩展包可根据实际情况【新增】自有函数; 3、扩展包可通过Hook机制实现业务的个性化; 4、扩展包可根据自身业务情况实现标品中的扩展点; 5、……

    2024年6月1日
    00
  • 非存储字段搜索,适应灵活的搜索场景

    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日
    00
  • 如何改变调度策略,让Schedule独立执行线程

    1、后台创建task type相关的类,继承BaseScheduleNoTransactionTask,要加springbean的注解,参考:task type建议使用类名 2、提交任务的时候,设置tasktype为步骤1的TaskType 3、控制台新增策略和任务bean名称为步骤1的spring beanName,任务名称 $xxx,右边的占位符内容为yml里面配置的ownSign字段任务的名称也是步骤1的 spring beanName 4、配置完成后,控制台启动任务,就可以测试了

    2024年2月20日
    00
  • 左树右表,支撑不同场景的树表结构

    左树右表俩种情况 假设存在 A模型 B模型 1: 左树为A模型,右表为B模型 举例 A模型为类目 B模型为类目属性模型代码实例: @Model.model(AriesPlatformCategory.MODEL_MODEL) @Model(displayName = "平台后台类目", labelFields = "name") @Model.Advanced(type = ModelTypeEnum.PROXY) public class AriesPlatformCategory extends AriesCategory { public static final String MODEL_MODEL = "aries.item.AriesPlatformCategory"; @Field.many2one @Field.Relation(relationFields = {"parentCateCode"}, referenceFields = {"code"},store = true) @Field(displayName = "平台父类目") private AriesPlatformCategory platformCategory; @Field.one2many @Field(displayName = "类目属性") @Field.Relation(relationFields = "code", referenceFields = "categoryCode", store = true) private List<AriesPlatformCategoryAttr> platformCategoryAttrs; } @Model.model(AriesPlatformCategoryAttr.MODEL_MODEL) @Model(displayName = "Aries_平台类目属性", labelFields = "name") @Model.Advanced(type = ModelTypeEnum.PROXY) public class AriesPlatformCategoryAttr extends CategoryAttr { public static final String MODEL_MODEL = "aries.item.AriesPlatformCategoryAttr"; @Field.many2one @Field(displayName = "平台后台类目") @Field.Relation(relationFields = "categoryCode", referenceFields = "code", store = true) private AriesPlatformCategory platformCategory; } 在设计器设计左树右表之前,需要在模型 中配置好关联关系 。如下部分代码 配置好类目与父类目的关联关系。 @Field.many2one @Field.Relation(relationFields = {"parentCateCode"}, referenceFields = {"code"},store = true) @Field(displayName = "平台父类目") private AriesPlatformCategory platformCategory; 配置好 类目与类目属性的关联关系。一个类目可以有多个类目属性,一对多one2many @Field.one2many @Field(displayName = "类目属性") @Field.Relation(relationFields = "code", referenceFields = "categoryCode", store = true) private List<AriesPlatformCategoryAttr> platformCategoryAttrs; 在类目属性模型中,配置好属性与类目的关联关系,一个类目属性只属于一个类目,一个类目可以有多个类目属性。类目属性对类目多对一many2one @Field.many2one @Field(displayName = "平台后台类目") @Field.Relation(relationFields = "categoryCode", referenceFields = "code", store = true) private AriesPlatformCategory platformCategory; 设计器实例:1.需要选择 平台类目属性 做为主模型创建树表页面2.构建关联关系 选择平台后台类目 第一级的筛选条件 上级编码为空 表格关联关系字段 选择 平台类目属性。 3.表格拖拽好需要的属性字段 2: 左树为A模型,右表也为A模型 举例 左A模型 组织结构管理 右A模型 组织结构管理模型代码实例: @Model.model(BasicOrg.MODEL_MODEL) @Model(displayName = "组织结构管理", summary…

    2024年2月20日
    00

Leave a Reply

登录后才能评论