VueJS - 如何检查单选按钮是否与某个值匹配

cra*_*dev 1 javascript vue.js vuejs3

我的数组中有对象(产品)。每个产品都有自己的评级,评级是来自数据库的数字。我在产品本身上显示每个产品的四舍五入平均评分。

\n
{{ Math.round(Object.values(product.rating)[0]) }}\n
Run Code Online (Sandbox Code Playgroud)\n

我想显示人们用来评价产品的星星(单选按钮)的平均值。如果我点击对产品进行评分,则应检查与当前评分匹配的星星数量。在单个产品上,我可以只使用v-model,但是如果列表中有一堆产品并且每个产品都有不同的评级,我该怎么办?

\n

每个单选按钮的值是 ID 和 value 属性。如何将单选按钮的 ID 或值与产品的当前评级相匹配?

\n

没有this,所以我尝试这样做,但显然不起作用:

\n
<div class="rating">\n  <input\n    type="radio"\n    value="5"\n    id="5"\n    :checked="\n    this.value ==\n    Math.round(Object.values(product.rating)[0])\n    "\n    @change="rateproduct"\n    /><label for="5">\xe2\x98\x86</label>\n  <input\n    type="radio"\n    value="4"\n    id="4"\n    @change="rateproduct"\n    :checked="\n    this.value ==\n    Math.round(Object.values(product.rating)[0])\n    "\n    /><label for="4">\xe2\x98\x86</label>\n  <input\n    type="radio"\n    value="3"\n    id="3"\n    @change="rateproduct"\n    :checked="\n    this.value ==\n    Math.round(Object.values(product.rating)[0])\n    "\n    /><label for="3">\xe2\x98\x86</label>\n  <input\n    type="radio"\n    value="2"\n    id="2"\n    @change="rateproduct"\n    :checked="\n    this.value ==\n    Math.round(Object.values(product.rating)[0])\n    "\n    /><label for="2">\xe2\x98\x86</label>\n  <input\n    type="radio"\n    value="1"\n    id="1"\n    @change="rateproduct"\n    :checked="\n    this.value ==\n    Math.round(Object.values(product.rating)[0])\n    "\n    /><label for="1">\xe2\x98\x86</label>\n</div>   \n
Run Code Online (Sandbox Code Playgroud)\n

我尝试这样做,但它也不起作用:

\n
<input\n  type="radio"\n  value="5"\n  id="5"\n  :checked="\n  Math.round(Object.values(post.rating)[0])\n  ? 'checked'\n  : ''\n  "\n  @change="ratePost"\n  /><label for="5">\xe2\x98\x86</label>\n</input>\n
Run Code Online (Sandbox Code Playgroud)\n

这是我想要完成的任务,但它不起作用,并且只选择了最后一个帖子评级

\n
<template>\n    <div class="postsList">\n        <div class="post" v-for="post in posts" :key="post.id">\n            <div class="title">Title: {{post.title}}</div>\n            <div class="currentrating">Rating: {{post.rating}}</div>\n            <div class="vote">\n                <div class="rating">\n                    <div class="star" v-for="index in stars" :key="index">\n                        <input\n                            type="radio"\n                            name="stars"\n                            :value="index"\n                            v-model="post.rating"\n                            @change="ratePost"\n                        /><label>\xe2\x98\x86</label>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n</template>\n\n<script setup>\n  import {ref} from 'vue'\n    const stars = 5;\n    const posts = ref([\n        { id:1, title: "post1", rating: 2 },\n        { id:2, title: "post2", rating: 3 },\n        { id:3, title: "post3", rating: 2 },\n        { id:4, title: "post4", rating: 5 },\n        { id:5, title: "post5", rating: 2 },\n        { id:6, title: "post6", rating: 1 },\n        { id:7, title: "post7", rating: 2 },\n        { id:8, title: "post8", rating: 3 },\n        { id:9, title: "post9", rating: 4 },\n        { id:10, title: "post10", rating: 1 },\n    ]);\n</script>\n\n<style scoped>\n  .post{\n    padding:10px;\n  }\n  .rating{\n    display:flex;\n    margin-bottom: 10px;\n  }\n  .title{\n    margin-bottom:10px;\n  }\n  .currentrating{\n    margin-bottom:10px\n  }\n</style>\n
Run Code Online (Sandbox Code Playgroud)\n

Hov*_*els 5

一种可能的解决方案:

\n

在此输入图像描述

\n

Vue SFC Playground 链接

