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

背景

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

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

解决思路

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

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,
    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 {
    @Widget.Reactive()
    public get action() {
      // console.log('---1',this.activeRecords?.[0].customViewAction);
      // customViewAction 约定的数据名称
      const list = (this.activeRecords?.[0].customViewAction || []) as RuntimeViewAction[];
      // 上下文参数指定要跳什么类型的页面,默认跳表单
      const viewType = super.action.context?.viewType || ViewType.Form;
      // 从列表里找到指定的动作
      const item =
        list.find((item) => {
          return item.viewType == viewType;
        }) || ({} as RuntimeViewAction);
      return {
        ...item,
        contextType: ActionContextType.Single,
        actionType: ActionType.View,
        sessionPath: `/${item.model}/${item.name}`,
        context: { id: this.activeRecords?.[0].id, ...(super.action.context || {}) }
      };
    }
    // 注: 目标页面的加载函数需配置为 queryOne queryOne
  }

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

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

(0)
yexiu的头像yexiu数式员工
上一篇 2025年2月17日 am11:43
下一篇 2025年2月22日 pm2:17

相关推荐

  • 如何给角色增加菜单权限

    对接第三方的权限时,第三方传过来菜单项,需要拿着这些菜单在平台这边进行授权,可以使用代码的方式给指定菜单创建权限代码示例: public class demo { @Autowired private PermissionNodeLoader permissionNodeLoader; @Autowired private AuthRbacRolePermissionServiceImpl authRbacRolePermissionService; public void roleAuthorization() { ArrayList<Menu> menus = new ArrayList<>(); menus.add(new Menu().queryOneByWrapper(Pops.<Menu>lambdaQuery() .from(Menu.MODEL_MODEL) .eq(Menu::getName, "uiMenu90dd10ae7cc4459bacd2845754b658a8") .eq(Menu::getModule, TopModule.MODULE_MODULE))); menus.add(new Menu().queryOneByWrapper(Pops.<Menu>lambdaQuery() .from(Menu.MODEL_MODEL) .eq(Menu::getName, "TopMenus_shoppMenu_Shop3Menu_ShopSayHello52eMenu") .eq(Menu::getModule, TopModule.MODULE_MODULE))); //加载指定角色的全部资源权限项 ResourcePermissionNodeLoader loader = permissionNodeLoader.getManagementLoader(); List<PermissionNode> nodes = loader.buildRootPermissions(); List<AuthRbacResourcePermissionItem> authRbacRolePermissionProxies = new ArrayList<>(); //给指定角色创建权限,如果需要多个角色,可以for循环依次执行authRbacRolePermissionService.update(authRbacRolePermissionProxy) AuthRole authRole = new AuthRole().queryOneByWrapper(Pops.<AuthRole>lambdaQuery() .from(AuthRole.MODEL_MODEL) .eq(AuthRole::getCode, "R003") .eq(AuthRole::getName, "R003")); AuthRbacRolePermissionProxy authRbacRolePermissionProxy = new AuthRbacRolePermissionProxy(); AuthRole.transfer(authRole, authRbacRolePermissionProxy); for (PermissionNode node : nodes) { traverse(node, authRbacRolePermissionProxies, menus); } authRbacRolePermissionProxy.setResourcePermissions(authRbacRolePermissionProxies); authRbacRolePermissionService.update(authRbacRolePermissionProxy); } private void traverse(PermissionNode node, List<AuthRbacResourcePermissionItem> authRbacRolePermissionProxies, ArrayList<Menu> menus) { if (node == null) { return; } //按照指定菜单进行过滤,如果不是指定菜单,则设置菜单项不可访问,如果是指定菜单,则设置可访问 Set<Long> menuIds = new HashSet<>(); for (Menu menu : menus) { menuIds.add(menu.getId()); } if (node instanceof MenuPermissionNode) { AuthRbacResourcePermissionItem item = new AuthRbacResourcePermissionItem(); if (menuIds.contains(Long.parseLong(node.getId()))) { item.setCanAccess(Boolean.TRUE); } else { item.setCanAccess(Boolean.FALSE); } item.setCanManagement(node.getCanManagement()); item.setPath(node.getPath()); item.setSubtype(node.getNodeType()); item.setType(AuthEnumerationHelper.getResourceType(node.getNodeType())); item.setDisplayName(node.getDisplayValue()); item.setResourceId(node.getResourceId()); authRbacRolePermissionProxies.add(item); } List<PermissionNode> childNodes = node.getNodes(); if (CollectionUtils.isNotEmpty(childNodes)) { for (PermissionNode child : childNodes) { traverse(child, authRbacRolePermissionProxies, menus); } } } } 执行看效果

    2024年11月14日
    93400
  • 如何在代码中使用自增ID和获取序列

    在使用继承IDModel或CodeModel时,id和code是系统默认自动生成, 默认值规则:ID–>分布式ID; CODE–>根据定义的SequenceConfig规则自动生成。 在特定情况下需要落库前先生成ID或者Code,这些场景下可参照如下代码示例 一、使用自增ID 单个字段设置方式 // 主键字段,可以使用mysql的自增能力 @Field.Integer @Field.PrimaryKey(keyGenerator = KeyGeneratorEnum.AUTO_INCREMENT) @Field.Advanced(batchStrategy = FieldStrategyEnum.NEVER) @Field(displayName = "id", summary = "Id字段,⾃增") private Long id; @Field.Integer @Field(displayName = "自增版本") @Field.Sequence(sequence = "SEQ", initial = 1) private Long version; 全局设置方式 该方式会作用到每一个存储模型的id字段,在application.yml配置文件中修改id的生成规则,查找配置项关键字key-generator,默认为DISTRIBUTION(分布式id),可修改为 AUTO_INCREMENT(自增id) 二、手动方式获取序列 获取方式示例1 /** * 在特定场景下需要手动生成Id或者code时,可参照这个示例 */ public void manualSetIdCode(){ DemoItem demoItem = new DemoItem(); //手动生成ID和code Object idObj = Spider.getDefaultExtension(IdGenerator.class).generate(PamirsTableInfo.fetchKeyGenerator(DemoItem.MODEL_MODEL)); demoItem.setId(TypeUtils.createLong(idObj)); Object codeObj = CommonApiFactory.getSequenceGenerator().generate("SEQ",DemoItem.MODEL_MODEL); String code = TypeUtils.stringValueOf(codeObj); demoItem.setCode(code); //…… } 获取方式示例2 1、在系统启动的时候初始化SequenceConfig package pro.shushi.pamirs.demo.core.init; import org.springframework.stereotype.Component; import pro.shushi.pamirs.boot.common.api.command.AppLifecycleCommand; import pro.shushi.pamirs.boot.common.extend.MetaDataEditor; import pro.shushi.pamirs.core.common.InitializationUtil; import pro.shushi.pamirs.demo.api.DemoModule; import pro.shushi.pamirs.demo.core.constant.SeqConstants; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import pro.shushi.pamirs.meta.api.dto.meta.Meta; import pro.shushi.pamirs.meta.enmu.SequenceEnum; import java.util.Map; /** * DemoMetadataEditor */ @Slf4j @Component public class DemoMetadataEditor implements MetaDataEditor { @Override public void edit(AppLifecycleCommand command, Map<String, Meta> metaMap) { InitializationUtil util = InitializationUtil.get(metaMap, DemoModule.MODULE_MODULE, DemoModule.MODULE_NAME); if (util == null) { log.error("获取初始化序列失败"); return; } bizSequence(util); } private void bizSequence(InitializationUtil util) { util.createSequenceConfig("申请单编码生成", SeqConstants.NABEL_SAMPLE_APPLY_SEQ, SequenceEnum.ORDERLY_SEQ, 8) .setStep(1) .setInitial(80000000L) .setIsRandomStep(false); util.createSequenceConfig("订单编码生成", SeqConstants.NABEL_SAMPLE_ORDER_SEQ_YP, SequenceEnum.ORDERLY_SEQ, 8) .setPrefix("YP") .setStep(1) .setInitial(80000000L) .setIsRandomStep(false); } } 2、在代码中使用序列 public static String getSaleOrderCode() { Object sequence = CommonApiFactory.getSequenceGenerator().generate(SequenceEnum.ORDERLY_SEQ.value(), SeqConstants.NABEL_SAMPLE_STRUCTURE_SEQ); return TypeUtils.stringValueOf(sequence); } public static String getApplyOrderCode(String prefix) { Object sequence = CommonApiFactory.getSequenceGenerator().generate(SequenceEnum.ORDERLY_SEQ.value(), SeqConstants.NABEL_SAMPLE_APPLY_SEQ); return…

    2024年5月25日
    1.9K00
  • 【前端】项目开发前端知识要点地图

    概述 下面整理了目前现有的所有文章,并提供了基本的学习路径。所有使用*标记的文章属于推荐必读文章。 目录 基础篇 【路由】浏览器地址栏url参数介绍 母版-布局-DSL 渲染基础(v4)* 组件SPI机制(v4)* 组件数据交互基础(v4)* 组件生命周期(v4) 入门篇 自定义视图组件(v4)* 如何通过浏览器开发者工具提高调试效率* 如何提高自定义组件的开发效率* 自定义组件之自动渲染(组件插槽的使用)(v4)* GraphQL请求详解(v4)* 上下文在字段和动作中的应用 如何实现页面间的跳转 如何自定义指定页面的样式 进阶篇 自定义组件之手动渲染基础(v4) 自定义组件之手动渲染弹出层(v4) 自定义组件之手动渲染任意视图(v4) 【前端】IOC容器(v4) 最佳实践篇 【前端】工程结构最佳实践(v4)* 【前端】移动端工程结构最佳实践(v4)* 界面设计器实战篇 基础篇 【界面设计器】模型增删改查基础 【界面设计器】他表字段 【界面设计器】左树右表 【界面设计器】树形表格 【界面设计器】树下拉/级联选择 【界面设计器】自定义字段组件基础 展示篇 【界面设计器】自定义字段组件实战——轮播图 【界面设计器】自定义字段组件实战——表格字段组合展示 【界面设计器】自定义字段组件实战——表格字段内嵌表格 交互篇 【界面设计器】自定义字段组件实战——千分位输入框 其他 前端低无一体使用教程 如何自定义表格字段? 【界面设计器】组件开发常见问题 【前端】低无一体部署常见问题 【前端】生产环境性能调优 API文档 OioProvider详解(v4.3.0)* 前端环境配置(v4)* 默认布局模板(v4) 表格主题配置(v4) 运行时上下文API文档(v4) Class Component(ts)(v4)

    2024年5月25日
    4.2K00
  • 系统图标使用自定义CDN地址(内网部署)

    在实际项目中,客户网络环境不能访问外网即纯内网部署。此时需要将所有的静态资源都放在客户内部的CDN上,该篇详细说明实现步骤。 实现步骤 1、把图片等静态资源上传到本地CDN上(如MINIO、Nginx等),图片等静态资源找 数式支持人员 提供; 【注意】:MINIO情况,放置图片等静态资源的桶权限需设置为公共读; 2、项目中YAML的OSS配置,使用本地CDN、并指定使用的本地CDN图标的标识appLogoUseCdn: true, OSS配置参考如下: 本地CDN使用MINIO(仅示例需根据实际情况修改) cdn: oss: name: MINIO type: MINIO # MINIO的配置根据实际情况修改 bucket: pamirs(您的bucket) # 上传和下载地址根据实际情况修改 uploadUrl: http://39.103.145.77:9000 downloadUrl: http://39.103.145.77:9000 accessKeyId: 您的accessKeyId accessKeySecret: 您的accessKeySecret # mainDir对用CDN的图片目录,根据项目情况自行修改 mainDir: upload/demo/ validTime: 3600000 timeout: 600000 active: true referer: # 使用客户自己的CDN的图片,否则系统默认的从数式的CDN中获取 appLogoUseCdn: true 或本地CDN使用Nginx(仅示例需根据实际情况修改) cdn: oss: name: 本地文件NG系统 type: LOCAL bucket: # uploadUrl 这个是Oinone后端服务地址和端口 uploadUrl: http://192.168.0.129:8099 # downloadUrl前端地址,即直接映射在nginx的静态资源的路径和端口 downloadUrl: http://192.168.0.129:9999 validTime: 3600000 timeout: 600000 active: true referer: # 本地Nginx静态资源目录 localFolderUrl: /opt/pamirs/static # 使用客户自己的CDN的图片,否则系统默认的从数式的CDN中获取 appLogoUseCdn: true 3、前端工程3.1 前端源码工程,在.evn中把 STATIC_IMG地址进行修改;http(https)、IP和端口改成与CDN对应的配置,URL中/oinone/static/images是固定的;例如: 本地CDN使用MINIO(仅示例需根据实际情况修改) STATIC_IMG: 'http://39.103.145.77:9000/pamirs(这里替换为OSS中的bucket)/oinone/static/images' 或本地CDN使用Nginx(仅示例需根据实际情况修改) STATIC_IMG: 'http://192.168.0.129:9999/static/oinone/static/images' 3.2 对于已经打包好的前端资源对于已打包好的前端资源即无法修改.evn的情况;需在前端资源的根目录,新建config/manifest.js. 如果已存在则不需要新建,同时原来的内容也不需要删除(追加即可),需增加的配置: 本地CDN使用MINIO(仅示例需根据实际情况修改) runtimeConfigResolve({ STATIC_IMG: 'http://39.103.145.77:9000/pamirs(这里替换为OSS中的bucket)/oinone/static/images', plugins: { usingRemote: true } }) 或本地CDN使用Nginx(仅示例需根据实际情况修改) runtimeConfigResolve({ STATIC_IMG: 'http://192.168.0.129:9999/static/oinone/static/images', plugins: { usingRemote: true } })

    2025年2月8日
    1.2K00
  • 如何使用位运算的数据字典

    场景举例 日常有很多项目,数据库中都有表示“多选状态标识”的字段。在这里用我们项目中的一个例子进行说明一下: 示例一: 表示某个商家是否支持多种会员卡打折(如有金卡、银卡、其他卡等),项目中的以往的做法是:在每条商家记录中为每种会员卡建立一个标志位字段。如图: 用多字段来表示“多选标识”存在一定的缺点:首先这种设置方式很明显不符合数据库设计第一范式,增加了数据冗余和存储空间。再者,当业务发生变化时,不利于灵活调整。比如,增加了一种新的会员卡类型时,需要在数据表中增加一个新的字段,以适应需求的变化。  – 改进设计:标签位flag设计二进制的“位”本来就有表示状态的作用。可以用各个位来分别表示不同种类的会员卡打折支持:这样,“MEMBERCARD”字段仍采用整型。当某个商家支持金卡打折时,则保存“1(0001)”,支持银卡时,则保存“2(0010)”,两种都支持,则保存“3(0011)”。其他类似。表结构如图: 我们在编写SQL语句时,只需要通过“位”的与运算,就能简单的查询出想要数据。通过这样的处理方式既节省存储空间,查询时又简单方便。 //查询支持金卡打折的商家信息:   select * from factory where MEMBERCARD & b'0001'; // 或者:   select * from factory where MEMBERCARD & 1;    // 查询支持银卡打折的商家信息:   select * from factory where MEMBERCARD & b'0010'; // 或者:   select * from factory where MEMBERCARD & 2; 二进制( 位运算)枚举 可以通过@Dict注解设置数据字典的bit属性或者实现BitEnum接口来标识该枚举值为2的次幂。二进制枚举最大的区别在于值的序列化和反序列化方式是不一样的。 位运算的枚举定义示例 import pro.shushi.pamirs.meta.annotation.Dict; import pro.shushi.pamirs.meta.common.enmu.BitEnum; @Dict(dictionary = ClientTypeEnum.DICTIONARY, displayName = "客户端类型枚举", summary = "客户端类型枚举") public enum ClientTypeEnum implements BitEnum { PC(1L, "PC端", "PC端"), MOBILE(1L << 1, "移动端", "移动端"), ; public static final String DICTIONARY = "base.ClientTypeEnum"; private final Long value; private final String displayName; private final String help; ClientTypeEnum(Long value, String displayName, String help) { this.value = value; this.displayName = displayName; this.help = help; } @Override public Long value() { return value; } @Override public String displayName() { return displayName; } @Override public String help() { return help; } } 使用方法示例 API: addTo 和 removeFrom List<ClientTypeEnum> clientTypes = module.getClientTypes(); // addTo ClientTypeEnum.PC.addTo(clientTypes); // removeFrom ClientTypeEnum.PC.removeFrom(clientTypes); 在查询条件中的使用 List<Menu> moduleMenus = new Menu().queryListByWrapper(menuPage, LoaderUtils.authQuery(wrapper).eq(Menu::getClientTypes, ClientTypeEnum.PC));

    2023年11月24日
    1.7K00

Leave a Reply

登录后才能评论