提交 57f5493c 编写于 作者: M miaodian

init

上级
{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": [
"> 1%",
"last 2 versions",
"not ie <= 20",
"not ie_mob <= 100",
"not ff <= 100",
"not and_ff <= 100",
"not Edge <= 100",
"Android >= 4.0"
]
}
}],
"stage-2"
],
"plugins": ["transform-runtime", "add-module-exports", "transform-es2015-modules-umd"],
"comments": false,
"env": {
"test": {
"presets": ["env", "stage-2"],
"plugins": [ "istanbul" ]
}
}
}
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
build/*.js
config/*.js
module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
},
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
extends: 'standard',
// required to lint *.vue files
plugins: [
'html'
],
// add your custom rules here
'rules': {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'no-tabs': 0,
'space-before-function-paren': 0
}
}
.idea/
.DS_Store
node_modules/
npm-debug.log
deploy/
test/unit/coverage
test/e2e/reports
selenium-debug.log
yarn.lock
package-lock=false
# Contribution Guideline
Thanks for considering to contribute this project. All issues and pull requests are highly appreciated.
## Pull Requests
Before sending pull request to this project, please read and follow guidelines below.
1. Branch: We only accept pull request on `dev` branch.
2. Coding style: Follow the coding style used in cube-ui.
3. Commit message: Use English and be aware of your spell.
4. Test: Make sure to test your code.
Add device mode, API version, related log, screenshots and other related information in your pull request if possible.
NOTE: We assume all your contribution can be licensed under the [Apache License 2.0](https://github.com/didi/cube-ui/blob/master/LICENSE).
## Issues
We love clearly described issues. :)
Following information can help us to resolve the issue faster.
* Device mode and hardware information.
* API version.
* Logs.
* Screenshots.
* Steps to reproduce the issue.
 Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
# cube-ui
> A fantastic mobile ui lib implement by Vue.
### Links
- [Home](https://didi.github.io/cube-ui/)
- [Docs](https://didi.github.io/cube-ui/#/en-US/docs)
- [Example](https://didi.github.io/cube-ui/example/)
![example QR](./assets/example-qr.png)
### Install
```shell
npm install cube-ui --save
```
### Usage
```js
import Vue from 'vue'
import Cube from 'cube-ui'
Vue.use(Cube)
```
#### Use modularized cube-ui
```js
import Vue from 'vue'
import { Button, ActionSheet } from 'cube-ui'
Vue.use(Button)
Vue.use(ActionSheet)
```
For more information, please refer to [Quick Start](https://didi.github.io/cube-ui/#/en-US/docs/quick-start)
### ToDo
- More components
- Support theme
### Development
```shell
git clone git@github.com:didi/cube-ui.git
cd cube-ui
npm install
npm run dev
```
### Changelog
Detailed changes for each release are documented in the [release notes](https://github.com/didi/cube-ui/releases).
# cube-ui
> A fantastic mobile ui lib implement by Vue.
### 导航
- [首页](https://didi.github.io/cube-ui/)
- [文档](https://didi.github.io/cube-ui/#/zh-CN/docs)
- [示例](https://didi.github.io/cube-ui/example/)
![示例二维码](./assets/example-qr.png)
### 安装
```shell
npm install cube-ui --save
```
### 使用
```js
import Vue from 'vue'
import Cube from 'cube-ui'
Vue.use(Cube)
```
#### 按需使用
```js
import Vue from 'vue'
import { Button, ActionSheet } from 'cube-ui'
Vue.use(Button)
Vue.use(ActionSheet)
```
注:上述使用依赖插件 [babel-plugin-transform-modules](https://www.npmjs.com/package/babel-plugin-transform-modules),详细内容请看 [开始文档](https://didi.github.io/cube-ui/#/zh-CN/docs/quick-start)
### ToDo
- 更多组件
- 主题支持
### Development
```shell
git clone git@github.com:didi/cube-ui.git
cd cube-ui
npm install
npm run dev
```
### Changelog
详细日志请看[发布日志](https://github.com/didi/cube-ui/releases)
// https://github.com/shelljs/shelljs
require('shelljs/global')
var config = require('../config')
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = JSON.parse(config.build.env.NODE_ENV)
}
var path = require('path')
var utils = require('./utils')
var ora = require('ora')
var webpack = require('webpack')
var webpackConfig = require('./webpack.prod.conf')
var webpackModulesConfig = require('./webpack.modules.conf')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
rm('-rf', assetsPath)
mkdir('-p', assetsPath)
// cp('-R', 'static/*', assetsPath)
function buildPack(webpackConfig, cb, spinnerText) {
var spinner = ora(spinnerText || 'building for uncompressed files...')
spinner.start()
webpack(webpackConfig, function (err, stats) {
spinner.stop()
if (err) {
throw err
}
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n')
cb && cb()
})
}
buildPack(webpackConfig, function () {
webpackConfig.output.filename = utils.assetsPath('[name].min.js')
webpackConfig.output.chunkFilename = '[name].min.js'
webpackConfig.plugins.splice(2, 1, new ExtractTextPlugin(utils.assetsPath('[name].min.css')), new OptimizeCSSPlugin({
cssProcessorOptions: {
safe: true
}
}))
// add UglifyJsPlugin
webpackConfig.plugins.splice(2, 0, new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}))
buildPack(webpackConfig, function () {
buildPack(webpackModulesConfig, function () {
webpackModulesConfig.output.filename = utils.assetsPath('[name]/[name].min.js')
webpackModulesConfig.output.chunkFilename = '[name]/[name].min.js'
webpackModulesConfig.plugins.splice(2, 1, new ExtractTextPlugin(utils.assetsPath('[name]/[name].min.css')), new OptimizeCSSPlugin({
cssProcessorOptions: {
safe: true
}
}))
// 增加 UglifyJsPlugin
webpackModulesConfig.plugins.splice(2, 0, new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}))
buildPack(webpackModulesConfig, null, 'building for compressed component files...')
}, 'building for uncompressed component files...')
}, 'building for compressed files...')
})
var chalk = require('chalk')
var semver = require('semver')
var packageConfig = require('../package.json')
var shell = require('shelljs')
function exec (cmd) {
return require('child_process').execSync(cmd).toString().trim()
}
var versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
},
]
if (shell.which('npm')) {
versionRequirements.push({
name: 'npm',
currentVersion: exec('npm --version'),
versionRequirement: packageConfig.engines.npm
})
}
module.exports = function () {
var warnings = []
var i
for (i = 0; i < versionRequirements.length; i++) {
var mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
)
}
}
if (warnings.length) {
console.log('')
console.log(chalk.yellow('To use this template, you must update following to modules:'))
console.log()
for (i = 0; i < warnings.length; i++) {
var warning = warnings[i]
console.log(' ' + warning)
}
console.log()
process.exit(1)
}
}
/* eslint-disable */
require('eventsource-polyfill')
var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
hotClient.subscribe(function (event) {
if (event.action === 'reload') {
window.location.reload()
}
})
require('./check-versions')()
var config = require('../config')
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
}
var path = require('path')
var express = require('express')
var webpack = require('webpack')
var opn = require('opn')
var proxyMiddleware = require('http-proxy-middleware')
var webpackConfig = require('./webpack.dev.conf')
// default port where dev server listens for incoming traffic
var port = process.env.PORT || config.dev.port
// Define HTTP proxies to your custom API backend
// https://github.com/chimurai/http-proxy-middleware
var proxyTable = config.dev.proxyTable
// automatically open browser, if not set will be false
var autoOpenBrowser = !!config.dev.autoOpenBrowser
var app = express()
var compiler = webpack(webpackConfig)
// var devMiddleware = require('webpack-dev-middleware')(compiler, {
// publicPath: webpackConfig.output.publicPath,
// stats: {
// colors: true,
// chunks: false
// },
// watchOptions: {
// aggregateTimeout: 300,
// poll: 1000
// }
// })
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
quiet: true
})
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
log: function () {}
})
// force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
hotMiddleware.publish({action: 'reload'})
cb()
})
})
// proxy api requests
Object.keys(proxyTable).forEach(function (context) {
var options = proxyTable[context]
if (typeof options === 'string') {
options = {target: options}
}
app.use(proxyMiddleware(context, options))
})
// handle fallback for HTML5 history API
app.use(require('connect-history-api-fallback')())
// serve webpack bundle output
app.use(devMiddleware)
// enable hot-reload and state-preserving
// compilation error display
app.use(hotMiddleware)
// serve pure static assets
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
app.use(staticPath, express.static('./static'))
var uri = 'http://localhost:' + port
var _resolve
var readyPromise = new Promise(resolve => {
_resolve = resolve
})
console.log('> Starting dev server...')
devMiddleware.waitUntilValid(() => {
console.log('> Listening at ' + uri + '\n')
// when env is testing, don't need open it
if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
opn(uri)
}
_resolve()
})
var server = app.listen(port)
module.exports = {
ready: readyPromise,
close: () => {
server.close()
}
}
require('shelljs/global')
process.env.NODE_ENV = 'production'
var ora = require('ora')
var rm = require('rimraf')
var path = require('path')
var chalk = require('chalk')
var webpack = require('webpack')
var config = require('../../config')
var webpackConfig = require('./webpack.prod.conf')
var spinner = ora('building for document production...')
spinner.start()
rm(path.join(config.docBuild.assetsRoot, config.docBuild.assetsSubDirectory), err => {
if (err) {
throw err
}
webpack(webpackConfig, function (err, stats) {
spinner.stop()
if (err) {
throw err
}
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n')
console.log(chalk.cyan(' Document build complete.\n'))
})
})
var path = require('path')
var express = require('express')
var webpack = require('webpack')
var config = require('../../config')
var opn = require('opn')
var proxyMiddleware = require('http-proxy-middleware')
var webpackConfig = require('./webpack.dev.conf')
// default port where dev server listens for incoming traffic
var port = process.env.PORT || config.docDev.port
// Define HTTP proxies to your custom API backend
// https://github.com/chimurai/http-proxy-middleware
var proxyTable = config.dev.proxyTable
var app = express()
var compiler = webpack(webpackConfig)
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
stats: {
colors: true,
chunks: false
},
watchOptions: {
aggregateTimeout: 300,
poll: 1000
}
})
var hotMiddleware = require('webpack-hot-middleware')(compiler)
// force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
hotMiddleware.publish({action: 'reload'})
cb()
})
})
// proxy api requests
Object.keys(proxyTable).forEach(function (context) {
var options = proxyTable[context]
if (typeof options === 'string') {
options = {target: options}
}
app.use(proxyMiddleware(context, options))
})
// handle fallback for HTML5 history API
app.use(require('connect-history-api-fallback')())
// serve webpack bundle output
app.use(devMiddleware)
// enable hot-reload and state-preserving
// compilation error display
app.use(hotMiddleware)
// serve pure static assets
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
app.use(staticPath, express.static('./static'))
module.exports = app.listen(port, function (err) {
if (err) {
console.log(err)
return
}
var uri = 'http://localhost:' + port
console.log('Listening at ' + uri + '\n')
opn(uri)
})
var config = require('../../config')
var devWebpackConfig = require('../webpack.dev.conf')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var entry = config.docDev.entry
// add hot-reload related code to entry chunks
Object.keys(entry).forEach(function (name) {
entry[name] = ['./build/dev-client'].concat(entry[name])
})
var docDevWebpackConfig = Object.assign({}, devWebpackConfig)
var index = docDevWebpackConfig.plugins.findIndex(function (plugin) {
return plugin instanceof HtmlWebpackPlugin
})
docDevWebpackConfig.plugins.splice(index, 1, new HtmlWebpackPlugin({
filename: 'index.html',
template: config.docDev.template,
inject: true
}))
docDevWebpackConfig.entry = entry
module.exports = docDevWebpackConfig
var path = require('path')
var config = require('../../config')
var utils = require('../utils')
var webpack = require('webpack')
var merge = require('webpack-merge')
var baseWebpackConfig = require('../webpack.base.conf')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
var webpackConfig = merge(baseWebpackConfig, {
entry: {
app: './document/main.js'
},
module: {
rules: utils.styleLoaders({
sourceMap: config.docBuild.productionSourceMap,
extract: true
})
},
devtool: config.docBuild.productionSourceMap ? '#source-map' : false,
output: {
path: config.docBuild.assetsRoot,
publicPath: '/cube-ui/',
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
new webpack.DefinePlugin({
'process.env': config.docBuild.env
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}),
// extract css into its own file
new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
new OptimizeCSSPlugin({
cssProcessorOptions: {
safe: true
}
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.docBuild.index,
template: 'document/index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
})
]
})
if (config.build.productionGzip) {
var CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
module.exports = webpackConfig
require('shelljs/global')
process.env.NODE_ENV = 'production'
var ora = require('ora')
var rm = require('rimraf')
var path = require('path')
var chalk = require('chalk')
var webpack = require('webpack')
var config = require('../../config')
var webpackConfig = require('./webpack.prod.conf')
var spinner = ora('building for example production...')
spinner.start()
rm(path.join(config.demoBuild.assetsRoot, config.demoBuild.assetsSubDirectory), err => {
if (err) {
throw err
}
webpack(webpackConfig, function (err, stats) {
spinner.stop()
if (err) {
throw err
}
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n')
console.log(chalk.cyan(' Example build complete.\n'))
})
})
var config = require('../../config')
var utils = require('../utils')
var docProdWebpackConfig = require('../document/webpack.prod.conf')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var webpackConfig = Object.assign({}, docProdWebpackConfig, {
entry: config.demoBuild.entry,
output: {
path: config.demoBuild.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
}
})
var index = webpackConfig.plugins.findIndex(function (plugin) {
return plugin instanceof HtmlWebpackPlugin
})
webpackConfig.plugins.splice(index, 1, new HtmlWebpackPlugin({
filename: 'index.html',
template: 'example/index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}))
module.exports = webpackConfig
#!/bin/bash
# git pull
git pull origin master
# build
npm run doc-demo-build
# ADD commit
git add docs/
git commit -m 'docs build'
git push origin master
#!/bin/bash
# git pull
git pull origin master
# build
npm run build
# ADD commit
git add -A
git commit -m 'publish build'
git push origin master
# replace src/ __VERSION__
node ./build/release/replace-version.js
# publish
npm publish
# checkout src/index.js
git checkout src/index.js
var fs = require('fs')
var path = require('path')
var pkg = require('../../package.json')
var version = pkg.version
var mainPath = path.resolve(__dirname, '../../src/index.js')
var content = fs.readFileSync(mainPath).toString()
content = content.replace('__VERSION__', '\'' + version + '\'')
fs.writeFileSync(mainPath, content)
var path = require('path')
var config = require('../config')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var postcssConfig = require('../postcss.config.js')
exports.assetsPath = function (_path) {
var assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
return path.posix.join(assetsSubDirectory, _path)
}
exports.cssLoaders = function (options) {
options = options || {}
var cssLoader = {
loader: 'css-loader',
options: {
minimize: process.env.NODE_ENV === 'production',
sourceMap: options.sourceMap
}
}
var postcssLoader = {
loader: 'postcss-loader',
options: {
sourceMap: options.sourceMap,
plugins: () => postcssConfig.plugins
}
}
// generate loader string to be used with extract text plugin
function generateLoaders(loader, loaderOptions) {
var loaders = [cssLoader, postcssLoader]
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader'
})
} else {
return ['vue-style-loader'].concat(loaders)
}
}
var stylusOptions = {
'resolve url': true
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', {indentedSyntax: true}),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus', stylusOptions),
styl: generateLoaders('stylus', stylusOptions)
}
}
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
var output = []
var loaders = exports.cssLoaders(options)
for (var extension in loaders) {
var loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}
var utils = require('./utils')
var config = require('../config')
var isProduction = process.env.NODE_ENV === 'production'
module.exports = {
loaders: utils.cssLoaders({
sourceMap: isProduction
? config.build.productionSourceMap
: config.dev.cssSourceMap,
extract: isProduction
}),
transformToRequire: {
video: 'src',
source: 'src',
img: 'src',
image: 'xlink:href'
}
}
var path = require('path')
var config = require('../config')
var vueLoaderConfig = require('./vue-loader.conf')
var utils = require('./utils')
var webpack = require('webpack')
var version = require('../package.json').version
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
const srcAndExampleAndTestAndDocument = [resolve('src'), resolve('example'), resolve('test'), resolve('document')]
module.exports = {
output: {
path: config.build.assetsRoot,
publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
filename: '[name].js',
chunkFilename: '[name].js'
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue2': 'vue/dist/vue.esm.js',
'@': resolve('src')
}
},
module: {
rules: [
{
test: /\.(js|vue)$/,
loader: 'eslint-loader',
enforce: 'pre',
include: srcAndExampleAndTestAndDocument,
options: {
formatter: require('eslint-friendly-formatter')
}
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: srcAndExampleAndTestAndDocument
},
{
test: /\.(png|jpe?g|gif)(\?.*)?$/,
loader: 'url-loader',
include: srcAndExampleAndTestAndDocument,
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/,
loader: 'url-loader',
include: srcAndExampleAndTestAndDocument,
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
},
{
test: /\.md$/,
include: resolve('document'),
loader: 'vue-markdown-loader'
}
]
},
plugins: [
new webpack.DefinePlugin({
__VERSION__: JSON.stringify(version)
})
]
}
var config = require('../config')
var webpack = require('webpack')
var merge = require('webpack-merge')
var utils = require('./utils')
var baseWebpackConfig = require('./webpack.base.conf')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
var entry = {
app: './example/main.js'
}
// add hot-reload related code to entry chunks
Object.keys(entry).forEach(function (name) {
entry[name] = ['./build/dev-client'].concat(entry[name])
})
module.exports = merge(baseWebpackConfig, {
entry: entry,
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
},
// eval-source-map is faster for development
devtool: '#cheap-module-eval-source-map',
plugins: [
new webpack.DefinePlugin({
'process.env': config.dev.env
}),
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: './example/index.html',
inject: true
}),
new FriendlyErrorsPlugin()
]
})
var path = require('path')
var fs = require('fs')
var config = require('../config')
var utils = require('./utils')
var webpack = require('webpack')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var env = process.env.NODE_ENV === 'testing'
? require('../config/test.env')
: config.build.env
var modules = {}
var cPath = path.join(__dirname, '../src/modules')
var files = fs.readdirSync(cPath)
if (files) {
files.forEach(function (name) {
var p = path.join(cPath, name)
if (fs.statSync(p).isDirectory()) {
modules[name] = p
}
})
}
var webpackConfig = merge(baseWebpackConfig, {
entry: modules,
module: {
rules: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true })
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('[name]/index.js'),
library: 'cube',
libraryTarget: 'commonjs2'
},
plugins: [
// http://vuejs.github.io/vue-loader/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
// extract css into its own file
new ExtractTextPlugin(utils.assetsPath('[name]/style.css'))
]
})
module.exports = webpackConfig
var path = require('path')
var config = require('../config')
var utils = require('./utils')
var webpack = require('webpack')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var CopyWebpackPlugin = require('copy-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
var env = process.env.NODE_ENV === 'testing'
? require('../config/test.env')
: config.build.env
var webpackConfig = merge(baseWebpackConfig, {
entry: {
cube: './src/index.js'
},
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true
})
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('index.js'),
library: 'cube',
libraryTarget: 'umd'
},
plugins: [
// http://vuejs.github.io/vue-loader/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
// extract css into its own file
new ExtractTextPlugin(utils.assetsPath('style.css'))
]
})
if (config.build.bundleAnalyzerReport) {
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig
// This is the webpack config used for unit tests.
var utils = require('./utils')
var webpack = require('webpack')
var merge = require('webpack-merge')
var baseConfig = require('./webpack.base.conf')
var webpackConfig = merge(baseConfig, {
// use inline sourcemap for karma-sourcemap-loader
module: {
rules: utils.styleLoaders({
sourceMap: true
})
},
devtool: '#inline-source-map',
plugins: [
new webpack.DefinePlugin({
'process.env': require('../config/test.env')
})
]
})
module.exports = webpackConfig
var merge = require('webpack-merge')
var prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})
// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')
module.exports = {
build: {
env: require('./prod.env'),
assetsRoot: path.resolve(__dirname, '../lib'),
assetsSubDirectory: '',
assetsPublicPath: './',
productionSourceMap: false,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report
},
dev: {
env: require('./dev.env'),
port: 8081,
autoOpenBrowser: true,
assetsSubDirectory: '',
assetsPublicPath: '/',
proxyTable: {},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
// In our experience, they generally work as expected,
// just be aware of this issue when enabling this option.
cssSourceMap: false
},
docBuild: {
env: require('./prod.env'),
index: path.resolve(__dirname, '../docs/index.html'),
assetsRoot: path.resolve(__dirname, '../docs'),
assetsSubDirectory: '',
productionSourceMap: false
},
docDev: {
env: require('./dev.env'),
entry: {
document: './document/main.js'
},
template: './document/index.html',
port: 8082,
},
demoBuild: {
entry: {
app: './example/main.js'
},
assetsRoot: path.resolve(__dirname, '../docs/example'),
assetsSubDirectory: ''
}
}
module.exports = {
NODE_ENV: '"production"'
}
var merge = require('webpack-merge')
var devEnv = require('./dev.env')
module.exports = merge(devEnv, {
NODE_ENV: '"testing"'
})
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<style lang="stylus" rel="stylesheet/stylus">
#app
width: 100%
height: 100%
</style>
{
"en-US": {
"overview": {
"name": "Overview",
"subList": {
"introduction": "Introduction",
"quick-start": "QuickStart",
"post-compile": "PostCompile"
}
},
"components": {
"name": "Components",
"subList": {
"basic": {
"name": "Basic",
"subList": {
"button": "Button",
"checkbox": "Checkbox",
"checkbox-group": "CheckboxGroup",
"loading": "Loading",
"tip": "Tip"
}
},
"popup": {
"name": "Popup",
"subList": {
"popup": "Popup",
"toast": "Toast",
"picker": "Picker",
"time-picker": "TimePicker",
"dialog": "Dialog",
"action-sheet": "ActionSheet"
}
},
"scroll": {
"name": "Scroll",
"subList": {
"scroll": "Scroll",
"slide": "Slide",
"index-list": "IndexList"
}
}
}
},
"modules": {
"name": "Modules",
"subList": {
"style": "style",
"create-api": "create-api",
"better-scroll": "better-scroll"
}
}
},
"zh-CN": {
"overview": {
"name": "概览",
"subList": {
"introduction": "介绍",
"quick-start": "快速上手",
"post-compile": "后编译"
}
},
"components": {
"name": "组件",
"subList": {
"basic": {
"name": "基础",
"subList": {
"button": "Button",
"checkbox": "Checkbox",
"checkbox-group": "CheckboxGroup",
"loading": "Loading",
"tip": "Tip"
}
},
"popup": {
"name": "弹出层",
"subList": {
"popup": "Popup",
"toast": "Toast",
"picker": "Picker",
"time-picker": "TimePicker",
"dialog": "Dialog",
"action-sheet": "ActionSheet"
}
},
"scroll": {
"name": "滚动",
"subList": {
"scroll": "Scroll",
"slide": "Slide",
"index-list": "IndexList"
}
}
}
},
"modules": {
"name": "模块",
"subList": {
"style": "style",
"create-api": "create-api",
"better-scroll": "better-scroll"
}
}
}
}
import Vue from 'vue'
import hljs from './highlight'
Vue.directive('highlight', (el) => {
let blocks = el.querySelectorAll('pre code')
blocks.forEach(block => {
hljs.highlightBlock(block)
})
})
import hljs from 'highlight.js/lib/highlight'
import shell from 'highlight.js/lib/languages/shell'
import css from 'highlight.js/lib/languages/css'
import markdown from 'highlight.js/lib/languages/markdown'
import js from 'highlight.js/lib/languages/javascript'
import json from 'highlight.js/lib/languages/json'
import stylus from 'highlight.js/lib/languages/stylus'
import xml from 'highlight.js/lib/languages/xml'
hljs.registerLanguage('css', css)
hljs.registerLanguage('markdown', markdown)
hljs.registerLanguage('javascript', js)
hljs.registerLanguage('json', json)
hljs.registerLanguage('shell', shell)
hljs.registerLanguage('stylus', stylus)
hljs.registerLanguage('xml', xml)
export default hljs
import cookie from 'js-cookie'
export function setItem(k, v) {
try {
window.localStorage.setItem(k, v)
} catch (e) {
cookie.set(k, v, {
expires: 365
})
}
}
export function getItem(k) {
let v = window.localStorage.getItem(k)
if (!v) {
v = cookie.get(k)
}
return v || ''
}
export function getCurrentLang() {
const itemKey = 'CUBE_LANGUAGE'
const hash = window.location.hash
const hashZhLang = hash ? hash.indexOf('/zh-') >= 0 ? 'zh-CN' : 'en-US' : ''
const lang = hashZhLang || getItem(itemKey) || window.navigator.language || 'en-US'
let defaultLang = 'en-US'
if (lang.indexOf('zh-') >= 0) {
defaultLang = 'zh-CN'
}
setItem(itemKey, defaultLang)
return defaultLang
}
@import "~@/common/stylus/variable.styl"
html, body
width: 100%
height: 100%
background: $color-background
font-family -apple-system,"Helvetica Neue",Helvetica,Arial,"PingFang SC","Hiragino Sans GB","WenQuanYi Micro Hei","Microsoft Yahei",sans-serif
-webkit-font-smoothing antialiased
input
outline: 0
@import '~@/common/stylus/reset.styl'
@import "./base.styl"
@import "./md.styl"
@import "~@/common/stylus/variable.styl"
.md-body
font-size: $fontsize-medium
line-height: 1.6
color: $color-dark-grey
p
margin: 1.2em 0
h2
margin-top: 1.2em
margin-bottom: .8em
font-size: 1.5em
font-weight: 600
h3
margin: 1em 0 1em
font-size: 1.25em
font-weight: 600
h4
margin: 0.8em 0 0.8em
font-size: 1.1em
font-weight: 600
blockquote
padding: 0 1em
color: $color-grey
border-left: .25em solid $color-light-grey-s
table
// margin 0.8em 1em 0.8em 1em
width: 100%
color: $color-blue
box-sizing: border-box
th, td
padding: 6px 13px
border: 1px solid rgb(223, 226, 229)
vertical-align: middle
text-align: left
th
font-weight: 600
background-color: $color-row-line
ul, ol, li
line-height: 20px
li
list-style: inherit
margin: 1em 0
ul
padding-left: 20px
pre
margin: .8em 0
font-size: 12px
code
font-family: monaco
line-height: 1.4
color: rgb(199, 37, 78)
background: rgb(249, 242, 244)
padding: 2px 4px
box-sizing: border-box
font-size: 0.93em
strong
margin: 1em 0 1em
font-weight: bold
a
text-decoration: none
color: rgb(0, 154, 97)
<template>
<div class="page-display">
<div class="display-wrapper">
<section class="mofang-demo">
<iframe :src="ifrSrc" frameborder="0" width="100%" height="100%"></iframe>
</section>
</div>
</div>
</template>
<script>
export default {
props: {
baseUrl: {
type: String,
default: 'http://localhost:8081/#/'
}
},
data () {
return {
hash: '/'
}
},
computed: {
ifrSrc () {
return `${this.baseUrl}${this.hash}`
}
},
watch: {
'$route': {
handler (to, from) {
const index = to.path.indexOf('/docs/')
this.hash = to.path.substr(index + 6)
},
immediate: true
}
},
methods: {
}
}
</script>
<style lang="stylus">
@import "~@/common/stylus/variable.styl"
.page-display
position: relative
.display-wrapper
position: absolute
width: 348px
height: 750px
top: 61%
left: 50%
transform: translate(-50%, -50%)
background: url("./phone.png") no-repeat
background-size: cover
.mofang-demo
position: absolute
top: 104px
left: 14px
right: 14px
height: 550px
border-radius: 4px
background-color: $color-white
</style>
<template>
<viewport lang="en-US"></viewport>
</template>
<script>
import Viewport from '../viewport/viewport.vue'
export default {
components: {
Viewport
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
</style>
## Action Sheet 操作列表
`action-sheet`可看做是`dialog`的延伸,二者都属于模态框,但是`action-sheet`提供了更多的功能按钮,提供给用户更多的操作选择。`action-sheet`组件提供了2中常见的操作列表类型。
### 单独引入
```javascript
import { ActionSheet } from 'cube-ui'
export default {
components: {
CubeActionSheet: ActionSheet
}
}
```
### 调用方式
通过在`action-sheet`组件上添加`ref`属性,获得对于组件的引用,然后调用`action-sheet`组件向外暴露出来的`show``hide`方法来控制组件的显示或消失:
```html
<template>
<div class="action-sheet">
<cube-action-sheet ref="actionSheet"></cube-action-sheet>
<cube-button @click="showActionSheet">拉起 Action Sheet<cube-button>
</div>
</template>
<script>
export default {
methods: {
showActionSheet () {
this.$refs.actionSheet.show()
}
}
}
</script>
```
### 示例
```html
<template>
<cube-action-sheet
ref="actionSheet"
:data="data"
:active="actionSheetActiveIndex"
:title="title"
@select="handleSelect"
@cancel="handleCancel"></cube-action-sheet>
<cube-action-sheet
ref="actionSheet2"
:data="data2"
:active="actionSheetActiveIndex2"
:title="title"
:style2="true"
@select="handleSelect2"
@cancel="handleCancel"></cube-action-sheet>
<cube-toast
ref="toast"
:txt="toastTxt"
:time=2000></cube-toast>
</template>
<script>
const data = [
{
content: '左对齐',
align: 'left'
},
{
content: '右对齐',
align: 'right'
},
{
content: '默认居中对齐'
},
{
content: '自定义样式的内容',
class: 'orange'
},
{
content: '<i class="didi-icons didi-icons-arrow_lift"></i><span>嵌入了HTML的内容</span>'
},
{
content: '点击我打开一个toast',
showToast: true
}
]
const data2 = [
{
content: '舒适型'
},
{
content: '七座商务'
},
{
content: '豪华型'
}
]
export default {
data () {
return {
data: data,
data2: data2,
toastTxt: '',
actionSheetActiveIndex: 2,
actionSheetActiveIndex2: 0
}
},
methods: {
showActionSheet() {
this.$refs.actionSheet.show()
},
showActionSheet2() {
this.$refs.actionSheet2.show()
},
handleSelect (item, index) {
console.log(item.content)
if (item.showToast) {
this.showToast(item.content)
}
this.actionSheetActiveIndex = index
},
handleSelect2 (item, index) {
console.log(item.content)
this.actionSheetActiveIndex2 = index
},
handleCancel() {
console.log('action sheet canceled')
},
showToast(txt) {
this.toastTxt = txt
this.$refs.toast.show()
}
}
}
</script>
```
### Props参数配置
| 参数 | 说明 | 类型 | 默认值 |
| ------------- |:-------------:| ---| ---|
| data | 需要展示的数据 | Array | [ ] |
| active | 高亮的选项 | Number | -1 |
| title | 组件的标题 | String | - |
| pickerStyle | 样式类型是否为picker型 | Boolean | false |
此外,在传入的`data`数组中,每一项为一个`object`,其中可配置的字段如下:
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| ------------- |:-------------:| -----:| ---| ---|
| content | 展示的文案内容 | String | 纯文本/html文本 | - |
| align | 展示的文案对齐的方式 | String | left/center/right | center |
| class | 展示文案添加的class属性,用来单独定义样式 | String | - |
### Event事件
| 参数 | 说明 | 参数1 | 参数2 |
| ------------- |:-------------:| --- | --- |
| cancel | 点击取消按钮向父组件传递的事件,同时action-sheet消失 | - | - |
| select | 点击action-sheet某项后向父组件传递的事件,同时action-sheet消失 | 选中项 | 选中项的索引值 |
## Button
`Button` provides various types, styles, states and icons.
### Example
- Button type
The default type is `button`, and you can set that to `submit` in form.
```html
<cube-button>Button</cube-button>
<cube-button type="submit">Submit Button</cube-button>
```
- Button state
Default is normal, can be set to active or disabled state.
```html
<cube-button :active="true">Active Button</cube-button>
<cube-button :disabled="true">Disabled Button</cube-button>
```
- Icon
You can set the class of `icon`.
```html
<cube-button icon="cubeic-right">Button With Icon</cube-button>
```
- Style
You can set attributes like `light`, `inline`, `outline`, and `primary` to change the button's style.
```html
<cube-button :light="true">Light Button</cube-button>
<cube-button :inline="true">Inline Button</cube-button>
<cube-button :outline="true">Outline Button</cube-button>
<cube-button :primary="true">Primary Button</cube-button>
```
### Props configuration
| Attribute | Description | Type | Accepted Values | Default |
| - | - | - | - | - |
| type | Button type | String | button/submit | button |
| active | active state | Boolean | true/false | false |
| disabled | disabled state | Boolean | true/false | false |
| icon | Icon | String | the class of icon | - |
| light | light style | Boolean | true/false | false |
| inline | whether inline | Boolean | true/false | false |
| outline | outline style | Boolean | true/false | false |
| primary | primary style | Boolean | true/false | false |
### Events
| Event Name | Description | Parameters |
| - | - | - |
| click | triggers when the button is clicked. If in disabled state, then it won't trigger | e - event target |
## CheckboxGroup
`CheckboxGroup` is a group of checkboxs, which is used to select a group of options. It has vertical and horizontal styles.
### Example
- Vertical order
Default is vertical order style.
```html
<cube-checkbox-group v-model="checkList">
<cube-checkbox label="1">
Checkbox 1
</cube-checkbox>
<cube-checkbox label="2">
Checkbox 2
</cube-checkbox>
<cube-checkbox label="3" :disabled="true">
Disabled Checkbox
</cube-checkbox>
<cube-checkbox label="4" :disabled="true">
Disabled & Checked Checkbox
</cube-checkbox>
</cube-checkbox-group>
```
The value of `checkList` is an array, which represents the set of the values of `label` in selected checkboxs.
- Horizontal order
You can set `horizontal` to change the style to horizontal order.
```html
<cube-checkbox-group v-model="checkList" :horizontal="true">
<cube-checkbox label="1">1</cube-checkbox>
<cube-checkbox label="2">2</cube-checkbox>
<cube-checkbox label="3" :disabled="true">3</cube-checkbox>
<cube-checkbox label="4" :disabled="true">4</cube-checkbox>
</cube-checkbox-group>
```
### Props configuration
| Attribute | Description | Type | Accepted Values | Default |
| - | - | - | - | - |
| horizontal | whether in horizontal order | Boolean | true/false | false |
### Events
| Event Name | Description | Parameters |
| - | - | - |
| input | triggers when the selecting state in the group changes | the set of values of selected checkboxs, which type is Array |
## Checkbox
`Checkbox` component. You can set the state, pass on special class and set the position of the checkbox's icon.
### Example
- Basic usage
```html
<cube-checkbox v-model="checked">
Checkbox
</cube-checkbox>
```
If selected, the value of `checked` is `true`.
- Disabled state
```html
<cube-checkbox v-model="checked" :disabled="true">
Disabled Checkbox
</cube-checkbox>
```
Set `disabled` to `true` to turn into the disabled state.
- Position of the checkbox icon
```html
<cube-checkbox v-model="checked" position="right">
Position Checkbox
</cube-checkbox>
```
If setting `position` to `'right'`, the position of the checkbox's icon is on the right.
- Change the value of model
```html
<cube-checkbox v-model="checked" label="labelValue">
Set label Checkbox
</cube-checkbox>
```
If `label` is setted, and when the checkbox is selected, the value of `checked` is `'labelValue'`. When not selected, the value is `false`; Therefore, in circumstances of single checkbox, better not set `label`.
### Props configuration
| Attribute | Description | Type | Accepted Values | Default |
| - | - | - | - | - |
| disabled | whether disabled | Boolean | true/false | false |
| position | position | String | left/right | left |
| label | if selected, then map the value to v-model | Boolean/String | - | '' |
### Events
| Event Name | Description | Parameters |
| - | - | - |
| input | triggers when the selecting state changes | the value of label if setted or boolean value which represents whether selected |
## create-api module
This module exports a function called `createAPI` with which you can invoke the custom component which has been instantiated in the api form.
\ No newline at end of file
## Dialog
`Dialog`模态框组件,提供了多种样式及交互形式。
### 单独引入
```javascript
import { Dialog } from 'cube-ui'
export default {
components: {
CubeDialog: Dialog
}
}
```
### 调用方式
通过在`dialog`组件上添加`ref`属性,获得对于组件的引用,然后调用`dialog`组件向外暴露出来的`show``hide`方法来控制组件的显示或消失:
```html
<template>
<div class="dialog">
<cube-dialog ref="dialog"></cube-dialog>
<cube-button @click="showDialog">拉起dialog<cube-button>
</div>
</template>
<script>
export default {
methods: {
showDialog () {
this.$refs.dialog.show()
}
}
}
</script>
```
### 示例:确认框
```html
<template>
<div class="dialog">
<cube-dialog
ref="dialog"
type="confirm"
icon="mfic-dialog-unwifi"
title="我是标题"
text="我是正文"
@confirm="dialogConfirm"
@cancel="dialogCancel"
></cube-dialog>
</div>
</template>
<script>
export default {
methods: {
showDialog () {
this.$refs.dialog.show()
},
dialogConfirm () {
console.log('dialog confirm')
},
dialogCancel () {
console.log('dialog cancel')
}
}
}
</script>
```
### Props参数配置
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| ------------- |:-------------:| -----:| ---| ---|
| type | dialog类型 | String | alert(只有1个按钮)/confirm(2个按钮) | alert |
| icon | icon | String | [参照icon列表](http://localhost:8083/#/icon) | - |
| title | 标题 | String | - | - |
| txt | 正文 | String | - | - |
| show-close | 是否显示关闭按钮 | Boolean | true/false | false |
| check | 是否带checkbox | Object | - | - |
| actions | 平级操作 | Array | - | [ ] |
| txts | 正文为列表形式 | Array | - | [ ] |
| link | 文字链接 | String | - | - |
| cancel-btn-content | 取消按钮文案 | String | - | - |
| confirm-btn-content | 确认按钮文案 | String | - | - |
| cancel-btn-href | 取消按钮跳转链接 | String | - | - |
| confirm-btn-href | 确认按钮跳转链接 | String | - | - |
| is-cancel-btn-active | 取消按钮是否高亮 | Boolean | true/false | false |
| is-confirm-btn-active | 确认按钮是否高亮 | Boolean | true/false | true |
其中`check`可配置的属性有:
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| ------------- |:-------------:| -----| ---| ---|
| txt | checkbox提示的文案 | String | - | - |
| value | 是否被选中 | Boolean | true/false | false |
`actions`可配置的属性有:
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| ------------- |:-------------:| -----| ---| ---|
| text | 显示的文案 | String | - | - |
| active | 是否高亮 | Boolean | true/false | false |
| href | a标签的href属性 | String | - | - |
### Event事件
| 事件名 | 说明 | 参数1 | 参数2 |
| ----- | ---- | ----| ---- |
| confirm | 点击确认按钮后触发 | e (event事件对象) | - |
| cancel | 点击取消按钮后触发 | e (event事件对象) | - |
| close | 点击关闭按钮后触发 | e (event事件对象) | - |
| link-click | 点击文字链接后触发 | e (event事件对象) | - |
| btn-click | 点击actions中配置的按钮后触发 | 被点击的action | e (event事件对象) |
## IndexList组件
`IndexList`基于`better-scroll`进行封装。提供了城市列表索引的功能。
### 单独引入
```javascript
import { IndexList } from 'cube-ui'
export default {
components: {
CubeListview: IndexList
}
}
```
### 示例
```html
<template>
<cube-index-list
:data="data"
:title="title"
@select="selectItem"
@titleClick="clickTitle"
></cube-index-list>
</template>
<script>
const cityData = [{
name: '★热门城市',
cities: [
{
name: '北京市',
tags: 'BEIJING,北京市',
cityid: 1
},
{
name: '上海市',
tags: 'SHANGHAI,上海市',
cityid: 4
},
{
name: '深圳市',
tags: 'SHENZHEN,深圳市',
cityid: 2
},
{
name: '广州市',
tags: 'GUANGZHOU,广州市',
cityid: 3
},
{
name: '武汉市',
tags: 'WUHAN,武汉市',
cityid: 6
}
]
},
{
name: 'A',
cities: [
{
name: '鞍山市',
tags: 'ANSHAN,鞍山市',
cityid: 64
},
{
name: '安庆市',
tags: 'ANQING,安庆市',
cityid: 149
},
{
name: '安阳市',
tags: 'ANYANG,安阳市',
cityid: 174
},
{
name: '阿拉善盟',
tags: 'ALASHANMENG,阿拉善盟',
cityid: 202
},
{
name: '阿坝州',
tags: 'ABAZHOU,阿坝州',
cityid: 290
},
{
name: '安顺市',
tags: 'ANSHUN,安顺市',
cityid: 294
},
{
name: '阿里地区',
tags: 'ALIDIQU,阿里地区',
cityid: 316
},
{
name: '安康市',
tags: 'ANKANG,安康市',
cityid: 320
},
{
name: '阿克苏地区',
tags: 'AKESUDIQU,阿克苏地区',
cityid: 348
},
{
name: '阿勒泰地区',
tags: 'ALETAIDIQU,阿勒泰地区',
cityid: 355
},
{
name: '阿拉尔市',
tags: 'ALAER,阿拉尔市',
cityid: 356
}
]
}]
export default {
data () {
return {
title: '当前城市: 北京市',
cityData: []
}
},
mounted () {
setTimeout(() => {
this.cityData = cityData
}, 100)
},
computed: {
data() {
let ret = []
this.cityData.forEach((cityGroup) => {
let group = {}
group.name = cityGroup.name
group.items = []
cityGroup.cities.forEach((city) => {
let item = {}
item.name = city.name
item.value = city.cityid
group.items.push(item)
})
ret.push(group)
})
return ret
}
},
methods: {
selectItem(item) {
console.log(item)
},
clickTitle(title) {
console.log(title)
}
}
}
</script>
```
## API
### Props参数配置
| 参数 | 说明 | 类型 | 默认值 |
| ------------- |:-------------:|---| ---| --- |
| title | 标题 | String | - |
| data | 需要展示的数据 | Array | [] |
### Event事件
| 事件名 | 说明 | 参数 |
| ----- | ---- | ----|
| select | 选中index-list的某一项后触发 | 被选中的选项 |
| title-click | 点击title后触发(title必须设置后才有效) | title值 |
## 安装
### NPM
推荐使用 NPM 安装`cube-ui`
```shell
$ npm install cube-ui
```
### CDN
也可通过 unpkg.com 获取资源,用标签引入 js 和 css 文件使用。
```html
<script src=""></script>
<link rel="stylesheet" href="">
```
### 版本日志
版本的更新日志见 [GitHub](https://github.com/didi/cube-ui/releases)
## Introduction
cube-ui is an elegant mobile component library based on Vue.js.
### Features
- Extracted from Didi interior component library and has been put to business test for more than one year
- Every component has full unit tests to provide assurance for continuous integration
- Pursue the perfection of the interactive experience, which makes cube-ui close to native components
- Support post-compile and importing on demand, lightweight and flexible
### Components
#### Basic
- [Button](#/en-US/docs/button)
- [Checkbox](#/en-US/docs/checkbox)
- [CheckboxGroup](#/en-US/docs/checkbox-group)
- [Loading](#/en-US/docs/loading)
- [Tip](#/en-US/docs/tip)
#### Layer
- [Toast](#/en-US/docs/toast)
- [Picker](#/en-US/docs/picker)
- [TimePicker](#/en-US/docs/time-picker)
- [Dialog](#/en-US/docs/dialog)
- [ActionSheet](#/en-US/docs/action-sheet)
All the layer components can be invoked by components and API, let's take `Toast` as an example:
Component invoking:
```html
<template>
<cube-button @click="showToast">show toast</cube-button>
<cube-toast ref="toast" :txt="toastTxt"></cube-toast>
</template>
<script>
export default {
data() {
return {
toastTxt: 'toast content'
}
},
methods: {
showToast() {
this.$refs.toast.show()
}
}
}
</script>
```
API invoking:
```html
<template>
<cube-button @click="showToast">show toast</cube-button>
</template>
<script>
export default {
data() {
return {
toastTxt: 'toast content'
}
},
methods: {
showToast() {
this.$createToast({
txt: this.toastTxt
}).show()
}
}
}
</script>
```
Both are pretty good but we recommend the latter, namely using `$createXxYy` API to invoke; By API invoking, the popup layer is attached to `body` element whose advantage is that it wouldn't be affected by outer styles. If using component invoking, it's fairly easy to be affected by outer styles like `transform`, `overflow`, etc.
Pay attention that the name of the API is `$create` + `${component name}`. For example, the API of `ActionSheet` component is `$createActionSheet`.
#### Scroll
- [Scroll](#/en-US/docs/scroll)
- [Slide](#/en-US/docs/slide)
- [IndexList](#/en-US/docs/index-list)
Scroll Components are all implemented based on [better-scroll](https://github.com/ustbhuangyi/better-scroll) and `Scroll` Component is the encapsulation of better-scroll.
### Modules
cube-ui has some special modules besides components.
#### style
The style module.
If you are under the circumstance of importing components on demand, then it is recommended to import this module in entry file because it contains basic reset, basic common styles and built-in icons.
For details, see [style module](#/en-US/docs/style).
#### create-api
Sometimes, developers may need to let their own encapsulated components support API invoking as well. At this time they can import create-api module or use the global interface `Cube.createAPI` to achieve the purpose.
For details, see [create-api module](#/en-US/docs/create-api)
#### better-scroll
[better-scroll](https://github.com/ustbhuangyi/better-scroll) is a library in `cube-ui`'s dependencies. To avoid duplicately importing, we expose the module.
For more detailed use, please refer to [official documentation](https://ustbhuangyi.github.io/better-scroll/#/zh)
## Loading
`Loading` provides loading animation which can has custom size.
### Example
- The size of icon
Default is `24px` and can be configured by setting `size`.
```html
<cube-loading></cube-loading>
<cube-loading :size="28"></cube-loading>
<cube-loading :size="34"></cube-loading>
```
### Props configuration
| Attribute | Description | Type | Default |
| - | - | - | - |
| size | the size of loading icon, unit px | Number | 24 |
## Picker组件
`Picker`组件支持多列选择器及数据联动。
### 单独引入
```javascript
import { Picker } from 'cube-ui'
export default {
components: {
CubePicker: Picker
}
}
```
### 调用方式
通过在`picker`组件上添加`ref`属性,获得对于组件的引用,然后调用`picker`组件向外暴露出来的`show``hide`方法来控制组件的显示或消失,`show`能接受回调函数:
```html
<template>
<div class="picker">
<cube-picker ref="picker"></cube-picker>
<cube-button @click="showPicker">拉起picker<cube-button>
</div>
</template>
<script>
export default {
methods: {
showPicker () {
this.$refs.picker.show(() => {
// do something
})
}
}
}
</script>
```
此外,`picker`还暴露出了`setData``setSelectedIndex`2个方法,用以动态设置`picker`需要展示的内容,以及当前被选中的值。
```javascript
const data = [{ text: 'a', value: 1}, { text: 'b', value: 2}]
this.$refs.picker.setData(data)
this.$refs.picker.setSelectedIndex([1])
```
### 示例
#### demo1
```html
<template>
<button @click="setData">改变单列选择器数据</button>
<div class="select" @click="showPicker" ref="select0">单列选择器示例 ...</div>
<cube-picker
ref="picker0"
@select="handleSelect"
:selected-index="selectedIndex[0]"
:title="title"></cube-picker>
</template>
<script>
let data1 = [{ text: '剧毒', value: 1 }, { text: '蚂蚁', value: 2 }, { text: '幽鬼', value: 3 }, { text: '主宰', value: 4 }]
let data2 = [{ text: '输出', value: 'a' }, { text: '控制', value: 'b' }, { text: '核心', value: 'c' }, { text: '爆发', value: 'd' }, { text: '辅助', value: 'e' }]
export default {
data () {
return {
title: '单列选择器',
selectedIndex: [0],
data: [data1]
}
},
mounted () {
this.$nextTick(() => {
this.$refs.picker0.setData([data1])
this.$refs.picker0.setSelectedIndex([1])
})
},
showPicker () {
this.$refs.picker0.show()
},
methods () {
select (selectedVal, selectedIndex) {
let text = ''
for (let i = 0; i < this.data.length; i++) {
text += this.data[i][selectedIndex[i]].text + ' '
}
this.$refs.select0.innerText = text
},
setData() {
this.data[0] = [data2]
this.$refs.picker0.setData([data2])
this.$refs.picker0.setSelectedIndex([3])
}
}
}
</script>
```
#### demo2
```html
<template>
<div class="select" @click="showPicker" ref="select1">两列选择器示例 ...</div>
<cube-picker
ref="picker1"
@select="handleSelect"
:data="data[1]"
:selected-index="selectedIndex"
:title="title"
:cancelTxt="englishTxt.cancelTxt"
:confirmTxt="englishTxt.confirmTxt"></cube-picker>
</template>
<script>
let data1 = [{ text: '剧毒', value: 1 }, { text: '蚂蚁', value: 2 }, { text: '幽鬼', value: 3 }, { text: '主宰', value: 4 }]
let data2 = [{ text: '输出', value: 'a' }, { text: '控制', value: 'b' }, { text: '核心', value: 'c' }, { text: '爆发', value: 'd' }, { text: '辅助', value: 'e' }]
export default {
data () {
return {
data: [data1, data2],
selectedIndex: [1, 2],
title: '双列选择器',
englishTxt: {
cancelTxt: 'cancel',
confirmTxt: 'confirm'
}
}
},
methods: {
showPicker () {
this.$refs.pick1.show()
},
handleSelect (selectedVal, selectedIndex) {
let text = ''
for (let i = 0; i < this.data.length; i++) {
text += this.data[i][selectedIndex[i]].text + ' '
}
this.$refs.select1.innerText = text
}
}
}
</script>
```
### API
#### Props参数配置
| 参数 | 说明 | 类型 | 默认值 | 示例 |
| ----- |----------| -----|---| --- |
| title | 标题 | String | '' | - |
| data | 传入picker数据,数组的长度决定了picker的列数 | Array | [] | - |
| cancelTxt | picker左侧按钮文案 | String | '取消' | - |
| confirmTxt | picker右侧按钮文案 | String | '确定' | - |
| select-index | 被选中的索引值,拉起picker后显示这个索引值对应的内容 | Array | [] | [1] |
其中传入`picker``data`数组中,每一项可配置的属性有:
| 参数 | 说明 | 类型 | 默认值 | 示例 |
| ------------- |-------------| -----| ---| ---|
| text | picker每一列展示的文案 | String/Number | - | - |
| value | picker每一列展示的每项文案对应的值 | String/Number/Boolean | - | - |
#### Event事件
| 事件名 | 说明 | 参数1 | 参数2 |
| ----- | ---- | ----| --- |
| select | 点击确认按钮触发此事件 | selectedVal: 当前选中项每一列的值,Array类型 | selectedIndex: 当前选中项每一列的索引,Array类型 |
| change | 滚轴滚动后触发此事件 | selectedVal: 当前选中项每一列的值,Array类型 | selectedIndex: 当前选中项每一列的索引,Array类型 |
| value-change | 所确认的值变化时触发此事件 | selectedVal: 当前确认项每一列的值,Array类型 | selectedIndex: 当前确认项每一列的索引,Array类型 |
| cancel | 点击取消按钮触发此事件 | - | - |
#### 组件向外暴露方法
| 方法名 | 说明 | 接受的参数1 |
| ----- | ---- | ---- |
| show | 拉起picker组件 | - |
| hide | 隐藏picker组件 | - |
| setData | 设置picker可选项 | picker每列可选项的文案和值,Array类型 |
| setSelectedIndex | 设置picker选中项 | picker每列选中的索引,Array类型 |
## Popup
The underlying popup component, mainly used to implement upper component encapsulation based on itself. It only provides basic functions: specifying the type, whether there's a background layer, showing content (HTML) and whether the component is in center position.
All of the built-in popup type components are implemented based on this component, including [Toast](#/en-US/docs/toast)[Picker](#/en-US/docs/picker)[TimePicker](#/en-US/docs/time-picker)[Dialog](#/en-US/docs/dialog)[ActionSheet](#/en-US/docs/action-sheet).
### Example
- Basic usage
```html
<cube-popup type="my-popup" ref="myPopup">
My Popup Content 1
</cube-popup>
<cube-button @click="showPopup('myPopup')">
Show Popup
</cube-button>
```
Specifying `type` will help control class according to the type. In the example above, `cube-my-popup` will be added to the classList of the root element.
```js
export default {
methods: {
showPopup(refId) {
const component = this.$refs[refId]
component.show()
setTimeout(() => {
component.hide()
}, 1000)
}
}
}
```
The component is hidden by default and won't show up unless invoking the `show()` method of the instance of the component. And of course, invoking the `hide()` method of the instance of the component will hide itself. That's what `showPopup()` does in the example above (first show and hide in one second).
- Without background layer
```html
<cube-popup type="my-popup" :mask="false" ref="myPopup2">
My Popup Content 2
</cube-popup>
<cube-button @click="showPopup('myPopup2')">
Show Popup - no mask
</cube-button>
```
Setting `mask` to `false` and the background layer is hidden.
- Show the HTML content
```html
<cube-popup
type="my-popup"
:mask="false"
content="<i>My Popup Content 3</i>"
ref="myPopup3" />
<cube-button @click="showPopup('myPopup3')">
Show Popup - with content
</cube-button>
```
You only need to pass the HTML fragment on to `content`.
- Setting to bottom
```html
<cube-popup type="my-popup" :center="false" ref="myPopup4">My Popup Content 4</cube-popup>
<cube-button @click="showPopup('myPopup4')">
Show Popup - bottom
</cube-button>
```
Considering that in mobile secnes, popup is in center or bottom most of the time (covered in horizontal direction when setted to bottom). So if setting `center` to `false`, you'll set the component to bottom.
- Upper layer encapsulation
```html
<template>
<cube-popup type="extend-popup" ref="popup">
<div class="cube-extend-popup-content" @click="hide">
<slot>{{content}}</slot>
</div>
</cube-popup>
</template>
<script type="text/ecmascript-6">
const COMPONENT_NAME = 'cube-extend-popup'
export default {
name: COMPONENT_NAME,
props: {
content: {
type: String
}
},
methods: {
show() {
this.$refs.popup.show()
},
hide() {
this.$refs.popup.hide()
this.$emit('hide')
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
.cube-extend-popup
.cube-extend-popup-content
padding: 20px
color: #fff
background-color: rgba(0, 0, 0, .8)
</style>
```
An upper layer encapsulated CubeExtendPopup component with some styles is implemented. It supports passing the content on, has default slot and can be hidden if clicking the content. You can use it in this way (need to be registers globally or partially):
```html
<cube-extend-popup content="lala" ref="extendPopup"></cube-extend-popup>
<cube-button @click="$refs.extendPopup.show()">
Show Extend Popup
</cube-button>
```
### Props configuration
| Attribute | Description | Type | Accepted Values | Default |
| - | - | - | - | - |
| type | the type of popup | String | - | '' |
| mask | whether to show background layer | Boolean | true/false | true |
| content | content, HTML string, valid when there's no slot | String | - | '' |
| center | whether to be in center in horizontal and vertical direction | Boolean | true/false | true |
### Events
| Event Name | Description | Parameters |
| - | - | - |
| mask-click | triggers when the background layer is clicked | click event target |
## Post-compile
Post-compile means the npm packages that the application depend on don't need to be compiled before distribution but compiled along with the application's compiling and packaging.
> Notes: For more detailed content about post-compile, see [webpack 应用编译优化之路](https://github.com/DDFE/DDFE-blog/issues/23).
### Background
More and more applications are developed with webpack + babel, and commonly their package management is by npm. With the growing of the number of package dependencies which are also developed with ES2015+ and needs to be compiled before distribution, the final compiled code usually contains a lot of compiling code. Therefore, to eliminate the redundancy, we recommend post-compile.
### Strengths and weaknesses
- Public dependencies can share use and only need one copy. The more important is that you only need to compile once.
### Rules
### The configuration and use of webpack
## Quick start
### Install
```shell
$ npm install cube-ui --save
```
### Usage
It is recommended to use [babel-plugin-transform-modules](https://www.npmjs.com/package/babel-plugin-transform-modules),which helps you import component modules and corresponding styles more elegantly.
Before use, the plugin needs some configuration. Modify .babelrc:
- webpack 1.x
```json
{
"plugins": ["transform-modules", {
"cube-ui": {
"transform": "cube-ui/lib/${member}",
"kebabCase": true,
"style": true
}
}]
}
```
- webpack 2+
```json
{
"plugins": ["transform-modules", {
"cube-ui": {
"transform": "cube-ui/src/modules/${member}",
"kebabCase": true
}
}]
}
```
> [why distinguishs the version of webpack?](#/en-US/docs/post-compile)
If not using babel-plugin-transform-modules, you need to import corresponding style files by hand.
#### Fully import
Commonly in the entry file:
```javascript
import Vue from 'vue'
import Cube from 'cube-ui'
Vue.use(Cube)
```
#### Import on demand
```javascript
import { Button } from 'cube-ui'
```
You can choose to register globally or partially:
```js
// register globally
Vue.use(Button)
// or register partially
// in certain somponents
{
components: {
CubeButton: Button
}
}
```
All the components that can be imported on demand are listed below:
```js
import {
Button,
Checkbox,
Loading,
Tip,
Toast,
Picker,
TimePicker,
Dialog,
ActionSheet,
Scroll,
Slide,
IndexList
} from 'cube-ui'
```
#### Examples
```html
<template>
<cube-button @click="showDiaog">show dialog<cube-button>
</template>
<script>
export default {
methods: {
showDialog() {
this.$createDialog({
type: 'alert',
title: 'Alert',
content: 'dialog content'
}).show()
}
}
}
</script>
```
## Scroll
`Scroll` component, which is encapsulated based on `better-scroll`, provides high-quality native scrolling experience and has convenient configuration along tith events.
### Example
- Basic usage
By setting `data` to an array, you can generate the list which can scrolls elegantly in the container.
```html
<div class="scroll-wrapper">
<cube-scroll :data="items"></cube-scroll>
</div>
```
- Scroll bar
Default is without scroll bar. You can set it to fade-in-fade-out or always-show style.
```html
<!-- fade-in-fade-out style -->
<cube-scroll :data="items" :scrollbar="true"></cube-scroll>
<!-- always-show style -->
<cube-scroll :data="items" :scrollbar="scrollbar"></cube-scroll>
```
```javascript
export default {
data() {
return {
items: [1, 2, 3, 4, 5],
scrollbar: {
fade: false
}
}
}
}
```
- Pull down to refresh
There is no pull-down-to-refresh function by default. Configuring `pull-down-refresh` can turn on the dispatching of the event `pulling-down` and the animation of pulling down. You can listen to `pulling-down` event to update data.
```html
<!-- turn on the pull-down-to-refresh function and use default configuration -->
<cube-scroll
ref="scroll"
:data="items"
:pull-down-refresh="true"
@pulling-down="onPullingDown"></cube-scroll>
<!-- turn on the pull-down-to-refresh function and use custom configuration -->
<cube-scroll
ref="scroll"
:data="items"
:pull-down-refresh="pullDownRefresh"
@pulling-down="onPullingDown"></cube-scroll>
```
```javascript
export default {
data() {
return {
items: [1, 2, 3, 4, 5],
pullDownRefresh: {
threshold: 90,
stop: 40,
txt: 'Refresh success'
}
}
},
methods: {
onPullingDown() {
// simulate updating data
setTimeout(() => {
if (Math.random() > 0.5) {
// if new data
this.items.unshift('I am new data: ' + +new Date())
} else {
// if no new data
this.$refs.scroll.forceUpdate()
}
}, 1000)
}
}
}
```
- Pulling up to load
There is no pull-up-to-load function by default. Configuring `pull-up-load` can turn on the dispatching of the event `pulling-up` and the animation of pulling up. You can listen to `pulling-up` event to update data.
```html
<!-- turn on the pull-up-to-load function and use default configuration -->
<cube-scroll
ref="scroll"
:data="items"
:pull-up-load="true"
@pulling-up="onPullingUp"></cube-scroll>
<!-- turn on the pull-up-to-load function and use custom configuration -->
<cube-scroll
ref="scroll"
:data="items"
:pull-up-load="pullUpLoad"
@pulling-up="onPullingUp"></cube-scroll>
```
```javascript
export default {
data() {
return {
items: [1, 2, 3, 4, 5],
itemIndex: 5,
pullUpLoad: {
threshold: 0,
txt: {
more: 'Load more',
noMore: 'No more data'
}
}
}
},
methods: {
onPullingUp() {
// simulate updating data
setTimeout(() => {
if (Math.random() > 0.5) {
// if new data
let newPage = [
'I am line ' + ++this.itemIndex,
'I am line ' + ++this.itemIndex,
'I am line ' + ++this.itemIndex,
'I am line ' + ++this.itemIndex,
'I am line ' + ++this.itemIndex
]
this.items = this.items.concat(newPage)
} else {
// if no new data
this.$refs.scroll.forceUpdate()
}
}, 1000)
}
}
}
```
- Customize the animation of pulling down refreshing and pulling up loading
If you don't like the built-in slots of pulling down refreshing and pulling up loading, you can use [scoped slots](https://vuejs.org/v2/guide/components.html#Scoped-Slots) to customize animation. The example below uses scoped slots to customize animation of pulling down refreshing, while pulling up loading keeps default built-in animation.
```html
<cube-scroll
ref="scroll"
:data="items"
:pull-down-refresh="pullDownRefresh"
:pull-up-load="pullUpLoad"
@pulling-down="onPullingDown"
@pulling-up="onPullingUp">
<template slot="pulldown" slot-scope="props">
<div
v-if="props.pullDownRefresh"
class="cube-pulldown-wrapper"
:style="props.pullDownStyle">
<div
v-if="props.beforePullDown"
class="before-trigger"
:style="{paddingTop: props.bubbleY + 'px'}">
<span :class="{rotate: props.bubbleY > 40}"></span>
</div>
<div class="after-trigger" v-else>
<div v-if="props.pulling" class="loading">
<cube-loading></cube-loading>
</div>
<div v-else><span>Refresh success</span></div>
</div>
</div>
</template>
</cube-scroll>
```
With the parameters that scoped slots provide, you can control the process of animation according to the change of the state. Detailed scope parameters and their meaning are shown below in 'Slots'.
### Props configuration
| Attribute | Description | Type | Accepted Values | Default |
| ------------------ | ---------------------------------------- | -------------- | ------------------------ | ---------- |
| data | data used for list rendering | Array | - | [] |
| direction | scrolling direction | String | 'vertical', 'horizontal' | 'vertical' |
| scrollbar | configuration for scroll bar | Boolean/Object | - | false |
| pullDownRefresh | configuration for pulling down refreshing | Boolean/Object | - | false |
| pullUpLoad | configuration for pulling up loading | Boolean/Object | - | false |
| listenScroll | whether to dispatch scroll event | Boolean | true/false | false |
| probeType | the dispatching time of scroll event <br>1. non-real-time dispatching; <br>2. real-time dispatching during the scrolling; <br>3. real-time dispatching during the entire scrolling including inertial stage | Number | 1, 2, 3 | 0 |
| listenBeforeScroll | whether to dispatch before-scroll-start event | Boolean | true/false | false |
- `scrollbar` sub configuration
| Attribute | Description | Type | Accepted Values | Default |
| --------- | -------------------------------------- | ------- | --------------- | ------- |
| fade | whether to have fade-in-fade-out style | Boolean | true/false | false |
- `pullDownRefresh` sub configuration
| Attribute | Description | Type | Accepted Values | Default |
| --------- | ---------------------------------------- | ------ | --------------- | ----------------- |
| threshold | the threshold of distance that pulling down for refreshing | Number | - | 90 |
| stop | the position where rebounding stays | Number | - | 40 |
| txt | the text shown when refreshing successfully | String | - | 'Refresh success' |
- `pullUpLoad` sub configuration
| Attribute | Description | Type | Accepted Values | Default |
| --------- | ---------------------------------------- | ------ | --------------- | ---------------------------------------- |
| threshold | the threshold of distance that pulling up for loading | Number | - | 0 |
| txt | the text shown when pulling up loading | Object | - | { more: 'Load more', noMore: 'No more data' } |
### Slot
| Name | Description | Scope Parameters |
| -------- | ---------------------------------------- | ---------------------------------------- |
| pulldown | located above the list and shown when pulling down refreshing | pullDownRefresh: whether to turn on pulling-down-refreshing function<br>pullDownStyle: the style of showing and fading<br>beforePullDown: whether in pulling down operation<br>pulling: whether in the process of pulling in data<br>bubbleY: the distance of pulling down currently - 50 |
| pullup | located below the list and shown when pulling up loading | pullUpLoad: whether to turn on pulling-up-loading function<br>isPullUpLoad: whether the data is being loaded |
### Events
| Event Name | Description | Parameters |
| ------------------- | ---------------------------------------- | ---------------------------------------- |
| click | triggers when clicking the list item | item - the data of the list item |
| scroll | triggers according to the value of probeType, if listenScroll is true | Object {x, y} - real-time scrolling coordinates |
| before-scroll-start | triggers before scrolling start, if listenBeforeScroll | - |
| pulling-down | triggers when the distance of pulling down exceeds the threshold, if pullDownRefresh is true | - |
| pulling-up | triggers when the distance of pulling up exceeds the threshold, if pullUpLoad is true | - |
## Slide
`Slide` component, which is also encapsulated based on `better-scroll`, provides common functions of `slider` and `swipe`.
### Example
- Basic usage
`cube-slide` element is the entire slide component while `cube-slide-item` element is the page of each slide and it's slot is the content of the page.
```html
<cube-slide @change="changePage">
<cube-slide-item v-for="(item, index) in items" :key="index" @click.native="clickHandler(item, index)">
<a :href="item.url">
<img :src="item.image">
</a>
</cube-slide-item>
</cube-slide>
```
```javascript
export default {
data() {
return {
items: [
{
url: 'http://www.didichuxing.com/',
image: '//webapp.didistatic.com/static/webapp/shield/cube-ui-examples-slide01.png'
}, {
url: 'http://www.didichuxing.com/',
image: '//webapp.didistatic.com/static/webapp/shield/cube-ui-examples-slide02.png'
}, {
url: 'http://www.didichuxing.com/',
image: '//webapp.didistatic.com/static/webapp/shield/cube-ui-examples-slide03.png'
}
]
}
},
methods: {
changePage(current) {
console.log('The index of current slide is ' + current)
},
clickHandle(item, index) {
console.log(item, index)
}
}
}
```
- Loop play
Loop play is turned on by default. You can cnfigure that with `loop` attribute.
```html
<cube-slide></cube-slide>
<cube-slide :loop="false"></cube-slide>
```
- Automatic play
Automatic play is turned on by default. You can cnfigure that with `auto-play` attribute.
```html
<cube-slide></cube-slide>
<cube-slide :auto-play="false"></cube-slide>
```
- Interval of automatic play
When automatic play is turned on, you can configure the interval with `interval` attribute.
```html
<cube-slide :interval="4000"></cube-slide>
```
- Sliding threshold of switching pages
You can configure the sliding threshold of switching pages with `threshold` attribute. It means that when the sliding distance exceeds the page's width * `threshold`, the page is switched. Default is 0.3.
```html
<cube-slide :threshold="0.4"></cube-slide>
```
- Speed of switching pages
Animation of switching costs 400 ms by default. You can cnfigure that with `speed` attribute.
```html
<cube-slide :speed="200"></cube-slide>
```
### Props configuration
| Attribute | Description | Type | Accepted Values | Default |
| --------- | ------------------------------------ | ------- | --------------- | ------- |
| loop | whether to loop play | Boolean | true/false | true |
| autoPlay | whether to play automatically | Boolean | true/false | true |
| interval | interval of play | Number | - | 4000 |
| threshold | sliding threshold of switching pages | Number | (0, 1) | 0.3 |
| speed | speed of switching pages | Number | - | 400 |
### 事件
| Event Name | Description | Parameters |
| ---------- | ----------------------------------- | ---------------------- |
| change | triggers when current slide changes | index of current slide |
## style 模块
style 模块主要包含就是样式部分,包含基础的 reset、基础通用样式以及内置 icon。
### reset.css
源码地址:[reset](https://github.com/didi/cube-ui/blob/master/src/common/stylus/reset.styl),使用的就是 Eric Meyer's Reset CSS。
```stylus
/**
* Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)
* http://cssreset.com
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header,
menu, nav, output, ruby, section, summary,
time, mark, audio, video, input
margin: 0
padding: 0
border: 0
font-size: 100%
font-weight: normal
vertical-align: baseline
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, menu, nav, section
display: block
body
line-height: 1
blockquote, q
quotes: none
blockquote:before, blockquote:after,
q:before, q:after
content: none
table
border-collapse: collapse
border-spacing: 0
/* custom */
a
color: #7e8c8d
-webkit-backface-visibility: hidden
text-decoration: none
li
list-style: none
body
-webkit-text-size-adjust: none
-webkit-tap-highlight-color: rgba(0, 0, 0, 0)
```
### base.css
源码地址:[base](https://github.com/didi/cube-ui/blob/master/src/common/stylus/base.styl),主要包含的就是 `html`, `body` 元素的 `font-family`, `line-height` 等的设定,修正浮动影响的 `.clear-fix`,以及上下左右四个边框的绝对 1px 边框的 class:`.border-top-1px`, `.border-right-1px`, `.border-bottom-1px`, `.border-left-1px`
```stylus
@import "./variable.styl"
body, html
line-height: 1
font-family: 'PingFang SC', 'STHeitiSC-Light', 'Helvetica-Light', arial, sans-serif, 'Droid Sans Fallback'
user-select: none
-webkit-tap-highlight-color: transparent
.clear-fix
&::after
content: ""
display: table
clear: both
.border-top-1px, .border-right-1px, .border-bottom-1px, .border-left-1px
position: relative
&::before, &::after
content: ""
display: block
position: absolute
transform-origin: 0 0
.border-top-1px
&::before
border-top: 1px solid $color-row-line
left: 0
top: 0
width: 100%
transform-origin: 0 top
.border-right-1px
&::after
border-right: 1px solid $color-col-line
top: 0
right: 0
height: 100%
transform-origin: right 0
.border-bottom-1px
&::after
border-bottom: 1px solid $color-row-line
left: 0
bottom: 0
width: 100%
transform-origin: 0 bottom
.border-left-1px
&::before
border-left: 1px solid $color-col-line
top: 0
left: 0
height: 100%
transform-origin: left 0
@media (min-resolution: 2dppx)
.border-top-1px
&::before
width: 200%
transform: scale(.5) translateZ(0)
.border-right-1px
&::after
height: 200%
transform: scale(.5) translateZ(0)
.border-bottom-1px
&::after
width: 200%
transform: scale(.5) translateZ(0)
.border-left-1px
&::before
height: 200%
transform: scale(.5) translateZ(0)
@media (min-resolution: 3dppx)
.border-top-1px
&::before
width: 300%
transform: scale(.333) translateZ(0)
.border-right-1px
&::after
height: 300%
transform: scale(.333) translateZ(0)
.border-bottom-1px
&::after
width: 300%
transform: scale(.333) translateZ(0)
.border-left-1px
&::before
height: 300%
transform: scale(.333) translateZ(0)
```
### 内置 icon
图标是利用 [font-face](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face) 规则自定义字体 `"cube-icon"` 实现的。
内置 icon 有 13 个:
![cube icons](https://raw.githubusercontent.com/didi/cube-ui/master/assets/icon.png)
使用的时候只需要加入对应的类名即可,例如 alert 图标,可以这样用:`<i class="cubeic-alert"></i>`
## TimerPicker组件
日期选择器。`TimerPicker`组件基于`picker`组件再次封装。第一列为日期,第二列为小时,第三列为分钟数,显示的数据都是依据当前时间,及配置的参数来动态生成。在实现的过程当中,每一列的`picker`都对应一个`better-scroll`的实例,滚轴发生变化后,`picker``time-picker`派发`change`事件,再由`time-picker`向父元素派发`change`事件,`cancel`, `select`事件同理。
### 单独引入
```javascript
import { TimePicker } from 'cube-ui'
export default {
components: {
CubeTimePicker: TimePicker
}
}
```
### 调用方式
通过在`time-picker`组件上添加`ref`属性,获得对于组件的引用,然后调用`time-picker`组件向外暴露出来的`show``hide`方法来控制组件的显示或消失,`show`能接受回调函数:
```html
<template>
<div class="time-picker">
<cube-time-picker ref="timePicker"></cube-time-picker>
<cube-button @click="showTimerPicker">拉起time-picker<cube-button>
</div>
</template>
<script>
export default {
methods: {
showTimerPicker () {
this.$refs.timePicker.show(() => {
// do something
})
}
}
}
</script>
```
### 示例
```html
<template>
<div class="select" @click="showTimePicker" ref="selectTime">时间选择器 ...</div>
<input type="text" v-model="time">
<button @click="setTime">设置时间</button>
<cube-time-picker
ref="timePicker"
:showNow="showNow"
:minuteStep="minuteStep"
@select="handleSelect"
@change="handleChange"
@cancel="handleCancel"
></cube-time-picker>
</template>
<script>
export default {
data () {
return {
time: 0,
showNow: true,
minuteStep: 5,
day: {
len: 5,
filter: ['今天', '明天', '后天'],
format: 'M月d日'
}
}
},
methods: {
setTime () {
this.$refs.timePicker.setTime(this.time)
},
showTimePicker () {
this.$refs.timePicker.show()
},
handleSelect (selectedVal, selectedText) {
this.$refs.selectTime.innerText = selectedText
},
handleChange (selectedVal, selectedText) {
console.log(selectedVal, selectedText)
},
handleCancel () {
console.log('cancel')
}
}
}
</script>
```
### API
#### Props参数配置
| 参数 | 说明 | 类型 | 默认值 |
| ------------- |:-------------:| -----:| ---|
| title | 标题 | String | '选择时间' |
| delay | 将当前时间向后推算的分钟数,决定了最小可选时间 | 15 |
| day | 日期配置 | Object | { len: 3, filter: ['今日'], format: 'M月d日' } |
| showNow | 是否显示当前时间 | Boolean | true |
| minuteStep | 分钟数的步长 | Number | 10 |
其中`day`属性可配置的参数有:
| 参数 | 说明 | 类型 | 默认值 |
| ------------- |:-------------:| ----- | ---|
| len | 日期列,从当前时间算起,往后推len天 | Number | 3 |
| filter | 日期列,将时间映射为filter中的文案内容 | Array | ['今日'] |
| format | 时间格式化 | String | 'M月d日' |
#### Event事件
| 事件名 | 说明 | 参数1 | 参数2 |
| ----- | ---- | ----| --- |
| select | 点击确认按钮触发此事件 | selectedTime: 当前选中的timestamp | selectText: 当前选中的时间文案 |
| change | 滑动改变time-picker滚轴时触发此事件 | selectedTime: 当前选中的timestamp | selectText: 当前选中的时间文案 |
| cancel | 点击取消按钮触发此事件 | - | - |
#### 组件向外暴露方法
| 方法名 | 说明 |
| ----- | ---- |
| show | 拉起time-picker组件 |
| hide | 隐藏time-picker组件 |
| setTime | 手动设置time-picker组件显示的时间,数据格式为unix时间戳 |
## Tip
`Tip`, used to popup tip bubble box.
### Example
- Basic usage
By adding `ref` to `Tip`, you can get the reference to the component and invoke `show` or `hide` methods which are exposed by `Tip` to control the show or hide state of the component.
```html
<div class="tip" @click="showTip">
<cube-tip ref="tip"></cube-tip>
</div>
```
```javascript
export default {
methods: {
showTip () {
this.$refs.tip.show()
}
}
}
```
- The position of the small triangle and the bubble box
You can configure the direction of the small triangle by `direction` and configure the position of the bubble box by `style`. Normally, the direction of the small triangle is opposite to the position of the bubble box.
```html
<p class="tip-eg">
<span>CubeUI</span>
<cube-tip
ref="tip"
:direction="direction"
:style="tipStyle"
@close="close"
@click="clickHandler">
<div>Awesome!</div>
</cube-tip>
</p>
</div>
<cube-button @click="showTip('bottom')">top</cube-button>
<cube-button @click="showTip('top')">bottom</cube-button>
<cube-button @click="showTip('right')">left</cube-button>
<cube-button @click="showTip('left')">right</cube-button>
```
```javascript
export default {
data() {
return {
direction: '',
tipStyle: ''
}
},
methods: {
showTip(direction) {
this.direction = direction
this.$refs.tip.show()
switch (direction) {
case 'top':
this.tipStyle = 'left: 100px; top: 100px;'
break
case 'bottom':
this.tipStyle = 'left: 100px; top: 20px;'
break
case 'left':
this.tipStyle = 'left: 200px; top: 60px;'
break
case 'right':
this.tipStyle = 'left: 5px; top: 60px;'
break
}
},
close() {
console.log('click close button')
},
clickHandler() {
console.log('click tip area')
}
}
}
```
### Props configuration
| Attribute | Description | Type | Accepted Values | Default |
| - | - | - | - | - |
| direction | the direction of the small triangle | String | top/bottom/left/right | left |
| offsetLeft | the distance between the small triangle and the left part of x axis | Number | - | 0 |
| offsetTop | the distance between the small triangle and the origin of y axis | Number | - | 0 |
| offsetRight | the distance between the small triangle and the right part of x axis | Number | - | 0 |
| offsetBottom | the distance between the small triangle and the bottom part of y axis | Number | - | 0 |
### Slot
| name | description |
| - | - |
| - | the content shown inside the tip component |
### Events
| Event Name | Description | Parameters |
| - | - | - |
| close | the event that exposed when clicking the close button of tips, which will hide at the same time | None |
| clicked | the event that exposed when clicking the content of tips, which will hide at the same time | None |
## Toast
`Toast` component.You can use it to show modaless tip message without user interaction.
### Example
- Duration of display
```html
<cube-button @click="showToastTime">Toast - time 1s</cube-button>
<cube-button @click="showToastTime0">Toast - time 0</cube-button>
```
```js
export default {
methods: {
showToastTime() {
const toast = this.$createToast({
time: 1000,
txt: 'Toast time 1s'
})
toast.show()
},
showToastTime0() {
const toast = this.$createToast({
time: 0,
txt: 'Toast time 0'
})
toast.show()
setTimeout(() => {
toast.hide()
}, 2000)
}
}
}
```
Set `time` to change the duration of display.If set to 0, it will not turn off automatically and you must invoke the component's `hide` method manually to trun off it.
- Show mask layer
```html
<cube-button @click="showToastMask">Toast- with mask</cube-button>
```
```js
export default {
methods: {
showToastMask () {
const toast = this.$createToast({
txt: 'Loading...',
mask: true
})
toast.show()
}
}
}
```
Set `mask` to `true` to display mask.
- Tip type
```html
<cube-button @click="showToastType">Toast - type</cube-button>
```
```js
export default {
methods: {
showToastType() {
const toast = this.$createToast({
txt: 'Correct',
type: 'correct'
})
toast.show()
}
}
}
```
Set `type` to change the tip icon.You can see accepted types in the following `Props configuration`.
### Props configuration
| Attribute | Description | Type | Accepted Values | Default |
| - | - | - | - | - |
| type | toast type(different types of icons) | String | loading/correct/error/warn | loading |
| mask | whether to show mask layer | Boolean | true/false | false |
| txt | tip text | String | - | '' |
| time | display duration, millisecond | Number | - | 3000 |
<template>
<viewport lang="zh-CN"></viewport>
</template>
<script>
import Viewport from '../viewport/viewport.vue'
export default {
components: {
Viewport
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
</style>
## ActionSheet
`ActionSheet`操作列表提供了两种常见的样式,灵活可控内容。
### 示例
- 基本用法
```html
<cube-button @click="showActionSheet">操作列表</cube-button>
```
```js
export default {
methods: {
showDefault() {
this.$createActionSheet({
title: '我是标题~~~',
data: [
{
content: '<em>舒适型</em>',
class: 'cube-foo'
},
{
content: '七座商务',
align: 'left'
},
{
content: '豪华型',
align: 'right'
}
],
onSelect: (item, index) => {
this.$createToast({
txt: `Clicked ${item.content}`,
time: 1000
}).show()
}
}).show()
}
}
}
```
配置标题 `title``data` 数据项,注意 `data` 中内容是 `content`,一段 HTML 字符串,额外还可以配置自定义 class:`class` 和方向:`align`(值可以是 `left``right`)。
- 高亮设置
```html
<cube-button @click="showActive">ActionSheet - active</cube-button>
```
```js
export default {
methods: {
showActive() {
this.$createActionSheet({
title: '我是标题~~~',
active: 0,
data: [
{
content: '舒适型'
},
{
content: '七座商务'
},
{
content: '豪华型'
}
]
}).show()
}
}
}
```
通过设置 `active` 属性来控制高亮的是第几个。
- Picker 样式设定
```html
<cube-button @click="showPickerStyle">ActionSheet - picker style</cube-button>
```
```js
export default {
methods: {
showPickerStyle() {
this.$createActionSheet({
title: '我是标题~~~',
pickerStyle: true,
data: [
{
content: '舒适型'
},
{
content: '七座商务'
},
{
content: '豪华型'
}
]
}).show()
}
}
}
```
`pickerStyle` 属性决定是否使用 Picker 样式。
### Props 配置
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| - | - | - | - | - |
| title | 组件的标题 | String | - | '' |
| data | 需要展示的数据列表 | Array | - | [] |
| active | 高亮第几个选项 | Number | - | -1 |
| pickerStyle | Picker 样式 | Boolean | true/false | false |
* `data` 子配置项
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| - | - | - | - | - |
| content | 展示的内容 | String | 任意 HTML 字符串 | '' |
| align | 内容对齐方向 | String | left/right | '' |
| class | 自定义 class | String | - | '' |
### 事件
| 参数 | 说明 | 参数1 | 参数2 |
| - | - | - | - |
| cancel | 点击取消 | - | - |
| select | 点击某项 | 点击项 item,即 data[index] | 点击项的索引值 index |
## better-scroll 模块
该模块默认暴露出一个 `BScroll` 函数对象,这个对象直接从依赖库 [better-scroll](https://github.com/ustbhuangyi/better-scroll) 获得。
### 链接
关于 better-scroll 详细的文档以及示例,请参考:
- [官方文档](https://ustbhuangyi.github.io/better-scroll/#/zh)
- [官方示例](https://ustbhuangyi.github.io/better-scroll/#/examples/zh)
## Button
按钮,提供了各种类型、样子、状态以及图标。
### 示例
- 按钮类型
默认类型为 `button`,还可设置为 `submit` 用于表单中
```html
<cube-button>Button</cube-button>
<cube-button type="submit">Submit Button</cube-button>
```
- 按钮状态
默认正常,可设置激活、禁用状态
```html
<cube-button :active="true">Active Button</cube-button>
<cube-button :disabled="true">Disabled Button</cube-button>
```
- 图标
可设置 icon 的 class
```html
<cube-button icon="cubeic-right">Button With Icon</cube-button>
```
- 样子
可以通过设置 `light``inline``outline``primary` 属性来改变按钮样子
```html
<cube-button :light="true">Light Button</cube-button>
<cube-button :inline="true">Inline Button</cube-button>
<cube-button :outline="true">Outline Button</cube-button>
<cube-button :primary="true">Primary Button</cube-button>
```
### Props 配置
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| - | - | - | - | - |
| type | 按钮类型 | String | button/submit | button |
| active | 激活状态 | Boolean | true/false | false |
| disabled | 禁用状态 | Boolean | true/false | false |
| icon | 图表 Icon | String | icon 的 class 值 | - |
| light | 亮色 | Boolean | true/false | false |
| inline | 是否内联 | Boolean | true/false | false |
| outline | 外边框 | Boolean | true/false | false |
| primary | 主要的 | Boolean | true/false | false |
### 事件
| 事件名 | 说明 | 参数 |
| - | - | - |
| click | 点击按钮后触发此事件,如果禁用状态,则不触发 | e - 事件对象 |
## CheckboxGroup 复选框组
复选框组就是一组复选框,主要用来选择一组可选项;有垂直和水平两种样式。
### 示例
- 垂直排列
默认就是垂直排列样式
```html
<cube-checkbox-group v-model="checkList">
<cube-checkbox label="1">
Checkbox 1
</cube-checkbox>
<cube-checkbox label="2">
Checkbox 2
</cube-checkbox>
<cube-checkbox label="3" :disabled="true">
Disabled Checkbox
</cube-checkbox>
<cube-checkbox label="4" :disabled="true">
Disabled & Checked Checkbox
</cube-checkbox>
</cube-checkbox-group>
```
`checkList` 的值是一个数组,代表的是选中的复选框 `label` 的值的集合。
- 水平排列
可通过设置 `horizontal` 改变样式为水平排列
```html
<cube-checkbox-group v-model="checkList" :horizontal="true">
<cube-checkbox label="1">1</cube-checkbox>
<cube-checkbox label="2">2</cube-checkbox>
<cube-checkbox label="3" :disabled="true">3</cube-checkbox>
<cube-checkbox label="4" :disabled="true">4</cube-checkbox>
</cube-checkbox-group>
```
### Props 配置
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| - | - | - | - | - |
| horizontal | 是否水平排列 | Boolean | true/false | false |
### 事件
| 事件名 | 说明 | 参数 |
| - | - | - |
| input | 组内可选项选中状态发生改变时触发 | 选中的复选框的值的集合,类型数组 |
## Checkbox 复选框
复选框,可设置其状态、传入特殊 class 以及复选框图标位置。
### 示例
- 基本用法
```html
<cube-checkbox v-model="checked">
Checkbox
</cube-checkbox>
```
如果选中了,则 `checked` 的值就为 `true`
- 禁用状态
```html
<cube-checkbox v-model="checked" :disabled="true">
Disabled Checkbox
</cube-checkbox>
```
设置 `disabled``true` 即为禁用状态
- 复选框图标位置
```html
<cube-checkbox v-model="checked" position="right">
Position Checkbox
</cube-checkbox>
```
设置 `position``'right'` 则复选框图标位置在右边
- 改变 model 的值
```html
<cube-checkbox v-model="checked" label="labelValue">
Set label Checkbox
</cube-checkbox>
```
设置 `label`,当复选框选中的时候,`checked` 的值就是 `'labelValue'`,当未选中的时候,`checked` 的值就是 `false`;所以其实在单个复选框的场景下,最好不要设置 `label`
### Props 配置
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| - | - | - | - | - |
| disabled | 是否被禁用 | Boolean | true/false | false |
| position | 位置 | String | left/right | left |
| label | 如果选中的话,则是把该值映射到 v-model 上 | Boolean/String | - | '' |
### 事件
| 事件名 | 说明 | 参数 |
| - | - | - |
| input | 选中状态发生改变时触发 | 设置的 label 的值或者是否选中的布尔值 |
## create-api 模块
该模块默认暴露出一个 `createAPI` 函数,可以实现以 API 的形式调用自定义组件。
### createAPI(Vue, Component, [events, single])
- 参数:
- `{Function} Vue` Vue 函数
- `{Function | Object} Component` 组件类或者组件对象,组件必须有 name
- `{Array} [events]` 组件实例 emit 的事件名集合
- `{Boolean} [single]` 是否为单例
- 用法:
- 该方法主要是在 Vue 的 prototype 上增加一个名为 `$create{camelize(Component.name)}` 的方法,这样就可以在其他组件中直接通过 `const instance = this.$createAaBb(config, [renderFn])` 这样来实例化组件了,而且这个实例化组件的元素是被附加到 `body` 元素下的;关于 `$createAaBb` 的参数:
- `{Object} config` 组件配置参数,默认所有的值都会当做 props 传给组件,但是要排除 `events` 中的事件(默认会做转换,例如:`events` 的值为 `['click']`,那么 `config` 中的 `onClick` 就是作为 `click` 事件的回调函数,而不是作为 props 传递给组件)。
- `{Function} [renderFn]` 是可选的参数,用于生成子 VNode 节点,一般场景是处理 slot。
- 注意调用后的返回值 `instance` 就是组件实例,这个实例会被**附加**或者**代理** `remove` 方法,如果调用了,该实例就会被销毁且会从 `body` 下移除。
- 示例:
```js
import Vue form 'vue'
// 得到 createAPI
import { createAPI } from 'cube-ui'
// or import Cube from 'cube-ui'
// const { createAPI } = Cube
// 需要提供 API 方式实例化的组件
const MyComponent = Vue.extend({
name: 'hello',
props: {
content: {
type: String,
default: 'Hello'
}
},
template: '<div @click="clickHandler">{{content}}<slot name="other"></slot></div>',
methods: {
clickHandler(e) {
this.$emit('click', e)
}
}
})
// 调用
createAPI(Vue, MyComponent, ['click'], true)
// 在其他组件中使用
new Vue({
el: '#app',
template: '<button @click="showHello">Show Hello</button>',
methods: {
showHello() {
// 直接调用
// 传入配置对象,默认传入的所有对象全都当做 props 传入组件
// 除了在调用 createAPI 的时候传入了 events,这里对应的就是
// on{event name} 会被当做事件回调处理
const instance = this.$createHello({
content: 'My Hello Content',
onClick(e) {
console.log('Hello component clicked.')
}
}, /* renderFn */ (createElement) => {
return [
createElement('p', {
slot: 'other'
}, 'other content')
]
})
// 通过 Vue 组件的 $on 也是可以监听的,看使用场景
instance.$on('click', (e) => {
console.log('on click', e)
})
// 移除销毁
instance.remove()
}
}
})
```
示例中就是创建了一个需要 API 调用的组件 `MyComponent`,名字为 `hello`,然后在其他组件中去使用,重点就是 `showHello()` 方法做的事情:调用 `this.$createHello(config, renderFn)` 实现组件的实例化。
## Dialog
`Dialog`模态框组件,提供了多种样式及交互形式。
### 示例
- 类型设置
```html
<cube-button @click="showAlert">Dialog - type</cube-button>
```
```js
export default {
methods: {
showAlert() {
this.$createDialog({
type: 'alert',
title: '我是标题',
content: '我是内容',
icon: 'cubeic-alert'
}).show()
}
}
}
```
`type` 可选的值为 `alert` (对应为提示框)、`confirm` (对应为确认框)。
- 按钮设置
```html
<cube-button @click="showBtn">Dialog - btn</cube-button>
```
```js
export default {
methods: {
showBtn() {
this.$createDialog({
type: 'confirm',
icon: 'cubeic-alert',
title: '我是标题',
content: '我是内容',
confirmBtn: {
text: '确定按钮',
active: true,
href: 'javascript:;'
},
cancelBtn: {
text: '取消按钮',
active: false,
href: 'javascript:;'
}
}).show()
}
}
}
```
按钮设置可接受 `String``Object` 类型数据,当传入 `Object` 类型的数据时,可通过 `text` 字段来设置按钮文案内容、`active` 字段来设置按钮文案是否高亮、`href` 字段为按钮的跳转链接。
- 关闭按钮
```html
<cube-button @click="showClose">Dialog - show close</cube-button>
```
```js
export default {
methods: {
showClose() {
this.$createDialog({
type: 'alert',
icon: 'cubeic-alert',
showClose: true,
title: '标题',
onClose: () => {
this.$createToast({
type: 'warn',
time: 1000,
txt: '点击关闭按钮'
}).show()
}
}).show()
}
}
}
```
`showClose` 字段决定是否需要显示关闭按钮,同时点击关闭按钮会触发 `close` 事件,如果传入了 `onClose` 则会被调用。
- 插槽
```html
<cube-button @click="showSlot">Dialog - slot</cube-button>
```
```js
export default {
methods: {
showSlot() {
this.$createDialog({
type: 'alert',
confirmBtn: {
text: '我知道了',
active: true
}
}, (createElement) => {
return [
createElement('div', {
'class': {
'my-title': true
},
slot: 'title'
}, [
createElement('div', {
'class': {
'my-title-img': true
}
}),
createElement('p', '附近车少,优选出租车将来接您')
]),
createElement('p', {
'class': {
'my-content': true
},
slot: 'content'
}, '价格仍按快车计算')
]
}).show()
}
}
}
```
`$createDialog` 的第二个参数是 [render 函数](https://vuejs.org/v2/guide/render-function.html),一般用于处理插槽的场景;Dialog 组件提供了 2 个具名的插槽 `title``content`,分别用来分发标题和内容。
### Props 配置
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| - | - | - | - | - |
| type | 类型 | String | 提示框 alert / 确认框 confirm | alert |
| icon | 图标的 class 名 | String | [参照 style 模块下的内置 icon 部分](#/style) | '' |
| title | 标题 | String | - | '' |
| content | 正文 | String | - | '' |
| showClose | 是否显示关闭按钮 | Boolean | true/false | false |
| confirmBtn | 确认按钮参数配置 | Object/String | - | { text: '确定', active: true, href: 'javascript:;' } |
| cancelBtn | 取消按钮参数配置 | Object/String | - | { text: '取消', active: false, href: 'javascript:;' } |
* `confirmBtn` 子配置项
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| - | - | - | - | - |
| text | 按钮文案 | String | - | '确认' |
| active | 是否高亮 | Boolean | true/false | true |
| href | 点击按钮后的跳转链接 | String | - | 'javascript:;' |
* `cancelBtn` 子配置项
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| - | - | - | - | - |
| text | 按钮文案 | String | - | '取消' |
| active | 是否高亮 | Boolean | true/false | false |
| href | 点击按钮后的跳转链接 | String | - | 'javascript:;' |
### 插槽
| 名字 | 说明 | 作用域参数 |
| - | - | - |
| title | 标题 | - |
| content | 内容 | - |
### 事件
| 事件名 | 说明 | 参数 |
| - | - | - | - |
| confirm | 点击确认按钮后触发 | 事件对象 e |
| cancel | 点击取消按钮后触发 | 事件对象 e |
| close | 点击关闭按钮后触发 | 事件对象 e |
## IndexList
索引列表,提供了列表索引的功能,也是一个基于`better-scroll`进行封装的组件。
### 示例
- 基本使用
构造如示例中 `cityData` 这样结构的数据,传入 `cube-index-list` 组件的 `data` 属性。
```html
<cube-index-list
:data="cityData"
:title="title"
@select="selectItem"
@title-click="clickTitle"></cube-index-list>
```
```javascript
const cityData = [
{
"name": "★Hot City",
"items": [
{
"name": "BEIJING",
"value": 1
},
{
"name": "SHANGHAI",
"value": 2
}
]
},
{
"name": "A",
"items": [
{
"name": "ANSHAN",
"value": 3
},
{
"name": "ANQING",
"value": 4
}
]
}
]
export default {
data() {
return {
title: 'Current City: BEIJING',
cityData: cityData
}
},
methods: {
selectItem(item) {
console.log(item.name)
},
clickTitle(title) {
console.log(title)
}
}
}
```
### Props 配置
| 参数 | 说明 | 类型 | 默认值 |
| - | - | - | - |
| title | 标题 | String | - |
| data | 需要展示的数据 | Array | [] |
- `data` 子配置项
`data` 是数组,表示的是一组数据,每一项配置:
| 参数 | 说明 | 类型 |
| - | - | - |
| name | 组名 | String |
| items | 当前组下的数据项 | Array |
`items` 数组中的每一项必须是对象,且包含 `name` 属性用于显示内容;例如 `items`: `[{name: 'xx', ...}, ...]`
### 事件
| 事件名 | 说明 | 参数 |
| - | - | - |
| select | 点击 IndexList 的某一项后触发 | 该选项的数据 |
| title-click | 点击 title 后触发(title 必须设置后才有效) | title属性值 |
## 安装
### NPM
推荐使用 NPM 安装`cube-ui`
```shell
$ npm install cube-ui
```
### CDN
也可通过 unpkg.com 获取资源,用标签引入 js 和 css 文件使用。
```html
<script src=""></script>
<link rel="stylesheet" href="">
```
### 版本日志
版本的更新日志见 [GitHub](https://github.com/didi/cube-ui/releases)
## 介绍
cube-ui 是基于 Vue.js 实现的精致移动端组件库。
### 特性
- **质量可靠**
由滴滴内部组件库精简提炼而来,经历了业务一年多的考验,并且每个组件都有充分单元测试,为后续集成提供保障。
- **体验极致**
以迅速响应、动画流畅、接近原生为目标,在交互体验方面追求极致。
- **标准规范**
遵循统一的设计交互标准,高度还原设计效果;接口标准化,统一规范使用方式,开发更加简单高效。
- **扩展性强**
支持按需引入和后编译,轻量灵活;扩展性强,可以方便地基于现有组件实现二次开发。
### 组件
#### 基础
- [Button 按钮](#/zh-CN/docs/button)
- [Checkbox 复选框](#/zh-CN/docs/checkbox)
- [CheckboxGroup 复选框组](#/zh-CN/docs/checkbox-group)
- [Loading 加载中](#/zh-CN/docs/loading)
- [Tip 提示](#/zh-CN/docs/tip)
#### 弹层
- [Toast 提醒](#/zh-CN/docs/toast)
- [Picker 选择器](#/zh-CN/docs/picker)
- [TimePicker 时间选择器](#/zh-CN/docs/time-picker)
- [Dialog 弹框](#/zh-CN/docs/dialog)
- [ActionSheet 操作菜单](#/zh-CN/docs/action-sheet)
所有的弹层类组件可以通过组件调用和 API 调用,来看示例(`Toast` 为例):
组件调用:
```html
<template>
<cube-button @click="showToast">show toast</cube-button>
<cube-toast ref="toast" :txt="toastTxt"></cube-toast>
</template>
<script>
export default {
data() {
return {
toastTxt: 'toast content'
}
},
methods: {
showToast() {
this.$refs.toast.show()
}
}
}
</script>
```
API 调用:
```html
<template>
<cube-button @click="showToast">show toast</cube-button>
</template>
<script>
export default {
data() {
return {
toastTxt: 'toast content'
}
},
methods: {
showToast() {
this.$createToast({
txt: this.toastTxt
}).show()
}
}
}
</script>
```
两种方式都可以,但是我们推荐后者,通过 `$createXxYy` API 来调用;通过 API 调用的话,这个弹框是附加到 `body` 元素下的,这样的好处是可以不受外层样式的影响,因为组件调用的话,很容易受到外层 `transform`, `overflow` 等样式的影响。
注意这个 API 的名字的组合就是:`$create` + `${组件名字}`,例如 `ActionSheet` 组件的话,API 名字就是 `$createActionSheet`
#### 滚动
- [Scroll 滚动](#/zh-CN/docs/scroll)
- [Slide 幻灯片](#/zh-CN/docs/slide)
- [IndexList 索引列表](#/zh-CN/docs/index-list)
滚动类组件都是基于 [better-scroll](https://github.com/ustbhuangyi/better-scroll) 实现,而 `Scroll` 组件就是对 better-scroll 的封装。
### 模块
除了组件之外,cube-ui 还有一些特殊的模块。
#### style
样式部分,如果你是在按需引入使用的场景下,那么建议在入口文件处也要引入这个模块,因为它包含基础的 reset、基础通用样式以及内置 icon。
详见 [style 模块](#/zh-CN/docs/style)
#### create-api
有些时候,开发者可能也需要自己封装的组件支持 API 式调用,此时可以通过引入 create-api 模块或者通过全局的 `Cube.createAPI` 接口来达到封装目的。
详见 [create-api 模块](#/zh-CN/docs/create-api)
#### better-scroll
[better-scroll](https://github.com/ustbhuangyi/better-scroll) 是组件库依赖的一个库,为了避免重复引入问题,我们暴露出了这个模块。
详见 [better-scroll 模块](#/zh-CN/docs/better-scroll)
## Loading
加载,提供了可自定义大小的加载动画。
### 示例
- 图标大小
默认大小为`24px`,可通过`size`属性配置
```html
<cube-loading></cube-loading>
<cube-loading :size="28"></cube-loading>
<cube-loading :size="34"></cube-loading>
```
### Props 配置
| 参数 | 说明 | 类型 | 默认值 |
| - | - | - | - |
| size | 加载图标的大小,单位px | Number | 24 |
此差异已折叠。
## Popup 弹层
底层弹层组件,主要用于基于此组件实现上层组件封装,只提供了基础功能:指定类型、是否有背景层、显示内容(HTML)以及是否居中。
内置所有的弹层类组件都是基于此组件实现,包括:[Toast](#/zh-CN/docs/toast)[Picker](#/zh-CN/docs/picker)[TimePicker](#/zh-CN/docs/time-picker)[Dialog](#/zh-CN/docs/dialog)[ActionSheet](#/zh-CN/docs/action-sheet)
### 示例
组件
- 基本用法
```html
<cube-popup type="my-popup" ref="myPopup">
My Popup Content 1
</cube-popup>
<cube-button @click="showPopup('myPopup')">
Show Popup
</cube-button>
```
指定类型 `type`,这样方便根据类型做 class 控制,如示例,会在根元素上会增加 `cube-my-popup` 的 class
```js
export default {
methods: {
showPopup(refId) {
const component = this.$refs[refId]
component.show()
setTimeout(() => {
component.hide()
}, 1000)
}
}
}
```
组件默认是隐藏的,需要调用组件实例的 `show()` 方法才会显示,调用组件实例的 `hide()` 方法隐藏,这里的 `showPopup()` 就是做的这件事情(首先显示,1 秒钟后隐藏)
- 不要背景层
```html
<cube-popup type="my-popup" :mask="false" ref="myPopup2">
My Popup Content 2
</cube-popup>
<cube-button @click="showPopup('myPopup2')">
Show Popup - no mask
</cube-button>
```
设置 `mask``false`,即不显示背景层
- 显示内容 HTML
```html
<cube-popup
type="my-popup"
:mask="false"
content="<i>My Popup Content 3</i>"
ref="myPopup3" />
<cube-button @click="showPopup('myPopup3')">
Show Popup - with content
</cube-button>
```
只需要传入 `content`,内容是一段 HTML 片段
- 置底
```html
<cube-popup type="my-popup" :center="false" ref="myPopup4">
My Popup Content 4
</cube-popup>
<cube-button @click="showPopup('myPopup4')">
Show Popup - bottom
</cube-button>
```
考虑移动端场景,大部分时候弹层类都是居中或置底的(置底的时候是在水平方向铺满的),所以如果设置了 `center``false` 的话,就是置底的效果
- 上层封装
```html
<template>
<cube-popup type="extend-popup" ref="popup">
<div class="cube-extend-popup-content" @click="hide">
<slot>{{content}}</slot>
</div>
</cube-popup>
</template>
<script type="text/ecmascript-6">
const COMPONENT_NAME = 'cube-extend-popup'
export default {
name: COMPONENT_NAME,
props: {
content: {
type: String
}
},
methods: {
show() {
this.$refs.popup.show()
},
hide() {
this.$refs.popup.hide()
this.$emit('hide')
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
.cube-extend-popup
.cube-extend-popup-content
padding: 20px
color: #fff
background-color: rgba(0, 0, 0, .8)
</style>
```
这样就实现了一个上层封装的 CubeExtendPopup 组件,自带了一些样式,支持传入内容还有默认插槽,点击内容可隐藏。你可以这样使用(需要全局注册或者局部注册到使用的组件中):
```html
<cube-extend-popup content="lala" ref="extendPopup"></cube-extend-popup>
<cube-button @click="$refs.extendPopup.show()">
Show Extend Popup
</cube-button>
```
### Props 配置
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| - | - | - | - | - |
| type | 弹层类型 | String | - | '' |
| mask | 是否显示背景层 | Boolean | true/false | true |
| content | 内容,HTML 字符串,在无插槽的时候有效 | String | - | '' |
| center | 是否水平垂直居中的 | Boolean | true/false | true |
### 事件
| 事件名 | 说明 | 参数 |
| - | - | - |
| mask-click | 背景层点击 | 点击事件对象 |
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册