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

Vue星级评分组件多实例渲染异常问题求助

Nuxt/Vue星级评分组件多实例异常问题修复

我开发了一个Vue(Nuxt)星级评分组件,单个实例使用正常,但同时使用多个指定ratingValue的实例时,仅最后一个组件的星级能正确着色,其余组件的星级显示异常。

组件代码如下:

<script setup lang="ts">
  import { PropType } from "nuxt/dist/app/compat/capi";
  interface Star {
    id: string;
    value: number;
  }

  const stars: Star[] = [
    { id: "star5", value: 5 },
    { id: "star4.5", value: 4.5 },
    { id: "star4", value: 4 },
    { id: "star3.5", value: 3.5 },
    { id: "star3", value: 3 },
    { id: "star2.5", value: 2.5 },
    { id: "star2", value: 2 },
    { id: "star1.5", value: 1.5 },
    { id: "star1", value: 1 },
    { id: "star0.5", value: 0.5 },
  ];

  const props = defineProps({
    ratingValue: {
      type: Number as PropType<number>,
    },
    starColor: {
      type: String as PropType<string>,
    },
  });

  const emit = defineEmits(["ratingChange"]);
  const selectedRating = ref<number | undefined>(undefined);

  const roundedRating = computed(() => {
    if (props.ratingValue !== undefined) {
      return Math.round(props.ratingValue * 2) / 2;
    } else {
      return selectedRating.value !== undefined
        ? Math.round(selectedRating.value * 2) / 2
        : undefined;
    }
  });

  const onRatingChange = (value: number) => {
    if (props.ratingValue === undefined) {
      selectedRating.value = value;
      emit("ratingChange", selectedRating.value);
    }
  };
</script>

<template>
  <div class="center">
    <div :class="['rating', { disable: props.ratingValue !== undefined }]">
      <template v-for="star in stars" :key="star.id">
        <input
          :id="star.id"
          v-model="roundedRating"
          :disabled="props.ratingValue !== undefined"
          type="radio"
          name="'rating'"
          :value="star.value"
          @input="onRatingChange(star.value)"
        />
        <label :for="star.id" :class="star.value % 1 === 0 ? 'full full-star' : 'half'"></label>
      </template>
    </div>
    <div class="rating-number">{{ props.ratingValue }}</div>
  </div>
</template>

使用示例:

<StarRating ratingValue="1" />
<StarRating ratingValue="4" />

问题原因

  1. 重复的radio name属性:所有组件实例的单选框都使用固定的name="'rating'",导致多个组件的单选框被归为同一组,选中状态互相覆盖,最终仅最后一个实例的选中状态生效。
  2. 重复的id属性:每个星级的id是固定值(如star5),多个组件实例会生成重复的id,违反HTML唯一性规则,导致label与input的关联混乱,无法正确触发选中样式。

修复方案

使用Vue3/Nuxt3提供的useId()生成唯一标识,为每个组件实例的单选框组和星级id添加唯一前缀,确保每个组件的表单元素独立。

修改后的组件代码:

<script setup lang="ts">
import { PropType, useId } from "nuxt/dist/app/compat/capi";
interface Star {
  id: string;
  value: number;
}

const stars: Star[] = [
  { id: "star5", value: 5 },
  { id: "star4.5", value: 4.5 },
  { id: "star4", value: 4 },
  { id: "star3.5", value: 3.5 },
  { id: "star3", value: 3 },
  { id: "star2.5", value: 2.5 },
  { id: "star2", value: 2 },
  { id: "star1.5", value: 1.5 },
  { id: "star1", value: 1 },
  { id: "star0.5", value: 0.5 },
];

const props = defineProps({
  ratingValue: {
    type: Number as PropType<number>,
  },
  starColor: {
    type: String as PropType<string>,
  },
});

const emit = defineEmits(["ratingChange"]);
const selectedRating = ref<number | undefined>(undefined);
// 生成组件实例唯一标识
const uniqueId = useId();

const roundedRating = computed(() => {
  if (props.ratingValue !== undefined) {
    return Math.round(props.ratingValue * 2) / 2;
  } else {
    return selectedRating.value !== undefined
      ? Math.round(selectedRating.value * 2) / 2
      : undefined;
  }
});

const onRatingChange = (value: number) => {
  if (props.ratingValue === undefined) {
    selectedRating.value = value;
    emit("ratingChange", selectedRating.value);
  }
};
</script>

<template>
  <div class="center">
    <div :class="['rating', { disable: props.ratingValue !== undefined }]">
      <template v-for="star in stars" :key="star.id">
        <input
          :id="`${uniqueId}-${star.id}`"
          v-model="roundedRating"
          :disabled="props.ratingValue !== undefined"
          type="radio"
          :name="`rating-${uniqueId}`"
          :value="star.value"
          @input="onRatingChange(star.value)"
        />
        <label :for="`${uniqueId}-${star.id}`" :class="star.value % 1 === 0 ? 'full full-star' : 'half'"></label>
      </template>
    </div>
    <div class="rating-number">{{ props.ratingValue }}</div>
  </div>
</template>

核心修改点

  1. 引入useId()生成每个组件实例的唯一标识uniqueId
  2. 为input的id添加唯一前缀,避免多实例id重复
  3. 为radio的name添加唯一前缀,确保每个组件的单选框组独立
  4. 同步修改label的for属性,与input的id保持一致,保证关联正确

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 15:26:08