关于二次封装element-plus table的一些归纳总结(2)

在上一篇文章中,我们展示了我们封装的element-plus table组件的一些基础示例,本篇文章将展示一些复杂示例的用法,让我们一起来看看吧。

示例8--table pro demo

这个示例主要展示了table的展开用法,以及table末尾插槽用法,然后对比el-table的用法。我们可以使用append来向表体下方添加一些描述信息之类。在表格列数据配置当中,我们同样可以使用renderHeader方法来自定义渲染列,不过这在element-plus中是会被警告的,不建议使用。同样的我们也可以指定fixed属性用于固定列。首先我们来看我们的html代码,如下所示:

html 复制代码
  <div>
   <!--第一个表格-->
    <element-table :data="tableData" :column="column0" :border="false" :stripe="false">
      <template #header> headersdfsf </template>
      <template #append> appendsdfsdf </template>
    </element-table>
      <!--第二个表格-->
    <element-table :data="tableData" :column="column" :border="false" :stripe="false">
      <template #default="props">
        <p>State: {{ props.row.state }}</p>
        <p>City: {{ props.row.city }}</p>
        <p>Address: {{ props.row.address }}</p>
        <p>Zip: {{ props.row.zip }}</p>
      </template>
      <template #header> headersdfsf </template>
      <template #append> appendsdfsdf </template>
    </element-table>
      <!--第三个表格-->
    <el-table :data="tableData" style="width: 100%">
      <el-table-column prop="date" label="Date" width="180" />
      <el-table-column prop="name" label="Name" width="180" />
      <el-table-column prop="address" label="Address" />
    </el-table>
  </div>

这其中,我们第一个表格使用了header插槽和append插槽自定义了列与表体下方的渲染。第二个表格同理,不过也增加了default默认插槽的渲染,第三个就是我们的el-table的基础使用用法。接下来我们来看tsx代码如下:

tsx 复制代码
import { defineComponent, ref } from 'vue';

export default defineComponent({
  name: 'ElTableProDemo',
  setup() {
    const searchVal = ref('');
    const tableData = [
      // 数据省略
    ];

    const column0 = [
      {
        label: '日期',
        prop: 'date',
      },
      {
        type: 'expand',
        slotName: 'default',
      },

      {
        label: '操作项',
        fixed: 'right',
        render() {
          return (
            <el-button
              onClick={() => {
                console.log(searchVal.value);
              }}
            >
              按钮
            </el-button>
          );
        },
        headerSlot: 'header',
        //这一种方法会有警告提示
        // renderHeader(){
        //   return <el-input vModel={searchVal.value} size="default" placeholder="Type to search" />
        // }
      },
    ];
    const column = [
      {
        label: '日期',
        prop: 'date',
      },
      {
        type: 'expand',
        slotName: 'default',
      },
      {
        label: '快递信息',
        children: [
          {
            label: '名称',
            prop: 'name',
          },
          {
            label: '地址',
            prop: 'state',
            // children: [
            //   {
            //     label: '州',
            //     prop: 'state',
            //   },
            //    {
            //     label: 'city',
            //     prop: 'city',
            //   }
            // ]
          },
        ],
      },
      {
        label: '操作项',
        fixed: 'right',
        render() {
          return (
            <el-button
              onClick={() => {
                console.log(searchVal.value);
              }}
            >
              按钮
            </el-button>
          );
        },
        headerSlot: 'header',
        // renderHeader(){
        //   return <el-input vModel={searchVal.value} size="default" placeholder="Type to search" />
        // }
      },
    ];
    return {
      column,
      column0,
      tableData,
    };
  }
});

可以看到,这里我们在render方法中返回一个按钮,注意这里我们使用的是tsx写法,如果是ts代码,则需要使用vue提供的h函数,如上一篇文章介绍到的示例用法。同时我们也使用了children属性来指定嵌套列。

以上代码展示效果如下所示:

示例9--可编辑的表格

