├── .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 |
8 |
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 |
3 |
4 |
5 |
6 |
7 |
8 | A Vue.js 2.0 Mobile UI at YouZan
9 |
10 | [](https://travis-ci.org/youzan/vant) [](https://codecov.io/github/youzan/vant?branch=dev) [](https://www.npmjs.com/package/vant) [](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 | 
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 | {modelValue-=1}">
100 | {modelValue+=1}">
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 |
{{percent}}%
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 |
2 |
3 |
4 |
5 |
17 |
--------------------------------------------------------------------------------
/docs/src/ExamplesDocsApp.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
47 |
48 |
66 |
--------------------------------------------------------------------------------
/docs/src/components/demo-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Zan UI
4 |
Vue中文社区 MILk-UI
5 |
10 |
11 |
12 |
13 |
49 |
50 |
81 |
--------------------------------------------------------------------------------
/docs/src/components/mobile-nav.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 | {{group.groupName}}
10 |
11 |
12 |
13 |
14 | -
18 |
21 |
22 | {{ navItem.title }}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
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 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
{{message}}
16 |
17 |
{{option}}
18 |
{{cancelText}}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
63 |
--------------------------------------------------------------------------------
/packages/actionsheet/index.js:
--------------------------------------------------------------------------------
1 | import VActionsheet from './actionsheet.vue';
2 |
3 | export default VActionsheet;
4 |
--------------------------------------------------------------------------------
/packages/button/button.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
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 |
2 |
3 |
9 |
10 |
11 |
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 |
2 |
7 |
8 |
18 |
19 |
20 |
21 |
22 |
23 |
33 |
34 |
35 |
36 |
88 |
--------------------------------------------------------------------------------
/packages/col/col.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
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 |
2 |
10 |
11 |
12 |
50 |
--------------------------------------------------------------------------------
/packages/icon/index.js:
--------------------------------------------------------------------------------
1 | import VIcon from './icon';
2 |
3 | export default VIcon;
4 |
--------------------------------------------------------------------------------
/packages/imagePicker/image-picker.vue:
--------------------------------------------------------------------------------
1 |
2 |
21 |
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 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
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 |
2 |
24 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{title}}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
49 |
--------------------------------------------------------------------------------
/packages/pagination/index.js:
--------------------------------------------------------------------------------
1 | import VPage from './pagination.vue';
2 |
3 | export default VPage;
4 |
--------------------------------------------------------------------------------
/packages/pagination/pagination.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{prevText}}
5 |
6 |
7 |
8 |
9 |
10 | {{currentPage}}/{{total}}
11 |
12 |
13 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {{nextText}}
29 |
30 |
31 |
32 |
33 |
34 |
35 |
110 |
--------------------------------------------------------------------------------
/packages/popover/index.js:
--------------------------------------------------------------------------------
1 | import VPopover from './popover.vue';
2 |
3 | export default VPopover;
4 |
--------------------------------------------------------------------------------
/packages/popover/popover.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
20 |
21 |
22 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
67 |
--------------------------------------------------------------------------------
/packages/progress/index.js:
--------------------------------------------------------------------------------
1 | import VProgress from './progress.vue';
2 |
3 | export default VProgress;
4 |
--------------------------------------------------------------------------------
/packages/progress/progress.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
50 |
--------------------------------------------------------------------------------
/packages/pulltorefresh/index.js:
--------------------------------------------------------------------------------
1 | import VPullToRefresh from './pulltorefresh.vue';
2 |
3 | export default VPullToRefresh;
--------------------------------------------------------------------------------
/packages/pulltorefresh/pulltorefresh.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
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 |
2 |
3 |
4 |
5 |
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 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
15 |
96 |
--------------------------------------------------------------------------------
/packages/swipeaction/index.js:
--------------------------------------------------------------------------------
1 | import VSwipeAction from './swipeaction.vue';
2 |
3 | export default VSwipeAction;
--------------------------------------------------------------------------------
/packages/swipeaction/swipeaction.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
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 |
2 |
19 |
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 |
2 |
8 |
9 |
10 |
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 |
2 |
3 |
4 |
5 |
6 |
{{ message }}
7 |
8 |
9 |
10 |
11 |
12 | {{ message }}
13 |
14 |
15 |
16 |
17 |
18 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
23 |
--------------------------------------------------------------------------------
/test/unit/components/checkbox.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 复选框{{item}}
4 |
5 |
6 |
7 |
30 |
--------------------------------------------------------------------------------
/test/unit/components/field.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | icon
4 |
5 |
6 |
7 |
18 |
19 |
--------------------------------------------------------------------------------
/test/unit/components/goods-action.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 客服
5 |
6 |
7 | 购物车
8 |
9 |
10 | 加入购物车
11 |
12 |
13 | 立即购买
14 |
15 |
16 |
17 |
18 |
38 |
--------------------------------------------------------------------------------
/test/unit/components/more-tabs.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 内容一
4 | 内容二
5 | 内容三
6 | 内容四
7 | 内容五
8 | 内容六
9 | 内容七
10 | 内容八
11 |
12 |
13 |
14 |
29 |
--------------------------------------------------------------------------------
/test/unit/components/notice-bar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 |
17 |
--------------------------------------------------------------------------------
/test/unit/components/number-keyboard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
20 |
--------------------------------------------------------------------------------
/test/unit/components/radio.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 单选框1
4 | 单选框2
5 |
6 |
7 |
8 |
25 |
--------------------------------------------------------------------------------
/test/unit/components/row.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | span: 8
5 |
6 |
7 | span: 8
8 |
9 |
10 | span: 8
11 |
12 |
13 |
14 |
15 |
26 |
--------------------------------------------------------------------------------
/test/unit/components/steps.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 买家下单
4 | 商家接单
5 | 买家提货
6 | 交易完成
7 |
8 |
9 |
10 |
27 |
--------------------------------------------------------------------------------
/test/unit/components/tabbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 自定义
5 |
6 |
7 | 标签
8 | 标签
9 | 标签
10 |
11 |
12 |
13 |
41 |
--------------------------------------------------------------------------------
/test/unit/components/tabs.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 内容一
4 | 内容二
5 | 内容三
6 | 内容四
7 |
8 |
9 |
10 |
47 |
--------------------------------------------------------------------------------
/test/unit/components/waterfall/waterfall-hide.vue:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 |
37 |
38 |
39 |
46 |
--------------------------------------------------------------------------------
/test/unit/components/waterfall/waterfall.vue:
--------------------------------------------------------------------------------
1 |
35 |
36 |
37 |
47 |
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 |
--------------------------------------------------------------------------------