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

相关推荐
qq_350897215 天前
trea实用工具
vue.js·uniapp
9765033355 天前
iOS 上架/审核 4.3a全面解读-最新
flutter·ios·objective-c·uniapp
上单带刀不带妹5 天前
小程序图片缓存问题:时间戳和版本号解决图片不更新
缓存·小程序·vue·uniapp
爱折腾的编程老炮7 天前
潮玩品牌自建抽盒机小程序——商城 + 抽盒 + 一番赏 + 会员 + 社区,一套代码怎么搭
spring boot·小程序·vue·mybatis·uniapp
蜡台7 天前
ux-camera 跨平台相机组件
数码相机·vue·uniapp·camera·nvue·取景框
海水冷却8 天前
uniappx 实现 IM 聊天:SDK 支持现状、三条集成路径与踩坑实战
uniapp·即时通讯
sunphp开发者11 天前
明灯智慧地图导览系统制作教程
vue·php·uniapp·js·贴图
zjwxhhzhf24 天前
2026.8.20前后端接口联调的问题
前端·uniapp·前后端对接·接口联调
梦想的颜色1 个月前
AI 时代小白 VibeCoding 做 APP:UniApp(含 Uni‑X)、Flutter 与 React Native+Expo全维度技术选型对比指南
flutter·react native·app·uniapp·vibecoding·unippx·app产品
梦想的颜色1 个月前
UniApp 请求层终极封装:统一拦截、错误处理、多端兼容,结合 Claude Code 高效落地
服务器·uniapp·uni.request·codex·claudecode·ai 辅助开发·uniapp 请求封装