uniapp评价组件



组件目录 components/Evaluation.vue

js 复制代码
<template>
  <view class="evaluation-container">
    <!-- 综合评价 -->
    <view class="evaluation-item" @tap="parentTap">
      <text class="label label-1">综合评价</text>
      <view class="rating-icons">
        <image
          v-for="(icon, index) in ratingIcons"
          :key="index"
          :class="['rating-icon', { active: icon.active }]"
          :src="icon.active ? icon.selectedSrcDynamic : icon.unselectedSrc"
          @tap.stop="setRating(index)"
        />
      </view>
      <text class="comment-text">
        {{ getCommentText(ratingIcons.filter(i => i.active).length) }}
      </text>
    </view>

    <!-- 描述相符 -->
    <view class="evaluation-item" @tap="parentTap">
      <text class="label">描述相符</text>
      <view class="heart-icons">
        <image
          v-for="(heart, index) in descriptionHearts"
          :key="index"
          :class="['heart-icon', { active: heart.active }]"
          :src="heart.active ? '/static/evaluate/heart-filled.png' : '/static/evaluate/heart-empty.png'"
          @tap.stop="setHeart(index, 'description')"
        />
      </view>
      <text class="comment-text">
        {{ getCommentText(descriptionHearts.filter(i => i.active).length) }}
      </text>
    </view>

    <!-- 物流服务 -->
    <view class="evaluation-item" @tap="parentTap">
      <text class="label">物流服务</text>
      <view class="heart-icons">
        <image
          v-for="(heart, index) in logisticsHearts"
          :key="index"
          :class="['heart-icon', { active: heart.active }]"
          :src="heart.active ? '/static/evaluate/heart-filled.png' : '/static/evaluate/heart-empty.png'"
          @tap.stop="setHeart(index, 'logistics')"
        />
      </view>
      <text class="comment-text">
        {{ getCommentText(logisticsHearts.filter(i => i.active).length) }}
      </text>
    </view>

    <!-- 服务态度 -->
    <view class="evaluation-item" @tap="parentTap">
      <text class="label">服务态度</text>
      <view class="heart-icons">
        <image
          v-for="(heart, index) in serviceHearts"
          :key="index"
          :class="['heart-icon', { active: heart.active }]"
          :src="heart.active ? '/static/evaluate/heart-filled.png' : '/static/evaluate/heart-empty.png'"
          @tap.stop="setHeart(index, 'service')"
        />
      </view>
      <text class="comment-text">
        {{ getCommentText(serviceHearts.filter(i => i.active).length) }}
      </text>
    </view>
  </view>
</template>

<script>
export default {
  name: 'Evaluation',
  props: {
    defaultValues: {
      type: Object,
      default: () => ({})
    },
    readonly: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      ratingIcons: [
        {
          unselectedSrc: '/static/evaluate/face-very-bad-unselected.png',
          selectedSrc: '/static/evaluate/face-very-bad.png',
          selectedSrcDynamic: null,
          active: false
        },
        {
          unselectedSrc: '/static/evaluate/face-bad-unselected.png',
          selectedSrc: '/static/evaluate/face-bad.png',
          selectedSrcDynamic: null,
          active: false
        },
        {
          unselectedSrc: '/static/evaluate/face-neutral-unselected.png',
          selectedSrc: '/static/evaluate/face-neutral.png',
          selectedSrcDynamic: null,
          active: false
        },
        {
          unselectedSrc: '/static/evaluate/face-good-unselected.png',
          selectedSrc: '/static/evaluate/face-good.png',
          selectedSrcDynamic: null,
          active: false
        },
        {
          unselectedSrc: '/static/evaluate/face-very-good-unselected.png',
          selectedSrc: '/static/evaluate/face-very-good.png',
          selectedSrcDynamic: null,
          active: false
        }
      ],
      descriptionHearts: Array(5).fill({ active: false }).map(h => ({ ...h })),
      logisticsHearts: Array(5).fill({ active: false }).map(h => ({ ...h })),
      serviceHearts: Array(5).fill({ active: false }).map(h => ({ ...h })),
      commentTexts: ['很差', '不满意', '一般', '满意', '超赞']
    };
  },
  mounted() {
    this.initEvaluation(this.defaultValues);
  },
  methods: {
    parentTap() {},
    setRating(index) {
      if (this.readonly) return;
      const selectedSrc = this.ratingIcons[index].selectedSrc;
      this.ratingIcons.forEach((icon, i) => {
        icon.active = i <= index;
        icon.selectedSrcDynamic = i <= index ? selectedSrc : null;
      });
    },
    setHeart(index, type) {
      if (this.readonly) return;
      const hearts = this[type + 'Hearts'];
      hearts.forEach((heart, i) => {
        heart.active = i <= index;
      });
    },
    getResult() {
      return {
        overall: this.ratingIcons.filter(i => i.active).length,
        description: this.descriptionHearts.filter(i => i.active).length,
        logistics: this.logisticsHearts.filter(i => i.active).length,
        service: this.serviceHearts.filter(i => i.active).length
      };
    },
    initEvaluation({ overall = 0, description = 0, logistics = 0, service = 0 }) {
      const selectedSrc = this.ratingIcons[overall - 1]?.selectedSrc || null;
      this.ratingIcons.forEach((icon, i) => {
        icon.active = i < overall;
        icon.selectedSrcDynamic = i < overall ? selectedSrc : null;
      });

      const setHearts = (arr, score) => {
        arr.forEach((item, i) => {
          item.active = i < score;
        });
      };

      setHearts(this.descriptionHearts, description);
      setHearts(this.logisticsHearts, logistics);
      setHearts(this.serviceHearts, service);
    },
    getCommentText(count) {
      if (count === 0) return '';
      return this.commentTexts[count - 1];
    }
  }
};
</script>

