<!--
 * @Author: your name
 * @Date: 2020-09-08 16:22:26
 * @LastEditTime: 2021-02-04 16:52:15
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: \dbadmin\src\components\TableForm\Table.vue
-->
<template>
  <div class="table__component" ref="root" :style="rootStyle">
    <el-button-group>
      <!-- 普通功能按钮插槽 -->
      <slot
        name="btns"
        :selectable="multiselect && selectedRows.length > 0"
        :disabled="!multiselect || selectedRows.length === 0"
        :selectrows="selectedRows"
        :multiStepAction="multiStepReq"
      ></slot>

      <!-- 数据导出按钮插槽 -->
      <slot v-if="allowExport" name="export-btn" :disabled="selectedRows.length === 0" :action="exportData" :loading="exporting"></slot>
    </el-button-group>

    <el-table
      :data="list"
      @selection-change="onSelectionChange"
      @current-change="$emit('current-change', $event)"
      ref="table"
      :height="tableHeight"
      v-loading="loading"
      :span-method="SpanMethod"
      :default-expand-all="expandAll"
      :class="tableClass"
      v-bind="tableOptions"
    >
      <el-table-column v-if="enableDrag" v-bind="dragColumnOptions">
        <i slot-scope="{ row }" class="el-icon-s-fold handle" style="font-size: 16px; cursor: move"></i>
      </el-table-column>
      <el-table-column type="selection" v-if="multiselect || allowExport" />
      <slot></slot>
    </el-table>

    <!-- 分页 -->
    <div v-if="visiblePage" class="page">
      <el-pagination
        :total="total"
        :page-size="size"
        background
        layout="sizes, prev, pager, next"
        :current-page.sync="currentPage"
        :page-sizes="[...(isDev ? [1, 2, 5] : []), 10, 20, 50, 100, 200]"
        @size-change="onSizeChange"
      ></el-pagination>
    </div>
  </div>
</template>

<script>
import { get, del as RequestDelete } from '@/utils/request'
import Vue from 'vue'
import { Component, Prop, Watch, Ref, ProvideReactive, Provide } from 'vue-property-decorator'
import { toTime, toDateTime, toDate } from '@/utils/util'
import { downloadXlsx } from '@/utils/excel'
import { debounce } from 'underscore'
import { getModule } from 'vuex-module-decorators'
import QueryStore, { DEFAULT_TAG as DEFAULT_QUERY_TAG } from '@/store/modules/query'
import Sortable from 'sortablejs'

// Table组件只读取不写vuex里面的查询参数，但会修改分页参数（入currentPage，pageSize）
import store from '@/store'
const queryStore = getModule(QueryStore, store)

@Component({
  name: 'Table'
})
export default class Table extends Vue {
  @Prop({ type: Boolean, default: false })
  enableDrag

  @Prop({ type: Object, default: () => ({}) })
  dragColumnOptions

  @Prop({ type: Object, default: () => ({}) })
  tableOptions

  @Prop({ type: Boolean, default: false })
  expandAll

  @Prop({ type: String })
  tableClass

  @Prop({ type: Boolean, default: false })
  multiselect

  @Prop({ type: Boolean, default: true })
  visiblePage

  @Prop({ type: Function })
  responseHandler

  /**
   * 是否允许导出数据
   */
  @Prop({ type: Boolean, default: false })
  allowExport

  /**
   * 导出数据时需要特殊处理的字段
   * {
   *  [字段名]:处理方式
   * }
   */
  @Prop({ type: Object, default: () => ({}) })
  exportSpecialField

  @Prop({ type: String, required: true })
  api

  @Prop({ type: Function })
  SpanMethod

  @Prop({ type: [String, Number] })
  height

  @Prop({ type: String, default: DEFAULT_QUERY_TAG })
  tag

  @Ref('table')
  tableRef

  @Ref('root')
  rootRef

  /**
   * 当前页码
   */
  // currentPage = 1

  get currentPage() {
    if (!this.pageData) return 1
    if (!this.pageData.page) this.pageData.page = 1
    return this.pageData.page
  }

  set currentPage(page) {
    if (!this.pageData) return
    this.pageData.page = page
    return page
  }

  // size = 20

  get size() {
    if (!this.pageData) return 20
    if (!this.pageData.size) this.pageData.size = 20
    return this.pageData.size
  }

  set size(size) {
    if (!this.pageData) return
    this.pageData.size = size
    return size
  }

