仿avuejs对el-table封装(vue3+element plus)

1.先看效果

使用

    <self-table
          :option="indexOption"
          :data="list"
          @selection-change="handleSelectionChange"
        >
        </self-table>

 option配置

export const indexOption = {
  column: [
    { label: '出库单号', prop: 'deliveryOrderNo' },
    { label: '相关单号', prop: 'relevantDocNo' },
    { label: '计划日期', prop: 'plannedDate', type: 'date', timeformat: '{y}-{m}-{d}' },
    { label: '计划类型', prop: 'planType', type: 'dic', dic: 'sys_plan_type' },
    { label: '货主名称', prop: 'companyName' },
    { label: '货品类型数量', prop: 'itemTypeQuantity' },
    { label: '创建人', prop: 'creatorName' },
    { label: '创建日期', prop: 'createTime', type: 'date', timeformat: '{y}-{m}-{d}' },
    { label: '状态', prop: 'state', type: 'status', dic: 'sys_instore_status' },
  ],
  optionWidth: 300,
  selection: true,
  defaluetOp: true,
};

 这样就可以直接配置出个table了,如果你想要这样的效果就往下看

2.实现

定义selfTable组件(配置了unplugin-auto-import就不用到处import vue的东西了)

<template>
  <el-row >
    <el-table
      :height="option.height"
      :data="data"
      :default-expand-all="option.defaultExpandAll"
 :tree-props="{ children: option.childrenProp, hasChildren: option.hasChildrenProp }"
      @selection-change="handleSelectionChange"
      @sort-change="handleSortChange"
    >
      <el-table-column v-if="option.selection" type="selection" width="55"> </el-table-column>

      <el-table-column v-if="option.expand" style="padding: 0 40px" type="expand">
        <template #default="scope">
          <el-table class="inner-table" :data="scope.row[option.inData]">
            <el-table-column
              :width="itm.width"
              v-for="itm in option.inColumn"
              :align="itm.align"

              :label="itm.label"
              :prop="itm.prop"
              :fixed="itm.fixed"
            >
              <template #default="sp">
                <template v-if="itm.type != 'slot'">
                  <span v-if="itm.type == 'dic'">{{ selectDictLabel(dictData[itm.dic], sp.row[itm.prop]) }}</span>
                  <span v-else-if="itm.type == 'date'">
                    {{ itm.type == 'date' ? parseTime(sp.row[itm.prop], itm.timeformat) : '' }}</span
                  >
                    <span v-else>  
					   <span v-if="itm.prefix">{{ itm.prefix }}</span>
                       <span> {{ sp.row[itm.prop] }}</span>
                       <span v-if="itm.suffix">{{ itm.suffix }}</span>
					</span>
                </template>
                <template v-else>
                  <slot :name="'in-' + itm.prop" :row="sp.row" :out="scope.row" :$index="sp.$index"></slot>
                </template>
              </template>
              <template v-if="itm.tip" #header="sp2">
                <span>{{ itm.label }}</span>
                <el-tooltip class="item" :content="itm.tipcontent" effect="dark" placement="top">
                  <el-icon color="#409eff"><question-filled /></el-icon>
                </el-tooltip>
              </template>
            </el-table-column>
            <el-table-column v-if="option.inOption" label="操作" align="center" class-name="small-padding fixed-width">
              <template #default="sp">
                <slot name="in-option" :outIndex="scope.$index" :row="sp.row" :$index="sp.$index"></slot>
              </template>
            </el-table-column>
          </el-table>
        </template>
      </el-table-column>
      <el-table-column
        v-for="item in option.column"
        :width="item.width"
        :align="item.align"
        :label="item.label"
        :prop="item.prop"
        :fixed="item.fixed"
      >
        <template #default="scope">
          <template v-if="item.type == 'dic'">
            <span>{{ selectDictLabel(dictData[item.dic], scope.row[item.prop]) }}</span>
          </template>
          <template v-else-if="item.type == 'date'">
            <span>{{ parseTime(scope.row[item.prop], item.timeformat) }}</span>
          </template>
          <template v-else-if="item.type == 'slot'">
            <slot :name="item.prop" :row="scope.row" :$index="scope.$index"></slot>
          </template>
          <template v-else-if="item.type == 'status'">
            <p class="status-dot" :style="{ color: getDictProp(dictData[item.dic], scope.row[item.prop], 'color') }">
              <span :style="{ background: getDictProp(dictData[item.dic], scope.row[item.prop], 'color') }"></span>
              {{ selectDictLabel(dictData[item.dic], scope.row[item.prop]) }}
            </p>
          </template>
          <template v-else>
            <span v-if="item.prefix">{{ item.prefix }}</span>
            <span>{{ scope.row[item.prop] }}</span>
            <span v-if="item.suffix">{{ item.suffix }}</span>
          </template>
        </template>
        <template v-if="item.tip" #header="sp">
          <span>{{ item.label }}</span>
          <el-tooltip class="item" :content="item.tipcontent" effect="dark" placement="top">
            <el-icon color="#409eff"><question-filled /></el-icon>
          </el-tooltip>
        </template>
      </el-table-column>

      <el-table-column
        :width="option.optionWidth"
        v-if="option.defaluetOp"
        label="操作"
        align="center"
        class-name="small-padding fixed-width"
      >
        <template #default="scope">
          <slot name="defaluetOp" :row="scope.row" :$index="scope.$index"></slot>
        </template>
      </el-table-column>
      <slot></slot>
    </el-table>
  </el-row>
