瀏覽代碼

初始化

seimin 1 年之前
當前提交
53a7ef654f
共有 100 個文件被更改,包括 33789 次插入0 次删除
  1. 21 0
      .babelrc
  2. 9 0
      .editorconfig
  3. 14 0
      .gitignore
  4. 10 0
      .postcssrc.js
  5. 5 0
      README.md
  6. 41 0
      build/build.js
  7. 54 0
      build/check-versions.js
  8. 9 0
      build/dev-client.js
  9. 89 0
      build/dev-server.js
  10. 二進制
      build/logo.png
  11. 125 0
      build/utils.js
  12. 22 0
      build/vue-loader.conf.js
  13. 86 0
      build/webpack.base.conf.js
  14. 97 0
      build/webpack.dev.conf.js
  15. 145 0
      build/webpack.prod.conf.js
  16. 7 0
      config/dev.env.js
  17. 101 0
      config/index.js
  18. 4 0
      config/prod.env.js
  19. 36 0
      index.html
  20. 27038 0
      package-lock.json
  21. 82 0
      package.json
  22. 40 0
      src/App.vue
  23. 二進制
      src/assets/HM-search/attention.png
  24. 二進制
      src/assets/HM-search/attention_forbid.png
  25. 二進制
      src/assets/HM-search/back.png
  26. 二進制
      src/assets/HM-search/delete.png
  27. 二進制
      src/assets/logo.png
  28. 7 0
      src/common/helpers/create-api.js
  29. 14 0
      src/common/helpers/debug.js
  30. 98 0
      src/common/helpers/dom.js
  31. 3 0
      src/common/helpers/ease.js
  32. 4 0
      src/common/helpers/env.js
  33. 4 0
      src/common/helpers/instantiate-component.js
  34. 43 0
      src/common/helpers/raf.js
  35. 50 0
      src/common/helpers/string-template.js
  36. 207 0
      src/common/helpers/util.js
  37. 3 0
      src/common/helpers/validator/index.js
  38. 44 0
      src/common/helpers/validator/language/chinese.js
  39. 44 0
      src/common/helpers/validator/language/english.js
  40. 18 0
      src/common/helpers/validator/messages.js
  41. 52 0
      src/common/helpers/validator/rules.js
  42. 31 0
      src/common/helpers/validator/types.js
  43. 208 0
      src/common/icon/cube-icon.styl
  44. 二進制
      src/common/icon/cubeic.ttf
  45. 二進制
      src/common/icon/cubeic.woff
  46. 23 0
      src/common/js/date.js
  47. 61 0
      src/common/js/util.js
  48. 109 0
      src/common/lang/date.js
  49. 12 0
      src/common/lang/string.js
  50. 51 0
      src/common/locale/index.js
  51. 44 0
      src/common/mixins/basic-picker.js
  52. 22 0
      src/common/mixins/deprecated.js
  53. 25 0
      src/common/mixins/locale.js
  54. 34 0
      src/common/mixins/picker.js
  55. 12 0
      src/common/mixins/popup.js
  56. 11 0
      src/common/mixins/scroll.js
  57. 45 0
      src/common/mixins/visibility.js
  58. 93 0
      src/common/stylus/base.styl
  59. 5 0
      src/common/stylus/index.styl
  60. 63 0
      src/common/stylus/mixin.styl
  61. 55 0
      src/common/stylus/reset.styl
  62. 224 0
      src/common/stylus/theme/default.styl
  63. 3 0
      src/common/stylus/var/box-shadow.styl
  64. 36 0
      src/common/stylus/var/color.styl
  65. 12 0
      src/common/stylus/var/size.styl
  66. 4 0
      src/common/stylus/variable.styl
  67. 0 0
      src/components/.gitkeep
  68. 163 0
      src/components/ConsumableMaterial/index.vue
  69. 163 0
      src/components/WorkHourManagement/index.vue
  70. 188 0
      src/components/action-sheet/action-sheet.vue
  71. 137 0
      src/components/bubble/bubble.vue
  72. 148 0
      src/components/button/button.vue
  73. 110 0
      src/components/cascade-picker/cascade-picker.vue
  74. 135 0
      src/components/checkbox-group/checkbox-group.vue
  75. 237 0
      src/components/checkbox/checkbox.vue
  76. 59 0
      src/components/checker/checker-item.vue
  77. 91 0
      src/components/checker/checker.vue
  78. 28 0
      src/components/cube-button-group.vue
  79. 139 0
      src/components/cube-page.vue
  80. 16 0
      src/components/cube-view.vue
  81. 99 0
      src/components/date-picker.vue
  82. 246 0
      src/components/date-picker/date-picker.vue
  83. 310 0
      src/components/dialog/dialog.vue
  84. 61 0
      src/components/drawer/drawer-item.vue
  85. 108 0
      src/components/drawer/drawer-panel.vue
  86. 226 0
      src/components/drawer/drawer.vue
  87. 34 0
      src/components/extend-popup.vue
  88. 34 0
      src/components/form/components.js
  89. 11 0
      src/components/form/fields/index.js
  90. 11 0
      src/components/form/fields/props.js
  91. 18 0
      src/components/form/fields/reset.js
  92. 31 0
      src/components/form/fields/types.js
  93. 28 0
      src/components/form/fields/validate.js
  94. 57 0
      src/components/form/form-group.vue
  95. 310 0
      src/components/form/form-item.vue
  96. 427 0
      src/components/form/form.vue
  97. 7 0
      src/components/form/layouts.js
  98. 21 0
      src/components/form/mixin.js
  99. 327 0
      src/components/image-preview/image-preview.vue
  100. 0 0
      src/components/index-list/index-list-group.vue

+ 21 - 0
.babelrc

@@ -0,0 +1,21 @@
1
+{
2
+  "presets": [
3
+    ["env", {
4
+      "modules": false,
5
+      "targets": {
6
+        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7
+      }
8
+    }],
9
+    "stage-2"
10
+  ],
11
+  "plugins": [
12
+    "transform-vue-jsx", 
13
+    ["transform-runtime"],
14
+    ["transform-modules", {
15
+      "cube-ui": {
16
+        "transform": "./node_modules/cube-ui/src/modules/${member}",
17
+        "kebabCase": true
18
+      }
19
+    }]
20
+  ]
21
+}

+ 9 - 0
.editorconfig

@@ -0,0 +1,9 @@
1
+root = true
2
+
3
+[*]
4
+charset = utf-8
5
+indent_style = space
6
+indent_size = 2
7
+end_of_line = lf
8
+insert_final_newline = true
9
+trim_trailing_whitespace = true

+ 14 - 0
.gitignore

@@ -0,0 +1,14 @@
1
+.DS_Store
2
+node_modules/
3
+/dist/
4
+npm-debug.log*
5
+yarn-debug.log*
6
+yarn-error.log*
7
+
8
+# Editor directories and files
9
+.idea
10
+.vscode
11
+*.suo
12
+*.ntvs*
13
+*.njsproj
14
+*.sln

+ 10 - 0
.postcssrc.js

@@ -0,0 +1,10 @@
1
+// https://github.com/michael-ciniawsky/postcss-load-config
2
+
3
+module.exports = {
4
+  "plugins": {
5
+    "postcss-import": {},
6
+    "postcss-url": {},
7
+    // to edit target browsers: use "browserslist" field in package.json
8
+    "autoprefixer": {}
9
+  }
10
+}

+ 5 - 0
README.md

@@ -0,0 +1,5 @@
1
+# ITSM-微信报修端
2
+
3
+# 统一版本
4
+## v3.5.0
5
+2024年3月29日-v3.5.0

+ 41 - 0
build/build.js

@@ -0,0 +1,41 @@
1
+'use strict'
2
+require('./check-versions')()
3
+
4
+process.env.NODE_ENV = 'production'
5
+
6
+const ora = require('ora')
7
+const rm = require('rimraf')
8
+const path = require('path')
9
+const chalk = require('chalk')
10
+const webpack = require('webpack')
11
+const config = require('../config')
12
+const webpackConfig = require('./webpack.prod.conf')
13
+
14
+const spinner = ora('building for production...')
15
+spinner.start()
16
+
17
+rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
18
+  if (err) throw err
19
+  webpack(webpackConfig, (err, stats) => {
20
+    spinner.stop()
21
+    if (err) throw err
22
+    process.stdout.write(stats.toString({
23
+      colors: true,
24
+      modules: false,
25
+      children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
26
+      chunks: false,
27
+      chunkModules: false
28
+    }) + '\n\n')
29
+
30
+    if (stats.hasErrors()) {
31
+      console.log(chalk.red('  Build failed with errors.\n'))
32
+      process.exit(1)
33
+    }
34
+
35
+    console.log(chalk.cyan('  Build complete.\n'))
36
+    console.log(chalk.yellow(
37
+      '  Tip: built files are meant to be served over an HTTP server.\n' +
38
+      '  Opening index.html over file:// won\'t work.\n'
39
+    ))
40
+  })
41
+})

+ 54 - 0
build/check-versions.js

@@ -0,0 +1,54 @@
1
+'use strict'
2
+const chalk = require('chalk')
3
+const semver = require('semver')
4
+const packageConfig = require('../package.json')
5
+const shell = require('shelljs')
6
+
7
+function exec (cmd) {
8
+  return require('child_process').execSync(cmd).toString().trim()
9
+}
10
+
11
+const versionRequirements = [
12
+  {
13
+    name: 'node',
14
+    currentVersion: semver.clean(process.version),
15
+    versionRequirement: packageConfig.engines.node
16
+  }
17
+]
18
+
19
+if (shell.which('npm')) {
20
+  versionRequirements.push({
21
+    name: 'npm',
22
+    currentVersion: exec('npm --version'),
23
+    versionRequirement: packageConfig.engines.npm
24
+  })
25
+}
26
+
27
+module.exports = function () {
28
+  const warnings = []
29
+
30
+  for (let i = 0; i < versionRequirements.length; i++) {
31
+    const mod = versionRequirements[i]
32
+
33
+    if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
34
+      warnings.push(mod.name + ': ' +
35
+        chalk.red(mod.currentVersion) + ' should be ' +
36
+        chalk.green(mod.versionRequirement)
37
+      )
38
+    }
39
+  }
40
+
41
+  if (warnings.length) {
42
+    console.log('')
43
+    console.log(chalk.yellow('To use this template, you must update following to modules:'))
44
+    console.log()
45
+
46
+    for (let i = 0; i < warnings.length; i++) {
47
+      const warning = warnings[i]
48
+      console.log('  ' + warning)
49
+    }
50
+
51
+    console.log()
52
+    process.exit(1)
53
+  }
54
+}

+ 9 - 0
build/dev-client.js

@@ -0,0 +1,9 @@
1
+/* eslint-disable */
2
+require('eventsource-polyfill')
3
+var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
4
+
5
+hotClient.subscribe(function (event) {
6
+  if (event.action === 'reload') {
7
+    window.location.reload()
8
+  }
9
+})

+ 89 - 0
build/dev-server.js

@@ -0,0 +1,89 @@
1
+require('./check-versions')()
2
+
3
+var config = require('../config')
4
+if (!process.env.NODE_ENV) {
5
+  process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
6
+}
7
+
8
+var opn = require('opn')
9
+var path = require('path')
10
+var express = require('express')
11
+var webpack = require('webpack')
12
+var proxyMiddleware = require('http-proxy-middleware')
13
+var webpackConfig = require('./webpack.dev.conf')
14
+
15
+// default port where dev server listens for incoming traffic
16
+var port = process.env.PORT || config.dev.port
17
+// automatically open browser, if not set will be false
18
+var autoOpenBrowser = !!config.dev.autoOpenBrowser
19
+// Define HTTP proxies to your custom API backend
20
+// https://github.com/chimurai/http-proxy-middleware
21
+var proxyTable = config.dev.proxyTable
22
+
23
+var app = express()
24
+var compiler = webpack(webpackConfig)
25
+
26
+var devMiddleware = require('webpack-dev-middleware')(compiler, {
27
+  publicPath: webpackConfig.output.publicPath,
28
+  quiet: true
29
+})
30
+
31
+var hotMiddleware = require('webpack-hot-middleware')(compiler, {
32
+  log: () => {}
33
+})
34
+// force page reload when html-webpack-plugin template changes
35
+compiler.plugin('compilation', function (compilation) {
36
+  compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
37
+    hotMiddleware.publish({ action: 'reload' })
38
+    cb()
39
+  })
40
+})
41
+
42
+// proxy api requests
43
+Object.keys(proxyTable).forEach(function (context) {
44
+  var options = proxyTable[context]
45
+  if (typeof options === 'string') {
46
+    options = { target: options }
47
+  }
48
+  app.use(proxyMiddleware(options.filter || context, options))
49
+})
50
+
51
+// handle fallback for HTML5 history API
52
+app.use(require('connect-history-api-fallback')())
53
+
54
+// serve webpack bundle output
55
+app.use(devMiddleware)
56
+
57
+// enable hot-reload and state-preserving
58
+// compilation error display
59
+app.use(hotMiddleware)
60
+
61
+// serve pure static assets
62
+var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
63
+app.use(staticPath, express.static('./static'))
64
+
65
+var uri = 'http://localhost:' + port
66
+
67
+var _resolve
68
+var readyPromise = new Promise(resolve => {
69
+  _resolve = resolve
70
+})
71
+
72
+console.log('> Starting dev server...')
73
+devMiddleware.waitUntilValid(() => {
74
+  console.log('> Listening at ' + uri + '\n')
75
+  // when env is testing, don't need open it
76
+  if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
77
+    opn(uri)
78
+  }
79
+  _resolve()
80
+})
81
+
82
+var server = app.listen(port)
83
+
84
+module.exports = {
85
+  ready: readyPromise,
86
+  close: () => {
87
+    server.close()
88
+  }
89
+}

二進制
build/logo.png


+ 125 - 0
build/utils.js

@@ -0,0 +1,125 @@
1
+'use strict'
2
+const path = require('path')
3
+const config = require('../config')
4
+const ExtractTextPlugin = require('extract-text-webpack-plugin')
5
+const packageConfig = require('../package.json')
6
+// var cssLoader = {
7
+//   loader: 'css-loader',
8
+//     options: {
9
+//     sourceMap: options.sourceMap
10
+//   }
11
+// }
12
+// var px2remLoader = {
13
+//   loader: 'px2rem-loader',
14
+//     options: {
15
+//     remUnit: 75
16
+//   }
17
+// }
18
+
19
+exports.assetsPath = function (_path) {
20
+  const assetsSubDirectory = process.env.NODE_ENV === 'production'
21
+    ? config.build.assetsSubDirectory
22
+    : config.dev.assetsSubDirectory
23
+
24
+  return path.posix.join(assetsSubDirectory, _path)
25
+}
26
+
27
+exports.cssLoaders = function (options) {
28
+  options = options || {}
29
+
30
+  const cssLoader = {
31
+    loader: 'css-loader',
32
+    options: {
33
+      sourceMap: options.sourceMap
34
+    }
35
+  }
36
+
37
+  const postcssLoader = {
38
+    loader: 'postcss-loader',
39
+    options: {
40
+      sourceMap: options.sourceMap
41
+    }
42
+  }
43
+  const px2remLoader = {
44
+    loader: 'px2rem-loader',
45
+      options: {
46
+      remUnit: 60
47
+    }
48
+  }
49
+
50
+  const stylusOptions = {
51
+    'resolve url': true
52
+  }
53
+
54
+  // generate loader string to be used with extract text plugin
55
+  function generateLoaders (loader, loaderOptions) {
56
+    const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader];
57
+    // var loaders = [cssLoader, px2remLoader]
58
+
59
+    if (loader) {
60
+      loaders.push({
61
+        loader: loader + '-loader',
62
+        options: Object.assign({}, loaderOptions, {
63
+          sourceMap: options.sourceMap
64
+        })
65
+      })
66
+    }
67
+
68
+    // Extract CSS when that option is specified
69
+    // (which is the case during production build)
70
+    if (options.extract) {
71
+      return ExtractTextPlugin.extract({
72
+        use: loaders,
73
+        fallback: 'vue-style-loader',
74
+        publicPath:'../../'
75
+      })
76
+    } else {
77
+      return ['vue-style-loader'].concat(loaders)
78
+    }
79
+  }
80
+
81
+  // https://vue-loader.vuejs.org/en/configurations/extract-css.html
82
+  return {
83
+    css: generateLoaders(),
84
+    postcss: generateLoaders(),
85
+    less: generateLoaders('less'),
86
+    sass: generateLoaders('sass', { indentedSyntax: true }),
87
+    scss: generateLoaders('sass'),
88
+    stylus: generateLoaders('stylus', stylusOptions),
89
+    styl: generateLoaders('stylus', stylusOptions)
90
+  }
91
+}
92
+
93
+// Generate loaders for standalone style files (outside of .vue)
94
+exports.styleLoaders = function (options) {
95
+  const output = []
96
+  const loaders = exports.cssLoaders(options)
97
+
98
+  for (const extension in loaders) {
99
+    const loader = loaders[extension]
100
+    output.push({
101
+      test: new RegExp('\\.' + extension + '$'),
102
+      use: loader
103
+    })
104
+  }
105
+
106
+  return output
107
+}
108
+
109
+exports.createNotifierCallback = () => {
110
+  const notifier = require('node-notifier')
111
+
112
+  return (severity, errors) => {
113
+    if (severity !== 'error') return
114
+
115
+    const error = errors[0]
116
+    const filename = error.file && error.file.split('!').pop()
117
+
118
+    notifier.notify({
119
+      title: packageConfig.name,
120
+      message: severity + ': ' + error.name,
121
+      subtitle: filename || '',
122
+      icon: path.join(__dirname, 'logo.png')
123
+    })
124
+  }
125
+}

+ 22 - 0
build/vue-loader.conf.js

@@ -0,0 +1,22 @@
1
+'use strict'
2
+const utils = require('./utils')
3
+const config = require('../config')
4
+const isProduction = process.env.NODE_ENV === 'production'
5
+const sourceMapEnabled = isProduction
6
+  ? config.build.productionSourceMap
7
+  : config.dev.cssSourceMap
8
+
9
+module.exports = {
10
+  loaders: utils.cssLoaders({
11
+    sourceMap: sourceMapEnabled,
12
+    extract: isProduction
13
+  }),
14
+  cssSourceMap: sourceMapEnabled,
15
+  cacheBusting: config.dev.cacheBusting,
16
+  transformToRequire: {
17
+    video: ['src', 'poster'],
18
+    source: 'src',
19
+    img: 'src',
20
+    image: 'xlink:href'
21
+  }
22
+}

+ 86 - 0
build/webpack.base.conf.js

@@ -0,0 +1,86 @@
1
+'use strict'
2
+const path = require('path')
3
+const utils = require('./utils')
4
+const config = require('../config')
5
+const vueLoaderConfig = require('./vue-loader.conf')
6
+
7
+function resolve (dir) {
8
+  return path.join(__dirname, '..', dir)
9
+}
10
+
11
+
12
+
13
+module.exports = {
14
+  context: path.resolve(__dirname, '../'),
15
+  entry: {
16
+    app: './src/main.js'
17
+  },
18
+  output: {
19
+    path: config.build.assetsRoot,
20
+    filename: '[name].js',
21
+    publicPath: process.env.NODE_ENV === 'production'
22
+      ? config.build.assetsPublicPath
23
+      : config.dev.assetsPublicPath
24
+  },
25
+  resolve: {
26
+    extensions: ['.js', '.vue', '.json'],
27
+    alias: {
28
+      'vue$': 'vue/dist/vue.esm.js',
29
+      '@': resolve('src'),
30
+    }
31
+  },
32
+  module: {
33
+    rules: [
34
+      {
35
+        test: /\.vue$/,
36
+        loader: 'vue-loader',
37
+        options: vueLoaderConfig
38
+      },
39
+      {
40
+        test: /\.js$/,
41
+        loader: 'babel-loader',
42
+        include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
43
+      },
44
+      {
45
+        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
46
+        loader: 'url-loader',
47
+        options: {
48
+          limit: 90000,
49
+          name: utils.assetsPath('img/[name].[hash:7].[ext]')
50
+        }
51
+      },
52
+      {
53
+        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
54
+        loader: 'url-loader',
55
+        options: {
56
+          limit: 90000,
57
+          name: utils.assetsPath('media/[name].[hash:7].[ext]')
58
+        }
59
+      },
60
+      {
61
+        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
62
+        loader: 'url-loader',
63
+        options: {
64
+          limit: 90000,
65
+          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
66
+        }
67
+      },
68
+      {
69
+        test: /\.less$/,
70
+        loader: "style-loader!css-loader!less-loader"
71
+      }
72
+    ]
73
+  },
74
+  node: {
75
+    // prevent webpack from injecting useless setImmediate polyfill because Vue
76
+    // source contains it (although only uses it if it's native).
77
+    setImmediate: false,
78
+    // prevent webpack from injecting mocks to Node native modules
79
+    // that does not make sense for the client
80
+    dgram: 'empty',
81
+    fs: 'empty',
82
+    net: 'empty',
83
+    tls: 'empty',
84
+    child_process: 'empty'
85
+  }
86
+}

+ 97 - 0
build/webpack.dev.conf.js

@@ -0,0 +1,97 @@
1
+'use strict'
2
+const utils = require('./utils')
3
+const webpack = require('webpack')
4
+const config = require('../config')
5
+const merge = require('webpack-merge')
6
+const path = require('path')
7
+const baseWebpackConfig = require('./webpack.base.conf')
8
+const CopyWebpackPlugin = require('copy-webpack-plugin')
9
+const HtmlWebpackPlugin = require('html-webpack-plugin')
10
+const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
11
+const portfinder = require('portfinder')
12
+const PostCompilePlugin = require('webpack-post-compile-plugin')
13
+
14
+const HOST = process.env.HOST
15
+const PORT = process.env.PORT && Number(process.env.PORT)
16
+
17
+const devWebpackConfig = merge(baseWebpackConfig, {
18
+  module: {
19
+    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
20
+  },
21
+  // cheap-module-eval-source-map is faster for development
22
+  devtool: config.dev.devtool,
23
+
24
+  // these devServer options should be customized in /config/index.js
25
+  devServer: {
26
+    clientLogLevel: 'warning',
27
+    historyApiFallback: {
28
+      rewrites: [
29
+        { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
30
+      ],
31
+    },
32
+    hot: true,
33
+    contentBase: false, // since we use CopyWebpackPlugin.
34
+    compress: true,
35
+    host: HOST || config.dev.host,
36
+    port: PORT || config.dev.port,
37
+    open: config.dev.autoOpenBrowser,
38
+    overlay: config.dev.errorOverlay
39
+      ? { warnings: false, errors: true }
40
+      : false,
41
+    publicPath: config.dev.assetsPublicPath,
42
+    proxy: config.dev.proxyTable,
43
+    quiet: true, // necessary for FriendlyErrorsPlugin
44
+    watchOptions: {
45
+      poll: config.dev.poll,
46
+    }
47
+  },
48
+  plugins: [
49
+    new webpack.DefinePlugin({
50
+      'process.env': require('../config/dev.env')
51
+    }),
52
+    new webpack.HotModuleReplacementPlugin(),
53
+    new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
54
+    new webpack.NoEmitOnErrorsPlugin(),
55
+    // https://github.com/ampedandwired/html-webpack-plugin
56
+    new HtmlWebpackPlugin({
57
+      filename: 'index.html',
58
+      template: 'index.html',
59
+      inject: true
60
+    }),
61
+    // copy custom static assets
62
+    new CopyWebpackPlugin([
63
+      {
64
+        from: path.resolve(__dirname, '../static'),
65
+        to: config.dev.assetsSubDirectory,
66
+        ignore: ['.*']
67
+      }
68
+    ]),
69
+    new PostCompilePlugin()
70
+  ]
71
+})
72
+
73
+module.exports = new Promise((resolve, reject) => {
74
+  portfinder.basePort = process.env.PORT || config.dev.port
75
+  portfinder.getPort((err, port) => {
76
+    if (err) {
77
+      reject(err)
78
+    } else {
79
+      // publish the new Port, necessary for e2e tests
80
+      process.env.PORT = port
81
+      // add port to devServer config
82
+      devWebpackConfig.devServer.port = port
83
+
84
+      // Add FriendlyErrorsPlugin
85
+      devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
86
+        compilationSuccessInfo: {
87
+          messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
88
+        },
89
+        onErrors: config.dev.notifyOnErrors
90
+        ? utils.createNotifierCallback()
91
+        : undefined
92
+      }))
93
+
94
+      resolve(devWebpackConfig)
95
+    }
96
+  })
97
+})

+ 145 - 0
build/webpack.prod.conf.js

@@ -0,0 +1,145 @@
1
+'use strict'
2
+const path = require('path')
3
+const utils = require('./utils')
4
+const webpack = require('webpack')
5
+const config = require('../config')
6
+const merge = require('webpack-merge')
7
+const baseWebpackConfig = require('./webpack.base.conf')
8
+const CopyWebpackPlugin = require('copy-webpack-plugin')
9
+const HtmlWebpackPlugin = require('html-webpack-plugin')
10
+const ExtractTextPlugin = require('extract-text-webpack-plugin')
11
+const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
12
+const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
13
+
14
+const env = require('../config/prod.env')
15
+
16
+const webpackConfig = merge(baseWebpackConfig, {
17
+  module: {
18
+    rules: utils.styleLoaders({
19
+      sourceMap: config.build.productionSourceMap,
20
+      extract: true,
21
+      usePostCSS: true
22
+    })
23
+  },
24
+  devtool: config.build.productionSourceMap ? config.build.devtool : false,
25
+  output: {
26
+    path: config.build.assetsRoot,
27
+    filename: utils.assetsPath('js/[name].[chunkhash].js'),
28
+    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
29
+  },
30
+  plugins: [
31
+    // http://vuejs.github.io/vue-loader/en/workflow/production.html
32
+    new webpack.DefinePlugin({
33
+      'process.env': env
34
+    }),
35
+    new UglifyJsPlugin({
36
+      uglifyOptions: {
37
+        compress: {
38
+          warnings: false
39
+        }
40
+      },
41
+      sourceMap: config.build.productionSourceMap,
42
+      parallel: true
43
+    }),
44
+    // extract css into its own file
45
+    new ExtractTextPlugin({
46
+      filename: utils.assetsPath('css/[name].[contenthash].css'),
47
+      // Setting the following option to `false` will not extract CSS from codesplit chunks.
48
+      // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
49
+      // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 
50
+      // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
51
+      allChunks: true,
52
+    }),
53
+    // Compress extracted CSS. We are using this plugin so that possible
54
+    // duplicated CSS from different components can be deduped.
55
+    new OptimizeCSSPlugin({
56
+      cssProcessorOptions: config.build.productionSourceMap
57
+        ? { safe: true, map: { inline: false } }
58
+        : { safe: true }
59
+    }),
60
+    // generate dist index.html with correct asset hash for caching.
61
+    // you can customize output by editing /index.html
62
+    // see https://github.com/ampedandwired/html-webpack-plugin
63
+    new HtmlWebpackPlugin({
64
+      filename: config.build.index,
65
+      template: 'index.html',
66
+      inject: true,
67
+      minify: {
68
+        removeComments: true,
69
+        collapseWhitespace: true,
70
+        removeAttributeQuotes: true
71
+        // more options:
72
+        // https://github.com/kangax/html-minifier#options-quick-reference
73
+      },
74
+      // necessary to consistently work with multiple chunks via CommonsChunkPlugin
75
+      chunksSortMode: 'dependency'
76
+    }),
77
+    // keep module.id stable when vendor modules does not change
78
+    new webpack.HashedModuleIdsPlugin(),
79
+    // enable scope hoisting
80
+    new webpack.optimize.ModuleConcatenationPlugin(),
81
+    // split vendor js into its own file
82
+    new webpack.optimize.CommonsChunkPlugin({
83
+      name: 'vendor',
84
+      minChunks (module) {
85
+        // any required modules inside node_modules are extracted to vendor
86
+        return (
87
+          module.resource &&
88
+          /\.js$/.test(module.resource) &&
89
+          module.resource.indexOf(
90
+            path.join(__dirname, '../node_modules')
91
+          ) === 0
92
+        )
93
+      }
94
+    }),
95
+    // extract webpack runtime and module manifest to its own file in order to
96
+    // prevent vendor hash from being updated whenever app bundle is updated
97
+    new webpack.optimize.CommonsChunkPlugin({
98
+      name: 'manifest',
99
+      minChunks: Infinity
100
+    }),
101
+    // This instance extracts shared chunks from code splitted chunks and bundles them
102
+    // in a separate chunk, similar to the vendor chunk
103
+    // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
104
+    new webpack.optimize.CommonsChunkPlugin({
105
+      name: 'app',
106
+      async: 'vendor-async',
107
+      children: true,
108
+      minChunks: 3
109
+    }),
110
+
111
+    // copy custom static assets
112
+    new CopyWebpackPlugin([
113
+      {
114
+        from: path.resolve(__dirname, '../static'),
115
+        to: config.build.assetsSubDirectory,
116
+        ignore: ['.*']
117
+      }
118
+    ])
119
+  ]
120
+})
121
+
122
+if (config.build.productionGzip) {
123
+  const CompressionWebpackPlugin = require('compression-webpack-plugin')
124
+
125
+  webpackConfig.plugins.push(
126
+    new CompressionWebpackPlugin({
127
+      asset: '[path].gz[query]',
128
+      algorithm: 'gzip',
129
+      test: new RegExp(
130
+        '\\.(' +
131
+        config.build.productionGzipExtensions.join('|') +
132
+        ')$'
133
+      ),
134
+      threshold: 10240,
135
+      minRatio: 0.8
136
+    })
137
+  )
138
+}
139
+
140
+if (config.build.bundleAnalyzerReport) {
141
+  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
142
+  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
143
+}
144
+
145
+module.exports = webpackConfig

