• 【vue组件】使用element-ui table 实现嵌套表格 点击展开时获取数据


    应用场景是这样
    主表格的数据是所有的学校
    然后点击展开的时候,获取学校下相应班级的数据
    并且班级要能选择后生成图表,但所有的班级最多选择5个

    首先是嵌套表格

    <div>
        <el-table
          :data="tableDisplayData"
          id="chartTableExpand"
          style="width: 100%"
          ref="chartTable"
          @expand-change="handleRowClick"
          :expand-row-keys="expandedRows"
          :row-key="getRowKeys"
        >
          <el-table-column type="expand">
            <template slot-scope="scope">
              <el-table
                :ref="'expandTable' + scope.row.id"
                :data="scope.row.tableExpandData"
                style="width: 100%"
                v-loading="expandLoading"
                @selection-change="
                  (val) => {
                    return handleExpandSelectionChange(val, scope.row.id);
                  }
                "
              >
                <el-table-column
                  :selectable="
                    (row) => {
                      return checkSelectable(row, 'id');
                    }
                  "
                  type="selection"
                >
                </el-table-column>
                <el-table-column
                  prop="className"
                  label="班级名称"
                  width="180"
                  fixed="left"
                >
                  <template slot-scope="scope">
                    <span>
                      {{ scope.row.Name }}
                    </span>
                  </template>
                </el-table-column>
                <el-table-column prop="studentCount" label="学生数量">
                  <template slot-scope="scope">
                    <span>
                      {{ scope.row.StudentCount }}
                    </span>
                  </template>
                </el-table-column>
                <el-table-column prop="answerCount" label="回答数量">
                  <template slot-scope="scope">
                    <span>
                      {{ scope.row.AnswerCount }}
                    </span>
                  </template>
                </el-table-column>
              </el-table>
            </template>
          </el-table-column>
          <el-table-column prop="schoolName" label="学校名">
            <template slot-scope="scope">
              <span>
                {{ scope.row.schoolName }}
              </span>
            </template>
          </el-table-column>
          <el-table-column prop="classCount" label="班级数量">
            <template slot-scope="scope">
              <span>
                {{ scope.row.classCount }}
              </span>
            </template>
          </el-table-column>
          <el-table-column prop="status" label="时间">
            <template slot-scope="scope">
              <span>
                {{ scope.row.date }}
              </span>
            </template>
          </el-table-column>
          <el-table-column prop="search">
            <template slot="header" slot-scope="scope">
              <el-input v-model="searchKey" size="medium" placeholder="Search" />
            </template>
            <template slot-scope="scope"> </template>
          </el-table-column>
        </el-table>
      </div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    在主表格type为expand的行()下面添加子表格,并且添加方法

    @selection-change="(val) => {return handleExpandSelectionChange(val, scope.row.id); }"
    
    • 1

    传入主表格row的数据和row的id

    在方法handleExpandSelectionChange中,将 multipleSelection的值对应相应的table存起来,也就是说一个table 对应它自己的 multipleSelection,键是tableId ;值是每个table自己的multipleSelection,这样能解决多个table共用一个multipleSelection时会出现前一个子table选中的值会被后一个子table选中的值替换掉的问题

    handleExpandSelectionChange(val, tableId) {
          let _this = this;
          // 如果是表格展开时去点击的话,就不要改变selections的值
          if (!_this.isClassTableExpanded) {
            // 这里将 multipleSelection的值对应相应的table存起来
            // 也就是说一个table 对应它自己的 multipleSelection
            // 键是tableId 值是 multipleSelection
            _this.selections[tableId] = val;
          }
          _this.updateMultipleSelection();
        },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在方法updateMultipleSelection中,会将各个表格的multipleSelection汇总,形成一个总的multipleSelection,再根据这个汇总的multipleSelection进行后面的处理

    updateMultipleSelection() {
          let _this = this;
          // 把selections里的row取出来汇总
          _this.multipleSelection = [].concat.apply(
            [],
            Object.keys(_this.selections).map(function (key) {
              return _this.selections[key];
            })
          );
          // 用汇总后的multipleSelection来生成图表
        },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    然后再看主表格的展开时触发的方法

    @expand-change="handleRowClick"
    
    • 1

    在handleRowClick方法中
    通过方法getExpandClassData中获取数据

    	// 点击展开
         handleRowClick(row,rows) {
          let _this = this;
    
          _this.getExpandClassData(row,rows);
        },
    
        // 获取学校或班级汇总数据
        async getExpandClassData(row,rows) {
          let _this = this;
          let schoolId = row.id
          // 展开class table对应的ref
          let expandTable = "expandTable" + schoolId;
    
          // table展开时,根据之前选中的选项通过toggleRowSelection点击checkbox
          _this.$nextTick(function () {
            if (_this.$refs[expandTable]) {
              let hasSelections =
                _this.selections.length > 0 ||
                _this.selections[schoolId] ||
                (_this.selections[schoolId]
                  ? _this.selections[schoolId].length
                  : undefined) > 0;
              if (hasSelections) {
                _this.isClassTableExpanded = true;
                let selectedIds = _this.selections[schoolId].map(
                  (mSelect) => mSelect.id
                );
                row.tableExpandData.forEach((row) => {
                  if (selectedIds.includes(row.id)) {
                    _this.$refs[expandTable].toggleRowSelection(row, true);
                  }
                });
              }
            }
            _this.isClassTableExpanded = false;
          });
          const delIndex = _this.expandedRows.findIndex((item)=>{return item === schoolId});
          if (delIndex > -1) {
            _this.expandedRows.splice(delIndex, 1);
          }
          
          const isRowNowExpand = rows.some(r => r.id === row.id) // 判断当前行展开状态
          if (isRowNowExpand) {
              _this.expandedRows = [schoolId,..._this.expandedRows];
          }
    
          // 如果已经展开获取或数据了,就返回不要再获取了
          if (row.isExpanded) {
            return;
          }
          _this.expandLoading = true;
          await _this.getClassList(row.id);
    
          // 将school下对应的class表格数据,赋值到相应的school下
          // 作为tableExpandData存起来
          _this.$nextTick(() => {
            _this.$set(row, "tableExpandData", _this.tableExpandData);
            _this.$set(row, "isExpanded", true);
            _this.expandLoading = false;
          });
        },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    注意在上面代码中
    通过await _this.getClassList(row.id);获取到班级数据
    然后将数据赋值给对应的row

    _this.$nextTick(() => {
       _this.$set(row, "tableExpandData", _this.tableExpandData);
       _this.$set(row, "isExpanded", true);
       _this.expandLoading = false;
     });
    
    • 1
    • 2
    • 3
    • 4
    • 5

    但这里会产生一个问题,用$set赋值后,页面会重新渲染,展开的table会收回去,我的想法是让展开的table保持展开的状态,
    这里使用到的就是:expand-row-keys="expandedRows"

    首先在主表格中添加

    <el-table
          ...
          :expand-row-keys="expandedRows"
          :row-key="getRowKeys"
        >
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意一定要设置row-key
    然后在下面代码里

    const delIndex = _this.expandedRows.findIndex((item)=>{return item === schoolId});
    if (delIndex > -1) {
      _this.expandedRows.splice(delIndex, 1);
    }
    
    const isRowNowExpand = rows.some(r => r.id === row.id) // 判断当前行展开状态
    if (isRowNowExpand) {
        _this.expandedRows = [schoolId,..._this.expandedRows];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    首先要将_this.expandedRows中对应主表展开行的数据清除掉
    然后通过const isRowNowExpand = rows.some(r => r.id === row.id)判断当前行是否展开
    如果展开就把当前行添加到expandedRows 中,那样页面刷新后会保持展开状态

    在getExpandClassData这个方法里还要注意的是
    首先对展开的子table设置对应的ref

    let expandTable = "expandTable" + schoolId;
    
    • 1

    然后,因为选中子table的单选框后,把展开的子table收齐再展开时,单选框的选中样式会丢失,这时我想的办法是根据之前选中的选项,调用toggleRowSelection这个方法,再把单选框选中

    // table展开时,根据之前选中的选项通过toggleRowSelection点击checkbox
       _this.$nextTick(function () {
         if (_this.$refs[expandTable]) {
           let hasSelections =
             _this.selections.length > 0 ||
             _this.selections[schoolId] ||
             (_this.selections[schoolId]
               ? _this.selections[schoolId].length
               : undefined) > 0;
           if (hasSelections) {
             _this.isClassTableExpanded = true;
             let selectedIds = _this.selections[schoolId].map(
               (mSelect) => mSelect.id
             );
             row.tableExpandData.forEach((row) => {
               if (selectedIds.includes(row.id)) {
                 _this.$refs[expandTable].toggleRowSelection(row, true);
               }
             });
           }
         }
         _this.isClassTableExpanded = false;
       });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在上面代码中hasSelections 是判断是否有选中的选项,然后把展开子表格选中的id取出来,根据选中的id调用toggleRowSelection去点击

    然后如果已经展开获取过数据了,就返回不要再调用接口获取了

      if (row.isExpanded) {
        return;
      }
    
    • 1
    • 2
    • 3

    最后要限制选中的数量,就通过下面的方法
    在展开的子表格中单选框对应的行中 添加:selectable

    <el-table-column
        :selectable="(row) => {return checkSelectable(row, 'id'); }"
        type="selection"
      >
    
    • 1
    • 2
    • 3
    • 4

    然后checkSelectable方法的实现如下:

    // 是否禁用多选
    checkSelectable: function (row, key) {
      let _this = this;
      let flag = true;
      // 多选最多选 banNumber 个
      if (_this.multipleSelection.length >= _this.banNumber) {
        if (!Array.isArray(row)) {
          flag = _this.multipleSelection.some(
            (selection) => row[key] === selection[key]
          );
        }
      }
      return flag;
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    然后通过banNumber 控制限制的数量

    最后还有一个搜索方法

     watch: {
        searchKey: function (val) {
          this.tableDisplayData = this.filterTableData.filter(function (data) {
            return data.schoolName.toLowerCase().includes(val.toLowerCase());
          });
        },
      },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    完整代码如下:

    
    <template>
      <div>
        <el-table
          :data="tableDisplayData"
          id="chartTableExpand"
          style="width: 100%"
          ref="chartTable"
          @expand-change="handleRowClick"
          :expand-row-keys="expandedRows"
          :row-key="getRowKeys"
        >
          <el-table-column type="expand">
            <template slot-scope="scope">
              <el-table
                :ref="'expandTable' + scope.row.id"
                :data="scope.row.tableExpandData"
                style="width: 100%"
                v-loading="expandLoading"
                @selection-change="
                  (val) => {
                    return handleExpandSelectionChange(val, scope.row.id);
                  }
                "
              >
                <el-table-column
                  :selectable="
                    (row) => {
                      return checkSelectable(row, 'id');
                    }
                  "
                  type="selection"
                >
                </el-table-column>
                <el-table-column
                  prop="className"
                  label="班级名称"
                  width="180"
                  fixed="left"
                >
                  <template slot-scope="scope">
                    <span>
                      {{ scope.row.Name }}
                    </span>
                  </template>
                </el-table-column>
                <el-table-column prop="studentCount" label="学生数量">
                  <template slot-scope="scope">
                    <span>
                      {{ scope.row.StudentCount }}
                    </span>
                  </template>
                </el-table-column>
                <el-table-column prop="answerCount" label="回答数量">
                  <template slot-scope="scope">
                    <span>
                      {{ scope.row.AnswerCount }}
                    </span>
                  </template>
                </el-table-column>
              </el-table>
            </template>
          </el-table-column>
          <el-table-column prop="schoolName" label="学校名">
            <template slot-scope="scope">
              <span>
                {{ scope.row.schoolName }}
              </span>
            </template>
          </el-table-column>
          <el-table-column prop="classCount" label="班级数量">
            <template slot-scope="scope">
              <span>
                {{ scope.row.classCount }}
              </span>
            </template>
          </el-table-column>
          <el-table-column prop="status" label="时间">
            <template slot-scope="scope">
              <span>
                {{ scope.row.date }}
              </span>
            </template>
          </el-table-column>
          <el-table-column prop="search">
            <template slot="header" slot-scope="scope">
              <el-input v-model="searchKey" size="medium" placeholder="Search" />
            </template>
            <template slot-scope="scope"> </template>
          </el-table-column>
        </el-table>
      </div>
    </template>
    
    <script>
    import { getClassData, getSchoolData } from "@/api/api";
    export default {
      name: "embededTable",
      props: {
        tooltip: {
          type: String,
          default: "",
        },
      },
      data() {
        return {
          multipleSelection: [],
          selections: [],
          banNumber: 5,
          isTableSelected: false,
          tableExpandData: [],
          filterTableData: [],
          searchKey: "",
          tableDisplayData: [],
          isClassTableExpanded: false,
          expandedRows: [],
          expandLoading: false,
        };
      },
      async created() {
        let _this = this;
        await _this.getData();
      },
      mounted() {
        let _this = this;
      },
      watch: {
        searchKey: function (val) {
          this.tableDisplayData = this.filterTableData.filter(function (data) {
            return data.schoolName.toLowerCase().includes(val.toLowerCase());
          });
        },
      },
    
      components: {},
      methods: {
        getRowKeys: function (row) {
          return row.id;
        },
        async getClassList(id) {
          let _this = this;
          await getClassData(id)
            .then((res) => {
              _this.tableExpandData = res;
            })
            .catch((err) => {
              console.log(err, "err");
            });
        },
        async getSchoolList() {
          let _this = this;
          await getSchoolData()
            .then((res) => {
              _this.tableData = res;
              _this.filterTableData = _this.tableData;
              _this.tableDisplayData = _this.tableData;
            })
            .catch((err) => {
              console.log(err, "err");
            });
        },
        async getData() {
          let _this = this;
          await _this.getSchoolList();
        },
    
        // 点击展开
         handleRowClick(row,rows) {
          let _this = this;
    
          _this.getExpandClassData(row,rows);
        },
    
        // 获取学校或班级汇总数据
        async getExpandClassData(row,rows) {
          let _this = this;
          let schoolId = row.id
          // 展开class table对应的ref
          let expandTable = "expandTable" + schoolId;
    
          // table展开时,根据之前选中的选项通过toggleRowSelection点击checkbox
          _this.$nextTick(function () {
            if (_this.$refs[expandTable]) {
              let hasSelections =
                _this.selections.length > 0 ||
                _this.selections[schoolId] ||
                (_this.selections[schoolId]
                  ? _this.selections[schoolId].length
                  : undefined) > 0;
              if (hasSelections) {
                _this.isClassTableExpanded = true;
                let selectedIds = _this.selections[schoolId].map(
                  (mSelect) => mSelect.id
                );
                row.tableExpandData.forEach((row) => {
                  if (selectedIds.includes(row.id)) {
                    _this.$refs[expandTable].toggleRowSelection(row, true);
                  }
                });
              }
            }
            _this.isClassTableExpanded = false;
          });
          const delIndex = _this.expandedRows.findIndex((item)=>{return item === schoolId});
          if (delIndex > -1) {
            _this.expandedRows.splice(delIndex, 1);
          }
          
          const isRowNowExpand = rows.some(r => r.id === row.id) // 判断当前行展开状态
          if (isRowNowExpand) {
              _this.expandedRows = [schoolId,..._this.expandedRows];
          }
          console.log(_this.expandedRows)
    
          // 如果已经展开获取或数据了,就返回不要再获取了
          if (row.isExpanded) {
            return;
          }
          _this.expandLoading = true;
          await _this.getClassList(row.id);
    
          // 将school下对应的class表格数据,赋值到相应的school下
          // 作为tableExpandData存起来
    
          // row.tableExpandData = _this.tableExpandData;
          // row.isExpanded = true;
          _this.$nextTick(() => {
            _this.$set(row, "tableExpandData", _this.tableExpandData);
            _this.$set(row, "isExpanded", true);
            // _this.expandedRows = [schoolId,..._this.expandedRows];
            _this.expandLoading = false;
          });
        },
    
        // 单选
        handleExpandSelectionChange(val, tableId) {
          let _this = this;
          // 如果是表格展开时去点击的话,就不要改变selections的值
          if (!_this.isClassTableExpanded) {
            // 这里将 multipleSelection的值对应相应的table存起来
            // 也就是说一个table 对应它自己的 multipleSelection
            // 键是tableId 值是 multipleSelection
            _this.selections[tableId] = val;
          }
          _this.updateMultipleSelection();
        },
    
        updateMultipleSelection() {
          let _this = this;
          // 把selections里的row取出来汇总
          _this.multipleSelection = [].concat.apply(
            [],
            Object.keys(_this.selections).map(function (key) {
              return _this.selections[key];
            })
          );
        },
    
        // 是否禁用多选
        checkSelectable: function (row, key) {
          let _this = this;
          let flag = true;
          // 多选最多选 banNumber 个
          if (_this.multipleSelection.length >= _this.banNumber) {
            if (!Array.isArray(row)) {
              flag = _this.multipleSelection.some(
                (selection) => row[key] === selection[key]
              );
            }
          }
          return flag;
        },
      },
    };
    </script>
    
    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style>
    /* 去掉全选按钮 */
    .el-table__fixed-header-wrapper .el-table__header th .el-checkbox .el-checkbox__input .el-checkbox__inner{
      display: none;
    }
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283

    效果图如下:
    在这里插入图片描述

  • 相关阅读:
    怀孕也不算-《软件方法》自测题解析023
    2022-12-02 编译Android平台OpenCV,用到读取视频时报错:AMediaXXX
    pymysql的使用-python连接MySQL数据库代码
    使用git发布(删除/更改)GitHub仓库中的内容
    复习 tomcat
    ComText让机器人有了情节记忆
    计算机系统常见故障及处理,电脑常见故障以及解决方案都在这里
    眼底相机系统设计
    LLAMA 3的测试之旅:在GPT-4的阴影下前行
    如何利用产品状态维度优化库存管理和采购策略?【ODOO15/16】
  • 原文地址:https://blog.csdn.net/loyd3/article/details/132875029