动态视图支持权限配置

动态页面在前端自定义之后,目前是没办法通过权限去控制路由页面的权限的,本篇文章将介绍如何解决这个问题。
阅读本篇文章之前,应已经实现自定义页面渲染动态视图。参考文章:自定义视图内部渲染动态视图


实现思路:

  1. 通过自定义页面里设计的组件api名字,获取配置的路由页面名字。
  2. 解析路由页面包含的所有动作,拼接权限节点。
  3. 将路由页面的权限节点,拼到自定义视图所在菜单的权限管理上。

效果,路由页面的动作出现在自定义视图所在的菜单权限节点上
动态视图支持权限配置

代码示例

package pro.shushi.pamirs.top.core.auth;

import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
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.load.PermissionNodeLoadExtendApi;
import pro.shushi.pamirs.auth.api.loader.visitor.AuthCompileContext;
import pro.shushi.pamirs.auth.api.loader.visitor.AuthCompileVisitor;
import pro.shushi.pamirs.auth.api.loader.visitor.DslParser;
import pro.shushi.pamirs.auth.api.pmodel.AuthResourceAuthorization;
import pro.shushi.pamirs.auth.api.utils.AuthAuthorizationHelper;
import pro.shushi.pamirs.boot.base.model.Menu;
import pro.shushi.pamirs.boot.base.model.View;
import pro.shushi.pamirs.boot.base.model.ViewAction;
import pro.shushi.pamirs.boot.base.ux.model.UIView;
import pro.shushi.pamirs.boot.base.ux.model.UIWidget;
import pro.shushi.pamirs.boot.base.ux.model.view.UIField;
import pro.shushi.pamirs.boot.web.loader.path.AccessResourceInfo;
import pro.shushi.pamirs.boot.web.loader.path.ResourcePath;
import pro.shushi.pamirs.boot.web.manager.MetaCacheManager;
import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j;
import pro.shushi.pamirs.meta.common.spi.SPI;
import pro.shushi.pamirs.top.api.TopModule;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Component
@Order(88)
@SPI.Service
@Slf4j
public class MyTestNodeLoadExtend implements PermissionNodeLoadExtendApi {

    @Autowired
    protected MetaCacheManager metaCacheManager;

    @Override
    public List<PermissionNode> buildNextPermissions(PermissionNode selected, List<PermissionNode> nodes) {
        // 动态页面所属的菜单name
        List<String> menuNames = new ArrayList<>();
        menuNames.add("uiMenu3c094a75bd88461ba0ad780825069b32");
        // 自定义组件api名称
        List<String> apiNames = new ArrayList<>();
        apiNames.add("dynamicView");

        List<ActionPermissionNode> newNodes = new ArrayList<>();
        for (String menuName : menuNames) {
            List<ActionPermissionNode> actionPermissionNodes = buildActionPermissionNodes(selected, menuName, apiNames);
            if (CollectionUtils.isNotEmpty(actionPermissionNodes)) {
                newNodes.addAll(actionPermissionNodes);
            }
        }
        nodes.addAll(newNodes);
        return nodes;
    }

    private List<ActionPermissionNode> buildActionPermissionNodes(PermissionNode selected, String menuName, List<String> apiNames) {
        String path = ResourcePath.generatorPath(TopModule.MODULE_MODULE, menuName);
        if (!path.equals(selected.getPath())) {
            return null;
        }
        Menu menu = metaCacheManager.fetchCloneMenus(TopModule.MODULE_MODULE).stream()
                .filter(v -> v.getName().equals(menuName))
                .findFirst()
                .orElse(null);
        if (menu == null) {
            return null;
        }
        menu.fieldQuery(Menu::getViewAction);
        View mainView = fetchMainView(menu.getViewAction());
        if (mainView == null) {
            return null;
        }
        UIView parser = new UIView();
        try {
            parser = DslParser.parser(mainView);
        } catch (Throwable e) {
            log.error("compile view error. model: {}, name: {}", mainView.getModel(), mainView.getName(), e);
        }
        List<ActionPermissionNode> dyNodes = new ArrayList<>();
        for (String apiName : apiNames) {
            List<ActionPermissionNode> viewPermissionNode = getViewPermissionNode(selected, parser, menu, apiName);
            if (CollectionUtils.isNotEmpty(viewPermissionNode)) {
                dyNodes.addAll(viewPermissionNode);
            }
        }
        return dyNodes;
    }

