From 68885d790cc722f9eddae9a463603ff6ce023a57 Mon Sep 17 00:00:00 2001 From: jimczj Date: Tue, 20 Nov 2018 14:44:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(range):=20=E6=96=B0=E5=A2=9E=20range=20?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.js | 3 +- src/components/range/index.js | 241 ++++++++++++++++++++++++++++++++ src/components/range/index.scss | 54 +++++++ src/index.js | 1 + src/pages/form/range/index.js | 83 +++++++++++ src/pages/form/range/index.scss | 4 + src/pages/panel/index.js | 4 + 7 files changed, 389 insertions(+), 1 deletion(-) create mode 100644 src/components/range/index.js create mode 100644 src/components/range/index.scss create mode 100644 src/pages/form/range/index.js create mode 100644 src/pages/form/range/index.scss diff --git a/src/app.js b/src/app.js index c306f12f0..b9a38b35a 100644 --- a/src/app.js +++ b/src/app.js @@ -54,7 +54,8 @@ class App extends Component { 'pages/form/picker-view/index', 'pages/form/slider/index', 'pages/form/search-bar/index', - 'pages/form/image-picker/index' + 'pages/form/image-picker/index', + 'pages/form/range/index' ], window: { backgroundTextStyle: 'light', diff --git a/src/components/range/index.js b/src/components/range/index.js new file mode 100644 index 000000000..47b6961e1 --- /dev/null +++ b/src/components/range/index.js @@ -0,0 +1,241 @@ +import Taro from '@tarojs/taro' +import PropTypes from 'prop-types' +import { View } from '@tarojs/components' +import classNames from 'classnames' + +import AtComponent from '../../common/component' +import './index.scss' + +const defaultFunc = () => {} + +export default class AtRange extends AtComponent { + static defaultProps = { + customStyle: '', + className: '', + sliderStyle: '', + railStyle: '', + trackStyle: '', + value: [0, 0], + min: 0, + max: 100, + disabled: false, + onChange: defaultFunc, + onAfterChange: defaultFunc + } + + static propTypes = { + customStyle: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.string + ]), + className: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.string + ]), + sliderStyle: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.string + ]), + railStyle: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.string + ]), + trackStyle: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.string + ]), + value: PropTypes.array, + min: PropTypes.number, + max: PropTypes.number, + disabled: PropTypes.bool, + onChange: PropTypes.func + } + + constructor (props) { + super(...arguments) + const { max, min } = props + // range 宽度 + this.width = 0 + // range 到屏幕左边的距离 + this.left = 0 + this.deltaValue = max - min + this.currentSlider = '' + this.state = { + slider1X: 0, + slider2X: 0 + } + } + + handleClick (event) { + if (this.currentSlider && !this.props.disabled) { + const env = Taro.getEnv() + let sliderValue = 0 + if (env === Taro.ENV_TYPE.WEB) { + sliderValue = event.pageX - this.left + } else if (env === Taro.ENV_TYPE.WEAPP) { + sliderValue = event.target.x - this.left + } + this.setSliderValue(this.currentSlider, sliderValue, 'onChange') + } + } + + handleTouchMove (sliderName, event) { + if (this.props.disabled) return + + const clientX = event.touches[0].clientX + this.setSliderValue(sliderName, clientX - this.left, 'onChange') + } + + handleTouchEnd (sliderName) { + if (this.props.disabled) return + + this.currentSlider = sliderName + this.triggerEvent('onAfterChange') + } + + setSliderValue (sliderName, targetValue, funcName) { + let value = 0 + if (targetValue < 0) { + value = 0 + } else if (targetValue > this.width) { + value = this.width + } else { + value = targetValue + } + + value = Math.floor((value / this.width) * 100) + if (funcName) { + this.setState({ + [sliderName]: value + }, () => (this.triggerEvent(funcName))) + } else { + this.setState({ + [sliderName]: value + }) + } + } + + setValue (value) { + const slider1X = Math.round((value[0] / this.deltaValue) * 100) + const slider2X = Math.round((value[1] / this.deltaValue) * 100) + this.setState({ + slider1X, + slider2X + }) + } + + triggerEvent (funcName) { + const { slider1X, slider2X } = this.state + const value1 = Math.round((slider1X / 100) * this.deltaValue) + const value2 = Math.round((slider2X / 100) * this.deltaValue) + const valueArr = [] + if (value1 > value2) { + valueArr.push(value2) + valueArr.push(value1) + } else { + valueArr.push(value1) + valueArr.push(value2) + } + + if (funcName === 'onChange') { + this.props.onChange(valueArr) + } else if (funcName === 'onAfterChange') { + this.props.onAfterChange(valueArr) + } + } + + componentWillReceiveProps (nextProps) { + const { value } = nextProps + if (this.props.value[0] !== value[0] && this.props.value[1] !== value[1]) { + this.setValue(value) + } + } + + componentDidMount () { + // 获取 range 宽度 + const env = Taro.getEnv() + const { value } = this.props + // 异步解决获取 offsetWidth 不准确问题 + setTimeout(() => { + if (env === Taro.ENV_TYPE.WEB) { + this.width = Math.round(this.rangeRef.vnode.dom.offsetWidth) + this.left = Math.round(this.rangeRef.vnode.dom.offsetLeft) + this.setValue(value) + } else if (env === Taro.ENV_TYPE.WEAPP) { + this.rangeRef.boundingClientRect(rect => { + this.width = Math.round(rect.width) + this.left = Math.round(rect.left) + this.setValue(value) + }).exec() + } + }) + } + + getRangeRef = node => (this.rangeRef = node) + + render () { + const { + className, + customStyle, + sliderStyle, + railStyle, + trackStyle, + disabled + } = this.props + + const { slider1X, slider2X } = this.state + const slider1Style = { + left: `${slider1X}%` + } + const slider2Style = { + left: `${slider2X}%` + } + const smallX = slider1X > slider2X ? slider2X : slider1X + const deltaX = Math.abs(slider1X - slider2X) + const atTrackStyle = { + left: `${smallX}%`, + width: `${deltaX}%` + } + + return ( + + + + + + + + + + + + ) + } +} diff --git a/src/components/range/index.scss b/src/components/range/index.scss new file mode 100644 index 000000000..f3c74f656 --- /dev/null +++ b/src/components/range/index.scss @@ -0,0 +1,54 @@ +@import "../../style/theme/default.scss"; +@import "../../style/mixins/index.scss"; + +$height: 22PX; + +.at-range { + position: relative; + width: 100%; + padding: 0 $height; + box-sizing: border-box; + + &__container { + position: relative; + display: flex; + align-items: center; + width: 100%; + height: $height; + box-sizing: content-box; + } + + &__rail { + position: absolute; + z-index: $zindex-form - 1; + width: 100%; + height: 2PX; + background-color: $color-grey-3; + box-sizing: border-box; + overflow: hidden; + } + + &__track { + position: absolute; + width: 20%; + height: 2PX; + background-color: $color-brand; + z-index: $zindex-form; + } + + &__slider { + position: absolute; + z-index: $zindex-form + 1; + width: $height; + height: $height; + border-radius: 50%; + border: 1px solid $color-border-grey; + box-shadow: 0 4px 4px 0 $color-border-grey; + background-color: #fff; + box-sizing: border-box; + } + + &--disabled { + opacity: $opacity-disabled; + } +} diff --git a/src/index.js b/src/index.js index 040cde924..ee16f7d62 100644 --- a/src/index.js +++ b/src/index.js @@ -53,6 +53,7 @@ export { default as AtSteps } from './components/steps' export { default as AtCurtain } from './components/curtain' export { AtMessage, message } from './components/message' export { default as AtImagePicker } from './components/image-picker' +export { default as AtRange } from './components/range' /* 私有的组件 */ export { default as AtLoading } from './components/loading' diff --git a/src/pages/form/range/index.js b/src/pages/form/range/index.js new file mode 100644 index 000000000..2eca1c845 --- /dev/null +++ b/src/pages/form/range/index.js @@ -0,0 +1,83 @@ +import Taro from '@tarojs/taro' +import { View } from '@tarojs/components' +import AtRange from '../../../components/range/index' +import AtToast from '../../../components/toast/index' +import DocsHeader from '../../components/doc-header' +import './index.scss' + +export default class Index extends Taro.Component { + config = { + navigationBarTitleText: 'Taro UI' + } + + constructor () { + super(...arguments) + this.state = { + isOpened: false, + text: '', + } + } + + handleChange (value) { + this.setState({ + isOpened: true, + text: `${value[0]}~${value[1]}` + }) + } + + handleClose () { + this.setState({ + isOpened: false + }) + } + render () { + return ( + + + {/* S Header */} + + {/* E Header */} + + {/* S Body */} + + {/* 基础用法 */} + + 基础用法 + + + + + {/* 自定义样式 */} + + 自定义样式 + + + + + {/* 禁止状态 */} + + 禁止状态 + + + + + + {/* E Body */} + + ) + } +} diff --git a/src/pages/form/range/index.scss b/src/pages/form/range/index.scss new file mode 100644 index 000000000..c802b34d5 --- /dev/null +++ b/src/pages/form/range/index.scss @@ -0,0 +1,4 @@ +.example-item { + color: #333; + font-size: 28px; +} diff --git a/src/pages/panel/index.js b/src/pages/panel/index.js index a8f4bc289..10ea2d93b 100644 --- a/src/pages/panel/index.js +++ b/src/pages/panel/index.js @@ -192,6 +192,10 @@ export default class PanelBasic extends Component { { id: 'Image-Picker', name: '图片选择器' + }, + { + id: 'Range', + name: '区域选择器' } ], layout: [