iView‐Tree - daniel-qa/Vue GitHub Wiki

#

  • 初始問題

問題:previewTree 初始值為 null,Tree 組件嘗試讀取 null.length 導致錯誤

解決:將初始值改為空陣列 [],Tree 組件可以正確處理空陣列

previewTree: [],

node-key="key" 是 Tree 用來「唯一識別每一個節點」的主鍵。

沒有它,所有勾選、展開、設定 checkedKeys 的行為都會變得不可靠。

node-key="key" 到底在幹嘛?

在 iView / View UI 的 裡:

<Tree
  :data="treeData"
  show-checkbox
  node-key="key"
/>

這行的意思是:

「請用每個 node 裡的 key 欄位,當作這個節點的唯一 ID」

  • 載入後再執行

iView 的 Modal 有 @on-open / @on-visible-change 事件,可以保證 Modal 打開後再操作 Tree。

<i-modal :visible.sync="showEditModal" @on-visible-change="handleModalVisibleChange">
  <i-tree ref="editModalTree" :data="treeData" />
</i-modal>
handleModalVisibleChange(visible) {
    if (visible) {
        this.$nextTick(() => {
            this.$refs.editModalTree.setCheckedKeys(shareVolumeIds);
        });
    }
}
  • 取得所有的 key
const tree = this.$refs.editModalTree;

const allKeys = tree.data.flatMap(function getKeys(node) {
    return [node.key].concat(node.children ? node.children.flatMap(getKeys) : []);
});
  • 延遲 50 毫秒後執行
setTimeout(() => {
    this.$refs.editModalTree.setCheckedKeys(shareVolumeIds);
}, 50);

直接鉤選

// 科目節點
{
  title: '數學',
  key: subject.id,        // 👈 就是 subjectId
  subjectId: subject.id,
  children: [
    {
      title: '第一冊',
      key: volume.id,     // 👈 就是 volumeId
      volumeId: volume.id
    }
  ]
}

iView Tree 的 setCheckedKeys 只認 key,沒有父子層的概念

在 key 剛好等於 subjectId , 可以直接指定 shareSubjects 陣列

this.$refs.editModalTree.setCheckedKeys(shareSubjects );

但後續要另外處理實際鉤選的部分( UI 只會顯示子項目鉤選,但資料層面要另外處理)

要加 ref,editModalTree 才能操作

  <Tree ref="editModalTree"
        :data="subjectTreeData"
        show-checkbox
        :check-strictly="false"
        @on-check-change="handleTreeCheckChange"
        style="margin-left: 20px;">
  </Tree>

@on-check-change 有鉤選時,觸發的事件

handleTreeCheckChange(checkedNodes, node) 

第二個參數 node 裡面有一個非常關鍵的屬性:

👉 node.children

如果有內容 → 父節點

如果沒有 → 葉子節點(子節點)

checkedNodes : 是全部的鉤選狀態

node : 是當下被鉤選的節點 (選中或取消)

checked 是否勾选 (父,子元件的狀態,都是獨立的)

學科固定展開、點擊後展開冊別、每個節點都可勾選;支援父子連動

expand: false // 預設收起,true 則展開

<template>
  <div style="padding: 20px;">
    <h3>學科與冊別選擇</h3>

    <!-- 樹狀選單 -->
    <Tree
      :data="subjectTreeData"
      show-checkbox
      @on-check-change="updateSelections"
    />

    <!-- 已選擇項目 -->
    <div style="margin-top: 15px; padding: 10px; background: #f5f5f5;">
      <strong>已選擇項目:</strong>

      <div v-if="selectedItems.length === 0" style="color: #999; margin-top: 5px;">
        尚未選擇任何項目
      </div>

      <div v-else style="margin-top: 5px;">
        <Tag
          v-for="item in selectedItems"
          :key="item.key"
          closable
          @on-close="removeItem(item)"
          style="margin: 2px;"
        >
          {{ item.title }}
        </Tag>
      </div>
    </div>

    <!-- 操作按鈕 -->
    <div style="margin-top: 20px;">
      <Button type="default" @click="clearAll">清除所有選擇</Button>
      <Button type="success" @click="getSelected">獲取選中資料</Button>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      subjectTreeData: [
        {
          title: '國語文',
          key: 'chinese',
          expand: true,
          children: [
            { title: '第一冊', key: 'chinese-1' },
            { title: '第二冊', key: 'chinese-2' },
            { title: '第三冊', key: 'chinese-3' }
          ]
        },
        {
          title: '數學',
          key: 'math',
          expand: true,
          children: [
            { title: '第一冊', key: 'math-1' },
            { title: '第二冊', key: 'math-2' },
            { title: '第三冊', key: 'math-3' }
          ]
        },
        {
          title: '英語',
          key: 'english',
          expand: true,
          children: [
            { title: '第一冊', key: 'english-1' },
            { title: '第二冊', key: 'english-2' }
          ]
        }
      ],
      selectedItems: []
    };
  },

  methods: {
    // 重新整理已選項目
    updateSelections(checkedNodes) {
      const list = [];

      const walk = node => {
        if (node.checked) list.push({ key: node.key, title: node.title, isParent: !!node.children });
        if (node.children) node.children.forEach(walk);
      };

      checkedNodes.forEach(walk);
      this.selectedItems = list;
    },

    // 移除 Tag 時,同步取消樹狀選取
    removeItem(item) {
      const uncheck = nodes => {
        nodes.forEach(n => {
          if (n.key === item.key) n.checked = false;
          if (n.children) uncheck(n.children);
        });
      };

      uncheck(this.subjectTreeData);
      this.selectedItems = this.selectedItems.filter(x => x.key !== item.key);
      this.$Message.info(`已移除 ${item.title}`);
    },

    // 清空所有勾選
    clearAll() {
      const clear = nodes => {
        nodes.forEach(n => {
          n.checked = false;
          if (n.children) clear(n.children);
        });
      };

      clear(this.subjectTreeData);
      this.selectedItems = [];
      this.$Message.info('已清除所有選擇');
    },

    // 取得已選項目
    getSelected() {
      const result = {
        subjects: this.selectedItems.filter(i => i.isParent),
        volumes: this.selectedItems.filter(i => !i.isParent)
      };

      console.log('選中的資料:', result);
      this.$Message.success(`學科 ${result.subjects.length},冊別 ${result.volumes.length}`);

      return result;
    }
  }
};
</script>
⚠️ **GitHub.com Fallback** ⚠️