    private List<ActionPermissionNode> getViewPermissionNode(PermissionNode selected, UIView parser, Menu menu, String apiName) {

        String viewName = findLabelByApiName(parser.getWidgets(), apiName);
        View view = new View().setName(viewName).queryOne();
        if (view == null) {
            log.info("incorrect view name, viewName: {}", viewName);
            return null;
        }
        Long groupId = selected.getGroupId();
        Map<String, AuthResourceAuthorization> authorizationMap = null;
        if (groupId != null) {
            authorizationMap = AuthAuthorizationHelper.fetchActionAuthorizationMap(groupId);
        }
        return compileByMenu(view, menu, menu.getViewAction(), authorizationMap);
    }

    private View fetchMainView(ViewAction viewAction) {
        String resModel = Optional.ofNullable(viewAction.getResModel()).orElse(viewAction.getModel());
        View resView = viewAction.getResView();
        View mainView;
        if (resView == null || resView.getTemplate() == null) {
            mainView = metaCacheManager.fetchView(resModel, viewAction.getResViewName(), viewAction.getViewType(), false);
        } else {
            mainView = resView;
        }
        return mainView;
    }

    private List<ActionPermissionNode> compileByMenu(View mainView, Menu menu, ViewAction viewAction, Map<String, AuthResourceAuthorization> authorizationMap) {
        AccessResourceInfo info = new AccessResourceInfo();
        info.setModule(menu.getModule());
        info.setModel(viewAction.getModel());
        info.setActionName(viewAction.getName());
        info.setMenu(menu.getName());
        info.setViewAction(viewAction);
        return compile(info, mainView, authorizationMap);
    }

    private List<ActionPermissionNode> compile(AccessResourceInfo info, View mainView, Map<String, AuthResourceAuthorization> authorizationMap) {
        if (log.isDebugEnabled()) {
            log.debug("auth compile view. model: {}, name: {}", mainView.getModel(), mainView.getName());
        }
        AuthCompileContext context = new AuthCompileContext(info, authorizationMap);
        AuthCompileVisitor visitor = new AuthCompileVisitor(context);
        try {
            DslParser.visit(mainView, visitor);
        } catch (Throwable e) {
            log.error("compile view error. model: {}, name: {}", mainView.getModel(), mainView.getName(), e);
        }
        return visitor.getNodes();
    }

    private String findLabelByApiName(List<? extends UIWidget> elements, String apiName) {
        if (elements == null || elements.isEmpty()) {
            return null;
        }
        for (UIWidget element : elements) {
            // 判断是否是指定组件。
            if (element instanceof UIField) {
                UIField field = (UIField) element;
                if (apiName.equals(field.getWidget())) {
                // 这里获取该组件的标题作为视图名字的配置,如业务有不同请自行修改。
                    return field.getLabel();
                }
            }

            List<? extends UIWidget> subElements = element.getWidgets();
            if (subElements != null && !subElements.isEmpty()) {
                String label = findLabelByApiName(subElements, apiName);
                if (label != null) {
                    return label;
                }
            }
        }
        return null;
    }

}

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

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

(0)
yexiu的头像yexiu数式员工
上一篇 2025年9月8日 am10:45
下一篇 2025年9月18日 pm2:51

