【vue】封装树形下拉框组件 el-popover+el-tree+el-select

news2024/11/24 12:54:05

父组件使用

<template>
    <div>
        {{ array }}  更多属性详见wgyTreeSelect组件
        <wgyTreeSelect
             v-model="array"
             :list="list"
             :multiple="true"
             :disabled-ids="[111,113,2]"
         />
    </div>
</template>


<script>
/* 
    注意: 默认是这种结构
                id: 'id', // ID
                label: 'name', // 显示名称
                children: 'children', // 子级字段名
                path: 'path', // 路径
                content: 'content', // 描述
                pid: 'pid', // 父id
    如果不是这种结构传入obj定义
*/
export default {
    data() {
        return {
            array: [],
            //数据源
            list: [
                {
                    id: '1', // ID
                    name: '上海市', // 显示名称
                    children: [
                        {
                            id: '2', // ID
                            name: '嘉定区', // 显示名称
                            children: [
                                {
                                    id: '3', // ID
                                    name: '江桥镇11111111111111111111111111111', // 显示名称
                                    content: '嘉定', // 描述
                                    pid: '2', // 父id
                                },
                                {
                                    id: '4', // ID
                                    name: '安亭镇', // 显示名称
                                    content: '安亭', // 描述
                                    pid: '2', // 父id
                                },
                            ], // 子级字段名
                            content: '嘉定', // 描述
                            pid: '1', // 父id
                        },
                        {
                            id: '2000000', // ID
                            name: '嘉定2区', // 显示名称
                            children: [
                                {
                                    id: '3876543', // ID
                                    name: '江桥2镇11111111111111111111111111111', // 显示名称
                                    content: '嘉定', // 描述
                                    pid: '2000000', // 父id
                                },
                            ], // 子级字段名
                            content: '嘉定', // 描述
                            pid: '1', // 父id
                        },
                    ], // 子级字段名
                    content: '上海魔都', // 描述
                    pid: '0', // 父id
                },
                {
                    id: '11', // ID
                    name: '北京市', // 显示名称
                    children: [
                        {
                            id: '12', // ID
                            name: '朝阳区', // 显示名称
                            children: [
                                {
                                    id: '13', // ID
                                    name: '三里屯', // 显示名称
                                    content: '三里屯', // 描述
                                    pid: '12', // 父id
                                },
                                {
                                    id: '111', // ID
                                    name: '四里屯--------------', // 显示名称
                                    pid: '12', // 父id
                                },
                                {
                                    id: '113', // ID
                                    name: '五里屯--------------', // 显示名称
                                    pid: '12', // 父id
                                },
                                {
                                    id: '123', // ID
                                    name: '六里屯--------------', // 显示名称
                                    pid: '12', // 父id
                                },
                                {
                                    id: '11111', // ID
                                    name: '七里屯--------------', // 显示名称
                                    pid: '12', // 父id
                                },
                                {
                                    id: '11311', // ID
                                    name: '八里屯--------------', // 显示名称
                                    pid: '12', // 父id
                                },
                                {
                                    id: '12312', // ID
                                    name: '九里屯--------------', // 显示名称
                                    pid: '12', // 父id
                                },
                                {
                                    id: '11113331', // ID
                                    name: '十里屯--------------', // 显示名称
                                    pid: '12', // 父id
                                },
                                {
                                    id: '11344411', // ID
                                    name: '十一里屯--------------', // 显示名称
                                    pid: '12', // 父id
                                },
                                {
                                    id: '12555312', // ID
                                    name: '十二里屯--------------', // 显示名称
                                    pid: '12', // 父id
                                },
                                {
                                    id: '14', // ID
                                    name: '左家庄--------------', // 显示名称
                                    content: '左家庄', // 描述
                                    pid: '12', // 父id
                                },
                            ], // 子级字段名
                            content: '朝阳', // 描述
                            pid: '11', // 父id
                        },
                    ], // 子级字段名
                    content: '北京', // 描述
                    pid: '0', // 父id
                },
            ],
            
        };
    },
}
</script>

子组件

