Vuejs html 复选框检查状态未正确更新

Vuejs html checkbox check state not updated correctly

在使某些复选框正常工作时遇到问题。所以在我的组件中,我在状态变量 tokenPermissions 中设置了一组对象,看起来像这样

tokenPermissions: [
        {
          groupName: "App",
          allSelected: false,
          someSelected: false,
          summary: "Full access to all project operations",
          permissions: [
            {
              name: "can_create_app",
              summary: "Create new projects",
              selected: false,
            },
            {
              name: "can_delete_app",
              summary: "Delete existing projects",
              selected: false,
            },
            {
              name: "can_edit_app",
              summary: "Edit an existing project",
              selected: false,
            },
          ],
        }
      ],

目标是遍历这个数组,并有一个像这样的父复选框和子复选框 tokenPermissions[i].allSelected 绑定到父复选框,对于 tokenPermissions[i].permissions 中的每个对象,一个对应的复选框绑定到 selected 属性 这样 tokenPermissions[i].permissions[j].selected.

选中父复选框时的期望行为,

  1. 如果选中所有子复选框,取消选中所有复选框,包括父复选框
  2. 如果未选中子复选框,请选中所有复选框,包括父复选框
  3. 如果只有部分子复选框被选中,父复选框将显示不确定的 - 图标或符号,并在单击时取消选中包括父复选框在内的所有子复选框。

问题是第 3 点。问题是有时根据绑定到的属性的状态未正确选中父复选框。例如 allSelected 可以是 false 但父复选框已选中。

我在 github 上放了一个完整的工作示例 https://github.com/oaks-view/vuejs-checkbox-issue

绑定的部分代码如下

<ul
      class="list-group"
      v-for="(permissionGroup, permissionGroupIndex) in tokenPermissions"
      :key="`${permissionGroup.groupName}_${permissionGroupIndex}`"
    >
      <li class="list-group-item">
        <div class="permission-container">
          <div>
            <input
              type="checkbox"
              :indeterminate.prop="
                !permissionGroup.allSelected && permissionGroup.someSelected
              "
              v-model="permissionGroup.allSelected"
              :id="permissionGroup.groupName"
              v-on:change="
                permissionGroupCheckboxChanged($event, permissionGroupIndex)
              "
            />
            <label :for="permissionGroup.groupName" class="cursor-pointer"
              >{{ permissionGroup.groupName }} -
              <span style="color: red; margin-left: 14px; padding-right: 3px">{{
                permissionGroup.allSelected
              }}</span></label
            >
          </div>
          <div class="permission-summary">
            {{ permissionGroup.summary }}
          </div>
        </div>
        <ul class="list-group">
          <li
            class="list-group-item list-group-item-no-margin"
            v-for="(permission, permissionIndex) in permissionGroup.permissions"
            :key="`${permissionGroup.groupName}_${permission.name}_${permissionIndex}`"
          >
            <div class="permission-container">
              <div>
                <input
                  type="checkbox"
                  :id="permission.name"
                  v-bind:checked="permission.selected"
                  v-on:change="
                    permissionGroupCheckboxChanged(
                      $event,
                      permissionGroupIndex,
                      permissionIndex
                    )
                  "
                />
                <label :for="permission.name" class="cursor-pointer"
                  >{{ permission.name
                  }}<span
                    style="color: red; margin-left: 3px; padding-right: 3px"
                    >&nbsp; {{ permission.selected }}</span
                  ></label
                >
              </div>
              <div class="permission-summary">
                {{ permission.summary }}
              </div>
            </div>
          </li>
        </ul>
      </li>
    </ul>

并更新复选框

    getPermissionGroupSelectionStatus: function (permissionGroup) {
      let allSelected = true;
      let someSelected = false;

      permissionGroup.permissions.forEach((permission) => {
        if (permission.selected === false) {
          allSelected = false;
        }
        if (permission.selected === true) {
          someSelected = true;
        }
      });

      return { allSelected, someSelected };
    },
    permissionGroupCheckboxChanged: function (
      $event,
      permissionGroupIndex,
      permissionIndex
    ) {
      const { checked } = $event.target;

      // its single permission selected
      if (permissionIndex !== undefined) {
        this.tokenPermissions[permissionGroupIndex].permissions[
          permissionIndex
        ].selected = checked;

        const { allSelected, someSelected } =
          this.getPermissionGroupSelectionStatus(
            this.tokenPermissions[permissionGroupIndex]
          );

        this.tokenPermissions[permissionGroupIndex].allSelected = allSelected;
        this.tokenPermissions[permissionGroupIndex].someSelected = someSelected;
      } else {
        // its selectAll check box
        const { allSelected, someSelected } =
          this.getPermissionGroupSelectionStatus(
            this.tokenPermissions[permissionGroupIndex]
          );

        let checkAll;

        // no checkbox / permission is selected then set all
        if (!someSelected && !allSelected) {
          checkAll = true;
        } else {
          checkAll = false;
        }

        this.tokenPermissions[permissionGroupIndex].allSelected = checkAll;
        this.tokenPermissions[permissionGroupIndex].someSelected = checkAll;

        for (
          let i = 0;
          i < this.tokenPermissions[permissionGroupIndex].permissions.length;
          i++
        ) {
          this.tokenPermissions[permissionGroupIndex].permissions[i].selected =
            checkAll;
        }
      }
    },

这是一个渲染问题。 Vue 将 allSelected 复选框设置为已选中,然后在同一循环中将其更新为 false;你可以在这里阅读 Vue 生命周期:https://it.vuejs.org/v2/guide/instance.html

解决它的一个非常残酷(但简单)的方法(我不推荐,但它有助于了解正在发生的事情)是延迟更新。

this.$nextTick:

包装方法 permissionGroupCheckboxChanged 的最后一部分
this.$nextTick(() => {
        this.tokenPermissions[permissionGroupIndex].allSelected = checkAll;
      this.tokenPermissions[permissionGroupIndex].someSelected = checkAll;

      for (
        let i = 0;
        i < this.tokenPermissions[permissionGroupIndex].permissions.length;
        i++
      ) {
        this.tokenPermissions[permissionGroupIndex].permissions[i].selected =
          checkAll;
      }
    })

这样,当您更改值时,引擎会做出相应的反应。 我仍然不推荐它(我认为 nextTick 对理解 Vue 生命周期很有用,但我建议尽可能不要使用它)。


checkAll 不为真 permissionGroupCheckboxChanged 时,将 allSelected 设置为 null 而不是 false 的方法不那么残酷(也更简单):

// this
this.tokenPermissions[permissionGroupIndex].allSelected = checkAll ? checkAll : null;

// instead of this
this.tokenPermissions[permissionGroupIndex].allSelected = checkAll;

这样道具就战胜了模型(因为模型值变为 null)。


但是更好的选择(恕我直言)是在 v-for 循环中使用它自己的组件,并将 allSelectedsomeSelected 作为计算属性而不是值绑定到实变量。 通常你不应该将 ui 状态存储为数据,当它可以从真实数据中推断出来时(我可能是错的,因为我不知道你的应用程序,但在你的情况下我怀疑你对单个复选框感兴趣'值,而 allSelected/someSelected 仅用于 ui).