在这个示例当中,我还碰到了一个很有意思的问题,那就是表格列数据如果发生了改变,页面视图是不会发生更新的,最开始我排查了底层封装,发现如果列数据发生变动,根本就不会执行到底层渲染逻辑,所以我才用了一种hack方式来解决掉这个问题,也就是给element-table组件标签添加一个key属性,值就时列数据,也就是说列数据如果发生变动,那么key属性值也就会发生变动,因此视图就会发生重新渲染更新。

首先我们来看html代码,就是一个按钮和一个表格,如下所示:

html 复制代码
  <el-button @click="toggleColumnSort">互换姓名与标签的顺序</el-button>
  <!-- colum变更时不添加key则不会变化,这个问题有点奇怪 -->
  <element-table :column="column" :data="tableData" :key="column"/>

接下来,主要逻辑在于tsx代码,我们创建2个ref响应式数据,用于存储列数据和表体数据,这里主要的难点在于列数据的定义,表体数据比较简单。如下:

tsx 复制代码
import { ref } from 'vue';
// 这里是我们封装的列数据类型,后续会提到
import { ElTableColumnProps } from '../components/tableProps';
const column = ref<ElTableColumnProps[]>([
   /* 这里先忽略 */
])
const tableData = ref([
   // 数据省略
]);

按钮事件逻辑也比较简单,就是调整一下列数据的索引即可,如下:

tsx 复制代码
const toggleColumnSort = () => {
  column.value = [...column.value.slice(0, 1), column.value[2], column.value[1], ...column.value.slice(3)];  
};

接下来就是列数据的定义,列数据,我们同样还是使用render函数来自定义列,请注意,这里我们为当前表格上下文的行数据中添加了一个_edit属性,代表是否可编辑,同时添加了一个_pre_data来存储行数据,这是因为我们编辑了行数据之后,数据会有所变动,所以我们需要在编辑之前提前存储数据,然后取消编辑的时候,我们就可以恢复原来数据。

因此,这里我们需要自定义三个按钮,分别是编辑,保存,取消按钮,日期我们会使用日期组件el-date-picker来编辑,姓名则是一个输入框组件,标签则是一个选择框组件。

首先,我们来看标签选项,如下所示:

ts 复制代码
const tagOptions = [
  {
    label: '家',
    value: '家',
  },
  {
    label: '公司',
    value: '公司',
  },
];

基于以上分析,我们就可以定义出列数据了,如下所示:

tsx 复制代码
const column = ref<ElTableColumnProps[]>([
  {
    prop: 'date',
    label: '日期',
    render: (value, scope) =>
      scope.row._edit ?
        <el-date-picker
          model-value={value}
          width={160}
          type='date'
          onUpdate:modelValue={(val: { toLocaleDateString: () => any; }) => {
            scope.row[scope.column.property] = val?.toLocaleDateString();
          }}
          placeholder='Pick a day'
        />
        : value,
  },
  {
    prop: 'name',
    label: '姓名',
    render: (value, scope) =>
      scope.row._edit ? <el-input
        model-value={value}
        onUpdate:modelValue={(val: any) => {
          scope.row[scope.column.property] = val;
        }}
      /> : value,
  },
  {
    prop: 'tag',
    label: '标签',
    render: (value, scope) =>
      scope.row._edit ?
        <el-select
          model-value={value}
          style='width: 120px'
          onUpdate:modelValue={(val: string) => {
            scope.row[scope.column.property] = val;
          }}
        >
          {tagOptions.map((option) => (
            <el-option label={option.label} value={option.value}></el-option>
          ))}
        </el-select>
        : <el-tag type={scope.row.tag === '家' ? 'info' : 'success'}>
          {value}
        </el-tag>,
  },
  {
    label: '操作',
    render: (scope) =>
      scope.row._edit ?
        <div>
          <el-button
            type='primary'
            onClick={() => {
              handleSave(scope);
            }}
          >保存</el-button>
          <el-button
            onClick={() => {
              handleCancel(scope);
            }}
          > 取消</el-button>
        </div> :
        <el-button
          type='primary'
          onClick={function () {
            handleEdit(scope);
          }}
        > 编辑</el-button>
  },
]);

