详情小程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

281 lines
6.9 KiB

  1. <template>
  2. <view>
  3. <view ref="uni-rate" class="uni-rate">
  4. <view class="uni-rate__icon"
  5. :style="{ 'margin-right': margin + 'px' }"
  6. v-for="(star, index) in stars" :key="index"
  7. @touchstart.stop="touchstart"
  8. @touchmove.stop="touchmove">
  9. <text class="le lf-icon-xingxing-shi" :style="{'color': color, 'font-size': size +'px'}" v-if="isFill"></text>
  10. <text class="le lf-icon-xingxing-kong" :style="{'color': color, 'font-size': size +'px'}" v-else></text>
  11. <!-- #ifdef APP-NVUE -->
  12. <view :style="{ width: star.activeWitch.replace('%','')*size/100+'px'}" class="uni-rate__icon-on" >
  13. <text class="le lf-icon-xingxing-shi" :style="{'text-align': 'left', 'color': disabled?'#ccc':activeColor, 'font-size': size +'px'}"></text>
  14. </view>
  15. <!-- #endif -->
  16. <!-- #ifndef APP-NVUE -->
  17. <view :style="{ width: star.activeWitch}" class="uni-rate__icon-on">
  18. <text class="le lf-icon-xingxing-shi" :style="{'color': disabled?disabledColor:activeColor, 'font-size': size +'px'}"></text>
  19. </view>
  20. <!-- #endif -->
  21. </view>
  22. <view class="uni-num" :style="{'color': activeColor}" v-if="number && valueSync">{{ valueSync }}</view>
  23. </view>
  24. </view>
  25. </template>
  26. <script>
  27. // #ifdef APP-NVUE
  28. const dom = uni.requireNativePlugin('dom');
  29. // #endif
  30. /**
  31. * Rate 评分
  32. * @description 评分组件
  33. * @tutorial https://ext.dcloud.net.cn/plugin?id=33
  34. * @property {Boolean} isFill = [true|false] 星星的类型是否为实心类型, 默认为实心
  35. * @property {String} color 未选中状态的星星颜色默认为 "#ececec"
  36. * @property {String} activeColor 选中状态的星星颜色默认为 "#ffca3e"
  37. * @property {String} disabledColor 禁用状态的星星颜色默认为 "#c0c0c0"
  38. * @property {Number} size 星星的大小
  39. * @property {Number} value/v-model 当前评分
  40. * @property {Number} max 最大评分评分数量目前一分一颗星
  41. * @property {Number} margin 星星的间距单位 px
  42. * @property {Boolean} disabled = [true|false] 是否为禁用状态默认为 false
  43. * @property {Boolean} readonly = [true|false] 是否为只读状态默认为 false
  44. * @property {Boolean} allowHalf = [true|false] 是否实现半星默认为 false
  45. * @property {Boolean} touchable = [true|false] 是否支持滑动手势默认为 true
  46. * @event {Function} change uniRate value 改变时触发事件e={value:Number}
  47. */
  48. export default {
  49. name: "UniRate",
  50. props: {
  51. isFill: {
  52. // 星星的类型,是否镂空
  53. type: [Boolean, String],
  54. default: true
  55. },
  56. color: {
  57. // 星星未选中的颜色
  58. type: String,
  59. default: "#ececec"
  60. },
  61. activeColor: {
  62. // 星星选中状态颜色
  63. type: String,
  64. default: "#ffca3e"
  65. },
  66. disabledColor: {
  67. // 星星禁用状态颜色
  68. type: String,
  69. default: "#c0c0c0"
  70. },
  71. size: {
  72. // 星星的大小
  73. type: [Number, String],
  74. default: 24
  75. },
  76. value: {
  77. // 当前评分
  78. type: [Number, String],
  79. default: 1
  80. },
  81. max: {
  82. // 最大评分
  83. type: [Number, String],
  84. default: 5
  85. },
  86. margin: {
  87. // 星星的间距
  88. type: [Number, String],
  89. default: 0
  90. },
  91. disabled: {
  92. // 是否可点击
  93. type: [Boolean, String],
  94. default: false
  95. },
  96. readonly: {
  97. // 是否只读
  98. type: [Boolean, String],
  99. default: false
  100. },
  101. allowHalf: {
  102. // 是否显示半星
  103. type: [Boolean, String],
  104. default: false
  105. },
  106. touchable: {
  107. // 是否支持滑动手势
  108. type: [Boolean, String],
  109. default: true
  110. },
  111. number: {
  112. // 是否显示数字,显示的是当前的value平分
  113. type: Boolean,
  114. default: false
  115. }
  116. },
  117. data() {
  118. return {
  119. valueSync: ""
  120. };
  121. },
  122. watch: {
  123. value(newVal) {
  124. this.valueSync = Number(newVal);
  125. }
  126. },
  127. computed: {
  128. stars() {
  129. const value = this.valueSync ? this.valueSync : 0;
  130. const starList = [];
  131. const floorValue = Math.floor(value);
  132. const ceilValue = Math.ceil(value);
  133. for (let i = 0; i < this.max; i++) {
  134. if (floorValue > i) {
  135. starList.push({
  136. activeWitch: "100%"
  137. });
  138. } else if (ceilValue - 1 === i) {
  139. starList.push({
  140. activeWitch: (value - floorValue) * 100 + "%"
  141. });
  142. } else {
  143. starList.push({
  144. activeWitch: "0"
  145. });
  146. }
  147. }
  148. return starList;
  149. }
  150. },
  151. created() {
  152. this.valueSync = Number(this.value);
  153. this._rateBoxLeft = 0
  154. this._oldValue = null
  155. },
  156. mounted() {
  157. setTimeout(() => {
  158. this._getSize()
  159. }, 100)
  160. },
  161. methods: {
  162. touchstart(e) {
  163. if (this.readonly || this.disabled) return
  164. const {
  165. clientX,
  166. screenX
  167. } = e.changedTouches[0]
  168. // TODO 做一下兼容,只有 Nvue 下才有 screenX,其他平台式 clientX
  169. this._getRateCount(clientX || screenX)
  170. },
  171. touchmove(e) {
  172. if (this.readonly || this.disabled || !this.touchable) return
  173. const {
  174. clientX,
  175. screenX
  176. } = e.changedTouches[0]
  177. this._getRateCount(clientX || screenX)
  178. },
  179. /**
  180. * 获取星星个数
  181. */
  182. _getRateCount(clientX) {
  183. const size = Number(this.size)
  184. if(size === NaN){
  185. return new Error('size 属性只能设置为数字')
  186. }
  187. const rateMoveRange = clientX - this._rateBoxLeft
  188. let index = parseInt(rateMoveRange / (size + this.margin))
  189. index = index < 0 ? 0 : index;
  190. index = index > this.max ? this.max : index;
  191. const range = parseInt(rateMoveRange - (size + this.margin) * index);
  192. let value = 0;
  193. if (this._oldValue === index) return;
  194. this._oldValue = index;
  195. if (this.allowHalf) {
  196. if (range > (size / 2)) {
  197. value = index + 1
  198. } else {
  199. value = index + 0.5
  200. }
  201. } else {
  202. value = index + 1
  203. }
  204. value = Math.max(0.5, Math.min(value, this.max))
  205. this.valueSync = value
  206. this._onChange()
  207. },
  208. /**
  209. * 触发动态修改
  210. */
  211. _onChange() {
  212. this.$emit("input", this.valueSync);
  213. this.$emit("change", {
  214. value: this.valueSync
  215. });
  216. },
  217. /**
  218. * 获取星星距离屏幕左侧距离
  219. */
  220. _getSize() {
  221. // #ifndef APP-NVUE
  222. uni.createSelectorQuery()
  223. .in(this)
  224. .select('.uni-rate')
  225. .boundingClientRect()
  226. .exec(ret => {
  227. if (ret) {
  228. this._rateBoxLeft = ret[0].left
  229. }
  230. })
  231. // #endif
  232. // #ifdef APP-NVUE
  233. dom.getComponentRect(this.$refs['uni-rate'], (ret) => {
  234. const size = ret.size
  235. if (size) {
  236. this._rateBoxLeft = size.left
  237. }
  238. })
  239. // #endif
  240. }
  241. }
  242. };
  243. </script>
  244. <style lang="scss" scoped>
  245. .uni-rate {
  246. /* #ifndef APP-NVUE */
  247. display: flex;
  248. /* #endif */
  249. line-height: 1;
  250. font-size: 0;
  251. flex-direction: row;
  252. }
  253. .uni-rate__icon {
  254. position: relative;
  255. line-height: 1;
  256. font-size: 0;
  257. }
  258. .uni-rate__icon-on {
  259. overflow: hidden;
  260. position: absolute;
  261. top: 0;
  262. left: 0;
  263. line-height: 1;
  264. text-align: left;
  265. }
  266. .uni-num{
  267. font-size: 28rpx;
  268. margin-left: 10rpx;
  269. display: flex;
  270. align-items: center;
  271. }
  272. </style>