<script lang="ts">
export default {
    name: "datamanagement",//试验数据管理
};
</script>

<script setup lang="ts">
import { ref, onMounted, nextTick, defineAsyncComponent, onBeforeUnmount } from "vue";
import { Search } from '@element-plus/icons-vue'
import { ElMessageBox, ElMessage } from "element-plus";
import { tstaskList, getTsNodesTree, tsFilesPage, deleteTsFilesByIds } from "@/api/datamanagement";
import { listLocalAndBackup, compare, compareLocalList, automaticFileBackupByIds, compareMd5List, compareMinioList, compareLocal, compareMd5, compareMinio, uploadToBackup, downloadToLocal, deleteTsFilesById, automaticFileBackup, obtainUrl, listBackupTree, listLocalTree,automaticFileBackupData } from "@/api/fileSynchronization";
import { debounce } from 'lodash-es';
import Page from '@/components/Pagination/page.vue';
//text文件编辑功能
import textEdit from '@/components/textEditing/index.vue'
import txtexl from '@/components/textEditing/txtexl.vue'
// 文件预览相关
import useFileData from "@/components/file/file/useFileData";
import Viewfile from '@/views/component/Viewfile.vue'
import useFilePreview from "@/components/file/file/useFilePreview";
import VideoPlayerAsyncLoading from "@/components/file/preview/VideoPlayerAsyncLoading.vue";
import TextViewerAsyncLoading from "@/components/file/preview/TextViewerAsyncLoading.vue";
import MarkdownViewerDialogAsyncLoading from "@/components/file/preview/MarkdownViewerDialogAsyncLoading.vue";
const { dialogVideoVisible, dialogTextVisible, dialogPdfVisible, dialogOfficeVisible, dialog3dVisible } = useFilePreview();

const { openRow } = useFileData();

//左侧树的选择框
const projectId = ref()
const projectArr = ref([])
//左侧树配置
const pathid = ref()
const treeRef = ref();
const treedata = ref([])
const treeloading = ref(false)
const defaultProps = {
    children: "children",
    label: "nodeName"
};
const treeForm = ref({
    taskId: '',
    nodeName: '',
})
//工作空间
const props = {
    label: 'fileName',
    children: 'children',
}
const workloading = ref(false)
const worktree = ref(false)
const workdata = ref([])
const workall = ref([])
const workref = ref()
//备份空间
const backupsdata = ref([])
// 右键菜单相关
const contextMenuVisible = ref(false)
const contextMenuVisible2 = ref(false)
const contextMenuPosition = ref({ x: 0, y: 0 })
const currentNode = ref<any>(null)
//文件差异性对比
const differential = ref(false)
function diffFile() {
    const seen = new Set();
    comparearr.value = comparearr.value.filter(item => {
        return seen.has(item.id) ? false : seen.add(item.id);
    });
    tabs.value = 1
    differential.value = true
    diffSure(false)
    diffChange(false)
    diffMiss(false)
}

function diffFile2() {
    const seen = new Set();
    comparearr.value = comparearr.value.filter(item => {
        return seen.has(item.id) ? false : seen.add(item.id);
    });
    getchayi()
}

//两棵树
function gettreeTwo() {
    getlocaltree()
    getminiotree()
}
///
const tonloading = ref(false)
const buttonmsg = ref('文件备份')
function tonstatus(){
    //sao
    automaticFileBackupData({nodeId:pathid.value,taskId:projectId.value}).then((res:any)=>{
        if(res.msg == '1'){
            tonloading.value = false
            buttonmsg.value = '文件备份'
        }else{
            tonloading.value = true
            buttonmsg.value = '处理中...'
        }
    })
}
////
function backups() {
    ElMessageBox.confirm(
        '您确定要将文件/文件夹自动备份到备份空间吗?',
        '警告',
        {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning',
        }
    )
        .then(() => {
            worktree.value = true
            worktree1.value = true
            const seen = new Set();
            comparearr.value = comparearr.value.filter(item => {
                return seen.has(item.id) ? false : seen.add(item.id);
            });
            const ids = []
            const params: any = {}
            if (comparearr.value.length > 0) {
                comparearr.value.forEach((item: any) => {
                    ids.push(item.id)
                })
                params.id = ids.join(',')
            } else {
                params.nodeId = pathid.value
                params.taskId = projectId.value
            }
            if (ids.length > 0) {
                automaticFileBackupByIds(params).then((res: any) => {
                    if (res.code == 0) {
                        ElMessage.success(res.msg)
                        tonstatus()
                        getlocaltree()
                        getminiotree()
                    }
                })
            } else {
                automaticFileBackup(params).then((res: any) => {
                    if (res.code == 0) {
                        ElMessage.success(res.msg)
                        tonstatus()
                        getlocaltree()
                        getminiotree()
                    }
                })
            }



        })

}
////////////
function getchayi() {
    const ids = []
    const params: any = {}
    if (comparearr.value.length > 0) {
        comparearr.value.forEach((item: any) => {
            ids.push(item.id)
        })
        params.id = ids.join(',')
    } else {
        params.nodeId = pathid.value
        params.taskId = projectId.value
    }
    compareLocalList(params).then((res: any) => {
        // debugger
        localOnlyFiles.value = res.data
        gettreeTwo()
    })
    compareMd5List(params).then((res: any) => {
        md5MismatchedFiles.value = res.data
        gettreeTwo()
    })
    compareMinioList(params).then((res: any) => {
        minioOnlyFiles.value = res.data
        gettreeTwo()
    })

}