\n
<template>\n    <fieldset>\n        <legend>Average Star Rating</legend>\n        <div class="radios">\n            <div class="radioGroup" v-for="index in MAX_VALUE" :key="index">\n                <input \n                    type="radio" \n                    name="stars" \n                    :value="index" \n                    v-model="starValue" \n                    @click.prevent=""\n                />\n                <label>{{ index }} {{ starText(index) }}</label>\n            </div>\n        </div>\n        <div>\n            Average Rating: {{  roundedAverageValue }}\n        </div>\n    </fieldset>\n    <fieldset>\n        <legend>Individual Item Ratings</legend>\n        <div v-for="(slider, index) in sliderValues" :key="index" >\n            <label for="slider.name" class="sliderLabel">{{ slider.name }}</label>\n            <input \n                type="range" \n                name="starSlider" \n                :min="MIN_VALUE" \n                :max="MAX_VALUE" \n                v-model="slider.value"\n            >\n            Value: {{ slider.value }}\n        </div>\n        <div>\n            Average: {{ averageValue }}\n        </div>\n    </fieldset>\n\n</template>\n\n<script setup>\nimport { ref, watch, computed } from \'vue\';\n\nconst MIN_VALUE = 1;\nconst MAX_VALUE = 5;\n\nconst starValue = ref(1);\nconst sliderValues = ref([\n    { \n        value: MIN_VALUE,\n        name: \'Item 1\',\n     },\n     { \n        value: MIN_VALUE,\n        name: \'Item 2\',\n     },\n     { \n        value: MIN_VALUE,\n        name: \'Item 3\',\n     },\n     { \n        value: MIN_VALUE,\n        name: \'Item 4\',\n     },\n]);\n\nconst starText = (index) => {\n    return index > 1 ? \'stars\' : \'star\';\n}\n\nconst averageValue = computed(() => {\n    let sum = 0;\n    sliderValues.value.forEach((sv) => {\n        sum += parseInt(sv.value);\n    })\n    return sum / sliderValues.value.length;\n});\n\nconst roundedAverageValue = computed(() => {\n    return Math.round(averageValue.value);\n});\n\nwatch(roundedAverageValue, (newValue) => {\n    starValue.value = newValue;\n})\n</script>\n\n<style scoped>\nfieldset {\n  margin: 20px 70px;\n  text-align: center;\n}\n\n.radioGroup {\n  display: inline-block;\n  text-align: center;\n  margin: 10px;\n}\n\n.radioGroup label {\n    display: block;\n}\n\n.sliderLabel {\n    margin-right: 8px;\n}\n</style>\n
Run Code Online (Sandbox Code Playgroud)\n

解释:
\n这使用 v-for 从 1 到 5 创建无线电输入:v-for="index in 5"。每个radio的值是for循环的int索引。所有收音机的型号都是相同的,starValue

\n

然后,我使用对象数组来创建滑块,并使用计算属性来获取平均值,并使用计算属性上的观察器来更改无线电的模型值(因为在计算属性中产生副作用是不好的做法)财产)。

\n
\n

请注意,您最近发布的代码在模板代码的这一部分中存在错误:

\n
<div class="post" v-for="post in posts" :key="post.id">\n    <div class="title">Title: {{post.title}}</div>\n    <div class="currentrating">Rating: {{post.rating}}</div>\n    <div class="vote">\n        <div class="rating">\n            <div class="star" v-for="index in stars" :key="index">\n                <input\n                    type="radio"\n                    name="stars"\n                    :value="index"\n                    v-model="post.rating"\n                    @change="ratePost"\n                /><label>\xe2\x98\x86</label>\n            </div>\n        </div>\n    </div>\n</div>\n
Run Code Online (Sandbox Code Playgroud)\n

此行:name="stars"对于外部 v-for 循环的每次迭代都是相同的,这意味着您创建的每个无线电输入都是相同的无线电。由于单选组一次只能显示一个选择,因此仅显示一颗星被选择。

\n

解决方案很简单:为每个恒星集合提供一个唯一的无线电输入名称属性。您需要绑定 name 属性并可以使用 post.id 来确保唯一性,因此更改此:

\n

name="stars"

\n

对此:

\n

:name="stars + post.id"

\n

并且您发布的代码应该可以工作。

\n

例如:

\n
<div class="post" v-for="post in posts" :key="post.id">\n    <div class="title">Title: {{post.title}}</div>\n    <div class="currentrating">Rating: {{post.rating}}</div>\n    <div class="vote">\n        <div class="rating">\n            <div class="star" v-for="index in stars" :key="index">\n                <input\n                    type="radio"\n                    :name="stars + post.id"\n                    :value="index"\n                    v-model="post.rating"\n                    @change="ratePost"\n                /><label>\xe2\x98\x86</label>\n            </div>\n        </div>\n    </div>\n</div>\n
Run Code Online (Sandbox Code Playgroud)\n
\n