<style scoped>
.evaluation-container {
  /* padding: 20px; */
}
.evaluation-item {
  display: flex;
  align-items: center;
  margin-bottom: 28rpx;
}
.label{
	font-size: 28rpx;
	color: #2D2D2D;
}
.label-1{
	color: #0F1724;
	font-weight: 600;
}

.rating-icons,
.heart-icons {
  display: flex;
  /* gap: 10px; */
}
.rating-icon,
.heart-icon {
  width: 60rpx;
  height: 60rpx;
  margin-left: 30rpx;
}

/* 动画效果 */
@keyframes bounce {
  0% {
    transform: scale(1);
  }
  40% {
    transform: scale(1.3);
  }
  100% {
    transform: scale(1);
  }
}

.rating-icon.active,
.heart-icon.active {
  animation: bounce 0.3s ease;
}

/* 文案样式 */
.comment-text {
  margin-left: 24rpx;
  font-weight: 600;
  font-size: 28rpx;
  color: #0F1724;
  white-space: nowrap;
}
</style>

页面使用

js 复制代码
<template>
  <view>
    <Evaluation
      ref="evaluation"
      :default-values="{ overall: 0, description: 0, logistics: 0, service: 0 }"
      :readonly="false"
    />
    <button @tap="submit">提交</button>
  </view>
</template>

<script>
import Evaluation from '@/components/Evaluation.vue';

export default {
  components: { Evaluation },
  methods: {
    submit() {
      const result = this.$refs.evaluation.getResult();
      console.log('提交评分结果', result);
    }
  }
};
</script>

readonly是否允许点击一般详情页可以传true不让点击

相关推荐
菌菇汤38 分钟前
微信小程序传参过来了,但是数据没有获取到
微信小程序·小程序·uniapp
weixin_ab2 天前
Uniapp 中 onShow 与 onLoad 的执行时机解析
uniapp
满分观测网友z3 天前
CSS实现元素撑满剩余空间的5种方法
uniapp
~央千澈~22 天前
UniApp X:鸿蒙原生开发的机会与DCloud的崛起之路·优雅草卓伊凡
uni-app·uniapp
MaCa .BaKa1 个月前
39-居住证管理系统(小程序)
java·vue.js·spring boot·mysql·小程序·maven·uniapp
佛系菜狗1 个月前
【菜狗work前端】小程序加if判断时不及时刷新 vs Web
微信小程序·uniapp·web
HumoChen991 个月前
GZip+Base64压缩字符串在ios上解压报错问题解决(安卓、PC模拟器正常)
android·小程序·uniapp·base64·gzip
Anthony_sun1 个月前
UniAppx 跳转Android 系统通讯录
android·uniapp
MaCa .BaKa1 个月前
36-校园反诈系统(小程序)
java·spring boot·mysql·小程序·vue·maven·uniapp