前端自定义组件之单页面步骤条

本文将讲解如何通过自定义,实现单页面的步骤条组件。其中每个步骤的元素里都是界面设计器拖出来的。
前端自定义组件之单页面步骤条

实现路径

整体的实现思路是界面设计器拖个选项卡组件,自定义这个选项卡,里面的每个选项页都当成一步渲染出来,每一步的名称是选项页的标题。

1. 界面设计器拖出页面

我们界面设计器拖个选项卡组件,然后在每个选项页里拖拽任意元素。完成后点击右上角九宫格,选中选项卡,填入组件 api 名称,作用是把选项卡切换成我们自定义的步骤条组件,这里的 api 名称和自定义组件的 widget 对应。最后发布页面,并绑定菜单。
前端自定义组件之单页面步骤条

2. 组件实现

widget 组件重写了选项卡,核心函数 renderStep,通过 DslRender.render 方法渲染界面设计器拖拽的元素,每一步的 step 又是解析选卡页得到的。

import {
  SPI,
  Widget,
  DefaultTabsWidget,
  BasePackWidget,
  DslDefinition,
  DslRender,
  DslDefinitionType,
  CallChaining,
  customMutation
} from '@oinone/kunlun-dependencies';
import { VNode } from 'vue';
import NextStepSinglePage from './NextStepSinglePage.vue';

@SPI.ClassFactory(BasePackWidget.Token({ widget: 'NextStepSinglePage' }))
export class NextStepSinglePageWidget extends DefaultTabsWidget {
  public initialize(props) {
    super.initialize(props);
    this.setComponent(NextStepSinglePage);
    return this;
  }

  @Widget.Reactive()
  public get invisible() {
    return false;
  }

  // 配置的每一步名称,解析选项页的标题
  @Widget.Reactive()
  public get titles() {
    return this.template?.widgets?.map((item) => item.title) || [];
  }

  // region 上一步下一步配置

  // 步骤数组,数组里的元素即步骤要渲染的内容
  @Widget.Reactive()
  public get steps(): DslDefinition[] {
    // 每个 tab 是一个步骤,这里会有多个步骤
    // 每个步骤里有多个元素,又是数组
    // 所以这里是二维数组
    const tabDsls: DslDefinition[][] = this.template?.widgets.map((item) => item.widgets) || [];

    // 对每个步骤的子元素们,外侧包一层 row 布局,所以变回了一维数组
    return tabDsls.map((tabDsl) => {
      return {
        ...(this.template || {}),
        dslNodeType: DslDefinitionType.PACK,
        widgets: tabDsl,
        widget: 'row',
        resolveOptions: {
          mode: 1
        }
      };
    });
  }

  // 渲染步骤,每个步骤有多个子元素
  @Widget.Method()
  public renderStep(step: DslDefinition): VNode | undefined {
    return DslRender.render(step);
  }

  // region 校验相关

  // 校验的钩子
  @Widget.Reactive()
  @Widget.Inject('validatorCallChaining')
  protected parentValidatorCallChaining: CallChaining<boolean> | undefined;

  // 校验步骤表单
  @Widget.Method()
  public async onValidator(): Promise<boolean> {
    const res = await this.parentValidatorCallChaining?.syncCall();
    return res ?? true;
  }

  // region 数据提交相关

  // 提交数据的方法
  @Widget.Method()
  public async onSave() {
    const submitData = this.formData;
    await customMutation(this.model.model, 'create', submitData || {});
    window.history.back();
  }
}

vue组件核心内容是用component :is属性,渲染出配置的选项页组件

<template>
  <div class="next-step">
    <a-steps :current="current">
      <a-step v-for="title in titles" :title="title" />
    </a-steps>
    <OioSpin :loading="loading" class="oio-spin">
      <div class="step-main">
        <template v-for="(stepEl, index) in stepsVNodes">
          <div v-if="current === index" class="step-item">
            <component :is="stepEl" />
          </div>
        </template>
      </div>
      <div class="step-footer" v-if="stepsVNodes.length">
        <a-button v-if="current > 0" class="oio-button" type="primary" @click="previous"> 上一步 </a-button>
        <a-button v-if="current < stepsVNodes.length - 1" class="oio-button" type="primary" @click="next">
          下一步
        </a-button>
        <a-button v-if="current === stepsVNodes.length - 1" class="oio-button" type="primary" @click="finish">
          完成
        </a-button>
      </div>
    </OioSpin>
  </div>