相关推荐

  • 左树右表,支撑不同场景的树表结构

    左树右表俩种情况 假设存在 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日
    3.0K00
  • 如何跳过固定path路径下面所有的按钮权限

    场景: 业务上需要跳过弹窗打开里面的所有按钮权限。 实践: 实现AuthFilterService权限接口。 package pro.shushi.pamirs.top.api.spi; import org.apache.commons.lang3.StringUtils; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import pro.shushi.pamirs.auth.api.spi.AuthFilterService; import pro.shushi.pamirs.boot.web.session.AccessResourceInfoSession; import pro.shushi.pamirs.meta.common.spi.SPI; /** * @author Yexiu at 09:04 on 2024/9/27 */ @Order(88) @Component @SPI.Service public class CustomAuthFilterService implements AuthFilterService { public static final String skipPath = "/top_demo/uiMenuc6238c29bca44250a041691565056a63/ACTION#top.Teacher#uiView2b60cc6daa334c7280cb78207d41addc"; @Override public Boolean isAccessAction(String model, String name) { String path = AccessResourceInfoSession.getInfo().getOriginPath(); if (StringUtils.isNotEmpty(path) && path.startsWith(skipPath)) { //返回true就代表通过验证 return true; } return null; } @Override public Boolean isAccessAction(String path) { if (StringUtils.isNotEmpty(path) && path.startsWith(skipPath)) { //返回true就代表通过验证 return true; } return null; } } 可以看到弹窗下面的按钮都不需要权限控制了。

    2025年3月11日
    97700
  • 自定义表格支持合并或列、表头分组

    本文将讲解如何通过自定义实现表格支持单元格合并和表头分组。 点击下载对应的代码 在学习该文章之前,你需要先了解: 1: 自定义视图2: 自定义视图、字段只修改 UI,不修改数据和逻辑3: 自定义视图动态渲染界面设计器配置的视图、动作 1. 自定义 widget 创建自定义的 MergeTableWidget,用于支持合并单元格和表头分组。 // MergeTableWidget.ts import { BaseElementWidget, SPI, ViewType, TableWidget, Widget, DslRender } from '@kunlun/dependencies'; import MergeTable from './MergeTable.vue'; @SPI.ClassFactory( BaseElementWidget.Token({ viewType: ViewType.Table, widget: 'MergeTableWidget' }) ) export class MergeTableWidget extends TableWidget { public initialize(props) { super.initialize(props); this.setComponent(MergeTable); return this; } /** * 表格展示字段 */ @Widget.Reactive() public get currentModelFields() { return this.metadataRuntimeContext.model.modelFields.filter((f) => !f.invisible); } /** * 渲染行内动作VNode */ @Widget.Method() protected renderRowActionVNodes() { const table = this.metadataRuntimeContext.viewDsl!; const rowAction = table?.widgets.find((w) => w.slot === 'rowActions'); if (rowAction) { return rowAction.widgets.map((w) => DslRender.render(w)); } return null; } } 2. 创建对应的 Vue 组件 定义一个支持合并单元格与表头分组的 Vue 组件。 <!– MergeTable.vue –> <template> <vxe-table border height="500" :column-config="{ resizable: true }" :merge-cells="mergeCells" :data="showDataSource" @checkbox-change="checkboxChange" @checkbox-all="checkedAllChange" > <vxe-column type="checkbox" width="50"></vxe-column> <!– 渲染界面设计器配置的字段 –> <vxe-column v-for="field in currentModelFields" :key="field.name" :field="field.name" :title="field.label" ></vxe-column> <!– 表头分组 https://vxetable.cn/v4.6/#/table/base/group –> <vxe-colgroup title="更多信息"> <vxe-column field="role" title="Role"></vxe-column> <vxe-colgroup title="详细信息"> <vxe-column field="sex" title="Sex"></vxe-column> <vxe-column field="age" title="Age"></vxe-column> </vxe-colgroup> </vxe-colgroup> <vxe-column title="操作" width="120"> <template #default="{ row, $rowIndex }"> <!– 渲染界面设计器配置的行内动作 –> <row-action-render :renderRowActionVNodes="renderRowActionVNodes" :row="row" :rowIndex="$rowIndex" :parentHandle="currentHandle" ></row-action-render> </template> </vxe-column> </vxe-table> <!– 分页 –> <oio-pagination :pageSizeOptions="pageSizeOptions" :currentPage="pagination.current"…

    2025年1月9日
    1.9K00
  • 工作流:通过业务数据操作工作流程(催办、撤销等)

    一、抽象模型,需要操作流程的模型继承此模型 定义流程相关的一些信息在模型中;如果直接定义在存储模型中,下面这些字段都是显示的指定为非存储字段。更好的建议:1、如果有多个业务模型有这类需要,则可以把这些字段抽取到抽象模型中2、如果仅有一个业务模型需要,则可以放到代理模型中 /** * 定义流程相关的一些信息在模型中 */ @Model.model(DemoBaseAbstractModel.MODEL_MODEL) @Model(displayName = "抽象模型") @Model.Advanced(type= ModelTypeEnum.ABSTRACT) public abstract class DemoBaseAbstractModel extends IdModel { public static final String MODEL_MODEL = "hr.simple.DemoBaseAbstractModel"; // 流程相关 @Field.Integer @Field(displayName = "工作流用户任务ID", summary = "业务数据列表中审核流程使用", invisible = true, store = NullableBoolEnum.FALSE) private Long workflowUserTaskId; @Field.Integer @Field(displayName = "流程实例ID", summary = "业务数据列表中催办使用", invisible = true, store = NullableBoolEnum.FALSE) private Long instanceId; @Field.String @UxForm.FieldWidget(@UxWidget(invisible = "true")) @UxDetail.FieldWidget(@UxWidget(invisible = "true")) @Field(displayName = "当前流程节点", store = NullableBoolEnum.FALSE) private String currentFlowNode; @Field.Boolean @Field(displayName = "能否催办", invisible = true, defaultValue = "false", store = NullableBoolEnum.FALSE) private Boolean canUrge; // 审批状态控制申请单是否可以被发起流程、能否编辑等控制 @Field.Enum @Field(displayName = "审批状态", defaultValue = "NC") @UxForm.FieldWidget(@UxWidget(invisible = "true")) private ApprovalStatusEnum approvalStatus; } @Dict(dictionary = ApprovalStatusEnum.dictionary, displayName = "审批状态") public enum ApprovalStatusEnum implements IEnum<String> { NC("NC", "待提交", "待提交"), PENDING("PENDING", "已提交", "已提交,待审批"), APPROVED("APPROVED", "已批准", "已批准"), REJECTED("REJECTED", "已拒绝", "已拒绝"); public static final String dictionary = "land.enums.LandApprovalStatusEnum"; private final String value; private final String displayName; private final String help; ApprovalStatusEnum(String value, String displayName, String help) { this.value = value; this.displayName = displayName; this.help = help; } public String getValue() { return value; } public String getDisplayName() { return…

    2025年6月26日
    77900
  • 推送自定义消息

    项目中添加消息依赖 pro.shushi.pamirs.core pamirs-message-api 调用pro.shushi.pamirs.message.engine.message.MessageSender#sendSystemMail发送系统消息示例: @Action(displayName = "发送消息") public Student sendMessage(Student data){ MessageSender mailSender = (MessageSender) MessageEngine.get(MessageEngineTypeEnum.MAIL_SEND).get(null); String content = "发送自定义消息"; String subject = null; List<Long> userIds = new ArrayList<>(); userIds.add(PamirsSession.getUserId()); PamirsMessage message = new PamirsMessage() .setName(subject) .setSubject(subject) .setBody(content) .setMessageType(MessageTypeEnum.NOTIFICATION); List<PamirsMessage> messages = new ArrayList<>(); messages.add(message); SystemMessage systemMessage = new SystemMessage(); systemMessage.setPartners(userIds.stream().map(i -> (PamirsUser) new PamirsUser().setId(i)).collect(Collectors.toList())) .setType(MessageGroupTypeEnum.SYSTEM_MAIL) .setMessages(messages); mailSender.sendSystemMail(systemMessage); return data; }

    2024年8月19日
    1.4K00

Leave a Reply

登录后才能评论