+ 7 - 0
config/dev.env.js

@@ -0,0 +1,7 @@
1
+'use strict'
2
+const merge = require('webpack-merge')
3
+const prodEnv = require('./prod.env')
4
+
5
+module.exports = merge(prodEnv, {
6
+  NODE_ENV: '"development"'
7
+})

+ 101 - 0
config/index.js

@@ -0,0 +1,101 @@
1
+'use strict'
2
+// Template version: 1.3.1
3
+// see http://vuejs-templates.github.io/webpack for documentation.
4
+
5
+const path = require('path')
6
+
7
+module.exports = {
8
+  dev: {
9
+    // 新加的一行
10
+    env: require('./dev.env'),
11
+    // Paths
12
+    assetsSubDirectory: 'static',
13
+    assetsPublicPath: '/',
14
+    proxyTable: {
15
+      '/service': {//自定义名字,代表的是以下target中的内容
16
+        target: 'http://192.168.3.111',//微信
17
+        // target: 'http://weixintest5.ngser.dashitech.com/service/',//微信
18
+        // target: 'http://localhost:8080/service/',//微信
19
+        // target: 'http://192.168.199.165:8080/service/',//黄伟
20
+        // target: 'http://192.168.3.100:8080/service/',//100
21
+        changeOrigin: true,//是否允许跨域
22
+        // pathRewrite: {
23
+        //   '^/service': ''
24
+        // }
25
+      },
26
+      '/file': {//自定义名字,代表的是以下target中的内容
27
+        target: 'http://192.168.3.111',//微信
28
+        // target: 'http://weixintest5.ngser.dashitech.com/service/',//微信
29
+        // target: 'http://localhost:8080/service/',//微信
30
+        // target: 'http://192.168.199.165:8080/service/',//黄伟
31
+        // target: 'http://192.168.3.100:8080/service/',//100
32
+        changeOrigin: true,//是否允许跨域
33
+        // pathRewrite: {
34
+        //   '^/service': ''
35
+        // }
36
+      }
37
+    },
38
+
39
+    // Various Dev Server settings
40
+    host: '0.0.0.0', // can be overwritten by process.env.HOST
41
+    // host: '127.0.0.1', // can be overwritten by process.env.HOST
42
+    // host: '192.168.199.177', // can be overwritten by process.env.HOST
43
+    // host: '192.168.31.32', // can be overwritten by process.env.HOST
44
+    // host: '192.168.31.32', // can be overwritten by process.env.HOST
45
+    // host: '0.0.0.0', // can be overwritten by process.env.HOST
46
+    port: 8081, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
47
+    // 住了的4行
48
+    autoOpenBrowser: true,
49
+    // errorOverlay: true,
50
+    // notifyOnErrors: true,
51
+    // poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
52
+
53
+
54
+    /**
55
+     * Source Maps
56
+     */
57
+
58
+    // https://webpack.js.org/configuration/devtool/#development
59
+    // 住了一行
60
+    devtool: 'source-map',
61
+
62
+    // If you have problems debugging vue-files in devtools,
63
+    // set this to false - it *may* help
64
+    // https://vue-loader.vuejs.org/en/options.html#cachebusting
65
+    // 住了的
66
+    // cacheBusting: true,
67
+
68
+    cssSourceMap: true
69
+  },
70
+
71
+  build: {
72
+    // Template for index.html
73
+    index: path.resolve(__dirname, '../dist/index.html'),
74
+
75
+    // Paths
76
+    assetsRoot: path.resolve(__dirname, '../dist'),
77
+    assetsSubDirectory: 'static',
78
+    assetsPublicPath: './',
79
+
80
+    /**
81
+     * Source Maps
82
+     */
83
+
84
+    productionSourceMap: false,
85
+    // https://webpack.js.org/configuration/devtool/#production
86
+    devtool: '#source-map',
87
+
88
+    // Gzip off by default as many popular static hosts such as
89
+    // Surge or Netlify already gzip all static assets for you.
90
+    // Before setting to `true`, make sure to:
91
+    // npm install --save-dev compression-webpack-plugin
92
+    productionGzip: false,
93
+    productionGzipExtensions: ['js', 'css'],
94
+
95
+    // Run the build command with an extra argument to
96
+    // View the bundle analyzer report after build finishes:
97
+    // `npm run build --report`
98
+    // Set to `true` or `false` to always turn it on or off
99
+    bundleAnalyzerReport: process.env.npm_config_report
100
+  }
101
+}

+ 4 - 0
config/prod.env.js

@@ -0,0 +1,4 @@
1
+'use strict'
2
+module.exports = {
3
+  NODE_ENV: '"production"'
4
+}

+ 36 - 0
index.html

@@ -0,0 +1,36 @@
1
+<!DOCTYPE html>
2
+<html>
3
+  <head>
4
+    <meta charset="utf-8">
5
+    <link rel="stylesheet" href="./static/css/iconfont.css">
6
+    <link rel="stylesheet" href="./static/font/seimin/iconfont.css">
7
+    <script src="./static/js/JQ-3.3.1.js"></script>
8
+    <meta name="viewport"
9
+        content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover">
10
+    <!-- <meta name="viewport" content="width=device-width,intial-scale=1,user-
11
+        scalable=no"> -->
12
+    <title></title>
13
+    <style>
14
+      html{
15
+        background-color: white;
16
+        height: 100vh;
17
+        margin: 0 auto!important;
18
+      }
19
+      body{
20
+        margin: 0;
21
+        padding: 0;
22
+        background-color: white
23
+      }
24
+      img{
25
+        vertical-align: middle
26
+      }
27
+      a{
28
+        text-decoration: none
29
+      }
30
+    </style>
31
+  </head>
32
+  <body>
33
+    <div id="app"></div>
34
+  </body>
35
+</html>
36
+<script src="./static/js/public.js"></script>

File diff suppressed because it is too large
+ 27038 - 0
package-lock.json


+ 82 - 0
package.json

@@ -0,0 +1,82 @@
1
+{
2
+  "name": "jry",
3
+  "version": "1.0.0",
4
+  "description": "jryVue",
5
+  "author": "jiang",
6
+  "private": true,
7
+  "scripts": {
8
+    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
9
+    "start": "npm run dev",
10
+    "build": "node build/build.js",
11
+    "development": "node ./upload/development.js",
12
+    "production": "node ./upload/production.js"
13
+  },
14
+  "dependencies": {
15
+    "axios": "^0.18.0",
16
+    "cube-ui": "^1.12.10",
17
+    "js-base64": "^3.6.1",
18
+    "less": "^3.9.0",
19
+    "less-loader": "^4.1.0",
20
+    "lib-flexible": "^0.3.2",
21
+    "lodash": "^4.17.21",
22
+    "vue": "^2.5.2",
23
+    "vue-axios": "^2.1.4",
24
+    "vue-router": "^3.0.1",
25
+    "weixin-jsapi": "^1.1.0"
26
+  },
27
+  "compileDependencies": [
28
+    "cube-ui"
29
+  ],
30
+  "devDependencies": {
31
+    "autoprefixer": "^7.1.2",
32
+    "babel-core": "^6.22.1",
33
+    "babel-helper-vue-jsx-merge-props": "^2.0.3",
34
+    "babel-loader": "^7.1.1",
35
+    "babel-plugin-syntax-jsx": "^6.18.0",
36
+    "babel-plugin-transform-modules": "^0.0.2",
37
+    "babel-plugin-transform-runtime": "^6.22.0",
38
+    "babel-plugin-transform-vue-jsx": "^3.5.0",
39
+    "babel-preset-env": "^1.3.2",
40
+    "babel-preset-stage-2": "^6.22.0",
41
+    "chalk": "^2.0.1",
42
+    "copy-webpack-plugin": "^4.0.1",
43
+    "css-loader": "^0.28.0",
44
+    "extract-text-webpack-plugin": "^3.0.0",
45
+    "file-loader": "^1.1.4",
46
+    "friendly-errors-webpack-plugin": "^1.6.1",
47
+    "html-webpack-plugin": "^2.30.1",
48
+    "node-notifier": "^5.1.2",
49
+    "optimize-css-assets-webpack-plugin": "^3.2.0",
50
+    "ora": "^1.2.0",
51
+    "portfinder": "^1.0.13",
52
+    "postcss-import": "^11.0.0",
53
+    "postcss-loader": "^2.0.8",
54
+    "postcss-url": "^7.2.1",
55
+    "px2rem-loader": "^0.1.9",
56
+    "rimraf": "^2.6.0",
57
+    "semver": "^5.3.0",
58
+    "shelljs": "^0.7.6",
59
+    "ssh2-sftp-client": "^7.2.0",
60
+    "stylus": "^0.54.5",
61
+    "stylus-loader": "^2.1.1",
62
+    "uglifyjs-webpack-plugin": "^1.1.1",
63
+    "url-loader": "^0.5.8",
64
+    "vue-loader": "^13.3.0",
65
+    "vue-style-loader": "^3.0.1",
66
+    "vue-template-compiler": "^2.5.2",
67
+    "webpack": "^3.6.0",
68
+    "webpack-bundle-analyzer": "^2.9.0",
69
+    "webpack-dev-server": "^2.9.1",
70
+    "webpack-merge": "^4.1.0",
71
+    "webpack-post-compile-plugin": "^0.1.2"
72
+  },
73
+  "engines": {
74
+    "node": ">= 6.0.0",
75
+    "npm": ">= 3.0.0"
76
+  },
77
+  "browserslist": [
78
+    "> 1%",
79
+    "last 2 versions",
80
+    "not ie <= 8"
81
+  ]
82
+}

+ 40 - 0
src/App.vue

@@ -0,0 +1,40 @@
1
+<template>
2
+	<div id="app">
3
+		<router-view></router-view>
4
+	</div>
5
+</template>
6
+
7
+<script>
8
+export default {
9
+	name: 'app',
10
+	components: {
11
+
12
+	},
13
+	methods:{
14
+
15
+	},
16
+}
17
+
18
+</script>
19
+
20
+<style>
21
+.cube-picker-wheel-item{
22
+  font-size: 12px!important;
23
+}
24
+.cube-popup_mask{
25
+  margin: 0 auto;
26
+}
27
+.overflowHidden{
28
+  overflow: hidden!important;
29
+}
30
+* {
31
+  font-family: "微软雅黑";
32
+  max-width: 750px;
33
+}
34
+.noDataFont{
35
+	height: .5rem;
36
+	line-height: .5rem;
37
+	color: #666666;
38
+	text-align: center
39
+}
40
+</style>

二進制
src/assets/HM-search/attention.png


二進制
src/assets/HM-search/attention_forbid.png


二進制
src/assets/HM-search/back.png


二進制
src/assets/HM-search/delete.png


二進制
src/assets/logo.png


+ 7 - 0
src/common/helpers/create-api.js

@@ -0,0 +1,7 @@
1
+import createAPIComponent from 'vue-create-api'
2
+
3
+export default function createAPI (Vue, Component, events, single) {
4
+  Vue.use(createAPIComponent, {componentPrefix: 'cube-'})
5
+  const api = Vue.createAPI(Component, events, single)
6
+  return api
7
+}

+ 14 - 0
src/common/helpers/debug.js

@@ -0,0 +1,14 @@
1
+export const warn = function (msg, componentName) {
2
+   /* istanbul ignore if */
3
+  if (process.env.NODE_ENV !== 'production') {
4
+    const component = componentName ? `<${componentName}> ` : ''
5
+    console.error(`[Cube warn]: ${component}${msg}`)
6
+  }
7
+}
8
+
9
+export const tip = function (msg, componentName) {
10
+  if (process.env.NODE_ENV !== 'production') {
11
+    const component = componentName ? `<${componentName}> ` : ''
12
+    console.warn(`[Cube tip]: ${component}${msg}`)
13
+  }
14
+}

+ 98 - 0
src/common/helpers/dom.js

@@ -0,0 +1,98 @@
1
+import { inBrowser } from './env'
2
+
3
+export function hasClass(el, className) {
4
+  const reg = new RegExp('(^|\\s)' + className + '(\\s|$)')
5
+  return reg.test(el.className)
6
+}
7
+
8
+export function addClass(el, className) {
9
+  /* istanbul ignore if */
10
+  if (hasClass(el, className)) {
11
+    return
12
+  }
13
+
14
+  const newClass = el.className.split(' ')
15
+  newClass.push(className)
16
+  el.className = newClass.join(' ')
17
+}
18
+
19
+export function removeClass(el, className) {
20
+  /* istanbul ignore if */
21
+  if (!hasClass(el, className)) {
22
+    return
23
+  }
24
+
25
+  const reg = new RegExp('(^|\\s)' + className + '(\\s|$)', 'g')
26
+  el.className = el.className.replace(reg, ' ')
27
+}
28
+
29
+export function getData(el, name) {
30
+  const prefix = 'data-'
31
+  return el.getAttribute(prefix + name)
32
+}
33
+
34
+export function getRect(el) {
35
+  return {
36
+    top: el.offsetTop,
37
+    left: el.offsetLeft,
38
+    width: el.offsetWidth,
39
+    height: el.offsetHeight
40
+  }
41
+}
42
+
43
+let vendor = (() => {
44
+  /* istanbul ignore if */
45
+  if (!inBrowser) {
46
+    return false
47
+  }
48
+  let elementStyle = document.createElement('div').style
49
+  let transformNames = {
50
+    standard: 'transform',
51
+    webkit: 'webkitTransform',
52
+    Moz: 'MozTransform',
53
+    O: 'OTransform',
54
+    ms: 'msTransform'
55
+  }
56
+
57
+  for (let key in transformNames) {
58
+    if (elementStyle[transformNames[key]] !== undefined) {
59
+      return key
60
+    }
61
+  }
62
+
63
+  /* istanbul ignore next */
64
+  return false
65
+})()
66
+
67
+export function prefixStyle(style) {
68
+  /* istanbul ignore if */
69
+  if (vendor === false) {
70
+    return false
71
+  }
72
+
73
+  if (vendor === 'standard') {
74
+    if (style === 'transitionEnd') {
75
+      return 'transitionend'
76
+    }
77
+    return style
78
+  }
79
+
80
+  return vendor + style.charAt(0).toUpperCase() + style.substr(1)
81
+}
82
+
83
+export function getMatchedTarget(e, targetClassName) {
84
+  let el = e.target
85
+
86
+  while (el && !hasClass(el, targetClassName)) {
87
+    if (el === e.currentTarget) return null
88
+    el = el.parentNode
89
+  }
90
+
91
+  return el
92
+}
93
+
94
+export function dispatchEvent(ele, name, { type = 'Event', bubbles = true, cancelable = true } = {}) {
95
+  const e = document.createEvent(type)
96
+  e.initEvent(name, bubbles, cancelable)
97
+  ele.dispatchEvent(e)
98
+}

+ 3 - 0
src/common/helpers/ease.js

@@ -0,0 +1,3 @@
1
+export const easeOutBack = 'cubic-bezier(0.18, 0.89, 0.32, 1.28)'
2
+export const easeOutQuart = 'cubic-bezier(0.165, 0.84, 0.44, 1)'
3
+export const easeOutCubic = 'cubic-bezier(0.22, 0.61, 0.36, 1)'

+ 4 - 0
src/common/helpers/env.js

@@ -0,0 +1,4 @@
1
+// ssr support
2
+export const inBrowser = typeof window !== 'undefined'
3
+export const ua = inBrowser && navigator.userAgent.toLowerCase()
4
+export const isAndroid = ua && ua.indexOf('android') > 0

+ 4 - 0
src/common/helpers/instantiate-component.js

@@ -0,0 +1,4 @@
1
+import createAPI from 'vue-create-api'
2
+const { instantiateComponent } = createAPI
3
+
4
+export default instantiateComponent

+ 43 - 0
src/common/helpers/raf.js

@@ -0,0 +1,43 @@
1
+import { inBrowser } from './env'
2
+
3
+const DEFAULT_INTERVAL = 100 / 60
4
+
5
+function noop() {
6
+}
7
+
8
+export const requestAnimationFrame = (() => {
9
+  /* istanbul ignore next */
10
+  if (!inBrowser) {
11
+    return noop
12
+  }
13
+  return window.requestAnimationFrame ||
14
+    /* istanbul ignore next */
15
+    window.webkitRequestAnimationFrame ||
16
+    /* istanbul ignore next */
17
+    window.mozRequestAnimationFrame ||
18
+    /* istanbul ignore next */
19
+    window.oRequestAnimationFrame ||
20
+    // if all else fails, use setTimeout
21
+    /* istanbul ignore next */
22
+    function (callback) {
23
+      return window.setTimeout(callback, (callback.interval || DEFAULT_INTERVAL) / 2) // make interval as precise as possible.
24
+    }
25
+})()
26
+
27
+export const cancelAnimationFrame = (() => {
28
+  /* istanbul ignore next */
29
+  if (!inBrowser) {
30
+    return noop
31
+  }
32
+  return window.cancelAnimationFrame ||
33
+    /* istanbul ignore next */
34
+    window.webkitCancelAnimationFrame ||
35
+    /* istanbul ignore next */
36
+    window.mozCancelAnimationFrame ||
37
+    /* istanbul ignore next */
38
+    window.oCancelAnimationFrame ||
39
+    /* istanbul ignore next */
40
+    function (id) {
41
+      window.clearTimeout(id)
42
+    }
43
+})()

+ 50 - 0
src/common/helpers/string-template.js

