如何设计公用跳转动作(类似导入导出动作)

背景

设计一个公共动作,在界面设计器可以拖到页面里,点击之后跳转指定页面。就像导入导出一样。

实现思路

元数据计算时,初始化跳转动作,为本模块以及依赖于本模块的所有模块生成该跳转动作。实现所有模型都有该跳转动作的元数据。

代码示例:

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

import com.google.common.collect.Lists;
import org.apache.commons.collections4.MapUtils;
import org.springframework.stereotype.Component;
import pro.shushi.pamirs.boot.base.enmu.ActionTargetEnum;
import pro.shushi.pamirs.boot.base.enmu.ActionTypeEnum;
import pro.shushi.pamirs.boot.base.model.ViewAction;
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.meta.annotation.fun.extern.Slf4j;
import pro.shushi.pamirs.meta.api.dto.meta.Meta;
import pro.shushi.pamirs.meta.api.dto.meta.MetaData;
import pro.shushi.pamirs.meta.domain.model.ModelDefinition;
import pro.shushi.pamirs.meta.enmu.ActionContextTypeEnum;
import pro.shushi.pamirs.meta.enmu.SystemSourceEnum;
import pro.shushi.pamirs.meta.enmu.ViewTypeEnum;
import pro.shushi.pamirs.top.api.TopModule;
import pro.shushi.pamirs.top.api.model.Teacher;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Component
public class DemoModuleAppInstall implements MetaDataEditor {
    @Override
    public void edit(AppLifecycleCommand command, Map<String, Meta> metaMap) {
        InitializationUtil util = InitializationUtil.get(metaMap, TopModule.MODULE_MODULE, TopModule.MODULE_NAME);
        if (util == null) {
            return;
        }
        if (MapUtils.isEmpty(metaMap)) {
            return;
        }

        Set<String> dependencyFileModels = metaMap.values().stream()
                .filter(v -> v.getData().containsKey(TopModule.MODULE_MODULE))
                .map(Meta::getModule)
                .collect(Collectors.toSet());
        for (String module : metaMap.keySet()) {
            if (!dependencyFileModels.contains(module)) {
                // 不依赖本模块,不生成跳转动作
                continue;
            }
            Meta meta = metaMap.get(module);
            MetaData metaData = meta.getCurrentModuleData();
            List<ModelDefinition> modelList = metaData.getDataList(ModelDefinition.MODEL_MODEL);
            for (ModelDefinition data : modelList) {
                makeDefaultModelViewAction(meta, data, dependencyFileModels);
            }
        }
    }

    // 跳转的xml模版 name
    public static final String DEFAULT_VIEW_NAME = "fixed_teacher_table";

    private void makeDefaultModelViewAction(Meta meta, ModelDefinition data, Set<String> dependencyFileModels) {
        if (!dependencyFileModels.contains(data.getModule())) {
            // 当前模块使用了其他模块的模型,对方模块未依赖本模块,不生成跳转动作
            return;
        }
        Map<String, Object> context = new HashMap<>();
        context.put("model", "'" + data.getModel() + "'");
        // 创建 跳转表格页面 viewAction,根据实际需求更改
        makeDefaultViewAction(meta, data,
                "teacherListDialog",
                "教师表格",
                null,
                ActionContextTypeEnum.CONTEXT_FREE,
                ViewTypeEnum.TABLE, 99,
                Teacher.MODEL_MODEL, DEFAULT_VIEW_NAME, ActionTargetEnum.DIALOG,
                context);
    }

    private void makeDefaultViewAction(Meta meta, ModelDefinition data,
                                             String viewActionName,
                                             String displayName,
                                             String title,
                                             ActionContextTypeEnum contextType,
                                             ViewTypeEnum viewType,
                                             int priority,
                                             String resModel,
                                             String resViewName,
                                             ActionTargetEnum target,
                                             Map<String, Object> context) {
        String sign = ViewAction.sign(data.getModel(), viewActionName);
        ViewAction defaultViewAction = meta.getData().get(data.getModule())
                .getDataItem(ViewAction.MODEL_MODEL, sign);
        boolean newAction = false;
        if (null == defaultViewAction) {
            defaultViewAction = new ViewAction();
            newAction = true;
        }
        if (newAction || SystemSourceEnum.MANUAL.equals(defaultViewAction.getSystemSource())) {
            defaultViewAction.setDisplayName(displayName)
                    .setLabel(defaultViewAction.getDisplayName())
                    .setName(viewActionName)
                    .setModel(data.getModel());
            defaultViewAction.setTitle(title)
                    .setViewType(viewType)
                    .setTarget(Optional.ofNullable(target).orElse(ActionTargetEnum.ROUTER))
                    .setResModel(resModel)
                    .setResViewName(resViewName)
                    .setResModule(null)
                    .setResModuleName(null)
                    .setContextType(contextType)
                    .setActionType(ActionTypeEnum.VIEW)
                    .setBindingType(Lists.newArrayList(ViewTypeEnum.TABLE))
                    .setContext(context)
                    .setPriority(priority)
                    .setSystemSource(SystemSourceEnum.MANUAL);
            defaultViewAction.setSign(sign);
            if (newAction) {
                defaultViewAction.construct();
                meta.getData().get(data.getModule()).addData(defaultViewAction);
            } else {
                defaultViewAction.disableMetaCompleted();
            }
        }
    }
}

