Vue 折騰記 - (19) 基於Antd Design Vue 封裝一個符合業務的樹形元件

CRPER發表於2019-04-23

前言

原型上有個許可權分配的功能;

Vue 折騰記 - (19)  基於Antd Design Vue 封裝一個符合業務的樹形元件

仔細翻了下對應的文件(antd vue),發現有那麼一個樹形控制元件,但是沒有上面部分全域性控制的功能。

那麼只能自己動手改造出一個符合業務的了,有興趣的看官可以瞅瞅。


效果圖

Vue 折騰記 - (19)  基於Antd Design Vue 封裝一個符合業務的樹形元件

實現的思路

首先先梳理下要實現的功能點

  • 要考慮預設值的傳遞以及產生的聯動
  • 全域性開關對樹控制元件產生的影響
  • 子項操作要反饋給全域性實現聯動;
  • 最後避免太多伺服器資源(若是勾選一次觸發一次有點大),回撥改由按鈕觸發提交到外部

確定了功能點就可以開始搞起了,為此我實現過三個版本;

第一版是switch開關控制元件來控制, 為此樹元件和開關組是抽離出兩個獨立元件,發現很難避免一些極端的操作行為;

第二版用的.sync+ switch來實現,發現一樣難避免一些極端的操作行為;

第三版是改由按鈕組去實現,發現可以很好的解決極端的情況,可以分解成三種情況去實現。

程式碼實現

  • TreePanel.vue
<template>
  <div :bordered="false" :bodyStyle="{ padding: 0 }">
    <a-row type="flex" justify="space-between" align="middle">
      <a-col :span="20">
        <a-radio-group :size="size" @change="onCheckedBtnGroupChange" v-model="allChecked" buttonStyle="solid">
          <a-radio-button value="2" :disabled="true">區域性選中</a-radio-button>
          <a-radio-button value="1">全選</a-radio-button>
          <a-radio-button value="0">全不選</a-radio-button>
        </a-radio-group>
        <a-divider type="vertical" />
        <a-radio-group :size="size" @change="onExpandedBtnGroupChange" v-model="allExpanded" buttonStyle="solid">
          <a-radio-button value="2" :disabled="true">區域性展開</a-radio-button>
          <a-radio-button value="1">展開所有</a-radio-button>
          <a-radio-button value="0">摺疊所有</a-radio-button>
        </a-radio-group>
      </a-col>

      <a-col>
        <a-button type="primary" :size="size" href="javascript:;" @click="callBackData">
          <span>提交改動</span>
        </a-button>
      </a-col>
    </a-row>
    <a-tree
      :expandedKeys="innerExpandedKeys"
      :treeData="treeData"
      checkable
      :checkedKeys="innerCheckedKeys"
      @expand="onExpand"
      @check="onCheck"
    />
  </div>
</template>
<script>
import { getTreeKey } from './utils';

