列表视图、卡片视图切换

在日常项目开发中,我们可能会遇到当前视图是个表格,通过某个操作按钮将它变成卡片的形式.
列表视图、卡片视图切换
列表视图、卡片视图切换

这篇文章将带大家实现这种功能。通过上面两张图片可以看到出来不管是表格还是卡片,它都在当前的视图里面,所以我们需要一个视图容器来包裹表格卡片,并且表格可以使用平台默认的表格渲染,我们只需要自定义卡片即可。

我们用资源模块下面的国家菜单来实现这么一个功能这是对应的URL:

http://localhost:8080/page;module=resource;viewType=TABLE;model=resource.ResourceCountry;action=resource%23%E5%9B%BD%E5%AE%B6;scene=resource%23%E5%9B%BD%E5%AE%B6;target=OPEN_WINDOW;menu=%7B%22selectedKeys%22:%5B%22%E5%9B%BD%E5%AE%B6%22%5D,%22openKeys%22:%5B%22%E5%9C%B0%E5%9D%80%E5%BA%93%22,%22%E5%9C%B0%E5%8C%BA%22%5D%7D

源码下载

views

创建外层的视图容器

刚刚我们讲过,不管是表格还是卡片,它都在当前的视图里面,所以我们需要写一个视图容器来包裹它们,并且对应的容器里面允许拆入表格卡片,我们先创建TableWithCardViewWidget.ts

// TableWithCardViewWidget.ts 
import { BaseElementWidget, SPI, Widget } from '@kunlun/dependencies';
import TableWithCardView from './TableWithCardView.vue';

enum ListViewType {
  TABLE = 'table',
  CARD = 'card'
}

@SPI.ClassFactory(
  BaseElementWidget.Token({
    widget: 'TableWithCardViewWidget'
  })
)
export class TableWithCardViewWidget extends BaseElementWidget {
  @Widget.Reactive()
  private listViewType: ListViewType = ListViewType.TABLE; // 当前视图展示的类型,是展示卡片还是表格

  public initialize(props) {
    if (!props.slotNames) {
      props.slotNames = ['tableWidget', 'cardWidget'];
    }
    super.initialize(props);
    this.setComponent(TableWithCardView);

    return this;
  }
}

TableWithCardViewWidget中的initialize函数中,我们定义了两个插槽: tableWidget、cardWidget,所以需要在对应的vue文件里面里接收这两个插槽

<template>
  <div class="list-view-wrapper">
    <!-- 表格插槽 -->
    <div style="height: 100%" v-if="listViewType === 'table'">
      <slot name="tableWidget" />
    </div>

    <!-- 卡片插槽 -->
    <div v-if="listViewType === 'card'">
      <slot name="cardWidget"></slot>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  props: ['listViewType', 'onChangeViewType'],
  inheritAttrs: false,
  setup(props, context) {
    const onChangeViewType = (listViewType) => {
      props.onChangeViewType(listViewType);
    };
  }
});
</script>

这样一来,我们就定义好了视图容器,接下来就是通过自定义layout的方式注册该容器

layout注册

import { registerLayout, ViewType } from '@kunlun/dependencies';

registerLayout(
  `<view type="TABLE">
    <pack widget="group">
        <view type="SEARCH">
            <element widget="search" cols="4" slot="search"/>
        </view>
    </pack>
    <pack widget="group" slot="tableGroup" style="position: relative">
      <element widget="actionBar" slot="actionBar" slotSupport="action">
        <xslot name="actions" slotSupport="action" />
      </element>
      <element widget="TableWithCardViewWidget">
        <template slot="tableWidget">
          <element widget="table" slot="table" datasource-provider="true">
            <element widget="expandColumn" slot="expandRow" />
            <xslot name="fields" />
            <element widget="rowActions" slot="rowActions" />
          </element>
        </template>
        <template slot="cardWidget">
          <element widget="CardListViewWidget" datasource-provider="true" />
        </template>
      </element>
    </pack>
  </view>
`,
  {
    moduleName: 'resource.ResourceCountry',
    actionName: 'resource#国家',
    viewType: ViewType.Table
  }
);

这个layout是基于平台默认的table layout改造的,大家可以看到

      <element widget="TableWithCardViewWidget">
        <template slot="tableWidget">
          ...
        </template>
        <template slot="cardWidget">
             ...
        </template>
      </element>

这段模版是将自定义的视图容器TableWithCardViewWidget注册进去,并且有两个template, 每个template里面的slot属性其实就是在TableWithCardViewWidget中的initialize函数定义的两个插槽: tableWidget、cardWidget,这两个名字要对应上。

<template slot="tableWidget">
  <element widget="table" slot="table" datasource-provider="true">
    <element widget="expandColumn" slot="expandRow" />
    <xslot name="fields" />
    <element widget="rowActions" slot="rowActions" />
  </element>