//文件差异性对比结果
const comparearr: any = ref([])
const localOnlyFiles: any = ref([])
const md5MismatchedFiles: any = ref([])
const minioOnlyFiles: any = ref([])
const loading1 = ref(false)
const loading2 = ref(false)
const loading3 = ref(false)
//文件对比-新增内容
const sureSize = ref(10)
const sureTotal = ref()
const sureCurrent = ref(1)
function diffSure(row: any) {
    loading1.value = true
    const ids = []
    const params: any = {}
    if (comparearr.value.length > 0) {
        comparearr.value.forEach((item: any) => {
            ids.push(item.id)
        })
        params.id = ids.join(',')
    } else {
        params.nodeId = pathid.value
        params.taskId = projectId.value
    }
    params.size = sureSize.value
    params.current = sureCurrent.value
    compareLocal(params).then((res: any) => {
        loading1.value = false
        localOnlyFiles.value = res.data.records
        sureSize.value = res.data.size
        sureTotal.value = res.data.total
        sureCurrent.value = res.data.current
    })
}
//文件对比-变更内容
const ChangeSize = ref(10)
const ChangeTotal = ref()
const ChangeCurrent = ref(1)
function diffChange(row: any) {
    loading2.value = true
    const ids = []
    const params: any = {}
    if (comparearr.value.length > 0) {
        comparearr.value.forEach((item: any) => {
            ids.push(item.id)
        })
        params.id = ids.join(',')
    } else {
        params.nodeId = pathid.value
        params.taskId = projectId.value
    }
    params.size = ChangeSize.value
    params.current = ChangeCurrent.value
    compareMd5(params).then((res: any) => {
        md5MismatchedFiles.value = res.data.records
        loading2.value = false
        ChangeSize.value = res.data.size
        ChangeTotal.value = res.data.total
        ChangeCurrent.value = res.data.current

    })
}
//文件对比-缺失内容
const MisseSize = ref(10)
const MissTotal = ref()
const MissCurrent = ref(1)
function diffMiss(row: any) {
    loading3.value = true
    const ids = []
    const params: any = {}
    if (comparearr.value.length > 0) {
        comparearr.value.forEach((item: any) => {
            ids.push(item.id)
        })
        params.id = ids.join(',')
    } else {
        params.nodeId = pathid.value
        params.taskId = projectId.value
    }
    params.size = MisseSize.value
    params.current = MissCurrent.value
    compareMinio(params).then((res: any) => {
        minioOnlyFiles.value = res.data.records
        loading3.value = false
        MisseSize.value = res.data.size
        MissTotal.value = res.data.total
        MissCurrent.value = res.data.current

    })
}
//差异染色
// 优化后的diffColor方法
function diffColor() {
    // 1. 使用Map优化查找性能
    const createMap = (arr: any[]) => new Map(arr.map(item => [item.id, item]));
    const localMap = createMap(localOnlyFiles.value);
    const md5Map = createMap(md5MismatchedFiles.value);
    const minioMap = createMap(minioOnlyFiles.value);

    // 2. 单次遍历优化
    const processTree = (tree: any[], location: 'local' | 'minio') => {
        const stack = [...tree];

        while (stack.length) {
            const node = stack.pop();
            if (!node) continue;

            // 3. 使用位运算优化状态判断
            let status = 0;
            if (location === 'local') {
                status |= localMap.has(node.id) ? 1 : 0;
                status |= md5Map.has(node.id) ? 2 : 0;
            } else {
                status |= minioMap.has(node.id) ? 3 : 0;
                status |= md5Map.has(node.id) ? 2 : 0;
            }

            // 4. 使用按位操作确定最终状态
            node.station = status.toString(10).split('').sort().reverse()[0];

            // 5. 非递归方式处理子节点
            if (node.children?.length) {
                stack.push(...node.children);
            }
        }
    };

    // 6. 避免重复处理相同数据
    if (workdata.value.length) {
        processTree(workdata.value, 'local');
    }
    if (backupsdata.value.length) {
        processTree(backupsdata.value, 'minio');
    }

    // 7. 延迟更新UI
    nextTick(() => {
        workref.value?.setCheckedKeys([], false);
        beifentree.value?.setCheckedKeys([], false);
    });
}
function diffClose() {
    comparearr.value.length = 0
    workall.value.length = 0
    differential.value = false
    diffFile2()

}
//获取所有项目
function getProject() {
    tstaskList().then((res: any) => {
        projectArr.value = res.data
        projectId.value = projectArr.value[0].id
        gettreedata()
    })
}
//获取树数据
function gettreedata() {
    treeloading.value = true
    treeForm.value.taskId = projectId.value
    getTsNodesTree(treeForm.value).then((res: any) => {
        treedata.value = res.data
        treeloading.value = false
        if (treedata.value[0].nodeId) {
            // pathid.value = treedata.value[0].nodeId
            // nextTick(() => {
            //     treeRef.value?.setCurrentKey(pathid.value);
            // });
            // getWorkData()
        } else {
            workdata.value.length = 0
            backupsdata.value.length = 0
        }

    })
}
function handleNodeClick(data: any, node: any) {
    pathid.value = data.nodeId
    localOnlyFiles.value.length = 0
    md5MismatchedFiles.value.length = 0
    minioOnlyFiles.value.length = 0
    getlocaltree()
    getminiotree()
    tonstatus()
}
//工作空间树点击
const handleCheckChange = (
    data: any,
    checked: boolean,
    indeterminate: boolean
) => {
    // 使用Map优化查找性能
    const nodeMap = new Map(workall.value.map(item => [item.id, item]));

    // 当完全选中时添加节点
    if (checked && !indeterminate) {
        if (!nodeMap.has(data.id)) {
            workall.value = [...workall.value, data];
        }
    }
    // 当取消选中时移除节点
    else {
        workall.value = workall.value.filter(item => item.id !== data.id);
    }

    // 使用防抖优化批量操作
    const updateCompare = debounce(() => {
        // 
        comparearr.value = getclickdata(workall.value);
        const comparearrval = getclickdata(workall.value);
        // 转换节点格式
        const checkedKeys = comparearrval
            .map(node => node.id)
        nextTick(() => {
            beifentree.value?.setCheckedKeys(checkedKeys, false);
        });
    }, 50);

    updateCompare();
};
//递归整理点击参数
function getclickdata(nodes: any[]): any[] {
    // 使用 Set 存储需要排除的节点 ID
    const excludeIds = new Set<string>();
    // 使用栈结构进行迭代处理
    const stack = [...nodes];

    // 第一阶段：收集所有需要排除的子节点 ID
    while (stack.length > 0) {
        const node = stack.pop();
        if (node.isFile === "FOLDER" && node.children) {
            // 将子节点加入待处理队列
            stack.push(...node.children);
            // 收集所有子节点 ID
            node.children.forEach((child: any) => {
                excludeIds.add(child.id);
            });
        }
    }

    // 第二阶段：过滤原始数据
    return nodes.filter(node => {
        // 保留非排除项，且如果是文件夹则清空其子节点
        if (!excludeIds.has(node.id)) {
            // 创建新对象避免污染原始数据
            return {
                ...node,
                children: node.isFile === "FOLDER" ? [] : node.children
            };
        }
        return false;
    });
}
//获取本地树
function getlocaltree() {
    if (!projectId.value || !pathid.value) return;

    worktree.value = true;
    listLocalTree({ taskId: projectId.value, nodeId: pathid.value }).then((res: any) => {
        const CHUNK_SIZE = 500; // 每批处理500个节点
        const rawData = res.data.localTrees;

        // 使用requestAnimationFrame分批次处理
        const processChunk = (index = 0) => {
            const chunk = rawData.slice(index, index + CHUNK_SIZE);
            if (chunk.length > 0) {
                assignment(chunk, 'local');
                workdata.value.push(...chunk); // 分批追加数据
                requestAnimationFrame(() => processChunk(index + CHUNK_SIZE));
            } else {
                worktree.value = false;
                diffColor();
            }
        };

        workdata.value = []; // 清空旧数据
        processChunk();
    });
}
const worktree1 = ref(false)
//获取minio树
function getminiotree() {
    if (!projectId.value || !pathid.value) return;

    worktree1.value = true;
    listBackupTree({ taskId: projectId.value, nodeId: pathid.value }).then((res: any) => {
        const CHUNK_SIZE = 500;
        const rawData = res.data.minioTrees;

        const processChunk = (index = 0) => {
            const chunk = rawData.slice(index, index + CHUNK_SIZE);
            if (chunk.length > 0) {
                assignment(chunk, 'minio');
                backupsdata.value.push(...chunk);
                requestAnimationFrame(() => processChunk(index + CHUNK_SIZE));
            } else {
                worktree1.value = false;
                diffColor();
            }
        };

        backupsdata.value = [];
        processChunk();
    });
}
//数据递归赋值
function assignment(data: any[], difference: 'local' | 'minio') {
    const queue = [...data]; // 使用队列代替栈
    while (queue.length > 0) {
        const node = queue.shift();
        if (!node) continue;

        // 使用Object.defineProperty避免Vue响应式转换
        Object.defineProperty(node, 'location', {
            value: difference,
            writable: true,
            enumerable: true
        });

        Object.defineProperty(node, 'station', {
            value: '0',
            writable: true,
            enumerable: true
        });

        if (node.children?.length) {
            queue.push(...node.children);
        }
    }
}
//备份空间
const beifentree = ref()
const backupsChange = (
    data: any,
    checked: boolean,
    indeterminate: boolean
) => {
    // 使用临时变量避免多次访问响应式对象
    const currentWorkall = [...workall.value];

    // 使用Map优化查找性能
    const nodeMap = new Map(currentWorkall.map(item => [item.id, item]));

    // 当完全选中时添加节点（防止重复添加）
    if (checked && !indeterminate) {
        if (!nodeMap.has(data.id)) {
            workall.value = [...currentWorkall, data];
        }
    }
    // 当取消选中时精确移除节点
    else {
        workall.value = currentWorkall.filter(item => item.id !== data.id);
    }

    // 使用防抖优化批量操作

    const updateCompare = debounce(() => {
        // comparearr.value.length = 0
        comparearr.value = getclickdata(workall.value);
        const comparearrval = getclickdata(workall.value);
        // 转换节点格式
        const checkedKeys = comparearrval
            .map(node => node.id)
        nextTick(() => {
            workref.value?.setCheckedKeys(checkedKeys, false);
        });
    }, 50);
    updateCompare();
};
//右键菜单
const handleRightClick = (event: Event, data: any, node: any) => {
    if (data.station != '0') {
        event.preventDefault()
        currentNode.value = data
        if (data.location == 'local') {
            contextMenuVisible.value = true
        } else {
            contextMenuVisible2.value = true
        }
        contextMenuPosition.value = {
            x: (event as MouseEvent).clientX + 10,
            y: (event as MouseEvent).clientY
        }
    } else {
        contextMenuVisible.value = false
        contextMenuVisible2.value = false
    }

}
function workclick() {
    contextMenuVisible.value = false
    contextMenuVisible2.value = false
}

