├── .eslintignore
├── .eslintrc.js
├── .github
└── ISSUE_TEMPLATE
│ ├── Bug_Report.md
│ └── Feature_Request.md
├── .gitignore
├── LICENSE
├── README.md
├── README_ZH.md
├── babel.config.js
├── build-test
├── build.js
├── check-versions.js
├── dev-client.js
├── dev-server.js
├── utils.js
├── vue-loader.conf.js
├── webpack.base.conf.js
├── webpack.dev.conf.js
└── webpack.prod.conf.js
├── build
├── build.dev.js
├── build.js
├── check-versions.js
├── utils.js
├── vue-loader.conf.js
├── webpack.base.conf.js
├── webpack.dev.conf.js
└── webpack.prod.conf.js
├── config
├── dev.env.js
├── index.js
└── prod.env.js
├── index.html
├── package.json
├── src
├── css
│ ├── card.css
│ ├── card.less
│ └── line.css
├── index.js
├── js
│ └── utils.js
├── mixins
│ ├── maxNumMixin.js
│ └── offsetMixin.js
└── tabs.vue
├── static
└── vue-tab-component.gif
└── test
├── App.vue
├── assets
├── Tangerine-Regular.ttf
└── fork.png
└── index.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 | dist/
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'env': {
3 | 'browser': true,
4 | 'es6': true
5 | },
6 | 'extends': 'standard',
7 | 'globals': {
8 | 'Atomics': 'readonly',
9 | 'SharedArrayBuffer': 'readonly'
10 | },
11 | 'parserOptions': {
12 | 'ecmaVersion': 2018,
13 | 'sourceType': 'module'
14 | },
15 | 'plugins': [
16 | 'vue'
17 | ],
18 | 'rules': {
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/Bug_Report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug描述
3 | about: 🐞🐞🐞 遇到问题了
4 | ---
5 |
6 | ### 异常现象
7 |
8 | ### 重现链接
9 |
10 | ### 重现步骤
11 |
12 | ### 期望的效果
13 |
14 | ### 建议方案
15 |
16 | ### 环境
17 | - 模块:
18 | - 程序版本:
19 | - 浏览器:
20 | - node/npm 版本:[e.g. Node 10.15/npm 6.9]]
21 |
22 | ### 其他说明
23 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/Feature_Request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature Request
3 | about: 🚀🚀🚀 需要新功能
4 | ---
5 |
6 | ### 描述你的业务场景
7 |
8 | ### 描述你的解决方案
9 |
10 | ### 进度
11 |
12 | ### 未完成
13 |
14 | ### 问题
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | npm-debug.log*
4 | yarn-debug.log*
5 | yarn-error.log*
6 | test/e2e/reports
7 | selenium-debug.log
8 | package-lock.json
9 | dist/
10 |
11 | # Editor directories and files
12 | .vscode
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 马林
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-tab-component
2 |
3 | [](https://www.npmjs.com/package/vue-tab-component)[](https://www.npmjs.com/package/vue-tab-component)[](https://opensource.org/licenses/MIT)
4 |
5 | > A Vue2.0 tab component which can control DOM render number
6 |
7 | **Feature**
8 |
9 | - **Custom** DOM render number
10 | - provide **goBegin/goEnd** Function
11 | - suppord **left、right** Position
12 | - suppord **slot** to change tab content
13 |
14 | **[EN](README.md)** || **如果它对你有帮助的话,请Star支持!!!**
15 | **[exemple project](https://github.com/qq240814476/vue-tab-component-demo)**
16 |
17 | ### 预览
18 |
19 | ------
20 |
21 | 
22 |
23 | ### 快速开始
24 |
25 | ------
26 |
27 | **Install**
28 |
29 | `npm install vue-tab-component --save`
30 |
31 | **Usage**
32 |
33 | [A simple project use vue-tab-component](https://github.com/qq240814476/vue-tab-component-demo)
34 |
35 | main.js
36 |
37 | ```vue
38 | import Vue from 'vue'
39 | import tabs from 'vue-tab-component'
40 |
41 | Vue.use(tabs)
42 | ```
43 |
44 | ### API
45 |
46 | ---
47 |
48 | **Attribute**
49 |
50 | | Name | Description | Type | Default |
51 | | :--------------- | :---------------------------------------------------- | :------- | :------------ |
52 | | data | data of tab {key:'key',title:'word'} | Array | [] |
53 | | tabPosition | tab' position, one of ['top','bottom','left','right'] | String | bottom |
54 | | type | css type, one of ['card','line'] | String | line |
55 | | closable | if tab can delete | Boolean | false |
56 | | activeName | active tab's key | String | '' |
57 | | maxnum | max number of DOM render tab at once | Number | 20 |
58 | | beforeChangeTab | param: key, tab won't change when return false | Function | (key) => true |
59 | | stretch (todo) | if tab stretchable because of content | Boolean | false |
60 | | hideDirectionBar | if tab's left/right bar show or hide | Boolean | false |
61 |
62 | **Method**
63 |
64 | | Name | Description | Param |
65 | | ----------------- | ------------------- | ----- |
66 | | goBegin | jump to tab's begin | - |
67 | | goEnd | jump to tab's end | - |
68 | | scrollPrev | slip to after part | - |
69 | | scrollNext | slip to next part | - |
70 | | scrollToActiveTab | slip to active tab | - |
71 |
72 | **Event**
73 |
74 | | Name | Description | Param |
75 | | ------------- | -------------------------------------- | ------------- |
76 | | on-click | when tab on clicking, return tab's key | key tab's key |
77 | | on-tab-remove | when tab on deleting, return tab's key | key tab's key |
78 |
79 | **slot**
80 |
81 | | name | data | exemple |
82 | | ---- | ---------- | -------------------------------------------------------------------- |
83 | | tab | tab's data | |
84 |
85 |
86 | **License**
87 |
88 | ------
89 |
90 | [The 996ICU License (996ICU)](LICENSE)
91 |
--------------------------------------------------------------------------------
/README_ZH.md:
--------------------------------------------------------------------------------
1 | # vue-tab-component
2 |
3 | [](https://www.npmjs.com/package/vue-tab-component)[](https://www.npmjs.com/package/vue-tab-component)[](https://opensource.org/licenses/MIT)
4 |
5 | > 基于Vue2.0的轻量级tab组件。可支持大数据量渲染
6 |
7 | **功能**
8 |
9 | - **自定义**实际渲染数量(处理tab过多情况)
10 | - 提供**前进后退**方法
11 | - 支持**left、right**排列
12 | - tab内容区域提供**slot**,方便自定义
13 |
14 | **[EN](README.md)** || **如果它对你有帮助的话,请Star支持!!!**
15 | **[示例项目](https://github.com/qq240814476/vue-tab-component-demo)**
16 |
17 | ### 预览
18 |
19 | ------
20 |
21 | 
22 |
23 | ### 快速开始
24 |
25 | ------
26 |
27 | **Install**
28 |
29 | `npm install vue-tab-component --save`
30 |
31 | **Usage**
32 |
33 | [一个简单的项目,用了vue-tab-component](https://github.com/qq240814476/vue-tab-component-demo)
34 |
35 | main.js
36 |
37 | ```vue
38 | import Vue from 'vue'
39 | import tabs from 'vue-tab-component'
40 |
41 | Vue.use(tabs)
42 | ```
43 |
44 | ### 接口
45 |
46 | ---
47 |
48 | **属性**
49 |
50 | | 属性名 | 描述 | 类型 | 默认值 |
51 | | :--------------- | :-------------------------------------------------- | :------- | :------------ |
52 | | data | tab的数据 {key:'key',title:'文字'} | Array | [] |
53 | | tabPosition | tab位置,总共有四种: top、bottom、left、right | String | bottom |
54 | | type | 样式种类,有card、line 两种 | String | line |
55 | | closable | 是否可删除tab | Boolean | false |
56 | | activeName | 当前激活状态的key | String | '' |
57 | | maxnum | 最多渲染多少tab,用以解决大数据量tab | Number | 20 |
58 | | beforeChangeTab | 参数是key,切换tab之前,可以返回boolean值,阻止切换 | Function | (key) => true |
59 | | stretch (todo) | 是否根据tab内容长度伸缩 | Boolean | false |
60 | | hideDirectionBar | 是否隐藏左右方向图标 | Boolean | false |
61 |
62 | **方法**
63 |
64 | | 方法名 | 描述 | 参数 |
65 | | ----------------- | ------------------- | ---- |
66 | | goBegin | 跳转到tab起始位置 | - |
67 | | goEnd | 跳转到tab末尾 | - |
68 | | scrollPrev | 向前滑动tab | - |
69 | | scrollNext | 向后滑动tab | - |
70 | | scrollToActiveTab | 定位到active的tab上 | - |
71 |
72 | **事件**
73 |
74 | | 事件名 | 描述 | 参数 |
75 | | ------------- | ----------------------------- | ------------ |
76 | | on-click | 当tab点击时触发,返回tab的key | key tab的key |
77 | | on-tab-remove | 当删除tab时触发,返回tab的key | key tab的key |
78 |
79 | **插槽**
80 |
81 | | 插槽名 | 数据 | 例子 |
82 | | ------ | ---------------- | -------------------------------------------------------------------- |
83 | | tab | tab:单个tab数据 | |
84 |
85 |
86 | **License**
87 |
88 | ------
89 |
90 | [The 996ICU License (996ICU)](LICENSE)
91 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | '@babel/preset-env',
5 | {
6 | targets: {
7 | chrome: '58',
8 | ie: '9'
9 | }
10 | }
11 | ]
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/build-test/build.js:
--------------------------------------------------------------------------------
1 | require('./check-versions')()
2 |
3 | process.env.NODE_ENV = 'production'
4 |
5 | var ora = require('ora')
6 | var rm = require('rimraf')
7 | var path = require('path')
8 | var chalk = require('chalk')
9 | var webpack = require('webpack')
10 | var config = require('../config')
11 | var webpackConfig = require('./webpack.prod.conf')
12 |
13 | var spinner = ora('building for production...')
14 | spinner.start()
15 |
16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
17 | if (err) throw err
18 | webpack(webpackConfig, function (err, stats) {
19 | spinner.stop()
20 | if (err) throw err
21 | process.stdout.write(stats.toString({
22 | colors: true,
23 | modules: false,
24 | children: false,
25 | chunks: false,
26 | chunkModules: false
27 | }) + '\n\n')
28 |
29 | console.log(chalk.cyan(' Build complete.\n'))
30 | console.log(chalk.yellow(
31 | ' Tip: built files are meant to be served over an HTTP server.\n' +
32 | ' Opening index.html over file:// won\'t work.\n'
33 | ))
34 | })
35 | })
36 |
--------------------------------------------------------------------------------
/build-test/check-versions.js:
--------------------------------------------------------------------------------
1 | var chalk = require('chalk')
2 | var semver = require('semver')
3 | var packageConfig = require('../package.json')
4 | var shell = require('shelljs')
5 | function exec (cmd) {
6 | return require('child_process').execSync(cmd).toString().trim()
7 | }
8 |
9 | var versionRequirements = [
10 | {
11 | name: 'node',
12 | currentVersion: semver.clean(process.version),
13 | versionRequirement: packageConfig.engines.node
14 | },
15 | ]
16 |
17 | if (shell.which('npm')) {
18 | versionRequirements.push({
19 | name: 'npm',
20 | currentVersion: exec('npm --version'),
21 | versionRequirement: packageConfig.engines.npm
22 | })
23 | }
24 |
25 | module.exports = function () {
26 | var warnings = []
27 | for (var i = 0; i < versionRequirements.length; i++) {
28 | var mod = versionRequirements[i]
29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
30 | warnings.push(mod.name + ': ' +
31 | chalk.red(mod.currentVersion) + ' should be ' +
32 | chalk.green(mod.versionRequirement)
33 | )
34 | }
35 | }
36 |
37 | if (warnings.length) {
38 | console.log('')
39 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
40 | console.log()
41 | for (var i = 0; i < warnings.length; i++) {
42 | var warning = warnings[i]
43 | console.log(' ' + warning)
44 | }
45 | console.log()
46 | process.exit(1)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/build-test/dev-client.js:
--------------------------------------------------------------------------------
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 | })
10 |
--------------------------------------------------------------------------------
/build-test/dev-server.js:
--------------------------------------------------------------------------------
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 | }
90 |
--------------------------------------------------------------------------------
/build-test/utils.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var config = require('../config')
3 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
4 |
5 | exports.assetsPath = function (_path) {
6 | var assetsSubDirectory = process.env.NODE_ENV === 'production'
7 | ? config.build.assetsSubDirectory
8 | : config.dev.assetsSubDirectory
9 | return path.posix.join(assetsSubDirectory, _path)
10 | }
11 |
12 | exports.cssLoaders = function (options) {
13 | options = options || {}
14 |
15 | var cssLoader = {
16 | loader: 'css-loader'
17 | }
18 |
19 | // generate loader string to be used with extract text plugin
20 | function generateLoaders (loader, loaderOptions) {
21 | var loaders = [cssLoader]
22 | if (loader) {
23 | loaders.push({
24 | loader: loader + '-loader',
25 | options: Object.assign({}, loaderOptions, {
26 | sourceMap: options.sourceMap
27 | })
28 | })
29 | }
30 |
31 | // Extract CSS when that option is specified
32 | // (which is the case during production build)
33 | if (options.extract) {
34 | return ExtractTextPlugin.extract({
35 | use: loaders,
36 | fallback: 'vue-style-loader'
37 | })
38 | } else {
39 | return ['vue-style-loader'].concat(loaders)
40 | }
41 | }
42 |
43 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
44 | return {
45 | css: generateLoaders(),
46 | postcss: generateLoaders(),
47 | less: generateLoaders('less'),
48 | sass: generateLoaders('sass', { indentedSyntax: true }),
49 | scss: generateLoaders('sass'),
50 | stylus: generateLoaders('stylus'),
51 | styl: generateLoaders('stylus')
52 | }
53 | }
54 |
55 | // Generate loaders for standalone style files (outside of .vue)
56 | exports.styleLoaders = function (options) {
57 | var output = []
58 | var loaders = exports.cssLoaders(options)
59 | for (var extension in loaders) {
60 | var loader = loaders[extension]
61 | output.push({
62 | test: new RegExp('\\.' + extension + '$'),
63 | use: loader
64 | })
65 | }
66 | return output
67 | }
68 |
--------------------------------------------------------------------------------
/build-test/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | var utils = require('./utils')
2 | var config = require('../config')
3 | var isProduction = process.env.NODE_ENV === 'production'
4 |
5 | module.exports = {
6 | loaders: utils.cssLoaders({
7 | sourceMap: isProduction
8 | ? config.build.productionSourceMap
9 | : config.dev.cssSourceMap,
10 | extract: isProduction
11 | })
12 | }
13 |
--------------------------------------------------------------------------------
/build-test/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var utils = require('./utils')
3 | var config = require('../config')
4 | var vueLoaderConfig = require('./vue-loader.conf')
5 |
6 | function resolve (dir) {
7 | return path.join(__dirname, '..', dir)
8 | }
9 |
10 | module.exports = {
11 | entry: {
12 | app: './test/index.js'
13 | },
14 | output: {
15 | path: config.build.assetsRoot,
16 | filename: '[name].js',
17 | publicPath: process.env.NODE_ENV === 'production'
18 | ? config.build.assetsPublicPath
19 | : config.dev.assetsPublicPath
20 | },
21 | resolve: {
22 | extensions: ['.js', '.vue', '.json'],
23 | alias: {
24 | 'vue$': 'vue/dist/vue.min.js',
25 | '@': resolve('src')
26 | }
27 | },
28 | module: {
29 | rules: [
30 | {
31 | test: /\.vue$/,
32 | loader: 'vue-loader',
33 | options: vueLoaderConfig
34 | },
35 | {
36 | test: /\.js$/,
37 | loader: 'babel-loader',
38 | include: [resolve('src'), resolve('test')]
39 | },
40 | {
41 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
42 | loader: 'url-loader',
43 | options: {
44 | limit: 10000,
45 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
46 | }
47 | },
48 | {
49 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
50 | loader: 'url-loader',
51 | options: {
52 | limit: 10000,
53 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
54 | }
55 | }
56 | ]
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/build-test/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | var utils = require('./utils')
2 | var webpack = require('webpack')
3 | var config = require('../config')
4 | var merge = require('webpack-merge')
5 | var baseWebpackConfig = require('./webpack.base.conf')
6 | var HtmlWebpackPlugin = require('html-webpack-plugin')
7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
8 | const VueLoaderPlugin = require('vue-loader/lib/plugin')
9 |
10 | // add hot-reload related code to entry chunks
11 | Object.keys(baseWebpackConfig.entry).forEach(function (name) {
12 | baseWebpackConfig.entry[name] = ['./build-test/dev-client'].concat(baseWebpackConfig.entry[name])
13 | })
14 |
15 | module.exports = merge(baseWebpackConfig, {
16 | module: {
17 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
18 | },
19 | // cheap-module-eval-source-map is faster for development
20 | devtool: '#cheap-module-eval-source-map',
21 | plugins: [
22 | new VueLoaderPlugin(),
23 |
24 | new webpack.DefinePlugin({
25 | 'process.env': config.dev.env
26 | }),
27 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
28 | new webpack.HotModuleReplacementPlugin(),
29 | new webpack.NoEmitOnErrorsPlugin(),
30 | // https://github.com/ampedandwired/html-webpack-plugin
31 | new HtmlWebpackPlugin({
32 | filename: 'index.html',
33 | template: 'index.html',
34 | inject: true
35 | }),
36 | new FriendlyErrorsPlugin()
37 | ]
38 | })
39 |
--------------------------------------------------------------------------------
/build-test/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var utils = require('./utils')
3 | var webpack = require('webpack')
4 | var config = require('../config')
5 | var merge = require('webpack-merge')
6 | var baseWebpackConfig = require('./webpack.base.conf')
7 | var CopyWebpackPlugin = require('copy-webpack-plugin')
8 | var HtmlWebpackPlugin = require('html-webpack-plugin')
9 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
11 | const VueLoaderPlugin = require('vue-loader/lib/plugin')
12 |
13 | var env = config.build.env
14 |
15 | var webpackConfig = merge(baseWebpackConfig, {
16 | module: {
17 | rules: utils.styleLoaders({
18 | sourceMap: config.build.productionSourceMap,
19 | extract: true
20 | })
21 | },
22 | devtool: config.build.productionSourceMap ? '#source-map' : false,
23 | output: {
24 | path: config.build.assetsRoot,
25 | filename: utils.assetsPath('js/[name].[chunkhash].js'),
26 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
27 | },
28 | plugins: [
29 | new VueLoaderPlugin(),
30 |
31 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
32 | new webpack.DefinePlugin({
33 | 'process.env': env
34 | }),
35 | // generate dist index.html with correct asset hash for caching.
36 | // you can customize output by editing /index.html
37 | // see https://github.com/ampedandwired/html-webpack-plugin
38 | new HtmlWebpackPlugin({
39 | filename: config.build.index,
40 | template: 'index.html',
41 | inject: true,
42 | minify: {
43 | removeComments: true,
44 | collapseWhitespace: true,
45 | removeAttributeQuotes: true
46 | // more options:
47 | // https://github.com/kangax/html-minifier#options-quick-reference
48 | },
49 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin
50 | chunksSortMode: 'dependency'
51 | }),
52 | // split vendor js into its own file
53 | new webpack.optimize.CommonsChunkPlugin({
54 | name: 'vendor',
55 | minChunks: function(module, count) {
56 | // any required modules inside node_modules are extracted to vendor
57 | return (
58 | module.resource &&
59 | /\.js$/.test(module.resource) &&
60 | module.resource.indexOf(
61 | path.join(__dirname, '../node_modules')
62 | ) === 0
63 | )
64 | }
65 | }),
66 | // extract webpack runtime and module manifest to its own file in order to
67 | // prevent vendor hash from being updated whenever app bundle is updated
68 | new webpack.optimize.CommonsChunkPlugin({
69 | name: 'manifest',
70 | chunks: ['vendor']
71 | }),
72 | // copy custom static assets
73 | new CopyWebpackPlugin([{
74 | from: path.resolve(__dirname, '../static'),
75 | to: config.build.assetsSubDirectory,
76 | ignore: ['.*']
77 | }])
78 | ]
79 | })
80 |
81 | if (config.build.productionGzip) {
82 | var CompressionWebpackPlugin = require('compression-webpack-plugin')
83 |
84 | webpackConfig.plugins.push(
85 | new CompressionWebpackPlugin({
86 | asset: '[path].gz[query]',
87 | algorithm: 'gzip',
88 | test: new RegExp(
89 | '\\.(' +
90 | config.build.productionGzipExtensions.join('|') +
91 | ')$'
92 | ),
93 | threshold: 10240,
94 | minRatio: 0.8
95 | })
96 | )
97 | }
98 |
99 | if (config.build.bundleAnalyzerReport) {
100 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
101 | webpackConfig.plugins.push(new BundleAnalyzerPlugin())
102 | }
103 |
104 | module.exports = webpackConfig
--------------------------------------------------------------------------------
/build/build.dev.js:
--------------------------------------------------------------------------------
1 | require('./check-versions')()
2 |
3 | var ora = require('ora')
4 | var rm = require('rimraf')
5 | var path = require('path')
6 | var chalk = require('chalk')
7 | var webpack = require('webpack')
8 | var webpackConfig = require('./webpack.dev.conf')
9 |
10 | var spinner = ora('building for production...')
11 | spinner.start()
12 |
13 | rm(path.join(path.resolve(__dirname, '../dist'), 'static'), err => {
14 | if (err) throw err
15 | webpack(webpackConfig, function (err, stats) {
16 | spinner.stop()
17 | if (err) throw err
18 | process.stdout.write(stats.toString({
19 | colors: true,
20 | modules: false,
21 | children: false,
22 | chunks: false,
23 | chunkModules: false
24 | }) + '\n\n')
25 |
26 | console.log(chalk.cyan(' Build complete.\n'))
27 | console.log(chalk.yellow(
28 | ' Tip: built files are meant to be served over an HTTP server.\n' +
29 | ' Opening index.html over file:// won\'t work.\n'
30 | ))
31 | })
32 | })
33 |
--------------------------------------------------------------------------------
/build/build.js:
--------------------------------------------------------------------------------
1 | require('./check-versions')()
2 |
3 | //终端微调器
4 | var ora = require('ora')
5 | //记录日志
6 | var rm = require('rimraf')
7 | var path = require('path')
8 | //粉笔
9 | var chalk = require('chalk')
10 | var webpack = require('webpack')
11 | var webpackConfig = require('./webpack.prod.conf')
12 |
13 | var spinner = ora('building for production...')
14 | spinner.start()
15 |
16 | rm(path.join(path.resolve(__dirname, '../dist'), 'static'), err => {
17 | if (err) throw err
18 | webpack(webpackConfig, function (err, stats) {
19 | spinner.stop()
20 | if (err) throw err
21 | process.stdout.write(stats.toString({
22 | colors: true,
23 | modules: false,
24 | children: false,
25 | chunks: false,
26 | chunkModules: false
27 | }) + '\n\n')
28 |
29 | console.log(chalk.cyan(' Build complete.\n'))
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/build/check-versions.js:
--------------------------------------------------------------------------------
1 | var chalk = require('chalk')
2 | var semver = require('semver')
3 | var packageConfig = require('../package.json')
4 | var shell = require('shelljs')
5 | function exec (cmd) {
6 | return require('child_process').execSync(cmd).toString().trim()
7 | }
8 |
9 | var versionRequirements = [
10 | {
11 | name: 'node',
12 | currentVersion: semver.clean(process.version),
13 | versionRequirement: packageConfig.engines.node
14 | },
15 | ]
16 |
17 | if (shell.which('npm')) {
18 | versionRequirements.push({
19 | name: 'npm',
20 | currentVersion: exec('npm --version'),
21 | versionRequirement: packageConfig.engines.npm
22 | })
23 | }
24 |
25 | module.exports = function () {
26 | var warnings = []
27 | for (var i = 0; i < versionRequirements.length; i++) {
28 | var mod = versionRequirements[i]
29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
30 | warnings.push(mod.name + ': ' +
31 | chalk.red(mod.currentVersion) + ' should be ' +
32 | chalk.green(mod.versionRequirement)
33 | )
34 | }
35 | }
36 |
37 | if (warnings.length) {
38 | console.log('')
39 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
40 | console.log()
41 | for (var i = 0; i < warnings.length; i++) {
42 | var warning = warnings[i]
43 | console.log(' ' + warning)
44 | }
45 | console.log()
46 | process.exit(1)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/build/utils.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | exports.assetsPath = function (_path) {
4 | var assetsSubDirectory = 'static'
5 | return path.posix.join(assetsSubDirectory, _path)
6 | }
7 |
8 | exports.cssLoaders = function (options) {
9 | options = options || {}
10 |
11 | var cssLoader = {
12 | test: /\.css$/,
13 | loader: 'css-loader',
14 | options: {
15 | minimize: true,
16 | sourceMap: options.sourceMap
17 | }
18 | }
19 |
20 | // generate loader string to be used with extract text plugin
21 | function generateLoaders (loader, loaderOptions) {
22 | var loaders = [cssLoader]
23 | if (loader) {
24 | loaders.push({
25 | loader: loader + '-loader',
26 | options: Object.assign({}, loaderOptions, {
27 | sourceMap: options.sourceMap
28 | })
29 | })
30 | }
31 | }
32 |
33 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
34 | return {
35 | css: generateLoaders(),
36 | postcss: generateLoaders(),
37 | less: generateLoaders('less'),
38 | sass: generateLoaders('sass', { indentedSyntax: true }),
39 | scss: generateLoaders('sass'),
40 | stylus: generateLoaders('stylus'),
41 | styl: generateLoaders('stylus')
42 | }
43 | }
44 |
45 | // Generate loaders for standalone style files (outside of .vue)
46 | exports.styleLoaders = function (options) {
47 | var output = []
48 | var loaders = exports.cssLoaders(options)
49 | for (var extension in loaders) {
50 | var loader = loaders[extension]
51 | output.push({
52 | test: new RegExp('\\.' + extension + '$'),
53 | use: loader
54 | })
55 | }
56 | return output
57 | }
58 |
--------------------------------------------------------------------------------
/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | var utils = require('./utils')
2 |
3 | module.exports = {
4 | loaders: utils.cssLoaders({
5 | sourceMap: true,
6 | extract: true
7 | })
8 | }
9 |
--------------------------------------------------------------------------------
/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var utils = require('./utils')
3 |
4 | function resolve (dir) {
5 | return path.join(__dirname, '..', dir)
6 | }
7 |
8 | module.exports = {
9 | entry: {
10 | app: './src/index.js'
11 | },
12 | resolve: {
13 | extensions: ['.js', '.vue', '.json'],
14 | alias: {
15 | 'vue$': 'vue/dist/vue.esm.js',
16 | '@': resolve('src')
17 | }
18 | },
19 | externals: {
20 | vue: {
21 | root: 'Vue',
22 | commonjs: 'vue',
23 | commonjs2: 'vue',
24 | amd: 'vue'
25 | },
26 | 'view-design': 'view-design'
27 | },
28 | module: {
29 | rules: [
30 | // {
31 | // test: /\.(js|vue)$/,
32 | // loader: 'eslint-loader',
33 | // enforce: 'pre',
34 | // include: [resolve('src'), resolve('test')],
35 | // options: {
36 | // formatter: require('eslint-friendly-formatter')
37 | // }
38 | // },
39 | {
40 | test: /\.vue$/,
41 | loader: 'vue-loader',
42 | options: {
43 | loaders: {
44 | css: [
45 | 'vue-style-loader',
46 | {
47 | loader: 'css-loader',
48 | options: {
49 | sourceMap: true,
50 | extract: true
51 | },
52 | },
53 | ],
54 | less: [
55 | 'vue-style-loader',
56 | {
57 | loader: 'css-loader',
58 | options: {
59 | sourceMap: true,
60 | },
61 | },
62 | {
63 | loader: 'less-loader',
64 | options: {
65 | sourceMap: true,
66 | },
67 | },
68 | ]
69 | },
70 | postLoaders: {
71 | html: 'babel-loader?sourceMap'
72 | },
73 | sourceMap: true
74 | }
75 | },
76 | {
77 | test: /\.js$/,
78 | loader: 'babel-loader',
79 | include: [resolve('src'), resolve('test')]
80 | },
81 | {
82 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
83 | loader: 'url-loader',
84 | options: {
85 | limit: 10000,
86 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
87 | }
88 | },
89 | {
90 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
91 | loader: 'url-loader',
92 | options: {
93 | limit: 10000,
94 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
95 | }
96 | },
97 | {
98 | test: /\.css$/,
99 | loaders: [
100 | {
101 | loader: 'style-loader',
102 | options: {
103 | sourceMap: true,
104 | },
105 | },
106 | {
107 | loader: 'css-loader',
108 | options: {
109 | sourceMap: true,
110 | },
111 | }
112 | ]
113 | }
114 | ]
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var utils = require('./utils')
3 | var merge = require('webpack-merge')
4 | var baseWebpackConfig = require('./webpack.base.conf')
5 | const VueLoaderPlugin = require('vue-loader/lib/plugin')
6 |
7 | var webpackConfig = merge(baseWebpackConfig, {
8 | module: {
9 | rules: utils.styleLoaders({
10 | sourceMap: true
11 | })
12 | },
13 | devtool:'eval-source-map',
14 | output: {
15 | path: path.resolve(__dirname, '../dist'),
16 | publicPath: '',
17 | filename: 'vue-tabs.min.js',
18 | library: 'vueTabs',
19 | libraryTarget: 'umd',
20 | umdNamedDefine: true
21 | },
22 | plugins: [
23 | new VueLoaderPlugin()
24 | ]
25 | })
26 |
27 | module.exports = webpackConfig
28 |
--------------------------------------------------------------------------------
/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var utils = require('./utils')
3 | var merge = require('webpack-merge')
4 | var baseWebpackConfig = require('./webpack.base.conf')
5 | const VueLoaderPlugin = require('vue-loader/lib/plugin')
6 |
7 |
8 | var webpackConfig = merge(baseWebpackConfig, {
9 | mode: 'production',
10 | module: {
11 | rules: utils.styleLoaders({
12 | sourceMap: true
13 | })
14 | },
15 | devtool:'source-map',
16 | output: {
17 | path: path.resolve(__dirname, '../dist'),
18 | publicPath: '',
19 | filename: 'vue-tabs.min.js',
20 | library: 'vueTabs',
21 | libraryTarget: 'umd',
22 | umdNamedDefine: true
23 | },
24 | optimization:{
25 | minimize: true
26 | },
27 | plugins: [
28 | new VueLoaderPlugin()
29 | ]
30 | })
31 |
32 | module.exports = webpackConfig
33 |
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | var merge = require('webpack-merge')
2 | var prodEnv = require('./prod.env')
3 |
4 | module.exports = merge(prodEnv, {
5 | NODE_ENV: '"development"'
6 | })
7 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | // see http://vuejs-templates.github.io/webpack for documentation.
2 | var path = require('path')
3 |
4 | module.exports = {
5 | build: {
6 | env: require('./prod.env'),
7 | index: path.resolve(__dirname, '../dist/index.html'),
8 | assetsRoot: path.resolve(__dirname, '../dist'),
9 | assetsSubDirectory: 'static',
10 | assetsPublicPath: './',
11 | productionSourceMap: true,
12 | // Gzip off by default as many popular static hosts such as
13 | // Surge or Netlify already gzip all static assets for you.
14 | // Before setting to `true`, make sure to:
15 | // npm install --save-dev compression-webpack-plugin
16 | productionGzip: false,
17 | productionGzipExtensions: ['js', 'css'],
18 | // Run the build command with an extra argument to
19 | // View the bundle analyzer report after build finishes:
20 | // `npm run build --report`
21 | // Set to `true` or `false` to always turn it on or off
22 | bundleAnalyzerReport: process.env.npm_config_report
23 | },
24 | dev: {
25 | env: require('./dev.env'),
26 | port: 8084,
27 | autoOpenBrowser: true,
28 | assetsSubDirectory: 'static',
29 | assetsPublicPath: '/',
30 | proxyTable: {},
31 | // CSS Sourcemaps off by default because relative paths are "buggy"
32 | // with this option, according to the CSS-Loader README
33 | // (https://github.com/webpack/css-loader#sourcemaps)
34 | // In our experience, they generally work as expected,
35 | // just be aware of this issue when enabling this option.
36 | cssSourceMap: false
37 | }
38 | }
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | NODE_ENV: '"production"'
3 | }
4 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-tab-component",
3 | "version": "1.6.4",
4 | "description": "easy tabs component",
5 | "main": "dist/vue-tabs.min.js",
6 | "scripts": {
7 | "dev": "node build-test/dev-server.js",
8 | "build:dev": "node ./build/build.dev.js",
9 | "build": "node ./build/build.js",
10 | "lint": "./node_modules/.bin/eslint --fix --ext .js,.vue src",
11 | "test": "echo \"Error: no test specified\" && exit 1"
12 | },
13 | "files": [
14 | "dist"
15 | ],
16 | "repository": {
17 | "type": "git",
18 | "url": "git+https://github.com/qq240814476/vue-tab-component.git"
19 | },
20 | "author": "malin",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/qq240814476/vue-tab-component/issues"
24 | },
25 | "homepage": "https://github.com/qq240814476/vue-tab-component#readme",
26 | "engines": {
27 | "node": ">= 10.15.0",
28 | "npm": ">= 6.0.0"
29 | },
30 | "keywords": [
31 | "vue-tab",
32 | "vue-tab-component",
33 | "DOM render",
34 | "light tab",
35 | "ml",
36 | "left-tab",
37 | "right-tab",
38 | "tabPosition",
39 | "left",
40 | "right",
41 | "vue",
42 | "vue-components"
43 | ],
44 | "publishConfig": {
45 | "registry": "https://registry.npmjs.org/"
46 | },
47 | "devDependencies": {
48 | "view-design": "^4.0.2",
49 | "vue": "^2.6.10",
50 | "vue-template-compiler": "^2.6.10",
51 | "webpack": "^4.30.0",
52 | "@babel/core": "^7.5.5",
53 | "@babel/preset-env": "^7.5.5",
54 | "babel-loader": "^8.0.5",
55 | "connect-history-api-fallback": "^1.6.0",
56 | "css-loader": "^2.1.1",
57 | "eslint": "^5.16.0",
58 | "eslint-config-standard": "^12.0.0",
59 | "eslint-plugin-import": "^2.17.2",
60 | "eslint-plugin-node": "^8.0.1",
61 | "eslint-plugin-promise": "^4.1.1",
62 | "eslint-plugin-standard": "^4.0.0",
63 | "eslint-plugin-vue": "^5.2.2",
64 | "eventsource-polyfill": "^0.9.6",
65 | "express": "^4.17.1",
66 | "extract-text-webpack-plugin": "^3.0.2",
67 | "file-loader": "^4.2.0",
68 | "friendly-errors-webpack-plugin": "^1.7.0",
69 | "html-webpack-plugin": "^3.2.0",
70 | "http-proxy-middleware": "^0.20.0",
71 | "opn": "^6.0.0",
72 | "optimize-css-assets-webpack-plugin": "^5.0.1",
73 | "ora": "^3.4.0",
74 | "shelljs": "^0.8.3",
75 | "style-loader": "^0.23.1",
76 | "terser-webpack-plugin": "^1.2.3",
77 | "url-loader": "^2.2.0",
78 | "vue-loader": "^15.7.0",
79 | "webpack-dev-middleware": "^3.7.2",
80 | "webpack-hot-middleware": "^2.25.0",
81 | "webpack-merge": "^4.2.1"
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/css/card.css:
--------------------------------------------------------------------------------
1 | /* card */
2 | .card .ml-tab-item {
3 | display: inline-block;
4 | padding: 2px 10px;
5 | cursor: default;
6 | margin-right: 5px;
7 | border: 1px solid #dcdee2;
8 | border-bottom: 0;
9 | border-radius: 4px 4px 0 0;
10 | transition: padding-left 0.3s ease-in-out;
11 | }
12 | .card .ml-tab-item:hover{
13 | color:#5b87a5;
14 | }
15 | .card .ml-tab-container {
16 | position: relative;
17 | overflow: hidden;
18 | white-space: nowrap;
19 | transition: all 0.3s ease-in-out;
20 | }
21 | .card .ml-tab-item-slot {
22 | transition: all 0.3s ease-in-out;
23 | display: inline-block;
24 | height: 28px;
25 | line-height: 28px;
26 | padding: 0px;
27 | box-sizing: border-box;
28 | list-style: none;
29 | font-size: 13px;
30 | position: relative;
31 | }
32 | .card .ml-tab-item-slot div,
33 | .card .ml-tab-item-close-div{
34 | display: inline-block;
35 | vertical-align: middle;
36 | }
37 | .card .ml-tab-item-close-div{
38 | width: 16px;
39 | line-height: 16px;
40 | }
41 | .ml-tab-is-left .ml-tab-item,
42 | .ml-tab-is-right .ml-tab-item{
43 | display: block;
44 | padding: 5px 2px;
45 | margin-bottom: 5px;
46 | border: 1px solid #dcdee2;
47 | transition: padding-top 0.3s ease-in-out;
48 | }
49 | .ml-tab-is-left .ml-tab-item-close
50 | .ml-tab-is-right .ml-tab-item-close{
51 | margin-left: 15px;
52 | }
53 | .ml-tab-is-left .ml-tab-item{
54 | border-right: 0;
55 | border-radius: 4px 0px 0px 4px;
56 | }
57 | .ml-tab-is-right .ml-tab-item{
58 | border-left: 0;
59 | border-radius: 0px 4px 4px 0;
60 | }
61 | .ml-tab-is-left .ml-tab-item div,
62 | .ml-tab-is-right .ml-tab-item div{
63 | display: block;
64 | margin: auto;
65 | text-align: center;
66 | width: 28px;
67 | font-size: 13px;
68 | line-height: 28px;
69 | }
70 | .ml-tab-is-left .ml-tab-item-slot div,
71 | .ml-tab-is-right .ml-tab-item-slot div{
72 | writing-mode: vertical-lr;
73 | writing-mode: tb-lr;
74 | letter-spacing: 2px;
75 | }
76 | .ml-tab-is-left i,
77 | .ml-tab-is-right i,
78 | .ml-tab-is-left .ml-tab-item-slot,
79 | .ml-tab-is-right .ml-tab-item-slot{
80 | height: auto;
81 | }
82 | .ml-tab-item.ml-tab-item-active {
83 | padding: 2px 5px 0px 5px;
84 | color: #5b87a5;
85 | border-bottom: 2px solid #5b87a5;
86 | }
87 | .ml-tab-is-left .ml-tab-item.ml-tab-item-active {
88 | padding: 5px 0px 5px 2px;
89 | border-right: 2px solid #5b87a5;
90 | border-bottom: 1px solid #dcdee2;
91 | }
92 | .ml-tab-is-right .ml-tab-item.ml-tab-item-active {
93 | padding: 5px 2px 5px 0px;
94 | border-left: 2px solid #5b87a5;
95 | border-bottom: 1px solid #dcdee2;
96 | }
97 | .ml-tab-container-left,
98 | .ml-tab-container-right {
99 | position: absolute;
100 | line-height: 32px;
101 | cursor: pointer;
102 | font-size: 14px;
103 | z-index: 200;
104 | background: #fff;
105 | }
106 | .ml-tab-container-left {
107 | left: 0px;
108 | transition: all 0.3s ease-in-out;
109 | }
110 | .ml-tab-container-right {
111 | right: 0px;
112 | transition: all 0.3s ease-in-out;
113 | }
114 | .ml-tab-item-scroll {
115 | overflow: hidden;
116 | white-space: nowrap;
117 | }
118 | .ml-tab-item-scroll-nav {
119 | float: left;
120 | list-style: none;
121 | transition: all 0.3s ease-in-out;
122 | }
123 | .ml-tab-is-left {
124 | float: left;
125 | height: 100%;
126 | margin-bottom: 0;
127 | margin-right: 10px;
128 | }
129 | .ml-tab-is-bottom {
130 | margin-bottom: 0;
131 | }
132 | .ml-tab-is-right {
133 | float: right;
134 | height: 100%;
135 | margin-bottom: 0;
136 | margin-left: 10px;
137 | }
138 | .ml-tab-is-left .ml-tab-item,
139 | .ml-tab-is-right .ml-tab-item {
140 | display: block;
141 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
142 | }
143 | .ml-tab-is-left .ml-tab-container,
144 | .ml-tab-is-right .ml-tab-container {
145 | height: 100%;
146 | }
147 | .ml-tab-is-left .ml-tab-item-scroll,
148 | .ml-tab-is-right .ml-tab-item-scroll {
149 | height: 100%;
150 | }
151 | .ml-tab-is-left .ml-tab-container-left,
152 | .ml-tab-is-right .ml-tab-container-left {
153 | top: 0;
154 | transform: rotate(90deg) translateY(-3px);
155 | transform-origin: right center;
156 | -ms-transform: rotate(90deg) translateY(-3px); /* Internet Explorer 9*/
157 | -ms-transform-origin: right center;
158 | -moz-transform: rotate(90deg) translateY(-3px); /* Firefox */
159 | -moz-transform-origin: right center;
160 | -webkit-transform: rotate(90deg) translateY(-3px); /* Safari 和 Chrome */
161 | -webkit-transform-origin: right center;
162 | -o-transform: rotate(90deg) translateY(-3px); /* Opera */
163 | -o-transform-origin: right center;
164 | }
165 | .ml-tab-is-left .ml-tab-container-right,
166 | .ml-tab-is-right .ml-tab-container-right {
167 | bottom: 0;
168 | transform: rotate(90deg) translateY(6px);
169 | transform-origin: left center;
170 | -ms-transform: rotate(90deg) translateY(6px); /* Internet Explorer 9*/
171 | -ms-transform-origin: left center;
172 | -moz-transform: rotate(90deg) translateY(6px); /* Firefox */
173 | -moz-transform-origin: left center;
174 | -webkit-transform: rotate(90deg) translateY(6px); /* Safari 和 Chrome */
175 | -webkit-transform-origin: left center;
176 | -o-transform: rotate(90deg) translateY(6px); /* Opera */
177 | -o-transform-origin: left c
178 | }
179 | .ml-tab-item-close:hover{
180 | background-color: #C0C4CC;
181 | color: #FFFFFF;
182 | }
183 | .ml-tab-item-close{
184 | font-size: 16px;
185 | border-radius: 50%;
186 | vertical-align: text-top;
187 | transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
188 | }
--------------------------------------------------------------------------------
/src/css/card.less:
--------------------------------------------------------------------------------
1 | div {
2 | box-sizing: border-box;
3 | }
4 |
5 | .card{
6 | & .ml-tab{
7 | &-item{
8 | display: inline-block;
9 | padding: 2px 10px;
10 | cursor: default;
11 | margin-right: 5px;
12 | border: 1px solid #dcdee2;
13 | border-bottom: 0;
14 | border-radius: 4px 4px 0 0;
15 | transition: padding-left 0.3s ease-in-out;
16 | &:hover{
17 | color:#5b87a5;
18 | }
19 | &-slot{
20 | transition: all 0.3s ease-in-out;
21 | line-height: 28px;
22 | padding: 0px;
23 | list-style: none;
24 | font-size: 13px;
25 | position: relative;
26 | & div{
27 | width: 16px;
28 | line-height: 16px;
29 | display: inline-block;
30 | vertical-align: middle;
31 | }
32 | }
33 | &-close-div{
34 | display: inline-block;
35 | vertical-align: middle;
36 | }
37 | }
38 | &-container{
39 | position: relative;
40 | overflow: hidden;
41 | white-space: nowrap;
42 | transition: all 0.3s ease-in-out;
43 | }
44 |
45 | &-is{
46 | // left
47 | &-left{
48 |
49 | }
50 |
51 | // right
52 | &-right{
53 |
54 | }
55 | }
56 | }
57 | }
58 | /* card */
59 | .ml-tab-is-left .ml-tab-item,
60 | .ml-tab-is-right .ml-tab-item{
61 | display: block;
62 | padding: 5px 2px;
63 | margin-bottom: 5px;
64 | border: 1px solid #dcdee2;
65 | transition: padding-top 0.3s ease-in-out;
66 | }
67 | .ml-tab-is-left .ml-tab-item-close
68 | .ml-tab-is-right .ml-tab-item-close{
69 | margin-left: 15px;
70 | }
71 | .ml-tab-is-left .ml-tab-item{
72 | border-right: 0;
73 | border-radius: 4px 0px 0px 4px;
74 | }
75 | .ml-tab-is-right .ml-tab-item{
76 | border-left: 0;
77 | border-radius: 0px 4px 4px 0;
78 | }
79 | .ml-tab-is-left .ml-tab-item div,
80 | .ml-tab-is-right .ml-tab-item div{
81 | display: block;
82 | margin: auto;
83 | text-align: center;
84 | width: 28px;
85 | font-size: 13px;
86 | line-height: 28px;
87 | }
88 | .ml-tab-is-left .ml-tab-item-slot div,
89 | .ml-tab-is-right .ml-tab-item-slot div{
90 | writing-mode: vertical-lr;
91 | writing-mode: tb-lr;
92 | letter-spacing: 2px;
93 | }
94 | .ml-tab-is-left i,
95 | .ml-tab-is-right i,
96 | .ml-tab-is-left .ml-tab-item-slot,
97 | .ml-tab-is-right .ml-tab-item-slot{
98 | height: auto;
99 | }
100 | .ml-tab-item.ml-tab-item-active {
101 | padding: 2px 5px 0px 5px;
102 | color: #5b87a5;
103 | border-bottom: 2px solid #5b87a5;
104 | }
105 | .ml-tab-is-left .ml-tab-item.ml-tab-item-active {
106 | padding: 5px 0px 5px 2px;
107 | border-right: 2px solid #5b87a5;
108 | border-bottom: 1px solid #dcdee2;
109 | }
110 | .ml-tab-is-right .ml-tab-item.ml-tab-item-active {
111 | padding: 5px 2px 5px 0px;
112 | border-left: 2px solid #5b87a5;
113 | border-bottom: 1px solid #dcdee2;
114 | }
115 | .ml-tab-container-left,
116 | .ml-tab-container-right {
117 | position: absolute;
118 | line-height: 32px;
119 | cursor: pointer;
120 | font-size: 14px;
121 | z-index: 200;
122 | background: #fff;
123 | }
124 | .ml-tab-container-left {
125 | left: 0px;
126 | transition: all 0.3s ease-in-out;
127 | }
128 | .ml-tab-container-right {
129 | right: 0px;
130 | transition: all 0.3s ease-in-out;
131 | }
132 | .ml-tab-item-scroll {
133 | overflow: hidden;
134 | white-space: nowrap;
135 | }
136 | .ml-tab-item-scroll-nav {
137 | float: left;
138 | list-style: none;
139 | transition: all 0.3s ease-in-out;
140 | }
141 | .ml-tab-is-left {
142 | float: left;
143 | height: 100%;
144 | margin-bottom: 0;
145 | margin-right: 10px;
146 | }
147 | .ml-tab-is-bottom {
148 | margin-bottom: 0;
149 | }
150 | .ml-tab-is-right {
151 | float: right;
152 | height: 100%;
153 | margin-bottom: 0;
154 | margin-left: 10px;
155 | }
156 | .ml-tab-is-left .ml-tab-item,
157 | .ml-tab-is-right .ml-tab-item {
158 | display: block;
159 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
160 | }
161 | .ml-tab-is-left .ml-tab-container,
162 | .ml-tab-is-right .ml-tab-container {
163 | height: 100%;
164 | }
165 | .ml-tab-is-left .ml-tab-item-scroll,
166 | .ml-tab-is-right .ml-tab-item-scroll {
167 | height: 100%;
168 | }
169 | .ml-tab-is-left .ml-tab-container-left,
170 | .ml-tab-is-right .ml-tab-container-left {
171 | top: 0;
172 | transform: rotate(90deg) translateY(-3px);
173 | transform-origin: right center;
174 | -ms-transform: rotate(90deg) translateY(-3px); /* Internet Explorer 9*/
175 | -ms-transform-origin: right center;
176 | -moz-transform: rotate(90deg) translateY(-3px); /* Firefox */
177 | -moz-transform-origin: right center;
178 | -webkit-transform: rotate(90deg) translateY(-3px); /* Safari 和 Chrome */
179 | -webkit-transform-origin: right center;
180 | -o-transform: rotate(90deg) translateY(-3px); /* Opera */
181 | -o-transform-origin: right center;
182 | }
183 | .ml-tab-is-left .ml-tab-container-right,
184 | .ml-tab-is-right .ml-tab-container-right {
185 | bottom: 0;
186 | transform: rotate(90deg) translateY(6px);
187 | transform-origin: left center;
188 | -ms-transform: rotate(90deg) translateY(6px); /* Internet Explorer 9*/
189 | -ms-transform-origin: left center;
190 | -moz-transform: rotate(90deg) translateY(6px); /* Firefox */
191 | -moz-transform-origin: left center;
192 | -webkit-transform: rotate(90deg) translateY(6px); /* Safari 和 Chrome */
193 | -webkit-transform-origin: left center;
194 | -o-transform: rotate(90deg) translateY(6px); /* Opera */
195 | -o-transform-origin: left c
196 | }
197 | .ml-tab-item-close:hover{
198 | background-color: #C0C4CC;
199 | color: #FFFFFF;
200 | }
201 | .ml-tab-item-close{
202 | font-size: 16px;
203 | border-radius: 50%;
204 | vertical-align: text-top;
205 | transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
206 | }
--------------------------------------------------------------------------------
/src/css/line.css:
--------------------------------------------------------------------------------
1 | /* line */
2 | .line .ml-tab-item {
3 | display: inline-block;
4 | padding: 2px 10px;
5 | margin-right: 5px;
6 | }
7 | .line .ml-tab-item:hover{
8 | color:#5b87a5;
9 | }
10 | .line .ml-tab-container {
11 | position: relative;
12 | overflow: hidden;
13 | white-space: nowrap;
14 | transition: all 0.3s ease-in-out;
15 | }
16 | .line .ml-tab-item-slot {
17 | transition: all 0.3s ease-in-out;
18 | display: inline-block;
19 | height: 28px;
20 | line-height: 28px;
21 | padding: 0px;
22 | box-sizing: border-box;
23 | list-style: none;
24 | font-size: 13px;
25 | position: relative;
26 | }
27 | .line .ml-tab-item-slot div,
28 | .line .ml-tab-item-close-div{
29 | display: inline-block;
30 | vertical-align: middle;
31 | }
32 | .line .ml-tab-item-close-div{
33 | width: 16px;
34 | line-height: 16px;
35 | }
36 | .line .ml-tab-item.ml-tab-item-active {
37 | padding: 2px 10px 0px 10px;
38 | color: #5b87a5;
39 | border-bottom: 2px solid #5b87a5;
40 | }
41 | .line .ml-tab-container-left,
42 | .line .ml-tab-container-right {
43 | position: absolute;
44 | line-height: 32px;
45 | cursor: pointer;
46 | font-size: 14px;
47 | z-index: 200;
48 | background: #fff;
49 | }
50 | .line .ml-tab-container-left {
51 | left: 0px;
52 | transition: all 0.3s ease-in-out;
53 | }
54 | .line .ml-tab-container-right {
55 | right: 0px;
56 | transition: all 0.3s ease-in-out;
57 | }
58 | .line .ml-tab-item-scroll {
59 | overflow: hidden;
60 | white-space: nowrap;
61 | }
62 | .line .ml-tab-item-scroll-nav {
63 | float: left;
64 | list-style: none;
65 | transition: all 0.3s ease-in-out;
66 | }
67 | .line .ml-tab-is-bottom {
68 | margin-bottom: 0;
69 | }
70 | .line .ml-tab-item-close:hover{
71 | background-color: #C0C4CC;
72 | color: #FFFFFF;
73 | }
74 | .line .ml-tab-item-close{
75 | font-size: 16px;
76 | border-radius: 50%;
77 | vertical-align: text-top;
78 | transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
79 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import tabs from './tabs.vue'
2 |
3 | const tab = {
4 | install: function (Vue) {
5 | if (typeof window !== 'undefined' && window.Vue) {
6 | Vue = window.Vue
7 | }
8 | Vue.component('tabs', tabs)
9 | }
10 | }
11 |
12 | export default tab
13 |
--------------------------------------------------------------------------------
/src/js/utils.js:
--------------------------------------------------------------------------------
1 | const charWidthTable = {
2 | 1: ['.', '!', '|', ','],
3 | 2: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
4 | 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
5 | 'W', 'X', 'Y', 'Z', '@', '~', '#', '$', '%', '^', '&', '*', '(', ')', '-', '=', '+', '{', '}', '[', ']', '\\', '/', '<', '>',
6 | '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'
7 | ]
8 | }
9 | const getCharWidth = (char) => {
10 | for (let i = 0; i < charWidthTable[1].length; i += 1) {
11 | if (char === charWidthTable[1][i]) {
12 | return 1
13 | }
14 | }
15 | for (let i = 0; i < charWidthTable[2].length; i += 1) {
16 | if (char === charWidthTable[2][i]) {
17 | return 1.5
18 | }
19 | }
20 | return 3
21 | }
22 |
23 | export const shortText = (text, length) => {
24 | if (!text) return ''
25 | let showtextShort = text
26 | let short = true
27 | const strArr = text.split('')
28 | const strWidth = []
29 | let sum = 0
30 | for (let i = 0; i < strArr.length; i += 1) {
31 | sum += getCharWidth(strArr[i])
32 | strWidth.push(sum)
33 | }
34 | const lengthFlag = length * 3
35 | if (sum > lengthFlag) {
36 | short = false
37 | let splitPoint = 0
38 | for (let j = 0; j < strWidth.length; j += 1) {
39 | if (strWidth[j] > lengthFlag) {
40 | splitPoint = j
41 | break
42 | }
43 | }
44 | showtextShort = `${text.substring(0, splitPoint)}...`
45 | }
46 | return short ? false : showtextShort
47 | }
48 |
--------------------------------------------------------------------------------
/src/mixins/maxNumMixin.js:
--------------------------------------------------------------------------------
1 | export default {
2 | props: {
3 | maxnum: {
4 | type: Number,
5 | default: 10000
6 | }
7 | },
8 | data () {
9 | return {
10 | dataLength: this.data.length,
11 | showList: this.dataLength <= this.maxnum ? this.data : this.data.slice(0, this.maxnum),
12 | hideContainerBar: true
13 | }
14 | },
15 | watch: {
16 | data: {
17 | deep: true,
18 | handler: function (val, oldval) {
19 | if (this.dataLength !== val.length) {
20 | this.dataLength = val.length
21 | this.updataShowList()
22 | // 是否隐藏向左向右箭头
23 | this.isHideLRIcon()
24 | } else {
25 | // 数量不变,数据更新,记录offset,更新数据,设置offset
26 | // TODO(还需要修改,如果内容长度改变很大,offset也记录不准确)
27 | const recordOffset = this.getCurrentScrollOffset(this.isHorizontal ? 1 : 2)
28 | this.setOffset(0, 0)
29 | this.updateShowListData()
30 | this.isHorizontal ? this.setOffset(recordOffset, 0) : this.setOffset(0, recordOffset)
31 | }
32 | }
33 | },
34 | hideDirectionBar () {
35 | this.isHideLRIcon()
36 | }
37 | },
38 | computed: {
39 | beginPos () {
40 | let index = -1
41 | if (this.showList.length > 0) {
42 | index = this.data.findIndex(obj => obj.id === this.showList[0].id)
43 | }
44 | return index
45 | }
46 | },
47 | methods: {
48 | /**
49 | * 更新显示的list
50 | */
51 | updataShowList () {
52 | // data数量改变,showList重置,如果要定位请设置activeTab
53 | this.showList = this.dataLength <= this.maxnum ? this.data : this.data.slice(0, this.maxnum)
54 | // 数据改变移动到初始位置
55 | this.goBegin()
56 | },
57 | /**
58 | * 改变showList
59 | * @param {*} val showList
60 | */
61 | changeShowList (val) {
62 | const showIndex = this.showList.findIndex(obj => obj.id === val)
63 | if (showIndex !== -1) {
64 | return
65 | }
66 | const index = this.data.findIndex((obj) => obj.id === val)
67 | if (index !== -1) {
68 | if (this.dataLength <= this.maxnum) {
69 | this.showList = this.data
70 | } else if (index <= this.maxnum / 2) {
71 | this.showList = this.data.slice(0, this.maxnum)
72 | } else if ((this.dataLength - index) >= (this.maxnum / 2)) {
73 | this.showList = this.data.slice(index - this.maxnum / 2, index + (this.maxnum - this.maxnum / 2))
74 | } else {
75 | this.showList = this.data.slice(this.dataLength - this.maxnum, this.dataLength)
76 | }
77 | }
78 | },
79 | /**
80 | * 判断是否隐藏左右移动按钮
81 | */
82 | isHideLRIcon () {
83 | if (this.hideDirectionBar) {
84 | this.hideContainerBar = true
85 | } else {
86 | let nav = ''
87 | let container = ''
88 | setTimeout(() => {
89 | nav = this.isHorizontal
90 | ? this.$refs.nav.offsetWidth
91 | : this.$refs.nav.offsetHeight
92 | container = this.isHorizontal
93 | ? this.$refs.navScroll.offsetWidth
94 | : this.$refs.navScroll.offsetHeight
95 | this.hideContainerBar = nav <= container
96 | })
97 | }
98 | },
99 | updateShowListData () {
100 | if (this.dataLength > this.maxnum) {
101 | this.showList = [...this.data.slice(this.beginPos, this.beginPos + this.maxnum)]
102 | } else {
103 | this.showList = [...this.data]
104 | }
105 | }
106 | },
107 | mounted () {
108 | this.isHideLRIcon()
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/mixins/offsetMixin.js:
--------------------------------------------------------------------------------
1 | export default {
2 | data () {
3 | return {
4 | navStyle: {
5 | transform: ''
6 | }
7 | }
8 | },
9 | watch: {
10 | tabPosition (val, old) {
11 | if (
12 | (
13 | old !== 'left' &&
14 | old !== 'right' &&
15 | (val === 'left' || val === 'right')
16 | ) ||
17 | (
18 | old !== 'top' &&
19 | old !== 'bottom' &&
20 | (val === 'top' || val === 'bottom')
21 | )
22 | ) {
23 | this.goBegin()
24 | }
25 | if ((val === 'left' || val === 'right') && this.type !== 'card') {
26 | this.tabPosition = 'bottom'
27 | }
28 | }
29 | },
30 | methods: {
31 | /**
32 | * 向前滚动
33 | */
34 | scrollPrev () {
35 | const currentOffset = this.isHorizontal
36 | ? this.getCurrentScrollOffset()
37 | : this.getCurrentScrollOffset(2)
38 |
39 | // tabs每次移动距离
40 | const stepWidth = this.$el.getBoundingClientRect().width * 3 / 4
41 |
42 | if (!currentOffset && this.beginPos === 0) return
43 |
44 | // const beginIndex = (this.beginPos - 5) > 0 ? this.beginPos - 5 : 0
45 | // const endIndex = (this.beginPos - 5) > 0 ? this.beginPos + this.maxnum - 5 : this.maxnum
46 | const newOffset = (currentOffset - stepWidth) > 0 ? currentOffset - stepWidth : 0
47 | // this.showList = [...this.data.slice(beginIndex, endIndex)]
48 |
49 | this.isHorizontal
50 | ? this.setOffset(newOffset, 0)
51 | : this.setOffset(0, newOffset)
52 | },
53 | /**
54 | * 向后滚动
55 | */
56 | scrollNext () {
57 | const navWidth = this.isHorizontal
58 | ? this.$refs.nav.offsetWidth
59 | : this.$refs.nav.offsetHeight
60 | const containerWidth = this.isHorizontal
61 | ? this.$refs.navScroll.offsetWidth
62 | : this.$refs.navScroll.offsetHeight
63 | const currentOffset = this.isHorizontal
64 | ? this.getCurrentScrollOffset()
65 | : this.getCurrentScrollOffset(2)
66 |
67 | // tabs每次移动距离
68 | const stepWidth = this.$el.getBoundingClientRect().width * 3 / 4
69 |
70 | if (navWidth - currentOffset <= containerWidth && this.showList[this.showList.length - 1].id === this.data[this.dataLength - 1].id) return
71 |
72 | // this.showList = [...this.data.slice(this.beginPos + 5, this.beginPos + this.maxnum + 5)]
73 | const newOffset = currentOffset + stepWidth
74 |
75 | this.isHorizontal
76 | ? this.setOffset(newOffset, 0)
77 | : this.setOffset(0, newOffset)
78 | },
79 | /**
80 | * 滚动到active状态的tab
81 | */
82 | scrollToActiveTab () {
83 | this.changeShowList(!this.currActive && this.data[0] ? this.data[0].id : this.currActive)
84 | this.$nextTick(() => {
85 | const nav = this.$refs.nav
86 | const activeTab = this.$el.querySelector('.ml-tab-item-active')
87 | if (!activeTab) return
88 | const navScroll = this.$refs.navScroll
89 | const activeTabBounding = activeTab.getBoundingClientRect()
90 | const navScrollBounding = navScroll.getBoundingClientRect()
91 | let maxOffset = this.isHorizontal ? nav.offsetWidth - navScrollBounding.width : nav.offsetHeight - navScrollBounding.height
92 | if (maxOffset < 0) {
93 | maxOffset = 0
94 | }
95 | const currentOffset = this.isHorizontal
96 | ? this.getCurrentScrollOffset()
97 | : this.getCurrentScrollOffset(2)
98 | let newOffset = currentOffset
99 |
100 | if (this.isHorizontal) {
101 | if (activeTabBounding.left < navScrollBounding.left) {
102 | newOffset =
103 | currentOffset - (navScrollBounding.left - activeTabBounding.left)
104 | }
105 | if (activeTabBounding.right > navScrollBounding.right) {
106 | newOffset =
107 | currentOffset + activeTabBounding.right - navScrollBounding.right
108 | }
109 | } else {
110 | if (activeTabBounding.top < navScrollBounding.top) {
111 | newOffset =
112 | currentOffset - (navScrollBounding.top - activeTabBounding.top)
113 | }
114 | if (activeTabBounding.bottom > navScrollBounding.bottom) {
115 | newOffset =
116 | currentOffset + activeTabBounding.bottom - navScrollBounding.bottom
117 | }
118 | }
119 |
120 | newOffset = Math.max(newOffset, 0)
121 | this.isHorizontal
122 | ? this.setOffset(Math.min(newOffset, maxOffset), 0)
123 | : this.setOffset(0, Math.min(newOffset, maxOffset))
124 | })
125 | },
126 | /**
127 | * 根据当前是横向还是纵向,获得当前offset
128 | * @param {*} type 1:横向 2:纵向
129 | */
130 | getCurrentScrollOffset (type = 1) {
131 | const { navStyle } = this
132 | return navStyle.transform
133 | ? type === 1
134 | ? Number(navStyle.transform.match(/-(\d+(\.\d+)*)px/)[1])
135 | : Number(navStyle.transform.match(/ -(\d+(\.\d+)*)px/)[1])
136 | : 0
137 | },
138 | /**
139 | * 设置offset
140 | * @param {*} x 横向offset
141 | * @param {*} y 纵向offset
142 | */
143 | setOffset (x, y) {
144 | this.navStyle.transform = `translate(-${x}px, -${y}px)`
145 | },
146 | /**
147 | * 移动到最开始的位置
148 | */
149 | goBegin () {
150 | if (this.dataLength > this.maxnum) {
151 | this.showList = this.data.slice(0, this.maxnum)
152 | }
153 | this.$nextTick(() => {
154 | this.setOffset(0, 0)
155 | })
156 | },
157 | /**
158 | * 移动到结束的位置
159 | */
160 | goEnd () {
161 | if (this.dataLength > this.maxnum) {
162 | this.showList = this.data.slice(this.dataLength - this.maxnum, this.dataLength)
163 | }
164 | this.$nextTick(() => {
165 | const nav = this.$refs.nav
166 | const navScroll = this.$refs.navScroll
167 | const navScrollBounding = navScroll.getBoundingClientRect()
168 | let maxOffset = this.isHorizontal ? nav.offsetWidth - navScrollBounding.width : nav.offsetHeight - navScrollBounding.height
169 | if (maxOffset < 0) {
170 | maxOffset = 0
171 | }
172 | this.isHorizontal
173 | ? this.setOffset(maxOffset, 0)
174 | : this.setOffset(0, maxOffset)
175 | })
176 | }
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/src/tabs.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
41 |
42 |
43 |
46 |
47 |
48 |
183 |
193 |
--------------------------------------------------------------------------------
/static/vue-tab-component.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamma1024/vue-tab-component/5040079ccb147489226d607941f28e4b7bc78aa7/static/vue-tab-component.gif
--------------------------------------------------------------------------------
/test/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | vue-tab-component
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 切到指定的tab:
24 |
25 |
26 |
27 |
28 |
29 |
30 |
39 |
40 |
41 | line 的样式
42 |
43 |
44 |
53 |
54 |
55 |
56 |
59 |
60 |

61 |
62 |
63 |
64 |
122 |
182 |
183 |
--------------------------------------------------------------------------------
/test/assets/Tangerine-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamma1024/vue-tab-component/5040079ccb147489226d607941f28e4b7bc78aa7/test/assets/Tangerine-Regular.ttf
--------------------------------------------------------------------------------
/test/assets/fork.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamma1024/vue-tab-component/5040079ccb147489226d607941f28e4b7bc78aa7/test/assets/fork.png
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import iview from 'view-design'
3 | import 'view-design/dist/styles/iview.css'
4 | import App from './App.vue'
5 | import tabs from '../src'
6 |
7 | Vue.use(iview)
8 | Vue.use(tabs)
9 |
10 | var app = new Vue({
11 | el: '#app',
12 | template: '',
13 | components: { App }
14 | })
15 |
--------------------------------------------------------------------------------