1.0.5 • Published 8 months ago

common-pro-table v1.0.5

Weekly downloads
-
License
ISC
Repository
-
Last release
8 months ago

安装和引入

安装

// npm
npm install common-pro-table
// yarn
yarn add common-pro-table

引入

该组件依赖 element-plus,需要自行引入

// 引入element-plus
import ElementPlus from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css'
// 引入中文语言包
import 'dayjs/locale/zh-cn'
import locale from 'element-plus/lib/locale/lang/zh-cn'
app.use(ElementPlus, { locale })
// 引入vue-pro-table
import commonProTable from 'common-pro-table'
app.use(commonProTable)

表格配置

  • border 表格是否有边框。布尔值,默认为 false

  • columns 属性的配置,是一个数组

参数说明类型可选值默认值
label对应 el-table-column 的 labelstring--
type对应 el-table-column 的 typestringselection/index/expand-
prop对应 el-table-column 的 propstring--
width对应 el-table-column 的 widthstring,number--
minWidth对应 el-table-column 的 min-widthstring,number--
align对应 el-table-column 的 alignstringleft/center/rightleft
fixed对应 el-table-column 的 fixedstring, booleantrue, left, right-
sortable对应 el-table-column 的 sortablebooleanfalse/truefalse
filters对应 el-table-column 的 filtersArray{ text, value }--
tdSlot单元格要自定义内容时,可以通过此属性配置一个插槽名称,并且是作用域插槽,可以接收 scope 数据string--
labelSlot表头要自定义内容时,可以通过此属性配置一个插槽名称,并且是作用域插槽,可以接收 scope 数据string--
  • row-key 属性配置

    对应 el-table 的 row-key,默认值是'id'

搜索配置

  • search 属性的配置,是一个对象

    如果不想显示搜索表单,可以不配置 search 或者 search 设置为 false

参数说明类型可选值默认值
labelWidthlabel 文字长度string--
inputWidth表单项长度string--
fields表单字段列表,包含 text,select,radio,checkbox,datetime 等类型,所有字段类型配置见下表Array{字段类型}--
  • fields 列表的字段类型配置
参数说明类型可选值默认值
type字段类型stringtext,textarea,select,radio,radio-button,checkbox,checkbox-button,number,date,daterange,datetime,datetimerangetext
labellabel 文本string--
name搜索时的提交的参数名称string--
style额外的样式object--
defaultValue默认值--
options当 type 是 select,radio,radio-button,checkbox,checkbox-button 时的枚举选项Array{name, value}--
filterable当 type 是 select 时,下拉框是否支持模糊搜索booleantrue, falsefalse
multiple当 type 是 select 时,下拉框是否支持多选booleantrue, falsefalse
transform搜索前对表单数据进行转换,比如表单数据是数组,但是搜索的时候需要传递字符串。它是一个函数,默认参数是字段的 value,需要返回转换后的结果function(value)--
trueNames当 type 是 daterange,datetimerange 时,开始时间和结束时间是在一个数组里面,但是搜索时可能需要两个字段,这时就需要把开始时间和结束时间分别赋值给两个字段,这两个字段的名称就是通过 trueNames 配置,它是一个数组,例如:trueNames: 'startTime', 'endTime'Arraystring
min当 type 是 number 时的最小值number--
max当 type 是 number 时的最大值number--

分页配置

  • pagination 属性的配置,是一个对象

    如果不想显示分页,将 pagination 设置为 false

参数说明类型可选值默认值
layout组件布局stringtotal, sizes, prev, pager, next, jumpertotal, sizes, prev, pager, next, jumper
pageSize每页显示条目个数number-10
pageSizes每页显示个数选择器的选项设置Arraynumber-10, 20, 30, 40, 50, 100

标题栏配置

表格上方有一个标题栏,标题栏左侧显示一个标题,右侧是一个可自定义的工具栏

  • hide-title-bar

    是否隐藏标题栏,布尔值

  • title

    表格标题

用法