<template>
    <div>
        <el-popover
            v-model="isShowSelect"
            placement="bottom-start"
            :width="popoverWidth"
            :close-on-click-modal="false"
            trigger="manual"
            @hide="popoverHide"
        >
            <el-tree
                ref="tree"
                v-bind="$attrs"
                class="common-tree"
                :width="width"
                :data="treeData"
                :props="obj"
                :show-checkbox="multiple"
                :node-key="obj.id"
                :check-strictly="checkStrictly"
                :default-expanded-keys="defaultKeys"
                :expand-on-click-node="multiple&&expandClickNode"
                :check-on-click-node="checkClickNode"
                :highlight-current="true"
                @check-change="nodeClick"
                @node-click="nodeClick"
            />
            <el-select
                slot="reference"
                ref="select"
                v-bind="$attrs"
                v-model="returnDataKeys"
                :size="size"
                :width="width"
                :multiple="multiple"
                :clearable="clearable"
                :collapse-tags="collapseTags"
                class="tree-select"
                @click.native="selectClick"
                @remove-tag="removeTag"
                @clear="clear"
                @mouseenter.native="showCloseIcon = true"
                @mouseleave.native="showCloseIcon = false"
            >
                <el-option
                    v-for="item in options"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                />
                <!-- 这里是删除整个下拉框的内容,多选的时候,是没有这个功能的,所有自己弄了一个icon -->
                <template
                    slot="prefix"
                >
                    <i
                        v-if="multiple && clearable && returnDataKeys.length && showCloseIcon"
                        class="el-icon-close"
                        @click.stop="clearSelectedNodes"
                    ></i>
                    <i></i>
                </template>
            </el-select>
        </el-popover>
    </div>
</template>

