研发图纸文件管理系统-前端项目
chenghongxing
2020-10-02 517c1959d8b72e62b80c503d2ba89cdf48c96313
feat: add AdvanceTable.vue component; :star:
新增:高级表格;
6个文件已添加
1个文件已修改
886 ■■■■■ 已修改文件
src/components/table/advance/ActionColumns.vue 166 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/table/advance/ActionSize.vue 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/table/advance/AdvanceTable.vue 239 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/table/advance/SearchArea.vue 262 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/table/advance/index.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/components/Table.vue 168 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/config.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/table/advance/ActionColumns.vue
New file
@@ -0,0 +1,166 @@
<template>
  <div class="action-columns" ref="root">
    <a-tooltip title="列设置" :get-popup-container="() => $refs.root">
      <a-popover v-model="visible" placement="bottomRight" trigger="click" :get-popup-container="() => $refs.root">
        <div slot="title">
          <a-checkbox :indeterminate="indeterminate" :checked="checkAll" @change="onCheckAllChange" class="check-all" />列展示
          <a-button @click="resetColumns" style="float: right" type="link" size="small">重置</a-button>
        </div>
        <a-list style="width: 100%" size="small" :key="i" v-for="(col, i) in columns" slot="content">
          <a-list-item>
            <a-checkbox v-model="col.visible" @change="e => onCheckChange(e, col)"/>
            <template v-if="col.title">
              {{col.title}}:
            </template>
            <slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
            <template slot="actions">
              <a-tooltip title="固定在列头" :get-popup-container="() => $refs.root">
                <a-icon :class="['left', {active: col.fixed === 'left'}]" @click="fixColumn('left', col)" type="vertical-align-top" />
              </a-tooltip>
              <a-tooltip title="固定在列尾" :get-popup-container="() => $refs.root">
                <a-icon :class="['right', {active: col.fixed === 'right'}]" @click="fixColumn('right', col)" type="vertical-align-bottom" />
              </a-tooltip>
              <a-tooltip title="添加搜索" :get-popup-container="() => $refs.root">
                <a-icon :class="{active: col.searchAble}" @click="setSearch(col)" type="search" />
              </a-tooltip>
            </template>
          </a-list-item>
        </a-list>
        <a-icon class="action" type="setting" />
      </a-popover>
    </a-tooltip>
  </div>
