cl-rate.uvue 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. <template>
  2. <view class="cl-rate" :class="[pt.className]">
  3. <view
  4. v-for="(item, index) in max"
  5. :key="index"
  6. class="cl-rate__item"
  7. :class="[
  8. {
  9. 'cl-rate__item--active': item <= modelValue
  10. },
  11. pt.item?.className
  12. ]"
  13. @touchstart="onTap(index)"
  14. >
  15. <cl-icon
  16. :name="voidIcon"
  17. :color="voidColor"
  18. :size="size"
  19. :pt="{
  20. className: `${pt.icon?.className}`
  21. }"
  22. ></cl-icon>
  23. <cl-icon
  24. v-if="getIconActiveWidth(item) > 0"
  25. :name="icon"
  26. :color="color"
  27. :size="size"
  28. :width="getIconActiveWidth(item)"
  29. :pt="{
  30. className: `absolute left-0 ${pt.icon?.className}`
  31. }"
  32. ></cl-icon>
  33. </view>
  34. <cl-text
  35. v-if="showScore"
  36. :pt="{
  37. className: parseClass(['cl-rate__score ml-2 font-bold', pt.score?.className])
  38. }"
  39. >{{ modelValue }}</cl-text
  40. >
  41. </view>
  42. </template>
  43. <script setup lang="ts">
  44. import { computed } from "vue";
  45. import { parseClass, parsePt } from "@/cool";
  46. import type { PassThroughProps } from "../../types";
  47. import type { ClIconProps } from "../cl-icon/props";
  48. defineOptions({
  49. name: "cl-rate"
  50. });
  51. // 组件属性定义
  52. const props = defineProps({
  53. // 样式穿透
  54. pt: {
  55. type: Object,
  56. default: () => ({})
  57. },
  58. // 评分值
  59. modelValue: {
  60. type: Number,
  61. default: 0
  62. },
  63. // 最大评分
  64. max: {
  65. type: Number,
  66. default: 5
  67. },
  68. // 是否禁用
  69. disabled: {
  70. type: Boolean,
  71. default: false
  72. },
  73. // 是否允许半星
  74. allowHalf: {
  75. type: Boolean,
  76. default: false
  77. },
  78. // 是否显示分数
  79. showScore: {
  80. type: Boolean,
  81. default: false
  82. },
  83. // 组件尺寸
  84. size: {
  85. type: Number,
  86. default: 40
  87. },
  88. // 图标名称
  89. icon: {
  90. type: String,
  91. default: "star-fill"
  92. },
  93. // 未激活图标
  94. voidIcon: {
  95. type: String,
  96. default: "star-fill"
  97. },
  98. // 激活颜色
  99. color: {
  100. type: String,
  101. default: "primary"
  102. },
  103. // 默认颜色
  104. voidColor: {
  105. type: String,
  106. default: "#dddddd"
  107. }
  108. });
  109. // 定义事件
  110. const emit = defineEmits(["update:modelValue", "change"]);
  111. // 透传样式类型定义
  112. type PassThrough = {
  113. className?: string;
  114. item?: PassThroughProps;
  115. icon?: ClIconProps;
  116. score?: PassThroughProps;
  117. };
  118. // 解析透传样式
  119. const pt = computed(() => parsePt<PassThrough>(props.pt));
  120. // 获取图标激活宽度
  121. function getIconActiveWidth(item: number) {
  122. // 如果评分值大于等于当前项,返回null表示完全填充
  123. if (props.modelValue >= item) {
  124. return props.size;
  125. }
  126. // 如果评分值的整数部分小于当前项,返回0表示不填充
  127. if (Math.floor(props.modelValue) < item - 1) {
  128. return 0;
  129. }
  130. // 处理小数部分填充
  131. return Math.floor((props.modelValue % 1) * props.size);
  132. }
  133. // 点击事件处理
  134. function onTap(index: number) {
  135. if (props.disabled) {
  136. return;
  137. }
  138. let value: number;
  139. if (props.allowHalf) {
  140. // 半星逻辑:点击同一位置切换半星和整星
  141. const currentValue = index + 1;
  142. if (props.modelValue == currentValue) {
  143. value = index + 0.5;
  144. } else if (props.modelValue == index + 0.5) {
  145. value = index;
  146. } else {
  147. value = currentValue;
  148. }
  149. } else {
  150. value = index + 1;
  151. }
  152. // 确保值在有效范围内
  153. value = Math.max(0, Math.min(value, props.max));
  154. emit("update:modelValue", value);
  155. emit("change", value);
  156. }
  157. </script>
  158. <style lang="scss" scoped>
  159. .cl-rate {
  160. @apply flex flex-row items-center;
  161. &__item {
  162. @apply flex items-center justify-center relative duration-200;
  163. transition-property: color;
  164. margin-right: 6rpx;
  165. overflow: hidden;
  166. }
  167. }
  168. </style>