</template>

<script setup>
import * as dictData from '@/utils/dictData';
const props = defineProps({
  option: {
    selection: {
      type: Boolean,
      default: false,
    },
    expand: {
      type: Boolean,
      default: false,
    },
    defaluetOp: {
      type: Boolean,
      default: false,
    },
    defaultExpandAll: {
      type: Boolean,
      default: false,
    },
  childrenProp: {
      type: String,
      default: 'children',
    },
    hasChildrenProp: {
      type: String,
      default: 'hasChildren',
    },
    inData: {
      type: String,
      default: 'list',
    },
    inOption: {
      type: Boolean,
      default: false,
    },
    optionWidth: {
      type: String,
      default: '200',
    },
    column: [],
  },
  data: {
    type: Array,
    default: [],
  },
});

function handleSelectionChange(selection) {
  emit('selectionChange', selection);
}
function refresh() {
  refreshTable.value = false;
  nextTick(() => {
    refreshTable.value = true;
  });
}
const emit = defineEmits(['selectionChange']);
defineExpose({
  refresh,
});
</script>
<style scoped></style>

 用到的字典转换和时间转换方法

// 回显数据字典
export function selectDictLabel(dicts, value) {
  if (value === undefined) {
    return '';
  }
  if (dicts === undefined) {
    return '';
  }
  const actions = [];
  Object.keys(dicts).some((key) => {
    if (dicts[key].value === `${value}`) {
      actions.push(dicts[key].label);
      return true;
    }
  });
  if (actions.length === 0) {
    actions.push(value);
  }
  return actions.join('');
}
//返回字典其他栏位
export function getDictProp(dicts, value, prop) {
  if (value === undefined) {
    return '';
  }
  if (dicts === undefined) {
    return '';
  }
  let res = '';
  Object.keys(dicts).some((key) => {
    if (dicts[key].value === `${value}`) {
      res = dicts[key][prop];
      return true;
    }
  });
  return res;
}
// 日期格式化  
export function parseTime(time, pattern) {
  if (arguments.length === 0 || !time) {
    return null;
  }
  const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}';
  let date;
  if (typeof time === 'object') {
    date = time;
  } else {
    if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
      time = parseInt(time);
    } else if (typeof time === 'string') {
      time = time
        .replace(new RegExp(/-/gm), '/')
        .replace('T', ' ')
        .replace(new RegExp(/\.[\d]{3}/gm), '');
    }
    if (typeof time === 'number' && time.toString().length === 10) {
      time *= 1000;
    }
    date = new Date(time);
  }
  const formatObj = {
    y: date.getFullYear(),
    m: date.getMonth() + 1,
    d: date.getDate(),
    h: date.getHours(),
    i: date.getMinutes(),
    s: date.getSeconds(),
    a: date.getDay(),
  };
  const timeStr = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
    let value = formatObj[key];
    // Note: getDay() returns 0 on Sunday
    if (key === 'a') {
      return ['日', '一', '二', '三', '四', '五', '六'][value];
    }
    if (result.length > 0 && value < 10) {
      value = `0${value}`;
    }
    return value || 0;
  });
  return timeStr;
}

然后就是注册全局组件,全局的方法 ,在main.js中