</template>
<script>
  import cloneDeep from 'lodash.clonedeep'
  export default {
    name: 'ActionColumns',
    props: ['columns', 'visibleColumns'],
    data() {
      return {
        visible: false,
        indeterminate: false,
        checkAll: true,
        checkedCounts: this.columns.length,
        backColumns: cloneDeep(this.columns)
      }
    },
    watch: {
      checkedCounts(val) {
        this.checkAll = val === this.columns.length
        this.indeterminate = val > 0 && val < this.columns.length
      }
    },
    created() {
      this.$emit('update:visibleColumns', [...this.columns])
      for (let col of this.columns) {
        if (col.visible === undefined) {
          this.$set(col, 'visible', true)
        }
        if (!col.visible) {
          this.checkedCounts -= 1
          this.$set(col, 'colSpan', 0)
          this.$set(col, 'customCell', () => ({style: 'display: none;'}))
        }
      }
    },
    methods: {
      onCheckChange(e, col) {
        if (!col.visible) {
          this.checkedCounts -= 1
          this.$set(col, 'colSpan', 0)
          this.$set(col, 'customCell', () => ({style: 'display: none;'}))
        } else {
          this.checkedCounts += 1
          this.$set(col, 'colSpan', undefined)
          this.$set(col, 'customCell', undefined)
        }
      },
      fixColumn(fixed, col) {
        if (fixed !== col.fixed) {
          this.$set(col, 'fixed', fixed)
        } else {
          this.$set(col, 'fixed', undefined)
        }
      },
      setSearch(col) {
        this.$set(col, 'searchAble', !col.searchAble)
        if (!col.searchAble && col.search) {
          this.resetSearch(col)
        }
      },
      resetSearch(col) {
        col.search.value = col.dataType === 'boolean' ? false : undefined
        col.search.backup = undefined
      },
      resetColumns() {
        const {columns, backColumns} = this
        let counts = columns.length
        backColumns.forEach((back, index) => {
          const column = columns[index]
          column.visible = back.visible === undefined || back.visible
          if (column.visible) {
            this.$set(column, 'colSpan', undefined)
            this.$set(column, 'customCell', undefined)
          } else {
            counts -= 1
            this.$set(column, 'colSpan', 0)
            this.$set(column, 'customCell', () => ({style: 'display: none;'}))
          }
          if (back.fixed !== undefined) {
            column.fixed = back.fixed
          } else {
            this.$set(column, 'fixed', undefined)
          }
          column.searchAble = back.searchAble
          this.resetSearch(column)
        })
        this.checkedCounts = counts
        this.visible = false
        this.$emit('reset', this.getConditions(columns))
      },
      onCheckAllChange(e) {
        if (e.target.checked) {
          this.checkedCounts = this.columns.length
          this.columns.forEach(col => {
            col.visible = true
            this.$set(col, 'colSpan', undefined)
            this.$set(col, 'customCell', undefined)
          })
        } else {
          this.checkedCounts = 0
          this.columns.forEach(col => {
            col.visible = false
            this.$set(col, 'colSpan', 0)
            this.$set(col, 'customCell', () => ({style: 'display: none;'}))
          })
        }
      },
      getConditions(columns) {
        const conditions = {}
        columns.filter(item => item.search.value !== undefined && item.search.value !== '' && item.search.value !== null)
          .forEach(col => {
            conditions[col.dataIndex] = col.search.value
          })
        return conditions
      }
    }
  }
</script>
<style scoped lang="less">
.action-columns{
  display: inline-block;
  .check-all{
    margin-right: 8px;
  }
  .left,.right{
    transform: rotate(-90deg);
  }
  .active{
    color: @primary-color;
  }
}
</style>
src/components/table/advance/ActionSize.vue
New file
@@ -0,0 +1,44 @@
<template>
  <div class="action-size" ref="root">
    <a-tooltip title="密度">
      <a-dropdown placement="bottomCenter" :trigger="['click']" :get-popup-container="() => $refs.root">
        <a-icon class="action" type="column-height" />
        <a-menu :selected-keys="[value]" slot="overlay" @click="onClick">
          <a-menu-item key="default">
            默认
          </a-menu-item>
          <a-menu-item key="middle">
            中等
          </a-menu-item>
          <a-menu-item key="small">
            紧密
          </a-menu-item>
        </a-menu>
      </a-dropdown>
    </a-tooltip>
  </div>
</template>
<script>
  export default {
    name: 'ActionSize',
    props: ['value'],
    inject: ['table'],
    data() {
      return {
        selectedKeys: ['middle']
      }
    },
    methods: {
      onClick({key}) {
        this.$emit('input', key)
      }
    }
  }