</template>

第一个slot是 tableWidget,内部是默认的表格layout,所以在运行时的时候,会渲染平台默认的表格组件

<template slot="cardWidget">
    <element widget="CardListViewWidget" datasource-provider="true" />
 </template>

第二个slot是 cardWidget,里面渲染的是 CardListViewWidget, 所以这个时候我们需要按照自定义视图的方式自定义CardListViewWidget即可。

自定义卡片

// CardListViewWidget.ts

import {
  ActiveRecord,
  BaseElementListViewWidget,
  BaseElementWidget,
  Condition,
  DEFAULT_TRUE_CONDITION,
  ISort,
  Pagination,
  QueryContext,
  queryPage,
  QueryVariables,
  SPI,
  Widget
} from '@kunlun/dependencies';
import cardList from './card-list.vue';

@SPI.ClassFactory(
  BaseElementWidget.Token({
    widget: 'CardListViewWidget'
  })
)
export class CardListWidget extends BaseElementListViewWidget {
  public initialize(props) {
    super.initialize(props);
    this.setComponent(cardList);
    this.viewModel = props.model as string;
    return this;
  }

  @Widget.Reactive()
  public viewModel: string = '';

  @Widget.Reactive()
  public condition: string = '';

  @Widget.Reactive()
  public setCondition() {
    this.condition === '1==1' ? (this.condition = '2==2') : (this.condition = '1==1');
  }

  public async queryPage<T = ActiveRecord>(
    condition: Condition,
    pagination: Pagination,
    sort: ISort[],
    variables: QueryVariables,
    context: QueryContext
  ): Promise<any> {
    const model = this.metadataRuntimeContext.model;
    this.loading = true;
    const result = await queryPage(
      model.model,
      {
        currentPage: pagination.current,
        pageSize: this.showPagination ? pagination.pageSize : -1,
        sort,
        condition: condition.toString() === DEFAULT_TRUE_CONDITION ? '' : condition
      },
      undefined,
      variables,
      {
        maxDepth: 0
      }
    );
    this.loading = false;
    return result;
  }
}
<template>
  <div v-if="showDataSource && showDataSource.length">
    <div v-for="data in showDataSource" :key="data.id">
      {{ data.id }}
    </div>
  </div>

  <div v-else>暂无数据</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref, watch } from 'vue';

export default defineComponent({
  mixins: [ManualWidget],
  props: {
    viewModel: {
      type: String,
      default: ''
    },
    loading: {
      type: Boolean,
      default: false
    },
    pagination: {
      type: Object
    },
    showDataSource: {
      type: Array
    },
    refreshProcess: {
      type: Function
    },
    onPaginationChange: {
      type: Function
    }
  },
  components: { OioPagination, OioSpin },
  setup(props) {
    return {};
  }
});
</script>

当卡片对应的widget写完后,我们还需要一个切换卡片跟表格的功能。

视图类型切换

我们只需要在TableWithCardViewWidget对应的vue里面添加切换视图类型的功能就行了。

<template>
  <div class="list-view-wrapper">
    <!-- 切换视图类型 -->
    <button @click="onChangeViewType(listViewType === 'table' ? 'card' : 'table')">
      {{ listViewType === 'table' ? '切换成卡片' : '切换成表格' }}
    </button>
    <!-- 表格插槽 -->
    <div style="height: 100%" v-if="listViewType === 'table'">
      <slot name="tableWidget" />
    </div>

    <!-- 卡片插槽 -->
    <div v-if="listViewType === 'card'">
      <slot name="cardWidget"></slot>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  props: ['listViewType', 'onChangeViewType'],
  inheritAttrs: false,
  setup(props, context) {
    const onChangeViewType = (listViewType) => {
      props.onChangeViewType(listViewType);
    };
  }
});
</script>

最后在TableWithCardViewWidget.ts里面写对应的onChangeViewType方法即可.

public resetSearch() {
    getRouterInstance()!.push({
      segments: [
        {
          path: 'page',
          parameters: {
            searchBody: undefined,
            currentPage: undefined
          },
          extra: { preserveParameter: true }
        }
      ]
    });
  }

  @Widget.Method()
  public onChangeViewType(viewType: ListViewType, init: boolean): void {
    this.listViewType = viewType;
    this.reloadDataSource(undefined);
    this.reloadActiveRecords(undefined);
    // 重置搜索,如果有需要就放开
    // this.resetSearch();
    if (!init) {
      const tableWidget = this.dslSlots?.tableWidget?.widgets?.[0];
      if (tableWidget && tableWidget.automatic == null) {
        tableWidget.automatic = false;
      }
      const cardWidget = this.dslSlots?.cardWidget?.widgets?.[0];
      if (cardWidget && cardWidget.automatic == null) {
        cardWidget.automatic = false;
      }
    }
  }