<script>
export default {
    name: 'TreeSelect',
    props: {
        // 绑定的值
        value: {
            tyep: Object,
        },
        // 树结构数据
        list: {
            type: Array,
            default: () => ([]),
        },
        obj: {
            type: Object,
            required: false,
            default: () => ({
                id: 'id', // ID
                label: 'name', // 显示名称
                children: 'children', // 子级字段名
                path: 'path', // 路径
                content: 'content', // 描述
                pid: 'pid', // 父id
            }),
        },
        // 配置是否可多选
        multiple: {
            type: Boolean,
            default: false,
        },
        // 配置是否可清空选择,只对单选时生效, 多选时自定义清空按钮
        clearable: {
            type: Boolean,
            default: true,
        },
        // 配置多选时是否将选中值按文字的形式展示
        collapseTags: {
            type: Boolean,
            default: false,
        },
        // 显示复选框情况下,是否严格遵循父子不互相关联
        checkStrictly: {
            type: Boolean,
            default: false,
        },
        // 多选时设置点击节点是否可以选中,false是只有点击多选框才选中,点击元素不选中
        checkClickNode: {
            type: Boolean,
            default: false,
        },
        // 多选时:点击节点展开还是点三角标,true是点击节点展开,false是点击三角形展开
        expandClickNode: {
            type: Boolean,
            default: false,
        },
        size: {
            type: String,
            default: 'small',
        },
        width: {
            type: String,
            default: '200px',
        },
        // 父节点全选时, 是否只展示子节点
        onlyLeaf: {
            type: Boolean,
            default: false,
        },
        // 父节点全选时,是否只展示父节点
        mergeTag: {
            type: Boolean,
            default: false,
        },
        // 外部点击是否收起树形, 默认收起
        closeOnOutsideClick: {
            type: Boolean,
            default: true,
        },
        // 禁止选中的数据
        disabledIds: {
            type: Array,
            default: () => ([]),
        },
    },
    // 上面是父组件可传入参数
    data() {
        return {
            defaultKeys: [], // 默认展开的节点
            defaultKey: '',
            first: false, //
            popoverWidth: '0px', // 下拉框大小
            isShowSelect: false, // 是否显示树状选择器
            options: [], // select option选项
            returnDatas: [], // 返回给父组件数组对象
            returnDataKeys: [], // 返回父组件数组主键值
            showCloseIcon: false, // 清空icon
        };
    },
    computed: {
        treeData() {
            // 判断传入的数据是不是树形结构
            const isTreeStructure = JSON.stringify(this.list).indexOf(this.obj.children) !== -1;
            // 是树形结构,就返回传入的数据, 不是的话格式化成树形结构
            return isTreeStructure ? this.list : this.toTreeStructure(this.list);
        },
    },
    watch: {
        // 是否显示树状选择器
        isShowSelect() {
            // 隐藏select自带的下拉框
            this.$refs.select.blur();
        },
        // 监听tree数据
        treeData() {
            this.$nextTick(() => {
                this.init();
            });
        },
        // 监听value从新赋值
        value: {
            handler(val) {
                this.$nextTick(() => {
                    if (this.multiple) {
                        this.defaultKeys = val;
                    } else {
                        this.defaultKey = val;
                    }
                    this.init();
                });
            },
            immediate: true,
        },
        // 监听选中的值
        returnDataKeys: {
            handler(val) {
                if (this.first || val) {
                    this.first = true;
                    this.$emit('input', val);
                } else {
                    this.first = true;
                }
            },
        },
        // 监听禁用数组
        disabledIds: {
            handler(val) {
                console.log(val);
                this.addDisabledProperty(this.list, val);
            },
        },
    },
    mounted() {
        if (this.closeOnOutsideClick) {
            document.addEventListener('click', this.handleClickOutside);
        }
    },
    beforeDestroy() {
        if (this.closeOnOutsideClick) {
            document.removeEventListener('click', this.handleClickOutside);
        }
    },
    methods: {
        // 传入数据类型不是树形结构,转成树形结构
        toTreeStructure(list) {
            console.log(list);
            const map = {}; let node; const roots = []; let
                i;
            for (i = 0; i < list.length; i += 1) {
                map[list[i].id] = i; // 初始化map
                list[i].children = []; // 初始化children
            }
            for (i = 0; i < list.length; i += 1) {
                node = list[i];
                if (node.pid !== '0') {
                    // 如果有父级
                    list[map[node.pid]].children.push(node);
                } else {
                    // 如果没有父级,则为根节点
                    roots.push(node);
                }
            }
            return roots;
        },

        // 点击其他元素, 树隐藏
        handleClickOutside(event) {
            if (!this.$refs.select?.$el?.contains(event.target)) {
                this.isShowSelect = false;
            }
        },

        init() {
            //  如果是多选,
            if (this.multiple) {
                // 且默认展开的节点大于0
                if (Array.isArray(this.defaultKeys) && this.defaultKeys.length > 0) {
                    // 检测this.defaultKeys[0]是否是一个对象。
                    if (Object.prototype.toString.call(this.defaultKeys[0]).indexOf('Object') !== -1) { // 对象
                        this.setDatas(this.defaultKeys);
                    // 检测this.defaultKeys[0]是否是一个数字或者字符串。
                    } else if (Object.prototype.toString.call(this.defaultKeys[0]).indexOf('Number') !== -1
                            || Object.prototype.toString.call(this.defaultKeys[0]).indexOf('String') !== -1) {
                        this.setKeys(this.defaultKeys);
                    } else {
                        console.log('多选:传入参数类型不匹配');
                    }
                }
            } else {
                // 单选
                if (Object.prototype.toString.call(this.defaultKey).indexOf('Number') !== -1
                    || Object.prototype.toString.call(this.defaultKey).indexOf('String') !== -1
                    || Object.prototype.toString.call(this.defaultKey).indexOf('Object') !== -1) {
                    this.setKey(this.defaultKey);
                } else {
                    console.log('单选:传入参数类型不匹配');
                }
            }
        },

        // 下拉框select点击[入口]
        selectClick() {
            this.isShowSelect = !this.isShowSelect;
        },

        // 节点被点击  
        nodeClick(a, node) {
            // 单选
            if (!this.multiple) {
                this.isShowSelect = false;
                this.setKey(node.key);
            // 多选
            } else {
                // 所有被选中的节点的 key 所组成的数组数据
                const checkedKeys = this.$refs.tree.getCheckedKeys();
                let selectedNodes = checkedKeys.map((item) => {
                    // 所有被选中的节点对应的node
                    const { data, label, key } = this.$refs.tree.getNode(item);
                    return { label, value: key, data };
                });

                // 如果onlyLeaf为true,mergeTag为false只保留叶子节点
                if (this.onlyLeaf && !this.mergeTag) {
                    selectedNodes = selectedNodes.filter((n) => !n.data.children);
                }

                // 如果mergeTag为true,onlyLeaf为false只保留父节点
                if (this.mergeTag && !this.onlyLeaf) {
                    selectedNodes = selectedNodes.filter((n) => {
                        // 判断当前节点是否有父节点
                        if (n.data.pid) {
                            // 判断当前节点的父节点是否也在selectedNodes中
                            const parentInSelectedNodes = selectedNodes.some((upN) => upN.data.id === n.data.pid);
                            // 如果父节点也在selectedNodes中,就将当前节点从selectedNodes中移除
                            return !parentInSelectedNodes;
                        }
                        return true;
                    });
                }

                // 设置option选项
                this.options = selectedNodes;
                this.returnDataKeys = selectedNodes.map((item) => item.value);
                this.returnDatas = selectedNodes.map((n) => n.data);
            }
        },

        // 单选:清空选中
        clear() {
            this.$refs.tree.setCurrentKey(null);// 清除树选中key
            this.returnDatas = null;
            this.returnDataKeys = '';
            this.popoverHide();
            this.isShowSelect = false;
        },

        // 单选:设置、初始化值 key
        setKey(thisKey) {
            if (thisKey) {
                // 设置当前选中的值, 获取当前值对应的节点, 设置当前节点
                this.$refs.tree.setCurrentKey(thisKey);
                const node = this.$refs.tree.getNode(thisKey);
                this.setData(node.data);
            }
        },

        // 单选:设置、初始化对象
        setData(data) {
            this.options = [];
            this.options.push({ label: data[this.obj.label], value: data[this.obj.id] });
            this.returnDatas = data;
            this.returnDataKeys = data[this.obj.id];
        },

        // 多选:设置、初始化值 keys
        setKeys(checkedKeys) {
            // 给树状选择器设置选中的节点。 给select赋值
            this.$refs.tree.setCheckedKeys(checkedKeys);
            this.returnDataKeys = checkedKeys;

            const selectedNodes = checkedKeys.map((item) => {
                // 所有被选中的节点对应的node
                const node = this.$refs.tree.getNode(item);
                return { label: node.label, value: node.key, data: node.data };
            });
            this.returnDatas = selectedNodes.map((node) => node.data);
            this.popoverHide();
        },

        // 多选:设置、初始化对象  
        setDatas(data) {
            // 获取选中节点的主键值
            const checkedKeys = data.map((item) => item[this.obj.id]);
            // 设置树状选择器的选中节点
            this.$refs.tree.setCheckedKeys(checkedKeys);
            // 设置返回给父组件的数组对象
            this.returnDatas = data;
            this.returnDataKeys = checkedKeys;
            this.popoverHide();
        },

        // 多选模式下,当删除选中的节点时,将该节点及其子节点设置为未选中状态,并更新选中的节点。 ok
        removeTag(val) {
            // 获取删除的节点
            const node = this.$refs.tree.getNode(val);
            // 判断获取的节点是否为叶子节点
            const isLeafNode = node.childNodes.length === 0;
            // 是叶子节点,那么只需要将该节点设置为未选中状态
            // 不是叶子节点,那么需要将该节点及其所有子节点设置为未选中状态
            const nodesToUncheck = isLeafNode ? [node] : this.treeToList(node);

            // 将每个节点设置为未选中状态。
            nodesToUncheck.forEach((n) => this.$refs.tree.setChecked(n, false));

            // 更新选中的节点。
            this.nodeClick();
            this.popoverHide();
        },

        // 下拉框关闭执行
        popoverHide() {
            this.$emit('getValue', this.returnDataKeys, this.returnDatas);
        },

        // 多选,清空所有被选中的节点
        clearSelectedNodes() {
            this.$refs.tree.setCheckedKeys([]);
        },

        // 树形转为集合
        treeToList(tree) {
            let queen = [];
            const out = [];
            queen = queen.concat(tree);
            while (queen.length) {
                const first = queen.shift();
                if (first.childNodes) {
                    queen = queen.concat(first.childNodes);
                }
                out.push(first);
            }
            return out;
        },

        // 禁用某个节点
        addDisabledProperty(data, ids) {
            data.forEach((item) => {
                if (ids.includes(+item.id)) {
                    item.disabled = true;
                }
                if (item.children) {
                    this.addDisabledProperty(item.children, ids);
                }
            });
        },

    },
};
</script>

