架构图的实现过程

项目需求架构图

实现代码

index.vue
<template>
    <!-- 外层div -->
    <div class="topu-container" :style="{ minWidth: `${functionDomainList.length * 330}px` }">
        <!-- 头部显示 -->
        <div class="topu-heard">
            <!-- 网关 -->
            <el-tooltip effect="dark" :content="`供应商:${gatewayObj.supplierName || ''}`" placement="top">
                <div class="topu-gateway" @click="handleNodeClick(gatewayObj)">
                    <span>{{ gatewayObj.ecuTypeCode || '' }}</span>
                </div>
            </el-tooltip>
            <!-- 域 -->
            <div class="topu-domain-list">
                <div class="topu-domain" v-for="(item, index) in functionDomainList" :key="index">{{ item }}</div>
            </div>
            <el-checkbox @click.stop.native="() => { }" @change="$event => handleChange($event, gatewayObj)"
                         v-if="selection && gatewayObj.logicalEcuAddressHex && gatewayObj.status != 3"></el-checkbox>
        </div>
        <!-- ECU树结构 -->
        <div class="topu-body">
            <!-- 获取指定域下的ECU节点 -->
            <div class="ecu-tree" v-for="(domain, index) in functionDomainList" :key="index">
                <div>
                    <template v-for="item in getNodeTree(domain)">
                        <!-- 存在下属节点 -->
                        <EcuNodeTree v-if="item.children && item.children.length > 0" :node="item" :selection="selection"
                                     :busType="busType" @change="handleChange" @click="handleNodeClick"
                                     :key="`node-tree-${item.id}`" />
                        <!-- 不存在下属节点 -->
                        <el-tooltip v-else :key="`node-${item.id}`" effect="dark"
                                    :content="`供应商:${item.supplierName || ''}`" placement="top">
                            <div class="ecu-node"
                                 :class="[backgroundMap[item.status], getBusTypeColor(item)]"
                                 @click="handleNodeClick(item)">
                                <span>{{ item.ecuTypeCode || '' }}</span>
                                <el-checkbox @click.stop.native="() => { }" @change="$event => handleChange($event, item)"
                                             v-if="selection && item.logicalEcuAddressHex && item.status != 3"></el-checkbox>
                            </div>
                        </el-tooltip>
                    </template>
                </div>
                <!-- 以太网总线类型存在-横线 -->
                <template v-for="key in getBusEthernetIndexList(getNodeTree(domain))">
                    <div class="ethernet-node" :style="{ top: `${getBusEthernetXTop(key)}px` }">{{ key }}</div>
                </template>

                <!-- Ethernet需要单独右侧一条线 -->
                <div class="bus-ethernet" :style="{ height: `${getBusEthernetHeight(getNodeTree(domain))}px` }"></div>
            </div>
        </div>
        <!-- 尾部显示 -->
        <div class="topu-footer">
            <div class="bus-legend">
                <div class="bus-item" v-for="key in Object.keys(busColos)">
                    <div class="bus-line" :class="busColos[key]"></div>
                    <div class="ml-sm">{{ key }}</div>
                </div>
            </div>
            <div class="status-legend">
                <div class="legend-item" v-for="item in legendList">
                    <div class="legend-square" :class="item.color"></div>
                    <div>{{ item.text }}</div>
                </div>
            </div>
        </div>
    </div>