</script>
<style scoped lang="less">
.action-size{
  display: inline-block;
}
</style>
src/components/table/advance/AdvanceTable.vue
New file
@@ -0,0 +1,239 @@
<template>
  <div ref="table" :id="id" class="advanced-table">
    <a-spin :spinning="loading">
    <div :class="['header-bar', size]">
      <div class="title">
        <template v-if="title">{{title}}</template>
        <slot v-else-if="$slots.title" name="title"></slot>
        <template v-else>高级表格</template>
      </div>
      <div class="search">
        <search-area @change="onSearchChange" :columns="columns" >
          <template :slot="slot" v-for="slot in slots">
            <slot :name="slot"></slot>
          </template>
        </search-area>
      </div>
      <div class="actions">
        <a-tooltip title="刷新">
          <a-icon @click="refresh" class="action" :type="loading ? 'loading' : 'reload'" />
        </a-tooltip>
        <action-size v-model="sSize" class="action" />
        <action-columns :columns="columns" @reset="onColumnsReset" class="action">
          <template :slot="slot" v-for="slot in slots">
            <slot :name="slot"></slot>
          </template>
        </action-columns>
        <a-tooltip title="全屏">
          <a-icon @click="toggleScreen" class="action" :type="fullScreen ? 'fullscreen-exit' : 'fullscreen'" />
        </a-tooltip>
      </div>
    </div>
    <a-table
      v-bind="{...$options.propsData, title: undefined, loading: false}"
      :size="sSize"
      @expandedRowsChange="onExpandedRowsChange"
      @change="onChange"
      @expand="onExpand"
    >
      <template slot-scope="text, record, index" :slot="slot" v-for="slot in scopedSlots ">
        <slot :name="slot" v-bind="{text, record, index}"></slot>
      </template>
      <template :slot="slot" v-for="slot in slots">
        <slot :name="slot"></slot>
      </template>
      <template slot-scope="record, index, indent, expanded" :slot="$scopedSlots.expandedRowRender ? 'expandedRowRender' : ''">
        <slot v-bind="{record, index, indent, expanded}" :name="$scopedSlots.expandedRowRender ? 'expandedRowRender' : ''"></slot>
      </template>
    </a-table>
    </a-spin>
  </div>
</template>
<script>
  import ActionSize from '@/components/table/advance/ActionSize'
  import ActionColumns from '@/components/table/advance/ActionColumns'
  import SearchArea from '@/components/table/advance/SearchArea'
  export default {
    name: 'AdvanceTable',
    components: {SearchArea, ActionColumns, ActionSize},
    props: {
      tableLayout: String,
      bordered: Boolean,
      childrenColumnName: Array[String],
      columns: Array,
      components: Object,
      dataSource: Array,
      defaultExpandAllRows: Array[String],
      expandedRowKeys: Array[String],
      expandedRowRender: Function,
      expandIcon: Function,
      expandRowByClick: Boolean,
      expandIconColumnIndex: Number,
      footer: Function,
      indentSize: Number,
      loading: Boolean,
      locale: Object,
      pagination: Object,
      rowClassName: Function,
      rowKey: [String, Function],
      rowSelection: Object,
      scroll: Object,
      showHeader: Boolean,
      size: String,
      title: String,
      customHeaderRow: Function,
      customRow: Function,
      getPopupContainer: Function,
      transformCellText: Function
    },
    provide() {
      return {
        table: this
      }
    },
    data() {
      return {
        id: `${new Date().getTime()}-${Math.floor(Math.random() * 10)}`,
        sSize: this.size || 'default',
        fullScreen: false,
        conditions: []
      }
    },
    computed: {
      slots() {
        return Object.keys(this.$slots).filter(slot => slot !== 'title')
      },
      scopedSlots() {
        return Object.keys(this.$scopedSlots).filter(slot => slot !== 'expandedRowRender' && slot !== 'title')
      }
    },
    created() {
      this.addListener()
    },
    beforeDestroy() {
      this.removeListener()
    },
    methods: {
      refresh() {
        this.$emit('refresh', this.conditions)
      },
      onSearchChange(conditions) {
        this.conditions = conditions
        this.$emit('search', conditions)
      },
      toggleScreen() {
        if (this.fullScreen) {
          this.outFullScreen()
        } else {
          this.inFullScreen()
        }
      },
      inFullScreen() {
        const el = this.$refs.table
        if (el.requestFullscreen) {
          el.requestFullscreen()
          return true
        } else if (el.webkitRequestFullScreen) {
          el.webkitRequestFullScreen()
          return true
        } else if (el.mozRequestFullScreen) {
          el.mozRequestFullScreen()
          return true
        } else if (el.msRequestFullscreen) {
          el.msRequestFullscreen()
          return true
        }
        this.$message.warn('对不起,您的浏览器不支持全屏模式')
        return false
      },
      outFullScreen() {
        if (document.exitFullscreen) {
          document.exitFullscreen()
        } else if (document.webkitCancelFullScreen) {
          document.webkitCancelFullScreen();
        } else if (document.mozCancelFullScreen) {
          document.mozCancelFullScreen()
        } else if (document.msExitFullscreen) {
          document.msExiFullscreen()
        }
      },
      onColumnsReset(conditions) {
        this.$emit('reset', conditions)
      },
      onExpandedRowsChange(expandedRows) {
        this.$emit('expandedRowsChange', expandedRows)
      },
      onChange(pagination, filters, sorter, options) {
        this.$emit('expandedRowsChange', pagination, filters, sorter, options)
      },
      onExpand(expanded, record) {
        this.$emit('expandedRowsChange', expanded, record)
      },
      addListener() {
        document.addEventListener('fullscreenchange', this.fullScreenListener)
        document.addEventListener('webkitfullscreenchange', this.fullScreenListener)
        document.addEventListener('mozfullscreenchange', this.fullScreenListener)
        document.addEventListener('msfullscreenchange', this.fullScreenListener)
      },
      removeListener() {
        document.removeEventListener('fullscreenchange', this.fullScreenListener)
        document.removeEventListener('webkitfullscreenchange', this.fullScreenListener)
        document.removeEventListener('mozfullscreenchange', this.fullScreenListener)
        document.removeEventListener('msfullscreenchange', this.fullScreenListener)
      },
      fullScreenListener(e) {
        if (e.target.id === this.id) {
          this.fullScreen = !this.fullScreen
        }
      }
    }
  }