const handleMenuClick = (action: string, type: any) => {
    contextMenuVisible.value = false
    contextMenuVisible2.value = false
    switch (action) {
        case 'restore':
            // 处理恢复逻辑
            ElMessageBox.confirm(
                '您确定要将该文件/文件夹恢复到工作空间吗?',
                '警告',
                {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning',
                }
            )
                .then(() => {
                    const params = [{
                        path: currentNode.value.path,
                        name: currentNode.value.fileName,
                        size: currentNode.value.fileSize,
                        type: currentNode.value.isFile
                    }]
                    downloadToLocal({ parameterLists: params }).then((res: any) => {
                        if (res.code == '0') {
                            ElMessage.success("恢复成功")
                            minioOnlyFiles.value = minioOnlyFiles.value.filter((item: any) => item.id !== currentNode.value.id)
                            md5MismatchedFiles.value = md5MismatchedFiles.value.filter((item: any) => item.id !== currentNode.value.id)
                            getlocaltree()
                            getminiotree()
                        }
                    })
                })
            break
        case 'backups':
            // 处理备份逻辑
            ElMessageBox.confirm(
                '您确定要将该文件/文件夹上传到备份空间吗?',
                '警告',
                {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning',
                }
            )
                .then(() => {
                    const params = [{
                        path:  currentNode.value.path,
                        name: currentNode.value.fileName,
                        size: currentNode.value.fileSize,
                        type: currentNode.value.isFile
                    }]
                    uploadToBackup({ parameterLists: params }).then((res: any) => {
                        if (res.code == '0') {
                            ElMessage.success("上传成功")
                            localOnlyFiles.value = localOnlyFiles.value.filter((item: any) => item.id !== currentNode.value.id)
                            md5MismatchedFiles.value = md5MismatchedFiles.value.filter((item: any) => item.id !== currentNode.value.id)
                            getlocaltree()
                            getminiotree()
                            // getchayi()
                        }
                    })
                })
            break
        case 'delete':
            // 处理删除逻辑
            deleteTsFilesById({ id: currentNode.value.id, type: type }).then((res: any) => {
                if (res.code == '0') {
                    ElMessage.success("删除成功")
                    getlocaltree()
                    getminiotree()
                }

            })
            break
        case 'preview':
            // 处理预览逻辑
            openPreview(currentNode.value, type)
            break
    }
}

