table表格 组件

 

<template>
  <div>
    <BaseTable
      :searchConfig="searchConfig"
      :operateBtnConfig="operateBtn"
      :tableData="tableData"
      :tableConfig="tableConfig"
      :pagination="pagination"
      :tableLoading="tableLoading"
      @search="search"
      @reset="reset"
      @selectionChange="selectionChange"
      @changePage="changePage"
      @keyupEnterNative="fetchList"
      @receiveSearch="receiveSearch"
      ref="BaseTable"
      idField="otherId"
    >
      <template v-slot:operate="{row}">
        <el-link type="primary" size="medium" :underline="false" @click="editRow(row)" style="margin-right: 10px;">编辑</el-link>
        <el-link type="danger" size="medium" :underline="false" @click="deleteData(row)">删除</el-link>
      </template>
    </BaseTable>

    <editForm
      v-if="editVisible"
      :visible="editVisible"
      :editId="editId"
      :companyList ="companyList"
      :editType="editType"
      @close="(type) => {type === 'reload' && this.fetchList();this.reloadCreators();editVisible = false}"
    ></editForm>
  </div>
</template>
<script>
import { rules, searchConfig, tableConfig } from "./config";
import BaseTable from "@/components/BaseTable/index.vue"
import baseMixins from "@/components/BaseTable/baseMixins"
import editForm from "./components/editForm.vue"
import { getCodingTypeList, getCreators ,} from "@/api/system/encodingRules"
import{invoiceConfig ,invoiceConfigDelete} from "@/api/system/tickets"
import { deepClone } from '@/utils';
import { getBranchCompany } from "@/api/shippingWorkOrder/index";