</script>
<style scoped lang="less">
.advanced-table{
  background-color: @component-background;
  .header-bar{
    padding: 16px 24px;
    display: flex;
    align-items: center;
    border-radius: 4px;
    transition: all 0.3s;
    &.middle{
      padding: 12px 16px;
    }
    &.small{
      padding: 8px 12px;
      border: 1px solid @border-color;
      border-bottom: 0;
      .title{
        font-size: 16px;
      }
    }
    .title{
      transition: all 0.3s;
      font-size: 18px;
      color: @title-color;
      font-weight: 700;
    }
    .search{
      flex: 1;
      text-align: right;
      margin: 0 24px;
    }
    .actions{
      text-align: right;
      font-size: 17px;
      color: @text-color;
      .action{
        margin: 0 8px;
        cursor: pointer;
        &:hover{
          color: @primary-color;
        }
      }
    }
  }
}
</style>
src/components/table/advance/SearchArea.vue
New file
@@ -0,0 +1,262 @@
<template>
  <div class="search-area" ref="root">
    <div class="search-item" :key="index" v-for="(col, index) in searchCols">
      <div v-if="col.dataType === 'boolean'" class="title active">
        <template v-if="col.title">
          {{col.title}}:
        </template>
        <slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
        <a-switch @change="onSwitchChange" class="switch" v-model="col.search.value" size="small" checked-children="是" un-checked-children="否" />
      </div>
      <div v-else-if="col.dataType === 'time'" class="title active">
        <template v-if="col.title">
          {{col.title}}:
        </template>
        <slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
        <a-time-picker v-model="col.search.value" placeholder="选择时间" @change="(time, timeStr) => onCalendarChange(time, timeStr, col)" @openChange="open => onCalendarOpenChange(open, col)" class="time-picker" size="small" />
      </div>
      <div v-else-if="col.dataType === 'date'" class="title active">
        <template v-if="col.title">
          {{col.title}}:
        </template>
        <slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
        <a-date-picker v-model="col.search.value" @change="onDateChange(col)" class="date-picker" size="small" />
      </div>
      <div v-else-if="col.dataType === 'datetime'" class="title datetime active">
        <template v-if="col.title">
          {{col.title}}:
        </template>
        <slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
        <a-date-picker v-model="col.search.value" @change="(date, dateStr) => onCalendarChange(date, dateStr, col)" @openChange="open => onCalendarOpenChange(open, col)" show-time class="datetime-picker" size="small" />
      </div>
      <div v-else-if="col.dataType === 'select'" class="title active">
        <template v-if="col.title">
          {{col.title}}:
        </template>
        <slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
        <a-select :allowClear="true" :options="col.search.selectOptions" v-model="col.search.value" placeholder="请选择..." @change="onSelectChange(col)" class="select" slot="content" size="small">
        </a-select>
      </div>
      <a-popover v-else @visibleChange="onVisibleChange(col, index)" v-model="col.search.visible" placement="bottom" :trigger="['click']" :get-popup-container="() => $refs.root">
        <div :class="['title', {active: col.search.value}]">
          <template v-if="col.title">
            {{col.title}}
          </template>
          <slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
          <div class="value " v-if="col.search.value">:&nbsp;&nbsp;{{col | searchValue}}</div>
          <a-icon class="icon-down" type="down"/>
        </div>
        <div class="operations" slot="content">
          <a-button @click="onCancel(col)" class="btn" size="small" type="link">取消</a-button>
          <a-button @click="onConfirm(col)" class="btn" size="small" type="primary">确认</a-button>
        </div>
        <div class="search-overlay" slot="title">
          <a-input :id="`${searchIdPrefix}${index}`" :allow-clear="true" @keyup.esc="onCancel(col)" @keyup.enter="onConfirm(col)" v-model="col.search.value" size="default" />
        </div>
      </a-popover>
    </div>
  </div>