// 点击其他地方关闭菜单
document.addEventListener('click', () => {
    contextMenuVisible.value = false
    contextMenuVisible2.value = false
})
//备份操作
function tableBeifen(row: any) {
    ElMessageBox.confirm(
        '您确定要将该文件/文件夹上传到备份空间吗?',
        '警告',
        {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning',
        }
    )
        .then(() => {
            const params = [{
                path:  row.workPath,
                name: row.fileName,
                size: row.fileSize,
                type: row.isFile
            }]
            loading1.value = true
            loading2.value = true
            uploadToBackup({ parameterLists: params }).then((res: any) => {
                if (res.code == '0') {
                    ElMessage.success("上传成功")
                    diffSure(false)
                    diffChange(false)
                }
            })
        })

}
const beifenArr = ref([])
function bifenChange(row: any) {
    beifenArr.value = row
}
function moretableBeifen() {
    ElMessageBox.confirm(
        '您确定要将这些文件/文件夹上传到备份空间吗?',
        '警告',
        {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning',
        }
    )
        .then(() => {
            let beifenArr3 = []
            let beifenArr2 = JSON.parse(JSON.stringify(beifenArr.value))
            beifenArr2.forEach((items: any) => {
                beifenArr3.push({ path: items.workPath, name: items.fileName, size: items.fileSize, type: items.isFile })
            })
            loading1.value = true
            loading2.value = true
            uploadToBackup({ parameterLists: beifenArr3 }).then((res: any) => {
                if (res.code == '0') {
                    diffSure(false)
                    diffChange(false)
                    ElMessage.success("上传成功")
                }
            })

        })

}
//恢复操作
function tablerestore(row: any) {
    ElMessageBox.confirm(
        '您确定要将该文件/文件夹恢复到工作空间吗?',
        '警告',
        {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning',
        }
    )
        .then(() => {
            const params = [{
                path:  row.backupPath,
                name: row.fileName,
                size: row.fileSize,
                type: row.isFile
            }]
            loading3.value = true
            loading2.value = true
            downloadToLocal({ parameterLists: params }).then((res: any) => {
                if (res.code == '0') {
                    ElMessage.success("恢复成功")
                    diffChange(false)
                    diffMiss(false)
                }
            })
        })
}
const restoreArr = ref([])
function restoreChange(row: any) {
    restoreArr.value = row
}
function moretablerestore() {
    ElMessageBox.confirm(
        '您确定要将这些文件/文件夹恢复到工作空间吗?',
        '警告',
        {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning',
        }
    )
        .then(() => {
            let restoreArr3 = []

            let restoreArr2 = JSON.parse(JSON.stringify(restoreArr.value))
            restoreArr2.forEach((items: any) => {
                restoreArr3.push({ path:  items.backupPath, name: items.fileName, size: items.fileSize, type: items.isFile })
            })
            loading2.value = true
            loading3.value = true
            downloadToLocal({ parameterLists: restoreArr3 }).then((res: any) => {
                if (res.code == '0') {
                    ElMessage.success("恢复成功")
                    diffChange(false)
                    diffMiss(false)
                }
            })

        })
}
function delhuifu() {
    ElMessageBox.confirm(
        '您确定要删除这些文件/文件夹吗?(删除后将无法恢复)',
        '警告',
        {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning',
        }
    )
        .then(() => {
            const ids = ref([])
            restoreArr.value.forEach((item: any) => {
                ids.value.push(item.id)
            })
            loading3.value = true
            deleteTsFilesByIds({ ids: ids.value.join(','), type: 'minio' }).then((res: any) => {
                if (res.code == '0') {
                    ElMessage.success("删除成功")
                    diffMiss(false)
                }
            })

        })
}
//变更
const changeclick = ref([])
function handleChange(row: any) {
    changeclick.value = row
}
function bfclick(type: any) {
    let tstitle = ''
    if (type == 'bf') {
        tstitle = '您确定要将这些文件/文件夹上传到备份空间吗?'
    } else {
        tstitle = '您确定要将这些文件/文件夹恢复到工作空间吗'
    }
    ElMessageBox.confirm(
        tstitle,
        '警告',
        {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning',
        }
    )
        .then(() => {
            loading1.value = true
            loading2.value = true
            if (type == 'bf') {
                let beifenArr3 = []
                let beifenArr2 = JSON.parse(JSON.stringify(changeclick.value))
                beifenArr2.forEach((items: any) => {
                    beifenArr3.push({ path: items.workPath, name: items.fileName, size: items.fileSize, type: items.isFile })
                })
                uploadToBackup({ parameterLists: beifenArr3 }).then((res: any) => {
                    if (res.code == '0') {
                        ElMessage.success("上传成功")
                        diffChange(false)
                    }
                })
            } else {
                let restoreArr3 = []
                let restoreArr2 = JSON.parse(JSON.stringify(changeclick.value))
                restoreArr2.forEach((items: any) => {
                    restoreArr3.push({ path:  items.backupPath, name: items.fileName, size: items.fileSize, type: items.isFile })
                })
                downloadToLocal({ parameterLists: restoreArr3 }).then((res: any) => {
                    if (res.code == '0') {
                        ElMessage.success("恢复成功")
                        diffChange(false)
                    }
                })
            }


        })

}
onMounted(() => {
    getProject()
});
// 组件卸载时取消防抖（可选）
// onBeforeUnmount(() => {
//     handleCheckChange.cancel()
// })
//拖动条
const vMove = {
    mounted(el: any) {
        el.onmousedown = function (e: any) {
            var init = e.clientX;
            var parent: any = document.getElementById("silderLeft");
            const initWidth: any = parent.offsetWidth;
            document.onmousemove = function (e) {
                var end = e.clientX;
                var newWidth = end - init + initWidth;
                parent.style.width = newWidth + "px";
            };
            document.onmouseup = function () {
                document.onmousemove = document.onmouseup = null;
            };
        };
    }
}
//预览
// 文件预览相关, 视频、音频、文本、图片
const VideoPlayer = defineAsyncComponent({
    loader: () => import("@/components/file/preview/VideoPlayer.vue"),
    loadingComponent: VideoPlayerAsyncLoading
})
const TextViewer = defineAsyncComponent({
    loader: () => import("@/components/file/preview/TextViewer.vue"),
    loadingComponent: TextViewerAsyncLoading
})
const MarkdownViewer = defineAsyncComponent({
    loader: () => import("@/components/file/preview/MarkdownViewer.vue"),
    loadingComponent: MarkdownViewerDialogAsyncLoading
})
const PdfViewer = defineAsyncComponent({
    loader: () => import("@/components/file/preview/PdfViewer.vue"),
    loadingComponent: MarkdownViewerDialogAsyncLoading
})
const OfficeViewer = defineAsyncComponent({
    loader: () => import("@/components/file/preview/OfficeViewer.vue"),
    loadingComponent: MarkdownViewerDialogAsyncLoading
})
const Three3dPreview = defineAsyncComponent({
    loader: () => import("@/components/file/preview/Three3dPreview.vue"),
    loadingComponent: MarkdownViewerDialogAsyncLoading
})
//单击预览
const filePreview: any = ref({})
const ViewfileUrl: any = ref("")
const title1 = ref('')
const isViewfile = ref(false)
const fileType = ref('')
function openPreview(row: any, type: any) {
    if (getFileExtension(row.fileName) == 'pdf' || getFileExtension(row.fileName) == 'pptx' || getFileExtension(row.fileName) == 'xlsx' || getFileExtension(row.fileName) == 'xls' || getFileExtension(row.fileName) == 'docx' || getFileExtension(row.fileName) == 'doc' || getFileExtension(row.fileName) == 'bin') {
        title1.value = row.fileName
        geturl(row, type, true)
       

    } else {
        row.fileType = getFileType(row.fileName)
        filePreview.value = row
        geturl(row, type, false)
       
    }
}
function geturl(row: any, type1: any, pan: any) {
    obtainUrl({ id: row.id, type: type1 }).then((res: any) => {
        if (pan) {
            ViewfileUrl.value = res.data.url
            isViewfile.value = true
            fileType.value = getFileExtension(row.fileName)
        } else {
            filePreview.value.url = res.data.url
            localStorage.setItem('videorow', JSON.stringify(row));
            openRow(row)
        }


    })
}