xml模版示例,默认模版文件放到/pamirs/views/模块编码/template下面

可以利用界面设计器生成需要的跳转页面。在base_view表里面根据model以及title查询,template字段就是xml模版。拷贝出来之后,在view标签里指定一个name(即上文的DEFAULT_VIEW_NAME),然后保存为xml文件放到resource/pamirs/views/模块编码/template路径下面

<view model="top.Teacher" name = "fixed_teacher_table"  type="table">
    <template colSpan="FULL" slot="search">
        <field colSpan="QUARTER" data="enumType" widget="Select"/>
        <field allowClear="true" autoFillOptions="true" colSpan="QUARTER" data="professionalId" label="科目id" showThousandth="false" statistics="false" widget="Integer"/>
    </template>
    <template colSpan="FULL" cols="24" slot="tableGroup"/>
    <template slot="actionBar">
        <action label="导入" name="internalGotoListImportDialog"/>
        <action label="导出" name="internalGotoListExportDialog" sync="true"/>
        <action label="打印" name="internalGotoPrintDialog"/>
    </template>
    <template checkbox="true" colSpan="FULL" defaultPageSize="OPTION_2" enableSequence="false" filter="" inlineActiveCount="THREE" slot="table" sortable="false">
        <field allowClear="true" autoFillOptions="true" colSpan="HALF" data="teacherName" label="教师名字" patternType="NONE" showCount="false" type="TEXT" widget="Input"/>
        <field allowClear="true" autoFillOptions="true" colSpan="HALF" data="field00001" invisible="true" label="嵌入网页" patternType="NONE" showCount="false" type="TEXT" widget="Input"/>
        <field allowClear="true" autoFillOptions="true" colSpan="HALF" data="readStatus" label="读取状态" optionColorStyle="COLORFUL" widget="Select">
            <options>
                <option displayName="未读" label="未读" name="NO_READ"/>
                <option displayName="结束" label="结束" name="READ"/>
            </options>
        </field>
        <field data="createDate" widget="DateTimePicker"/>
        <field data="writeDate" widget="DateTimePicker"/>
        <template slot="rowActions" colSpan="FULL">
        </template>
        <field data="id" invisible="true"/>
    </template>
</view>

效果:

如何设计公用跳转动作(类似导入导出动作)

Oinone社区 作者:yexiu原创文章,如若转载,请注明出处:https://doc.oinone.top/other/22199.html

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

(0)
yexiu的头像yexiu数式员工
上一篇 2025年10月13日 pm3:50
下一篇 2025年11月10日 am10:37