</template>
<script>
    import EcuNodeTree from './ecuNode.vue'
    import { arr2tree } from 'utils'
    import { apiQueryBusPage } from 'api/CarManage/CarPartManage';

    export default {
        components: { EcuNodeTree },
        props: {
            // 总线类型颜色
            busColos: {
                type: Object,
                default: () => {
                    return {
                        CAN: 'can',
                        CANFD: 'canfd',
                        Ethernet: 'ethernet',
                        LIN: 'lin',
                        KLIN: 'klin',
                        Other: 'other'
                    }
                }
            },
            legendList: {
                type: Array,
                default: () => {
                    return [
                        { value: 3, color: 'yellow', text: 'ECU无诊断数据' },
                        { value: 2, color: 'red', text: 'ECU存在故障' },
                        { value: 1, color: 'green', text: 'ECU不存在故障' },
                        { value: 4, color: 'gray', text: 'ECU无通讯' }
                    ]
                }
            },
            // 节点信息
            node: {
                type: Array,
                default: () => {
                    return []
                }
            },
            // 域
            functionDomainList: {
                type: Array,
                default: () => {
                    return []
                }
            },
            // node背景色标识
            backgroundMap: {
                type: Object,
                default: () => {
                    return {
                        0: '',
                        1: 'green',
                        2: 'red',
                        3: 'yellow',
                        4: 'gray',
                        5: 'gray'
                    }
                }
            },
            // 是否可选
            selection: {
                type: Boolean,
                default: false
            }
        },
        data () {
            return {
                busType: {},
                // 选中项
                selections: []
            }
        },
        computed: {
            // 获取网关信息
            gatewayObj () {
                // 不存在父节点
                return this.node.find(el => !el.parentId)
            },
            // 节点转换为树结构
            ecuList () {
                const list = this.node.filter(el => el.parentId)
                return arr2tree(list)
            }
        },
        created () {
            this.getBusType()
        },
        methods: {
            /**
             * 获取指定域下的ECU节点
             * @param {*} funcDomainCode 域名标识
             */
            getNodeTree (funcDomainCode) {
                return this.ecuList.filter(el => el.funcDomainCode == funcDomainCode)
            },
            /**
             * 递归的方式获取指定域下的ECU节点中存在ethernet总线时的位置计算高度
             * @param {*} nodelist 域下的节点
             * @param {*} nodeLeveIndex 当前节点位置 位置从1计算不分层级
             * @param {*} nodeIndex[] 存在ethernet总线时的节点位置
             */
            getBusEthernet (nodelist, nodeLeveIndex = 0, nodeIndex = []) {
                nodelist.forEach(el => {
                    // 当前节点位置默认位置
                    nodeLeveIndex += 1
                    // 判断是否存在子节点
                    if (el?.children?.length > 0) {
                        this.getBusEthernet(el.children, nodeLeveIndex, nodeIndex)
                    } else {
                        // 存在Ethernet总线时,存入
                        if (this.hasEthernet(el)) {
                            nodeIndex.push(nodeLeveIndex)
                        }
                    }
                });
                return nodeIndex
            },
            // 获取以太网总线类型总长度 - y轴
            getBusEthernetHeight (nodelist) {
                const nodeList = this.getBusEthernet(nodelist, 0, [])
                const [ lastIndex ] = [ ...nodeList ].reverse()
                return lastIndex ? ((lastIndex - 1) * 57 + 35) : 0
            },
            // 获取以太网总线类型 - x轴
            getBusEthernetIndexList (nodelist) {
                return this.getBusEthernet(nodelist, 0, []) || []
            },
            // 计算以太网总线类型 - x轴Top值
            getBusEthernetXTop (index) {
                return (index - 1) * 57 + 35
            },
            /**
             * 获取总线类型颜色汇总
             * @param {*} param0 当前几点总线类型
             */
            getBusTypeColor ({ busType }) {
                const colors = busType.split(',').map(el => {
                    return this.busColos[this.busType[Number(el)]] || 'other'
                })
                return colors
            },
            // 是否存在Ethernet 总线
            hasEthernet ({ busType }) {
                const typeList = busType.split(',').map(el => {
                    return this.busType[Number(el)]
                })
                const index = typeList.findIndex(el => el == 'Ethernet')
                return index != -1
            },
            // 获取总线类型
            async getBusType () {
                const { code, data, msg } = await this.$ajax(apiQueryBusPage, { pageSize: 200, pageNumber: 1 })
                if (code === this.$OK) {
                    this.busType = {
                        ...Object.fromEntries(data.list.map(el => [ el.id, el.busType ]))
                    }
                } else {
                    this.$message.error(msg)
                }
            },
            /**
             * 多选框选择事件
             * @param {*} val 选中结果
             * @param {*} node 节点
             */
            handleChange (val, node) {
                if (val) {
                    this.selections.push(node)
                } else {
                    const index = this.selections.findIndex(el => el.id == node.id)
                    this.selections.splice(index, 1)
                }
                this.$emit('check', this.selections)
            },
            // 获取选中项
            getChecked () {
                return this.selections
            },
            // node点击事件
            handleNodeClick (item) {
                this.$emit('click', item)
            }
        }
    }