const textedit = ref(false)
const rowId = ref('')
const title = ref('')
const fileUrl1 = ref('')
function testClick(row: any) {
    rowId.value = row.id
    textedit.value = true
    title.value = '编辑 - ' + row.fileName
    fileUrl1.value = row.url
}
function textClose() {
    textedit.value = false
}
//文本以表格形式预览
const textedit1 = ref(false)
const fileUrl = ref('')
function testexcelClick(row: any) {
    rowId.value = row.id
    textedit1.value = true
    title.value = row.fileName
    fileUrl.value = row.url
}
function texexceltClose() {
    textedit1.value = false
}
function getFileExtension(filename: any) {
    // 获取点号在文件名中的位置（从后往前查找）
    const lastIndex = filename.lastIndexOf('.');

    // 如果点号存在且不是文件名的第一个字符，则返回后缀名
    if (lastIndex !== -1 && lastIndex !== 0) {
        return filename.substring(lastIndex + 1);
    } else {
        // 如果没有找到点号，则返回空字符串或根据需要返回其他值
        return '';
    }
}
// 文件分类
const fileTypeMap = {
    image: ['gif', 'jpg', 'jpeg', 'png', 'bmp', 'webp', 'ico'],
    video: ['mp4', 'webm', 'm3u8', 'rmvb', 'avi', 'swf', '3gp', 'mkv', 'flv'],
    audio: ['mp3', 'wav', 'wma', 'ogg', 'aac', 'flac', 'm4a'],
    text: ['scss', 'sass', 'kt', 'gitignore', 'bat', 'properties', 'yml', 'css', 'js', 'md', 'xml', 'txt', 'py', 'go', 'html', 'less', 'php', 'rb', 'rust', 'script', 'java', 'sh', 'sql'],
    executable: ['exe', 'dll', 'com', 'vbs'],
    archive: ['7z', 'zip', 'rar', 'tar', 'gz'],
    pdf: ['pdf'],
    office: ['doc', 'docx', 'csv', 'xls', 'xlsx', "ppt", 'pptx'],
    three3d: ['dae', 'fbx', 'gltf', 'glb', 'obj', 'ply', 'stl'],
    document: ['txt', 'pages', 'epub', 'numbers', 'keynote']
};
function getFileType(name: any) {
    let fileType: any;
    for (let key in fileTypeMap) {
        let suffix = getFileSuffix(name);
        if (fileTypeMap[key].indexOf(suffix) !== -1) {
            fileType = key;
            break;
        }
    }
    return fileType;
}
function getFileSuffix(name: any) {
    let lastIndex = name.lastIndexOf('.');
    if (lastIndex === -1) {
        return 'other';
    }
    return name.substring(lastIndex + 1).toLowerCase();
}
function CloseView() {
    isViewfile.value = false
}
//tabbas
const tabs = ref(1)
</script>