<template>
  <div class="page-box">
    <!-- 搜索选项 -->
    <div class="search" v-if="search">
      <div class="head" v-if="search.name">
        <span v-if="search.icon" class="icon"></span>
        {{ search.name }}
      </div>
      <el-form
        :model="searchModel"
        :inline="true"
        :label-position="search.labelPosition"
        :label-width="search.labelWidth"
        ref="searchForm"
        :style="search.style"
      >
        <el-form-item
          v-for="item in search.fields"
          :key="item.name"
          :label="item.label"
          :prop="item.name"
        >
          <slot v-if="item.type === 'custom'" :name="item.slot" />

          <el-select
            v-else-if="item.type === 'select'"
            v-model="searchModel[item.name]"
            :filterable="!!item.filterable"
            :multiple="!!item.multiple"
            clearable
            :placeholder="`${item.placeholder}`"
            :style="{ width: search.inputWidth, ...item.style }"
          >
            <el-option
              v-for="option of item.options"
              :key="option.code"
              :label="option.name"
              :value="option.code"
            >
            </el-option>
          </el-select>

          <el-input
            v-else-if="item.type === 'tel'"
            v-model="searchModel[item.name]"
            :placeholder="`${item.placeholder}`"
            :style="{ width: search.inputWidth, ...item.style }"
          >
            <template #prepend v-if="item.select">
              <el-select
                v-model="searchModel[item.select.name]"
                :placeholder="`${item.select.placeholder}`"
                :style="{ width: item.select.width, ...item.select.style }"
              >
                <template #prefix>
                  <span> + </span>
                </template>
                <el-option
                  v-for="option in item.select.options"
                  :key="option.code"
                  :label="option.name"
                  :value="option.code"
                >
                </el-option>
              </el-select>
            </template>
          </el-input>

          <el-radio-group
            v-model="searchModel[item.name]"
            v-else-if="item.type === 'radio'"
            :style="{ width: search.inputWidth, ...item.style }"
          >
            <el-radio
              v-for="option of item.options"
              :key="option.code"
              :label="option.code"
            >
              {{ option.name }}
            </el-radio>
          </el-radio-group>

          <el-radio-group
            v-model="searchModel[item.name]"
            v-else-if="item.type === 'radio-button'"
            :style="{ width: search.inputWidth, ...item.style }"
          >
            <el-radio-button
              v-for="option of item.options"
              :key="option.code"
              :label="option.code"
            >
              {{ option.name }}
            </el-radio-button>
          </el-radio-group>

          <el-checkbox-group
            v-model="searchModel[item.name]"
            v-else-if="item.type === 'checkbox'"
            :style="{ width: search.inputWidth, ...item.style }"
          >
            <el-checkbox
              v-for="option of item.options"
              :key="option.code"
              :label="option.code"
            >
              {{ option.name }}
            </el-checkbox>
          </el-checkbox-group>

          <el-checkbox-group
            v-model="searchModel[item.name]"
            v-else-if="item.type === 'checkbox-button'"
            :style="{ width: search.inputWidth, ...item.style }"
          >
            <el-checkbox-button
              v-for="option of item.options"
              :key="option.code"
              :label="option.code"
            >
              {{ option.name }}
            </el-checkbox-button>
          </el-checkbox-group>

          <el-date-picker
            v-else-if="item.type === 'week'"
            v-model="searchModel[item.name]"
            type="week"
            clearable
            :placeholder="`${item.placeholder}`"
            :style="{ width: search.inputWidth, ...item.style }"
          >
          </el-date-picker>

          <el-date-picker
            v-else-if="item.type === 'month'"
            v-model="searchModel[item.name]"
            type="month"
            clearable
            :placeholder="`${item.placeholder}`"
            :style="{ width: search.inputWidth, ...item.style }"
          >
          </el-date-picker>

          <el-date-picker
            v-else-if="item.type === 'year'"
            v-model="searchModel[item.name]"
            type="year"
            clearable
            :placeholder="`${item.placeholder}`"
            :style="{ width: search.inputWidth, ...item.style }"
          >
          </el-date-picker>

          <el-date-picker
            v-else-if="item.type === 'date'"
            v-model="searchModel[item.name]"
            type="date"
            format="YYYY-MM-DD"
            clearable
            @change="handleDateChange($event, item, 'YYYY-MM-DD')"
            :placeholder="`${item.placeholder}`"
            :style="{ width: search.inputWidth, ...item.style }"
          >
          </el-date-picker>

          <el-date-picker
            v-else-if="item.type === 'datetime'"
            v-model="searchModel[item.name]"
            type="datetime"
            clearable
            @change="handleDateChange($event, item, 'YYYY-MM-DD HH:mm:ss')"
            format="YYYY-MM-DD HH:mm:ss"
            :placeholder="`${item.placeholder}`"
            :style="{ width: search.inputWidth, ...item.style }"
          >
          </el-date-picker>

          <el-date-picker
            v-else-if="item.type === 'daterange'"
            v-model="searchModel[item.name]"
            type="daterange"
            format="YYYY-MM-DD"
            :range-separator="item.rangeSeparator"
            :start-placeholder="`${item.startPlaceholder}`"
            :end-placeholder="`${item.endPlaceholder}`"
            clearable
            @change="handleRangeChange($event, item, 'YYYY-MM-DD')"
            :style="{ width: search.inputWidth, ...item.style }"
          >
          </el-date-picker>

          <el-date-picker
            v-else-if="item.type === 'datetimerange'"
            v-model="searchModel[item.name]"
            type="datetimerange"
            format="YYYY-MM-DD HH:mm:ss"
            :range-separator="item.rangeSeparator"
            :start-placeholder="`${item.startPlaceholder}`"
            :end-placeholder="`${item.endPlaceholder}`"
            clearable
            @change="handleRangeChange($event, item, 'YYYY-MM-DD HH:mm:ss')"
            :style="{ width: search.inputWidth, ...item.style }"
          >
          </el-date-picker>

          <el-input-number
            v-else-if="item.type === 'number'"
            v-model="searchModel[item.name]"
            :placeholder="`${item.placeholder}`"
            controls-position="right"
            :min="item.min"
            :max="item.max"
            :style="{ width: search.inputWidth, ...item.style }"
          />

          <el-input
            v-else-if="item.type === 'textarea'"
            type="textarea"
            clearable
            v-model="searchModel[item.name]"
            :placeholder="`${item.placeholder}`"
            :style="{ width: search.inputWidth, ...item.style }"
          >
          </el-input>

          <el-input
            v-else
            v-model="searchModel[item.name]"
            clearable
            :placeholder="`${item.placeholder}`"
            :style="{ width: search.inputWidth, ...item.style }"
          >
          </el-input>
        </el-form-item>

        <el-form-item class="search-btn">
          <sinosoft-common-button
            v-for="item in search.btns"
            :key="item.name"
            :btnCode="item.btnCode"
            :plain="item.plain"
            :primary="item.primary"
            :default="item.default"
            :info="item.info"
            :circle="item.circle"
            :Width="item.Width"
            :height="item.height"
            :color="item.color"
            :bgColor="item.bgColor"
            :borderColor="item.borderColor"
            :round="item.round"
            :loading="item.loading"
            :loadingColor="item.loadingColor"
            :borderStyle="item.borderStyle"
            :icon="item.icon"
            :size="item.size"
            :style="{ ...item.style }"
            @handleClick="item.fn"
          >
            {{ item.name }}
          </sinosoft-common-button>

          <!-- <el-button
            v-for="item in search.btns"
            :key="item.name"
            @click="item.fn"
          >
            {{ item.name }}
          </el-button> -->

          <!-- <el-button @click="handleSearch" icon="el-icon-search" >
          查询
        </el-button> -->
        </el-form-item>
      </el-form>
    </div>

    <!-- table表格栏 -->
    <div class="table" v-for="(tableItem, idx) in tableConfig" :key="idx">
      <!-- title 和 工具栏 -->
      <div class="head" v-if="!tableItem.hideTitleBar">
        <slot name="title">
          <span class="title">{{ tableItem.title }}</span>
        </slot>
        <div
          class="toolbar"
          v-if="tableItem.toolbar && tableItem.toolbar.length > 0"
        >
          <slot :name="tableItem.toolbar"></slot>

          <sinosoft-common-button
            v-for="item in tableItem.toolbar"
            :key="item.name"
            :btnCode="item.btnCode"
            :plain="item.plain"
            :primary="item.primary"
            :default="item.default"
            :info="item.info"
            :circle="item.circle"
            :Width="item.Width"
            :height="item.height"
            :color="item.color"
            :bgColor="item.bgColor"
            :borderColor="item.borderColor"
            :round="item.round"
            :loading="item.loading"
            :loadingColor="item.loadingColor"
            :borderStyle="item.borderStyle"
            :icon="item.icon"
            :size="item.size"
            :style="{ ...item.style }"
            @handleClick="item.fn"
          >
            {{ item.name }}
          </sinosoft-common-button>

          <!-- <el-button
            v-for="item in tableItem.toolbar"
            :key="item.name"
            @click="item.fn"
          >
            {{ item.name }}
          </el-button> -->
        </div>
      </div>

      <el-table
        v-loading="tableItem.loading"
        :ref="tableItem.ref"
        :data="tableItem.tableData"
        :tree-props="tableItem.tree.treeProps"
        :lazy="tableItem.tree.lazy"
        :load="tableItem.tree.load"
        tooltip-effect="dark"
        :header-cell-style="{}"
        :cell-style="{}"
        stripe
        :border="tableItem.border"
        :max-height="tableItem.maxHeight"
        @select="tableItem.select"
        @selection-change="tableItem.handleSelectionChange"
        @sort-change="tableItem.handleSortChange"
      >
        <el-table-column
          v-for="columnItem in tableItem.columns"
          :key="columnItem.label"
          :filter-method="columnItem.filters && filterHandler"
          :show-overflow-tooltip="!columnItem.wrap"
          :sort-method="columnItem.sortMethod"
          v-bind="columnItem"
        >
          <template #header="scope" v-if="!!columnItem.labelSlot">
            <slot :name="columnItem.labelSlot" v-bind="scope"></slot>
          </template>
          <template #default="scope" v-if="!!columnItem.tdSlot">
            <slot :name="columnItem.tdSlot" v-bind="scope"></slot>
          </template>
        </el-table-column>
      </el-table>

      <!-- 分页 -->
      <el-pagination
        v-if="tableItem.paginationConfig.show && tableItem.total > 0"
        class="pagination"
        :background="tableItem.paginationConfig.background"
        :style="tableItem.paginationConfig.style"
        @size-change="tableItem.handleSizeChange"
        v-model:currentPage="tableItem.pageNum"
        @current-change="tableItem.handleCurrentChange"
        :page-sizes="tableItem.paginationConfig.pageSizes"
        v-model:pageSize="tableItem.pageSize"
        :layout="tableItem.paginationConfig.layout"
        :total="tableItem.total"
      >
      </el-pagination>
    </div>
  </div>