</template>
<script setup lang="ts">
import { computed, PropType, ref, VNode } from 'vue';
import { OioSpin } from '@oinone/kunlun-vue-ui-antd';
import { DslDefinition } from '@oinone/kunlun-dependencies';

const props = defineProps({
  loading: {
    type: Boolean,
    default: false
  },
  titles: {
    type: Array as PropType<string[]>,
    default: []
  },
  steps: {
    type: Array as PropType<DslDefinition[][]>,
    default: []
  },
  renderStep: {
    type: Function as PropType<(step: DslDefinition[]) => VNode[]>
  },
  onValidator: {
    type: Function
  },
  onSave: {
    type: Function
  }
});

const stepsVNodes = computed(() => {
  return props.steps?.map((step) => props.renderStep?.(step));
});

const current = ref<number>(0);

const next = async () => {
  if (await props.onValidator?.()) {
    current.value++;
  }
};
const previous = async () => {
  // if (await props.onValidator?.()) {
  current.value--;
  // }
};
const finish = async () => {
  if (await props.onValidator?.()) {
    props.onSave?.();
  }
};
</script>
<style lang="scss" scoped>
.next-step {
  height: 100%;
  background-color: #fff;
  padding: 16px;

  .step-main {
    display: flex;
    justify-content: flex-start;

    .step-item {
      width: 100%;
    }
  }

  .step-footer {
    display: flex;
    justify-content: flex-start;
    align-items: center;
    gap: 16px;
    margin-top: 18px;
  }
}
</style>

3. 效果

前端自定义组件之单页面步骤条

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

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

(0)
银时的头像银时数式员工
上一篇 4小时前
下一篇 2小时前