<template>
    <div class="faulttemplate-box">
        <aside id="silderLeft">
            <div>
                <el-select v-model="projectId" placeholder="请选择项目" @change="gettreedata()">
                    <el-option v-for="item in projectArr" :key="item.id" :label="item.taskName" :value="item.id" />
                </el-select>
                <div class="tree_sou">
                    <el-input v-model="treeForm.nodeName" style="width:100%;margin:10px 9px 10px 0px;"
                        placeholder="节点名称" clearable :suffix-icon="Search" @change="gettreedata()" />
                </div>
                <el-tree ref="treeRef" node-key="nodeId" :data="treedata" :highlight-current="true"
                    :props="defaultProps" v-loading="treeloading" @node-click="handleNodeClick"
                    class="silderLeft-default" style="height: calc(100vh - 280px); overflow: auto;margin-top: 10px;">
                    <template #default="{ data }">
                        <span class="custom-tree-node">
                            <span class="text">{{ data.nodeName }}</span>
                        </span>
                    </template>
                </el-tree>
            </div>
            <div class="moveBtn" v-move>
                <div class="moveBtn-line"></div>
            </div>
        </aside>
        <section class="silderRight">
            <div style="display: flex;justify-content: space-between;align-items: center;">
                <div class="legend">
                    <div class="legend_box">
                        <div class="legend_color1"></div>
                        <div class="legend_text">工作空间新增文件</div>
                    </div>
                    <div class="legend_box">
                        <div class="legend_color2"></div>
                        <div class="legend_text">工作空间已删除文件</div>
                    </div>
                    <div class="legend_box">
                        <div class="legend_color3"></div>
                        <div class="legend_text">工作空间内容变动文件</div>
                    </div>
                </div>
                <div class="tree_button">
                    <el-button type="primary" :loading="tonloading" :disabled="worktree && worktree1" @click="backups()">{{buttonmsg}}</el-button>
                    <el-button type="primary" :disabled="worktree && worktree1" @click="diffFile2()">差异检测</el-button>
                    <el-button type="primary" :disabled="worktree && worktree1" @click="diffFile()">差异批量处理</el-button>
                </div>
            </div>

            <div class="tree_box">
                <div class="tree_left">
                    <div class="tree_title">工作空间:</div>
                    <div class="tree_content">
                        <!-- <el-scrollbar height="73vh"> -->
                        <el-tree-v2 ref="workref" style="max-width: 600px" :props="props" :data="workdata" :height="700"
                            @node-click="workclick" @node-contextmenu="handleRightClick" default-expand-all
                            :expand-on-click-node="false" show-checkbox node-key="id" v-loading="worktree"
                            @check-change="handleCheckChange">
                            <template #default="{ data }">
                                <span
                                    :class="data.station == '0' ? 'custom-tree-node' : (data.station == '1' ? 'custom-tree-node1' : (data.station == '2' ? 'custom-tree-node2' : 'custom-tree-node3'))">
                                    <span class="text">
                                        {{ data.fileName }}({{ data.isFile == 'FOLDER' ? data.children.length :
                                            data.fileSize + 'MB' }})
                                        <!-- <span v-if="data.station != '0'">({{ data.station == '1' ? '新增' :
                                            (data.station
                                                == '2' ? '已变更' : '已删除') }})</span> -->
                                    </span>
                                </span>
                            </template>
                        </el-tree-v2>
                        <div v-show="contextMenuVisible" class="context-menu" :style="{
                            left: `${contextMenuPosition.x}px`,
                            top: `${contextMenuPosition.y}px`
                        }">
                            <div class="menu-item" @click="handleMenuClick('backups', 'local')">备份</div>
                            <div class="menu-item" @click="handleMenuClick('delete', 'local')">删除</div>
                            <div v-if="currentNode && currentNode.isFile == 'FILE'" class="menu-item"
                                @click="handleMenuClick('preview', 'local')">预览</div>
                        </div>
                        <!-- </el-scrollbar> -->

                    </div>
                </div>
                <div class="tree_left">
                    <div class="tree_title">备份空间:</div>
                    <div class="tree_content">
                        <!-- <el-scrollbar height="73vh"> -->
                        <el-tree-v2 ref="beifentree" style="max-width: 600px" :props="props" :data="backupsdata"
                            :height="700" default-expand-all @node-click="workclick"
                            @node-contextmenu="handleRightClick" :expand-on-click-node="false" show-checkbox
                            node-key="id" v-loading="worktree1" @check-change="backupsChange">
                            <template #default="{ data }">
                                <span
                                    :class="data.station == '0' ? 'custom-tree-node' : (data.station == '1' ? 'custom-tree-node1' : (data.station == '2' ? 'custom-tree-node2' : 'custom-tree-node3'))">
                                    <!-- data.children.length -->
                                    <span class="text">{{ data.fileName }}({{ data.isFile == 'FOLDER' ?
                                        data.children.length :
                                        data.fileSize + 'MB' }})</span>
                                    <!-- <span v-if="data.station != '0'">({{ data.station == '1' ? '新增' :
                                        (data.station
                                            == '2' ? '已变更' : '已删除') }})</span> -->
                                </span>
                            </template>
                        </el-tree-v2>
                        <div v-show="contextMenuVisible2" class="context-menu" :style="{
                            left: `${contextMenuPosition.x}px`,
                            top: `${contextMenuPosition.y}px`
                        }">
                            <div class="menu-item" @click="handleMenuClick('restore', 'minio')">恢复</div>
                            <div class="menu-item" @click="handleMenuClick('delete', 'minio')">删除</div>
                            <div v-if="currentNode && currentNode.isFile == 'FILE'" class="menu-item"
                                @click="handleMenuClick('preview', 'minio')">预览</div>
                        </div>
                        <!-- </el-scrollbar> -->

                    </div>
                </div>
                <!-- 文件差异性对比 -->
                <el-dialog title="差异批量处理" v-model="differential" width="50%" :before-close="diffClose" top="30px" :close-on-click-modal="false"
                    draggable destroy-on-close>
                    <div class="tabbs_all">
                        <div @click="tabs = 1" :class="tabs == 1 ? 'tabbs_box1' : 'tabbs_box'">新增文件</div>
                        <div @click="tabs = 2" :class="tabs == 2 ? 'tabbs_box1' : 'tabbs_box'">修改文件</div>
                        <div @click="tabs = 3" :class="tabs == 3 ? 'tabbs_box1' : 'tabbs_box'">删除文件</div>
                    </div>
                    <div>
                        <!-- <el-scrollbar height="400px"> -->
                        <div class="newContent" v-if="tabs == 1">
                            <div class="newContent_title">
                                <el-button type="primary" :disabled="beifenArr.length == 0"
                                    @click="moretableBeifen()">备份</el-button>
                            </div>
                            <el-table v-loading="loading1" :data="localOnlyFiles" @selection-change="bifenChange"
                                :header-cell-style="{ background: 'rgb(250 250 250)', height: '50px' }"
                                style="width: 100%; height: calc(60vh);margin-bottom: 20px;" border>
                                <el-table-column type="selection" width="40" />

                                <el-table-column prop="fileName" label="文件名称"></el-table-column>
                                <el-table-column prop="isFile" label="文件类型" width="90">
                                    <template #default="scope">
                                        <span v-if="scope.row.isFile == 'FOLDER'">文件夹</span>
                                        <span v-else>文件</span>
                                    </template>
                                </el-table-column>
                                <el-table-column prop="fileSize" label="文件大小" width="100">
                                    <template #default="scope">
                                        <span v-if="scope.row.isFile != 'FOLDER'">{{ scope.row.fileSize + 'MB' }}</span>
                                        <span v-else>--</span>
                                    </template>
                                </el-table-column>
                                <el-table-column prop="workPath" label="路径"></el-table-column>
                                <el-table-column prop="uploadTime" width="170" label="修改日期"></el-table-column>
                                <el-table-column label="操作" width="60" align="center">
                                    <template #default="scope">
                                        <span style="display: flex;justify-content:center;">
                                            <img @click="tableBeifen(scope.row)" style="cursor: pointer;" title="备份"
                                                src="@/assets/images/beifen.png" alt="">
                                        </span>
                                    </template>
                                </el-table-column>
                            </el-table>
                            <Page :total="sureTotal" v-model:size="sureSize" v-model:current="sureCurrent"
                                @pagination="diffSure(false)">
                            </Page>
                        </div>
                        <div class="newContent" v-if="tabs == 2">
                            <div class="newContent_title">
                                <div>
                                    <el-button type="primary" :disabled="changeclick.length == 0"
                                        @click="bfclick('bei')">备份</el-button>
                                    <el-button type="primary" :disabled="changeclick.length == 0"
                                        @click="bfclick('hui')">恢复</el-button>
                                </div>
                            </div>
                            <el-table v-loading="loading2" :data="md5MismatchedFiles" @selection-change="handleChange"
                                :header-cell-style="{ background: 'rgb(250 250 250)', height: '50px' }"
                                style="width: 100%; height: calc(60vh);margin-bottom: 20px;" border>
                                <el-table-column type="selection" width="40" />
                                <el-table-column prop="fileName" label="文件名称"></el-table-column>
                                <el-table-column prop="isFile" label="文件类型" width="90">
                                    <template #default="scope">
                                        <span v-if="scope.row.isFile == 'FOLDER'">文件夹</span>
                                        <span v-else>文件</span>
                                    </template>
                                </el-table-column>
                                <el-table-column prop="fileSize" label="文件大小" width="100">
                                    <template #default="scope">
                                        <span v-if="scope.row.isFile != 'FOLDER'">{{ scope.row.fileSize + 'MB' }}</span>
                                        <span v-else>--</span>
                                    </template>
                                </el-table-column>
                                <el-table-column prop="workPath" label="路径"></el-table-column>
                                <el-table-column prop="uploadTime" width="170" label="修改日期"></el-table-column>
                                <el-table-column label="操作" width="80" align="center">
                                    <template #default="scope">
                                        <span style="display: flex;justify-content:space-around;">
                                            <img @click="tableBeifen(scope.row)" style="cursor: pointer;" title="备份"
                                                src="@/assets/images/beifen.png" alt="">
                                            <img @click="tablerestore(scope.row)" style="cursor: pointer;" title="恢复"
                                                src="@/assets/images/huifu.png" alt="">
                                        </span>
                                    </template>
                                </el-table-column>
                            </el-table>
                            <Page :total="ChangeTotal" v-model:size="ChangeSize" v-model:current="ChangeCurrent"
                                @pagination="diffChange(false)">
                            </Page>
                        </div>
                        <div class="newContent" v-if="tabs == 3">
                            <div class="newContent_title">
                                <div>
                                    <el-button type="primary" :disabled="restoreArr.length == 0"
                                        @click="moretablerestore()">恢复</el-button>
                                    <el-button type="primary" :disabled="restoreArr.length == 0"
                                        @click="delhuifu()">删除</el-button>
                                </div>

                            </div>
                            <el-table v-loading="loading3" :data="minioOnlyFiles" @selection-change="restoreChange"
                                :header-cell-style="{ background: 'rgb(250 250 250)', height: '50px' }"
                                style="width: 100%; height: calc(60vh);margin-bottom: 20px;" border>
                                <el-table-column type="selection" width="40" />
                                <el-table-column prop="fileName" label="文件名称"></el-table-column>
                                <el-table-column prop="isFile" label="文件类型" width="90">
                                    <template #default="scope">
                                        <span v-if="scope.row.isFile == 'FOLDER'">文件夹</span>
                                        <span v-else>文件</span>
                                    </template>
                                </el-table-column>
                                <el-table-column prop="fileSize" label="文件大小" width="100">
                                    <template #default="scope">
                                        <span v-if="scope.row.isFile != 'FOLDER'">{{ scope.row.fileSize + 'MB' }}</span>
                                        <span v-else>--</span>
                                    </template>
                                </el-table-column>
                                <el-table-column prop="backupPath" label="路径"></el-table-column>
                                <el-table-column prop="uploadTime" width="170" label="修改日期"></el-table-column>
                                <el-table-column label="操作" width="60" align="center">
                                    <template #default="scope">
                                        <span style="display: flex;justify-content:center;">
                                            <img @click="tablerestore(scope.row)" style="cursor: pointer;" title="恢复"
                                                src="@/assets/images/huifu.png" alt="">
                                        </span>
                                    </template>
                                </el-table-column>
                            </el-table>
                            <Page :total="MissTotal" v-model:size="MisseSize" v-model:current="MissCurrent"
                                @pagination="diffMiss(false)">
                            </Page>
                        </div>
                        <!-- </el-scrollbar> -->

                    </div>
                </el-dialog>
                <!-- 组件预览 -->
                <!-- 视频播放器 -->
                <el-dialog draggable :close-on-click-modal="false" class="zfile-video-dialog" :destroy-on-close="true" v-model="dialogVideoVisible">
                    <video-player v-if="dialogVideoVisible" ref="videoPlayer" />
                </el-dialog>
                <!-- 文本编辑器 -->
                <el-dialog draggable :close-on-click-modal="false" class="zfile-text-dialog zfile-dialog-mini-close" :destroy-on-close="true"
                    :title="filePreview.fileName" v-model="dialogTextVisible">
                    <TextViewer :file-name="filePreview.fileName" :file-url="filePreview.url"
                        v-if="dialogTextVisible && filePreview.fileName.indexOf('.md') === -1" />
                    <MarkdownViewer :file-name="filePreview.fileName" :file-url="filePreview.url"
                        v-if="dialogTextVisible && filePreview.fileName.indexOf('.md') !== -1" />
                </el-dialog>

                <!-- pdf 在线预览 -->
                <el-dialog draggable :close-on-click-modal="false" class="zfile-pdf-dialog" :title="filePreview.fileName" v-model="dialogPdfVisible">
                    <PdfViewer :file-name="filePreview.fileName" :file-url="filePreview.url" v-if="dialogPdfVisible" />
                </el-dialog>

                <!-- office 在线预览 -->
                <el-dialog draggable :close-on-click-modal="false" class="zfile-office-dialog zfile-dialog-mini-close zfile-dialog-hidden-title"
                    :title="filePreview.fileName" v-model="dialogOfficeVisible">
                    <OfficeViewer :file-name="filePreview.fileName" :file-url="filePreview.url"
                        v-if="dialogOfficeVisible" />
                </el-dialog>

                <!-- 3d 在线预览 -->
                <el-dialog draggable :close-on-click-modal="false" class="zfile-3d-dialog" :title="filePreview.fileName" v-model="dialog3dVisible">
                    <Three3dPreview :file-name="filePreview.fileName" :file-url="filePreview.url"
                        v-if="dialog3dVisible" />
                </el-dialog>
                <!-- 音频播放器 -->
                <AudioPlayer></AudioPlayer>
                <Viewfile v-if="isViewfile" :showTime="true" :title="title1" :url="ViewfileUrl" :type="fileType"
                    @update="CloseView" />
                <el-dialog :title="title" v-model="textedit" :before-close="textClose" top="30px" draggable :close-on-click-modal="false" width="60%"
                    destroy-on-close>
                    <textEdit :rowId="rowId" :fileUrl="fileUrl1" />
                    <!-- </el-scrollbar> -->
                </el-dialog>
                <el-dialog :title="title" v-model="textedit1" :before-close="texexceltClose" top="30px" draggable :close-on-click-modal="false"
                    width="60%" destroy-on-close>
                    <txtexl :file-url="fileUrl" :rowId="rowId" />
                </el-dialog>
            </div>
        </section>



    </div>
