├── static
└── .gitkeep
├── .eslintignore
├── WechatIMG132.jpeg
├── config
├── prod.env.js
├── dev.env.js
└── index.js
├── src
├── grid
│ ├── error.png
│ ├── more.png
│ ├── offcheck.png
│ ├── oncheck.png
│ ├── history.js
│ ├── checkbox.vue
│ ├── scroller.js
│ ├── painted.js
│ ├── index.vue
│ ├── events.js
│ └── calculate.js
├── App.vue
├── router
│ └── index.js
├── main.js
└── components
│ └── Example.vue
├── .editorconfig
├── .gitignore
├── .postcssrc.js
├── index.html
├── .babelrc
├── README.md
├── .eslintrc.js
└── package.json
/static/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 | config/*.js
3 |
--------------------------------------------------------------------------------
/WechatIMG132.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubyvirus/vue-grid-canvas/HEAD/WechatIMG132.jpeg
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | NODE_ENV: '"production"'
4 | }
5 |
--------------------------------------------------------------------------------
/src/grid/error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubyvirus/vue-grid-canvas/HEAD/src/grid/error.png
--------------------------------------------------------------------------------
/src/grid/more.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubyvirus/vue-grid-canvas/HEAD/src/grid/more.png
--------------------------------------------------------------------------------
/src/grid/offcheck.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubyvirus/vue-grid-canvas/HEAD/src/grid/offcheck.png
--------------------------------------------------------------------------------
/src/grid/oncheck.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubyvirus/vue-grid-canvas/HEAD/src/grid/oncheck.png
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const merge = require('webpack-merge')
3 | const prodEnv = require('./prod.env')
4 |
5 | module.exports = merge(prodEnv, {
6 | NODE_ENV: '"development"'
7 | })
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 4
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Editor directories and files
9 | .idea
10 | .vscode
11 | *.suo
12 | *.ntvs*
13 | *.njsproj
14 | *.sln
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 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | vue-grid-canvas
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import Example from '@/components/Example'
4 |
5 | Vue.use(Router)
6 |
7 | export default new Router({
8 | routes: [
9 | {
10 | path: '/',
11 | name: 'Example',
12 | component: Example,
13 | },
14 | ],
15 | })
16 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-runtime"],
12 | "env": {
13 | "test": {
14 | "presets": ["env", "stage-2"],
15 | "plugins": ["istanbul"]
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/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 App from './App'
5 | import router from './router'
6 |
7 | Vue.config.productionTip = false
8 |
9 | /* eslint-disable no-new */
10 | new Vue({
11 | el: '#app',
12 | router,
13 | template: '',
14 | components: { App },
15 | })
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-grid-canvas
2 |
3 | > a vue component,基于vue的表格组件,主要解决大数据量的表格渲染性能问题,使用canvas绘制表格,同时支持类似excel的批量选中,复制黏贴删除,实时编辑等功能。
4 |
5 | 
6 |
7 | ### 目前测试阶段,暂不发布npm仓库,定制性较弱,需要日后完善,有需要的朋友,可参考example使用
8 |
9 | 一个类似excel的表格组件,说明:
10 | * 1,通过canvas实现,能处理万级数据
11 | * 2,类似excel,选中单元格并实时编辑
12 | * 3,复制黏贴,支持批量,从excel复制,复制到excel都可以
13 | * 4,撤销/前进
14 | * 5,checkbox勾选框,全选功能,可开关
15 | * 6,固定列(目前只支持固定到右侧)
16 | * 7,删除单元格,支持批量
17 | * 7,支持文本的重新计算渲染(通过计算的单元格不支持实时编辑)
18 | * 8,支持基础按钮显示及点击事件
19 | * 9,隐藏列功能,可开关
20 |
21 |
22 | 以后计划:
23 | * 1,由于使用canvas不支持浏览器的检索功能,以后加上表格的搜索功能
24 | * 2,行列拖拽
25 |
26 |
27 |
28 | ## 运行例子
29 |
30 | ``` bash
31 | # install dependencies
32 | npm install
33 |
34 | # serve with hot reload at localhost:8080
35 | npm run dev
36 |
37 | # build for production with minification
38 | npm run build
39 |
40 | # build for production and view the bundle analyzer report
41 | npm run build --report
42 | ```
43 |
--------------------------------------------------------------------------------
/.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 | "indent": [2, 4],
39 | "eol-last": 1,
40 | "semi": [2, "never"],
41 | "no-restricted-syntax": 0,
42 | "no-param-reassign": 0,
43 | "max-len": 0,
44 | "no-labels": 0,
45 | "no-continue": 0,
46 | "global-require": 0,
47 | "import/no-dynamic-require": 0,
48 | "no-shadow": 0,
49 | "no-underscore-dangle": 0,
50 | "linebreak-style": 0,
51 | "linebreak-style": 0
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 |
2 | 'use strict'
3 | // Template version: 1.1.1
4 | // see http://vuejs-templates.github.io/webpack for documentation.
5 |
6 | const path = require('path')
7 |
8 | module.exports = {
9 | build: {
10 | env: require('./prod.env'),
11 | index: path.resolve(__dirname, '../dist/index.html'),
12 | assetsRoot: path.resolve(__dirname, '../dist'),
13 | assetsSubDirectory: 'static',
14 | assetsPublicPath: '/',
15 | productionSourceMap: true,
16 | // Gzip off by default as many popular static hosts such as
17 | // Surge or Netlify already gzip all static assets for you.
18 | // Before setting to `true`, make sure to:
19 | // npm install --save-dev compression-webpack-plugin
20 | productionGzip: false,
21 | productionGzipExtensions: ['js', 'css'],
22 | // Run the build command with an extra argument to
23 | // View the bundle analyzer report after build finishes:
24 | // `npm run build --report`
25 | // Set to `true` or `false` to always turn it on or off
26 | bundleAnalyzerReport: process.env.npm_config_report
27 | },
28 | dev: {
29 | env: require('./dev.env'),
30 | port: process.env.PORT || 8080,
31 | autoOpenBrowser: true,
32 | assetsSubDirectory: 'static',
33 | assetsPublicPath: '/',
34 | proxyTable: {},
35 | // CSS Sourcemaps off by default because relative paths are "buggy"
36 | // with this option, according to the CSS-Loader README
37 | // (https://github.com/webpack/css-loader#sourcemaps)
38 | // In our experience, they generally work as expected,
39 | // just be aware of this issue when enabling this option.
40 | cssSourceMap: false
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-grid-canvas",
3 | "version": "1.0.0",
4 | "description": "a vue component",
5 | "author": "Harvey Zhao ",
6 | "private": true,
7 | "scripts": {
8 | "dev": "node build/dev-server.js",
9 | "start": "npm run dev",
10 | "build": "node build/build.js",
11 | "lint": "eslint --ext .js,.vue src"
12 | },
13 | "dependencies": {
14 | "throttle-debounce": "^1.0.1",
15 | "vue": "^2.4.2",
16 | "vue-router": "^2.7.0"
17 | },
18 | "devDependencies": {
19 | "autoprefixer": "^7.1.2",
20 | "babel-core": "^6.22.1",
21 | "babel-eslint": "^7.1.1",
22 | "babel-loader": "^7.1.1",
23 | "babel-plugin-transform-runtime": "^6.22.0",
24 | "babel-preset-env": "^1.3.2",
25 | "babel-preset-stage-2": "^6.22.0",
26 | "babel-register": "^6.22.0",
27 | "chalk": "^2.0.1",
28 | "connect-history-api-fallback": "^1.3.0",
29 | "copy-webpack-plugin": "^4.0.1",
30 | "css-loader": "^0.28.0",
31 | "eslint": "^3.19.0",
32 | "eslint-config-airbnb-base": "^11.3.0",
33 | "eslint-friendly-formatter": "^3.0.0",
34 | "eslint-import-resolver-webpack": "^0.8.3",
35 | "eslint-loader": "^1.7.1",
36 | "eslint-plugin-html": "^3.0.0",
37 | "eslint-plugin-import": "^2.7.0",
38 | "eventsource-polyfill": "^0.9.6",
39 | "express": "^4.14.1",
40 | "extract-text-webpack-plugin": "^3.0.0",
41 | "file-loader": "^1.1.4",
42 | "friendly-errors-webpack-plugin": "^1.6.1",
43 | "html-webpack-plugin": "^2.30.1",
44 | "http-proxy-middleware": "^0.17.3",
45 | "node-sass": "^4.5.3",
46 | "opn": "^5.1.0",
47 | "optimize-css-assets-webpack-plugin": "^3.2.0",
48 | "ora": "^1.2.0",
49 | "portfinder": "^1.0.13",
50 | "rimraf": "^2.6.0",
51 | "sass-loader": "^6.0.6",
52 | "semver": "^5.3.0",
53 | "shelljs": "^0.7.6",
54 | "url-loader": "^0.5.8",
55 | "vue-loader": "^13.0.4",
56 | "vue-style-loader": "^3.0.1",
57 | "vue-template-compiler": "^2.4.2",
58 | "webpack": "^3.6.0",
59 | "webpack-bundle-analyzer": "^2.9.0",
60 | "webpack-dev-middleware": "^1.12.0",
61 | "webpack-hot-middleware": "^2.18.2",
62 | "webpack-merge": "^4.1.0"
63 | },
64 | "engines": {
65 | "node": ">= 4.0.0",
66 | "npm": ">= 3.0.0"
67 | },
68 | "browserslist": [
69 | "> 1%",
70 | "last 2 versions",
71 | "not ie <= 8"
72 | ]
73 | }
74 |
--------------------------------------------------------------------------------
/src/grid/history.js:
--------------------------------------------------------------------------------
1 | export default {
2 | data() {
3 | return {
4 | history: [],
5 | historyIndex: 0,
6 | currentStateValue: null,
7 | }
8 | },
9 | created() {
10 | this.$on('history_back', () => {
11 | if (this.historyIndex >= 0) {
12 | const needBackValue = this.history[this.historyIndex]
13 | if (needBackValue.type === 'edit') {
14 | this.saveItem(needBackValue.before, false)
15 | this.focusCellByOriginCell(this.getCellByRowAndKey(needBackValue.before.index, needBackValue.before.key))
16 | this.selectArea = null
17 | this.isSelect = false
18 | } else if (needBackValue.type === 'editMore') {
19 | this.$emit('updateValue', this.saveItems(needBackValue.before, false))
20 | this.focusCell = needBackValue.focusCell
21 | this.rowFocus = {
22 | cellX: this.focusCell.x,
23 | cellY: this.focusCell.y,
24 | rowIndex: this.focusCell.rowIndex,
25 | offset: { ...this.offset },
26 | }
27 | this.paintFocusCell(this.focusCell)
28 | this.selectArea = needBackValue.selectArea
29 | this.isSelect = true
30 | this.rePainted()
31 | }
32 | this.historyIndex -= 1
33 | }
34 | })
35 | this.$on('history_forward', () => {
36 | if (this.historyIndex !== this.history.length - 1) {
37 | this.historyIndex += 1
38 | const needforwardValue = this.history[this.historyIndex]
39 | if (needforwardValue.type === 'edit') {
40 | this.saveItem(needforwardValue.after, false)
41 | this.focusCellByOriginCell(this.getCellByRowAndKey(needforwardValue.after.index, needforwardValue.after.key))
42 | }
43 | }
44 | })
45 | },
46 | methods: {
47 | pushState(data) {
48 | this.history = this.history.slice(0, this.historyIndex + 1)
49 | this.history.push(data)
50 | if (this.history.length > 20) {
51 | this.history.splice(0, 1)
52 | }
53 | this.historyIndex = this.history.length - 1
54 | },
55 | },
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/Example.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
72 |
--------------------------------------------------------------------------------
/src/grid/checkbox.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
94 |
95 |
187 |
--------------------------------------------------------------------------------
/src/grid/scroller.js:
--------------------------------------------------------------------------------
1 | export default {
2 | data() {
3 | return {
4 | horizontalBar: {
5 | x: 0,
6 | size: 0,
7 | move: false,
8 | cursorX: 0,
9 | k: 1,
10 | },
11 | verticalBar: {
12 | y: 0,
13 | size: 0,
14 | move: false,
15 | cursorY: 0,
16 | k: 1,
17 | },
18 | }
19 | },
20 | created() {
21 | this.$on('scroll', () => {
22 | this.horizontalBar.x = -parseInt(this.offset.x * this.horizontalBar.k, 10)
23 | this.verticalBar.y = -parseInt(this.offset.y * this.verticalBar.k, 10)
24 | })
25 | },
26 | mounted() {
27 | // document.addEventListener('mouseup', () => {
28 | // this.horizontalBar.move = false
29 | // this.verticalBar.move = false
30 | // })
31 | // document.addEventListener('mousemove', (e) => {
32 | // if (this.verticalBar.move) {
33 | // const height = this.maxPoint.y - this.verticalBar.size
34 | // const moveHeight = this.verticalBar.y + (e.screenY - this.verticalBar.cursorY)
35 | // if (moveHeight > 0 && moveHeight < height) {
36 | // this.verticalBar.y += e.screenY - this.verticalBar.cursorY
37 | // } else if (moveHeight <= 0) {
38 | // this.verticalBar.y = 0
39 | // } else {
40 | // this.verticalBar.y = height
41 | // }
42 | // this.verticalBar.cursorY = e.screenY
43 | // this.offset.y = -this.verticalBar.y / this.verticalBar.k
44 | // requestAnimationFrame(this.rePainted)
45 | // }
46 | // if (this.horizontalBar.move) {
47 | // let width = 0
48 | // if (this.fillWidth > 0) {
49 | // width = this.maxPoint.x - this.horizontalBar.size
50 | // } else {
51 | // width = (this.maxPoint.x + this.fixedWidth) - this.horizontalBar.size
52 | // }
53 | // const moveWidth = this.horizontalBar.x + (e.screenX - this.horizontalBar.cursorX)
54 | // if (moveWidth > 0 && moveWidth < width) {
55 | // this.horizontalBar.x += e.screenX - this.horizontalBar.cursorX
56 | // } else if (moveWidth <= 0) {
57 | // this.horizontalBar.x = 0
58 | // } else {
59 | // this.horizontalBar.x = width
60 | // }
61 | // this.horizontalBar.cursorX = e.screenX
62 | // this.offset.x = -this.horizontalBar.x / this.horizontalBar.k
63 | // requestAnimationFrame(this.rePainted)
64 | // }
65 | // })
66 | },
67 | methods: {
68 | scroll(e, type) {
69 | if (type && this.verticalBar.size) {
70 | if (e.offsetY < this.verticalBar.y) {
71 | let k = 15
72 | if (this.verticalBar.y - e.offsetY < 15) {
73 | k = this.verticalBar.y - e.offsetY
74 | }
75 | this.verticalBar.y -= k
76 | this.offset.y = -this.verticalBar.y / this.verticalBar.k
77 | requestAnimationFrame(this.rePainted)
78 | } else if (e.offsetY > this.verticalBar.y + this.verticalBar.size) {
79 | let k = 15
80 | if (e.offsetY - this.verticalBar.y - this.verticalBar.size < 15) {
81 | k = e.offsetY - this.verticalBar.y - this.verticalBar.size
82 | }
83 | this.verticalBar.y += k
84 | this.offset.y = -this.verticalBar.y / this.verticalBar.k
85 | requestAnimationFrame(this.rePainted)
86 | }
87 | }
88 | if (type === 0 && this.horizontalBar.size) {
89 | if (e.offsetX < this.horizontalBar.x) {
90 | let k = 15
91 | if (this.horizontalBar.x - e.offsetX < 15) {
92 | k = this.horizontalBar.x - e.offsetX
93 | }
94 | this.horizontalBar.x -= k
95 | this.offset.x = -this.horizontalBar.x / this.horizontalBar.k
96 | requestAnimationFrame(this.rePainted)
97 | } else if (e.offsetX > this.horizontalBar.x + this.horizontalBar.size) {
98 | let k = 15
99 | if (e.offsetX - this.horizontalBar.x - this.horizontalBar.size < 15) {
100 | k = e.offsetX - this.horizontalBar.x - this.horizontalBar.size
101 | }
102 | this.horizontalBar.x += k
103 | this.horizontalBar.x += 15
104 | this.offset.x = -this.horizontalBar.x / this.horizontalBar.k
105 | requestAnimationFrame(this.rePainted)
106 | }
107 | }
108 | },
109 | dragMove(e, type) {
110 | if (type) {
111 | this.verticalBar.move = true
112 | this.verticalBar.cursorY = e.screenY
113 | } else {
114 | this.horizontalBar.move = true
115 | this.horizontalBar.cursorX = e.screenX
116 | }
117 | },
118 | resetScrollBar({ x, y }, bodyWidth, bodyHeight, fixedWidth) {
119 | let width = 0
120 | if (this.fillWidth > 0) {
121 | width = x
122 | } else {
123 | width = x + fixedWidth
124 | }
125 |
126 | const horizontalRatio = width / bodyWidth
127 | if (horizontalRatio >= 1) {
128 | this.horizontalBar.size = 0
129 | } else {
130 | this.horizontalBar.size = width - ((bodyWidth - width) * horizontalRatio)
131 | }
132 | this.horizontalBar.k = horizontalRatio
133 |
134 | let verticalRatio = y / bodyHeight
135 | if (verticalRatio > 1) {
136 | this.verticalBar.size = 0
137 | } else {
138 | this.verticalBar.size = y - ((bodyHeight - y) * verticalRatio)
139 | if (this.verticalBar.size < 30) {
140 | this.verticalBar.size = 30
141 | verticalRatio = (y - 30) / (bodyHeight - y)
142 | }
143 | }
144 | this.verticalBar.k = verticalRatio
145 |
146 | if (width - this.horizontalBar.size < -this.offset.x * this.horizontalBar.k) {
147 | this.offset.x = width - this.bodyWidth
148 | }
149 | if (this.verticalBar.k > 1) {
150 | this.offset.y = 0
151 | } else if (this.maxPoint.y - this.verticalBar.size < -this.offset.y * this.verticalBar.k) {
152 | this.offset.y = this.maxPoint.y - this.bodyHeight
153 | }
154 | this.horizontalBar.x = -this.offset.x * this.horizontalBar.k
155 | this.verticalBar.y = -this.offset.y * this.verticalBar.k
156 | },
157 | },
158 | }
159 |
--------------------------------------------------------------------------------
/src/grid/painted.js:
--------------------------------------------------------------------------------
1 | export default {
2 |
3 | data() {
4 | const oncheck = new Image()
5 | oncheck.src = require('./oncheck.png')
6 | const offcheck = new Image()
7 | offcheck.src = require('./offcheck.png')
8 | const more = new Image()
9 | more.src = require('./more.png')
10 | return {
11 | headerColor: '#333333',
12 | textColor: '#666666',
13 | borderColor: '#d4d4d4',
14 | white: '#ffffff',
15 | shadowColor: 'rgba(0,0,0,0.2)',
16 | fillColor: '#f9f9f9',
17 | buttonColor: '#20a0ff',
18 | focusColor: '#4285f4',
19 | selectColor: '#6bc9ff',
20 | selectAreaColor: 'rgba(160, 195, 255, 0.2)',
21 | selectRowColor: '#f6f6f6',
22 | dotColor: '#74d337',
23 | oncheck,
24 | offcheck,
25 | more,
26 | }
27 | },
28 | methods: {
29 | initCanvas() {
30 | const canvas = this.$refs.canvas
31 | let ctx = ''
32 | if (this.ctx) {
33 | ctx = this.ctx
34 | } else {
35 | ctx = canvas.getContext('2d')
36 | this.ctx = ctx
37 | }
38 | ctx.font = 'normal 12px PingFang SC'
39 | const backingStore = ctx.backingStorePixelRatio ||
40 | ctx.webkitBackingStorePixelRatio ||
41 | ctx.mozBackingStorePixelRatio ||
42 | ctx.msBackingStorePixelRatio ||
43 | ctx.oBackingStorePixelRatio ||
44 | ctx.backingStorePixelRatio || 1
45 |
46 | this.ratio = (window.devicePixelRatio || 1) / backingStore
47 |
48 | this.getAllCells(this.data, this.columns)
49 | this.setBodyHeight(this.allRows, this.originPoint)
50 | this.setMaxpoint(this.width, this.height, this.fixedWidth, this.scrollerWidth)
51 | this.resetScrollBar(this.maxPoint, this.bodyWidth, this.bodyHeight, this.fixedWidth)
52 | },
53 | p(value) {
54 | const temp = `${value}`
55 | if (temp && temp.indexOf && temp.indexOf('.') === -1) {
56 | return value + 0.5
57 | }
58 | return value
59 | },
60 | i(value) {
61 | return Math.round(value)
62 | },
63 | rePainted() {
64 | let items = this.initDisplayItems()
65 | if (this.autoAddRow) { // 自动增加行,减少行
66 | if (items.displayRows[items.displayRows.length - 1].rowIndex >= this.allRows.length - 50) {
67 | const startIndex = this.data.length
68 | for (let i = 0; i < 100; i += 1) {
69 | this.data.push(this.templateData)
70 | }
71 | this.setAllCells(startIndex)
72 | items = this.initDisplayItems()
73 | } else if (this.data.length > this.initRows && items.displayRows[items.displayRows.length - 1].rowIndex <= this.allRows.length - 200) {
74 | this.data.splice(this.data.length - 100, 100)
75 | this.allCells.splice(this.allCells.length - 100, 100)
76 | this.allRows.splice(this.allRows.length - 100, 100)
77 | this.setBodyHeight(this.allRows, this.originPoint)
78 | this.resetScrollBar(this.maxPoint, this.bodyWidth, this.bodyHeight, this.fixedWidth)
79 | items = this.initDisplayItems()
80 | }
81 | }
82 | this.clearPainted()
83 | this.painted(items)
84 | return items
85 | },
86 | clearPainted() {
87 | this.ctx.clearRect(0, 0, this.width, this.height)
88 | },
89 | painted(displayItems) {
90 | const ctx = this.ctx
91 | const { displayColumns, displayRows, displayCells, displayFixedCells } = displayItems
92 |
93 | ctx.fillStyle = this.headerColor// text color
94 | ctx.textAlign = 'center'
95 | ctx.lineWidth = 1
96 | ctx.strokeStyle = this.borderColor
97 | ctx.textBaseline = 'middle'
98 | ctx.save()
99 |
100 | this.renderButtons = []
101 |
102 | this.paintLine(ctx, displayRows, displayColumns)
103 |
104 | this.paintBody(ctx, displayCells)
105 |
106 | if (this.isSelect) {
107 | this.paintSelect(ctx, this.selectArea)
108 | }
109 | if (this.isFocus) {
110 | this.paintFocus(ctx, this.focusCell)
111 | }
112 |
113 | this.paintHeader(ctx, displayColumns)
114 |
115 | if (this.showCheckbox) {
116 | this.paintCheckbox(ctx, displayRows)
117 | }
118 |
119 | this.paintSerial(ctx, displayRows)
120 |
121 | this.paintNo(ctx)
122 |
123 | if (displayFixedCells.length > 0 && this.fillWidth === 0) {
124 | this.paintFixedCells(ctx, displayFixedCells, displayColumns)
125 | }
126 |
127 | this.painScroller(ctx, this.scrollerWidth)
128 | },
129 | paintCheckbox(ctx, displayRows) {
130 | this.checkboxs = []
131 | const { i, p, offset, maxPoint, allRows, focusCell, rowFocus, checkboxWidth, rowHeight, serialWidth, originPoint, height, oncheck, offcheck, selected } = this
132 | ctx.fillStyle = this.fillColor
133 | ctx.save()
134 | if (offset.x !== 0) {
135 | ctx.shadowBlur = 10
136 | ctx.shadowColor = this.shadowColor
137 | }
138 | ctx.fillRect(i(0), p(0), i(checkboxWidth + serialWidth), i(height))
139 | ctx.restore()
140 | ctx.beginPath()
141 | ctx.strokeStyle = this.borderColor
142 | ctx.lineWidth = 1
143 | ctx.moveTo(p(serialWidth), i(0))
144 | ctx.lineTo(p(serialWidth), i(maxPoint.y))
145 | ctx.moveTo(p(serialWidth + checkboxWidth), i(0))
146 | ctx.lineTo(p(serialWidth + checkboxWidth), i(maxPoint.y))
147 |
148 | for (const item of displayRows) {
149 | if (15 + item.y > -item.height) {
150 | if (rowFocus && rowFocus.cellY === item.y) {
151 | ctx.fillStyle = this.selectRowColor
152 | ctx.fillRect(p(serialWidth), p(item.y), i(checkboxWidth - 1), i(item.height))
153 | }
154 | ctx.moveTo(p(serialWidth), p(item.y + item.height))
155 | ctx.lineTo(p(serialWidth + checkboxWidth), p(item.y + item.height))
156 | if (selected.indexOf(item.rowIndex) !== -1) {
157 | ctx.drawImage(oncheck, p(serialWidth + 5), p(item.y + 5), 20, 20)
158 | } else {
159 | ctx.drawImage(offcheck, p(serialWidth + 5), p(item.y + 5), 20, 20)
160 | }
161 | this.checkboxs.push({
162 | rowIndex: item.rowIndex,
163 | x: p(serialWidth + 5),
164 | y: p(item.y + 5),
165 | width: 20,
166 | height: 20,
167 | })
168 | }
169 | }
170 | ctx.stroke()
171 | if (this.focusCell) {
172 | ctx.beginPath()
173 | ctx.strokeStyle = this.focusColor
174 | ctx.lineWidth = 2
175 | ctx.moveTo(i(serialWidth + checkboxWidth), i(focusCell.y - 1))
176 | ctx.lineTo(i(serialWidth + checkboxWidth), i(focusCell.y + focusCell.height + 1))
177 | ctx.stroke()
178 | }
179 | ctx.beginPath()
180 | ctx.strokeStyle = this.borderColor
181 | ctx.fillStyle = this.fillColor
182 | ctx.lineWidth = 1
183 | ctx.fillRect(p(serialWidth + 1), p(0), i(checkboxWidth), i(rowHeight))
184 | ctx.moveTo(p(serialWidth), p(originPoint.y))
185 | ctx.lineTo(p(serialWidth + checkboxWidth), p(originPoint.y))
186 | ctx.lineTo(p(serialWidth + checkboxWidth), p(0))
187 | ctx.stroke()
188 | if (selected.length === allRows.length) {
189 | ctx.drawImage(oncheck, p(serialWidth + 5), p(5), 20, 20)
190 | } else {
191 | ctx.drawImage(offcheck, p(serialWidth + 5), p(5), 20, 20)
192 | }
193 | },
194 | paintLine(ctx, displayRows, displayColumns) {
195 | const { p, i, maxPoint, rowHeight, rowFocus, serialWidth, bodyHeight } = this
196 |
197 | for (const item of displayRows) {
198 | if (rowFocus && rowFocus.cellY === item.y) {
199 | ctx.fillStyle = this.selectRowColor
200 | ctx.fillRect(p(-1), p(item.y), i(maxPoint.x), i(item.height))
201 | }
202 | }
203 | ctx.beginPath()
204 | ctx.strokeStyle = this.borderColor
205 | ctx.lineWidth = 1
206 | for (const column of displayColumns) {
207 | if (!column.fixed) {
208 | ctx.moveTo(p(column.x + column.width) + 0.25, i(0))
209 | ctx.lineTo(p(column.x + column.width), i(bodyHeight))
210 | }
211 | }
212 | for (const item of displayRows) {
213 | ctx.moveTo(i(0), p(item.y + item.height))
214 | ctx.lineTo(i(maxPoint.x), p(item.y + item.height))
215 | }
216 | ctx.moveTo(p(serialWidth), p(rowHeight))
217 | ctx.lineTo(p(serialWidth), i(bodyHeight))
218 | ctx.moveTo(i(0), p(rowHeight))
219 | ctx.lineTo(i(maxPoint.x), p(rowHeight))
220 |
221 | ctx.stroke()
222 | },
223 | paintFixedCells(ctx, displayFixedCells, displayColumns) {
224 | const { bodyHeight, rowHeight, maxPoint, paintText, paintButton, p, i, allColumns, fixedWidth, fixedColumns, rowFocus } = this
225 | ctx.save()
226 | const lastDisplayColumn = displayColumns[displayColumns.length - 1]
227 | if (lastDisplayColumn.cellIndex === allColumns.length - 1 - fixedColumns.length) {
228 | if (lastDisplayColumn.x + lastDisplayColumn.width > maxPoint.x) {
229 | ctx.shadowBlur = 10
230 | ctx.shadowColor = this.shadowColor
231 | }
232 | } else {
233 | ctx.shadowBlur = 10
234 | ctx.shadowColor = this.shadowColor
235 | }
236 | ctx.fillStyle = this.white
237 | ctx.fillRect(p(maxPoint.x), p(0), i(fixedWidth + 1), i(bodyHeight))
238 | ctx.restore()
239 |
240 | ctx.beginPath()
241 | ctx.fillStyle = this.textColor
242 | ctx.strokeStyle = this.borderColor
243 | ctx.lineWidth = 1
244 | let cellX = maxPoint.x
245 | for (const rows of displayFixedCells.reverse()) {
246 | let width = 0
247 | for (const item of rows) {
248 | width = item.width
249 | if (rowFocus && rowFocus.cellY === item.y) {
250 | ctx.fillStyle = this.selectRowColor
251 | ctx.fillRect(p(cellX), p(item.y), i(this.maxPoint.x), i(item.height))
252 | }
253 | if (item.buttons) {
254 | paintButton(ctx, item, cellX)
255 | } else if (item.paintText && item.paintText.length > 0) {
256 | paintText(ctx, i(cellX + (item.width / 2)), i(15 + item.y), item.paintText)
257 | }
258 | ctx.moveTo(p(cellX), p(item.y))
259 | ctx.lineTo(p(cellX), p(item.y + item.height))
260 | ctx.lineTo(p(cellX + item.width), p(item.y + item.height))
261 | }
262 | cellX += width
263 | }
264 | ctx.stroke()
265 |
266 | ctx.beginPath()
267 | ctx.font = 'bold 12px PingFang SC'
268 | let columnX = maxPoint.x
269 | for (const column of fixedColumns) {
270 | let textColor = this.headerColor
271 | if (rowFocus && rowFocus.cellX === columnX) {
272 | ctx.fillStyle = this.selectRowColor
273 | ctx.fillRect(columnX, 0, column.width, rowHeight)
274 | textColor = this.focusColor
275 | } else {
276 | ctx.fillStyle = this.fillColor
277 | ctx.fillRect(columnX, 0, column.width, rowHeight)
278 | }
279 | ctx.fillStyle = textColor
280 | ctx.fillText(column.title, i(columnX + (column.width / 2)), 15)
281 | ctx.moveTo(p(columnX), p(0))
282 | ctx.lineTo(p(columnX), p(rowHeight))
283 | ctx.lineTo(p(columnX + column.width), p(rowHeight))
284 | columnX += column.width
285 | }
286 | ctx.stroke()
287 | },
288 | paintButton(ctx, item, cellX) {
289 | let buttonGroupWidth = 0
290 | for (const button of item.buttons) {
291 | buttonGroupWidth += ctx.measureText(button.title).width
292 | }
293 | if (item.buttons.length > 1) {
294 | buttonGroupWidth += 20 * (item.buttons.length - 1)
295 | }
296 | let startX = 0
297 | if (item.width - buttonGroupWidth > 0) {
298 | startX = (item.width - buttonGroupWidth) / 2
299 | } else {
300 | startX = 0
301 | }
302 | ctx.save()
303 | ctx.font = 'normal 12px PingFang SC'
304 | for (const button of item.buttons) {
305 | const buttonWidth = ctx.measureText(button.title).width
306 | ctx.fillStyle = button.color ? button.color : this.buttonColor
307 | ctx.fillText(button.title, startX + cellX + (buttonWidth / 2), item.y + 15)
308 | this.renderButtons.push({
309 | x: startX + cellX,
310 | y: item.y + 7.5,
311 | cellX,
312 | cellY: item.y,
313 | width: buttonWidth,
314 | height: 12,
315 | click: button.click,
316 | rowIndex: item.rowIndex,
317 | offset: { ...this.offset },
318 | fixed: item.fixed,
319 | })
320 | startX += buttonWidth + 20
321 | }
322 | ctx.restore()
323 | },
324 | paintNo(ctx) {
325 | const { p, rowHeight, serialWidth, more } = this
326 | ctx.beginPath()
327 | ctx.strokeStyle = this.borderColor
328 | ctx.fillStyle = this.fillColor
329 | ctx.fillRect(0, 0, serialWidth, rowHeight)
330 | ctx.fillStyle = this.headerColor
331 | ctx.fillText('序号', serialWidth / 2, 15)
332 | ctx.lineWidth = 1
333 | ctx.moveTo(p(serialWidth), p(0))
334 | ctx.lineTo(p(serialWidth), p(rowHeight))
335 | ctx.lineTo(p(0), p(rowHeight))
336 | ctx.stroke()
337 |
338 | if (this.columnSet) {
339 | ctx.drawImage(more, 50, 6, 18, 18)
340 | }
341 | },
342 | painScroller(ctx, height) {
343 | const p = this.p
344 | ctx.fillStyle = this.white
345 | ctx.fillRect((this.width - height) + 1, 0, height - 1, this.height)
346 | ctx.fillRect(0, (this.height - height) + 1, this.width, height - 1)
347 | ctx.beginPath()
348 | ctx.lineWidth = 1
349 | ctx.strokeStyle = this.borderColor
350 | ctx.moveTo(p((this.width - height) + 1), p(0))
351 | ctx.lineTo(p((this.width - height) + 1), p((this.height - height) + 1))
352 | ctx.lineTo(p(0), p((this.height - height) + 1))
353 | ctx.fillStyle = this.white
354 | ctx.fillRect(p((this.width - height) + 1), p((this.height - height) + 1), height - 1, height - 1)
355 | ctx.stroke()
356 | },
357 | paintSelect(ctx, area) {
358 | const { p, originPoint, maxPoint } = this
359 | if (area.x + area.width > originPoint.x && area.y + area.height > originPoint.y && area.x < maxPoint.x && area.y < maxPoint.y) {
360 | ctx.beginPath()
361 | ctx.lineWidth = 1
362 | ctx.strokeStyle = this.selectColor
363 | ctx.moveTo(p(area.x), p(area.y))
364 | ctx.lineTo(p(area.x + area.width), p(area.y))
365 | ctx.lineTo(p(area.x + area.width), p(area.y + area.height))
366 | ctx.lineTo(p(area.x), p(area.y + area.height))
367 | ctx.closePath()
368 | ctx.fillStyle = this.selectAreaColor
369 | ctx.fill()
370 | ctx.stroke()
371 | }
372 | },
373 | paintFocus(ctx, cell) {
374 | const { i, originPoint, maxPoint } = this
375 | if (cell.x + cell.width > originPoint.x && cell.y + cell.height > originPoint.y && cell.x < maxPoint.x && cell.y < maxPoint.y) {
376 | ctx.lineWidth = 2
377 | ctx.strokeStyle = this.focusColor
378 | ctx.strokeRect(i(cell.x), i(cell.y), cell.width, cell.height)
379 | }
380 | },
381 | paintSerial(ctx, displayRows) {
382 | const { i, p, offset, bodyHeight, focusCell, rowFocus, serialWidth } = this
383 | if (!this.showCheckbox) {
384 | ctx.fillStyle = this.fillColor
385 | ctx.save()
386 | if (offset.x !== 0) {
387 | ctx.shadowBlur = 10
388 | ctx.shadowOffsetX = 3
389 | ctx.shadowColor = this.shadowColor
390 | }
391 | ctx.fillRect(0, 0, serialWidth, bodyHeight)
392 | ctx.restore()
393 | }
394 |
395 | ctx.lineWidth = 1
396 | for (const item of displayRows) {
397 | if (15 + item.y > -item.height) {
398 | ctx.beginPath()
399 | ctx.strokeStyle = this.borderColor
400 | let textColor = this.textColor
401 | if (rowFocus && rowFocus.cellY === item.y) {
402 | ctx.fillStyle = this.selectRowColor
403 | ctx.fillRect(-1, item.y + 1, serialWidth + 1, item.height)
404 | textColor = this.focusColor
405 | }
406 | ctx.fillStyle = textColor
407 |
408 | ctx.fillText(`${item.rowIndex + 1}`, serialWidth / 2, 15 + item.y)
409 | ctx.moveTo(p(0), p(item.y + item.height))
410 | ctx.lineTo(p(serialWidth), p(item.y + item.height))
411 | ctx.stroke()
412 |
413 | if (item.showDot) {
414 | ctx.beginPath()
415 | ctx.fillStyle = this.dotColor
416 | ctx.strokeStyle = this.fillColor
417 | ctx.arc(15, 15 + item.y, 4, 0, 2 * Math.PI)
418 | ctx.fill()
419 | ctx.stroke()
420 | }
421 | }
422 | }
423 | ctx.stroke()
424 |
425 | if (this.focusCell && !this.showCheckbox) {
426 | ctx.beginPath()
427 | ctx.strokeStyle = this.focusColor
428 | ctx.lineWidth = 2
429 | ctx.moveTo(i(serialWidth), i(focusCell.y - 1))
430 | ctx.lineTo(i(serialWidth), i(focusCell.y + focusCell.height + 1))
431 | ctx.stroke()
432 | }
433 | },
434 | paintHeader(ctx, displayColumns) {
435 | const { p, i, focusCell, width, rowHeight, rowFocus } = this
436 | ctx.fillStyle = this.fillColor
437 | ctx.fillRect(0, 0, width, rowHeight)
438 | ctx.beginPath()
439 | ctx.strokeStyle = this.borderColor
440 | ctx.font = 'bold 12px PingFang SC'
441 | ctx.lineWidth = 1
442 | for (const column of displayColumns) {
443 | if (!column.fixed || this.fillWidth > 0) {
444 | let textColor = this.headerColor
445 | if (rowFocus && rowFocus.cellX === column.x) {
446 | ctx.fillStyle = this.selectRowColor
447 | ctx.fillRect(p(column.x), p(0), p(column.width), p(rowHeight - 1))
448 | textColor = this.focusColor
449 | }
450 | ctx.fillStyle = textColor
451 | ctx.fillText(column.title, p(column.x + (column.width / 2)), p(15))
452 | ctx.moveTo(p(column.x + column.width), p(0))
453 | ctx.lineTo(p(column.x + column.width), p(rowHeight))
454 | }
455 | }
456 | ctx.stroke()
457 |
458 | if (focusCell) {
459 | ctx.beginPath()
460 | ctx.strokeStyle = this.focusColor
461 | ctx.lineWidth = 2
462 | ctx.moveTo(i(focusCell.x - 1), i(rowHeight))
463 | ctx.lineTo(i(focusCell.x + focusCell.width + 1), i(rowHeight))
464 | ctx.stroke()
465 | }
466 | },
467 | paintBody(ctx, displayCells) {
468 | const { paintText, i } = this
469 | ctx.beginPath()
470 | ctx.font = 'normal 12px PingFang SC'
471 | ctx.fillStyle = this.textColor
472 | for (const rows of displayCells) {
473 | for (const item of rows) {
474 | if (!item.fixed || this.fillWidth > 0) {
475 | if (item.buttons) {
476 | this.paintButton(ctx, item, i(item.x))
477 | } else if (item.paintText && item.paintText.length > 0) {
478 | paintText(ctx, i(item.x + (item.width / 2)), i(15 + item.y), item.paintText)
479 | }
480 | }
481 | }
482 | }
483 | ctx.stroke()
484 | },
485 | paintText(ctx, x, y, row) {
486 | for (let b = 0; b < row.length; b += 1) {
487 | ctx.fillText(row[b], x, y + (b * 18))
488 | }
489 | },
490 | },
491 | }
492 |
--------------------------------------------------------------------------------
/src/grid/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
16 |
17 |
18 |
19 | {{tipMessage}}
20 |
21 |
22 |
23 |
24 |
25 |
请选择需要显示的列
26 |
27 |
28 | -
29 | {{item.title}}
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
471 |
472 |
638 |
639 |
--------------------------------------------------------------------------------
/src/grid/events.js:
--------------------------------------------------------------------------------
1 | import throttle from 'throttle-debounce/throttle'
2 |
3 | export default {
4 | data() {
5 | return {
6 | rowFocus: null,
7 | isFirefox: false,
8 | pixelRatio: 1,
9 | shiftDown: false,
10 | ctrlDown: false,
11 | }
12 | },
13 | created() {
14 | this.isFirefox = typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().indexOf('firefox') > -1
15 | },
16 | watch: {
17 | retract() {
18 | this.handleResize()
19 | },
20 | },
21 | mounted() {
22 | this.pixelRatio = window.devicePixelRatio
23 | },
24 | methods: {
25 | removeEvent() {
26 | window.removeEventListener('mousedown', this.handleMousedown, false)
27 | window.removeEventListener('mousemove', throttle(16, this.handleMousemove), true)
28 | window.removeEventListener('mouseup', this.handleMouseup, false)
29 | window.removeEventListener('resize', this.handleResize, false)
30 | window.removeEventListener(this.isFirefox ? 'DOMMouseScroll' : 'mousewheel', this.handleWheel)
31 | window.removeEventListener('keydown', this.handleKeydown, false)
32 | window.removeEventListener('keyup', this.handleKeyup, false)
33 | },
34 | initEvent() {
35 | window.addEventListener('mousedown', this.handleMousedown, false)
36 | window.addEventListener('mousemove', throttle(16, this.handleMousemove), true)
37 | window.addEventListener('mouseup', this.handleMouseup, false)
38 | // this.$refs.canvas.addEventListener('mouseleave', this.handleMouseup, false)
39 | this.$refs.canvas.addEventListener('dblclick', this.handleDoubleClick, false)
40 | this.$refs.canvas.addEventListener('click', this.handleClick, false)
41 | window.addEventListener('resize', this.handleResize, false)
42 | window.addEventListener(this.isFirefox ? 'DOMMouseScroll' : 'mousewheel', this.handleWheel)
43 | window.addEventListener('keydown', this.handleKeydown, false)
44 | window.addEventListener('keyup', this.handleKeyup, false)
45 | },
46 | handleResize() {
47 | this.isFocus = false
48 | this.focusCell = null
49 |
50 | this.selectArea = null
51 | this.isSelect = false
52 | this.save()
53 | this.hideInput()
54 | this.initSize()
55 | },
56 | handleColumnSet() {
57 | this.showColumnSet = false
58 | this.initSize()
59 | },
60 | handleClick(evt) {
61 | if (!this.isSelect) {
62 | const x = evt.offsetX
63 | const y = evt.offsetY
64 | if (x > this.maxPoint.x && y > this.maxPoint.y && x < this.width && y < this.height) {
65 | this.fullScreen()
66 | }
67 | if (this.showCheckbox) {
68 | if (x > this.serialWidth && x < this.originPoint.x) {
69 | const checkbox = this.getCheckboxAt(x, y)
70 | if (checkbox) {
71 | if (this.selected.indexOf(checkbox.rowIndex) !== -1) {
72 | this.selected.splice(this.selected.findIndex((item) => { if (item === checkbox.rowIndex) { return true } return false }), 1)
73 | } else {
74 | this.selected.push(checkbox.rowIndex)
75 | }
76 | this.rePainted()
77 | } else if (x > this.serialWidth + 5 && x < this.serialWidth + 5 + 20 && y > 5 && y < 25) {
78 | if (this.selected.length === this.allRows.length) {
79 | this.selected = []
80 | } else {
81 | this.selected = []
82 | for (const row of this.allRows) {
83 | this.selected.push(row.rowIndex)
84 | }
85 | }
86 | this.rePainted()
87 | }
88 | return
89 | }
90 | }
91 |
92 | if (this.columnSet) {
93 | if (!this.showColumnSet) {
94 | if (x > 55 && x < 64 && y > 7 && y < 23) {
95 | this.showColumnSet = true
96 | return
97 | }
98 | } else {
99 | this.handleColumnSet()
100 | }
101 | }
102 |
103 | const button = this.getButtonAt(x, y)
104 | if (button) {
105 | this.rowFocus = button
106 | button.click(this.data[button.rowIndex], button.rowIndex)
107 | this.rePainted()
108 | }
109 | }
110 | },
111 | handleDoubleClick() {
112 | if (this.focusCell) {
113 | const { x, y, width, height, content } = this.focusCell
114 | this.$refs.input.innerHTML = content
115 | this.keepLastIndex(this.$refs.input)
116 | this.showInput(x, y, width, height)
117 | }
118 | },
119 | handleWheel(e) {
120 | if (e.target.tagName === 'CANVAS') {
121 | if (!this.isEditing) {
122 | const { deltaX, deltaY } = e
123 | if (Math.abs(deltaX) > Math.abs(deltaY)) {
124 | const lastScrollX = this.offset.x
125 | let maxWidth = 0
126 | if (this.fillWidth > 0) {
127 | maxWidth = this.maxPoint.x
128 | } else {
129 | maxWidth = this.maxPoint.x + this.fixedWidth
130 | }
131 | if (this.offset.x - deltaX > 0) {
132 | this.offset.x = 0
133 | } else if ((this.bodyWidth - maxWidth) + this.offset.x < deltaX) {
134 | this.offset.x = maxWidth - this.bodyWidth
135 | if (maxWidth - this.bodyWidth < 0) {
136 | this.offset.x = maxWidth - this.bodyWidth
137 | } else {
138 | e.preventDefault()
139 | e.returnValue = false
140 | }
141 | } else {
142 | e.preventDefault()
143 | e.returnValue = false
144 | this.offset.x -= deltaX
145 | }
146 | if (lastScrollX !== this.offset.x) {
147 | requestAnimationFrame(this.rePainted)
148 | this.$emit('scroll')
149 | }
150 | } else {
151 | const lastScrollY = this.offset.y
152 | if (lastScrollY - deltaY > 0) {
153 | this.offset.y = 0
154 | } else if ((this.bodyHeight - this.maxPoint.y) + lastScrollY < deltaY) {
155 | if (this.maxPoint.y - this.bodyHeight < 0) {
156 | this.offset.y = this.maxPoint.y - this.bodyHeight
157 | } else {
158 | e.preventDefault()
159 | e.returnValue = false
160 | }
161 | } else {
162 | e.preventDefault()
163 | e.returnValue = false
164 | this.offset.y -= deltaY
165 | }
166 | if (lastScrollY !== this.offset.y) {
167 | requestAnimationFrame(this.rePainted)
168 | this.$emit('scroll')
169 | }
170 | }
171 | }
172 | }
173 | },
174 | handleMousemove(evt) {
175 | if (this.isDown && this.isFocus && evt.target.tagName === 'CANVAS') {
176 | const eX = evt.offsetX
177 | const eY = evt.offsetY
178 | const { x, y, width, height, rowIndex, cellIndex } = this.focusCell
179 | if (eX >= x && eX <= x + width && eY >= y && eY <= y + height) {
180 | if (this.selectArea) {
181 | this.selectArea = null
182 | this.isSelect = false
183 | this.rePainted()
184 | }
185 | } else {
186 | const cell = this.getCellAt(eX, eY)
187 | if (cell) {
188 | if (cell.x >= x && cell.y >= y) {
189 | this.selectArea = { x, y, width: (cell.x - x) + cell.width, height: (cell.y - y) + cell.height, cellIndex, rowIndex, offset: { ...this.offset } }
190 | } else if (cell.x >= x && cell.y <= y) {
191 | this.selectArea = { x, y: cell.y, width: (cell.x - x) + cell.width, height: (y - cell.y) + height, rowIndex: cell.rowIndex, cellIndex, offset: { ...this.offset } }
192 | } else if (cell.x <= x && cell.y <= y) {
193 | this.selectArea = { x: cell.x, y: cell.y, width: (x - cell.x) + width, height: (y - cell.y) + height, rowIndex: cell.rowIndex, cellIndex: cell.cellIndex, offset: { ...this.offset } }
194 | } else if (cell.x <= x && cell.y >= y) {
195 | this.selectArea = { x: cell.x, y, width: (x - cell.x) + width, height: (cell.y - y) + cell.height, rowIndex, cellIndex: cell.cellIndex, offset: { ...this.offset } }
196 | }
197 | this.selectArea.rowCount = Math.abs(cell.rowIndex - rowIndex) + 1
198 | this.isSelect = true
199 | this.rePainted()
200 | }
201 | }
202 | }
203 | if (this.verticalBar.move) {
204 | const height = this.maxPoint.y - this.verticalBar.size
205 | const moveHeight = this.verticalBar.y + (evt.screenY - this.verticalBar.cursorY)
206 | if (moveHeight > 0 && moveHeight < height) {
207 | this.verticalBar.y += evt.screenY - this.verticalBar.cursorY
208 | } else if (moveHeight <= 0) {
209 | this.verticalBar.y = 0
210 | } else {
211 | this.verticalBar.y = height
212 | }
213 | this.verticalBar.cursorY = evt.screenY
214 | this.offset.y = -this.verticalBar.y / this.verticalBar.k
215 | requestAnimationFrame(this.rePainted)
216 | }
217 | if (this.horizontalBar.move) {
218 | let width = 0
219 | if (this.fillWidth > 0) {
220 | width = this.maxPoint.x - this.horizontalBar.size
221 | } else {
222 | width = (this.maxPoint.x + this.fixedWidth) - this.horizontalBar.size
223 | }
224 | const moveWidth = this.horizontalBar.x + (evt.screenX - this.horizontalBar.cursorX)
225 | if (moveWidth > 0 && moveWidth < width) {
226 | this.horizontalBar.x += evt.screenX - this.horizontalBar.cursorX
227 | } else if (moveWidth <= 0) {
228 | this.horizontalBar.x = 0
229 | } else {
230 | this.horizontalBar.x = width
231 | }
232 | this.horizontalBar.cursorX = evt.screenX
233 | this.offset.x = -this.horizontalBar.x / this.horizontalBar.k
234 | requestAnimationFrame(this.rePainted)
235 | }
236 | },
237 | handleMousedown(evt) {
238 | this.save()
239 | let needRepaint = false
240 | if (evt.target.tagName === 'CANVAS') {
241 | setTimeout(() => {
242 | this.isDown = true
243 | this.hideInput()
244 | this.selectArea = null
245 | this.isSelect = false
246 | const eX = evt.offsetX
247 | const eY = evt.offsetY
248 | if (eX > this.originPoint.x && eY > this.rowHeight && eX < this.maxPoint.x) {
249 | const cell = this.getCellAt(eX, eY)
250 | if (cell && !cell.buttons && !cell.readOnly) {
251 | if (this.shiftDown) {
252 | const { x, y, width, height, rowIndex, cellIndex } = this.focusCell
253 | if (eX >= x && eX <= x + width && eY >= y && eY <= y + height) {
254 | this.selectArea = null
255 | this.isSelect = false
256 | this.rePainted()
257 | } else {
258 | if (cell.x >= x && cell.y >= y) {
259 | this.selectArea = { x, y, width: (cell.x - x) + cell.width, height: (cell.y - y) + cell.height, cellIndex, rowIndex, offset: { ...this.offset } }
260 | } else if (cell.x >= x && cell.y <= y) {
261 | this.selectArea = { x, y: cell.y, width: (cell.x - x) + cell.width, height: (y - cell.y) + height, rowIndex: cell.rowIndex, cellIndex, offset: { ...this.offset } }
262 | } else if (cell.x <= x && cell.y <= y) {
263 | this.selectArea = { x: cell.x, y: cell.y, width: (x - cell.x) + width, height: (y - cell.y) + height, rowIndex: cell.rowIndex, cellIndex: cell.cellIndex, offset: { ...this.offset } }
264 | } else if (cell.x <= x && cell.y >= y) {
265 | this.selectArea = { x: cell.x, y, width: (x - cell.x) + width, height: (cell.y - y) + cell.height, rowIndex, cellIndex: cell.cellIndex, offset: { ...this.offset } }
266 | }
267 | this.selectArea.rowCount = Math.abs(cell.rowIndex - rowIndex) + 1
268 | this.isSelect = true
269 | this.rePainted()
270 | }
271 | } else {
272 | this.focusCell = cell
273 | this.rowFocus = {
274 | cellX: cell.x,
275 | cellY: cell.y,
276 | rowIndex: this.focusCell.rowIndex,
277 | offset: { ...this.offset },
278 | }
279 | this.paintFocusCell(cell)
280 | this.$emit('focus', cell.rowData)
281 | }
282 | } else {
283 | this.isFocus = false
284 | this.focusCell = null
285 | this.rePainted()
286 | }
287 | } else {
288 | this.isFocus = false
289 | this.focusCell = null
290 | this.rePainted()
291 | }
292 | }, 0)
293 | } else if (!evt.target.classList.contains('input-content')) {
294 | if (evt.target.tagName !== 'CANVAS') {
295 | if (this.isEditing) {
296 | this.save()
297 | this.hideInput()
298 | needRepaint = true
299 | }
300 | if (this.isFocus) {
301 | this.isFocus = false
302 | this.focusCell = null
303 | needRepaint = true
304 | }
305 | if (this.isSelect) {
306 | this.selectArea = null
307 | this.isSelect = false
308 | needRepaint = true
309 | }
310 | if (this.showColumnSet) {
311 | setTimeout(() => {
312 | if (evt.path.indexOf(this.$refs.columnSet) === -1) {
313 | this.showColumnSet = false
314 | this.initSize()
315 | }
316 | }, 0)
317 | }
318 | if (needRepaint) {
319 | this.rePainted()
320 | }
321 | }
322 | }
323 | },
324 | handleMouseup() {
325 | this.isDown = false
326 | this.horizontalBar.move = false
327 | this.verticalBar.move = false
328 | },
329 | handleKeyup(e) {
330 | if (e.keyCode === 16) {
331 | this.shiftDown = false
332 | }
333 | },
334 | handleKeydown(e) {
335 | if (this.isFocus) {
336 | if (!this.isEditing) {
337 | if (e.keyCode === 38) {
338 | e.preventDefault()
339 | this.moveFocus('up')
340 | } else if (e.keyCode === 40) {
341 | e.preventDefault()
342 | this.moveFocus('down')
343 | } else if (e.keyCode === 37) {
344 | e.preventDefault()
345 | this.moveFocus('left')
346 | } else if (e.keyCode === 39) {
347 | e.preventDefault()
348 | this.moveFocus('right')
349 | } else if (e.keyCode === 16) {
350 | this.shiftDown = true
351 | } else if (e.keyCode === 8 || e.keyCode === 46) {
352 | if (this.isSelect) {
353 | const selectCells = this.getCellsBySelect(this.selectArea)
354 | const deleteData = []
355 | for (const row of selectCells) {
356 | const temp = {
357 | rowData: row[0].rowData,
358 | index: row[0].rowIndex,
359 | items: [],
360 | }
361 | for (const item of row) {
362 | if (item.readOnly) {
363 | temp.items.push({
364 | key: '',
365 | value: '',
366 | })
367 | } else {
368 | temp.items.push({
369 | key: item.key,
370 | value: '',
371 | })
372 | }
373 | }
374 | deleteData.push(temp)
375 | }
376 | this.$emit('update', deleteData)
377 | } else {
378 | this.$emit('updateItem', {
379 | index: this.focusCell.rowIndex,
380 | key: this.focusCell.key,
381 | value: '',
382 | })
383 | }
384 | } else if (/macintosh|mac os x/i.test(navigator.userAgent)) {
385 | if (e.keyCode === 90 && e.metaKey) {
386 | e.preventDefault()
387 | this.$emit('history_back')
388 | } else if (e.keyCode === 89 && e.metaKey) {
389 | e.preventDefault()
390 | this.$emit('history_forward')
391 | } else if (e.keyCode === 67 && e.metaKey) {
392 | if (this.isSelect) {
393 | e.preventDefault()
394 | this.selectText(this.$refs.inputSelect)
395 | document.execCommand('Copy')
396 | }
397 | }
398 | } else if (e.keyCode === 90 && e.ctrlKey) {
399 | e.preventDefault()
400 | this.$emit('history_back')
401 | } else if (e.keyCode === 89 && e.ctrlKey) {
402 | e.preventDefault()
403 | this.$emit('history_forward')
404 | } else if (e.keyCode === 67 && e.ctrlKey) {
405 | if (this.isSelect) {
406 | e.preventDefault()
407 | this.selectText(this.$refs.inputSelect)
408 | document.execCommand('Copy')
409 | }
410 | }
411 | }
412 | if (e.keyCode === 13) {
413 | this.save()
414 | this.moveFocus('down')
415 | } else if (e.keyCode === 27) {
416 | this.hideInput()
417 | this.$refs.input.innerHTML = ''
418 | } else if (e.keyCode === 9) {
419 | this.save()
420 | this.moveFocus('right')
421 | }
422 | }
423 | },
424 | handleInputKeyup() {
425 |
426 | },
427 | moveFocus(type) {
428 | if (!this.focusCell) {
429 | return
430 | }
431 | if (this.isSelect) {
432 | this.selectArea = null
433 | this.isSelect = false
434 | }
435 | const row = this.focusCell.rowIndex
436 | const cell = this.focusCell.cellIndex
437 | this.hideInput()
438 | if (type === 'up') {
439 | if (this.getDisplayCellIndexByRowIndex(row) !== 0) {
440 | this.focusCell = Object.assign({}, this.getDisplayCellByRowIndex(this.displayCells, row - 1, cell), { offset: { ...this.offset } })
441 | if (this.focusCell.y < this.originPoint.y) {
442 | this.offset.y += this.originPoint.y - this.focusCell.y
443 | }
444 | this.paintFocusCell(this.focusCell)
445 | } else {
446 | const rowIndex = this.displayRows[0].rowIndex
447 | if (rowIndex > 0) {
448 | this.offset.y += this.allRows[this.displayRows[0].rowIndex - 1].height
449 | const { displayCells } = this.rePainted()
450 | this.focusCell = Object.assign({}, this.getDisplayCellByRowIndex(displayCells, displayCells[0][0].rowIndex, cell), { offset: { ...this.offset } })
451 | this.paintFocusCell(this.focusCell)
452 | }
453 | }
454 | } else if (type === 'down') {
455 | if (row !== this.displayCells[this.displayCells.length - 1][0].rowIndex) {
456 | this.focusCell = Object.assign({}, this.getDisplayCellByRowIndex(this.displayCells, row + 1, cell), { offset: { ...this.offset } })
457 | if (this.focusCell.y + this.focusCell.height > this.maxPoint.y) {
458 | this.offset.y -= (this.focusCell.y + this.focusCell.height) - this.maxPoint.y
459 | }
460 | this.paintFocusCell(this.focusCell)
461 | } else {
462 | const rowIndex = this.displayRows[this.displayRows.length - 1].rowIndex
463 | if (rowIndex < this.allRows.length - 1) {
464 | this.offset.y -= this.allRows[this.displayRows[this.displayRows.length - 1].rowIndex + 1].height
465 | const { displayCells } = this.rePainted()
466 | this.focusCell = Object.assign({}, this.getDisplayCellByRowIndex(displayCells, displayCells[displayCells.length - 1][0].rowIndex, cell), { offset: { ...this.offset } })
467 | this.paintFocusCell(this.focusCell)
468 | }
469 | }
470 | } else if (type === 'left') {
471 | if (cell !== this.getFirstDisplayCellIndex(this.displayCells)) {
472 | this.focusCell = Object.assign({}, this.getDisplayCellByRowIndex(this.displayCells, row, cell - 1), { offset: { ...this.offset } })
473 | if (this.focusCell.x < this.originPoint.x) {
474 | this.offset.x += this.originPoint.x - this.focusCell.x
475 | }
476 | this.paintFocusCell(this.focusCell)
477 | } else {
478 | const cellIndex = this.displayColumns[0].cellIndex
479 | if (cellIndex > 0) {
480 | this.offset.x += this.allColumns[cellIndex - 1].width
481 | const { displayCells } = this.rePainted()
482 | this.focusCell = Object.assign({}, this.getDisplayCellByRowIndex(displayCells, row, this.getFirstDisplayCellIndex(displayCells)), { offset: { ...this.offset } })
483 | this.paintFocusCell(this.focusCell)
484 | }
485 | }
486 | } else if (type === 'right') {
487 | if (cell !== this.getLastDisplayCellIndex(this.displayCells)) {
488 | this.focusCell = Object.assign({}, this.getDisplayCellByRowIndex(this.displayCells, row, cell + 1), { offset: { ...this.offset } })
489 | if (this.focusCell.x + this.focusCell.width > this.maxPoint.x - this.fixedWidth) {
490 | this.offset.x -= (this.focusCell.x + this.focusCell.width) - (this.maxPoint.x - this.fixedWidth)
491 | }
492 | this.paintFocusCell(this.focusCell)
493 | } else {
494 | const cellIndex = this.displayColumns[this.displayColumns.length - 1 - this.displayFixedCells.length].cellIndex
495 | if (cellIndex < this.allColumns.length - 1) {
496 | this.offset.x -= this.allColumns[cellIndex + 1].width
497 | const { displayCells } = this.rePainted()
498 | this.focusCell = Object.assign({}, this.getDisplayCellByRowIndex(displayCells, row, this.getLastDisplayCellIndex(displayCells)), { offset: { ...this.offset } })
499 | this.paintFocusCell(this.focusCell)
500 | }
501 | }
502 | }
503 | this.$emit('scroll')
504 | },
505 | getDisplayCellByRowIndex(displayCells, rowIndex, cellIndex) {
506 | for (const item of displayCells) {
507 | if (item[0].rowIndex === rowIndex) {
508 | for (const cell of item) {
509 | if (cell.cellIndex === cellIndex) {
510 | return cell
511 | }
512 | }
513 | }
514 | }
515 | return null
516 | },
517 | getDisplayCellIndexByRowIndex(row) {
518 | let index = 0
519 | for (const item of this.displayCells) {
520 | if (item[0].rowIndex === row) {
521 | return index
522 | }
523 | index += 1
524 | }
525 | return null
526 | },
527 | getLastDisplayCellIndex(displayCells) {
528 | return displayCells[0][displayCells[0].length - 1].cellIndex
529 | },
530 | getFirstDisplayCellIndex(displayCells) {
531 | return displayCells[0][0].cellIndex
532 | },
533 | keepLastIndex(obj) {
534 | if (window.getSelection) { // ie11 10 9 ff safari
535 | obj.focus() // 解决ff不获取焦点无法定位问题
536 | const range = window.getSelection()// 创建range
537 | range.selectAllChildren(obj)// range 选择obj下所有子内容
538 | range.collapseToEnd()// 光标移至最后
539 | } else if (document.selection) { // ie10 9 8 7 6 5
540 | const range = document.selection.createRange()// 创建选择对象
541 | // var range = document.body.createTextRange();
542 | range.moveToElementText(obj)// range定位到obj
543 | range.collapse(false)// 光标移至最后
544 | range.select()
545 | }
546 | },
547 | selectText(obj) {
548 | if (document.selection) {
549 | const range = document.body.createTextRange()
550 | range.moveToElementText(obj)
551 | range.select()
552 | } else if (window.getSelection) {
553 | const range = document.createRange()
554 | range.selectNodeContents(obj)
555 | window.getSelection().removeAllRanges()
556 | window.getSelection().addRange(range)
557 | }
558 | },
559 | },
560 | }
561 |
--------------------------------------------------------------------------------
/src/grid/calculate.js:
--------------------------------------------------------------------------------
1 | export default {
2 | data() {
3 | const rowHeight = 30
4 | const serialWidth = 70
5 | const checkboxWidth = 30
6 | const scrollerWidth = 20
7 | const height = this.leftHeight ? window.innerHeight - this.leftHeight : 500
8 | let originPointX = serialWidth
9 | if (this.showCheckbox) {
10 | originPointX += checkboxWidth
11 | }
12 | return {
13 | width: 0,
14 | height,
15 | rowHeight,
16 | scrollerWidth,
17 | fixedWidth: 0,
18 | bodyWidth: 0,
19 | bodyHeight: 0,
20 | serialWidth,
21 | checkboxWidth: 30,
22 | fillWidth: 0,
23 |
24 | allCells: [],
25 | displayCells: [],
26 | allRows: [],
27 | displayRows: [],
28 | allColumns: [],
29 | displayColumns: [],
30 | allFixedCells: [],
31 | displayFixedCells: [],
32 | fixedColumns: [],
33 | renderButtons: [],
34 | checkboxs: [],
35 | selected: [],
36 |
37 | offset: {
38 | x: 0,
39 | y: 0,
40 | },
41 | originPoint: {
42 | x: originPointX,
43 | y: rowHeight,
44 | },
45 | maxPoint: {
46 | x: 0,
47 | y: height - scrollerWidth,
48 | },
49 | }
50 | },
51 | watch: {
52 | showCheckbox() {
53 | this.initSize()
54 | },
55 | leftHeight() {
56 | this.initSize()
57 | },
58 | },
59 | mounted() {
60 | this.width = this.$refs.grid.offsetWidth - 2
61 |
62 | this.height = this.leftHeight ? window.innerHeight - this.leftHeight : 500
63 | this.maxPoint.y = this.height - this.scrollerWidth
64 |
65 | this.bodyWidth = this.originPoint.x
66 | for (const column of this.columns) {
67 | this.bodyWidth += column.width ? column.width : 100
68 | }
69 | if (this.bodyWidth < this.width - this.scrollerWidth) {
70 | this.fillWidth = (this.width - this.bodyWidth - this.scrollerWidth) / this.columns.length
71 | this.bodyWidth = this.width - this.scrollerWidth
72 | }
73 | },
74 | methods: {
75 | fullScreen() {
76 | // TODO fullscrean
77 |
78 | // this.$refs.canvas.style.position = 'fixed'
79 | // this.$refs.canvas.style.top = 0
80 | // this.$refs.canvas.style.left = 0
81 | // this.$refs.canvas.style.right = 0
82 | // this.$refs.canvas.style.bottom = 0
83 | // this.$refs.canvas.style.zIndex = 1000
84 |
85 | // this.width = window.innerWidth
86 | // this.height = window.innerHeight
87 |
88 | // if (this.showCheckbox) {
89 | // this.selected = [...this.initSelected]
90 | // this.originPoint.x = this.serialWidth + this.checkboxWidth
91 | // this.bodyWidth += this.checkboxWidth
92 | // } else {
93 | // this.originPoint.x = this.serialWidth
94 | // this.bodyWidth -= this.checkboxWidth
95 | // }
96 | // this.bodyWidth = this.originPoint.x
97 | // let columnCount = 0
98 | // for (const column of this.allColumns) {
99 | // if (column.checked) {
100 | // this.bodyWidth += column.width ? column.width : 100
101 | // columnCount += 1
102 | // }
103 | // }
104 | // this.fillWidth = 0
105 | // if (this.bodyWidth < this.width - this.scrollerWidth) {
106 | // this.fillWidth = (this.width - this.bodyWidth - this.scrollerWidth) / columnCount
107 | // this.bodyWidth = this.width - this.scrollerWidth
108 | // }
109 |
110 | // this.setBodyHeight(this.allRows, this.originPoint)
111 | // this.setFixedWidth(this.allColumns, this.fillWidth)
112 | // this.setMaxpoint(this.width, this.height, this.fixedWidth, this.scrollerWidth, this.fillWidth)
113 | // this.resetScrollBar(this.maxPoint, this.bodyWidth, this.bodyHeight, this.fixedWidth)
114 | // requestAnimationFrame(this.rePainted)
115 | },
116 | initSize() {
117 | if (this.$refs.grid) {
118 | this.width = this.$refs.grid.offsetWidth - 2
119 | this.height = this.leftHeight ? window.innerHeight - this.leftHeight : 500
120 |
121 | if (this.showCheckbox) {
122 | if (this.initSelected) {
123 | this.selected = [...this.initSelected]
124 | }
125 | this.originPoint.x = this.serialWidth + this.checkboxWidth
126 | this.bodyWidth += this.checkboxWidth
127 | } else {
128 | this.originPoint.x = this.serialWidth
129 | this.bodyWidth -= this.checkboxWidth
130 | }
131 | this.bodyWidth = this.originPoint.x
132 | let columnCount = 0
133 | for (const column of this.allColumns) {
134 | if (column.checked) {
135 | this.bodyWidth += column.width ? column.width : 100
136 | columnCount += 1
137 | }
138 | }
139 | this.fillWidth = 0
140 | if (this.bodyWidth < this.width - this.scrollerWidth) {
141 | this.fillWidth = (this.width - this.bodyWidth - this.scrollerWidth) / columnCount
142 | this.bodyWidth = this.width - this.scrollerWidth
143 | }
144 | this.setBodyHeight(this.allRows, this.originPoint)
145 | this.setFixedWidth(this.allColumns, this.fillWidth)
146 | this.setMaxpoint(this.width, this.height, this.fixedWidth, this.scrollerWidth, this.fillWidth)
147 | this.resetScrollBar(this.maxPoint, this.bodyWidth, this.bodyHeight, this.fixedWidth)
148 | requestAnimationFrame(this.rePainted)
149 | }
150 | },
151 | setFixedWidth(allColumns, fillWidth) {
152 | this.fixedWidth = 0
153 | for (const column of allColumns) {
154 | if (column.checked && column.fixed) {
155 | this.fixedWidth += column.width ? column.width : 100
156 | this.fixedWidth += fillWidth
157 | }
158 | }
159 | },
160 | setBodyHeight(allRows, { y }) {
161 | this.bodyHeight = y
162 | for (const row of allRows) {
163 | this.bodyHeight += row.height
164 | }
165 | },
166 | setMaxpoint(width, height, fixedWidth, scrollerWidth, fillWidth) {
167 | if (fillWidth > 0) {
168 | this.maxPoint.x = width - scrollerWidth
169 | } else {
170 | this.maxPoint.x = width - scrollerWidth - fixedWidth
171 | }
172 | this.maxPoint.y = height - scrollerWidth
173 | },
174 | getAllCells(value, columns) {
175 | this.allCells = []
176 | this.allRows = []
177 | this.allColumns = []
178 | this.allFixedCells = []
179 | this.fixedColumns = []
180 | this.fixedWidth = 0
181 | const { rowHeight, ctx, getTextLine, allRows, allCells, allColumns, fixedColumns, allFixedCells } = this
182 | let rowIndex = 0
183 | for (const item of value) {
184 | let maxHeight = rowHeight
185 | let cellIndex = 0
186 | const cellTemp = []
187 | for (const column of columns) {
188 | if (rowIndex === 0) {
189 | if (column.fixed) {
190 | this.fixedWidth += column.width
191 | fixedColumns.push({
192 | cellIndex,
193 | ...column,
194 | })
195 | allColumns.push({
196 | height: rowHeight,
197 | cellIndex,
198 | ...column,
199 | checked: true,
200 | })
201 | } else {
202 | allColumns.push({
203 | height: rowHeight,
204 | cellIndex,
205 | ...column,
206 | checked: true,
207 | })
208 | }
209 | }
210 | let text = ''
211 | let buttons
212 | let textLine
213 | if (column.renderText) {
214 | text = column.renderText(item)
215 | } else if (column.renderButton) {
216 | buttons = column.renderButton(this.data[rowIndex], rowIndex)
217 | } else {
218 | text = item[column.key]
219 | }
220 | if (text || text === 0) {
221 | textLine = getTextLine(ctx, text, column.width ? column.width : 100)
222 | let textLineCount = 0
223 | if (textLine) {
224 | textLineCount = textLine.length
225 | }
226 | if (textLineCount > 1) {
227 | if (maxHeight < rowHeight + ((textLineCount - 1) * 18)) {
228 | maxHeight = rowHeight + ((textLineCount - 1) * 18)
229 | }
230 | }
231 | }
232 |
233 | if (column.fixed) {
234 | cellTemp.push({
235 | width: column.width ? column.width : 100,
236 | content: item[column.key],
237 | key: column.key,
238 | rowIndex,
239 | cellIndex,
240 | paintText: textLine,
241 | fixed: column.fixed === true,
242 | readOnly: column.readOnly === true,
243 | buttons,
244 | renderText: column.renderText,
245 | renderButton: column.renderButton,
246 | rowData: item,
247 | type: column.type,
248 | })
249 | } else {
250 | cellTemp.push({
251 | width: column.width ? column.width : 100,
252 | content: item[column.key],
253 | key: column.key,
254 | rowIndex,
255 | cellIndex,
256 | paintText: textLine,
257 | fixed: column.fixed === true,
258 | readOnly: column.readOnly === true,
259 | buttons,
260 | renderText: column.renderText,
261 | renderButton: column.renderButton,
262 | rowData: item,
263 | type: column.type,
264 | })
265 | }
266 | cellIndex += 1
267 | }
268 | allCells.push(cellTemp)
269 |
270 | let showDot = false
271 | if (this.showDot) {
272 | if (item[this.showDot.key] === this.showDot.value) {
273 | showDot = true
274 | }
275 | }
276 | allRows.push({
277 | height: maxHeight,
278 | rowIndex,
279 | showDot,
280 | })
281 | rowIndex += 1
282 | }
283 | for (const item of fixedColumns) {
284 | const temp = []
285 | let index = 0
286 | for (const row of allCells) {
287 | const cell = row[item.cellIndex]
288 | temp.push({
289 | ...cell,
290 | height: allRows[index].height,
291 | })
292 | index += 1
293 | }
294 | allFixedCells.push(temp)
295 | }
296 | },
297 | setAllCells(startIndex) {
298 | const { rowHeight, ctx, getTextLine, allRows, allCells, columns } = this
299 | let rowIndex = startIndex
300 | for (let i = startIndex; i < this.data.length; i += 1) {
301 | const item = this.data[i]
302 | let maxHeight = rowHeight
303 | let cellIndex = 0
304 | const cellTemp = []
305 | for (const column of columns) {
306 | let text = ''
307 | let buttons
308 | let textLine
309 | if (column.renderText) {
310 | text = column.renderText(item)
311 | } else if (column.renderButton) {
312 | buttons = column.renderButton(this.data[rowIndex], rowIndex)
313 | } else {
314 | text = item[column.key]
315 | }
316 | if (text || text === 0) {
317 | textLine = getTextLine(ctx, text, column.width ? column.width : 100)
318 | let textLineCount = 0
319 | if (textLine) {
320 | textLineCount = textLine.length
321 | }
322 | if (textLineCount > 1) {
323 | if (maxHeight < rowHeight + ((textLineCount - 1) * 18)) {
324 | maxHeight = rowHeight + ((textLineCount - 1) * 18)
325 | }
326 | }
327 | }
328 | cellTemp.push({
329 | width: column.width ? column.width : 100,
330 | content: item[column.key],
331 | key: column.key,
332 | rowIndex,
333 | cellIndex,
334 | paintText: textLine,
335 | fixed: column.fixed === true,
336 | readOnly: column.readOnly === true,
337 | buttons,
338 | renderText: column.renderText,
339 | renderButton: column.renderButton,
340 | rowData: item,
341 | type: column.type,
342 | })
343 | cellIndex += 1
344 | }
345 | allCells.push(cellTemp)
346 |
347 | let showDot = false
348 | if (this.showDot) {
349 | if (item[this.showDot.key] === this.showDot.value) {
350 | showDot = true
351 | }
352 | }
353 | allRows.push({
354 | height: maxHeight,
355 | rowIndex,
356 | showDot,
357 | })
358 | rowIndex += 1
359 | }
360 | this.setBodyHeight(this.allRows, this.originPoint)
361 | this.resetScrollBar(this.maxPoint, this.bodyWidth, this.bodyHeight, this.fixedWidth)
362 | },
363 | initRowHeight() {
364 |
365 | },
366 | setCellItem(rowIndex, cellIndex, text) {
367 | const { ctx, allRows, allCells, getTextLine, rowHeight } = this
368 | const row = allRows[rowIndex]
369 | const cell = allCells[rowIndex][cellIndex]
370 | let maxHeight = 0
371 | const textLine = getTextLine(ctx, text, cell.width)
372 | let textLineCount = 0
373 | if (textLine) {
374 | textLineCount = textLine.length
375 | }
376 | if (textLineCount > 1) {
377 | if (maxHeight < rowHeight + ((textLineCount - 1) * 18)) {
378 | maxHeight = rowHeight + ((textLineCount - 1) * 18)
379 | }
380 | }
381 | if (maxHeight > row.height) {
382 | row.height = maxHeight
383 | }
384 | cell.content = text
385 | cell.paintText = textLine
386 | },
387 | setCellItemByKey(rowIndex, key, text) {
388 | const { ctx, allRows, allCells, getTextLine, rowHeight } = this
389 | const row = allRows[rowIndex]
390 | const cells = allCells[rowIndex]
391 | let index = 0
392 | let cell = null
393 | for (const item of cells) {
394 | if (item.key === key) {
395 | cell = allCells[rowIndex][index]
396 | break
397 | }
398 | index += 1
399 | }
400 | if (cell) {
401 | let maxHeight = 0
402 | const textLine = getTextLine(ctx, text, cell.width)
403 | let textLineCount = 0
404 | if (textLine) {
405 | textLineCount = textLine.length
406 | }
407 | if (textLineCount > 1) {
408 | if (maxHeight < rowHeight + ((textLineCount - 1) * 18)) {
409 | maxHeight = rowHeight + ((textLineCount - 1) * 18)
410 | }
411 | }
412 | if (maxHeight > row.height) {
413 | row.height = maxHeight
414 | this.initSize()
415 | }
416 | cell.content = text
417 | cell.paintText = textLine
418 | }
419 | },
420 | setCellItemAll(rowIndex, data) {
421 | let index = 0
422 | for (const item of data) {
423 | this.setCellItem(rowIndex, index, item)
424 | index += 1
425 | }
426 | },
427 | getDisplayCells(displayRows, displayColumns) {
428 | const temp = []
429 | const { allCells, fillWidth, setCellRenderText } = this
430 | for (const row of displayRows) {
431 | const cellTemp = []
432 | for (const column of displayColumns) {
433 | let cell = allCells[row.rowIndex][column.cellIndex]
434 | if (cell.renderText) {
435 | cell = setCellRenderText(cell)
436 | }
437 | if (cell.renderButton) {
438 | cell.buttons = column.renderButton(this.data[cell.rowIndex], cell.rowIndex)
439 | }
440 | const cellClone = Object.assign({}, cell, { x: column.x, y: row.y, width: cell.width + fillWidth, height: row.height }) //eslint-disable-line
441 | cellTemp.push(cellClone)
442 | }
443 | temp.push(cellTemp)
444 | }
445 | setTimeout(() => { this.displayCells = [...temp] }, 0)
446 | return temp
447 | },
448 | setCellRenderText(cell) {
449 | const text = cell.renderText(cell.rowData)
450 | const row = this.allRows[cell.rowIndex]
451 | if (text) {
452 | let maxHeight = 0
453 | const textLine = this.getTextLine(this.ctx, text, cell.width)
454 | let textLineCount = 0
455 | if (textLine) {
456 | textLineCount = textLine.length
457 | }
458 | if (textLineCount > 1) {
459 | if (maxHeight < this.rowHeight + ((textLineCount - 1) * 18)) {
460 | maxHeight = this.rowHeight + ((textLineCount - 1) * 18)
461 | }
462 | }
463 | if (maxHeight > row.height) {
464 | row.height = maxHeight
465 | }
466 | cell.content = text
467 | cell.paintText = textLine
468 | } else {
469 | cell.content = ''
470 | cell.paintText = []
471 | }
472 | return cell
473 | },
474 | getDisplayFixedCells(displayRows) {
475 | const temp = []
476 | const { allFixedCells, fillWidth } = this
477 | for (const fixedCell of allFixedCells) {
478 | const fixedCellTemp = []
479 | for (const row of displayRows) {
480 | const fixed = fixedCell[row.rowIndex]
481 | if (fixed.renderButton) {
482 | fixed.buttons = fixed.renderButton(this.data[fixed.rowIndex], fixed.rowIndex)
483 | }
484 | const fixedCellClone = Object.assign({}, fixed, { y: row.y, width: fixed.width + fillWidth, height: row.height })
485 | fixedCellTemp.push(fixedCellClone)
486 | }
487 | temp.push(fixedCellTemp)
488 | }
489 | setTimeout(() => { this.displayallFixedCells = [...temp] }, 0)
490 | return temp
491 | },
492 | getDisplayRows() {
493 | const { offset: { y }, originPoint, maxPoint, allRows } = this
494 | const temp = []
495 | let startY = originPoint.y + y
496 | for (const row of allRows) {
497 | if (startY + row.height > originPoint.y
498 | && startY < maxPoint.y) {
499 | const rowClone = Object.assign({}, row, { y: startY })
500 | temp.push(rowClone)
501 | } else if (startY >= maxPoint.y) {
502 | break
503 | }
504 | startY += row.height
505 | }
506 | setTimeout(() => { this.displayRows = [...temp] }, 0)
507 | return temp
508 | },
509 | getDisplayColumns() {
510 | const { offset: { x }, originPoint, maxPoint, allColumns, fillWidth } = this
511 | const temp = []
512 | let startX = originPoint.x + x
513 |
514 | for (const column of allColumns) {
515 | if (column.checked) {
516 | const width = column.width + fillWidth
517 | if (width + startX > originPoint.x && startX < maxPoint.x) {
518 | const columnClone = Object.assign({}, column, { x: startX, width })
519 | temp.push(columnClone)
520 | }
521 | startX += width
522 | }
523 | }
524 | setTimeout(() => { this.displayColumns = [...temp] }, 0)
525 | return temp
526 | },
527 | getTextLine(ctx, text, width) {
528 | if (!text && text !== 0) {
529 | return null
530 | }
531 | const chr = `${text}`.split('')
532 | let temp = ''
533 | const row = []
534 | for (let a = 0; a < chr.length; a += 1) {
535 | if (ctx.measureText(temp).width >= width - 20) {
536 | row.push(temp)
537 | temp = ''
538 | }
539 | temp += chr[a]
540 | }
541 | row.push(temp)
542 | return row
543 | },
544 | getCellAt(x, y) {
545 | for (const rows of this.displayCells) {
546 | for (const cell of rows) {
547 | if (x >= cell.x && y >= cell.y && x <= cell.x + cell.width && y <= cell.y + cell.height) {
548 | return Object.assign({}, cell, { offset: { ...this.offset } })
549 | }
550 | }
551 | }
552 | return null
553 | },
554 | getCheckboxAt(x, y) {
555 | for (const check of this.checkboxs) {
556 | if (x >= check.x && y >= check.y && x <= check.x + check.width && y <= check.y + check.height) {
557 | return Object.assign({}, check)
558 | }
559 | }
560 | return null
561 | },
562 | getButtonAt(x, y) {
563 | for (const button of this.renderButtons) {
564 | if (x >= button.x && y >= button.y && x <= button.x + button.width && y <= button.y + button.height) {
565 | return Object.assign({}, button)
566 | }
567 | }
568 | return null
569 | },
570 | getCellsBySelect(area) {
571 | const cells = []
572 | for (let i = area.rowIndex; i < area.rowIndex + area.rowCount; i += 1) {
573 | const row = this.allCells[i]
574 | const temp = []
575 | let startX = 0
576 | let maxWidth = Infinity
577 | for (let j = 0; j < row.length; j += 1) {
578 | if (area.cellIndex === j) {
579 | maxWidth = startX + area.width
580 | }
581 | if (startX < maxWidth && j >= area.cellIndex) {
582 | temp.push(row[j])
583 | } else if (startX > maxWidth) {
584 | break
585 | }
586 | startX += row[j].width + this.fillWidth
587 | }
588 | cells.push(temp)
589 | }
590 | return cells
591 | },
592 | getCellByRowAndKey(rowIndex, key) {
593 | const cells = this.allCells[rowIndex]
594 | for (const cell of cells) {
595 | if (cell.key === key) {
596 | return cell
597 | }
598 | }
599 | return null
600 | },
601 | focusCellByOriginCell(cell) {
602 | for (const row of this.displayCells) {
603 | for (const item of row) {
604 | if (item.rowIndex === cell.rowIndex && item.key === cell.key) {
605 | const focusCell = Object.assign({}, item, { offset: { ...this.offset } })
606 | this.focusCell = focusCell
607 | this.rowFocus = {
608 | cellX: focusCell.x,
609 | cellY: focusCell.y,
610 | rowIndex: this.focusCell.rowIndex,
611 | offset: { ...this.offset },
612 | }
613 | this.paintFocusCell(focusCell)
614 | return focusCell
615 | }
616 | }
617 | }
618 | return null
619 | },
620 | freshFocusCell(rowIndex, cellIndex, displayRows, displayColumns) {
621 | const firstRowIndex = displayRows[0].rowIndex
622 | const lastRowIndex = displayRows[displayRows.length - 1].rowIndex
623 | if (rowIndex >= firstRowIndex && rowIndex <= lastRowIndex) {
624 | this.focusCell.height = displayRows[rowIndex - firstRowIndex].height
625 | }
626 | for (const item of displayColumns) {
627 | if (item.cellIndex === cellIndex) {
628 | this.focusCell.width = item.width
629 | }
630 | }
631 | },
632 | initDisplayItems() {
633 | const displayColumns = this.getDisplayColumns()
634 | const displayRows = this.getDisplayRows()
635 | const displayCells = this.getDisplayCells(displayRows, displayColumns)
636 | const displayFixedCells = this.getDisplayFixedCells(displayRows)
637 | if (this.focusCell) {
638 | this.freshFocusCell(this.focusCell.rowIndex, this.focusCell.cellIndex, displayRows, displayColumns)
639 | const lastOffset = this.focusCell.offset
640 | if (lastOffset.x !== this.offset.x || lastOffset.y !== this.offset.y) {
641 | this.focusCell.x -= lastOffset.x - this.offset.x
642 | this.focusCell.y -= lastOffset.y - this.offset.y
643 | this.focusCell.offset = { ...this.offset }
644 | }
645 | }
646 | if (this.selectArea) {
647 | const lastOffset = this.selectArea.offset
648 | this.selectArea.x -= lastOffset.x - this.offset.x
649 | this.selectArea.y -= lastOffset.y - this.offset.y
650 | this.selectArea.offset = { ...this.offset }
651 | let height = 0
652 | for (let i = this.selectArea.rowIndex; i < (this.selectArea.rowIndex + this.selectArea.rowCount); i += 1) {
653 | height += this.allRows[i].height
654 | }
655 | this.selectArea.height = height
656 | }
657 | if (this.rowFocus) {
658 | const lastOffset = this.rowFocus.offset
659 | if (lastOffset.x !== this.offset.x || lastOffset.y !== this.offset.y) {
660 | this.rowFocus.y -= lastOffset.y - this.offset.y
661 | this.rowFocus.cellY -= lastOffset.y - this.offset.y
662 | if (!this.rowFocus.fixed) {
663 | this.rowFocus.x -= lastOffset.x - this.offset.x
664 | this.rowFocus.cellX -= lastOffset.x - this.offset.x
665 | }
666 | this.rowFocus.offset = { ...this.offset }
667 | }
668 | }
669 | return { displayColumns, displayRows, displayCells, displayFixedCells }
670 | },
671 | },
672 | }
673 |
--------------------------------------------------------------------------------