</script>
<style lang="scss" scoped>
@import "../ecuTopu.scss";
</style>
子节点代码-ecuNode.vue
<template>
    <div class="ecu-tree-node">
        <el-tooltip effect="dark" :content="`供应商:${node.supplierName || ''}`" placement="top">
            <div class="ecu-sub-node" :class="[backgroundMap[node.status], getBusTypeColor(node)]"
                 @click="$emit('handleNodeClick', node)">
                <span>{{ node.ecuTypeCode || '' }}</span>
                <el-checkbox @click.stop.native="() => { }" @change="$event => emit('change', $event, node)"
                             v-if="selection && node.logicalEcuAddressHex && node.status != 3"></el-checkbox>
            </div>
        </el-tooltip>
        <div class="ecu-tree">
            <template v-for="item in node.children">
                <EcuNodeTree v-if="item.children && item.children.length > 0" :node="item" :selection="selection"
                             :busType="busType" @change="$event => $emit('change', $event)"
                             @click="$event => $emit('click', $event)" />
                <el-tooltip v-else effect="dark" :content="`供应商:${item.supplierName || ''}`" placement="top">
                    <div class="ecu-node" :class="[backgroundMap[item.status], getBusTypeColor(item)]"
                         @click="$emit('click', item)">
                        <span>{{ item.ecuTypeCode || '' }}</span>
                        <el-checkbox @click.stop.native="() => { }" @change="$event => $emit('change', $event, item)"
                                     v-if="selection && item.logicalEcuAddressHex && item.status != 3"></el-checkbox>
                    </div>
                </el-tooltip>
            </template>

        </div>
    </div>
</template>

<script>
    export default {
        name: 'EcuNodeTree',
        props: {
            node: {
                type: Object,
                default: () => { }
            },
            busType: {
                type: Object,
                default: () => { }
            },
            // 总线类型颜色
            busColos: {
                type: Object,
                default: () => {
                    return {
                        CAN: 'can',
                        CANFD: 'canfd',
                        Ethernet: 'ethernet',
                        LIN: 'lin',
                        KLIN: 'klin',
                        Other: 'other'
                    }
                }
            },
            backgroundMap: {
                type: Object,
                default: () => {
                    return {
                        0: '',
                        1: 'green',
                        2: 'red',
                        3: 'yellow',
                        4: 'gray',
                        5: 'gray'
                    }
                }
            },
            // 是否可选
            selection: {
                type: Boolean,
                default: false
            }
        },
        methods: {
            /**
             * 获取总线类型颜色汇总
             * @param {*} param0 当前几点总线类型
             */
            getBusTypeColor ({ busType }) {
                const colors = busType.split(',').map(el => {
                    return this.busColos[this.busType[Number(el)]] || 'other'
                })
                return colors
            },
            // 是否存在Ethernet 总线
            hasEthernet ({ busType }) {
                const typeList = busType.split(',').map(el => {
                    return this.busType[Number(el)]
                })
                const index = typeList.findIndex(el => el == 'Ethernet')
                return index != -1
            }
        }
    }
</script>

