Vue 3 - 第二次下拉更改后按钮不更新

Vue 3 - Buttons not updating after second dropdown changes

我是 Vue 的新手,很难弄清楚如何解决这个问题。我在一个深入的项目上已经走得很远了,但是我正在努力思考什么应该是一个简单的功能......

简要Background/Overview:

我从 API(我自己的)获取数据,它获取用户创建的研究。他们可以切换到另一项研究,这会更改页面上的数据(星号表示该项目已经在研究中)。这是他们的“整体页面浏览研究”。他们可以将页面上的项目添加到研究中。他们单击该项目,弹出 Bootstrap 5 模态,其中包含他们的研究下拉列表。当前页面研究默认为 selected,但如果他们愿意,他们可以选择将其添加到任何其他研究中。因此,如果他们将项目添加到另一项研究(不是他们正在查看的研究),我们 假设 他们可以添加它并让 API return 一个特殊的响应 (409),其中它将向用户显示“已经在研究中”的消息 (toast/flash message/etc)。如果这是他们当前的“页面研究”,那么他们应该能够在它不存在时添加它,或者在它已经在研究中时将其删除。

我只是想提供一个背景概述,以防其中的某些内容可以对解决方案产生影响(Vue3,Bootstrap 5,bootstrap 模态,select 下拉菜单,等)。

由于复杂性和适当性,我创建了一个复制问题的通用模型,以及 CodePen 上的演示。

问题:

我有一个下拉菜单(研究 selector)和 2 个按钮(添加和删除)。如果他们从不是当前查看的“页面研究”的下拉列表中选择一项研究,我们假设“添加”。如果是当前浏览的“页面学习”,我们需要做一个检查。它检查该“项目”是否在研究中。如果是,则显示“删除”按钮,否则显示“添加”按钮。

当我选择一个已经在 selected“页面研究”中的项目时,它会显示“删除”按钮(最初)。然后,当我切换到下拉列表中的不同研究时,它会交换按钮(显示“添加”)。到目前为止一切顺利......但是,当我将它切换回当前的“页面研究”时,“添加”按钮在它应该切换到“删除”按钮时保持不变......就像它更新了一次并且忽略了我不再听了哈哈

我尝试过观察者、计算、尝试强制更新...我只是对 Vue 了解不够,无法让它正常工作。

使用虚拟数据模拟应用程序代码:

<div id="app">
  Study ID: {{ currentStudyId }}
  
  <br><br>

  Selected StudyData Item: 
  <select v-model="selectedStudyDataItem">
    <option value="1">1</option>
    <option value="2">2</option>
    <option value="3">3</option>
    <option value="4">4</option>
    <option value="5">5</option>
    <option value="6">6</option>
    <option value="7">7</option>
    <option value="8">8</option>
  </select>

  <br><br>

  <select v-model="selectedStudyModel" name="study_id" class="form-select">
    <option disabled value="">Choose a study...</option>
    <option v-for="item in studies" :value="item.id" :key="item.id">
      {{ item.title }}
    </option>
  </select>

  <button v-show="showAddButton()" type="submit" class="btn btn-primary">Add</button>

  <button v-show="showRemoveButton()" type="submit" class="btn btn-danger">Remove</button>
</div>

const TestApp = {
  data() {
    return {
      currentStudyId: 2,
      selectedStudyDataItem: 1,
      selectedStudyModel: "", // dropdown model
      studies: [
        { id: 1, title: "Study 1" },
        { id: 2, title: "Study 2" },
        { id: 3, title: "Study 3" },
        { id: 4, title: "Study 4" }
      ],
      studyData: {
        title: "My Cool Study",
        user_id: 1,
        items: [1, 3]
      }
    };
  },

  methods: {
    showAddButton() {
      // if it isnt the current study, we assume add and let api handle it
      if (this.selectedStudyModel !== this.currentStudyId) {
        return true;
      }

      // else, if it isn't in the current viewed study, we can add it
      if (!this.isInStudy(this.selectedStudyDataItem)) {
        return true;
      }

      return false;
    },
    showRemoveButton() {
      if (this.selectedStudyModel === this.currentStudyId) {
        if (this.isInStudy(this.selectedStudyDataItem)) {
          return true;
        }
      }

      return false;
    },
    isInStudy(something) {
      if (this.studyData) {
        const match = (item) => item === something;
        return this.studyData.items.some(match);
      }

      return false;
    }
  }
};

Vue.createApp(TestApp).mount("#app");

CodePen Example