</template>
<script>
  import fastEqual from 'fast-deep-equal'
  import moment from 'moment'
  export default {
    name: 'SearchArea',
    props: ['columns'],
    inject: ['table'],
    created() {
      this.columns.forEach(item => {
        this.$set(item, 'search', {...item.search, visible: false, value: item.dataType === 'boolean' ? false : undefined, format: this.getCalendarFormat(item)})
      })
    },
    filters: {
      searchValue(col) {
        if (col.dataType === 'time' && col.search.value) {
          return col.search.value.format('HH:mm:ss')
        }
        return col.search.value
      }
    },
    watch: {
      searchCols(newVal, oldVal) {
        if (newVal.length != oldVal.length) {
          const newConditions = this.getConditions(newVal)
          if (!fastEqual(newConditions, this.conditions)) {
            this.conditions = newConditions
            this.$emit('change', this.conditions)
          }
        }
      }
    },
    data() {
      return {
        conditions: []
      }
    },
    computed: {
      searchCols() {
        return this.columns.filter(item => item.searchAble)
      },
      searchIdPrefix() {
        return this.table.id + '-ipt-'
      }
    },
    methods: {
      onCancel(col) {
        col.search.value = col.search.backup
        col.search.visible = false
      },
      onConfirm(col) {
        col.search.backup = col.search.value
        col.search.visible = false
        const conditions = this.getConditions(this.searchCols)
        if (!fastEqual(conditions, this.conditions)) {
          this.conditions = conditions
          this.$emit('change', this.conditions)
        }
      },
      onSwitchChange() {
        this.conditions = this.getConditions(this.searchCols)
        this.$emit('change', this.conditions)
      },
      onSelectChange() {
        this.conditions = this.getConditions(this.searchCols)
        this.$emit('change', this.conditions)
      },
      onCalendarOpenChange(open, col) {
        col.search.visible = open
        const {momentEqual, getConditions} = this
        const {value, backup, format} = col.search
        if (!open && !momentEqual(value, backup, format)) {
          col.search.backup = moment(value)
          this.conditions = getConditions(this.searchCols)
          this.$emit('change', this.conditions)
        }
      },
      onCalendarChange(date, dateStr, col) {
        const {momentEqual, getConditions} = this
        const {value, backup, format} = col.search
        if (!col.search.visible && !momentEqual(value, backup, format)) {
          col.search.backup = moment(value)
          this.conditions = getConditions(this.searchCols)
          this.$emit('change', this.conditions)
        }
      },
      onDateChange(col) {
        const {momentEqual, getConditions} = this
        const {value, backup} = col.search
        if (!momentEqual(value, backup, 'YYYY-MM-DD')) {
          col.search.backup = moment(value)
          this.conditions = getConditions(this.searchCols)
          this.$emit('change', this.conditions)
        }
      },
      getCalendarFormat(col) {
        const dataType = col.dataType
        switch(dataType) {
          case 'time': return 'HH:mm:ss'
          case 'date': return 'YYYY-MM-DD'
          case 'datetime': return 'YYYY-MM-DD HH:mm:ss'
          default: return col.search && col.search.format
        }
      },
      getConditions(columns) {
        const conditions = {}
        columns.filter(item => item.search.value !== undefined && item.search.value !== '' && item.search.value !== null)
          .forEach(col => {
            conditions[col.dataIndex] = col.search.value
          })
        return conditions
      },
      onVisibleChange(col, index) {
        if (!col.search.visible) {
          col.search.value = col.search.backup
        } else {
          let input = document.getElementById(`${this.searchIdPrefix}${index}`)
          if (input) {
            setTimeout(() => {input.focus()}, 0)
          } else {
            this.$nextTick(() => {
              input = document.getElementById(`${this.searchIdPrefix}${index}`)
              input.focus()
            })
          }
        }
      },
      momentEqual(target, source, format) {
        if (target === source) {
          return true
        } else if (target && source && target.format(format) === source.format(format)) {
          return true
        }
        return false
      }
    }
  }