<style scoped lang="scss">
::v-deep .el-input__prefix{
    position: absolute;
    height: 100%;
    right: 5px;
    .el-icon-close {
        position: absolute;
        right: 6px;
        top: 11px;
        z-index: 1;
        border-radius:50%;
        color: #fff;
        background-color: #B0B3B8;
    }
}
    .mask{
        height: 100%;
        position: fixed;
        top: 0;
        left: 0;
        opacity: 0;
        z-index: 11;
    }
    .common-tree{
        overflow: auto;
    }
    .tree-select{
        position: relative;
        z-index: 111;
    }
    .ok{
        float: right;
    }
    .el-row{
        padding-top: 0px !important;
    }
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1167936.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

本地通过跳板机连接开发机流程简单记录

1、配置跳板机 主机名、端口、用户名&#xff0c;这些都是跳板机的&#xff0c;填完后选择PublicKey&#xff0c;点属性 属性设置打开后&#xff0c;选择使用全局公钥设置->使用身份或整数文件->配上相应的私钥地址&#xff0c;再选择使用整数作为武安市SSH2密钥指纹&am…

多线程环境下使用责任链,串链了

问题描述&#xff1a; 系统定义了一套校验链&#xff1a;链1,链2,,,链N&#xff1b;交由不同的业务初始化自定义的链结构。比如 业务a&#xff1a;定义的是链1、链3 业务b&#xff1a;定义的是链2、链4 结果&#xff1a; 业务a走的链成2、4了。 public abstract class Ch…