import { selectDictLabel, getDictProp } from '@/utils/dict';
import { parseTime } from '@/utils/dateUtil';
import SelfTable from '@/components/selfPack/selfTable';
app.config.globalProperties.parseTime = parseTime;
app.component('SelfTable', SelfTable);

至此就可以在项目中直接使用了

3.复杂一点的使用

在实际项目中会有比如嵌套表格,按钮,或者树形结构的情况,设置了一些slot的位置

比如组件中

     <slot :name="'in-' + itm.prop" :row="sp.row" :out="scope.row" :$index="sp.$index"></slot>

后续在使用中就可以用in-栏位名指定到内部嵌套表单的slot, sp.out取到外层数据

   <self-table
          v-loading="loading"
          :option="detailOption"
          style="width: 100%"
          :data="params.inStockMaterialDTOList"
          @selection-change="handleSelectionChange"
        >
          <template #in-defaultStorageAreaName="sp">
            {{ sp.out.defaultStorageAreaName }}
          </template>
        </self-table>

下面是嵌套表单的范例 

export const detailOption = {
  selection: true,
  expand: true,
  defaultExpandAll: true,
  column: [
    { label: 'xxx', prop: 'materialName' },
    { label: 'xxx', prop: 'materialNo' },
    { label: 'xxx', prop: 'companyName', type: 'slot' },
    { label: 'xxx', prop: 'materialType', type: 'slot' },
    { label: 'xxx', prop: 'defaultStorageAreaName' },
    { label: 'xxx', prop: 'alertDays', suffix: '天' },
    { label: 'xxx', prop: 'color' },
    { label: 'xxx', prop: 'decorativePattern' },
    { label: 'xxx', prop: 'gramWeight' },
    { label: 'xxx', prop: 'unit' },
    { label: 'xxx', prop: 'measurement' },
    { label: 'xxx', prop: 'num', type: 'slot' },
  ],
  inData: 'inStockMaterialRollEntityList',
  inColumn: [
    { label: 'xxx', prop: 'serialNumber', width: '200' },
    { label: 'xxx', prop: 'companyName', type: 'slot' },
    { label: 'xxx', prop: 'materialType', type: 'slot' },
    { label: 'xxx', prop: 'createTime', type: 'date', timeformat: '{y}-{m}-{d}' },
    { label: 'xxx', prop: 'batchNumber' },
    { label: 'xxx', prop: 'materialStatus' },
    { label: 'xxx', prop: 'defaultStorageAreaName', type: 'slot' },
    { label: 'xxx', prop: 'storageQuantity' },
    { label: 'xxx', prop: 'unit' },
  ],
  defaluetOp: true,
  inOption: true,
};

4. 栏位说明

option说明

  selection: {        
      type: Boolean,
      default: false, //是否可选
    },
    expand: {
      type: Boolean,
      default: false,//是否能展开
    },
    defaluetOp: {
      type: Boolean,
      default: false,//是否有操作栏位
    },
    defaultExpandAll: {
      type: Boolean,
      default: false,//是否默认展开所有
    },
    inData: {
      type: String,
      default: 'list',//嵌套表单栏位名
    },
    inOption: {
      type: Boolean,//嵌套表单是否可选
      default: false,
    },
    childrenProp: {
      type: String,  //树形表单children栏位
      default: 'children',
    },
    hasChildrenProp: {
      type: String,//树形表单hasChildren栏位  参照element
      default: 'hasChildren',
    },
   
    optionWidth: {
      type: String,//操作栏宽度
      default: '200',
    },
      column: {
      type: Array,
      default: [],//列栏位数组
    },
    inColumn: {
      type: Array,
      default: [],//嵌套表单列栏位数组
    },

column/inColumn说明

  label:{
      type: String,//表单栏位名称
    },
   prop:{
      type: String,//表单栏位名
    },
   type:{
      type: String,//表单栏位类型 
                  // dic   配合 dic栏位指定数据字典
                   //  date   配合  timeFormat栏位格式化时间
                   //  slot          自定义插槽
    },
    prefix:{
      type: String,//表单栏位前缀
    },
    suffix:{
      type: String,//表单栏位后缀
    },
    tip:{
         type:Boolean,  //表单栏位列名称是否显示提示
         defalut:false   //配合tipcontent 栏位 显示具体内容
	}

5 .todo

数据字典暂时是本地的配置文件,后续会修改到远程接口的,额外的功能大家可以根据自己的项目要求再修改一下

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值