export default {
  data() {
    return {
      rules: rules,
      searchConfig: {},
      tableConfig: tableConfig,
      tableData: [],
      editVisible: false,
      editType: 'add',
      editId: [],
      typeOptions: [],
      companyOptions: [],
      creatorsList: [],
      companyList:[],
    };
  },
  mixins: [ baseMixins ],
  components: { BaseTable, editForm },
  computed: {
    operateBtn: function() {
      return [
        {
          name: '新增',
          plain: false,
          icon: 'el-icon-plus',
          event: this.handleAdd
        },
        {
          name: '删除',
          type: 'danger',
          icon: 'el-icon-delete',
          event: this.deleteData
        },
      ]
    },
  },
  created() {
    this.fetchList()
    Promise.allSettled([this.getBranchCompany(), this.getCodingTypeList(), this.getCreators()]).then(() => {
      setTimeout(() => {
        this.init()
      }, 200)
    })
  },
  methods: {
    init() {
      this.searchConfig = searchConfig({companyOptions: this.companyOptions, typeOptions: this.typeOptions, creatorsList: this.creatorsList})
    },

    reloadCreators() {
      Promise.allSettled([this.getCreators()]).then(() => {
        this.init()
      })
    },

    // 录入人
    async getCreators() {
      let { code, data } = await getCreators();
      if (code == 0) {
        this.creatorsList = data.map(r => {return {value: r}});
      }
    },

    fetchList() {
      const { pageNo , pageSize } = this.pagination
      const {...rest } = this.searchForm
      const data = {
        pageNo,
        pageSize,
        ...rest
      }
      this.tableLoading = true
      invoiceConfig(data).then(res => {
        const { code, msg, data: { list, total } } = res

        if (code === 0) {
          list.forEach((r, i) => {
            r.monthLength = 2
            r.dayLength = 2
            r.otherId = new Date().getTime() + i
          })
          this.tableData = list;
          this.$set(this.pagination, 'total', total)
        } else {
          this.tableData = []
          msg && this.$message.warning(msg)
        }
      }).finally(() => {
        this.tableLoading = false
      })
    },

    getCodingTypeList() {
      getCodingTypeList().then(res => {
        this.typeOptions = res.data
      })
    },

    getBranchCompany() {
      getBranchCompany().then((res) => {
        this.companyList = res.data;
        this.companyOptions = deepClone(res.data.map(r => { return {...r, checked: false} }));
      });
    },

    // 新增
    handleAdd() {
      this.editVisible = false
      this.editType = 'add'
      this.editId = {}
      setTimeout(() => {this.editVisible = true}, 100)
    },

    // 编辑
    editRow(row) {
      this.editVisible = false
      this.editId = row
      this.editType = 'edit'
      setTimeout(() => {this.editVisible = true}, 100)
    },

    // 删除
    deleteData(row) {
      console.log(row);
      let ids = []
      if (row) {
        ids.push(row.openInvoiceConfigId);
      } else {
        if (!this.getSelectRows()) return;
       
        ids = this.selectRows.map(r => r.openInvoiceConfigId)
      }
      console.log(ids);
      this.$confirm(`确认删除该编码规则吗?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        invoiceConfigDelete(ids).then(res => {
          const { code, data, msg} = res
          if (code === 0 && data) {
            this.$message.success(msg || '删除成功')
            this.reloadCreators()
            this.fetchList()
          } else {
            this.$message.warning(msg || '删除失败')
          }
        })
      });
    },
  },
};
</script>
<style scoped lang="scss">
</style>
===============================================================================
引入解释:
config.js 配置
 
import { changeOptionsName } from '@/utils/utils'
import { rulesItem } from '@/utils/constants'

export const searchConfig = ({ companyOptions, typeOptions, creatorsList }) => {

  return {
    inline: true,
    form: [
      {
        type: 'select',
        name: 'deptId',
        placeholder: '公司',
        options: changeOptionsName(companyOptions, 'id', 'name') || [],
      },
     
      {
        type: 'input',
        name: 'taxNum',
        placeholder: '企业税号',
        options: creatorsList || [],
      },
    ]
  }
}

export const tableConfig = {
  highlightCurrentRow: true,
  fixedSelection: 'left',
  columns: [
    {
      prop: 'deptName',
      label: '公司名',
      minWidth: 100
    },
    {
      prop: 'taxNum',
      label: '企业税号',
    },
    {
      prop: 'appKey',
      label: 'APP KEY',
    },
    {
      prop: 'appSecret',
      label: 'APP SECRET',
    },
    {
      prop: 'token',
      label: 'TOKEN',
      minWidth: 80
    },
    {
      prop: 'extensionNum',
      label: '分机号',
      minWidth: 80
    },
    {
      prop: 'address',
      label: '地址',
      minWidth: 80
    },
    {
      prop: 'phone',
      label: '电话',
      minWidth: 80
    },
    {
      prop: 'payee',
      label: '收款人',
      minWidth: 110
    },
    {
      prop: 'checker',
      label: '复核人',
      minWidth: 110
    },
    {
      prop: 'drawer',
      label: '开票人',
      minWidth: 110
    },
    {
      prop: 'creator',
      label: '录入人',
    },
    {
      prop: 'createTime',
      label: '录入时间',
      minWidth: 150
    },
    {
      prop: 'updater',
      label: '修改人',
    },
    {
      prop: 'updateTime',
      label: '修改时间',
      minWidth: 150
    },
    {
      prop: 'operate',
      label: '操作',
      fixed: 'right',
      width: 96,
      align: 'center',
      headerAlign: 'center',
      columShow: 'true',
      slot: true
    }
  ]
}

export const editRules = {
  taxNum: rulesItem,
  deptId:rulesItem,
  appKey:rulesItem,
  appSecret:rulesItem,
  token:rulesItem,
  extensionNum:rulesItem,
}
 
searchConfig 中是搜索菜单中的搜索条件
tableConfig :  是配置tableb表格
tableConfig  ={
highlightCurrentRow :true  行高亮
fixedSelection: 'left',  左对齐
columns: [{}]  列参数
}
editRules: 必填判断0
======================================================================== 
编辑弹框:
<template>
  <BaseDialog :title="typeName[editType]" :visible.sync="visible" width="500px" @close="handleClose" @submit="submit" :loading="loading">
    <el-form :model="form" :rules="editRules" ref="form" label-position="right">
      <el-form-item label="公司名" prop="deptId">
        <el-select v-model="form.deptId" filterable placeholder>
          <el-option v-for="(item, index) in companyList" :key="index" :label="item.name" :value="item.id"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="企业税号" prop="taxNum">
        <el-input v-model="form.taxNum" style="width: 194px;"></el-input>
      </el-form-item>
      <el-form-item label="APP_KEY" prop="appKey">
        <el-input v-model="form.appKey" style="width: 194px;"></el-input>
      </el-form-item>
      <el-form-item label="APP_SECRET" prop="appSecret">
        <el-input v-model="form.appSecret" style="width: 194px;"></el-input>
      </el-form-item>
      <el-form-item label="TOKEN" prop="token">
        <el-input v-model="form.token" style="width: 194px;"></el-input>
      </el-form-item>
      <el-form-item label="分机号" prop="extensionNum">
        <el-input v-model="form.extensionNum" style="width: 194px;"></el-input>
      </el-form-item>
      <el-form-item label="地址" prop="address">
        <el-input v-model="form.address" style="width: 194px;" type="textarea" show-word-limit  maxlength="500" rows="5"></el-input>
      </el-form-item>
      <el-form-item label="电话" prop="phone">
        <el-input v-model="form.phone" style="width: 194px;"></el-input>
      </el-form-item>
      <el-form-item label="收款人" prop="payee">
        <el-select v-model="form.payee" filterable placeholder>
          <el-option v-for="(item, index) in listPayee" :key="index" :label="item.username" :value="item.username" ></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="复核人" prop="checker">
         <el-select v-model="form.checker" filterable placeholder>
          <el-option v-for="(item, index) in listChecker" :key="index" :label="item.username" :value="item.username" ></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="开票人" prop="drawer">
        <el-select v-model="form.drawer" filterable placeholder>
          <el-option v-for="(item, index) in listDrawer" :key="index" :label="item.username" :value="item.username" ></el-option>
        </el-select>
      </el-form-item>
    </el-form>
  </BaseDialog>
</template>

<script>
import { editRules } from "../config";    //调用的必填项参数
import { invoiceConfigCreate,invoiceConfigUpdate,invoiceConfigGet } from "@/api/system/tickets";   //接口数据
import {getAssignUser} from "@/api/shippingWorkOrder/index"   //接口数据

export default {
  props: {
    editType: { // add edit copyAdd
      type: String,
      default: 'add'
    },
    visible: false,
    editId: {
      type: Object,
      default: () => {}
    },
    companyList:{
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      dialogVisible: this.visible,
      typeName: {
        add: '新增',
        edit: '编辑',
      },
      typeOptions:[],
      listDrawer:[],
      listChecker:[],
      listPayee:[],
      form: {
     
      },
      editIdInfo:this.editId,
      editRules: editRules,
      loading: false,
    };
  },
  created() {
    this.getAssignUserList();
    if(this.editType == 'edit'){
      this.invoiceConfigGetList()
    }
  },
  mounted() {
  },
  methods: {
    handleClose(type) {
      this.$emit('close', type)
    },
    // 提交
    submit() {
      this.$refs.form.validate((valid) => {
        if (!valid) return false;
        const data = {
          ...this.form,
        }
        this.loading = true
        if (this.editType != 'edit') {
          this.handleRes(invoiceConfigCreate(data))
        } else {
          this.handleRes(invoiceConfigUpdate(data))
        }
      });
    },
    handleRes(_Promise) {
      _Promise.then(res => {
        const { code, msg } = res
        this.$message.success(msg || this.editType == 'add' ? '新增成功' : '编辑成功')
        this.handleClose('reload')
      }).finally(() => this.loading = false)
    },
    getAssignUserList(){
      getAssignUser().then((res)=>{
        this.listDrawer = res.data;
        this.listChecker = res.data;
        this.listPayee = res.data;
      })
    },
    invoiceConfigGetList(){
      console.log(this.editIdInfo,"222");
      let params={
        id:this.editIdInfo.openInvoiceConfigId
      }
      invoiceConfigGet(params).then((res)=>{
        this.form = {
          ...res.data
        }
      })
    }
  }
};
</script>

<style lang="scss" scoped>
.el-form-item{
  ::v-deep .el-form-item__label{
    width: 100px !important;
  }
  ::v-deep .el-form-item__content{
    .el-select{
       width: 300px;
    }
    .el-input{
      width: 300px !important;
    }
   .el-textarea{
    width: 300px !important;
   }
  }
}
.title {
  display: inline-block;
  padding: 0px 5px;
  color: var(--current-color);
  font-size: 16px;
  font-weight: 400;
  margin-bottom: 7px;
  margin-left: -5px;
}

::v-deep .el-dialog__footer {
  text-align: center;
}

.flip-list-move {
  transition: transform 0.5s;
}

.no-move {
  transition: transform 0s;
}
</style>
=======================================================================
引入的base tabe 文件夹
 BaseTable 下的index.vue
 
<template>
  <div class="container baseTable-container">
    <!-- 搜索栏 -->
    <div class="search search-box">
      <template v-if="searchConfig.form && searchConfig.form.length">
        <el-form ref="searchRef" :model="searchForm" :inline="searchConfig.inline" :label-width="searchConfig.labelWidth" @submit.native.prevent>
          <el-form-item v-for="(item, index) in searchConfig.form" :label="item.label" :key="index" :prop="item.name">
            <el-input v-if="item.type === 'input'" v-model="searchForm[item.name]" :style="item.style" :placeholder="item.placeholder" :clearable="item.clearable === false ? false : true"
              :disabled="item.disabled" :readonly="item.readonly" @keyup.enter.native="keyupEnterNative" @dblclick.native="v => commonInputDblclick(v, item)" :suffix-icon="item.suffixIcon" :prefix-icon="item.prefixIcon" ></el-input>

            <el-input v-if="item.type === 'textarea'" type="textarea" v-model="searchForm[item.name]" :style="item.style" :placeholder="item.placeholder"
              :clearable="item.clearable === false ? false : true" :disabled="item.disabled" :readonly="item.readonly" @keyup.enter.native="keyupEnterNative" @dblclick.native="v => commonInputDblclick(v, item)" :suffix-icon="item.suffixIcon" :prefix-icon="item.prefixIcon"></el-input>

            <el-input v-if="item.type === 'number'" type="number" v-model="searchForm[item.name]" :style="item.style" :placeholder="item.placeholder"
              :clearable="item.clearable === false ? false : true" :disabled="item.disabled" :readonly="item.readonly" @keyup.enter.native="keyupEnterNative" @dblclick.native="v => commonInputDblclick(v, item)" :suffix-icon="item.suffixIcon" :prefix-icon="item.prefixIcon"></el-input>

            <el-date-picker
              v-if="item.type === 'date'"
              :type="item.datePickerType || 'daterange'"
              :clearable="item.clearable === false ? false : true"
              :disabled="item.disabled"
              :style="item.style"
              v-model="searchForm[item.name]"
              :range-separator="item.rangeSeparator || '至'"
              :start-placeholder="item.startPlaceholder || '开始时间'"
              :end-placeholder="item.endPlaceholder || '结束时间'"
              :value-format="item.valueFormat || (['daterange', 'data'].includes(item.datePickerType) ? 'yyyy-MM-dd' : ['datetimerange'].includes(item.datePickerType) ? 'yyyy-MM-dd HH:hh:mm' : 'yyyy-MM-dd')"
              :default-time="item.defaultTime || (item.datePickerType === 'datetimerange' ? ['00:00:00', '23:59:59'] : null)"
              :readonly="item.readonly"
              @keyup.enter.native="keyupEnterNative"
            ></el-date-picker>

            <el-time-select
              v-if="item.type === 'timeSelect'"
              v-model="searchForm[item.name]"
              :disabled="item.disabled"
              :clearable="item.clearable === false ? false : true"
              :style="item.style"
              :picker-options="item.pickerOptions"
              :placeholder="item.placeholder"
              :readonly="item.readonly"
              @keyup.enter.native="keyupEnterNative">
            </el-time-select>

            <el-time-picker
              v-if="item.type === 'timePicker'"
              :is-range="item.isRange"
              :disabled="item.disabled"
              :clearable="item.clearable === false ? false : true"
              v-model="searchForm[item.name]"
              :style="item.style"
              :picker-options="item.pickerOptions"
              :range-separator="item.rangeSeparator || '至'"
              :start-placeholder="item.startPlaceholder || '开始时间'"
              :end-placeholder="item.endPlaceholder || '结束时间'"
              :placeholder="item.placeholder"
              :readonly="item.readonly"
              @keyup.enter.native="keyupEnterNative">
            </el-time-picker>

            <el-select v-if="item.type === 'select'" v-model="searchForm[item.name]" :style="item.style" :placeholder="item.placeholder" :clearable="item.clearable === false ? false : true" filterable
              :disabled="item.disabled" :readonly="item.readonly" @keyup.enter.native="keyupEnterNative" @change="v => {commonSelectChange(v, item)}">
              <template v-if="typeof(item.renderOption) === 'function'">
                <el-option v-for="(selectItem, selectIndex) in item.options" :key="selectIndex" :label="item.renderOption(selectItem)" :value="selectItem.value">
                  {{ item.renderOption(selectItem) }}
                </el-option>
              </template>
              <template v-else>
                <el-option v-for="(selectItem, selectIndex) in item.options" :key="selectIndex" :label="selectItem.label" :value="selectItem.value"></el-option>
              </template>
            </el-select>

            <template v-if="item.type === 'selectGroup'">
              <el-col :span="11">
                <el-select v-model="searchForm[item.list[0].name]" :style="item.list[0].style" :clearable="item.list[0].clearable === false ? false : true" :placeholder="item.list[0].placeholder || ''">
                  <el-option v-for="(selectItem, selectIndex) in item.list[0].options" :key="selectIndex" :label="selectItem.label" :value="selectItem.value"></el-option>
                </el-select>
              </el-col>
              <el-col style="text-align: center;" :span="2">—</el-col>
              <el-col :span="11">
                <el-select v-model="searchForm[item.list[1].name]" :style="item.list[1].style" :clearable="item.list[1].clearable === false ? false : true" :placeholder="item.list[1].placeholder || ''">
                  <el-option v-for="(selectItem, selectIndex) in item.list[1].options" :key="selectIndex" :label="selectItem.label" :value="selectItem.value"></el-option>
                </el-select>
              </el-col>
            </template>

            <template v-if="item.type === 'inputGroup'">
              <el-col :span="11">
                <el-input v-model="searchForm[item.list[0].name]" :type="item.list[0].type" :style="item.list[0].style" :placeholder="item.list[0].placeholder || ''" :clearable="item.list[0].clearable === false ? false : true"
                  :disabled="item.list[0].disabled" :readonly="item.list[0].readonly" @keyup.enter.native="keyupEnterNative" ></el-input>
              </el-col>
              <el-col style="text-align: center;" :span="2">—</el-col>
              <el-col :span="11">
                <el-input v-model="searchForm[item.list[1].name]" :type="item.list[0].type" :style="item.list[1].style" :placeholder="item.list[1].placeholder || ''" :clearable="item.list[1].clearable === false ? false : true"
                  :disabled="item.list[1].disabled" :readonly="item.list[1].readonly" @keyup.enter.native="keyupEnterNative" ></el-input>
              </el-col>
            </template>

            <v-selectpage v-if="item.type === 'selectPage'" :style="item.style" :ref="`selectPage_${item.name}`" v-model="searchForm[item.name]" :data="item.options" :key-field="item.keyField"
              :result-format="item.resultFormat || resultFormat"
              :show-field="item.showField"
              :search-field="item.searchField || 'searchField'"
              :params="item.searchParams"
              :multiple="item.multiple"
              :clearable="item.clearable === false ? false : true"
              :disabled="item.disabled"
              :tb-columns="item.tbColumns || defaultSelectPageColumns" :placeholder="item.placeholder || ' '">
            </v-selectpage>

            <base-selectPage v-if="item.type === 'baseSelectPage'" :style="item.style" v-model="searchForm[item.name]" :data="item.options" :keyField="item.keyField"
              :showField="item.showField"
              :searchField="item.searchField || 'keyword'"
              :concatField="item.concatField"
              :defaultRow="item.defaultRow || {}"
              :searchParams="item.searchParams"
              :multiple="item.multiple"
              :tb-columns="item.tbColumns || defaultSelectPageColumns"
              :disabled="item.disabled"
              :clearable="item.clearable === false ? false : true"
              :placeholder="item.placeholder || ''"
              @change="v => {commonSelectChange(v, item)}">
            </base-selectPage>

            <el-cascader v-if="item.type === 'cascader'" v-model="searchForm[item.name]" :clearable="item.clearable === false ? false : true" :options="item.options" :style="item.style" :placeholder="item.placeholder" :disabled="item.disabled" :readonly="item.readonly"></el-cascader>

            <treeselect v-if="item.type === 'treeselect'" v-model="searchForm[item.name]" :options="item.options" :normalizer="typeof(item.normalizer) === 'function' ? item.normalizer : normalizer" :show-count="item.showCount === false ? false : true"
              :placeholder="item.placeholder"
              :disabled="item.disabled"
              :multiple="item.multiple"
              :clearable="item.clearable === false ? false : true"/>

            <template v-if="item.type === 'slot'">
              <slot :name="item.name" :form="searchForm"></slot>
            </template>
          </el-form-item>

          <!-- 三种布局类型 last / bottom / more -->
          <el-form-item v-if="!searchConfig.layoutType || searchConfig.layoutType === 'last'">
            <el-button type="primary" @click="search">查 询</el-button>
            <el-button @click="reset" v-if="searchConfig.showResetBtn === false ? false : true">重 置</el-button>
          </el-form-item>
        </el-form>
        <div v-if="searchConfig.layoutType === 'bottom'" class="searchBtn">
          <el-button type="primary" @click="search">查 询</el-button>
          <el-button @click="reset" v-if="searchConfig.showResetBtn === false ? false : true">重 置</el-button>
        </div>
      </template>
      <template v-else>
        <p class="search-loading"><i class="el-icon-loading"></i> 加载中...</p>
      </template>
    </div>

    <div class="line"></div>

    <!-- 操作栏 -->
    <div class="operateBtn btn-box" v-if="operateBtnConfig.length">
      <div class="operateBtn-left">
        <!-- formcreate 循环按钮使用自定义组件 -->
        <BaseBtnList :list="operateBtnConfig"></BaseBtnList>
      </div>

      <div class="operateBtn-right">
        <slot name="operateBtnRight"></slot>
      </div>
    </div>

    <!-- Table栏 -->
    <div class="baseTable" v-if="tableConfig.showTable === false ? false : true">
      <el-table
        v-if="!tableConfig.isVirtualTable"
        ref="table"
        class="tableMaxHeightClass"
        v-loading="tableLoading"
        :data="tableData"
        :tooltip-effect="tableConfig.tooltipEffect || 'dark'"
        style="width: 100%"
        :border="tableConfig.border === false ? false : true"
        :highlight-current-row="tableConfig.highlightCurrentRow"
        :current-row-key="tableConfig.currentRowKey || 'id'"
        :row-key="tableConfig.rowKey"
        :max-height="tableMaxHeight"
        :row-class-name="toString.call(tableConfig.rowClassName) ===  '[object Undefined]' && idField ? defaultRowClassName : tableConfig.rowClassName"
        :cell-class-name="tableConfig.cellClassName"
        :header-row-class-name="tableConfig.headerRowClassName"
        :header-cell-class-name="tableConfig.headerCellClassName"
        :span-method="({row, column, rowIndex, columnIndex}) => toString.call(tableConfig.spanMethod) ===  '[object Undefined]' ? () => {} : tableConfig.spanMethod({row, column, rowIndex, columnIndex, tableRef: $refs.table})"
        @select="select"
        @select-all="selectAll"
        @selection-change="selectionChange"
        @row-dblclick="rowDblclick"
        @row-click="rowClick"
        @cell-click="cellClick"
        @row-contextmenu="rowContextmenu"
        @header-dragend="headerDragend"
        @sort-change="sortChange">
        <el-table-column
          type="expand"
          v-if="tableConfig.showExpand"
          align="center"
          width="55">
          <template slot-scope="props">
            <slot name="expand" :props="props" :row="props.row"></slot>
          </template>
        </el-table-column>

        <el-table-column
          type="selection"
          v-if="tableConfig.showSelection === false ? false : true"
          :reserve-selection="tableConfig.reserveSelection === true ? true : false"
          :fixed="tableConfig.fixedSelection"
          :selectable="tableConfig.selectable"
          align="center"
          width="44">
        </el-table-column>

        <el-table-column
          type="index"
          v-if="tableConfig.showIndex === false ? false : true"
          align="center"
          label="序号"
          width="56">
        </el-table-column>

        <template v-for="(item, index) in tableConfig.columns">
          <el-table-column
            v-if="(item.columShow === 'false' || item.columShow === false) ? false : true"
            :key="index"
            :width="item.width"
            :min-width="item.minWidth || 120"
            :prop="item.prop"
            :label="item.label"
            :fixed="item.fixed"
            :sortable="item.sortable === false ? false : true"
            :render-header="item.renderHeader"
            :align="item.align || 'left'"
            :header-align="item.headerAlign || 'left'"
            :class-name="item.className"
            :sort-method="item.sortMethod"
            :show-overflow-tooltip="item.showOverflowTooltip === false ? false : true">
            <template slot-scope="scope" v-if="!item.formatter">
                <slot v-if="item.slot" :name="item.prop" :scope="scope" :index="scope.$index" :row="scope.row"></slot>
                <span v-else>{{ scope.row[item.prop] }}</span>
            </template>

            <!-- 合并单元格 传children -->
            <template v-if="item.children">
              <el-table-column
                v-for="(childItem, childIndex) in item.children"
                :key="childIndex"
                :label="childItem.label"
                :prop="childItem.prop"
                :width="childItem.width"
                :min-width="childItem.minWidth || 100"
                :align="childItem.align || 'left'"
                :header-align="childItem.headerAlign || 'left'"
                :class-name="childItem.className"
                show-overflow-tooltip
                :sortable="item.sortable === false ? false : true"
                :sort-method="item.sortMethod"
              >
                <template slot-scope="scope">
                    <slot v-if="childItem.slot" :name="childItem.prop" :scope="scope" :index="scope.$index" :row="scope.row"></slot>
                    <span v-else>{{ scope.row[childItem.prop] ? scope.row[childItem.prop] : childItem.empty }}</span>
                </template>
              </el-table-column>
            </template>
          </el-table-column>
        </template>
      </el-table>

      <!-- 虚拟表格 -->
      <u-table
        v-else
        ref="table"
        class="tableMaxHeightClass"
        v-loading="tableLoading"
        :data="tableData"
        :tooltip-effect="tableConfig.tooltipEffect || 'dark'"
        style="width: 100%"
        :border="tableConfig.border === false ? false : true"
        :highlight-current-row="tableConfig.highlightCurrentRow"
        :current-row-key="tableConfig.currentRowKey || 'id'"
        :max-height="tableConfig.tableMaxHeight || tableMaxHeight"
        :row-class-name="toString.call(tableConfig.rowClassName) ===  '[object Undefined]' && idField ? defaultRowClassName : tableConfig.rowClassName"
        :cell-class-name="tableConfig.cellClassName"
        :header-row-class-name="tableConfig.headerRowClassName"
        :header-cell-class-name="tableConfig.headerCellClassName"
        :span-method="({row, column, rowIndex, columnIndex}) => toString.call(tableConfig.spanMethod) ===  '[object Undefined]' ? () => {} : tableConfig.spanMethod({row, column, rowIndex, columnIndex, tableRef: $refs.table})"
        @select="select"
        @select-all="selectAll"
        @selection-change="selectionChange"
        @row-dblclick="rowDblclick"
        @row-click="rowClick"
        @cell-click="cellClick"
        @row-contextmenu="rowContextmenu"
        @header-dragend="headerDragend"
        @sort-change="sortChange"
        use-virtual
        :row-height="tableConfig.rowHeight || uTableRowHeight"
        >
        <u-table-column
          type="expand"
          v-if="tableConfig.showExpand"
          align="center"
          width="56">
          <template slot-scope="props">
            <slot name="expand" :props="props" :row="props.row"></slot>
          </template>
        </u-table-column>

        <u-table-column
          type="selection"
          v-if="tableConfig.showSelection === false ? false : true"
          :fixed="tableConfig.fixedSelection"
          :selectable="tableConfig.selectable"
          align="center"
          header-align="center"
          width="40">
        </u-table-column>

        <!-- 有些需要在序号前加东西 -->
        <u-table-column
          v-if="tableConfig.showOther"
          align="center"
          width="56">
          <template slot-scope="scope">
            <slot name="showOther" :scope="scope" :index="scope.$index" :row="scope.row"></slot>
          </template>
        </u-table-column>

        <u-table-column
          type="index"
          v-if="tableConfig.showIndex === false ? false : true"
          align="center"
          label="序号"
          class-name="uIndex"
          width="56">
        </u-table-column>

        <template v-for="(item, index) in tableConfig.columns">
          <u-table-column
            v-if="(item.columShow === 'false' || item.columShow === false) ? false : true"
            :key="index"
            :width="item.width"
            :min-width="item.minWidth || 120"
            :prop="item.prop"
            :label="item.label"
            :fixed="item.fixed"
            :sortable="item.sortable === false ? false : true"
            :render-header="item.renderHeader"
            :align="item.align || 'left'"
            :header-align="item.headerAlign || 'left'"
            :class-name="item.className"
            :sort-method="item.sortMethod"
            :show-overflow-tooltip="item.showOverflowTooltip === false ? false : true">
            <template slot-scope="scope" v-if="!item.formatter">
                <slot v-if="item.slot" :name="item.prop" :scope="scope" :index="scope.$index" :row="scope.row"></slot>
                <span v-else>{{ scope.row[item.prop] }}</span>
            </template>

            <!-- 合并单元格 传children -->
            <template v-if="item.children">
              <u-table-column
                v-for="(childItem, childIndex) in item.children"
                :key="childIndex"
                :label="childItem.label"
                :prop="childItem.prop"
                :width="childItem.width"
                :min-width="childItem.minWidth || 100"
                :align="childItem.align || 'left'"
                :header-align="childItem.headerAlign || 'left'"
                :class-name="childItem.className"
                show-overflow-tooltip
                :sortable="item.sortable === false ? false : true"
                :sort-method="item.sortMethod"
              >
                <template slot-scope="scope">
                    <slot v-if="childItem.slot" :name="childItem.prop" :scope="scope" :index="scope.$index" :row="scope.row"></slot>
                    <span v-else>{{ scope.row[childItem.prop] ? scope.row[childItem.prop] : childItem.empty }}</span>
                </template>
              </u-table-column>
            </template>
          </u-table-column>
        </template>
      </u-table>

      <div class="pagination" v-if="tableConfig.showPagination === false ? false : true">
        <el-pagination
          background
          @size-change="val => handlePageChange(val, 'pageSize')"
          @current-change="val => handlePageChange(val, 'pageNo')"
          :current-page="basePagination.pageNo"
          :page-sizes="basePagination.pageSizes"
          :page-size="basePagination.pageSize"
          :pager-count="pagerCount"
          :layout="pagerCount === 7 ? 'total, sizes, prev, pager, next, jumper' : 'total, pager'"
          :total="basePagination.total">
        </el-pagination>
      </div>
    </div>
  </div>
</template>

<script>
import tableConfigMixins from "@/views/mixin/tableConfigMixins"
import { defaultSelectPageColumns } from "@/utils/constants";
import { resetSelectPage } from "@/utils/utils";
import { deepClone } from '@/utils';

import treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";

import BaseBtnList from '@/components/BaseBtnList/index.vue'

export default {
  name: 'BaseTable',
  props: {
    // 搜索栏配置
    searchConfig: {
      type: Object,
      default: () => {
        return {
          inline: true,
          layoutType: 'last',
          form: [
            {
              // type: 'input',
              // name: 'name',
              // label: '活动名称',
              // placeholder: '活动名称'
            }
          ]
        }
      }
    },
    // 操作栏配置
    operateBtnConfig: {
      type: Array,
      default: () => {
        return []
      }
    },
    // table数据
    tableData: {
      type: Array,
      default: () => {
        return []
      }
    },
    // table栏配置
    tableConfig: {
      showTable: true,
      showSelection: true,
      isVirtualTable: false,  // 是否虚拟表格
      columns: [
        {
          prop: '',
          label: '',
          minWidth: ''
        }
      ]
    },
    tableLoading: false,
    // 分页配置
    pagination: {
      type: Object,
      default: () => {
        return {
          pageNo: 1,
          total: 0,
          pageSize: 30,
          pageSizes: [ 30, 100, 500, 1000 ]
        }
      }
    },
    // table的唯一id字段, 用做默认row-class-name使用
    idField: String
  },
  components: {treeselect, BaseBtnList},
  data() {
    return {
      // 选中的数据
      selectRows: [],
      // 表单
      searchForm: {},
      // 分页
      basePagination: this.pagination,
      // 分页组件的默认table配置
      defaultSelectPageColumns: defaultSelectPageColumns,
      uTableRowHeight: 30,
      pagerCount: document.body.clientWidth < 992 ? 5 : 7
    }
  },
  mixins: [ tableConfigMixins ],
  created() {
  },
  mounted() {
    this.searchConfig?.form?.forEach(r => {
      if (r.defaultValue != null) {
        this.$nextTick(() => {
          this.$set(this.searchForm, r.name, r.defaultValue)
        })
      }
    })
  },
  methods: {
    select(rows, row) {
      this.$emit('select', rows, row)
    },
    selectAll(rows) {
      this.$emit('selectAll', rows)
    },
    selectionChange(rows) {
      this.selectRows = rows
      this.$emit('selectionChange', rows)
    },
    search() {
      let obj = deepClone(this.searchForm)
      let searchForm = {}
      for (const key in obj) {
        if (key != 'undefined') {
          searchForm[key] = obj[key]
        }
      }
      this.$emit('search', searchForm)
    },
    reset() {
      this.$refs.searchRef.resetFields()

      this.searchConfig.form.forEach(r => {
        if (r.type === 'selectPage') {
          resetSelectPage(`selectPage_${r.name}`, this)
        }
      })

      this.searchForm = {}

      this.$nextTick(() => {
        this.searchConfig?.form?.forEach(r => {
          if (r.defaultValue != null) {
              this.$set(this.searchForm, r.name, r.type === 'selectPage' ?
              String(typeof(r.defaultValue) === 'function' ? r.defaultValue()
              :
              r.defaultValue) : typeof(r.defaultValue) === 'function' ? r.defaultValue() : r.defaultValue)
          }
        })
        // 重置数据
        let obj = deepClone(this.searchForm)
        let searchForm = {}
        for (const key in obj) {
          if (key != 'undefined') {
            searchForm[key] = obj[key]
          }
        }
        this.$emit('reset', searchForm)
      })
    },
    handlePageChange(val, type) {
      this.$set(this.basePagination, type, val)
      type === 'pageSize' && (this.$set(this.basePagination, 'pageNo', 1))
      this.$emit('changePage', this.basePagination)
    },
    rowDblclick(row, column, event) {
      this.$emit('rowDblclick', row, column, event)
    },
    rowClick(row, column, event) {
      this.$emit('rowClick', row, column, event)
    },
    cellClick(row, column, cell, event) {
      this.$emit('cellClick', row, column, cell, event)
    },
    rowContextmenu(row, column, event) {
      this.$emit('rowContextmenu', row, column, event)
    },
    // 设置默认值
    setDefaultValue() {
      this.searchConfig?.form?.forEach(r => {
        if (r.defaultValue != null) {
          this.$set(this.searchForm, r.name, r.type === 'selectPage' ?
          String(typeof(r.defaultValue) === 'function' ? r.defaultValue()
          :
          r.defaultValue) : typeof(r.defaultValue) === 'function' ? r.defaultValue() : r.defaultValue)
        }
      })
    },
    resultFormat(res) {
      const {list, total} = res.data
      return {list, totalRow: total}
    },
    // 输入框触发enter事件
    keyupEnterNative() {
      this.$emit('keyupEnterNative')
    },
    // 列宽拖拽
    headerDragend(newWidth, oldWidth, column, event) {
      this.$emit('headerDragend', newWidth, oldWidth, column, event)
    },
    // 设置勾选时默认的rowClassName
    defaultRowClassName({row}) {
      const isSelect = this.selectRows.some(r => r[this.idField] === row[this.idField])
      if (isSelect) return'select-row'
    },
    commonSelectChange(v, item) {
      toString.call(item.change) === '[object Function]' ? item.change(v) : null
    },
    commonInputDblclick(v, item) {
      toString.call(item.dblclick) === '[object Function]' ? item.dblclick(v) : null
    },
    // 转换tree数据结构
    normalizer(node) {
      if (node.children && !node.children.length) {
        delete node.children;
      }
      return {
        id: node.id,
        label: node.name,
        children: node.children
      };
    },
    sortChange({ column, prop, order }) {
      this.$emit('sortChange', {column, prop, order, tableData: this.$refs?.table?.tableData})
    }
  },
  watch: {
    'searchConfig': function() {
      this.setDefaultValue()
    },
    'pagination': {
      handler(n, o) {
        this.basePagination = n
      },
      deep: true
    },
    tableData: function(n) {
      if (n.length && this.tableConfig.isVirtualTable) {
        this.$nextTick(() => {
          this.uTableRowHeight = document.querySelectorAll('.uIndex')?.[1]?.getBoundingClientRect()?.height || 30
        })
      }
    },
    'searchForm': {
      handler(n, o) {
        this.$emit('receiveSearch', n)
      },
      deep: true
    },
  }
}
</script>

<style lang="scss" scoped>
.container {
  padding: 0 0 20px;
  min-height: calc(100vh - 94px);
  width: calc(100% - 20px);
  margin: 0 auto;
  margin-top: 10px;
 
  border-radius: 4px;
}
.line {
  width: 100%;
  height: 10px;
 
}
.search, .operateBtn, .baseTable{
 
}
.search {
  padding: 17px 20px 7px;
  border-radius: 4px;
  .search-loading {
    padding-bottom: 20px;
    font-size: 15px;
  }
  ::v-deep .el-form-item{
    margin-bottom: 10px;
    margin-top: 0;
  }
  ::v-deep .el-button--small {
    transform: translateY(-1px);
  }
}
.operateBtn {
  padding: 16px 20px 6px;
  border-top-left-radius: 4px;
  border-top-right-radius: 4px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  ::v-deep .iconfont {
    font-size: 12px;
    margin-right: 5px;
  }
  .operateBtn-left {
    ::v-deep .el-button{
      margin-left: 0;
      margin-right: 10px;
      margin-bottom: 10px;
    }
  }
  .operateBtn-right {
    max-width: 40%;
  }
}
.baseTable {
  padding: 0 20px 15px;
  border-bottom-left-radius: 4px;
  border-bottom-right-radius: 4px;
}

.searchBtn {
  text-align: center;
  padding: 0 0 20px;
}

.pagination {
  text-align: right;
  padding-top: 15px;
}

@media screen and (max-width: 1366px) {
  ::v-deep .el-select, ::v-deep .el-input {
    max-width: 120px;
  }
  ::v-deep .el-date-editor--daterange.el-input, ::v-deep .el-date-editor--daterange.el-input__inner, ::v-deep .el-date-editor--timerange.el-input, ::v-deep .el-date-editor--timerange.el-input__inner {
    width: 220px;
  }
  ::v-deep .el-form-item__label {
    vertical-align: super !important;
  }
  .el-form-item {
    margin-bottom: 10px !important;
    margin-right: 10px !important;
  }
  .search .el-form-item.el-form-item--small .v-selectpage {
    width: 120px;
  }
  .search .el-form-item.el-form-item--mini .v-selectpage div.sp-input-container div.sp-base{
    width: 120px;
  }
  .pagination {
    max-width: 95%;
  }
}
</style>
 
baseTable 文件夹下的  baseMixins.js
import baseFetchOptions from "@/views/mixin/baseFetchOptions"
import { deepClone } from "@/utils/index";

export default {
  data() {
    return {
      tableLoading: false,
      searchForm: {},
      tableData: [],
      selectRows: [],
      pagination: {
        pageNo: 1,
        total: 0,
        pageSize: 30,
        pageSizes: [ 30, 100, 500, 1000 ]
      }
    }
  },
  mixins: [ baseFetchOptions ],
  created() {
  },
  methods: {
    // 默认的请求列表函数
    fetchList() {},
    // 搜索
    search(form) {
      this.searchForm = form
      this.fetchList()
    },
    // 接收改变的searchForm
    receiveSearch(form) {
      this.searchForm = form
    },
    // 重置
    reset(form) {
      this.searchForm = form
      this.pagination.pageNo = 1
      this.fetchList()
    },
    // 选中行
    selectionChange(rows) {
      this.selectRows = rows
    },
    // 分页事件
    changePage(pageCnfig) {
      this.pagination = deepClone(pageCnfig)
      this.fetchList()
    },
    // 限制多选
    getSelectRows() {
      const { length } = this.selectRows
      if (!length) {
        this.$message.warning("至少选择一条数据!")
        return false
      }
      return true
    },
    // 限制单选
    getSelectRow() {
      const { length } = this.selectRows
      if (length != 1) {
        this.$message.warning("请选择一条数据!")
        return false
      }
      return true
    }
  }
}
===============================================================
baseTable 下的index 引入的  tableConfigMixins
 
tableConfigMixins:
  
// 此mixins用做处理所有table max height
import { debounce } from 'throttle-debounce'
export default {
  data() {
    return {
      tableMaxHeight: localStorage.getItem('tableMaxHeight') || 1000,
      otherHeight: 0, // 防止有别的高度没计算到
    }
  },
  computed: {
  },
  created() {
  },
  mounted() {
    this.setTableMaxHeight()

    window.onresize = debounce(200, () => {
      this.setTableMaxHeight()
    })
  },
  methods: {
    setTableMaxHeight() {
      this.$nextTick(() => {
        setTimeout(() => {
          const { height } = document.body.getBoundingClientRect()
          const { height: navHeight } = document.querySelector('.navbar').parentElement.getBoundingClientRect()
          const { height: tableHeaderHeight, top: tableHeaderMarginTop } = document.querySelector('.el-table__header-wrapper').getBoundingClientRect()

          // 使用属性控制
          const h1 = height - 30 - tableHeaderHeight - tableHeaderMarginTop - this.otherHeight
          this.tableMaxHeight = h1 < 450 ? 450 : h1

          // 使用class和属性控制
          if (document.querySelector('.el-table.tableMaxHeightClass')) {
            const { height: searchH } = document.querySelector('.search-box')?.getBoundingClientRect() || {height: 0}
            const { height: btnH } = document.querySelector('.btn-box')?.getBoundingClientRect() || {height: 0}
            const { height: paginationH } = document.querySelector('.pagination')?.getBoundingClientRect() || {}
            const h = (height - navHeight - searchH - btnH - paginationH - 80 - this.otherHeight)
            const _h =  h < 450 ? 450 : h
            document.querySelector('.el-table.tableMaxHeightClass').style.maxHeight = _h + 'px'
            this.tableMaxHeight = _h
          }
          // 使用class和属性控制, 虚拟 table 使用
          if (document.querySelector('.tableMaxHeightClass')?.querySelector('.el-table')) {
            const { height: searchH } = document.querySelector('.search-box')?.getBoundingClientRect()
            const { height: btnH } = document.querySelector('.btn-box')?.getBoundingClientRect()
            const { height: paginationH } = document.querySelector('.pagination')?.getBoundingClientRect() || {}
            const h = (height - navHeight - searchH - btnH - paginationH - 80 - this.otherHeight)
            const _h =  h < 450 ? 450 : h
            document.querySelector('.tableMaxHeightClass').querySelector('.el-table').style.maxHeight = _h + 'px'
            this.tableMaxHeight = _h
          }
        },500)
      })
    }
  },
}
====================================================================================
import { defaultSelectPageColumns } from "@/utils/constants";
 
defaultSelectPageColumns :
 
// 基础分页下拉Columns
export const defaultSelectPageColumns = [
  { title: 'CODE', data: 'code', width: 130 },
  { title: '中文名', data: 'nameCn', minWidth: 180 },
  { title: '英文名', data: 'nameEn', minWidth: 250  },
]
====================================================================================
import { resetSelectPage } from "@/utils/utils";
 
resetSelectPage :
/**
 * 清除selectPage选中项
 * @param {*} name
 * @param {*} $vm vue实例
 */
export function resetSelectPage (name, $vm) {
  const el = toString.call($vm.$refs[name]) === '[object Array]' ? $vm.$refs[name][0] : $vm.$refs[name]
  el?.remove?.()

  // 处理重置后多选selectPage会展开下拉框问题
  setTimeout(() => {
    el?.close?.()
  })
}
============================================================================================
import { deepClone } from '@/utils';
 
// 深拷贝对象
// https://github.com/JakHuang/form-generator/blob/dev/src/utils/index.js#L107
export function deepClone(obj) {
  const _toString = Object.prototype.toString

  // null, undefined, non-object, function
  if (!obj || typeof obj !== 'object') {
    return obj
  }

  // DOM Node
  if (obj.nodeType && 'cloneNode' in obj) {
    return obj.cloneNode(true)
  }

  // Date
  if (_toString.call(obj) === '[object Date]') {
    return new Date(obj.getTime())
  }

  // RegExp
  if (_toString.call(obj) === '[object RegExp]') {
    const flags = []
    if (obj.global) { flags.push('g') }
    if (obj.multiline) { flags.push('m') }
    if (obj.ignoreCase) { flags.push('i') }

    return new RegExp(obj.source, flags.join(''))
  }

  const result = Array.isArray(obj) ? [] : obj.constructor ? new obj.constructor() : {}

  for (const key in obj) {
    result[key] = deepClone(obj[key])
  }

  return result
}
 
====================================================================
import BaseBtnList from '@/components/BaseBtnList/index.vue'
 
BaseBtnList :
<template>
  <div>
    <template v-for="(item, index) in list">
      <el-button
        :type="item.type || 'primary'"
        :icon="typeof item.icon != 'function' ? item.icon : null"
        :key="index"
        :plain="item.plain === false ? false : true"
        :loading="item.loading === true ? true : false"
        :style="item.style"
        :class="item.class"
        :disabled="item.disabled"
        @click="item.event()"
        v-if="item.show === false ? false : true"
        >{{ item.name }}</el-button
      >
    </template>
  </div>
</template>

<script>
export default {
  props: {
    list: {
      type: Array,
      default: () => [],
    },
  },
  data() {
    return {

    };
  },
  created() {},
  methods: {
  },
};
</script>
<style scoped lang="scss">
::v-deep .el-button+.el-button {
  margin-bottom: 10px;
}
</style>
=================================================================
import treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
 此引入需要安装插件 安装的命令如下,哪里需要哪里引入,同时使用的components中也需要在页面中使用
yarn  add @riophae/vue-treeselect
 
posted @ 2023-12-29 11:34  一封未寄出的信  阅读(16)  评论(0编辑  收藏  举报