</template>
<script lang="ts">
import {
  defineComponent,
  reactive,
  toRefs,
  onBeforeMount,
  // ref,
  // nextTick,
} from 'vue'

// import sinosoftCommonButton from 'sinosoft-common-button'
// import '../../node_modules/sinosoft-common-button/style.css'

const formatDate = (date: any, format: any) => {
  var obj = {
    '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+)/i.test(format)) {
    format = format.replace(
      RegExp.$1,
      (date.getFullYear() + '').substr(4 - RegExp.$1.length)
    )
  }
  for (var k in obj) {
    if (new RegExp('(' + k + ')').test(format)) {
      format = format.replace(
        RegExp.$1,
        RegExp.$1.length == 1
          ? obj[k]
          : ('00' + obj[k]).substr(('' + obj[k]).length)
      )
    }
  }
  return format
}

const getSearchModel = (search: any) => {
  const searchModel = {}
  if (search && search?.fields) {
    search.fields.forEach((item: any) => {
      switch (item.type) {
        case 'checkbox':
        case 'checkbox-button':
          searchModel[item.name] = []
          break
        default:
          break
      }

      if (item.defaultValue !== undefined) {
        searchModel[item.name] = item.defaultValue

        if (item.type === 'tel' && item.select) {
          searchModel[item.select.name] = item.select.defaultValue
        }

        // 日期范围和时间范围真实变量默认值
        if (
          (item.type === 'daterange' || item.type === 'datetimerange') &&
          !!item.trueNames &&
          Array.isArray(item.defaultValue)
        ) {
          item.defaultValue.forEach((val: any, idx: any) => {
            searchModel[item.trueNames[idx]] = val
          })
        }
      }
    })
  }
  return searchModel
}