这里尤其需要注意的就是在tsx代码中,我们可能无法使用v-model双向绑定指令,因此我们使用的是model-value属性和onUpdate:modelValue事件来实现数据的绑定已经变更,当然如果是写在ts代码中,则不必如此麻烦,不过如果是写ts代码,那么写组件就需要用h函数,写起来一样的麻烦。

接下来,就是三个方法的实现了,保存很简单,就是修改_edit属性为false即可,如果在实际业务当中,就需要调用保存接口。而取消就是重置行数据,编辑的逻辑则是修改_edit属性为true,并缓存行数据,代码分别如下所示:

ts 复制代码
const handleEdit = (scope: { row: { _pre_data: string; _edit: boolean; }; }) => {
  scope.row._pre_data = JSON.stringify(scope.row);
  scope.row._edit = true;
};
const handleSave = (scope: { row: { _edit: boolean; }; }) => {
  scope.row._edit = false;
};
const handleCancel = (scope: { row: { _pre_data: string; }; }) => {
  Object.assign(scope.row, {
    ...JSON.parse(scope.row._pre_data),
    _edit: false,
    _pre_data: null,
  });
};

以上代码展示效果如下所示:

示例10--表体数据展开

数据的展开,只需要使用type属性值为expand,然后需要自定义展开项,就可以使用插槽,以下是一个简单的示例。html代码如下所示:

html 复制代码
 <element-table :column="column" :data="tableData" style="width: 100%">
    <template #default="props">
      <p>State: {{ props.row.state }}</p>
      <p>City: {{ props.row.city }}</p>
      <p>Address: {{ props.row.address }}</p>
      <p>Zip: {{ props.row.zip }}</p>
    </template>
  </element-table>

ts代码如下所示:

ts 复制代码
const column = [
  // 主要在这里,哪一列想要展开,就在哪里添加这个配置对象,这里是第一列展开
  {
    type: "expand",
    slotName: "default",
  },
  {
    label: "Date",
    prop: "date",
  },
  {
    label: "Name",
    prop: "name",
  },
];
const tableData = [
   // 数据省略
];

以上代码展示效果如下所示:

示例11--数据过滤器(包含排序)

在列中设置 filtersfilter-method 属性即可开启该列的筛选, filters 是一个数组,filter-method 是一个方法,它用于决定某些数据是否显示, 会传入三个参数:value, rowcolumn

同样的也可以配置sortable属性,用于开启排序功能。

表格实例对象,我们通过一个ref响应式数据绑定,表格实例对象提供了很多方法,其中就包含了清除过滤的方法,即clearFilter方法,该方法支持传入一个列属性名组成的数组,比如你的表格配置了日期列,属性名为date,那么就可以传入["date"]参数。

并且我们也可以给列设置一个formatter函数,用于格式化列的展示数据,还可以设置filter-placement属性来设置过滤弹出框的定位。

接下来,我们就来看看具体的html代码和ts代码。

html 复制代码
<!--重置日期过滤-->
 <el-button @click="resetDateFilter">reset date filter</el-button>
 <!--重置所有过滤-->
  <el-button @click="clearFilter">reset all filters</el-button>
  <!--表格-->
  <element-table
    :column="column"
    ref="tableRef"
    row-key="date"
    :data="tableData"
    style="width: 100%"
  >
    <template #default="scope">
      <el-tag :type="scope.row.tag === 'Home' ? 'info' : 'success'" disable-transitions>
        {{ scope.row.tag }}
      </el-tag>
    </template>
  </element-table>

需要注意,这里我们需要给row-key设置一个过滤属性值,这里设置的是date属性,当然我们也可以设置其它值。然后我们绑定了一个tableRef值,这里就需要用到element-plus提供的2个类型TableColumnCtx和ElTable,即表格列上下文类型和表格实例对象类型。