@@ -0,0 +1,50 @@
1
+import { isFunc } from './util'
2
+import { warn } from './debug'
3
+import Locale from '../../modules/locale'
4
+const stringRE = /\{\{((?:.|\n)+?)\}\}/g
5
+const quoteRe = /['"]/g
6
+const INVALID_INDEX = -1
7
+
8
+function format (string, config = '') {
9
+  return string.replace(stringRE, (match, group1, index) => {
10
+    const helpersArr = group1.split('|').slice(1).map(_ => _.trim())
11
+    const hasHelpers = helpersArr.length
12
+    let result = config
13
+
14
+    if (hasHelpers) {
15
+      helpersArr.forEach((helperString) => {
16
+        let { fnName, args } = resolveHelperFnString(helperString)
17
+        args.unshift(result)
18
+        /* istanbul ignore else */
19
+        if (isFunc(Locale.helpers[fnName])) {
20
+          result = Locale.helpers[fnName].apply(null, args)
21
+        } else {
22
+          warn(`A helper function named "${fnName}" is not registered, ` +
23
+               `please register it by Validator.addHelper()`)
24
+          result = ''
25
+        }
26
+      })
27
+    }
28
+
29
+    return result
30
+  })
31
+}
32
+
33
+function resolveHelperFnString (helperString) {
34
+  const leftBracketsIndex = helperString.indexOf('(')
35
+  const rightBracketsIndex = helperString.indexOf(')')
36
+  let fnName = ''
37
+  let args = []
38
+  /* istanbul ignore if */
39
+  if (leftBracketsIndex === INVALID_INDEX) {
40
+    args = []
41
+    fnName = helperString
42
+  } else if (leftBracketsIndex !== INVALID_INDEX && rightBracketsIndex !== INVALID_INDEX) {
43
+    const argsStr = helperString.slice(leftBracketsIndex + 1, rightBracketsIndex)
44
+    args = argsStr.split(',').map(_ => _.trim().replace(quoteRe, ''))
45
+    fnName = helperString.slice(0, leftBracketsIndex)
46
+  }
47
+  return { fnName, args }
48
+}
49
+
50
+export default format

+ 207 - 0
src/common/helpers/util.js

@@ -0,0 +1,207 @@
1
+import { camelize } from '../lang/string'
2
+
3
+function findIndex(ary, fn) {
4
+  if (ary.findIndex) {
5
+    return ary.findIndex(fn)
6
+  }
7
+  /* istanbul ignore next */
8
+  let index = -1
9
+  /* istanbul ignore next */
10
+  ary.some(function (item, i, ary) {
11
+    const ret = fn.call(this, item, i, ary)
12
+    if (ret) {
13
+      index = i
14
+      return ret
15
+    }
16
+  })
17
+  /* istanbul ignore next */
18
+  return index
19
+}
20
+
21
+function deepAssign(to, from) {
22
+  for (let key in from) {
23
+    if (!to[key] || typeof to[key] !== 'object') {
24
+      to[key] = from[key]
25
+    } else {
26
+      deepAssign(to[key], from[key])
27
+    }
28
+  }
29
+}
30
+
31
+function createAddAPI(baseObj) {
32
+  return function add(...args) {
33
+    if (typeof args[0] === 'string') {
34
+      args[0] = {
35
+        [args[0]]: args[1]
36
+      }
37
+    }
38
+    deepAssign(baseObj, args[0])
39
+  }
40
+}
41
+
42
+function judgeTypeFnCreator (type) {
43
+  const toString = Object.prototype.toString
44
+  return function isType (o) {
45
+    return toString.call(o) === `[object ${type}]`
46
+  }
47
+}
48
+
49
+const typesReset = {
50
+  _set(obj, key, value) {
51
+    obj[key] = value
52
+  },
53
+  string(obj, key) {
54
+    typesReset._set(obj, key, '')
55
+  },
56
+  number(obj, key) {
57
+    typesReset._set(obj, key, 0)
58
+  },
59
+  boolean(obj, key) {
60
+    typesReset._set(obj, key, false)
61
+  },
62
+  object(obj, key, value) {
63
+    if (Array.isArray(value)) {
64
+      typesReset._set(obj, key, [])
65
+    } else {
66
+      typesReset._set(obj, key, {})
67
+    }
68
+  }
69
+}
70
+function resetTypeValue(obj, key, defVal) {
71
+  if (defVal !== undefined) {
72
+    return typesReset._set(obj, key, defVal)
73
+  }
74
+  if (key) {
75
+    const value = obj[key]
76
+    const resetHandler = typesReset[typeof value]
77
+    resetHandler && resetHandler(obj, key, value)
78
+  } else {
79
+    Object.keys(obj).forEach((key) => {
80
+      resetTypeValue(obj, key)
81
+    })
82
+  }
83
+}
84
+
85
+function parallel(tasks, cb) {
86
+  let doneCount = 0
87
+  let results = []
88
+  const tasksLen = tasks.length
89
+  if (!tasksLen) {
90
+    return cb(results)
91
+  }
92
+  tasks.forEach((task, i) => {
93
+    task((ret) => {
94
+      doneCount += 1
95
+      results[i] = ret
96
+      if (doneCount === tasksLen) {
97
+        // all tasks done
98
+        cb(results)
99
+      }
100
+    })
101
+  })
102
+}
103
+
104
+function cb2PromiseWithResolve(cb) {
105
+  let promise
106
+  if (typeof window.Promise !== 'undefined') {
107
+    const _cb = cb
108
+    promise = new window.Promise((resolve) => {
109
+      cb = (data) => {
110
+        _cb && _cb(data)
111
+        resolve(data)
112
+      }
113
+    })
114
+    promise.resolve = cb
115
+  }
116
+  return promise
117
+}
118
+
119
+function debounce(func, wait, immediate, initValue) {
120
+  let timeout
121
+  let result = initValue
122
+
123
+  const later = function (context, args) {
124
+    timeout = null
125
+    if (args) {
126
+      result = func.apply(context, args)
127
+    }
128
+  }
129
+
130
+  const debounced = function (...args) {
131
+    if (timeout) {
132
+      clearTimeout(timeout)
133
+    }
134
+    if (immediate) {
135
+      const callNow = !timeout
136
+      timeout = setTimeout(later, wait)
137
+      if (callNow) {
138
+        result = func.apply(this, args)
139
+      }
140
+    } else {
141
+      timeout = setTimeout(() => {
142
+        later(this, args)
143
+      }, wait)
144
+    }
145
+
146
+    return result
147
+  }
148
+
149
+  debounced.cancel = function () {
150
+    clearTimeout(timeout)
151
+    timeout = null
152
+  }
153
+
154
+  return debounced
155
+}
156
+
157
+function processComponentName(Component, { prefix = '', firstUpperCase = false } = {}) {
158
+  const name = Component.name
159
+  const pureName = name.replace(/^cube-/i, '')
160
+  let camelizeName = `${camelize(`${prefix}${pureName}`)}`
161
+   /* istanbul ignore if */
162
+  if (firstUpperCase) {
163
+    camelizeName = camelizeName.charAt(0).toUpperCase() + camelizeName.slice(1)
164
+  }
165
+  return camelizeName
166
+}
167
+
168
+function parsePath (obj, path = '') {
169
+  const segments = path.split('.')
170
+  let result = obj
171
+  for (let i = 0; i < segments.length; i++) {
172
+    const key = segments[i]
173
+     /* istanbul ignore if */
174
+    if (isUndef(result[key])) {
175
+      result = ''
176
+      break
177
+    } else {
178
+      result = result[key]
179
+    }
180
+  }
181
+  return result
182
+}
183
+
184
+const isFunc = judgeTypeFnCreator('Function')
185
+const isUndef = judgeTypeFnCreator('Undefined')
186
+const isArray = judgeTypeFnCreator('Array')
187
+const isString = judgeTypeFnCreator('String')
188
+const isObject = judgeTypeFnCreator('Object')
189
+const isNumber = judgeTypeFnCreator('Number')
190
+
191
+export {
192
+  findIndex,
193
+  deepAssign,
194
+  createAddAPI,
195
+  resetTypeValue,
196
+  parallel,
197
+  cb2PromiseWithResolve,
198
+  debounce,
199
+  processComponentName,
200
+  parsePath,
201
+  isUndef,
202
+  isFunc,
203
+  isArray,
204
+  isString,
205
+  isObject,
206
+  isNumber
207
+}

+ 3 - 0
src/common/helpers/validator/index.js

@@ -0,0 +1,3 @@
1
+export { rules, addRule } from './rules'
2
+export { addMessage } from './messages'
3
+export { types, addType } from './types'

+ 44 - 0
src/common/helpers/validator/language/chinese.js

@@ -0,0 +1,44 @@
1
+import { toLocaleDateString } from '../../util'
2
+
3
+export default {
4
+  required: '此为必填项',
5
+  type: {
6
+    string: '请输入字符',
7
+    number: '请输入数字',
8
+    array: '数据类型应为数组',
9
+    date: '请选择有效日期',
10
+    email: '请输入有效邮箱',
11
+    tel: '请输入有效的手机号码',
12
+    url: '请输入有效网址'
13
+  },
14
+  min: {
15
+    string: /* istanbul ignore next */ (config) => `至少输入 ${config} 位字符`,
16
+    number: /* istanbul ignore next */ (config) => `不得小于 ${config}`,
17
+    array: /* istanbul ignore next */ (config) => `请选择至少 ${config} 项`,
18
+    date: /* istanbul ignore next */ (config) => `请选择 ${toLocaleDateString(config, 'zh')} 之后的时间`,
19
+    email: /* istanbul ignore next */ (config) => `至少输入 ${config} 位字符`,
20
+    tel: /* istanbul ignore next */ (config) => `至少输入 ${config} 位字符`,
21
+    url: /* istanbul ignore next */ (config) => `至少输入 ${config} 位字符`
22
+  },
23
+  max: {
24
+    string: /* istanbul ignore next */ (config) => `请勿超过 ${config} 位字符`,
25
+    number: /* istanbul ignore next */ (config) => `请勿大于 ${config}`,
26
+    array: /* istanbul ignore next */ (config) => `最多选择 ${config} 项`,
27
+    date: /* istanbul ignore next */ (config) => `请选择 ${toLocaleDateString(config, 'zh')} 之前的时间`,
28
+    email: /* istanbul ignore next */ (config) => `请勿超过 ${config} 位字符`,
29
+    tel: /* istanbul ignore next */ (config) => `请勿超过 ${config} 位字符`,
30
+    url: /* istanbul ignore next */ (config) => `请勿超过 ${config} 位字符`
31
+  },
32
+  len: {
33
+    string: /* istanbul ignore next */ (config) => `请输入 ${config} 位字符`,
34
+    number: /* istanbul ignore next */ (config) => `长度应等于 ${config}`,
35
+    array: /* istanbul ignore next */ (config) => `请选择 ${config} 项`,
36
+    date: /* istanbul ignore next */ (config) => `请选择 ${toLocaleDateString(config, 'zh')}`,
37
+    email: /* istanbul ignore next */ (config) => `请输入 ${config} 位字符`,
38
+    tel: /* istanbul ignore next */ (config) => `请输入 ${config} 位字符`,
39
+    url: /* istanbul ignore next */ (config) => `请输入 ${config} 位字符`
40
+  },
41
+  pattern: '格式错误',
42
+  custom: '未通过校验',
43
+  notWhitespace: '空白内容无效'
44
+}

+ 44 - 0
src/common/helpers/validator/language/english.js

@@ -0,0 +1,44 @@
1
+import { toLocaleDateString } from '../../util'
2
+
3
+export default {
4
+  required: 'Required.',
5
+  type: {
6
+    string: 'Please input characters.',
7
+    number: 'Please input numbers.',
8
+    array: 'The data type should be array.',
9
+    date: 'Please select a valid date.',
10
+    email: 'Please input a valid E-mail.',
11
+    tel: 'Please input a valid telphone number.',
12
+    url: 'Please input a valid web site.'
13
+  },
14
+  min: {
15
+    string: /* istanbul ignore next */ (config) => `Please input at least ${config} characters.`,
16
+    number: /* istanbul ignore next */ (config) => `The number could not smaller than ${config}.`,
17
+    array: /* istanbul ignore next */ (config) => `Please select at least ${config} items.`,
18
+    date: /* istanbul ignore next */ (config) => `Please select a date after ${toLocaleDateString(config, 'en')}`,
19
+    email: /* istanbul ignore next */ (config) => `Please input at least ${config} characters.`,
20
+    tel: /* istanbul ignore next */ (config) => `Please input at least ${config} characters.`,
21
+    url: /* istanbul ignore next */ (config) => `Please input at least ${config} characters.`
22
+  },
23
+  max: {
24
+    string: /* istanbul ignore next */ (config) => `Please input no more than ${config} characters.`,
25
+    number: /* istanbul ignore next */ (config) => `The number could not bigger than ${config}`,
26
+    array: /* istanbul ignore next */ (config) => `Please select no more than  ${config} items`,
27
+    date: /* istanbul ignore next */ (config) => `Please select a date before ${toLocaleDateString(config, 'en')}`,
28
+    email: /* istanbul ignore next */ (config) => `Please input no more than ${config} characters.`,
29
+    tel: /* istanbul ignore next */ (config) => `Please input no more than ${config} characters.`,
30
+    url: /* istanbul ignore next */ (config) => `Please input no more than ${config} characters.`
31
+  },
32
+  len: {
33
+    string: /* istanbul ignore next */ (config) => `Please input ${config} characters.`,
34
+    number: /* istanbul ignore next */ (config) => `The length should equal ${config}`,
35
+    array: /* istanbul ignore next */ (config) => `Please select ${config} items`,
36
+    date: /* istanbul ignore next */ (config) => `Please select ${toLocaleDateString(config, 'en')}`,
37
+    email: /* istanbul ignore next */ (config) => `Please input ${config} characters.`,
38
+    tel: /* istanbul ignore next */ (config) => `Please input ${config} characters.`,
39
+    url: /* istanbul ignore next */ (config) => `Please input ${config} characters.`
40
+  },
41
+  pattern: 'The input don"t match pattern.',
42
+  custom: 'Invalid.',
43
+  notWhitespace: 'Whitespace is invalid.'
44
+}

+ 18 - 0
src/common/helpers/validator/messages.js

@@ -0,0 +1,18 @@
1
+import { deepAssign } from '../util'
2
+
3
+function addMessage (...args) {
4
+  const NAMESPACE = 'validator'
5
+  const vueProto = this._base.prototype
6
+  const lang = vueProto.$cubeLang
7
+  const baseMessages = vueProto.$cubeMessages[lang][NAMESPACE]
8
+
9
+  if (typeof args[0] === 'string') {
10
+    args[0] = {
11
+      [args[0]]: args[1]
12
+    }
13
+  }
14
+
15
+  deepAssign(baseMessages, args[0])
16
+}
17
+
18
+export { addMessage }

+ 52 - 0
src/common/helpers/validator/rules.js

@@ -0,0 +1,52 @@
1
+import { createAddAPI } from '../util'
2
+import { types } from './types'
3
+
4
+const rules = {
5
+  required: (val, required, type) => {
6
+    type = type || (Array.isArray(val) ? 'array' : typeof val)
7
+    if (type === 'array' && Array.isArray(val)) {
8
+      return val.length > 0
9
+    }
10
+    return val !== '' && val !== undefined && val !== null
11
+  },
12
+  type: (val, type) => {
13
+    return !types[type] || types[type](val)
14
+  },
15
+  min: (val, min, type) => {
16
+    type = type || (typeof val)
17
+    if (type === 'number' || type === 'date') {
18
+      return Number(val) >= min
19
+    } else {
20
+      return val.length >= min
21
+    }
22
+  },
23
+  max: (val, max, type) => {
24
+    type = type || (typeof val)
25
+    if (type === 'number' || type === 'date') {
26
+      return Number(val) <= max
27
+    } else {
28
+      return val.length <= max
29
+    }
30
+  },
31
+  len: (val, len, type) => {
32
+    type = type || (typeof val)
33
+    let target = val
34
+    if (target.length === undefined) {
35
+      target = type === 'object' ? Object.keys(target) : String(target)
36
+    }
37
+    return target.length === len
38
+  },
39
+  notWhitespace: (val, config, type) => {
40
+    return !/^\s+$/.test(val)
41
+  },
42
+  pattern: (val, pattern, type) => {
43
+    return pattern.test(val)
44
+  },
45
+  custom: (val, custom, type) => {
46
+    return custom(val)
47
+  }
48
+}
49
+
50
+const addRule = createAddAPI(rules)
51
+
52
+export { rules, addRule }

+ 31 - 0
src/common/helpers/validator/types.js

@@ -0,0 +1,31 @@
1
+import { createAddAPI } from '../util'
2
+
3
+const DATE_RE = /^(1|2)\d{3}[.\-/]\d{1,2}[.\-/]\d{1,2}$/
4
+
5
+const types = {
6
+  string: (val) => {
7
+    return typeof val === 'string'
8
+  },
9
+  number: (val) => {
10
+    return !isNaN(Number(val))
11
+  },
12
+  array: (val) => {
13
+    return Array.isArray(val)
14
+  },
15
+  date: (val) => {
16
+    return !isNaN(Number(val)) || DATE_RE.test(val)
17
+  },
18
+  email: (val) => {
19
+    return typeof val === 'string' && /^[a-z0-9_-]+@[a-z0-9_-]+(\.[a-z0-9_-]+)+$/i.test(val)
20
+  },
21
+  tel: (val) => {
22
+    return typeof val === 'string' && /^(11|13|14|15|17|18|19)[0-9]{9}$/.test(val)
23
+  },
24
+  url: (val) => {
25
+    return typeof val === 'string' && /(https?|ftp|file):\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/.test(val)
26
+  }
27
+}
28
+
29
+const addType = createAddAPI(types)
30
+
31
+export { types, addType }

+ 208 - 0
src/common/icon/cube-icon.styl

@@ -0,0 +1,208 @@
1
+@font-face
2
+  font-family: "cube-icon"
3
+  src:
4
+    url("./cubeic.woff") format("woff"),
5
+    url("./cubeic.ttf") format("truetype")
6
+
7
+[class^="cubeic-"], [class*=" cubeic-"]
8
+  font-family: "cube-icon"!important
9
+  font-size: 100%
10
+  font-style: normal
11
+  -webkit-font-smoothing: antialiased
12
+  -webkit-text-stroke-width: 0.2px
13
+  -moz-osx-font-smoothing: grayscale
14
+
15
+.cubeic-eye-invisible::before
16
+  content: "\e624"
17
+
18
+.cubeic-eye-visible::before
19
+  content: "\e625"
20
+
21
+.cubeic-square-right::before
22
+  content: "\e67d"
23
+
24
+.cubeic-select::before
25
+  content: "\e609"
26
+
27
+.cubeic-pulldown::before
28
+  content: "\e603"
29
+
30
+.cubeic-pullup::before
31
+  content: "\e604"
32
+
33
+.cubeic-more::before
34
+  content: "\e607"
35
+
36
+.cubeic-back::before
37
+  content: "\e608"
38
+
39
+.cubeic-arrow::before
40
+  content: "\e60b"
41
+
42
+.cubeic-close::before
43
+  content: "\e60d"
44
+
45
+.cubeic-warn::before
46
+  content: "\e614"
47
+
48
+.cubeic-question::before
49
+  content: "\e616"
50
+
51
+.cubeic-right::before
52
+  content: "\e617"
53
+
54
+.cubeic-wrong::before
55
+  content: "\e618"
56
+
57
+.cubeic-info::before
58
+  content: "\e619"
59
+
60
+.cubeic-remove::before
61
+  content: "\e61a"
62
+
63
+.cubeic-add::before
64
+  content: "\e61c"
65
+
66
+.cubeic-share::before
67
+  content: "\e631"
68
+
69
+.cubeic-no-wifi::before
70
+  content: "\e632"
71
+
72
+.cubeic-smile::before
73
+  content: "\e634"
74
+
75
+.cubeic-sad::before
76
+  content: "\e636"
77
+
78
+.cubeic-email::before
79
+  content: "\e637"
80
+
81
+.cubeic-game::before
82
+  content: "\e638"
83
+
84
+.cubeic-wifi::before
85
+  content: "\e639"
86
+
87
+.cubeic-hot::before
88
+  content: "\e63b"
89
+
90
+.cubeic-notification::before
91
+  content: "\e63d"
92
+
93
+.cubeic-delete::before
94
+  content: "\e63e"
95
+
96
+.cubeic-vip::before
97
+  content: "\e63f"
98
+
99
+.cubeic-mute::before
100
+  content: "\e640"
101
+
102
+.cubeic-danger::before
103
+  content: "\e641"
104
+
105
+.cubeic-volume::before
106
+  content: "\e642"
107
+
108
+.cubeic-bad::before
109
+  content: "\e643"
110
+
111
+.cubeic-mobile-phone::before
112
+  content: "\e644"
113
+
114
+.cubeic-aim::before
115
+  content: "\e645"
116
+
117
+.cubeic-navigation::before
118
+  content: "\e64d"
119
+
120
+.cubeic-safe-pay::before
121
+  content: "\e64e"
122
+
123
+.cubeic-tag::before
124
+  content: "\e64f"
125
+
126
+.cubeic-lock::before
127
+  content: "\e651"
128
+
129
+.cubeic-unlock::before
130
+  content: "\e652"
131
+
132
+.cubeic-edit::before
133
+  content: "\e653"
134
+
135
+.cubeic-scan::before
136
+  content: "\e654"
137
+
138
+.cubeic-qr-code::before
139
+  content: "\e655"
140
+
141
+.cubeic-calendar::before
142
+  content: "\e659"
143
+
144
+.cubeic-time::before
145
+  content: "\e65f"
146
+
147
+.cubeic-red-packet::before
148
+  content: "\e664"
149
+
150
+.cubeic-star::before
151
+  content: "\e668"
152
+
153
+.cubeic-setting::before
154
+  content: "\e669"
155
+
156
+.cubeic-home::before
157
+  content: "\e66d"
158
+
159
+.cubeic-credit-card::before
160
+  content: "\e66e"
161
+
162
+.cubeic-mall::before
163
+  content: "\e670"
164
+
165
+.cubeic-microphone::before
166
+  content: "\e673"
167
+
168
+.cubeic-search::before
169
+  content: "\e674"
170
+
171
+.cubeic-good::before
172
+  content: "\e675"
173
+
174
+.cubeic-alert::before
175
+  content: "\e676"
176
+
177
+.cubeic-picture::before
178
+  content: "\e677"
179
+
180
+.cubeic-message::before
181
+  content: "\e678"
182
+
183
+.cubeic-phone::before
184
+  content: "\e67a"
185
+
186
+.cubeic-location::before
187
+  content: "\e67b"
188
+
189
+.cubeic-like::before
190
+  content: "\e67c"
191
+
192
+.cubeic-camera::before
193
+  content: "\e67e"
194
+
195
+.cubeic-person::before
196
+  content: "\e67f"
197
+
198
+.cubeic-round-border::before
199
+  content: "\e683"
200
+
201
+.cubeic-important::before
202
+  content: "\e68b"
203
+
204
+.cubeic-ok::before
205
+  content: "\e68c"
206
+
207
+.cubeic-square-border::before
208
+  content: "\e990"

二進制
src/common/icon/cubeic.ttf


二進制
src/common/icon/cubeic.woff


+ 23 - 0
src/common/js/date.js

@@ -0,0 +1,23 @@
1
+export function formatDate (date, fmt) {
2
+    if (/(y+)/.test(fmt)) {
3
+    fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
4
+    }
5
+    let o = {
6
+    'M+': date.getMonth() + 1,
7
+    'd+': date.getDate(),
8
+    'h+': date.getHours(),
9
+    'm+': date.getMinutes(),
10
+    's+': date.getSeconds()
11
+    };
12
+    for (let k in o) {
13
+    if (new RegExp(`(${k})`).test(fmt)) {
14
+    let str = o[k] + '';
15
+    fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str));
16
+    }
17
+    }
18
+    return fmt;
19
+    };
20
+    
21
+    function padLeftZero (str) {
22
+    return ('00' + str).substr(str.length);
23
+    };

+ 61 - 0
src/common/js/util.js

@@ -0,0 +1,61 @@
1
+var SIGN_REGEXP = /([yMdhsm])(\1*)/g;
2
+var DEFAULT_PATTERN = 'yyyy-MM-dd';
3
+function padding(s, len) {
4
+    var len = len - (s + '').length;
5
+    for (var i = 0; i < len; i++) { s = '0' + s; }
6
+    return s;
7
+};
8
+
9
+export default {
10
+    getQueryStringByName: function (name) {
11
+        var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
12
+        var r = window.location.search.substr(1).match(reg);
13
+        var context = "";
14
+        if (r != null)
15
+            context = r[2];
16
+        reg = null;
17
+        r = null;
18
+        return context == null || context == "" || context == "undefined" ? "" : context;
19
+    },
20
+    formatDate: {
21
+
22
+
23
+        format: function (date, pattern) {
24
+            pattern = pattern || DEFAULT_PATTERN;
25
+            return pattern.replace(SIGN_REGEXP, function ($0) {
26
+                switch ($0.charAt(0)) {
27
+                    case 'y': return padding(date.getFullYear(), $0.length);
28
+                    case 'M': return padding(date.getMonth() + 1, $0.length);
29
+                    case 'd': return padding(date.getDate(), $0.length);
30
+                    case 'w': return date.getDay() + 1;
31
+                    case 'h': return padding(date.getHours(), $0.length);
32
+                    case 'm': return padding(date.getMinutes(), $0.length);
33
+                    case 's': return padding(date.getSeconds(), $0.length);
34
+                }
35
+            });
36
+        },
37
+        parse: function (dateString, pattern) {
38
+            var matchs1 = pattern.match(SIGN_REGEXP);
39
+            var matchs2 = dateString.match(/(\d)+/g);
40
+            if (matchs1.length == matchs2.length) {
41
+                var _date = new Date(1970, 0, 1);
42
+                for (var i = 0; i < matchs1.length; i++) {
43
+                    var _int = parseInt(matchs2[i]);
44
+                    var sign = matchs1[i];
45
+                    switch (sign.charAt(0)) {
46
+                        case 'y': _date.setFullYear(_int); break;
47
+                        case 'M': _date.setMonth(_int - 1); break;
48
+                        case 'd': _date.setDate(_int); break;
49
+                        case 'h': _date.setHours(_int); break;
50
+                        case 'm': _date.setMinutes(_int); break;
51
+                        case 's': _date.setSeconds(_int); break;
52
+                    }
53
+                }
54
+                return _date;
55
+            }
56
+            return null;
57
+        }
58
+
59
+    }
60
+
61
+};

+ 109 - 0
src/common/lang/date.js

@@ -0,0 +1,109 @@
1
+const DAY_TIMESTAMP = 60 * 60 * 24 * 1000
2
+const HOUR_TIMESTAMP = 60 * 60 * 1000
3
+const MINUTE_TIMESTAMP = 60 * 1000
4
+
5
+function formatType(type, format, value, regExpAttributes) {
6
+  const regExpMap = {
7
+    year: '(Y+)',
8
+    month: '(M+)',
9
+    date: '(D+)',
10
+    hour: '(h+)',
11
+    minute: '(m+)',
12
+    second: '(s+)',
13
+    quarter: '(q+)',
14
+    millisecond: '(S)'
15
+  }
16
+
17
+  if (new RegExp(regExpMap[type], regExpAttributes).test(format)) {
18
+    const replaceStr = type === 'year'
19
+                       ? value.toString().substr(4 - RegExp.$1.length)
20
+                       : (RegExp.$1.length === 1) ? value : pad(value)
21
+    format = format.replace(RegExp.$1, replaceStr)
22
+  }
23
+
24
+  return format
25
+}
26
+
27
+function pad(value) {
28
+  return ('00' + value).substr(('' + value).length)
29
+}
30
+
31
+function formatDate(date, format) {
32
+  const map = {
33
+    year: {
34
+      value: date.getFullYear(),
35
+      regExpAttributes: 'i'
36
+    },
37
+    month: {
38
+      value: date.getMonth() + 1
39
+    },
40
+    date: {
41
+      value: date.getDate(),
42
+      regExpAttributes: 'i'
43
+    },
44
+    hour: {
45
+      value: date.getHours(),
46
+      regExpAttributes: 'i'
47
+    },
48
+    minute: {
49
+      value: date.getMinutes()
50
+    },
51
+    second: {
52
+      value: date.getSeconds()
53
+    },
54
+    quarter: {
55
+      value: Math.floor((date.getMonth() + 3) / 3),
56
+      regExpAttributes: 'i'
57
+    },
58
+    millisecond: {
59
+      value: date.getMilliseconds()
60
+    }
61
+  }
62
+
63
+  for (const key in map) {
64
+    format = formatType(key, format, map[key].value, map[key].regExpAttributes)
65
+  }
66
+
67
+  return format
68
+}
69
+
70
+function getZeroStamp(date) {
71
+  const year = date.getFullYear()
72
+  const month = date.getMonth() + 1
73
+  const day = date.getDate()
74
+  return +new Date(year + '/' + month + '/' + day + ' 00:00:00')
75
+}
76
+
77
+function getDayDiff(date1, date2) {
78
+  return Math.floor((getZeroStamp(date1) - getZeroStamp(date2)) / DAY_TIMESTAMP)
79
+}
80
+
81
+function getNow() {
82
+  return window.performance && window.performance.now ? (window.performance.now() + window.performance.timing.navigationStart) : +new Date()
83
+}
84
+
85
+function computeNatureMaxDay(month, year) {
86
+  let natureMaxDay = 30
87
+  if ([1, 3, 5, 7, 8, 10, 12].indexOf(month) > -1) {
88
+    natureMaxDay = 31
89
+  } else {
90
+    if (month === 2) {
91
+      natureMaxDay = !year || (!(year % 400) || (!(year % 4) && year % 100)) ? 29 : 28
92
+    }
93
+  }
94
+
95
+  return natureMaxDay
96
+}
97
+
98
+export {
99
+  DAY_TIMESTAMP,
100
+  HOUR_TIMESTAMP,
101
+  MINUTE_TIMESTAMP,
102
+  pad,
103
+  formatType,
104
+  formatDate,
105
+  getZeroStamp,
106
+  getDayDiff,
107
+  getNow,
108
+  computeNatureMaxDay
109
+}

+ 12 - 0
src/common/lang/string.js

@@ -0,0 +1,12 @@
1
+const camelizeRE = /-(\w)/g
2
+export function camelize (str) {
3
+  str = String(str)
4
+  return str.replace(camelizeRE, function (m, c) {
5
+    return c ? c.toUpperCase() : ''
6
+  })
7
+}
8
+
9
+export function kebab (str) {
10
+  str = String(str)
11
+  return str.replace(/([A-Z])/g, '-$1').toLowerCase()
12
+}

+ 51 - 0
src/common/locale/index.js

@@ -0,0 +1,51 @@
1
+import defaultMessages from '../../locale/lang/zh-CN'
2
+import { warn } from '../helpers/debug'
3
+import { isUndef, isNumber } from '../helpers/util'
4
+import { formatDate } from '../lang/date'
5
+
6
+let proto
7
+
8
+const DEFAULT_LANG = 'zh-CN'
9
+
10
+const locale = {
11
+  name: 'locale',
12
+  install (Vue) {
13
+    if (locale.installed) return
14
+    proto = Vue.prototype
15
+    Vue.util.defineReactive(proto, '$cubeLang', DEFAULT_LANG)
16
+    proto['$cubeMessages'] = { [DEFAULT_LANG]: defaultMessages }
17
+    locale.installed = true
18
+  },
19
+  use (lang, messages) {
20
+    proto['$cubeLang'] = lang
21
+    const cubeMessages = proto['$cubeMessages']
22
+    // if messages have never been stored in vue.prototye
23
+    if (!(lang in cubeMessages)) {
24
+      cubeMessages[[lang]] = messages
25
+    }
26
+  },
27
+  helpers: {
28
+    toLocaleDateString (config, formatRules) {
29
+      /**
30
+       * Safari don't support formatRules like
31
+       * 'yyyy-MM-dd hh:mm:ss', so transfer it to 'yyyy/MM/dd hh:mm:ss'
32
+       */
33
+      const compatibleConfig = isNumber(config) ? config : config.replace(/-/g, '/')
34
+      const date = new Date(compatibleConfig)
35
+      /* istanbul ignore if */
36
+      if (isUndef(formatRules)) return date.toDateString()
37
+      return formatDate(date, formatRules)
38
+    }
39
+  },
40
+  addHelper (fnName, fn) {
41
+    // check existed helper fn
42
+    /* istanbul ignore if */
43
+    if (fnName in locale.helpers) {
44
+      warn(`${fnName} has already been registered on helpers function, please change another name`)
45
+      return
46
+    }
47
+    locale.helpers[fnName] = fn
48
+  }
49
+}
50
+
51
+export default locale

+ 44 - 0
src/common/mixins/basic-picker.js

@@ -0,0 +1,44 @@
1
+const DEFAULT_KEYS = {
2
+  value: 'value',
3
+  text: 'text'
4
+}
5
+
6
+export default {
7
+  props: {
8
+    data: {
9
+      type: Array,
10
+      default() {
11
+        return []
12
+      }
13
+    },
14
+    selectedIndex: {
15
+      type: Array,
16
+      default() {
17
+        return []
18
+      }
19
+    },
20
+    alias: {
21
+      type: Object,
22
+      default() {
23
+        return {}
24
+      }
25
+    }
26
+  },
27
+  computed: {
28
+    valueKey() {
29
+      return this.alias.value || DEFAULT_KEYS.value
30
+    },
31
+    textKey() {
32
+      return this.alias.text || DEFAULT_KEYS.text
33
+    },
34
+    merge() {
35
+      return [this.data, this.selectedIndex]
36
+    }
37
+  },
38
+  watch: {
39
+    // Merge the watch handlers of data and selectedIndex into one.
40
+    merge(newVal) {
41
+      this.setData(newVal[0], newVal[1])
42
+    }
43
+  }
44
+}

+ 22 - 0
src/common/mixins/deprecated.js

@@ -0,0 +1,22 @@
1
+import { tip } from '../../common/helpers/debug'
2
+import { kebab } from '../../common/lang/string'
3
+
4
+export default {
5
+  methods: {
6
+    _checkDeprecated() {
7
+      const props = this.$options.props
8
+      const componentName = this.$options.name
9
+
10
+      Object.entries(props).forEach(([key, prop]) => {
11
+        const deprecated = prop.deprecated
12
+
13
+        if (deprecated && this[key] !== undefined) {
14
+          tip(`The property "${kebab(key)}" is deprecated, please use the recommended property "${deprecated.replacedBy}" to replace it. Details could be found in https://didi.github.io/cube-ui/#/en-US/docs/${componentName.substr(5)}#cube-Propsconfiguration-anchor`, componentName)
15
+        }
16
+      })
17
+    }
18
+  },
19
+  mounted() {
20
+    this._checkDeprecated()
21
+  }
22
+}

+ 25 - 0
src/common/mixins/locale.js

@@ -0,0 +1,25 @@
1
+import locale from '../locale'
2
+import { parsePath, isUndef } from '../helpers/util'
3
+import { warn } from '../helpers/debug'
4
+
5
+const TRANSLATION_ABSENT = `Translation is not registered correctly, ` +
6
+                           `you can call Locale.use() to install it.`
7
+
8
+export default {
9
+  computed: {
10
+    $t () {
11
+      const lang = this.$cubeLang
12
+      const messages = this.$cubeMessages[lang]
13
+      if (isUndef(messages)) {
14
+        warn(TRANSLATION_ABSENT)
15
+        return ''
16
+      }
17
+      return (path) => {
18
+        return parsePath(messages, path)
19
+      }
20
+    }
21
+  },
22
+  beforeCreate() {
23
+    locale.install(this.$root.constructor)
24
+  }
25
+}

+ 34 - 0
src/common/mixins/picker.js

@@ -0,0 +1,34 @@
1
+export default {
2
+  props: {
3
+    title: {
4
+      type: String
5
+    },
6
+    subtitle: {
7
+      type: String
8
+    },
9
+    cancelTxt: {
10
+      type: String,
11
+      default: ''
12
+    },
13
+    confirmTxt: {
14
+      type: String,
15
+      default: ''
16
+    },
17
+    swipeTime: {
18
+      type: Number,
19
+      default: 2500
20
+    },
21
+    maskClosable: {
22
+      type: Boolean,
23
+      default: true
24
+    }
25
+  },
26
+  computed: {
27
+    _cancelTxt () {
28
+      return this.cancelTxt || this.$t('cancel')
29
+    },
30
+    _confirmTxt () {
31
+      return this.confirmTxt || this.$t('ok')
32
+    }
33
+  }
34
+}

+ 12 - 0
src/common/mixins/popup.js

@@ -0,0 +1,12 @@
1
+export default {
2
+  props: {
3
+    zIndex: {
4
+      type: Number,
5
+      default: 100
6
+    },
7
+    maskClosable: {
8
+      type: Boolean,
9
+      default: false
10
+    }
11
+  }
12
+}

+ 11 - 0
src/common/mixins/scroll.js

@@ -0,0 +1,11 @@
1
+export default {
2
+  props: {
3
+    // the options of BetterScroll
4
+    options: {
5
+      type: Object,
6
+      default() {
7
+        return {}
8
+      }
9
+    }
10
+  }
11
+}

+ 45 - 0
src/common/mixins/visibility.js

@@ -0,0 +1,45 @@
1
+const EVENT_TOGGLE = 'toggle'
2
+
3
+export default {
4
+  model: {
5
+    prop: 'visible',
6
+    event: EVENT_TOGGLE
7
+  },
8
+  props: {
9
+    visible: {
10
+      type: Boolean,
11
+      default: false
12
+    }
13
+  },
14
+  data() {
15
+    return {
16
+      // If use the prop visible directly, the toggle will failed when user haven't set v-model as a reactive property.
17
+      // So we use the data isVisible instead.
18
+      isVisible: false
19
+    }
20
+  },
21
+  watch: {
22
+    isVisible(newVal) {
23
+      this.$emit(EVENT_TOGGLE, newVal)
24
+    }
25
+  },
26
+  mounted() {
27
+    this.$watch('visible', (newVal, oldVal) => {
28
+      if (newVal) {
29
+        this.show()
30
+      } else if (oldVal && !this._createAPI_reuse) {
31
+        this.hide()
32
+      }
33
+    }, {
34
+      immediate: true
35
+    })
36
+  },
37
+  methods: {
38
+    show() {
39
+      this.isVisible = true
40
+    },
41
+    hide() {
42
+      this.isVisible = false
43
+    }
44
+  }
45
+}

+ 93 - 0
src/common/stylus/base.styl

@@ -0,0 +1,93 @@
1
+@require "./variable.styl"
2
+@require "./mixin.styl"
3
+
4
+body, html
5
+  line-height: 1
6
+  font-family: 'PingFang SC', 'STHeitiSC-Light', 'Helvetica-Light', arial, sans-serif, 'Droid Sans Fallback'
7
+  user-select: none
8
+  -webkit-tap-highlight-color: transparent
9
+
10
+.clear-fix
11
+  &::after
12
+    content: ""
13
+    display: table
14
+    clear: both
15
+
16
+.border-top-1px, .border-right-1px, .border-bottom-1px, .border-left-1px
17
+  position: relative
18
+  &::before, &::after
19
+    content: ""
20
+    display: block
21
+    position: absolute
22
+    transform-origin: 0 0
23
+
24
+.border-top-1px
25
+  &::before
26
+    border-top: 1PX solid $color-row-line
27
+    left: 0
28
+    top: 0
29
+    width: 100%
30
+    transform-origin: 0 top
31
+
32
+.border-right-1px
33
+  &::after
34
+    border-right: 1PX solid $color-col-line
35
+    top: 0
36
+    right: 0
37
+    height: 100%
38
+    transform-origin: right 0
39
+
40
+.border-bottom-1px
41
+  &::after
42
+    border-bottom: 1PX solid $color-row-line
43
+    left: 0
44
+    bottom: 0
45
+    width: 100%
46
+    transform-origin: 0 bottom
47
+
48
+.border-left-1px
49
+  &::before
50
+    border-left: 1PX solid $color-col-line
51
+    top: 0
52
+    left: 0
53
+    height: 100%
54
+    transform-origin: left 0
55
+
56
+.cube-safe-area-pb
57
+  safe-area-mixin(padding-bottom, bottom, true)
58
+
59
+@media (min-resolution: 2dppx)
60
+  .border-top-1px
61
+    &::before
62
+      width: 200%
63
+      transform: scale(.5)
64
+  .border-right-1px
65
+    &::after
66
+      height: 200%
67
+      transform: scale(.5)
68
+  .border-bottom-1px
69
+    &::after
70
+      width: 200%
71
+      transform: scale(.5)
72
+  .border-left-1px
73
+    &::before
74
+      height: 200%
75
+      transform: scale(.5)
76
+
77
+@media (min-resolution: 3dppx)
78
+  .border-top-1px
79
+    &::before
80
+      width: 300%
81
+      transform: scale(.333)
82
+  .border-right-1px
83
+    &::after
84
+      height: 300%
85
+      transform: scale(.333)
86
+  .border-bottom-1px
87
+    &::after
88
+      width: 300%
89
+      transform: scale(.333)
90
+  .border-left-1px
91
+    &::before
92
+      height: 300%
93
+      transform: scale(.333)