新迭代,现在有星星

\n

我的答案的最新版本现在创建了一个单独的评级面板,显示 5 颗星悬停效果,并将填充所有星星,直至并包括所选评级。为了实现这一点,我必须从 using 更改为input[type="radio"]using input[type="checkbox"]It,然后允许其他组件使用该组件,将面板的标题作为 prop 传递,并将其值作为v-model.

\n

在此输入图像描述

\n

评级面板.vue

\n
<template>\n  <div class="star-panel">\n    <fieldset class="flex-row">\n      <legend>{{ title }}</legend>\n      <div class="radio-wrapper">\n        <div class="radios">\n          <div class="radioGroup" v-for="star in stars" :key="star.value">\n            <input\n              type="checkbox"\n              name="stars"\n              :value="star.value"\n              v-model="star.checked"\n              :id="title + star.text"\n              @change="selectedItem(star.value)"\n            /><label class="star-label" :for="title + star.text">\n              {{ starChar(star.checked) }}</label\n            >\n          </div>\n        </div>\n        <div class="text-display">Rating: {{ stars[value - 1].text }}</div>\n      </div>\n    </fieldset>\n  </div>\n</template>\n
Run Code Online (Sandbox Code Playgroud)\n
<script setup>\nimport { computed, onMounted, ref } from "vue";\n\nconst props = defineProps(["title", "modelValue"]);\nconst emit = defineEmits(["update:modelValue"]);\nconst value = computed({\n  get() {\n    return props.modelValue;\n  },\n  set(value) {\n    emit("update:modelValue", value);\n  },\n});\n\nconst stars = ref([\n  {\n    text: "1 stars",\n    value: 1,\n    checked: false,\n  },\n  {\n    text: "2 stars",\n    value: 2,\n    checked: false,\n  },\n  {\n    text: "3 stars",\n    value: 3,\n    checked: false,\n  },\n  {\n    text: "4 stars",\n    value: 4,\n    checked: false,\n  },\n  {\n    text: "5 stars",\n    value: 5,\n    checked: false,\n  },\n]);\n\nconst selectedItem = (index) => {\n  value.value = index;\n\n  stars.value.forEach((star) => {\n    star.checked = index >= star.value;\n  });\n};\n\nconst FILLED_STAR = String.fromCharCode(9733);\nconst EMPTY_STAR = String.fromCharCode(9734);\n\nconst starChar = (checked) => {\n  if (checked) {\n    return FILLED_STAR;\n  } else {\n    return EMPTY_STAR;\n  }\n};\n\nonMounted(() => {\n  value.value = props.modelValue;\n\n  stars.value.forEach((star) => {\n    star.checked = props.modelValue >= star.value;\n  });\n});\n</script>\n
Run Code Online (Sandbox Code Playgroud)\n
<style scoped>\n.star-panel {\n  max-width: 400px;\n}\n.star-label {\n  font-size: 30px;\n  color: orange;\n}\n\n.star-label:hover {\n  color: rgb(173, 113, 2);\n  font-weight: bold;\n}\ninput[type="checkbox"] {\n  display: none;\n}\n.radio-wrapper {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: center;\n}\n.radios {\n  display: flex;\n  align-items: center;\n}\n.radioGroup {\n  margin: 2px 15px;\n  flex: 0 1 auto;\n}\n</style>\n
Run Code Online (Sandbox Code Playgroud)\n

然后是持有多个RatingPanel的组件:

\n

多重评级.vue

\n
<template>\n  <div class="post-list" v-for="post in posts" :key="post.id">\n    <SingleRatingPanel :title="post.title" v-model="post.rating" />\n  </div>\n</template>\n\n<script setup>\nimport { ref, watch } from "vue";\nimport SingleRatingPanel from "./RatingPanel.vue";\n\nconst posts = ref([\n  { id: 1, title: "Post 1", rating: 2 },\n  { id: 2, title: "Post 2", rating: 3 },\n  { id: 3, title: "Post 3", rating: 2 },\n  { id: 4, title: "Post 4", rating: 5 },\n  { id: 5, title: "Post 5", rating: 2 },\n  { id: 6, title: "Post 6", rating: 1 },\n  { id: 7, title: "Post 7", rating: 2 },\n  { id: 8, title: "Post 8", rating: 3 },\n  { id: 9, title: "Post 9", rating: 4 },\n  { id: 10, title: "Post 10", rating: 1 },\n]);\n\n// to demonstrate efficacy:\nwatch(\n  posts,\n  (newValue) => {\n    newValue.forEach((p) => console.log(p));\n  },\n  { deep: true }\n);\n</script>\n
Run Code Online (Sandbox Code Playgroud)\n