  /**
   * 列表数据
   */
  @ProvideReactive('table_data')
  list = []

  /**
   * 列表数据是否加载中
   */
  loading = false

  /**
   * 列表数据总长度
   */
  total = 1

  /**
   * 选中的行
   */
  selectedRows = []

  /**
   * 数据导出处理中
   */
  exporting = false

  rootHeight = null

  tableHeightData = 50

  get tableHeight() {
    if (this.propHeightOk) {
      return this.height
    }

    return this.tableHeightData
  }

  get propHeightOk() {
    return ['string', 'number'].includes(typeof this.height)
  }

  get rootStyle() {
    const style = {}
    if (this.rootHeight) {
      style.height = `${this.rootHeight}px`
    }

    return style
  }

  pageData = {}

  get path() {
    return this.$route.path
  }

  async created() {
    queryStore.init({ path: this.path, tag: this.tag })
    queryStore.registerFetch({ path: this.path, func: this.fetch, tag: this.tag })

    this.$set(this, 'pageData', queryStore.pageData[this.$route.path][this.tag])
    await this.fetch()
    this.resizeHeight()
  }

  async mounted() {
    this.initSortable()
    window.addEventListener('resize', this.resizeHeight)
    await this.$nextTick()
  }

  initSortable() {
    if (!this.enableDrag) return
    const tbody = this.tableRef.$el.querySelector('.el-table__body-wrapper table tbody')
    if (!tbody) return

    const onEnd = async event => {
      const list = this.list.splice(0)
      await this.$nextTick()
      const { oldIndex, newIndex } = event
      const oldObj = list[oldIndex]
      const newObj = list[newIndex]
      oldObj.sort = newIndex
      newObj.sort = oldIndex
      list[oldIndex] = newObj
      list[newIndex] = oldObj
      this.list.push(...list)
      this.$emit('sort', this.list)
      this.$emit('sort-change', { newObj, oldObj })
    }

    new Sortable(tbody, {
      group: `TableComponent:${this._uid}`,
      handle: '.el-icon-s-fold.handle',
      draggable: 'tr.el-table__row',
      onEnd
    })
  }

  @Watch('api')
  onWatchApiChange(api) {
    this.currentPage = 1
    this.fetch()
  }

  beforeDestroy() {
    window.removeEventListener('resize', this.resizeHeight)
    queryStore.unregisterFetch(this.path, this.tag)
  }

  async setHeight() {
    if (this.propHeightOk) return
    if (!this.tableRef) await this.$nextTick()
    if (!this.tableRef || !this.tableRef.$el) return
    this.rootHeight = document.body.clientHeight - this.tableRef.$el.offsetTop - 60
    this.tableHeightData = this.rootHeight - 85
  }

  resizeHeight = debounce(this.setHeight.bind(this), 300)

  @Watch('currentPage')
  /**
   * 页码有变化时获取数据
   */
  onWatchCurrentPageChange(page) {
    this.fetch()
  }

  onSizeChange(size) {
    this.size = size
    this.currentPage = 1
    this.fetch()
  }

  onSelectionChange(rows) {
    this.$emit('selection-change', rows)
    this.$set(this, 'selectedRows', rows)
  }

  /**
   * 导出数据
   * @param {string} name 工作薄名
   * @param {string} filename 文件名
   * @param {Array} 英文字段-中文转换表
   */
  exportData(name, filename, fields) {
    if (this.exporting) return
    this.exporting = true
    const data = this.selectedRows.map(item => {
      const result = {}

      fields.forEach(([en, cn]) => {
        result[cn] = item[en]

        // 特殊处理的字段
        if (en in this.exportSpecialField) {
          const type = this.exportSpecialField[en]
          switch (type) {
            case 'time':
              result[cn] = toTime(result[cn])
              return
            case 'date-time':
              result[cn] = toDateTime(result[cn])
              return
            case 'date':
              result[cn] = toDate(result[cn])
              return
            default:
              break
          }
        } else if (typeof result[cn] === 'boolean') {
          result[cn] = result[cn] ? '是' : '否'
        }
      })

      return result
    })
    downloadXlsx(
      {
        [name]: data
      },
      filename
    )
    this.exporting = false
  }