+ 5 - 0
src/common/stylus/index.styl

@@ -0,0 +1,5 @@
1
+@require "./variable.styl"
2
+@require "./mixin.styl"
3
+@require "./reset.styl"
4
+@require "./base.styl"
5
+@require "../icon/cube-icon.styl"

+ 63 - 0
src/common/stylus/mixin.styl

@@ -0,0 +1,63 @@
1
+border-1px($color = #ccc, $radius = 2PX, $style = solid)
2
+  position: relative
3
+  &::after
4
+    content: ""
5
+    pointer-events: none
6
+    display: block
7
+    position: absolute
8
+    left: 0
9
+    top: 0
10
+    transform-origin: 0 0
11
+    border: 1PX $style $color
12
+    border-radius: $radius
13
+    box-sizing border-box
14
+    width 100%
15
+    height 100%
16
+    @media (min-resolution: 2dppx)
17
+      width: 200%
18
+      height: 200%
19
+      border-radius: $radius * 2
20
+      transform: scale(.5)
21
+    @media (min-resolution: 3dppx)
22
+      width: 300%
23
+      height: 300%
24
+      border-radius: $radius * 3
25
+      transform: scale(.333)
26
+
27
+border-none()
28
+  &::before
29
+    display: none
30
+  &::after
31
+    display: none
32
+
33
+flex-fix()
34
+  flex: 1
35
+  flex-basis: 0.000000001px
36
+  width: 1%
37
+
38
+touch-active(type = orange)
39
+  if (type == orange)
40
+    &:active
41
+      color: #fcc1a6
42
+      background-color: rgba(250, 143, 84, .04)
43
+  else
44
+    &:active
45
+      color: #c6c6c6
46
+      background-color: rgba(0, 0, 0, .04)
47
+
48
+hide-scrollbar()
49
+  &::-webkit-scrollbar
50
+    width: 0
51
+    height: 0
52
+
53
+bg-image($url, $ext = ".png")
54
+  background-image: url($url + "@2x" + $ext)
55
+  @media (min-resolution: 3dppx)
56
+    background-image: url($url + "@3x" + $ext)
57
+
58
+functions = constant env
59
+safe-area-fn(fn, position)
60
+  s("%s(safe-area-inset-%s)", fn, position)
61
+safe-area-mixin(property, position, important = false)
62
+  for fn in functions
63
+    {property} safe-area-fn(fn, position) important == true ? !important : unquote("")

+ 55 - 0
src/common/stylus/reset.styl

@@ -0,0 +1,55 @@
1
+/**
2
+ * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)
3
+ * http://cssreset.com
4
+ */
5
+html, body, div, span, applet, object, iframe,
6
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
7
+a, abbr, acronym, address, big, cite, code,
8
+del, dfn, em, img, ins, kbd, q, s, samp,
9
+small, strike, strong, sub, sup, tt, var,
10
+b, u, i, center,
11
+dl, dt, dd, ol, ul, li,
12
+fieldset, form, label, legend,
13
+table, caption, tbody, tfoot, thead, tr, th, td,
14
+article, aside, canvas, details, embed,
15
+figure, figcaption, footer, header,
16
+menu, nav, output, ruby, section, summary,
17
+time, mark, audio, video, input
18
+  margin: 0
19
+  padding: 0
20
+  border: 0
21
+  font-size: 100%
22
+  font-weight: normal
23
+  vertical-align: baseline
24
+
25
+/* HTML5 display-role reset for older browsers */
26
+article, aside, details, figcaption, figure,
27
+footer, header, menu, nav, section
28
+  display: block
29
+
30
+body
31
+  line-height: 1
32
+
33
+blockquote, q
34
+  quotes: none
35
+
36
+blockquote:before, blockquote:after,
37
+q:before, q:after
38
+  content: none
39
+
40
+table
41
+  border-collapse: collapse
42
+  border-spacing: 0
43
+
44
+/* custom */
45
+
46
+a
47
+  color: #7e8c8d
48
+  text-decoration: none
49
+
50
+li
51
+  list-style: none
52
+
53
+body
54
+  -webkit-text-size-adjust: none
55
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0)

+ 224 - 0
src/common/stylus/theme/default.styl

@@ -0,0 +1,224 @@
1
+@require "../var/color.styl"
2
+
3
+// action-sheet
4
+$action-sheet-color := $color-grey
5
+$action-sheet-active-color := $color-orange
6
+$action-sheet-bgc := $color-white
7
+$action-sheet-active-bgc := $color-light-grey-opacity
8
+$action-sheet-title-color := $color-dark-grey
9
+$action-sheet-space-bgc := $color-mask-bg
10
+/// picker style
11
+$action-sheet-picker-cancel-color := $color-light-grey
12
+$action-sheet-picker-cancel-active-color := $color-light-grey-s
13
+
14
+// bubble
15
+
16
+// button
17
+$btn-color := $color-white
18
+$btn-bgc := $color-regular-blue
19
+$btn-active-bgc := $color-blue
20
+/// primary
21
+$btn-primary-color := $color-white
22
+$btn-primary-bgc := $color-orange
23
+$btn-primary-active-bgc := $color-dark-orange
24
+/// light
25
+$btn-light-color := $color-grey
26
+$btn-light-bgc := $color-light-grey-sss
27
+$btn-light-active-bgc := $color-active-grey
28
+/// outline
29
+$btn-outline-color := $color-grey
30
+$btn-outline-bgc := transparent
31
+$btn-outline-bdc := $color-grey
32
+$btn-outline-active-bgc := $color-grey-opacity
33
+$btn-outline-active-bdc := $color-grey
34
+/// outline-primary
35
+$btn-outline-primary-color := $color-orange
36
+$btn-outline-primary-bgc := transparent
37
+$btn-outline-primary-bdc := $color-orange
38
+$btn-outline-primary-active-bgc := $color-orange-opacity
39
+$btn-outline-primary-active-bdc := $color-dark-orange
40
+/// disabled
41
+$btn-disabled-color := $color-white
42
+$btn-disabled-bgc := $color-light-grey-s
43
+$btn-disabled-bdc := $color-light-grey-s
44
+
45
+// toolbar
46
+$toolbar-bgc := $color-light-grey-sss
47
+$toolbar-active-bgc := $color-active-grey
48
+
49
+// checkbox
50
+$checkbox-color := $color-grey
51
+$checkbox-icon-color := $color-light-grey-s
52
+/// checked
53
+$checkbox-checked-icon-color := $color-orange
54
+$checkbox-checked-icon-bgc := $color-white
55
+/// disabled
56
+$checkbox-disabled-icon-color := $color-light-grey-ss
57
+$checkbox-disabled-icon-bgc := $color-light-grey-ss
58
+// checkbox hollow
59
+$checkbox-hollow-checked-icon-color := $color-orange
60
+$checkbox-hollow-disabled-icon-color := $color-light-grey-ss
61
+// checkbox-group
62
+$checkbox-group-bgc := $color-white
63
+$checkbox-group-horizontal-bdc := $color-light-grey-s
64
+
65
+// radio
66
+$radio-group-bgc := $color-white
67
+$radio-group-horizontal-bdc := $color-light-grey-s
68
+$radio-color := $color-grey
69
+$radio-icon-color := $color-light-grey-s
70
+/// selected
71
+$radio-selected-icon-color := $color-white
72
+$radio-selected-icon-bgc := $color-orange
73
+/// disabled
74
+$radio-disabled-icon-bgc := $color-light-grey-ss
75
+// radio hollow
76
+$radio-hollow-selected-icon-color := $color-orange
77
+$radio-hollow-disabled-icon-color := $color-light-grey-ss
78
+
79
+//checker
80
+$checker-item-color := $color-grey
81
+$checker-item-bdc := $color-light-grey-sss
82
+$checker-item-bgc := $color-white
83
+$checker-item-active-color := $color-orange
84
+$checker-item-active-bdc := $color-orange
85
+$checker-item-active-bgc := $color-light-orange-opacity
86
+
87
+// dialog
88
+$dialog-color := $color-grey
89
+$dialog-bgc := $color-white
90
+$dialog-icon-color := $color-regular-blue
91
+$dialog-icon-bgc := $color-background
92
+$dialog-title-color := $color-dark-grey
93
+$dialog-close-color := $color-light-grey
94
+$dialog-btn-color := $color-light-grey
95
+$dialog-btn-bgc := $color-white
96
+$dialog-btn-active-bgc := $color-light-grey-opacity
97
+$dialog-btn-highlight-color := $color-orange
98
+$dialog-btn-highlight-active-bgc := $color-light-orange-opacity
99
+$dialog-btn-disabled-color := $color-light-grey
100
+$dialog-btn-disabled-active-bgc := transparent
101
+$dialog-btns-split-color := $color-row-line
102
+
103
+// index-list
104
+$index-list-bgc := $color-white
105
+$index-list-title-color := $color-dark-grey
106
+$index-list-anchor-color := $color-light-grey
107
+$index-list-anchor-bgc := #f7f7f7
108
+$index-list-item-color := $color-dark-grey
109
+$index-list-item-active-bgc := $color-light-grey-opacity
110
+$index-list-nav-color := $color-grey
111
+$index-list-nav-active-color := $color-orange
112
+
113
+// loading
114
+
115
+// picker
116
+$picker-bgc := $color-white
117
+$picker-title-color := $color-dark-grey
118
+$picker-subtitle-color := $color-light-grey
119
+$picker-confirm-btn-color := $color-orange
120
+$picker-confirm-btn-active-color := $color-light-orange
121
+$picker-cancel-btn-color := $color-light-grey
122
+$picker-cancel-btn-active-color := $color-light-grey-s
123
+$picker-item-color := $color-dark-grey
124
+
125
+// popup
126
+$popup-mask-bgc := rgb(37, 38, 45)
127
+$popup-mask-opacity := .4
128
+
129
+//scroll
130
+
131
+// slide
132
+$slide-dot-bgc := $color-light-grey-s
133
+$slide-dot-active-bgc := $color-orange
134
+
135
+// time-picker
136
+
137
+// tip
138
+$tip-color := $color-white
139
+$tip-bgc := $color-dark-grey-opacity
140
+
141
+// toast
142
+$toast-color := $color-light-grey-s
143
+$toast-bgc := rgba(37, 38, 45, 0.9)
144
+
145
+// upload
146
+$upload-btn-color := $color-grey
147
+$upload-btn-bgc := $color-white
148
+$upload-btn-active-bgc := $color-light-grey-opacity
149
+$upload-btn-box-shadow := 0 0 6px 2px $color-grey-opacity
150
+$upload-btn-border-color := #e5e5e5
151
+$upload-file-bgc := $color-white
152
+$upload-file-remove-color := rgba(0, 0, 0, .8)
153
+$upload-file-remove-bgc := $color-white
154
+$upload-file-state-bgc := $color-mask-bg
155
+$upload-file-success-color := $color-orange
156
+$upload-file-error-color := #f43530
157
+$upload-file-status-bgc := $color-white
158
+$upload-file-progress-color := $color-white
159
+
160
+// switch
161
+$switch-on-bgc := $color-orange
162
+$switch-off-bgc := $color-white
163
+$switch-off-border-color := #e4e4e4
164
+
165
+// input
166
+$input-color := $color-grey
167
+$input-bgc := $color-white
168
+$input-border-color := $color-row-line
169
+$input-focus-border-color := $color-orange
170
+$input-placeholder-color := $color-light-grey-s
171
+$input-clear-icon-color := $color-light-grey
172
+
173
+//textarea
174
+$textarea-color := $color-grey
175
+$textarea-bgc := $color-white
176
+$textarea-border-color := $color-row-line
177
+$textarea-focus-border-color := $color-orange
178
+$textarea-outline-color := $color-orange
179
+$textarea-placeholder-color := $color-light-grey-s
180
+$textarea-indicator-color := $color-light-grey-s
181
+
182
+// validator
183
+$validator-msg-def-color := #e64340
184
+
185
+// select
186
+$select-color := $color-grey
187
+$select-bgc := $color-white
188
+$select-disabled-color := #b8b8b8
189
+$select-disabled-bgc := $color-light-grey-opacity
190
+$select-border-color := $color-light-grey-s
191
+$select-border-active-color := $color-orange
192
+$select-icon-color := $color-light-grey
193
+$select-placeholder-color := $color-light-grey-s
194
+
195
+// swipe
196
+$swipe-btn-color := $color-white
197
+
198
+// form
199
+$form-color := $color-grey
200
+$form-bgc := $color-white
201
+$form-invalid-color := #e64340
202
+$form-group-legend-color := $color-light-grey
203
+$form-group-legend-bgc := $color-background
204
+$form-label-required-color := #e64340
205
+
206
+// drawer
207
+$drawer-color := $color-dark-grey
208
+$drawer-title-bdc := $color-light-grey-ss
209
+$drawer-title-bgc := $color-white
210
+$drawer-panel-bgc := $color-white
211
+$drawer-item-active-bgc := $color-light-grey-opacity
212
+
213
+// scroll-nav
214
+$scroll-nav-bgc := $color-white
215
+$scroll-nav-color := $color-grey
216
+$scroll-nav-active-color := $color-orange
217
+
218
+// image-preview
219
+$image-preview-counter-color := $color-white
220
+
221
+// tab-bar & tab-panel
222
+$tab-color := $color-grey
223
+$tab-active-color := $color-dark-orange
224
+$tab-slider-bgc := $color-dark-orange

+ 3 - 0
src/common/stylus/var/box-shadow.styl

@@ -0,0 +1,3 @@
1
+// box-shadow
2
+
3
+$box-shadow-content = 0 1px 3px rgba(0, 0, 0, .1)

+ 36 - 0
src/common/stylus/var/color.styl

@@ -0,0 +1,36 @@
1
+// color vars
2
+//// basic
3
+$color-orange = #fc9153
4
+$color-regular-blue = #4a4c5b
5
+$color-background = #f3f4f5
6
+$color-white = #fff
7
+
8
+//// gray
9
+$color-dark-grey = #333
10
+$color-grey = #666
11
+$color-light-grey = #999
12
+$color-light-grey-s = #ccc
13
+$color-light-grey-ss = #eee
14
+$color-light-grey-sss = #fcfcfc
15
+$color-active-grey = #e8e8e8
16
+
17
+$color-dark-grey-opacity = rgba(74, 76, 91, 0.8)
18
+$color-grey-opacity = rgba(0, 0, 0, .08)
19
+$color-light-grey-opacity = rgba(0, 0, 0, .04)
20
+
21
+//// orange
22
+$color-dark-orange = #e8864c
23
+$color-light-orange = #fdc2a5
24
+$color-orange-opacity = rgba(252, 145, 83, .08)
25
+$color-light-orange-opacity = rgba(252, 145, 83, .04)
26
+
27
+//// blue
28
+$color-blue = #444654
29
+
30
+//// row line
31
+$color-row-line = #ebebeb
32
+//// column line
33
+$color-col-line = #f5f5f5
34
+
35
+/// mask
36
+$color-mask-bg = rgba(37, 38, 45, .4)

+ 12 - 0
src/common/stylus/var/size.styl

@@ -0,0 +1,12 @@
1
+// font-size vars
2
+$fontsize-large-xxxx = 30px
3
+$fontsize-large-xxx = 24px
4
+$fontsize-large-xx = 20px
5
+$fontsize-large-x = 18px
6
+$fontsize-large = 16px
7
+$fontsize-medium = 14px
8
+$fontsize-small = 12px
9
+$fontsize-small-s = 10px
10
+
11
+// radius-size
12
+$radius-size-medium = 5px

+ 4 - 0
src/common/stylus/variable.styl

@@ -0,0 +1,4 @@
1
+@require "./var/color.styl"
2
+@require "./var/size.styl"
3
+@require "./var/box-shadow.styl"
4
+@require "./theme/default.styl"

+ 0 - 0
src/components/.gitkeep


+ 163 - 0
src/components/ConsumableMaterial/index.vue

@@ -0,0 +1,163 @@
1
+<template>
2
+  <div class="eventInformation">
3
+    <div class="head">
4
+      <p>
5
+        <span>{{ model.consumableName }}{{ model.consumableBrandModel }}</span>
6
+        <span class="textRight">单价:<strong class="fontBold">{{ model.consumableEndPrice }}元</strong></span>
7
+      </p>
8
+    </div>
9
+    <p>
10
+      <span class="fl"></span>
11
+      <span class="fr defaultColor"><strong class="fontBold">×{{ model.consumablesNum }}</strong>&emsp;&emsp;<span class="totalPrice">总价:<strong class="fontBold">{{model.consumablesNum * model.consumableEndPrice}}元</strong></span></span>
12
+    </p>
13
+  </div>
14
+</template>
15
+
16
+<script>
17
+export default {
18
+  name: "ConsumableMaterial",
19
+  data() {
20
+    return {};
21
+  },
22
+  props: ["model", "id", "summaryId", "incidentId", "noClick"],
23
+};
24
+</script>
25
+
26
+<style lang="less" scoped>
27
+.eventInformation {
28
+  .fl{
29
+    float: left;
30
+  }
31
+  .fr{
32
+    float: right;
33
+  }
34
+  .defaultColor{
35
+    color: #005395;
36
+  }
37
+  .totalPrice{
38
+    display: inline-block;
39
+    width: 2rem;
40
+    text-align: right;
41
+  }
42
+  .boeder_B {
43
+    border-bottom: 0.01rem solid #ccc;
44
+  }
45
+  .shows {
46
+    display: none;
47
+  }
48
+  .head {
49
+    border-bottom: 0.01rem solid #e6e6e6;
50
+    p {
51
+      padding: 0.24rem;
52
+      display: flex;
53
+      justify-content: space-between;
54
+      align-items: center;
55
+      i {
56
+        color: #00559d;
57
+      }
58
+      span {
59
+        flex: 1;
60
+        flex-shrink: 0;
61
+        width: 50%;
62
+        &.textRight{
63
+          text-align: right;
64
+        }
65
+      }
66
+    }
67
+  }
68
+  p {
69
+    line-height: 0.4rem;
70
+    padding: 0.1rem 0.24rem;
71
+    overflow: hidden;
72
+    .overflowEllipsis2 {
73
+      margin-left: 1.96rem;
74
+    }
75
+  }
76
+  .info_hide {
77
+    padding: 0.2rem 0.24rem;
78
+    border-bottom: 0.01rem solid #e6e6e6;
79
+    .hide {
80
+      color: #00559d;
81
+    }
82
+  }
83
+  .imgs-container {
84
+    a {
85
+      color: #03c !important;
86
+      &:visited {
87
+        color: #551a8b !important;
88
+      }
89
+    }
90
+    img {
91
+      width: 1.5rem;
92
+      height: 1.5rem;
93
+      margin-right: 0.7rem;
94
+      &:nth-child(1) {
95
+        margin-left: 0.75rem;
96
+      }
97
+    }
98
+  }
99
+  .progress {
100
+    padding: 0.2rem 0.2rem;
101
+    overflow: hidden;
102
+    transition-duration: 0.2s;
103
+    transition-timing-function: linear;
104
+    &.progressHide {
105
+      height: 1.7rem;
106
+    }
107
+    .progress_info {
108
+      overflow: hidden;
109
+      margin-bottom: 0.1rem;
110
+      &:nth-last-child(1) {
111
+        .cont {
112
+          border: none !important;
113
+        }
114
+      }
115
+      .progress_info_L {
116
+        float: left;
117
+        color: #333;
118
+        max-width: 18%;
119
+      }
120
+      .progress_info_R {
121
+        float: right;
122
+        margin-left: 0.09rem;
123
+        width: 80%;
124
+        font-size: 0.25rem;
125
+        .time {
126
+          i {
127
+            margin-left: -0.15rem;
128
+            &.icon-icon_weizuo {
129
+              color: #005495;
130
+            }
131
+            &.icon-icon_zhengzaijinx {
132
+              color: #48a843;
133
+              font-size: 0.37rem;
134
+            }
135
+          }
136
+          span {
137
+            margin-left: 0.15rem;
138
+          }
139
+        }
140
+        .cont {
141
+          border-left: 1px solid #999;
142
+          padding-left: 0.4rem;
143
+          min-height: 0.4rem;
144
+          &.blue {
145
+            border-left: 1px solid #005395;
146
+          }
147
+        }
148
+
149
+        .text1 {
150
+          font-size: 0.15rem;
151
+        }
152
+        .text2 {
153
+          color: #666;
154
+          word-break: break-all;
155
+        }
156
+        p {
157
+          padding: 0;
158
+        }
159
+      }
160
+    }
161
+  }
162
+}
163
+</style>

+ 163 - 0
src/components/WorkHourManagement/index.vue

@@ -0,0 +1,163 @@
1
+<template>
2
+  <div class="eventInformation">
3
+    <div class="head">
4
+      <p>
5
+        <span>{{ model.workName }}</span>
6
+        <span class="textRight">单价:<strong class="fontBold">{{ model.wage }}元</strong></span>
7
+      </p>
8
+    </div>
9
+    <p>
10
+      <span class="fl"></span>
11
+      <span class="fr defaultColor"><strong class="fontBold">×{{ model.workHourNum2 }}{{ model.workUnit }}</strong>&emsp;&emsp;<span class="totalPrice">总价:<strong class="fontBold">{{model.workHourNum2 * model.wage}}元</strong></span></span>
12
+    </p>
13
+  </div>
14
+</template>
15
+
16
+<script>
17
+export default {
18
+  name: "WorkHourManagement",
19
+  data() {
20
+    return {};
21
+  },
22
+  props: ["model", "id", "summaryId", "incidentId", "summaryObj", "noClick"],
23
+};
24
+</script>
25
+
26
+<style lang="less" scoped>
27
+.eventInformation {
28
+  .fl{
29
+    float: left;
30
+  }
31
+  .fr{
32
+    float: right;
33
+  }
34
+  .defaultColor{
35
+    color: #005395;
36
+  }
37
+  .totalPrice{
38
+    display: inline-block;
39
+    width: 2rem;
40
+    text-align: right;
41
+  }
42
+  .boeder_B {
43
+    border-bottom: 0.01rem solid #ccc;
44
+  }
45
+  .shows {
46
+    display: none;
47
+  }
48
+  .head {
49
+    border-bottom: 0.01rem solid #e6e6e6;
50
+    p {
51
+      padding: 0.24rem;
52
+      display: flex;
53
+      justify-content: space-between;
54
+      align-items: center;
55
+      i {
56
+        color: #00559d;
57
+      }
58
+      span {
59
+        flex: 1;
60
+        flex-shrink: 0;
61
+        width: 50%;
62
+        &.textRight{
63
+          text-align: right;
64
+        }
65
+      }
66
+    }
67
+  }
68
+  p {
69
+    line-height: 0.4rem;
70
+    padding: 0.1rem 0.24rem;
71
+    overflow: hidden;
72
+    .overflowEllipsis2 {
73
+      margin-left: 1.96rem;
74
+    }
75
+  }
76
+  .info_hide {
77
+    padding: 0.2rem 0.24rem;
78
+    border-bottom: 0.01rem solid #e6e6e6;
79
+    .hide {
80
+      color: #00559d;
81
+    }
82
+  }
83
+  .imgs-container {
84
+    a {
85
+      color: #03c !important;
86
+      &:visited {
87
+        color: #551a8b !important;
88
+      }
89
+    }
90
+    img {
91
+      width: 1.5rem;
92
+      height: 1.5rem;
93
+      margin-right: 0.7rem;
94
+      &:nth-child(1) {
95
+        margin-left: 0.75rem;
96
+      }
97
+    }
98
+  }
99
+  .progress {
100
+    padding: 0.2rem 0.2rem;
101
+    overflow: hidden;
102
+    transition-duration: 0.2s;
103
+    transition-timing-function: linear;
104
+    &.progressHide {
105
+      height: 1.7rem;
106
+    }
107
+    .progress_info {
108
+      overflow: hidden;
109
+      margin-bottom: 0.1rem;
110
+      &:nth-last-child(1) {
111
+        .cont {
112
+          border: none !important;
113
+        }
114
+      }
115
+      .progress_info_L {
116
+        float: left;
117
+        color: #333;
118
+        max-width: 18%;
119
+      }
120
+      .progress_info_R {
121
+        float: right;
122
+        margin-left: 0.09rem;
123
+        width: 80%;
124
+        font-size: 0.25rem;
125
+        .time {
126
+          i {
127
+            margin-left: -0.15rem;
128
+            &.icon-icon_weizuo {
129
+              color: #005495;
130
+            }
131
+            &.icon-icon_zhengzaijinx {
132
+              color: #48a843;
133
+              font-size: 0.37rem;
134
+            }
135
+          }
136
+          span {
137
+            margin-left: 0.15rem;
138
+          }
139
+        }
140
+        .cont {
141
+          border-left: 1px solid #999;
142
+          padding-left: 0.4rem;
143
+          min-height: 0.4rem;
144
+          &.blue {
145
+            border-left: 1px solid #005395;
146
+          }
147
+        }
148
+
149
+        .text1 {
150
+          font-size: 0.15rem;
151
+        }
152
+        .text2 {
153
+          color: #666;
154
+          word-break: break-all;
155
+        }
156
+        p {
157
+          padding: 0;
158
+        }
159
+      }
160
+    }
161
+  }
162
+}
163
+</style>

+ 188 - 0
src/components/action-sheet/action-sheet.vue

@@ -0,0 +1,188 @@
1
+<template>
2
+  <transition name="cube-action-sheet-fade">
3
+    <cube-popup
4
+      type="action-sheet"
5
+      :class="{'cube-action-sheet_picker': pickerStyle}"
6
+      :center="false"
7
+      :mask="true"
8
+      :z-index="zIndex"
9
+      v-show="isVisible"
10
+      @mask-click="maskClick">
11
+      <transition name="cube-action-sheet-move">
12
+        <div class="cube-action-sheet-panel cube-safe-area-pb" v-show="isVisible" @click.stop>
13
+          <h1 class="cube-action-sheet-title border-bottom-1px" v-show="pickerStyle || title">{{title}}</h1>
14
+          <div class="cube-action-sheet-content">
15
+            <ul class="cube-action-sheet-list">
16
+              <li
17
+                class="cube-action-sheet-item border-bottom-1px"
18
+                v-for="(item, index) in data"
19
+                :data-align="item.align"
20
+                :class="[
21
+                  item.class || '',
22
+                  index === active ? 'cube-action-sheet-item_active': ''
23
+                ]"
24
+                v-html="item.content"
25
+                @click="itemClick(item, index)"></li>
26
+            </ul>
27
+          </div>
28
+          <div class="cube-action-sheet-space"></div>
29
+          <div class="cube-action-sheet-cancel" @click="cancel"><span>{{_cancelTxt}}</span></div>
30
+        </div>
31
+      </transition>
32
+    </cube-popup>
33
+  </transition>
34
+</template>
35
+
36
+<script type="text/ecmascript-6">
37
+  import CubePopup from '../popup/popup.vue'
38
+  import visibilityMixin from '../../common/mixins/visibility'
39
+  import popupMixin from '../../common/mixins/popup'
40
+  import localeMixin from '../../common/mixins/locale'
41
+
42
+  const COMPONENT_NAME = 'cube-action-sheet'
43
+  const EVENT_SELECT = 'select'
44
+  const EVENT_CANCEL = 'cancel'
45
+
46
+  export default {
47
+    name: COMPONENT_NAME,
48
+    mixins: [visibilityMixin, popupMixin, localeMixin],
49
+    props: {
50
+      data: {
51
+        type: Array,
52
+        default() {
53
+          return []
54
+        }
55
+      },
56
+      active: {
57
+        type: Number,
58
+        default: -1
59
+      },
60
+      title: {
61
+        type: String,
62
+        default: ''
63
+      },
64
+      pickerStyle: {
65
+        type: Boolean,
66
+        default: false
67
+      },
68
+      maskClosable: {
69
+        type: Boolean,
70
+        default: true
71
+      },
72
+      cancelTxt: {
73
+        type: String,
74
+        default: ''
75
+      }
76
+    },
77
+    computed: {
78
+      _cancelTxt () {
79
+        return this.cancelTxt || this.$t('cancel')
80
+      }
81
+    },
82
+    methods: {
83
+      maskClick() {
84
+        this.maskClosable && this.cancel()
85
+      },
86
+      cancel() {
87
+        this.hide()
88
+        this.$emit(EVENT_CANCEL)
89
+      },
90
+      itemClick(item, index) {
91
+        this.hide()
92
+        this.$emit(EVENT_SELECT, item, index)
93
+      }
94
+    },
95
+    components: {
96
+      CubePopup
97
+    }
98
+  }
99
+</script>
100
+
101
+<style lang="stylus" rel="stylesheet/stylus">
102
+  @require "../../common/stylus/mixin.styl"
103
+  @require "../../common/stylus/variable.styl"
104
+
105
+  .cube-action-sheet-fade-enter, .cube-action-sheet-fade-leave-active
106
+    opacity: 0
107
+  .cube-action-sheet-fade-enter-active, .cube-action-sheet-fade-leave-active
108
+    transition: all .3s ease-in-out
109
+
110
+  .cube-action-sheet-panel
111
+    text-align: center
112
+    font-size: $fontsize-medium
113
+    background-color: $action-sheet-bgc
114
+  .cube-action-sheet-move-enter, .cube-action-sheet-move-leave-active
115
+    transform: translate3d(0, 100%, 0)
116
+  .cube-action-sheet-move-enter-active, .cube-action-sheet-move-leave-active
117
+    transition: all .3s ease-in-out
118
+  .cube-action-sheet-cancel
119
+    background-color: $action-sheet-bgc
120
+  .cube-action-sheet-cancel span, .cube-action-sheet-title, .cube-action-sheet-item
121
+    display: block
122
+    padding: 17px 16px
123
+    margin: 0
124
+    text-align: center
125
+    overflow: hidden
126
+    white-space: nowrap
127
+    font-size: $fontsize-large
128
+    font-weight: normal
129
+    line-height: 1
130
+    color: $action-sheet-color
131
+    background-color: $action-sheet-bgc
132
+
133
+  .cube-action-sheet-cancel span, .cube-action-sheet-item
134
+    &:active
135
+      background-color: $action-sheet-active-bgc
136
+
137
+  .cube-action-sheet-title
138
+    padding-top: 16px
139
+    padding-bottom: 16px
140
+    color: $action-sheet-title-color
141
+    font-size: $fontsize-large-x
142
+
143
+  .cube-action-sheet-content
144
+    overflow: hidden
145
+    background: $action-sheet-bgc
146
+
147
+  .cube-action-sheet-list
148
+    list-style: none
149
+
150
+  .cube-action-sheet-item
151
+    list-style: none
152
+    user-select: none
153
+    &:last-of-type
154
+      border-none()
155
+    &[data-align="left"]
156
+      text-align: left
157
+    &[data-align="right"]
158
+      text-align: right
159
+
160
+  .cube-action-sheet-space
161
+    height: 6px
162
+    background-color: $action-sheet-space-bgc
163
+
164
+  .cube-action-sheet-item_active
165
+    color: $action-sheet-active-color
166
+
167
+  .cube-action-sheet_picker
168
+    .cube-action-sheet-space
169
+      height: 0
170
+    .cube-action-sheet-title
171
+      height: 1em
172
+      padding-top: 21px
173
+      padding-bottom: 21px
174
+
175
+    .cube-action-sheet-cancel
176
+      position: absolute
177
+      top: 0
178
+      background-color: transparent
179
+      span
180
+        padding-top: 23px
181
+        padding-bottom: 23px
182
+        color: $action-sheet-picker-cancel-color
183
+        font-size: $fontsize-medium
184
+        background-color: transparent
185
+        &:active
186
+          color: $action-sheet-picker-cancel-active-color
187
+          background-color: transparent
188
+</style>

