<template>
    <div :class="wrapClasses" :style="styles">
        <div :class="classes">
            <div 
                ref="title"
                v-if="showSlotHeader" 
                :class="[prefixCls + '-title', prefixCls + '-frame-header']">
                <slot name="header"></slot>
            </div>
            <div :class="[prefixCls + '-frame-body']">
                <div :class="[prefixCls + '-frame-body-left']" v-if="isLeftFixed">
                    <div
                        :class="[prefixCls + '-fixed']" 
                        :style="fixedTableStyle">
                        <div 
                            v-if="showHeader"
                            :class="fixedHeaderClasses">
                            <TableHead
                                fixed="left"
                                :border="border"
                                :prefix-cls="prefixCls"
                                :styleObject="fixedTableStyle"
                                :columns="leftFixedColumns"
                                :column-rows="columnRows"
                                :fixed-column-rows="leftFixedColumnRows"
                                :obj-data="objData"
                                :columns-width="columnsWidth"
                                :data="rebuildData"/>
                        </div>
                        <div 
                            ref="fixedBody"
                            :class="[prefixCls + '-fixed-body']" 
                            :style="fixedBodyStyle" 
                            @mousewheel="handleFixedMousewheel" 
                            @DOMMouseScroll="handleFixedMousewheel">
                            <TableBody
                                fixed="left"
                                :draggable="draggable"
                                :prefix-cls="prefixCls"
                                :styleObject="fixedTableStyle"
                                :columns="leftFixedColumns"
                                :data="rebuildData"
                                :columns-width="columnsWidth"
                                :obj-data="objData"/>
                        </div>
                    </div>
                </div>
                <div :class="[prefixCls + '-frame-body-main', {'right-fixed': isRightFixed}]">
                    <div 
                        ref="header"
                        v-if="showHeader" 
                        :class="[prefixCls + '-header']" 
                        @mousewheel="handleMouseWheel">
                        <TableHead
                            :prefix-cls="prefixCls"
                            :border="border"
                            :styleObject="tableHeaderStyle"
                            :columns="mainColumns"
                            :column-rows="columnRows"
                            :obj-data="objData"
                            :columns-width="columnsWidth"
                            :data="rebuildData"/>
                    </div>
                    <div 
                        ref="body" 
                        v-if="!((!!localeNoDataText && (!data || data.length === 0)) || (!!localeNoFilteredDataText && (!rebuildData || rebuildData.length === 0)))"
                        :class="[prefixCls + '-body', 'scroll-content-horizonal']" 
                        :style="bodyStyle">
                        <TableBody
                            ref="tbody"
                            :draggable="draggable"
                            :prefix-cls="prefixCls"
                            :styleObject="tableStyle"
                            :columns="mainColumns"
                            :data="rebuildData"
                            :columns-width="columnsWidth"
                            :obj-data="objData"/>
                    </div>
                    <div
                        ref="body" 
                        :class="[prefixCls + '-tip', 'scroll-content-horizonal']" 
                        v-else
                        :style="bodyStyle">
                        <table cellspacing="0" cellpadding="0" border="0">
                            <tbody>
                                <tr>
                                    <td 
                                        :style="{'height':parseInt(bodyStyle.height) - 2 + 'px','width':`${this.headerWidth}px`}">
                                        <span v-html="localeNoDataText" v-if="!data || data.length === 0"></span>
                                        <span v-html="localeNoFilteredDataText" v-else></span>
                                    </td>
                                </tr>
                            </tbody>
                        </table>
                    </div>
                </div>
                <div :class="[prefixCls + '-frame-body-right']" v-if="isRightFixed">
                    <div 
                        :class="[prefixCls + '-fixed-right']" 
                        :style="fixedRightTableStyle">
                        <div :class="fixedHeaderClasses" v-if="showHeader">
                            <TableHead
                                fixed="right"
                                :border="border"
                                :prefix-cls="prefixCls"
                                :styleObject="fixedRightTableStyle"
                                :columns="rightFixedColumns"
                                :column-rows="columnRows"
                                :fixed-column-rows="rightFixedColumnRows"
                                :obj-data="objData"
                                :columns-width="columnsWidth"
                                :data="rebuildData"/>
                        </div>
                        <div 
                            ref="fixedRightBody" 
                            :class="[prefixCls + '-fixed-body']" 
                            :style="fixedBodyStyle" 
                            @mousewheel="handleFixedMousewheel" 
                            @DOMMouseScroll="handleFixedMousewheel">
                            <TableBody
                                fixed="right"
                                :draggable="draggable"
                                :prefix-cls="prefixCls"
                                :styleObject="fixedRightTableStyle"
                                :columns="rightFixedColumns"
                                :data="rebuildData"
                                :columns-width="columnsWidth"
                                :obj-data="objData"/>
                        </div>
                    </div>
                    <!-- <div 
                        v-if="isRightFixed"
                        :class="[prefixCls + '-fixed-right-header']" 
                        :style="fixedRightHeaderStyle"></div> -->
                </div>
            </div>
            <div 
                ref="footer"
                v-if="showSlotFooter"
                :class="[prefixCls + '-footer', prefixCls + '-frame-footer']"><slot name="footer"></slot></div>
        </div>
        <div 
            ref="resizeProxy" 
            v-show="resizeProxyVisible"
            :class="prefixCls + '-resize-proxy'"></div>
        <Loading mask theme="light" v-model="loading"></Loading>
    </div>