export default defineComponent({
  props: {
    // 搜索配置
    search: {
      type: Object,
      default: {},
    },
    // 表格配置
    tableConfig: {
      type: [Object],
      default: [],
    },
  },
  components: {
    // button 组件
    // sinosoftCommonButton,
  },
  setup(props: any, { emit }: any) {
    // 优化字段
    const optimizeFields = (search: any) => {
      const searchModel = JSON.parse(JSON.stringify(state.searchModel))
      if (search && search.fields) {
        search.fields.forEach((item: any) => {
          if (!searchModel.hasOwnProperty(item.name)) {
            return
          }
          if (!!item.transform) {
            searchModel[item.name] = item.transform(searchModel[item.name])
          }
          if (
            (item.type === 'daterange' || item.type === 'datetimerange') &&
            !!item.trueNames
          ) {
            delete searchModel[item.name]
          }
        })
      }
      return searchModel
    }

    onBeforeMount(() => {
      state.searchModel = optimizeFields(props.search)
      emit('getTableData', { searchModel: state.searchModel, pageNum: 1 })
    })

    const state = reactive({
      searchModel: getSearchModel(props.search),
      // paginationConfig: {},
      // 搜索
      handleSearch() {
        emit('getTableData', { searchModel: state.searchModel, pageNum: 1 })
      },
      // 重置
      handleReset() {
        if (JSON.stringify(state.searchModel) === '{}') {
          return
        }
        state.searchModel = getSearchModel(props.search)
        console.log(state.searchModel)
        emit('getTableData', { searchModel: state.searchModel, pageNum: 1 })
      },
      // 刷新
      refresh() {
        emit('getTableData', { searchModel: state.searchModel, pageNum: 1 })
      },
      // 过滤
      filterHandler(value: any, row: any, column: any) {
        const property = column['property']
        return row[property] === value
      },
      // 日期范围
      handleDateChange(date: any, item: any, format: any) {
        state.searchModel[item.name] = !!date ? formatDate(date, format) : ''
      },
      handleRangeChange(date: any, item: any, format: any) {
        const arr = !!date && date.map((d: any) => formatDate(d, format))
        state.searchModel[item.name] = !!arr ? arr : []
        if (!item.trueNames) {
          return
        }
        if (!!arr) {
          arr.forEach((val: any, idx: any) => {
            state.searchModel[item.trueNames[idx]] = val
          })
        } else {
          item.trueNames.forEach((key: any) => {
            delete state.searchModel[key]
          })
        }
      },
    })
    // props.table.forEach((item: any) => {
    //   if (typeof item.paginationConfig === "object") {
    //     const { layout, pageSizes, style } = item.paginationConfig;
    //     state.paginationConfig = {
    //       show: true,
    //       layout: layout || "total, sizes, prev, pager, next, jumper",
    //       pageSizes: pageSizes || [10, 20, 30, 40, 50, 100],
    //       style: style || {},
    //     };
    //   }
    // });
    return {
      ...toRefs(state),
    }
  },
})
</script>
<style lang="scss" scoped>
.page-box {
  width: 100%;
  background: #fff;
  font-family: 'Arial', 'Microsoft YaHei', '黑体', sans-serif;
  font-size: 14px;

  .search {
    padding: 20px;

    // margin-bottom: 10px;
    .head {
      padding-bottom: 20px;
      font-weight: bold;
      display: flex;
      align-items: center;

      .icon {
        display: inline-block;
        width: 5px;
        height: 16px;
        background: #216fed;
        margin-right: 5px;
      }
    }

    .el-form {
      display: flex;
      align-items: flex-end;
      flex-wrap: wrap;
      padding: 20px;
      // background: #f6f6f6;
      border-radius: 5px;
    }

    .el-form-item {
      margin-bottom: 20px;
    }

    ::v-deep(.el-form-item__label) {
      font-weight: bold;
    }

    .search-btn {
      margin-left: auto;
    }

    ::v-deep(.el-input-number .el-input__inner) {
      text-align: left;
    }

    ::v-deep(.el-input-group__prepend) {
      background: #fff;
    }

    ::v-deep(.el-range-separator) {
      color: #b1b6c3;
    }
  }

  .table {
    padding: 20px 20px 0 20px;
    background: #fff;

    ::v-deep(th) {
      background: #f6f6f6;
      color: rgba(0, 0, 0, 0.85);
    }

    ::v-deep(th.el-table-fixed-column--right) {
      background: #f6f6f6;
    }

    .head {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding-bottom: 20px;
      font-weight: bold;
      // background: #fff;
      // .title {
      //   font-size: 14px;
      // }

      .toolbar {
        display: flex;
        flex-direction: row;
      }
    }

    ::v-deep(.cell) {
      display: flex;
      justify-content: center;
      align-items: center;
    }

    .pagination {
      display: flex;
      justify-content: flex-end;
      padding: 20px;

      :last-child {
        margin-right: 0;
      }
    }
  }
}
</style>
1.0.5

8 months ago

1.0.2

9 months ago