+ 137 - 0
src/components/bubble/bubble.vue

@@ -0,0 +1,137 @@
1
+<template>
2
+  <canvas ref="bubble" :width="width" :height="height" :style="style"></canvas>
3
+</template>
4
+
5
+<script type="text/ecmascript-6">
6
+  export default {
7
+    props: {
8
+      y: {
9
+        type: Number,
10
+        default: 0
11
+      }
12
+    },
13
+    data() {
14
+      return {
15
+        width: 50,
16
+        height: 80
17
+      }
18
+    },
19
+    computed: {
20
+      distance() {
21
+        return Math.max(0, Math.min(this.y * this.ratio, this.maxDistance))
22
+      },
23
+      style() {
24
+        return `width:${this.width / this.ratio}px;height:${this.height / this.ratio}px`
25
+      }
26
+    },
27
+    mounted() {
28
+      this.ratio = window.devicePixelRatio
29
+      this.width *= this.ratio
30
+      this.height *= this.ratio
31
+      this.initRadius = 18 * this.ratio
32
+      this.minHeadRadius = 12 * this.ratio
33
+      this.minTailRadius = 5 * this.ratio
34
+      this.initArrowRadius = 10 * this.ratio
35
+      this.minArrowRadius = 6 * this.ratio
36
+      this.arrowWidth = 3 * this.ratio
37
+      this.maxDistance = 40 * this.ratio
38
+      this.initCenterX = 25 * this.ratio
39
+      this.initCenterY = 25 * this.ratio
40
+      this.headCenter = {
41
+        x: this.initCenterX,
42
+        y: this.initCenterY
43
+      }
44
+      this._draw()
45
+    },
46
+    methods: {
47
+      _draw() {
48
+        const bubble = this.$refs.bubble
49
+        let ctx = bubble.getContext('2d')
50
+        ctx.clearRect(0, 0, bubble.width, bubble.height)
51
+
52
+        this._drawBubble(ctx)
53
+
54
+        this._drawArrow(ctx)
55
+      },
56
+      _drawBubble(ctx) {
57
+        ctx.save()
58
+        ctx.beginPath()
59
+
60
+        const rate = this.distance / this.maxDistance
61
+        const headRadius = this.initRadius - (this.initRadius - this.minHeadRadius) * rate
62
+
63
+        this.headCenter.y = this.initCenterY - (this.initRadius - this.minHeadRadius) * rate
64
+
65
+        // upper semicircle
66
+        ctx.arc(this.headCenter.x, this.headCenter.y, headRadius, 0, Math.PI, true)
67
+
68
+        // left bessel
69
+        const tailRadius = this.initRadius - (this.initRadius - this.minTailRadius) * rate
70
+        const tailCenter = {
71
+          x: this.headCenter.x,
72
+          y: this.headCenter.y + this.distance
73
+        }
74
+
75
+        const tailPointL = {
76
+          x: tailCenter.x - tailRadius,
77
+          y: tailCenter.y
78
+        }
79
+        const controlPointL = {
80
+          x: tailPointL.x,
81
+          y: tailPointL.y - this.distance / 2
82
+        }
83
+
84
+        ctx.quadraticCurveTo(controlPointL.x, controlPointL.y, tailPointL.x, tailPointL.y)
85
+
86
+        // lower semicircle
87
+        ctx.arc(tailCenter.x, tailCenter.y, tailRadius, Math.PI, 0, true)
88
+
89
+        // right bessel
90
+        const headPointR = {
91
+          x: this.headCenter.x + headRadius,
92
+          y: this.headCenter.y
93
+        }
94
+        const controlPointR = {
95
+          x: tailCenter.x + tailRadius,
96
+          y: headPointR.y + this.distance / 2
97
+        }
98
+        ctx.quadraticCurveTo(controlPointR.x, controlPointR.y, headPointR.x, headPointR.y)
99
+
100
+        ctx.fillStyle = 'rgb(170,170,170)'
101
+        ctx.fill()
102
+        ctx.strokeStyle = 'rgb(153,153,153)'
103
+        ctx.stroke()
104
+        ctx.restore()
105
+      },
106
+      _drawArrow(ctx) {
107
+        ctx.save()
108
+        ctx.beginPath()
109
+
110
+        const rate = this.distance / this.maxDistance
111
+        const arrowRadius = this.initArrowRadius - (this.initArrowRadius - this.minArrowRadius) * rate
112
+
113
+        // inner circle
114
+        ctx.arc(this.headCenter.x, this.headCenter.y, arrowRadius - (this.arrowWidth - rate), -Math.PI / 2, 0, true)
115
+
116
+        // outer circle
117
+        ctx.arc(this.headCenter.x, this.headCenter.y, arrowRadius, 0, Math.PI * 3 / 2, false)
118
+
119
+        ctx.lineTo(this.headCenter.x, this.headCenter.y - arrowRadius - this.arrowWidth / 2 + rate)
120
+        ctx.lineTo(this.headCenter.x + this.arrowWidth * 2 - rate * 2, this.headCenter.y - arrowRadius + this.arrowWidth / 2)
121
+
122
+        ctx.lineTo(this.headCenter.x, this.headCenter.y - arrowRadius + this.arrowWidth * 3 / 2 - rate)
123
+
124
+        ctx.fillStyle = 'rgb(255,255,255)'
125
+        ctx.fill()
126
+        ctx.strokeStyle = 'rgb(170,170,170)'
127
+        ctx.stroke()
128
+        ctx.restore()
129
+      }
130
+    },
131
+    watch: {
132
+      y() {
133
+        this._draw()
134
+      }
135
+    }
136
+  }
137
+</script>

+ 148 - 0
src/components/button/button.vue

@@ -0,0 +1,148 @@
1
+<template>
2
+  <button
3
+    class="cube-btn"
4
+    :type="type"
5
+    :class="btnClass"
6
+    @click="handleClick">
7
+    <i :class="icon" v-if="icon"></i>
8
+    <slot></slot>
9
+  </button>
10
+</template>
11
+
12
+<script>
13
+  const COMPONENT_NAME = 'cube-button'
14
+  export default {
15
+    name: COMPONENT_NAME,
16
+    props: {
17
+      icon: {
18
+        type: String,
19
+        default: ''
20
+      },
21
+      active: {
22
+        type: Boolean,
23
+        default: false
24
+      },
25
+      disabled: {
26
+        type: Boolean,
27
+        default: false
28
+      },
29
+      inline: {
30
+        type: Boolean,
31
+        default: false
32
+      },
33
+      primary: {
34
+        type: Boolean,
35
+        default: false
36
+      },
37
+      outline: {
38
+        type: Boolean,
39
+        default: false
40
+      },
41
+      light: {
42
+        type: Boolean,
43
+        default: false
44
+      },
45
+      type: {
46
+        type: String,
47
+        default: 'button'
48
+      }
49
+    },
50
+    computed: {
51
+      btnClass() {
52
+        return {
53
+          'cube-btn_active': this.active,
54
+          'cube-btn_disabled': this.disabled,
55
+          'cube-btn-inline': this.inline,
56
+          'cube-btn-primary': this.primary,
57
+          'cube-btn-outline': this.outline,
58
+          'cube-btn-outline-primary': this.outline && this.primary,
59
+          'cube-btn-light': this.light
60
+        }
61
+      }
62
+    },
63
+    methods: {
64
+      handleClick(event) {
65
+        if (this.disabled) {
66
+          event.preventDefault()
67
+          event.stopPropagation()
68
+          return
69
+        }
70
+        this.$emit('click', event)
71
+      }
72
+    }
73
+  }
74
+</script>
75
+
76
+<style lang="stylus" rel="stylesheet/stylus">
77
+  @require "../../common/stylus/variable.styl"
78
+  @require "../../common/stylus/mixin.styl"
79
+  btn-active($bg, $border = null)
80
+    &.cube-btn_active, &:active
81
+      background: $bg
82
+      if $border != null
83
+        border-1px($border)
84
+
85
+  .cube-btn
86
+    display: block
87
+    margin: 0
88
+    padding: 17px 16px
89
+    width: 100%
90
+    text-align: center
91
+    white-space: nowrap
92
+    cursor: pointer
93
+    font-size: $fontsize-large
94
+    line-height: 1
95
+    color: $btn-color
96
+    background: $btn-bgc
97
+    outline: none
98
+    border: none
99
+    border-radius: 2px
100
+    box-sizing: border-box
101
+    -webkit-tap-highlight-color: transparent
102
+    btn-active($btn-active-bgc)
103
+    > i
104
+      display: inline-block
105
+      margin-right: 4px
106
+      font-size: 100%
107
+      transform: scale(1.13)
108
+      transform-origin: right center
109
+
110
+  .cube-btn-inline
111
+    width: auto
112
+    display: inline-block
113
+    vertical-align: middle
114
+    padding: 9px 10px
115
+    font-size: $fontsize-small
116
+    > i
117
+      margin-right: 2px
118
+      transform: scale(1.14)
119
+
120
+  .cube-btn-primary
121
+    color: $btn-primary-color
122
+    background: $btn-primary-bgc
123
+    btn-active($btn-primary-active-bgc)
124
+
125
+  .cube-btn-light
126
+    color: $btn-light-color
127
+    background: $btn-light-bgc
128
+    box-shadow: $box-shadow-content
129
+    btn-active($btn-light-active-bgc)
130
+
131
+  .cube-btn-outline
132
+    color: $btn-outline-color
133
+    background: $btn-outline-bgc
134
+    border-1px($btn-outline-bdc)
135
+    btn-active($btn-outline-active-bgc, $btn-outline-active-bdc)
136
+
137
+  .cube-btn-outline-primary
138
+    color: $btn-outline-primary-color
139
+    background: $btn-outline-primary-bgc
140
+    border-1px($btn-outline-primary-bdc)
141
+    btn-active($btn-outline-primary-active-bgc, $btn-outline-primary-active-bdc)
142
+
143
+  .cube-btn_disabled
144
+    color: $btn-disabled-color
145
+    background: $btn-disabled-bgc
146
+    border-1px($btn-disabled-bdc)
147
+    btn-active($btn-disabled-bgc, $btn-disabled-bdc)
148
+</style>

+ 110 - 0
src/components/cascade-picker/cascade-picker.vue

@@ -0,0 +1,110 @@
1
+<template>
2
+  <cube-picker
3
+    ref="picker"
4
+    v-model="isVisible"
5
+    :data="pickerData"
6
+    :selected-index="pickerSelectedIndex"
7
+    :pending="pending"
8
+    :title="title"
9
+    :subtitle="subtitle"
10
+    :z-index="zIndex"
11
+    :cancel-txt="_cancelTxt"
12
+    :confirm-txt="_confirmTxt"
13
+    :swipe-time="swipeTime"
14
+    :mask-closable="maskClosable"
15
+    @select="_pickerSelect"
16
+    @cancel="_pickerCancel"
17
+    @change="_pickerChange">
18
+  </cube-picker>
19
+</template>
20
+
21
+<script type="text/ecmascript-6">
22
+  import CubePicker from '../picker/picker.vue'
23
+  import visibilityMixin from '../../common/mixins/visibility'
24
+  import popupMixin from '../../common/mixins/popup'
25
+  import basicPickerMixin from '../../common/mixins/basic-picker'
26
+  import pickerMixin from '../../common/mixins/picker'
27
+  import localeMixin from '../../common/mixins/locale'
28
+
29
+  const COMPONENT_NAME = 'cube-cascade-picker'
30
+  const EVENT_SELECT = 'select'
31
+  const EVENT_CANCEL = 'cancel'
32
+  const EVENT_CHANGE = 'change'
33
+
34
+  export default {
35
+    name: COMPONENT_NAME,
36
+    mixins: [visibilityMixin, popupMixin, basicPickerMixin, pickerMixin, localeMixin],
37
+    props: {
38
+      async: {
39
+        type: Boolean,
40
+        default: false
41
+      }
42
+    },
43
+    data () {
44
+      return {
45
+        cascadeData: this.data.slice(),
46
+        pickerSelectedIndex: this.selectedIndex.slice(),
47
+        pickerData: [],
48
+        pending: false
49
+      }
50
+    },
51
+    created() {
52
+      this._updatePickerData()
53
+    },
54
+    methods: {
55
+      setData(data, selectedIndex = []) {
56
+        this.pending = false
57
+        this.cascadeData = data.slice()
58
+        this.pickerSelectedIndex = selectedIndex.slice()
59
+        this._updatePickerData()
60
+      },
61
+      _pickerSelect(selectedVal, selectedIndex, selectedText) {
62
+        this.$emit(EVENT_SELECT, selectedVal, selectedIndex, selectedText)
63
+      },
64
+      _pickerCancel() {
65
+        this.$emit(EVENT_CANCEL)
66
+      },
67
+      _pickerChange(i, newIndex) {
68
+        if (newIndex !== this.pickerSelectedIndex[i]) {
69
+          this.pickerSelectedIndex.splice(i, 1, newIndex)
70
+          this.async
71
+            ? (this.pending = i !== this.pickerData.length - 1)
72
+            : this._updatePickerData(i + 1)
73
+        }
74
+        this.$emit(EVENT_CHANGE, i, newIndex)
75
+      },
76
+      _updatePickerData(fromColumn = 0) {
77
+        let data = this.cascadeData
78
+        let i = 0
79
+        while (data) {
80
+          if (i >= fromColumn) {
81
+            let columnData = []
82
+            data.forEach((item) => {
83
+              columnData.push({
84
+                value: item[this.valueKey],
85
+                text: item[this.textKey]
86
+              })
87
+            })
88
+            this.pickerData[i] = columnData
89
+            /* refillColumn could only be called after show() */
90
+            this.pickerSelectedIndex[i] = fromColumn === 0
91
+              ? (this.pickerSelectedIndex[i] < data.length ? this.pickerSelectedIndex[i] || 0 : 0)
92
+              : this.$refs.picker.refillColumn(i, columnData)
93
+          }
94
+          data = data.length ? data[this.pickerSelectedIndex[i]].children : null
95
+
96
+          i++
97
+        }
98
+
99
+        if (i < this.pickerData.length) {
100
+          this.pickerData.splice(i, this.pickerData.length - i)
101
+        }
102
+
103
+        this.pickerData = this.pickerData.slice()
104
+      }
105
+    },
106
+    components: {
107
+      CubePicker
108
+    }
109
+  }
110
+</script>

+ 135 - 0
src/components/checkbox-group/checkbox-group.vue

@@ -0,0 +1,135 @@
1
+<template>
2
+  <div class="cube-checkbox-group" ref="group" :class="groupClass" :data-horz="horizontal">
3
+    <slot>
4
+      <cube-checkbox
5
+        v-for="(option, index) in options"
6
+        :key="index"
7
+        :option="option"
8
+        :shape="shape"
9
+        :position="position"
10
+        :hollow-style="hollowStyle" />
11
+    </slot>
12
+  </div>
13
+</template>
14
+<script type="text/ecmascript-6">
15
+  import CubeCheckbox from '../checkbox/checkbox.vue'
16
+  const COMPONENT_NAME = 'cube-checkbox-group'
17
+
18
+  const EVENT_INPUT = 'input'
19
+  const EVENT_CHECKED = 'checked'
20
+  const EVENT_CANCLE_CHECKED = 'cancel-checked'
21
+
22
+  export default {
23
+    name: COMPONENT_NAME,
24
+    props: {
25
+      value: {
26
+        type: Array
27
+      },
28
+      horizontal: {
29
+        type: Boolean,
30
+        default: false
31
+      },
32
+      shape: {
33
+        type: String,
34
+        default: 'circle'
35
+      },
36
+      position: {
37
+        type: String,
38
+        default: 'left'
39
+      },
40
+      hollowStyle: {
41
+        type: Boolean,
42
+        default: false
43
+      },
44
+      options: {
45
+        type: Array,
46
+        default() {
47
+          return []
48
+        }
49
+      },
50
+      min: {
51
+        type: Number,
52
+        default: 0
53
+      },
54
+      max: {
55
+        type: Number,
56
+        default: Infinity
57
+      }
58
+    },
59
+    data () {
60
+      return {
61
+        _value: [],
62
+        _checkboxGroup: true
63
+      }
64
+    },
65
+    computed: {
66
+      groupClass() {
67
+        if (!this.horizontal) {
68
+          return 'border-top-1px border-bottom-1px'
69
+        }
70
+      }
71
+    },
72
+    watch: {
73
+      value: {
74
+        immediate: true,
75
+        handler (newValue, oldValue) {
76
+          this._value = this.value.concat()
77
+        }
78
+      }
79
+    },
80
+    mounted () {
81
+      this.$on(EVENT_CHECKED, (value) => {
82
+        if (this._value.length < this.max) {
83
+          this._value.push(value)
84
+        }
85
+        this.$emit(EVENT_INPUT, this._value)
86
+      })
87
+      this.$on(EVENT_CANCLE_CHECKED, (value) => {
88
+        if (this._value.length > this.min) {
89
+          const index = this._value.indexOf(value)
90
+          this._value.splice(index, 1)
91
+        }
92
+        this.$emit(EVENT_INPUT, this._value)
93
+      })
94
+    },
95
+    components: {
96
+      CubeCheckbox
97
+    }
98
+  }
99
+</script>
100
+<style lang="stylus" rel="stylesheet/stylus">
101
+  @require "../../common/stylus/variable.styl"
102
+  @require "../../common/stylus/mixin.styl"
103
+
104
+  .cube-checkbox-group
105
+    z-index: 1
106
+    overflow: hidden
107
+    background-color: $checkbox-group-bgc
108
+    .cube-checkbox
109
+      &:last-child
110
+        .cube-checkbox-wrap
111
+          border-none()
112
+  .cube-checkbox-group[data-horz="true"]
113
+    display: flex
114
+    padding-left: 0
115
+    border-1px($checkbox-group-horizontal-bdc, 2px)
116
+    border-radius: 2px
117
+    .cube-checkbox
118
+      flex-fix()
119
+      text-align: center
120
+      padding-left: 10px
121
+      padding-right: 10px
122
+      &:after
123
+        border-color: $checkbox-group-horizontal-bdc
124
+      &:last-child
125
+        border-none()
126
+      &[data-pos="right"]
127
+        .cube-checkbox-ui
128
+          position: relative
129
+          margin-left: .42em
130
+          order: 1
131
+        .cube-checkbox-label
132
+          margin-right: 0
133
+    .cube-checkbox-wrap
134
+      justify-content: center
135
+</style>

+ 237 - 0
src/components/checkbox/checkbox.vue

@@ -0,0 +1,237 @@
1
+<template>
2
+  <div class="cube-checkbox" :class="_containerClass" :data-pos="position">
3
+    <label class="cube-checkbox-wrap" :class="_wrapClass">
4
+      <input class="cube-checkbox-input" type="checkbox" :disabled="computedOption.disabled" v-model="checkValue">
5
+      <span class="cube-checkbox-ui" :class="_borderIconClass">
6
+        <i :class="_rightIconClass"></i>
7
+      </span>
8
+      <span class="cube-checkbox-label">
9
+        <slot>{{computedOption.label}}</slot>
10
+      </span>
11
+    </label>
12
+  </div>
13
+</template>
14
+<script type="text/ecmascript-6">
15
+  const COMPONENT_NAME = 'cube-checkbox'
16
+
17
+  const EVENT_INPUT = 'input'
18
+  const EVENT_CHECKED = 'checked'
19
+  const EVENT_CANCLE_CHECKED = 'cancel-checked'
20
+
21
+  export default {
22
+    name: COMPONENT_NAME,
23
+    props: {
24
+      value: {
25
+        type: [Boolean, String]
26
+      },
27
+      label: {
28
+        type: [Boolean, String]
29
+      },
30
+      disabled: {
31
+        type: Boolean,
32
+        default: false
33
+      },
34
+      option: {
35
+        type: [Boolean, String, Object],
36
+        default () {
37
+          return {
38
+            _def_option: true
39
+          }
40
+        }
41
+      },
42
+      position: {
43
+        type: String,
44
+        default: 'left'
45
+      },
46
+      shape: {
47
+        type: String,
48
+        default: 'circle'
49
+      },
50
+      hollowStyle: {
51
+        type: Boolean,
52
+        default: false
53
+      }
54
+    },
55
+    data () {
56
+      const parent = this.$parent
57
+      const isInGroup = parent.$data._checkboxGroup
58
+      const isInHorizontalGroup = isInGroup && parent.$props.horizontal
59
+      return {
60
+        isInGroup,
61
+        isInHorizontalGroup
62
+      }
63
+    },
64
+    computed: {
65
+      computedOption() {
66
+        let option = this.option
67
+        const label = this.label
68
+        const disabled = this.disabled
69
+        if (option._def_option === true) {
70
+          option = {
71
+            label,
72
+            value: label,
73
+            disabled
74
+          }
75
+        } else if (typeof option === 'string') {
76
+          option = {
77
+            label: option,
78
+            value: option,
79
+            disabled: false
80
+          }
81
+        }
82
+        return option
83
+      },
84
+      checkValue: {
85
+        get () {
86
+          if (this.isInGroup) {
87
+            return this.$parent.value.indexOf(this.computedOption.value) > -1
88
+          } else {
89
+            return Boolean(this.value)
90
+          }
91
+        },
92
+        set (newValue) {
93
+          const value = this.computedOption.value
94
+          const emitValue = value && newValue ? value : newValue
95
+          const parentEmitEvent = newValue ? EVENT_CHECKED : EVENT_CANCLE_CHECKED
96
+          this.$emit(EVENT_INPUT, emitValue)
97
+          if (this.isInGroup) {
98
+            this.$parent.$emit(parentEmitEvent, value || newValue)
99
+          }
100
+        }
101
+      },
102
+      _containerClass() {
103
+        return {
104
+          'cube-checkbox-hollow': this.hollowStyle,
105
+          'cube-checkbox_checked': this.checkValue,
106
+          'cube-checkbox_disabled': this.computedOption.disabled,
107
+          'border-right-1px': this.isInHorizontalGroup
108
+        }
109
+      },
110
+      _wrapClass() {
111
+        if (this.isInGroup && !this.isInHorizontalGroup) {
112
+          return 'border-bottom-1px'
113
+        }
114
+      },
115
+      isSquare() {
116
+        return this.shape === 'square' || this.hollowStyle
117
+      },
118
+      _borderIconClass() {
119
+        return this.isSquare ? 'cubeic-square-border' : 'cubeic-round-border'
120
+      },
121
+      _rightIconClass() {
122
+        return this.isSquare ? 'cubeic-square-right' : 'cubeic-right'
123
+      }
124
+    }
125
+  }
126
+</script>
127
+<style lang="stylus" rel="stylesheet/stylus">
128
+  @require "../../common/stylus/variable.styl"
129
+  @require "../../common/stylus/mixin.styl"
130
+
131
+  $ui-width = 1.42em
132
+  .cube-checkbox
133
+    position: relative
134
+    padding: 0 16px
135
+    text-align: left
136
+    font-size: 100%
137
+    color: $checkbox-color
138
+    &[data-pos="right"]
139
+      .cube-checkbox-ui
140
+        margin-right: 0
141
+        position: absolute
142
+        right: 0
143
+      .cube-checkbox-label
144
+        margin-right: $ui-width
145
+  .cube-checkbox-wrap
146
+    position: relative
147
+    display: flex
148
+    align-items: center
149
+    box-sizing: border-box
150
+    width: 100%
151
+    height: 100%
152
+    padding: 11px 0
153
+    line-height: 1.5
154
+    word-break: break-word
155
+    word-wrap: break-word
156
+  .cube-checkbox-input
157
+    z-index: 1
158
+    position: absolute
159
+    top: 0
160
+    left: 0
161
+    width: 100%
162
+    height: 100%
163
+    opacity: 0
164
+  .cube-checkbox-ui
165
+    position: relative
166
+    width: 1em
167
+    height: 1em
168
+    margin-right: $ui-width - 1em
169
+    line-height: 1
170
+    border-radius: 50%
171
+    &.cubeic-square-border
172
+      border-radius: 2px
173
+    &::before, i
174
+      transition: all .2s
175
+    &::before
176
+      color: $checkbox-icon-color
177
+      display: inline-block
178
+      transform: scale(1.24)
179
+    i
180
+      position: absolute
181
+      top: 0
182
+      left: 0
183
+      color: transparent
184
+      transform: scale(.4)
185
+  .cube-checkbox_checked
186
+    .cube-checkbox-ui
187
+      &::before
188
+        color: transparent
189
+      i
190
+        color: $checkbox-checked-icon-color
191
+        transform: scale(1.23)
192
+  .cube-checkbox_disabled
193
+    .cube-checkbox-ui
194
+      background-color: $checkbox-disabled-icon-bgc
195
+      &::before, i
196
+        transition: none
197
+      &::before
198
+        color: transparent
199
+      i
200
+        color: $checkbox-disabled-icon-color
201
+  .cube-checkbox_checked.cube-checkbox_disabled
202
+    .cube-checkbox-ui
203
+      background-color: $checkbox-checked-icon-bgc
204
+  .cube-checkbox-hollow
205
+    i
206
+      width: 100%
207
+      height: 100%
208
+      &::before
209
+        content: ""
210
+        position: absolute
211
+        top: 50%
212
+        left: 50%
213
+        width: 50%
214
+        height: 50%
215
+        transform: translate(-50%, -50%)
216
+        background-color: currentColor
217
+        border-radius: 2px
218
+    &.cube-checkbox_checked
219
+      .cube-checkbox-ui
220
+        &::before
221
+          color: $checkbox-hollow-checked-icon-color
222
+        i
223
+          transform: scale(1)
224
+          color: $checkbox-hollow-checked-icon-color
225
+    &.cube-checkbox_disabled
226
+      .cube-checkbox-ui
227
+        background-color: transparent
228
+        &::before
229
+          color: $checkbox-hollow-disabled-icon-color
230
+        i
231
+          transform: scale(1)
232
+          color: transparent
233
+      &.cube-checkbox_checked
234
+        .cube-checkbox-ui
235
+          i
236
+            color: $checkbox-hollow-disabled-icon-color
237
+</style>

+ 59 - 0
src/components/checker/checker-item.vue

@@ -0,0 +1,59 @@
1
+<template>
2
+  <li
3
+    class="cube-checker-item"
4
+    :class="{'cube-checker-item_active': isActive}"
5
+    @click="clickHandler"
6
+  >
7
+    <slot>
8
+      <span v-html="option.text"></span>
9
+    </slot>
10
+  </li>
11
+</template>
12
+<script type="text/ecmascript-6">
13
+  const COMPONENT_NAME = 'cube-checker-item'
14
+
15
+  export default {
16
+    name: COMPONENT_NAME,
17
+    props: {
18
+      option: {
19
+        type: Object,
20
+        default() {
21
+          /* istanbul ignore next */
22
+          return {}
23
+        }
24
+      }
25
+    },
26
+    computed: {
27
+      isActive() {
28
+        const isRadio = this.$parent.isRadio
29
+        const currentValue = this.$parent.currentValue
30
+        const value = this.option.value
31
+        return isRadio ? currentValue === value : currentValue.indexOf(value) >= 0
32
+      }
33
+    },
34
+    methods: {
35
+      clickHandler() {
36
+        this.$parent.check(this.option)
37
+      }
38
+    }
39
+  }
40
+</script>
41
+<style lang="stylus" rel="stylesheet/stylus">
42
+  @require "../../common/stylus/variable.styl"
43
+  @require "../../common/stylus/mixin.styl"
44
+  .cube-checker-item
45
+    display: inline-block
46
+    vertical-align: top
47
+    text-align: center
48
+    padding: 8px 10px
49
+    margin-right: 10px
50
+    color: $checker-item-color
51
+    background: $checker-item-bgc
52
+    border-radius: 4px
53
+    border-1px($checker-item-bdc, 4px)
54
+  .cube-checker-item_active
55
+    color: $checker-item-active-color
56
+    background: $checker-item-active-bgc
57
+    border-1px($checker-item-active-bdc, 4px)
58
+
59
+</style>