</template>

<style scoped lang="scss">
.faulttemplate-box {
    height: 100%;
    display: flex;
    display: -webkit-flex;
    background-color: #f2f4f9;

}

#silderLeft {
    width: 300px;
    padding: 10px;
    box-sizing: border-box;
    background: #fff;
    border-radius: 3px;
    position: relative;

    .tree_sou {
        display: flex;
        align-items: center;
        justify-content: space-between;
    }

    &:hover {
        .moveBtn {
            opacity: 1;
        }
    }
}

/* 拖动条 */
.moveBtn {
    height: 100%;
    width: 15px;
    padding: 0 6px;
    opacity: 0;
    position: absolute;
    right: -15px;
    top: 0;
}

.moveBtn-line {
    width: 100%;
    height: 100%;
    cursor: col-resize;
    user-select: none;
    background-color: #60bfff;
}

.silderRight {
    flex: 1;
    width: 100%;
    height: calc(100vh - 130px);
    overflow: auto;
    background-color: rgba(255, 255, 255, 1);
    border-radius: 3px;
    padding: 15px;
    // padding-bottom: 0px;
    box-sizing: border-box;
    margin-left: 15px;

    .tree_button {
        // display: flex;
        // align-items: center;
        // justify-content: flex-end;
        margin-bottom: 15px;
    }

    .tree_box {
        width: 100%;
        display: flex;
        align-items: center;
        justify-content: space-between;

        .tree_left {
            width: 49%;
            height: calc(77vh);


            .tree_title {
                font-size: 20px;
                font-weight: 400;

            }
        }
    }


}