代码如下所示:

ts 复制代码
import { ref } from "vue";
//导入的两个类型
import type { TableColumnCtx } from "element-plus/es/components/table/src/table-column/defaults";
import type { ElTable } from "element-plus";

接下来,我们就创建一个存储表格实例对象的响应式ref,代码如下:

ts 复制代码
const tableRef = ref<InstanceType<typeof ElTable>>();

接下来就基于这个表格实例调用clearFilter方法,这就是2个按钮的事件逻辑,同样的我们也定义了formatter以及filter-method的方法,代码如下所示:

ts 复制代码
const resetDateFilter = () => tableRef.value!.clearFilter(["date"]);
const clearFilter = () => tableRef.value!.clearFilter();
const formatter = (row: User) => row.address;
const filterTag = (value: string, row: User) => row.tag === value;
const filterHandler = (value: string, row: User, column: TableColumnCtx<User>) => {
  const property = column["property"] as keyof User;
  return row[property] === value;
};

note: 这里的!是ts的非空断言操作符。

然后就是列数据和表体数据的定义了,如下所示:

ts 复制代码
const column = [
  {
    type: "index",
    width: 50,
  },
  {
    prop: "date",
    label: "Date",
    sortable: true,
    width: 180,
    "column-key": "date",
    filters: [
      { text: "2016-05-01", value: "2016-05-01" },
      { text: "2016-05-02", value: "2016-05-02" },
      { text: "2016-05-03", value: "2016-05-03" },
      { text: "2016-05-04", value: "2016-05-04" },
    ],
    "filter-method": filterHandler,
  },
  {
    prop: "name",
    label: "Name",
    width: 120,
  },
  {
    prop: "address",
    label: "Address",
    formatter,
  },
  {
    prop: "tag",
    label: "Tag",
    filters: [
      { text: "Home", value: "Home" },
      { text: "Office", value: "Office" },
    ],
    "filter-method": filterTag,
    "filter-placement": "bottom-end",
    slotName: "default",
  },
];

const tableData: User[] = [
   // 数据忽略
]

以上代码展示效果如下所示:

示例12--固定列

可以为列数据配置一个fixed字段,该字段是一个布尔值true或者false或者字符串值'left'或者'right',默认值是false,即可设置一个固定列。代码如下:

html 复制代码
 <element-table border :column="column" :data="tableData" />
ts 复制代码
const handleClick = () => {
  console.log("click");
};
const column = [
  {
    prop: "date",
    fixed: true,
    width: 150,
    label: "Date",
  },
  {
    prop: "name",
    label: "Name",
    width: 120,
  },
  {
    prop: "state",
    label: "State",
    width: 120,
  },
  {
    prop: "city",
    label: "City",
    width: 120,
  },
  {
    prop: "address",
    label: "Address",
    width: 360,
  },
  {
    prop: "zip",
    label: "Zip",
    width: 180,
  },
  {
    label: "Operations",
    width: 200,
    fixed: "right",
    render() {
      return (
        <>
          <el-button type="text" size="small" onClick={handleClick}>
            Detail
          </el-button>
          <el-button type="text" size="small">
            Edit
          </el-button>
        </>
      );
    },
  },
];
const tableData = [
  // ...
];

以上代码展示效果如下图所示:

示例13--固定表头

可以为表格设置一个具体的高度,即height属性,这样当视图高度小于设置的高度,则会固定住表头,只允许滚动表体数据。代码如下所示:

html 复制代码
<element-table border :column="column" :data="tableData" :height="250" />
ts 复制代码
// 略

以上代码展示效果如下图所示:

示例14--流体高度

当数据量动态变化时,可以为表格设置一个最大高度。

通过设置 max-height 属性为 el-table 指定最大高度。 此时若表格所需的高度大于最大高度,则会显示一个滚动条。

来看示例代码如下:

