├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── README.zh-CN.md ├── build ├── bin │ ├── build-changelog.sh │ ├── build-components.js │ ├── build-entry.js │ ├── build-lib.js │ ├── build-style-entry.js │ └── get-components.js ├── release.sh ├── webpack.build.js ├── webpack.config.dev.js └── webpack.config.prod.js ├── components.list.md ├── docs ├── assets │ └── zanui.ico ├── examples-docs │ └── zh-CN │ │ ├── actionsheet.md │ │ ├── button.md │ │ ├── changelog.md │ │ ├── checkbox.md │ │ ├── drawer.md │ │ ├── icon.md │ │ ├── image-picker.md │ │ ├── input.md │ │ ├── layout.md │ │ ├── list.md │ │ ├── menu.md │ │ ├── modal.md │ │ ├── nav-bar.md │ │ ├── pagination.md │ │ ├── popover.md │ │ ├── progress.md │ │ ├── pulltorefresh.md │ │ ├── quickstart.md │ │ ├── radio.md │ │ ├── search-bar.md │ │ ├── share.md │ │ ├── stepper.md │ │ ├── swipeaction.md │ │ ├── switch.md │ │ ├── tabs.md │ │ ├── textarea.md │ │ └── toast.md ├── mock │ ├── area.json │ └── sku.js └── src │ ├── ExamplesApp.vue │ ├── ExamplesDocsApp.vue │ ├── components │ ├── demo-list.vue │ └── mobile-nav.vue │ ├── doc.config.js │ ├── examples.js │ ├── index.js │ ├── index.tpl │ ├── router.config.js │ └── utils │ ├── iframe-router.js │ ├── iframe.js │ ├── is-mobile.js │ └── lang.js ├── issue_template.md ├── package-lock.json ├── package.json ├── packages ├── actionsheet │ ├── actionsheet.vue │ └── index.js ├── button │ ├── button.vue │ └── index.js ├── checkbox │ ├── agree.vue │ ├── checkbox.vue │ ├── index.js │ └── item.vue ├── col │ ├── col.vue │ └── index.js ├── drawer │ └── index.js ├── icon │ ├── icon.vue │ ├── index.js │ └── loadSprite.js ├── imagePicker │ ├── image-picker.vue │ └── index.js ├── index.js ├── input │ ├── index.js │ └── input.vue ├── list │ ├── index.js │ └── list.vue ├── listItem │ ├── index.js │ └── listItem.vue ├── menu │ ├── index.js │ ├── menu.vue │ └── subMenu.vue ├── modal │ ├── index.js │ ├── modal.vue │ └── render.js ├── navbar │ ├── index.js │ └── nav-bar.vue ├── pagination │ ├── index.js │ └── pagination.vue ├── popover │ ├── index.js │ └── popover.vue ├── progress │ ├── index.js │ └── progress.vue ├── pulltorefresh │ ├── index.js │ └── pulltorefresh.vue ├── radio │ ├── index.js │ ├── item.vue │ └── radio.vue ├── row │ ├── index.js │ └── row.vue ├── searchBar │ ├── index.js │ └── search-bar.vue ├── share │ ├── index.js │ └── share.vue ├── stepper │ ├── index.js │ └── stepper.vue ├── swipeaction │ ├── index.js │ └── swipeaction.vue ├── switch │ ├── index.js │ └── switch.vue ├── tabPane │ └── index.js ├── tabs │ ├── index.js │ ├── tabPane.vue │ └── tabs.vue ├── textarea │ ├── index.js │ └── textarea.vue └── toast │ ├── index.js │ └── toast.vue ├── postcss.config.js ├── style ├── anim.less ├── images │ ├── arrow-down@2x.png │ ├── arrow-down@3x.png │ ├── arrow-up@2x.png │ ├── arrow-up@3x.png │ ├── arrow@2x.png │ ├── arrow@3x.png │ ├── check@2x.png │ ├── check@3x.png │ ├── check_w@2x.png │ ├── check_w@3x.png │ ├── cross@2x.png │ ├── cross@3x.png │ ├── cross_w@2x.png │ ├── cross_w@3x.png │ ├── error@2x.png │ ├── error@3x.png │ ├── more@2x.png │ ├── more@3x.png │ ├── more_w@2x.png │ ├── more_w@3x.png │ ├── search@2x.png │ └── search@3x.png ├── index.less ├── mixins.less ├── mixins │ ├── actionsheet.less │ ├── button.less │ ├── checkbox.less │ ├── drawer.less │ ├── hairline.less │ ├── icon.less │ ├── image-picker.less │ ├── input.less │ ├── list.less │ ├── menu.less │ ├── modal.less │ ├── nav-bar.less │ ├── pagination.less │ ├── popover.less │ ├── progress.less │ ├── pulltorefresh.less │ ├── radio.less │ ├── row.less │ ├── search-bar.less │ ├── share.less │ ├── stepper.less │ ├── swipeactioin.less │ ├── switch.less │ ├── tabs.less │ ├── textarea.less │ ├── toast.less │ └── util.less └── themes │ └── default.less ├── test └── unit │ ├── components │ ├── badge.vue │ ├── checkbox.vue │ ├── field.vue │ ├── goods-action.vue │ ├── more-tabs.vue │ ├── notice-bar.vue │ ├── number-keyboard.vue │ ├── radio.vue │ ├── row.vue │ ├── steps.vue │ ├── tabbar.vue │ ├── tabs.vue │ └── waterfall │ │ ├── waterfall-hide.vue │ │ └── waterfall.vue │ ├── get-webpack-conf.js │ ├── index.js │ ├── karma.conf.js │ ├── mock │ ├── area.json │ └── sku.js │ ├── selector.js │ ├── specs │ ├── actionsheet.spec.js │ ├── address-edit.spec.js │ ├── address-list.spec.js │ ├── area.spec.js │ ├── badge.spec.js │ ├── button.spec.js │ ├── card.spec.js │ ├── cell-swipe.spec.js │ ├── cell.spec.js │ ├── checkbox.spec.js │ ├── contact.spec.js │ ├── coupon.spec.js │ ├── datetime-picker.spec.js │ ├── dialog.spec.js │ ├── field.spec.js │ ├── goods-action.spec.js │ ├── icon.spec.js │ ├── image-preview.spec.js │ ├── layout.spec.js │ ├── loading.spec.js │ ├── nav-bar.spec.js │ ├── notice-bar.spec.js │ ├── number-keyboard.spec.js │ ├── password-input.spec.js │ ├── picker.spec.js │ ├── popup.spec.js │ ├── progress.spec.js │ ├── pull-refresh.spec.js │ ├── radio.spec.js │ ├── search.spec.js │ ├── sku.spec.js │ ├── stepper.spec.js │ ├── steps.spec.js │ ├── submit-bar.spec.js │ ├── switch-cell.spec.js │ ├── switch.spec.js │ ├── tabbar.spec.js │ ├── tabs.spec.js │ ├── tag.spec.js │ ├── toast.spec.js │ ├── tree-select.spec.js │ ├── uploader.spec.js │ └── waterfall.spec.js │ └── utils.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["env", { "modules": false, "loose": true }]], 3 | "plugins": ["transform-vue-jsx", "transform-runtime", "transform-object-rest-spread"], 4 | "env": { 5 | "commonjs": { 6 | "presets": [["env", { "modules": "commonjs", "loose": true }]] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | 13 | [*.js] 14 | indent_size = 2 15 | 16 | [*.vue] 17 | indent_size = 2 18 | 19 | [*.css] 20 | indent_size = 2 21 | 22 | [Makefile] 23 | indent_style = tab 24 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | dist/ 3 | node_modules/ 4 | build/**/*.js 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log* 2 | .cache 3 | .DS_Store 4 | .idea 5 | .vscode 6 | packages/**/lib 7 | lib/ 8 | lib/* 9 | !lib/index.js 10 | !lib/style.css 11 | node_modules 12 | example/dist 13 | /docs/dist 14 | test/unit/coverage 15 | packages/vant-css/build 16 | packages/vant-css/icons 17 | docs/examples-dist 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "7" 4 | before_install: 5 | - git config --global push.default matching 6 | - git config --global user.name "febobo" 7 | - git config --global user.email "584516601@qq.com" 8 | script: 9 | - npm run deploy:docs 10 | - cd docs 11 | - cd dist 12 | - git init 13 | - git add -A 14 | - git commit -m "Deploy GH" 15 | - git push -u https://$GH_TOKEN@github.com/vueClub/milk-vue.git HEAD:gh-pages --force 16 | cache: 17 | directories: 18 | - node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017-present Youzan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | The MIT License (MIT) 10 | 11 | Copyright (c) 2016 ElemeFE 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 |

5 |

6 | 7 | logo 8 | logo 9 |

10 |

11 | 12 |

13 | 14 | ## Docs 15 | [中文版](https://vueclub.github.io/milk-vue/#/) 16 | 17 | ## Install 18 | 19 | ```shell 20 | npm i milk-vue 21 | ``` 22 | 23 | ## Quickstart 24 | 25 | #### 1. Use [babel-plugin-import](https://github.com/ant-design/babel-plugin-import) (Recommended) 26 | 27 | ```bash 28 | # Install babel-plugin-import 29 | npm i babel-plugin-import -D 30 | ``` 31 | 32 | ```js 33 | // set babel config in .babelrc or babel-loader 34 | { 35 | "plugins": [ 36 | ["import", { "libraryName": "milk-vue", "style": true }] 37 | ] 38 | } 39 | ``` 40 | 41 | Then you can import components from vant, equivalent to import manually below. 42 | 43 | ```js 44 | import { Button } from 'milk-vue'; 45 | ``` 46 | 47 | #### 2. Manually import 48 | 49 | ```js 50 | import { Button } from 'milk-vue/lib/button'; 51 | ``` 52 | 53 | #### 3. Import all components 54 | 55 | ```js 56 | import Vue from 'vue'; 57 | import Milk from 'milk-vue'; 58 | import 'milk-vue/lib/milk-css/index.css'; 59 | 60 | Vue.use(Milk); 61 | ``` 62 | 63 | ## Contribution 64 | 65 | [Contributioner](https://github.com/vueClub/milk-vue/blob/master/components.list.md) 66 | 67 | ## Thanks 68 | 69 | + [Vue](https://github.com/vuejs/vue) 70 | + [WeUI](https://github.com/weui/weui) 71 | + [Ant Design Mobile](http://github.com/ant-design/ant-design-mobile) 72 | 73 | 74 | ## LICENSE 75 | 76 | [MIT](https://zh.wikipedia.org/wiki/MIT%E8%A8%B1%E5%8F%AF%E8%AD%89) 77 | 78 | 79 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 |

2 | 有赞logo 3 | 4 |

5 |

6 | 项目logo 7 |

8 |

A Vue.js 2.0 Mobile UI at YouZan

9 | 10 | [![Build Status](https://travis-ci.org/youzan/vant.svg?branch=master)](https://travis-ci.org/youzan/vant) [![Coverage Status](https://img.shields.io/codecov/c/github/youzan/vant/dev.svg)](https://codecov.io/github/youzan/vant?branch=dev) [![npm version](https://img.shields.io/npm/v/vant.svg?style=flat)](https://www.npmjs.com/package/vant) [![downloads](https://img.shields.io/npm/dt/vant.svg)](https://www.npmjs.com/package/vant) 11 | 12 | ## 一、安装 13 | 14 | ```shell 15 | npm i -S vant 16 | ``` 17 | 18 | ## 二、使用 19 | 20 | 21 | ### 使用 [babel-plugin-import](https://github.com/ant-design/babel-plugin-import) (推荐) 22 | 23 | ```js 24 | // .babelrc or babel-loader option 25 | { 26 | "plugins": [ 27 | ["import", { "libraryName": "vant", "style": true }] 28 | ] 29 | } 30 | ``` 31 | 32 |   接着你可以直接引入 vant 组件,等价于下方的按需引入组件 33 | 34 | ```js 35 |   // 模块化地引入 js 和 css, 通过 babel-plugin-import 插件解析 36 |   import { Button } from 'vant'; 37 | ``` 38 | 39 | ### 按需引入组件 40 | 41 | ```jsx 42 | import { Button } from 'vant/lib/button'; 43 | import 'vant/lib/vant-css/button.css'; 44 | ``` 45 | 46 | ### 导入所有组件 47 | 48 | ```javascript 49 | import Vue from 'vue'; 50 | import vant from 'vant'; 51 | import 'vant/lib/vant-css/index.css'; 52 | 53 | Vue.use(vant); 54 | ``` 55 | 56 | ## 三、贡献代码 57 | 58 | 修改代码请阅读我们的 [开发指南](./docs/examples-docs/zh-CN/contribute.md)。 59 | 60 | 使用过程中发现任何问题都可以提 [Issue](https://github.com/youzan/vant/issues) 给我们,当然,我们也非常欢迎你给我们发 [PR](https://github.com/youzan/vant/pulls)。 61 | 62 | ## 四、手机预览 63 | 64 | 可以手机扫码以下二维码访问手机端demo: 65 | 66 | ![zanui_vue_mobile_qrcode](https://img.yzcdn.cn/v2/image/youzanyun/zanui/pc/zanui_vue_mobile_preview_03.png) 67 | 68 | ## 五、开源协议 69 | 70 | 本项目基于 [MIT](https://zh.wikipedia.org/wiki/MIT%E8%A8%B1%E5%8F%AF%E8%AD%89) 协议,请自由地享受和参与开源。 71 | -------------------------------------------------------------------------------- /build/bin/build-changelog.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | if ! command_exists github_changelog_generator ; then 4 | fail 'github_changelog_generator is required to publish packages' 5 | fi 6 | 7 | if [ -z "$CHANGELOG_GITHUB_TOKEN" ] ; then 8 | fail 'You must set CHANGELOG_GITHUB_TOKEN environment variable\nhttps://github.com/skywinder/github-changelog-generator#github-token' 9 | fi 10 | 11 | basepath=$(dirname $0) 12 | 13 | github_changelog_generator \ 14 | --header-label "## 更新日志" \ 15 | --bugs-label "**Bug Fixes**" \ 16 | --enhancement-label "**Breaking changes**" \ 17 | --issues-label "**Issue**" \ 18 | --pr-label "**Improvements**" \ 19 | --no-unreleased \ 20 | -o $basepath/../../docs/examples-docs/zh-CN/changelog-generated.md 21 | -------------------------------------------------------------------------------- /build/bin/build-components.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 编译 components 到 lib 目录 3 | */ 4 | const fs = require('fs-extra'); 5 | const path = require('path'); 6 | const compiler = require('vue-sfc-compiler'); 7 | const libDir = path.resolve(__dirname, '../../lib'); 8 | const srcDir = path.resolve(__dirname, '../../packages'); 9 | require('shelljs/global'); 10 | 11 | // 清空 lib 目录 12 | fs.emptyDirSync(libDir); 13 | 14 | // 复制 packages 15 | fs.copySync(srcDir, libDir); 16 | 17 | // 编译所有 .vue 文件到 .js 18 | compileVueFiles(libDir); 19 | 20 | // babel 编译 21 | exec('cross-env BABEL_ENV=commonjs babel lib --out-dir lib'); 22 | 23 | function compileVueFiles(dir) { 24 | const files = fs.readdirSync(dir); 25 | 26 | files.forEach(file => { 27 | const absolutePath = path.resolve(dir, file); 28 | 29 | // 移除 vant-css 30 | if (file.indexOf('vant-css') !== -1) { 31 | fs.removeSync(absolutePath); 32 | } 33 | // 遍历文件夹 34 | else if (isDir(absolutePath)) { 35 | return compileVueFiles(absolutePath); 36 | } 37 | // 编译 .vue 文件 38 | else if (/\.vue$/.test(file)) { 39 | const source = fs.readFileSync(absolutePath, 'utf-8'); 40 | fs.removeSync(absolutePath); 41 | 42 | const outputVuePath = absolutePath + '.js'; 43 | const outputJsPath = absolutePath.replace('.vue', '.js'); 44 | const output = fs.existsSync(outputJsPath) ? outputVuePath : outputJsPath; 45 | fs.outputFileSync(output, compiler(source)); 46 | } 47 | }); 48 | } 49 | 50 | function isDir(dir) { 51 | return fs.lstatSync(dir).isDirectory(); 52 | } 53 | -------------------------------------------------------------------------------- /build/bin/build-entry.js: -------------------------------------------------------------------------------- 1 | var Components = require('./get-components')(); 2 | var fs = require('fs'); 3 | var render = require('json-templater/string'); 4 | var uppercamelcase = require('uppercamelcase'); 5 | var path = require('path'); 6 | 7 | var OUTPUT_PATH = path.join(__dirname, '../../packages/index.js'); 8 | var STYLE_PATH = '../style/index.less'; 9 | var IMPORT_TEMPLATE = 'import {{name}} from \'./{{package}}\';'; 10 | var IMPORT_STYLE_TEMPLATE = 'import \'{{package}}\';'; 11 | var ISNTALL_COMPONENT_TEMPLATE = ' {{name}}'; 12 | var MAIN_TEMPLATE = `{{include}} 13 | {{style}} 14 | 15 | const version = '{{version}}'; 16 | const components = [ 17 | {{components}} 18 | ]; 19 | 20 | const install = function(Vue) { 21 | if (install.installed) return; 22 | 23 | components.forEach(component => { 24 | Vue.component(component.name, component); 25 | }); 26 | }; 27 | 28 | /* istanbul ignore if */ 29 | if (typeof window !== 'undefined' && window.Vue) { 30 | install(window.Vue); 31 | } 32 | 33 | export { 34 | install, 35 | version, 36 | {{list}} 37 | }; 38 | export default { 39 | install, 40 | version 41 | }; 42 | `; 43 | 44 | delete Components.font; 45 | 46 | var includeComponentTemplate = []; 47 | var installTemplate = []; 48 | var listTemplate = []; 49 | var styleTemplate = []; 50 | var commonTemplate = []; 51 | var useTemplate = []; 52 | 53 | Components.forEach(name => { 54 | var componentName = uppercamelcase(name); 55 | 56 | includeComponentTemplate.push(render(IMPORT_TEMPLATE, { 57 | name: componentName, 58 | package: name 59 | })); 60 | 61 | if ([ 62 | // directives 63 | 'Lazyload', 64 | 'Waterfall', 65 | 66 | // services 67 | 'Dialog', 68 | 'Toast', 69 | 'ImagePreview' 70 | ].indexOf(componentName) === -1) { 71 | installTemplate.push(render(ISNTALL_COMPONENT_TEMPLATE, { 72 | name: componentName, 73 | component: name 74 | })); 75 | } 76 | 77 | listTemplate.push(` ${componentName}`); 78 | }); 79 | 80 | 81 | styleTemplate.push(render(IMPORT_STYLE_TEMPLATE, { 82 | package: STYLE_PATH 83 | })); 84 | 85 | var template = render(MAIN_TEMPLATE, { 86 | include: includeComponentTemplate.join('\n'), 87 | style: styleTemplate.join('\n'), 88 | list: listTemplate.join(',\n'), 89 | components: installTemplate.join(',\n') || ' ', 90 | version: process.env.VERSION || require('../../package.json').version 91 | }); 92 | 93 | fs.writeFileSync(OUTPUT_PATH, template); 94 | console.log('[build entry] DONE:' + OUTPUT_PATH); 95 | 96 | -------------------------------------------------------------------------------- /build/bin/build-lib.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Build npm lib 3 | * Steps: 4 | * 1. 代码格式校验 5 | * 2. 构建 JS 入口文件 6 | * 4. 构建每个组件对应的 [component].js 7 | * 4. 构建 vant-css 8 | * 5. 打包 JS 文件:vant.js && vant.min.js 9 | * 6. 生成每个组件目录下的 style 入口 10 | */ 11 | 12 | const chalk = require('chalk'); 13 | require('shelljs/global'); 14 | 15 | // 1. lint 16 | log('Starting', 'lint'); 17 | exec('npm run lint --silent'); 18 | log('Finished', 'lint'); 19 | 20 | // 2. build entry 21 | log('Starting', 'build:entry'); 22 | exec('npm run build:file --silent'); 23 | log('Finished', 'build:entry'); 24 | 25 | // 3. build [component].js 26 | log('Starting', 'build:component'); 27 | exec('npm run build:components --silent'); 28 | log('Finished', 'build:component'); 29 | 30 | // 4. build vant-css 31 | log('Starting', 'build:vant-css'); 32 | exec('npm run build:vant-css --silent'); 33 | log('Finished', 'build:vant-css'); 34 | 35 | // 5. build vant.js 36 | log('Starting', 'build:vant'); 37 | exec('npm run build:vant --silent'); 38 | log('Finished', 'build:vant'); 39 | 40 | // 6. build style entrys 41 | log('Starting', 'build:style-entries'); 42 | exec('npm run build:style-entry --silent'); 43 | log('Finished', 'build:style-entries'); 44 | 45 | // helpers 46 | function log(status, action, breakLine) { 47 | const now = new Date(); 48 | const clock = `${breakLine ? '\n' : ''}[${padZero(now.getHours())}:${padZero(now.getMinutes())}:${padZero(now.getSeconds())}]`; 49 | console.log(`${chalk.gray(clock)} ${status} '${action ? chalk.cyan.bold(action ) : ''}'`); 50 | } 51 | 52 | function padZero(num) { 53 | return (num < 10 ? '0' : '') + num; 54 | } 55 | -------------------------------------------------------------------------------- /build/bin/build-style-entry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 生成每个组件目录下的 style 入口 3 | */ 4 | 5 | const fs = require('fs-extra'); 6 | const path = require('path'); 7 | const components = require('./get-components')(); 8 | const source = require('../../lib/vant'); 9 | 10 | components.forEach(componentName => { 11 | const dependencies = analyzeDependencies(componentName); 12 | const styleDir = path.join(__dirname, '../../lib/', componentName, '/style'); 13 | const content = dependencies.map(component => `require('../../vant-css/${component}.css');`); 14 | fs.outputFileSync(path.join(styleDir, './index.js'), content.join('\n')); 15 | }); 16 | 17 | // 递归分析组件依赖 18 | // 样式引入顺序:基础样式, 组件依赖样式,组件本身样式 19 | function analyzeDependencies(componentName) { 20 | const checkList = ['base']; 21 | const search = component => { 22 | const componentSource = source[toPascal(component)]; 23 | if (componentSource && componentSource.components) { 24 | Object.keys(componentSource.components).forEach(name => { 25 | name = name.replace('van-', ''); 26 | if (checkList.indexOf(name) === -1) { 27 | search(name); 28 | } 29 | }); 30 | } 31 | if (checkList.indexOf(component) === -1) { 32 | checkList.push(component); 33 | } 34 | } 35 | 36 | search(componentName); 37 | return checkList.filter(component => checkComponentHasStyle(component)); 38 | } 39 | 40 | // 判断组件是否有样式 41 | function checkComponentHasStyle(componentName) { 42 | const cssPath = path.join(__dirname, '../../lib/vant-css/', `${componentName}.css`); 43 | return fs.existsSync(cssPath); 44 | } 45 | 46 | function toPascal(str) { 47 | return ('_' + str).replace(/[_.-](\w|$)/g, (_, x) => x.toUpperCase()); 48 | } 49 | -------------------------------------------------------------------------------- /build/bin/get-components.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | module.exports = function() { 5 | const dirs = fs.readdirSync(path.resolve(__dirname, '../../packages')); 6 | const excludes = ['index.js', 'vant-css', 'mixins', 'utils', '.DS_Store']; 7 | return dirs.filter(dirName => excludes.indexOf(dirName) === -1) 8 | } 9 | -------------------------------------------------------------------------------- /build/release.sh: -------------------------------------------------------------------------------- 1 | git checkout master 2 | git merge dev 3 | 4 | #!/usr/bin/env sh 5 | set -e 6 | echo "Enter release version: " 7 | read VERSION 8 | 9 | read -p "Releasing $VERSION - are you sure? (y/n)" -n 1 -r 10 | echo # (optional) move to a new line 11 | if [[ $REPLY =~ ^[Yy]$ ]] 12 | then 13 | echo "Releasing $VERSION ..." 14 | 15 | # build 16 | VERSION=$VERSION npm run dist 17 | 18 | # publish vant-css 19 | echo "Releasing vant-css $VERSION ..." 20 | cd packages/vant-css 21 | npm version $VERSION --message "[release] $VERSION" 22 | npm publish 23 | cd ../.. 24 | 25 | # commit 26 | git add -A 27 | git commit -m "[build] $VERSION" 28 | npm version $VERSION --message "[release] $VERSION" 29 | 30 | # publish 31 | git push origin master 32 | git push origin refs/tags/v$VERSION 33 | git checkout dev 34 | git rebase master 35 | git push origin dev 36 | 37 | npm publish 38 | fi 39 | -------------------------------------------------------------------------------- /build/webpack.build.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const config = require('./webpack.config.dev.js'); 3 | const isMinify = process.argv.indexOf('-p') !== -1; 4 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 5 | 6 | config.entry = { 7 | 'milk-vue': './packages/index.js' 8 | }; 9 | 10 | config.output = { 11 | filename: isMinify ? './lib/[name].min.js' : './lib/[name].js', 12 | library: 'vant', 13 | libraryTarget: 'umd', 14 | umdNamedDefine: true 15 | }; 16 | 17 | config.externals = { 18 | vue: { 19 | root: 'Vue', 20 | commonjs: 'vue', 21 | commonjs2: 'vue', 22 | amd: 'vue' 23 | } 24 | }; 25 | 26 | config.plugins = [ 27 | new webpack.DefinePlugin({ 28 | 'process.env.NODE_ENV': '"production"' 29 | }), 30 | new webpack.LoaderOptionsPlugin({ 31 | minimize: true, 32 | debug: false, 33 | options: { 34 | vue: { 35 | autoprefixer: false, 36 | preserveWhitespace: false 37 | } 38 | } 39 | }), 40 | new webpack.optimize.ModuleConcatenationPlugin() 41 | ]; 42 | 43 | // analyze bundle size if need 44 | // if (isMinify) { 45 | // config.plugins.push(new BundleAnalyzerPlugin()); 46 | // } 47 | 48 | delete config.devtool; 49 | 50 | module.exports = config; 51 | -------------------------------------------------------------------------------- /build/webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const merge = require('webpack-merge'); 3 | const path = require('path'); 4 | const devConfig = require('./webpack.config.dev.js'); 5 | 6 | module.exports = merge(devConfig, { 7 | output: { 8 | path: path.join(__dirname, '../docs/dist'), 9 | publicPath: '/milk-vue/', 10 | filename: '[name].[hash:8].js', 11 | umdNamedDefine: true, 12 | chunkFilename: 'async_[name].[chunkhash:8].js' 13 | }, 14 | devtool: false, 15 | plugins: [ 16 | new webpack.DefinePlugin({ 17 | 'process.env': { 18 | NODE_ENV: JSON.stringify(process.env.NODE_ENV) 19 | } 20 | }), 21 | // new webpack.optimize.UglifyJsPlugin({ 22 | // compress: { 23 | // warnings: false, 24 | // drop_console: true 25 | // }, 26 | // output: { 27 | // comments: false 28 | // }, 29 | // sourceMap: true 30 | // }) 31 | ] 32 | }); 33 | -------------------------------------------------------------------------------- /components.list.md: -------------------------------------------------------------------------------- 1 | 2 | ## Milk UI 模块列表 3 | 4 | - [ ] 表示未开始 5 | - [x] 表示进行中 6 | - [x] ~~表示已结束~~ 7 | 8 | ## 布局 9 | - [x] ~~Layout 布局~~ [@liujian10](https://github.com/liujian10) 10 | - [x] ~~Icon 图标~~ [@liujian10](https://github.com/liujian10) 11 | - [x] ~~List 列表~~ [@liujian10](https://github.com/liujian10) 12 | - [ ] Swipe 轮播图 13 | - [ ] SearchBar 搜索栏 14 | 15 | ## 导航 16 | - [x] ~~NavBar 导航栏~~ [@liujian10](https://github.com/liujian10) 17 | - [x] ~~Drawer 抽屉~~ [@liujian10](https://github.com/liujian10) 18 | - [x] ~~Menu 菜单~~ [@liujian10](https://github.com/liujian10) 19 | - [x] ~~Popover 气泡~~ [@liujian10](https://github.com/liujian10) 20 | - [ ] Tabs 标签页 21 | - [ ] TabBar 标签栏 22 | - [ ] Steps 步骤条 23 | 24 | ## 数据录入 25 | - [x] ~~Switch 开关~~ [@liujian10](https://github.com/liujian10) 26 | - [x] ~~Button 按钮~~ [@liujian10](https://github.com/liujian10) 27 | - [x] ~~Input 输入~~ [@liujian10](https://github.com/liujian10) 28 | - [x] ~~Textarea 多行输入~~ [@liujian10](https://github.com/liujian10) 29 | - [x] ~~Radio 单选框~~ [@liujian10](https://github.com/liujian10) 30 | - [x] ~~Checkbox 复选框~~ [@liujian10](https://github.com/liujian10) 31 | - [ ] Calendar 日历 32 | - [ ] DatePicker 日期选择 33 | - [ ] Picker 选择器 34 | - [ ] ImagePicker 图片选择器 [@bulgerxie](https://github.com/bulgerxie) 35 | - [ ] Range 区域选择 36 | - [ ] SearchBar 搜索栏 37 | - [x] ~~Stepper 步进器~~ [@zyl1314](https://github.com/zyl1314) 38 | - [ ] Slider 滑动输入条 39 | 40 | ## 操作反溃 41 | - [ ] Progress 进度条 42 | - [ ] ActionSheet 动作面板 43 | - [ ] Modal 对话框 [@lzy1043](https://github.com/lzy1043) 44 | - [ ] ActivityIndicator 活动指示器 45 | - [x] ~~Toast 吐司~~ [@liujian10](https://github.com/liujian10) 46 | 47 | ## 手势操作 48 | - [ ] PullToRefresh 拉动刷新 49 | - [x] SwipeAction 滑动操作 [@ttian226](https://github.com/ttian226) 50 | 51 | 52 | ## 业务组件 (待更新) 53 | - [ ] 个人中心 54 | 55 | ## 集成SDK 56 | - [ ] 分享 - 支持主流社区分享 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /docs/assets/zanui.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/docs/assets/zanui.ico -------------------------------------------------------------------------------- /docs/examples-docs/zh-CN/actionsheet.md: -------------------------------------------------------------------------------- 1 | 19 | 20 | ## Actionsheet 动作面板 21 | 22 | ### 使用指南 23 | 24 | ```javascript 25 | import { Actionsheet } from 'milk-vue'; 26 | Vue.component(Actionsheet); 27 | ``` 28 | 29 | ### 代码演示 30 | 31 | ```javascript 32 | export default { 33 | data() { 34 | return { 35 | show: false, 36 | message: 'I am description, description, description', 37 | options: ['Operation1', 'Operation2', 'Operation2'], 38 | cancelText: '取消', 39 | maskClosable: true 40 | } 41 | }, 42 | methods: { 43 | showActionSheet() { 44 | this.show = true 45 | } 46 | } 47 | } 48 | ``` 49 | 50 | :::demo 基本 51 | ```html 52 | showActionSheet 53 | 54 | ``` 55 | 56 | ::: 57 | 58 | ### API 59 | 60 | | 参数 | 说明 | 类型 | 默认值 | 可选值 | 61 | |------------|-----------|-----------|-------------|-------------| 62 | | show | 是否显示actionsheet | `Boolean` | `false` | `true` `false` | 63 | | message | 顶部标题下的简要消息 | `String` | - | - | 64 | | options | 按钮标题列表 | `Array` | - | - | 65 | | cancel-text | 取消按钮文案 | `String` | - | - | 66 | | mask-closable | 点击遮罩是否关闭 | `Boolean` | `true` | `true` `false` | 67 | 68 | -------------------------------------------------------------------------------- /docs/examples-docs/zh-CN/button.md: -------------------------------------------------------------------------------- 1 | 26 | 27 | ## Button 按钮 28 | 29 | ### 使用指南 30 | ``` javascript 31 | import { Button } from 'milk-vue'; 32 | Vue.component(Button.name, Button); 33 | ``` 34 | 35 | ### 代码演示 36 | 37 | #### 按钮类型 38 | 39 | 支持`default`、`primary`、`danger`三种类型,默认为`default` 40 | 41 | :::demo 按钮类型 42 | ```html 43 |
44 | default 45 | Primary 46 | Danger 47 | Ghost 48 |
49 | ``` 50 | ::: 51 | 52 | #### 按钮尺寸 53 | 54 | 默认为`normal`,可选`small` 55 | 56 | :::demo 按钮尺寸 57 | ```html 58 |
59 | inline normal 60 | inline normal 61 | inline small 62 | inline small 63 |
64 | ``` 65 | ::: 66 | 67 | 68 | #### 禁用状态 69 | 70 | 通过`disabled`属性来禁用按钮,此时按钮不可点击 71 | 72 | :::demo 禁用状态 73 | ```html 74 |
75 | Diabled 76 |
77 | ``` 78 | ::: 79 | 80 | #### 加载状态 81 | 82 | :::demo 加载状态 83 | ```html 84 |
85 | loading 86 |
87 | ``` 88 | ::: 89 | 90 | #### 自定义按钮标签 91 | 92 | 按钮标签默认为`button`,可以使用`tag`属性来修改按钮标签 93 | 94 | #### 场景示例 95 | 96 | :::demo 场景 97 | ```html 98 | 99 | 100 | Title 101 |
102 | button 103 |
104 |
105 |
106 | ``` 107 | ::: 108 | 109 | ### API 110 | 111 | | 参数 | 说明 | 类型 | 默认值 | 可选值 | 112 | |-----------|-----------|-----------|-------------|-------------| 113 | | type | 按钮类型 | `String` | `default` | `primary` `danger` | 114 | | size | 按钮尺寸 | `String` | `normal` | `normal` `small` | 115 | | tag | 按钮标签 | `String` | `button` | 任意`HTML`标签 | 116 | | disabled | 是否禁用 | `Boolean` | `false` | - | 117 | | loading | 是否显示为加载状态 | `Boolean` | `false` | - | 118 | -------------------------------------------------------------------------------- /docs/examples-docs/zh-CN/changelog.md: -------------------------------------------------------------------------------- 1 | ## 更新日志 -------------------------------------------------------------------------------- /docs/examples-docs/zh-CN/image-picker.md: -------------------------------------------------------------------------------- 1 | 10 | 33 | 34 | ## ImagePicker 图片选择器 35 | 注意:只是图片选择器,一般用于上传图片前的文件选择操作,但不包括图片上传的功能 36 | 37 | ### 使用指南 38 | ``` javascript 39 | import { ImagePicker } from 'milk-vue'; 40 | Vue.component(ImagePicker.name, ImagePicker); 41 | ``` 42 | 43 | ### 代码演示 44 | ```javascript 45 | export default { 46 | data(){ 47 | return { 48 | files: [ 49 | { 50 | url: 'https://zos.alipayobjects.com/rmsportal/PZUUCKTRIHWiZSY.jpeg' 51 | } 52 | ], 53 | multiple: true, 54 | radioValue: false 55 | } 56 | }, 57 | methods:{ 58 | onClick:function(index){ 59 | console.log(index); 60 | }, 61 | onChange:function(files, type, index){ 62 | console.log(files, type, index); 63 | } 64 | } 65 | } 66 | ``` 67 | 68 | ### 图片选择 69 | :::demo 70 | ```html 71 |
72 | 73 | 74 |
75 |
76 | 77 |
78 | ``` 79 | ::: 80 | 81 | ### Attributes 82 | | 参数 | 说明 | 类型 | 默认值 | 83 | |-----------|-----------|-----------|-------------| 84 | | files | 图片文件数组,元素为对象 | `Array` | `[]` | 85 | | selectable | 是否显示选择按钮 | `Boolean` | `true` | 86 | | removeable | 是否显示移除按钮 | `Boolean` | `true` | 87 | | multiple | 是否支持多选 | `Boolean` | `false` | 88 | 89 | 90 | ### Events 91 | | 参数 | 说明 | 类型 | 默认值 | 92 | |-----------|-----------|-----------|-------------| 93 | | change | files 值发生变化触发的回调函数, type 操作类型有添加,移除,如果是移除操作,则第三个参数代表的是移除图片的索引 | `(files: Object, operationType: string, index: number): void` || 94 | | image-click | 图片被点击时的回调, | `(index: number): void` | | 95 | | fail | 选择图片错误时的回调 | `(error: object): void` | | 96 | -------------------------------------------------------------------------------- /docs/examples-docs/zh-CN/nav-bar.md: -------------------------------------------------------------------------------- 1 | 11 | 12 | ## NavBar 抽屉 13 | 14 | ### 使用指南 15 | 16 | ```javascript 17 | import { NavBar } from 'milk-vue'; 18 | Vue.component(NavBar.name, NavBar); 19 | ``` 20 | 21 | ### 代码演示 22 | 23 | ```javascript 24 | import { Toast } from 'packages'; 25 | export default { 26 | methods:{ 27 | clickToast:function(info){ 28 | Toast.info(info); 29 | } 30 | } 31 | } 32 | ``` 33 | 34 | :::demo 基本 35 | ```html 36 | 37 | 38 | 39 | 40 | back 41 | NavBar 42 |
43 | 44 | 45 |
46 |
47 | ``` 48 | ::: 49 | 50 | ### API 51 | 52 | | 参数 | 说明 | 类型 | 默认值 | 可选值 | 53 | |-----------|-----------|-----------|-------------|-------------| 54 | | mode | 模式 | `String` | `dark` | `dark` `light` | 55 | | title | 标题 | `String` | - | - | 56 | | icon | 左侧图标 | `String` | - | icon type | 57 | | icon-click | 左侧图标点击事件 | `Function` | - | - | 58 | 59 | ### Slot 60 | 61 | | name | 描述 | 62 | |------|------| 63 | | default | 自定义标题内容,如果存在会覆盖 `title` 值 | 64 | | left | 自定义左侧内容 | 65 | | right | 自定义右侧内容 | 66 | 67 | ### Event 68 | 69 | | 事件名称 | 说明 | 回调参数 | 70 | |-----------|-----------|-----------| 71 | | icon-click | 设置icon后,点击左侧图标时触发 | - | 72 | -------------------------------------------------------------------------------- /docs/examples-docs/zh-CN/pagination.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | 24 | 25 | ## Pagination 分页器 26 | 27 | ### 使用指南 28 | 29 | ```javascript 30 | import { Pagination } from 'milk-vue'; 31 | Vue.component(Pagination.name, Pagination); 32 | ``` 33 | 34 | ### 代码演示 35 | 36 | ```javascript 37 | import { Toast } from 'packages'; 38 | export default { 39 | data(){ 40 | return { 41 | modelValue:1 42 | } 43 | }, 44 | methods:{ 45 | consolePage:function(info){ 46 | console.log(info); 47 | } 48 | } 49 | } 50 | ``` 51 | 52 | #### 默认模式 53 | 54 | 包含上下页按钮,页码信息 55 | 56 | :::demo 基本 57 | ```html 58 | 59 | ``` 60 | ::: 61 | 62 | #### 按钮模式 63 | 64 | 只包含上下页按钮,可结合`v-model`自定义显示页码信息 65 | 66 | :::demo button 67 | ```html 68 | Page: {{modelValue}} 69 | ``` 70 | ::: 71 | 72 | #### 数字模式 73 | 74 | 只包含页码信息,可结合`v-model`实现翻页操作 75 | 76 | :::demo number 77 | ```html 78 | 79 | ``` 80 | ::: 81 | 82 | #### 指示器模式 83 | 84 | 只包含指示器信息,可结合`v-model`实现翻页操作 85 | 86 | :::demo pointer 87 | ```html 88 | 89 | ``` 90 | ::: 91 | 92 | #### 自定义 93 | 94 | 结合不同模式下的`slot`可实现不一样的定制分页器 95 | 96 | :::demo custom 97 | ```html 98 | 99 | 100 | 101 | 102 | ``` 103 | ::: 104 | 105 | ### API 106 | 107 | | 参数 | 说明 | 类型 | 默认值 | 可选值 | 108 | |-----------|-----------|-----------|-------------|-------------| 109 | | mode | 模式 | `String` | `normal` | `normal` `button` `number` `pointer` | 110 | | v-model | 数据绑定页数 | `Number` | - | - | 111 | | current | 数据绑定页数 | `Number` | `1` | - | 112 | | total | 总页数 | `Number` | `0` | - | 113 | | prev-text | 上页按钮文案 | `String` | `上页` | - | 114 | | next-text | 下页按钮文案 | `String` | `下页` | - | 115 | 116 | ### Slot 117 | 118 | | name | 描述 | 119 | |------|------| 120 | | default | 自定义上页按钮,只在`button`下有效 | 121 | | prev | 自定义上页按钮,只在`number`,`pointer`下有效 | 122 | | next | 自定义下页按钮,只在`number`,`pointer`下有效 | 123 | 124 | ### Event 125 | 126 | | 事件名称 | 说明 | 回调参数 | 127 | |-----------|-----------|-----------| 128 | | change | 点击上下页按钮时触发 | `Number` 变化后的页码 | 129 | -------------------------------------------------------------------------------- /docs/examples-docs/zh-CN/popover.md: -------------------------------------------------------------------------------- 1 | 18 | 19 | ## Popover 气泡 20 | 21 | ### 使用指南 22 | 23 | ```javascript 24 | import { Popover } from 'milk-vue'; 25 | Vue.component(Popover.name, Popover); 26 | ``` 27 | 28 | ### 代码演示 29 | 30 | ```javascript 31 | import { Toast } from 'packages'; 32 | export default { 33 | data(){ 34 | return { 35 | open:false, 36 | listData:["search","info-circle","question-circle"] 37 | } 38 | }, 39 | methods:{ 40 | clickToast:function(info){ 41 | Toast.info(info); 42 | this.open=false; 43 | } 44 | } 45 | } 46 | ``` 47 | 48 | #### 基本用法 49 | 50 | :::demo 基本 51 | ```html 52 | 53 | 54 | 55 | 56 | 63 | {{item}} 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 80 | {{item}} 81 | 82 | 83 | 84 | 85 | ``` 86 | ::: 87 | 88 | ### API 89 | 90 | | 参数 | 说明 | 类型 | 默认值 | 可选值 | 91 | |-----------|-----------|-----------|-------------|-------------| 92 | | open | 是否默认显示气泡 | `Boolean` | `false` | `false` `true` | 93 | | disable | 是否禁用 | `Boolean` | `false` | `false` `true` | 94 | | offset | 偏移距离,单位px | `Number` | - | - | 95 | 96 | 更多 API 可查看 [v-tooltip](https://www.npmjs.com/package/v-tooltip) 97 | -------------------------------------------------------------------------------- /docs/examples-docs/zh-CN/progress.md: -------------------------------------------------------------------------------- 1 | 14 | 32 | 33 | ## Pregress 进度条 34 | 35 | ### 使用指南 36 | 37 | ```javascript 38 | import { Progress } from 'milk-vue'; 39 | Vue.component(Progress.name, Progress); 40 | ``` 41 | 42 | ### 代码演示 43 | 44 | ```javascript 45 | export default { 46 | data(){ 47 | return { 48 | percent: 40 49 | } 50 | }, 51 | methods:{ 52 | add() { 53 | if(this.percent >= 100) { 54 | this.percent = 0 55 | }else { 56 | this.percent += 10 57 | } 58 | } 59 | } 60 | } 61 | ``` 62 | 63 | :::demo 基本 64 | ```html 65 | 66 | 67 | 68 |
69 |
70 | 71 |
72 | 73 |
74 | (+-)10 75 | ``` 76 | ::: 77 | 78 | ### API 79 | 80 | | 参数 | 说明 | 类型 | 默认值 | 可选值 | 81 | |-----------|-----------|-----------|-------------|-------------| 82 | | percent | 进度条百分比 | `Number` | `0` | - | 83 | | position | 进度条的位置,fixed 将浮出固定在最顶层,可选: fixed normal | `String` | `fixed` | `fixed` `normal` | 84 | | unfilled | 是否显示未填充轨道 | `Boolean` | `true` | `true` `false` | 85 | -------------------------------------------------------------------------------- /docs/examples-docs/zh-CN/pulltorefresh.md: -------------------------------------------------------------------------------- 1 | 4 | 21 | ## PullToRefresh 拉动刷新 22 | 23 | ### 使用指南 24 | ```javascript 25 | import { Pulltorefresh } from 'milk-vue'; 26 | Vue.component(Pulltorefresh.name, Pulltorefresh); 27 | ``` 28 | 29 | ### 代码演示 30 | 31 | :::demo 32 | ```html 33 | {{ down ? 'down' : 'up' }} 34 | 40 |
45 | {{ down ? 'pull down' : 'pull up' }} {{index}} 46 |
47 |
48 | ``` 49 | ::: 50 | 51 | ### API 52 | 53 | | 属性 | 说明 | 类型 | 默认值 | 54 | |-----------|-----------|-----------|-------------| 55 | | direction | 拉动方向可以是`up`或`down` | `String` | `down` | 56 | | distanceToRefresh | 刷新距离 | `number` | 25 | 57 | | refreshing | 是否显示刷新状态 | `bool` | `false` | 58 | | onRefresh | 刷新回调函数 | `(): void` | | 59 | | indicator | 指示器配置 | `Object` | | -------------------------------------------------------------------------------- /docs/examples-docs/zh-CN/quickstart.md: -------------------------------------------------------------------------------- 1 | ## Milk 2 | 基于`Vue 2.0`的 Mobile 组件库 3 | 4 | ### 安装 5 | 6 | ```shell 7 | npm i milk-vue --save-dev 8 | ``` 9 | 10 | ### CDN 11 | 12 | [https://unpkg.com/milk-vue/lib/milk-vue.min.js](https://unpkg.com/milk-vue/lib/milk-vue.min.js) 13 | 14 | ``` shell 15 | https://unpkg.com/milk-vue/lib/milk-vue.min.js 16 | ``` 17 | 18 | 19 | 我们建议使用 CDN 引入 Milk 的用户在链接地址上锁定版本,以免将来 Milk 升级时受到非兼容性更新的影响。锁定版本的方法请查看 [unpkg.com](https://unpkg.com)。 20 | 21 | ### Hello Milk (Example) 22 | 23 | 通过 CDN 的方式我们可以很容易地使用 Milk 写出一个 Hello Milk 页面 24 | 25 | ``` html 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | Hello Milk! 34 |
35 | 36 | 37 | 39 | 40 | 42 | 43 | 48 | 49 | ``` 50 | 51 | ### 引入组件 52 | 53 | #### 方式一. 使用 [babel-plugin-import](https://github.com/ant-design/babel-plugin-import) (推荐) 54 | ```bash 55 | # 安装 babel-plugin-import 插件 56 | npm i babel-plugin-import -D 57 | ``` 58 | 59 | ```js 60 | // 在 .babelrc 或 babel-loader 中添加插件配置 61 | { 62 | "plugins": [ 63 | ["import", { "libraryName": "milk-vue", "style": true }] 64 | ] 65 | } 66 | ``` 67 | 68 | 接着你可以在代码中直接引入 Milk 组件,插件会自动将代码转化为方式二中的按需引入形式。 69 | 70 | ```js 71 | import { Button } from 'milk-vue'; 72 | ``` 73 | 74 | #### 方式二. 按需引入组件 75 | 76 | ```js 77 | import { Button } from 'milk-vue/lib/button'; 78 | import 'milk-vue/lib/milk-vue-css/button.css'; 79 | ``` 80 | 81 | #### 方式三. 导入所有组件 82 | 83 | ```js 84 | import Vue from 'vue'; 85 | import Milk from 'milk-vue'; 86 | import 'milk-vue/lib/milk-vue/index.css'; 87 | 88 | Vue.use(Milk); 89 | ``` 90 | 91 | ### vue-cli 模板 92 | 可以使用`vue-cli`来初始化`milk-vue`的通用模板: 93 | 94 | -------------------------------------------------------------------------------- /docs/examples-docs/zh-CN/search-bar.md: -------------------------------------------------------------------------------- 1 | 6 | 31 | ## SearchBar 搜索栏 32 | 一般用于导航栏下方,点击『取消按钮』退出激活状态 33 | 34 | ### 使用指南 35 | ``` javascript 36 | import { SearchBar } from 'milk-vue'; 37 | Vue.component(SearchBar.name, SearchBar); 38 | ``` 39 | 40 | ### 代码演示 41 | ``` javascript 42 | export default { 43 | data(){ 44 | return { 45 | value: '', 46 | showCancelButton: true, 47 | autofocus: true 48 | } 49 | }, 50 | methods:{ 51 | onSubmit:function(e) { 52 | console.log(e); 53 | }, 54 | onChange:function(e) { 55 | console.log(e); 56 | }, 57 | onCancel:function(e) { 58 | console.log(e); 59 | }, 60 | onClear:function(e) { 61 | console.log(e); 62 | } 63 | } 64 | } 65 | ``` 66 | 67 | 68 | #### normal 69 | ::: demo normal 70 | ```html 71 |
72 | 79 | 80 |
81 | ::: 82 | 83 | #### 自动获取焦点 84 | ::: demo 自动获取焦点 85 | ```html 86 |
87 | 90 | 91 |
92 | ::: 93 | 94 | #### 显示取消按钮 95 | ::: demo 显示取消按钮 96 | ```html 97 |
98 | 102 | 103 |
104 | ::: 105 | 106 | ### Attributes 107 | 108 | | 参数 | 说明 | 类型 | 默认值 | 109 | |-----------|-----------|-----------|-------------| 110 | | defaultValue | 搜索框当前默认值 | `String` | | 111 | | value | 绑定值 | `String` | | 112 | | placeholder | placeholder | `String` || 113 | | cancelText | 取消按钮的文字 | `String` | `取消` | 114 | | showCancelButton | 是否一直显示取消按钮 | `Boolean` | `false` | 115 | | disabled | 禁用搜索框 | `Boolean` | `false` | 116 | | maxLength | 最多允许输入的字符个数 | `Number` | | 117 | 118 | 119 | ### Events 120 | | 参数 | 说明 | 类型 | 默认值 | 121 | |-----------|-----------|-----------|-------------| 122 | | submit | submit 事件回调 (点击键盘的 enter) | `(val: string): void`,如果绑定值为空,就返回默认值 `defaultValue` | | 123 | | change | change 事件回调 | `(val: string): void` | | 124 | | focus | focus 事件回调 | | | 125 | | blur | blur 事件回调 | | | 126 | | clear | clear 事件回调 | `(val: string): void` | | 127 | | cancel | cancel 事件回调 | `(val: string): void` | | 128 | -------------------------------------------------------------------------------- /docs/examples-docs/zh-CN/share.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | 25 | 26 | 27 | ## share 第三方分享 28 | 29 | 目前只支持微信、微博、QQ、知乎四个平台 30 | 31 | ### 使用指南 32 | ``` javascript 33 | import { Share } from 'milk-vue'; 34 | Vue.component(Share.name, Share); 35 | ``` 36 | 37 | :::demo 38 | ```html 39 |
40 | 41 |
42 | ``` 43 | ::: 44 | 45 | ### 启用部分 46 | 47 | :::demo 48 | ```html 49 |
50 | 51 |
52 | ``` 53 | ::: 54 | 55 | ### API 56 | 57 | | 参数 | 说明 | 类型 | 默认值 | 可选值 | 58 | |-----------|-----------|-----------|-------------|-------------| 59 | | shareList | 第三方列表 | Array | - | - | 60 | | type | 样式类型,bloom类型下建议分享按钮不超过4个 | String | default | default/bloom | 61 | | btntext | bloom类型下按钮文字,可以使用图标 | String | 分享 | - | 62 | 63 | shareList 默认值为 [{key: 'wx',title: '微信'}, {key: 'wb',title: '微博'}, {key: 'qq',title: 'QQ'}, {key: 'douban',title: '豆瓣'}] 64 | 65 | 可以根据需求启用相应的第三方分享 66 | 67 | -------------------------------------------------------------------------------- /docs/examples-docs/zh-CN/stepper.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | ## Stepper 步进器 24 | 25 | ### 使用指南 26 | ``` javascript 27 | import { Stepper } from 'milk-vue'; 28 | Vue.component(Stepper.name, Stepper); 29 | ``` 30 | 31 | ### 代码演示 32 | 33 | #### 步进范围 34 | 35 | 默认为`-Infinity`到`Infinity` 36 | 37 | :::demo 步进范围 38 | ```html 39 |
40 | 41 |
42 | ``` 43 | ::: 44 | 45 | #### 禁用状态 46 | 47 | 通过`disabled`属性来禁用步进器 48 | 49 | :::demo 禁用状态 50 | ```html 51 |
52 | 53 |
54 | ``` 55 | ::: 56 | 57 | #### 输入框只读 58 | 59 | :::demo 输入框只读 60 | ```html 61 |
62 | 63 |
64 | ``` 65 | ::: 66 | 67 | ### API 68 | 69 | | 参数 | 说明 | 类型 | 默认值 | 70 | |-----------|-----------|-----------|-------------| 71 | | min | 最小值 | `Number` | `-Infinity` | 72 | | max | 最大值 | `Number` | `Infinity` | 73 | | value | 当前值 | `Number` | 无 | 74 | | step | 每次改变的步数 | `Number` | `1` | 75 | | defaultValue | 默认初始值 | `Number` | `1` | 76 | | onChange | 变化时回调函数 | `Function` | 无 | 77 | | disabled | 禁用 | `Boolean` | `false` | 78 | | readonly | input只读 | `Boolean` | `false` | 79 | -------------------------------------------------------------------------------- /docs/src/ExamplesApp.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | -------------------------------------------------------------------------------- /docs/src/ExamplesDocsApp.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 47 | 48 | 66 | -------------------------------------------------------------------------------- /docs/src/components/demo-list.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 49 | 50 | 81 | -------------------------------------------------------------------------------- /docs/src/components/mobile-nav.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 64 | 65 | 142 | -------------------------------------------------------------------------------- /docs/src/examples.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | import App from './ExamplesApp'; 4 | import routes from './router.config'; 5 | import { setLang } from './utils/lang'; 6 | import Vant from 'packages'; 7 | import ZanDoc from 'zan-doc'; 8 | import ZanClass from 'zan-doc/dist/milk-doc.vendor.css'; 9 | // import 'packages/vant-css/src/index.css'; 10 | import 'zan-doc/src/helper/touch-simulator'; 11 | 12 | Vue.use(Vant); 13 | Vue.use(ZanDoc); 14 | // Vue.use(Lazyload, { 15 | // lazyComponent: true 16 | // }); 17 | Vue.use(VueRouter); 18 | 19 | const routesConfig = routes(true); 20 | const router = new VueRouter({ 21 | mode: 'hash', 22 | routes: routesConfig 23 | }); 24 | 25 | router.afterEach((route) => { 26 | const container = document.querySelector('.examples-container'); 27 | if (container) { 28 | document.querySelector('.examples-container').scrollTop = 0; 29 | } 30 | setLang(route.meta.lang); 31 | }); 32 | 33 | window.vueRouter = router; 34 | 35 | if (process.env.NODE_ENV !== 'production') { 36 | Vue.config.productionTip = false; 37 | } 38 | 39 | new Vue({ // eslint-disable-line 40 | render: h => h(App), 41 | router, 42 | el: '#app-container' 43 | }); 44 | -------------------------------------------------------------------------------- /docs/src/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | import App from './ExamplesDocsApp'; 4 | import routes from './router.config'; 5 | import ZanDoc from 'zan-doc'; 6 | import isMobile from './utils/is-mobile'; 7 | import ZanClass from 'zan-doc/dist/milk-doc.vendor.css'; 8 | 9 | Vue.use(VueRouter); 10 | Vue.use(ZanDoc); 11 | 12 | const routesConfig = routes(); 13 | 14 | const router = new VueRouter({ 15 | mode: 'hash', 16 | base: '/zanui/vue/', 17 | routes: routesConfig 18 | }); 19 | 20 | router.beforeEach((route, redirect, next) => { 21 | if (isMobile) { 22 | window.location.replace(`${window.location.pathname}examples.html`); 23 | } 24 | document.title = route.meta.title || document.title; 25 | next(); 26 | }); 27 | 28 | router.afterEach(() => { 29 | window.scrollTo(0, 0); 30 | Vue.nextTick(() => window.syncPath()); 31 | }); 32 | 33 | window.vueRouter = router; 34 | 35 | if (process.env.NODE_ENV !== 'production') { 36 | Vue.config.productionTip = false; 37 | } 38 | 39 | new Vue({ // eslint-disable-line 40 | render: h => h(App), 41 | router, 42 | el: '#app-container' 43 | }); 44 | -------------------------------------------------------------------------------- /docs/src/index.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | VUE中文社区- 移动端 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/src/router.config.js: -------------------------------------------------------------------------------- 1 | import docConfig from './doc.config'; 2 | import { getLang } from './utils/lang'; 3 | import DemoList from './components/demo-list'; 4 | import componentDocs from '../examples-dist/entry-docs'; 5 | import componentDemos from '../examples-dist/entry-demos'; 6 | import './utils/iframe-router'; 7 | 8 | const registerRoute = (isExample) => { 9 | const route = [{ 10 | path: '/', 11 | redirect: to => { 12 | return `/${getLang()}/`; 13 | } 14 | }, { 15 | path: '*', 16 | redirect: to => { 17 | return `/${getLang()}/`; 18 | } 19 | }]; 20 | 21 | Object.keys(docConfig).forEach((lang, index) => { 22 | if (isExample) { 23 | route.push({ 24 | path: `/${lang}`, 25 | component: DemoList, 26 | meta: { lang } 27 | }); 28 | } else { 29 | route.push({ 30 | path: `/${lang}`, 31 | redirect: `/${lang}/component/quickstart` 32 | }); 33 | } 34 | 35 | const navs = docConfig[lang].nav || []; 36 | navs.forEach(nav => { 37 | if (isExample && !nav.showInMobile) { 38 | return; 39 | } 40 | 41 | if (nav.groups) { 42 | nav.groups.forEach(group => { 43 | group.list.forEach(page => addRoute(page, lang)); 44 | }); 45 | } else if (nav.children) { 46 | nav.children.forEach(page => addRoute(page, lang)); 47 | } else { 48 | addRoute(nav, lang); 49 | } 50 | }); 51 | 52 | function addRoute(page, lang) { 53 | const { path } = page; 54 | if (path) { 55 | const name = lang + '/' + path.replace('/', ''); 56 | route.push({ 57 | path: `/${lang}/component${path}`, 58 | component: isExample ? componentDemos[name] : componentDocs[name], 59 | meta: { lang } 60 | }); 61 | } 62 | } 63 | }); 64 | 65 | return route; 66 | }; 67 | 68 | export default registerRoute; 69 | -------------------------------------------------------------------------------- /docs/src/utils/iframe-router.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 同步父窗口和 iframe 的 vue-router 状态 3 | */ 4 | 5 | import isMobile from './is-mobile'; 6 | import { setLang } from './lang'; 7 | import { iframeReady } from './iframe'; 8 | 9 | window.syncPath = function(dir) { 10 | const router = window.vueRouter; 11 | const isInIframe = window !== window.top; 12 | const currentDir = router.history.current.path; 13 | const iframe = document.querySelector('iframe'); 14 | 15 | if (!isInIframe && !isMobile && iframe) { 16 | iframeReady(iframe, () => { 17 | iframe.contentWindow.changePath(currentDir); 18 | }); 19 | } 20 | }; 21 | 22 | window.changePath = function(path = '') { 23 | const pathParts = path.split('/'); 24 | let lang = pathParts[0]; 25 | if (path[0] === '/') { 26 | lang = pathParts[1]; 27 | } 28 | 29 | setLang(lang); 30 | window.vueRouter.replace(path); 31 | }; 32 | -------------------------------------------------------------------------------- /docs/src/utils/iframe.js: -------------------------------------------------------------------------------- 1 | export function iframeReady(iframe, callback) { 2 | const doc = iframe.contentDocument || iframe.contentWindow.document; 3 | const interval = () => { 4 | if (iframe.contentWindow.changePath) { 5 | callback(); 6 | } else { 7 | setTimeout(() => { 8 | interval(); 9 | }, 50); 10 | } 11 | }; 12 | 13 | if (doc.readyState === 'complete') { 14 | interval(); 15 | } else { 16 | iframe.onload = interval; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/src/utils/is-mobile.js: -------------------------------------------------------------------------------- 1 | const isMobile = (function() { 2 | var platform = navigator.userAgent.toLowerCase(); 3 | return (/(android|bb\d+|meego).+mobile|kdtunion|weibo|m2oapp|micromessenger|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i).test(platform) || 4 | (/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i).test(platform.substr(0, 4)); 5 | })(); 6 | 7 | export default isMobile; 8 | -------------------------------------------------------------------------------- /docs/src/utils/lang.js: -------------------------------------------------------------------------------- 1 | const userLang = window.localStorage.getItem('VANT_LANGUAGE') || window.navigator.language || 'en-US'; 2 | let defaultLang = 'en-US'; 3 | if (userLang.indexOf('zh-') !== -1) { 4 | defaultLang = 'zh-CN'; 5 | } 6 | 7 | let currentLang = defaultLang; 8 | 9 | export function getLang() { 10 | return currentLang; 11 | } 12 | 13 | export function setLang(lang) { 14 | window.localStorage.setItem('VANT_LANGUAGE', lang); 15 | currentLang = lang; 16 | } 17 | -------------------------------------------------------------------------------- /issue_template.md: -------------------------------------------------------------------------------- 1 | 1.申请认领组件任务 2 | 3 | 2.预计pr周期天 (注意实际pr时间超出预期*2且没有任何说明,此任务将会被注销重新分配) 4 | -------------------------------------------------------------------------------- /packages/actionsheet/actionsheet.vue: -------------------------------------------------------------------------------- 1 | 29 | 63 | -------------------------------------------------------------------------------- /packages/actionsheet/index.js: -------------------------------------------------------------------------------- 1 | import VActionsheet from './actionsheet.vue'; 2 | 3 | export default VActionsheet; 4 | -------------------------------------------------------------------------------- /packages/button/button.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 68 | 69 | -------------------------------------------------------------------------------- /packages/button/index.js: -------------------------------------------------------------------------------- 1 | import VButton from './button.vue'; 2 | 3 | export default VButton; 4 | -------------------------------------------------------------------------------- /packages/checkbox/agree.vue: -------------------------------------------------------------------------------- 1 | 12 | 50 | -------------------------------------------------------------------------------- /packages/checkbox/index.js: -------------------------------------------------------------------------------- 1 | import VCheckBox from './checkbox.vue'; 2 | import VItem from './item.vue'; 3 | import VAgree from './agree.vue'; 4 | import Vue from 'vue'; 5 | 6 | Vue.component(VItem.name, VItem); 7 | Vue.component(VAgree.name, VAgree); 8 | 9 | VCheckBox.item = VItem; 10 | VCheckBox.agree = VAgree; 11 | 12 | export default VCheckBox; 13 | -------------------------------------------------------------------------------- /packages/checkbox/item.vue: -------------------------------------------------------------------------------- 1 | 36 | 88 | -------------------------------------------------------------------------------- /packages/col/col.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 43 | -------------------------------------------------------------------------------- /packages/col/index.js: -------------------------------------------------------------------------------- 1 | import VCol from './col'; 2 | 3 | export default VCol; 4 | -------------------------------------------------------------------------------- /packages/drawer/index.js: -------------------------------------------------------------------------------- 1 | import VDrawer from 'v-drawer'; 2 | 3 | export default VDrawer; 4 | -------------------------------------------------------------------------------- /packages/icon/icon.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 50 | -------------------------------------------------------------------------------- /packages/icon/index.js: -------------------------------------------------------------------------------- 1 | import VIcon from './icon'; 2 | 3 | export default VIcon; 4 | -------------------------------------------------------------------------------- /packages/imagePicker/image-picker.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 98 | -------------------------------------------------------------------------------- /packages/imagePicker/index.js: -------------------------------------------------------------------------------- 1 | import VImagePicker from './image-picker.vue'; 2 | 3 | export default VImagePicker; 4 | -------------------------------------------------------------------------------- /packages/index.js: -------------------------------------------------------------------------------- 1 | import Actionsheet from './actionsheet'; 2 | import Button from './button'; 3 | import Checkbox from './checkbox'; 4 | import Col from './col'; 5 | import Drawer from './drawer'; 6 | import Icon from './icon'; 7 | import ImagePicker from './imagePicker'; 8 | import Input from './input'; 9 | import List from './list'; 10 | import ListItem from './listItem'; 11 | import Menu from './menu'; 12 | import Modal from './modal'; 13 | import Navbar from './navbar'; 14 | import Pagination from './pagination'; 15 | import Popover from './popover'; 16 | import Progress from './progress'; 17 | import Pulltorefresh from './pulltorefresh'; 18 | import Radio from './radio'; 19 | import Row from './row'; 20 | import SearchBar from './searchBar'; 21 | import Share from './share'; 22 | import Stepper from './stepper'; 23 | import Swipeaction from './swipeaction'; 24 | import Switch from './switch'; 25 | import TabPane from './tabPane'; 26 | import Tabs from './tabs'; 27 | import Textarea from './textarea'; 28 | import Toast from './toast'; 29 | import '../style/index.less'; 30 | 31 | const version = '0.9.3'; 32 | const components = [ 33 | Actionsheet, 34 | Button, 35 | Checkbox, 36 | Col, 37 | Drawer, 38 | Icon, 39 | ImagePicker, 40 | Input, 41 | List, 42 | ListItem, 43 | Menu, 44 | Modal, 45 | Navbar, 46 | Pagination, 47 | Popover, 48 | Progress, 49 | Pulltorefresh, 50 | Radio, 51 | Row, 52 | SearchBar, 53 | Share, 54 | Stepper, 55 | Swipeaction, 56 | Switch, 57 | TabPane, 58 | Tabs, 59 | Textarea 60 | ]; 61 | 62 | const install = function(Vue) { 63 | if (install.installed) return; 64 | 65 | components.forEach(component => { 66 | Vue.component(component.name, component); 67 | }); 68 | }; 69 | 70 | /* istanbul ignore if */ 71 | if (typeof window !== 'undefined' && window.Vue) { 72 | install(window.Vue); 73 | } 74 | 75 | export { 76 | install, 77 | version, 78 | Actionsheet, 79 | Button, 80 | Checkbox, 81 | Col, 82 | Drawer, 83 | Icon, 84 | ImagePicker, 85 | Input, 86 | List, 87 | ListItem, 88 | Menu, 89 | Modal, 90 | Navbar, 91 | Pagination, 92 | Popover, 93 | Progress, 94 | Pulltorefresh, 95 | Radio, 96 | Row, 97 | SearchBar, 98 | Share, 99 | Stepper, 100 | Swipeaction, 101 | Switch, 102 | TabPane, 103 | Tabs, 104 | Textarea, 105 | Toast 106 | }; 107 | export default { 108 | install, 109 | version 110 | }; 111 | -------------------------------------------------------------------------------- /packages/input/index.js: -------------------------------------------------------------------------------- 1 | import VInput from './input'; 2 | 3 | export default VInput; 4 | -------------------------------------------------------------------------------- /packages/list/index.js: -------------------------------------------------------------------------------- 1 | import VList from './list'; 2 | 3 | export default VList; 4 | -------------------------------------------------------------------------------- /packages/list/list.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 28 | -------------------------------------------------------------------------------- /packages/listItem/index.js: -------------------------------------------------------------------------------- 1 | import VListItem from './listItem.vue'; 2 | 3 | export default VListItem; 4 | -------------------------------------------------------------------------------- /packages/menu/index.js: -------------------------------------------------------------------------------- 1 | import VMenu from './menu.vue'; 2 | 3 | export default VMenu; 4 | -------------------------------------------------------------------------------- /packages/menu/subMenu.vue: -------------------------------------------------------------------------------- 1 | 25 | 75 | -------------------------------------------------------------------------------- /packages/modal/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VModal from './modal'; 3 | 4 | let instance; 5 | 6 | const defaultOptions = { 7 | type: 'basic', 8 | value: true, 9 | duration: 3000, 10 | mask: true, 11 | title: '', 12 | message: '', 13 | promptRender: null, 14 | btns: null, 15 | forbidClick: true, 16 | popupRender: null, 17 | callback: action => { 18 | instance[action === 'confirm' ? 'resolve' : 'reject'](action); 19 | }, 20 | clear: () => { 21 | instance.value = false; 22 | } 23 | }; 24 | 25 | const createInstance = () => { 26 | const ModalConstructor = Vue.extend(VModal); 27 | instance = new ModalConstructor({ 28 | el: document.createElement('div') 29 | }); 30 | 31 | instance.$on('input', value => { 32 | instance.value = value; 33 | }); 34 | 35 | document.body.appendChild(instance.$el); 36 | }; 37 | 38 | const Modal = options => { 39 | return new Promise((resolve, reject) => { 40 | if (!instance) { 41 | createInstance(); 42 | } 43 | Object.assign(instance, { resolve, reject }, ...defaultOptions, ...options); 44 | }); 45 | }; 46 | 47 | const createModal = function(options) { 48 | return Modal({ 49 | ...options 50 | }); 51 | }; 52 | 53 | Modal.confirm = options => createModal({ 54 | ...options, 55 | type: 'confirm' 56 | }); 57 | 58 | Modal.popup = options => createModal({ 59 | ...options, 60 | type: 'popup' 61 | }); 62 | 63 | export default Modal; 64 | export { 65 | VModal 66 | }; 67 | -------------------------------------------------------------------------------- /packages/modal/render.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'render', 3 | functional: true, 4 | props: { 5 | render: Function 6 | }, 7 | render: (h, ctx) => { 8 | return ctx.props.render(h); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /packages/navbar/index.js: -------------------------------------------------------------------------------- 1 | import VNavBar from './nav-bar.vue'; 2 | 3 | export default VNavBar; 4 | -------------------------------------------------------------------------------- /packages/navbar/nav-bar.vue: -------------------------------------------------------------------------------- 1 | 16 | 49 | -------------------------------------------------------------------------------- /packages/pagination/index.js: -------------------------------------------------------------------------------- 1 | import VPage from './pagination.vue'; 2 | 3 | export default VPage; 4 | -------------------------------------------------------------------------------- /packages/pagination/pagination.vue: -------------------------------------------------------------------------------- 1 | 35 | 110 | -------------------------------------------------------------------------------- /packages/popover/index.js: -------------------------------------------------------------------------------- 1 | import VPopover from './popover.vue'; 2 | 3 | export default VPopover; 4 | -------------------------------------------------------------------------------- /packages/popover/popover.vue: -------------------------------------------------------------------------------- 1 | 35 | 67 | -------------------------------------------------------------------------------- /packages/progress/index.js: -------------------------------------------------------------------------------- 1 | import VProgress from './progress.vue'; 2 | 3 | export default VProgress; 4 | -------------------------------------------------------------------------------- /packages/progress/progress.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 50 | -------------------------------------------------------------------------------- /packages/pulltorefresh/index.js: -------------------------------------------------------------------------------- 1 | import VPullToRefresh from './pulltorefresh.vue'; 2 | 3 | export default VPullToRefresh; -------------------------------------------------------------------------------- /packages/pulltorefresh/pulltorefresh.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 50 | -------------------------------------------------------------------------------- /packages/radio/index.js: -------------------------------------------------------------------------------- 1 | import VRadio from './radio.vue'; 2 | import VRadioItem from './item.vue'; 3 | import Vue from 'vue'; 4 | 5 | Vue.component(VRadioItem.name, VRadioItem); 6 | 7 | VRadio.item = VRadioItem; 8 | 9 | export default VRadio; 10 | -------------------------------------------------------------------------------- /packages/radio/item.vue: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /packages/radio/radio.vue: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /packages/row/index.js: -------------------------------------------------------------------------------- 1 | import VRow from './row'; 2 | 3 | export default VRow; 4 | -------------------------------------------------------------------------------- /packages/row/row.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 37 | -------------------------------------------------------------------------------- /packages/searchBar/index.js: -------------------------------------------------------------------------------- 1 | import VSearchBar from './search-bar.vue'; 2 | 3 | export default VSearchBar; 4 | -------------------------------------------------------------------------------- /packages/share/index.js: -------------------------------------------------------------------------------- 1 | import VShare from './share.vue'; 2 | 3 | export default VShare; 4 | -------------------------------------------------------------------------------- /packages/stepper/index.js: -------------------------------------------------------------------------------- 1 | import VStepper from './stepper'; 2 | 3 | export default VStepper; 4 | -------------------------------------------------------------------------------- /packages/stepper/stepper.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 96 | -------------------------------------------------------------------------------- /packages/swipeaction/index.js: -------------------------------------------------------------------------------- 1 | import VSwipeAction from './swipeaction.vue'; 2 | 3 | export default VSwipeAction; -------------------------------------------------------------------------------- /packages/swipeaction/swipeaction.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 43 | -------------------------------------------------------------------------------- /packages/switch/index.js: -------------------------------------------------------------------------------- 1 | import VSwitch from './switch.vue'; 2 | 3 | export default VSwitch; 4 | -------------------------------------------------------------------------------- /packages/switch/switch.vue: -------------------------------------------------------------------------------- 1 | 20 | 112 | -------------------------------------------------------------------------------- /packages/tabPane/index.js: -------------------------------------------------------------------------------- 1 | import VTabPane from '../tabs/tabPane.vue'; 2 | 3 | export default VTabPane; 4 | -------------------------------------------------------------------------------- /packages/tabs/index.js: -------------------------------------------------------------------------------- 1 | import VTabs from './tabs'; 2 | 3 | export default VTabs; 4 | -------------------------------------------------------------------------------- /packages/tabs/tabPane.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 41 | 42 | 50 | -------------------------------------------------------------------------------- /packages/textarea/index.js: -------------------------------------------------------------------------------- 1 | import VTextarea from './textarea'; 2 | 3 | export default VTextarea; 4 | -------------------------------------------------------------------------------- /packages/toast/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueToast from './toast'; 3 | 4 | let instance; 5 | 6 | const defaultOptions = { 7 | visible: true, 8 | type: 'text', 9 | position: 'middle', 10 | duration: 3000, 11 | mask: false, 12 | forbidClick: false, 13 | clear: () => { 14 | instance.visible = false; 15 | } 16 | }; 17 | 18 | const createInstance = () => { 19 | if (!instance) { 20 | const ToastConstructor = Vue.extend(VueToast); 21 | instance = new ToastConstructor({ 22 | el: document.createElement('div') 23 | }); 24 | document.body.appendChild(instance.$el); 25 | } 26 | }; 27 | 28 | const Toast = (options = {}) => { 29 | createInstance(); 30 | 31 | options = typeof options === 'string' ? { message: options } : options; 32 | options = { ...defaultOptions, ...options }; 33 | Object.assign(instance, options); 34 | 35 | clearTimeout(instance.timer); 36 | 37 | if (options.duration !== 0) { 38 | instance.timer = setTimeout(() => { 39 | instance.clear(); 40 | }, options.duration); 41 | } 42 | 43 | return instance; 44 | }; 45 | 46 | const createMethod = type => (options = {}) => Toast({ 47 | type, 48 | message: typeof options === 'string' ? options : options.message, 49 | ...options 50 | }); 51 | 52 | Toast.loading = createMethod('loading'); 53 | Toast.success = createMethod('success'); 54 | Toast.fail = createMethod('fail'); 55 | Toast.info = createMethod('text'); 56 | Toast.clear = () => { 57 | instance && instance.clear(); 58 | }; 59 | 60 | export default Toast; 61 | -------------------------------------------------------------------------------- /packages/toast/toast.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 77 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('postcss-easy-import')({ 4 | extensions: ['pcss', 'css'] 5 | }), 6 | require('precss')(), 7 | require('postcss-calc')(), 8 | require('autoprefixer')({ 9 | browsers: ['Android >= 4.0', 'iOS >= 7'] 10 | }) 11 | ] 12 | }; 13 | -------------------------------------------------------------------------------- /style/images/arrow-down@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/arrow-down@2x.png -------------------------------------------------------------------------------- /style/images/arrow-down@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/arrow-down@3x.png -------------------------------------------------------------------------------- /style/images/arrow-up@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/arrow-up@2x.png -------------------------------------------------------------------------------- /style/images/arrow-up@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/arrow-up@3x.png -------------------------------------------------------------------------------- /style/images/arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/arrow@2x.png -------------------------------------------------------------------------------- /style/images/arrow@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/arrow@3x.png -------------------------------------------------------------------------------- /style/images/check@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/check@2x.png -------------------------------------------------------------------------------- /style/images/check@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/check@3x.png -------------------------------------------------------------------------------- /style/images/check_w@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/check_w@2x.png -------------------------------------------------------------------------------- /style/images/check_w@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/check_w@3x.png -------------------------------------------------------------------------------- /style/images/cross@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/cross@2x.png -------------------------------------------------------------------------------- /style/images/cross@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/cross@3x.png -------------------------------------------------------------------------------- /style/images/cross_w@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/cross_w@2x.png -------------------------------------------------------------------------------- /style/images/cross_w@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/cross_w@3x.png -------------------------------------------------------------------------------- /style/images/error@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/error@2x.png -------------------------------------------------------------------------------- /style/images/error@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/error@3x.png -------------------------------------------------------------------------------- /style/images/more@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/more@2x.png -------------------------------------------------------------------------------- /style/images/more@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/more@3x.png -------------------------------------------------------------------------------- /style/images/more_w@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/more_w@2x.png -------------------------------------------------------------------------------- /style/images/more_w@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/more_w@3x.png -------------------------------------------------------------------------------- /style/images/search@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/search@2x.png -------------------------------------------------------------------------------- /style/images/search@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vue3-club/milk-vue/fca6b141b32d77a7ef69983d0ed161c7ee04a0c1/style/images/search@3x.png -------------------------------------------------------------------------------- /style/index.less: -------------------------------------------------------------------------------- 1 | @import './themes/default.less'; 2 | @import './mixins.less'; 3 | @import './anim.less'; 4 | 5 | *, 6 | *:before, 7 | *:after { 8 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 9 | } 10 | 11 | body { 12 | font-size: 16 * @hd; 13 | background-color: @fill-body; 14 | font-family: system, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 15 | } 16 | 17 | *[contenteditable] { 18 | -webkit-user-select: auto !important; 19 | } 20 | 21 | *:focus { 22 | outline: none; 23 | } 24 | 25 | a { 26 | background: transparent; 27 | text-decoration: none; 28 | outline: none; 29 | } 30 | 31 | // v-feedback 样式 32 | .e-feedback { 33 | background-color: #e5e5e5; 34 | } 35 | -------------------------------------------------------------------------------- /style/mixins.less: -------------------------------------------------------------------------------- 1 | @import "./mixins/hairline.less"; 2 | @import "./mixins/icon.less"; 3 | @import "./mixins/row.less"; 4 | @import "./mixins/button.less"; 5 | @import "./mixins/input.less"; 6 | @import "./mixins/textarea.less"; 7 | @import "./mixins/toast.less"; 8 | @import "./mixins/list.less"; 9 | @import "./mixins/nav-bar.less"; 10 | @import "./mixins/drawer.less"; 11 | @import "./mixins/checkbox.less"; 12 | @import "./mixins/radio.less"; 13 | @import "./mixins/switch.less"; 14 | @import "./mixins/menu.less"; 15 | @import "./mixins/popover.less"; 16 | @import "./mixins/tabs.less"; 17 | @import "./mixins/swipeactioin.less"; 18 | @import "./mixins/pulltorefresh.less"; 19 | @import "./mixins/progress.less"; 20 | @import "./mixins/pagination.less"; 21 | @import "./mixins/stepper.less"; 22 | @import "./mixins/image-picker.less"; 23 | @import "./mixins/modal.less"; 24 | @import "./mixins/actionsheet.less"; 25 | @import "./mixins/search-bar.less"; 26 | @import "./mixins/share.less"; 27 | -------------------------------------------------------------------------------- /style/mixins/button.less: -------------------------------------------------------------------------------- 1 | @import "../themes/default"; 2 | @import "./hairline"; 3 | 4 | @buttonPrefixCls: vm-button; 5 | @iconPrefixCls: vm-icon; 6 | 7 | .@{buttonPrefixCls} { 8 | display: block; 9 | outline: 0 none; 10 | -webkit-appearance: none; 11 | box-sizing: border-box; 12 | padding: 0; 13 | text-align: center; 14 | font-size: @button-font-size; 15 | height: @button-height; 16 | line-height: @button-height; 17 | border-radius: @radius-md; 18 | overflow: hidden; 19 | text-overflow: ellipsis; 20 | word-break: break-all; 21 | white-space: nowrap; 22 | cursor:pointer; 23 | 24 | // default 25 | width: 100%; 26 | color: @color-text-base; 27 | background-color: @fill-base; 28 | 29 | .hairline('all', @border-color-base, @radius-md); 30 | 31 | &:active { 32 | background-color: @fill-tap; 33 | } 34 | 35 | &&-disabled { 36 | color: fade(@color-text-base, 30%); 37 | opacity: 0.6; 38 | } 39 | 40 | &-primary { 41 | color: @color-text-base-inverse; 42 | background-color: @primary-button-fill; 43 | .hairline('all', @primary-button-fill, @radius-md); 44 | 45 | &:active { 46 | color: fade(@color-text-base-inverse, 30%); 47 | background-color: @primary-button-fill-tap; 48 | } 49 | 50 | &.@{buttonPrefixCls}-disabled { 51 | color: fade(@color-text-base-inverse, 60%); 52 | opacity: 0.4; 53 | } 54 | } 55 | 56 | &-inline { 57 | width: auto; 58 | display: inline-block; 59 | padding: 0 @h-spacing-lg; 60 | } 61 | 62 | &-small { 63 | font-size: @button-font-size-sm; 64 | height: @button-height-sm; 65 | line-height: @button-height-sm; 66 | padding: 0 @h-spacing-lg; 67 | border-radius: @radius-md; 68 | } 69 | 70 | &-ghost { 71 | color: @ghost-button-color; 72 | background-color: transparent; 73 | .hairline('all', @ghost-button-color, @radius-md); 74 | 75 | &:active { 76 | color: @ghost-button-fill-tap; 77 | background-color: transparent; 78 | .hairline('all', @ghost-button-fill-tap, @radius-md); 79 | } 80 | 81 | &.@{buttonPrefixCls}-disabled { 82 | color: fade(@color-text-base, 10%); 83 | .hairline('all', fade(@color-text-base, 10%), @radius-md); 84 | opacity: 1; // override default opacity: 0.6 85 | } 86 | } 87 | 88 | &-danger { 89 | color: @color-text-base-inverse; 90 | background-color: @warning-button-fill; 91 | 92 | &:active { 93 | color: fade(@color-text-base-inverse, 30%); 94 | background-color: @warning-button-fill-tap; 95 | } 96 | 97 | &.@{buttonPrefixCls}-disabled { 98 | color: fade(@color-text-base-inverse, 60%); 99 | opacity: 0.4; 100 | } 101 | } 102 | 103 | &-loading { 104 | display: flex; 105 | align-items: center; 106 | justify-content: center; 107 | } 108 | 109 | > .@{iconPrefixCls} { 110 | margin-right: 0.5em; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /style/mixins/checkbox.less: -------------------------------------------------------------------------------- 1 | @import "../themes/default"; 2 | 3 | @listPrefixCls: vm-list; 4 | @checkboxWarpPrefixCls: vm-checkbox; 5 | @checkboxInnerPrefixCls: ~"@{checkboxWarpPrefixCls}-inner"; 6 | 7 | .@{checkboxWarpPrefixCls} { 8 | position: relative; 9 | display: inline-block; 10 | vertical-align: middle; 11 | width: @icon-size-sm; 12 | height: @icon-size-sm; 13 | 14 | &-inner { 15 | position: absolute; 16 | right: 0; 17 | width: @icon-size-sm; 18 | height: @icon-size-sm; 19 | border: 1 * @hd solid #ccc; 20 | border-radius: @radius-circle; 21 | transform: rotate(0deg); 22 | box-sizing: border-box; 23 | 24 | &:after { 25 | position: absolute; 26 | display: none; 27 | top: 1.5 * @hd; 28 | right: 6 * @hd; 29 | z-index: 999; 30 | width: 5 * @hd; 31 | height: 11 * @hd; 32 | border-style: solid; 33 | border-width: 0 1 * @hd 1 * @hd 0; 34 | content: '\0020'; 35 | transform: rotate(45deg); 36 | } 37 | } 38 | 39 | &-input { 40 | position: absolute; 41 | top: 0; 42 | left: 0; 43 | opacity: 0; 44 | width: 100%; 45 | height: 100%; 46 | z-index: 2; 47 | border: 0 none; 48 | appearance: none; 49 | } 50 | 51 | &.@{checkboxWarpPrefixCls}-checked { 52 | .@{checkboxInnerPrefixCls} { 53 | border-color: @color-link; 54 | background: @color-link; 55 | 56 | &:after { 57 | display: block; 58 | border-color: #fff; 59 | } 60 | } 61 | } 62 | 63 | &.@{checkboxWarpPrefixCls}-disabled { 64 | opacity: @opacity-disabled; 65 | 66 | &.@{checkboxWarpPrefixCls}-checked { 67 | .@{checkboxInnerPrefixCls} { 68 | border-color: @color-text-caption; 69 | background: none; 70 | 71 | &:after { 72 | border-color: @color-text-caption; 73 | } 74 | } 75 | } 76 | } 77 | } 78 | 79 | .@{listPrefixCls} { 80 | & &-item { 81 | &.@{checkboxWarpPrefixCls}-item { 82 | .@{listPrefixCls}-thumb { 83 | width: @icon-size-sm; 84 | height: @icon-size-sm; 85 | 86 | .@{checkboxWarpPrefixCls} { 87 | position: absolute; 88 | top: 0; 89 | left: 0; 90 | right: 0; 91 | bottom: 0; 92 | width: 100%; 93 | height: @list-item-height; 94 | 95 | &-inner { 96 | left: @h-spacing-lg; 97 | top: 12 * @hd; 98 | } 99 | } 100 | } 101 | 102 | &.@{checkboxWarpPrefixCls}-item-disabled { 103 | .@{listPrefixCls}-content { 104 | color: @color-text-disabled; 105 | } 106 | } 107 | } 108 | } 109 | } 110 | 111 | .@{checkboxWarpPrefixCls}-agree { 112 | position: relative; 113 | display: flex; 114 | align-items: center; 115 | margin-left: @h-spacing-lg; 116 | padding-top: @v-spacing-md; 117 | padding-bottom: @v-spacing-md; 118 | 119 | .@{checkboxWarpPrefixCls}-agree-label { 120 | display: inline-block; 121 | font-size: @font-size-subhead; 122 | color: @color-text-base; 123 | line-height: @line-height-paragraph; 124 | margin-right: @v-spacing-md; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /style/mixins/drawer.less: -------------------------------------------------------------------------------- 1 | @drawerPrefixCls: vm-drawer; 2 | .@{drawerPrefixCls} { 3 | position: absolute; 4 | top: 0; 5 | left: 0; 6 | right: 0; 7 | bottom: 0; 8 | overflow: hidden; 9 | 10 | &-sidebar { 11 | z-index: 2; 12 | position: absolute; 13 | transition: transform .3s ease-out; 14 | will-change: transform; 15 | overflow-y: auto; 16 | } 17 | &-draghandle { 18 | z-index: 1; 19 | position: absolute; 20 | background-color: rgba(50, 50, 50, 0.1); 21 | } 22 | &-overlay { 23 | z-index: 1; 24 | position: absolute; 25 | top: 0; 26 | left: 0; 27 | right: 0; 28 | bottom: 0; 29 | opacity: 0; 30 | visibility: hidden; 31 | transition: opacity 0.3s ease-out; 32 | background-color: rgba(0, 0, 0, 0.3); 33 | } 34 | &-content { 35 | position: absolute; 36 | top: 0; 37 | left: 0; 38 | right: 0; 39 | bottom: 0; 40 | overflow: auto; 41 | transition: left .3s ease-out, right .3s ease-out, top .3s ease-out, bottom .3s ease-out; 42 | } 43 | 44 | &&-left, 45 | &&-right { 46 | .@{drawerPrefixCls}-sidebar, 47 | .@{drawerPrefixCls}-draghandle { 48 | top: 0; 49 | bottom: 0; 50 | } 51 | .@{drawerPrefixCls}-draghandle { 52 | width: 20px; 53 | height: 100%; 54 | } 55 | } 56 | &&-top, 57 | &&-bottom { 58 | .@{drawerPrefixCls}-sidebar, 59 | .@{drawerPrefixCls}-draghandle { 60 | left: 0; 61 | right: 0; 62 | } 63 | .@{drawerPrefixCls}-draghandle { 64 | width: 100%; 65 | height: 20px; 66 | } 67 | } 68 | &&-left { 69 | .@{drawerPrefixCls}-sidebar { 70 | left: 0; 71 | transform: translateX(-100%); 72 | .@{drawerPrefixCls}-open& { 73 | box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.15); 74 | } 75 | } 76 | .@{drawerPrefixCls}-draghandle { 77 | left: 0; 78 | } 79 | } 80 | 81 | &&-right { 82 | .@{drawerPrefixCls}-sidebar { 83 | right: 0; 84 | transform: translateX(100%); 85 | .@{drawerPrefixCls}-open& { 86 | box-shadow: -2px 2px 4px rgba(0, 0, 0, 0.15); 87 | } 88 | } 89 | .@{drawerPrefixCls}-draghandle { 90 | right: 0; 91 | } 92 | } 93 | 94 | &&-top { 95 | .@{drawerPrefixCls}-sidebar { 96 | top: 0; 97 | transform: translateY(-100%); 98 | .@{drawerPrefixCls}-open& { 99 | box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.15); 100 | } 101 | } 102 | .@{drawerPrefixCls}-draghandle { 103 | top: 0; 104 | } 105 | } 106 | 107 | &&-bottom { 108 | .@{drawerPrefixCls}-sidebar { 109 | bottom: 0; 110 | transform: translateY(100%); 111 | .@{drawerPrefixCls}-open& { 112 | box-shadow: 2px -2px 4px rgba(0, 0, 0, 0.15); 113 | } 114 | } 115 | .@{drawerPrefixCls}-draghandle { 116 | bottom: 0; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /style/mixins/image-picker.less: -------------------------------------------------------------------------------- 1 | @import "../themes/default"; 2 | 3 | @imagePickerPrefixCls: vm-image-picker; 4 | 5 | .@{imagePickerPrefixCls} { 6 | &-wrap { 7 | margin-bottom: 15 * @hd; 8 | padding: 9 * @hd 8 * @hd 0; 9 | } 10 | &-flexbox { 11 | position: relative; 12 | width: 25%; 13 | display: inline-block; 14 | } 15 | 16 | &-item { 17 | position: absolute; 18 | top: 50%; 19 | -webkit-transform: translateY(-50%); 20 | -ms-transform: translateY(-50%); 21 | transform: translateY(-50%); 22 | width: 95%; 23 | height: 95%; 24 | background-size: cover; 25 | } 26 | 27 | &-flexbox:after { 28 | content: ""; 29 | display: block; 30 | padding-bottom: 100%; 31 | } 32 | 33 | &-upload-btn { 34 | box-sizing: border-box; 35 | border-radius: 3 * @hd; 36 | border: 1 * @hd solid @color-text-secondary; 37 | background-color: #fff; 38 | } 39 | 40 | &-upload-btn:after, 41 | &-upload-btn:before { 42 | width: 1 * @hd; 43 | height: 25 * @hd; 44 | content: " "; 45 | position: absolute; 46 | top: 50%; 47 | left: 50%; 48 | transform: translate(-50%, -50%); 49 | background-color: @color-text-secondary; 50 | } 51 | 52 | &-upload-btn:before { 53 | width: 25 * @hd; 54 | height: 1 * @hd; 55 | } 56 | 57 | &-upload-btn input { 58 | position: absolute; 59 | top: 0; 60 | left: 0; 61 | bottom: 0; 62 | right: 0; 63 | opacity: 0; 64 | } 65 | 66 | &-item-remove { 67 | width: 15 * @hd; 68 | height: 15 * @hd; 69 | position: absolute; 70 | right: 3 * @hd; 71 | top: 0; 72 | text-align: right; 73 | vertical-align: top; 74 | z-index: 2; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /style/mixins/input.less: -------------------------------------------------------------------------------- 1 | @import "../themes/default"; 2 | @import "./hairline"; 3 | @import "./icon"; 4 | 5 | @listPrefixCls: vm-list; 6 | @inputPrefixCls: vm-input; 7 | 8 | .input-loop-label-span(@n, @i:1) when (@i <= @n) { 9 | .@{inputPrefixCls}-label-@{i} { 10 | width: @i * @input-label-width; 11 | } 12 | .input-loop-label-span(@n, (@i + 1)); 13 | } 14 | 15 | .@{listPrefixCls}-item { 16 | &.@{inputPrefixCls}-item { 17 | height: @list-item-height; 18 | padding-left: @h-spacing-lg; 19 | } 20 | 21 | &:not(:last-child) { 22 | .@{listPrefixCls}-line { 23 | .hairline('bottom'); 24 | } 25 | } 26 | 27 | .@{inputPrefixCls}-label { 28 | color: @color-text-base; 29 | font-size: @font-size-heading; 30 | margin-left: 0; 31 | margin-right: @h-spacing-sm; 32 | text-align: left; 33 | white-space: nowrap; 34 | overflow: hidden; 35 | padding: 2 * @hd 0; 36 | &-center { 37 | text-align: center; 38 | } 39 | &-right { 40 | text-align: right; 41 | } 42 | } 43 | 44 | .input-loop-label-span(10); 45 | 46 | .@{inputPrefixCls}-control { 47 | font-size: @input-font-size; 48 | flex: 1; 49 | 50 | input, textarea { 51 | color: @color-text-base; 52 | font-size: @font-size-heading; 53 | appearance: none; 54 | width: 100%; 55 | padding: 2 * @hd 0; 56 | border: 0; 57 | background-color: transparent; 58 | line-height: @line-height-base*1.2; 59 | box-sizing: border-box; 60 | 61 | &::placeholder { 62 | color: @color-text-placeholder; 63 | } 64 | 65 | &:disabled { 66 | color: @color-text-disabled; 67 | background-color: #fff; 68 | } 69 | } 70 | 71 | textarea { 72 | resize: none; 73 | } 74 | } 75 | 76 | .@{inputPrefixCls}-clear { 77 | display: none; 78 | width: @icon-size-sm; 79 | height: @icon-size-sm; 80 | border-radius: @radius-circle; 81 | overflow: hidden; 82 | font-style: normal; 83 | color: @fill-base; 84 | background-color: @input-color-icon; 85 | background-repeat: no-repeat; 86 | .encoded-svg-background('input_item_delete'); 87 | 88 | background-size: @icon-size-sm auto; 89 | background-position: 2 * @hd 2 * @hd; 90 | 91 | &-active { 92 | background-color: @input-color-icon-tap; 93 | } 94 | } 95 | 96 | &.@{inputPrefixCls}-focus { 97 | .@{inputPrefixCls}-clear { 98 | display: block; 99 | } 100 | } 101 | 102 | .@{inputPrefixCls}-extra { 103 | flex: initial; 104 | min-width: 0; 105 | max-height: @icon-size-sm; 106 | overflow: hidden; 107 | padding-right: 0; 108 | line-height: @line-height-base; 109 | color: @color-text-caption; 110 | font-size: @font-size-subhead; 111 | margin-left: @h-spacing-sm; 112 | } 113 | 114 | &.@{inputPrefixCls}-error { 115 | .@{inputPrefixCls}-control { 116 | input { 117 | color: @brand-error; 118 | } 119 | } 120 | 121 | .@{inputPrefixCls}-error-extra { 122 | height: @icon-size-sm; 123 | width: @icon-size-sm; 124 | margin-left: @v-spacing-sm; 125 | .encoded-svg-background('error'); 126 | 127 | background-size: @icon-size-sm auto; 128 | } 129 | } 130 | 131 | &.@{inputPrefixCls}-disabled { 132 | .@{inputPrefixCls}-label { 133 | color: @color-text-disabled; 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /style/mixins/modal.less: -------------------------------------------------------------------------------- 1 | .vm-modal { 2 | &-mask { 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | width: 100%; 7 | height: 100%; 8 | background: rgba(0,0,0,0.4); 9 | } 10 | &-wrapper { 11 | position: fixed; 12 | overflow: auto; 13 | width: 100%; 14 | height: 100%; 15 | top: 0; 16 | left: 0; 17 | z-index: @modal-zindex; 18 | outline: none; 19 | display: flex; 20 | align-items: center; 21 | justify-content: center; 22 | } 23 | &-header { 24 | padding: 6px 15px 15px; 25 | } 26 | &-title { 27 | text-align: center; 28 | font-size: @color-text-base; 29 | font-size: 18px; 30 | } 31 | &-content { 32 | position: relative; 33 | width: 270px; 34 | height: auto; 35 | border-radius: 7px; 36 | padding-top: 15px; 37 | background: #fff; 38 | .vm-modal-close { 39 | position: absolute; 40 | top: 0; 41 | right: 0; 42 | padding: 10px; 43 | } 44 | } 45 | &-body { 46 | padding: 0 15px 15px; 47 | font-size: 14px; 48 | color: @color-text-caption; 49 | } 50 | &-body-basic { 51 | max-height: 85px; 52 | overflow: auto; 53 | line-height: @line-height-paragraph; 54 | text-align: center; 55 | } 56 | &-body-confirm { 57 | height: auto; 58 | overflow: hidden; 59 | text-align: center; 60 | color: @color-text-caption; 61 | } 62 | &-footer-confirm { 63 | display: flex; 64 | justify-content: space-between; 65 | } 66 | &-btn { 67 | font-size: 18px; 68 | border-top: 1px solid @border-color-base; 69 | .hairline('top', @border-color-base); 70 | border-radius: 0 0 7px 7px; 71 | } 72 | .btn-close { 73 | color: @color-text-base; 74 | } 75 | .btn-confirm { 76 | color: @brand-primary; 77 | } 78 | &-btn-confirm { 79 | width: 50%; 80 | &:first-child { 81 | border-radius: 0 0 0 7px; 82 | }; 83 | &:last-child { 84 | border-radius: 0 0 7px 0; 85 | } 86 | } 87 | &-btn-mult { 88 | border-bottom-color: transparent; 89 | border-radius: 0 0 0 0; 90 | &:last-child { 91 | border-radius: 0 0 7px 7px; 92 | } 93 | } 94 | &-body-popup { 95 | padding: 0; 96 | } 97 | &-content-popup { 98 | position: fixed; 99 | bottom: 0; 100 | width: 100%; 101 | border-radius: 0; 102 | transition: bottom .2s; 103 | } 104 | &-btn-popup { 105 | border-radius: 0; 106 | } 107 | } 108 | .vm-modal-fade-enter-active, .vm-modal-fade-leave-active { 109 | transition: opacity .2s; 110 | } 111 | .vm-modal-fade-enter, .vm-modal-fade-leave-to { 112 | opacity: 0; 113 | } 114 | .vm-modal-slideup-enter-active, .vm-modal-slideup-leave-active { 115 | transition: all .2s; 116 | } 117 | .vm-modal-slideup-enter, .vm-modal-slideup-leave-to { 118 | opacity: 0; 119 | } 120 | .vm-modal-slideup-enter .vm-modal-content-popup, .vm-modal-slideup-leave-to .vm-modal-content-popup { 121 | bottom: -100%; 122 | } 123 | -------------------------------------------------------------------------------- /style/mixins/nav-bar.less: -------------------------------------------------------------------------------- 1 | @import "../themes/default"; 2 | 3 | @navBarHeight: 45 * @hd; 4 | @navBarPrefixCls: vm-nav-bar; 5 | 6 | .@{navBarPrefixCls} { 7 | display: flex; 8 | align-items: center; 9 | height: @navBarHeight; 10 | max-width: 100%; 11 | overflow-x: hidden; 12 | background-color: @brand-primary; 13 | color: @fill-base; 14 | 15 | &-left, 16 | &-title, 17 | &-right { 18 | flex: 1; 19 | height: 100%; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | &-left { 25 | padding-left: @h-spacing-lg; 26 | font-size: @link-button-font-size; 27 | 28 | &-icon { 29 | margin-right: @h-spacing-sm; 30 | display: inherit; 31 | } 32 | } 33 | 34 | &-title { 35 | justify-content: center; 36 | font-size: 18 * @hd; 37 | white-space: nowrap; 38 | } 39 | 40 | &-right { 41 | justify-content: flex-end; 42 | font-size: @link-button-font-size; 43 | margin-right: @h-spacing-lg; 44 | } 45 | 46 | &-light { 47 | background-color: @fill-base; 48 | color: @brand-primary; 49 | } 50 | 51 | &-light &-title { 52 | color: @color-text-base; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /style/mixins/pagination.less: -------------------------------------------------------------------------------- 1 | @import "../themes/default"; 2 | 3 | @paginationPrefixCls: vm-pagination; 4 | 5 | .@{paginationPrefixCls} { 6 | &-wrap { 7 | font-size: 18 * @hd; 8 | color: @color-text-base; 9 | background: none; 10 | text-align: center; 11 | 12 | .active { 13 | color: @color-link; 14 | } 15 | 16 | &-btn { 17 | text-align: center; 18 | 19 | &-prev { 20 | text-align: left; 21 | } 22 | 23 | &-next { 24 | text-align: right; 25 | } 26 | } 27 | 28 | &-dot { 29 | display: inline-block; 30 | zoom: 1; 31 | 32 | > span { 33 | display: block; 34 | width: 8 * @hd; 35 | height: 8 * @hd; 36 | margin-right: @h-spacing-sm; 37 | border-radius: @radius-circle; 38 | background: @color-icon-base; 39 | } 40 | 41 | &-active { 42 | > span { 43 | background: #888; 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /style/mixins/progress.less: -------------------------------------------------------------------------------- 1 | @import '../themes/default'; 2 | 3 | @pickerPrefixClass: vm-progress; 4 | 5 | .@{pickerPrefixClass} { 6 | &-outer { 7 | background-color: @border-color-base; 8 | display: block; 9 | } 10 | 11 | &-fixed-outer { 12 | position: fixed; 13 | width: 100%; 14 | top: 0; 15 | left: 0; 16 | z-index: @progress-zindex; 17 | } 18 | 19 | &-hide-outer { 20 | background-color: transparent; 21 | } 22 | 23 | &-bar { 24 | border: @border-width-lg solid @brand-primary; 25 | transition: all .3s linear 0s; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /style/mixins/pulltorefresh.less: -------------------------------------------------------------------------------- 1 | @import "../themes/default"; 2 | 3 | @pull-to-refresh: vm-pull-to-refresh; 4 | 5 | .@{pull-to-refresh} { 6 | &-content { 7 | &-wrapper { 8 | overflow: hidden; 9 | } 10 | transform-origin: left top 0; 11 | } 12 | 13 | &-transition { 14 | transition: transform 0.3s; 15 | } 16 | 17 | &-indicator { 18 | color: grey; 19 | text-align: center; 20 | height: 25 * @hd; 21 | } 22 | 23 | &-down .@{pull-to-refresh}-indicator { 24 | margin-top: -25 * @hd; 25 | } 26 | 27 | &-up .@{pull-to-refresh}-indicator { 28 | margin-bottom: -25 * @hd; 29 | } 30 | } -------------------------------------------------------------------------------- /style/mixins/radio.less: -------------------------------------------------------------------------------- 1 | @import "../themes/default"; 2 | 3 | @listPrefixCls: vm-list; 4 | @radioWarpPrefixCls: vm-radio; 5 | @radioInnerPrefixCls: ~"@{radioWarpPrefixCls}-inner"; 6 | 7 | .@{radioWarpPrefixCls} { 8 | position: relative; 9 | display: inline-block; 10 | vertical-align: middle; 11 | width: @icon-size-xxs; 12 | height: @icon-size-xxs; 13 | 14 | &-inner { 15 | position: absolute; 16 | right: 0; 17 | width: @icon-size-xxs; 18 | height: @icon-size-xxs; 19 | box-sizing: border-box; 20 | transform: rotate(0deg); 21 | 22 | &:after { 23 | position: absolute; 24 | display: none; 25 | top: -2.5 * @hd; 26 | right: 5 * @hd; 27 | z-index: 999; 28 | width: 7 * @hd; 29 | height: 14 * @hd; 30 | border-style: solid; 31 | border-width: 0 1.5 * @hd 1.5 * @hd 0; 32 | content: '\0020'; 33 | transform: rotate(45deg); 34 | } 35 | } 36 | 37 | &-input { 38 | position: absolute; 39 | top: 0; 40 | left: 0; 41 | opacity: 0; 42 | width: 100%; 43 | height: 100%; 44 | z-index: 2; 45 | border: 0 none; 46 | appearance: none; 47 | } 48 | 49 | &.@{radioWarpPrefixCls}-checked { 50 | .@{radioInnerPrefixCls} { 51 | border-width: 0; 52 | 53 | &:after { 54 | display: block; 55 | border-color: @color-link; 56 | } 57 | } 58 | } 59 | 60 | &.@{radioWarpPrefixCls}-disabled { 61 | &.@{radioWarpPrefixCls}-checked { 62 | .@{radioInnerPrefixCls} { 63 | &:after { 64 | display: block; 65 | border-color: @color-text-disabled; 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | .@{listPrefixCls} { 73 | & &-item { 74 | &.@{radioWarpPrefixCls}-item { 75 | .@{listPrefixCls}-line { 76 | .@{listPrefixCls}-extra { 77 | flex: 0; 78 | 79 | .@{radioWarpPrefixCls} { 80 | position: absolute; 81 | top: 0; 82 | left: 0; 83 | right: 0; 84 | bottom: 0; 85 | width: 100%; 86 | height: @list-item-height; 87 | overflow: visible; 88 | 89 | &-inner { 90 | right: @v-spacing-lg; 91 | top: @h-spacing-lg; 92 | } 93 | } 94 | } 95 | } 96 | 97 | &.@{radioWarpPrefixCls}-item-disabled { 98 | .@{listPrefixCls}-content { 99 | color: @color-text-disabled; 100 | } 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /style/mixins/row.less: -------------------------------------------------------------------------------- 1 | @import "../themes/default"; 2 | 3 | @flexPrefixCls: vm-flexbox; 4 | 5 | .vm-loop-span(@n, @i:1) when (@i <= @n) { 6 | .@{flexPrefixCls}-item-span-@{i} { 7 | width: percentage(@i/@n) 8 | } 9 | .vm-loop-span(@n, (@i + 1)); 10 | } 11 | 12 | .vm-loop-offset(@n, @i:1) when (@i <= @n) { 13 | .@{flexPrefixCls}-item-offset-@{i} { 14 | margin-left: percentage(@i/@n) 15 | } 16 | .vm-loop-offset(@n, (@i + 1)); 17 | } 18 | 19 | /* flexbox */ 20 | .@{flexPrefixCls} { 21 | text-align: left; 22 | overflow: hidden; 23 | display: flex; 24 | align-items: center; 25 | 26 | .vm-loop-span(24); 27 | .vm-loop-offset(24); 28 | 29 | & &-item-flex { 30 | flex: 1; 31 | } 32 | 33 | &&-dir-row { 34 | flex-direction: row; 35 | } 36 | 37 | &&-dir-row-reverse { 38 | flex-direction: row-reverse; 39 | } 40 | 41 | &&-dir-column { 42 | flex-direction: column; 43 | } 44 | 45 | &&-dir-column-reverse { 46 | flex-direction: column-reverse; 47 | } 48 | 49 | &&-nowrap { 50 | flex-wrap: nowrap; 51 | } 52 | 53 | &&-wrap { 54 | flex-wrap: wrap; 55 | } 56 | 57 | &&-wrap-reverse { 58 | flex-wrap: wrap-reverse; 59 | } 60 | 61 | &&-justify-start { 62 | justify-content: flex-start; 63 | } 64 | 65 | &&-justify-end { 66 | justify-content: flex-end; 67 | } 68 | 69 | &&-justify-center { 70 | justify-content: center; 71 | } 72 | 73 | &&-justify-between { 74 | justify-content: space-between; 75 | } 76 | 77 | &&-justify-around { 78 | justify-content: space-around; 79 | } 80 | 81 | &&-align-start { 82 | align-items: flex-start; 83 | } 84 | 85 | &&-align-end { 86 | align-items: flex-end; 87 | } 88 | 89 | &&-align-center { 90 | align-items: center; 91 | } 92 | 93 | &&-align-stretch { 94 | align-items: stretch; 95 | } 96 | 97 | &&-align-baseline { 98 | align-items: baseline; 99 | } 100 | 101 | &&-align-content-start { 102 | align-content: flex-start; 103 | } 104 | 105 | &&-align-content-end { 106 | align-items: flex-end; 107 | } 108 | 109 | &&-align-content-center { 110 | align-items: center; 111 | } 112 | 113 | &&-align-content-between { 114 | align-items: stretch; 115 | } 116 | 117 | &&-align-content-around { 118 | align-items: baseline; 119 | } 120 | 121 | &&-align-content-stretch { 122 | align-items: baseline; 123 | } 124 | 125 | & &-item { 126 | box-sizing: border-box; 127 | min-width: 10 * @hd; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /style/mixins/search-bar.less: -------------------------------------------------------------------------------- 1 | @import "../themes/default"; 2 | 3 | @searchBarPrefixCls: vm-search-bar; 4 | 5 | .@{searchBarPrefixCls} { 6 | display: flex; 7 | align-items: center; 8 | padding: 0 8 * @hd; 9 | overflow: hidden; 10 | height: 44 * @hd; 11 | &-input { 12 | flex: 1; 13 | width: 100%; 14 | height: 28 * @hd; 15 | background-color: #fff; 16 | border-radius: 3 * @hd; 17 | position: relative; 18 | &-placeholder { 19 | z-index: 1; 20 | text-align: center; 21 | transition: width .3s; 22 | height: 28 * @hd; 23 | position: absolute; 24 | top: 0; 25 | left: 0; 26 | &-wrap { 27 | display: inline-block; 28 | line-height: 28 * @hd; 29 | color: #bbb; 30 | font-size: 15 * @hd; 31 | text-align: left; 32 | } 33 | } 34 | &-icon { 35 | margin-right: 5 * @hd; 36 | width: 15 * @hd; 37 | height: 15 * @hd; 38 | position: relative; 39 | top: 2 * @hd; 40 | } 41 | &-value { 42 | z-index: 2; 43 | opacity: 1; 44 | width: 100%; 45 | display: block; 46 | color: #000; 47 | height: 28 * @hd; 48 | font-size: 15 * @hd; 49 | background-color: transparent; 50 | border: 0; 51 | position: absolute; 52 | top: 0; 53 | left: 0; 54 | } 55 | &-clear { 56 | z-index: 3; 57 | position: absolute; 58 | top: 0; 59 | right: 0; 60 | padding: 6.5px; 61 | display: none; 62 | > svg { 63 | width: 15px; 64 | height: 15px; 65 | } 66 | &-show { 67 | display: inline-block; 68 | } 69 | } 70 | } 71 | &-cancel { 72 | flex: none; 73 | padding-left: 8px; 74 | height: 44px; 75 | line-height: 44px; 76 | opacity: 0; 77 | font-size: 16px; 78 | color: #108ee9; 79 | text-align: right; 80 | transition: margin-right 0.3s, opacity 0.3s; 81 | transition-delay: .1s; 82 | &-show { 83 | opacity: 1; 84 | } 85 | } 86 | &.@{searchBarPrefixCls}-start { 87 | .@{searchBarPrefixCls}-input-placeholder { 88 | padding-left: 15px; 89 | } 90 | .@{searchBarPrefixCls}-input-value { 91 | opacity: 1; 92 | padding: 0 28px 0 35px; 93 | } 94 | } 95 | input[type=search]::-webkit-search-cancel-button { 96 | -webkit-appearance: none; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /style/mixins/stepper.less: -------------------------------------------------------------------------------- 1 | @import "../themes/default"; 2 | 3 | .vm-stepper { 4 | display: inline-block; 5 | font-size: 0; 6 | &-handle { 7 | position: relative; 8 | box-sizing: border-box; 9 | padding: 4px; 10 | width: 30px; 11 | height: 30px; 12 | line-height: 20px; 13 | border: 1px solid #ddd; 14 | border-radius: 5px; 15 | background: none; 16 | &::after, 17 | &::before { 18 | content: ''; 19 | position: absolute; 20 | margin: auto; 21 | top: 0; 22 | bottom: 0; 23 | left: 0; 24 | right: 0; 25 | background-color: @color-text-base; 26 | } 27 | &:active { 28 | background-color: @fill-tap; 29 | } 30 | &.vm-stepper-plus-disabled, 31 | &.vm-stepper-minus-disabled { 32 | opacity: @opacity-disabled; 33 | &:active { 34 | background: none; 35 | } 36 | } 37 | } 38 | 39 | &-handle-minus { 40 | &::before { 41 | width: 10px; 42 | height: 1px; 43 | } 44 | &::after { 45 | display: none; 46 | } 47 | } 48 | 49 | &-handle-plus { 50 | &::before { 51 | width: 10px; 52 | height: 1px; 53 | } 54 | &::after { 55 | width: 1px; 56 | height: 10px; 57 | } 58 | } 59 | 60 | &-input { 61 | box-sizing: border-box; 62 | width: 60px; 63 | height: 30px; 64 | line-height: 30px; 65 | text-align: center; 66 | vertical-align: top; 67 | border: none; 68 | outline: none; 69 | color: @color-text-base; 70 | background: none; 71 | &.vm-stepper-input-disabled { 72 | opacity: @opacity-disabled; 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /style/mixins/swipeactioin.less: -------------------------------------------------------------------------------- 1 | @import "../themes/default"; 2 | 3 | @swipeout-prefix-cls: vm-swipeout; 4 | 5 | .@{swipeout-prefix-cls} { 6 | overflow: hidden; 7 | position: relative; 8 | &-content { 9 | position: relative; 10 | background-color: #fff; 11 | } 12 | &-cover { 13 | position: absolute; 14 | z-index: 2; 15 | background: transparent; 16 | height: 100%; 17 | width: 100%; 18 | top: 0; 19 | display: none; 20 | } 21 | & .@{swipeout-prefix-cls}-content, 22 | & .@{swipeout-prefix-cls}-actions { 23 | transition: all 250ms; 24 | } 25 | &-swiping .@{swipeout-prefix-cls}-content { 26 | transition: none; 27 | } 28 | &-actions { 29 | position: absolute; 30 | top: 0; 31 | bottom: 0; 32 | display: flex; 33 | overflow: hidden; 34 | white-space: nowrap; 35 | &-left { 36 | left: 0; 37 | } 38 | &-right { 39 | right: 0; 40 | } 41 | } 42 | &-btn { 43 | display: flex; 44 | align-items: center; 45 | justify-content: center; 46 | position: relative; 47 | overflow: hidden; 48 | &-text { 49 | padding: 0 12px; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /style/mixins/switch.less: -------------------------------------------------------------------------------- 1 | @import "../themes/default"; 2 | 3 | @switchPrefixCls: vm-switch; 4 | 5 | .@{switchPrefixCls} { 6 | display: inline-block; 7 | vertical-align: middle; 8 | box-sizing: border-box; 9 | position: relative; 10 | cursor: pointer; 11 | align-self: center; 12 | 13 | .checkbox { 14 | width: 51 * @hd; 15 | height: 31 * @hd; 16 | border-radius: 31 * @hd; 17 | box-sizing: border-box; 18 | background: #e5e5e5; 19 | z-index: 0; 20 | margin: 0; 21 | padding: 0; 22 | appearance: none; 23 | border: 0; 24 | cursor: pointer; 25 | position: relative; 26 | transition: all 300ms; 27 | 28 | &:before { 29 | content: ' '; 30 | position: absolute; 31 | left: 1.5 * @hd; 32 | top: 1.5 * @hd; 33 | width: 48 * @hd; 34 | height: 28 * @hd; 35 | border-radius: 28 * @hd; 36 | box-sizing: border-box; 37 | background: @fill-base; 38 | z-index: 1; 39 | transition: all 200ms; 40 | transform: scale(1); 41 | } 42 | 43 | &:after { 44 | content: ' '; 45 | height: 28 * @hd; 46 | width: 28 * @hd; 47 | border-radius: 28 * @hd; 48 | background: @fill-base; 49 | position: absolute; 50 | z-index: 2; 51 | left: 1.5 * @hd; 52 | top: 1.5 * @hd; 53 | transform: translateX(0); 54 | transition: all 200ms; 55 | box-shadow: 2 * @hd 2 * @hd 4 * @hd rgba(0, 0, 0, 0.21); 56 | } 57 | 58 | &.checkbox-disabled { 59 | z-index: 3; 60 | } 61 | } 62 | 63 | input[type="checkbox"] { 64 | position: absolute; 65 | top: 0; 66 | left: 0; 67 | opacity: 0; 68 | width: 100%; 69 | height: 100%; 70 | z-index: 2; 71 | border: 0 none; 72 | appearance: none; 73 | 74 | &:checked { 75 | & + .checkbox { 76 | background: @switch-fill; 77 | 78 | &:before { 79 | transform: scale(0); 80 | } 81 | 82 | &:after { 83 | transform: translateX(20 * @hd); 84 | } 85 | } 86 | } 87 | 88 | &:disabled { 89 | & + .checkbox { 90 | opacity: @opacity-disabled; 91 | } 92 | } 93 | } 94 | 95 | &&-android { 96 | .checkbox { 97 | width: 72 * @hd; 98 | height: 23 * @hd; 99 | border-radius: @radius-sm; 100 | background: #a7aaa6; 101 | 102 | &:before { 103 | display: none; 104 | } 105 | 106 | &:after { 107 | width: 35 * @hd; 108 | height: 21 * @hd; 109 | border-radius: @radius-xs; 110 | box-shadow: none; 111 | left: 1PX; 112 | top: 1PX; 113 | } 114 | } 115 | 116 | input[type="checkbox"] { 117 | &:checked { 118 | & + .checkbox { 119 | background: @switch-fill-android; 120 | 121 | &:before { 122 | transform: scale(0); 123 | } 124 | 125 | &:after { 126 | transform: translateX(35 * @hd); 127 | } 128 | } 129 | } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /style/mixins/toast.less: -------------------------------------------------------------------------------- 1 | @import "../themes/default"; 2 | 3 | .vm-toast { 4 | position: fixed; 5 | top: 50%; 6 | left: 50%; 7 | display: flex; 8 | color: @color-text-base-inverse; 9 | z-index: 3001; 10 | font-size: @font-size-base; 11 | line-height: 1.2; 12 | border-radius: 5px; 13 | align-items: center; 14 | justify-content: center; 15 | flex-direction: column; 16 | box-sizing: border-box; 17 | transform: translate3d(-50%, -50%, 0); 18 | background-color: rgba(40, 40, 40, .8); 19 | 20 | &-wrapper { 21 | transition: opacity .2s; 22 | } 23 | 24 | &__overlay { 25 | position: fixed; 26 | top: 0; 27 | left: 0; 28 | width: 100%; 29 | height: 100%; 30 | z-index: 3000; 31 | background-color: transparent; 32 | 33 | &--mask { 34 | background-color: rgba(0, 0, 0, .5); 35 | } 36 | } 37 | 38 | &--text { 39 | padding: 12px; 40 | min-width: 220px; 41 | } 42 | 43 | &--default { 44 | width: 120px; 45 | min-height: 120px; 46 | padding: 15px; 47 | 48 | .vm-toast__icon { 49 | font-size: 50px; 50 | } 51 | 52 | .vm-loading { 53 | margin: 10px 0 5px; 54 | } 55 | 56 | .vm-toast__text { 57 | font-size: 14px; 58 | padding-top: 10px; 59 | } 60 | } 61 | 62 | &--top { 63 | top: 50px; 64 | } 65 | 66 | &--bottom { 67 | top: auto; 68 | bottom: 50px; 69 | } 70 | } 71 | 72 | .vm-toast-fade-enter, .vm-toast-fade-leave-to { 73 | opacity: 0; 74 | } 75 | -------------------------------------------------------------------------------- /style/mixins/util.less: -------------------------------------------------------------------------------- 1 | // Encoded SVG Background 2 | .encoded-svg-background-i(@svg) { 3 | // @url: `encodeURIComponent(@{svg})`; 4 | 5 | background-image: url("data:image/svg+xml;charset=utf-8,@{svg}"); 6 | } 7 | 8 | .ellipsis() { 9 | width: auto; 10 | overflow: hidden; 11 | text-overflow: ellipsis; 12 | white-space: nowrap; 13 | } 14 | 15 | .background(@start: #ffffff, @end: #000000) { 16 | background: @end; 17 | background-image: -webkit-gradient(linear, left top, left bottom, from(@start), to(@end)); /* Saf4+, Chrome */ 18 | background-image: linear-gradient(@start, @end); 19 | } 20 | -------------------------------------------------------------------------------- /test/unit/components/badge.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | -------------------------------------------------------------------------------- /test/unit/components/checkbox.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 30 | -------------------------------------------------------------------------------- /test/unit/components/field.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | -------------------------------------------------------------------------------- /test/unit/components/goods-action.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 38 | -------------------------------------------------------------------------------- /test/unit/components/more-tabs.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | -------------------------------------------------------------------------------- /test/unit/components/notice-bar.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 | -------------------------------------------------------------------------------- /test/unit/components/number-keyboard.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | -------------------------------------------------------------------------------- /test/unit/components/radio.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 25 | -------------------------------------------------------------------------------- /test/unit/components/row.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 26 | -------------------------------------------------------------------------------- /test/unit/components/steps.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 27 | -------------------------------------------------------------------------------- /test/unit/components/tabbar.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 41 | -------------------------------------------------------------------------------- /test/unit/components/tabs.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 47 | -------------------------------------------------------------------------------- /test/unit/components/waterfall/waterfall-hide.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 38 | 39 | 46 | -------------------------------------------------------------------------------- /test/unit/components/waterfall/waterfall.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 48 | -------------------------------------------------------------------------------- /test/unit/index.js: -------------------------------------------------------------------------------- 1 | require('packages/vant-css/src/index.css'); 2 | 3 | // hack for test touch event 4 | window.ontouchstart = {}; 5 | 6 | // 读取配置文件,判断运行单个测试文件还是所有测试文件 7 | const testsReq = require.context('./specs', true, /\.spec$/); 8 | if (process.env.TEST_FILE) { 9 | testsReq.keys().forEach((file) => { 10 | if (file.indexOf(process.env.TEST_FILE) !== -1) { 11 | testsReq(file); 12 | } 13 | }); 14 | } else { 15 | testsReq.keys().forEach(testsReq); 16 | } 17 | -------------------------------------------------------------------------------- /test/unit/karma.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-polyfill'); 2 | 3 | require('babel-core/register')({ 4 | presets: [require('babel-preset-env')] 5 | }); 6 | 7 | var getWebpackConfig = require('./get-webpack-conf'); 8 | var travis = process.env.TRAVIS; 9 | 10 | module.exports = function(config) { 11 | config.set({ 12 | browsers: travis ? ['PhantomJS'] : ['PhantomJS', 'Chrome'], 13 | frameworks: ['mocha', 'sinon-chai'], 14 | reporters: ['spec', 'coverage'], 15 | files: ['./index.js'], 16 | preprocessors: { 17 | './index.js': ['webpack', 'sourcemap'], 18 | 'test/unit/!(components)/**/*.vue': ['coverage'] 19 | }, 20 | webpack: getWebpackConfig(getTestFileName()), 21 | webpackMiddleware: { 22 | noInfo: true 23 | }, 24 | coverageReporter: { 25 | dir: './coverage', 26 | reporters: [ 27 | { type: 'lcov', subdir: '.' }, 28 | { type: 'text-summary' } 29 | ] 30 | }, 31 | singleRun: false 32 | }); 33 | }; 34 | 35 | function getTestFileName() { 36 | const flagIndex = process.argv.indexOf('--file'); 37 | return flagIndex !== -1 ? process.argv[flagIndex + 1] : ''; 38 | } 39 | -------------------------------------------------------------------------------- /test/unit/selector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 运行单个测试文件 3 | */ 4 | 5 | const fs = require('fs'); 6 | const inquirer = require('inquirer'); 7 | const path = require('path'); 8 | const shell = require('shelljs'); 9 | const files = fs.readdirSync(path.resolve(__dirname, './specs')); 10 | 11 | inquirer.prompt([{ 12 | type: 'list', 13 | name: 'select', 14 | message: '请选择要运行的测试文件:', 15 | choices: files 16 | }], (result) => { 17 | const file = result.select.replace('.spec.js', ''); 18 | shell.exec('karma start test/unit/karma.conf.js --color alway --file ' + file); 19 | }); 20 | -------------------------------------------------------------------------------- /test/unit/specs/address-list.spec.js: -------------------------------------------------------------------------------- 1 | import { mount } from 'avoriaz'; 2 | import AddressList from 'packages/address-list'; 3 | 4 | const list = [ 5 | { 6 | id: '1', 7 | name: '张三', 8 | tel: '13000000000', 9 | address: '浙江省杭州市西湖区文三路 138 号东方通信大厦 7 楼 501 室' 10 | }, 11 | { 12 | id: '2', 13 | name: '李四', 14 | tel: '1310000000', 15 | address: '浙江省杭州市拱墅区莫干山路 50 号' 16 | }, 17 | { 18 | id: '3', 19 | name: '王五', 20 | tel: '1320000000', 21 | address: '浙江省杭州市滨江区江南大道 15 号' 22 | } 23 | ]; 24 | 25 | describe('AddressList', () => { 26 | let wrapper; 27 | afterEach(() => { 28 | wrapper && wrapper.destroy(); 29 | }); 30 | 31 | it('create a AddressList', () => { 32 | wrapper = mount(AddressList); 33 | expect(wrapper.hasClass('van-address-list')).to.be.true; 34 | }); 35 | 36 | it('create a AddressList with three items', () => { 37 | wrapper = mount(AddressList, { 38 | propsData: { 39 | value: '1', 40 | list 41 | } 42 | }); 43 | expect(wrapper.find('.van-address-list__group .van-cell').length).to.equal(3); 44 | expect(wrapper.find('.van-icon-checked').length).to.equal(1); 45 | }); 46 | 47 | it('listen to add & edit event', (done) => { 48 | wrapper = mount(AddressList, { 49 | propsData: { 50 | list 51 | } 52 | }); 53 | 54 | const add = sinon.spy(); 55 | wrapper.vm.$on('add', add); 56 | wrapper.find('.van-address-list__add')[0].trigger('click'); 57 | expect(add.calledOnce).to.be.true; 58 | 59 | wrapper.vm.$on('edit', (item, index) => { 60 | expect(index).to.equal(0); 61 | done(); 62 | }); 63 | wrapper.find('.van-address-list__edit')[0].trigger('click'); 64 | }); 65 | 66 | it('listen to select event', (done) => { 67 | wrapper = mount(AddressList, { 68 | propsData: { 69 | value: '1', 70 | list 71 | } 72 | }); 73 | 74 | wrapper.vm.$on('select', (item, index) => { 75 | expect(item.id).to.equal('3'); 76 | done(); 77 | }); 78 | wrapper.find('.van-radio')[2].trigger('click'); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/unit/specs/area.spec.js: -------------------------------------------------------------------------------- 1 | import Area from 'packages/area'; 2 | import { mount } from 'avoriaz'; 3 | import AreaList from '../mock/area.json'; 4 | 5 | describe('Area', () => { 6 | let wrapper; 7 | afterEach(() => { 8 | wrapper && wrapper.destroy(); 9 | }); 10 | 11 | it('create an area', () => { 12 | wrapper = mount(Area, { 13 | propsData: { 14 | areaList: AreaList 15 | } 16 | }); 17 | 18 | expect(wrapper.hasClass('van-area')).to.be.true; 19 | }); 20 | 21 | it('create an area with default value', (done) => { 22 | wrapper = mount(Area, { 23 | propsData: { 24 | areaList: AreaList, 25 | value: '110101' 26 | } 27 | }); 28 | 29 | expect(wrapper.hasClass('van-area')).to.be.true; 30 | 31 | const confirmBtn = wrapper.find('.van-picker__confirm')[0]; 32 | const eventStub = sinon.stub(wrapper.vm, '$emit'); 33 | 34 | confirmBtn.trigger('click'); 35 | wrapper.vm.$nextTick(() => { 36 | expect(eventStub.calledOnce).to.be.true; 37 | expect(eventStub.calledWith('confirm')); 38 | expect(wrapper.vm.$refs.picker.getColumnValue(2).code).to.equal('110101'); 39 | done(); 40 | }); 41 | }); 42 | 43 | it('create an area and set value', (done) => { 44 | wrapper = mount(Area, { 45 | propsData: { 46 | areaList: AreaList, 47 | value: '110101' 48 | } 49 | }); 50 | 51 | expect(wrapper.hasClass('van-area')).to.be.true; 52 | expect(wrapper.vm.$refs.picker.getColumnValue(2).code).to.equal('110101'); 53 | 54 | wrapper.setProps({ 55 | value: '110102' 56 | }); 57 | wrapper.vm.$nextTick(() => { 58 | expect(wrapper.vm.$refs.picker.getColumnValue(2).code).to.equal('110102'); 59 | done(); 60 | }); 61 | }); 62 | 63 | it('create an area with invalid areaList', () => { 64 | wrapper = mount(Area, { 65 | propsData: { 66 | areaList: null 67 | } 68 | }); 69 | 70 | expect(wrapper.hasClass('van-area')).to.be.true; 71 | expect(wrapper.vm.areaColumns.length).to.equal(0); 72 | 73 | }); 74 | 75 | it('create an area with columnsNum equal 2', () => { 76 | wrapper = mount(Area, { 77 | propsData: { 78 | areaList: AreaList, 79 | columnsNum: 2 80 | } 81 | }); 82 | 83 | expect(wrapper.hasClass('van-area')).to.be.true; 84 | expect(wrapper.vm.areaColumns.length).to.equal(2); 85 | }); 86 | 87 | it('create an area with columnsNum equal 1', () => { 88 | wrapper = mount(Area, { 89 | propsData: { 90 | areaList: AreaList, 91 | columnsNum: 1 92 | } 93 | }); 94 | 95 | expect(wrapper.hasClass('van-area')).to.be.true; 96 | expect(wrapper.vm.areaColumns.length).to.equal(1); 97 | }); 98 | 99 | it('create an area and click cancel', (done) => { 100 | wrapper = mount(Area, { 101 | propsData: { 102 | areaList: AreaList 103 | } 104 | }); 105 | 106 | expect(wrapper.hasClass('van-area')).to.be.true; 107 | const cancelBtn = wrapper.find('.van-picker__cancel')[0]; 108 | const eventStub = sinon.stub(wrapper.vm, '$emit'); 109 | 110 | cancelBtn.trigger('click'); 111 | wrapper.vm.$nextTick(() => { 112 | expect(eventStub.calledOnce).to.be.true; 113 | expect(eventStub.calledWith('cancel')); 114 | done(); 115 | }); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /test/unit/specs/badge.spec.js: -------------------------------------------------------------------------------- 1 | import { mount } from 'avoriaz'; 2 | import BadgeTestComponent from '../components/badge'; 3 | 4 | describe('BadgeGroup', () => { 5 | let wrapper; 6 | afterEach(() => { 7 | wrapper && wrapper.destroy(); 8 | }); 9 | 10 | it('create a badge-group', () => { 11 | wrapper = mount(BadgeTestComponent); 12 | 13 | expect(wrapper.hasClass('van-badge-group')).to.be.true; 14 | 15 | expect(wrapper.vNode.child.activeKey).to.equal(0); 16 | expect(wrapper.vNode.child.badges.length).to.equal(2); 17 | }); 18 | 19 | it('emit a click event when click badge', () => { 20 | wrapper = mount(BadgeTestComponent); 21 | 22 | const badge = wrapper.find('.van-badge')[0]; 23 | const eventStub = sinon.stub(badge.vNode.child, '$emit'); 24 | badge.trigger('click'); 25 | 26 | expect(eventStub.calledWith('click')).to.be.true; 27 | }); 28 | }); 29 | 30 | -------------------------------------------------------------------------------- /test/unit/specs/card.spec.js: -------------------------------------------------------------------------------- 1 | import Card from 'packages/card'; 2 | import { mount } from 'avoriaz'; 3 | 4 | describe('Card', () => { 5 | let wrapper; 6 | afterEach(() => { 7 | wrapper && wrapper.destroy(); 8 | }); 9 | 10 | it('create', () => { 11 | wrapper = mount(Card, { 12 | propsData: { 13 | thumb: 'thumb' 14 | } 15 | }); 16 | 17 | expect(wrapper.hasClass('van-card')).to.be.true; 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/unit/specs/cell.spec.js: -------------------------------------------------------------------------------- 1 | import CellGroup from 'packages/cell-group'; 2 | import Cell from 'packages/cell'; 3 | import { mount } from 'avoriaz'; 4 | 5 | describe('CellGroup', () => { 6 | let wrapper; 7 | afterEach(() => { 8 | wrapper && wrapper.destroy(); 9 | }); 10 | 11 | it('create a cell-group', () => { 12 | wrapper = mount(CellGroup, { 13 | propsData: {} 14 | }); 15 | 16 | expect(wrapper.hasClass('van-cell-group')).to.be.true; 17 | }); 18 | }); 19 | 20 | describe('Cell', () => { 21 | let wrapper; 22 | afterEach(() => { 23 | wrapper && wrapper.destroy(); 24 | }); 25 | 26 | it('create', () => { 27 | wrapper = mount(Cell); 28 | 29 | expect(wrapper.hasClass('van-cell')).to.be.true; 30 | }); 31 | 32 | it('create a required cell', () => { 33 | wrapper = mount(Cell, { 34 | propsData: { 35 | required: true 36 | } 37 | }); 38 | 39 | expect(wrapper.hasClass('van-cell')).to.be.true; 40 | expect(wrapper.hasClass('van-cell--required')).to.be.true; 41 | }); 42 | 43 | it('emit a click event', () => { 44 | wrapper = mount(Cell); 45 | 46 | const eventStub = sinon.stub(wrapper.vm, '$emit'); 47 | wrapper.trigger('click'); 48 | 49 | expect(eventStub.calledOnce).to.be.true; 50 | expect(eventStub.calledWith('click')).to.be.true; 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/unit/specs/dialog.spec.js: -------------------------------------------------------------------------------- 1 | import Dialog from 'packages/dialog'; 2 | 3 | describe('Dialog', () => { 4 | afterEach(() => { 5 | Dialog.close(); 6 | }); 7 | 8 | it('create a alert dialog', (done) => { 9 | Dialog.alert({ 10 | title: 'title', 11 | message: 'message' 12 | }).then((action) => { 13 | expect(action).to.equal('confirm'); 14 | done(); 15 | }); 16 | 17 | setTimeout(() => { 18 | expect(document.querySelector('.van-dialog')).to.exist; 19 | expect(document.querySelector('.van-dialog__cancel').style.display).to.equal('none'); 20 | document.querySelector('.van-dialog__confirm').click(); 21 | }, 500); 22 | }); 23 | 24 | it('create a confirm dialog', (done) => { 25 | Dialog.confirm({ 26 | title: 'title', 27 | message: 'message' 28 | }).catch((action) => { 29 | expect(action).to.equal('cancel'); 30 | done(); 31 | }); 32 | 33 | expect(document.querySelector('.van-dialog')).to.exist; 34 | 35 | setTimeout(() => { 36 | document.querySelector('.van-dialog__cancel').click(); 37 | }, 500); 38 | }); 39 | 40 | it('create a confirm dialog with callback', (done) => { 41 | Dialog.confirm({ 42 | callback: (action) => { 43 | expect(action).to.equal('cancel'); 44 | done(); 45 | } 46 | }); 47 | 48 | setTimeout(() => { 49 | document.querySelector('.van-dialog__cancel').click(); 50 | }, 500); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/unit/specs/goods-action.spec.js: -------------------------------------------------------------------------------- 1 | import GoodsAction from '../components/goods-action'; 2 | import GoodsActionBigBtn from 'packages/goods-action-big-btn'; 3 | import GoodsActionMiniBtn from 'packages/goods-action-mini-btn'; 4 | import { mount } from 'avoriaz'; 5 | import { DOMChecker } from '../utils'; 6 | 7 | describe('GoodsAction', () => { 8 | let wrapper; 9 | 10 | afterEach(() => { 11 | wrapper && wrapper.destroy(); 12 | }); 13 | 14 | it('create a GoodsAction', () => { 15 | wrapper = mount(GoodsAction, {}); 16 | 17 | DOMChecker(wrapper, { 18 | count: { 19 | '.van-goods-action__mini-btn': 2, 20 | '.van-goods-action__big-btn': 2, 21 | '.van-icon-chat': 1 22 | } 23 | }); 24 | }); 25 | 26 | it('click GoodsActionBigBtn', () => { 27 | wrapper = mount(GoodsActionBigBtn, {}); 28 | 29 | const submitSpyFunc = sinon.spy(); 30 | wrapper.vm.$on('click', submitSpyFunc); 31 | wrapper.trigger('click'); 32 | expect(submitSpyFunc.calledOnce).to.be.true; 33 | }); 34 | 35 | it('click GoodsActionMiniBtn', () => { 36 | wrapper = mount(GoodsActionMiniBtn, { 37 | propsData: { 38 | icon: 'card' 39 | } 40 | }); 41 | 42 | const submitSpyFunc = sinon.spy(); 43 | wrapper.vm.$on('click', submitSpyFunc); 44 | wrapper.trigger('click'); 45 | expect(submitSpyFunc.calledOnce).to.be.true; 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/unit/specs/icon.spec.js: -------------------------------------------------------------------------------- 1 | import Icon from 'packages/icon'; 2 | import { mount } from 'avoriaz'; 3 | 4 | describe('Icon', () => { 5 | let wrapper; 6 | 7 | afterEach(() => { 8 | wrapper && wrapper.destroy(); 9 | }); 10 | 11 | it('create a icon', () => { 12 | wrapper = mount(Icon, { 13 | propsData: { 14 | name: 'arrow' 15 | } 16 | }); 17 | 18 | expect(wrapper.hasClass('van-icon')).to.be.true; 19 | expect(wrapper.hasClass('van-icon-arrow')).to.be.true; 20 | }); 21 | 22 | it('emit a click event', () => { 23 | wrapper = mount(Icon, { 24 | propsData: { 25 | name: 'arrow' 26 | } 27 | }); 28 | 29 | const eventStub = sinon.stub(wrapper.vm, '$emit'); 30 | wrapper.trigger('click'); 31 | 32 | expect(eventStub.calledOnce).to.be.true; 33 | expect(eventStub.calledWith('click')).to.be.true; 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/unit/specs/image-preview.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { mount } from 'avoriaz'; 3 | import { triggerTouch } from '../utils'; 4 | import ImagePreview from 'packages/image-preview'; 5 | import ImagePreviewVue from 'packages/image-preview/image-preview'; 6 | 7 | const images = [ 8 | 'https://img.yzcdn.cn/upload_files/2017/03/15/FkubrzN7AgGwLlTeb1E89-T_ZjBg.png', 9 | 'https://img.yzcdn.cn/upload_files/2017/03/14/FmTPs0SeyQaAOSK1rRe1sL8RcwSY.jpeg', 10 | 'https://img.yzcdn.cn/upload_files/2017/03/15/FvexrWlG_WxtCE9Omo5l27n_mAG_.jpeg' 11 | ]; 12 | 13 | describe('ImagePreview', () => { 14 | let wrapper; 15 | 16 | afterEach(() => { 17 | wrapper && wrapper.destroy(); 18 | }); 19 | 20 | it('call ImagePreview Function', (done) => { 21 | ImagePreview(images); 22 | Vue.nextTick(() => { 23 | expect(document.querySelectorAll('.van-image-preview img').length).to.equal(3); 24 | done(); 25 | }); 26 | }); 27 | 28 | it('create a ImagePreview Component', (done) => { 29 | wrapper = mount(ImagePreviewVue); 30 | wrapper.vm.images = images; 31 | wrapper.vm.value = true; 32 | 33 | expect(wrapper.hasClass('van-image-preview')).to.be.true; 34 | 35 | wrapper.vm.$nextTick(() => { 36 | expect(wrapper.find('img').length).to.equal(3); 37 | triggerTouch(wrapper, 'touchstart', 0, 0); 38 | triggerTouch(wrapper, 'touchmove', 100, 100); 39 | triggerTouch(wrapper, 'touchend', 0, 0); 40 | expect(wrapper.vm.value).to.be.true; 41 | 42 | triggerTouch(wrapper, 'touchstart', 0, 0); 43 | triggerTouch(wrapper, 'touchmove', 0, 0); 44 | triggerTouch(wrapper, 'touchend', 0, 0); 45 | expect(wrapper.vm.value).to.be.false; 46 | done(); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/unit/specs/layout.spec.js: -------------------------------------------------------------------------------- 1 | import { mount } from 'avoriaz'; 2 | import Col from 'packages/col'; 3 | import Row from 'packages/row'; 4 | import RowTestComponent from '../components/row'; 5 | 6 | describe('Layout', () => { 7 | let wrapper; 8 | afterEach(() => { 9 | wrapper && wrapper.destroy(); 10 | }); 11 | 12 | it('create a simple row', () => { 13 | wrapper = mount(Row); 14 | 15 | expect(wrapper.hasClass('van-row')).to.be.true; 16 | expect(wrapper.vm.style).to.be.empty; 17 | }); 18 | 19 | it('create a simple column', () => { 20 | wrapper = mount(Col, { 21 | propsData: { 22 | span: 8, 23 | offset: 8 24 | } 25 | }); 26 | expect(wrapper.hasClass('van-col')).to.be.true; 27 | expect(wrapper.hasClass('van-col-8')).to.be.true; 28 | expect(wrapper.hasClass('van-col-offset-8')).to.be.true; 29 | expect(wrapper.vm.gutter).to.equal(0); 30 | }); 31 | 32 | it('create a gutter row', () => { 33 | wrapper = mount(RowTestComponent); 34 | const row = wrapper.find(Row)[0]; 35 | const column = wrapper.find(Col)[0]; 36 | expect(row.hasStyle('margin-left', '-5px')).to.be.true; 37 | expect(row.hasStyle('margin-right', '-5px')).to.be.true; 38 | expect(column.hasStyle('padding-left', '5px')).to.be.true; 39 | expect(column.hasStyle('padding-right', '5px')).to.be.true; 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/unit/specs/loading.spec.js: -------------------------------------------------------------------------------- 1 | import Loading from 'packages/loading'; 2 | import { mount } from 'avoriaz'; 3 | 4 | describe('Loading', () => { 5 | let wrapper; 6 | afterEach(() => { 7 | wrapper && wrapper.destroy(); 8 | }); 9 | 10 | it('create default', () => { 11 | wrapper = mount(Loading); 12 | 13 | expect(wrapper.hasClass('van-loading')).to.be.true; 14 | }); 15 | 16 | it('create gradient-circle black', () => { 17 | wrapper = mount(Loading, { 18 | propsData: { 19 | type: 'gradient-circle', 20 | color: 'black' 21 | } 22 | }); 23 | const spinner = wrapper.find('.van-loading__spinner')[0]; 24 | 25 | expect(spinner.hasClass('van-loading__spinner--gradient-circle')).to.be.true; 26 | expect(spinner.hasClass('van-loading__spinner--black')).to.be.true; 27 | }); 28 | 29 | it('create gradient-circle white', () => { 30 | wrapper = mount(Loading, { 31 | propsData: { 32 | type: 'gradient-circle', 33 | color: 'white' 34 | } 35 | }); 36 | const spinner = wrapper.find('.van-loading__spinner')[0]; 37 | 38 | expect(spinner.hasClass('van-loading__spinner--gradient-circle')).to.be.true; 39 | expect(spinner.hasClass('van-loading__spinner--white')).to.be.true; 40 | }); 41 | 42 | it('create circle black', () => { 43 | wrapper = mount(Loading, { 44 | propsData: { 45 | type: 'circle', 46 | color: 'black' 47 | } 48 | }); 49 | const spinner = wrapper.find('.van-loading__spinner')[0]; 50 | 51 | expect(spinner.hasClass('van-loading__spinner--circle')).to.be.true; 52 | expect(spinner.hasClass('van-loading__spinner--black')).to.be.true; 53 | }); 54 | 55 | it('create circle white', () => { 56 | wrapper = mount(Loading, { 57 | propsData: { 58 | type: 'circle', 59 | color: 'white' 60 | } 61 | }); 62 | const spinner = wrapper.find('.van-loading__spinner')[0]; 63 | 64 | expect(spinner.hasClass('van-loading__spinner--circle')).to.be.true; 65 | expect(spinner.hasClass('van-loading__spinner--white')).to.be.true; 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/unit/specs/nav-bar.spec.js: -------------------------------------------------------------------------------- 1 | import NavBar from 'packages/nav-bar'; 2 | import { mount } from 'avoriaz'; 3 | import { DOMChecker } from '../utils'; 4 | 5 | describe('NavBar', () => { 6 | let wrapper; 7 | afterEach(() => { 8 | wrapper && wrapper.destroy(); 9 | }); 10 | 11 | it('create a NavBar', () => { 12 | wrapper = mount(NavBar, { 13 | propsData: { 14 | title: '标题', 15 | leftText: '返回', 16 | rightText: '按钮', 17 | leftArrow: true 18 | } 19 | }); 20 | 21 | DOMChecker(wrapper, { 22 | text: { 23 | '.van-nav-bar__title': '标题', 24 | '.van-nav-bar__left .van-nav-bar__text': '返回', 25 | '.van-nav-bar__right .van-nav-bar__text': '按钮' 26 | }, 27 | count: { 28 | '.van-nav-bar__arrow': 1 29 | } 30 | }); 31 | expect(wrapper.hasClass('van-nav-bar')).to.be.true; 32 | }); 33 | 34 | it('NavBar fixed', () => { 35 | wrapper = mount(NavBar, { 36 | propsData: { 37 | fixed: true 38 | } 39 | }); 40 | 41 | expect(wrapper.hasClass('van-nav-bar')).to.be.true; 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/unit/specs/notice-bar.spec.js: -------------------------------------------------------------------------------- 1 | import NoticeBar from '../components/notice-bar'; 2 | import { mount } from 'avoriaz'; 3 | 4 | describe('NoticeBar', () => { 5 | let wrapper; 6 | afterEach(() => { 7 | wrapper && wrapper.destroy(); 8 | }); 9 | 10 | it('create a notice-bar', () => { 11 | wrapper = mount(NoticeBar, { 12 | propsData: {}, 13 | attachToDocument: true 14 | }); 15 | 16 | expect(wrapper.find('.van-notice-bar').length).to.equal(1); 17 | }); 18 | 19 | it('mode closeable', () => { 20 | wrapper = mount(NoticeBar, { 21 | propsData: { 22 | mode: 'closeable' 23 | }, 24 | attachToDocument: true 25 | }); 26 | 27 | const icon = wrapper.find('.van-icon-close'); 28 | expect(icon.length).to.equal(1); 29 | 30 | icon[0].trigger('click'); 31 | expect(wrapper.hasStyle('display', 'none')); 32 | }); 33 | 34 | it('mode link', () => { 35 | wrapper = mount(NoticeBar, { 36 | propsData: { 37 | mode: 'link' 38 | }, 39 | attachToDocument: true 40 | }); 41 | 42 | expect(wrapper.find('.van-icon-arrow').length).to.equal(1); 43 | }); 44 | 45 | it('notice-bar transitionend', (done) => { 46 | wrapper = mount(NoticeBar, { 47 | propsData: { 48 | text: '足协杯战线连续第2年上演广州德比战', 49 | speed: 1000, 50 | delay: 0 51 | }, 52 | attachToDocument: true 53 | }); 54 | 55 | const content = wrapper.find('.van-notice-bar__content')[0]; 56 | setTimeout(() => { 57 | expect(content.hasStyle('transition-delay', '0s')).to.be.true; 58 | done(); 59 | }, 500); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/unit/specs/password-input.spec.js: -------------------------------------------------------------------------------- 1 | import PasswordInput from 'packages/password-input'; 2 | import { mount } from 'avoriaz'; 3 | 4 | describe('PasswordInput', () => { 5 | let wrapper; 6 | afterEach(() => { 7 | wrapper && wrapper.destroy(); 8 | }); 9 | 10 | it('create a PasswordInput', () => { 11 | wrapper = mount(PasswordInput, {}); 12 | expect(wrapper.find('.van-password-input').length).to.equal(1); 13 | }); 14 | 15 | it('create a PasswordInput with value && info', (done) => { 16 | wrapper = mount(PasswordInput, { 17 | propsData: { 18 | value: '000', 19 | info: '测试info' 20 | } 21 | }); 22 | 23 | expect(wrapper.find('.van-password-input i')[2].hasStyle('visibility', 'visible')).to.be.true; 24 | expect(wrapper.find('.van-password-input i')[3].hasStyle('visibility', 'visible')).to.be.false; 25 | expect(wrapper.find('.van-password-input__info')[0].text()).to.equal('测试info'); 26 | 27 | wrapper.vm.value = '0000'; 28 | wrapper.vm.errorInfo = '测试errorInfo'; 29 | wrapper.vm.$nextTick(() => { 30 | expect(wrapper.find('.van-password-input i')[3].hasStyle('visibility', 'visible')).to.be.true; 31 | expect(wrapper.find('.van-password-input__info').length).to.equal(0); 32 | expect(wrapper.find('.van-password-input__error-info')[0].text()).to.equal('测试errorInfo'); 33 | done(); 34 | }); 35 | }); 36 | 37 | it('listen to focus event', () => { 38 | wrapper = mount(PasswordInput, {}); 39 | 40 | const focus = sinon.spy(); 41 | wrapper.vm.$on('focus', focus); 42 | wrapper.find('.van-password-input__security')[0].trigger('touchstart'); 43 | 44 | expect(focus.calledOnce).to.be.true; 45 | }); 46 | 47 | it('change password length', () => { 48 | wrapper = mount(PasswordInput, { 49 | propsData: { 50 | length: 2 51 | } 52 | }); 53 | 54 | expect(wrapper.find('.van-password-input i').length).to.equal(2); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/unit/specs/popup.spec.js: -------------------------------------------------------------------------------- 1 | import Popup from 'packages/popup'; 2 | import { mount } from 'avoriaz'; 3 | import { triggerTouch } from '../utils'; 4 | 5 | describe('Popup', () => { 6 | let wrapper; 7 | afterEach(() => { 8 | wrapper && wrapper.destroy(); 9 | }); 10 | 11 | it('create a popup', () => { 12 | wrapper = mount(Popup, { 13 | propsData: { 14 | position: 'bottom' 15 | } 16 | }); 17 | 18 | expect(wrapper.hasClass('van-popup')).to.be.true; 19 | expect(wrapper.instance().currentTransition).to.equal('popup-slide-bottom'); 20 | }); 21 | 22 | it('create a show popup', (done) => { 23 | wrapper = mount(Popup, { 24 | propsData: { 25 | value: false 26 | } 27 | }); 28 | 29 | const eventStub = sinon.stub(wrapper.vm, '$emit'); 30 | expect(wrapper.data().currentValue).to.be.false; 31 | 32 | wrapper.vm.value = true; 33 | wrapper.update(); 34 | wrapper.vm.$nextTick(() => { 35 | expect(wrapper.data().currentValue).to.be.true; 36 | expect(eventStub.calledWith('input')); 37 | done(); 38 | }); 39 | }); 40 | 41 | it('toggle popup show', () => { 42 | wrapper = mount(Popup, { 43 | propsData: { 44 | value: true 45 | } 46 | }); 47 | 48 | expect(wrapper.data().currentValue).to.be.true; 49 | }); 50 | 51 | it('create a popup-fade transition popup', () => { 52 | wrapper = mount(Popup, { 53 | propsData: { 54 | transition: 'popup-fade' 55 | } 56 | }); 57 | 58 | expect(wrapper.hasClass('van-popup')).to.be.true; 59 | expect(wrapper.instance().currentTransition).to.equal('popup-fade'); 60 | }); 61 | 62 | it('popup prevent scroll', (done) => { 63 | wrapper = mount(Popup, { 64 | propsData: { 65 | preventScroll: true, 66 | value: true 67 | } 68 | }); 69 | 70 | expect(wrapper.hasClass('van-popup')).to.be.true; 71 | 72 | setTimeout(() => { 73 | expect(wrapper.data().currentValue).to.be.true; 74 | wrapper.vm.value = false; 75 | triggerTouch(document, 'touchstart', 0, 0); 76 | triggerTouch(document, 'touchmove', 0, 10); 77 | triggerTouch(document, 'touchmove', 0, 30); 78 | triggerTouch(document, 'touchmove', 0, -30); 79 | 80 | setTimeout(() => { 81 | expect(wrapper.data().currentValue).to.be.false; 82 | done(); 83 | }, 300); 84 | }, 300); 85 | }); 86 | 87 | it('popup modal', (done) => { 88 | wrapper = mount(Popup, { 89 | propsData: { 90 | preventScroll: true, 91 | value: true 92 | } 93 | }); 94 | 95 | wrapper.vm.$on('input', val => { 96 | wrapper.vm.value = val; 97 | }); 98 | 99 | expect(wrapper.hasClass('van-popup')).to.be.true; 100 | 101 | const modal = document.querySelector('.van-modal'); 102 | 103 | setTimeout(() => { 104 | triggerTouch(modal, 'touchstart', 0, 0); 105 | triggerTouch(modal, 'touchmove', 0, 10); 106 | triggerTouch(modal, 'touchmove', 0, 30); 107 | triggerTouch(modal, 'touchmove', 0, -30); 108 | expect(modal).to.exist; 109 | 110 | modal.click(); 111 | setTimeout(() => { 112 | expect(wrapper.data().currentValue).to.be.false; 113 | done(); 114 | }, 300); 115 | }, 300); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /test/unit/specs/progress.spec.js: -------------------------------------------------------------------------------- 1 | import Progress from 'packages/progress'; 2 | import { mount } from 'avoriaz'; 3 | 4 | describe('Progress', () => { 5 | let wrapper; 6 | let bar; 7 | let pivot; 8 | const initProgressBar = function(propsData) { 9 | wrapper = mount(Progress, { 10 | propsData: propsData 11 | }); 12 | bar = wrapper.find('.van-progress__portion')[0]; 13 | pivot = wrapper.find('.van-progress__pivot')[0]; 14 | }; 15 | 16 | afterEach(() => { 17 | wrapper && wrapper.destroy(); 18 | }); 19 | 20 | it('create active 3% progress bar', () => { 21 | initProgressBar({ percentage: 3 }); 22 | 23 | expect(wrapper.hasClass('van-progress')).to.be.true; 24 | expect(bar.is('span')).to.be.true; 25 | expect(bar.hasStyle('width', '3%')); 26 | 27 | expect(pivot.is('span')).to.be.true; 28 | expect(pivot.hasStyle('left', '0%')); 29 | expect(pivot.hasStyle('marginLeft', '0')); 30 | expect(pivot.text()).to.equal('3%'); 31 | }); 32 | 33 | it('create active 35% progress bar', () => { 34 | initProgressBar({ percentage: 35 }); 35 | 36 | expect(wrapper.hasClass('van-progress')).to.be.true; 37 | expect(bar.is('span')).to.be.true; 38 | expect(bar.hasStyle('width', '35%')); 39 | 40 | expect(pivot.is('span')).to.be.true; 41 | expect(pivot.hasStyle('left', '35%')); 42 | expect(pivot.hasStyle('marginLeft', '-14px')); 43 | expect(pivot.text()).to.equal('35%'); 44 | }); 45 | 46 | it('create active 98% progress bar', () => { 47 | initProgressBar({ percentage: 98 }); 48 | 49 | expect(wrapper.hasClass('van-progress')).to.be.true; 50 | expect(bar.is('span')).to.be.true; 51 | expect(bar.hasStyle('width', '98%')); 52 | 53 | expect(pivot.is('span')).to.be.true; 54 | expect(pivot.hasStyle('left', '100%')); 55 | expect(pivot.hasStyle('marginLeft', '-28px')); 56 | expect(pivot.text()).to.equal('98%'); 57 | }); 58 | 59 | it('create inactive 35% progress bar', () => { 60 | initProgressBar({ percentage: 35, inactive: true }); 61 | 62 | expect(pivot.hasStyle('backgroundColor', '#cacaca')); 63 | }); 64 | 65 | it('create progress bar with custom text', () => { 66 | initProgressBar({ percentage: 35, pivotText: 'pivotText' }); 67 | 68 | expect(pivot.text()).to.equal('pivotText'); 69 | }); 70 | 71 | it('create progress bar with custom color', () => { 72 | initProgressBar({ percentage: 35, color: 'red' }); 73 | 74 | expect(pivot.hasStyle('backgroundColor', 'red')); 75 | }); 76 | 77 | it('create progress bar with text color', () => { 78 | initProgressBar({ percentage: 35, textColor: 'red' }); 79 | 80 | expect(pivot.hasStyle('color', 'red')); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/unit/specs/steps.spec.js: -------------------------------------------------------------------------------- 1 | import Steps from 'packages/steps'; 2 | import { mount } from 'avoriaz'; 3 | import StepsTestComponent from '../components/steps'; 4 | 5 | describe('Steps', () => { 6 | let wrapper; 7 | 8 | afterEach(() => { 9 | wrapper && wrapper.destroy(); 10 | }); 11 | 12 | it('create a steps', () => { 13 | wrapper = mount(Steps); 14 | 15 | expect(wrapper.hasClass('van-steps')).to.be.true; 16 | expect(wrapper.data().steps.length).to.equal(0); 17 | }); 18 | 19 | it('create a steps with step', () => { 20 | wrapper = mount(StepsTestComponent); 21 | 22 | const finishStep = wrapper.find('.van-step')[0]; 23 | expect(finishStep.hasClass('van-step--finish')).to.be.true; 24 | expect(finishStep.hasClass('van-step--horizontal')).to.be.true; 25 | 26 | const proccessStep = wrapper.find('.van-step')[1]; 27 | expect(proccessStep.hasClass('van-step--process')).to.be.true; 28 | }); 29 | 30 | it('create a vertical step', () => { 31 | wrapper = mount(Steps, { 32 | propsData: { 33 | direction: 'vertical' 34 | } 35 | }); 36 | 37 | expect(wrapper.hasClass('van-steps')).to.be.true; 38 | expect(wrapper.hasClass('van-steps--vertical')).to.be.true; 39 | expect(wrapper.data().steps.length).to.equal(0); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/unit/specs/submit-bar.spec.js: -------------------------------------------------------------------------------- 1 | import SubmitBar from 'packages/submit-bar'; 2 | import { mount } from 'avoriaz'; 3 | import { DOMChecker } from '../utils'; 4 | 5 | describe('SubmitBar', () => { 6 | let wrapper; 7 | afterEach(() => { 8 | wrapper && wrapper.destroy(); 9 | }); 10 | 11 | it('default', () => { 12 | const props = { 13 | price: 3050, 14 | buttonText: '提交订单', 15 | tip: '您的收货地址不支持同城送, 我们已为您推荐快递' 16 | }; 17 | 18 | wrapper = mount(SubmitBar, { 19 | propsData: props 20 | }); 21 | 22 | DOMChecker(wrapper, { 23 | text: { 24 | '.van-button__text': props.buttonText, 25 | '.van-submit-bar__price-interger': '¥30.', 26 | '.van-submit-bar__price-decimal': '50', 27 | '.van-submit-bar__tip': props.tip 28 | } 29 | }); 30 | }); 31 | 32 | it('no tip', () => { 33 | wrapper = mount(SubmitBar, { 34 | propsData: { 35 | price: 3005, 36 | buttonText: '提交订单', 37 | buttonType: 'default' 38 | } 39 | }); 40 | 41 | DOMChecker(wrapper, { 42 | text: { 43 | '.van-button__text': '提交订单', 44 | '.van-submit-bar__price-interger': '¥30.', 45 | '.van-submit-bar__price-decimal': '05', 46 | '.van-submit-bar__tip': '' 47 | } 48 | }); 49 | }); 50 | 51 | it('handle submit', () => { 52 | wrapper = mount(SubmitBar, { 53 | propsData: { 54 | price: 3005, 55 | buttonText: '提交订单' 56 | } 57 | }); 58 | 59 | const submitSpyFunc = sinon.spy(); 60 | wrapper.vm.$on('submit', submitSpyFunc); 61 | wrapper.find('.van-button')[0].trigger('click'); 62 | setTimeout(() => { 63 | expect(submitSpyFunc.calledOnce).to.be.true; 64 | }, 300); 65 | }); 66 | 67 | it('can not submit when disabled', () => { 68 | wrapper = mount(SubmitBar, { 69 | propsData: { 70 | disabled: true, 71 | buttonText: '提交订单' 72 | } 73 | }); 74 | 75 | const submitSpyFunc = sinon.spy(); 76 | wrapper.vm.$on('submit', submitSpyFunc); 77 | wrapper.find('.van-button')[0].trigger('click'); 78 | setTimeout(() => { 79 | expect(submitSpyFunc.calledOnce).to.be.false; 80 | }, 300); 81 | }); 82 | 83 | it('can not submit when loading', () => { 84 | wrapper = mount(SubmitBar, { 85 | propsData: { 86 | loading: true, 87 | buttonText: '提交订单' 88 | } 89 | }); 90 | 91 | const submitSpyFunc = sinon.spy(); 92 | wrapper.vm.$on('submit', submitSpyFunc); 93 | wrapper.find('.van-button')[0].trigger('click'); 94 | setTimeout(() => { 95 | expect(submitSpyFunc.calledOnce).to.be.false; 96 | }, 300); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /test/unit/specs/switch-cell.spec.js: -------------------------------------------------------------------------------- 1 | import SwitchCell from 'packages/switch-cell'; 2 | import { mount } from 'avoriaz'; 3 | import { DOMChecker } from '../utils'; 4 | 5 | describe('SwitchCell', () => { 6 | let wrapper; 7 | afterEach(() => { 8 | wrapper && wrapper.destroy(); 9 | }); 10 | 11 | it('default', () => { 12 | wrapper = mount(SwitchCell, { 13 | attachToDocument: true 14 | }); 15 | 16 | DOMChecker(wrapper, { 17 | count: { 18 | '.van-switch--off': 1, 19 | '.van-switch--disabled': 0 20 | } 21 | }); 22 | }); 23 | 24 | it('set title', () => { 25 | wrapper = mount(SwitchCell, { 26 | attachToDocument: true, 27 | propsData: { 28 | title: '测试标题' 29 | } 30 | }); 31 | 32 | DOMChecker(wrapper, { 33 | text: { 34 | '.van-cell__text': '测试标题' 35 | }, 36 | count: { 37 | '.van-switch--off': 1, 38 | '.van-switch--disabled': 0 39 | } 40 | }); 41 | }); 42 | 43 | it('checked', () => { 44 | wrapper = mount(SwitchCell, { 45 | attachToDocument: true, 46 | propsData: { 47 | value: true 48 | } 49 | }); 50 | 51 | DOMChecker(wrapper, { 52 | count: { 53 | '.van-switch--on': 1, 54 | '.van-switch--disabled': 0 55 | } 56 | }); 57 | }); 58 | 59 | it('disabled', () => { 60 | wrapper = mount(SwitchCell, { 61 | attachToDocument: true, 62 | propsData: { 63 | disabled: true 64 | } 65 | }); 66 | 67 | DOMChecker(wrapper, { 68 | count: { 69 | '.van-switch--off': 1, 70 | '.van-switch--disabled': 1 71 | } 72 | }); 73 | }); 74 | 75 | it('listen to change event', (done) => { 76 | wrapper = mount(SwitchCell, { 77 | attachToDocument: true, 78 | propsData: { 79 | value: false 80 | } 81 | }); 82 | 83 | wrapper.vm.$on('input', (value) => { 84 | wrapper.vm.value = value; 85 | }); 86 | 87 | wrapper.vm.$on('change', (value) => { 88 | expect(value).to.be.true; 89 | done(); 90 | }); 91 | 92 | const switchEl = wrapper.find('.van-switch')[0]; 93 | switchEl.trigger('click'); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /test/unit/specs/switch.spec.js: -------------------------------------------------------------------------------- 1 | import Switch from 'packages/switch'; 2 | import VanLoading from 'packages/loading'; 3 | import { mount } from 'avoriaz'; 4 | 5 | describe('Switch', () => { 6 | let wrapper; 7 | 8 | afterEach(() => { 9 | wrapper && wrapper.destroy(); 10 | }); 11 | 12 | it('create on switch', () => { 13 | wrapper = mount(Switch, { 14 | propsData: { 15 | value: true 16 | } 17 | }); 18 | 19 | expect(wrapper.hasClass('van-switch')).to.be.true; 20 | expect(wrapper.hasClass('van-switch--on')).to.be.true; 21 | }); 22 | 23 | it('create off switch', () => { 24 | wrapper = mount(Switch, { 25 | propsData: { 26 | value: false 27 | } 28 | }); 29 | 30 | expect(wrapper.hasClass('van-switch')).to.be.true; 31 | expect(wrapper.hasClass('van-switch--off')).to.be.true; 32 | }); 33 | 34 | it('create loading switch', () => { 35 | wrapper = mount(Switch, { 36 | propsData: { 37 | loading: true 38 | } 39 | }); 40 | const loading = wrapper.find(VanLoading)[0]; 41 | 42 | expect(wrapper.hasClass('van-switch')).to.be.true; 43 | expect(loading.isVueComponent).to.be.true; 44 | }); 45 | 46 | it('loading switch should be unclickable', () => { 47 | wrapper = mount(Switch, { 48 | propsData: { 49 | loading: true, 50 | value: true 51 | } 52 | }); 53 | 54 | expect(wrapper.hasClass('van-switch--on')).to.be.true; 55 | wrapper.trigger('click'); 56 | expect(wrapper.hasClass('van-switch--on')).to.be.true; 57 | }); 58 | 59 | it('create disabled switch', () => { 60 | wrapper = mount(Switch, { 61 | propsData: { 62 | disabled: true 63 | } 64 | }); 65 | 66 | expect(wrapper.hasClass('van-switch')).to.be.true; 67 | expect(wrapper.hasClass('van-switch--disabled')).to.be.true; 68 | }); 69 | 70 | it('disabled switch should be unclickable', () => { 71 | wrapper = mount(Switch, { 72 | propsData: { 73 | disabled: true, 74 | value: false 75 | } 76 | }); 77 | 78 | expect(wrapper.hasClass('van-switch--off')).to.be.true; 79 | wrapper.trigger('click'); 80 | expect(wrapper.hasClass('van-switch--off')).to.be.true; 81 | }); 82 | 83 | it('click should toggle the switch', () => { 84 | wrapper = mount(Switch, { 85 | propsData: { 86 | value: false 87 | } 88 | }); 89 | 90 | wrapper.vm.$on('input', val => { 91 | wrapper.vm.value = val; 92 | }); 93 | 94 | expect(wrapper.hasClass('van-switch--off')).to.be.true; 95 | wrapper.trigger('click'); 96 | expect(wrapper.hasClass('van-switch--on')).to.be.true; 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /test/unit/specs/tabbar.spec.js: -------------------------------------------------------------------------------- 1 | import TabbarExample from '../components/tabbar'; 2 | import { mount } from 'avoriaz'; 3 | 4 | describe('Progress', () => { 5 | let wrapper; 6 | 7 | afterEach(() => { 8 | wrapper && wrapper.destroy(); 9 | }); 10 | 11 | it('Tabbar with four items', (done) => { 12 | wrapper = mount(TabbarExample); 13 | 14 | wrapper.vm.$nextTick(() => { 15 | expect(wrapper.find('.van-tabbar-item').length).to.equal(4); 16 | 17 | wrapper.find('.van-tabbar-item')[3].element.click(); 18 | expect(wrapper.vm.active).to.equal(3); 19 | expect(wrapper.vm.changeRecord).to.equal(3); 20 | done(); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/unit/specs/tag.spec.js: -------------------------------------------------------------------------------- 1 | import Tag from 'packages/tag'; 2 | import { mount } from 'avoriaz'; 3 | 4 | describe('Tag', () => { 5 | let wrapper; 6 | afterEach(() => { 7 | wrapper && wrapper.destroy(); 8 | }); 9 | 10 | it('create without typeProps', () => { 11 | wrapper = mount(Tag); 12 | }); 13 | 14 | it('create with right typeProps', () => { 15 | wrapper = mount(Tag, { 16 | propsData: { 17 | type: 'primary' 18 | } 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/unit/specs/tree-select.spec.js: -------------------------------------------------------------------------------- 1 | import TreeSelect from 'packages/tree-select'; 2 | import { mount } from 'avoriaz'; 3 | 4 | describe('TreeSelect', () => { 5 | let wrapper; 6 | afterEach(() => { 7 | wrapper && wrapper.destroy(); 8 | }); 9 | 10 | it('create an empty tree-select', () => { 11 | wrapper = mount(TreeSelect); 12 | expect(wrapper.hasStyle('height', '0px')).to.be.true; 13 | }); 14 | 15 | it('create a tree-select correctly', () => { 16 | wrapper = mount(TreeSelect, { 17 | propsData: { 18 | items: [{ 19 | text: 'A', 20 | children: [{ 21 | text: 'Cc', 22 | id: 123 23 | }] 24 | }], 25 | maxHeight: 200 26 | } 27 | }); 28 | expect(wrapper.hasClass('van-tree-select')).to.be.true; 29 | expect(wrapper.hasStyle('height', '44px')).to.be.true; 30 | expect(wrapper.vm.maxHeight).to.equal(200); 31 | }); 32 | 33 | it('interact with this component', () => { 34 | wrapper = mount(TreeSelect, { 35 | propsData: { 36 | items: [{ 37 | text: 'A', 38 | children: [{ 39 | text: 'Cc', 40 | id: 123 41 | }, { 42 | text: 'Bb', 43 | id: 234 44 | }] 45 | }, { 46 | text: 'B', 47 | children: [{ 48 | text: 'Nmi', 49 | id: 345 50 | }] 51 | }], 52 | maxHeight: 220 53 | } 54 | }); 55 | wrapper.vm.$on('navclick', index => { 56 | wrapper.vm.mainActiveIndex = index; 57 | }); 58 | wrapper.vm.$on('itemclick', item => { 59 | wrapper.vm.activeId = item.id; 60 | }); 61 | const secondNav = wrapper.find('.van-tree-select__nitem')[1]; 62 | secondNav.trigger('click'); 63 | expect(wrapper.vm.mainActiveIndex).to.equal(1); 64 | const target = wrapper.find('.van-tree-select__item')[0]; 65 | target.trigger('click'); 66 | expect(wrapper.vm.activeId).to.equal(345); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/unit/specs/uploader.spec.js: -------------------------------------------------------------------------------- 1 | import Uploader from 'packages/uploader'; 2 | import { mount } from 'avoriaz'; 3 | window.File = function() { 4 | this.name = 'test'; 5 | }; 6 | window.FileReader = function() { 7 | this.readAsDataURL = this.readAsText = function() { 8 | this.onload && this.onload({ 9 | target: { 10 | result: 'test' 11 | } 12 | }); 13 | }; 14 | }; 15 | describe('Uploader', () => { 16 | let wrapper; 17 | afterEach(() => { 18 | wrapper && wrapper.destroy(); 19 | }); 20 | 21 | it('enabled', () => { 22 | wrapper = mount(Uploader, { 23 | propsData: { 24 | disabled: false 25 | } 26 | }); 27 | 28 | expect(wrapper.contains('input')).to.equal(true); 29 | expect(wrapper.vm.onValueChange({ target: { files: [] }})).to.equal(undefined); 30 | }); 31 | 32 | it('disabled', () => { 33 | wrapper = mount(Uploader, { 34 | propsData: { 35 | disabled: true 36 | } 37 | }); 38 | 39 | expect(wrapper.contains('input')).to.equal(true); 40 | expect(wrapper.vm.onValueChange({ target: { files: [] }})).to.equal(undefined); 41 | }); 42 | 43 | it('before read', () => { 44 | wrapper = mount(Uploader, { 45 | propsData: { 46 | disabled: false, 47 | beforeRead: () => { 48 | return false; 49 | } 50 | } 51 | }); 52 | 53 | expect(wrapper.contains('input')).to.equal(true); 54 | expect(wrapper.vm.onValueChange({ target: { files: [new File([], '')] }})).to.equal(undefined); 55 | }); 56 | 57 | it('read text', () => { 58 | wrapper = mount(Uploader, { 59 | propsData: { 60 | disabled: false, 61 | resultType: 'text', 62 | afterRead: (file) => { 63 | } 64 | } 65 | }); 66 | 67 | expect(wrapper.contains('input')).to.equal(true); 68 | expect(wrapper.vm.onValueChange({ target: { files: [new File([], '/Users')] }})).to.equal(undefined); 69 | }); 70 | 71 | it('read text no after hook', () => { 72 | wrapper = mount(Uploader, { 73 | propsData: { 74 | disabled: false, 75 | resultType: 'text' 76 | } 77 | }); 78 | 79 | expect(wrapper.contains('input')).to.equal(true); 80 | expect(wrapper.vm.onValueChange({ target: { files: [new File([], '/Users')] }})).to.equal(undefined); 81 | }); 82 | 83 | it('read dataUrl', () => { 84 | wrapper = mount(Uploader, { 85 | propsData: { 86 | disabled: false, 87 | resultType: 'dataUrl', 88 | afterRead: (file) => { 89 | } 90 | } 91 | }); 92 | 93 | expect(wrapper.contains('input')).to.equal(true); 94 | expect(wrapper.vm.onValueChange({ target: { files: [new File([], '/Users')] }})).to.equal(undefined); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /test/unit/specs/waterfall.spec.js: -------------------------------------------------------------------------------- 1 | import Waterfall from '../components/waterfall/waterfall'; 2 | import HiddenWaterfall from '../components/waterfall/waterfall-hide'; 3 | import { mount } from 'avoriaz'; 4 | 5 | describe('Waterfall', () => { 6 | let wrapper; 7 | afterEach(() => { 8 | wrapper && wrapper.destroy(); 9 | }); 10 | 11 | it('create', (done) => { 12 | const waterfallLowerSpy = sinon.spy(); 13 | wrapper = mount(Waterfall, { 14 | attachToDocument: true, 15 | propsData: { 16 | disabled: false, 17 | list: [], 18 | onWaterfallLower: waterfallLowerSpy 19 | } 20 | }); 21 | 22 | setTimeout(() => { 23 | expect(waterfallLowerSpy.called).to.be.true; 24 | done(); 25 | }, 500); 26 | }); 27 | 28 | it('test waterfall lower function', (done) => { 29 | const waterfallLowerSpy = sinon.spy(function() { 30 | wrapper.vm.list = wrapper.vm.list.concat([{ id: 1 }, { id: 2 }, { id: 3 }]); 31 | wrapper.vm.disabled = true; 32 | }); 33 | wrapper = mount(Waterfall, { 34 | attachToDocument: true, 35 | propsData: { 36 | disabled: false, 37 | list: [{ id: 10 }], 38 | onWaterfallLower: waterfallLowerSpy 39 | } 40 | }); 41 | 42 | setTimeout(() => { 43 | const item = wrapper.find('.waterfall-item'); 44 | expect(waterfallLowerSpy.calledOnce).to.be.true; 45 | expect(item.length).to.equal(4); 46 | expect(item[item.length - 1].text()).to.equal('3'); 47 | done(); 48 | }, 500); 49 | }); 50 | 51 | it('test waterfall upper function', (done) => { 52 | const waterfallUpperSpy = sinon.spy(function() { 53 | wrapper.vm.list.unshift({ id: 1 }, { id: 2 }, { id: 3 }); 54 | wrapper.vm.disabled = true; 55 | }); 56 | wrapper = mount(Waterfall, { 57 | attachToDocument: true, 58 | propsData: { 59 | disabled: false, 60 | list: [{ id: 10 }], 61 | onWaterfallUpper: waterfallUpperSpy 62 | } 63 | }); 64 | 65 | setTimeout(() => { 66 | const item = wrapper.find('.waterfall-item'); 67 | expect(waterfallUpperSpy.calledOnce).to.be.true; 68 | expect(item.length).to.equal(4); 69 | expect(item[0].text()).to.equal('1'); 70 | done(); 71 | }, 500); 72 | }); 73 | 74 | it('test waterfall function after hide', (done) => { 75 | const waterfallLowerSpy = sinon.spy(); 76 | wrapper = mount(HiddenWaterfall, { 77 | attachToDocument: true, 78 | propsData: { 79 | show: false, 80 | disabled: false, 81 | list: [{ id: 10 }], 82 | onWaterfallLower: waterfallLowerSpy 83 | } 84 | }); 85 | 86 | setTimeout(() => { 87 | expect(waterfallLowerSpy.called).to.be.false; 88 | done(); 89 | }, 500); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /test/unit/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 按照一定的规则进行匹配 3 | */ 4 | export function DOMChecker(wrapper, rules) { 5 | const { text, count, src, style, noStyle, value } = rules; 6 | 7 | if (text) { 8 | Object.keys(text).forEach(key => { 9 | expect(wrapper.find(key)[0].text().trim()).to.equal(text[key]); 10 | }); 11 | } 12 | 13 | if (count) { 14 | Object.keys(count).forEach(key => { 15 | expect(wrapper.find(key).length).to.equal(count[key]); 16 | }); 17 | } 18 | 19 | if (src) { 20 | Object.keys(src).forEach(key => { 21 | expect(wrapper.find(key)[0].element.src).to.equal(src[key]); 22 | }); 23 | } 24 | 25 | if (value) { 26 | Object.keys(value).forEach(key => { 27 | expect(wrapper.find(key)[0].element.value).to.equal(value[key]); 28 | }); 29 | } 30 | 31 | if (style) { 32 | Object.keys(style).forEach(key => { 33 | Object.keys(style[key]).forEach(prop => { 34 | expect(wrapper.find(key)[0].hasStyle(prop, style[key][prop])).to.equal( 35 | true 36 | ); 37 | }); 38 | }); 39 | } 40 | 41 | if (noStyle) { 42 | Object.keys(noStyle).forEach(key => { 43 | Object.keys(noStyle[key]).forEach(prop => { 44 | expect( 45 | wrapper.find(key)[0].hasStyle(prop, noStyle[key][prop]) 46 | ).to.equal(false); 47 | }); 48 | }); 49 | } 50 | } 51 | 52 | // 触发一个 touch 事件 53 | export function triggerTouch(wrapper, eventName, x, y) { 54 | const el = wrapper.element ? wrapper.element : wrapper; 55 | const touch = { 56 | identifier: Date.now(), 57 | target: el, 58 | pageX: x, 59 | pageY: y, 60 | clientX: x, 61 | clientY: y, 62 | radiusX: 2.5, 63 | radiusY: 2.5, 64 | rotationAngle: 10, 65 | force: 0.5 66 | }; 67 | 68 | const event = document.createEvent('CustomEvent'); 69 | event.initCustomEvent(eventName, true, true, {}); 70 | event.touches = [touch]; 71 | event.targetTouches = [touch]; 72 | event.changedTouches = [touch]; 73 | 74 | el.dispatchEvent(event); 75 | } 76 | --------------------------------------------------------------------------------