</script>
<style scoped lang="less">
.search-area{
  margin: -4px 0;
  .search-item{
    margin: 4px 4px;
    display: inline-block;
    .title{
      padding: 4px 8px;
      cursor: pointer;
      border-radius: 4px;
      user-select: none;
      display: inline-flex;
      align-items: center;
      .switch{
        margin-left: 4px;
      }
      .time-picker{
        margin-left: 4px;
        width: 96px;
      }
      .date-picker{
        margin-left: 4px;
        width: 120px;
      }
      .datetime-picker{
        margin-left: 4px;
        width: 195px;
      }
      .value{
        display: inline-block;
        overflow: hidden;
        flex:1;
        max-width: 144px;
        text-overflow: ellipsis;
        word-break: break-all;
        white-space: nowrap;
      }
      &.active{
        background-color: @layout-bg-color;
      }
    }
    .icon-down{
      margin-left: 4px;
      font-size: 12px;
    }
  }
  .search-overlay{
    padding: 8px 0px;
    text-align: center;
  }
  .select{
    margin-left: 4px;
    max-width: 144px;
    min-width: 96px;
  }
  .operations{
    display: flex;
    justify-content: space-between;
    .btn{
    }
  }
}
</style>
src/components/table/advance/index.js
New file
@@ -0,0 +1,2 @@
import AdvanceTable from './AdvanceTable'
export default AdvanceTable
src/pages/components/Table.vue
New file
@@ -0,0 +1,168 @@
<template>
  <div>
    <advance-table
      :columns="columns"
      :data-source="dataSource"
      title="高级表格-Beta"
      :loading="loading"
      rowKey="id"
      @search="onSearch"
      @refresh="onRefresh"
      @reset="onReset"
    >
      <template slot="statusTitle">
        状态<a-icon style="margin: 0 4px" type="info-circle" />
      </template>
      <template slot="send" slot-scope="{text}">
        {{text ? '是' : '否'}}
      </template>
      <template slot="status" slot-scope="{text}">
        {{text | statusStr}}
      </template>
    </advance-table>
  </div>
