diff --git a/components/index.js b/components/index.js index e37bca7b2..c86f04856 100644 --- a/components/index.js +++ b/components/index.js @@ -3,6 +3,7 @@ import './checkbox/style' import './icon/style' import './radio/style' import './grid/style' +import './rate/style' export { default as Button } from './button' @@ -13,3 +14,5 @@ export { default as Icon } from './icon' export { default as Radio } from './radio' export { default as Grid } from './grid' + +export { default as Rate } from './rate' diff --git a/components/rate/Rate.vue b/components/rate/Rate.vue new file mode 100644 index 000000000..ee6f9276b --- /dev/null +++ b/components/rate/Rate.vue @@ -0,0 +1,119 @@ +<template> + <ul + :class="[prefixCls, disabled ? `${prefixCls}-disabled` : '', className]" + @mouseleave="onMouseLeave"> + <template v-for="i in count"> + <Star + ref="stars" + :index="i" + :disabled="disabled" + :prefix-cls="`${prefixCls}-star`" + :allowHalf="allowHalf" + :value="currentValue" + @onClick="onClick" + @onHover="onHover" + :key="i"> + <template slot-scope="props"> + <slot> + <Icon type="star"/> + </slot> + </template> + </Star> + </template> + </ul> +</template> + +<script> +import Star from './Star.vue'; +import Icon from '../icon/index'; +import { getOffsetLeft } from '../util/util'; + +export default { + name: 'Rate', + props: { + count: { + type: Number, + default: 5, + }, + value: { + type: Number, + default: 0, + }, + defaultValue: { + type: Number, + default: 0, + }, + onChange: { + type: Function, + default: () => {}, + }, + onHoverChange: { + type: Function, + default: () => {}, + }, + allowHalf: { + type: Boolean, + default: false, + }, + disabled: { + type: Boolean, + default: false, + }, + className: String, + }, + data() { + return { + prefixCls: 'ant-rate', + hoverValue: undefined, + currentValue: undefined, + markValue: undefined, + } + }, + created () { + this.currentValue = this.markValue = this.value || this.defaultValue + }, + watch: { + hoverValue(val) { + if(val === undefined) { + this.currentValue = this.markValue; + return; + } + this.currentValue = val; + } + }, + methods: { + onClick(event, index) { + let clValue = this.getStarValue(index, event.pageX); + this.markValue = clValue; + this.onMouseLeave(); + this.onChange(clValue); + }, + onHover(event, index) { + this.hoverValue = this.getStarValue(index, event.pageX); + this.onHoverChange(this.hoverValue); + }, + getStarDOM (index) { + return this.$refs.stars[index].$el + }, + getStarValue (index, x) { + let value = index; + if (this.allowHalf) { + const leftEdge = getOffsetLeft(this.getStarDOM(0)) + const width = getOffsetLeft(this.getStarDOM(1)) - leftEdge + if ((x - leftEdge - width * (index-1)) < width / 2) { + value -= 0.5 + } + } + return value + }, + onMouseLeave() { + this.hoverValue = undefined + this.onHoverChange(undefined); + }, + }, + components: { + Star, + Icon, + } +} +</script> diff --git a/components/rate/Star.vue b/components/rate/Star.vue new file mode 100644 index 000000000..3c4ec3d30 --- /dev/null +++ b/components/rate/Star.vue @@ -0,0 +1,52 @@ +<template> + <li + :class="getClassName()" + @click="onClick" + @mousemove="onHover"> + <div :class="`${this.prefixCls}-first`"><slot></slot></div> + <div :class="`${this.prefixCls}-second`"><slot></slot></div> + </li> +</template> +<script> +export default { + name: 'Star', + props: { + index: Number, + disabled: Boolean, + prefixCls: String, + allowHalf: Boolean, + value: Number, + }, + data() { + return { + } + }, + mounted() { + }, + computed: { + + }, + methods: { + getClassName() { + const { prefixCls, index, value, allowHalf } = this; + const starValue = index; + if (allowHalf && value + 0.5 === starValue) { + return `${prefixCls} ${prefixCls}-half ${prefixCls}-active`; + } + return starValue <= value ? `${prefixCls} ${prefixCls}-full` : `${prefixCls} ${prefixCls}-zero`; + }, + onClick(e) { + if(this.disabled) return; + this.$emit("onClick", e, this.index); + }, + onHover(e) { + if(this.disabled) return; + this.$emit("onHover", e, this.index); + }, + }, + watch: { + }, + commponents: { + } +} +</script> diff --git a/components/rate/index.js b/components/rate/index.js new file mode 100644 index 000000000..0c1d021f2 --- /dev/null +++ b/components/rate/index.js @@ -0,0 +1,3 @@ +import Rate from './Rate'; + +export default Rate; diff --git a/components/rate/style/index.js b/components/rate/style/index.js new file mode 100644 index 000000000..3a3ab0de5 --- /dev/null +++ b/components/rate/style/index.js @@ -0,0 +1,2 @@ +import '../../style/index.less'; +import './index.less'; diff --git a/components/rate/style/index.less b/components/rate/style/index.less new file mode 100644 index 000000000..e22dbbd0d --- /dev/null +++ b/components/rate/style/index.less @@ -0,0 +1,73 @@ +@import "../../style/themes/default"; +@import "../../style/mixins/index"; + +@rate-prefix-cls: ~"@{ant-prefix}-rate"; + +.@{rate-prefix-cls} { + margin: 0; + padding: 0; + list-style: none; + font-size: 20px; + display: inline-block; + vertical-align: middle; + + &-disabled &-star { + cursor: not-allowed; + &:hover { + transform: scale(1); + } + } + + &-star { + margin: 0; + padding: 0; + display: inline-block; + margin-right: 8px; + position: relative; + transition: all .3s; + color: @rate-star-bg; + cursor: pointer; + + &-first, + &-second { + user-select: none; + transition: all .3s; + } + + &:hover { + transform: scale(1.1); + } + + &-first { + position: absolute; + left: 0; + top: 0; + width: 50%; + height: 100%; + overflow: hidden; + opacity: 0; + } + + &-half &-first, + &-half &-second { + opacity: 1; + } + + &-half &-first, + &-full &-second { + color: @rate-star-color; + } + + &-half:hover &-first, + &-full:hover &-second { + color: tint(@rate-star-color, 20%); + } + } + + &-text { + margin-left: 8px; + vertical-align: middle; + display: inline-block; + font-size: @font-size-base; + } +} diff --git a/components/util/util.js b/components/util/util.js new file mode 100644 index 000000000..6d0070323 --- /dev/null +++ b/components/util/util.js @@ -0,0 +1,40 @@ +function getScroll(w, top) { + let ret = top ? w.pageYOffset : w.pageXOffset; + const method = top ? 'scrollTop' : 'scrollLeft'; + if (typeof ret !== 'number') { + const d = w.document; + // ie6,7,8 standard mode + ret = d.documentElement[method]; + if (typeof ret !== 'number') { + // quirks mode + ret = d.body[method]; + } + } + return ret; +} + +function getClientPosition(elem) { + let box; + let x; + let y; + const doc = elem.ownerDocument; + const body = doc.body; + const docElem = doc && doc.documentElement; + box = elem.getBoundingClientRect(); + x = box.left; + y = box.top; + x -= docElem.clientLeft || body.clientLeft || 0; + y -= docElem.clientTop || body.clientTop || 0; + return { + left: x, + top: y, + }; +} + +export const getOffsetLeft = (el) => { + const pos = getClientPosition(el); + const doc = el.ownerDocument; + const w = doc.defaultView || doc.parentWindow; + pos.left += getScroll(w); + return pos.left; +} diff --git a/examples/index.js b/examples/index.js index 4a4d55917..39fea342b 100644 --- a/examples/index.js +++ b/examples/index.js @@ -4,6 +4,7 @@ import Button from './button.vue' import Radio from './radio.vue' import Grid from './grid.vue' // import Dialog from './dialog.vue' +import Rate from './rate.vue' import './index.less' new Vue({ el: '#app', @@ -13,6 +14,7 @@ new Vue({ <Checkbox /> <AntButton /> <Radio /> + <Rate /> </div> `, components: { @@ -21,5 +23,6 @@ new Vue({ Checkbox, Grid, Radio, + Rate, }, }) diff --git a/examples/rate.vue b/examples/rate.vue new file mode 100644 index 000000000..b6f624e7f --- /dev/null +++ b/examples/rate.vue @@ -0,0 +1,63 @@ +<template> + <div> + 基本 + <Rate className="custom"></Rate> + </br> + 半星 + <Rate :allowHalf="allowHalf"></Rate> + </br> + 默认3颗星 + <Rate :value="initValue"></Rate> + </br> + 只读 + <Rate :value="initValue" :disabled="disabled"></Rate> + </br> + 回调函数 + <Rate + :onChange="onChange" + :onHoverChange="onHoverChange"></Rate> + <span v-if="hoverValue">{{hoverValue}}stars</span> + <span v-if="rValue">{{rValue}}stars</span> + <br/> + <Rate + :allowHalf="allowHalf" + :onHoverChange="onHoverChangeAH"></Rate> + <span v-if="hoverValueAH">{{hoverValueAH}}stars</span> + </br> + 自定义 + <Rate :value="initValue"> + <template slot-scope="props"> + <span>A</span> + </template> + </Rate> + </div> +</template> +<script> +import { Rate } from '../components/index' +export default { + data () { + return { + allowHalf: true, + initValue: 3, + disabled: true, + hoverValue: undefined, + rValue: undefined, + hoverValueAH: undefined, + } + }, + methods: { + onHoverChange(val) { + this.hoverValue = val; + }, + onChange(val) { + this.rValue = val; + }, + onHoverChangeAH(val) { + this.hoverValueAH = val; + } + }, + components: { + Rate + }, +} +</script>