<template>
    <div class="dhc-proTree" v-loading="loading">
        <a-input placeholder="输入关键字进行过滤" v-model="filterText" size="small" :clearable="true" class="dhc-proTree-input"
            prefix-icon="a-icon-search"></a-input>
        <div class="dhc-tree-container" ref="treeContainer" :style="style">
            <a-tree ref="proTree" highlight-current :filter-node-method="filterNode" :check-strictly="checkStrictly"
                @node-click="handlerNodeClick" @check-change="handlerCheckChange" @check="handlerCheck"
                :default-expanded-keys="defaultExpandedKeys" :default-expand-all="false" :tree-data='treeData'
                :render-content="renderContent" @node-contextmenu="nodeContextmenu" @node-expand="nodeExpand"
                @node-collapse="nodeCollapse" v-bind="$attrs">
            </a-tree>
            <!-- 鼠标右键菜单 -->
            <div class="dhc-proTree-tree-menu" :id="uuid" v-show="menuShowFlag">
                <ul>
                    <li v-for="(item, index) in rightMenuList" @click="item.handler" :key="index">
                        {{ item.text }}
                    </li>
                </ul>
            </div>
        </div>
    </div>
</template>

<script lang="jsx">
import { addWindowResizeHandler } from '@/utils/resizeUtils'
import { queryTargetHeight } from '@/utils/resizeUtils'
import { getDepartmentTree } from '@/api/department';
import { v4 as uuid } from 'uuid'