.custom-tree-node1 {
    display: inline-block;
    width: 100%;
    font-size: 14px;
    padding: 5px;
    background-color: green;
    color: #fff;
    // margin-bottom: 5px !important;
}

.custom-tree-node2 {
    display: inline-block;
    width: 100%;
    font-size: 14px;
    padding: 5px;
    background-color: yellow;
    color: #409eff;
}

.custom-tree-node3 {
    display: inline-block;
    width: 100%;
    font-size: 14px;
    padding: 5px;
    background-color: red;
    color: #fff;
    margin-bottom: 5px !important;
}

.custom-tree-node {
    display: inline-block;
    width: 100%;
    font-size: 14px;
    padding: 5px;
    // margin-bottom: 5px !important;
}

:deep() {
    .el-radio-group {
        display: block !important;

    }
}

//文字换行处理
.text {
    overflow-wrap: break-word;
    word-break: break-all;
    white-space: normal;
    // line-height: 1.5;
    max-width: 72%;
    // border: 1px solid #000;
    // padding: 10px;
}

:deep() {
    .el-tree-node__content {
        height: auto !important;
        line-height: 20px !important;
        // padding-left: 10px !important;
    }

    .el-tree-node__content>.el-tree-node__expand-icon {
        padding: 0px !important;
    }
}

.tree_title {
    :deep(.el-tree-node.is-current > .el-tree-node__content) {

        background-color: #606266 !important;
        color: #606266 !important;
    }

}

.context-menu {
    position: fixed;
    background: #fff;
    border: 1px solid #ebeef5;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
    z-index: 9999;
    min-width: 100px;
    border-radius: 10px;

    .menu-item {
        padding: 8px 16px;
        cursor: pointer;

        &:hover {
            background: #409eff;
            color: #fff;
            border-radius: 10px;
        }
    }
}

//文件差异性对比
.newContent {
    width: 100%;

    .newContent_title {
        display: flex;
        justify-content: flex-end;
        align-items: center;
        margin-bottom: 10px;

        .newContent_title_text {
            font-size: 20px;
            font-weight: 800;
        }
    }

}

//tab
.tabbs_all {
    width: 100%;
    display: flex;
    align-items: center;
    border-bottom: 1px solid #ebeef5;
    margin-bottom: 5px;

    .tabbs_box {
        padding: 3px 5px 6px 5px;
        cursor: pointer;
        // color: #;
        box-sizing: border-box;
        font-size: 18px;
        font-weight: 600;

    }

    .tabbs_box:hover {
        color: #409eff;
        cursor: pointer;
    }

    .tabbs_box1 {
        padding: 3px 5px;
        cursor: pointer;
        color: #409eff;
        box-sizing: border-box;
        font-size: 18px;
        font-weight: 600;
        border-bottom: 3px solid #409eff;
    }
}

//
.legend {
    width: 40%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    background-color: #409eff;
    padding: 5px 8px;
    box-sizing: border-box;
    border-radius: 4px;
    color: #fff;

    .legend_box {
        display: flex;
        align-items: center;

        .legend_color1 {
            width: 14px;
            height: 14px;
            background-color: green;
        }

        .legend_color2 {
            width: 14px;
            height: 14px;
            background-color: red;
        }

        .legend_color3 {
            width: 14px;
            height: 14px;
            background-color: yellow;
        }

        .legend_text {
            font-size: 14px;
            margin-left: 10px;
        }
    }
}
</style>