这样一来,我们就完成了所有的功能。

Oinone社区 作者:汤乾华原创文章,如若转载,请注明出处:https://doc.oinone.top/frontend/18377.html

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

(0)
汤乾华的头像汤乾华数式员工
上一篇 2024年10月15日 pm5:38
下一篇 2024年10月17日 pm7:43

相关推荐

  • 函数扩展

    oinone 平台内置了一些函数, 如果当前函数不满足,那么可以通过扩展的方式添加函数 后端实现 后端实现 import {Expression} from '@kunlun/dependencies' Expression.getInstance().registerFunction(); registerFunction函数的参数如下 /** @param {string} name 函数名字 @param {unknown[]} argTypes 参数类型 @param {Function} handle 回调函数 */ 函数名需要跟后端的保持一致import {Expression} from '@kunlun/dependencies';Expression.getInstance().registerFunction('FUNCTION_NAME', ['number|string'], (input: number | string) => { // todo});第二个参数要注意下,该参数跟回调函数里面的参数要保持一致 Expression.getInstance().registerFunction('FUNCTION_NAME', ['number'], (input: number ) => { // todo } ); Expression.getInstance().registerFunction('FUNCTION_NAME', ['string'], (input: string ) => { // todo } ); Expression.getInstance().registerFunction('FUNCTION_NAME', ['array'], (input: any[] ) => { // todo } );

    2023年11月9日
    1.6K00
  • 在前端视图添加自定义的区域块

    添加自定义区域块 平台提供了一系列默认的视图布局,可以帮助开发人员快速构建出复杂的企业应用系统。当然,我们可以使用自定义区域块来扩展表格、表单、画廊、树形等视图。 自定义区域块概述 平台视图布局都是通过XML配置实现的。在视图布局中,我们可以使用一些特定的元素标签来构建视图的表头、表单、搜索区域等部分。而自定义区域块,就是这些元素标签之外的部分。我们可以通过在视图布局的XML中添加自定义区域块,来扩展页面功能。 视图类型及相关元素 视图类型分为表格(TABLE)、表单(FORM)、画廊(GALLERY)、树形(TREE)等。不同类型的视图布局,包含的元素也有所不同。 下面是几种视图类型及其对应的元素: 表格:搜索区域、表格主体,其中表格主体包含了表格上面的动作、表格区域等部分。 表单:表单区域,包含了表单动作、表单区域等部分。 画廊:动作、卡片详细信息。 在表格页面添加自定义区域块 以下是一个示例,演示如何在表格页面顶部添加自定义区域块。 1. 修改视图布局XML 首先,我们需要修改表格视图的XML布局,添加自定义区域块元素标签。 <view type="TABLE"> <!– 这是搜索区域 –> <pack widget="group"> <view type="SEARCH"> <element widget="search" slot="search" slotSupport="field" /> </view> </pack> <!– 这是表格主体 –> <pack widget="group" slot="tableGroup"> <!– 在这里添加自定义区域块元素标签 –> <element widget="MyCustomElement"></element> <!– 这是表格上面的动作 –> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> <!– 这是表格区域 –> <element widget="table" slot="table" slotSupport="field"> <element widget="expandColumn" slot="expandRow" /> <xslot name="fields" slotSupport="field" /> <element widget="rowActions" slot="rowActions" slotSupport="action" /> </element> </pack> </view> 在上述代码中,我们添加了一个名为MyCustomElement的元素标签。这将作为我们自定义区域块的容器。 2. 创建自定义Vue组件 接下来,我们需要创建一个Vue组件,并将其指定为自定义元素标签MyCustomElement的模板。 <template> <div> <!– 在这里编写自定义区域块的内容 –> <p>Hello, world!</p> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; export default defineComponent({ components: { }, props: [], setup(props) { return {}; } }); </script> 在上述代码中,我们定义了一个非常简单的Vue组件,它在页面上显示一个“Hello, world!”的文本信息。 3. 创建自定义Element Widget 为了使自定义Vue组件与XML布局文件关联起来,我们需要创建一个对应的Element Widget。 import { BaseElementWidget, SPI, BaseElementViewWidget, Widget, ViewMode, FormWidget, BaseElementWidgetProps } from '@kunlun/dependencies'; import MyCustomElement from './MyCustomElement.vue'; @SPI.ClassFactory(BaseElementWidget.Token({ widget: 'MyCustomElementWidget' })) export class MyCustomElementWidget extends BaseElementWidget { public initialize(props: BaseElementWidgetProps): this { super.initialize(props) this.setComponent(MyCustomElement) return this } } 在上述代码中,我们继承了BaseElementWidget类,并在其中指定了Vue组件MyCustomElement。这样,XML布局文件中的元素标签就能够正确地与Vue组件关联起来。 4. 注册视图布局 最后,我们需要将上述代码配置注册。具体而言,我们需要调用registerLayout方法来将XML布局文件、模块名和视图类型进行关联。 import { registerLayout, ViewType } from '@kunlun/dependencies'; registerLayout( `<view type="TABLE"> <pack widget="group"> <view type="SEARCH"> <element widget="search" slot="search" slotSupport="field" />…

    2023年11月1日
    2.3K00
  • 如何关闭table的checkbox?

    需要修改xml的配置 将xml中的fields改成table,并将配置加上 // 原来的写法 <template slot=”fields” > … </template> // 配置后的写法 <template slot=”table” checkbox=”false”> … </template>

    2023年11月1日
    66000
  • 【前端】登录页面扩展点

    登录页面扩展点 场景 1: 登录之前需要二次确认框2: 前端默认的错误提示允许被修改3: 后端返回的错误提示允许被修改4: 登录后跳转到自定义的页面 方案 前端默认错误可枚举 errorMessages: { loginEmpty: '用户名不能为空', passwordEmpty: '密码不能为空', picCodeEmpty: '图形验证码不能为空', phoneEmpty: '手机号不能为空', verificationCodeEmpty: '验证码不能为空', picCodeError: '图形验证码错误', inputVerificationCodeAlign: '请重新输入验证码' } 登录按钮添加拓展点beforeClick、afterClick 代码 新增一个ts文件,继承平台默认的LoginPageWidget @SPI.ClassFactory(RouterWidget.Token({ widget: 'Login' })) export class CustomLoginPageWidget extends LoginPageWidget { constructor() { super(); // 修改前端默认的错误文案 this.errorMessages.loginEmpty = '登录用户名不能为空'; } /** * 用来处理点击「登录」之前的事件,可以做二次确定或者其他的逻辑 * 只有return true,才会继续往下执行 */ public beforeClick(): Promise<Boolean | null | undefined> { return new Promise((resolve) => { Modal.confirm({ title: '提示', content: '是否登录?', onOk: () => { resolve(true); } }); }); } /** * * @param result 后端接口返回的数据 * * 用来处理「登录」接口调用后的逻辑,可以修改后端返回的错误文案,也可以自定义 * * 只有return true,才会执行默认的跳转事件 */ public afterClick(result): Promise<any | null | undefined> { // if(result.redirect) { // 自定义跳转 //return false //} if (result.errorCode === 20060023) { result.errorMsg = '手机号不对,请联系管理员'; } return result; } }

    2023年11月1日
    1.0K00
  • oio-select 选择器

    API Select props 参数 说明 类型 默认值 版本 allowClear 支持清除 boolean false autofocus 默认获取焦点 boolean false clearIcon 自定义的多选框清空图标 VNode | slot – disabled 是否禁用 boolean false dropdownClassName 下拉菜单的 className 属性 string – dropdownRender 自定义下拉框内容 ({menuNode: VNode, props}) => VNode | v-slot – filterOption 是否根据输入项进行筛选。当其为一个函数时,会接收 inputValue option 两个参数,当 option 符合筛选条件时,应返回 true,反之则返回 false。 boolean | function(inputValue, option) true getTriggerContainer 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。 function(triggerNode) () => document.body menuItemSelectedIcon 自定义当前选中的条目图标 VNode | slot – options options 数据,如果设置则不需要手动构造 selectOption 节点 array<{value, label, [disabled, key, title]}> [] placeholder 选择框默认文字 string|slot – removeIcon 自定义的多选框清除图标 VNode | slot – suffixIcon 自定义的选择框后缀图标 VNode | slot – value(v-model:value) 指定当前选中的条目 string|string[]|number|number[] – 注意,如果发现下拉菜单跟随页面滚动,或者需要在其他弹层中触发 Select,请尝试使用 getPopupContainer={triggerNode => triggerNode.parentNode} 将下拉弹层渲染节点固定在触发器的父元素中。 事件 事件名称 说明 回调参数 blur 失去焦点的时回调 function change 选中 option,或 input 的 value 变化(combobox 模式下)时,调用此函数 function(value, option:Option/Array<Option>) deselect 取消选中时调用,参数为选中项的 value (或 key) 值,仅在 multiple 或 tags 模式下生效 function(value,option:Option) dropdownVisibleChange 展开下拉菜单的回调 function(open) focus 获得焦点时回调 function inputKeyDown 键盘按下时回调 function mouseenter 鼠标移入时回调 function mouseleave 鼠标移出时回调 function popupScroll 下拉列表滚动时的回调 function search 文本框值变化时回调 function(value: string) select 被选中时调用,参数为选中项的 value (或 key) 值 function(value, option:Option)

    2023年12月18日
    49700

Leave a Reply

登录后才能评论