+ 91 - 0
src/components/checker/checker.vue

@@ -0,0 +1,91 @@
1
+<template>
2
+  <ul class="cube-checker">
3
+    <slot>
4
+      <cube-checker-item
5
+        v-for="(option, index) in options"
6
+        :option="option"
7
+        :key="index" />
8
+    </slot>
9
+  </ul>
10
+</template>
11
+<script type="text/ecmascript-6">
12
+  import CubeCheckerItem from './checker-item.vue'
13
+  const COMPONENT_NAME = 'cube-checker'
14
+  const EVENT_INPUT = 'input'
15
+
16
+  export default {
17
+    name: COMPONENT_NAME,
18
+    props: {
19
+      value: [String, Number, Array],
20
+      options: {
21
+        type: Array,
22
+        default() {
23
+          /* istanbul ignore next */
24
+          return []
25
+        }
26
+      },
27
+      type: {
28
+        type: String,
29
+        default: 'checkbox'
30
+      },
31
+      min: {
32
+        type: Number,
33
+        default: 0
34
+      },
35
+      max: {
36
+        type: Number,
37
+        default() {
38
+          return this.options.length
39
+        }
40
+      }
41
+    },
42
+    data () {
43
+      return {
44
+        currentValue: this.value
45
+      }
46
+    },
47
+    computed: {
48
+      isRadio() {
49
+        return this.type === 'radio'
50
+      }
51
+    },
52
+    watch: {
53
+      value (newValue) {
54
+        this.currentValue = newValue
55
+      },
56
+      currentValue (val) {
57
+        this.$emit(EVENT_INPUT, val)
58
+      }
59
+    },
60
+    methods: {
61
+      check(option) {
62
+        if (this.isRadio) {
63
+          this.checkRadio(option)
64
+        } else {
65
+          this.checkCheckbox(option)
66
+        }
67
+      },
68
+      checkRadio(option) {
69
+        this.currentValue = option.value
70
+      },
71
+      checkCheckbox(option) {
72
+        const value = option.value
73
+        const currentValue = this.currentValue
74
+        const valueLen = currentValue.length
75
+        const min = this.min
76
+        const max = this.max
77
+
78
+        const index = currentValue.indexOf(value)
79
+
80
+        if (index > -1) {
81
+          (valueLen > min) && currentValue.splice(index, 1)
82
+        } else {
83
+          (valueLen < max) && currentValue.push(value)
84
+        }
85
+      }
86
+    },
87
+    components: {
88
+      CubeCheckerItem
89
+    }
90
+  }
91
+</script>

+ 28 - 0
src/components/cube-button-group.vue

@@ -0,0 +1,28 @@
1
+<template>
2
+  <div class="cube-btn-group" :class="direction"><slot></slot></div>
3
+</template>
4
+
5
+<script>
6
+  export default {
7
+    props: {
8
+      direction: {
9
+        type: String,
10
+        default: 'column'
11
+      }
12
+    }
13
+  }
14
+</script>
15
+
16
+<style lang="stylus" rel="stylesheet/stylus">
17
+  .cube-btn-group
18
+    > button
19
+      margin: 10px 0
20
+    &.row > button
21
+      display: inline-block
22
+      width: auto
23
+      margin: 10px 5px
24
+      &:first-child
25
+        margin-left: 0
26
+      &:last-child
27
+        margin-right: 0
28
+</style>

+ 139 - 0
src/components/cube-page.vue

@@ -0,0 +1,139 @@
1
+<template>
2
+  <div class="cube-page" :class="type">
3
+    <header class="header">
4
+      <h1>{{title}}</h1>
5
+      <i @click="back" class="cubeic-back"></i>
6
+    </header>
7
+    <div class="wrapper">
8
+      <section v-show="desc" class="desc"><slot name="desc">{{desc}}</slot></section>
9
+      <main class="content">
10
+        <slot name="content">{{content}}</slot>
11
+      </main>
12
+    </div>
13
+  </div>
14
+</template>
15
+
16
+<script type="text/ecmascript-6">
17
+  export default {
18
+    props: {
19
+      title: {
20
+        type: String,
21
+        default: '',
22
+        required: true
23
+      },
24
+      type: {
25
+        type: String,
26
+        default: ''
27
+      },
28
+      desc: {
29
+        type: String,
30
+        default: ''
31
+      },
32
+      content: {
33
+        type: String,
34
+        default: ''
35
+      }
36
+    },
37
+    methods: {
38
+      back() {
39
+        this.$router.back()
40
+      }
41
+    }
42
+  }
43
+</script>
44
+
45
+<style lang="stylus" rel="stylesheet/stylus">
46
+  @import "~@/common/stylus/variable.styl"
47
+
48
+  .cube-page
49
+    position: absolute
50
+    z-index: 10
51
+    top: 0
52
+    left: 0
53
+    width: 100%
54
+    height: 100%
55
+    background: #efeff4
56
+    .header
57
+      position: relative
58
+      height: 44px
59
+      line-height: 44px
60
+      text-align: center
61
+      background-color: #edf0f4
62
+      box-shadow: 0 1px 6px #ccc
63
+      -webkit-backface-visibility: hidden
64
+      backface-visibility: hidden
65
+      z-index: 5
66
+      h1
67
+        font-size: 16px
68
+        font-weight: 700
69
+      .cubeic-back
70
+        position: absolute
71
+        top: 0
72
+        left: 0
73
+        padding: 0 15px
74
+        color: #fc9153
75
+    >.wrapper
76
+      height: calc(100% - 44px)
77
+      overflow-x: hidden
78
+      overflow-y: auto
79
+      // -webkit-overflow-scrolling: touch
80
+      .desc
81
+        padding: 10px
82
+        margin: 10px 0
83
+        line-height: 20px
84
+        font-size: 14px
85
+      .content
86
+        margin: 10px
87
+
88
+    &.option-demo
89
+      .wrapper
90
+        background-color: $color-white
91
+      .title
92
+        font-size: $fontsize-large
93
+        font-weight: 500
94
+        color: $color-dark-grey
95
+        padding: 15px
96
+        border-bottom: 1px solid rgba(0, 0, 0, .1)
97
+        margin-bottom: 15px
98
+      .options
99
+        margin-bottom: 15px
100
+      .option-list
101
+        .group
102
+          margin-bottom: 15px
103
+          border: 1px solid rgba(0, 0, 0, .1)
104
+          border-radius: $radius-size-medium
105
+        .item
106
+          height: 52px
107
+          border-bottom: 1px solid rgba(0, 0, 0, .1)
108
+          &.sub
109
+            font-size: $fontsize-medium
110
+            background-color: $color-light-grey-opacity
111
+            &.first
112
+              box-shadow: 0 1px 1px 1px #eee inset
113
+            &.last
114
+              border-bottom: none
115
+      .demo
116
+        margin-bottom: 15px
117
+      .methods
118
+        .method-list
119
+          .group
120
+            margin-bottom: 15px
121
+            border: 1px solid rgba(0, 0, 0, .1)
122
+            border-radius: $radius-size-medium
123
+          .item
124
+          button
125
+            height: 40px
126
+            font-size: $fontsize-large
127
+          .item
128
+            background-color: $color-active-light-gray
129
+            border-bottom: 1px solid rgba(0, 0, 0, .1)
130
+          button
131
+            width: 100%
132
+            border-bottom-left-radius: $radius-size-medium
133
+            border-bottom-right-radius: $radius-size-medium
134
+            background-color: $color-orange
135
+            box-shadow: 0 0 0 1px $color-orange
136
+            border: none
137
+            outline: none
138
+            color: $color-white
139
+</style>

+ 16 - 0
src/components/cube-view.vue

@@ -0,0 +1,16 @@
1
+<template>
2
+  <transition name="page-move">
3
+    <router-view class="cube-view"></router-view>
4
+  </transition>
5
+</template>
6
+
7
+<script type="text/ecmascript-6">
8
+  export default {}
9
+</script>
10
+
11
+<style lang="stylus" rel="stylesheet/stylus">
12
+  .page-move-enter, .page-move-leave-active
13
+    transform: translate(100%, 0)
14
+  .page-move-enter-active, .page-move-leave-active
15
+    transition: transform .3s
16
+</style>

+ 99 - 0
src/components/date-picker.vue

@@ -0,0 +1,99 @@
1
+<template>
2
+  <cube-cascade-picker
3
+    ref="cascadePicker"
4
+    title="Date Picker"
5
+    :data="data"
6
+    :selectedIndex="selectedIndex"
7
+    @select="select"
8
+    @cancel="cancel">
9
+  </cube-cascade-picker>
10
+</template>
11
+
12
+<script>
13
+  const COMPONENT_NAME = 'date-picker'
14
+  const EVENT_SELECT = 'select'
15
+  const EVENT_CANCEL = 'cancel'
16
+
17
+  export default {
18
+    name: COMPONENT_NAME,
19
+    props: {
20
+      min: {
21
+        type: Array,
22
+        default() {
23
+          return [2010, 2, 1]
24
+        }
25
+      },
26
+      max: {
27
+        type: Array,
28
+        default() {
29
+          return [2020, 2, 1]
30
+        }
31
+      },
32
+      selectedIndex: {
33
+        type: Array,
34
+        default() {
35
+          return [0, 0, 0]
36
+        }
37
+      }
38
+    },
39
+    data() {
40
+      return {
41
+      }
42
+    },
43
+    computed: {
44
+      data() {
45
+        let data = range(this.min[0], this.max[0], false, '年')
46
+
47
+        data.forEach(year => {
48
+          let minMonth = year.value === this.min[0] ? this.min[1] : 1
49
+          let maxMonth = year.value === this.max[0] ? this.max[1] : 12
50
+
51
+          year.children = range(minMonth, maxMonth, false, '月')
52
+          year.children.forEach(month => {
53
+            let day = 30
54
+            if ([1, 3, 5, 7, 8, 10, 12].indexOf(month.value) > -1) {
55
+              day = 31
56
+            } else {
57
+              if (month.value === 2) {
58
+                day = !(year.value % 400) || (!(year.value % 4) && year.value % 100) ? 29 : 28
59
+              }
60
+            }
61
+
62
+            let minDay = year.value === this.min[0] && month.value === this.min[1] ? this.min[2] : 1
63
+            let maxDay = year.value === this.max[0] && month.value === this.max[1] ? this.max[2] : day
64
+
65
+            month.children = range(minDay, maxDay, false, '日')
66
+          })
67
+        })
68
+
69
+        return data
70
+      }
71
+    },
72
+    methods: {
73
+      show() {
74
+        this.$refs.cascadePicker.show()
75
+      },
76
+      hide() {
77
+        this.$refs.cascadePicker.hide()
78
+      },
79
+      select(selectedVal, selectedIndex, selectedText) {
80
+        this.$emit(EVENT_SELECT, selectedVal, selectedIndex, selectedText)
81
+      },
82
+      cancel() {
83
+        this.$emit(EVENT_CANCEL)
84
+      }
85
+    }
86
+  }
87
+
88
+  function range(n, m, polyfill = false, unit = '') {
89
+    let arr = []
90
+    for (let i = n; i <= m; i++) {
91
+      let value = (polyfill && i < 10 ? '0' + i : i) + unit
92
+      arr.push({
93
+        text: value,
94
+        value: i
95
+      })
96
+    }
97
+    return arr
98
+  }
99
+</script>

+ 246 - 0
src/components/date-picker/date-picker.vue

@@ -0,0 +1,246 @@
1
+<template>
2
+  <cube-cascade-picker
3
+    v-model="isVisible"
4
+    :data="data"
5
+    :selected-index="selectedIndex"
6
+    :title="title"
7
+    :subtitle="subtitle"
8
+    :cancel-txt="_cancelTxt"
9
+    :confirm-txt="_confirmTxt"
10
+    :swipe-time="swipeTime"
11
+    :z-index="zIndex"
12
+    :mask-closable="maskClosable"
13
+    @select="_select"
14
+    @cancel="_cancel"
15
+    @change="_change">
16
+  </cube-cascade-picker>
17
+</template>
18
+
19
+<script>
20
+  import visibilityMixin from '../../common/mixins/visibility'
21
+  import popupMixin from '../../common/mixins/popup'
22
+  import pickerMixin from '../../common/mixins/picker'
23
+  import localeMixin from '../../common/mixins/locale'
24
+  import { deepAssign, findIndex } from '../../common/helpers/util'
25
+  import { computeNatureMaxDay, formatType } from '../../common/lang/date'
26
+
27
+  const COMPONENT_NAME = 'cube-date-picker'
28
+  const EVENT_SELECT = 'select'
29
+  const EVENT_CANCEL = 'cancel'
30
+  const EVENT_CHANGE = 'change'
31
+
32
+  const TYPE_LIST = ['year', 'month', 'date', 'hour', 'minute', 'second']
33
+  const NATURE_BOUNDARY_MAP = {
34
+    month: {
35
+      natureMin: 1,
36
+      natureMax: 12
37
+    },
38
+    date: {
39
+      natureMin: 1,
40
+      natureMax: 31
41
+    },
42
+    hour: {
43
+      natureMin: 0,
44
+      natureMax: 23
45
+    },
46
+    minute: {
47
+      natureMin: 0,
48
+      natureMax: 59
49
+    },
50
+    second: {
51
+      natureMin: 0,
52
+      natureMax: 59
53
+    }
54
+  }
55
+
56
+  const DEFAULT_FORMAT = {
57
+    year: 'YYYY',
58
+    month: 'M',
59
+    date: 'D',
60
+    hour: 'hh',
61
+    minute: 'mm',
62
+    second: 'ss'
63
+  }
64
+
65
+  export default {
66
+    name: COMPONENT_NAME,
67
+    mixins: [visibilityMixin, popupMixin, pickerMixin, localeMixin],
68
+    props: {
69
+      min: {
70
+        type: [Date, Array],
71
+        default() {
72
+          return new Date(2010, 1, 1)
73
+        }
74
+      },
75
+      max: {
76
+        type: [Date, Array],
77
+        default() {
78
+          return new Date(2020, 12, 31)
79
+        }
80
+      },
81
+      startColumn: {
82
+        type: String,
83
+        default() {
84
+          return 'year'
85
+        }
86
+      },
87
+      columnCount: {
88
+        type: Number,
89
+        default: 3
90
+      },
91
+      format: {
92
+        type: Object,
93
+        default() {
94
+          return {}
95
+        }
96
+      },
97
+      value: {
98
+        type: [Date, Array],
99
+        default() {
100
+          return this.min
101
+        }
102
+      }
103
+    },
104
+    computed: {
105
+      formatConfig() {
106
+        const formatConfig = Object.assign({}, DEFAULT_FORMAT)
107
+        deepAssign(formatConfig, this.format)
108
+
109
+        return formatConfig
110
+      },
111
+      natureRangeCache() {
112
+        const natureRangeCache = {
113
+          hour: [],
114
+          minute: [],
115
+          second: []
116
+        }
117
+
118
+        Object.keys(natureRangeCache).forEach((key) => {
119
+          natureRangeCache[key] = this._range(key, NATURE_BOUNDARY_MAP[key].natureMin, NATURE_BOUNDARY_MAP[key].natureMax)
120
+        })
121
+
122
+        return natureRangeCache
123
+      },
124
+      startIndex() {
125
+        const startIndex = TYPE_LIST.indexOf(this.startColumn)
126
+        return startIndex < 0 ? 0 : startIndex
127
+      },
128
+      minArray() {
129
+        return this.min instanceof Date
130
+                ? dateToArray(this.min).slice(this.startIndex, this.startIndex + this.columnCount)
131
+                : this.min
132
+      },
133
+      maxArray() {
134
+        return this.max instanceof Date
135
+                ? dateToArray(this.max).slice(this.startIndex, this.startIndex + this.columnCount)
136
+                : this.max
137
+      },
138
+      valueArray() {
139
+        return this.value instanceof Date
140
+                ? dateToArray(this.value).slice(this.startIndex, this.startIndex + this.columnCount)
141
+                : this.value
142
+      },
143
+      data() {
144
+        const data = []
145
+        this._generateData(this.startIndex, 0, data)
146
+        return data
147
+      },
148
+      selectedIndex() {
149
+        const selectedIndex = []
150
+        let data = this.data
151
+        let index
152
+
153
+        for (let i = 0; i < this.columnCount && i < 6 - this.startIndex; i++) {
154
+          index = findIndex(data, (item) => {
155
+            return this.valueArray[i] && item.value === this.valueArray[i]
156
+          })
157
+          selectedIndex[i] = index !== -1 ? index : 0
158
+          data = data[selectedIndex[i]] && data[selectedIndex[i]].children
159
+        }
160
+
161
+        return selectedIndex
162
+      }
163
+    },
164
+    methods: {
165
+      _select(selectedVal, selectedIndex, selectedText) {
166
+        this.$emit(EVENT_SELECT, this._arrayToDate(selectedVal), selectedVal, selectedText)
167
+      },
168
+      _cancel() {
169
+        this.$emit(EVENT_CANCEL)
170
+      },
171
+      _change(i, newIndex) {
172
+        this.$emit(EVENT_CHANGE, i, newIndex)
173
+      },
174
+      _generateData(i, count, item) {
175
+        if (count === 0) {
176
+          const min = i === 0 ? this.minArray[0] : Math.max(this.minArray[0], NATURE_BOUNDARY_MAP[TYPE_LIST[i]].natureMin)
177
+          const max = i === 0 ? this.maxArray[0] : Math.min(this.maxArray[0], NATURE_BOUNDARY_MAP[TYPE_LIST[i]].natureMax)
178
+          item.push.apply(item, this._range(TYPE_LIST[i], min, max, true, true))
179
+        } else {
180
+          if (i < 3 || item.isMin || item.isMax) {
181
+            const natureMax = i === 2 ? computeNatureMaxDay(item.value, item.year) : NATURE_BOUNDARY_MAP[TYPE_LIST[i]].natureMax
182
+            const min = item.isMin ? Math.max(this.minArray[count], NATURE_BOUNDARY_MAP[TYPE_LIST[i]].natureMin) : NATURE_BOUNDARY_MAP[TYPE_LIST[i]].natureMin
183
+            const max = item.isMax ? Math.min(this.maxArray[count], natureMax) : natureMax
184
+
185
+            const storageYear = i === 1 && this.startIndex === 0 && this.columnCount >= 3 && item.value
186
+            item.children = this._range(TYPE_LIST[i], min, max, item.isMin, item.isMax, storageYear)
187
+          } else {
188
+            item.children = this.natureRangeCache[TYPE_LIST[i]]
189
+          }
190
+        }
191
+        if (count < this.columnCount - 1 && i < 5) {
192
+          (item.children || item).forEach(subItem => {
193
+            (!subItem.children || subItem.isMin || subItem.isMax) && this._generateData(i + 1, count + 1, subItem)
194
+          })
195
+        }
196
+      },
197
+      _arrayToDate(selectedVal) {
198
+        const args = []
199
+        const defaultDateArray = dateToArray(new Date(0))
200
+
201
+        for (let i = 0; i < 6; i++) {
202
+          if (i < this.startIndex) {
203
+            args[i] = defaultDateArray[i]
204
+          } else if (i >= this.startIndex + this.columnCount) {
205
+            args[i] = NATURE_BOUNDARY_MAP[TYPE_LIST[i]].natureMin
206
+          } else {
207
+            args[i] = selectedVal[i - this.startIndex]
208
+          }
209
+        }
210
+        // Month need to subtract 1.
211
+        args[1]--
212
+
213
+        return new Date(...args)
214
+      },
215
+      _range(type, min, max, fatherIsMin, fatherIsMax, year = 0) {
216
+        if (!this._rangeCache) {
217
+          this._rangeCache = {}
218
+        }
219
+        const k = type + year + min + max + fatherIsMin + fatherIsMax
220
+        if (this._rangeCache[k]) {
221
+          return this._rangeCache[k]
222
+        }
223
+        const arr = []
224
+        const format = this.formatConfig[type]
225
+        for (let i = min; i <= max; i++) {
226
+          const object = {
227
+            text: formatType(type, format, i, 'i'),
228
+            value: i
229
+          }
230
+
231
+          if (fatherIsMin && i === min) object.isMin = true
232
+          if (fatherIsMax && i === max) object.isMax = true
233
+          if (year) object.year = year
234
+
235
+          arr.push(object)
236
+        }
237
+        this._rangeCache[k] = arr
238
+        return arr
239
+      }
240
+    }
241
+  }
242
+
243
+  function dateToArray(date) {
244
+    return [date.getFullYear(), date.getMonth() + 1, date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds()]
245
+  }
246
+</script>

+ 310 - 0
src/components/dialog/dialog.vue

@@ -0,0 +1,310 @@
1
+<template>
2
+  <transition name="cube-dialog-fade">
3
+    <cube-popup
4
+      type="dialog"
5
+      :z-index="zIndex"
6
+      :mask="true"
7
+      :center="true"
8
+      v-show="isVisible"
9
+      @mask-click="maskClick"
10
+      >
11
+      <div class="cube-dialog-main">
12
+        <span class="cube-dialog-close" v-show="showClose" @click="close"><i class="cubeic-close"></i></span>
13
+        <div :class="containerClass">
14
+          <p class="cube-dialog-icon" v-if="icon"><i :class="icon"></i></p>
15
+          <h2 v-if="title || $slots.title" class="cube-dialog-title">
16
+            <slot name="title">
17
+              <p class="cube-dialog-title-def">{{title}}</p>
18
+            </slot>
19
+          </h2>
20
+          <div class="cube-dialog-content">
21
+            <slot name="content">
22
+              <div class="cube-dialog-content-def">
23
+                <p v-html="content" v-if="content"></p>
24
+                <cube-input v-bind="prompt" v-model="promptValue" v-if="isPrompt" />
25
+              </div>
26
+            </slot>
27
+          </div>
28
+          <div class="cube-dialog-btns" :class="{'border-right-1px': isConfirm || isPrompt}">
29
+            <slot name="btns">
30
+              <a :href="_cancelBtn.href" class="cube-dialog-btn border-top-1px" :class="{'cube-dialog-btn_highlight': _cancelBtn.active, 'cube-dialog-btn_disabled': _cancelBtn.disabled}" v-if="isConfirm || isPrompt" @click="cancel">{{_cancelBtn.text}}</a>
31
+              <a :href="_confirmBtn.href" class="cube-dialog-btn border-top-1px" :class="{'cube-dialog-btn_highlight': _confirmBtn.active, 'cube-dialog-btn_disabled': _confirmBtn.disabled}" @click="confirm">{{_confirmBtn.text}}</a>
32
+            </slot>
33
+          </div>
34
+        </div>
35
+      </div>
36
+    </cube-popup>
37
+  </transition>
38
+</template>
39
+
40
+<script type="text/ecmascript-6">
41
+  import CubePopup from '../popup/popup.vue'
42
+  import CubeInput from '../input/input.vue'
43
+  import visibilityMixin from '../../common/mixins/visibility'
44
+  import popupMixin from '../../common/mixins/popup'
45
+  import localeMixin from '../../common/mixins/locale'
46
+
47
+  const COMPONENT_NAME = 'cube-dialog'
48
+  const EVENT_CONFIRM = 'confirm'
49
+  const EVENT_CANCEL = 'cancel'
50
+  const EVENT_CLOSE = 'close'
51
+
52
+  const defHref = 'javascript:;'
53
+  const defConfirmBtn = {
54
+    textType: 'ok',
55
+    active: true,
56
+    disabled: false,
57
+    href: defHref
58
+  }
59
+  const defCancelBtn = {
60
+    textType: 'cancel',
61
+    active: false,
62
+    disabled: false,
63
+    href: defHref
64
+  }
65
+  const parseBtn = function (btn, defBtn) {
66
+    if (typeof btn === 'string') {
67
+      btn = {
68
+        text: btn
69
+      }
70
+    }
71
+    const text = defBtn && this.$t(defBtn.textType)
72
+    return Object.assign({}, defBtn, { text }, btn)
73
+  }
74
+
75
+  export default {
76
+    name: COMPONENT_NAME,
77
+    mixins: [visibilityMixin, popupMixin, localeMixin],
78
+    props: {
79
+      type: {
80
+        type: String,
81
+        default: 'alert'
82
+      },
83
+      prompt: {
84
+        type: Object,
85
+        default() {
86
+          return {
87
+            value: '',
88
+            placeholder: ''
89
+          }
90
+        }
91
+      },
92
+      icon: {
93
+        type: String,
94
+        default: ''
95
+      },
96
+      title: {
97
+        type: String,
98
+        default: ''
99
+      },
100
+      content: {
101
+        type: String,
102
+        default: ''
103
+      },
104
+      showClose: {
105
+        type: Boolean,
106
+        default: false
107
+      },
108
+      confirmBtn: {
109
+        type: [Object, String],
110
+        default() {
111
+          return {
112
+            ...defConfirmBtn
113
+          }
114
+        }
115
+      },
116
+      cancelBtn: {
117
+        type: [Object, String],
118
+        default() {
119
+          return {
120
+            ...defCancelBtn
121
+          }
122
+        }
123
+      }
124
+    },
125
+    data() {
126
+      return {
127
+        defHref,
128
+        promptValue: this.prompt.value
129
+      }
130
+    },
131
+    computed: {
132
+      _confirmBtn() {
133
+        return parseBtn.call(this, this.confirmBtn, defConfirmBtn)
134
+      },
135
+      _cancelBtn() {
136
+        return parseBtn.call(this, this.cancelBtn, defCancelBtn)
137
+      },
138
+      isConfirm() {
139
+        return this.type === 'confirm'
140
+      },
141
+      isPrompt() {
142
+        return this.type === 'prompt'
143
+      },
144
+      containerClass() {
145
+        return `cube-dialog-${this.type}`
146
+      }
147
+    },
148
+    watch: {
149
+      'prompt.value': {
150
+        handler: function (newVal) {
151
+          this.promptValue = newVal
152
+        }
153
+      }
154
+    },
155
+    methods: {
156
+      maskClick(e) {
157
+        this.maskClosable && this.cancel(e)
158
+      },
159
+      confirm(e) {
160
+        if (this._confirmBtn.disabled) {
161
+          return
162
+        }
163
+        this.hide()
164
+        this.$emit(EVENT_CONFIRM, e, this.promptValue)
165
+      },
166
+      cancel(e) {
167
+        if (this._cancelBtn.disabled) {
168
+          return
169
+        }
170
+        this.hide()
171
+        this.$emit(EVENT_CANCEL, e)
172
+      },
173
+      close(e) {
174
+        this.hide()
175
+        this.$emit(EVENT_CLOSE, e)
176
+      }
177
+    },
178
+    components: {
179
+      CubePopup,
180
+      CubeInput
181
+    }
182
+  }
183
+</script>
184
+
185
+<style lang="stylus" rel="stylesheet/stylus">
186
+  @require "../../common/stylus/variable.styl"
187
+  @require "../../common/stylus/mixin.styl"
188
+
189
+  .cube-dialog-main
190
+    width: 270px
191
+    padding: 0
192
+    text-align: center
193
+    overflow: hidden
194
+    border-radius: 2px
195
+    background-color: $dialog-bgc
196
+  .cube-dialog-confirm, .cube-dialog-alert
197
+    position: relative
198
+    overflow: hidden
199
+  .cube-dialog-icon
200
+    margin-top: 20px
201
+    margin-bottom: 16px
202
+    line-height: 1
203
+    color: $dialog-icon-color
204
+    font-size: $fontsize-large-xxxx
205
+    i
206
+      display: inline-block
207
+      width: 30px
208
+      height: 30px
209
+      padding: 10px
210
+      box-sizing: content-box
211
+      border-radius: 50%
212
+      background-color: $dialog-icon-bgc
213
+    +
214
+      .cube-dialog-title
215
+        .cube-dialog-title-def
216
+          margin-top: 0
217
+    +
218
+      .cube-dialog-content
219
+        margin-top: -4px
220
+  .cube-dialog-title
221
+    color: $dialog-title-color
222
+    font-size: $fontsize-large
223
+    line-height: 1
224
+    +
225
+      .cube-dialog-content
226
+        margin-top: 12px
227
+  .cube-dialog-title-def
228
+    margin: 30px 16px 0
229
+    overflow: hidden
230
+    white-space: nowrap
231
+  .cube-dialog-content
232
+    margin: 16px 0
233
+    text-align: left
234
+    color: $dialog-color
235
+    font-size: $fontsize-medium
236
+    line-height: 22px
237
+  .cube-dialog-content-def
238
+    padding: 0 16px
239
+    > p
240
+      display: table
241
+      margin: auto
242
+      + .cube-input
243
+        margin-top: 12px
244
+  .cube-dialog-confirm, .cube-dialog-prompt
245
+    .cube-dialog-btns
246
+      .cube-dialog-btn
247
+        width: 50%
248
+        float: left
249
+      &.border-right-1px
250
+        &::after
251
+          right: 50%
252
+          border-color: $dialog-btns-split-color
253
+  .cube-dialog-close
254
+    display: flex
255
+    align-items: center
256
+    justify-content: center
257
+    z-index: 1
258
+    position: absolute
259
+    top: 0
260
+    right: 0
261
+    width: 32px
262
+    height: 32px
263
+    color: $dialog-close-color
264
+    font-size: $fontsize-large-x
265
+  .cube-dialog-btns
266
+    overflow: hidden
267
+    width: 100%
268
+    font-size: 0
269
+  .cube-dialog-btn
270
+    display: inline-block
271
+    width: 100%
272
+    padding: 17px 10px
273
+    margin: 0
274
+    font-size: $fontsize-large
275
+    line-height: 1
276
+    text-align: center
277
+    text-decoration: none
278
+    color: $dialog-btn-color
279
+    background-color: $dialog-btn-bgc
280
+    background-clip: padding-box
281
+    box-sizing: border-box
282
+    &:active
283
+      background-color: $dialog-btn-active-bgc
284
+  .cube-dialog-btn_highlight
285
+    color: $dialog-btn-highlight-color
286
+    &:active
287
+      background-color: $dialog-btn-highlight-active-bgc
288
+  .cube-dialog-btn_disabled
289
+    color: $dialog-btn-disabled-color
290
+    &:active
291
+      background-color: $dialog-btn-disabled-active-bgc
292
+
293
+  .cube-dialog-fade-enter-active
294
+    animation: dialog-fadein .4s
295
+    .cube-dialog-main
296
+      animation: dialog-zoom .4s
297
+
298
+  @keyframes dialog-fadein
299
+    0%
300
+      opacity: 0
301
+    100%
302
+      opacity: 1
303
+  @keyframes dialog-zoom
304
+    0%
305
+      transform: scale(0)
306
+    50%
307
+      transform: scale(1.1)
308
+    100%
309
+      transform: scale(1)
310
+</style>

