列表视图、卡片视图切换

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

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

我们用资源模块下面的国家菜单来实现这么一个功能这是对应的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低代码应用平台体验

Like (0)
汤乾华's avatar汤乾华数式员工
Previous 2024年10月15日 pm5:38
Next 2024年10月17日 pm7:43

相关推荐

  • 表格如何支持表尾合计计算

    介绍 可以通过扩展TableWidget.ts实现 示例代码 import { BaseElementWidget, DslDefinitionType, SPI, TableWidget, ViewType, Widget } from '@kunlun/dependencies'; @SPI.ClassFactory( BaseElementWidget.Token({ type: ViewType.Table, widget: 'table', model: 'resource.k2.Model0000000109', viewName: '移动端品牌_TABLE_0000000000021513' }) ) export class FooterStatisticsTable extends TableWidget { public initialize(props) { if (props.template) { props.template?.widgets?.forEach((a) => { if (a.dslNodeType === DslDefinitionType.FIELD && this.statisticsFieldList.includes(a.name)) { a.statistics = true; } }); } super.initialize(props); return this; } // 需要表尾做合并的字段名称 public statisticsFieldList = ['fansNum']; @Widget.Reactive() protected get showFooter(): boolean | undefined { return true; } } 效果预览

    2024年10月14日
    2.3K00
  • 表格主题配置(v4)

    TableThemeConfig /** * 表格主题配置 */ export interface TableThemeConfig { border: boolean | string; stripe: boolean; isCurrent: boolean; isHover: boolean; /** * 表格列主题配置 */ column: Partial<TableColumnThemeConfig>; } /** * 表格列主题配置 */ export interface TableColumnThemeConfig { /** * <h3>最小宽度</h3> * <ul> * <li>boolean: enabled column width auto compute</li> * <li>number: using css width (default: px)</li> * <li>string: using css width</li> * <li> * object: auto compute width for label by default function * <ul> * <li>min: min min width (default: 120)</li> * <li>max: max min width (default: 432)</li> * <li>chineseWidth: chinese width (default: 14 -> fontSize: 14px)</li> * <li>otherWidth: non chinese width (default: 9 -> fontSize: 14px)</li> * <li>sortableFixWidth: sortable handle width (default: 40)</li> * <li>nonSortableFixWidth: non sortable fix width (default: 22)</li> * </ul> * </li> * <li>function: auto compute width for label by function</li> * </ul> */ minWidth: boolean | number | string | Partial<TableColumnMinWidthComputeConfig> | TableColumnMinWidthComputeFunction; /** * 操作列 */ operation: { /** * 宽度 (default: 165) */ width?: number | string; /** * 最小宽度 (default: 120) */ minWidth?: number | string; }; } export interface TableColumnMinWidthComputeConfig { min: number;…

    2023年11月6日
    1.9K00
  • 界面设计器 扩展字段的查询上下文

    默认情况下oinone平台对于查询条件,只提供的当前登录用户这一个配置,但是允许开发者扩展 开发者可以在前端代码的main.ts进行扩展 import { SessionContextOptions, ModelFieldType } from '@kunlun/dependencies'; const currentDeptOption = { ttype: ModelFieldType.String, value: '$#{currentDept}', displayName: '当前登录部门', label: '当前登录部门' }; SessionContextOptions.push(currentDeptOption as any); 加上上面的代码,然后再去界面设计器,我们就会发现,多了一个配置

    2023年11月8日
    2.6K00
  • 主题设置-设置表格全局样式

    在启动工程的main.ts通过主题配置表格全局样式 registerThemeItem('demoTheme', { 'table-config': { // 是否带有边框 default(默认), full(完整边框), outer(外边框), inner(内边框), none(无边框) border: 'full', // 是否带有斑马纹 stripe: false, // 当鼠标点击行时,是否要高亮当前行 isCurrent: true } as Partial<TableThemeConfig> }); VueOioProvider( { browser: { title: 'Oinone – 构你想象!', favicon: 'https://pamirs.oss-cn-hangzhou.aliyuncs.com/pamirs/image/default_favicon.ico' }, theme: ['demoTheme'], // 其他代码 });

    2024年8月2日
    1.6K00
  • 前端日期组件国际化支持方案

    在 oinone 平台中,系统默认支持基础的国际化翻译功能。但由于日期时间组件的国际化依赖对应语言包,而全量引入语言包会显著增加打包体积,因此前端默认仅集成了中、英文的日期时间支持。若需为日期时间组件扩展其他语言(如日语)的国际化支持,需手动导入对应语言包并完成配置,具体步骤如下: 假设我们现在国际化翻译切换成了日语,那么我们在日期时间也要支持日语,那么需要如下操作: 1: 重写 RootWidget 继承平台默认的 RootWidget,SPI 注册条件保持跟平台一致即可覆盖平台默认的RootWidget // CustomRootWidget.ts import { RootComponentSPI, RootWidget, SPIFactory } from '@oinone/kunlun-dependencies'; import Root from './Root.vue'; // 通过SPI注册覆盖平台默认的root组件 @SPIFactory.Register(RootComponentSPI.Token({ widget: 'root' })) export class CustomRootWidget extends RootWidget { public initialize() { super.initialize(); this.setComponent(Root); return this; } } 2: 覆盖 Root 组件的 Vue 文件 自定义的 Vue 文件需负责导入目标语言(如日语)的语言包,并根据当前语言环境动态切换配置。这里需要同时处理 ant-design-vue、element-plus 组件库及 dayjs 工具的语言包,确保日期组件的展示和交互统一适配目标语言。 <!– Root.vue –> <template> <a-config-provider :locale="antLocale"> <el-config-provider :locale="eleLocale"> <match :rootToken="root"> <template v-for="page in pages" :key="page.widget"> <route v-if="page.widget" :path="page.path" :slotName="page.slotName" :widget="page.widget"> <slot :name="page.slotName" /> </route> </template> <route :path="pagePath" slotName="page" :widgets="{ page: widgets.page }"> <slot name="page" /> </route> <route path="/" slotName="homePage"> <slot name="homePage" /> </route> </match> </el-config-provider> </a-config-provider> </template> <script lang="ts"> import { CurrentLanguage, EN_US_CODE, UrlHelper, ZH_CN_CODE } from '@oinone/kunlun-dependencies'; import { ConfigProvider as AConfigProvider } from 'ant-design-vue'; import { ElConfigProvider } from 'element-plus'; import dayjs from 'dayjs'; // 导入ant-design-vue语言包 import enUS from 'ant-design-vue/es/locale/en_US'; import zhCN from 'ant-design-vue/lib/locale/zh_CN'; import jaJP from 'ant-design-vue/lib/locale/ja_JP'; // 新增:日语语言包 // 导入 dayjs的语言包 import 'dayjs/locale/zh-cn'; import 'dayjs/locale/ja'; // 新增:日语语言包 // 导入element-plus语言包 import elEn from 'element-plus/dist/locale/en.mjs'; import elZhCn from 'element-plus/dist/locale/zh-cn.mjs'; import elJaJP from 'element-plus/dist/locale/ja.mjs'; // 新增:日语语言包 import { computed, defineComponent, onMounted,…

    2025年8月13日
    1.0K00

Leave a Reply

Please Login to Comment