├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── README.md ├── build ├── build.js ├── check-versions.js ├── config.js ├── dev-client.js ├── dev-server.js ├── utils.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── example ├── Example.vue ├── Switch │ ├── Switch.less │ └── Switch.vue ├── example.gif ├── index.html └── main.js ├── package.json ├── src ├── Checkbox │ ├── Checkbox.less │ └── Checkbox.vue ├── Table │ ├── Table.less │ ├── Table.vue │ ├── TableBody.js │ ├── TableFooter.js │ ├── TableHeader.js │ ├── font │ │ ├── iconfont.eot │ │ ├── iconfont.less │ │ ├── iconfont.svg │ │ ├── iconfont.ttf │ │ └── iconfont.woff │ └── utils │ │ ├── index.js │ │ ├── mixins.js │ │ └── scrollBarWidth.js └── index.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", { 5 | "modules": false, 6 | "targets": { 7 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 8 | } 9 | } 10 | ], 11 | "stage-0" 12 | ], 13 | "plugins": [ 14 | "transform-runtime", 15 | "transform-vue-jsx" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 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 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // http://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | }, 12 | extends: 'airbnb-base', 13 | // required to lint *.vue files 14 | plugins: [ 15 | 'html' 16 | ], 17 | // check if imports actually resolve 18 | 'settings': { 19 | 'import/resolver': { 20 | 'webpack': { 21 | 'config': 'build/webpack.base.conf.js' 22 | } 23 | } 24 | }, 25 | // add your custom rules here 26 | 'rules': { 27 | // don't require .vue extension when importing 28 | 'import/extensions': ['error', 'always', { 29 | 'js': 'never', 30 | 'vue': 'never' 31 | }], 32 | // allow optionalDependencies 33 | 'import/no-extraneous-dependencies': ['error', { 34 | 'optionalDependencies': ['test/unit/index.js'] 35 | }], 36 | // allow debugger during development 37 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 38 | 'no-plusplus': ["error", { "allowForLoopAfterthoughts": true }] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | lib/ 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 15 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-table-with-tree-grid 2 | 3 | > A table (with tree-grid) component for Vue.js 2.0. (Its style extends [@iView](https://github.com/iview/iview)) 4 | 5 | ## Example 6 | 7 | ![Example](./example/example.gif) 8 | 9 | ## Installation 10 | 11 | Use npm: 12 | 13 | ```shell 14 | npm i vue-table-with-tree-grid -S 15 | ``` 16 | 17 | Or use yarn: 18 | 19 | ```shell 20 | yarn add vue-table-with-tree-grid 21 | ``` 22 | 23 | ## Usage 24 | 25 | ```javascript 26 | import Vue from 'vue' 27 | import ZkTable from 'vue-table-with-tree-grid' 28 | 29 | Vue.use(ZkTable) 30 | ``` 31 | 32 | Or 33 | 34 | ```javascript 35 | import Vue from 'vue' 36 | import ZkTable from 'vue-table-with-tree-grid' 37 | 38 | Vue.component(ZkTable.name, ZkTable) 39 | ``` 40 | 41 | more information please see [example...](https://github.com/MisterTaki/vue-table-with-tree-gird/blob/master/example/Example.vue), or git clone this repository and open dev mode (I will give it a homepage later...). 42 | 43 | ## API 44 | 45 | ### Table Attributes 46 | 47 | | 属性 | 说明 | 类型 | 参数 | 默认值 | 48 | | ---- | ---- | ---- | ---- | ---- | 49 | | data | 表格各行的数据 | Array | - | [] | 50 | | empty-text | 表格数据为空时显示的文字 | String | - | '暂无数据' | 51 | | columns | 表格各列的配置(具体见下文:Columns Configs) | Array | - | [] | 52 | | show-header | 是否显示表头 | Boolean | - | true | 53 | | show-index | 是否显示数据索引 | Boolean | - | false | 54 | | index-text | 数据索引名称 | String | - | '序号' | 55 | | show-summary | 是否显示表尾合计行 | Boolean | - | false | 56 | | sum-text | 表尾合计行首列名称 | String | - | '合计' | 57 | | summary-method | 表尾合计行计算方法 | Function | data, column, columnIndex | - | 58 | | max-height | 最大高度 | [String, Number] | - | 'auto' | 59 | | stripe | 是否显示间隔斑马纹 | Boolean | - | false | 60 | | border | 是否显示纵向边框 | Boolean | - | false | 61 | | show-row-hover | 鼠标悬停时,是否高亮当前行 | Boolean | - | true | 62 | | tree-type | 是否为树形表格 | Boolean | - | false | 63 | | children-prop | 树形表格中遍历的属性名称 | String | - | 'children' | 64 | | is-fold | 树形表格中父级是否默认折叠 | Boolean | - | true | 65 | | expand-type | 是否为展开行类型表格(为 True 时,需要添加名称为 '$expand' 的[作用域插槽](https://cn.vuejs.org/v2/guide/components.html#作用域插槽), 它可以获取到 row, rowIndex) | Boolean | - | false | 66 | | selection-type | 是否为多选类型表格 | Boolean | - | false | 67 | | row-key | 行数据的 Key,用来优化 Table 的渲染 | Function | row, rowIndex | rowIndex | 68 | | row-class-name | 额外的表格行的类名 | String, Function | row, rowIndex | - | 69 | | cell-class-name | 额外的表格行的类名 | String, Function | row, rowIndex, column, columnIndex | - | 70 | | row-style | 额外的表格行的样式 | Object, Function | row, rowIndex | - | 71 | | cell-style | 额外的表格单元格的样式 | Object, Function | row, rowIndex, column, columnIndex | - | 72 | 73 | ### Columns Configs 74 | 75 | | 属性 | 说明 | 类型 | 默认值 | 76 | | ---- | ---- | ---- | ---- | 77 | | label | 列标题名称 | String | '' | 78 | | prop | 对应列内容的属性名 | String | '' | 79 | | align | 对应列内容的对齐方式,可选值有 'center', 'right' | String | 'left' | 80 | | headerAlign | 对应列标题的对齐方式,可选值有 'center', 'right' | String | 'left' | 81 | | width | 列宽度 | [String, Number] | 'auto' | 82 | | minWidth | 列最小宽度 | [String, Number] | '80px' | 83 | | type | 列类型,可选值有 'template'(自定义列模板) | String | '' | 84 | | template | 列类型为 'template'(自定义列模板) 时,对应的[作用域插槽](https://cn.vuejs.org/v2/guide/components.html#作用域插槽)(它可以获取到 row, rowIndex, column, columnIndex)名称 | String | '' | 85 | 86 | ### Table Events 87 | 88 | | 事件名 | 说明 | 参数 | 89 | | ---- | ---- | ---- | 90 | | cell-click | 单击某一单元格 | row, rowIndex, column, columnIndex, $event | 91 | | cell-dblclick | 双击某一单元格 | row, rowIndex, column, columnIndex, $event | 92 | | cell-contextmenu | 在某一单元格上点击鼠标右键 | row, rowIndex, column, columnIndex, $event | 93 | | cell-mouseenter | 鼠标滑入某一单元格 | row, rowIndex, column, columnIndex, $event | 94 | | cell-mouseleave | 鼠标滑出某一单元格 | row, rowIndex, column, columnIndex, $event | 95 | | row-click | 单击某一行 | row, rowIndex, $event | 96 | | row-dblclick | 双击某一行 | row, rowIndex, $event | 97 | | row-contextmenu | 在某一行上点击鼠标右键 | row, rowIndex, $event | 98 | | row-mouseenter | 鼠标滑入某一行 | row, rowIndex, $event | 99 | | row-mouseleave | 鼠标滑出某一行 | row, rowIndex, $event | 100 | | checkbox-click | 鼠标单击checkbox | row, rowIndex, $event | 101 | | tree-icon-click | 鼠标单击树形icon | row, rowIndex, $event | 102 | | expand-cell-click | 鼠标单击展开单元格 | row, rowIndex, $event | 103 | 104 | ### Table Methods 105 | 106 | | 方法名 | 说明 | 参数 | 107 | | ---- | ---- | ---- | 108 | | getCheckedProp | 当表格为多选类型表格时,用于获取当前所选项的属性,返回一个数组;属性默认为'index'。 | prop | 109 | -------------------------------------------------------------------------------- /build/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), 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/config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | 3 | module.exports = { 4 | build: { 5 | env: { 6 | NODE_ENV: '"production"' 7 | }, 8 | entry: './src/index.js', 9 | assetsRoot: path.resolve(__dirname, '../lib') 10 | }, 11 | dev: { 12 | env: { 13 | NODE_ENV: '"development"' 14 | }, 15 | entry: './example/main.js', 16 | port: 8080, 17 | autoOpenBrowser: true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /build/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/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 webpackConfig = require('./webpack.dev.conf') 13 | 14 | // default port where dev server listens for incoming traffic 15 | var port = process.env.PORT || config.dev.port 16 | // automatically open browser, if not set will be false 17 | var autoOpenBrowser = !!config.dev.autoOpenBrowser 18 | 19 | var app = express() 20 | var compiler = webpack(webpackConfig) 21 | 22 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 23 | quiet: true 24 | }) 25 | 26 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 27 | log: false, 28 | heartbeat: 2000 29 | }) 30 | // force page reload when html-webpack-plugin template changes 31 | compiler.plugin('compilation', function (compilation) { 32 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 33 | hotMiddleware.publish({ action: 'reload' }) 34 | cb() 35 | }) 36 | }) 37 | 38 | // handle fallback for HTML5 history API 39 | app.use(require('connect-history-api-fallback')()) 40 | 41 | // serve webpack bundle output 42 | app.use(devMiddleware) 43 | 44 | // enable hot-reload and state-preserving 45 | // compilation error display 46 | app.use(hotMiddleware) 47 | 48 | var uri = 'http://localhost:' + port 49 | 50 | var _resolve 51 | var readyPromise = new Promise(resolve => { 52 | _resolve = resolve 53 | }) 54 | 55 | console.log('> Starting dev server...') 56 | devMiddleware.waitUntilValid(() => { 57 | console.log('> Listening at ' + uri + '\n') 58 | // when env is testing, don't need open it 59 | if (autoOpenBrowser) { 60 | opn(uri) 61 | } 62 | _resolve() 63 | }) 64 | 65 | var server = app.listen(port) 66 | 67 | module.exports = { 68 | ready: readyPromise, 69 | close: () => { 70 | server.close() 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('./config') 3 | 4 | exports.cssLoaders = function (options) { 5 | options = options || {} 6 | 7 | var cssLoader = { 8 | loader: 'css-loader', 9 | options: { 10 | minimize: process.env.NODE_ENV === 'production', 11 | sourceMap: options.sourceMap 12 | } 13 | } 14 | 15 | function generateLoaders (loader, loaderOptions) { 16 | var loaders = [cssLoader] 17 | if (loader) { 18 | loaders.push({ 19 | loader: loader + '-loader', 20 | options: Object.assign({}, loaderOptions, { 21 | sourceMap: options.sourceMap 22 | }) 23 | }) 24 | } 25 | return ['vue-style-loader'].concat(loaders) 26 | } 27 | 28 | return { 29 | css: generateLoaders(), 30 | postcss: generateLoaders(), 31 | less: generateLoaders('less'), 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var config = require('./config') 4 | var isProduction = process.env.NODE_ENV === 'production' 5 | 6 | function resolve (dir) { 7 | return path.join(__dirname, '..', dir) 8 | } 9 | 10 | module.exports = { 11 | entry: { 12 | app: isProduction 13 | ? config.build.entry 14 | : config.dev.entry 15 | }, 16 | output: { 17 | path: config.build.assetsRoot, 18 | filename: '[name].js' 19 | }, 20 | resolve: { 21 | extensions: ['.js', '.vue', '.json'], 22 | alias: { 23 | 'vue$': 'vue/dist/vue.esm.js' 24 | } 25 | }, 26 | module: { 27 | rules: [ 28 | { 29 | test: /\.(js|vue)$/, 30 | loader: 'eslint-loader', 31 | enforce: 'pre', 32 | include: [resolve('src'), resolve('example')], 33 | options: { 34 | formatter: require('eslint-friendly-formatter') 35 | } 36 | }, 37 | { 38 | test: /\.vue$/, 39 | loader: 'vue-loader', 40 | options: { 41 | loaders: utils.cssLoaders({ 42 | sourceMap: isProduction, 43 | extract: isProduction 44 | }) 45 | } 46 | }, 47 | { 48 | test: /\.js$/, 49 | loader: 'babel-loader', 50 | include: [resolve('src'), resolve('example')] 51 | }, 52 | { 53 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 54 | loader: 'url-loader', 55 | options: { 56 | limit: 10000, 57 | name: 'img/[name].[hash:7].[ext]' 58 | } 59 | }, 60 | { 61 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 62 | loader: 'url-loader', 63 | options: { 64 | limit: 10000, 65 | name: 'media/[name].[hash:7].[ext]' 66 | } 67 | }, 68 | { 69 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 70 | loader: 'url-loader', 71 | options: { 72 | limit: 10000, 73 | name: 'fonts/[name].[hash:7].[ext]' 74 | } 75 | } 76 | ] 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /build/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 | 9 | // add hot-reload related code to entry chunks 10 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 11 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 12 | }) 13 | 14 | module.exports = merge(baseWebpackConfig, { 15 | // cheap-module-eval-source-map is faster for development 16 | devtool: '#cheap-module-eval-source-map', 17 | plugins: [ 18 | new webpack.DefinePlugin({ 19 | 'process.env': config.dev.env 20 | }), 21 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 22 | new webpack.HotModuleReplacementPlugin(), 23 | new webpack.NoEmitOnErrorsPlugin(), 24 | // https://github.com/ampedandwired/html-webpack-plugin 25 | new HtmlWebpackPlugin({ 26 | filename: 'index.html', 27 | template: './example/index.html', 28 | inject: true 29 | }), 30 | new FriendlyErrorsPlugin() 31 | ] 32 | }) 33 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | var merge = require('webpack-merge') 4 | var config = require('./config') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | 7 | var env = config.build.env 8 | 9 | var webpackConfig = merge(baseWebpackConfig, { 10 | devtool: '#source-map', 11 | output: { 12 | path: config.build.assetsRoot, 13 | filename: 'vue-table-with-tree-grid.js', 14 | library: 'vue-table-with-tree-grid', 15 | libraryTarget: 'umd', 16 | umdNamedDefine: true 17 | }, 18 | externals: { 19 | vue: { 20 | root: 'Vue', 21 | commonjs: 'vue', 22 | commonjs2: 'vue', 23 | amd: 'vue' 24 | } 25 | }, 26 | plugins: [ 27 | new webpack.DefinePlugin({ 28 | 'process.env': env 29 | }), 30 | new webpack.optimize.UglifyJsPlugin({ 31 | compress: { 32 | warnings: false 33 | }, 34 | sourceMap: true 35 | }) 36 | ] 37 | }) 38 | 39 | module.exports = webpackConfig 40 | -------------------------------------------------------------------------------- /example/Example.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 234 | 235 | 252 | -------------------------------------------------------------------------------- /example/Switch/Switch.less: -------------------------------------------------------------------------------- 1 | // text 2 | @prefixCls: zk-switch; 3 | // color 4 | @border: #dddee1; 5 | @hoverBorder: #bcbcbc; 6 | @blue: #2d8cf0; 7 | 8 | .@{prefixCls} { 9 | display: inline-block; 10 | width: 60px; 11 | height: 24px; 12 | line-height: 22px; 13 | border-radius: 24px; 14 | vertical-align: middle; 15 | border: 1px solid #ccc; 16 | background-color: #ccc; 17 | position: relative; 18 | cursor: pointer; 19 | -webkit-user-select: none; 20 | -moz-user-select: none; 21 | -ms-user-select: none; 22 | user-select: none; 23 | transition: all .2s ease-in-out; 24 | &::after { 25 | content: ""; 26 | width: 20px; 27 | height: 20px; 28 | border-radius: 20px; 29 | background-color: #fff; 30 | position: absolute; 31 | left: 1px; 32 | top: 1px; 33 | cursor: pointer; 34 | transition: left .2s ease-in-out,width .2s ease-in-out; 35 | } 36 | &:active::after { 37 | width: 32px; 38 | } 39 | } 40 | 41 | .@{prefixCls}__inner { 42 | color: #fff; 43 | font-size: 12px; 44 | position: absolute; 45 | left: 25px; 46 | } 47 | 48 | .@{prefixCls}--checked { 49 | border-color: #2d8cf0; 50 | background-color: #2d8cf0; 51 | &::after { 52 | left: 37px; 53 | } 54 | &:active::after { 55 | left: 25px; 56 | } 57 | .@{prefixCls}__inner { 58 | left: 8px; 59 | } 60 | } 61 | 62 | .@{prefixCls}--disabled { 63 | cursor: not-allowed; 64 | background: #f3f3f3; 65 | border-color: #f3f3f3; 66 | &::after { 67 | background: #ccc; 68 | cursor: not-allowed; 69 | } 70 | .@{prefixCls}__inner { 71 | color: #ccc; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /example/Switch/Switch.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /example/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MisterTaki/vue-table-with-tree-grid/b3d4a537a79cd2d409e30fd4ea0da0d91d5b2969/example/example.gif -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue'; 4 | import Example from './Example'; 5 | import ZkTable from '../src'; 6 | 7 | Vue.config.productionTip = false; 8 | 9 | // Vue.component(ZkTable.name, ZkTable); 10 | 11 | Vue.use(ZkTable); 12 | 13 | /* eslint-disable no-new */ 14 | new Vue({ 15 | el: '#example', 16 | template: '', 17 | components: { Example }, 18 | }); 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-table-with-tree-grid", 3 | "version": "0.2.4", 4 | "description": "A table (with tree-grid) component for Vue.js 2.0. (Its style extends iView)", 5 | "keywords": [ 6 | "vue", 7 | "vue-table", 8 | "tree-grid", 9 | "vue-component", 10 | "vue-jsx" 11 | ], 12 | "author": "GaoQi ", 13 | "homepage": "https://github.com/MisterTaki/vue-table-with-tree-gird", 14 | "bugs": "https://github.com/MisterTaki/vue-table-with-tree-gird/issues", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/MisterTaki/vue-table-with-tree-gird" 18 | }, 19 | "main": "lib/vue-table-with-tree-grid.js", 20 | "files": [ 21 | "lib", 22 | "example", 23 | "src" 24 | ], 25 | "license": "MIT", 26 | "scripts": { 27 | "dev": "node build/dev-server.js", 28 | "build": "node build/build.js" 29 | }, 30 | "dependencies": { 31 | "vue": "^2.4.3" 32 | }, 33 | "devDependencies": { 34 | "autoprefixer": "^7.1.2", 35 | "babel-core": "^6.22.1", 36 | "babel-eslint": "^7.1.1", 37 | "babel-helper-vue-jsx-merge-props": "^2.0.2", 38 | "babel-loader": "^7.1.1", 39 | "babel-plugin-istanbul": "^4.1.1", 40 | "babel-plugin-syntax-jsx": "^6.18.0", 41 | "babel-plugin-transform-runtime": "^6.22.0", 42 | "babel-plugin-transform-vue-jsx": "^3.5.0", 43 | "babel-preset-env": "^1.3.2", 44 | "babel-preset-stage-0": "^6.24.1", 45 | "babel-register": "^6.22.0", 46 | "connect-history-api-fallback": "^1.3.0", 47 | "css-loader": "^0.28.0", 48 | "cssnano": "^3.10.0", 49 | "eslint": "^3.19.0", 50 | "eslint-config-airbnb-base": "^11.1.3", 51 | "eslint-friendly-formatter": "^3.0.0", 52 | "eslint-import-resolver-webpack": "^0.8.1", 53 | "eslint-loader": "^1.7.1", 54 | "eslint-plugin-html": "^3.0.0", 55 | "eslint-plugin-import": "^2.2.0", 56 | "eventsource-polyfill": "^0.9.6", 57 | "friendly-errors-webpack-plugin": "^1.1.3", 58 | "html-webpack-plugin": "^2.30.1", 59 | "less": "^2.7.2", 60 | "less-loader": "^4.0.5", 61 | "opn": "^5.1.0", 62 | "ora": "^1.3.0", 63 | "url-loader": "^0.5.9", 64 | "vue-loader": "^12.1.0", 65 | "vue-style-loader": "^3.0.1", 66 | "vue-template-compiler": "^2.3.3", 67 | "webpack": "^2.6.1", 68 | "webpack-bundle-analyzer": "^2.2.1", 69 | "webpack-dev-middleware": "^1.10.0", 70 | "webpack-hot-middleware": "^2.18.0", 71 | "webpack-merge": "^4.1.0" 72 | }, 73 | "peerDependencies": { 74 | "vue": "^2.4.3" 75 | }, 76 | "engines": { 77 | "node": ">= 4.0.0", 78 | "npm": ">= 3.0.0" 79 | }, 80 | "browserslist": [ 81 | "> 1%", 82 | "last 2 versions", 83 | "not ie < 9" 84 | ] 85 | } 86 | -------------------------------------------------------------------------------- /src/Checkbox/Checkbox.less: -------------------------------------------------------------------------------- 1 | // text 2 | @prefixCls: zk-checkbox; 3 | // color 4 | @border: #dddee1; 5 | @hoverBorder: #bcbcbc; 6 | @blue: #2d8cf0; 7 | 8 | .@{prefixCls}-wrapper { 9 | display: inline-block; 10 | position: relative; 11 | vertical-align: middle; 12 | white-space: nowrap; 13 | } 14 | 15 | .@{prefixCls} { 16 | display: inline-block; 17 | position: relative; 18 | line-height: 1; 19 | white-space: nowrap; 20 | vertical-align: middle; 21 | cursor: pointer; 22 | outline: none; 23 | &:hover { 24 | .@{prefixCls}__inner { 25 | border-color: @hoverBorder; 26 | } 27 | } 28 | } 29 | 30 | .@{prefixCls}__inner { 31 | display: inline-block; 32 | width: 14px; 33 | height: 14px; 34 | position: relative; 35 | top: 0; 36 | left: 0; 37 | border: 1px solid @border; 38 | border-radius: 2px; 39 | background-color: #ffffff; 40 | transition: border-color .2s ease-in-out,background-color .2s ease-in-out; 41 | &::after { 42 | content: ""; 43 | display: table; 44 | width: 4px; 45 | height: 8px; 46 | position: absolute; 47 | top: 1px; 48 | left: 4px; 49 | border: 2px solid #fff; 50 | border-top: 0; 51 | border-left: 0; 52 | transform: rotate(45deg) scale(0); 53 | transition: all .2s ease-in-out; 54 | } 55 | } 56 | 57 | .@{prefixCls}--indeterminate { 58 | .@{prefixCls}__inner { 59 | background-color: @blue; 60 | border-color: @blue; 61 | &::after { 62 | content: ""; 63 | width: 8px; 64 | height: 1px; 65 | transform: scale(1); 66 | position: absolute; 67 | left: 2px; 68 | top: 5px; 69 | } 70 | } 71 | &:hover { 72 | .@{prefixCls}__inner { 73 | border-color: @blue; 74 | } 75 | } 76 | } 77 | 78 | .@{prefixCls}--checked { 79 | .@{prefixCls}__inner { 80 | border-color: @blue; 81 | background-color: @blue; 82 | &::after { 83 | content: ""; 84 | display: table; 85 | width: 4px; 86 | height: 8px; 87 | position: absolute; 88 | top: 1px; 89 | left: 4px; 90 | border: 2px solid #ffffff; 91 | border-top: 0; 92 | border-left: 0; 93 | transform: rotate(45deg) scale(1); 94 | transition: all .2s ease-in-out; 95 | } 96 | } 97 | &:hover { 98 | .@{prefixCls}__inner { 99 | border-color: @blue; 100 | } 101 | } 102 | } 103 | 104 | .@{prefixCls}--disabled { 105 | cursor: not-allowed; 106 | .@{prefixCls}__inner { 107 | background-color: #f3f3f3; 108 | border-color: @border; 109 | &::after { 110 | animation-name: none; 111 | border-color: #ccc; 112 | } 113 | } 114 | &:hover { 115 | .@{prefixCls}__inner { 116 | border-color: @border; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Checkbox/Checkbox.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/Table/Table.less: -------------------------------------------------------------------------------- 1 | @import "./font/iconfont"; 2 | 3 | // text 4 | @prefixCls: zk-table; 5 | // color 6 | @black: #1F2D3D; 7 | @white: #ffffff; 8 | @border: #e9eaec; 9 | @hoverRow: #ebf7ff; 10 | @backgroundRow: #f8f8f9; 11 | 12 | .@{prefixCls} { 13 | position: relative; 14 | width: 100%; 15 | box-sizing: border-box; 16 | background-color: @white; 17 | border: 1px solid @border; 18 | font-size: 12px; 19 | line-height: 26px; 20 | color: @black; 21 | overflow: hidden; 22 | } 23 | 24 | .@{prefixCls}__header-wrapper, 25 | .@{prefixCls}__footer-wrapper { 26 | overflow: hidden; 27 | } 28 | 29 | .@{prefixCls}__body-wrapper { 30 | overflow: auto; 31 | } 32 | 33 | .@{prefixCls}__header, 34 | .@{prefixCls}__body, 35 | .@{prefixCls}__footer { 36 | width: 100%; 37 | table-layout: fixed; 38 | border-collapse: collapse; 39 | border-spacing: 0; 40 | } 41 | 42 | .@{prefixCls}__header-row { 43 | height: 40px; 44 | box-sizing: border-box; 45 | background-color: @backgroundRow; 46 | border-bottom: 1px solid @border; 47 | } 48 | 49 | .@{prefixCls}__footer-row { 50 | height: 40px; 51 | box-sizing: border-box; 52 | background-color: @white; 53 | border-top: 1px solid @border; 54 | } 55 | 56 | .@{prefixCls}__body-row { 57 | height: 48px; 58 | box-sizing: border-box; 59 | &:not(:first-of-type) { 60 | border-top: 1px solid @border; 61 | } 62 | } 63 | 64 | .@{prefixCls}__header-cell, 65 | .@{prefixCls}__body-cell, 66 | .@{prefixCls}__footer-cell { 67 | box-sizing: border-box; 68 | text-align: left; 69 | vertical-align: middle; 70 | word-break: break-all; 71 | overflow: hidden; 72 | } 73 | 74 | .@{prefixCls}__header-cell { 75 | font-weight: bold; 76 | } 77 | 78 | .@{prefixCls}__cell-inner { 79 | padding: 6px 12px; 80 | } 81 | 82 | .@{prefixCls}--firstProp-header-inner { 83 | padding-left: 32px; 84 | } 85 | 86 | .@{prefixCls}--empty-row { 87 | height: 80px; 88 | } 89 | 90 | .@{prefixCls}--empty-content { 91 | text-align: center; 92 | } 93 | 94 | .@{prefixCls}--center-cell { 95 | text-align: center; 96 | } 97 | 98 | .@{prefixCls}--right-cell { 99 | text-align: right; 100 | } 101 | 102 | .@{prefixCls}--stripe-row { 103 | background-color: @backgroundRow; 104 | } 105 | 106 | .@{prefixCls}--row-hover { 107 | background-color: @hoverRow; 108 | } 109 | 110 | .@{prefixCls}--border-cell { 111 | &:not(:last-of-type) { 112 | border-right: 1px solid @border; 113 | } 114 | } 115 | 116 | .@{prefixCls}--tree-icon { 117 | margin-right: 6px; 118 | cursor: pointer; 119 | } 120 | 121 | .@{prefixCls}--expand-inner { 122 | text-align: center; 123 | cursor: pointer; 124 | transition: transform .2s ease-in-out; 125 | } 126 | 127 | .@{prefixCls}--expanded-inner { 128 | transform: rotate(90deg); 129 | } 130 | 131 | .@{prefixCls}--expand-content { 132 | padding: 20px; 133 | } 134 | -------------------------------------------------------------------------------- /src/Table/Table.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 319 | 320 | 321 | -------------------------------------------------------------------------------- /src/Table/TableBody.js: -------------------------------------------------------------------------------- 1 | import Checkbox from '../Checkbox/Checkbox'; // eslint-disable-line 2 | import { mixins } from './utils'; 3 | 4 | /* eslint-disable no-underscore-dangle */ 5 | export default { 6 | name: 'zk-table__body', 7 | mixins: [mixins], 8 | data() { 9 | return { 10 | 11 | }; 12 | }, 13 | computed: { 14 | table() { 15 | return this.$parent; 16 | }, 17 | }, 18 | methods: { 19 | toggleStatus(type, row, rowIndex, value) { 20 | this.validateType(type, ['Expanded', 'Checked', 'Hide', 'Fold'], 'toggleStatus', false); 21 | const target = this.table.bodyData[rowIndex]; 22 | this.table.bodyData.splice(rowIndex, 1, { 23 | ...target, 24 | [`_is${type}`]: typeof value === 'undefined' ? !row[`_is${type}`] : value, 25 | }); 26 | }, 27 | getChildrenIndex(parentLevel, parentIndex, careFold = true) { 28 | const data = this.table.bodyData; 29 | let childrenIndex = []; 30 | for (let i = parentIndex + 1; i < data.length; i++) { 31 | if (data[i]._level <= parentLevel) break; 32 | if (data[i]._level - 1 === parentLevel) { 33 | childrenIndex.push(i); 34 | } 35 | } 36 | const len = childrenIndex.length; // important!!! 37 | if (len > 0) { 38 | for (let i = 0; i < len; i++) { 39 | const childData = data[childrenIndex[i]]; 40 | if ( 41 | childData._childrenLen && 42 | (!careFold || (careFold && !childData._isFold)) 43 | ) { 44 | childrenIndex = childrenIndex.concat( 45 | this.getChildrenIndex(childData._level, childrenIndex[i], careFold)); 46 | } 47 | } 48 | } 49 | return childrenIndex; 50 | }, 51 | handleEvent($event, type, data, others) { 52 | const certainType = this.validateType(type, ['cell', 'row', 'checkbox', 'icon'], 'handleEvent'); 53 | const eventType = $event ? $event.type : ''; 54 | const { row, rowIndex, column, columnIndex } = data; 55 | const latestData = this.table.bodyData; 56 | // Checkbox 57 | if (certainType.checkbox) { 58 | const { isChecked } = others; 59 | this.toggleStatus('Checked', row, rowIndex, isChecked); 60 | if (row._childrenLen > 0) { 61 | const childrenIndex = this.getChildrenIndex(row._level, rowIndex, false); 62 | for (let i = 0; i < childrenIndex.length; i++) { 63 | this.toggleStatus('Checked', latestData[childrenIndex[i]], childrenIndex[i], isChecked); 64 | } 65 | } 66 | return this.table.$emit('checkbox-click', latestData[rowIndex], column, columnIndex, $event); 67 | } 68 | // Tree's icon 69 | if (certainType.icon) { 70 | $event.stopPropagation(); 71 | this.toggleStatus('Fold', row, rowIndex); 72 | const childrenIndex = this.getChildrenIndex(row._level, rowIndex); 73 | for (let i = 0; i < childrenIndex.length; i++) { 74 | this.toggleStatus('Hide', latestData[childrenIndex[i]], childrenIndex[i]); 75 | } 76 | return this.table.$emit('tree-icon-click', latestData[rowIndex], column, columnIndex, $event); 77 | } 78 | if (certainType.cell && eventType === 'click') { 79 | // 点击扩展单元格 80 | if (this.isExpandCell(this.table, columnIndex)) { 81 | this.toggleStatus('Expanded', row, rowIndex); 82 | return this.table.$emit('expand-cell-click', latestData[rowIndex], column, columnIndex, $event); 83 | } 84 | } 85 | // 行:Hover 86 | if (certainType.row && (eventType === 'mouseenter' || eventType === 'mouseleave')) { 87 | const { hover } = others; 88 | const target = latestData[rowIndex]; 89 | latestData.splice(rowIndex, 1, { 90 | ...target, 91 | _isHover: hover, 92 | }); 93 | } 94 | if (certainType.cell) { 95 | return this.table.$emit(`${type}-${eventType}`, latestData[rowIndex], rowIndex, column, columnIndex, $event); 96 | } 97 | return this.table.$emit(`${type}-${eventType}`, latestData[rowIndex], rowIndex, $event); 98 | }, 99 | }, 100 | render() { 101 | // key 102 | function getKey(row, rowIndex) { 103 | const rowKey = this.table.rowKey; 104 | if (rowKey) { 105 | return rowKey.call(null, row, rowIndex); 106 | } 107 | return rowIndex; 108 | } 109 | 110 | // style 111 | function getStyle(type, row, rowIndex, column, columnIndex) { 112 | const certainType = this.validateType(type, ['cell', 'row'], 'getStyle'); 113 | const style = this.table[`${type}Style`]; 114 | if (typeof style === 'function') { 115 | if (certainType.row) { 116 | return style.call(null, row, rowIndex); 117 | } 118 | if (certainType.cell) { 119 | return style.call(null, row, rowIndex, column, columnIndex); 120 | } 121 | } 122 | return style; 123 | } 124 | 125 | // className 126 | function getClassName(type, row, rowIndex, column, columnIndex) { 127 | const certainType = this.validateType(type, ['cell', 'row', 'inner'], 'getClassName'); 128 | const classList = []; 129 | if (certainType.row || certainType.cell) { 130 | const className = this.table[`${type}ClassName`]; 131 | if (typeof className === 'string') { 132 | classList.push(className); 133 | } else if (typeof className === 'function') { 134 | if (certainType.row) { 135 | classList.push(className.call(null, row, rowIndex) || ''); 136 | } 137 | if (certainType.cell) { 138 | classList.push(className.call(null, row, rowIndex, column, columnIndex) || ''); 139 | } 140 | } 141 | if (certainType.row) { 142 | classList.push(`${this.prefixCls}__body-row`); 143 | if (this.table.stripe && rowIndex % 2 !== 0) { 144 | classList.push(`${this.prefixCls}--stripe-row`); 145 | } 146 | if (this.table.showRowHover && row._isHover) { 147 | classList.push(`${this.prefixCls}--row-hover`); 148 | } 149 | } 150 | if (certainType.cell) { 151 | classList.push(`${this.prefixCls}__body-cell`); 152 | if (this.table.border) { 153 | classList.push(`${this.prefixCls}--border-cell`); 154 | } 155 | const align = column.align; 156 | if (['center', 'right'].indexOf(align) > -1) { 157 | classList.push(`${this.prefixCls}--${align}-cell`); 158 | } 159 | } 160 | } 161 | if (certainType.inner) { 162 | classList.push(`${this.prefixCls}__cell-inner`); 163 | if (this.isExpandCell(this.table, columnIndex)) { 164 | classList.push(`${this.prefixCls}--expand-inner`); 165 | if (row._isExpanded) { 166 | classList.push(`${this.prefixCls}--expanded-inner`); 167 | } 168 | } 169 | } 170 | return classList.join(' '); 171 | } 172 | 173 | // 根据type渲染单元格Cell 174 | function renderCell(row, rowIndex, column, columnIndex) { 175 | // ExpandType 176 | if (this.isExpandCell(this.table, columnIndex)) { 177 | return ; 178 | } 179 | // SelectionType's Checkbox 180 | if (this.isSelectionCell(this.table, columnIndex)) { 181 | let allCheck; 182 | let childrenIndex; 183 | const hasChildren = row._childrenLen > 0; 184 | if (hasChildren) { 185 | childrenIndex = this.getChildrenIndex(row._level, rowIndex, false); 186 | allCheck = true; 187 | for (let i = 0; i < childrenIndex.length; i++) { 188 | if (!this.table.bodyData[childrenIndex[i]]._isChecked) { 189 | allCheck = false; 190 | break; 191 | } 192 | } 193 | } else { 194 | allCheck = row._isChecked; 195 | } 196 | let indeterminate = false; 197 | if (hasChildren && !allCheck) { 198 | for (let i = 0; i < childrenIndex.length; i++) { 199 | if (this.table.bodyData[childrenIndex[i]]._isChecked) { 200 | indeterminate = true; 201 | break; 202 | } 203 | } 204 | } 205 | return this.handleEvent(null, 'checkbox', { row, rowIndex, column, columnIndex }, { isChecked }) }> 209 | ; 210 | } 211 | // Tree's firstProp 212 | if (this.table.treeType && this.table.firstProp === column.prop) { 213 | return 219 | { row._childrenLen > 0 && 220 | this.handleEvent($event, 'icon', { row, rowIndex, column, columnIndex }, { isFold: row._isFold }) }> 223 | } 224 | { row[column.prop] ? row[column.prop] : '' } 225 | ; 226 | } 227 | // TreeType children's index 228 | if (this.table.showIndex && this.table.treeType && column.prop === '_normalIndex' && row._level > 1) { 229 | return ''; 230 | } 231 | if (column.type === undefined || column.type === 'custom') { 232 | return row[column.prop]; 233 | } else if (column.type === 'template') { 234 | return this.table.$scopedSlots[column.template] 235 | ? this.table.$scopedSlots[column.template]({ row, rowIndex, column, columnIndex }) 236 | : ''; 237 | } 238 | return ''; 239 | } 240 | 241 | // Template 242 | return ( 243 | 244 | 245 | { this.table.tableColumns.map(column => 246 | ) 247 | } 248 | 249 | 250 | { this.table.bodyData.length > 0 251 | ? this.table.bodyData.map((row, rowIndex) => 252 | [ 253 | this.handleEvent($event, 'row', { row, rowIndex }) } 259 | on-dblclick={ $event => this.handleEvent($event, 'row', { row, rowIndex }) } 260 | on-contextmenu={ $event => this.handleEvent($event, 'row', { row, rowIndex }) } 261 | on-mouseenter={ $event => this.handleEvent($event, 'row', { row, rowIndex }, { hover: true }) } 262 | on-mouseleave={ $event => this.handleEvent($event, 'row', { row, rowIndex }, { hover: false }) }> 263 | { this.table.tableColumns.map((column, columnIndex) => 264 | ) 276 | } 277 | , 278 | this.table.expandType && row._isExpanded && 279 | 282 | 290 | , 291 | ]) 292 | : 294 | 299 | 300 | } 301 | 302 |
this.handleEvent($event, 'cell', { row, rowIndex, column, columnIndex }) } 268 | on-dblclick={ $event => this.handleEvent($event, 'cell', { row, rowIndex, column, columnIndex }) } 269 | on-contextmenu={ $event => this.handleEvent($event, 'cell', { row, rowIndex, column, columnIndex }) } 270 | on-mouseenter={ $event => this.handleEvent($event, 'cell', { row, rowIndex, column, columnIndex }) } 271 | on-mouseleave={ $event => this.handleEvent($event, 'cell', { row, rowIndex, column, columnIndex }) }> 272 |
273 | { renderCell.call(this, row, rowIndex, column, columnIndex) } 274 |
275 |
285 | { this.table.$scopedSlots.$expand 286 | ? this.table.$scopedSlots.$expand({ row, rowIndex }) 287 | : '' 288 | } 289 |
297 | { this.table.emptyText } 298 |
303 | ); 304 | }, 305 | }; 306 | -------------------------------------------------------------------------------- /src/Table/TableFooter.js: -------------------------------------------------------------------------------- 1 | import { mixins } from './utils'; 2 | 3 | /* eslint-disable no-underscore-dangle */ 4 | export default { 5 | name: 'zk-table__footer', 6 | mixins: [mixins], 7 | data() { 8 | return { 9 | 10 | }; 11 | }, 12 | computed: { 13 | table() { 14 | return this.$parent; 15 | }, 16 | }, 17 | methods: { 18 | 19 | }, 20 | render() { 21 | // 计算各列总和 22 | function renderCell({ prop }, columnIndex) { 23 | if (columnIndex === 0) { 24 | return this.table.sumText; 25 | } 26 | const rows = this.table.bodyData; 27 | const values = rows.map(row => Number(row[prop])); 28 | const precisions = []; 29 | let notNumber = true; 30 | values.forEach((value) => { 31 | if (!isNaN(value)) { 32 | notNumber = false; 33 | const decimal = value.toString().split('.')[1]; 34 | precisions.push(decimal ? decimal.length : 0); 35 | } 36 | }); 37 | const precision = Math.max.apply(null, precisions); 38 | if (!notNumber) { 39 | return values.reduce((prev, curr) => { 40 | const value = Number(curr); 41 | if (!isNaN(value)) { 42 | return parseFloat((prev + curr).toFixed(precision)); 43 | } 44 | return prev; 45 | }, 0); 46 | } 47 | return ''; 48 | } 49 | 50 | // className 51 | function getClassName() { 52 | const classList = []; 53 | classList.push(`${this.prefixCls}__footer-cell`); 54 | if (this.table.border) { 55 | classList.push(`${this.prefixCls}--border-cell`); 56 | } 57 | return classList.join(' '); 58 | } 59 | 60 | // Template 61 | return ( 62 | 63 | 64 | { this.table.tableColumns.map(column => 65 | ) 66 | } 67 | 68 | 69 | 70 | { this.table.tableColumns.map((column, columnIndex) => 71 | ) 78 | } 79 | 80 | 81 |
72 |
73 | { this.table.summaryMethod 74 | ? this.table.summaryMethod(this.table.bodyData, column, columnIndex) 75 | : renderCell.call(this, column, columnIndex) } 76 |
77 |
82 | ); 83 | }, 84 | }; 85 | -------------------------------------------------------------------------------- /src/Table/TableHeader.js: -------------------------------------------------------------------------------- 1 | import Checkbox from '../Checkbox/Checkbox'; // eslint-disable-line 2 | import { mixins } from './utils'; 3 | 4 | /* eslint-disable no-underscore-dangle */ 5 | export default { 6 | name: 'zk-table__header', 7 | mixins: [mixins], 8 | data() { 9 | return { 10 | 11 | }; 12 | }, 13 | computed: { 14 | table() { 15 | return this.$parent; 16 | }, 17 | }, 18 | methods: { 19 | toggleAllChecked(checked) { 20 | this.table.bodyData = this.table.bodyData.map(row => ({ 21 | ...row, 22 | _isChecked: checked, 23 | })); 24 | }, 25 | }, 26 | render() { 27 | // className 28 | function getClassName(type, { headerAlign, prop }) { 29 | const certainType = this.validateType(type, ['cell', 'inner'], 'getClassName'); 30 | const classList = []; 31 | if (certainType.cell) { 32 | classList.push(`${this.prefixCls}__header-cell`); 33 | if (this.table.border) { 34 | classList.push(`${this.prefixCls}--border-cell`); 35 | } 36 | if (['center', 'right'].indexOf(headerAlign) > -1) { 37 | classList.push(`${this.prefixCls}--${headerAlign}-cell`); 38 | } 39 | } 40 | if (certainType.inner) { 41 | classList.push(`${this.prefixCls}__cell-inner`); 42 | if (this.table.treeType && this.table.firstProp === prop) { 43 | classList.push(`${this.prefixCls}--firstProp-header-inner`); 44 | } 45 | } 46 | return classList.join(' '); 47 | } 48 | 49 | // 根据type渲染单元格Label 50 | function renderLabel(column, columnIndex) { 51 | if (this.isSelectionCell(this.table, columnIndex)) { 52 | const allCheck = this.table.bodyData.every(row => row._isChecked); 53 | const indeterminate = !allCheck && this.table.bodyData.some(row => row._isChecked); 54 | return this.toggleAllChecked(checked) } 58 | >; 59 | } 60 | return column.label ? column.label : ''; 61 | } 62 | 63 | // Template 64 | return ( 65 | 66 | 67 | { this.table.tableColumns.map(column => 68 | ) 69 | } 70 | 71 | 72 | 73 | { this.table.tableColumns.map((column, columnIndex) => 74 | ) 79 | } 80 | 81 | 82 |
75 |
76 | { renderLabel.call(this, column, columnIndex) } 77 |
78 |
83 | ); 84 | }, 85 | }; 86 | -------------------------------------------------------------------------------- /src/Table/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MisterTaki/vue-table-with-tree-grid/b3d4a537a79cd2d409e30fd4ea0da0d91d5b2969/src/Table/font/iconfont.eot -------------------------------------------------------------------------------- /src/Table/font/iconfont.less: -------------------------------------------------------------------------------- 1 | // icon 2 | @font-face {font-family: "iconfont"; 3 | src: url('iconfont.eot?t=1505310522875'); /* IE9*/ 4 | src: url('iconfont.eot?t=1505310522875#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAW0AAsAAAAACOQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFZW7kggY21hcAAAAYAAAABuAAABojLtBtFnbHlmAAAB8AAAAa8AAAKA73SzT2hlYWQAAAOgAAAALwAAADYO3fRqaGhlYQAAA9AAAAAcAAAAJAfeA4ZobXR4AAAD7AAAABMAAAAUE+kAAGxvY2EAAAQAAAAADAAAAAwBbAHYbWF4cAAABAwAAAAeAAAAIAEUAF1uYW1lAAAELAAAAUUAAAJtPlT+fXBvc3QAAAV0AAAAQAAAAFryy5h0eJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2Bk/s04gYGVgYOpk+kMAwNDP4RmfM1gxMjBwMDEwMrMgBUEpLmmMDgwVDwzZm7438AQw9zA0AAUZgTJAQAoHgyieJzFkcENwyAUQ98HyqHKKDmEZoEOklOnYOK/RmI+uXSCWDLG/pZAALyALK5iAfthDBxKLfLMO/LCJl+lRqL7fp7y3VuoKprV0KxO0qbyGOy5o/+xxPq9nV6YflNX9DY5fsA/k6Pj+yTpAn3jEO8AAHiclVDNitNQFD7n3slNE9vE5N7kpOn0J0mbKB3DGDMZRGw3bhQXA2LB5TyAbmfjohvBhQvfYEAEoc8wr+EDiK4KPkITU0EcXDmHw3fOgfN9fHygATTf+BUPQMIduA9P4AwAxRxjiw0xysqczdGLNI+UxbMki/QkzvljpFgov6jKlIQubLRwhA+iospyluFJuWCPsPCHiP1B+MKdHbr8I5pBNnpXP2Of0Bsnh/biXv30aKmKiexcdF2377ofOkLTOowd2Ba+Jt/QDFPUnzU79K7Gd9kYu/0sfP6qNxm45+/LN8MZGYjrNcrBxPqydEKn7behL92+frvXCcJeMlV48eNWILvD9Du0hXtgl+wSHIBZnJZLzNKyKgh9paPAdVca260hAxPBMBowz9t1uzUDuT8CswEDDtq8Gr7mADaM4QgetrKRhbozQooWeOrkKJVIojg9ccqqjcT3uBJ6lGNZnUYjnBU+ORbGaeYskM93m+kx4vGUrX5PQXK3kUSSrSS9JFWvFJHCjaIGJCFSugcOLeM6c7f6wyFZf/37+PO6AgD/x/vNnN/A7H8bhF86tmcbAHicY2BkYGAAYl/p/jnx/DZfGbhZGEDg6v0IKwT9/yELA7MEkMvBwAQSBQAZYgnZAHicY2BkYGBu+N/AEMPCAAJAkpEBFbACAEcLAm54nGNhYGBgfsnAwMKAwAAOmwD9AAAAAAAAdgCYAPYBQHicY2BkYGBgZQgEYhBgAmIuIGRg+A/mMwAAES0BcgAAeJxlj01OwzAQhV/6B6QSqqhgh+QFYgEo/RGrblhUavdddN+mTpsqiSPHrdQDcB6OwAk4AtyAO/BIJ5s2lsffvHljTwDc4Acejt8t95E9XDI7cg0XuBeuU38QbpBfhJto41W4Rf1N2MczpsJtdGF5g9e4YvaEd2EPHXwI13CNT+E69S/hBvlbuIk7/Aq30PHqwj7mXle4jUcv9sdWL5xeqeVBxaHJIpM5v4KZXu+Sha3S6pxrW8QmU4OgX0lTnWlb3VPs10PnIhVZk6oJqzpJjMqt2erQBRvn8lGvF4kehCblWGP+tsYCjnEFhSUOjDFCGGSIyujoO1Vm9K+xQ8Jee1Y9zed0WxTU/3OFAQL0z1xTurLSeTpPgT1fG1J1dCtuy56UNJFezUkSskJe1rZUQuoBNmVXjhF6XNGJPyhnSP8ACVpuyAAAAHicY2BigAAuBuyAlZGJkZmRhZGVkY2BsYI7MS89J1W3KDM9o4S3IKe0WLe4sDSxKFU3ny83Mw+Jy8AAAHSWD8A=') format('woff'), 6 | url('iconfont.ttf?t=1505310522875') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('iconfont.svg?t=1505310522875#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .zk-icon { 11 | font-family: "iconfont" !important; 12 | font-size: 14px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .zk-icon-plus-square-o:before { content: "\e631"; } 19 | 20 | .zk-icon-minus-square-o:before { content: "\e632"; } 21 | 22 | .zk-icon-angle-right:before { content: "\e633"; } 23 | -------------------------------------------------------------------------------- /src/Table/font/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/Table/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MisterTaki/vue-table-with-tree-grid/b3d4a537a79cd2d409e30fd4ea0da0d91d5b2969/src/Table/font/iconfont.ttf -------------------------------------------------------------------------------- /src/Table/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MisterTaki/vue-table-with-tree-grid/b3d4a537a79cd2d409e30fd4ea0da0d91d5b2969/src/Table/font/iconfont.woff -------------------------------------------------------------------------------- /src/Table/utils/index.js: -------------------------------------------------------------------------------- 1 | export mixins from './mixins'; 2 | export scrollBarWidth from './scrollBarWidth'; 3 | -------------------------------------------------------------------------------- /src/Table/utils/mixins.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | prefixCls: 'zk-table', 5 | }; 6 | }, 7 | methods: { 8 | validateType(type, validTypes, funcName, isReturn = true) { 9 | if (validTypes.indexOf(type) < 0) throw new Error(`${funcName}'s type must is ${validTypes.join(' or ')}.`); 10 | if (isReturn) { 11 | const certainType = {}; 12 | validTypes.forEach((item) => { 13 | certainType[item] = item === type; 14 | }); 15 | return certainType; 16 | } 17 | return true; 18 | }, 19 | isExpandCell(table, columnIndex) { 20 | return table.expandType && ( 21 | (table.showIndex && columnIndex === 1) || 22 | (!table.showIndex && columnIndex === 0) 23 | ); 24 | }, 25 | isSelectionCell(table, columnIndex) { 26 | return table.selectionType && ( 27 | (table.showIndex && table.expandType && columnIndex === 2) || 28 | (!table.showIndex && table.expandType && columnIndex === 1) || 29 | (table.showIndex && !table.expandType && columnIndex === 1) || 30 | (!table.showIndex && !table.expandType && columnIndex === 0) 31 | ); 32 | }, 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /src/Table/utils/scrollBarWidth.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | let scrollBarWidth; 4 | 5 | export default function () { 6 | if (Vue.prototype.$isServer) return 0; 7 | if (scrollBarWidth !== undefined) return scrollBarWidth; 8 | 9 | const outer = document.createElement('div'); 10 | outer.style.visibility = 'hidden'; 11 | outer.style.width = '100px'; 12 | outer.style.position = 'absolute'; 13 | outer.style.top = '-9999px'; 14 | document.body.appendChild(outer); 15 | 16 | const widthNoScroll = outer.offsetWidth; 17 | outer.style.overflow = 'scroll'; 18 | 19 | const inner = document.createElement('div'); 20 | inner.style.width = '100%'; 21 | outer.appendChild(inner); 22 | 23 | const widthWithScroll = inner.offsetWidth; 24 | outer.parentNode.removeChild(outer); 25 | scrollBarWidth = widthNoScroll - widthWithScroll; 26 | 27 | return scrollBarWidth; 28 | } 29 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import ZkTable from './Table/Table'; 2 | 3 | ZkTable.install = (Vue) => { 4 | Vue.component(ZkTable.name, ZkTable); 5 | }; 6 | 7 | export default ZkTable; 8 | --------------------------------------------------------------------------------