会声会影2024出来了吗?有哪些新功能?剪辑后音乐剪辑教程

会声会影 2024视频编辑软件&#xff0c;既加入光影、动态特效的滤镜效果&#xff0c;也提供了与色彩调整相关的LUT配置文件滤镜&#xff0c;可选择性大&#xff0c;运用起来更显灵活。会声会影在用户的陪伴下走过20余载&#xff0c;经过上百个版本的优化迭代&#xff0c;已将操…

硬件工程师到底可以从哪些方面提升自己?

大家好,这里是大话硬件。 最近在大话硬件群里,聊得比较多的就是讨论怎么提升自己的能力,怎么拿到更高的工资。我想,这可能并不是只在大话硬件群才有的话题,其实在每一位工作的人心里应该都在想的两个问题。 因此,这篇文章简单分享一下,作为一名硬件工程师,可以在做哪…

【LeetCode刷题-链表】--876.链表的中间结点

876.链表的中间结点 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode next) { this.val val; this.next next; }* }*…

Jmeter压测 —— 1秒发送1次请求

场景&#xff1a;有时候测试场景需要设置请求频率为一秒一次&#xff08;或几秒一次&#xff09;实现方法一&#xff1a;1、首先需要在线程组下设置循环次数&#xff08;可以理解为请求的次数&#xff09; 次数设置为请求300次&#xff0c;其中线程数跟时间自行设置 2、在设置…

Python数据分析实战-筛选出DataFrame中指定列都不包含缺失值的记录(附源码和实现效果)

实现功能 筛选出DataFrame中指定列都不包含缺失值的记录 实现代码 import pandas as pd# 创建示例DataFrame data {A: [1, 2, 3, None, 5],B: [1, None, 3, 4, 5],C: [1, 2, 3, 4, 5] } df pd.DataFrame(data)# 筛选出指定列都不包含缺失值的记录 columns_to_check [A, B…

【四、http】go的http的文件下载

一、日常下载图片到本地 //下载文件func downloadfile(url, filename string) {r, err : http.Get(url)if err ! nil {fmt.Println("err", err.Error())}defer r.Body.Close()f, err : os.Create(filename)if err ! nil {fmt.Println("err", err.Error())…

