<template>
  <!-- 不要在这个组件添加或修改业务相关的代码，这个组件会在更新时被覆盖 -->
  <!-- 绝大部分的更改都可以通过组件的输入参数配置 -->
  <div class="easy-table">
    <el-table
      ref="table"
      v-loading="loading"
      :data="tableData"
      :row-key="_tableConfig.rowKey"
      v-bind="{
        cellStyle: (tableScope)=>{
          return getDefaultFieldStyle('cell',tableScope)
        },
        headerCellStyle: (tableScope)=>{
          return getDefaultFieldStyle('headerCell',tableScope)
        },
        ...(getPropValue(_tableConfig.tableProps) || {}),
        class: {
          ...((getPropValue(_tableConfig.tableProps) || {}).class || {}),
          'disable-select-all': getPropValue(_tableConfig.disableSelectAll)
        }
      }"
      @select="(selection) => {
        $emit('update:selected', selection)
      }"
      @select-all="(selection) => {
        $emit('update:selected', selection)
      }"
      @cell-click="
        (row, column, cell, event) => {
          callFieldListeners('click', row, column, cell, event)
        }
      "
      @cell-dblclick="
        (row, column, cell, event) => {
          callFieldListeners('dblclick', row, column, cell, event)
        }
      "
      @cell-contextmenu="
        (row, column, cell, event) => {
          callFieldListeners('contextmenu', row, column, cell, event)
        }
      "
      v-on="getPropValue(_tableConfig.tableListeners)"
    >
      <template v-for="name in _tableConfig.sortCol || ['selection','index']">
        <el-table-column
          v-if="name === 'selection' && getPropValue(_tableConfig.selection)"
          :key="name"
          type="selection"
          :min-width="getPropValue(_tableConfig.tableSelectionProps,[],{}).minWidth || getAutoColWidth(getPropValue(_tableConfig.selectionAutoColWidth, [], 0))"
          v-bind="{
            width: _tableConfig.selectionWidth,
            align: 'center',
            headerAlign: 'center',
            ..._tableConfig.publicTableProps,
            ..._tableConfig.tableSelectionProps
          }"
        />
        <el-table-column
          v-if="name === 'index' && getPropValue(_tableConfig.showIndex)"
          :key="name"
          :index="(index)=>{
            const defaultIndex = defaultIndexMethod(index)
            return typeof _tableConfig.indexMethod === 'function' ? _tableConfig.indexMethod(index, componentScope,defaultIndex) : defaultIndex
          }"
          type="index"
          :min-width="getPropValue(_tableConfig.tableIndexProps,[],{}).minWidth || getAutoColWidth(getPropValue(_tableConfig.indexAutoColWidth, [], 0))"
          v-bind="{
            width: _tableConfig.indexWidth,
            align: 'center',
            headerAlign: 'center',
            label: '序号',
            ..._tableConfig.publicTableProps,
            ..._tableConfig.tableIndexProps
          }"
        />
      </template>

      <el-table-column
        v-if="$scopedSlots['el-expand'] || $slots['el-expand']"
        type="expand"
      >
        <template slot-scope="scope">
          <slot
            v-bind="{
              ...componentScope,
              ...scope
            }"
            :name="'el-expand'"
            :isSelected="selected.includes(scope.row)"
          />
        </template>
      </el-table-column>
      <template
        v-if="$scopedSlots['el-empty'] || $slots['el-empty']"
        v-slot:empty
      >
        <slot
          v-bind="componentScope"
          :name="'el-empty'"
        />
      </template>
      <template v-for="field in _fields">
        <el-table-column
          :key="field.prop"
          :prop="field.prop"
          :label="getPropValue(field.label)"
          :min-width="getPropValue(field.tableProps,[],{}).minWidth || getAutoColWidth(getPropValue(field.autoColWidth, [], 0)) || getPropValue(_tableConfig.publicTableProps,[],{}).minWidth"
          v-bind="{
            showOverflowTooltip: getPropValue(field.type) === 'textarea',
            ..._tableConfig.publicTableProps,
            ...getPropValue(field.tableProps,[],{})
          }"
        >
          <template v-slot:header="scope">
            <slot
              v-if="getPropValue(field.headerSlotName)"
              :field="field"
              :name="getPropValue(field.headerSlotName)"
              :componentScope="componentScope"
              :formatter="createFormatter(field, '', scope)"
              v-bind="scope"
            />
            <span v-else :class="`easy-table-header-cell-${field.prop}`">
              {{ getPropValue(field.label) }}
            </span>
          </template>
          <template v-slot:default="scope">
            <slot
              v-if="getPropValue(field.type) === 'slot' || slotMode"
              v-bind="scope"
              :field="field"
              :name="slotMode ? field.prop : (getPropValue(field.slotName) || field.prop)"
              :componentScope="componentScope"
              :isSelected="selected.includes(scope.row)"
              :formatter="createFormatter(field, '', scope)"
            />
            <component
              :is="getPropValue(field.is)"
              v-else-if="getPropValue(field.type) === 'component'"
              :value="createFormatter(field, 'el-table', scope)(scope.row, scope.column, scope.row[field.prop], scope.$index)"
              v-bind="getPropValue(field.formProps)"
              v-on="getPropValue(field.formListeners)"
            />
            <span v-else :class="`easy-table-cell-${field.prop}`">
              {{
                createFormatter(field, 'el-table', scope)(scope.row, scope.column, scope.row[field.prop], scope.$index)
              }}
            </span>
          </template>
        </el-table-column>
      </template>

      <el-table-column
        v-if="buttons.length > 0 && getPropValue(_tableConfig.showHandler) !== false"
        :min-width="getPropValue(_tableConfig.tableHandlerProps,[],{}).minWidth || getAutoColWidth(getPropValue(_tableConfig.handlerAutoColWidth, [], 0))"
        v-bind="{
          className: `table-handler`,
          fixed: 'right',
          width: getPropValue(_tableConfig.handlerWidth),
          ...(getPropValue(_tableConfig.tableHandlerProps) || {})
        }"
      >
        <template slot="header" slot-scope="scope">
          <template v-for="button in _buttons.filter((item)=>{return item.position === 'header'})">
            <el-button
              v-if="getPropValue(button.show,[scope]) !== false"
              :key="getPropValue(button.label,[scope])"
              :loading="getPropValue(button.loading,[scope])"
              :type="getPropValue(button.type,[scope])"
              :size="getPropValue(button.size,[scope])"
              :disabled="getPropValue(button.disabled,[scope])"
              v-bind="getPropValue(button.buttonProps)"
              @click="callButtonHandler(button,scope)"
            >{{ getPropValue(button.label,[scope]) }}</el-button>
          </template>
          <span v-if="buttons.filter((item)=>{return item.position === 'header'}).length === 0">
            {{ getPropValue(_tableConfig.tableHandlerProps,[],{}).label || '操作' }}
          </span>
        </template>
        <template slot-scope="scope">
          <template v-for="button in _buttons.filter((item)=>{return item.position !== 'header'})">
            <el-button
              v-if="getPropValue(button.show,[scope]) !== false"
              :key="getPropValue(button.label,[scope])"
              :loading="button.loading && scope.row._easytableLoading"
              :type="getPropValue(button.type,[scope])"
              :size="getPropValue(button.size,[scope])"
              :disabled="getPropValue(button.disabled,[scope])"
              v-bind="getPropValue(button.buttonProps)"
              @click="callButtonHandler(button,scope)"
            >{{ getPropValue(button.label,[scope]) }}</el-button>
          </template>
        </template>
      </el-table-column>
    </el-table>
    <el-row v-if="_tableConfig.showPagination" class="pagination-con">
      <el-col :span="24" class="pagination-con-col">
        <slot
          :pageParam="pageParam"
          :total="total"
          :onPaginationChange="onPaginationChange"
          :name="_tableConfig.paginationSlotName || 'pagination'"
          v-bind="componentScope"
        >
          <el-pagination
            :page-size="pageParam.size"
            :current-page="pageParam.current"
            :total="total"
            :page-sizes="[10, 20, 30, 40, 50, 100, 200]"
            layout="slot, total, sizes, prev, pager, next, jumper"
            @size-change="(size)=>{pageParam.size = size;onPaginationChange()}"
            @current-change="(current)=>{pageParam.current = current;onPaginationChange()}"
          >
            <span class="page-info">
              第 {{ pageParam.current }} 页 /
              共 {{ (total !== 0 ? parseInt((total + pageParam.size - 1)/pageParam.size) : 1) }} 页
            </span>
          </el-pagination>
        </slot>
      </el-col>
    </el-row>
  </div>