  /**
   * 获取列表数据
   */
  @Provide('fetch')
  async fetch(query) {
    if (this.loading) return
    this.loading = true
    let data, params

    if (query) {
      this.currentPage = 1
      params = query
    } else {
      params = queryStore.data[this.path][this.tag]
    }

    Object.entries(params).forEach(([key, value]) => {
      if ([undefined, NaN, null, ''].includes(value) || key === '__ob__') {
        delete params[key]
      }
    })

    const page_params = {}

    if (this.visiblePage) {
      page_params.limit = this.size
      page_params.page = this.currentPage
    }

    try {
      data = await get(this.api, {
        params: {
          ...params,
          ...page_params
        }
      })
      this.$emit('response', data)
    } catch (err) {
      this.loading = false
      return
    }
    this.total = data.count
    this.currentPage = data.page || data.currentPage
    const list = data.results.map(detail => {
      if (detail.created_time) {
        detail.created_time = toDateTime(detail.created_time)
      }
      if (detail.date_joined) {
        detail.date_joined = toDateTime(detail.date_joined)
      }
      if (detail.release_time) {
        detail.release_time = toDateTime(detail.release_time)
      }
      if (this.responseHandler) return this.responseHandler(detail)
      return detail
    })
    this.$emit('fetch-success', list)
    this.list.splice(0)
    this.$set(this, 'list', list)
    await this.$nextTick()
    this.$emit('get-table-ref', this.$refs.table)
    this.loading = false
  }

  /**
   * 下一页
   */
  nextPage() {
    this.currentPage++
  }

  /**
   * 上一页
   */
  prevPage() {
    this.currentPage--
  }

  /**
   * 重置当前页码
   */
  resetPage() {
    this.currentPage = 1
  }

  /**
   * 切换某行某列的布尔值
   * @param {number} rowIndex 行
   * @param {string} name 字段
   * @param {boolean} status 修改的布尔值
   */
  toggleColumnBoolean(rowIndex, name, status) {
    const detail = this.list[rowIndex]
    if (!detail) throw new Error(`列${rowIndex}不存在`)
    if (typeof detail[name] !== 'boolean') throw new Error(`列${rowIndex}-键${name}不是布尔值`)
    const oldValue = detail[name]
    detail[name] = status
    this.$emit('column-bool-change', {
      row: rowIndex,
      name,
      value: status,
      oldValue
    })
    return status
  }

  /**
   * 设置某行某列的值
   * @param {number} rowIndex 行
   * @param {string} name 字段
   * @param {any} value 修改的值
   */
  setColumnValue(rowIndex, name, value) {
    const detail = this.list[rowIndex]
    if (!detail) throw new Error(`列${rowIndex}不存在`)
    const oldValue = detail[name]
    detail[name] = value
    this.$emit('column-value-change', {
      row: rowIndex,
      name,
      value,
      oldValue
    })
    return value
  }

  /**
   * 格式化日期对象
   * @param {string|Date} date 日期对象/字符串
   * @returns {string}
   */
  formatDate(date, time = true) {
    return time ? toDateTime(date) : toDate(date)
  }

  /**
   * 格式化日期对象
   * @param {string|Date} date 日期对象/字符串
   * @returns {string}
   */
  formatTime(date) {
    return toTime(date)
  }

  /**
   * 调用删除接口
   * @param {string} api 接口地址
   * @param {string} message 提示消息
   * @returns {Promise<boolean>} 是否删除成功
   */
  async del(api, message = '是否确认删除') {
    await this.$confirm(message)
    try {
      await RequestDelete(api)
    } catch (err) {
      console.error(`
Table组件调用删除接口失败
接口地址   ${api}
原因：
${err.message || err}
`)
      return false
    }
    this.fetch()
    return true
  }

  async multiStepReq(ids, req, options = {}) {
    const baseOptions = {
      confirmMessage: '是否确认此操作？',
      successMessage: '批量操作成功'
    }
    options = Object.assign({}, baseOptions, options)
    await this.$confirm(options.confirmMessage)
    for (const id of ids) {
      await req(id, this.selectedRows)
    }
    this.$message.success(options.successMessage)
    this.fetch()
  }
}
</script>

<style lang="scss" scoped>
.page {
  display: flex;
  justify-content: center;
  margin-top: 1em;
}
</style>

<style lang="scss">
.mo3 {
  overflow: hidden;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  display: -webkit-box;
  text-overflow: ellipsis;
}

.el-tooltip__popper {
  max-width: 500px;
}
</style>
