├── .babelrc ├── .eslintrc.js ├── .gitignore ├── README.md ├── dist ├── vue-awesome-picker.js └── vue-awesome-picker.js.map ├── index.html ├── manifest.json ├── package-lock.json ├── package.json ├── src ├── App.vue ├── area.js ├── lib │ ├── data │ │ ├── date.js │ │ ├── range.js │ │ └── time.js │ ├── index.js │ └── vue-awesome-picker.vue └── main.js ├── static └── img │ ├── qr-code.png │ └── vue-logo.png ├── sw.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint' 7 | }, 8 | env: { 9 | browser: true, 10 | }, 11 | extends: [ 12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 14 | 'plugin:vue/essential', 15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 16 | 'standard' 17 | ], 18 | // required to lint *.vue files 19 | plugins: [ 20 | 'vue' 21 | ], 22 | // add your custom rules here 23 | rules: { 24 | // allow async-await 25 | 'generator-star-spacing': 'off', 26 | // allow debugger during development 27 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log 4 | yarn-error.log 5 | 6 | # Editor directories and files 7 | .idea 8 | *.suo 9 | *.ntvs* 10 | *.njsproj 11 | *.sln 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-awesome-picker [![NPM Version][npm-image]][npm-url] [![NPM Downloads][downloads-image]][downloads-url] 2 | 基于 [Vue.js](https://github.com/vuejs/vue) & [Better-Scroll](https://github.com/ustbhuangyi/better-scroll) 的移动端 picker 组件 3 | 4 | ## Features 5 | * 支持单列、多列和联级数据 6 | * 内置时间、日期数据 7 | * 滚轮 3D 效果 8 | * 颜色可配置 9 | 10 | ## Demo 11 | >PS:Demo 已启用 Service Worker 12 | 13 | ![](./static/img/qr-code.png) 14 | 15 | ## Installation 16 | ``` bash 17 | npm install vue-awesome-picker --save 18 | ``` 19 | 20 | ## Usage 21 | ``` javascript 22 | /* main.js */ 23 | import AwesomePicker from 'vue-awesome-picker'; 24 | Vue.use(AwesomePicker); 25 | ``` 26 | ``` javascript 27 | /* 详细使用方法参照源码 App.vue */ 28 | 41 | 42 | ``` 43 | ``` javascript 44 | methods: { 45 | show() { 46 | this.$refs.picker.show(); 47 | } 48 | } 49 | ``` 50 | 51 | ## Props 52 | 53 | | 参数 | 描述 | 可选 | 类型 | 默认 54 | | ----- | ----- | ----- | ----- | ----- | 55 | | data | 详细描述见下文 || Array | 56 | | anchor | 详细描述见下文 || Array | 57 | | type | 内置 picker 类型
无需传入 data | date, time | String | 58 | | textTitle | title 文案 || String | 59 | | textConfirm | confirm 文案 || String | 确定 60 | | textCancel | cancel 文案 || String | 取消 61 | | colorTitle | title 颜色 || String | #000000 62 | | colorConfirm | confirm 颜色 || String | #42b983 63 | | colorCancel | cancel 颜色 || String | #999999 64 | | swipeTime | 滚动速度([better-scroll swipeTime](https://ustbhuangyi.github.io/better-scroll/doc/zh-hans/options.html#swipetime)) | | Number | 1800 65 | 66 | ### data 67 | >vue-awesome-picker 通过数据结构不同来区分是普通 picker 还是联级 picker, 所以请严格按照以下数据结构进行配置 68 | 69 | 单列、多列 picker 以双层数组的形式传入 data 70 | ``` javascript 71 | [ 72 | ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's','t', 'u', 'v', 'w', 'x', 'y', 'z'], 73 | ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S','T', 'U', 'V', 'W', 'X', 'Y', 'Z'] 74 | ] 75 | ``` 76 | 77 | 联级 picker 通过 children 构造出具有层级关系的数据 78 | ```javascript 79 | [ 80 | { 81 | value: 'A', 82 | children: [ 83 | { value: 'A-a' }, 84 | { value: 'A-b' }, 85 | { value: 'A-c' } 86 | ] 87 | }, 88 | { 89 | value: 'B', 90 | children: [ 91 | { value: 'B-a' }, 92 | { value: 'B-b' } 93 | ] 94 | }, 95 | ] 96 | ``` 97 | ### anchor 98 | >anchor 是 picker 展开时每一列默认滚动的锚点位置或值的数组, 兼容两种数据结构, 未匹配到默认选中第0项 99 | 100 | [推荐]数组对象形式: 与事件 confirm 返回的参数数据结构相同, 对象里可以只存在 index 或 value, 当存在 index 时优先匹配 index 101 | ```javascript 102 | [ 103 | { 104 | index: 0, 105 | value: 'A' 106 | }, 107 | { 108 | index: 0, 109 | value: 'A-a' 110 | } 111 | ] 112 | ``` 113 | 单层数组形式: index 组成的数组 114 | ```javascript 115 | [0, 0] 116 | ``` 117 | 118 | ## Methods 119 | | 方法 | 描述 | 120 | | ----- | ----- | 121 | | show | 展开显示 picker | 122 | 123 | ## Events 124 | | 事件 | 描述 | 参数 125 | | ----- | ----- | ----- 126 | | confirm | 点击 confirm 按钮后触发 | [{ index: xxx, value: xxx }...]
index: 当前选中的 item 在当列的 index
value: 当前选中 item 的 value 127 | | cancel | 点击 cancel 按钮后触发 | 128 | 129 | ## Development 130 | 131 | ``` bash 132 | git clone git@github.com:Fyerl/vue-awesome-picker.git 133 | cd vue-awesome-picker 134 | npm install 135 | npm run dev 136 | ``` 137 | 138 | [npm-image]: https://img.shields.io/npm/v/vue-awesome-picker.svg?style=flat 139 | [npm-url]: https://npmjs.org/package/vue-awesome-picker 140 | [downloads-image]: https://img.shields.io/npm/dt/vue-awesome-picker.svg?style=flat 141 | [downloads-url]: https://npmjs.org/package/vue-awesome-picker 142 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vue-awesome-picker 9 | 10 | 11 |
12 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-awesome-picker", 3 | "short_name": "AwesomePicker", 4 | "display": "standalone", 5 | "start_url": "./", 6 | "theme_color": "#ffffff", 7 | "background_color": "#ffffff", 8 | "icons": [ 9 | { 10 | "src": "./static/img/vue-logo.png", 11 | "sizes": "256x256", 12 | "type": "image/png" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-awesome-picker", 3 | "description": "A Vue.js Picker Component", 4 | "version": "1.1.0", 5 | "author": "Fyerl ", 6 | "license": "MIT", 7 | "private": false, 8 | "main": "dist/vue-awesome-picker.js", 9 | "keywords": [ 10 | "vue", 11 | "awesome", 12 | "picker", 13 | "cascade", 14 | "components", 15 | "better-scroll" 16 | ], 17 | "scripts": { 18 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot --host 0.0.0.0", 19 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules", 20 | "lint": "eslint --ext .js,.vue src" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/Fyerl/vue-awesome-picker" 25 | }, 26 | "dependencies": { 27 | "better-scroll": "^1.9.0", 28 | "reset-css": "^2.2.1", 29 | "vue": "^2.5.11" 30 | }, 31 | "devDependencies": { 32 | "babel-core": "^6.26.0", 33 | "babel-eslint": "^8.2.1", 34 | "babel-loader": "^7.1.2", 35 | "babel-preset-env": "^1.6.1", 36 | "babel-preset-es2015": "^6.24.1", 37 | "babel-preset-stage-2": "^6.24.1", 38 | "babel-preset-stage-3": "^6.24.1", 39 | "cross-env": "^5.0.5", 40 | "css-loader": "^0.28.7", 41 | "eslint": "^4.15.0", 42 | "eslint-config-standard": "^10.2.1", 43 | "eslint-friendly-formatter": "^3.0.0", 44 | "eslint-loader": "^1.7.1", 45 | "eslint-plugin-import": "^2.7.0", 46 | "eslint-plugin-node": "^5.2.0", 47 | "eslint-plugin-promise": "^3.4.0", 48 | "eslint-plugin-standard": "^3.0.1", 49 | "eslint-plugin-vue": "^4.0.0", 50 | "file-loader": "^1.1.4", 51 | "node-sass": "^4.5.3", 52 | "sass-loader": "^6.0.6", 53 | "vue-loader": "^13.0.5", 54 | "vue-template-compiler": "^2.4.4", 55 | "webpack": "^3.6.0", 56 | "webpack-dev-server": "^2.9.1" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 163 | 164 | 193 | -------------------------------------------------------------------------------- /src/lib/data/date.js: -------------------------------------------------------------------------------- 1 | import range from './range' 2 | 3 | const START_YEAR = 1900 4 | const END_YEAR = 2100 5 | 6 | const UNIT_YEAR = '年' 7 | const UNIT_MONTH = '月' 8 | const UNIT_DAY = '日' 9 | 10 | function isLeapYear (y) { 11 | return (y % 4 === 0) && (y % 100 !== 0 || y % 400 === 0) 12 | }; 13 | 14 | function getDays (y, m) { 15 | y = Number(y) 16 | m = Number(m) 17 | let endDay = null 18 | switch (m) { 19 | case 2: 20 | endDay = isLeapYear(y) ? 29 : 28; break 21 | case 1: 22 | case 3: 23 | case 5: 24 | case 7: 25 | case 8: 26 | case 10: 27 | case 12: 28 | endDay = 31; break 29 | case 4: 30 | case 6: 31 | case 9: 32 | case 11: 33 | default: 34 | endDay = 30; break 35 | } 36 | const days = range(1, endDay, false, UNIT_DAY) 37 | return days.map((day) => { 38 | return { value: day } 39 | }) 40 | }; 41 | 42 | const yearData = range(START_YEAR, END_YEAR, false, UNIT_YEAR) 43 | const monthData = range(1, 12, false, UNIT_MONTH) 44 | 45 | const cascadeMonthData = monthData.map((month) => { 46 | return { 47 | value: month, 48 | children: [] 49 | } 50 | }) 51 | 52 | const dateData = yearData.map((year) => { 53 | const item = { 54 | value: year, 55 | children: cascadeMonthData.slice() 56 | } 57 | item.children.forEach((month) => { 58 | month.children = getDays(year.slice(0, -1), month.value.slice(0, -1)) 59 | }) 60 | return item 61 | }) 62 | 63 | const date = new Date() 64 | const dateAnchor = [ 65 | { value: `${date.getFullYear()}${UNIT_YEAR}` }, 66 | { value: `${date.getMonth() + 1}${UNIT_MONTH}` }, 67 | { value: `${date.getDate()}${UNIT_DAY}` } 68 | ] 69 | 70 | export { 71 | dateAnchor, 72 | dateData 73 | } 74 | -------------------------------------------------------------------------------- /src/lib/data/range.js: -------------------------------------------------------------------------------- 1 | export default function (n, m, polyfill = false, unit = '') { 2 | let arr = [] 3 | for (let i = n; i <= m; i++) { 4 | let value = (polyfill && i < 10 ? '0' + i : i) + unit 5 | arr.push(value) 6 | } 7 | return arr 8 | } 9 | -------------------------------------------------------------------------------- /src/lib/data/time.js: -------------------------------------------------------------------------------- 1 | import range from './range' 2 | 3 | export default [range(0, 23, true, '点'), range(0, 59, true, '分'), range(0, 59, true, '秒')] 4 | -------------------------------------------------------------------------------- /src/lib/index.js: -------------------------------------------------------------------------------- 1 | import AwesomePicker from './vue-awesome-picker.vue' 2 | const picker = { 3 | install (Vue) { 4 | Vue.component(AwesomePicker.name, AwesomePicker) 5 | } 6 | } 7 | if (typeof window !== 'undefined' && window.Vue) { 8 | window.Vue.use(picker) 9 | } 10 | export default picker 11 | -------------------------------------------------------------------------------- /src/lib/vue-awesome-picker.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 325 | 326 | 484 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import Vue from 'vue' 3 | import App from './App.vue' 4 | import AwesomePicker from './lib/index.js' 5 | import 'reset-css' 6 | 7 | Vue.use(AwesomePicker) 8 | new Vue({ 9 | el: '#app', 10 | render: h => h(App) 11 | }) 12 | -------------------------------------------------------------------------------- /static/img/qr-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fyerl/vue-awesome-picker/ae463ff1231d8ca10414a96ead4f2cc9fd2ea698/static/img/qr-code.png -------------------------------------------------------------------------------- /static/img/vue-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fyerl/vue-awesome-picker/ae463ff1231d8ca10414a96ead4f2cc9fd2ea698/static/img/vue-logo.png -------------------------------------------------------------------------------- /sw.js: -------------------------------------------------------------------------------- 1 | const CACHE_NAME = 'vap-cache-7' 2 | const cacheUrls = [ 3 | './', 4 | './dist/vue-awesome-picker.js', 5 | './static/img/vue-logo.png' 6 | ] 7 | 8 | self.addEventListener('install', (event) => { 9 | event.waitUntil( // 确保在缓存之后完成 install 10 | caches.open(CACHE_NAME).then(cache => cache.addAll(cacheUrls)) 11 | ) 12 | }) 13 | 14 | self.addEventListener('fetch', (event) => { 15 | event.respondWith( 16 | caches.match(event.request).then((response) => { 17 | return response || fetch(event.request) 18 | }) 19 | ) 20 | }) 21 | 22 | self.addEventListener('activate', (event) => { 23 | event.waitUntil( 24 | caches.keys().then((cacheKeys) => { 25 | return Promise.all( 26 | cacheKeys.map((cacheKey) => { 27 | if (cacheKey !== CACHE_NAME) { 28 | return caches.delete(cacheKey) 29 | } 30 | }) 31 | ) 32 | }) 33 | ) 34 | }) -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | 4 | module.exports = { 5 | entry: './src/main.js', // dev 6 | // entry: './src/lib/index.js', // npm 7 | output: { 8 | path: path.resolve(__dirname, './dist'), 9 | publicPath: 'dist/', 10 | filename: 'vue-awesome-picker.js', 11 | library: 'VueAwesomePicker', 12 | libraryTarget: 'umd', 13 | umdNamedDefine: true 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.css$/, 19 | use: [ 20 | 'vue-style-loader', 21 | 'css-loader' 22 | ], 23 | }, 24 | { 25 | test: /\.scss$/, 26 | use: [ 27 | 'vue-style-loader', 28 | 'css-loader', 29 | 'sass-loader' 30 | ], 31 | }, 32 | { 33 | test: /\.sass$/, 34 | use: [ 35 | 'vue-style-loader', 36 | 'css-loader', 37 | 'sass-loader?indentedSyntax' 38 | ], 39 | }, 40 | { 41 | test: /\.vue$/, 42 | loader: 'vue-loader', 43 | options: { 44 | loaders: { 45 | // Since sass-loader (weirdly) has SCSS as its default parse mode, we map 46 | // the "scss" and "sass" values for the lang attribute to the right configs here. 47 | // other preprocessors should work out of the box, no loader config like this necessary. 48 | 'scss': [ 49 | 'vue-style-loader', 50 | 'css-loader', 51 | 'sass-loader' 52 | ], 53 | 'sass': [ 54 | 'vue-style-loader', 55 | 'css-loader', 56 | 'sass-loader?indentedSyntax' 57 | ] 58 | } 59 | // other vue-loader options go here 60 | } 61 | }, 62 | { 63 | test: /\.js$/, 64 | loader: 'babel-loader', 65 | exclude: /node_modules/ 66 | }, 67 | { 68 | test: /\.(png|jpg|gif|svg)$/, 69 | loader: 'file-loader', 70 | options: { 71 | name: '[name].[ext]?[hash]' 72 | } 73 | } 74 | ] 75 | }, 76 | resolve: { 77 | alias: { 78 | 'vue$': 'vue/dist/vue.esm.js' 79 | }, 80 | extensions: ['*', '.js', '.vue', '.json'] 81 | }, 82 | devServer: { 83 | historyApiFallback: true, 84 | noInfo: true, 85 | overlay: true 86 | }, 87 | performance: { 88 | hints: false 89 | }, 90 | devtool: '#eval-source-map' 91 | } 92 | 93 | if (process.env.NODE_ENV === 'production') { 94 | module.exports.devtool = '#source-map' 95 | // http://vue-loader.vuejs.org/en/workflow/production.html 96 | module.exports.plugins = (module.exports.plugins || []).concat([ 97 | new webpack.DefinePlugin({ 98 | 'process.env': { 99 | NODE_ENV: '"production"' 100 | } 101 | }), 102 | new webpack.optimize.UglifyJsPlugin({ 103 | sourceMap: true, 104 | compress: { 105 | warnings: false 106 | } 107 | }), 108 | new webpack.LoaderOptionsPlugin({ 109 | minimize: true 110 | }) 111 | ]) 112 | } 113 | --------------------------------------------------------------------------------