【Vue实战】聚合选人桥

1,126 阅读4分钟

背景

  • 楼主负责的项目中,存在各种选人桥,例如:组织架构桥、本部门桥和常用选人桥。在此之前,这三个桥相安无事,各有用途。
  • 直到有一天,搞事的客户提了一个需求,把这个三个桥放到一起,可以一起选人。产品还给它起了个新名字叫做:聚合选人桥。乀(ˉεˉ 乀),这时候 就来了。淦 活

分析

聚合选人桥的结构分析
选人桥属于高频使用场景,主要承载选人的功能。
桥的页面可拆分为 人员树 页面和 已选人池 页面。
可根据桥的页面,对逻辑进行拆分。
下文中对 常用人员树 这一类嵌入的树,简称为 嵌入树

1. 人员树
    1.1. 单选时,没有勾选状态
    1.2. 多选时,有正反选、勾选状态的逻辑
    1.3. 选人桥支持搜索人员

2. 已选人池
    2.1. 包含增加和删除人员的逻辑
    2.2. 多选时,删除操作需联动人员树的勾选状态
  • 包含关系拆分 脑图拆分
  • 页面结构拆分(已完成效果) 聚合选人桥

面临的问题

  • 旧问题:三个桥分别是已经离职的三个前辈写的,代码的结构和实现的思路不一致。想简单的把代码复用,那是不可能。多选时,选人的逻辑和选中转态的逻辑强耦合,选了人,才触发选中逻辑。
  • 新特性:聚合选人桥,多选支持正反全选;已选人池的人员数据需要联动三个桥的人员树的选中状态。

思考

  • 针对旧问题: 将三个桥的代码,整合到一个新组件来实现?可行,但是三个桥的代码整合的工作,不亚于重写这三个桥,工作量大,耗时长
    对三个桥的代码进行差异对比,差异最大的那个桥进行重构,然后将三个桥以子组件的方式嵌入到新的组件?可行,减少需要重构的桥的数量,耗时少,工作量也减少
  • 针对新特性:三个桥需要提供统一的数据入口和出口,便于勾选状态的管理。

解决方案

  • 解决旧问题:通读三个桥的代码,把差异最大的一个桥,进行代码重构。 三个桥的提供一致的数据出口和入口,简化数据处理逻辑
  • 新特性的实现: 把选人和已选人触发勾选状态的逻辑解耦 ,触发勾选状态支持外部数据进行触发

谈谈具体的实现

// 1.1. 处理多个页签,采取项目中 element-ui框架的 el-tabs 实现
<template>
    ...
    <el-tabs
        v-model="activeTab"
        v-show="!inputting"
        @tab-click="handleTabClick"
        class="small-tab">
        <el-tab-pane
            v-for="tab in tabs"
            :key="tab.id"
            :name="tab.id"
            :label="tab.label"
            :lazy="true">
        </el-tab-pane>
    </el-tabs>
    ...
<template>
<script>
    methods () {
        // 在此处处理每一棵 嵌入树 的数据,达到懒加载的目的
        handleTabClick () {}
    }
</script>
// 1.2. 改造每一棵 嵌入树,抛出 select 和 unselect 两个事件,事件携带数据,并由他们的最上层父组件 进行统一处理。
<template>
    <common-person-tree
        v-for="(tree, index) in commonPersonTree"
        :key="index"
        :personList="selectedPerson"
        :tree="tree"
        :type="type"
        @select="handleSelect"
        @unselect="handleUnselect">
    </common-person-tree>
<template>

<script>
    methods () {
        // 加人
        handleSelect () {},
        // 减人
        handleUnselect () {}
    }
</script>
/**
* 1.3. 嵌入树 需要提供的能力
*
*     关键点:把 选人的逻辑 和 勾选状态的逻辑 拆分。
*
*            处理选人的逻辑时,只往父组件抛事件和传数据,由选人池进行处理。
*
*            处理状态勾选时,根据选人池的传入数据和当前组的数据,单独维护状态,而不是耦合在选人的逻辑之中。
*/

<script>
    data () {
        countNum: 0, // 维护一个计数器,记录是否达到全选状态
        isLoaded: false, // 避免二次加载
        personsData: [], // 当前组的人员数据
        isExpanded: false, // 是否展开
        allChecked: false // 是否全选
    },

    props: {
        tree: {
            type: Object,
            default: () => ({})
        },

        personList: {
            type: Array,
            default: () => ([])
        }
    },

    watch: {
        // 观察 选人池传入的 personList 再次计算 人员的选中的状态
        personList (val) {
            this.personsData = this.initCheckedState(val, this.personsData)
        },
        // 观察 计数器,判断已选人的数量  是否达到了全选的状态
        countNum (val) {
            this.allChecked = val === this.tree.personCount
        }
    },

    methods () {
        /**
         * 1、单多选
         * 2、多选支持正反选
         * 3、单选无反选
         */
        selectOnePerson (person) {},

        /**
         * 多选的 全选
         * 支持正反选
         */
        selectAllPerson () {},

        // 首次加载人员
        loadGroupPerson (groupId) {
            if (this.isLoaded) return
            this.fetchComPersGroupPers(groupId).then((res) => {
                this.isLoaded = true
                this.personsData = this.initCheckedState(this.personList, res)
            })
        },

        /**
         * 维护一个处理勾选状态的方法
         * 计算人员的选中状态
         */
        initCheckedState (personList, value) {
            this.countNum = 0
            let oIds = personList.map(item => item.oId)
            value.forEach(each => {
                if (includes(oIds, each.oId)) {
                    this.countNum++
                    this.$set(each, 'checked', true)
                } else {
                    this.$set(each, 'checked', false)
                }
            })
            return value
        }
    }
</script>
/*
 * 2. 选人池
 *       2.1 只有选人池才可以对人员的增删进行处理,其他的地方一律不能处理
 *        2.2 维护一个 vuex 对选人池的数据进行管理,具体可分为:
 */
<script>
    computed: {
      ...mapGetters({
          selectedPerson: "getSelectedPerson", // 选人池 的 总数据
      })
    },

    methods: {...mapActions([
        "addSelectedPerson", // 添加一个人员
        "addManySelectedPerson", // 添加几个人员 (传数组)
        "delSelectedPerson", // 删除一个人员
        "initSelectedPerson", // 初始化 选人池 的 总数据
        "clearSelectedPerson", // 清除所有人
    ])}
</script>

总结

  • 关键:改变了数据的流向 关键

  • 嵌入树:需要有处理自身选中状态的能力,还需要对数据传输进行改造

  • 数据只能在 选人池 进行处理

  • 要实现代码的可拓展,必须清晰地划分各个模块组件该做的事情。

  • 最近楼主一直在看算法,这趟活干下来,觉得很适合用动态规划的思想来简单总结一下。
    聚合选人桥这个大问题拆分为多个子问题,而这几个子问题又有相互联系,解决了各个子问题,也就解决了大问题。
    也可以称之简单的模块化吧!

最后,好好学习不会差,大家一起进步!