├── .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 | 
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 |
2 |
3 |
4 |
多列
5 |
联级
6 |
区域
7 |
时间(内置)
8 |
日期(内置)
9 |
15 |
16 |
17 |
23 |
24 |
25 |
31 |
32 |
33 |
39 |
40 |
41 |
47 |
48 |
49 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{textCancel}}
10 | {{textConfirm}}
11 |
{{textTitle}}
12 |
13 |
24 |
25 |
26 |
27 |
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 |
--------------------------------------------------------------------------------