You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue.js中基于Ajax/JSON的动态分类下拉框异常修复求助

Hey there, let's tackle this issue step by step. The main problems here are how you're tracking selected values across dynamic categories and some Vue binding conflicts that are causing parent selections to reset. Here's a lightweight fix that preserves the history of your selections:

What's Going Wrong

  1. Single selectedId can't track multi-level selections: You're using a single variable to store the selected ID, which gets overwritten every time you pick a new category. This means parent selections aren't preserved when loading child categories.
  2. Conflict between v-model and v-bind:selected: When you use both on an <option>, Vue's v-model takes control of the selected state, and your manual selected binding gets ignored or overwritten during updates.
  3. Unstable key on <select>: Using option[0].id as the key for your repeated <select> elements is risky—if the first option in a new set of categories has the same ID as a previous one, Vue will reuse the DOM element incorrectly, leading to unexpected behavior.

Lightweight Fix

Let's adjust the component to track a history of selected IDs, fix the binding conflicts, and stabilize the rendering:

Updated Template

<template>
  <div>
    <!-- Use the index as the key for each select to ensure stable DOM tracking -->
    <select 
      @change="handleCategoryChange(index)" 
      v-model="selectedIds[index]" 
      :key="index" 
      class="browser-default custom-select"
      v-for="(optionGroup, index) in options"
    >
      <!-- Remove the manual selected binding—v-model handles this now -->
      <option 
        :value="op.id" 
        :key="op.id" 
        v-for="op in optionGroup"
      >
        {{ op.name }}
      </option>
    </select>
  </div>
</template>

Updated Script

<script>
export default {
  name: "ProductFormCategory",
  data() {
    return {
      options: [], // Now an array of category groups (one per level)
      selectedIds: [] // Array to track selected ID for each level
    };
  },
  created() {
    // Load root categories on initial mount
    this.loadCategories();
  },
  methods: {
    async loadCategories(parentId = "") {
      try {
        // Adjust the URL based on parentId—empty for root categories
        const url = parentId ? `category/${parentId}` : "category";
        const response = await this.$http.get(url);
        
        if (parentId) {
          // If loading child categories, replace or add to the options array
          // We replace all levels after the current selected one to avoid stale data
          this.options = [
            ...this.options.slice(0, this.selectedIds.length),
            response.data
          ];
          // Update the selectedIds array to include this new level
          this.selectedIds = [
            ...this.selectedIds.slice(0, this.selectedIds.length),
            "" // Initialize new level with no selection
          ];
        } else {
          // Initial load: set root categories as the first option group
          this.options = [response.data];
          this.selectedIds = [""]; // Initialize first level selection
        }
      } catch (err) {
        console.error("Failed to load categories:", err);
      }
    },
    handleCategoryChange(index) {
      const selectedId = this.selectedIds[index];
      if (!selectedId) {
        // If no selection, remove all child levels
        this.options = this.options.slice(0, index + 1);
        this.selectedIds = this.selectedIds.slice(0, index + 1);
        return;
      }
      // Load child categories for the selected ID
      this.loadCategories(selectedId);
    }
  }
};
</script>

Style (Unchanged)

<style scoped>
.browser-default{
 margin-bottom: 10px !important;
}
</style>

Key Changes Explained

  • selectedIds array: Instead of a single value, we use an array where each index corresponds to a category level (e.g., selectedIds[0] is the root category, selectedIds[1] is the child, etc.). This preserves all parent selections.
  • Fixed binding conflicts: We removed v-bind:selected from <option> elements—v-model on the <select> now fully manages the selected state, avoiding Vue's internal state conflicts.
  • Stable :key for <select>: Using the loop index (index) as the key ensures each <select> element is uniquely identified by its level, preventing DOM reuse issues when categories update.
  • Controlled category loading: The handleCategoryChange method manages adding/removing category levels—if you select a parent category, it loads child categories and adds a new level; if you deselect, it removes child levels to keep the UI clean.
  • Simplified API calls: The loadCategories method handles both root and child category requests by adjusting the URL based on the parent ID.

Quick Test

  1. On initial load, you'll see the root categories.
  2. Select a root category—child categories should load in a new <select> below, and the root selection stays intact.
  3. Select a child category—grandchild categories load, and both parent selections remain.
  4. If you deselect a category, all child levels will be removed.

内容的提问来源于stack exchange,提问作者Ezequiel Cardoso

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 09:04:50