export default {
    name: 'ProTree',
    props: {
        // 滚动高度(为数字时自动在后面加上px；为字符串且包含calc时直接赋值给高度)
        scrollerHeight: {
            type: [Number, String],
            default: 0
        },
        // 高度差微调滚动高度
        altitudeDiff: {
            type: Number,
            default: -28,
        },
        expandTier: {
            type: [Number, String],
            default: () => 0,
        },
        //是否设置高度
        isSettingHeight: {
            type: Boolean,
            default: true
        },
        //静态数据源
        data: {
            type: Array,
            default: () => [],
        },
        //树类型；departTree：机构树(不包含删除状态的机构)
        type: String,
        /**
         * 级联类型；
         *  noneCascade:无级联(check-strictly属性将为true，但遵循父子互相关联的做法)
         *  settableCascade:右键点击节点弹出设置框，可实现多种选中节点方式
         */
        cascadeType: String,
        //过滤时的节点名
        filterNodeName: {
            type: String,
            default: 'nodeName'
        },
        renderContentSetting: {
            type: Object,
            default: () => ({
                defaultRender: false,   //是否按照默认渲染方式进行渲染，false时会将渲染函数内容放到右边，左侧自动生成节点名称
                displayType: "always",   //渲染内容展示方式;always:总是、hover:悬浮、selected:选中
            })
        },
        /**
         *渲染在节点名称后的的渲染函数；
          返回值：{
            dom:要渲染的元素，
            title:鼠标悬浮在label上时显示的文本
          }
         */
        labelAfterNodeContent: {
            type: Function,
            default: () => () => { }
        },
        //是否为ProSelectTree组件调用
        isProSelectTree: {
            type: Boolean,
            default: () => false
        },
        proSelectTreeStyle: String,
        /* 禁用的节点(不可选中) */
        disabledNode: {
            type: Array,
            default: () => ([])
        },
        /* 禁用节点选中状态(true将一直被选中，false将一直不可选中) */
        disabledNodeSts: {
            type: Boolean,
            default: () => true
        },
    },
    data () {
        return {
            treeData: this.data, //数据源
            filterText: '', //过滤输入框文字
            defaultExpandedKeys: [], //默认展开的节点的 key 的数组
            in_scrollerHeight: 0,
            loading: false,
            rightMenuList: [],
            rightMenuListAll: [
                { text: "选中当前节点", handler: () => this.menuListHandler(1) },
                { text: "选中当前节点及下级节点", handler: () => this.menuListHandler(2) },
                { text: "取消当前节点", handler: () => this.menuListHandler(3) },
                { text: "取消当前节点及下级节点", handler: () => this.menuListHandler(4) },
            ],
            menuShowFlag: false, //右键列表展示标志
            rightClickNodeData: { data: {}, node: {} }, //右击节点数据
            uuid: uuid()  //组件唯一标识
        }
    },
    mounted () {
        const _this = this
        if (this.treeData && this.treeData.length > 0) {
            this.setDefaultExpandedKeys(this.treeData)
        } else if (this.type) {
            switch (this.type) {
                case "departTree": this.getDepartmentTree(); break;
            }
        }
        if (this.scrollerHeight === 0) {
            // console.log('targetHeight altitudeDiff:',this.altitudeDiff)
            let targetHeight = queryTargetHeight(this.$refs['treeContainer'], this.altitudeDiff)
            _this.in_scrollerHeight = targetHeight
            addWindowResizeHandler(() => {
                _this.$nextTick(() => {
                    _this.in_scrollerHeight = queryTargetHeight(_this.$refs['treeContainer'], _this.altitudeDiff)
                })
            })
        } else {
            this.in_scrollerHeight = this.scrollerHeight
        }
        // 鼠标点击其他位置时隐藏菜单
        document.addEventListener("click", this.hideMenuList)
        // 鼠标滚轮滚动时隐藏菜单
        document.addEventListener("mousewheel", this.hideMenuList)
    },
    destroyed () {
        // 删除事件监听
        document.removeEventListener("click", this.hideMenuList)
        document.removeEventListener("mousewheel", this.hideMenuList)
    },
    methods: {
        /*------------暴露的方法-----------*/
        //重新加载远程数据源
        reload () {
            if (this.type && !this.data?.length > 0) {
                switch (this.type) {
                    case "departTree": this.getDepartmentTree(); break;
                }
            }
        },
        //获取原生ref
        getOrignRef () {
            return this.$refs.proTree
        },
        //获取被选中的节点数据
        getSelected () {
            let selectedKeys = this.$refs.proTree.selectedKeys
            if (selectedKeys.length > 0) {
                return this.treeData.filter(item => selectedKeys.includes(item.key))[0]
            } else {
                return null;
            }
        },
        /*-------------------------------*/
        resizeAltitudeSub (subVale) {
            this.in_scrollerHeight = this.in_scrollerHeight + subVale
        },
        //机构树过滤方法
        filterNode (value, data) {
            if (!value) return true;
            return data[this.filterNodeName]?.indexOf(value) !== -1;
        },
        //节点被点击时的回调
        handlerNodeClick (data, node, event) {
            if (this.$attrs["node-key"]) {
                this.$refs.proTree.setCurrentKey(this.$attrs["node-key"])
            }
            this.$emit('node-click', data, node, event)
            //隐藏右键菜单
            this.hideMenuList()
        },
        //节点选中状态发生变化时的回调
        handlerCheckChange (data, node, event) {
            this.$emit('check-change', data, node, event)
        },
        //当复选框被点击的时候触发
        handlerCheck (data, node) {
            let { cascadeType } = this
            let { checkedKeys, checkedNodes } = node
            if (cascadeType === 'noneCascade' || cascadeType === 'settableCascade') {
                //当前被点击节点的key
                let currentKey = data[this.$attrs["node-key"]]
                //当前节点被选中还是取消选中
                let checkedFlag = node.checkedKeys.indexOf(currentKey) > -1 ? true : false
                let setCheckedKeys = []
                let childrenKeys = []
                //被选中
                if (checkedFlag) {
                    //处理子节点
                    if (data?.children?.length > 0) {
                        //当前节点的孩子节点
                        this.getChildrenKeys(data, childrenKeys)
                        childrenKeys = childrenKeys.filter(item => item != currentKey)
                    }
                    //获取当前节点的父节点
                    let parentKeys = this.getParentKeys(this.treeData, currentKey).filter(item => item != currentKey)
                    //要自动选中的父节点（子节点全部选中自动选中父节点，否则不选中）
                    let checkedParentKeys = []
                    for (let i = parentKeys.length - 1; i >= 0; i--) {
                        let parentKey = parentKeys[i];
                        //父节点数据
                        let parentNode = this.getNodeByKey(this.treeData, parentKey)
                        //父节点的子节点
                        let cKeys = parentNode.children.map(tiem => tiem[this.$attrs["node-key"]])
                        //判断 已选中节点 是否完全包含父节点所有的子节点
                        let isInclude = this.checkArr([...checkedKeys, ...checkedParentKeys], cKeys)
                        if (isInclude) {
                            checkedParentKeys.push(parentKey)
                        }
                    }
                    setCheckedKeys = Array.from(new Set([...checkedKeys, ...childrenKeys, ...checkedParentKeys]))
                    this.$emit('check', data, { checkedKeys: setCheckedKeys })
                    this.$refs.proTree.setCheckedKeys(setCheckedKeys)
                } else {//取消选中
                    let keys = checkedKeys;
                    //------------处理子节点-------------
                    if (data?.children?.length > 0) {
                        //当前节点的孩子节点
                        let childrenKeys = []
                        this.getChildrenKeys(data, childrenKeys)
                        childrenKeys = childrenKeys.filter(item => item != currentKey)
                        //子节点中存在未被选中的节点
                        if (!this.checkArr(checkedKeys, childrenKeys)) {
                            keys = Array.from(new Set([...checkedKeys, ...childrenKeys, currentKey]))

                        } else {
                            //删除当前节点被选中的子节点
                            keys = checkedKeys.filter((item1) => !childrenKeys.some((item2) => item1 === item2))
                        }
                    }
                    //------------处理父节点-------------
                    //获取当前节点的父节点
                    let parentKeys = this.getParentKeys(this.treeData, currentKey).filter(item => item != currentKey)
                    //已被选中的父节点
                    let checkedParentKeys = keys.filter(item => parentKeys.includes(item))
                    //需要排除的父节点
                    let excludeParentKeys = []
                    for (let i = checkedParentKeys.length - 1; i >= 0; i--) {
                        let parentKey = checkedParentKeys[i];
                        //父节点数据
                        let parentNode = this.getNodeByKey(this.treeData, parentKey)
                        //父节点的子节点
                        let cKeys = parentNode.children.map(tiem => tiem[this.$attrs["node-key"]])
                        keys = keys.filter(item1 => !excludeParentKeys.some((item2) => item1 === item2))
                        //判断 已选中节点 是否完全包含父节点所有的子节点
                        let isInclude = this.checkArr(keys, cKeys)
                        if (!isInclude) {
                            excludeParentKeys.push(parentKey)
                        }
                    }
                    keys = keys.filter(item1 => !excludeParentKeys.some((item2) => item1 === item2))
                    this.$emit('check', data, { checkedKeys: keys })
                    this.$refs.proTree.setCheckedKeys(keys)
                }
            } else {
                this.$emit('check', data, node)
            }
            //隐藏右键菜单
            this.hideMenuList()
        },
        //获取子节点的keys
        getChildrenKeys (data, childrenKeys) {
            const _this = this;
            if (data?.children?.length > 0) {
                childrenKeys.push(data[this.$attrs["node-key"]])
                data.children.forEach(item => {
                    _this.getChildrenKeys(item, childrenKeys)
                })
            } else {
                childrenKeys.push(data[this.$attrs["node-key"]])
            }
        },
        /**
         * 根据key获取指定节点的详细数据
         * @param {*} data 数据源
         * @param {*} key 要查询的节点key
         */
        getNodeByKey (data, key) {
            for (let i = 0; i < data.length; i++) {
                let item = data[i]
                if (item[this.$attrs["node-key"]] == key) {
                    return item;
                } else if (item.children?.length > 0) {
                    let result = this.getNodeByKey(item.children, key);
                    if (result) {
                        return result;
                    }
                }
            }
        },
        //获取父节点的keys
        getParentKeys (tree, code) {
            let arr = [] //要返回的数组
            for (let i = 0; i < tree.length; i++) {
                let item = tree[i]
                arr = []
                arr.push(item[this.$attrs["node-key"]]) //保存当前节点id
                if (code == item[this.$attrs["node-key"]]) { //判断当前id是否是默认id
                    return arr //是则退出循环、返回数据
                } else { //否则进入下面判断，判断当前节点是否有子节点数据
                    if (item.children && item.children.length > 0) {
                        //合并子节点返回的数据
                        arr = arr.concat(this.getParentKeys(item.children, code))
                        if (arr.includes(code)) { //如果当前数据中已包含默认节点，则退出循环、返回数据
                            return arr
                        }
                    }
                }
            }
        },
        /**
         * 判断一个数组是否包含另一个数组
         * arr1是否包含arr2
         */
        checkArr (arr1, arr2) {
            let arr3 = []
            for (var i = 0; i < arr2.length; i++) {
                if (arr1.indexOf(arr2[i]) > -1) {
                    arr3.push(arr2[i])
                }
            }
            if (arr3.length == arr2.length) {
                return true
            } else {
                return false
            }
        },
        //设置树默认展开的节点
        setDefaultExpandedKeys (data) {
            let that = this
            let {
                expandTier
            } = this
            if (expandTier > 0) {
                let defaultExpandedKeys = []
                that.getDataTier(data, 0, defaultExpandedKeys, expandTier)
                that.defaultExpandedKeys = defaultExpandedKeys;
            } else {
                that.defaultExpandedKeys = []
            }
        },
        /**
         * 根据设置层级返回要展开的keys
         * @param {*} data 数据源
         * @param {*} expandTierFlag  递归次数
         * @param {*} dataTier  层级
         * @param {*} expandTier 返回的展开keys
         */
        getDataTier (data, expandTierFlag, dataTier, expandTier) {
            for (let i = 0, len = data.length; i < len; i++) {
                dataTier.push(data[i][this.$attrs["node-key"]])
            }
            expandTierFlag++;
            if (expandTierFlag >= expandTier) {
                return;
            } else {
                for (let i = 0, len = data.length; i < len; i++) {
                    if (data[i].children && data[i].children.length > 0) {
                        this.getDataTier(data[i].children, expandTierFlag, dataTier, expandTier)
                    }
                }
            }
        },
        //加载数据源
        getDepartmentTree () {
            let that = this;
            that.loading = true;
            let key = this.$refs.proTree.getCurrentKey(this.$attrs["node-key"])
            getDepartmentTree({
                treeName: 'allDepartmentTree',
                param: {
                    hasDataLevel: true,
                }
            }).then(resp => {
                if (resp.success) {
                    let data = resp.data;
                    //过滤掉删除状态的机构
                    data = that.filterByDepSts(data);
                    this.setDefaultExpandedKeys(data)
                    that.treeData = data
                    if (key) {
                        that.$nextTick(() => {
                            that.$refs.proTree.setCurrentKey(key)
                        })
                    }
                }
                that.loading = false;
            })
        },
        //过滤掉删除状态的机构
        filterByDepSts (data) {
            let indexs = []
            for (let i = 0; i < data.length; i++) {
                if (data[i].children) {
                    data[i].children = this.filterByDepSts(data[i].children)
                }
                if (data[i].depSts == '*') {
                    indexs.push(i)
                }
            }
            if (indexs.length > 0) {
                data = data.filter((item, index) => indexs.indexOf(index) == -1)
            }
            return data
        },
        /**
         * 树节点的内容区的渲染函数
         */
        renderContent (h, { node, data, store }) {
            let renderContent = this.$attrs["render-content"]
            //放在label后的渲染函数
            let labelAfterNodeContent = this.labelAfterNodeContent

            //渲染方法不存在，直接返回
            if (renderContent == undefined) {
                return (
                    <span class="custom-tree-node" title={node.label}>
                        <span>{node.label}</span>
                    </span>
                )
            }
            let { displayType, defaultRender } = this.renderContentSetting
            //默认渲染方式渲染
            if (defaultRender) {
                return renderContent(h, { node, data, store });
            }
            //自定义渲染内容
            ////放在label后的渲染函数
            let labelAfterRenderDom = undefined
            let labelAfterRenderTitle = ""
            if (labelAfterNodeContent != undefined) {
                let result = labelAfterNodeContent(h, { node, data, store });
                if (result) {
                    labelAfterRenderDom = result.dom ?? undefined;
                    labelAfterRenderTitle = result.title ?? '';
                }
            }
            //固定在右边的渲染函数
            let renderDom = renderContent(h, { node, data, store });
            return (
                <span class="custom-tree-node">
                    <span title={node.label + labelAfterRenderTitle}>{node.label}{labelAfterRenderDom}</span>
                    <span class={"render-content display_" + displayType}>
                        {renderDom}
                    </span>
                </span>
            )
        },
        /**
        * 右键菜单列表回调
        * @param {*} itemType 列类型
        */
        menuListHandler (itemType) {
            let { data, node } = this.rightClickNodeData
            let setCheckedKeys = [];
            const nodeKey = this.$attrs["node-key"]
            //已被选中的节点keys
            let checkedKeys = this.$refs.proTree.getCheckedKeys()
            //当前节点的key
            let currentKey = data[nodeKey]
            //子节点keys
            let childrenKeys = []
            //处理子节点
            if ((itemType == 2 || itemType == 4) && data?.children?.length > 0) {
                //当前节点的孩子节点
                this.getChildrenKeys(data, childrenKeys)
                //排除当前节点
                childrenKeys = childrenKeys.filter(item => item != currentKey)
            }

            //选中当前节点
            if (itemType == 1) {
                setCheckedKeys = Array.from(new Set([...checkedKeys, currentKey]))

                //选中当前节点及下级节点
            } else if (itemType == 2) {
                setCheckedKeys = Array.from(new Set([...checkedKeys, ...childrenKeys, currentKey]))

                //取消当前节点
            } else if (itemType == 3) {
                setCheckedKeys = checkedKeys?.filter(item => item != currentKey)

                //取消当前节点及下级节点
            } else if (itemType == 4) {
                let excludeKeys = [...childrenKeys, currentKey];
                setCheckedKeys = checkedKeys?.filter(item => excludeKeys.indexOf(item) == -1)
            }

            //处理禁用节点
            if (this.disabledNode.length > 0) {
                if (this.disabledNodeSts) {
                    setCheckedKeys = Array.from(new Set([...setCheckedKeys, ...this.disabledNode]))
                } else {
                    setCheckedKeys = setCheckedKeys.filter(item => this.disabledNode.indexOf(item) == -1)
                }
            }
            this.$refs.proTree.setCheckedKeys(setCheckedKeys)
            this.$emit('handler-check', setCheckedKeys)
            //关闭右键菜单
            this.menuShowFlag = false
        },

        /**
        * 当某一节点被鼠标右键点击时会触发该事件
        */
        nodeContextmenu (event, data, node, el) {
            //单选情况下不展示列表
            this.$emit("node-contextmenu", event, data, node, el)
            if (this.cascadeType !== 'settableCascade' || this.$attrs["show-checkbox"] == undefined) return;
            if (data.children == null) {
                this.rightMenuList = this.rightMenuListAll.filter((item, index) => index == 0 || index == 2);
            } else {
                this.rightMenuList = this.rightMenuListAll;
            }
            //修改偏移量
            let { clientX, clientY } = event;
            let menuDom = document.getElementById(this.uuid)
            menuDom.style.left = clientX + 'px';
            menuDom.style.top = clientY + 'px';
            this.menuShowFlag = true
            //保存当前节点数据
            this.rightClickNodeData = { data, node }
        },
        /**
        * 节点被展开时触发的事件
        * @param {*} data 传递给 data 属性的数组中该节点所对应的对象
        * @param {*} node 节点对应的 Node
        * @param {*} el 节点组件本身
        */
        nodeExpand (data, node, el) {
            this.hideMenuList()
            this.$emit("node-expand", data, node, el)
        },
        /**
        * 节点被关闭时触发的事件
        * @param {*} data 传递给 data 属性的数组中该节点所对应的对象
        * @param {*} node 节点对应的 Node
        * @param {*} el 节点组件本身
        */
        nodeCollapse (data, node, el) {
            //隐藏右键菜单
            this.hideMenuList()
            this.$emit("node-collapse", data, node, el)
        },
        /**
        * 隐藏右键菜单
        */
        hideMenuList () {
            //隐藏右键菜单
            this.menuShowFlag = false
        }
    },
    computed: {
        checkStrictly () {
            if (this.cascadeType === 'noneCascade' || this.cascadeType === 'settableCascade' || this.$attrs["check-strictly"]) {
                return true
            } else {
                return false
            }
        },
        style () {
            if (this.isProSelectTree) {
                return this.proSelectTreeStyle
            }
            if (this.isSettingHeight) {
                let style = {}
                if (typeof this.in_scrollerHeight == "string" && this.in_scrollerHeight.indexOf("calc") > -1) {
                    style['height'] = this.in_scrollerHeight
                } else {
                    style.height = this.in_scrollerHeight == 0 ? 'auto' : this.in_scrollerHeight + 'px'

                }
                return style
            }
            return null
        }
    },
    watch: {
        filterText (val) {
            if (val != undefined) {
                this.$refs.proTree.filter(val);
            }
        },
        data (newValue, oldValue) {
            this.treeData = newValue
            this.setDefaultExpandedKeys(newValue);
        },
        scrollerHeight (newValue, oldValue) {
            this.in_scrollerHeight = newValue;
        },
    }
}
</script>

