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

Vue动态Select重复选值异常:选中项重置为默认值问题

问题分析与解决方案

问题现象

  • 动态新增Select组件后,选择已存在于selects数组中的选项时,选中项会被重置为默认选项{ id: '0', name: 'Please Select an action', value: '0' }
  • 无法在selects数组中添加多个相同的选项

核心原因

  1. select项标识冲突:直接用选项的id作为select项的id,当多个select选同一个选项时,所有这些select项的id完全相同,导致查找索引时定位错误,更新操作会影响到第一个匹配的项。
  2. v-for使用index作为key:数组新增/删除时Vue会复用DOM节点,导致组件状态与数据不匹配,出现选中状态混乱。
  3. option被错误设置为disabled:代码中所有<option>都添加了disabled属性,这会阻止用户正常选择选项(属于代码笔误)。
  4. changeOption逻辑错误:依赖旧的选项id查找select项,存在重复选项id时无法定位到正确的目标项。

解决方案

1. 给每个select项添加唯一标识

修改selects数组结构,每个项包含独立的唯一标识(自增id或uuid),与选项id分离:

  • uuid:select项的唯一身份标识
  • optionId:当前选中的选项id
  • name:选项名称
  • value:选项值

2. 替换v-for的key为唯一标识

将:key="index"改为:key="select.uuid",避免DOM复用导致的状态混乱。

3. 修正v-model与changeOption逻辑

  • v-model绑定到select.optionId,而非覆盖select项的唯一标识
  • changeOption方法通过select项的uuid定位目标,确保更新准确

4. 移除option的disabled属性

恢复选项的可选状态。

修改后的完整代码

模板部分

<div class="col-span-6 grid md:grid-cols-1 md:grid-cols-6 gap-2 mt-5 px-5 py-2 bg-gray-50 shadow rounded">
    <div class="mt-2 col-span-6 md:col-span-3" v-for="(select, index) in selects" :key="select.uuid">
        <label for="actions" class="block text-sm font-medium text-gray-700 mb-2">
            Actions: <span v-if="index > 0">(Optional)</span>
        </label>
        <div class="flex justify-center mt-1 gap-2">
            <select 
                v-model="select.optionId" 
                @change="changeOption(select.uuid, select.optionId)" 
                name="actions[]" 
                class="appearance-none rounded-md relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm" 
                placeholder="Task Action"
            >
                <option v-for="option in options" :key="option.id" :value="option.id">{{ option.name }}</option>
            </select>
            <button @click.prevent="remove(index)" v-if="index > 0" class="bg-red-50 text-red-600 px-2 py-2 rounded">
                <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
                    <path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
                </svg>
            </button>
        </div>
    </div>
    <div class="col-span-6">
        <button @click.prevent="addselect" class="bg-green-600 text-white px-2 py-2 mt-3 rounded">Add Actions</button>
    </div>
</div>

脚本部分

import axios from 'axios';

export default {
    data() {
        return {
            selectedActions: [],
            options: [
                { id: '0', name: 'Please Select an action', value: '0' },
                { id: '1', name: 'Contact Info > Provide Email Address', value: '0.05' },
                { id: '2', name: 'Contact Info > Provide Phone Number', value: '0.05' },
                // 其余选项保持不变
            ],
            selects: [{
                uuid: 1,
                optionId: '0',
                name: 'Please Select an action',
                value: '0'
            }],
            nextUuid: 2
        }
    },
    created() {
        if (this.$route.params.id) {
            axios.get(`/api/advertiser/task/${this.$route.params.id}`).then(res => {
                const actionsData = JSON.parse(res.data.actions);
                this.selects = actionsData.map((action, index) => ({
                    uuid: index + 1,
                    optionId: action.id,
                    name: action.name,
                    value: action.value
                }));
                this.nextUuid = this.selects.length + 1;
            })
        }
    },
    methods: {
        addselect() {
            const defaultOption = this.options.find(option => option.id === '0');
            if (defaultOption) {
                this.selects.push({
                    uuid: this.nextUuid++,
                    optionId: '0',
                    name: defaultOption.name,
                    value: defaultOption.value
                });
            }
        },
        changeOption(uuid, newOptionId) {
            const selectIndex = this.selects.findIndex(select => select.uuid === uuid);
            if (selectIndex === -1) return;

            const selectedOption = this.options.find(option => option.id === newOptionId);
            if (!selectedOption) return;

            this.selects[selectIndex] = {
                ...this.selects[selectIndex],
                name: selectedOption.name,
                value: selectedOption.value,
                optionId: newOptionId
            };

            const actionIndex = this.selectedActions.findIndex(action => action.uuid === uuid);
            if (actionIndex !== -1) {
                this.selectedActions[actionIndex] = this.selects[selectIndex];
            } else {
                this.selectedActions.push(this.selects[selectIndex]);
            }
        },
        remove(index) {
            const removedUuid = this.selects[index].uuid;
            this.selects.splice(index, 1);
            
            const actionIndex = this.selectedActions.findIndex(action => action.uuid === removedUuid);
            if (actionIndex !== -1) {
                this.selectedActions.splice(actionIndex, 1);
            }
        },
        edittask() {
            const formData = new FormData();
            const formattedSelects = this.selects.map(select => ({
                id: select.optionId,
                name: select.name,
                value: select.value
            }));
            formData.append('actions', JSON.stringify(formattedSelects));

            const config = {
                headers: { 'content-type': 'multipart/form-data' }
            };

            axios.post(`/api/advertiser/update/task/${this.$route.params.id}`, formData, config)
                .then(res => console.log(res.data))
                .catch(error => console.log(error));
        },
        onFileChange(e) {
            const file = e.target.files[0];
            if (file) {
                const reader = new FileReader();
                reader.onloadend = () => {
                    this.file = file;
                    this.preview = reader.result;
                };
                reader.readAsDataURL(file);
            }
        }
    },
    computed: {
        totalprice() {
            return this.selects.reduce((sum, select) => {
                const selectedOption = this.options.find(option => option.id === select.optionId);
                return sum + (selectedOption ? parseFloat(selectedOption.value) : 0);
            }, 0).toFixed(2);
        }
    }
}

关键修改说明

  • 唯一标识uuid:确保每个select项独立,避免重复选项id导致的定位错误
  • 分离选项id与select标识:v-model绑定optionId,保留select项的唯一身份
  • 移除disabled属性:恢复选项的可选状态
  • 精准定位更新:通过uuid查找目标select项,更新操作不会影响其他项
  • 同步数据一致性:删除select项时同步移除selectedActions中的对应项

内容的提问来源于stack exchange,提问作者DJ Emil

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 03:54:51