├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── build ├── dev-server.js ├── happypack.js ├── webpack.base.config.js ├── webpack.dev.config.js └── webpack.prod.config.js ├── components.json ├── components ├── area-cascader │ ├── cascader │ │ ├── index.js │ │ └── menu.js │ ├── emit.js │ └── index.js └── area-select │ ├── index.js │ └── select │ ├── index.js │ └── option.js ├── config └── index.js ├── demo ├── gh.a09051bf.js ├── gh.a09051bf.js.gz ├── gh.d33fa769.css ├── vendor.54999dc1.js └── vendor.54999dc1.js.gz ├── dist ├── index.css ├── index.js └── lib │ ├── area-cascader.js │ └── area-select.js ├── gh ├── components │ ├── footer │ │ └── index.js │ ├── header │ │ └── index.js │ ├── main │ │ ├── area-text-code.js │ │ ├── area-text.js │ │ ├── basic.js │ │ ├── cas-basic.js │ │ ├── cas-def-value.js │ │ ├── cas-linkage.js │ │ ├── cas-text-code.js │ │ ├── default-value.js │ │ ├── index.js │ │ ├── index.less │ │ └── size.js │ └── start │ │ ├── index.js │ │ └── index.less ├── general │ └── app │ │ ├── index.js │ │ └── index.less └── page │ ├── index.js │ └── reset.less ├── index.html ├── package.json ├── postcss.config.js ├── src ├── index.js ├── index.less └── utils.js ├── tpl.html ├── webpack.build.config.js └── webpack.components.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", { 5 | "targets": { 6 | "browsers": ["last 5 versions", "safari > 8"] 7 | }, 8 | "modules": false, 9 | "useBuiltIns": "entry", 10 | "loose": true 11 | } 12 | ], 13 | "stage-2", 14 | "react" 15 | ], 16 | "plugins": [ 17 | "transform-react-remove-prop-types", 18 | "transform-decorators-legacy", 19 | "transform-class-properties", 20 | "react-hot-loader/babel" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # Matches multiple files with brace expansion notation 12 | # Set default 13 | [*.{js,vue,less}] 14 | charset = utf-8 15 | indent_style = space 16 | indent_size = 4 17 | 18 | # Matches the exact files either package.json or .travis.yml 19 | [{package.json,.travis.yml}] 20 | indent_style = space 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | root: true, 3 | parser: "babel-eslint", 4 | parserOptions: { 5 | ecmaVersion: 7, 6 | sourceType: "module", 7 | allowImportExportEverywhere: false, 8 | ecmaFeatures: { 9 | jsx: true, 10 | modules: true 11 | } 12 | }, 13 | env: { 14 | es6: true, 15 | node: true, 16 | browser: true 17 | }, 18 | extends: [ 19 | "eslint:recommended", 20 | "plugin:import/errors", 21 | "plugin:react/recommended" 22 | ], 23 | rules: { 24 | indent: [2, 4, { "SwitchCase": 1 }], 25 | quotes: [2, "single", { "allowTemplateLiterals": true }], 26 | linebreak-style: [2, "unix"], 27 | semi: [2, "always"], 28 | eqeqeq: [2, "always"], 29 | strict: [2, "global"], 30 | key-spacing: [2, { "afterColon": true }], 31 | no-console: 0, 32 | no-debugger: 0, 33 | no-empty: 0, 34 | no-unused-vars: 0, 35 | no-constant-condition: 0, 36 | no-undef: 0, 37 | import/no-unresolved: 0, 38 | react/jsx-no-target-blank: 0, 39 | react/no-unescaped-entities: 0, 40 | react/prop-types: 0, 41 | react/jsx-no-comment-textnodes: 0, 42 | no-case-declarations: 0, 43 | react/no-find-dom-node: 0 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # nyc test coverage 20 | .nyc_output 21 | 22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 23 | .grunt 24 | 25 | # node-waf configuration 26 | .lock-wscript 27 | 28 | # Compiled binary addons (http://nodejs.org/api/addons.html) 29 | build/Release 30 | 31 | # Dependency directories 32 | node_modules 33 | jspm_packages 34 | 35 | # Optional npm cache directory 36 | .npm 37 | 38 | # Optional REPL history 39 | .node_repl_history 40 | 41 | .idea 42 | public/dist 43 | .DS_Store 44 | .cache 45 | converage 46 | .vscode 47 | reports 48 | .cache 49 | .happypack 50 | package-lock.json -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | .gitignore 3 | .vscode 4 | .npmrc 5 | .babelrc 6 | node_modules 7 | demo 8 | gh 9 | build 10 | config 11 | *.html 12 | *.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Pomy 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![npm-version](https://img.shields.io/npm/v/react-area-linkage.svg) ![license](https://img.shields.io/npm/l/react-area-linkage.svg) 2 | # react-area-linkage 3 | 省市区联动选择,组合数据来源:[area-data](https://github.com/dwqs/area-data) 4 | 5 | ## Installation 6 | Install the pkg with npm: 7 | ``` 8 | // v3之前的版本 9 | npm i --save react-area-linkage 10 | 11 | // v3及之后的版本 12 | npm i --save react-area-linkage area-data 13 | ``` 14 | 15 | or yarn 16 | 17 | ``` 18 | // v3之前的版本 19 | yarn add react-area-linkage 20 | 21 | // v3及之后的版本 22 | yarn add react-area-linkage area-data 23 | ``` 24 | 25 | ## Usage 26 | ``` 27 | import React from 'react'; 28 | import ReactDOM from 'react-dom'; 29 | import { pca, pcaa } from 'area-data'; // v3 or higher 30 | import 'react-area-linkage/dist/index.css'; // v2 or higher 31 | import { AreaSelect, AreaCascader } from 'react-area-linkage'; 32 | 33 | // basic 34 | 35 | 36 | 37 | //setting 38 | 39 | 40 | ``` 41 | 42 | More demo to visit [here](https://dwqs.github.io/react-area-linkage/). 43 | 44 | ## On Demand Import 45 | > version >= 2.0.0 46 | 47 | 安装 [babel-plugin-on-demand-import](https://github.com/dwqs/babel-plugin-on-demand-import): 48 | 49 | ``` 50 | npm i babel-plugin-on-demand-import -D 51 | ``` 52 | 53 | 修改 `.babelrc`: 54 | 55 | ``` 56 | { 57 | // ... 58 | "plugins": [ 59 | ["on-demand-import" { 60 | "libraryName": "react-area-linkage", 61 | "libraryPath": "dist/lib" 62 | }] 63 | ] 64 | } 65 | ``` 66 | 67 | ``` 68 | import 'react-area-linkage/dist/index.css'; // v2 or higher 69 | import pcaa from 'area-data/pcaa'; 70 | // Only import AreaCascader component 71 | import { AreaCascader } from 'react-area-linkage'; 72 | 73 | 74 | 75 | // Only import AreaSelect component 76 | import { AreaSelect } from 'react-area-linkage'; 77 | 78 | 79 | ``` 80 | 81 | ## 属性 82 | ### area-select 组件 83 | | 参数 | 类型 | 可选值 | 默认值 | 说明 | 84 | | :--: | :--: | :--: | :--: | :--: | 85 | | type | String | all/code/text | code | 设置返回的数据格式 | 86 | | placeholders | Array | - | [] | 设置 placeholder text | 87 | | level | Number | 0/1/2 | 1 | 设置联动层级(0-只选省份/1-省市联动/2-省市区联动) | 88 | | size | String | small/medium/large | medium | 设置输入框的大小 | 89 | | defaultArea | Array | - | [] | 设置默认值(值类型数据需统一,要么全是文本,要么全是区域代码) | 90 | | onChange | Function | - | - | 选择项改变时的回调 | 91 | | disabled | Boolean | - | false | 是否禁用 | 92 | | data | Object | - | - | 地区数据(v3需要传入) | 93 | 94 | > v2 仅支持省市区联动,即 v2 不再支持 level 的值为 3 95 | 96 | ### area-cascader 组件 97 | | 参数 | 类型 | 可选值 | 默认值 | 说明 | 98 | | :--: | :--: | :--: | :--: | :--: | 99 | | type | String | all/code/text | code | 设置返回的数据格式 | 100 | | placeholder | String | - | '' | 设置 placeholder text | 101 | | level | Number | 0/1 | 0 | 设置联动层级(0-省市联动/1-省市区联动) | 102 | | size | String | small/medium/large | medium | 设置输入框的大小 | 103 | | defaultArea | Array | - | [] | 设置默认值(值类型数据需统一,要么全是文本,要么全是区域代码) | 104 | | onChange | Function | - | - | 选择项改变时的回调 | 105 | | disabled | Boolean | - | false | 是否禁用 | 106 | | separator | String | - | '/' | 显示选中文本的分隔符 | 107 | | data | Object | - | - | 地区数据(v3需要传入) | 108 | 109 | ## Related Project 110 | * [vue-area-linkage](https://github.com/dwqs/vue-area-linkage/) 111 | 112 | ## License 113 | MIT. 114 | -------------------------------------------------------------------------------- /build/dev-server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by pomy on 20/07/2017. 3 | */ 4 | 5 | let webpack = require('webpack'); 6 | let WebpackDevServer = require('webpack-dev-server'); 7 | let ora = require('ora'); 8 | let gutil = require('gulp-util'); 9 | 10 | let webpackDevConfig = require('./webpack.dev.config.js'); 11 | let config = require('../config/index'); 12 | 13 | let compiler = webpack(webpackDevConfig); 14 | let server = new WebpackDevServer(compiler, webpackDevConfig.devServer); 15 | 16 | const url = `localhost:${config.dev.port}/`; 17 | 18 | let spinner = ora({ 19 | text: 'Webpack 正在编译...\n', 20 | color: 'green' 21 | }).start(); 22 | 23 | function compiledFail(){ 24 | if(spinner){ 25 | spinner.color = 'red'; 26 | spinner.text = gutil.colors.white('Webpack 编译失败: \n'); 27 | spinner.fail(); 28 | spinner = null; 29 | } 30 | } 31 | 32 | server.listen(config.dev.port, 'localhost', (err) => { 33 | if(err){ 34 | compiledFail(); 35 | throw new gutil.PluginError('[webpack-dev-server err]', err); 36 | } 37 | }); 38 | 39 | //编译完成 40 | compiler.plugin('done', (stats) => { 41 | if(spinner){ 42 | spinner.text = gutil.colors.green(`Webpack 编译成功, open browser to visit ${url}\n`); 43 | spinner.succeed(); 44 | spinner = null; 45 | } 46 | }); 47 | 48 | //编译失败 49 | compiler.plugin('failed', (err) => { 50 | compiledFail(); 51 | throw new gutil.PluginError('[webpack build err]', err); 52 | }); 53 | 54 | //监听文件修改 55 | compiler.plugin('compilation', compilation => {}); 56 | -------------------------------------------------------------------------------- /build/happypack.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by pomy on 20/07/2017. 3 | */ 4 | 5 | let HappyPack = require('happypack'); 6 | let os = require('os'); 7 | let happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length }); 8 | 9 | module.exports = function (opts) { 10 | return { 11 | id: opts.id, 12 | threadPool: happyThreadPool, 13 | verbose: true, 14 | loaders: opts.loaders, 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /build/webpack.base.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by pomy on 20/07/2017. 3 | */ 4 | 5 | let path = require('path'); 6 | let webpack = require('webpack'); 7 | let HtmlWebpackPlugin = require('html-webpack-plugin'); 8 | let CopyWebpackPlugin = require('copy-webpack-plugin'); 9 | 10 | let config = require('../config'); 11 | 12 | module.exports = { 13 | module: { 14 | rules: [] 15 | }, 16 | 17 | resolve: { 18 | extensions: ['.js', '.jsx'], 19 | modules: [path.join(__dirname, '../node_modules')], 20 | alias: { 21 | '@src': path.resolve(__dirname, '../src'), 22 | '@gh': path.resolve(__dirname, '../gh'), 23 | '@components': path.resolve(__dirname, '../gh/components') 24 | } 25 | }, 26 | 27 | resolveLoader: { 28 | modules: [path.join(__dirname, '../node_modules')] 29 | }, 30 | 31 | performance: { 32 | hints: false 33 | }, 34 | 35 | plugins: [ 36 | new HtmlWebpackPlugin({ 37 | filename: 'index.html', 38 | template: 'tpl.html', 39 | inject: true, 40 | env: process.env.NODE_ENV, 41 | minify: { 42 | removeComments: true, 43 | collapseWhitespace: true, 44 | removeAttributeQuotes: false 45 | } 46 | }) 47 | ] 48 | }; 49 | -------------------------------------------------------------------------------- /build/webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by pomy on 20/07/2017. 3 | */ 4 | 5 | let path = require('path'); 6 | let webpack = require('webpack'); 7 | let OpenBrowserPlugin = require('open-browser-webpack-plugin'); 8 | let HappyPack = require('happypack'); 9 | 10 | let getHappyPackConfig = require('./happypack'); 11 | 12 | let devConfig = require('./webpack.base.config'); 13 | let config = require('../config'); 14 | const url = `http://localhost:${config.dev.port}/`; 15 | 16 | devConfig.module.rules.unshift({ 17 | test: /\.jsx?$/, 18 | exclude: /node_modules/, 19 | use: ['happypack/loader?id=js-dev'] 20 | },{ 21 | test: /\.less$/, 22 | use: ['happypack/loader?id=less-dev'] 23 | }, { 24 | test: /\.css$/, 25 | use: ['happypack/loader?id=css-dev'] 26 | }); 27 | 28 | devConfig.plugins = (devConfig.plugins || []).concat([ 29 | new HappyPack(getHappyPackConfig({ 30 | id: 'js-dev', 31 | loaders: ['babel-loader'] 32 | })), 33 | 34 | new webpack.HotModuleReplacementPlugin(), 35 | 36 | new webpack.DefinePlugin({ 37 | 'process.env': { 38 | 'NODE_ENV': JSON.stringify(config.dev.env) 39 | } 40 | }), 41 | 42 | new HappyPack(getHappyPackConfig({ 43 | id: 'less-dev', 44 | loaders: ['style-loader','css-loader', 'postcss-loader', 'less-loader'] 45 | })), 46 | 47 | new HappyPack(getHappyPackConfig({ 48 | id: 'css-dev', 49 | loaders: ['style-loader','css-loader', 'postcss-loader'] 50 | })), 51 | 52 | new webpack.NoEmitOnErrorsPlugin(), 53 | new webpack.NamedModulesPlugin(), 54 | new OpenBrowserPlugin({ url: url }) 55 | ]); 56 | 57 | // see https://webpack.github.io/docs/webpack-dev-server.html 58 | devConfig.devServer = { 59 | hot: true, 60 | noInfo: false, 61 | quiet: false, 62 | port: config.dev.port, 63 | // #https://github.com/webpack/webpack-dev-server/issues/882 64 | disableHostCheck: true, 65 | headers: { 66 | 'Access-Control-Allow-Origin': '*', 67 | 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept' 68 | }, 69 | inline: true, 70 | // 解决开发模式下 在子路由刷新返回 404 的情景 71 | historyApiFallback: { 72 | index: config.dev.assetsPublicPath 73 | }, 74 | stats: { 75 | colors: true, 76 | modules: false 77 | }, 78 | contentBase: config.dev.contentBase, 79 | publicPath: config.dev.assetsPublicPath 80 | }; 81 | 82 | module.exports = Object.assign({},devConfig,{ 83 | entry: { 84 | app: [ 85 | 'react-hot-loader/patch', 86 | 'webpack/hot/dev-server', 87 | `webpack-dev-server/client?http://localhost:${config.dev.port}/`, 88 | path.resolve(__dirname, '../gh/page/index.js') 89 | ] 90 | }, 91 | output: { 92 | filename: '[name].js', 93 | path: config.dev.assetsRoot, 94 | publicPath: config.dev.assetsPublicPath, 95 | sourceMapFilename: '[file].map', 96 | chunkFilename: '[name].js' 97 | }, 98 | devtool: 'source-map' 99 | }); 100 | -------------------------------------------------------------------------------- /build/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by pomy on 20/07/2017. 3 | */ 4 | 5 | let path = require('path'); 6 | let webpack = require('webpack'); 7 | let ExtractTextPlugin = require('extract-text-webpack-plugin'); 8 | let OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin'); 9 | let ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin'); 10 | let CleanWebpackPlugin = require('clean-webpack-plugin'); 11 | let WebpackMd5Hash = require('webpack-md5-hash'); 12 | let os = require('os'); 13 | let CompressionPlugin = require('compression-webpack-plugin'); 14 | let HappyPack = require('happypack'); 15 | 16 | let getHappyPackConfig = require('./happypack'); 17 | 18 | let prodConfig = require('./webpack.base.config'); 19 | let config = require('../config'); 20 | 21 | prodConfig.module.rules.unshift({ 22 | test: /\.jsx?$/, 23 | exclude: /node_modules/, 24 | use: ['happypack/loader?id=js-prod'] 25 | },{ 26 | test: /\.less$/, 27 | use: ExtractTextPlugin.extract({ 28 | fallback: 'style-loader', 29 | use: ['happypack/loader?id=less-prod'] 30 | }) 31 | }, { 32 | test: /\.css$/, 33 | use: ExtractTextPlugin.extract({ 34 | fallback: 'style-loader', 35 | use: ['happypack/loader?id=css-prod'] 36 | }) 37 | }); 38 | 39 | prodConfig.plugins = (prodConfig.plugins || []).concat([ 40 | new CleanWebpackPlugin(['demo'], { 41 | root: path.join(__dirname, '../'), 42 | verbose: true, 43 | dry: false 44 | }), 45 | 46 | new webpack.DefinePlugin({ 47 | 'process.env': { 48 | 'NODE_ENV': JSON.stringify(config.build.env) 49 | } 50 | }), 51 | 52 | new ExtractTextPlugin({ 53 | filename: '[name].[contenthash:8].css' 54 | }), 55 | 56 | new HappyPack(getHappyPackConfig({ 57 | id: 'js-prod', 58 | loaders: ['babel-loader'] 59 | })), 60 | 61 | new HappyPack(getHappyPackConfig({ 62 | id: 'less-prod', 63 | loaders: ['css-loader', { 64 | path: 'postcss-loader', 65 | query: { 66 | sourceMap: 'inline' 67 | } 68 | }, 'less-loader'] 69 | })), 70 | 71 | new HappyPack(getHappyPackConfig({ 72 | id: 'css-prod', 73 | loaders: ['css-loader', { 74 | path: 'postcss-loader', 75 | query: { 76 | sourceMap: 'inline' 77 | } 78 | }] 79 | })), 80 | 81 | // Compress extracted CSS. We are using this plugin so that possible 82 | // duplicated CSS from different components can be deduped. 83 | new OptimizeCSSPlugin({ 84 | cssProcessorOptions: { 85 | safe: true 86 | } 87 | }), 88 | 89 | new webpack.optimize.CommonsChunkPlugin({ 90 | name: 'vendor', 91 | minChunks: ({resource}) => ( 92 | resource && 93 | resource.indexOf('node_modules') >= 0 && 94 | resource.match(/\.js$/) 95 | ) 96 | }), 97 | 98 | // gzip 99 | new CompressionPlugin({ 100 | asset: '[path].gz[query]', 101 | algorithm: 'gzip', 102 | test: /\.(js|html|less)$/, 103 | threshold: 10240, 104 | minRatio: 0.8 105 | }), 106 | 107 | new ParallelUglifyPlugin({ 108 | workerCount: os.cpus().length, 109 | cacheDir: '.cache/', 110 | sourceMap: true, 111 | uglifyJS: { 112 | compress: { 113 | warnings: false, 114 | drop_debugger: true, 115 | drop_console: true 116 | }, 117 | mangle: true 118 | } 119 | }), 120 | 121 | new webpack.optimize.ModuleConcatenationPlugin(), 122 | new WebpackMd5Hash() 123 | ]); 124 | 125 | module.exports = Object.assign({},prodConfig,{ 126 | entry: { 127 | gh: path.resolve(__dirname, '../gh/page/index.js') 128 | }, 129 | output: { 130 | filename: '[name].[chunkhash:8].js', 131 | path: config.build.assetsRoot, 132 | publicPath: config.build.assetsPublicPath, 133 | sourceMapFilename: '[file].map', 134 | chunkFilename: '[name].[chunkhash:8].js' 135 | } 136 | }); 137 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "area-select": "./components/area-select/index.js", 3 | "area-cascader": "./components/area-cascader/index.js" 4 | } -------------------------------------------------------------------------------- /components/area-cascader/cascader/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import { findDOMNode } from 'react-dom'; 5 | 6 | import CascaderMenu from './menu'; 7 | 8 | import { contains, setPanelPosition } from '@src/utils.js'; 9 | 10 | // import emitter from '../emit'; 11 | 12 | export default class Cascader extends React.Component { 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | top: 32, 17 | shown: false, 18 | label: '', 19 | values: [] 20 | }; 21 | 22 | this.rootDOM = null; 23 | this.areaRect = null; 24 | } 25 | 26 | static displayName = 'Cascader' 27 | 28 | static propTypes = { 29 | options: PropTypes.array.isRequired, 30 | disabled: PropTypes.bool, 31 | placeholder: PropTypes.string, 32 | size: PropTypes.oneOf(['small', 'medium', 'large']), 33 | separator: PropTypes.string, 34 | data: PropTypes.object.isRequired 35 | } 36 | 37 | static defaultProps = { 38 | disabled: false, 39 | placeholder: '请选择', 40 | size: 'medium', 41 | separator: '/' 42 | } 43 | 44 | handleTriggerClick = (e) => { 45 | e.stopPropagation(); 46 | if (this.props.disabled) { 47 | return; 48 | } 49 | 50 | const tmp = this.state.shown; 51 | if (!tmp) { 52 | this.props.emitter.emit('shown'); 53 | } 54 | 55 | this.setState({ 56 | shown: !tmp, 57 | top: this.setPosition(false) 58 | }, () => { 59 | if (!this.state.shown) { 60 | this.props.emitter.emit('doc-click'); 61 | } 62 | }); 63 | } 64 | 65 | setPosition = (isUpdate = true) => { 66 | const panelHeight = parseInt(window.getComputedStyle(this.listWrap, null).getPropertyValue('height')); 67 | if (isUpdate) { 68 | this.setState({ 69 | top: setPanelPosition(panelHeight, this.areaRect) 70 | }); 71 | } else { 72 | return setPanelPosition(panelHeight, this.areaRect); 73 | } 74 | } 75 | 76 | handleDocResize = () => { 77 | this.areaRect = this.rootDOM.getBoundingClientRect(); 78 | this.setPosition(); 79 | } 80 | 81 | handleDocClick = (e) => { 82 | const target = e.target; 83 | if (!contains(this.rootDOM, target) && this.state.shown) { 84 | this.setState({ 85 | shown: false 86 | }, () => { 87 | this.props.emitter.emit('doc-click'); 88 | }); 89 | } 90 | } 91 | 92 | handleSelectedChange = (codes, labels) => { 93 | this.setState({ 94 | shown: false, 95 | label: labels.join(this.props.separator), 96 | values: codes 97 | }); 98 | } 99 | 100 | // 设置默认值 101 | setDefValues = (codes, labels) => { 102 | this.setState({ 103 | label: labels.join(this.props.separator), 104 | values: codes 105 | }); 106 | } 107 | 108 | setWrapRef = (node) => { 109 | this.listWrap = node; 110 | } 111 | 112 | render () { 113 | const { data, placeholder, disabled, size, children, options, ...rest } = this.props; 114 | const { shown, top, label, values } = this.state; 115 | 116 | const classes = classNames('area-select', { 117 | 'medium': size === 'medium', 118 | 'small': size === 'small', 119 | 'large': size === 'large', 120 | 'is-disabled': disabled 121 | }); 122 | 123 | return ( 124 |
125 | {label ? label : placeholder} 126 | 127 |
130 | 131 |
132 |
133 | ); 134 | } 135 | 136 | componentDidMount () { 137 | this.rootDOM = findDOMNode(this); 138 | this.areaRect = this.rootDOM.getBoundingClientRect(); 139 | 140 | window.document.addEventListener('scroll', this.handleDocResize, false); 141 | window.document.addEventListener('click', this.handleDocClick, false); 142 | window.addEventListener('resize', this.handleDocResize, false); 143 | 144 | // some emit 145 | this.props.emitter.on('selected', this.handleSelectedChange); 146 | this.props.emitter.on('set-def-values', this.setDefValues); 147 | } 148 | 149 | componentWillUnmount() { 150 | window.document.removeEventListener('scroll', this.handleDocResize, false); 151 | window.document.removeEventListener('click', this.handleDocClick, false); 152 | window.removeEventListener('resize', this.handleDocResize, false); 153 | 154 | this.rootDOM = null; 155 | this.areaRect = null; 156 | } 157 | } -------------------------------------------------------------------------------- /components/area-cascader/cascader/menu.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import arrayTreeFilter from 'array-tree-filter'; 5 | import { findDOMNode } from 'react-dom'; 6 | 7 | import { isArray, scrollIntoView } from '@src/utils'; 8 | 9 | export default class CascaderMenu extends React.Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | activeValues: props.values, 14 | labels: [] 15 | }; 16 | this.menuItems = {}; 17 | } 18 | 19 | static displayName = 'CascaderMenu' 20 | 21 | static propTypes = { 22 | data: PropTypes.array, 23 | values: PropTypes.array, 24 | area: PropTypes.object.isRequired 25 | } 26 | 27 | static defaultProps = { 28 | data: [], 29 | values: [] 30 | } 31 | 32 | handleMenuItemClick = (option, menuIndex) => { 33 | return (e) => { 34 | e.stopPropagation(); 35 | const { label, value, children } = option; 36 | let { activeValues, labels } = this.state; 37 | 38 | activeValues = activeValues.slice(0, menuIndex + 1); 39 | activeValues[menuIndex] = value; 40 | labels = labels.slice(0, menuIndex + 1); 41 | labels[menuIndex] = label; 42 | 43 | this.setState({ 44 | activeValues: activeValues, 45 | labels: labels 46 | }); 47 | 48 | if (!children) { 49 | this.props.emitter.emit('selected', activeValues, labels); 50 | } 51 | }; 52 | } 53 | 54 | getOption = (option, menuIndex) => { 55 | const { activeValues, values } = this.state; 56 | return ( 57 |
  • 61 | {option.label} 62 |
  • 63 | ); 64 | } 65 | 66 | getActiveOptions = () => { 67 | const { data } = this.props; 68 | const activeValues = this.state.activeValues; 69 | 70 | return arrayTreeFilter(data, (o, level) => o.value === activeValues[level]); 71 | } 72 | 73 | getShowOptions = () => { 74 | const { data } = this.props; 75 | const result = this.getActiveOptions() 76 | .map(activeOption => activeOption.children) 77 | .filter(activeOption => !!activeOption); 78 | 79 | result.unshift(data); 80 | return result; 81 | } 82 | 83 | getActiveLabels = (codes) => { 84 | const provinces = this.props.area['86']; 85 | const citys = this.props.area[codes[0]]; 86 | const l = codes.length; 87 | 88 | if (l < 2) { 89 | return []; 90 | } 91 | 92 | let labels = []; 93 | 94 | if (l === 2) { 95 | labels = [provinces[codes[0]], citys[codes[1]]]; 96 | } else if (l === 3) { 97 | // fix https://github.com/dwqs/vue-area-linkage/issues/7 98 | const areas = this.props.area[codes[1]]; 99 | labels = [provinces[codes[0]], citys[codes[1]], areas ? areas[codes[2]] : citys[codes[2]]]; 100 | } 101 | 102 | return labels; 103 | } 104 | 105 | resetActiveVal = (labels) => { 106 | this.setState({ 107 | activeValues: this.props.values, 108 | labels: this.getActiveLabels(this.props.values) 109 | }); 110 | } 111 | 112 | setDefValues = (codes, labels) => { 113 | this.setState({ 114 | activeValues: codes, 115 | labels: labels 116 | }); 117 | } 118 | 119 | scrollToSelectedOption = () => { 120 | const optionsLength = this.getShowOptions().length; 121 | for (let i = 0; i < optionsLength; i++) { 122 | const itemComponent = this.menuItems[i]; 123 | if (itemComponent) { 124 | const ul = findDOMNode(itemComponent); 125 | const target = ul.querySelector('.selected'); 126 | target && scrollIntoView(ul, target); 127 | } 128 | } 129 | } 130 | 131 | saveMenuRef = index => node => { 132 | this.menuItems[index] = node; 133 | } 134 | 135 | render () { 136 | return ( 137 |
    138 | { 139 | this.getShowOptions().map((options, index) => { 140 | return ( 141 |
      142 | { 143 | options.map(option => this.getOption(option, index)) 144 | } 145 |
    146 | ); 147 | }) 148 | } 149 |
    150 | ); 151 | } 152 | 153 | componentDidMount () { 154 | this.props.emitter.on('doc-click', this.resetActiveVal); 155 | this.props.emitter.on('set-def-values', this.setDefValues); 156 | this.props.emitter.on('shown', this.scrollToSelectedOption); 157 | } 158 | } -------------------------------------------------------------------------------- /components/area-cascader/emit.js: -------------------------------------------------------------------------------- 1 | export default class Emitter { 2 | constructor () { 3 | this.eventsMap = Object.create(null); 4 | } 5 | 6 | on (eventName, cb) { 7 | if (typeof cb !== 'function') { 8 | return; 9 | } 10 | const callbacks = this.eventsMap[eventName] || []; 11 | callbacks.push(cb); 12 | this.eventsMap[eventName] = callbacks; 13 | } 14 | 15 | emit (eventName, ...args) { 16 | const callbacks = this.eventsMap[eventName]; 17 | callbacks && callbacks.forEach(cb => { 18 | cb(...args); 19 | }); 20 | } 21 | 22 | off (eventName, cb) { 23 | if (!cb) { 24 | delete this.eventsMap[eventName]; 25 | } else if (typeof cb === 'function' && this.eventsMap[eventName]) { 26 | const callbacks = this.eventsMap[eventName]; 27 | const index = callbacks.findIndex(callback => callback === cb); 28 | if (index > -1) { 29 | callbacks.splice(index, 1); 30 | this.eventsMap[eventName] = callbacks; 31 | } 32 | } 33 | } 34 | 35 | destroyed () { 36 | this.eventsMap = null; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /components/area-cascader/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | 5 | import find from 'lodash.find'; 6 | 7 | import Cascader from './cascader/index'; 8 | 9 | import { assert, isArray } from '@src/utils'; 10 | 11 | import Emitter from './emit'; 12 | 13 | export default class AreaCascader extends React.Component { 14 | constructor(props) { 15 | super(props); 16 | this.state = { 17 | // 区域数据 18 | provinces: this.props.data['86'], 19 | options: [], 20 | 21 | // 设置默认值的判断 22 | defaults: [], 23 | isCode: false, 24 | isSetDefault: false 25 | }; 26 | // 避免多个 AreaCascader 组件的影响 27 | this.emitter = new Emitter(); 28 | } 29 | 30 | static displayName = 'AreaCascader' 31 | 32 | static propTypes = { 33 | type: PropTypes.oneOf(['code', 'text', 'all']), // 返回类型 34 | placeholder: PropTypes.string, 35 | level: PropTypes.oneOf([0, 1]), // 0->二联 1->三联 36 | size: PropTypes.oneOf(['small', 'medium', 'large']), // 大小 37 | defaultArea: PropTypes.array, // 默认值 38 | onChange: PropTypes.func, 39 | disabled: PropTypes.bool, 40 | separator: PropTypes.string, 41 | data: PropTypes.object.isRequired 42 | } 43 | 44 | static defaultProps = { 45 | type: 'code', 46 | placeholder: '请选择', 47 | level: 0, 48 | size: 'medium', 49 | defaultArea: [], 50 | disabled: false, 51 | separator: '/' 52 | } 53 | 54 | iterate (obj) { 55 | const temp = []; 56 | for (const key in obj) { 57 | temp.push({ 58 | label: obj[key], 59 | value: key 60 | }); 61 | } 62 | return temp; 63 | } 64 | 65 | iterateCities () { 66 | const temp = []; 67 | const provinces = this.iterate(this.props.data['86']); 68 | 69 | for (let i = 0, l = provinces.length; i < l; i++) { 70 | const item = {}; 71 | item['label'] = provinces[i].label; 72 | item['value'] = provinces[i].value; 73 | 74 | item['children'] = this.iterate(this.props.data[provinces[i].value]); 75 | temp.push(item); 76 | } 77 | return temp; 78 | } 79 | 80 | iterateAreas () { 81 | const temp = []; 82 | const cities = this.iterateCities(); 83 | 84 | for (let i = 0, c = cities.length; i < c; i++) { 85 | const city = cities[i]; 86 | for (let j = 0, l = city.children.length; j < l; j++) { 87 | const item = city.children[j]; 88 | const areas = this.iterate(this.props.data[city.children[j].value]); 89 | // fix: https://github.com/dwqs/vue-area-linkage/issues/7 90 | if (areas.length) { 91 | item['children'] = areas; 92 | } else { 93 | item['children'] = [{ 94 | label: item.label, 95 | value: item.value 96 | }]; 97 | } 98 | } 99 | temp.push(city); 100 | } 101 | 102 | return temp; 103 | } 104 | 105 | handleSelectChange = (codes, labels) => { 106 | const { onChange, type } = this.props; 107 | 108 | assert(codes.length === labels.length, '地区数据可能出错了'); 109 | if (this.state.isSetDefault) { 110 | this.emitter.emit('set-def-values', codes, labels); 111 | } 112 | 113 | if (labels[0] === labels[1]) { 114 | // fix #2: 纠正台湾省的 code 返回 115 | codes[1] = codes[0]; 116 | } 117 | 118 | if(typeof onChange === 'function') { 119 | if(type === 'code') { 120 | onChange(codes); 121 | } else if (type === 'text') { 122 | onChange(labels); 123 | } else if (type === 'all') { 124 | onChange(codes.map((code, index) => { 125 | return { 126 | [code]: labels[index] 127 | }; 128 | })); 129 | } 130 | } 131 | } 132 | 133 | beforeSetDefault () { 134 | const { defaultArea } = this.props; 135 | const chinese = /^[\u4E00-\u9FA5\uF900-\uFA2D]{2,}$/; 136 | const num = /^\d{6,}$/; 137 | const isCode = num.test(defaultArea[0]); 138 | 139 | let isValid; 140 | 141 | if (!isCode) { 142 | isValid = defaultArea.every((item) => chinese.test(item)); 143 | } else { 144 | isValid = defaultArea.every((item) => num.test(item)); 145 | } 146 | assert(isValid, '传入的默认值参数有误'); 147 | 148 | // 映射默认值,避免直接更改props 149 | this.setState({ 150 | isCode, 151 | defaults: defaultArea, 152 | isSetDefault: true 153 | }, () => { 154 | this.setDefVal(); 155 | }); 156 | } 157 | 158 | setDefVal () { 159 | const { provinces, defaults, isCode } = this.state; 160 | const { level } = this.props; 161 | 162 | let provinceCode = ''; 163 | let province = ''; 164 | 165 | if (isCode) { 166 | provinceCode = defaults[0]; 167 | province = provinces[provinceCode]; 168 | } else { 169 | province = find(provinces, (item) => item === defaults[0]); 170 | assert(province, `省份 ${defaults[0]} 不存在`); 171 | provinceCode = find(Object.keys(provinces), (item) => provinces[item] === defaults[0]); 172 | } 173 | 174 | const citys = this.props.data[provinceCode]; 175 | 176 | if (!citys) { 177 | assert(citys, `(城市)地区数据出现了错误`); 178 | return; 179 | } 180 | 181 | let curCity = Object.values(citys)[0]; 182 | let curCityCode = Object.keys(citys)[0]; 183 | 184 | if(defaults[1]) { 185 | if(isCode) { 186 | curCityCode = find(Object.keys(citys), (item) => item === defaults[1]); 187 | assert(curCityCode, `城市 ${defaults[1]} 不存在于省份 ${defaults[0]} 中`); 188 | curCity = citys[curCityCode]; 189 | } else { 190 | curCity = find(citys, (item) => item === defaults[1]); 191 | assert(curCity, `城市 ${defaults[1]} 不存在于省份 ${defaults[0]} 中`); 192 | curCityCode = find(Object.keys(citys), (item) => citys[item] === defaults[1]); 193 | } 194 | } 195 | 196 | if (level === 0) { 197 | this.handleSelectChange([provinceCode, curCityCode], [province, curCity]); 198 | } else if (level === 1) { 199 | const areas = this.props.data[curCityCode]; 200 | 201 | if (!areas) { 202 | assert(areas, `(市区)地区数据出现了错误`); 203 | return; 204 | } 205 | 206 | let curArea = Object.values(areas)[0]; 207 | let curAreaCode = Object.keys(areas)[0]; 208 | 209 | if (defaults[2]) { 210 | if(isCode) { 211 | curAreaCode = find(Object.keys(areas), (item) => item === defaults[2]); 212 | assert(curArea, `县区 ${defaults[2]} 不存在于城市 ${defaults[1]} 中`); 213 | curArea = areas[curAreaCode]; 214 | } else { 215 | curArea = find(areas, (item) => item === defaults[2]); 216 | assert(curArea, `县区 ${defaults[2]} 不存在于城市 ${defaults[1]} 中`); 217 | curAreaCode = find(Object.keys(areas), (item) => areas[item] === defaults[2]); 218 | } 219 | } 220 | this.handleSelectChange([provinceCode, curCityCode, curAreaCode], [province, curCity, curArea]); 221 | } else { 222 | assert(false, `设置的默认值和 level 值不匹配`); 223 | } 224 | 225 | 226 | // 还原默认值,避免用户选择出错 227 | this.setState({ 228 | defaults: [] 229 | }); 230 | } 231 | 232 | render () { 233 | const { options } = this.state; 234 | let { size, placeholder, disabled, separator, data } = this.props; 235 | 236 | if(!['large', 'medium', 'small'].includes(size)) { 237 | size = 'medium'; 238 | } 239 | 240 | return ( 241 |
    242 | 250 |
    251 | ); 252 | } 253 | 254 | componentDidMount () { 255 | const { level, defaultArea } = this.props; 256 | 257 | if (level === 0) { 258 | this.setState({ 259 | options: this.iterateCities() 260 | }); 261 | } else if (level === 1) { 262 | this.setState({ 263 | options: this.iterateAreas() 264 | }); 265 | } else { 266 | assert(false, `设置的 level 值只支持 0/1`); 267 | } 268 | 269 | if (isArray(defaultArea) && defaultArea.length === level + 2) { 270 | this.beforeSetDefault(); 271 | } 272 | 273 | if (isArray(defaultArea) && defaultArea.length && defaultArea.length !== level + 2) { 274 | assert(false, `设置的默认值和 level 值不匹配`); 275 | } 276 | 277 | this.emitter.on('selected', this.handleSelectChange); 278 | } 279 | 280 | componentDidUpdate () { 281 | if (!this.props.data || !this.props.data['86']) { 282 | throw new Error('[react-area-linkage]: 需要提供地区数据,格式参考见:https://github.com/dwqs/area-data'); 283 | } 284 | 285 | const { defaultArea, level } = this.props; 286 | const { isSetDefault } = this.state; 287 | 288 | if (!isSetDefault && isArray(defaultArea) && defaultArea.length === level + 2) { 289 | this.beforeSetDefault(); 290 | } 291 | 292 | if (!isSetDefault && isArray(defaultArea) && defaultArea.length && defaultArea.length !== level + 2) { 293 | assert(false, `设置的默认值和 level 值不匹配`); 294 | } 295 | } 296 | 297 | componentWillUnmount () { 298 | this.emitter.destroyed(); 299 | this.emitter = null; 300 | } 301 | } -------------------------------------------------------------------------------- /components/area-select/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import find from 'lodash.find'; 5 | 6 | import Select from './select/index'; 7 | import Option from './select/option'; 8 | 9 | import { assert, isArray } from '@src/utils'; 10 | 11 | export default class AreaSelect extends React.Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = { 15 | // 区域数据 16 | provinces: this.props.data['86'], 17 | citys: {}, 18 | areas: {}, 19 | 20 | // state 21 | curProvince: '', // text 22 | curProvinceCode: '', // code 23 | curCity: '', 24 | curCityCode: '', 25 | curArea: '', 26 | curAreaCode: '', 27 | 28 | // 设置默认值的判断 29 | defaults: [], 30 | isCode: false, 31 | isSetDefault: false 32 | }; 33 | } 34 | 35 | static propTypes = { 36 | type: PropTypes.oneOf(['code', 'text', 'all']), // 返回类型 37 | placeholders: PropTypes.array, 38 | level: PropTypes.oneOf([0, 1, 2]), // 0-->一联 1->二联 2->三联 39 | size: PropTypes.oneOf(['small', 'medium', 'large']), // 大小 40 | defaultArea: PropTypes.array, // 默认值 41 | onChange: PropTypes.func, 42 | disabled: PropTypes.bool, 43 | data: PropTypes.object.isRequired 44 | } 45 | 46 | static defaultProps = { 47 | type: 'code', 48 | placeholders: [], 49 | level: 1, 50 | size: 'medium', 51 | defaultArea: [], 52 | disabled: false 53 | } 54 | 55 | beforeSetDefault () { 56 | const { defaultArea } = this.props; 57 | const chinese = /^[\u4E00-\u9FA5\uF900-\uFA2D]{2,}$/; 58 | const num = /^\d{6,}$/; 59 | const isCode = num.test(defaultArea[0]); 60 | 61 | let isValid; 62 | 63 | if (!isCode) { 64 | isValid = defaultArea.every((item) => chinese.test(item)); 65 | } else { 66 | isValid = defaultArea.every((item) => num.test(item)); 67 | } 68 | assert(isValid, '传入的默认值参数有误'); 69 | 70 | // 映射默认值,避免直接更改props 71 | this.setState({ 72 | isCode, 73 | defaults: defaultArea, 74 | isSetDefault: true 75 | }, () => { 76 | this.setDefVal(); 77 | }); 78 | } 79 | 80 | setDefVal () { 81 | const { provinces, defaults } = this.state; 82 | let provinceCode = ''; 83 | let province = ''; 84 | 85 | if (this.state.isCode) { 86 | provinceCode = defaults[0]; 87 | province = provinces[provinceCode]; 88 | } else { 89 | province = find(provinces, (item) => item === defaults[0]); 90 | assert(province, `省份 ${defaults[0]} 不存在`); 91 | provinceCode = find(Object.keys(provinces), (item) => provinces[item] === defaults[0]); 92 | } 93 | 94 | this.provinceChange(provinceCode, province); 95 | 96 | // 还原默认值,避免用户选择出错 97 | this.setState({ 98 | defaults: [] 99 | }); 100 | } 101 | 102 | getAreaCode () { 103 | const { level } = this.props; 104 | let { curProvince, curProvinceCode, curCity, curCityCode, curArea, curAreaCode } = this.state; 105 | 106 | if (curProvince === curCity) { 107 | // fix #2: 纠正台湾省的 code 返回 108 | curCityCode = curProvinceCode; 109 | } 110 | 111 | let codes = []; 112 | 113 | switch (level) { 114 | case 0: 115 | codes = [curProvinceCode]; 116 | break; 117 | case 1: 118 | codes = [curProvinceCode, curCityCode]; 119 | break; 120 | case 2: 121 | codes = [curProvinceCode, curCityCode, curAreaCode]; 122 | break; 123 | } 124 | 125 | return codes; 126 | } 127 | 128 | getAreaText () { 129 | const { level } = this.props; 130 | const { curProvince, curCity, curArea } = this.state; 131 | 132 | let texts = []; 133 | 134 | switch (level) { 135 | case 0: 136 | texts = [curProvince]; 137 | break; 138 | case 1: 139 | texts = [curProvince, curCity]; 140 | break; 141 | case 2: 142 | texts = [curProvince, curCity, curArea]; 143 | break; 144 | } 145 | 146 | return texts; 147 | } 148 | 149 | getAreaCodeAndText () { 150 | const { level } = this.props; 151 | let { 152 | curProvince, curCity, curArea, 153 | curProvinceCode, curCityCode, curAreaCode 154 | } = this.state; 155 | 156 | if (curProvince === curCity) { 157 | // 纠正台湾省的 code 返回 158 | curCityCode = curProvinceCode; 159 | } 160 | 161 | let textCodes = []; 162 | 163 | switch (level) { 164 | case 0: 165 | textCodes = [{[curProvinceCode]: curProvince}]; 166 | break; 167 | case 1: 168 | textCodes = [{[curProvinceCode]: curProvince}, {[curCityCode]: curCity}]; 169 | break; 170 | case 2: 171 | textCodes = [{[curProvinceCode]: curProvince}, {[curCityCode]: curCity}, {[curAreaCode]: curArea}]; 172 | break; 173 | } 174 | 175 | return textCodes; 176 | } 177 | 178 | provinceChange (code, text) { 179 | const { defaults, isCode } = this.state; 180 | const { level } = this.props; 181 | 182 | if (level === 0) { 183 | this.setState({ 184 | curProvince: text, 185 | curProvinceCode: code 186 | }, this.selectChange); 187 | return; 188 | } 189 | 190 | const citys = this.props.data[code]; 191 | if (!citys) { 192 | assert(citys, `(城市)地区数据出现了错误`); 193 | this.setState({ 194 | citys: { 195 | [code]: text 196 | }, 197 | curCity: text, 198 | curCityCode: code, 199 | }); 200 | return; 201 | } 202 | 203 | let curCity = Object.values(citys)[0]; 204 | let curCityCode = Object.keys(citys)[0]; 205 | 206 | if(defaults[1]) { 207 | if(isCode) { 208 | curCityCode = find(Object.keys(citys), (item) => item === defaults[1]); 209 | assert(curCityCode, `城市 ${defaults[1]} 不存在于省份 ${defaults[0]} 中`); 210 | curCity = citys[curCityCode]; 211 | } else { 212 | curCity = find(citys, (item) => item === defaults[1]); 213 | assert(curCity, `城市 ${defaults[1]} 不存在于省份 ${defaults[0]} 中`); 214 | curCityCode = find(Object.keys(citys), (item) => citys[item] === defaults[1]); 215 | } 216 | } 217 | 218 | if (level === 1) { 219 | this.setState({ 220 | curProvince: text, 221 | curProvinceCode: code, 222 | curCity, 223 | curCityCode, 224 | citys 225 | }, this.selectChange); 226 | } else if (level === 2) { 227 | const areas = this.props.data[curCityCode]; 228 | 229 | if (!areas) { 230 | assert(areas, `(市区)地区数据出现了错误`); 231 | this.setState({ 232 | areas: { 233 | [curCityCode]: curCity 234 | }, 235 | curArea: curCity, 236 | curAreaCode: curCityCode 237 | }); 238 | return; 239 | } 240 | 241 | let curArea = Object.values(areas)[0]; 242 | let curAreaCode = Object.keys(areas)[0]; 243 | 244 | if (defaults[2]) { 245 | if(isCode) { 246 | curAreaCode = find(Object.keys(areas), (item) => item === defaults[2]); 247 | assert(curAreaCode, `县区 ${defaults[2]} 不存在于城市 ${defaults[1]} 中`); 248 | curArea = areas[curAreaCode]; 249 | } else { 250 | curArea = find(areas, (item) => item === defaults[2]); 251 | assert(curArea, `县区 ${defaults[2]} 不存在于城市 ${defaults[1]} 中`); 252 | curAreaCode = find(Object.keys(areas), (item) => areas[item] === defaults[2]); 253 | } 254 | } 255 | 256 | this.setState({ 257 | curProvince: text, 258 | curProvinceCode: code, 259 | curCity, 260 | curCityCode, 261 | curArea, 262 | curAreaCode, 263 | citys, 264 | areas 265 | }, this.selectChange); 266 | } else { 267 | assert(false, `设置的 level 值只支持 0/1/2`); 268 | } 269 | } 270 | 271 | cityChange (code, text) { 272 | const { level } = this.props; 273 | 274 | if (level === 1) { 275 | this.setState({ 276 | curCity: text, 277 | curCityCode: code 278 | }, this.selectChange); 279 | } else if (level === 2) { 280 | const areas = this.props.data[code]; 281 | 282 | if (!areas) { 283 | assert(areas, `(市区)地区数据出现了错误`); 284 | this.setState({ 285 | areas: {} 286 | }); 287 | return; 288 | } 289 | 290 | const curArea = Object.values(areas)[0]; 291 | const curAreaCode = Object.keys(areas)[0]; 292 | 293 | this.setState({ 294 | curCity: text, 295 | curCityCode: code, 296 | areas, 297 | curArea, 298 | curAreaCode, 299 | }, this.selectChange); 300 | } 301 | } 302 | 303 | areaChange (code, text) { 304 | const { level } = this.props; 305 | 306 | this.setState({ 307 | curArea: text, 308 | curAreaCode: code, 309 | }, this.selectChange); 310 | } 311 | 312 | selectChange () { 313 | const { onChange, type } = this.props; 314 | 315 | if(typeof onChange === 'function') { 316 | if(type === 'code') { 317 | onChange(this.getAreaCode()); 318 | } else if (type === 'text') { 319 | onChange(this.getAreaText()); 320 | } else if (type === 'all') { 321 | onChange(this.getAreaCodeAndText()); 322 | } 323 | } 324 | } 325 | 326 | renderSelectComponent (val, level, cb, data) { 327 | const { size, placeholders, ...rest } = this.props; 328 | const { curProvince, curCity, curArea } = this.state; 329 | const label = level === 0 ? curProvince : level === 1 ? curCity : curArea; 330 | 331 | return ( 332 | 348 | ); 349 | } 350 | 351 | render () { 352 | const { provinces, citys, areas, curProvinceCode, curCityCode, curAreaCode } = this.state; 353 | let { size, type, placeholders, level } = this.props; 354 | 355 | if(!['large', 'medium', 'small'].includes(size)) { 356 | size = 'medium'; 357 | } 358 | 359 | if(!['all', 'code', 'text'].includes(type)) { 360 | type = 'code'; 361 | } 362 | 363 | if(![0,1,2].includes(level)) { 364 | level = 1; 365 | } 366 | 367 | if(!isArray(placeholders)) { 368 | placeholders = []; 369 | } 370 | 371 | return ( 372 |
    373 | { this.renderSelectComponent(curProvinceCode, 0, this.provinceChange.bind(this), provinces) } 374 | { 375 | level >= 1 ? this.renderSelectComponent(curCityCode, 1, this.cityChange.bind(this), citys) : null 376 | } 377 | { 378 | level >= 2 ? this.renderSelectComponent(curAreaCode, 2, this.areaChange.bind(this), areas) : null 379 | } 380 |
    381 | ); 382 | } 383 | 384 | componentDidMount () { 385 | if (!this.props.data || !this.props.data['86']) { 386 | throw new Error('[react-area-linkage]: 需要提供地区数据,格式参考见:https://github.com/dwqs/area-data'); 387 | } 388 | const { defaultArea, level } = this.props; 389 | if (isArray(defaultArea) && defaultArea.length === level + 1) { 390 | this.beforeSetDefault(); 391 | } 392 | 393 | if (isArray(defaultArea) && defaultArea.length && defaultArea.length !== level + 1) { 394 | assert(false, `设置的默认值和 level 值不匹配`); 395 | } 396 | } 397 | 398 | componentDidUpdate () { 399 | const { defaultArea, level } = this.props; 400 | const { isSetDefault } = this.state; 401 | 402 | if (!isSetDefault && isArray(defaultArea) && defaultArea.length === level + 1) { 403 | this.beforeSetDefault(); 404 | } 405 | 406 | if (!isSetDefault && isArray(defaultArea) && defaultArea.length && defaultArea.length !== level + 1) { 407 | assert(false, `设置的默认值和 level 值不匹配`); 408 | } 409 | } 410 | } -------------------------------------------------------------------------------- /components/area-select/select/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import { findDOMNode } from 'react-dom'; 5 | 6 | import { contains, scrollIntoView, setPanelPosition } from '@src/utils'; 7 | 8 | export default class Select extends React.Component { 9 | constructor (props) { 10 | super(props); 11 | this.state = { 12 | top: 32, 13 | shown: false 14 | }; 15 | 16 | this.rootDOM = null; 17 | this.areaRect = null; 18 | } 19 | 20 | static propTypes = { 21 | value: PropTypes.any.isRequired, 22 | label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 23 | disabled: PropTypes.bool, 24 | placeholder: PropTypes.string, 25 | size: PropTypes.oneOf(['small', 'medium', 'large']), 26 | onChange: PropTypes.func 27 | } 28 | 29 | static defaultProps = { 30 | disabled: false, 31 | placeholder: '请选择', 32 | size: 'medium', 33 | label: '' 34 | } 35 | 36 | static displayName = 'Select' 37 | 38 | handleTriggerClick = (e) => { 39 | e.stopPropagation(); 40 | if (this.props.disabled) { 41 | return; 42 | } 43 | 44 | this.setState({ 45 | shown: !this.state.shown, 46 | top: this.setPosition(false) 47 | }, this.scrollToSelectedOption); 48 | } 49 | 50 | setPosition = (isUpdate = true) => { 51 | const panelHeight = parseInt(window.getComputedStyle(this.listWrap, null).getPropertyValue('height')); 52 | if (isUpdate) { 53 | this.setState({ 54 | top: setPanelPosition(panelHeight, this.areaRect) 55 | }); 56 | } else { 57 | return setPanelPosition(panelHeight, this.areaRect); 58 | } 59 | } 60 | 61 | handleDocClick = (e) => { 62 | const target = e.target; 63 | if (!contains(this.rootDOM, target) && this.state.shown) { 64 | this.setState({ 65 | shown: false 66 | }); 67 | } 68 | } 69 | 70 | handleDocResize = () => { 71 | this.areaRect = this.rootDOM.getBoundingClientRect(); 72 | this.setPosition(); 73 | } 74 | 75 | handleOptionClick = (option) => { 76 | const { value, label } = option.props; 77 | if (typeof this.props.onChange === 'function') { 78 | this.props.onChange(value, label); 79 | } 80 | 81 | this.setState({ 82 | shown: false 83 | }); 84 | } 85 | 86 | scrollToSelectedOption = () => { 87 | const target = this.listWrap.querySelector('.selected'); 88 | target && scrollIntoView(this.listWrap, target); 89 | } 90 | 91 | setWrapRef = (node) => { 92 | this.listWrap = node; 93 | } 94 | 95 | render () { 96 | const { placeholder, value, disabled, size, children, label } = this.props; 97 | const { shown, top } = this.state; 98 | 99 | const classes = classNames('area-select', { 100 | 'medium': size === 'medium', 101 | 'small': size === 'small', 102 | 'large': size === 'large', 103 | 'is-disabled': disabled 104 | }); 105 | 106 | return ( 107 |
    108 | {label ? label : placeholder} 109 | 110 |
    113 |
      114 | { 115 | React.Children.map(children, el => { 116 | return React.cloneElement(el, Object.assign({}, el.props, { 117 | className: classNames(el.props.className, { 118 | 'selected': el.props.value === value 119 | }), 120 | onClick: this.handleOptionClick.bind(this, el) 121 | })); 122 | }) 123 | } 124 |
    125 |
    126 |
    127 | ); 128 | } 129 | 130 | componentDidMount () { 131 | this.rootDOM = findDOMNode(this); 132 | this.areaRect = this.rootDOM.getBoundingClientRect(); 133 | 134 | window.document.addEventListener('scroll', this.handleDocResize, false); 135 | window.document.addEventListener('click', this.handleDocClick, false); 136 | window.addEventListener('resize', this.handleDocResize, false); 137 | } 138 | 139 | componentWillUnmount() { 140 | window.document.removeEventListener('scroll', this.handleDocResize, false); 141 | window.document.removeEventListener('click', this.handleDocClick, false); 142 | window.removeEventListener('resize', this.handleDocResize, false); 143 | 144 | this.rootDOM = null; 145 | this.areaRect = null; 146 | } 147 | } -------------------------------------------------------------------------------- /components/area-select/select/option.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | 5 | export default class Option extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | hover: false 10 | }; 11 | } 12 | 13 | static propTypes = { 14 | value: PropTypes.any.isRequired, 15 | label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]) 16 | } 17 | 18 | static displayName = 'Option' 19 | 20 | leaveItem = () => { 21 | this.setState({ 22 | hover: false 23 | }); 24 | } 25 | 26 | enterItem = () => { 27 | this.setState({ 28 | hover: true 29 | }); 30 | } 31 | 32 | render () { 33 | const { value, label, selected, className, ...rest } = this.props; 34 | const { hover } = this.state; 35 | 36 | const classes = classNames('area-select-option', className, { 37 | 'hover': hover 38 | }); 39 | 40 | return ( 41 |
  • 45 | {label || value} 46 |
  • 47 | ); 48 | } 49 | } -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | let path = require('path'); 2 | 3 | module.exports = { 4 | dev: { 5 | env: 'development', 6 | assetsRoot: path.resolve(__dirname, '../demo'), 7 | assetsPublicPath: '/', 8 | contentBase: path.resolve(__dirname, '../demo'), 9 | port: 3000 10 | }, 11 | build: { 12 | env: 'production', 13 | assetsRoot: path.resolve(__dirname, '../demo'), 14 | assetsPublicPath: './demo/' 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /demo/gh.a09051bf.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([0],{10:function(e,t){},25:function(e,t){},26:function(e,t){},27:function(e,t){},30:function(e,t){},9:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});n(10);var a=n(0),f=n.n(a),o=n(4),r=n.n(o);n(20),n(25);var l=function(e){function t(){return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,e.call(this))}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(t,e),t.prototype.render=function(){return f.a.createElement("header",null,f.a.createElement("a",{href:"https://github.com/dwqs/react-area-linkage",className:"github-corner","aria-label":"View source on Github"},f.a.createElement("svg",{width:"80",height:"80",viewBox:"0 0 250 250",style:{fill:"#ABD3B4",color:"#fff",position:"absolute",top:0,border:0,right:0},"aria-hidden":"true"},f.a.createElement("path",{d:"M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"}),f.a.createElement("path",{d:"M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2",fill:"currentColor",style:{transformOrigin:"130px 106px"},className:"octo-arm"}),f.a.createElement("path",{d:"M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z",fill:"currentColor",className:"octo-body"}))),f.a.createElement("h2",null,"React Area Linkage: 中国行政区联动选择器"),f.a.createElement("p",null,"省、市、区联动选择"))},t}(a.Component);n(26);var c,s,i=function(e){function t(){return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,e.call(this))}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(t,e),t.prototype.render=function(){return f.a.createElement("div",{className:"start"},f.a.createElement("h3",null,"快速开始"),f.a.createElement("div",{className:"install"},f.a.createElement("h4",null,"安装"),f.a.createElement("pre",null,"npm i --save react-area-linkage"),f.a.createElement("p",null,"或者"),f.a.createElement("pre",null,"yarn add react-area-linkage")),f.a.createElement("div",{className:"register"},f.a.createElement("h4",null,"使用"),f.a.createElement("pre",null,"import React from 'react';",f.a.createElement("br",null),f.a.createElement("br",null),"import ReactDOM from 'react-dom';",f.a.createElement("br",null),f.a.createElement("br",null),"import 'react-area-linkage/dist/index.css';",f.a.createElement("br",null),f.a.createElement("br",null),"import { AreaSelect, AreaCascader } from 'react-area-linkage';",f.a.createElement("br",null),f.a.createElement("br",null),"",f.a.createElement("br",null),f.a.createElement("br",null),"")),f.a.createElement("br",null))},t}(a.Component),u=(n(27),n(1)),p=(n(30),n(2),n(8)),d=n.n(p),h=n(3),m=n.n(h);function y(e,t){if(!(Object.prototype.toString.call(e).includes("Element")&&Object.prototype.toString.call(t).includes("Element")))return!1;for(var n=t;n;){if(n===e)return!0;n=n.parentNode}return!1}function v(e){1")))),this.state.shown&&e&&f.a.createElement("div",{className:"original-code"},f.a.createElement("pre",null,f.a.createElement("code",null,f.a.createElement("span",null,"//设置 placeholders,其值应该和关联层次对应"),f.a.createElement("br",null),f.a.createElement("span",null,"<"),"AreaSelect placeholders={['选择省', '选择市']} onChange={this.handleSelectedChange}/",f.a.createElement("span",null,">")))),f.a.createElement("div",{className:"show-code",onClick:this.toggle},this.state.shown?"Hide Code":"Show Code"))},n}(a.Component);var F=function(e){function n(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,n);var t=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,e.call(this));return t.handleSelectedChange=function(e){t.setState({selected:e})},t.toggle=function(){t.setState({shown:!t.state.shown})},t.state={selected:[],shown:!1},t}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(n,e),n.prototype.render=function(){return f.a.createElement("div",{className:"code-area"},f.a.createElement("div",{className:"area-left"},f.a.createElement(D,{data:u.pca,size:"large",onChange:this.handleSelectedChange})),f.a.createElement("div",{className:"area-right"},f.a.createElement("pre",null,f.a.createElement("code",null,"["+this.state.selected+"]"))),this.state.shown&&f.a.createElement("div",{className:"original-code"},f.a.createElement("pre",null,f.a.createElement("code",null,f.a.createElement("span",null,"//size:['small','default','large'], 默认值是 default"),f.a.createElement("br",null),f.a.createElement("span",null,"<"),"AreaSelect size='large' onChange={this.handleSelectedChange}/",f.a.createElement("span",null,">")))),f.a.createElement("div",{className:"show-code",onClick:this.toggle},this.state.shown?"Hide Code":"Show Code"))},n}(a.Component);var U=function(e){function n(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,n);var t=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,e.call(this));return t.handleSelectedChange=function(e){t.setState({selected:e})},t.toggle=function(){t.setState({shown:!t.state.shown})},t.state={selected:[],shown:!1},t}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(n,e),n.prototype.render=function(){return f.a.createElement("div",{className:"code-area"},f.a.createElement("div",{className:"area-left"},f.a.createElement(D,{type:"text",data:u.pca,onChange:this.handleSelectedChange})),f.a.createElement("div",{className:"area-right"},f.a.createElement("pre",null,f.a.createElement("code",null,"["+this.state.selected+"]"))),this.state.shown&&f.a.createElement("div",{className:"original-code"},f.a.createElement("pre",null,f.a.createElement("code",null,f.a.createElement("span",null,"//type:['text','all','code'], 默认值是 code. 返回值是一个数组,分别是省市的行政区域文本"),f.a.createElement("br",null),f.a.createElement("span",null,"<"),"AreaSelect  type='text' onChange={this.handleSelectedChange}/",f.a.createElement("span",null,">")))),f.a.createElement("div",{className:"show-code",onClick:this.toggle},this.state.shown?"Hide Code":"Show Code"))},n}(a.Component);var J=function(e){function n(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,n);var t=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,e.call(this));return t.handleSelectedChange=function(e){t.setState({selected:e})},t.toggle=function(){t.setState({shown:!t.state.shown})},t.state={selected:[],shown:!1},t}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(n,e),n.prototype.render=function(){return f.a.createElement("div",{className:"code-area"},f.a.createElement("div",{className:"area-left"},f.a.createElement(D,{data:u.pca,type:"all",onChange:this.handleSelectedChange})),f.a.createElement("div",{className:"area-right"},f.a.createElement("pre",null,f.a.createElement("code",null,""+JSON.stringify(this.state.selected)))),this.state.shown&&f.a.createElement("div",{className:"original-code"},f.a.createElement("pre",null,f.a.createElement("code",null,f.a.createElement("span",null,"//返回值是一个数组,数组项是包含省市的行政区域代码和文本的对象"),f.a.createElement("br",null),f.a.createElement("span",null,"<"),"AreaSelect  type='all' onChange={this.handleSelectedChange}/",f.a.createElement("span",null,">")))),f.a.createElement("div",{className:"show-code",onClick:this.toggle},this.state.shown?"Hide Code":"Show Code"))},n}(a.Component);var $=function(e){function n(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,n);var t=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,e.call(this));return t.handleSelectedChange=function(e){t.setState({selected:e})},t.toggle=function(){t.setState({shown:!t.state.shown})},t.state={selected:[],shown:!1},t}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(n,e),n.prototype.render=function(){var e=this.props,t=e.level,n=e.type,a=[],r={};return 0===t?(a=["440000"],r=u.pca):2===t&&(a=["440000","440300","440305"],r=u.pcaa),f.a.createElement("div",{className:"code-area"},f.a.createElement("div",{className:"area-left"},f.a.createElement(D,{data:r,type:n,level:t,defaultArea:a,onChange:this.handleSelectedChange})),f.a.createElement("div",{className:"area-right"},0")))),this.state.shown&&3===t&&f.a.createElement("div",{className:"original-code"},f.a.createElement("pre",null,f.a.createElement("code",null,f.a.createElement("span",null,"//返回值是一个数组,分别是省份行政区域文本"),f.a.createElement("br",null),f.a.createElement("span",null,"<"),"AreaSelect type='text' level={3} defaultArea={['广东省', '深圳市', '南山区', '粤海街道']} onChange={this.handleSelectedChange}/",f.a.createElement("span",null,">")))),this.state.shown&&0===t&&f.a.createElement("div",{className:"original-code"},f.a.createElement("pre",null,f.a.createElement("code",null,f.a.createElement("span",null,"//返回值是一个数组,分别是省份行政区域代码"),f.a.createElement("br",null),f.a.createElement("span",null,"<"),"AreaSelect type='all' level={0} defaultArea={['440000']} onChange={this.handleSelectedChange}/",f.a.createElement("span",null,">")))),f.a.createElement("div",{className:"show-code",onClick:this.toggle},this.state.shown?"Hide Code":"Show Code"))},n}(a.Component);var Z=function(e){function n(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,n);var t=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,e.call(this));return t.handleSelectedChange=function(e){t.setState({selected:e})},t.toggle=function(){t.setState({shown:!t.state.shown})},t.state={selected:[],shown:!1},t}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(n,e),n.prototype.render=function(){return f.a.createElement("div",{className:"code-area"},f.a.createElement("div",{className:"area-left"},f.a.createElement(q,{data:u.pca,onChange:this.handleSelectedChange})),f.a.createElement("div",{className:"area-right"},f.a.createElement("pre",null,f.a.createElement("code",null,"["+this.state.selected+"]"))),this.state.shown&&f.a.createElement("div",{className:"original-code"},f.a.createElement("pre",null,f.a.createElement("code",null,f.a.createElement("span",null,"//返回值是一个数组,分别是省市的行政区域代码"),f.a.createElement("br",null),f.a.createElement("span",null,"<"),"AreaCascader onChange={this.handleSelectedChange}/",f.a.createElement("span",null,">")))),f.a.createElement("div",{className:"show-code",onClick:this.toggle},this.state.shown?"Hide Code":"Show Code"))},n}(a.Component);var G=function(e){function n(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,n);var t=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,e.call(this));return t.handleSelectedChange=function(e){t.setState({selected:e})},t.toggle=function(){t.setState({shown:!t.state.shown})},t.state={selected:[],shown:!1},t}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(n,e),n.prototype.render=function(){return f.a.createElement("div",{className:"code-area"},f.a.createElement("div",{className:"area-left"},f.a.createElement(q,{data:u.pca,type:"all",onChange:this.handleSelectedChange})),f.a.createElement("div",{className:"area-right"},f.a.createElement("pre",null,f.a.createElement("code",null,""+JSON.stringify(this.state.selected)))),this.state.shown&&f.a.createElement("div",{className:"original-code"},f.a.createElement("pre",null,f.a.createElement("code",null,f.a.createElement("span",null,"//返回值是一个数组,数组项是包含省市的行政区域代码和文本的对象"),f.a.createElement("br",null),f.a.createElement("span",null,"<"),"AreaCascader  type='all' onChange={this.handleSelectedChange}/",f.a.createElement("span",null,">")))),f.a.createElement("div",{className:"show-code",onClick:this.toggle},this.state.shown?"Hide Code":"Show Code"))},n}(a.Component);var K=function(e){function n(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,n);var t=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,e.call(this));return t.handleSelectedChange=function(e){t.setState({selected:e})},t.toggle=function(){t.setState({shown:!t.state.shown})},t.state={selected:[],shown:!1},t}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(n,e),n.prototype.render=function(){return f.a.createElement("div",{className:"code-area"},f.a.createElement("div",{className:"area-left"},f.a.createElement(q,{data:u.pcaa,type:"text",level:1,defaultArea:["440000","440300","440305"],onChange:this.handleSelectedChange})),f.a.createElement("div",{className:"area-right"},f.a.createElement("pre",null,f.a.createElement("code",null,"["+this.state.selected+"]"))),this.state.shown&&f.a.createElement("div",{className:"original-code"},f.a.createElement("pre",null,f.a.createElement("code",null,f.a.createElement("span",null,"//绑定默认值defaultArea=['440000','440300','440305'], 代码对应的区域文本是['广东省', '深圳市', '南山区']"),f.a.createElement("br",null),f.a.createElement("span",null,"<"),"AreaCascader type='text' level={1} defaultArea={defaultArea} onChange={this.handleSelectedChange}/",f.a.createElement("span",null,">")))),f.a.createElement("div",{className:"show-code",onClick:this.toggle},this.state.shown?"Hide Code":"Show Code"))},n}(a.Component);var Q=function(e){function n(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,n);var t=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,e.call(this));return t.handleSelectedChange=function(e){t.setState({selected:e})},t.toggle=function(){t.setState({shown:!t.state.shown})},t.state={selected:[],shown:!1},t}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(n,e),n.prototype.render=function(){return f.a.createElement("div",{className:"code-area"},f.a.createElement("div",{className:"area-left"},f.a.createElement(q,{data:u.pcaa,type:"text",level:1,onChange:this.handleSelectedChange})),f.a.createElement("div",{className:"area-right"},f.a.createElement("pre",null,f.a.createElement("code",null,"["+this.state.selected+"]"))),this.state.shown&&f.a.createElement("div",{className:"original-code"},f.a.createElement("pre",null,f.a.createElement("code",null,f.a.createElement("span",null,"//代码对应的区域文本是['广东省', '深圳市', '南山区']"),f.a.createElement("br",null),f.a.createElement("span",null,"<"),"AreaCascader type='text' level={1}  onChange={this.handleSelectedChange}/",f.a.createElement("span",null,">")))),f.a.createElement("div",{className:"show-code",onClick:this.toggle},this.state.shown?"Hide Code":"Show Code"))},n}(a.Component);var X=function(e){function t(){return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,e.call(this))}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(t,e),t.prototype.render=function(){return f.a.createElement("div",{className:"app-main"},f.a.createElement("h3",null,"基本使用"),f.a.createElement("h4",null,"1. 作为选择器"),f.a.createElement("h5",null,"默认形式"),f.a.createElement(B,null),f.a.createElement("h5",null,"改变大小"),f.a.createElement(F,null),f.a.createElement("h5",null,"返回区域文本"),f.a.createElement(U,null),f.a.createElement("h5",null,"返回区域代码和文本"),f.a.createElement(J,null),f.a.createElement("h5",null,"设置 placeholders"),f.a.createElement(B,{placeholders:["选择省","选择市"]}),f.a.createElement("p",{className:"desc"},f.a.createElement("code",null,"placeholders")," 是一个数组, 数组项顺序分别对应省/市/区."),f.a.createElement("h5",null,"设置默认值及省市区联动"),f.a.createElement($,{level:2}),f.a.createElement("p",{className:"desc"},f.a.createElement("code",null,"selected")," 是一个数组, 数组项顺序分别对应省/市/区, 且类型(区域代码/区域文本)必须统一. 以第一个元素类型为基准. 类型不统一将报错."),f.a.createElement("h5",null,"只选省份"),f.a.createElement($,{level:0,type:"all"}),f.a.createElement("h4",null,"2. 作为级联器(只支持2/3级联动)"),f.a.createElement("h5",null,"默认形式"),f.a.createElement(Z,null),f.a.createElement("h5",null,"返回区域文本和代码"),f.a.createElement(G,null),f.a.createElement("p",{className:"desc"},f.a.createElement("code",null,"type")," 表示返回值, 支持:['text', 'code', 'all'], 默认值是 code"),f.a.createElement("h5",null,"设置默认值"),f.a.createElement(K,null),f.a.createElement("h5",null,"省市区联动"),f.a.createElement(Q,null))},t}(a.Component);var Y=function(e){function t(){return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,e.call(this))}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(t,e),t.prototype.render=function(){return f.a.createElement("footer",null,f.a.createElement("p",null,"Vue 版本:",f.a.createElement("a",{target:"_blank",href:"https://github.com/dwqs/vue-area-linkage"},"Vue Area Linkage")),f.a.createElement("p",null,"最新数据来源:",f.a.createElement("a",{href:"https://github.com/dwqs/area-data",target:"_blank"},"省/市/区数据")),f.a.createElement("p",null,"官方数据:",f.a.createElement("a",{href:"http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2016/index.html",target:"_blank"},"国家统计局-城乡划分")))},t}(a.Component);var ee=function(t){function n(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,n);var e=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,t.call(this));return e.onChange=function(e){},e.state={selected:[]},e}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(n,t),n.prototype.render=function(){return f.a.createElement("div",{className:"app-wrap"},f.a.createElement(l,null),f.a.createElement(i,null),f.a.createElement(X,null),f.a.createElement(Y,null))},n.prototype.componentDidMount=function(){},n}(a.Component);window.onload=function(){r.a.render(f.a.createElement(ee,null),document.getElementById("app"))}}},[9]); -------------------------------------------------------------------------------- /demo/gh.a09051bf.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwqs/react-area-linkage/3b34efeabe5d5cf1df591ea87af360e7fc2896e7/demo/gh.a09051bf.js.gz -------------------------------------------------------------------------------- /demo/gh.d33fa769.css: -------------------------------------------------------------------------------- 1 | a,abbr,acronym,address,applet,article,aside,audio,b,big,blockquote,body,canvas,caption,center,cite,code,dd,del,details,dfn,div,dl,dt,em,embed,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,output,p,pre,q,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,tt,u,ul,var,video{margin:0;padding:0;border:0;font-size:100%;outline:0;vertical-align:baseline;background:transparent}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body,html{height:100%;width:100%;font-family:-apple-system,BlinkMacSystemFont,PingFang SC,Helvetica Neue,STHeiti,Microsoft Yahei,Tahoma,Simsun,sans-serif}body{margin:0;padding:0;box-sizing:border-box;font-size:14px;line-height:1;background:none;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none;overflow-x:hidden;-webkit-overflow-scrolling:touch;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;tap-highlight-color:transparent;-webkit-tap-highlight-color:transparent}nav ul,ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:""}table{border-collapse:collapse;border-spacing:0}a{text-decoration:none;margin:0;padding:0;border:0;font-size:100%;vertical-align:baseline;background:transparent;-webkit-appearance:none;-webkit-tap-highlight-color:transparent}:focus{outline:none}ins{text-decoration:none}ins,mark{background-color:#ff9;color:#000}mark{font-style:italic;font-weight:700}del{text-decoration:line-through}abbr[title],dfn[title]{border-bottom:1px dotted #000;cursor:help}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0}input,input[type=password],input[type=text],select,textarea{outline:none;-webkit-appearance:none}input,select{vertical-align:middle}*{box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,PingFang SC,Helvetica Neue,STHeiti,Microsoft Yahei,Tahoma,Simsun,sans-serif;color:inherit}.app-wrap{width:960px;height:200px;margin:0 auto}header{text-align:center;margin-top:100px}h2{font-size:27px;font-weight:400}header p{margin-top:20px;margin-bottom:20px;font-size:16px}header>a{position:fixed;right:0;top:0}header p a{color:#008efd;text-decoration:none;padding-left:5px}footer{margin-top:30px;text-align:center;color:#999}footer a{text-decoration:none;color:#999}footer p{margin-top:15px;cursor:pointer}footer p:hover,footer p:hover a{color:#008efd}.start{margin-top:40px}.start h3{text-align:center;font-size:25px;color:#6289ad}.start h4{font-size:18px;color:#6289ad}.start pre{margin-top:15px;background:#f8f8f8;padding:15px 10px;color:#000;font-size:16px;font-weight:400}.start .install{margin-top:20px}.start .install p{margin-top:20px;margin-bottom:20px}.app-main,.start .register{margin-top:20px}.app-main h3{text-align:center;font-size:25px;color:#6289ad}.app-main h4{margin-top:25px;font-size:18px;color:#6289ad}.app-main h5{margin-top:20px;font-size:16px;color:#6289ad}.app-main .desc{margin-top:15px}.app-main .desc code{background:#f8f8f8;color:#333;padding:5px}.app-main .code-area{position:relative;width:100%;height:auto;border:1px solid #dce7f4;border-radius:4px;margin-top:20px;padding:0}.app-main .code-area>div{margin-top:40px;display:inline-block;vertical-align:middle}.app-main .code-area .area-left{width:75%;margin-left:10px}.app-main .code-area .area-right{width:21%;margin-left:10px}.app-main .code-area .area-right pre{background:#f8f8f8;padding:15px 10px;color:#6289ad;line-height:20px}.app-main .code-area .original-code{width:90%;margin:25px 45px -10px}.app-main .code-area .original-code pre{background:#f8f8f8;padding:15px 10px}.app-main .code-area .show-code{width:100%;cursor:pointer;text-align:center;border-top:1px solid #dce7f4;padding-top:3px;padding-bottom:3px;color:#6289ad;font-weight:500;font-size:18px}.app-main .code-area .show-code:hover{background-color:#dce7f4;color:#fff}.area-zoom-in-top-enter,.area-zoom-in-top-exit-active{opacity:0;transform:scaleY(0)}.area-zoom-in-top-enter-active,.area-zoom-in-top-exit{opacity:1;transform:scaleY(1);transition:all .3s cubic-bezier(.645,.045,.355,1);transform-origin:center top}.area-select-wrap .area-select{margin-left:10px}.area-select-wrap .area-select-empty{padding:4px 0;margin:0;text-align:center;color:#999;font-size:14px}.area-select{position:relative;display:inline-block;vertical-align:top;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;height:32px;cursor:pointer;background:#fff;border-radius:4px;border:1px solid #e1e2e6}.area-select *{box-sizing:border-box}.area-select:hover{border-color:#a1a4ad}.area-select.small{width:126px}.area-select.medium{width:160px}.area-select.large{width:194px}.area-select.is-disabled{background:#eceff5;cursor:not-allowed}.area-select.is-disabled:hover{border-color:#e1e2e6}.area-select.is-disabled .area-selected-trigger{cursor:not-allowed}.area-select .area-selected-trigger{position:relative;display:block;font-size:14px;cursor:pointer;margin:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;height:100%;line-height:100%;padding:8px 20px 7px 12px}.area-select .area-select-icon{position:absolute;top:50%;margin-top:-2px;right:6px;content:"";width:0;height:0;border:6px solid transparent;border-top-color:#a1a4ad;transition:all .3s linear;transform-origin:center}.area-select .area-select-icon.active{margin-top:-8px;transform:rotate(180deg)}.area-selectable-list-wrap{position:absolute;width:100%;max-height:275px;z-index:15000;border:1px solid #a1a4ad;border-radius:2px;background-color:#fff;box-shadow:0 2px 4px rgba(0,0,0,.12),0 0 6px rgba(0,0,0,.04);box-sizing:border-box;margin:5px 0;overflow-x:hidden;overflow-x:auto}.area-selectable-list{position:relative;margin:0;padding:6px 0;width:100%;font-size:14px;color:#565656;list-style:none}.area-selectable-list .area-select-option{position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;padding:0 15px 0 10px;height:32px;line-height:32px}.area-selectable-list .area-select-option.hover{background-color:#e4e8f1}.area-selectable-list .area-select-option.selected{background-color:#e4e8f1;color:#ff6200;font-weight:700}.cascader-menu-list-wrap{position:absolute;white-space:nowrap;z-index:15000;border:1px solid #a1a4ad;border-radius:2px;background-color:#fff;box-shadow:0 2px 4px rgba(0,0,0,.12),0 0 6px rgba(0,0,0,.04);box-sizing:border-box;margin:5px 0;overflow:hidden;font-size:0}.cascader-menu-list{position:relative;margin:0;font-size:14px;color:#565656;padding:6px 0;list-style:none;display:inline-block;height:204px;overflow-x:hidden;overflow-y:auto;min-width:160px;vertical-align:top;background-color:#fff;border-right:1px solid #e4e7ed}.cascader-menu-list:last-child{border-right:none}.cascader-menu-list .cascader-menu-option{position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;padding:0 15px 0 10px;height:32px;line-height:32px}.cascader-menu-list .cascader-menu-option.hover,.cascader-menu-list .cascader-menu-option:hover{background-color:#e4e8f1}.cascader-menu-list .cascader-menu-option.selected{background-color:#e4e8f1;color:#ff6200;font-weight:700}.cascader-menu-list .cascader-menu-option.cascader-menu-extensible:after{position:absolute;top:50%;margin-top:-4px;right:5px;content:"";width:0;height:0;border:4px solid transparent;border-left-color:#a1a4ad}.area-selectable-list-wrap::-webkit-scrollbar,.cascader-menu-list::-webkit-scrollbar{width:8px;background:transparent}.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:decremen,.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:end:decrement,.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:increment,.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:start:increment,.cascader-menu-list::-webkit-scrollbar-button:vertical:decremen,.cascader-menu-list::-webkit-scrollbar-button:vertical:end:decrement,.cascader-menu-list::-webkit-scrollbar-button:vertical:increment,.cascader-menu-list::-webkit-scrollbar-button:vertical:start:increment{display:none}.area-selectable-list-wrap::-webkit-scrollbar-thumb:vertical,.cascader-menu-list::-webkit-scrollbar-thumb:vertical{background-color:#b8b8b8;border-radius:4px}.area-selectable-list-wrap::-webkit-scrollbar-thumb:vertical:hover,.cascader-menu-list::-webkit-scrollbar-thumb:vertical:hover{background-color:#777} -------------------------------------------------------------------------------- /demo/vendor.54999dc1.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwqs/react-area-linkage/3b34efeabe5d5cf1df591ea87af360e7fc2896e7/demo/vendor.54999dc1.js.gz -------------------------------------------------------------------------------- /dist/index.css: -------------------------------------------------------------------------------- 1 | .area-zoom-in-top-enter,.area-zoom-in-top-exit-active{opacity:0;transform:scaleY(0)}.area-zoom-in-top-enter-active,.area-zoom-in-top-exit{opacity:1;transform:scaleY(1);transition:all .3s cubic-bezier(.645,.045,.355,1);transform-origin:center top}.area-select-wrap .area-select{margin-left:10px}.area-select-wrap .area-select-empty{padding:4px 0;margin:0;text-align:center;color:#999;font-size:14px}.area-select{position:relative;display:inline-block;vertical-align:top;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;height:32px;cursor:pointer;background:#fff;border-radius:4px;border:1px solid #e1e2e6}.area-select *{box-sizing:border-box}.area-select:hover{border-color:#a1a4ad}.area-select.small{width:126px}.area-select.medium{width:160px}.area-select.large{width:194px}.area-select.is-disabled{background:#eceff5;cursor:not-allowed}.area-select.is-disabled:hover{border-color:#e1e2e6}.area-select.is-disabled .area-selected-trigger{cursor:not-allowed}.area-select .area-selected-trigger{position:relative;display:block;font-size:14px;cursor:pointer;margin:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;height:100%;line-height:100%;padding:8px 20px 7px 12px}.area-select .area-select-icon{position:absolute;top:50%;margin-top:-2px;right:6px;content:"";width:0;height:0;border:6px solid transparent;border-top-color:#a1a4ad;transition:all .3s linear;transform-origin:center}.area-select .area-select-icon.active{margin-top:-8px;transform:rotate(180deg)}.area-selectable-list-wrap{position:absolute;width:100%;max-height:275px;z-index:15000;border:1px solid #a1a4ad;border-radius:2px;background-color:#fff;box-shadow:0 2px 4px rgba(0,0,0,.12),0 0 6px rgba(0,0,0,.04);box-sizing:border-box;margin:5px 0;overflow-x:hidden;overflow-x:auto}.area-selectable-list{position:relative;margin:0;padding:6px 0;width:100%;font-size:14px;color:#565656;list-style:none}.area-selectable-list .area-select-option{position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;padding:0 15px 0 10px;height:32px;line-height:32px}.area-selectable-list .area-select-option.hover{background-color:#e4e8f1}.area-selectable-list .area-select-option.selected{background-color:#e4e8f1;color:#ff6200;font-weight:700}.cascader-menu-list-wrap{position:absolute;white-space:nowrap;z-index:15000;border:1px solid #a1a4ad;border-radius:2px;background-color:#fff;box-shadow:0 2px 4px rgba(0,0,0,.12),0 0 6px rgba(0,0,0,.04);box-sizing:border-box;margin:5px 0;overflow:hidden;font-size:0}.cascader-menu-list{position:relative;margin:0;font-size:14px;color:#565656;padding:6px 0;list-style:none;display:inline-block;height:204px;overflow-x:hidden;overflow-y:auto;min-width:160px;vertical-align:top;background-color:#fff;border-right:1px solid #e4e7ed}.cascader-menu-list:last-child{border-right:none}.cascader-menu-list .cascader-menu-option{position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;padding:0 15px 0 10px;height:32px;line-height:32px}.cascader-menu-list .cascader-menu-option.hover,.cascader-menu-list .cascader-menu-option:hover{background-color:#e4e8f1}.cascader-menu-list .cascader-menu-option.selected{background-color:#e4e8f1;color:#ff6200;font-weight:700}.cascader-menu-list .cascader-menu-option.cascader-menu-extensible:after{position:absolute;top:50%;margin-top:-4px;right:5px;content:"";width:0;height:0;border:4px solid transparent;border-left-color:#a1a4ad}.area-selectable-list-wrap::-webkit-scrollbar,.cascader-menu-list::-webkit-scrollbar{width:8px;background:transparent}.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:decremen,.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:end:decrement,.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:increment,.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:start:increment,.cascader-menu-list::-webkit-scrollbar-button:vertical:decremen,.cascader-menu-list::-webkit-scrollbar-button:vertical:end:decrement,.cascader-menu-list::-webkit-scrollbar-button:vertical:increment,.cascader-menu-list::-webkit-scrollbar-button:vertical:start:increment{display:none}.area-selectable-list-wrap::-webkit-scrollbar-thumb:vertical,.cascader-menu-list::-webkit-scrollbar-thumb:vertical{background-color:#b8b8b8;border-radius:4px}.area-selectable-list-wrap::-webkit-scrollbar-thumb:vertical:hover,.cascader-menu-list::-webkit-scrollbar-thumb:vertical:hover{background-color:#777} -------------------------------------------------------------------------------- /gh/components/footer/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component, Children} from 'react'; 2 | 3 | export default class Footer extends Component { 4 | constructor (){ 5 | super (); 6 | } 7 | 8 | render () { 9 | return ( 10 | 15 | ); 16 | } 17 | } -------------------------------------------------------------------------------- /gh/components/header/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component, Children} from 'react'; 2 | 3 | export default class Header extends Component { 4 | constructor (){ 5 | super (); 6 | } 7 | 8 | render () { 9 | return ( 10 |
    11 | 12 | 15 | 16 |

    React Area Linkage: 中国行政区联动选择器

    17 |

    18 | 省、市、区联动选择 19 |

    20 |
    21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /gh/components/main/area-text-code.js: -------------------------------------------------------------------------------- 1 | import React, {Component, Children} from 'react'; 2 | import { pca } from 'area-data'; 3 | 4 | import { AreaSelect } from '../../../src/index'; 5 | 6 | export default class AreaText extends Component { 7 | constructor (){ 8 | super (); 9 | this.state = { 10 | selected: [], 11 | shown: false 12 | }; 13 | } 14 | 15 | handleSelectedChange = (val) => { 16 | this.setState({ 17 | selected: val 18 | }); 19 | } 20 | 21 | toggle = () => { 22 | this.setState({ 23 | shown: !this.state.shown 24 | }); 25 | } 26 | 27 | render () { 28 | return ( 29 |
    30 |
    31 | 32 |
    33 |
    34 |
    {`${JSON.stringify(this.state.selected)}`}
    35 |
    36 | { 37 | this.state.shown && 38 |
    39 |
    //返回值是一个数组,数组项是包含省市的行政区域代码和文本的对象
    <AreaSelect  type='all' onChange={this.handleSelectedChange}/>
    40 |
    41 | } 42 |
    43 | {this.state.shown ? 'Hide Code' : 'Show Code'} 44 |
    45 |
    46 | ); 47 | } 48 | } -------------------------------------------------------------------------------- /gh/components/main/area-text.js: -------------------------------------------------------------------------------- 1 | import React, {Component, Children} from 'react'; 2 | import { pca } from 'area-data'; 3 | 4 | import { AreaSelect } from '../../../src/index'; 5 | 6 | export default class AreaText extends Component { 7 | constructor (){ 8 | super (); 9 | this.state = { 10 | selected: [], 11 | shown: false 12 | }; 13 | } 14 | 15 | handleSelectedChange = (val) => { 16 | this.setState({ 17 | selected: val 18 | }); 19 | } 20 | 21 | toggle = () => { 22 | this.setState({ 23 | shown: !this.state.shown 24 | }); 25 | } 26 | 27 | render () { 28 | return ( 29 |
    30 |
    31 | 32 |
    33 |
    34 |
    {`[${this.state.selected}]`}
    35 |
    36 | { 37 | this.state.shown && 38 |
    39 |
    //type:['text','all','code'], 默认值是 code. 返回值是一个数组,分别是省市的行政区域文本
    <AreaSelect  type='text' onChange={this.handleSelectedChange}/>
    40 |
    41 | } 42 |
    43 | {this.state.shown ? 'Hide Code' : 'Show Code'} 44 |
    45 |
    46 | ); 47 | } 48 | } -------------------------------------------------------------------------------- /gh/components/main/basic.js: -------------------------------------------------------------------------------- 1 | import React, {Component, Children} from 'react'; 2 | import { pca } from 'area-data'; 3 | 4 | import { AreaSelect } from '../../../src/index'; 5 | 6 | export default class Basic extends Component { 7 | constructor (){ 8 | super (); 9 | this.state = { 10 | selected: [], 11 | shown: false 12 | }; 13 | } 14 | 15 | handleSelectedChange = (val) => { 16 | this.setState({ 17 | selected: val 18 | }); 19 | } 20 | 21 | toggle = () => { 22 | this.setState({ 23 | shown: !this.state.shown 24 | }); 25 | } 26 | 27 | render () { 28 | const { placeholders } = this.props; 29 | return ( 30 |
    31 |
    32 | 33 |
    34 |
    35 |
    {`[${this.state.selected}]`}
    36 |
    37 | { 38 | this.state.shown && !placeholders && 39 |
    40 |
    //返回值是一个数组,分别是省市的行政区域代码
    <AreaSelect onChange={this.handleSelectedChange}/>
    41 |
    42 | } 43 | { 44 | this.state.shown && placeholders && 45 |
    46 |
    //设置 placeholders,其值应该和关联层次对应
    <AreaSelect placeholders={['选择省', '选择市']} onChange={this.handleSelectedChange}/>
    47 |
    48 | } 49 |
    50 | {this.state.shown ? 'Hide Code' : 'Show Code'} 51 |
    52 |
    53 | ); 54 | } 55 | } -------------------------------------------------------------------------------- /gh/components/main/cas-basic.js: -------------------------------------------------------------------------------- 1 | import React, {Component, Children} from 'react'; 2 | import { pca } from 'area-data'; 3 | 4 | import { AreaCascader } from '../../../src/index'; 5 | 6 | export default class CasBasic extends Component { 7 | constructor (){ 8 | super (); 9 | this.state = { 10 | selected: [], 11 | shown: false 12 | }; 13 | } 14 | 15 | handleSelectedChange = (val) => { 16 | this.setState({ 17 | selected: val 18 | }); 19 | } 20 | 21 | toggle = () => { 22 | this.setState({ 23 | shown: !this.state.shown 24 | }); 25 | } 26 | 27 | render () { 28 | 29 | return ( 30 |
    31 |
    32 | 33 |
    34 |
    35 |
    {`[${this.state.selected}]`}
    36 |
    37 | { 38 | this.state.shown && 39 |
    40 |
    //返回值是一个数组,分别是省市的行政区域代码
    <AreaCascader onChange={this.handleSelectedChange}/>
    41 |
    42 | } 43 |
    44 | {this.state.shown ? 'Hide Code' : 'Show Code'} 45 |
    46 |
    47 | ); 48 | } 49 | } -------------------------------------------------------------------------------- /gh/components/main/cas-def-value.js: -------------------------------------------------------------------------------- 1 | import React, {Component, Children} from 'react'; 2 | import { pcaa } from 'area-data'; 3 | 4 | import { AreaCascader } from '../../../src/index'; 5 | 6 | export default class DefaultVal extends Component { 7 | constructor (){ 8 | super (); 9 | this.state = { 10 | selected: [], 11 | shown: false 12 | }; 13 | } 14 | 15 | handleSelectedChange = (val) => { 16 | this.setState({ 17 | selected: val 18 | }); 19 | } 20 | 21 | toggle = () => { 22 | this.setState({ 23 | shown: !this.state.shown 24 | }); 25 | } 26 | 27 | render () { 28 | let def = ['440000','440300','440305']; 29 | // if(level === 0) { 30 | // def = ['440000']; 31 | // } else if (level === 2) { 32 | // def = ['440000','440300','440305']; 33 | // } else { 34 | // def = ['广东省', '深圳市', '南山区', '粤海街道']; 35 | // } 36 | 37 | return ( 38 |
    39 |
    40 | 41 |
    42 |
    43 |
    {`[${this.state.selected}]`}
    44 |
    45 | { 46 | this.state.shown && 47 |
    48 |
    //绑定默认值defaultArea=['440000','440300','440305'], 代码对应的区域文本是['广东省', '深圳市', '南山区']
    <AreaCascader type='text' level={1} defaultArea={defaultArea} onChange={this.handleSelectedChange}/>
    49 |
    50 | } 51 |
    52 | {this.state.shown ? 'Hide Code' : 'Show Code'} 53 |
    54 |
    55 | ); 56 | } 57 | } -------------------------------------------------------------------------------- /gh/components/main/cas-linkage.js: -------------------------------------------------------------------------------- 1 | import React, {Component, Children} from 'react'; 2 | import { pcaa } from 'area-data'; 3 | 4 | import { AreaCascader } from '../../../src/index'; 5 | 6 | export default class DefaultVal extends Component { 7 | constructor (){ 8 | super (); 9 | this.state = { 10 | selected: [], 11 | shown: false 12 | }; 13 | } 14 | 15 | handleSelectedChange = (val) => { 16 | this.setState({ 17 | selected: val 18 | }); 19 | } 20 | 21 | toggle = () => { 22 | this.setState({ 23 | shown: !this.state.shown 24 | }); 25 | } 26 | 27 | render () { 28 | 29 | return ( 30 |
    31 |
    32 | 33 |
    34 |
    35 |
    {`[${this.state.selected}]`}
    36 |
    37 | { 38 | this.state.shown && 39 |
    40 |
    //代码对应的区域文本是['广东省', '深圳市', '南山区']
    <AreaCascader type='text' level={1}  onChange={this.handleSelectedChange}/>
    41 |
    42 | } 43 |
    44 | {this.state.shown ? 'Hide Code' : 'Show Code'} 45 |
    46 |
    47 | ); 48 | } 49 | } -------------------------------------------------------------------------------- /gh/components/main/cas-text-code.js: -------------------------------------------------------------------------------- 1 | import React, {Component, Children} from 'react'; 2 | import { pca } from 'area-data'; 3 | 4 | import { AreaCascader } from '../../../src/index'; 5 | 6 | export default class CasAreaText extends Component { 7 | constructor (){ 8 | super (); 9 | this.state = { 10 | selected: [], 11 | shown: false 12 | }; 13 | } 14 | 15 | handleSelectedChange = (val) => { 16 | this.setState({ 17 | selected: val 18 | }); 19 | } 20 | 21 | toggle = () => { 22 | this.setState({ 23 | shown: !this.state.shown 24 | }); 25 | } 26 | 27 | render () { 28 | return ( 29 |
    30 |
    31 | 32 |
    33 |
    34 |
    {`${JSON.stringify(this.state.selected)}`}
    35 |
    36 | { 37 | this.state.shown && 38 |
    39 |
    //返回值是一个数组,数组项是包含省市的行政区域代码和文本的对象
    <AreaCascader  type='all' onChange={this.handleSelectedChange}/>
    40 |
    41 | } 42 |
    43 | {this.state.shown ? 'Hide Code' : 'Show Code'} 44 |
    45 |
    46 | ); 47 | } 48 | } -------------------------------------------------------------------------------- /gh/components/main/default-value.js: -------------------------------------------------------------------------------- 1 | import React, {Component, Children} from 'react'; 2 | import { pca, pcaa } from 'area-data'; 3 | 4 | import { AreaSelect } from '../../../src/index'; 5 | 6 | export default class DefaultVal extends Component { 7 | constructor (){ 8 | super (); 9 | this.state = { 10 | selected: [], 11 | shown: false 12 | }; 13 | } 14 | 15 | handleSelectedChange = (val) => { 16 | this.setState({ 17 | selected: val 18 | }); 19 | } 20 | 21 | toggle = () => { 22 | this.setState({ 23 | shown: !this.state.shown 24 | }); 25 | } 26 | 27 | render () { 28 | const { level, type } = this.props; 29 | let def = []; 30 | let d = {}; 31 | if(level === 0) { 32 | def = ['440000']; 33 | d = pca; 34 | } else if (level === 2) { 35 | def = ['440000','440300','440305']; 36 | d = pcaa; 37 | } 38 | 39 | return ( 40 |
    41 |
    42 | 43 |
    44 |
    45 | { 46 | level > 0 && 47 |
    {`[${this.state.selected}]`}
    48 | } 49 | { 50 | level === 0 && 51 |
    {`${JSON.stringify(this.state.selected)}`}
    52 | } 53 |
    54 | { 55 | this.state.shown && level === 2 && 56 |
    57 |
    //绑定默认值defaultArea=['440000','440300','440305'],代码对应的区域文本是['广东省', '深圳市', '南山区']
    <AreaSelect level={2} defaultArea={['440000','440300','440305']} onChange={this.handleSelectedChange}/>
    58 |
    59 | } 60 | { 61 | this.state.shown && level === 3 && 62 |
    63 |
    //返回值是一个数组,分别是省份行政区域文本
    <AreaSelect type='text' level={3} defaultArea={['广东省', '深圳市', '南山区', '粤海街道']} onChange={this.handleSelectedChange}/>
    64 |
    65 | } 66 | { 67 | this.state.shown && level === 0 && 68 |
    69 |
    //返回值是一个数组,分别是省份行政区域代码
    <AreaSelect type='all' level={0} defaultArea={['440000']} onChange={this.handleSelectedChange}/>
    70 |
    71 | } 72 |
    73 | {this.state.shown ? 'Hide Code' : 'Show Code'} 74 |
    75 |
    76 | ); 77 | } 78 | } -------------------------------------------------------------------------------- /gh/components/main/index.js: -------------------------------------------------------------------------------- 1 | import './index.less'; 2 | 3 | import React, {Component, Children} from 'react'; 4 | 5 | import Basic from './basic'; 6 | import Size from './size'; 7 | import AreaText from './area-text'; 8 | import AreaTextCode from './area-text-code'; 9 | import DefaultVal from './default-value'; 10 | 11 | import CasBasic from './cas-basic'; 12 | import CasAreaTextCode from './cas-text-code'; 13 | import CasAreaDef from './cas-def-value'; 14 | import CasLink from './cas-linkage'; 15 | 16 | export default class Main extends Component { 17 | constructor (){ 18 | super (); 19 | } 20 | 21 | render () { 22 | return ( 23 |
    24 |

    基本使用

    25 |

    1. 作为选择器

    26 |
    默认形式
    27 | 28 |
    改变大小
    29 | 30 |
    返回区域文本
    31 | 32 |
    返回区域代码和文本
    33 | 34 |
    设置 placeholders
    35 | 36 |

    37 | placeholders 是一个数组, 数组项顺序分别对应省/市/区. 38 |

    39 |
    设置默认值及省市区联动
    40 | 41 |

    42 | selected 是一个数组, 数组项顺序分别对应省/市/区, 且类型(区域代码/区域文本)必须统一. 以第一个元素类型为基准. 类型不统一将报错. 43 |

    44 |
    只选省份
    45 | 46 |

    2. 作为级联器(只支持2/3级联动)

    47 |
    默认形式
    48 | 49 |
    返回区域文本和代码
    50 | 51 |

    52 | type 表示返回值, 支持:['text', 'code', 'all'], 默认值是 code 53 |

    54 |
    设置默认值
    55 | 56 |
    省市区联动
    57 | 58 |
    59 | ); 60 | } 61 | } -------------------------------------------------------------------------------- /gh/components/main/index.less: -------------------------------------------------------------------------------- 1 | .app-main{ 2 | margin-top: 20px; 3 | h3{ 4 | text-align: center; 5 | font-size: 25px; 6 | color: #6289ad 7 | } 8 | h4{ 9 | margin-top: 25px; 10 | font-size: 18px; 11 | color: #6289ad 12 | } 13 | h5{ 14 | margin-top: 20px; 15 | font-size: 16px; 16 | color: #6289ad 17 | } 18 | .desc{ 19 | margin-top: 15px; 20 | code{ 21 | background: #f8f8f8; 22 | color: #333; 23 | padding: 5px; 24 | } 25 | } 26 | .code-area{ 27 | position: relative; 28 | width: 100%; 29 | height: auto; 30 | border: 1px solid #dce7f4; 31 | border-radius: 4px; 32 | margin-top: 20px; 33 | padding: 0; 34 | &>div{ 35 | margin-top: 40px; 36 | display: inline-block; 37 | vertical-align: middle 38 | } 39 | .area-left{ 40 | width: 75%; 41 | margin-left: 10px; 42 | } 43 | .area-right{ 44 | width: 21%; 45 | margin-left: 10px; 46 | pre{ 47 | background: #f8f8f8; 48 | padding: 15px 10px; 49 | color: #6289ad; 50 | line-height: 20px 51 | } 52 | } 53 | .original-code{ 54 | width: 90%; 55 | margin: 25px 45px -10px; 56 | pre{ 57 | background: #f8f8f8; 58 | padding: 15px 10px; 59 | } 60 | } 61 | .show-code{ 62 | width: 100%; 63 | cursor: pointer; 64 | text-align: center; 65 | border-top: 1px solid #dce7f4; 66 | padding-top: 3px; 67 | padding-bottom: 3px; 68 | color: #6289ad; 69 | font-weight: 500; 70 | font-size: 18px; 71 | &:hover{ 72 | background-color: #dce7f4; 73 | color: #fff; 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /gh/components/main/size.js: -------------------------------------------------------------------------------- 1 | import React, {Component, Children} from 'react'; 2 | 3 | import { pca } from 'area-data'; 4 | 5 | import { AreaSelect } from '../../../src/index'; 6 | 7 | export default class Size extends Component { 8 | constructor (){ 9 | super (); 10 | this.state = { 11 | selected: [], 12 | shown: false 13 | }; 14 | } 15 | 16 | handleSelectedChange = (val) => { 17 | this.setState({ 18 | selected: val 19 | }); 20 | } 21 | 22 | toggle = () => { 23 | this.setState({ 24 | shown: !this.state.shown 25 | }); 26 | } 27 | 28 | render () { 29 | return ( 30 |
    31 |
    32 | 33 |
    34 |
    35 |
    {`[${this.state.selected}]`}
    36 |
    37 | { 38 | this.state.shown && 39 |
    40 |
    //size:['small','default','large'], 默认值是 default
    <AreaSelect size='large' onChange={this.handleSelectedChange}/>
    41 |
    42 | } 43 |
    44 | {this.state.shown ? 'Hide Code' : 'Show Code'} 45 |
    46 |
    47 | ); 48 | } 49 | } -------------------------------------------------------------------------------- /gh/components/start/index.js: -------------------------------------------------------------------------------- 1 | import './index.less'; 2 | import React, {Component, Children} from 'react'; 3 | 4 | export default class Start extends Component { 5 | constructor (){ 6 | super (); 7 | } 8 | 9 | render () { 10 | return ( 11 |
    12 |

    快速开始

    13 |
    14 |

    安装

    15 |
    npm i --save react-area-linkage
    16 |

    或者

    17 |
    yarn add react-area-linkage
    18 |
    19 |
    20 |

    使用

    21 |
    import React from 'react';

    import ReactDOM from 'react-dom';

    import 'react-area-linkage/dist/index.css';

    import { AreaSelect, AreaCascader } from 'react-area-linkage';

    <AreaSelect/>

    <AreaCascader/>
    22 |
    23 |
    24 |
    25 | ); 26 | } 27 | } -------------------------------------------------------------------------------- /gh/components/start/index.less: -------------------------------------------------------------------------------- 1 | .start{ 2 | margin-top: 40px; 3 | h3{ 4 | text-align: center; 5 | font-size: 25px; 6 | color: #6289ad 7 | } 8 | h4{ 9 | font-size: 18px; 10 | color: #6289ad 11 | } 12 | pre{ 13 | margin-top: 15px; 14 | background: #f8f8f8; 15 | padding: 15px 10px; 16 | color: #000; 17 | font-size: 16px; 18 | font-weight: 400 19 | } 20 | .install{ 21 | margin-top: 20px; 22 | p{ 23 | margin-top: 20px; 24 | margin-bottom: 20px; 25 | } 26 | } 27 | .register{ 28 | margin-top: 20px 29 | } 30 | } -------------------------------------------------------------------------------- /gh/general/app/index.js: -------------------------------------------------------------------------------- 1 | import './index.less'; 2 | 3 | import React, {Component, Children} from 'react'; 4 | 5 | import Header from '@components/header/index'; 6 | import Start from '@components/start/index'; 7 | import Main from '@components/main/index'; 8 | import Footer from '@components/footer/index'; 9 | 10 | import AreaSelect from '../../../components/area-select/index'; 11 | import AreaCascader from '../../../components/area-cascader/index'; 12 | 13 | export default class App extends Component { 14 | constructor (){ 15 | super (); 16 | this.state = { 17 | selected: [] //['广东省', '深圳市', '南山区'] //['440000','440300','440305'] 18 | }; 19 | } 20 | 21 | onChange = (val) => { 22 | console.log('aaaaaa', val); 23 | } 24 | 25 | render () { 26 | return ( 27 |
    28 | { 29 | //

    1

    30 | //

    1

    31 | //

    1

    32 | //

    1

    33 | //

    1

    34 | // 35 | // 36 | } 37 |
    38 | 39 |
    40 |
    41 |
    42 | ); 43 | } 44 | 45 | componentDidMount () { 46 | // setTimeout(() => { 47 | // this.setState({ 48 | // selected: ['440000','440300','440305'] 49 | // }); 50 | // }, 2000); 51 | } 52 | } -------------------------------------------------------------------------------- /gh/general/app/index.less: -------------------------------------------------------------------------------- 1 | .app-wrap{ 2 | width: 960px; 3 | height: 200px; 4 | margin: 0 auto; 5 | } 6 | 7 | // header 部分 8 | header{ 9 | text-align: center; 10 | margin-top: 100px; 11 | } 12 | h2{ 13 | font-size: 27px; 14 | font-weight: 400 15 | } 16 | header p{ 17 | margin-top: 20px; 18 | margin-bottom: 20px; 19 | font-size: 16px 20 | } 21 | 22 | header>a{ 23 | position: fixed; 24 | right: 0; 25 | top: 0 26 | } 27 | 28 | header p a{ 29 | color: #008efd; 30 | text-decoration: none; 31 | padding-left: 5px 32 | } 33 | 34 | // footer 部分 35 | footer{ 36 | margin-top: 30px; 37 | text-align: center; 38 | color: #999; 39 | a { 40 | text-decoration: none; 41 | color: #999; 42 | } 43 | } 44 | footer p{ 45 | margin-top: 15px; 46 | cursor: pointer; 47 | } 48 | footer p:hover{ 49 | color: #008efd; 50 | a { 51 | color: #008efd 52 | } 53 | } -------------------------------------------------------------------------------- /gh/page/index.js: -------------------------------------------------------------------------------- 1 | import './reset.less'; 2 | 3 | import React, {Component, Children} from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import { AppContainer } from 'react-hot-loader'; 6 | 7 | import App from '../general/app/index'; 8 | 9 | const env = process.env.NODE_ENV || 'development'; 10 | 11 | if(env === 'development'){ 12 | window.onload = function () { 13 | const render = Component => { 14 | ReactDOM.render( 15 | 16 | 17 | , 18 | document.getElementById('app') 19 | ); 20 | }; 21 | 22 | render(App); 23 | 24 | // HMR 25 | if (module.hot) { 26 | module.hot.accept('../general/app/index', () => { render(App); }); 27 | } 28 | }; 29 | } else { 30 | window.onload = function () { 31 | ReactDOM.render( 32 | , 33 | document.getElementById('app') 34 | ); 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /gh/page/reset.less: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | font-size: 100%; 18 | outline:0; 19 | vertical-align:baseline; 20 | background:transparent; 21 | } 22 | /* HTML5 display-role reset for older browsers */ 23 | article, aside, details, figcaption, figure, 24 | footer, header, hgroup, menu, nav, section { 25 | display: block; 26 | } 27 | 28 | html,body { 29 | height: 100%; 30 | width: 100%; 31 | /* # https://github.com/AlloyTeam/Mars/blob/master/solutions/font-family.md */ 32 | font-family: -apple-system, BlinkMacSystemFont, "PingFang SC","Helvetica Neue",STHeiti,"Microsoft Yahei",Tahoma,Simsun,sans-serif; 33 | } 34 | 35 | body { 36 | margin:0; 37 | padding:0; 38 | box-sizing: border-box; 39 | font-size: 14px; 40 | line-height: 1; 41 | background: none; 42 | -moz-user-select:none; 43 | -webkit-user-select:none; 44 | -ms-user-select:none; 45 | user-select:none; 46 | overflow-x:hidden; 47 | -webkit-overflow-scrolling:touch; 48 | -webkit-font-smoothing: antialiased; 49 | -moz-osx-font-smoothing: grayscale; 50 | tap-highlight-color: transparent; 51 | -webkit-tap-highlight-color: transparent; 52 | } 53 | 54 | ol, ul, nav ul { 55 | list-style: none; 56 | } 57 | 58 | blockquote, q { 59 | quotes: none; 60 | } 61 | 62 | blockquote:before, blockquote:after, 63 | q:before, q:after { 64 | content: ''; 65 | } 66 | 67 | table { 68 | border-collapse:collapse; 69 | border-spacing:0; 70 | } 71 | 72 | a{ 73 | text-decoration:none; 74 | margin: 0; 75 | padding: 0; 76 | border: 0; 77 | font-size:100%; 78 | vertical-align:baseline; 79 | background:transparent; 80 | -webkit-appearance: none; 81 | -webkit-tap-highlight-color: transparent; 82 | } 83 | 84 | :focus { 85 | outline: none; 86 | } 87 | 88 | ins { 89 | background-color:#ff9; 90 | color:#000; 91 | text-decoration:none; 92 | } 93 | 94 | mark { 95 | background-color:#ff9; 96 | color:#000; 97 | font-style:italic; 98 | font-weight:bold; 99 | } 100 | 101 | del { 102 | text-decoration: line-through; 103 | } 104 | 105 | abbr[title], dfn[title] { 106 | border-bottom:1px dotted #000; 107 | cursor:help; 108 | } 109 | 110 | hr { 111 | display:block; 112 | height:1px; 113 | border:0; 114 | border-top:1px solid #cccccc; 115 | margin:1em 0; 116 | padding:0; 117 | } 118 | 119 | input[type="text"], input[type="password"], textarea, input, select { 120 | outline: none; 121 | -webkit-appearance: none; 122 | } 123 | 124 | input, select { 125 | vertical-align:middle; 126 | } 127 | 128 | * { 129 | box-sizing: border-box; 130 | /* # https://github.com/AlloyTeam/Mars/blob/master/solutions/font-family.md */ 131 | font-family: -apple-system, BlinkMacSystemFont, "PingFang SC","Helvetica Neue",STHeiti,"Microsoft Yahei",Tahoma,Simsun,sans-serif; 132 | /*font: 0.12rem/1 "Microsoft YaHei", Tahoma, Arial, Helvetica, \\5b8b\4f53, "Helvetica Neue", "PingFang SC", "Heiti SC", "Hiragino Sans GB", "\5FAE\8F6F\96C5\9ED1", sans-serif;*/ 133 | color: inherit; 134 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | React Area Linkage-中国省市区联动选择器
    -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-area-linkage", 3 | "version": "3.0.0", 4 | "description": "React area linkage", 5 | "author": "dwqs", 6 | "license": "MIT", 7 | "main": "dist/index.js", 8 | "private": false, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/dwqs/react-area-linkage.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/dwqs/react-area-linkage/issues" 15 | }, 16 | "keywords": [ 17 | "react", 18 | "react 16", 19 | "area", 20 | "picker", 21 | "react picker", 22 | "react linkage" 23 | ], 24 | "scripts": { 25 | "prepush": "npm run ilint -q", 26 | "dev": "npx cross-env NODE_ENV=development node ./build/dev-server.js", 27 | "build:components": "NODE_ENV=production npx webpack --config ./webpack.components.config.js --progress --hide-modules", 28 | "build": "npx cross-env NODE_ENV=production npx webpack --config ./build/webpack.prod.config.js --progress --hide-modules", 29 | "ilint": "npx eslint src/**/*.js", 30 | "fix": "npx eslint --fix src/**/*.js", 31 | "postbuild": "mv ./demo/index.html ./", 32 | "prepublishOnly": "rm -rf dist && npm run build:components && npx webpack --config ./webpack.build.config.js --progress --hide-modules" 33 | }, 34 | "dependencies": { 35 | "array-tree-filter": "^2.1.0", 36 | "classnames": "^2.2.5", 37 | "lodash.find": "^4.6.0", 38 | "prop-types": "^15.6.0" 39 | }, 40 | "peerDependencies": { 41 | "react": ">= 15.0.0", 42 | "react-dom": ">=15.0.0", 43 | "area-data": ">=5.0.6" 44 | }, 45 | "devDependencies": { 46 | "area-data": "^5.0.6", 47 | "autoprefixer": "^7.1.5", 48 | "babel-core": "^6.26.0", 49 | "babel-eslint": "^8.0.1", 50 | "babel-loader": "^7.1.2", 51 | "babel-plugin-transform-class-properties": "^6.24.1", 52 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 53 | "babel-plugin-transform-react-remove-prop-types": "^0.4.9", 54 | "babel-preset-env": "^1.6.0", 55 | "babel-preset-react": "^6.24.1", 56 | "babel-preset-stage-2": "^6.24.1", 57 | "clean-webpack-plugin": "^0.1.17", 58 | "compression-webpack-plugin": "^1.0.1", 59 | "copy-webpack-plugin": "^4.1.1", 60 | "cross-env": "^5.0.5", 61 | "css-loader": "^0.28.7", 62 | "cssnano": "^3.10.0", 63 | "eslint": "^4.8.0", 64 | "eslint-plugin-import": "^2.7.0", 65 | "eslint-plugin-react": "^7.4.0", 66 | "extract-text-webpack-plugin": "^3.0.1", 67 | "gulp-util": "^3.0.8", 68 | "happypack": "^4.0.0", 69 | "html-webpack-plugin": "^2.30.1", 70 | "husky": "^0.14.3", 71 | "less": "^2.7.2", 72 | "less-loader": "^4.0.5", 73 | "open-browser-webpack-plugin": "^0.0.5", 74 | "optimize-css-assets-webpack-plugin": "^3.2.0", 75 | "ora": "^1.3.0", 76 | "postcss-loader": "^2.0.6", 77 | "react": "^16.0.0", 78 | "react-dom": "^16.0.0", 79 | "react-hot-loader": "^3.0.0-beta.7", 80 | "style-loader": "^0.19.0", 81 | "webpack": "^3.6.0", 82 | "webpack-dev-server": "^2.9.1", 83 | "webpack-md5-hash": "^0.0.5", 84 | "webpack-parallel-uglify-plugin": "^1.0.0" 85 | }, 86 | "engines": { 87 | "node": ">= 6.0.0", 88 | "npm": ">= 5.2.0" 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by pomy on 20/07/2017. 3 | */ 4 | 5 | //fix: https://github.com/akveo/ng2-admin/issues/604 6 | //使用 happypack 之后 需单独提供 postcss 配置文件 7 | module.exports = { 8 | plugins: [ 9 | require('autoprefixer')({ browsers: ['last 5 versions','Android >= 4.0', 'iOS >= 7'] }) 10 | ] 11 | }; 12 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import './index.less'; 2 | 3 | import AreaSelect from '../components/area-select/index'; 4 | import AreaCascader from '../components/area-cascader/index'; 5 | 6 | export { 7 | AreaSelect, 8 | AreaCascader 9 | }; 10 | 11 | const ReactAreaLinkage = { 12 | AreaSelect, 13 | AreaCascader 14 | }; 15 | 16 | export default ReactAreaLinkage; -------------------------------------------------------------------------------- /src/index.less: -------------------------------------------------------------------------------- 1 | .area-zoom-in-top-enter, 2 | .area-zoom-in-top-exit-active { 3 | opacity: 0; 4 | transform: scaleY(0); 5 | } 6 | 7 | .area-zoom-in-top-enter-active, 8 | .area-zoom-in-top-exit { 9 | opacity: 1; 10 | transform: scaleY(1); 11 | transition: all 300ms cubic-bezier(.645,.045,.355,1); 12 | transform-origin: center top; 13 | } 14 | 15 | .area-select-wrap .area-select{ 16 | margin-left: 10px; 17 | } 18 | 19 | .area-select-wrap .area-select-empty{ 20 | padding: 4px 0; 21 | margin: 0; 22 | text-align: center; 23 | color: #999; 24 | font-size: 14px; 25 | } 26 | 27 | // commoon 28 | .area-select { 29 | position: relative; 30 | display: inline-block; 31 | vertical-align: top; 32 | user-select: none; 33 | height: 32px; 34 | cursor: pointer; 35 | background: #FFFFFF; 36 | border-radius: 4px; 37 | border: 1px solid #e1e2e6; 38 | * { 39 | box-sizing: border-box; 40 | } 41 | &:hover { 42 | border-color: #a1a4ad; 43 | } 44 | &.small { 45 | width: 126px; 46 | } 47 | &.medium { 48 | width: 160px; 49 | } 50 | &.large { 51 | width: 194px; 52 | } 53 | &.is-disabled { 54 | background: #eceff5; 55 | cursor: not-allowed; 56 | &:hover { 57 | border-color: #e1e2e6; 58 | } 59 | .area-selected-trigger { 60 | cursor: not-allowed; 61 | } 62 | } 63 | .area-selected-trigger { 64 | position: relative; 65 | display: block; 66 | font-size: 14px; 67 | cursor: pointer; 68 | margin: 0; 69 | overflow: hidden; 70 | white-space: nowrap; 71 | text-overflow: ellipsis; 72 | height: 100%; 73 | line-height: 100%; 74 | padding: 8px 20px 7px 12px; 75 | } 76 | .area-select-icon { 77 | position: absolute; 78 | top: 50%; 79 | margin-top: -2px; 80 | right: 6px; 81 | content: ''; 82 | width: 0; 83 | height: 0; 84 | border: 6px solid transparent; 85 | border-top-color: #a1a4ad; 86 | transition: all .3s linear; 87 | transform-origin: center; 88 | &.active { 89 | margin-top: -8px; 90 | transform: rotate(180deg) 91 | } 92 | } 93 | } 94 | 95 | // select start 96 | .area-selectable-list-wrap { 97 | position: absolute; 98 | width: 100%; 99 | max-height: 275px; 100 | z-index: 15000; 101 | border: 1px solid #a1a4ad; 102 | border-radius: 2px; 103 | background-color: #FFFFFF; 104 | box-shadow: 0 2px 4px rgba(0,0,0,.12), 0 0 6px rgba(0,0,0,.04); 105 | box-sizing: border-box; 106 | margin: 5px 0; 107 | overflow-x: hidden; 108 | overflow-x: auto; 109 | } 110 | 111 | .area-selectable-list { 112 | position: relative; 113 | margin: 0; 114 | padding: 6px 0; 115 | width: 100%; 116 | font-size: 14px; 117 | color: #565656; 118 | list-style: none; 119 | .area-select-option { 120 | position: relative; 121 | white-space: nowrap; 122 | overflow: hidden; 123 | text-overflow: ellipsis; 124 | cursor: pointer; 125 | padding: 0px 15px 0px 10px; 126 | height: 32px; 127 | line-height: 32px; 128 | &.hover { 129 | background-color: #e4e8f1; 130 | } 131 | &.selected { 132 | background-color: #e4e8f1; 133 | color: #FF6200; 134 | font-weight: 700; 135 | } 136 | } 137 | } 138 | 139 | // select end 140 | 141 | // cascader start 142 | .cascader-menu-list-wrap { 143 | position: absolute; 144 | // max-height: 275px; 145 | white-space: nowrap; 146 | z-index: 15000; 147 | border: 1px solid #a1a4ad; 148 | border-radius: 2px; 149 | background-color: #FFFFFF; 150 | box-shadow: 0 2px 4px rgba(0,0,0,.12), 0 0 6px rgba(0,0,0,.04); 151 | box-sizing: border-box; 152 | margin: 5px 0; 153 | overflow: hidden; 154 | font-size: 0; 155 | } 156 | 157 | .cascader-menu-list { 158 | position: relative; 159 | margin: 0; 160 | font-size: 14px; 161 | color: #565656; 162 | padding: 6px 0; 163 | list-style: none; 164 | display: inline-block; 165 | height: 204px; 166 | overflow-x: hidden; 167 | overflow-y: auto; 168 | min-width: 160px; 169 | vertical-align: top; 170 | background-color: #fff; 171 | border-right: 1px solid #e4e7ed; 172 | 173 | &:last-child { 174 | border-right: none; 175 | } 176 | .cascader-menu-option { 177 | position: relative; 178 | white-space: nowrap; 179 | overflow: hidden; 180 | text-overflow: ellipsis; 181 | cursor: pointer; 182 | padding: 0px 15px 0px 10px; 183 | height: 32px; 184 | line-height: 32px; 185 | &.hover { 186 | background-color: #e4e8f1; 187 | } 188 | &:hover { 189 | background-color: #e4e8f1; 190 | } 191 | &.selected { 192 | background-color: #e4e8f1; 193 | color: #FF6200; 194 | font-weight: 700; 195 | } 196 | &.cascader-menu-extensible { 197 | &:after { 198 | position: absolute; 199 | top: 50%; 200 | margin-top: -4px; 201 | right: 5px; 202 | content: ""; 203 | width: 0; 204 | height: 0; 205 | border: 4px solid transparent; 206 | border-left-color: #a1a4ad; 207 | } 208 | } 209 | } 210 | } 211 | // cascader end 212 | 213 | // custom scrollbar 214 | .cascader-menu-list, .area-selectable-list-wrap { 215 | &::-webkit-scrollbar { 216 | width: 8px; 217 | background: transparent; 218 | } 219 | 220 | &::-webkit-scrollbar-button:vertical:increment, 221 | &::-webkit-scrollbar-button:vertical:decremen, 222 | &::-webkit-scrollbar-button:vertical:start:increment, 223 | &::-webkit-scrollbar-button:vertical:end:decrement { 224 | display: none; 225 | } 226 | 227 | &::-webkit-scrollbar-thumb:vertical { 228 | background-color: #b8b8b8; 229 | border-radius: 4px; 230 | &:hover { 231 | background-color:#777; 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | function contains (root, target) { 2 | // root 节点是否包含 target 节点 3 | const isElement = Object.prototype.toString.call(root).includes('Element') && Object.prototype.toString.call(target).includes('Element'); 4 | if (!isElement) { 5 | return false; 6 | } 7 | let node = target; 8 | while (node) { 9 | if (node === root) { 10 | return true; 11 | } 12 | node = node.parentNode; 13 | } 14 | return false; 15 | } 16 | 17 | function assert (condition, msg = '') { 18 | if (!condition) { 19 | console.error(`[react-area-linkage]: ${msg}`); 20 | } 21 | } 22 | 23 | function isArray (param) { 24 | return Object.prototype.toString.call(param) === '[object Array]'; 25 | } 26 | 27 | function scrollIntoView (container, target) { 28 | if (!target) { 29 | container.scrollTop = 0; 30 | return; 31 | } 32 | 33 | // refrence: https://github.com/ElemeFE/element/blob/dev/src/utils/scroll-into-view.js 34 | const top = target.offsetTop; 35 | const bottom = target.offsetTop + target.offsetHeight; 36 | const viewRectTop = container.scrollTop; 37 | const viewRectBottom = viewRectTop + container.clientHeight; 38 | 39 | if (top < viewRectTop) { 40 | container.scrollTop = top; 41 | } else if (bottom > viewRectBottom) { 42 | container.scrollTop = bottom - container.clientHeight; 43 | } 44 | } 45 | 46 | function setPanelPosition (panelHeight, wrapRect) { 47 | const wrapHeight = wrapRect.height; 48 | const wrapTop = wrapRect.top; 49 | 50 | const docHeight = document.documentElement.clientHeight; 51 | const panelDefTop = wrapTop + wrapHeight; 52 | 53 | const diff = docHeight - panelDefTop; 54 | if (diff < panelHeight) { 55 | if (wrapTop > panelHeight) { 56 | return -(panelHeight + 10); 57 | } else { 58 | return diff - panelHeight; 59 | } 60 | } else { 61 | return wrapHeight; 62 | } 63 | } 64 | 65 | export { 66 | contains, 67 | assert, 68 | isArray, 69 | scrollIntoView, 70 | setPanelPosition 71 | }; 72 | -------------------------------------------------------------------------------- /tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | React Area Linkage-中国省市区联动选择器 10 | 11 | 12 |
    13 | 14 | -------------------------------------------------------------------------------- /webpack.build.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin'); 4 | const os = require('os'); 5 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 6 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin'); 7 | 8 | module.exports = { 9 | entry: { 10 | index: path.resolve(__dirname, './src/index') 11 | }, 12 | 13 | output: { 14 | path: path.join(__dirname, './dist'), 15 | filename: '[name].js', 16 | library: 'ReactAreaLinkage', 17 | libraryTarget: 'umd' 18 | }, 19 | 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.jsx?$/, 24 | exclude: /node_modules/, 25 | loader: 'babel-loader' 26 | }, 27 | { 28 | test: /\.less$/, 29 | use: ExtractTextPlugin.extract({ 30 | fallback: 'style-loader', 31 | use: ['css-loader', 'postcss-loader', 'less-loader'] 32 | }) 33 | }, 34 | { 35 | test: /\.css$/, 36 | use: ExtractTextPlugin.extract({ 37 | fallback: 'style-loader', 38 | use: ['css-loader'] 39 | }) 40 | } 41 | ] 42 | }, 43 | 44 | // fix: https://stackoverflow.com/questions/38053561/only-a-reactowner-can-have-refs-you-might-be-adding-a-ref-to-a-component-that-w 45 | externals: [{ 46 | 'react': { 47 | root: 'React', 48 | commonjs2: 'react', 49 | commonjs: 'react', 50 | amd: 'react' 51 | } 52 | }, { 53 | 'react-dom': { 54 | root: 'ReactDOM', 55 | commonjs2: 'react-dom', 56 | commonjs: 'react-dom', 57 | amd: 'react-dom' 58 | } 59 | }], 60 | 61 | resolve: { 62 | extensions: ['.js', '.jsx'], 63 | modules: [path.join(__dirname, './node_modules')], 64 | alias: { 65 | '@src': path.resolve(__dirname, './src') 66 | } 67 | }, 68 | 69 | plugins: [ 70 | new ExtractTextPlugin({ 71 | filename: '[name].css' 72 | }), 73 | 74 | new OptimizeCSSPlugin({ 75 | cssProcessorOptions: { 76 | safe: true 77 | }, 78 | cssProcessor: require('cssnano'), 79 | assetNameRegExp: /\.less|\.css$/g 80 | }), 81 | 82 | // new ParallelUglifyPlugin({ 83 | // workerCount: os.cpus().length, 84 | // cacheDir: '.cache/', 85 | // sourceMap: false, 86 | // compress: { 87 | // warnings: false, 88 | // drop_debugger: true, 89 | // drop_console: true 90 | // }, 91 | // mangle: true 92 | // }), 93 | new webpack.optimize.ModuleConcatenationPlugin() 94 | ] 95 | }; 96 | -------------------------------------------------------------------------------- /webpack.components.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | const Components = require('./components.json'); 5 | 6 | module.exports = { 7 | entry: Components, 8 | output: { 9 | path: path.join(__dirname, './dist/lib'), 10 | filename: '[name].js', 11 | libraryTarget: 'commonjs2' 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.js$/, 17 | exclude: /node_modules/, 18 | loader: 'babel-loader' 19 | } 20 | ] 21 | }, 22 | 23 | externals: [{ 24 | 'react': { 25 | root: 'React', 26 | commonjs2: 'react', 27 | commonjs: 'react', 28 | amd: 'react' 29 | } 30 | }, { 31 | 'react-dom': { 32 | root: 'ReactDOM', 33 | commonjs2: 'react-dom', 34 | commonjs: 'react-dom', 35 | amd: 'react-dom' 36 | } 37 | }], 38 | 39 | resolve: { 40 | extensions: ['.js', '.jsx'], 41 | modules: [path.join(__dirname, './node_modules')], 42 | alias: { 43 | '@src': path.resolve(__dirname, './src') 44 | } 45 | }, 46 | 47 | plugins: [ 48 | new webpack.optimize.ModuleConcatenationPlugin() 49 | ] 50 | }; 51 | --------------------------------------------------------------------------------