html 复制代码
  <element-table border :column="column" :data="tableData" max-height="250">
    <template #default="scope">
      <el-button text type="primary" size="small" @click.prevent="deleteRow(scope.$index)">Remove</el-button>
    </template>
  </element-table>
  <el-button class="mt-4" style="width: 100%" @click="onAddItem">Add Item</el-button>

ts代码如下所示:

ts 复制代码
import { ref } from "vue";

const now = new Date();

const column = [
  {
    prop: "date",
    fixed: true,
    width: 150,
    label: "Date",
  },
  {
    prop: "name",
    label: "Name",
    width: 120,
  },
  {
    prop: "state",
    label: "State",
    width: 120,
  },
  {
    prop: "city",
    label: "City",
    width: 120,
  },
  {
    prop: "address",
    label: "Address",
    width: 360,
  },
  {
    prop: "zip",
    label: "Zip",
    width: 180,
  },
  {
    label: "Operations",
    fixed: "right",
    width: 120,
    slotName: "default",
  },
];

const tableData = ref([
   // ...
]);

const deleteRow = (index: number) => {
  tableData.value.splice(index, 1);
};

const onAddItem = () => {
  now.setDate(now.getDate() + 1);
  tableData.value.push({
    date: formatDate(now, "yyyy-MM-dd")!,
    name: "Tom",
    state: "California",
    city: "Los Angeles",
    address: "No. 189, Grove St, Los Angeles",
    zip: "CA 90036",
  });
};

这里可以额外介绍一下formatDate方法的实现,当然我们也可以使用day.js依赖。这个方法有多种实现方式,这里采用的是正则表达式匹配法,将字符串按照年月日来匹配,每次匹配到一个,用一个括号包裹,这样就可以当成一个分组(捕获组),然后通过读取$1实例属性就可以获取到每一个匹配到的日期了,然后我们为每一个日期定义一个字符串属性,值分别利用date日期实例对象的相关方法,例如年就是date.getFullYear()。基于以上的分析,我们就能实现如下这个工具函数:

ts 复制代码
// 第一个参数为日期对象或者日期字符串,第二个参数则是格式化的参数
const formatDate = (date: string | Date, fmt: string = "yyyy-MM-dd hh:mm:ss") => {
  if (typeof date == "string") {
    return date;
  }
  if (!date || date === null) return null;
  const o = {
    "M+": date.getMonth() + 1, // 月份
    "d+": date.getDate(), // 日
    "h+": date.getHours(), // 小时
    "m+": date.getMinutes(), // 分
    "s+": date.getSeconds(), // 秒
    "q+": Math.floor((date.getMonth() + 3) / 3), // 季度
    S: date.getMilliseconds(), // 毫秒
  };
  if (/(y+)/.test(fmt))
    fmt = fmt.replace(RegExp.$1, `${date.getFullYear()}`.slice(4 - RegExp.$1.length));
  for (const k in o) {
    if (new RegExp(`(${k})`).test(fmt)) {
      const item = o[k as keyof typeof o]
      fmt = fmt.replace(
        RegExp.$1,
        RegExp.$1.length === 1 ? String(item) : `00${item}`.slice(`${item}`.length)
      );
    }

  }
  return fmt;
}

以上代码展示效果如下图所示:

本篇文章就到这里为止,下一章我们将介绍最后的几个示例用法。

相关推荐
耶啵奶膘36 分钟前
uniapp-是否删除
linux·前端·uni-app
王哈哈^_^2 小时前
【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!
前端·人工智能·深度学习·yolo·目标检测·计算机视觉·pyqt
cs_dn_Jie3 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic3 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿4 小时前
webWorker基本用法
前端·javascript·vue.js
cy玩具4 小时前
点击评论详情,跳到评论页面,携带对象参数写法:
前端
qq_390161775 小时前
防抖函数--应用场景及示例
前端·javascript
John.liu_Test5 小时前
js下载excel示例demo
前端·javascript·excel
Yaml45 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事5 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro