列表视图、卡片视图切换

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

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

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

相关推荐

  • 前端-如何修改指定页面的内组件的css样式

    为组件加自定义class,用该class作为父选择器写特定的css样式 以form为例,自定义了以下class <view/>标签的表单视图(FormView)组件 <element/>标签的form(FormWidget)组件 <element/>标签的actionBar(ActionBarWidget)组件 import { registerLayout, ViewType } from '@kunlun/dependencies'; export const install = () => { registerLayout( ` <view type="FORM" class="my-form-view"> <element widget="form" slot="form" class="my-form-widget"> <xslot name="fields" slotSupport="pack,field" /> </element> <element widget="actionBar" slot="actionBar" class="my-action-bar" slotSupport="action" > <xslot name="actions" slotSupport="action" /> </element> </view> `, { viewType: ViewType.Form, model: 'resource.k2.Model0000000109', actionName: 'uiViewb2de116be1754ff781e1ffa8065477fa' } ); }; install(); 查看修改后的页面html结构 编写样式的css .my-form-view .oio-form { /** TODO **/ } .my-form-widget .oio-row { /** TODO **/ } .my-action-bar .oio-col { /** TODO **/ }

    2024年6月17日
    1.7K00
  • 前端6.2.x升级指南

    升级背景 从 6.2.x 版本开始,平台底层核心代码已全面开源(设计器相关代码除外),本次升级涉及包名变更和配置调整,以下是详细的升级步骤 1. 更新 .npmrc 配置 修改当前项目根目录 .npmrc 文件,内容如下: registry=https://registry.npmmirror.com/ @oinone:registry=http://nexus.shushi.pro/repository/kunlun/ @kunlun:registry=http://nexus.shushi.pro/repository/kunlun/ shamefully-hoist=true legacy-peer-deps=true link-workspace-packages = true prefer-workspace-packages = true recursive-install = true lockfile=true 2. 包名变更 以前的包是 @kunlun/xxxx,现在的包名是@oinone/kunlun开头,所以需要全部替换修改。 旧包名 新包名 @kunlun/dependencies @oinone/kunlun-dependencies @kunlun/vue-ui-antd @oinone/kunlun-vue-ui-antd 3. 升级依赖版本 修改所有 package.json 中更新所有平台相关依赖: – "@kunlun/dependencies": "~5.7.0" + "@oinone/kunlun-dependencies": "~6.2.0" boot工程的package.json新增devDependencies依赖 "devDependencies": { "@types/lodash": "4.14.182", "@types/lodash-es": "4.17.6", ….. } 4. 更新代码引用 全局搜索并替换代码中的所有 @kunlun/ 为 @oinone/kunlun- 完整的 main.ts import 'ant-design-vue/dist/antd.min.css'; import 'element-plus/dist/index.css'; import '@oinone/kunlun-vue-ui-antd/dist/oinone-kunlun-vue-ui-antd.css'; import '@oinone/kunlun-vue-ui-el/dist/oinone-kunlun-vue-ui-el.css'; import '@oinone/kunlun-designer-common/dist/oinone-kunlun-designer-common.css'; import '@oinone/kunlun-model-designer/dist/oinone-kunlun-model-designer.css'; import '@oinone/kunlun-workflow-designer/dist/oinone-kunlun-workflow-designer.css'; import '@oinone/kunlun-workflow/dist/oinone-kunlun-workflow.css'; import '@oinone/kunlun-data-designer/dist/oinone-kunlun-data-designer.css'; import '@oinone/kunlun-microflow-designer/dist/oinone-kunlun-microflow-designer.css'; import 'reflect-metadata'; import ElementPlus from 'element-plus'; import { App } from 'vue'; import { VueOioProvider, RuntimeContextManager } from '@oinone/kunlun-dependencies'; // 设计器 import '@oinone/kunlun-ui-designer-dependencies'; import '@oinone/kunlun-print-designer-dependencies'; import '@oinone/kunlun-workflow'; import '@oinone/kunlun-workflow-designer'; import '@oinone/kunlun-model-designer'; import '@oinone/kunlun-data-designer'; import '@oinone/kunlun-data-designer-open-pc'; import '@oinone/kunlun-eip-dependencies'; import '@oinone/kunlun-ai-dependencies'; import '@oinone/kunlun-microflow-designer'; import '@oinone/kunlun-designer-common-ee'; // 下面三个依赖是当前工程的包,请根据实际场景修改 import '@ss/oinone'; import '@ss/admin-widget'; import '@ss/project'; VueOioProvider( { browser: { title: 'Oinone – 构你想象!', favicon: 'https://pamirs.oss-cn-hangzhou.aliyuncs.com/pamirs/image/default_favicon.ico' } }, [ () => { /// 获取当前vue实例 const app = RuntimeContextManager.createOrReplace().frameworkInstance as App; } ] ); 当配置全部修改完成后,重写安装依赖 # 清理旧依赖 pnpm clean # 安装新依赖 pnpm install

    2025年6月17日
    70600
  • 前端环境和启动前端工程

    本节核心是带大家直观的感受下我们上节构建的demo模块,并搭建前端环境为后续学习打下基础 环境准备 配置NPM源 npm config set registry http://nexus.shushi.pro/repository/kunlun/ 登录NPM源账号 npm login –registry "http://nexus.shushi.pro/repository/kunlun/" # username、password、email 获取方式: # 1、请见oinone开源社区群公告,也可以联系oinone合作伙伴或服务人员; # 2、参考数式发过去的部署包(部署.zip)中的账号说明:docker-mvn-npm账号.md npm info underscore 环境准备参考 [前端环境准备Mac版本]https://doc.oinone.top/oio4/9225.html [前端环境准备Windows版本]https://doc.oinone.top/oio4/9226.html 启动前端工程 1、下载前端工程本地运行 [ss-front-modules.zip]ss-front-modules 2、解压下载后的工程,可以查看README.MD快速上手指南; 找到vue.config.js文件,修改devServer.proxy.pamirs.target为后端服务的地址和端口 const WidgetLoaderPlugin = require('@kunlun/widget-loader/dist/plugin.js').default; const Dotenv = require('dotenv-webpack'); module.exports = { lintOnSave: false, runtimeCompiler: true, configureWebpack: { module: { rules: [ { test: /\.widget$/, loader: '@kunlun/widget-loader' } ] }, plugins: [new WidgetLoaderPlugin(), new Dotenv()], resolveLoader: { alias: { '@kunlun/widget-loader': require.resolve('@kunlun/widget-loader') } } }, devServer: { port: 8081, disableHostCheck: true, progress: false, proxy: { pamirs: { // 支持跨域 changeOrigin: true, // 改成本地后端对应的IP和端口; 本地后端未启动的情况也可改成无代码后端IP和端口 target: 'http://192.168.0.121:8190' } } } }; 3、 安装依赖和运行在工程目录ss-front-modules下执行 # 安装依赖 npm i # 运行 npm run dev 4、若安装失败 检查本地node、npm、vue对应的版本 5、 如果启动报错 清除node_modules后重新 npm i mac清除命令:npm run cleanOs windows清除命令: npm run clean 注:要用localhost域名访问,.env文件这里也要改成localhost。如果开发中一定要出现前后端域名不一致,老版本Chrome会有问题,修改可以请参https://www.cnblogs.com/willingtolove/p/12350429.html。或者下载新版本Chrome 进入前端工程ss-front-modules文件目录下,执行 npm run dev,最后出现下图就代表启动成功 6、使用 http://127.0.0.1:8081/login 进行访问,并用admin账号登陆,默认密码为admin 5、点击左上角进行应用切换,会进入App Finder页面,可以看到所有已经安装的应用,可以对照boot的yml配置文件看。 在后续的学习过程中我们会不断完善示例中的模块。至此恭喜您,前端工程已经启动完成。 示例工程分层说明 # ss-boot 不做业务研发,只做包的组装和依赖 # ss-oinone 与Oinone结合层,这个工程结构可以把数式Oinone的改造收口,也是业务工程依赖的核心层 # ss-admin-widget 与界面设计器无代码的结合工程,在这个工程结构里可以把组件放在无代码平台上使用 # ss-project 模拟的项目工程,做某个项目的个性化开发

    2024年5月28日
    2.8K00
  • 自定义的「视图、字段」调用界面设计器配置的按钮(包含权限控制)

    我们在业务开发中,经常会遇到自定义的视图或字段。有时候期望点击某一块区域的时候,打开一个弹窗或者是跳转新页面亦或者是执行服务端动作(调接口),但是希望这个动作是界面设计器拖拽进来的。 这篇文章详细的讲解了自定义的视图、字段怎么执行界面设计器拖出来的按钮。 自定义视图 1: 先设计一个页面,把对应的动作拖拽进来,可以不需要配置字段2: 将该页面绑定菜单 3: 自定义对应的页面 当我们自定义视图的时候,首先会注册一个视图,下面是我自定义的一个表单视图 registerLayout( `<view type="FORM"> <element widget="actionBar" slot="actionBar"> <xslot name="actions" /> </element> <element widget="MyWidget" slot="form"> <xslot name="fields" /> </element> </view>`, { moduleName: 'ys0328', model: 'ys0328.k2.Model0000000453', actionName: 'MenuuiMenu78ec23b054314ff5a12b4fe95fe4d7b5', viewType: ViewType.Form } ); 我自定义了一个叫做MyWidget的 element,下面是对应的ts代码 @SPI.ClassFactory(BaseElementWidget.Token({ widget: 'MyWidget' })) export class MyWidgetManageWidget extends FormWidget { public initialize(props): this { super.initialize(props); this.setComponent(MyVue); return this; } } 这是对应的 vue 文件: MyVue.vue <template> <div @click="onClick">点击执行动作</div> </template> <script lang="ts"> import { defineComponent } from 'vue'; export default defineComponent({ props: ['onClick'] }); </script> 这个时候,我希望点击的时候,执行 onClick,会执行对应的动作,这时只需要在对应的 ts 文件中写对应的代码逻辑即可: @SPI.ClassFactory(BaseElementWidget.Token({ widget: 'MyWidget' })) export class MyWidgetManageWidget extends BaseElementWidget { // 获取当前页面所有的按钮 @Widget.Reactive() public get modelActions() { return this.metadataRuntimeContext.model.modelActions || [] } // 用来解析上下文表达式的,如果不需要,可以删除 public executeCustomExpression<T>( parameters: Partial<ExpressionRunParam>, expression: string, errorValue?: T ): T | string | undefined { const scene = this.scene; return Expression.run( { activeRecords: parameters.activeRecords, rootRecord: parameters.rootRecord, openerRecord: parameters.openerRecord, scene: parameters.scene || scene, activeRecord: parameters.activeRecord } as ExpressionRunParam, expression, errorValue ); } // 点击事件 @Widget.Method() public onClick() { // 找到对应的按钮 const action = this.modelActions.find((a) => a.label === '动作的显示名称'); /** * 如果是服务端动作,就执行 executeServerAction */ // executeServerAction(action, 参数对象) // 第二个参数是调用对应的接口传递的参数 /** *…

    2023年11月8日
    1.9K02
  • 【前端】低无一体部署常见问题

    如何检查上传的SDK是否有效? 1. 在任意页面刷新后,查看是否发起【查询SDK组件】的请求。 2. 在返回的js和css列表中是否能找到在界面设计器上传的js和css文件。 3. 检查浏览器的Console中是否有组件相关报错。 4. 检查sdk中是否包含了启动工程未加入的包依赖。 启动工程包依赖:main.ts VueOioProvider( { dependencies: { vue: import('vue'), lodashEs: import('lodash-es'), antDesignVue: import('ant-design-vue'), elementPlusIconsVue: import('@element-plus/icons-vue'), elementPlus: import('element-plus'), kunlunDependencies: import('@kunlun/dependencies'), kunlunVueUiAntd: import('@kunlun/vue-ui-antd'), kunlunVueUiEl: import('@kunlun/vue-ui-el') } } ); SDK依赖:rollup.config.ts const globals = { vue: 'vue', 'lodash-es': 'lodashEs', 'ant-design-vue': 'antDesignVue', '@element-plus/icons-vue': 'elementPlusIconsVue', 'element-plus': 'elementPlus', '@kunlun/dependencies': 'kunlunDependencies', '@kunlun/vue-ui-antd': 'kunlunVueUiAntd', '@kunlun/vue-ui-el': 'kunlunVueUiEl', '@kunlun/mobile-dependencies': 'kunlunMobileDependencies', '@kunlun/vue-ui-mobile-vant': 'kunlunVueUiMobileVant' }; 上述两个文件配置的依赖和对应名称必须匹配才能在sdk上传后正常运行,否则会出现内存变量无法共享的问题。 当未发起【查询SDK组件】的请求时如何处理? 1. 在任意页面刷新后,查看manifest.js加载路径。 业务工程通常为:http://${host}:${port}/manifest.js 设计器镜像中通常为:http://${host}:${port}/config/manifest.js 2. 若未正确加载manifest.js,则在dist目录中根据请求路径添加manifest.js文件。此文件称为运行时配置文件,可点击查看参考文档。 runtimeConfigResolve({ plugins: { usingRemote: true } });

    低无一体 2023年11月1日
    2.7K00

Leave a Reply

Please Login to Comment