Vue 项目开发常见 bug 整理与解决方案(二)

874 阅读3分钟

最近开发项目用的是 Vue + Element-UI 框架,期间遇到了几个 bug,整理一下以便记忆和巩固。

1. el-table 展开行问题

问题描述: 使用 el-table 组件的展开行功能,希望可以做到显示的 table 表格全部展开,然后切换页签的时候显示的内容又全部收起。于是想当然的动态设置 default-expand-all 属性,当活跃页签改变时,default-expand-all 属性也随之改变。但是事实证明这个方法是不可行的。因为切换页签时,el-table 组件是共用的, default-expand-all 属性只能渲染一次。之后不管怎么改变这个属性,都不起作用。

解决办法: 目前想到的方法是仍然动态设置 default-expand-all 属性,在切换页签时,default-expand-all 属性改变,同时刷新 el-table 组件。 代码如下:

  <ItemBomTable
        v-if="refreshTable"
        :bom-data="bomData"
        :status="status"
        :expand-all="expandAll"
      >
        <template slot="itemButton" slot-scope="item">
          <el-button
            v-if="status !== 'cancel'"
            type="primary"
            size="mini"
            plain
            class="project-btn"
            @click="onShowDialog(item.item.row)"
          >修改数量</el-button>
          <el-button
            v-if="status === 'doing'"
            type="primary"
            size="mini"
            class="project-btn"
            @click="onReturn(item.item.row)"
          >退回物料车</el-button>
          <el-button
            type="success"
            size="mini"
            class="project-btn"
            @click="downloadFile(item.item.row)"
          >下载资料</el-button>
        </template>
        <template slot="bomButton" slot-scope="item">
          <div v-if="status === 'doing'">
            <el-button
              type="primary"
              size="mini"
              class="project-btn"
              @click="onDownload(item.item.row)"
            >导出 BOM</el-button>
            <el-button
              type="success"
              placement="top"
              effect="light"
              size="mini"
              class="project-btn"
              @click="onFinish(item.item.row)"
            >完成 BOM</el-button>
            <el-button
              type="info"
              placement="top"
              effect="light"
              size="mini"
              class="project-btn"
              @click="onCancel(item.item.row)"
            >取消 BOM</el-button>
          </div>
          <div v-else>
            <el-button
              type="primary"
              size="mini"
              class="project-btn"
              @click="onMultiReturn(item.item.row)"
            >加入物料车</el-button>
          </div>
        </template>
      </ItemBomTable>

在组件中用 v-if=“refreshTable” 语句判断是否刷新 table 表格。

// 切换按钮
    changeStatus(value) {
      this.status = value;
      this.refreshTable = false;
      if (value !== 'doing') {
        this.expandAll = false;
      } else {
        this.expandAll = true;
      }
      this.$nextTick(() => {
        this.refreshTable = true;
      });
    }

在 methods 方法中定义函数 changeStatus,当切换页签时,status 相应改变,然后根据 status 的值来改变 expandAll 的值,将此值传给子组件 ItemBOMTable,从而改变 default-expand-all 属性。同时给 refreshTable 赋值为 false, 当状态改变时使用 this.$nexTick(()=>{ this.refreshTable = true;}) 来刷新 table 表格。Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。例如,当你设置 vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。

2. el-tree 数据响应问题

问题描述:使用 el-tree 组件时,希望修改节点内容,通过改变节点数据实现 input 输入框输入新的内容。于是在点击编辑按钮的同时给节点对象新增属性 editable,然后根据 editable 属性来判断是否显示 input 输入框。但是并不起作用,于是查看 vue.js 文档发现,由于 JavaScript 的限制,Vue 不能检测数组和对象的变化并响应渲染。

解决办法:根据 vue.js 文档描述,由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property。于是代码如下:

 <div class="block">
              <el-tree
                :data="locationTreeData"
                node-key="id"
                :expand-on-click-node="false"
                default-expand-all
                :indent="30"
                style="font-size: 14px"
              >
                <span slot-scope="{ node, data }" class="custom-tree-node">
                  <template v-if="node.editable">
                    <el-input
                      ref="slotTreeInput"
                      v-model="data.location"
                      class="put-style"
                      size="mini"
                    ><el-button
                      slot="suffix"
                      size="mini"
                      icon="el-icon-check"
                      type="text"
                      @click="confirmEdit(node, data)"
                    />
                    </el-input>
                  </template>
                  <template v-else>
                    <span>
                      {{ data.location }}
                    </span>
                  </template>
                  <span>
                    <el-button
                      type="text"
                      icon="el-icon-edit"
                      @click="handleLocateClick(node, data)"
                    />
                    <el-button
                      type="text"
                      icon="el-icon-circle-plus-outline"
                      @click="() => addLocation(node, data)"
                    />
                    <el-button
                      icon="el-icon-remove-outline"
                      type="text"
                      @click="() => removeLocation(node, data)"
                    />
                  </span>
                </span>
              </el-tree>
              <el-row type="flex" justify="center" style="margin: 10px">
                <el-button
                  type="success"
                  class="button-new-tag"
                  size="small"
                  round
                  @click="showLocateInput"
                >+ 区域</el-button>
              </el-row>
            </div>

网页代码用 v-if = “node.editable” 来判断是否显示 input 输入框。


  handleLocateClick(node, data) {
      // console.log(data);
      if (data.pid > 0 || data.pid === 0) {
        if (!node.editable) {
          this.$set(node, 'editable', true);
        }
        // 输入框聚焦
        this.$nextTick(() => {
          this.$refs.slotTreeInput.$refs.input.focus();
        });
      } else {
        this.isAddArea = false;
        this.$store.dispatch('admin/getAreaList').then((res) => {
          console.log(res);
          this.inputLocateVisible = true;
          res.forEach((el) => {
            if (el.name === data.location) {
              this.areaForm.name = el.name;
              this.areaForm.egname = el.egname;
              this.areaForm.department_id = el.department_id;
              this.areaForm.id = el.id;
            }
          });
        });
      }
    },

逻辑代码用 this.$set(node, ‘editable’, true) 来给节点对象赋值,

这样就给节点对象添加了 editable 为 true 的属性,从而完成数据响应。

参考资料:Vue.js 中文文档