+ 61 - 0
src/components/drawer/drawer-item.vue

@@ -0,0 +1,61 @@
1
+<template>
2
+  <li
3
+    class="cube-drawer-item border-bottom-1px"
4
+    :class="itemClass"
5
+    @click="clickItem(item)"
6
+  >
7
+    <slot>
8
+      {{item.text || item}}
9
+    </slot>
10
+  </li>
11
+</template>
12
+
13
+<script type="text/ecmascript-6">
14
+  const COMPONENT_NAME = 'cube-drawer-item'
15
+
16
+  export default {
17
+    name: COMPONENT_NAME,
18
+    props: {
19
+      item: {
20
+        type: [String, Object],
21
+        default: ''
22
+      },
23
+      index: {
24
+        type: Number,
25
+        default: -1
26
+      }
27
+    },
28
+    computed: {
29
+      itemClass() {
30
+        return this.$parent.$parent.selectedIndex === this.index ? 'cube-drawer-item_active' : ''
31
+      }
32
+    },
33
+    methods: {
34
+      clickItem(item) {
35
+        this.$parent.$parent.itemClickHandler(item, this.index)
36
+      }
37
+    }
38
+  }
39
+</script>
40
+<style lang="stylus" rel="stylesheet/stylus">
41
+  @require "../../common/stylus/variable.styl"
42
+  @require "../../common/stylus/mixin.styl"
43
+
44
+  .cube-drawer-item
45
+    padding: 0 20px
46
+    height: 50px
47
+    line-height: 50px
48
+    white-space: nowrap
49
+    overflow: hidden
50
+    font-size: 15px
51
+    &::after
52
+      left: 20px
53
+    &:last-child
54
+      &::after
55
+        display: none
56
+    &:first-child
57
+      &::after
58
+        display: block
59
+  .cube-drawer-item_active
60
+    background: $drawer-item-active-bgc
61
+</style>

+ 108 - 0
src/components/drawer/drawer-panel.vue

@@ -0,0 +1,108 @@
1
+<template>
2
+  <transition name="cube-drawer-move">
3
+    <div class="cube-drawer-panel" v-show="isVisible">
4
+      <div class="cube-drawer-scroll-wrapper">
5
+        <cube-scroll ref="scroll" :data="data">
6
+          <ul class="cube-drawer-list">
7
+            <slot>
8
+              <cube-drawer-item v-for="(item, i) in data" :item="item" :key="i" :index="i" />
9
+            </slot>
10
+          </ul>
11
+        </cube-scroll>
12
+      </div>
13
+    </div>
14
+  </transition>
15
+</template>
16
+
17
+<script type="text/ecmascript-6">
18
+  import visibilityMixin from '../../common/mixins/visibility'
19
+  import CubeScroll from '../scroll/scroll.vue'
20
+  import CubeDrawerItem from './drawer-item.vue'
21
+
22
+  const COMPONENT_NAME = 'cube-drawer-panel'
23
+
24
+  export default {
25
+    name: COMPONENT_NAME,
26
+    mixins: [visibilityMixin],
27
+    props: {
28
+      data: {
29
+        type: Array,
30
+        default() {
31
+          /* istanbul ignore next */
32
+          return []
33
+        }
34
+      },
35
+      index: {
36
+        type: Number,
37
+        default: -1
38
+      }
39
+    },
40
+    computed: {
41
+      selectedIndex() {
42
+        const selectedIndex = this.$parent.selected[this.index]
43
+        return selectedIndex === undefined ? -1 : selectedIndex
44
+      }
45
+    },
46
+    watch: {
47
+      data() {
48
+        this.scrollToTop()
49
+      },
50
+      isVisible() {
51
+        this.$nextTick(() => {
52
+          this.refresh()
53
+        })
54
+      }
55
+    },
56
+    mounted() {
57
+      this.$parent.addPanel(this)
58
+    },
59
+    beforeDestroy() {
60
+      this.$parent.removePanel(this)
61
+    },
62
+    methods: {
63
+      refresh() {
64
+        this.$refs.scroll.refresh()
65
+      },
66
+      scrollToTop() {
67
+        this.$refs.scroll.scroll && this.$refs.scroll.scroll.scrollTo(0, 0, 0)
68
+      },
69
+      itemClickHandler(item, index) {
70
+        if (this.selectedIndex !== index) {
71
+          this.$parent.changeHandler(this.index, item, index)
72
+        }
73
+      }
74
+    },
75
+    components: {
76
+      CubeScroll,
77
+      CubeDrawerItem
78
+    }
79
+  }
80
+</script>
81
+<style lang="stylus" rel="stylesheet/stylus">
82
+  @require "../../common/stylus/variable.styl"
83
+  @require "../../common/stylus/mixin.styl"
84
+
85
+  .cube-drawer-panel
86
+    position: relative
87
+    z-index: 1
88
+    flex: 1
89
+    width: 170px
90
+    overflow: hidden
91
+    background-color: $drawer-panel-bgc
92
+    box-shadow: 0 1px 2px rgba(0, 0, 0, .2)
93
+    + .cube-drawer-panel
94
+      margin-left: -67px
95
+    &:first-child
96
+      box-shadow: none
97
+  .cube-drawer-scroll-wrapper // fix flex item height: 100% bug in android
98
+    position: absolute
99
+    top: 0
100
+    left: 0
101
+    width: 100%
102
+    height: 100%
103
+
104
+  .cube-drawer-move-enter, .cube-drawer-move-leave-to
105
+    transform: translate(67px, 0)
106
+  .cube-drawer-move-enter-active, .cube-drawer-move-leave-active
107
+    transition: transform .3s ease-in-out
108
+</style>

+ 226 - 0
src/components/drawer/drawer.vue

@@ -0,0 +1,226 @@
1
+<template>
2
+  <div class="cube-drawer" @click="drawerClick" v-show="isVisible">
3
+    <div class="cube-drawer-main" :style="slideStyle" @click.stop @transitionend="transitionend">
4
+      <div class="cube-drawer-title" v-show="$slots.title || title">
5
+        <slot name="title">{{title}}</slot>
6
+      </div>
7
+      <div class="cube-drawer-panels" @transitionend.stop>
8
+        <slot>
9
+          <cube-drawer-panel
10
+            v-for="(panel, index) in data"
11
+            :key="index"
12
+            :index="index"
13
+            :data="panel" />
14
+        </slot>
15
+      </div>
16
+    </div>
17
+  </div>
18
+</template>
19
+
20
+<script type="text/ecmascript-6">
21
+  import { prefixStyle } from '../../common/helpers/dom'
22
+  import CubeDrawerPanel from './drawer-panel.vue'
23
+  import visibilityMixin from '../../common/mixins/visibility'
24
+  import popupMixin from '../../common/mixins/popup'
25
+
26
+  const COMPONENT_NAME = 'cube-drawer'
27
+  const EVENT_CHANGE = 'change'
28
+  const EVENT_SELECT = 'select'
29
+  const EVENT_CANCEL = 'cancel'
30
+
31
+  const transform = prefixStyle('transform')
32
+
33
+  export default {
34
+    name: COMPONENT_NAME,
35
+    mixins: [visibilityMixin, popupMixin],
36
+    props: {
37
+      title: {
38
+        type: String,
39
+        default: ''
40
+      },
41
+      data: {
42
+        type: Array,
43
+        default() {
44
+          /* istanbul ignore next */
45
+          return []
46
+        }
47
+      },
48
+      selectedIndex: {
49
+        type: Array,
50
+        default() {
51
+          /* istanbul ignore next */
52
+          return []
53
+        }
54
+      }
55
+    },
56
+    data() {
57
+      return {
58
+        index: -1,
59
+        selectedVal: [],
60
+        selectedText: [],
61
+        selected: [...this.selectedIndex],
62
+        slideStyle: {
63
+          [transform]: 'translate3d(0, 0, 0)'
64
+        }
65
+      }
66
+    },
67
+    watch: {
68
+      selectedIndex(newVal) {
69
+        this.selected = [...newVal]
70
+      },
71
+      index(newIndex, oldIndex) {
72
+        this.showPanel()
73
+        if (newIndex < oldIndex) {
74
+          this.hidePanel()
75
+        }
76
+      }
77
+    },
78
+    created() {
79
+      this.panels = []
80
+    },
81
+    methods: {
82
+      show() {
83
+        if (this.isVisible) {
84
+          return
85
+        }
86
+        this.isVisible = true
87
+
88
+        let len = this.data.length
89
+        for (let i = 0; i < len; i++) {
90
+          this.index = i
91
+          if (this.selected[i] < 0 || this.selected[i] === undefined) {
92
+            if (i > 0) {
93
+              const lastIndex = i - 1
94
+              const index = this.selected[lastIndex]
95
+              this.changeHandler(lastIndex, this.data[lastIndex][index], index)
96
+            }
97
+            break
98
+          }
99
+        }
100
+        this.computedStyle()
101
+      },
102
+      hide() {
103
+        this.slideStyle[transform] = 'translate3d(0, 0, 0)'
104
+        this.shouldHide = true
105
+      },
106
+      addPanel(panel) {
107
+        this.panels.push(panel)
108
+      },
109
+      removePanel(panel) {
110
+        const i = this.panels.indexOf(panel)
111
+        this.panels.splice(i, 1)
112
+      },
113
+      transitionend() {
114
+        if (this.shouldHide) {
115
+          this.isVisible = false
116
+          this.shouldHide = false
117
+        }
118
+      },
119
+      refill(panelIndex, data, index) {
120
+        this.$set(this.data, panelIndex, data)
121
+        this.index = panelIndex
122
+        this.selected = this.selected.slice(0, panelIndex)
123
+        this.selectedVal = this.selectedVal.slice(0, panelIndex)
124
+        this.selectedText = this.selectedText.slice(0, panelIndex)
125
+        if (index >= 0) {
126
+          this.$set(this.selected, panelIndex, index)
127
+          this.changeHandler(panelIndex, this.data[panelIndex][index], index)
128
+        }
129
+      },
130
+      showPanel() {
131
+        const index = this.index
132
+        let i = 0
133
+        while (i <= index) {
134
+          this.panels[i].show()
135
+          i++
136
+        }
137
+        this.computedStyle()
138
+      },
139
+      hidePanel() {
140
+        const len = this.data.length
141
+        let i = this.index + 1
142
+        while (i < len) {
143
+          this.panels[i].hide()
144
+          i++
145
+        }
146
+      },
147
+      computedStyle() {
148
+        this.$nextTick(() => {
149
+          let allWidth = 0
150
+          let i = 0
151
+          const index = this.index
152
+          while (i <= index) {
153
+            const el = this.panels[i].$el
154
+            allWidth += el.offsetWidth
155
+            const elStyle = window.getComputedStyle(el)
156
+            allWidth += parseInt(elStyle.marginLeft)
157
+            allWidth += parseInt(elStyle.marginRight)
158
+            i++
159
+          }
160
+          this.slideStyle[transform] = `translate3d(-${allWidth}px, 0, 0)`
161
+        })
162
+      },
163
+      changeHandler(panelIndex, item, index) {
164
+        if (typeof item === 'string') {
165
+          this.selectedVal[panelIndex] = item
166
+          this.selectedText[panelIndex] = item
167
+        } else {
168
+          this.selectedVal[panelIndex] = item.value
169
+          this.selectedText[panelIndex] = item.text
170
+        }
171
+        this.$set(this.selected, panelIndex, index)
172
+        if (panelIndex === (this.data.length - 1)) {
173
+          // last column
174
+          this.$emit(EVENT_SELECT, this.selectedVal, this.selected, this.selectedText)
175
+          this.hide()
176
+        } else {
177
+          this.$emit(EVENT_CHANGE, panelIndex, item, this.selectedVal, this.selected, this.selectedText)
178
+        }
179
+      },
180
+      drawerClick() {
181
+        this.hide()
182
+        this.$emit(EVENT_CANCEL)
183
+      }
184
+    },
185
+    components: {
186
+      CubeDrawerPanel
187
+    }
188
+  }
189
+</script>
190
+<style lang="stylus" rel="stylesheet/stylus">
191
+  @require "../../common/stylus/variable.styl"
192
+  @require "../../common/stylus/mixin.styl"
193
+
194
+  .cube-drawer
195
+    position: absolute
196
+    z-index: 5
197
+    top: 0
198
+    right: 0
199
+    bottom: 0
200
+    left: 0
201
+    overflow: hidden
202
+    color: $drawer-color
203
+  .cube-drawer-main
204
+    position: absolute
205
+    top: 0
206
+    left: 100%
207
+    bottom: 0
208
+    max-width: 90%
209
+    display: flex
210
+    flex-direction: column
211
+    overflow: hidden
212
+    transform: translate3d(0, 0, 0)
213
+    transition: transform .3s ease-in-out
214
+    box-shadow: -2px 0 2px rgba(0, 0, 0, .2)
215
+  .cube-drawer-title
216
+    position: relative
217
+    padding: 0 20px
218
+    height: 50px
219
+    line-height: 50px
220
+    border-bottom: 1px solid $drawer-title-bdc
221
+    font-size: $fontsize-large
222
+    background-color: $drawer-title-bgc
223
+  .cube-drawer-panels
224
+    display: flex
225
+    flex: 1
226
+</style>

+ 34 - 0
src/components/extend-popup.vue

@@ -0,0 +1,34 @@
1
+<template>
2
+  <cube-popup type="extend-popup" ref="popup">
3
+    <div class="cube-extend-popup-content" @click="hide">
4
+      <slot>{{content}}</slot>
5
+    </div>
6
+ </cube-popup>
7
+</template>
8
+<script type="text/ecmascript-6">
9
+  const COMPONENT_NAME = 'cube-extend-popup'
10
+  export default {
11
+    name: COMPONENT_NAME,
12
+    props: {
13
+      content: {
14
+        type: String
15
+      }
16
+    },
17
+    methods: {
18
+      show() {
19
+        this.$refs.popup.show()
20
+      },
21
+      hide() {
22
+        this.$refs.popup.hide()
23
+        this.$emit('hide')
24
+      }
25
+    }
26
+  }
27
+</script>
28
+<style lang="stylus" rel="stylesheet/stylus">
29
+  .cube-extend-popup
30
+    .cube-extend-popup-content
31
+      padding: 20px
32
+      color: #fff
33
+      background-color: rgba(0, 0, 0, .8)
34
+</style>

+ 34 - 0
src/components/form/components.js

@@ -0,0 +1,34 @@
1
+import CubeButton from '../button/button.vue'
2
+import CubeCheckbox from '../checkbox/checkbox.vue'
3
+import CubeCheckboxGroup from '../checkbox-group/checkbox-group.vue'
4
+import CubeChecker from '../checker/checker.vue'
5
+import CubeInput from '../input/input.vue'
6
+import CubeRadio from '../radio/radio.vue'
7
+import CubeRadioGroup from '../radio/radio-group.vue'
8
+import CubeRate from '../rate/rate.vue'
9
+import CubeSelect from '../select/select.vue'
10
+import CubeSwitch from '../switch/switch.vue'
11
+import CubeTextarea from '../textarea/textarea.vue'
12
+import CubeUpload from '../upload/upload.vue'
13
+
14
+const allComponents = [
15
+  CubeButton,
16
+  CubeCheckbox,
17
+  CubeCheckboxGroup,
18
+  CubeChecker,
19
+  CubeInput,
20
+  CubeRadio,
21
+  CubeRadioGroup,
22
+  CubeRate,
23
+  CubeSelect,
24
+  CubeSwitch,
25
+  CubeTextarea,
26
+  CubeUpload
27
+]
28
+
29
+const components = {}
30
+allComponents.forEach((Component) => {
31
+  components[Component.name] = Component
32
+})
33
+
34
+export default components

+ 11 - 0
src/components/form/fields/index.js

@@ -0,0 +1,11 @@
1
+import processTypes from './types'
2
+
3
+function processField(field) {
4
+  const _field = {
5
+    ...field
6
+  }
7
+  processTypes(_field)
8
+  return _field
9
+}
10
+
11
+export { processField }

+ 11 - 0
src/components/form/fields/props.js

@@ -0,0 +1,11 @@
1
+const toButtonHandler = (field, type) => {
2
+  field.type = 'button'
3
+  if (!field.props) {
4
+    field.props = {}
5
+  }
6
+  field.props.type = type
7
+}
8
+
9
+export {
10
+  toButtonHandler
11
+}

+ 18 - 0
src/components/form/fields/reset.js

@@ -0,0 +1,18 @@
1
+const typesResetMap = {
2
+  checkbox() {
3
+    /* istanbul ignore next */
4
+    return false
5
+  },
6
+  select() {
7
+    /* istanbul ignore next */
8
+    return null
9
+  }
10
+}
11
+function getResetValueByType(type) {
12
+  const resetHandler = typesResetMap[type]
13
+  return resetHandler && resetHandler()
14
+}
15
+
16
+export {
17
+  getResetValueByType
18
+}

+ 31 - 0
src/components/form/fields/types.js

@@ -0,0 +1,31 @@
1
+import {
2
+  boolRequiredHandler,
3
+  numberGT0RequiredHandler
4
+} from './validate'
5
+import {
6
+  toButtonHandler
7
+} from './props'
8
+
9
+const typesMap = {
10
+  submit(field) {
11
+    toButtonHandler(field, 'submit')
12
+  },
13
+  reset(field) {
14
+    toButtonHandler(field, 'reset')
15
+  },
16
+  checkbox(field) {
17
+    boolRequiredHandler(field)
18
+  },
19
+  switch(field) {
20
+    boolRequiredHandler(field)
21
+  },
22
+  rate(field) {
23
+    numberGT0RequiredHandler(field)
24
+  }
25
+}
26
+export default function processTypes(field) {
27
+  const typeFn = typesMap[field.type]
28
+  if (typeFn) {
29
+    typeFn(field)
30
+  }
31
+}

+ 28 - 0
src/components/form/fields/validate.js

@@ -0,0 +1,28 @@
1
+const handleValidateRule = (field, type, custom) => {
2
+  const ruleValue = field.rules && field.rules[type]
3
+  if (type !== 'custom' && ruleValue && typeof ruleValue !== 'function') {
4
+    field.rules[type] = custom
5
+  }
6
+}
7
+const createHandler = (type) => {
8
+  return (field, custom) => {
9
+    return handleValidateRule(field, type, custom)
10
+  }
11
+}
12
+
13
+const handleRequired = createHandler('required')
14
+const boolRequiredHandler = (field) => {
15
+  return handleRequired(field, (val) => {
16
+    return val !== false
17
+  })
18
+}
19
+const numberGT0RequiredHandler = (field) => {
20
+  return handleRequired(field, (val) => {
21
+    return val > 0
22
+  })
23
+}
24
+
25
+export {
26
+  boolRequiredHandler,
27
+  numberGT0RequiredHandler
28
+}

+ 57 - 0
src/components/form/form-group.vue

@@ -0,0 +1,57 @@
1
+<template>
2
+  <div class="cube-form-group">
3
+    <p class="cube-form-group-legend">{{legend}}</p>
4
+    <div class="cube-form-group-content">
5
+      <slot>
6
+        <cube-form-item
7
+          v-for="(field, index) in fields"
8
+          :key="index"
9
+          :field="field"
10
+        />
11
+      </slot>
12
+    </div>
13
+  </div>
14
+</template>
15
+
16
+<script>
17
+  import CubeFormItem from './form-item.vue'
18
+
19
+  const COMPONENT_NAME = 'cube-form-group'
20
+  export default {
21
+    name: COMPONENT_NAME,
22
+    props: {
23
+      legend: {
24
+        type: String,
25
+        default: ''
26
+      },
27
+      fields: {
28
+        type: Array,
29
+        default() {
30
+          /* istanbul ignore next */
31
+          return []
32
+        }
33
+      }
34
+    },
35
+    beforeCreate() {
36
+      this.form = this.$parent.form
37
+    },
38
+    beforeDestroy() {
39
+      this.form = null
40
+    },
41
+    components: {
42
+      CubeFormItem
43
+    }
44
+  }
45
+</script>
46
+
47
+<style lang="stylus" rel="stylesheet/stylus">
48
+  @require "../../common/stylus/variable.styl"
49
+  @require "../../common/stylus/mixin.styl"
50
+
51
+  .cube-form-group
52
+    overflow: hidden
53
+  .cube-form-group-legend
54
+    font-size: $fontsize-medium
55
+    color: $form-group-legend-color
56
+    background-color: $form-group-legend-bgc
57
+</style>

+ 310 - 0
src/components/form/form-item.vue

@@ -0,0 +1,310 @@
1
+<template>
2
+  <div class="cube-form-item border-bottom-1px" ref="formItem" :class="itemClass">
3
+    <template v-if="!isBtnField">
4
+      <slot name="label">
5
+        <div class="cube-form-label" v-show="fieldValue.label"><span>{{fieldValue.label}}</span></div>
6
+      </slot>
7
+      <cube-validator
8
+        class="cube-form-field"
9
+        v-if="hasRules"
10
+        ref="validator"
11
+        v-model="originValid"
12
+        :disabled="validatorDisabled"
13
+        :model="validatorModel"
14
+        :model-key="validatorModelKey"
15
+        :rules="fieldValue.rules"
16
+        :messages="fieldValue.messages"
17
+        @input="validatorChangeHandler"
18
+        @validating="validatingHandler"
19
+        @validated="validatedHandler"
20
+        @msg-click="msgClick"
21
+      >
22
+        <slot>
23
+          <component :is="componentName" v-model="modelValue" v-bind="fieldValue.props" v-on="fieldValue.events"></component>
24
+        </slot>
25
+      </cube-validator>
26
+      <div class="cube-form-field" v-else>
27
+        <slot>
28
+          <component :is="componentName" v-model="modelValue" v-bind="fieldValue.props" v-on="fieldValue.events"></component>
29
+        </slot>
30
+      </div>
31
+    </template>
32
+    <cube-button v-bind="fieldValue.props" v-else>{{fieldValue.label}}</cube-button>
33
+  </div>
34
+</template>
35
+
36
+<script>
37
+  import { processField } from './fields/index'
38
+  import { resetTypeValue, cb2PromiseWithResolve, debounce } from '../../common/helpers/util'
39
+  import CubeValidator from '../validator/validator.vue'
40
+  import LAYOUTS from './layouts'
41
+  import { getResetValueByType } from './fields/reset'
42
+  import mixin from './mixin'
43
+  import components from './components'
44
+  components.CubeValidator = CubeValidator
45
+
46
+  const COMPONENT_NAME = 'cube-form-item'
47
+  const EVENT_FOCUSIN = 'focusin'
48
+  const EVENT_FOCUSOUT = 'focusout'
49
+
50
+  export default {
51
+    name: COMPONENT_NAME,
52
+    mixins: [mixin],
53
+    props: {
54
+      field: {
55
+        type: Object,
56
+        default() {
57
+          /* istanbul ignore next */
58
+          return {}
59
+        }
60
+      }
61
+    },
62
+    data() {
63
+      const validatorModelKey = 'value'
64
+      const modelKey = this.field.modelKey
65
+      const modelValue = modelKey ? this.form.model[modelKey] : null
66
+      return {
67
+        validatorDisabled: false,
68
+        validatorModelKey,
69
+        modelValue: modelValue,
70
+        validatorModel: {
71
+          [validatorModelKey]: modelValue
72
+        }
73
+      }
74
+    },
75
+    computed: {
76
+      fieldValue() {
77
+        return processField(this.field)
78
+      },
79
+      hasRules() {
80
+        return Object.keys(this.fieldValue.rules || {}).length > 0
81
+      },
82
+      isBtnField() {
83
+        return this.fieldValue.type === 'button'
84
+      },
85
+      itemClass() {
86
+        const rules = this.fieldValue.rules
87
+        return {
88
+          // only handle required rule for now
89
+          'cube-form-item_required': rules && rules.required,
90
+          'cube-form-item_btn': this.isBtnField,
91
+          'cube-form-item_validating': this.validating,
92
+          'cube-form-item_pending': this.pending,
93
+          'cube-form-item_valid': this.valid,
94
+          'cube-form-item_invalid': this.invalid
95
+        }
96
+      },
97
+      modelVal() {
98
+        return this.form.model[this.fieldValue.modelKey]
99
+      },
100
+      componentName() {
101
+        const fieldValue = this.fieldValue
102
+        const component = fieldValue.component
103
+        if (component) {
104
+          return component
105
+        }
106
+        const type = fieldValue.type
107
+        const cubeType = `cube-${type}`
108
+        if (components[cubeType]) {
109
+          return cubeType
110
+        }
111
+        return type
112
+      }
113
+    },
114
+    watch: {
115
+      modelVal(newModel) {
116
+        if (this.modelValue !== newModel) {
117
+          this.modelValue = newModel
118
+        }
119
+      },
120
+      modelValue: {
121
+        handler(newModel) {
122
+          // update form model
123
+          this.form.model[this.fieldValue.modelKey] = newModel
124
+          this.updateValidatorModel()
125
+        },
126
+        sync: true
127
+      },
128
+      originValid(newVal) {
129
+        this.lastOriginValid = newVal
130
+      }
131
+    },
132
+    beforeCreate() {
133
+      this.form = this.$parent.form
134
+    },
135
+    created() {
136
+      this.form.addField(this)
137
+      this.getValidatorModel = (modelValue) => {
138
+        this.pending = false
139
+        return modelValue
140
+      }
141
+    },
142
+    mounted() {
143
+      this.initDebounce()
144
+      this.initFocusEvents()
145
+    },
146
+    methods: {
147
+      initDebounce() {
148
+        let debounceTime = this.fieldValue.debounce
149
+        if (debounceTime === true) {
150
+          debounceTime = 200
151
+        }
152
+        if ((!debounceTime && debounceTime !== 0) || debounceTime < 0 || this.fieldValue.trigger === 'blur') return
153
+        this.getValidatorModel = debounce((modelValue) => {
154
+          this.pending = false
155
+          this.validatorModel[this.validatorModelKey] = modelValue
156
+          this.form.updatePending()
157
+          this.validate()
158
+          return modelValue
159
+        }, debounceTime, false, this.validatorModel[this.validatorModelKey])
160
+      },
161
+      focusInHandler() {
162
+        this.focused = true
163
+      },
164
+      focusOutHandler() {
165
+        this.focused = false
166
+        this.updateValidatorModel()
167
+        this.validate()
168
+      },
169
+      initFocusEvents() {
170
+        if (this.fieldValue.trigger === 'blur') {
171
+          const formItem = this.$refs.formItem
172
+          formItem.addEventListener(EVENT_FOCUSIN, this.focusInHandler, false)
173
+          formItem.addEventListener(EVENT_FOCUSOUT, this.focusOutHandler, false)
174
+          this.getValidatorModel = (modelValue) => {
175
+            if (this.focused) {
176
+              return this.validatorModel[this.validatorModelKey]
177
+            } else {
178
+              this.pending = false
179
+              this.form.updatePending()
180
+              return modelValue
181
+            }
182
+          }
183
+        }
184
+      },
185
+      removeFocusEvents() {
186
+        const formItem = this.$refs.formItem
187
+        formItem.removeEventListener(EVENT_FOCUSIN, this.focusInHandler, false)
188
+        formItem.removeEventListener(EVENT_FOCUSOUT, this.focusOutHandler, false)
189
+      },
190
+      updateValidatorModel() {
191
+        this.pending = true
192
+        this.validatorModel[this.validatorModelKey] = this.getValidatorModel(this.modelValue)
193
+        if (this.pending) {
194
+          this.form.setPending(this.pending)
195
+          this.originValid = undefined
196
+        }
197
+      },
198
+      validatorChangeHandler() {
199
+        // disabled or true to true no update validity
200
+        if (this.validatorDisabled || (this.originValid && this.lastOriginValid)) {
201
+          return
202
+        }
203
+        this.updateValidity()
204
+      },
205
+      validatingHandler() {
206
+        this.validating = true
207
+        this.form.setValidating(true)
208
+      },
209
+      validatedHandler() {
210
+        this.validating = false
211
+        this.form.updateValidating()
212
+      },
213
+      updateValidity() {
214
+        const validator = this.$refs.validator
215
+        if (validator) {
216
+          // sync update validaty
217
+          this.form.updateValidity(this.fieldValue.modelKey, validator.valid, validator.result, validator.dirty)
218
+        }
219
+      },
220
+      validate(cb) {
221
+        const promise = cb2PromiseWithResolve(cb)
222
+        if (promise) {
223
+          cb = promise.resolve
224
+        }
225
+        const validator = this.$refs.validator
226
+        if (validator) {
227
+          validator.validate(() => {
228
+            this.validatorDisabled = true
229
+            this.updateValidity()
230
+            cb && cb()
231
+            this.$nextTick(() => {
232
+              this.validatorDisabled = false
233
+            })
234
+          })
235
+        } else {
236
+          cb && cb()
237
+        }
238
+        return promise
239
+      },
240
+      reset() {
241
+        const fieldValue = this.fieldValue
242
+        if (fieldValue.modelKey) {
243
+          const defValue = getResetValueByType(fieldValue.type)
244
+          this.validatorDisabled = true
245
+          resetTypeValue(this, 'modelValue', defValue)
246
+          this.$refs.validator && this.$refs.validator.reset()
247
+          this.$nextTick(() => {
248
+            this.validatorDisabled = false
249
+          })
250
+        }
251
+        this.validating = false
252
+        this.pending = false
253
+      },
254
+      msgClick() {
255
+        /* istanbul ignore if */
256
+        if (this.form.layout !== LAYOUTS.STANDARD) {
257
+          return
258
+        }
259
+        /* istanbul ignore next */
260
+        this.$createToast && this.$createToast({
261
+          type: 'warn',
262
+          txt: this.$refs.validator.msg,
263
+          time: 1000
264
+        }).show()
265
+      }
266
+    },
267
+    beforeDestroy() {
268
+      this.removeFocusEvents()
269
+      this.form.destroyField(this)
270
+      this.form = null
271
+    },
272
+    components
273
+  }
274
+</script>
275
+
276
+<style lang="stylus" rel="stylesheet/stylus">
277
+  @require "../../common/stylus/variable.styl"
278
+  @require "../../common/stylus/mixin.styl"
279
+
280
+  .cube-form-item
281
+    position: relative
282
+    display: flex
283
+    align-items: center
284
+    padding: 0 15px
285
+    &:last-child
286
+      &::after
287
+        display: none
288
+    .cube-checkbox-group, .cube-radio-group
289
+      background-color: transparent
290
+    .cube-checkbox, .cube-radio
291
+      padding-left: 0
292
+      padding-right: 0
293
+  .cube-form-item_btn
294
+    margin: 15px 0
295
+    &::after
296
+      display: none
297
+  .cube-form-label
298
+    display: flex
299
+    align-items: center
300
+    word-wrap: break-word
301
+    word-break: break-word
302
+  .cube-form-item_required
303
+    .cube-form-label
304
+      &::before
305
+        content: "*"
306
+        display: block
307
+        margin-top: 1px
308
+        margin-right: .3em
309
+        color: $form-label-required-color
310
+</style>