由于类型转换,您遇到了问题。

  <select v-model="selectedStudyDataItem">
    <option value="1">1</option>
    <option value="2">2</option>
    <option value="3">3</option>
    <option value="4">4</option>
    <option value="5">5</option>
    <option value="6">6</option>
    <option value="7">7</option>
    <option value="8">8</option>
  </select>

将使用 Sting 作为更改后的 selectedStudyModel

要解决这个问题,您需要更新 showAddButton 并包含 parseInt 来处理

showAddButton() {
  // if it isnt the current study, we assume add and let api handle it
  if ( parseInt(this.selectedStudyModel) !== this.currentStudyId ) {
    return true;
  }

  // else, if it isn't in the current viewed study, we can add it
  if ( ! this.isInStudy(parseInt(this.selectedStudyDataItem)) ) {
    return true;
  }

  return false;
},

或者,您也可以使用 :value 来转换它

  <select v-model="selectedStudyDataItem">
    <option :value="1">1</option>
    <option :value="2">2</option>
    <option :value="3">3</option>
    <option :value="4">4</option>
    <option :value="5">5</option>
    <option :value="6">6</option>
    <option :value="7">7</option>
    <option :value="8">8</option>
  </select>

这些现在将被转换为 Number 而不是 String,但是我建议最好在脚本中明确而不是依赖模板魔术。

其他一些注意事项

  • 使用计算而不是方法,它们的结果被缓存,所以你只在需要时调用它们
  • 如果你只有add或者remove作为选项,不需要计算remove,正好相反(你可以使用v-ifv-else

new Vue({
  el: '#app',
    data() {
        return {
        'currentStudyId': 2,
      'selectedStudyDataItem': 1,
            'selectedStudyModel': '',       // dropdown model
      'studies': [
        {'id': 1, 'title': 'Study 1'},
        {'id': 2, 'title': 'Study 2'},
        {'id': 3, 'title': 'Study 3'},
        {'id': 4, 'title': 'Study 4'},
      ],
      'studyData': {
        'title': 'My Cool Study',
        'user_id': 1,
        'items': [
            1, 3
        ],
      },
    }
  },
  computed:{
    showAddButton() {
      // if it isnt the current study, we assume add and let api handle it
      if ( parseInt(this.selectedStudyModel) !== this.currentStudyId ) {
        return true;
      }

      // else, if it isn't in the current viewed study, we can add it
      if ( ! this.isInStudy(parseInt(this.selectedStudyDataItem)) ) {
        return true;
      }

      return false;
    },
  },
  methods: {
    isInStudy(something) {
        if ( this.studyData ) {
                const match = (item) => item === something;
                return this.studyData.items.some(match);
            }

            return false;
    }
  }
  
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  Study ID: {{ currentStudyId }}
  
  <br><br>

  Selected StudyData Item: 
  <select v-model="selectedStudyDataItem">
    <option value="1">1</option>
    <option value="2">2</option>
    <option value="3">3</option>
    <option value="4">4</option>
    <option value="5">5</option>
    <option value="6">6</option>
    <option value="7">7</option>
    <option value="8">8</option>
  </select>

  <br><br>

  <select v-model="selectedStudyModel" name="study_id" class="form-select">
    <option disabled value="">Choose a study...</option>
    <option v-for="item in studies" :value="item.id" :key="item.id">
      {{ item.title }}
    </option>
  </select>

  <button v-if="showAddButton" type="submit" class="btn btn-primary">Add</button>

  <button v-else type="submit" class="btn btn-danger">Remove</button>

  <p>
    <strong>How to reproduce:</strong> Choose the "Study 2" option from the 2nd dropdown. The button should be "Remove" because "Selected StudyData Item [1]" (dropdown above it) is in the "studyData". 1 and 3 are in the studyData for "Study 2".
  </p>
  <p>
    Then select a "studyData Item" that isn't in the study (not 1 or 3), ie: 8. It should change the button to "Add".
  </p>
  <p>
  Now, <u>here is the problem</u>. Swap the "studyData Item" back to 1 or 3 (items in the studyData). It is supposed to change the button back to "Remove" because 1 and 3 are both in the study (study 2).. However, it just does nothing lol.. 
  </p>
  selectedStudyDataItem: {{JSON.stringify(selectedStudyDataItem)}} | {{ typeof selectedStudyDataItem }}
  <br/>
  selectedStudyModel: {{JSON.stringify(selectedStudyModel)}} | {{ typeof selectedStudyModel}}
</div>