相关推荐

  • 开放应用中的ip黑白名单

    IP白名单配置 入口:集成应用>开放管理>应用>授权调整>IP白名单配置 IP白名单取请求头中的X-Forwarded-For属性的最后一个值,X-Forwarded-For以英文,分割ip地址。 X-Forwarded-For: clientIP, proxy1IP, proxy2IP, …, proxyNIP 根据 RFC 7239 标准所述,X-Forwarded-For含义如下: clientIP:最左边的 IP,表示最初发起请求的客户端 IP(即真实用户 IP)。 proxyXIP:从左往右依次为中间各级代理服务器的 IP。 最右边的 IP:表示离当前 Web 服务器最近的一层代理服务器(IP白名单拦截的此IP)。 Nginx配置示例 以Nginx为例,为确保X-Forwarded-For拿到的是真实的IP地址,需要增加配置。 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; IP黑名单配置 入口:集成应用>开放管理>应用>黑名单 取值来源:从请求头 X-Real-IP 中提取客户端 IP。 验证逻辑: 若 X-Real-IP 不存在,直接拦截并返回异常提示:未获取到真实IP地址。 检查提取的 IP 是否在阻止列表中。 相关文章 IP黑白名单实现拦截三方用户

    2025年5月15日
    10900
  • 数式Oinone培训前注意事项

    一、快速上手 (建议至少预习 6 小时) 在正式培训之前,建议需要完成以下任务,以便对培训内容有基本了解: 点击阅读:快速启动入门 该文档为学员提供了从入门到实现 demo 的全过程说明,涵盖了开发工具、框架搭建、常见问题解答等内容 开始培训前,请参与人员确保完成以下任务 阅读并理解文档中的每个步骤。 配置好前后端开发环境。 完成 demo 的基础框架搭建,验证是否能够成功运行。 二、预期成果 通过上述预习,大家完成以下事项:• 成功搭建本地开发环境,并能运行前后端的基本 demo。• 对前后端技术栈有初步了解,为正式培训中的深度学习打下基础,并且提出对应的疑问点 三、其他准备工作 1.技术工具检查:请确在本地已安装并配置好必要的开发工具和环境(如 IDE、Node.js、数据库等);2.参与者反馈:在预习过程中,学员如遇到困难或无法解决的问题(前后端疑问),请提前记录并提交,以便培训期间重点解答;3.版本是否是最新的版本,且建议研发人员版本一致,且类型是 mini; ps:部署包相关信息,联系数式相关人员获取

    未分类 2024年8月2日
    2.2K00
  • 自定义字段样式

    在日常开发中,我们经常会遇到需要根据业务规则动态展示字段样式的场景,比如表格、表单或详情中的某些字段需要改变文字颜色。本文将通过一个具体的案例,带大家实现这一功能。 以下以 自定义表格字段文字颜色 为例。 实现步骤 1. 在界面设计器中添加组件 通过界面设计器,添加一个组件 2. 创建元件 以表格的「金额字段」为例,创建对应的元件(可根据自己的业务场景调整)。 3. 配置元件属性 进入元件设计页面,从组件库中拖入「单行文本」到设计区域。在右侧属性面板中填写相关配置并保存 4. 保存元件 完成配置后,保存元件。 5. 发布元件 将元件发布,供页面设计使用。 6. 切换表格字段 进入页面设计器,将表格中的字段切换为刚刚创建的元件。 7. 配置字段颜色 在右侧属性面板中,配置字段的文字颜色: 固定颜色:直接输入颜色值(如 red)。 动态颜色:输入表达式,根据业务逻辑动态展示颜色。例如:当前行的名称等于 1 时显示红色,否则为蓝色。 示例表达式: activeRecord.name === '1' ? 'red' : 'blue' 8: 在代码中,自定义对应的表格字段 import { SPI, BaseFieldWidget, ModelFieldType, ViewType, TableCurrencyFieldWidget, Widget, RowContext, numberZeroFill, numberAddThousandth, Expression, ExpressionRunParam } from '@kunlun/dependencies'; import { toString } from 'lodash-es'; import { createVNode, VNode } from 'vue'; @SPI.ClassFactory( BaseFieldWidget.Token({ viewType: [ViewType.Table], ttype: [ModelFieldType.Currency], widget: 'TableCurrencyColor' }) ) export class TableCustomCurrencyFieldWidget extends TableCurrencyFieldWidget { computedFieldColor(context: RowContext) { const { fieldColor = ' ' } = this.getDsl(); if (!fieldColor) { return null; } // 如果当前颜色是表达式,则需要计算 if (Expression.hasKeywords(fieldColor)) { const params: ExpressionRunParam = { activeRecords: [context.data], rootRecord: {}, openerRecord: {}, scene: '' }; return Expression.run(params, fieldColor, fieldColor)!; } return fieldColor; } @Widget.Method() public renderDefaultSlot(context: RowContext): VNode[] | string { let value = numberZeroFill(toString(super.compute(context)), this.getPrecision(context)); if (this.getShowThousandth(context)) { value = numberAddThousandth(value); } return [ createVNode( 'div', { style: { color: this.computedFieldColor(context) } }, value ) ]; } } 9: 页面效果图

    2025年1月9日
    4.4K00
  • 新人引导文档

    类型 文档链接 入门参考必看 7天入门到精通 Oinone 初级学习路径 Oinone 初级学习路径 平台部署启动 无代码docker启动说明 低代码启动说明 前端环境和启动前端工程 无代码设计器启动方式 后端无代码设计器Jar包启动方法 平台部署及依赖说明 Oinone平台部署及依赖说明(v5.0) 问题排查方法 问题排查工具使用手册 前后端研发帮助文档 前端文档 后端文档 项目开发实践要点 【前端】项目开发前端知识要点地图 【后端】项目开发后端知识要点地图 版本升级说明 版本更新日志 工具包 【附件一】下载说明 许可证使用说明 pamirs-license 许可证使用常见问题 更多文档新人引导建议,可以下方评论

    2024年7月15日
    1.2K00
  • 如何使用源码的方式配置表达式

    自定义占位符定义表达式 数据权限定义表达式: ${thisTeacherName} 界面设计器查询条件定义:$#{thisTeacherName}需要加上#号 以上配置都可以通过查看queryWrapper里面的originRsql查看占位符是否被正确替换。 显隐、过滤条件表达式定义 显隐、过滤都可以加载字段注解里以及xml定义里 显隐: invisible="$!{activeRecord.deadline}" / invisible = true @Field.String @Field(displayName = "视图/页面", invisible = true) private String viewName; 过滤。 domain = " code == ${activeRecord.id} " / domain = "code == '111' " @Field.one2many @Field(displayName = "子订单列表", summary = "子订单列表") @Field.Relation(relationFields = {"code"}, referenceFields = {"code"}, domain = "code != '1234'") private List<ChildOrder> orderList; 更多获取视图数据的写法参考文章上下文在字段和动作中的应用 rsql表达式定义 参考:oinone的rsql与传统sql语法对照表

    2025年3月13日
    50300

Leave a Reply

登录后才能评论