index.vue 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. <template>
  2. <view class="rate-box" :class="[{ animation }, containerClass]" @touchmove="ontouchmove" @touchend="touchMoving = false">
  3. <view
  4. v-for="(val, i) in list"
  5. :key="val"
  6. class="rate"
  7. :style="{ fontSize, paddingLeft: i !== 0 ? rateMargin : 0, paddingRight: i < list.length - 1 ? rateMargin : 0 }"
  8. :class="[
  9. { scale: !disabled && val <= rateValue && animation && touchMoving, 'color-base-text': val <= rateValue, defaultColor: val > rateValue },
  10. `rate-${i}`,
  11. rateClass
  12. ]"
  13. :data-val="val"
  14. @click="onItemClick"
  15. >
  16. <text class="iconfont icon-star"></text>
  17. </view>
  18. </view>
  19. </template>
  20. <script>
  21. import { getClientRect } from './common';
  22. export default {
  23. name: 'sx-rate',
  24. props: {
  25. // 当前值
  26. value: {
  27. type: [Number, String]
  28. },
  29. // 最大星星数量
  30. max: {
  31. type: Number,
  32. default: 5
  33. },
  34. // 禁用
  35. disabled: {
  36. type: Boolean,
  37. default: false
  38. },
  39. // 动画效果
  40. animation: {
  41. type: Boolean,
  42. default: true
  43. },
  44. // 默认星星颜色
  45. defaultColor: {
  46. type: String,
  47. default: '#ccc'
  48. },
  49. // 滑选后星星颜色
  50. activeColor: {
  51. type: String
  52. // default: '#FFB700'
  53. },
  54. // 星星大小
  55. fontSize: {
  56. type: String,
  57. default: 'inherit'
  58. },
  59. // 星星间距
  60. margin: {
  61. type: String,
  62. default: ''
  63. },
  64. // 自定义类名-容器
  65. containerClass: {
  66. type: String,
  67. default: ''
  68. },
  69. // 自定义类名-星星
  70. rateClass: {
  71. type: String,
  72. default: ''
  73. },
  74. index: {
  75. // 如果页面中存在多个该组件,通过该属性区分
  76. type: [Number, String]
  77. }
  78. },
  79. data() {
  80. return {
  81. rateValue: 0,
  82. touchMoving: false,
  83. startX: [],
  84. startW: 30
  85. };
  86. },
  87. computed: {
  88. list() {
  89. return [...new Array(this.max)].map((_, i) => i + 1);
  90. },
  91. rateMargin() {
  92. let margin = this.margin;
  93. if (!margin) return 0;
  94. switch (typeof margin) {
  95. case 'number':
  96. margin += 'px';
  97. case 'string':
  98. break;
  99. default:
  100. return 0;
  101. }
  102. let reg = /^(\d+)([^\d]*)/;
  103. let result = reg.exec(margin);
  104. if (!result) return 0;
  105. let [_, num, unit] = result;
  106. return num / 2 + unit;
  107. }
  108. },
  109. watch: {
  110. value: {
  111. handler(val) {
  112. this.rateValue = val;
  113. },
  114. immediate: true
  115. }
  116. },
  117. methods: {
  118. // 计算星星位置
  119. async initStartX() {
  120. let { max } = this;
  121. this.startX = [];
  122. for (let i = 0; i < max; i++) {
  123. let selector = `.rate-${i}`;
  124. let { left, width } = await getClientRect(selector, this);
  125. this.startX.push(left);
  126. this.startW = width;
  127. }
  128. },
  129. /**
  130. * 手指滑动事件回调
  131. * https://github.com/sunxi1997/uni-app-sx-rate/pull/1
  132. * 原本的触摸处理在自定了样式后可能会出现bug, 已解决
  133. */
  134. async ontouchmove(e) {
  135. if (!this.touchMoving) {
  136. this.touchMoving = true;
  137. // 开始手指滑动时重新计算星星位置,防止星星位置意外变化
  138. await this.initStartX();
  139. }
  140. let { startX, startW, max } = this;
  141. let { touches } = e;
  142. // 触摸焦点停留的位置
  143. let { pageX } = touches[touches.length - 1];
  144. // 超出最左边, 0 星
  145. if (pageX <= startX[0]) return this.toggle(0);
  146. // 刚好在第一颗星
  147. else if (pageX <= startX[0] + startW) return this.toggle(1);
  148. // 超出最右边, 最大星
  149. else if (pageX >= startX[max - 1]) return this.toggle(max);
  150. //计算星星停留的位置
  151. let startXHash = startX.concat(pageX).sort((a, b) => a - b);
  152. this.toggle(startXHash.indexOf(pageX));
  153. },
  154. // 点击回调
  155. onItemClick(e) {
  156. let { val } = e.currentTarget.dataset;
  157. this.toggle(val);
  158. },
  159. // 修改值
  160. toggle(val) {
  161. let { disabled } = this;
  162. if (disabled) return;
  163. if (this.rateValue !== val) {
  164. this.rateValue = val;
  165. this.$emit('update:value', val);
  166. let data = {
  167. index: this.index,
  168. value: val
  169. };
  170. this.$emit('change', data);
  171. }
  172. }
  173. },
  174. mounted() {}
  175. };
  176. </script>
  177. <style scoped>
  178. @import './sx-rate/iconfont.css';
  179. </style>
  180. <style lang="scss">
  181. .rate-box {
  182. min-height: 1.4em;
  183. display: flex;
  184. align-items: center;
  185. }
  186. .rate {
  187. display: inline-flex;
  188. justify-content: center;
  189. align-items: center;
  190. width: 1.2em;
  191. transition: all 0.15s linear;
  192. }
  193. .rate.scale {
  194. transform: scale(1.1);
  195. }
  196. .defaultColor {
  197. color: #ccc !important;
  198. }
  199. .activeColor {
  200. }
  201. </style>