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不让点击

相关推荐
Rysxt_5 天前
Uniapp全局配置教程
前端·uniapp
嗯嗯**5 天前
HBuilder学习1:概述、网站快速免费打包成apk
uniapp·apk·hbuilder·url快速打包成apk·网站快速打包成apk
CherishXt17 天前
对接腾讯IM,实现个人业务系统页面按钮直接跳转到和用户的聊天页面(不需要加好友)
uniapp·即时通讯·im
巴巴博一20 天前
UniApp实战:如何优雅地把 uv-ui (uv-qrcode) 生成的二维码保存到手机相册
微信小程序·uniapp·uvui
getaxiosluo21 天前
uniapp开发公众号,微信设置字体大小后,禁止改变页面字体大小
vue·uniapp·微信公众平台
特立独行的猫a22 天前
主要跨端开发框架对比:Flutter、RN、KMP、Uniapp、Cordova,谁是未来主流?
flutter·uni-app·uniapp·rn·kmp·kuikly
微:xsooop1 个月前
iOS上架被拒4.3(a) 10次到过审历程
flutter·unity·ios·uniapp
任小栗1 个月前
uniappx实现app壳子,可直接拿来用
vue·uniapp
计算机程序设计小李同学1 个月前
基于Web和Android的漫画阅读平台
java·前端·vue.js·spring boot·后端·uniapp
码界奇点1 个月前
基于Spring Boot 3与UniApp的跨平台新零售电商系统设计与实现
spring boot·uni-app·毕业设计·uniapp·零售·源代码管理