822eed2834626ea85cb8a4a3cf4e363e2d885846a658c9cf3df1a1e084c5cf345296462c24597a828bde4922eb1502b5a7f9ce731dff4111648c2ed44a8af5 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. <template>
  2. <div
  3. class="el-rate"
  4. @keydown="handleKey"
  5. role="slider"
  6. :aria-valuenow="currentValue"
  7. :aria-valuetext="text"
  8. aria-valuemin="0"
  9. :aria-valuemax="max"
  10. tabindex="0">
  11. <span
  12. v-for="(item, key) in max"
  13. class="el-rate__item"
  14. @mousemove="setCurrentValue(item, $event)"
  15. @mouseleave="resetCurrentValue"
  16. @click="selectValue(item)"
  17. :style="{ cursor: rateDisabled ? 'auto' : 'pointer' }"
  18. :key="key">
  19. <i
  20. :class="[classes[item - 1], { 'hover': hoverIndex === item }]"
  21. class="el-rate__icon"
  22. :style="getIconStyle(item)">
  23. <i
  24. v-if="showDecimalIcon(item)"
  25. :class="decimalIconClass"
  26. :style="decimalStyle"
  27. class="el-rate__decimal">
  28. </i>
  29. </i>
  30. </span>
  31. <span v-if="showText || showScore" class="el-rate__text" :style="{ color: textColor }">{{ text }}</span>
  32. </div>
  33. </template>
  34. <script>
  35. import { hasClass } from 'element-ui/src/utils/dom';
  36. import { isObject } from 'element-ui/src/utils/types';
  37. import Migrating from 'element-ui/src/mixins/migrating';
  38. export default {
  39. name: 'ElRate',
  40. mixins: [Migrating],
  41. inject: {
  42. elForm: {
  43. default: ''
  44. }
  45. },
  46. data() {
  47. return {
  48. pointerAtLeftHalf: true,
  49. currentValue: this.value,
  50. hoverIndex: -1
  51. };
  52. },
  53. props: {
  54. value: {
  55. type: Number,
  56. default: 0
  57. },
  58. lowThreshold: {
  59. type: Number,
  60. default: 2
  61. },
  62. highThreshold: {
  63. type: Number,
  64. default: 4
  65. },
  66. max: {
  67. type: Number,
  68. default: 5
  69. },
  70. colors: {
  71. type: [Array, Object],
  72. default() {
  73. return ['#F7BA2A', '#F7BA2A', '#F7BA2A'];
  74. }
  75. },
  76. voidColor: {
  77. type: String,
  78. default: '#C6D1DE'
  79. },
  80. disabledVoidColor: {
  81. type: String,
  82. default: '#EFF2F7'
  83. },
  84. iconClasses: {
  85. type: [Array, Object],
  86. default() {
  87. return ['el-icon-star-on', 'el-icon-star-on', 'el-icon-star-on'];
  88. }
  89. },
  90. voidIconClass: {
  91. type: String,
  92. default: 'el-icon-star-off'
  93. },
  94. disabledVoidIconClass: {
  95. type: String,
  96. default: 'el-icon-star-on'
  97. },
  98. disabled: {
  99. type: Boolean,
  100. default: false
  101. },
  102. allowHalf: {
  103. type: Boolean,
  104. default: false
  105. },
  106. showText: {
  107. type: Boolean,
  108. default: false
  109. },
  110. showScore: {
  111. type: Boolean,
  112. default: false
  113. },
  114. textColor: {
  115. type: String,
  116. default: '#1f2d3d'
  117. },
  118. texts: {
  119. type: Array,
  120. default() {
  121. return ['极差', '失望', '一般', '满意', '惊喜'];
  122. }
  123. },
  124. scoreTemplate: {
  125. type: String,
  126. default: '{value}'
  127. }
  128. },
  129. computed: {
  130. text() {
  131. let result = '';
  132. if (this.showScore) {
  133. result = this.scoreTemplate.replace(/\{\s*value\s*\}/, this.rateDisabled
  134. ? this.value
  135. : this.currentValue);
  136. } else if (this.showText) {
  137. result = this.texts[Math.ceil(this.currentValue) - 1];
  138. }
  139. return result;
  140. },
  141. decimalStyle() {
  142. let width = '';
  143. if (this.rateDisabled) {
  144. width = `${ this.valueDecimal }%`;
  145. } else if (this.allowHalf) {
  146. width = '50%';
  147. }
  148. return {
  149. color: this.activeColor,
  150. width
  151. };
  152. },
  153. valueDecimal() {
  154. return this.value * 100 - Math.floor(this.value) * 100;
  155. },
  156. classMap() {
  157. return Array.isArray(this.iconClasses)
  158. ? {
  159. [this.lowThreshold]: this.iconClasses[0],
  160. [this.highThreshold]: { value: this.iconClasses[1], excluded: true },
  161. [this.max]: this.iconClasses[2]
  162. } : this.iconClasses;
  163. },
  164. decimalIconClass() {
  165. return this.getValueFromMap(this.value, this.classMap);
  166. },
  167. voidClass() {
  168. return this.rateDisabled ? this.disabledVoidIconClass : this.voidIconClass;
  169. },
  170. activeClass() {
  171. return this.getValueFromMap(this.currentValue, this.classMap);
  172. },
  173. colorMap() {
  174. return Array.isArray(this.colors)
  175. ? {
  176. [this.lowThreshold]: this.colors[0],
  177. [this.highThreshold]: { value: this.colors[1], excluded: true },
  178. [this.max]: this.colors[2]
  179. } : this.colors;
  180. },
  181. activeColor() {
  182. return this.getValueFromMap(this.currentValue, this.colorMap);
  183. },
  184. classes() {
  185. let result = [];
  186. let i = 0;
  187. let threshold = this.currentValue;
  188. if (this.allowHalf && this.currentValue !== Math.floor(this.currentValue)) {
  189. threshold--;
  190. }
  191. for (; i < threshold; i++) {
  192. result.push(this.activeClass);
  193. }
  194. for (; i < this.max; i++) {
  195. result.push(this.voidClass);
  196. }
  197. return result;
  198. },
  199. rateDisabled() {
  200. return this.disabled || (this.elForm || {}).disabled;
  201. }
  202. },
  203. watch: {
  204. value(val) {
  205. this.currentValue = val;
  206. this.pointerAtLeftHalf = this.value !== Math.floor(this.value);
  207. }
  208. },
  209. methods: {
  210. getMigratingConfig() {
  211. return {
  212. props: {
  213. 'text-template': 'text-template is renamed to score-template.'
  214. }
  215. };
  216. },
  217. getValueFromMap(value, map) {
  218. const matchedKeys = Object.keys(map)
  219. .filter(key => {
  220. const val = map[key];
  221. const excluded = isObject(val) ? val.excluded : false;
  222. return excluded ? value < key : value <= key;
  223. })
  224. .sort((a, b) => a - b);
  225. const matchedValue = map[matchedKeys[0]];
  226. return isObject(matchedValue) ? matchedValue.value : (matchedValue || '');
  227. },
  228. showDecimalIcon(item) {
  229. let showWhenDisabled = this.rateDisabled && this.valueDecimal > 0 && item - 1 < this.value && item > this.value;
  230. /* istanbul ignore next */
  231. let showWhenAllowHalf = this.allowHalf &&
  232. this.pointerAtLeftHalf &&
  233. item - 0.5 <= this.currentValue &&
  234. item > this.currentValue;
  235. return showWhenDisabled || showWhenAllowHalf;
  236. },
  237. getIconStyle(item) {
  238. const voidColor = this.rateDisabled ? this.disabledVoidColor : this.voidColor;
  239. return {
  240. color: item <= this.currentValue ? this.activeColor : voidColor
  241. };
  242. },
  243. selectValue(value) {
  244. if (this.rateDisabled) {
  245. return;
  246. }
  247. if (this.allowHalf && this.pointerAtLeftHalf) {
  248. this.$emit('input', this.currentValue);
  249. this.$emit('change', this.currentValue);
  250. } else {
  251. this.$emit('input', value);
  252. this.$emit('change', value);
  253. }
  254. },
  255. handleKey(e) {
  256. if (this.rateDisabled) {
  257. return;
  258. }
  259. let currentValue = this.currentValue;
  260. const keyCode = e.keyCode;
  261. if (keyCode === 38 || keyCode === 39) { // left / down
  262. if (this.allowHalf) {
  263. currentValue += 0.5;
  264. } else {
  265. currentValue += 1;
  266. }
  267. e.stopPropagation();
  268. e.preventDefault();
  269. } else if (keyCode === 37 || keyCode === 40) {
  270. if (this.allowHalf) {
  271. currentValue -= 0.5;
  272. } else {
  273. currentValue -= 1;
  274. }
  275. e.stopPropagation();
  276. e.preventDefault();
  277. }
  278. currentValue = currentValue < 0 ? 0 : currentValue;
  279. currentValue = currentValue > this.max ? this.max : currentValue;
  280. this.$emit('input', currentValue);
  281. this.$emit('change', currentValue);
  282. },
  283. setCurrentValue(value, event) {
  284. if (this.rateDisabled) {
  285. return;
  286. }
  287. /* istanbul ignore if */
  288. if (this.allowHalf) {
  289. let target = event.target;
  290. if (hasClass(target, 'el-rate__item')) {
  291. target = target.querySelector('.el-rate__icon');
  292. }
  293. if (hasClass(target, 'el-rate__decimal')) {
  294. target = target.parentNode;
  295. }
  296. this.pointerAtLeftHalf = event.offsetX * 2 <= target.clientWidth;
  297. this.currentValue = this.pointerAtLeftHalf ? value - 0.5 : value;
  298. } else {
  299. this.currentValue = value;
  300. }
  301. this.hoverIndex = value;
  302. },
  303. resetCurrentValue() {
  304. if (this.rateDisabled) {
  305. return;
  306. }
  307. if (this.allowHalf) {
  308. this.pointerAtLeftHalf = this.value !== Math.floor(this.value);
  309. }
  310. this.currentValue = this.value;
  311. this.hoverIndex = -1;
  312. }
  313. },
  314. created() {
  315. if (!this.value) {
  316. this.$emit('input', 0);
  317. }
  318. }
  319. };
  320. </script>