export default {
  props: {
    size: {
      // 控制元件規格
      type: String,
      default: 'small'
    },
    checkedKeys: {
      // 傳遞選中的key
      type: Array,
      default: function() {
        return [];
      }
    },
    expandedKeys: {
      // 傳遞需要展開的key
      type: Array,
      default: function() {
        return [];
      }
    },
    treeData: {
      type: Array,
      default: function() {
        return [
          {
            title: '0-0',
            key: '0-0',
            children: [
              {
                title: '0-0-0',
                key: '0-0-0',
                children: [
                  { title: '0-0-0-0', key: '0-0-0-0' },
                  { title: '0-0-0-1', key: '0-0-0-1' },
                  { title: '0-0-0-2', key: '0-0-0-2' }
                ]
              },
              {
                title: '0-0-1',
                key: '0-0-1',
                children: [
                  { title: '0-0-1-0', key: '0-0-1-0' },
                  { title: '0-0-1-1', key: '0-0-1-1' },
                  { title: '0-0-1-2', key: '0-0-1-2' }
                ]
              },
              {
                title: '0-0-2',
                key: '0-0-2'
              }
            ]
          },
          {
            title: '0-1',
            key: '0-1',
            children: [
              { title: '0-1-0-0', key: '0-1-0-0' },
              { title: '0-1-0-1', key: '0-1-0-1' },
              { title: '0-1-0-2', key: '0-1-0-2' }
            ]
          },
          {
            title: '0-2',
            key: '0-2'
          }
        ];
      }
    }
  },
  data() {
    return {
      allChecked: '', // 全選按鈕組
      allExpanded: '', // 全部展開按鈕組
      innerCheckedKeys: [], // 選中的值
      innerExpandedKeys: [] //  展開的值
    };
  },
  watch: {
    checkedKeys: {
      // 複製props
      immediate: true,
      deep: true,
      handler(newValue, oldValue) {
        if (newValue) {
          if (newValue.length === this.getTreeAllKey.length) {
            this.allChecked = '1';
          } else if (newValue.length === 0) {
            this.allChecked = '0';
          } else {
            this.allChecked = '2';
          }
          this.innerCheckedKeys = newValue;
        }
      }
    },
    expandedKeys: {
      // 複製props
      immediate: true,
      deep: true,
      handler(newValue, oldValue) {
        if (newValue) {
          if (newValue.length === this.getTreeAllGroupKey.length) {
            this.allExpanded = '1';
          } else if (newValue.length === 0) {
            this.allExpanded = '0';
          } else {
            this.allExpanded = '2';
          }
          this.innerExpandedKeys = newValue;
        }
      }
    }
  },
  computed: {
    switchDataSource() {
      // 勾選資料來源
      return [
        {
          type: 'ALL_CHECKED',
          labelText: this.isAllChecked ? '全不選' : '全選',
          checked: this.isAllChecked
        },
        {
          type: 'ALL_EXPAND',
          labelText: this.isAllExpanded ? '摺疊所有' : '展開所有',
          checked: this.isAllExpanded
        }
      ];
    },
    cacheEmitValue() {
      // 快取響應的值
      return {
        checkedKeys: this.innerCheckedKeys,
        expandedKeys: this.innerExpandedKeys
      };
    },
    getTreeAllKey() {
      // 獲取樹的所有key
      return getTreeKey(this.treeData);
    },
    getTreeAllGroupKey() {
      // 獲取樹的所有組key
      return getTreeKey(this.treeData, true);
    },
    isAllChecked() {
      // 是否全部勾選
      return this.innerCheckedKeys.length === this.getTreeAllKey.length;
    },
    isAllExpanded() {
      // 是否全部展開
      return this.innerExpandedKeys.length === this.getTreeAllGroupKey.length;
    }
  },

  methods: {
    onExpandedBtnGroupChange({ target: { value: expanedBtnGroupValue } }) {
      console.log('expanedBtnGroupValue: ', expanedBtnGroupValue);
      switch (expanedBtnGroupValue) {
        case '0':
          this.innerExpandedKeys = [];
          break;
        case '1':
          this.innerExpandedKeys = this.getTreeAllGroupKey;
          break;
        default:
          break;
      }
    },
    onCheckedBtnGroupChange({ target: { value: checkedBtnGroupValue } }) {
      console.log('checkedBtnGroupValue: ', checkedBtnGroupValue);
      switch (checkedBtnGroupValue) {
        case '0':
          this.innerCheckedKeys = [];
          break;
        case '1':
          this.innerCheckedKeys = this.getTreeAllKey;
          break;
        default:
          break;
      }
    },
    callBackData(emit) {
      // 響應改動的值
      if (emit) {
        this.$emit('change', this.cacheEmitValue);
      }
      return false;
    },
    onExpand(expandedKeys) {
      if (expandedKeys.length === this.getTreeAllGroupKey.length) {
        this.allExpanded = '1';
      } else {
        this.allExpanded = '2';
      }
      this.innerExpandedKeys = expandedKeys;
    },
    onCheck(checkedKeys) {
      if (checkedKeys.length === this.getTreeAllKey.length) {
        this.allChecked = '1';
      } else {
        this.allChecked = '2';
      }
      this.innerCheckedKeys = checkedKeys;
    }
  }
};
</script>



複製程式碼
  • utils.js:遞迴獲取需要的資料
/**
 * @param arr 陣列物件
 * @param parent 是否只獲取父一層的屬性
 * @description 遞迴獲取需要的資料
 */
export function getTreeKey(arr, parent = false) {
  const dataList = [];
  const generateList = data => {
    for (let i = 0; i < data.length; i++) {
      const { key, title, children } = data[i];
      if (!parent) dataList.push({ key, title });
      if (Array.isArray(children) && children.length > 0) {
        if (parent) dataList.push({ key, title });
        generateList(children);
      }
    }
  };
  generateList(arr);
  return dataList.map(item => item.key);
}


複製程式碼

用法

  <tree-panel
          :treeData="treeData"
          :expandedKeys="treeExpandedKeys"
          :checkedKeys="treeCheckedKeys"
          @change="onTreePanelChange"
/>
複製程式碼
props 型別 介紹
treeData 陣列物件 整個樹的資料
expandedKeys 陣列 展開的陣列key
checkedKeys 陣列 選中的陣列key
@change 自定義事件 拿到返回值的回撥函式

總結

至此,符合我們業務的一個樹元件封裝已經可以正常使用。

有時候思路不通的時候,換個角度切入,發現會更美好;

有不對之處請留言,會及時修正,謝謝閱讀

相關文章