</template>
<script>
    import PerfectScrollbar from 'perfect-scrollbar'
    import TableHead from './table-head'
    import TableBody from './table-body'
    import Loading from '../../base/loading/'
    import Locale from '../../../mixins/locale'
    import elementResizeDetectorMaker from 'element-resize-detector'
    import { oneOf, getStyle, deepCopy, getScrollBarSize } from '../../../utils/assist'
    import { on, off } from '../../../utils/dom'
    import { getAllColumns, convertToRows, convertColumnOrder, getRandomStr } from './util'

    const prefixCls = 'yy-private-table'
    let rowKey = 1
    let columnKey = 1
    export default {
        name: 'YYPrivateTable',
        mixins: [Locale],
        components: {
            TableHead,
            TableBody,
            Loading
        },
        provide() {
            return {
                tableRoot: this
            }
        },
        props: {
            data: {
                type: Array,
                default () {
                    return []
                }
            },
            columns: {
                type: Array,
                default () {
                    return []
                }
            },
            size: {
                validator(value) {
                    return oneOf(value, ['small', 'large', 'default'])
                },
                default: 'default'
            },
            width: {
                type: [Number, String]
            },
            height: {
                type: [Number, String]
            },
            stripe: {
                type: Boolean,
                default: false
            },
            border: {
                type: Boolean,
                default: false
            },
            showHeader: {
                type: Boolean,
                default: true
            },
            highlightRow: {
                type: Boolean,
                default: false
            },
            rowClassName: {
                type: Function,
                default () {
                    return ''
                }
            },
            context: {
                type: Object
            },
            noDataText: {
                type: String
            },
            noFilteredDataText: {
                type: String
            },
            disabledHover: {
                type: Boolean
            },
            loading: {
                type: Boolean,
                default: false
            },
            draggable: {
                type: Boolean,
                default: false
            },
            tipTheme: {
                validator(value) {
                    return oneOf(value, ['dark', 'light'])
                },
                default: 'light'
            },
            wheelPropagation: {
                type: Boolean,
                default: false
            }
        },
        data() {
            const colsWithId = this.makeColumnsId(this.columns)
            return {
                ready: false,
                tableWidth: 0,
                columnsWidth: {},
                prefixCls: prefixCls,
                compiledUids: [],
                objData: this.makeObjData(),
                rebuildData: [], 
                cloneColumns: this.makeColumns(colsWithId),
                columnRows: this.makeColumnRows(false, colsWithId),
                leftFixedColumnRows: this.makeColumnRows('left', colsWithId),
                rightFixedColumnRows: this.makeColumnRows('right', colsWithId),
                allColumns: getAllColumns(colsWithId),
                showSlotHeader: true,
                showSlotFooter: true,
                bodyHeight: 0,
                scrollBarWidth: getScrollBarSize(),
                currentContext: this.context,
                cloneData: deepCopy(this.data),
                showVerticalScrollBar: false,
                showHorizontalScrollBar: false,
                headerWidth: 0,
                headerHeight: 0,
                resizeProxyVisible: false,
                hasResizeProxy: false,
                scroll: null,
            }
        },
        computed: {
            localeNoDataText() {
                if (this.noDataText === undefined) {
                    return this.t('table.noDataText')
                } else {
                    return this.noDataText
                }
            },
            localeNoFilteredDataText() {
                if (this.noFilteredDataText === undefined) {
                    return this.t('table.noFilteredDataText')
                } else {
                    return this.noFilteredDataText
                }
            },
            wrapClasses() {
                return [
                    `${prefixCls}-wrapper`,
                    {
                        [`${prefixCls}-hide`]: !this.ready,
                        [`${prefixCls}-with-header`]: this.showSlotHeader,
                        [`${prefixCls}-with-footer`]: this.showSlotFooter
                    }
                ]
            },
            classes() {
                return [
                    `${prefixCls}`,
                    `${prefixCls}-frame`,
                    {
                        [`${prefixCls}-${this.size}`]: !!this.size,
                        [`${prefixCls}-border`]: this.border,
                        [`${prefixCls}-stripe`]: this.stripe,
                        [`${prefixCls}-with-fixed-top`]: !!this.height
                    }
                ]
            },
            fixedHeaderClasses() {
                return [
                    `${prefixCls}-fixed-header`,
                    {
                        [`${prefixCls}-fixed-header-with-empty`]: !this.rebuildData.length
                    }
                ]
            },
            styles() {
                let style = {}
                if (this.height) {
                    const height = parseInt(this.height)
                    style.height = `${height}px`
                }
                this.$refs.body && (this.$refs.body.scrollTop = 0)
                if (this.width) style.width = `${this.width}px`
                return style
            },
            tableStyle() {
                let style = {}
                if (this.tableWidth !== 0) {
                    let width = ''
                    if (this.bodyHeight === 0) {
                        width = this.tableWidth
                    } else {
                        width = this.tableWidth - (this.showVerticalScrollBar ? this.scrollBarWidth : 0)
                    }
                    style.width = `${width}px`
                }
                return style
            },
            tableHeaderStyle() {
                let style = {}
                if (this.tableWidth !== 0) {
                    let width = ''
                    width = this.tableWidth
                    style.width = `${width}px`
                }
                return style
            },
            fixedTableStyle() {
                let style = {}
                let width = 0
                this.leftFixedColumns.forEach((col) => {
                    if (col.fixed && col.fixed === 'left') width += col._width
                })
                style.width = `${width}px`
                return style
            },
            fixedRightTableStyle() {
                let style = {}
                let width = 0
                this.rightFixedColumns.forEach((col) => {
                    if (col.fixed && col.fixed === 'right') width += col._width
                })

                //width += this.scrollBarWidth
                style.width = `${width}px`
                style.right = `${this.showVerticalScrollBar?this.scrollBarWidth:0}px`
                return style
            },
            fixedRightHeaderStyle() {
                let style = {}
                let width = 0
                let height = this.headerHeight + 1
                if (this.showVerticalScrollBar) {
                    width = this.scrollBarWidth
                }
                style.width = `${width}px`
                style.height = `${height}px`
                return style
            },
            bodyStyle() {
                let style = {}
                if (this.bodyHeight !== 0) {
                    const height = this.bodyHeight
                    style.height = `${height}px`
                }
                return style
            },
            fixedBodyStyle() {
                let style = {}
                if (this.bodyHeight !== 0) {
                    let height = this.bodyHeight - (this.showHorizontalScrollBar ? this.scrollBarWidth : 0)
                    style.height = this.showHorizontalScrollBar ? `${height}px` : `${height - 1}px`
                }
                return style
            },
            leftFixedColumns() {
                return convertColumnOrder(this.cloneColumns, 'left')
            },
            rightFixedColumns() {
                if (this.hasResizeProxy) {
                    this.hasResizeProxy = false
                }
                return convertColumnOrder(this.cloneColumns, 'right')
            },
            mainColumns() {
                return this.cloneColumns
            },
            isLeftFixed() {
                return this.columns.some(col => col.fixed && col.fixed === 'left')
            },
            isRightFixed() {
                return this.columns.some(col => col.fixed && col.fixed === 'right')
            }
        },
        watch: {
            data: {
                handler() {
                    const oldDataLen = this.rebuildData.length
                    this.objData = this.makeObjData()
                    this.rebuildData = this.makeDataWithSortAndFilter()
                    this.handleResize()
                    if (!oldDataLen) {
                        this.fixedHeader()
                    }
                    setTimeout(() => {
                        this.cloneData = deepCopy(this.data)
                    }, 0)
                },
                deep: true
            },
            columns: {
                handler() {
                    const colsWithId = this.makeColumnsId(this.columns)
                    this.allColumns = getAllColumns(colsWithId)
                    this.cloneColumns = this.makeColumns(colsWithId)

                    this.columnRows = this.makeColumnRows(false, colsWithId)
                    this.leftFixedColumnRows = this.makeColumnRows('left', colsWithId)
                    this.rightFixedColumnRows = this.makeColumnRows('right', colsWithId)
                    this.rebuildData = this.makeDataWithSortAndFilter()
                    this.handleResize()
                },
                deep: true
            },
            height() {
                this.handleResize()
            },
            showHorizontalScrollBar() {
                this.handleResize()
            },
            showVerticalScrollBar() {
                this.handleResize()
            }
        },
        methods: {
            resetScroll() {
                if (this.$refs.body) {
                    this.$refs.body.scrollLeft = 0
                    this.$refs.body.scrollTop = 0
                }
            },
            rowClsName(index) {
                return this.rowClassName(this.data[index], index)
            },
            handleResize() {
                //let tableWidth = parseInt(getStyle(this.$el, 'width')) - 1
                let tableWidth = this.$el.offsetWidth - 1
                let columnsWidth = {}
                let sumMinWidth = 0
                let hasWidthColumns = []
                let noWidthColumns = []
                let maxWidthColumns = []
                let noMaxWidthColumns = []
                this.cloneColumns.forEach((col) => {
                    if (col.width) {
                        hasWidthColumns.push(col)
                    } else {
                        noWidthColumns.push(col)
                        if (col.minWidth) {
                            sumMinWidth += col.minWidth
                        }
                        if (col.maxWidth) {
                            maxWidthColumns.push(col)
                        } else {
                            noMaxWidthColumns.push(col)
                        }
                    }
                    col._width = null
                })


                let unUsableWidth = hasWidthColumns.map(cell => cell.width).reduce((a, b) => a + b, 0)
                let usableWidth = tableWidth - unUsableWidth - sumMinWidth - (this.showVerticalScrollBar ? this.scrollBarWidth : 0) - 1
                let usableLength = noWidthColumns.length
                let columnWidth = 0
                if (usableWidth > 0 && usableLength > 0) {
                    columnWidth = parseInt(usableWidth / usableLength)
                }


                for (let i = 0; i < this.cloneColumns.length; i++) {
                    const column = this.cloneColumns[i]
                    let width = columnWidth + (column.minWidth ? column.minWidth : 0)
                    if (column.width) {
                        width = column.width
                    } else {
                        if (column._width) {
                            width = column._width
                        } else {
                            if (column.minWidth > width) {
                                width = column.minWidth
                            } else if (column.maxWidth < width) {
                                width = column.maxWidth
                            }

                            if (usableWidth > 0) {
                                usableWidth -= width - (column.minWidth ? column.minWidth : 0)
                                usableLength--
                                if (usableLength > 0) {
                                    columnWidth = parseInt(usableWidth / usableLength)
                                } else {
                                    columnWidth = 0
                                }
                            } else {
                                columnWidth = 0
                            }
                        }
                    }

                    column._width = width

                    columnsWidth[column._index] = {
                        width: width
                    }

                }
                if (usableWidth > 0) {
                    usableLength = noMaxWidthColumns.length
                    columnWidth = parseInt(usableWidth / usableLength)
                    for (let i = 0; i < noMaxWidthColumns.length; i++) {
                        const column = noMaxWidthColumns[i]
                        let width = column._width + columnWidth
                        if (usableLength > 1) {
                            usableLength--
                            usableWidth -= columnWidth
                            columnWidth = parseInt(usableWidth / usableLength)
                        } else {
                            columnWidth = 0
                        }

                        column._width = width

                        columnsWidth[column._index] = {
                            width: width
                        }

                    }
                }

                this.tableWidth = this.cloneColumns.map(cell => cell._width).reduce((a, b) => a + b, 0) + (this.showVerticalScrollBar ? this.scrollBarWidth : 0) + 1
                this.columnsWidth = columnsWidth
                this.fixedHeader()
            },
            handleMouseIn(_index) {
                if (this.disabledHover) return
                if (this.objData[_index]._isHover) return
                this.objData[_index]._isHover = true
            },
            handleMouseOut(_index) {
                if (this.disabledHover) return
                this.objData[_index]._isHover = false
            },
            // 通用处理 highlightCurrentRow 和 clearCurrentRow
            handleCurrentRow(type, _index) {
                let oldIndex = -1
                for (let i in this.objData) {
                    if (this.objData[i]._isHighlight) {
                        oldIndex = parseInt(i)
                        this.objData[i]._isHighlight = false
                    }
                }
                if (type === 'highlight') this.objData[_index]._isHighlight = true
                const oldData = oldIndex < 0 ? null : JSON.parse(JSON.stringify(this.cloneData[oldIndex]))
                const newData = type === 'highlight' ? JSON.parse(JSON.stringify(this.cloneData[_index])) : null
                this.$emit('on-current-change', newData, oldData)
            },
            highlightCurrentRow(_index) {
                if (!this.highlightRow || this.objData[_index]._isHighlight) return
                this.handleCurrentRow('highlight', _index)
            },
            clearCurrentRow() {
                if (!this.highlightRow) return
                this.handleCurrentRow('clear')
            },
            clickCurrentRow(_index) {
                this.highlightCurrentRow(_index)
                this.$emit('on-row-click', JSON.parse(JSON.stringify(this.cloneData[_index])), _index)
            },
            dblclickCurrentRow(_index) {
                this.highlightCurrentRow(_index)
                this.$emit('on-row-dblclick', JSON.parse(JSON.stringify(this.cloneData[_index])), _index)
            },
            getSelection() {
                let selectionIndexes = []
                for (let i in this.objData) {
                    if (this.objData[i]._isChecked) selectionIndexes.push(parseInt(i))
                }
                return JSON.parse(JSON.stringify(this.data.filter((data, index) => selectionIndexes.indexOf(index) > -1)))
            },
            toggleSelect(_index) {
                let data = {}

                for (let i in this.objData) {
                    if (parseInt(i) === _index) {
                        data = this.objData[i]
                        break
                    }
                }
                const status = !data._isChecked

                this.objData[_index]._isChecked = status

                const selection = this.getSelection()
                this.$emit(status ? 'on-select' : 'on-select-cancel', selection, JSON.parse(JSON.stringify(this.data[_index])))
                this.$emit('on-selection-change', selection)
            },
            toggleExpand(_index) {
                let data = {}

                for (let i in this.objData) {
                    if (parseInt(i) === _index) {
                        data = this.objData[i]
                        break
                    }
                }
                const status = !data._isExpanded
                this.objData[_index]._isExpanded = status
                this.$emit('on-expand', JSON.parse(JSON.stringify(this.cloneData[_index])), status)

                if (this.height) {
                    this.$nextTick(() => this.fixedBody())
                }
            },
            selectAll(status) {
                // this.rebuildData.forEach((data) => {
                //     if(this.objData[data._index]._isDisabled){
                //         this.objData[data._index]._isChecked = false
                //     }else{
                //         this.objData[data._index]._isChecked = status
                //     }

                // })
                for (const data of this.rebuildData) {
                    if (this.objData[data._index]._isDisabled) {
                        continue
                    } else {
                        this.objData[data._index]._isChecked = status
                    }
                }
                const selection = this.getSelection()
                if (status) {
                    this.$emit('on-select-all', selection)
                } else {
                    this.$emit('on-select-all-cancel', selection)
                }
                this.$emit('on-selection-change', selection)
            },
            fixedHeader() {
                if (this.height) {
                    this.$nextTick(() => {
                        const titleHeight = parseInt(getStyle(this.$refs.title, 'height')) || 0
                        const headerHeight = parseInt(getStyle(this.$refs.header, 'height')) || 0
                        const footerHeight = parseInt(getStyle(this.$refs.footer, 'height')) || 0
                        this.bodyHeight = this.height - titleHeight - headerHeight - footerHeight
                        this.$nextTick(() => this.fixedBody())
                    })
                } else {
                    this.bodyHeight = 0
                    this.$nextTick(() => this.fixedBody())
                }
            },
            fixedBody() {
                if (this.$refs.header) {
                    this.headerWidth = this.$refs.header.children[0].offsetWidth
                    this.headerHeight = this.$refs.header.children[0].offsetHeight
                    //this.showHorizontalScrollBar = this.headerWidth>this.$refs.header.offsetWidth
                }

                if (!this.$refs.tbody || !this.data || this.data.length === 0) {
                    this.showVerticalScrollBar = false
                } else {
                    let bodyContentEl = this.$refs.tbody.$el
                    let bodyEl = bodyContentEl.parentElement
                    let bodyContentHeight = bodyContentEl.offsetHeight
                    let bodyHeight = bodyEl.offsetHeight

                    this.showHorizontalScrollBar = bodyEl.offsetWidth < bodyContentEl.offsetWidth + (this.showVerticalScrollBar ? this.scrollBarWidth : 0)
                    this.showVerticalScrollBar = this.bodyHeight ? bodyHeight - (this.showHorizontalScrollBar ? this.scrollBarWidth : 0) < bodyContentHeight : false

                    if (this.showVerticalScrollBar) {
                        bodyEl.classList.add(this.prefixCls + '-overflowY')
                    } else {
                        bodyEl.classList.remove(this.prefixCls + '-overflowY')
                    }
                    if (this.showHorizontalScrollBar) {
                        bodyEl.classList.add(this.prefixCls + '-overflowX')
                    } else {
                        bodyEl.classList.remove(this.prefixCls + '-overflowX')
                    }
                }
            },
            hideColumnFilter() {
                this.cloneColumns.forEach((col) => col._filterVisible = false)
            },
            handleInitBodyScroll() {
                this.scroll = new PerfectScrollbar(this.$refs.body, {
                    wheelSpeed: 0.5,
                    wheelPropagation: this.wheelPropagation,
                    minScrollbarLength: 60,
                    maxScrollbarLength: 300
                })
                this.$refs.body.addEventListener('ps-scroll-y', (event) => {
                    const target = event && event.target
                    if (this.isLeftFixed) this.$refs.fixedBody.scrollTop = target.scrollTop
                    if (this.isRightFixed) this.$refs.fixedRightBody.scrollTop = target.scrollTop
                    this.hideColumnFilter()
                })
                this.$refs.body.addEventListener('ps-scroll-x', (event) => {
                    const target = event && event.target
                    if (this.showHeader) this.$refs.header.scrollLeft = target.scrollLeft
                    this.hideColumnFilter()
                })
            },
            handleFixedMousewheel(event) {
                let deltaY = event.deltaY
                if (!deltaY && event.detail) {
                    deltaY = event.detail * 40
                }
                if (!deltaY && event.wheelDeltaY) {
                    deltaY = -event.wheelDeltaY
                }
                if (!deltaY && event.wheelDelta) {
                    deltaY = -event.wheelDelta
                }
                if (!deltaY) return
                const body = this.$refs.body
                const currentScrollTop = body.scrollTop
                if (deltaY < 0 && currentScrollTop !== 0) {
                    event.preventDefault()
                }
                if (deltaY > 0 && body.scrollHeight - body.clientHeight > currentScrollTop) {
                    event.preventDefault()
                }
                //body.scrollTop += deltaY
                let step = 0
                let timeId = setInterval(() => {
                    step += 5
                    if (deltaY > 0) {
                        body.scrollTop += 2
                    } else {
                        body.scrollTop -= 2
                    }
                    if (step >= Math.abs(deltaY)) {
                        clearInterval(timeId)
                    }
                }, 5)
            },
            handleMouseWheel(event) {
                const deltaX = event.deltaX
                const $body = this.$refs.body

                if (deltaX > 0) {
                    $body.scrollLeft = $body.scrollLeft + 10
                } else {
                    $body.scrollLeft = $body.scrollLeft - 10
                }
            },
            sortData(data, type, index) {
                const key = this.cloneColumns[index].key
                data.sort((a, b) => {
                    if (this.cloneColumns[index].sortMethod) {
                        return this.cloneColumns[index].sortMethod(a[key], b[key], type)
                    } else {
                        if (type === 'asc') {
                            return a[key] > b[key] ? 1 : -1
                        } else if (type === 'desc') {
                            return a[key] < b[key] ? 1 : -1
                        }
                    }
                })
                return data
            },
            handleSort(_index, type) {
                const index = this.GetOriginalIndex(_index)
                this.cloneColumns.forEach((col) => col._sortType = 'normal')

                const key = this.cloneColumns[index].key
                if (this.cloneColumns[index].sortable !== 'custom') { // custom is for remote sort
                    if (type === 'normal') {
                        this.rebuildData = this.makeDataWithFilter()
                    } else {
                        this.rebuildData = this.sortData(this.rebuildData, type, index)
                    }
                }
                this.cloneColumns[index]._sortType = type

                this.$emit('on-sort-change', {
                    column: JSON.parse(JSON.stringify(this.allColumns[this.cloneColumns[index]._index])),
                    key: key,
                    order: type
                })
            },
            // 高级过滤
            handleFilterShow(index) {
                this.cloneColumns[index]._filterVisible = true
            },
            handleFilterHide(index) {
                this.cloneColumns[index]._filterVisible = false
            },
            handleFilterSubmit(index) {
                const column = this.cloneColumns[index]
                let filterData = this.makeDataWithSort()
                this.cloneColumns[index]._isFiltered = true
                this.cloneColumns[index]._filterVisible = false

                this.rebuildData = this.filterData(filterData, column)
                this.$emit('on-filter-change', column)
            },
            handleFilterReset(_index) {
                const index = this.GetOriginalIndex(_index)
                this.cloneColumns[index]._isFiltered = false
                this.cloneColumns[index]._filterVisible = false

                let filterData = this.makeDataWithSort()
                this.rebuildData = filterData
                this.$emit('on-filter-change', this.cloneColumns[index])
            },
            filterData(data, column, type) {
                if(column._isFiltered) {
                    return data.filter((row) => {
                        return column.filterMethod && column.filterMethod(row)
                    })
                } 
                return data
            },
            GetOriginalIndex(_index) {
                return this.cloneColumns.findIndex(item => item._index === _index)
            },
            makeData() { // copy data
                let data = deepCopy(this.data)
                data.forEach((row, index) => {
                    row._index = index
                    row._rowKey = rowKey++
                })
                return data
            },
            makeDataWithSort() { // copy sort data
                let data = this.makeData()
                let sortType = 'normal'
                let sortIndex = -1
                let isCustom = false

                for (let i = 0; i < this.cloneColumns.length; i++) {
                    if (this.cloneColumns[i]._sortType !== 'normal') {
                        sortType = this.cloneColumns[i]._sortType
                        sortIndex = i
                        isCustom = this.cloneColumns[i].sortable === 'custom'
                        break
                    }
                }
                if (sortType !== 'normal' && !isCustom) data = this.sortData(data, sortType, sortIndex)
                return data
            },
            makeDataWithFilter() { // copy filter data
                let data = this.makeData()
                this.cloneColumns.forEach(col => data = this.filterData(data, col))
                return data
            },
            makeDataWithSortAndFilter() { // copy sort and filter data
                let data = this.makeDataWithSort()
                this.cloneColumns.forEach(col => data = this.filterData(data, col))
                return data
            },
            makeObjData() {
                let data = {}
                this.data.forEach((row, index) => {
                    const newRow = deepCopy(row) 
                    newRow._isHover = false
                    if (newRow._disabled) {
                        newRow._isDisabled = newRow._disabled
                    } else {
                        newRow._isDisabled = false
                    }
                    if (newRow._checked) {
                        newRow._isChecked = newRow._checked
                    } else {
                        newRow._isChecked = false
                    }
                    if (newRow._expanded) {
                        newRow._isExpanded = newRow._expanded
                    } else {
                        newRow._isExpanded = false
                    }
                    if (newRow._highlight) {
                        newRow._isHighlight = newRow._highlight
                    } else {
                        newRow._isHighlight = false
                    }
                    data[index] = newRow
                })
                return data
            },
            makeColumnsId(columns) { // 修改列，设置隐藏的id，便于多级表头寻找头部
                return columns.map(item => {
                    if ('children' in item) this.makeColumnsId(item.children)
                    item.__id = getRandomStr(6)
                    return item
                })
            },
            makeColumns(cols) { // 在 data 时，this.allColumns 暂时为 undefined
                let columns = deepCopy(getAllColumns(cols))
                let left = []
                let right = []
                let center = []

                columns.forEach((column, index) => {
                    column._index = index
                    column._columnKey = columnKey++
                    column.width = parseInt(column.width)
                    column._width = column.width ? column.width : '' // update in handleResize()
                    column._sortType = 'normal'
                    column._filterVisible = false
                    column._isFiltered = false

                    if ('sortType' in column) {
                        column._sortType = column.sortType
                    }

                    if (column.fixed && column.fixed === 'left') {
                        left.push(column)
                    } else if (column.fixed && column.fixed === 'right') {
                        right.push(column)
                    } else {
                        center.push(column)
                    }
                })
                return left.concat(center).concat(right)
            },
            makeColumnRows(fixedType, cols) { // create a multiple table-head
                return convertToRows(cols, fixedType)
            },
            dragAndDrop(a, b) {
                this.$emit('on-drag-drop', a, b)
            }
        },
        created() {
            if (!this.context) this.currentContext = this.$parent
            this.showSlotHeader = this.$slots.header !== undefined
            this.showSlotFooter = this.$slots.footer !== undefined
            this.rebuildData = this.makeDataWithSortAndFilter()
        },
        mounted() {
            this.handleResize()
            this.$nextTick(() => this.ready = true)

            this.handleInitBodyScroll()

            on(window, 'resize', this.handleResize)
            this.observer = elementResizeDetectorMaker()
            this.observer.listenTo(this.$el, this.handleResize)

            this.$on('on-visible-change', (val) => {
                if (val) {
                    this.handleResize()
                }
            })
        },
        beforeDestroy() {
            off(window, 'resize', this.handleResize)
            this.observer.removeListener(this.$el, this.handleResize)
        }
    }
</script>