<style lang="scss">
//右键菜单列表
.dhc-proTree-tree-menu {
    z-index: 999;
    position: fixed;
    width: 160px;
    border: 1px solid #ccc;
    background: #eee;
    padding: 0px;
    background-color: white;
    border-radius: 4px;
    box-shadow: 2px 2px 3px 0 rgb(0 0 0 / 30%);

    ul {
        padding: 0px;
        margin: 0px;

        li {
            color: #21232E;
            font-size: 12px;
            text-align: center;
            cursor: pointer;
            list-style-type: none;
            display: flex;
            justify-content: left;
            padding: 7px 10px;
        }

        li:hover {
            background-color: #cccccc;
        }
    }

}

.dhc-proTree {
    .a-tree {
        margin-top: 8px;
    }

    .dhc-tree-container {
        overflow: auto;
    }

    //自定义节点
    .custom-tree-node {
        flex: 1;
        display: flex;
        align-items: center;
        justify-content: space-between;
        font-size: 12px;
        padding-right: 8px;

        .render-content {
            display: block;
            position: absolute;
            right: 0px;
            background-color: white;
            height: 26px;
            padding: 0 5px;
        }

        .display_hover {
            display: none;
        }

        &:hover {
            .display_hover {
                display: block;
                background-color: rgb(245, 247, 250);
            }

            .display_always {
                background-color: rgb(245, 247, 250);
            }
        }

        .display_selected {
            display: none;
        }
    }

    //被选中的节点
    .is-current {

        //展示自定义节点
        >.el-tree-node__content>.custom-tree-node {
            >.render-content {
                background-color: rgb(237, 246, 255);
            }

            .display_selected {
                display: block;
            }

        }
    }
}
</style>