+ 427 - 0
src/components/form/form.vue

@@ -0,0 +1,427 @@
1
+<template>
2
+  <form ref="form" class="cube-form" :class="formClass" :action="action" @submit="submitHandler" @reset="resetHandler">
3
+    <slot>
4
+      <cube-form-group v-for="(group, index) in groups" :fields="group.fields" :legend="group.legend" :key="index" />
5
+    </slot>
6
+  </form>
7
+</template>
8
+
9
+<script>
10
+  import { dispatchEvent } from '../../common/helpers/dom'
11
+  import { cb2PromiseWithResolve } from '../../common/helpers/util'
12
+  import CubeFormGroup from './form-group.vue'
13
+  import LAYOUTS from './layouts'
14
+  import mixin from './mixin'
15
+
16
+  const COMPONENT_NAME = 'cube-form'
17
+  const EVENT_SUBMIT = 'submit'
18
+  const EVENT_RESET = 'reset'
19
+  const EVENT_VALIDATE = 'validate'
20
+  const EVENT_VALID = 'valid'
21
+  const EVENT_INVALID = 'invalid'
22
+
23
+  export default {
24
+    name: COMPONENT_NAME,
25
+    mixins: [mixin],
26
+    props: {
27
+      action: String,
28
+      model: {
29
+        type: Object,
30
+        default() {
31
+          /* istanbul ignore next */
32
+          return {}
33
+        }
34
+      },
35
+      schema: {
36
+        type: Object,
37
+        default() {
38
+          /* istanbul ignore next */
39
+          return {}
40
+        }
41
+      },
42
+      options: {
43
+        type: Object,
44
+        default() {
45
+          return {
46
+            scrollToInvalidField: false,
47
+            layout: LAYOUTS.STANDARD
48
+          }
49
+        }
50
+      },
51
+      immediateValidate: {
52
+        type: Boolean,
53
+        default: false
54
+      }
55
+    },
56
+    data() {
57
+      return {
58
+        validatedCount: 0,
59
+        dirty: false,
60
+        firstInvalidField: null,
61
+        firstInvalidFieldIndex: -1
62
+      }
63
+    },
64
+    computed: {
65
+      groups() {
66
+        const schema = this.schema
67
+        const groups = schema.groups || []
68
+        if (schema.fields) {
69
+          groups.unshift({
70
+            fields: schema.fields
71
+          })
72
+        }
73
+        return groups
74
+      },
75
+      layout() {
76
+        const options = this.options
77
+        const layout = (options && options.layout) || LAYOUTS.STANDARD
78
+        return layout
79
+      },
80
+      formClass() {
81
+        const invalid = this.invalid
82
+        const valid = this.valid
83
+        const layout = this.layout
84
+        return {
85
+          'cube-form_standard': layout === LAYOUTS.STANDARD,
86
+          'cube-form_groups': this.groups.length > 1,
87
+          'cube-form_validating': this.validating,
88
+          'cube-form_pending': this.pending,
89
+          'cube-form_valid': valid,
90
+          'cube-form_invalid': invalid,
91
+          'cube-form_classic': layout === LAYOUTS.CLASSIC,
92
+          'cube-form_fresh': layout === LAYOUTS.FRESH
93
+        }
94
+      }
95
+    },
96
+    watch: {
97
+      validatedCount() {
98
+        this.$emit(EVENT_VALIDATE, {
99
+          validity: this.validity,
100
+          valid: this.valid,
101
+          invalid: this.invalid,
102
+          dirty: this.dirty,
103
+          firstInvalidFieldIndex: this.firstInvalidFieldIndex
104
+        })
105
+      }
106
+    },
107
+    beforeCreate() {
108
+      this.form = this
109
+      this.fields = []
110
+      this.validity = {}
111
+    },
112
+    mounted() {
113
+      if (this.immediateValidate) {
114
+        this.validate()
115
+      }
116
+    },
117
+    methods: {
118
+      submit(skipValidate = false) {
119
+        this.skipValidate = skipValidate
120
+        dispatchEvent(this.$refs.form, 'submit')
121
+        this.skipValidate = false
122
+      },
123
+      reset() {
124
+        dispatchEvent(this.$refs.form, 'reset')
125
+      },
126
+      submitHandler(e) {
127
+        if (this.skipValidate) {
128
+          this.$emit(EVENT_SUBMIT, e, this.model)
129
+          return
130
+        }
131
+        const submited = (submitResult) => {
132
+          if (submitResult) {
133
+            this.$emit(EVENT_VALID, this.validity)
134
+            this.$emit(EVENT_SUBMIT, e, this.model)
135
+          } else {
136
+            e.preventDefault()
137
+            this.$emit(EVENT_INVALID, this.validity)
138
+          }
139
+        }
140
+        if (this.valid === undefined) {
141
+          this._submit(submited)
142
+          if (this.validating || this.pending) {
143
+            // async validate
144
+            e.preventDefault()
145
+          }
146
+        } else {
147
+          submited(this.valid)
148
+        }
149
+      },
150
+      resetHandler(e) {
151
+        this._reset()
152
+        this.$emit(EVENT_RESET, e)
153
+      },
154
+      _submit(cb) {
155
+        this.validate(() => {
156
+          if (this.invalid) {
157
+            if (this.options.scrollToInvalidField && this.firstInvalidField) {
158
+              this.firstInvalidField.$el.scrollIntoView()
159
+            }
160
+          }
161
+          cb && cb(this.valid)
162
+        })
163
+      },
164
+      _reset() {
165
+        this.fields.forEach((fieldComponent) => {
166
+          fieldComponent.reset()
167
+        })
168
+        this.setValidity()
169
+        this.setValidating()
170
+        this.setPending()
171
+      },
172
+      validate(cb) {
173
+        const promise = cb2PromiseWithResolve(cb)
174
+        if (promise) {
175
+          cb = promise.resolve
176
+        }
177
+        let doneCount = 0
178
+        const len = this.fields.length
179
+        this.originValid = undefined
180
+        this.fields.forEach((fieldComponent) => {
181
+          fieldComponent.validate(() => {
182
+            doneCount++
183
+            if (doneCount === len) {
184
+              // all done
185
+              cb && cb(this.valid)
186
+            }
187
+          })
188
+        })
189
+        return promise
190
+      },
191
+      updateValidating() {
192
+        const validating = this.fields.some((fieldComponent) => fieldComponent.validating)
193
+        this.setValidating(validating)
194
+      },
195
+      updatePending() {
196
+        const pending = this.fields.some((fieldComponent) => fieldComponent.pending)
197
+        this.setPending(pending)
198
+      },
199
+      setValidating(validating = false) {
200
+        this.validating = validating
201
+      },
202
+      setPending(pending = false) {
203
+        this.pending = pending
204
+      },
205
+      updateValidity(modelKey, valid, result, dirty) {
206
+        const curResult = this.validity[modelKey]
207
+        if (curResult && curResult.valid === valid && curResult.result === result && curResult.dirty === dirty) {
208
+          return
209
+        }
210
+        this.setValidity(modelKey, {
211
+          valid,
212
+          result,
213
+          dirty
214
+        })
215
+      },
216
+      setValidity(key, val) {
217
+        let validity = {}
218
+        if (key) {
219
+          Object.assign(validity, this.validity)
220
+          if (val === undefined) {
221
+            delete validity[key]
222
+          } else {
223
+            validity[key] = val
224
+          }
225
+        }
226
+
227
+        let dirty = false
228
+        let invalid = false
229
+        let valid = true
230
+        let firstInvalidFieldKey = ''
231
+        this.fields.forEach((fieldComponent) => {
232
+          const modelKey = fieldComponent.fieldValue.modelKey
233
+          if (modelKey) {
234
+            const retVal = validity[modelKey]
235
+            if (retVal) {
236
+              if (retVal.dirty) {
237
+                dirty = true
238
+              }
239
+              if (retVal.valid === false) {
240
+                valid = false
241
+              } else if (valid && !retVal.valid) {
242
+                valid = retVal.valid
243
+              }
244
+
245
+              if (!invalid && retVal.valid === false) {
246
+                // invalid
247
+                invalid = true
248
+                firstInvalidFieldKey = modelKey
249
+              }
250
+            } else if (fieldComponent.hasRules) {
251
+              if (valid) {
252
+                valid = undefined
253
+              }
254
+              validity[modelKey] = {
255
+                valid: undefined,
256
+                result: {},
257
+                dirty: false
258
+              }
259
+            }
260
+          }
261
+        })
262
+        this.validity = validity
263
+        this.dirty = dirty
264
+        this.originValid = valid
265
+        this.setFirstInvalid(firstInvalidFieldKey)
266
+        this.validatedCount++
267
+      },
268
+      setFirstInvalid(key) {
269
+        if (!key) {
270
+          this.firstInvalidField = null
271
+          this.firstInvalidFieldIndex = -1
272
+          return
273
+        }
274
+        this.fields.some((fieldComponent, index) => {
275
+          if (fieldComponent.fieldValue.modelKey === key) {
276
+            this.firstInvalidField = fieldComponent
277
+            this.firstInvalidFieldIndex = index
278
+            return true
279
+          }
280
+        })
281
+      },
282
+      addField(fieldComponent) {
283
+        this.fields.push(fieldComponent)
284
+      },
285
+      destroyField(fieldComponent) {
286
+        const i = this.fields.indexOf(fieldComponent)
287
+        this.fields.splice(i, 1)
288
+        this.setValidity(fieldComponent.fieldValue.modelKey)
289
+      }
290
+    },
291
+    beforeDestroy() {
292
+      this.form = null
293
+      this.firstInvalidField = null
294
+    },
295
+    components: {
296
+      CubeFormGroup
297
+    }
298
+  }
299
+</script>
300
+
301
+<style lang="stylus" rel="stylesheet/stylus">
302
+  @require "../../common/stylus/variable.styl"
303
+  @require "../../common/stylus/mixin.styl"
304
+
305
+  .cube-form
306
+    position: relative
307
+    font-size: $fontsize-large
308
+    line-height: 1.429
309
+    color: $form-color
310
+    background-color: $form-bgc
311
+  .cube-form_groups
312
+    .cube-form-group-legend
313
+      padding: 10px 15px
314
+      &:empty
315
+        padding-top: 5px
316
+        padding-bottom: 5px
317
+  .cube-form_standard
318
+    .cube-form-item
319
+      min-height: 46px
320
+    .cube-form-field
321
+      flex: 1
322
+      font-size: $fontsize-medium
323
+    .cube-validator
324
+      display: flex
325
+      align-items: center
326
+      position: relative
327
+    .cube-validator_invalid
328
+      color: $form-invalid-color
329
+    .cube-validator-content
330
+      flex: 1
331
+    .cube-validator-msg-def
332
+      font-size: 0
333
+    .cube-validator_invalid
334
+      .cube-validator-msg
335
+        &::before
336
+          content: "\e614"
337
+          padding-left: 5px
338
+          font-family: "cube-icon"!important
339
+          font-size: $fontsize-large-xx
340
+          font-style: normal
341
+          -webkit-font-smoothing: antialiased
342
+          -webkit-text-stroke-width: 0.2px
343
+          -moz-osx-font-smoothing: grayscale
344
+    .cube-form-label
345
+      width: 100px
346
+      padding-right: 10px
347
+    .cube-checkbox-group, .cube-radio-group
348
+      &::before, &::after
349
+        display: none
350
+    .cube-input
351
+      input
352
+        padding: 13px 0
353
+        background-color: transparent
354
+      &::after
355
+        display: none
356
+    .cube-textarea-wrapper
357
+      padding: 13px 0
358
+      height: 20px
359
+      &.cube-textarea_expanded
360
+        height: 60px
361
+        padding-bottom: 20px
362
+        .cube-textarea-indicator
363
+          bottom: 2px
364
+      .cube-textarea
365
+        padding: 0
366
+        background-color: transparent
367
+      &::after
368
+        display: none
369
+    .cube-select
370
+      padding-left: 0
371
+      background-color: transparent
372
+      &::after
373
+        display: none
374
+    .cube-upload-def
375
+      padding: 5px 0
376
+      .cube-upload-btn, .cube-upload-file
377
+        margin: 5px 10px 5px 0
378
+  .cube-form_classic
379
+    .cube-form-item
380
+      display: block
381
+      padding: 15px
382
+      &:last-child
383
+        padding-bottom: 30px
384
+      &::after
385
+        display: none
386
+      .cube-validator-msg
387
+        position: absolute
388
+        margin-top: 3px
389
+        &::before
390
+          display: none
391
+      .cube-validator-msg-def
392
+        font-size: $fontsize-small
393
+    .cube-form-item_btn
394
+      padding-top: 0
395
+      padding-bottom: 0
396
+      &:last-child
397
+        padding-bottom: 0
398
+    .cube-form-label
399
+      padding-bottom: 15px
400
+  .cube-form_fresh
401
+    .cube-form-item
402
+      display: block
403
+      padding: 2em 15px 10px
404
+      &::after
405
+        display: none
406
+      .cube-validator-msg
407
+        position: absolute
408
+        top: 1em
409
+        right: 15px
410
+        bottom: auto
411
+        margin-top: -.4em
412
+        font-size: $fontsize-small
413
+        &::before
414
+          display: none
415
+      .cube-validator-msg-def
416
+        font-size: 100%
417
+    .cube-form-item_btn
418
+      padding-top: 0
419
+      padding-bottom: 0
420
+      &:last-child
421
+        padding-bottom: 0
422
+    .cube-form-label
423
+      position: absolute
424
+      top: 1em
425
+      margin-top: -.4em
426
+      font-size: $fontsize-small
427
+</style>

+ 7 - 0
src/components/form/layouts.js

@@ -0,0 +1,7 @@
1
+const LAYOUTS = {
2
+  STANDARD: 'standard',
3
+  CLASSIC: 'classic',
4
+  FRESH: 'fresh'
5
+}
6
+
7
+export default LAYOUTS

+ 21 - 0
src/components/form/mixin.js

@@ -0,0 +1,21 @@
1
+export default {
2
+  data() {
3
+    return {
4
+      validating: false,
5
+      pending: false,
6
+      originValid: undefined
7
+    }
8
+  },
9
+  computed: {
10
+    valid() {
11
+      const originValid = this.originValid
12
+      const pending = this.pending
13
+      const validating = this.validating
14
+      return (pending || validating) ? undefined : originValid
15
+    },
16
+    invalid() {
17
+      const valid = this.valid
18
+      return valid === undefined ? valid : !valid
19
+    }
20
+  }
21
+}

+ 327 - 0
src/components/image-preview/image-preview.vue

@@ -0,0 +1,327 @@
1
+<template>
2
+  <transition name="cube-image-preview-fade">
3
+    <cube-popup type="image-preview" :z-index="zIndex" :center="false" v-show="isVisible">
4
+      <div class="cube-image-preview-container">
5
+        <div class="cube-image-preview-header">
6
+          <slot name="header" :current="currentPageIndex"></slot>
7
+        </div>
8
+        <cube-slide
9
+          ref="slide"
10
+          v-if="isVisible"
11
+          :data="imgs"
12
+          :initial-index="currentPageIndex"
13
+          :auto-play="false"
14
+          :loop="loop"
15
+          :speed="speed"
16
+          :options="options"
17
+          @change="slideChangeHandler"
18
+        >
19
+          <cube-slide-item
20
+            v-for="(img, index) in imgs"
21
+            :key="index"
22
+          >
23
+            <div class="cube-image-preview-item" @click="itemClickHandler">
24
+              <cube-scroll
25
+                ref="items"
26
+                :options="scrollOptions"
27
+                @dblclick.native="dblclickHandler(index, $event)"
28
+              >
29
+                <img class="cube-image-preview-img" :src="img" @load="imgLoad(index)">
30
+              </cube-scroll>
31
+            </div>
32
+          </cube-slide-item>
33
+          <template slot="dots"><i></i></template>
34
+        </cube-slide>
35
+        <div class="cube-image-preview-footer">
36
+          <slot name="footer" :current="currentPageIndex">
37
+            <span class="cube-image-preview-counter">{{currentPageIndex + 1}}/{{imgs.length}}</span>
38
+          </slot>
39
+        </div>
40
+      </div>
41
+    </cube-popup>
42
+  </transition>
43
+</template>
44
+<script type="text/ecmascript-6">
45
+  import CubePopup from '../popup/popup.vue'
46
+  import CubeSlide from '../slide/slide.vue'
47
+  import CubeSlideItem from '../slide/slide-item.vue'
48
+  import CubeScroll from '../scroll/scroll.vue'
49
+  import visibilityMixin from '../../common/mixins/visibility'
50
+  import popupMixin from '../../common/mixins/popup'
51
+  import { isAndroid } from '../../common/helpers/env'
52
+
53
+  const COMPONENT_NAME = 'cube-image-preview'
54
+  const EVENT_CHANGE = 'change'
55
+  const EVENT_HIDE = 'hide'
56
+
57
+  export default {
58
+    name: COMPONENT_NAME,
59
+    mixins: [visibilityMixin, popupMixin],
60
+    props: {
61
+      initialIndex: {
62
+        type: Number,
63
+        default: 0
64
+      },
65
+      imgs: {
66
+        type: Array,
67
+        default() {
68
+          /* istanbul ignore next */
69
+          return []
70
+        }
71
+      },
72
+      loop: {
73
+        type: Boolean,
74
+        default: true
75
+      },
76
+      speed: {
77
+        type: Number,
78
+        default: 400
79
+      },
80
+      preventDefault: {
81
+        type: Boolean,
82
+        default: true
83
+      }
84
+    },
85
+    data() {
86
+      return {
87
+        currentPageIndex: this.initialIndex,
88
+        options: {
89
+          observeDOM: false,
90
+          bounce: {
91
+            left: true,
92
+            right: true
93
+          },
94
+          useTransition: !isAndroid,
95
+          probeType: 3,
96
+          preventDefault: this.preventDefault
97
+        },
98
+        scrollOptions: {
99
+          HWCompositing: isAndroid,
100
+          observeDOM: false,
101
+          zoom: true,
102
+          bindToWrapper: true,
103
+          freeScroll: true,
104
+          scrollX: true,
105
+          scrollY: true,
106
+          probeType: 3,
107
+          bounce: false,
108
+          click: false,
109
+          dblclick: true,
110
+          bounceTime: 300,
111
+          preventDefault: this.preventDefault
112
+        }
113
+      }
114
+    },
115
+    watch: {
116
+      initialIndex(newIndex) {
117
+        this.setPageIndex(newIndex)
118
+      }
119
+    },
120
+    methods: {
121
+      show() {
122
+        this.isVisible = true
123
+        this.$nextTick(() => {
124
+          this._listenSlide()
125
+          this._listenScroll()
126
+        })
127
+      },
128
+      _listenSlide() {
129
+        // waiting slide initial
130
+        this.$nextTick(() => {
131
+          const slide = this.$refs.slide.slide
132
+          slide.on('scrollStart', this.slideScrollStartHandler)
133
+          slide.on('scrollEnd', this.slideScrollEndHandler)
134
+        })
135
+      },
136
+      _listenScroll() {
137
+        // waiting scroll initial
138
+        this.$nextTick(() => {
139
+          this.$refs.items.forEach((scrollItem) => {
140
+            const scroll = scrollItem.scroll
141
+            scroll.on('zoomStart', this.zoomStartHandler.bind(this, scroll))
142
+            scroll.on('beforeScrollStart', this.beforeScrollHandler)
143
+            scroll.on('scroll', this.checkBoundary.bind(this, scroll))
144
+            scroll.on('scrollEnd', this.scrollEndHandler.bind(this, scroll))
145
+          })
146
+        })
147
+      },
148
+      hide() {
149
+        this.isVisible = false
150
+        this.$emit(EVENT_HIDE)
151
+      },
152
+      prev() {
153
+        const slide = this.$refs.slide.slide
154
+        slide && slide.prev()
155
+      },
156
+      next() {
157
+        const slide = this.$refs.slide.slide
158
+        slide && slide.next()
159
+      },
160
+      goTo(index) {
161
+        const slide = this.$refs.slide.slide
162
+        slide && slide.goToPage(index, 0)
163
+      },
164
+      imgLoad(i) {
165
+        /* istanbul ignore if */
166
+        if (this.isVisible && this.$refs.items) {
167
+          this.$refs.items[i].scroll.refresh()
168
+        }
169
+      },
170
+      setPageIndex(currentPageIndex) {
171
+        if (this.currentPageIndex >= 0 && this.currentPageIndex !== currentPageIndex) {
172
+          const item = this.$refs.items[this.currentPageIndex]
173
+          if (item) {
174
+            const scroll = item.scroll
175
+            /* istanbul ignore if */
176
+            if (scroll.scale !== 1) {
177
+              scroll.scale = 1
178
+              scroll.lastcale = 1
179
+              scroll.refresh()
180
+            }
181
+          }
182
+        }
183
+        this.currentPageIndex = currentPageIndex
184
+      },
185
+      slideChangeHandler(currentPageIndex) {
186
+        this.setPageIndex(currentPageIndex)
187
+        this.slideScrollEndHandler()
188
+        this.$emit(EVENT_CHANGE, currentPageIndex)
189
+      },
190
+      slideScrollStartHandler() {
191
+        const slide = this.$refs.slide.slide
192
+        if (this._scrolling && !this._hasEnableSlide) {
193
+          slide.disable()
194
+        } else {
195
+          slide.enable()
196
+        }
197
+      },
198
+      slideScrollEndHandler() {
199
+        this.$refs.items.forEach((scrollItem) => {
200
+          this.scrollEndHandler(scrollItem.scroll)
201
+        })
202
+      },
203
+      _scroll(scroll) {
204
+        const slide = this.$refs.slide.slide
205
+        slide.disable()
206
+        slide.refresh()
207
+        scroll.enable()
208
+      },
209
+      _slide(scroll) {
210
+        this.$refs.slide.slide.enable()
211
+        scroll.disable()
212
+      },
213
+      beforeScrollHandler() {
214
+        // for touchstart scrollEnd
215
+        // cancel it, do not enable slide
216
+        clearTimeout(this.enableSlideTid)
217
+      },
218
+      scrollEndHandler(scroll) {
219
+        clearTimeout(this.enableSlideTid)
220
+        if (this.dblZooming) {
221
+          this.dblZooming = false
222
+          clearTimeout(this.clickTid)
223
+        }
224
+        this._hasEnableSlide = false
225
+        this._scrolling = false
226
+        scroll.enable()
227
+        this.enableSlideTid = setTimeout(() => {
228
+          this.$refs.slide.slide.enable()
229
+        })
230
+      },
231
+      checkBoundary(scroll, pos) {
232
+        if (scroll.distX && Math.abs(scroll.distX) > Math.abs(scroll.distY)) {
233
+          this._scrolling = true
234
+          const reached = scroll.distX > 0 ? pos.x >= scroll.minScrollX : pos.x <= scroll.maxScrollX
235
+          if (reached) {
236
+            this._hasEnableSlide = true
237
+            this._slide(scroll)
238
+          } else {
239
+            if (!this._hasEnableSlide) {
240
+              this._scroll(scroll)
241
+            }
242
+          }
243
+        } else if (scroll.distY) {
244
+          this._scrolling = true
245
+          this._scroll(scroll)
246
+        }
247
+      },
248
+      zoomStartHandler(scroll) {
249
+        this._scroll(scroll)
250
+      },
251
+      dblclickHandler(index, e) {
252
+        const scroll = this.$refs.items[index].scroll
253
+        this.dblZooming = true
254
+        this.zoomTo(scroll, scroll.scale > 1 ? 1 : 2, e)
255
+        scroll.disable()
256
+      },
257
+      itemClickHandler() {
258
+        clearTimeout(this.clickTid)
259
+        this.clickTid = setTimeout(() => {
260
+          !this.dblZooming && this.hide()
261
+        }, this.scrollOptions.bounceTime)
262
+      },
263
+      zoomTo(scroll, scale, e) {
264
+        scroll.zoomTo(scale, e.pageX, e.pageY)
265
+      }
266
+    },
267
+    components: {
268
+      CubePopup,
269
+      CubeSlide,
270
+      CubeSlideItem,
271
+      CubeScroll
272
+    }
273
+  }
274
+</script>
275
+<style lang="stylus" rel="stylesheet/stylus">
276
+  @require "../../common/stylus/variable.styl"
277
+
278
+  .cube-image-preview-fade-enter, .cube-image-preview-fade-leave-active
279
+    opacity: 0
280
+  .cube-image-preview-fade-enter-active, .cube-image-preview-fade-leave-active
281
+    transition: all .3s ease-in-out
282
+
283
+  .cube-image-preview
284
+    .cube-popup-mask
285
+      opacity: .6
286
+    .cube-popup-content
287
+      width: 100%
288
+      height: 100%
289
+    .cube-slide-item
290
+      display: flex
291
+      align-items: center
292
+      justify-content: center
293
+      overflow: hidden
294
+  .cube-image-preview-container
295
+    height: 100%
296
+    margin: 0 -10px
297
+  .cube-image-preview-header,
298
+  .cube-image-preview-footer
299
+    position: absolute
300
+    left: 0
301
+    right: 0
302
+  .cube-image-preview-header
303
+    top: 0
304
+  .cube-image-preview-footer
305
+    bottom: 0
306
+  .cube-image-preview-counter
307
+    position: absolute
308
+    bottom: 50px
309
+    width: 100%
310
+    text-align: center
311
+    font-size: $fontsize-medium
312
+    color: $image-preview-counter-color
313
+  .cube-image-preview-item
314
+    position: relative
315
+    padding: 0 10px
316
+    width: 100%
317
+    height: 100%
318
+    .cube-scroll-wrapper
319
+      display: flex
320
+      align-items: center
321
+      justify-content: center
322
+    .cube-image-preview-img
323
+      display: block
324
+      height: auto
325
+      max-width: 100%
326
+      max-height: 100%
327
+</style>

+ 0 - 0
src/components/index-list/index-list-group.vue


Some files were not shown because too many files changed in this diff