<style lang="scss" scoped>
@import "../../style/ecuTopu.scss";
</style>
CSS部分
.topu-container {
    --node-height: '40px';
    position: relative;
    display: flex;
    flex-direction: column;
    height: 100%;
    width: 100%;
    background-color: #fff;
    padding: 20px;
    overflow-x: auto;

    .topu-heard {
        position: relative;
        display: flex;
        flex-direction: column;
        border-radius: 4px;
        padding: 0 20px;
        border: 1px solid #DCDFE6;
        box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.01);
        background: #EBECF0;

        .topu-gateway {
            position: relative;
            text-align: center;
            height: 42px;
            line-height: 42px;
            font-weight: bold;
        }

        .topu-domain-list {
            display: flex;
            flex-direction: row;
            justify-content: space-between;

            .topu-domain {
                height: 42px;
                line-height: 42px;
                min-width: 300px;
                flex: 1;
            }
        }

        .el-checkbox {
            position: absolute;
            right: 10px;
            top: 50%;
            transform: translateY(-50%);
        }
    }

    .topu-body {
        position: relative;
        display: flex;
        flex: 1;
        justify-content: space-between;
        overflow-y: auto;

        .ecu-tree {
            position: relative;
            width: 300px;
            margin-left: 40px;
            height: 100%;

            .ecu-sub-node,
            .ecu-node {
                position: relative;
                width: 200px;
                height: var(--node-height);
                line-height: 40px;
                margin-top: 15px;
                padding: 0 20px;
                box-sizing: border-box;
                border-radius: 4px;
                background: #EBECF0;
                border: 1px solid #DCDFE6;
                z-index: 2;

                .el-checkbox {
                    position: absolute;
                    right: 10px;
                    top: -3px;
                }
            }

            .bus-ethernet {
                position: absolute;
                top: 0;
                right: 30px;
                width: 1.5px;
                background: #F154B1;
            }
            .ethernet-node{
                // tree节点margin-left:40px 
                // ethernetY轴 right: 30px 
                width: calc(100% - 70px);
                height: 1.5px;
                left: 40px;
                position: absolute;
                background: #F154B1;
            }
        }
    }

    .topu-footer {
        display: flex;
        justify-content: space-between;
        padding: 0 30px;
        height: 40px;
        align-items: end;

        .bus-legend {
            display: flex;

            .bus-item {
                display: flex;
                align-items: center;
                margin-right: 20px;

                .bus-line {
                    width: 20px;
                    height: 3px;
                }
            }
        }

        .status-legend {
            display: flex;
            justify-content: flex-end;

            .legend-item {
                display: flex;
                flex-direction: row;
                align-items: center;
                font-size: 12px;
                margin-right: 20px;

                .legend-square {
                    width: 15px;
                    height: 15px;
                    margin-right: 5px;
                }
            }
        }
    }
}

.ecu-node {

    &::before {
        position: absolute;
        content: '';
        width: 3px;
        height: 57px;
        left: -21px;
        top: -16px;
        transform: scale(0.5, 1);
        background-color: #1989FA;
    }

    &:last-of-type {
        &::before {
            height: 38px !important;
        }
    }

    &::after {
        position: absolute;
        content: '';
        width: 20px;
        height: 3px;
        left: -21px;
        top: 20px;
        transform: scale(1, 0.5);
    }
}

.ecu-tree-node {
    position: relative;

    .ecu-node {
        width: 160px !important;
    }

    &::before {
        position: absolute;
        content: '';
        width: 3px;
        height: calc(100% + 50px);
        left: -20.2px;
        top: -16px;
        transform: scale(0.5, 1);
        background-color: #1989FA;
    }

    &:last-of-type {
        &::before {
            height: 55px;
            top: -32px;
        }
    }
}

.ecu-tree-node {
    .ecu-tree-node {
        &::before {
            position: absolute;
            content: '';
            width: 3px;
            height: calc(100% + 70px);
            left: -21px;
            top: -16px;
            transform: scale(0.5, 1);
            background-color: #1989FA;
        }
    }
}

.ecu-sub-node {
    &::after {
        position: absolute;
        content: '';
        width: 20px;
        height: 3px;
        left: -21px;
        top: 20px;
        transform: scale(1, 0.5);
        background-color: #1989FA;
    }
}

.can {
    background: #F56C6C;

    &::after {
        background-color: #F56C6C;
    }
}

.canfd {
    background: #EA7232;

    &::after {
        background-color: #EA7232;
    }
}

.ethernet {
    background: #F154B1;
}

.lin {
    background: #67C23A;

    &::after {
        background-color: #67C23A;
    }
}

.klin {
    background: #48B1DB;

    &::after {
        background-color: #48B1DB;
    }
}

.other {
    background: #8C64D0;

    &::after {
        background-color: #8C64D0;
    }
}

.green {
    background: #67C23A !important;
}

.yellow {
    background: #E6A23C !important;
}

.red {
    background: #F56C6C !important;
}

.gray {
    background: #C0C4CC !important;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值