├── .babelrc
├── index.js
├── demo
├── demo.scss
├── index.tpl.html
├── demo.js
└── webpack.js
├── Gruntfile.js
├── lib
├── areaGear.scss
├── select.scss
├── areas.scss
├── areas.js
├── areaGear.js
└── select.js
├── src
├── areaGear.scss
├── select.scss
├── areas.scss
├── areas.js
├── areaGear.js
└── select.js
├── LICENSE
├── package.json
└── README.md
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets":[
3 | "es2015",
4 | "react"
5 | ],
6 | "plugins":[]
7 | }
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by chkui on 2017/7/7.
3 | */
4 | module.exports = require('./lib/areas').default
5 | module.exports.default = module.exports
6 |
--------------------------------------------------------------------------------
/demo/demo.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | html {
4 | position: relative;
5 | font-size: 20px; /*由于chrome内核的浏览器最小标定字体为12px因此使用1rem=20px来换算布局大小*/
6 | height: 100%;
7 | * {
8 | box-sizing: border-box; //全局使用盒模型
9 | }
10 | }
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 | grunt.initConfig({
3 | copy: {
4 | main: {
5 | files: [
6 | //根目录文件打包
7 | {expand: true, cwd: 'src/', src: '**', dest: 'lib/'},
8 | ],
9 | },
10 | },
11 | });
12 | grunt.loadNpmTasks('grunt-contrib-copy');
13 | grunt.registerTask('default', ['copy']);
14 | };
--------------------------------------------------------------------------------
/demo/index.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 测试DEMO
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/lib/areaGear.scss:
--------------------------------------------------------------------------------
1 | .area_roll_mask {
2 | //-webkit-mask: -webkit-gradient(linear, 0% 40%, 0% 0%, from(#debb47), to(rgba(36, 142, 36, 0)));
3 | //-webkit-mask: -webkit-linear-gradient(bottom, #debb47 50%, rgba(36, 142, 36, 0));
4 | padding: 0
5 | }
6 |
7 | .area_roll {
8 | display: flex;
9 | width: 100%;
10 | height: auto;
11 | overflow: hidden;
12 | background-color: transparent;
13 | //-webkit-mask: -webkit-gradient(linear, 0% 50%, 0% 100%, from(#debb47), to(rgba(36, 142, 36, 0)));
14 | //-webkit-mask: -webkit-linear-gradient(top, #debb47 50%, rgba(36, 142, 36, 0))
15 | }
16 |
17 | .area_roll > div:nth-child(3) .area_grid > div {
18 | left: 42%
19 | }
--------------------------------------------------------------------------------
/src/areaGear.scss:
--------------------------------------------------------------------------------
1 | .area_roll_mask {
2 | //-webkit-mask: -webkit-gradient(linear, 0% 40%, 0% 0%, from(#debb47), to(rgba(36, 142, 36, 0)));
3 | //-webkit-mask: -webkit-linear-gradient(bottom, #debb47 50%, rgba(36, 142, 36, 0));
4 | padding: 0
5 | }
6 |
7 | .area_roll {
8 | display: flex;
9 | width: 100%;
10 | height: auto;
11 | overflow: hidden;
12 | background-color: transparent;
13 | //-webkit-mask: -webkit-gradient(linear, 0% 50%, 0% 100%, from(#debb47), to(rgba(36, 142, 36, 0)));
14 | //-webkit-mask: -webkit-linear-gradient(top, #debb47 50%, rgba(36, 142, 36, 0))
15 | }
16 |
17 | .area_roll > div:nth-child(3) .area_grid > div {
18 | left: 42%
19 | }
--------------------------------------------------------------------------------
/lib/select.scss:
--------------------------------------------------------------------------------
1 | $baseHeight:2rem;
2 | .gear-box {
3 | position: relative;
4 | font-size: 16px;
5 | height: $baseHeight*5;
6 | float: left;
7 | background-color: transparent;
8 | overflow: hidden;
9 | flex: 1
10 | }
11 |
12 | .gear {
13 | position: absolute;
14 | width: 100%;
15 | z-index: 9902;
16 | margin-top: $baseHeight*2;
17 | top:0;
18 | }
19 |
20 | .tooth {
21 | height: $baseHeight;
22 | line-height: $baseHeight;
23 | text-align: center;
24 | display: flex;
25 | line-clamp: 1;
26 | flex-direction: column;
27 | overflow: hidden
28 | }
29 |
30 | .area_grid {
31 | position: relative;
32 | top: $baseHeight*2;
33 | width: 100%;
34 | height: $baseHeight;
35 | margin: 0;
36 | box-sizing: border-box;
37 | z-index: 0;
38 | border-top: 1px solid #abaeb5;
39 | border-bottom: 1px solid #abaeb5
40 | }
--------------------------------------------------------------------------------
/src/select.scss:
--------------------------------------------------------------------------------
1 | $baseHeight:2rem;
2 | .gear-box {
3 | position: relative;
4 | font-size: 16px;
5 | height: $baseHeight*5;
6 | float: left;
7 | background-color: transparent;
8 | overflow: hidden;
9 | flex: 1
10 | }
11 |
12 | .gear {
13 | position: absolute;
14 | width: 100%;
15 | z-index: 9902;
16 | margin-top: $baseHeight*2;
17 | top:0;
18 | }
19 |
20 | .tooth {
21 | height: $baseHeight;
22 | line-height: $baseHeight;
23 | text-align: center;
24 | display: flex;
25 | line-clamp: 1;
26 | flex-direction: column;
27 | overflow: hidden
28 | }
29 |
30 | .area_grid {
31 | position: relative;
32 | top: $baseHeight*2;
33 | width: 100%;
34 | height: $baseHeight;
35 | margin: 0;
36 | box-sizing: border-box;
37 | z-index: 0;
38 | border-top: 1px solid #abaeb5;
39 | border-bottom: 1px solid #abaeb5
40 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/demo/demo.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by chkui on 2017/7/7.
3 | */
4 |
5 | import React from 'react'
6 | import {render} from 'react-dom'
7 |
8 | import RAreas from '../index'
9 | import './demo.scss'
10 |
11 | class Demo extends React.Component{
12 | constructor(...props){
13 | super(...props)
14 | this.state = {
15 | show:false,
16 | area:''
17 | }
18 | this.popHandle = this.popHandle.bind(this)
19 | this.cancelHandle = this.cancelHandle.bind(this)
20 | this.commitHandle = this.commitHandle.bind(this)
21 | }
22 |
23 | popHandle(){
24 | this.setState({
25 | show:true
26 | })
27 | }
28 |
29 | cancelHandle(){
30 | this.setState({
31 | show:false
32 | })
33 | }
34 |
35 | commitHandle(data){
36 | this.setState({
37 | show:false,
38 | area:`${data.province.name}${data.city.name}${data.area.name}`
39 | })
40 | }
41 |
42 | render(){
43 | return(
44 |
45 |
46 |
选择结果:
47 |
{this.state.area}
48 | {this.state.show && (
)}
49 |
50 | )
51 | }
52 | }
53 |
54 | render(, document.getElementById('root'))
55 |
56 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rareas",
3 | "version": "1.0.9",
4 | "description": "area select input for mobile",
5 | "main": "index.js",
6 | "scripts": {
7 | "demo": "webpack-dev-server --progress --colors --inline --config ./demo/webpack",
8 | "babel": "grunt && babel ./src/ -d ./lib/",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/palmg/RAreaS.git"
14 | },
15 | "keywords": [
16 | "react",
17 | "select input",
18 | "area select"
19 | ],
20 | "author": "chkui",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/palmg/RAreaS/issues"
24 | },
25 | "homepage": "https://github.com/palmg/RAreaS#readme",
26 | "dependencies": {
27 | "autoprefixer": "^7.1.1",
28 | "babel-core": "^6.24.1",
29 | "babel-loader": "^6.2.7",
30 | "babel-preset-es2015": "^6.18.0",
31 | "babel-preset-react": "^6.16.0",
32 | "classnames": "^2.2.5",
33 | "css-loader": "^0.26.0",
34 | "node-sass": "^4.1.1",
35 | "postcss-loader": "^1.2.1",
36 | "precss": "^1.4.0",
37 | "sass-loader": "^4.0.1",
38 | "style-loader": "^0.13.1"
39 | },
40 | "devDependencies": {
41 | "grunt": "^1.0.1",
42 | "grunt-contrib-copy": "^1.0.0",
43 | "file-loader": "^0.10.1",
44 | "html-loader": "^0.4.4",
45 | "html-webpack-plugin": "^2.24.1",
46 | "react": "^16.1.1",
47 | "react-dom": "^16.1.1",
48 | "url-loader": "^0.5.7",
49 | "webpack": "^2.6.1",
50 | "webpack-dev-server": "^2.4.5"
51 | }
52 | }
--------------------------------------------------------------------------------
/demo/webpack.js:
--------------------------------------------------------------------------------
1 | const path = require('path'),
2 | webpack = require('webpack'),
3 | HtmlWebpackPlugin = require('html-webpack-plugin')
4 |
5 | module.exports = {
6 | devtool: 'source-map',
7 | context: path.resolve(__dirname),
8 | entry: {
9 | bundle: './demo.js',
10 | vendor: ['react','react-dom']
11 | },
12 | output: {
13 | path: path.resolve(__dirname, "./dist"),
14 | filename: '[name].js',
15 | publicPath: '/'
16 | },
17 | module: {
18 | rules: [{
19 | test: /\.js$/,
20 | use: [{
21 | loader: 'babel-loader',
22 | options: { presets: ['es2015', 'react'] }
23 | }],
24 | exclude: /node_modules/
25 | }, {
26 | test: /\.scss$/,
27 | use: [
28 | 'style-loader',
29 | 'css-loader?modules&camelCase&importLoaders=1&localIdentName=[local][hash:base64:8]',
30 | {
31 | loader:'postcss-loader',
32 | options: {
33 | plugins: function() {
34 | return [
35 | require('autoprefixer')()
36 | ];
37 | }
38 | }
39 | },
40 | 'sass-loader'
41 | ]
42 | }, {
43 | test: /\.html$/,
44 | use: ['html-loader?minimize=false']
45 | }]
46 | },
47 | plugins: [
48 | new HtmlWebpackPlugin({
49 | filename: path.resolve(__dirname, './dist/index.html'),
50 | template: path.resolve(__dirname, './index.tpl.html'),
51 | }),
52 | ]
53 | }
54 |
--------------------------------------------------------------------------------
/lib/areas.scss:
--------------------------------------------------------------------------------
1 | .gearArea {
2 | display: block;
3 | position: fixed;
4 | top: 0;
5 | left: 0;
6 | width: 100%;
7 | height: 100%;
8 | z-index: 9900;
9 | overflow: hidden;
10 | animation-fill-mode: both;
11 | background-color: rgba(0, 0, 0, 0.2);
12 | box-sizing: border-box;
13 | font-size: .7rem;
14 | }
15 |
16 | .area_ctrl {
17 | vertical-align: middle;
18 | background-color: #d5d8df;
19 | color: #000;
20 | margin: 0;
21 | height: auto;
22 | width: 100%;
23 | position: fixed;
24 | left: 0;
25 | bottom: 0;
26 | z-index: 9901;
27 | overflow: hidden;
28 | transform: translate3d(0, 0, 0)
29 | }
30 |
31 | .slideInUp {
32 | animation: slideInUp .3s;
33 | }
34 |
35 | @keyframes slideInUp {
36 | from {
37 | transform: translate3d(0, 100%, 0)
38 | }
39 | to {
40 | transform: translate3d(0, 0, 0)
41 | }
42 | }
43 |
44 | .area_btn {
45 | color: #0575f2;
46 | font-size: .8rem;
47 | line-height: 1;
48 | text-align: center;
49 | padding: .8rem 1rem;
50 | }
51 |
52 | .area_btn_box:before,
53 | .area_btn_box:after {
54 | content: '';
55 | position: absolute;
56 | height: 1px;
57 | width: 100%;
58 | display: block;
59 | background-color: #96979b;
60 | z-index: 15;
61 | transform: scaleY(0.33)
62 | }
63 |
64 | .area_btn_box {
65 | display: flex;
66 | justify-content: space-between;
67 | align-items: stretch;
68 | background-color: #f1f2f4;
69 | position: relative
70 | }
71 |
72 | .area_btn_box:before {
73 | left: 0;
74 | top: 0;
75 | transform-origin: 50% 20%
76 | }
77 |
78 | .area_btn_box:after {
79 | left: 0;
80 | bottom: 0;
81 | transform-origin: 50% 70%
82 | }
--------------------------------------------------------------------------------
/src/areas.scss:
--------------------------------------------------------------------------------
1 | .gearArea {
2 | display: block;
3 | position: fixed;
4 | top: 0;
5 | left: 0;
6 | width: 100%;
7 | height: 100%;
8 | z-index: 9900;
9 | overflow: hidden;
10 | animation-fill-mode: both;
11 | background-color: rgba(0, 0, 0, 0.2);
12 | box-sizing: border-box;
13 | font-size: .7rem;
14 | }
15 |
16 | .area_ctrl {
17 | vertical-align: middle;
18 | background-color: #d5d8df;
19 | color: #000;
20 | margin: 0;
21 | height: auto;
22 | width: 100%;
23 | position: fixed;
24 | left: 0;
25 | bottom: 0;
26 | z-index: 9901;
27 | overflow: hidden;
28 | transform: translate3d(0, 0, 0)
29 | }
30 |
31 | .slideInUp {
32 | animation: slideInUp .3s;
33 | }
34 |
35 | @keyframes slideInUp {
36 | from {
37 | transform: translate3d(0, 100%, 0)
38 | }
39 | to {
40 | transform: translate3d(0, 0, 0)
41 | }
42 | }
43 |
44 | .area_btn {
45 | color: #0575f2;
46 | font-size: .8rem;
47 | line-height: 1;
48 | text-align: center;
49 | padding: .8rem 1rem;
50 | }
51 |
52 | .area_btn_box:before,
53 | .area_btn_box:after {
54 | content: '';
55 | position: absolute;
56 | height: 1px;
57 | width: 100%;
58 | display: block;
59 | background-color: #96979b;
60 | z-index: 15;
61 | transform: scaleY(0.33)
62 | }
63 |
64 | .area_btn_box {
65 | display: flex;
66 | justify-content: space-between;
67 | align-items: stretch;
68 | background-color: #f1f2f4;
69 | position: relative
70 | }
71 |
72 | .area_btn_box:before {
73 | left: 0;
74 | top: 0;
75 | transform-origin: 50% 20%
76 | }
77 |
78 | .area_btn_box:after {
79 | left: 0;
80 | bottom: 0;
81 | transform-origin: 50% 70%
82 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | React实现的移动端区域选择工具。
2 | ## 安装
3 | `npm install rareas`
4 | ## 使用
5 | 通过React组件的方式使用:
6 | ```JavaScript
7 | import React from 'react'
8 | import {render} from 'react-dom'
9 | import RAreas from 'RAreas' //引入RAreas组件 const RAreas = require('RAreas')
10 | render() //使用
11 | ```
12 | 组件默认提供中国的省市区选项。也可以自行传入区域定义的区域参数。
13 | ## 组件接口
14 | 接口 | 类型 | 说明
15 | ----- | ---- | ---
16 | list | array | 区域定义列表。列表的结构为:
`[{name:'',id:'',child:[{id:'',name:'',child:[{id:'',name:''}]}]}]`。
目前只支持三级结构。**如果不指定的话默认会使用内置的中国省市区列表。**
17 | onCancel | function | 当取消按钮被点击时触发的回调。`(event)=>{}`
18 | onCommit | function | 当确定按钮被点击时触发的回调。`(data,event)=>{}`。
`data`参数表示当前所选的地域。其结构为:`{province: {id: name:}city: {id: name:}area: {id: name:}}`。对应传入的数据。
19 | ## 打包说明
20 | 组件使用es6和scss开发,所以需要引入babel和node-sass打包。相关包已经通过*package.json*的`dependencies`参数引入。只要对webpack进行以下配置即可。
21 | ```JavaScript
22 | module: {
23 | rules: [{
24 | test: /\.js(x)$/,
25 | use: [{
26 | loader: 'babel-loader',
27 | options: { presets: ['es2015', 'react'] } //处理es6和react
28 | }],
29 | exclude: /node_modules/
30 | }, {
31 | test: /\.scss$/, //处理scss或sass样式。
32 | use: [
33 | 'style-loader',
34 | 'css-loader',
35 | {//postcss用于为样式自动添加浏览器头(例如:-webkit),如无需要,可以移除
36 | loader:'postcss-loader',
37 | options: {
38 | plugins: function() {
39 | return [
40 | require('autoprefixer')()
41 | ];
42 | }
43 | }
44 | },
45 | 'sass-loader' //sass加载器
46 | ]
47 | }
48 | //your rules
49 | ]
50 | }
51 | ```
52 | 如果nodejs的版本过低(7.2以下)安装最新的sass会报错,请单独使用cnpm安装node-sass:
53 | ```bash
54 | $ npm rm node-sass
55 | $ cnpm install node-sass
56 | ```
57 |
58 | **本项目的默认地域数据来自LArea开源项目:https://github.com/xfhxbb/LArea。感谢他们的辛勤工作。**
--------------------------------------------------------------------------------
/src/areas.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by chkui on 2017/7/7.
3 | */
4 |
5 | import React from 'react'
6 | import cnTool from 'classnames/bind'
7 | import Gear from './select'
8 | import styles from './areas.scss'
9 | import areaData from './areaData'
10 | import AreaGear from './areaGear'
11 | const cn = cnTool.bind(styles)
12 |
13 | /**
14 | * 移动端——三级区域选择。
15 | * @param {array} list 传入的区域列表.结构为:
16 | * [{
17 | * name:'',省
18 | * id:'',
19 | * child:[{
20 | * id:'',
21 | * name:'',市
22 | * child:[{
23 | * 区
24 | * }]
25 | * }]
26 | * }]
27 | * @param {function} onCancel (e)=>{} 取消按钮事件
28 | * @param {function} onCommit (data,e)=>{}确定按钮事件,data的结构为:
29 | * {
30 | * province: {id: name:}
31 | * city: {id: name:}
32 | * area: {id: name:}
33 | * }
34 | */
35 | class Areas extends React.Component {
36 | constructor(...props) {
37 | super(...props)
38 | this.deepClone = this.deepClone.bind(this)
39 | this.submitHandle = this.submitHandle.bind(this)
40 | }
41 |
42 | submitHandle(e) {
43 | const { onCommit } = this.props
44 | if (onCommit) {
45 | const address = this.deepClone(this.gear.data)
46 | delete address.province.child
47 | delete address.city.child
48 | onCommit(address)
49 | }
50 | }
51 |
52 | deepClone(currobj) {
53 | let newobj = {}
54 | for (var i in currobj) newobj[i] = typeof currobj[i] === 'object' ? this.deepClone(currobj[i]) : currobj[i]
55 | return newobj
56 | }
57 |
58 | render() {
59 | return (
60 |
61 |
65 |
this.gear = ref} list={this.props.list || areaData} />
66 |
67 | )
68 | }
69 | }
70 |
71 | export default Areas
72 |
--------------------------------------------------------------------------------
/src/areaGear.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by chkui on 2017/7/10.
3 | */
4 |
5 | import React from 'react'
6 | import cnTool from 'classnames/bind'
7 | import Select from './select'
8 | import styles from './areaGear.scss'
9 | const cn = cnTool.bind(styles)
10 |
11 | /**
12 | * 移动端——三级区域选择。
13 | * @param {array} list 传入的区域列表。结构分为省市区三级:
14 | * [{
15 | * name:'',省
16 | * id:'',
17 | * child:[{
18 | * id:'',
19 | * name:'',市
20 | * child:[{
21 | * 区
22 | * }]
23 | * }]
24 | * }]
25 | * 可以通过ref的this.data获取数据,结构为:
26 | * {
27 | * province: {id: name:}
28 | * city: {id: name:}
29 | * area: {id: name:}
30 | * }
31 | */
32 | class areaGear extends React.Component {
33 | constructor(...props) {
34 | super(...props)
35 | const provinceList = this.props.list,
36 | province = provinceList[0],
37 | cityList = province.child || [{ id: 'noneCity', name: '' }],
38 | city = cityList[0],
39 | areaList = city.child || [{ id: 'noneArea', name: '' }]
40 | this.state = {
41 | province: provinceList,
42 | city: cityList,
43 | area: areaList
44 | }
45 | this.data = {
46 | province: province,
47 | city: city,
48 | area: areaList[0]
49 | }
50 | this.provinceHandle = this.provinceHandle.bind(this)
51 | this.cityHandle = this.cityHandle.bind(this)
52 | this.areaHandle = this.areaHandle.bind(this)
53 | }
54 |
55 | //type=['province'|'city'|'area']
56 | buildData(self, type) {
57 | let { province, city, area } = this.state
58 | switch (type) {
59 | case 'province':
60 | city = self.child || [{ id: 'noneCity', name: '' }]
61 | area = city[0].child || [{ id: 'noneArea', name: '' }]
62 | this.data = {
63 | province: self,
64 | city: city[0],
65 | area: area[0]
66 | }
67 | break
68 | case 'city':
69 | area = self.child || [{ id: 'noneArea', name: '' }]
70 | this.data.city = self
71 | this.data.area = area[0]
72 | break
73 | case 'area':
74 | this.data.area = self
75 | break
76 |
77 | }
78 | this.setState({
79 | province: province,
80 | city: city,
81 | area: area
82 | })
83 | }
84 |
85 | provinceHandle(self) {
86 | this.buildData(self, 'province')
87 | }
88 |
89 | cityHandle(self) {
90 | this.buildData(self, 'city')
91 | }
92 |
93 | areaHandle(self) {
94 | this.buildData(self, 'area')
95 | }
96 |
97 | render() {
98 | const { province, city, area } = this.state
99 | return (
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | )
108 | }
109 | }
110 |
111 | export default areaGear
--------------------------------------------------------------------------------
/src/select.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by chkui on 2017/7/7.
3 | */
4 |
5 | import React from 'react'
6 | import cnTool from 'classnames/bind'
7 | import styles from './select.scss'
8 | const cn = cnTool.bind(styles)
9 |
10 | /**
11 | * 独立的下拉选择框。只有在this.props.list发生数据变更时,才会变更(非突变数据)
12 | * @param {string} id 标记当前组件的id,执行onSelect时会传回
13 | * @param {array} list 数据列表。组件只显示第一层,选定之后会返回对应的对象,结构如下:
14 | * [{
15 | * name:'',
16 | * *id:'',
17 | * *child:[{
18 | * id:'',
19 | * name:'',
20 | * child:[{
21 | * }]*id和*child属于扩展属性,如果需要还可以传入更多的熟悉,组件只负责显示name的内容
22 | * }]
23 | * }]
24 | * @param {function} onSelect (object)=>{} 选定值之后的回调函数,object为传入的列表对象
25 | */
26 | class Select extends React.Component {
27 | constructor(...props) {
28 | super(...props)
29 | //this.dom //当前容器的真实Dom对象,通过ref获取
30 | //this.timer //时间计时器
31 | //this.startY //开始touch事件时,拉动框的触屏事件位置
32 | //this.startTop //开始touch事件时,拉动框的最大高度
33 | //this.moveY //touchMove的上下移动偏移量
34 | //this.moveTop //touchMove操作之后的top位置
35 | this.length = -this.props.list.length || 0//传入下拉菜单长度
36 | this.touchStartHandle = this.touchStartHandle.bind(this)
37 | this.touchMoveHandle = this.touchMoveHandle.bind(this)
38 | this.touchEndHandle = this.touchEndHandle.bind(this)
39 | }
40 |
41 | shouldComponentUpdate(nextProps, nextState) {
42 | const modify = nextProps.list !== this.props.list
43 | modify && (() => {
44 | this.dom.style["top"] = '0rem'
45 | this.length = -nextProps.list.length || 0//传入下拉菜单长度
46 | })()
47 | return nextProps.list !== this.props.list
48 | }
49 |
50 | touchStartHandle(e) {
51 | clearTimeout(this.timer)
52 | const dom = this.dom, top = dom.style['top'];
53 | this.startY = e.targetTouches[0].screenY;
54 | if (top) {
55 | this.startTop = parseFloat(top.replace(/rem/g, ''));
56 | } else {
57 | this.startTop = 0;
58 | }
59 | dom.style.transitionDuration = '0ms';
60 | }
61 |
62 | touchMoveHandle(e) {
63 | this.moveY = e.targetTouches[0].screenY
64 | this.moveTop = this.startTop + (this.moveY - this.startY) * 30 / window.innerHeight
65 | this.dom.style['top'] = `${this.moveTop}rem`
66 | }
67 |
68 | touchEndHandle(e) {
69 | const { dom, length, onSelect, props } = this, _this = this
70 | let moveTop = this.moveTop;
71 | if (!moveTop) return;
72 | 0 < moveTop ? (moveTop = 0) : length * 2 + 1 > moveTop && (moveTop = (length + 1) * 2)
73 | const item = Math.round(moveTop / 2)
74 | dom.style["top"] = `${item * 2}rem`
75 | props.onSelect && (
76 | this.timer = setTimeout(() => {
77 | props.onSelect(props.list[Math.abs(item)])
78 | }), 200)
79 | }
80 |
81 | render() {
82 | const list = this.props.list || []
83 | return (
84 |
85 |
this.dom = dom}>
90 | {list.map(i =>
{i.name}
)}
91 |
92 |
93 |
94 | )
95 | }
96 | }
97 |
98 | export default Select
--------------------------------------------------------------------------------
/lib/areas.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
8 |
9 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
10 |
11 | var _react = require('react');
12 |
13 | var _react2 = _interopRequireDefault(_react);
14 |
15 | var _bind = require('classnames/bind');
16 |
17 | var _bind2 = _interopRequireDefault(_bind);
18 |
19 | var _select = require('./select');
20 |
21 | var _select2 = _interopRequireDefault(_select);
22 |
23 | var _areas = require('./areas.scss');
24 |
25 | var _areas2 = _interopRequireDefault(_areas);
26 |
27 | var _areaData = require('./areaData');
28 |
29 | var _areaData2 = _interopRequireDefault(_areaData);
30 |
31 | var _areaGear = require('./areaGear');
32 |
33 | var _areaGear2 = _interopRequireDefault(_areaGear);
34 |
35 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
36 |
37 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
38 |
39 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
40 |
41 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
42 | * Created by chkui on 2017/7/7.
43 | */
44 |
45 | var cn = _bind2.default.bind(_areas2.default);
46 |
47 | /**
48 | * 移动端——三级区域选择。
49 | * @param {array} list 传入的区域列表.结构为:
50 | * [{
51 | * name:'',省
52 | * id:'',
53 | * child:[{
54 | * id:'',
55 | * name:'',市
56 | * child:[{
57 | * 区
58 | * }]
59 | * }]
60 | * }]
61 | * @param {function} onCancel (e)=>{} 取消按钮事件
62 | * @param {function} onCommit (data,e)=>{}确定按钮事件,data的结构为:
63 | * {
64 | * province: {id: name:}
65 | * city: {id: name:}
66 | * area: {id: name:}
67 | * }
68 | */
69 |
70 | var Areas = function (_React$Component) {
71 | _inherits(Areas, _React$Component);
72 |
73 | function Areas() {
74 | var _ref;
75 |
76 | _classCallCheck(this, Areas);
77 |
78 | for (var _len = arguments.length, props = Array(_len), _key = 0; _key < _len; _key++) {
79 | props[_key] = arguments[_key];
80 | }
81 |
82 | var _this = _possibleConstructorReturn(this, (_ref = Areas.__proto__ || Object.getPrototypeOf(Areas)).call.apply(_ref, [this].concat(props)));
83 |
84 | _this.deepClone = _this.deepClone.bind(_this);
85 | _this.submitHandle = _this.submitHandle.bind(_this);
86 | return _this;
87 | }
88 |
89 | _createClass(Areas, [{
90 | key: 'submitHandle',
91 | value: function submitHandle(e) {
92 | var onCommit = this.props.onCommit;
93 |
94 | if (onCommit) {
95 | var address = this.deepClone(this.gear.data);
96 | delete address.province.child;
97 | delete address.city.child;
98 | onCommit(address);
99 | }
100 | }
101 | }, {
102 | key: 'deepClone',
103 | value: function deepClone(currobj) {
104 | var newobj = {};
105 | for (var i in currobj) {
106 | newobj[i] = _typeof(currobj[i]) === 'object' ? this.deepClone(currobj[i]) : currobj[i];
107 | }return newobj;
108 | }
109 | }, {
110 | key: 'render',
111 | value: function render() {
112 | var _this2 = this;
113 |
114 | return _react2.default.createElement(
115 | 'div',
116 | { className: cn("area_ctrl", "slideInUp") },
117 | _react2.default.createElement(
118 | 'div',
119 | { className: cn("area_btn_box") },
120 | _react2.default.createElement(
121 | 'div',
122 | { className: cn("area_btn", "larea_cancel"), onClick: this.props.onCancel },
123 | '\u53D6\u6D88'
124 | ),
125 | _react2.default.createElement(
126 | 'div',
127 | { className: cn("area_btn", "larea_finish"), onClick: this.submitHandle },
128 | '\u786E\u5B9A'
129 | )
130 | ),
131 | _react2.default.createElement(_areaGear2.default, { ref: function ref(_ref2) {
132 | return _this2.gear = _ref2;
133 | }, list: this.props.list || _areaData2.default })
134 | );
135 | }
136 | }]);
137 |
138 | return Areas;
139 | }(_react2.default.Component);
140 |
141 | exports.default = Areas;
--------------------------------------------------------------------------------
/lib/areaGear.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
8 |
9 | var _react = require('react');
10 |
11 | var _react2 = _interopRequireDefault(_react);
12 |
13 | var _bind = require('classnames/bind');
14 |
15 | var _bind2 = _interopRequireDefault(_bind);
16 |
17 | var _select = require('./select');
18 |
19 | var _select2 = _interopRequireDefault(_select);
20 |
21 | var _areaGear = require('./areaGear.scss');
22 |
23 | var _areaGear2 = _interopRequireDefault(_areaGear);
24 |
25 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
26 |
27 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
28 |
29 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
30 |
31 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
32 | * Created by chkui on 2017/7/10.
33 | */
34 |
35 | var cn = _bind2.default.bind(_areaGear2.default);
36 |
37 | /**
38 | * 移动端——三级区域选择。
39 | * @param {array} list 传入的区域列表。结构分为省市区三级:
40 | * [{
41 | * name:'',省
42 | * id:'',
43 | * child:[{
44 | * id:'',
45 | * name:'',市
46 | * child:[{
47 | * 区
48 | * }]
49 | * }]
50 | * }]
51 | * 可以通过ref的this.data获取数据,结构为:
52 | * {
53 | * province: {id: name:}
54 | * city: {id: name:}
55 | * area: {id: name:}
56 | * }
57 | */
58 |
59 | var areaGear = function (_React$Component) {
60 | _inherits(areaGear, _React$Component);
61 |
62 | function areaGear() {
63 | var _ref;
64 |
65 | _classCallCheck(this, areaGear);
66 |
67 | for (var _len = arguments.length, props = Array(_len), _key = 0; _key < _len; _key++) {
68 | props[_key] = arguments[_key];
69 | }
70 |
71 | var _this = _possibleConstructorReturn(this, (_ref = areaGear.__proto__ || Object.getPrototypeOf(areaGear)).call.apply(_ref, [this].concat(props)));
72 |
73 | var provinceList = _this.props.list,
74 | province = provinceList[0],
75 | cityList = province.child || [{ id: 'noneCity', name: '' }],
76 | city = cityList[0],
77 | areaList = city.child || [{ id: 'noneArea', name: '' }];
78 | _this.state = {
79 | province: provinceList,
80 | city: cityList,
81 | area: areaList
82 | };
83 | _this.data = {
84 | province: province,
85 | city: city,
86 | area: areaList[0]
87 | };
88 | _this.provinceHandle = _this.provinceHandle.bind(_this);
89 | _this.cityHandle = _this.cityHandle.bind(_this);
90 | _this.areaHandle = _this.areaHandle.bind(_this);
91 | return _this;
92 | }
93 |
94 | //type=['province'|'city'|'area']
95 |
96 |
97 | _createClass(areaGear, [{
98 | key: 'buildData',
99 | value: function buildData(self, type) {
100 | var _state = this.state,
101 | province = _state.province,
102 | city = _state.city,
103 | area = _state.area;
104 |
105 | switch (type) {
106 | case 'province':
107 | city = self.child || [{ id: 'noneCity', name: '' }];
108 | area = city[0].child || [{ id: 'noneArea', name: '' }];
109 | this.data = {
110 | province: self,
111 | city: city[0],
112 | area: area[0]
113 | };
114 | break;
115 | case 'city':
116 | area = self.child || [{ id: 'noneArea', name: '' }];
117 | this.data.city = self;
118 | this.data.area = area[0];
119 | break;
120 | case 'area':
121 | this.data.area = self;
122 | break;
123 |
124 | }
125 | this.setState({
126 | province: province,
127 | city: city,
128 | area: area
129 | });
130 | }
131 | }, {
132 | key: 'provinceHandle',
133 | value: function provinceHandle(self) {
134 | this.buildData(self, 'province');
135 | }
136 | }, {
137 | key: 'cityHandle',
138 | value: function cityHandle(self) {
139 | this.buildData(self, 'city');
140 | }
141 | }, {
142 | key: 'areaHandle',
143 | value: function areaHandle(self) {
144 | this.buildData(self, 'area');
145 | }
146 | }, {
147 | key: 'render',
148 | value: function render() {
149 | var _state2 = this.state,
150 | province = _state2.province,
151 | city = _state2.city,
152 | area = _state2.area;
153 |
154 | return _react2.default.createElement(
155 | 'div',
156 | { className: cn("area_roll_mask") },
157 | _react2.default.createElement(
158 | 'div',
159 | { className: cn("area_roll") },
160 | _react2.default.createElement(_select2.default, { onSelect: this.provinceHandle, list: province }),
161 | _react2.default.createElement(_select2.default, { onSelect: this.cityHandle, list: city }),
162 | _react2.default.createElement(_select2.default, { onSelect: this.areaHandle, list: area })
163 | )
164 | );
165 | }
166 | }]);
167 |
168 | return areaGear;
169 | }(_react2.default.Component);
170 |
171 | exports.default = areaGear;
--------------------------------------------------------------------------------
/lib/select.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
8 |
9 | var _react = require('react');
10 |
11 | var _react2 = _interopRequireDefault(_react);
12 |
13 | var _bind = require('classnames/bind');
14 |
15 | var _bind2 = _interopRequireDefault(_bind);
16 |
17 | var _select = require('./select.scss');
18 |
19 | var _select2 = _interopRequireDefault(_select);
20 |
21 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
22 |
23 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
24 |
25 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
26 |
27 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
28 | * Created by chkui on 2017/7/7.
29 | */
30 |
31 | var cn = _bind2.default.bind(_select2.default);
32 |
33 | /**
34 | * 独立的下拉选择框。只有在this.props.list发生数据变更时,才会变更(非突变数据)
35 | * @param {string} id 标记当前组件的id,执行onSelect时会传回
36 | * @param {array} list 数据列表。组件只显示第一层,选定之后会返回对应的对象,结构如下:
37 | * [{
38 | * name:'',
39 | * *id:'',
40 | * *child:[{
41 | * id:'',
42 | * name:'',
43 | * child:[{
44 | * }]*id和*child属于扩展属性,如果需要还可以传入更多的熟悉,组件只负责显示name的内容
45 | * }]
46 | * }]
47 | * @param {function} onSelect (object)=>{} 选定值之后的回调函数,object为传入的列表对象
48 | */
49 |
50 | var Select = function (_React$Component) {
51 | _inherits(Select, _React$Component);
52 |
53 | function Select() {
54 | var _ref;
55 |
56 | _classCallCheck(this, Select);
57 |
58 | for (var _len = arguments.length, props = Array(_len), _key = 0; _key < _len; _key++) {
59 | props[_key] = arguments[_key];
60 | }
61 |
62 | //this.dom //当前容器的真实Dom对象,通过ref获取
63 | //this.timer //时间计时器
64 | //this.startY //开始touch事件时,拉动框的触屏事件位置
65 | //this.startTop //开始touch事件时,拉动框的最大高度
66 | //this.moveY //touchMove的上下移动偏移量
67 | //this.moveTop //touchMove操作之后的top位置
68 | var _this2 = _possibleConstructorReturn(this, (_ref = Select.__proto__ || Object.getPrototypeOf(Select)).call.apply(_ref, [this].concat(props)));
69 |
70 | _this2.length = -_this2.props.list.length || 0; //传入下拉菜单长度
71 | _this2.touchStartHandle = _this2.touchStartHandle.bind(_this2);
72 | _this2.touchMoveHandle = _this2.touchMoveHandle.bind(_this2);
73 | _this2.touchEndHandle = _this2.touchEndHandle.bind(_this2);
74 | return _this2;
75 | }
76 |
77 | _createClass(Select, [{
78 | key: 'shouldComponentUpdate',
79 | value: function shouldComponentUpdate(nextProps, nextState) {
80 | var _this3 = this;
81 |
82 | var modify = nextProps.list !== this.props.list;
83 | modify && function () {
84 | _this3.dom.style["top"] = '0rem';
85 | _this3.length = -nextProps.list.length || 0; //传入下拉菜单长度
86 | }();
87 | return nextProps.list !== this.props.list;
88 | }
89 | }, {
90 | key: 'touchStartHandle',
91 | value: function touchStartHandle(e) {
92 | clearTimeout(this.timer);
93 | var dom = this.dom,
94 | top = dom.style['top'];
95 | this.startY = e.targetTouches[0].screenY;
96 | if (top) {
97 | this.startTop = parseFloat(top.replace(/rem/g, ''));
98 | } else {
99 | this.startTop = 0;
100 | }
101 | dom.style.transitionDuration = '0ms';
102 | }
103 | }, {
104 | key: 'touchMoveHandle',
105 | value: function touchMoveHandle(e) {
106 | this.moveY = e.targetTouches[0].screenY;
107 | this.moveTop = this.startTop + (this.moveY - this.startY) * 30 / window.innerHeight;
108 | this.dom.style['top'] = this.moveTop + 'rem';
109 | }
110 | }, {
111 | key: 'touchEndHandle',
112 | value: function touchEndHandle(e) {
113 | var dom = this.dom,
114 | length = this.length,
115 | onSelect = this.onSelect,
116 | props = this.props,
117 | _this = this;
118 |
119 | var moveTop = this.moveTop;
120 | if (!moveTop) return;
121 | 0 < moveTop ? moveTop = 0 : length * 2 + 1 > moveTop && (moveTop = (length + 1) * 2);
122 | var item = Math.round(moveTop / 2);
123 | dom.style["top"] = item * 2 + 'rem';
124 | props.onSelect && (this.timer = setTimeout(function () {
125 | props.onSelect(props.list[Math.abs(item)]);
126 | }), 200);
127 | }
128 | }, {
129 | key: 'render',
130 | value: function render() {
131 | var _this4 = this;
132 |
133 | var list = this.props.list || [];
134 | return _react2.default.createElement(
135 | 'div',
136 | { className: cn("gear-box") },
137 | _react2.default.createElement(
138 | 'div',
139 | { className: cn("gear"),
140 | onTouchStart: this.touchStartHandle,
141 | onTouchMove: this.touchMoveHandle,
142 | onTouchEnd: this.touchEndHandle,
143 | ref: function ref(dom) {
144 | return _this4.dom = dom;
145 | } },
146 | list.map(function (i) {
147 | return _react2.default.createElement(
148 | 'div',
149 | { key: i.id, className: cn('tooth') },
150 | i.name
151 | );
152 | })
153 | ),
154 | _react2.default.createElement('div', { className: cn('area_grid') })
155 | );
156 | }
157 | }]);
158 |
159 | return Select;
160 | }(_react2.default.Component);
161 |
162 | exports.default = Select;
--------------------------------------------------------------------------------