</template>

<script>
import { tableConfig } from '../globalConfig'
import { deepClone, parseDate } from '../utils'
import { getOptionsByBaseCode, OptionsMap } from '../optionsManager'
export default {
  name: 'EasyTable',
  props: {
    fields: {
      type: Array,
      default: () => {
        return []
      }
    },
    tableConfig: {
      type: Object,
      default: () => {
        return {}
      }
    },
    buttons: {
      type: Array,
      default: () => {
        return []
      }
    },
    emitLoadOnCreate: {
      type: Boolean,
      default: () => {
        return false
      }
    },
    onLoad: {
      type: Function,
      default(pageParam, instance) {
        return {
          tableData: this.tableData,
          total: this.total
        }
      }
    },
    value: {
      type: Array,
      default: () => {
        return []
      }
    },
    selected: {
      type: Array,
      default: () => {
        return []
      }
    },
    meta: {
      type: Object,
      default: () => {
        return {}
      }
    },
    slotMode: { // 所有表各项都使用默认插槽渲染
      type: Boolean,
      default: () => {
        return false
      }
    }
  },
  data() {
    return {
      pageParam: {
        current: 1,
        size: 10
      },
      total: 0,
      loading: false,
      onPaginationChangeId: null,
      OptionsMap // 此处引用只是为了能让vue检测到变动以更新视图
    }
  },
  computed: {
    tableData: {
      get() {
        return this.value
      },
      set(value) {
        this.$emit('input', value)
      }
    },
    componentScope() {
      return {
        tableData: this.tableData,
        tableConfig: this._tableConfig,
        pageParam: this.pageParam,
        total: this.total,
        tableInstance: this,
        meta: this.meta,
        label2propMap: this.label2propMap
      }
    },
    label2propMap() {
      return this._fields.reduce((a, b) => {
        a[b.label] = b.prop
        return a
      }, {})
    },
    _fields() {
      return this.fields.filter((item) => {
        return this.getPropValue(item.showInTable) !== false
      }).sort((a, b) => {
        return a.tableSort - b.tableSort
      })
    },
    autoColWidthTotal() {
      let total = 0
      if (this.getPropValue(this._tableConfig.showIndex)) {
        const indexAutoColWidth = this.getPropValue(this._tableConfig.indexAutoColWidth, [], 0)
        total += indexAutoColWidth
      }
      if (this.getPropValue(this._tableConfig.selection)) {
        const selectionAutoColWidth = this.getPropValue(this._tableConfig.selectionAutoColWidth, [], 0)
        total += selectionAutoColWidth
      }
      if (this.buttons.length > 0 && this.getPropValue(this._tableConfig.showHandler) !== false) {
        const handlerAutoColWidth = this.getPropValue(this._tableConfig.handlerAutoColWidth, [], 0)
        total += handlerAutoColWidth
      }
      return this._fields.map((field) => {
        const autoColWidth = this.getPropValue(field.autoColWidth, [], 0)
        return autoColWidth
      }).reduce((a, b) => {
        return a + b
      }, total)
    },
    _tableConfig() {
      return {
        ...tableConfig,
        ...this.tableConfig
      }
    },
    _buttons() {
      for (const item of this.buttons) {
        this.$set(item, 'loading', item.loading === undefined ? false : item.loading)
      }
      return this.buttons
    }
  },
  watch: {
    selected: {
      handler(value) {
        this.setSelected(value, true)
      }
    }
  },
  created() {
    if (this.emitLoadOnCreate) {
      this.onPaginationChange()
    }
    if (this.value.length === 0 && this._tableConfig.createRandomData) {
      const num = typeof this._tableConfig.createRandomData === 'number' ? this._tableConfig.createRandomData : 5
      for (let i = 0; i < num; i++) {
        const item = {}
        for (const field of this._fields) {
          if (field.type === 'date-picker') {
            item[field.prop] = `2233-12-13`
          } else {
            item[field.prop] = `${this.getPropValue(field.label)}-${i}`
          }
        }
        this.value.push(item)
      }
    }
  },
  mounted() {
    this.setSelected(this.selected)
  },
  methods: {
    getDefaultFieldStyle(styleScope, tableScope) {
      return this.getPropValue(
        this.fields.find((item) => {
          return item.prop === tableScope.column.property
        })?.styles?.[styleScope],
        [tableScope]
      )
    },
    getPropValue(prop, args = [], defaultValue) {
      const result = typeof prop === 'function' ? prop(this.componentScope, ...args) : prop
      return result !== undefined ? result : defaultValue
    },
    getAutoColWidth(autoColWidth) {
      if (autoColWidth) {
        return autoColWidth / this.autoColWidthTotal * 100
      } else {
        return undefined
      }
    },
    getFormProps(field, name) {
      return (this.getPropValue(field.formProps) || {})[name]
    },
    createFormatter(field, type, tableScope) {
      const baseFormatter = (cellValue) => {
        if (field.tableProps?.formatter) {
          return field.tableProps?.formatter(tableScope.row, tableScope.column, cellValue, tableScope.$index)
        }
        const baseCodeOptions = field.optionsBaseCode ? getOptionsByBaseCode(this.getPropValue(field.optionsBaseCode)) : []
        const formatterMap = field.options ? (this.getPropValue(field.options, [tableScope, baseCodeOptions]) || []) : baseCodeOptions
        const target = formatterMap.find((item) => {
          return item.value === cellValue
        })
        if (target) {
          return target.label
        }
        if (cellValue && this.getPropValue(field.type, [tableScope]) === 'date-picker') {
          return parseDate(cellValue, this.getFormProps(field, 'format') || 'yyyy-MM-dd') || cellValue
        }
        const valueSplitChar = this.getPropValue(field.valueSplitChar)
        if (Array.isArray(cellValue) || (valueSplitChar && typeof cellValue === 'string')) {
          const values = Array.isArray(cellValue) ? cellValue : cellValue.split(valueSplitChar)
          return values.map((cellValueItem) => {
            return (formatterMap.find((item) => {
              return item.value === cellValueItem
            }) || {}).label || cellValueItem
          }).join('、')
        }
        return cellValue
      }
      if (type === 'el-table') {
        return (row, column, cellValue, index) => {
          return baseFormatter(cellValue)
        }
      } else {
        return baseFormatter
      }
    },
    defaultIndexMethod(index) {
      return (this.pageParam.current - 1) * this.pageParam.size + index + 1
    },
    onPaginationChange() {
      const onPaginationChangeId = (new Date()).valueOf()
      this.onPaginationChangeId = onPaginationChangeId
      this.loading = true
      return Promise.resolve(this.onLoad(this.pageParam, this)).then((data) => {
        if ((data && Array.isArray(data.tableData)) || Array.isArray(data)) {
          return data
        } else {
          console.warn('[EasyTable] onLoad参数指定的方法未返回有效数据: { tableData: Array, total?: number } | Array', data)
          return {
            tableData: [],
            total: 0
          }
        }
      }).then((result) => {
        if (onPaginationChangeId !== this.onPaginationChangeId) {
          return
        }
        this.tableData.splice(0, this.tableData.length)
        if (Array.isArray(result)) {
          this.tableData.push(...result)
          this.total = result.length
        } else {
          const { tableData = [], total } = result
          this.tableData.push(...tableData)
          this.total = typeof total === 'number' ? total : tableData.length
        }
      }).catch((e) => {
        this.tableData.splice(0, this.tableData.length)
        this.total = 0
        console.error(e)
      }).finally(() => {
        if (onPaginationChangeId !== this.onPaginationChangeId) {
          return
        }
        this.loading = false
      })
    },
    callButtonHandler(button, tableScope) {
      if (tableScope.row) {
        this.$set(tableScope.row, '_easytableLoading', true)
      }
      this.$set(button, 'loading', true)
      try {
        const result = button.handler(this.componentScope, tableScope, button)
        if (result instanceof Promise) {
          result.finally(() => {
            if (tableScope.row) {
              this.$set(tableScope.row, '_easytableLoading', false)
            }
            this.$set(button, 'loading', false)
          })
        } else {
          if (tableScope.row) {
            this.$set(tableScope.row, '_easytableLoading', false)
          }
          this.$set(button, 'loading', false)
        }
      } catch (e) {
        console.error(e)
        if (tableScope.row) {
          this.$set(tableScope.row, '_easytableLoading', false)
        }
        this.$set(button, 'loading', false)
      }
    },
    callFieldListeners(type, row, column, cell, event) {
      for (const field of this.fields) {
        if (field.prop === column.rawColumnKey) {
          field.tableListeners?.[type]?.(row, column, cell, event)
        }
      }
    },
    reset() {
      this.$set(this, 'pageParam', {
        current: 1,
        size: 10
      })
      return this.onPaginationChange()
    },
    setSelected(selected, dontUpdateProps = false) {
      this.$refs?.table.clearSelection()
      if (!dontUpdateProps) {
        this.$emit('update:selected', selected)
      }
      for (const row of this.value) {
        this.$refs?.table.toggleRowSelection(row, selected.includes(row))
      }
    },
    getSelected() {
      return deepClone(this.selected)
    }
  }
}
</script>

<style lang="scss" scoped>
/**
  组件样式请在src/styles文件夹新建样式文件进行修改
*/
.pagination-con{
  margin-top: 20px;
  .pagination-con-col{
    text-align: right;
    .el-pagination{
      display: flex;
      align-items: center;
      justify-content: flex-end;
      .page-info{
        margin-right: 10px;
      }
      ::v-deep(.el-pagination__sizes){
        height: unset;
      }
    }
  }
}
.el-table.disable-select-all{
  & > ::v-deep(.el-table__header-wrapper){
    .el-table-column--selection{
      .el-checkbox{
        display: none;
      }
    }
  }
}
</style>