段错误如何调试

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言段错误产生的原因问题1&#xff1a;访问不存在的内存地址问题2&#xff1a;访问只读的内存地址问题3&#xff1a;栈溢出问题4&#xff1a;内存越界如何解决段错…

海康监控摄像机和录像机接入LiveMedia GB28181平台实现远程调取监控视频

海康威视各种型号监控摄像头或硬盘录像机&#xff08;NVR/HVR&#xff09;接入LiveMedia GB28181平台配置过程都非常简单明了&#xff0c;但有些细节需要注意&#xff0c;避免走弯路。 1、基本要求 (1) 网络要求 总体来说&#xff0c;只要监控设备和GB28181平台的网络是连通…

RT-DETR 项目【训练】【验证】【推理】脚本

文章目录 训练 --train.py推理 --detect.py验证 --val.py不训练,只查看模型结构/参数量 --test.py有同学问 RT-DETR 怎么训练,其实和 YOLOv8 几乎一样,但是有很多同学没接触过 v8 我这里直接给大家写好几个脚本,大家直接在我的脚本上调节参数就可以训练了, 脚本包含【训…

使用Gorm进行CRUD操作指南

使用GORM在Go中创建、读取、更新和删除记录的逐步教程 在数据库管理中&#xff0c;CRUD操作是应用程序的支柱&#xff0c;它们使数据的创建、检索、更新和删除成为可能。强大的Go对象关系映射库GORM通过抽象SQL语句的复杂性&#xff0c;使这些操作变得轻松。本文将作为您全面指…

内网穿透Windows下快速搭建个人WEB项目无需服务器

&#x1f4d1;前言 本文主要是windows下内网穿透文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &#x1f304;每日一句&#xff1a;努力…

实测用AI还原让子弹飞名场面

最近这两天&#xff0c;郭德纲说英语相声的视频刷到了一遍又一遍&#xff0c;这些视频并非简单的配音或者AI变声&#xff0c;而是高度贴合人物声线和风格、甚至连嘴型都匹配&#xff0c;如果不仔细看&#xff0c;你根本看不出任何破绽&#xff0c;因为内容是郭德纲的&#xff0…

Linux查看磁盘、内存、cpu信息

1. 查看磁盘空间信息&#xff1a;lsblk 2. 查看内存信息&#xff1a;awk $3"kB"{$2$2/1024^2;$3"GB";} 1 /proc/meminfo | column -t 3. 查看cup相关信息&#xff1a;lscup

解决ModuleNotFoundError: No module named ‘yaml‘

报错&#xff1a;ModuleNotFoundError: No module named yaml 使用&#xff1a; pip install yaml 仍然报错&#xff1a; 最终解决方案&#xff1a; pip install pyyaml 或者 conda install pyyaml

【Redis】数据类型前置知识

文章目录 前置知识redis的单线程架构 前置知识 type命令实际返回的就是当前键的数据结构类型&#xff0c;它们分别是&#xff1a;string&#xff08;字符串&#xff09;、list&#xff08;列表&#xff09;、hash&#xff08;哈希&#xff09;、set&#xff08;集合&#xff0…

各位社区工作者!打工而已,不要太上头!!

社工家人们&#xff0c;打工而已&#xff0c;不要太上头&#xff01;咱能偷懒的就偷懒啊&#xff01; 合情合理的偷懒不仅让你更轻松&#xff0c;工作效率还会提高&#xff0c;何乐而不为呢&#xff01;&#xff01;就比如说各种报告啊&#xff0c;活动方案这些啊&#xff0c;…

Latex编辑记录

1 第一段默认是不首行缩进的 在最前面合适的地方&#xff0c;也即是有usepackage类似定义的地方粘贴下列语句 \usepackage{indentfirst} 然后在要首行缩进的地方&#xff0c;放下面这个语句即可 \setlength{\parindent}{2em} 2 公式编辑 3 对文本高亮标记 \usepackage{soul}…

【前端设计】HTML+CSS+JavaScript基本特性

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…