相关推荐

  • 前端自定义组件之锚点分组

    本文将讲解如何通过自定义,实现锚点组件。这个锚点组件会根据界面设计器拖入的分组,动态解析出锚点。 实现路径 整体的实现思路是界面设计器拖个容器类的组件(这里以选项卡为例),自定义这个选项卡,往选项卡里拖拽的每个分组,每个锚点的名称是分组的标题。 1. 界面设计器拖出页面 我们界面设计器拖个选项卡组件,然后在选项页里拖拽任意多个分组。完成后点击右上角九宫格,选中选项卡,填入组件 api 名称,作用是把选项卡切换成我们自定义的锚点分组组件,这里的 api 名称和自定义组件的 widget 对应。最后发布页面,并绑定菜单。 2. 组件实现 widget 组件重写了选项卡,核心函数 renderGroups,通过 DslRender.render 方法渲染界面设计器拖拽的分组。 import { BasePackWidget, DslDefinition, DslRender, SPI, Widget } from '@oinone/kunlun-dependencies'; import TabsParseGroup from './TabsParseGroup.vue'; function fetchGroupChildren(widgets?: DslDefinition[], level = 1): DslDefinition[] { if (!widgets) { return []; } const children: DslDefinition[] = []; for (const widget of widgets) { if (widget.widget === 'group') { children.push(widget); } else if (level >= 1) { fetchGroupChildren(widget.widgets, level – 1).forEach((child) => children.push(child)); } } return children; } @SPI.ClassFactory( BasePackWidget.Token({ widget: 'TabsParseGroup' }) ) export class TabsParseGroupWidget extends BasePackWidget { public initialize(props) { super.initialize(props); this.setComponent(TabsParseGroup); return this; } // 获取分组的子元素 public get groupChildren(): DslDefinition[] { return fetchGroupChildren(this.template?.widgets); } @Widget.Reactive() public get groupTitles() { return this.groupChildren.map((group) => group.title); } // 根据容器子元素渲染左侧 @Widget.Method() public renderGroups() { if (this.groupChildren && this.groupChildren.length) { return this.groupChildren.map((group) => DslRender.render(group)); } } } vue组件核心内容是用component :is属性,渲染出配置的分组组件 <template> <div class="TabsParseGroup"> <a-anchor :affix="false"> <a-anchor-link v-for="(item, index) in groupTitles" :href="`#default-group-${index}`" :title="item" /> </a-anchor> <div v-for="(item, index) in groupComponents" :id="`default-group-${index}`"> <component :is="item" /> </div> </div> </template> <script lang="ts"> import { computed, defineComponent, PropType } from 'vue'; export default…

    2025年7月8日
    35800
  • 外部系统接入SSO-OAuth2(6.3.x 之后版本支持)

    一、概述 统一身份认证系统提供了单点登录功能。本文档详述了统一身份认证系统单点登录接口,介绍了应用系统对接统一身份认证系统的过程和方法,用以帮助开发人员将应用系统接入统一身份认证系统。 本文档支持的协议:OAuth2 二、统一认证集成规范 2.1 SSO登录场景 序号 场景 预期结果 确认 1 登录跳转,在未登录的条件下,直接访问业务系统 跳转到单点登录界面 是 2 登录跳转,在已登录的条件下,直接访问业务系统 直接进入业务系统 是 3 正常登录,先登录认证,然后访问业务系统 直接进入业务系统 是 4 正常登出,在单点登录系统登出 访问业务系统要重新登录 是 5 正常登出,在业务系统登出 单点登录系统同步登出 是 2.2 认证流程 OAuth2 登录和认证流程 后端验证 token 后,创建本地会话(如 JSESSIONID 或自定义 Cookie); 后续应用请求靠本地会话维持登录状态,不再依赖原始 token; 本地会话有过期时间(如 30 分钟无操作过期); 三、认证服务接口 3.1 SSO服务端认证 浏览器登录 分类 说明 请求地址(开发环境) http://${host}:${port}/pamirs/sso/auth?client_id={client_id}&redirect_uri={redirect_uri}&state={state} 请求地址(测试环境) http://${host}:${port}/pamirs/sso/auth?client_id={client_id}&redirect_uri={redirect_uri}&state={state} 请求方式 GET 请求参数 redirect_uri:应用系统回调地址client_id:SSO 服务端颁发的应用 IDstate:可选但推荐,用于防止 CSRF 的随机字符串(UUID 随机码) 请求示例 http://${host}:${port}/pamirs/sso/auth?client_id=client123456&redirect_rui=http://app.example.com&state=abc123 响应说明 1. 已登录:重定向到 redirect 地址并携带授权码 code,如 http://app.example.com&state=abc123&code=xxx2. 未登录:重定向到服务器 SSO 登录地址 备注 SSO 登录成功后,会携带授权码并重定向到 redirect 地址 3.2 验证授权码 分类 说明 请求地址(开发环境) http://${host}:${port}/pamirs/sso/oauth2/authorize 请求地址(测试环境) http://${host}:${port}/pamirs/sso/oauth2/authorize 请求方式 POST 请求 Body {"grant_type":"authorization_code","client_id":"xxxx","client_secret":"xxxxx","code":"xxxx"} 请求示例 http://${host}:${port}/pamirs/sso/oauth2/authorize 响应说明 {"code":"0","msg":"成功","data":{"access_token":"xxxxx","expires_in":7200,"refresh_token":"xxxxxx","refresh_token_expiresIn":604800}} 备注 — 3.2 根据token获取用户信息 分类 说明 请求地址(开发环境) http://${host}:${port}/pamirs/sso/oauth2/getUserInfo 请求地址(测试环境) http://${host}:${port}/pamirs/sso/oauth2/getUserInfo 请求方式 POST 请求头 Authorization: Bearer xxxxxxxx 请求 Body {"client_id":"xxxx"} 请求示例 http://${host}:${port}/pamirs/sso/oauth2/getUserInfo 响应 {"code":"0","msg":"成功","data":{"name":"xxxxx","email":"xxxxx","login":"xxxxxx","id":"xxxxxxx"}} 备注 成功返回用户信息;失败返回错误码:1 根据实际情况其他协议的验证接口 3.3 单点登出接口 分类 说明 请求地址(开发环境) http://${host}:${port}/pamirs/sso/oauth2/logout 请求地址(测试环境) http://${host}:${port}/pamirs/sso/oauth2/logout 请求方式 POST 请求头 Authorization: Bearer xxxxxxxx 请求 Body {"client_id":"xxxx"} 请求示例 http://${host}:${port}/pamirs/sso/oauth2/logout 响应头 HTTP/1.1 200;Content-Type: application/html;charset=UTF-8 响应体 登出成功页面 备注 应用系统向 SSO 系统发起登出请求,SSO 收到后会通知所有其它系统登出该用户 四、服务注册 添加以下依赖 <!– sso相关 –> <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-sso-api</artifactId> </dependency> <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-sso-common</artifactId> </dependency> <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-sso-server</artifactId> </dependency> 配置文件 pamirs sso: enabled: true server: loginUrl: http://127.0.0.1:8091/login authType:…

    2026年1月7日
    22100
  • 如何实现业务表格跳转页面设计器设计器页面

    后端实现 代理继承界面设计器视图模型 @Model.model(MyView.MODEL_MODEL) @Model(displayName = "视图代理") @Model.Advanced(type = ModelTypeEnum.PROXY) public class MyView extends UiDesignerViewProxy { public static final String MODEL_MODEL = "hr.simple.MyView"; @Field.Integer @Field(displayName = "页面布局ID") private Long uiDesignerViewLayoutId; } 重写查询接口,返回页面布局ID,重写创建接口,实现创建页面逻辑。 package pro.shushi.pamirs.top.core.action; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import pro.shushi.pamirs.boot.base.constants.ViewConstants; import pro.shushi.pamirs.boot.base.enmu.ActionTargetEnum; import pro.shushi.pamirs.boot.base.ux.annotation.action.UxAction; import pro.shushi.pamirs.boot.base.ux.annotation.action.UxRoute; import pro.shushi.pamirs.boot.base.ux.annotation.button.UxRouteButton; import pro.shushi.pamirs.framework.connectors.data.sql.Pops; import pro.shushi.pamirs.framework.connectors.data.sql.query.LambdaQueryWrapper; 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.api.dto.condition.Pagination; import pro.shushi.pamirs.meta.api.dto.wrapper.IWrapper; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.meta.constant.FunctionConstants; import pro.shushi.pamirs.meta.enmu.*; import pro.shushi.pamirs.top.api.model.MyView; import pro.shushi.pamirs.ui.designer.api.designe.UiDesignerViewLayoutService; import pro.shushi.pamirs.ui.designer.model.UiDesignerViewLayout; import pro.shushi.pamirs.ui.designer.pmodel.UiDesignerViewLayoutProxy; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * @author Yexiu at 20:39 on 2025/3/27 */ @Component @Model.model(MyView.MODEL_MODEL) public class MyViewAction { @Autowired private UiDesignerViewLayoutService uiDesignerViewLayoutService; @Action.Advanced(name = FunctionConstants.create, managed = true) @Action(displayName = "创建", summary = "添加", bindingType = ViewTypeEnum.FORM) @Function(name = FunctionConstants.create) @Function.fun(FunctionConstants.create) public MyView create(MyView data) { UiDesignerViewLayoutProxy uiDesignerViewLayoutProxy = new UiDesignerViewLayoutProxy(); uiDesignerViewLayoutProxy.setBizType(data.getBizType()); uiDesignerViewLayoutProxy.setDesignerActionBarType(data.getDesignerActionBarType()); uiDesignerViewLayoutProxy.setViewType(data.getType()); uiDesignerViewLayoutProxy.setModel(data.getModel()); uiDesignerViewLayoutProxy.setModule(PamirsSession.getServApp()); uiDesignerViewLayoutProxy.setViewTitle(data.getTitle()); uiDesignerViewLayoutProxy.setUsingDefaultView(data.getLoadLayout()); UiDesignerViewLayoutProxy saveUiDesigner = uiDesignerViewLayoutService.create(uiDesignerViewLayoutProxy); data.setDesignerViewId(saveUiDesigner.getId()); return data; } @Function.Advanced(type = FunctionTypeEnum.QUERY, displayName = "查询列表") @Function.fun(FunctionConstants.queryPage) @Function(openLevel = {FunctionOpenEnum.API, FunctionOpenEnum.LOCAL}) public Pagination<MyView> queryPage(Pagination<MyView> page, IWrapper<MyView> queryWrapper) { LambdaQueryWrapper<MyView> wrapper = Pops.<MyView>lambdaQuery().from(MyView.MODEL_MODEL) .eq(MyView::getSys, Boolean.FALSE) .eq(MyView::getSystemSource, SystemSourceEnum.UI); Pagination<MyView> myViewPagination = new MyView().queryPage(page, wrapper); List<MyView> content…

    2025年3月31日
    45800
  • 导入导出支持国际化语言分隔符

    需求 导出 Excel 时,所有整数、小数字段需要加千分位分隔符显示 例如:10000 导出成 10,000。 只影响“导出的显示效果”,不改变原始数据的语义。 实现思路 通过修改“ Excel 默认导出模版”的平台逻辑,将整型字段模版定义为文本类型,并自定义导出扩展,将所有整型字段的数据根据国际化配置进行分割。 代码示例 自定义导出扩展,分割整型字段 注意 所有已有的导出扩展必须修改继承类ExcelExportSameQueryPageTemplate<Object> –> GlobalExportExt<Object> 否则,已有的导出扩展生成的Excel将无法正常格式化整型字段。 package pro.shushi.pamirs.top.core.temp.exports; import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Component; import pro.shushi.pamirs.file.api.context.ExcelDefinitionContext; import pro.shushi.pamirs.file.api.entity.EasyExcelBlockDefinition; import pro.shushi.pamirs.file.api.entity.EasyExcelCellDefinition; import pro.shushi.pamirs.file.api.entity.EasyExcelSheetDefinition; import pro.shushi.pamirs.file.api.extpoint.ExcelExportFetchDataExtPoint; import pro.shushi.pamirs.file.api.extpoint.impl.ExcelExportSameQueryPageTemplate; import pro.shushi.pamirs.file.api.model.ExcelExportTask; import pro.shushi.pamirs.meta.annotation.Ext; import pro.shushi.pamirs.meta.annotation.ExtPoint; import pro.shushi.pamirs.meta.api.dto.config.ModelFieldConfig; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.meta.enmu.TtypeEnum; import pro.shushi.pamirs.meta.util.FieldUtils; import pro.shushi.pamirs.resource.api.model.ResourceLang; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @Component @Ext(ExcelExportTask.class) public class GlobalExportExt<T> extends ExcelExportSameQueryPageTemplate<T> implements ExcelExportFetchDataExtPoint { @ExtPoint.Implement(priority = 1) @Override public List<Object> fetchExportData(ExcelExportTask exportTask, ExcelDefinitionContext context) { List<Object> results = super.fetchExportData(exportTask, context); if (CollectionUtils.isEmpty(results)) { return results; } return dataFormat(context, results); } public static List<Object> dataFormat(ExcelDefinitionContext context, List<Object> results) { ResourceLang resourceLang = new ResourceLang().setCode(PamirsSession.getLang()).queryOne(); if (resourceLang == null) { return results; } // 小数分隔符 String decimalPoint = resourceLang.getDecimalPoint(); // 整数分隔符 String thousandsSep = resourceLang.getThousandsSep(); // 数字分组规则(每组多少位,比如 "3") String groupingRule = resourceLang.getGroupingRule(); // 解析 groupSize,只做一次 int groupSize = 3; if (groupingRule != null && groupingRule.matches("\\d+")) { try { groupSize = Integer.parseInt(groupingRule); } catch (NumberFormatException ignore) { } } boolean needGroup = groupSize > 0 && thousandsSep != null && !thousandsSep.isEmpty(); //…

    2025年11月18日
    32700

Leave a Reply

登录后才能评论