</template>
<script>
  import AdvanceTable from '@/components/table/advance/AdvanceTable'
  import moment from 'moment'
  const goods = ['运动鞋', 'T恤', '长裤', '短裤']
  const dataSource = []
  const current = new Date().getTime()
  for (let i = 0; i < 100; i++) {
    dataSource.push({
      id: i,
      name: goods[Math.floor((Math.random() * 4))],
      orderId: `${new Date().getTime()}-${Math.floor(Math.random() * 10)}`,
      status: Math.floor((Math.random() * 4) + 1),
      send: (i % 2) === 1,
      sendTime: moment(current -  Math.floor((Math.random() * 8000000))).format('YYYY-MM-DD HH:mm:ss'),
      orderDate: moment(current -  Math.floor((Math.random() * 800000000))).format('YYYY-MM-DD'),
      auditTime: moment(current -  Math.floor((Math.random() * 8000000))).format('HH:mm:ss'),
    })
  }
  export default {
    name: 'Table',
    components: {AdvanceTable},
    filters: {
      statusStr(val) {
        switch (val) {
          case 1: return '已下单'
          case 2: return '已付款'
          case 3: return '已审核'
          case 4: return '已发货'
        }
      }
    },
    data() {
      return {
        loading: false,
        columns: [
          {
            title: '商品名称',
            dataIndex: 'name',
            searchAble: true
          },
          {
            title: '订单号',
            dataIndex: 'orderId'
          },
          {
            searchAble: true,
            dataIndex: 'status',
            dataType: 'select',
            slots: {title: 'statusTitle'},
            scopedSlots: {customRender: 'status'},
            search: {
              selectOptions: [
                {title: '已下单', value: 1},
                {title: '已付款', value: 2},
                {title: '已审核', value: 3},
                {title: '已发货', value: 4}
              ]
            }
          },
          {
            title: '发货',
            searchAble: true,
            dataIndex: 'send',
            dataType: 'boolean',
            scopedSlots: {customRender: 'send'}
          },
          {
            title: '发货时间',
            dataIndex: 'sendTime',
            dataType: 'datetime'
          },
          {
            title: '下单日期',
            searchAble: true,
            dataIndex: 'orderDate',
            dataType: 'date'
          },
          {
            title: '审核时间',
            searchAble: true,
            dataIndex: 'auditTime',
            dataType: 'time',
          },
        ],
        dataSource: dataSource
      }
    },
    methods: {
      onSearch(conditions) {
        this.loading = true
        this.searchGoods(conditions).then(result => {
          this.dataSource = result
          this.loading = false
        })
      },
      onRefresh(conditions) {
        this.loading = true
        this.searchGoods(conditions).then(result => {
          this.dataSource = result
          this.loading = false
        })
      },
      onReset(conditions) {
        this.loading = true
        this.searchGoods(conditions).then(result => {
          this.dataSource = result
          this.loading = false
        })
      },
      async searchGoods(conditions) {
        const promise = new Promise((resolve, reject) => {
          try {
            const result = dataSource.filter(item => {
              for (let key of Object.keys(conditions)) {
                if (key === 'sendTime') {
                   if (conditions[key].format('YYYY-MM-DD HH:mm:ss') !== item[key]) return false
                } else if (key === 'orderDate') {
                  if (conditions[key].format('YYYY-MM-DD') !== item[key]) return false
                } else if (key === 'auditTime') {
                  if (conditions[key].format('HH:mm:ss') !== item[key]) return false
                } else if (item[key] !== conditions[key]) {
                  return false
                }
              }
              return true
            })
            setTimeout(() => {
              resolve(result)
            }, 300)
          } catch (e) {
            reject(e)
          }
        })
        return promise
      }
    }
  }
</script>
<style scoped>
</style>
src/router/config.js
@@ -208,6 +208,11 @@
              path: 'palette',
              name: '颜色复选框',
              component: () => import('@/pages/components/Palette')
            },
            {
              path: 'table',
              name: '高级表格',
              component: () => import('@/pages/components/Table')
            }
          ]
        },