├── .gitignore ├── README.md ├── dist └── daodaoExcel.js ├── package-lock.json ├── package.json ├── src ├── cell.js ├── config.js ├── contextMenu.js ├── copyedCell.js ├── edit.js ├── event.js ├── icon.js ├── main.js ├── scroll.js ├── selectCell.js ├── tableHeaderCell.js ├── tableIndexCell.js ├── toolBar.js ├── uploadFile.js └── utils.js ├── test └── index.html └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .history 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # daodaoExcel 2 | 基于canvas的和js开发的类excel表格,支持用户自定义添加工具栏以及右键菜单 3 | 4 | 项目演示: 5 | [https://ostask.github.io/daodaoExcel/](https://ostask.github.io/daodaoExcel/) 6 | 7 | ## 基本使用 8 | 9 | ### 初始化 10 | 引入daodaoExcel dist目录中的js文件 11 | 在使用 ZRender 前需要初始化实例,具体方式是传入一个对象,对象的id属性为dom的id 12 | 示例: 13 | ```html 14 |
15 | 16 | 19 | ``` 20 | 21 | ### 实例属性(实例使用 excel 命名) 22 | #### excel.cells 23 | 该excel中所有的单元格 24 | 25 | ##### 1.excel.cells[x][y].data 26 | 该单元格的数据以及格式 27 | 28 | | 属性 | 默认值 | 类型 | 含义 | 29 | | ---------- | ------ |------ |---------------------------------- | 30 | | name |(根据位置改变)| String |单元格的名字| 31 | | border | false | Boolean |是否有边框,true:有边框。false:无边框| 32 | | cellHeight | 30 | Number |单元格高度 | 33 | | cellWidth | 100 | Number |单元格宽度 | 34 | | fill | #ffffff| String |单元格颜色 | 35 | | fontFamily | 微软雅黑| String |单元格字体 | 36 | | fontSize | 14 | Number |字体大小 | 37 | | fontStyle | normal | String |字体样式,同css的fontStyle | 38 | | fontWeight | normal |String |字体粗细, 同css的fontWeight | 39 | | merge | false |Boolean |该单元格是否被合并,true:被合并, false:没有合并,注:如果merge为true,且mergeConfig为null,span为0,row为0的时候,该单元格不会显示出来| 40 | | row | 1 | Number |单元格占几行,注:单独设置row不会起作用,还要设置cellWidth,以及merge,mergeConfig,不建议自己改这个值 | 41 | | span | 1 | Number |单元格占几列,注:单独设置span不会起作用,还要设置cellHeight,以及merge,mergeConfig,不建议自己改这个值 | 42 | | text | "" | String |单元格的文字 | 43 | | textAlign |center | String |单元格的文字对齐方式(center,left,right)| 44 | | textFill |#000000 | String |单元格文字的颜色 | 45 | | x |(根据位置改变)| Number |单元格所在的横向的坐标 ,坐标从零开始 | 46 | | xPlace |(根据位置改变)| Number |单元格左上角的像素坐标 | 47 | | y |(根据位置改变)| Number |单元格所在的纵向的坐标 ,坐标从零开始 | 48 | | yPlace |(根据位置改变)| Number |单元格左上角的像素坐标 | 49 | | ltIcon |"none" | String |单元格左上角的图标 ,注:有五个默认的图标 "upArrow","downArrow","leftArrow","rightArrow","filter",暂时不支持别的图标,有需求联系作者 | 50 | | lbIcon |"none" | String |单元格左下角的图标 ,注:有五个默认的图标 "upArrow","downArrow","leftArrow","rightArrow","filter",暂时不支持别的图标,有需求联系作者 | 51 | | rtIcon |"none" | String |单元格右上角的图标 ,注:有五个默认的图标 "upArrow","downArrow","leftArrow","rightArrow","filter",暂时不支持别的图标,有需求联系作者 | 52 | | rbIcon |"none" | String |单元格右下角的图标 ,注:有五个默认的图标 "upArrow","downArrow","leftArrow","rightArrow","filter",暂时不支持别的图标,有需求联系作者 | 53 | | imgUrl |"none" | String |单元格的图片地址| 54 | 55 | ##### 2.其它属性 56 | 参考zrender的Elements属性 57 | 58 | #### excel.activeCell 59 | 当前激活的单元格 60 | 61 | #### excel.selectCells 62 | 当前选中的单元格 63 | 64 | #### excel.copyCells 65 | 当前复制在剪贴板中的数据 66 | 67 | #### excel.tableHeaderCell 68 | 最顶端A-Z的表头 69 | 70 | ##### 1.excel.tableHeaderCell.data 71 | 该表头的数据以及格式 72 | 73 | | 属性 | 默认值 | 类型 | 含义 | 74 | | ---------- | ------ |------ |---------------------------------- | 75 | | index | (根据位置改变)| Number |表头顺序 | 76 | | width | 100 | Number |表头宽度 | 77 | | xPlace | (根据位置改变)| Number |表头像素位置 | 78 | 79 | #### excel.tableIndexCell 80 | 表格最左的列头 81 | 82 | ##### 1. excel.tableIndexCell.data 83 | 该列头的数据以及格式 84 | 85 | | 属性 | 默认值 | 类型 | 含义 | 86 | | ---------- | ------ |------ |---------------------------------- | 87 | | index | (根据位置改变)| Number |列头顺序 | 88 | | height | 30 | Number |列头高度 | 89 | | yPlace | (根据位置改变)| Number |列头像素位置 | 90 | 91 | #### excel.edit 92 | 编辑框对象 93 | 94 | ##### 1. excel.edit.editEle 95 | 编辑框DOM 96 | 97 | #### excel.contextMenu 98 | 右键菜单对象 99 | 100 | ##### 1. excel.contextMenu.menuEl 101 | 右键菜单的DOM对象 102 | ##### 2. excel.contextMenu.menus 103 | 右键菜单按钮列表 104 | 105 | #### excel.uploadFile 106 | 上传图片的对象 107 | 108 | #### excel.toolBar 109 | 工具条对象 110 | 111 | ##### 1. excel.toolBar.el 112 | 工具条DOM 113 | 114 | ##### 2. excel.toolBar.parent 115 | 工具条父元素DOM 116 | 117 | ##### 3. excel.toolBar.copyButton 118 | 复制按钮 119 | 120 | ##### 4. excel.toolBar.pasteButton 121 | 粘贴按钮 122 | 123 | ##### 5. excel.toolBar.clearFormat 124 | 清除按钮 125 | 126 | ##### 6. excel.toolBar.typeFaceButton 127 | 选择字体按钮 128 | 129 | ##### 7. excel.toolBar.fontSizeButton 130 | 字体大小按钮 131 | 132 | ##### 8. excel.toolBar.fontWeightButton 133 | 加粗按钮 134 | 135 | ##### 9. excel.toolBar.fontItalicButton 136 | 斜体按钮 137 | 138 | ##### 10. excel.toolBar.textFillButton 139 | 文字颜色按钮 140 | 141 | ##### 11. excel.toolBar.fillButton 142 | 背景颜色按钮 143 | 144 | ##### 12. excel.toolBar.borderButton 145 | 边框按钮 146 | 147 | ##### 13. excel.toolBar.alignLiftButton 148 | 左对齐按钮 149 | 150 | ##### 14. excel.toolBar.alignRightButton 151 | 右对齐按钮 152 | 153 | ##### 15. excel.toolBar.alignCenterButton 154 | 居中对齐按钮 155 | 156 | ##### 16. excel.toolBar.mergeCellButton 157 | 合并单元格按钮 158 | 159 | ##### 17. excel.toolBar.splitCellButton 160 | 拆分单元格按钮 161 | 162 | ##### 18. excel.toolBar.addImageButton 163 | 添加图片按钮 164 | 165 | 166 | ### 实例方法(实例使用 excel 命名) 167 | #### 1. excel.refreshCell() 168 | 刷新视图 169 | #### 2. excel.getTableDatas() 170 | 获取全部数据 171 | 172 | #### 3. excel.setTableDatas(config) 173 | 批量填入数据 174 | config: 175 | | 属性 | 默认值 | 类型 | 含义 | 176 | | ---------- | ------ |------ |---------------------------------- | 177 | | data | 必填 | Array | 一维数组 或 二维数组,data中,x和y是必须的,其它属性参考 excel.cells[x][y].data | 178 | | clear | false | Boolean |填充数据时是否清空其他数据 true:清空 false:不清空 | 179 | 180 | #### 4. excel.cells[x][y].setData(data) 181 | 修改单元格数据,data格式见上面 cells.data 182 | 183 | #### 5. excel.cells[x][y].clear() 184 | 清除单元格数据,不清除格式 185 | 186 | #### 6. excel.cells[x][y].clearFormat() 187 | 清除单元格格式,不清除数据 188 | 189 | #### 7. excel.setSpanNum(number) 190 | 修改excel的列数 191 | 192 | #### 8. excel.setRowNum(number) 193 | 修改excel的行数 194 | 195 | #### 9. 获取某个单元格数据 196 | 这个功能我没有写。。大家可以用 excel.cells 的data (hhh,我是懒鬼,反正没人用,那我就怎么开心怎么来啦) 197 | 198 | #### 10. 添加右键菜单 199 | excel.contextMenu.addButton(text,func) 200 | 例:添加清除内容按钮 201 | ```js 202 | const clearInputBtn = excel.contextMenu.addButton('清除内容',() => { 203 | this.selectCells.forEach(cell => { 204 | cell.clear() 205 | }) 206 | }) 207 | ``` 208 | #### 11. 添加工具菜单 209 | excel.toolBar.addButton(html,tooltip,func) 210 | 例: 211 | ```js 212 | const sayHello = excel.toolBar.addButton( 213 | '', 214 | '说你好', 215 | (e)=>{ 216 | alert('你好') 217 | } 218 | ) 219 | ``` 220 | 221 | 222 | #### 10. excel.dispose() 223 | 移除自身。当不再需要使用该实例时,调用该方法以释放内存。 224 | 225 | #### 11. excel.setSelect(xstart,ystart,xend?,yend?) 226 | 设置选中的单元格 227 | #### 12. excel.clearTableDatasAndFormat() 228 | 初始化表格 229 | 230 | ### 事件 231 | 232 | #### 1.单元格点击事件 233 | clickCell事件 234 | 235 | 例: 236 | ```js 237 | excel.on("clickCell",(data) =>{ 238 | console.log(data) 239 | }) 240 | 241 | ``` 242 | #### 2.上下左右切换单元格事件 243 | moveCell事件 244 | 245 | 例: 246 | ```js 247 | excel.on("moveCell",(data) =>{ 248 | console.log(data) 249 | }) 250 | 251 | ``` 252 | 253 | #### 3.单元格数据改变事件 254 | changeCell事件 255 | 256 | 例: 257 | ```js 258 | excel.on("changeCell",(data) =>{ 259 | console.log(data) 260 | }) 261 | 262 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "daodaoexcel", 3 | "version": "1.0.0", 4 | "description": "a web excel plugin", 5 | "main": "src/main.js", 6 | "scripts": { 7 | "dev": "webpack-dev-server --hot --open --contentBase test", 8 | "build": "webpack --config webpack.config.js --grogress --colors", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "uuid": "^7.0.2", 15 | "zrender": "^4.3.0" 16 | }, 17 | "devDependencies": { 18 | "webpack": "^4.42.1", 19 | "webpack-cli": "^3.3.11", 20 | "webpack-dev-server": "^3.10.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/cell.js: -------------------------------------------------------------------------------- 1 | import zrender from 'zrender' 2 | import {headerHeight,indexWidth} from './config' 3 | import {generateCode } from "./utils.js" 4 | 5 | import icons from './icon' 6 | 7 | class Cell extends zrender.Group{ 8 | constructor(data){ 9 | let defaultCellConfig = { 10 | cursor:'default', 11 | scale:[0.5,0.5], 12 | z:1 13 | } 14 | //由于zrender的Rect描边粗细有bug,所以我的做法是先放大两倍然后再缩放0.5倍 15 | let xPlace = data.cellWidth * data.x + indexWidth 16 | let yPlace = data.cellHeight * data.y + headerHeight 17 | let countConfig = { 18 | shape:{ 19 | x:1, 20 | y:1, 21 | width:data.cellWidth * 2, 22 | height:data.cellHeight * 2 23 | }, 24 | style:{ 25 | truncate:{ 26 | outerWidth:data.cellWidth 27 | }, 28 | stroke: '#aaa', 29 | fill: 'white', 30 | lineWidth:'1', 31 | fontSize:data.fontSize, 32 | fontFamily:data.fontFamily, 33 | fontWeight:data.fontWeight, 34 | fontStyle:data.fontStyle, 35 | textFill:data.textFill, 36 | fill:data.fill, 37 | textAlign:'center', 38 | textPosition:'inside', 39 | textOffset:[0,0] 40 | } 41 | } 42 | let finnalconfig = Object.assign({},defaultCellConfig,countConfig) 43 | super({ 44 | position:[xPlace,yPlace] 45 | }) 46 | this.data = Object.assign({},data,{ 47 | xPlace:xPlace, 48 | yPlace:yPlace, 49 | name:generateCode(data.x)+(data.y+1) 50 | }) 51 | this.handlers = {} 52 | this.type = 'cell' 53 | this.img = null 54 | this.ltIcon = null 55 | this.rtIcon = null 56 | this.lbIcon = null 57 | this.rbIcon = null 58 | this.cell = null 59 | this.init(finnalconfig) 60 | } 61 | //初始化单元格 62 | init(finnalconfig){ 63 | this.cell = new zrender.Rect(finnalconfig) 64 | this.cell.type = 'cellborder' 65 | this.add(this.cell) 66 | } 67 | addEvent(type,handler){ 68 | if(typeof this.handlers[type] === "undefined"){ 69 | this.handlers[type] = [] 70 | } 71 | this.handlers[type].push(handler) 72 | } 73 | emit(type,event){ 74 | if(!event.target){ 75 | event.target = this 76 | } 77 | if(this.handlers[type] instanceof Array){ 78 | const handlers = this.handlers[type] 79 | handlers.forEach((handler)=>{ 80 | handler(event) 81 | }) 82 | } 83 | } 84 | removeEvent(type,handler){ 85 | if(this.handlers[type] instanceof Array){ 86 | const handlers = this.handlers[type] 87 | for(var i = 0,len = handlers.length; i < len; i++){ 88 | if(handlers[i] === handler){ 89 | break; 90 | } 91 | } 92 | handlers.splice(i,1) 93 | } 94 | } 95 | //设置选中单元格样式 96 | selectCell(){ 97 | let color = 'rgba(1,136,251,0.1)' 98 | let datafill = this.data.fill 99 | let newColor = zrender.color.lerp(0.8, [color,datafill],true) 100 | 101 | this.cell.attr({style:{fill:newColor.color}}) 102 | } 103 | //取消选择样式重置 104 | unSelectCell(){ 105 | this.cell.attr({style:{fill:this.data.fill}}) 106 | } 107 | //设置单元格文字 108 | setText(text,flag){ 109 | this.cell.attr({style:{text:text}}) 110 | this.data.text = text 111 | if(!flag){ 112 | this.emit('change',{data:this.data}) 113 | } 114 | } 115 | //设置字体 116 | setFontFamily(font,flag){ 117 | this.cell.attr({style:{fontFamily:font}}) 118 | this.data.fontFamily = font 119 | if(!flag){ 120 | this.emit('change',{data:this.data}) 121 | } 122 | } 123 | //设置字体大小 124 | setFontSize(size,flag){ 125 | this.cell.attr({style:{fontSize:size}}) 126 | this.data.fontSize = size 127 | if(!flag){ 128 | this.emit('change',{data:this.data}) 129 | } 130 | } 131 | //设置文字粗细 132 | setFontWeight(data,flag){ 133 | this.cell.attr({style:{fontWeight:data}}) 134 | this.data.fontWeight = data 135 | if(!flag){ 136 | this.emit('change',{data:this.data}) 137 | } 138 | } 139 | //设置文字倾斜 140 | setFontItalic(data,flag){ 141 | this.cell.attr({style:{fontStyle:data}}) 142 | this.data.fontStyle = data 143 | if(!flag){ 144 | this.emit('change',{data:this.data}) 145 | } 146 | } 147 | //设置字体颜色 148 | setTextFill(data,flag){ 149 | this.cell.attr({style:{textFill:data}}) 150 | this.data.textFill = data 151 | if(!flag){ 152 | this.emit('change',{data:this.data}) 153 | } 154 | } 155 | //设置背景颜色 156 | setFill(data,flag){ 157 | this.cell.attr({style:{fill:data}}) 158 | this.data.fill = data 159 | if(!flag){ 160 | this.emit('change',{data:this.data}) 161 | } 162 | } 163 | //设置边框 164 | setBorder(data,flag){ 165 | if(data===true || data == 'true'){ 166 | this.cell.attr({style:{stroke: '#000',lineWidth:2}}) 167 | this.cell.attr({z:2}) 168 | this.data.border = true 169 | }else{ 170 | this.cell.attr({style:{stroke: '#aaa',lineWidth:1}}) 171 | this.cell.attr({z:1}) 172 | this.data.border = false 173 | } 174 | if(!flag){ 175 | this.emit('change',{data:this.data}) 176 | } 177 | } 178 | //设置对齐方式 179 | setTextAlign(data,flag){ 180 | if(data == 'left'){ 181 | this.cell.attr({style:{ 182 | textAlign:'left', 183 | textPosition:'left', 184 | textOffset:[10,0] 185 | }}) 186 | }else if(data == 'right'){ 187 | this.cell.attr({style:{ 188 | textAlign:'right', 189 | textPosition:'right', 190 | textOffset:[-10,0] 191 | }}) 192 | }else if(data == 'center'){ 193 | this.cell.attr({style:{ 194 | textAlign:'center', 195 | textPosition:'inside', 196 | textOffset:[0,0] 197 | }}) 198 | } 199 | this.data.textAlign = data 200 | if(!flag){ 201 | this.emit('change',{data:this.data}) 202 | } 203 | } 204 | //设置单元格data 205 | setData(data){ 206 | this.data = Object.assign({},this.data,data) 207 | this.data.name = generateCode(this.data.x)+(this.data.y+1) 208 | //更改cell的大小 209 | this.cell.attr({shape:{ 210 | width:this.data.cellWidth * 2, 211 | height:this.data.cellHeight * 2 212 | }}) 213 | this.cell.attr({ 214 | style:{ 215 | truncate:{ 216 | outerWidth:data.cellWidth 217 | } 218 | } 219 | }) 220 | //更改图片 221 | if(this.data.imgUrl){ 222 | this.addImage(this.data.imgUrl,true) 223 | }else{ 224 | if(this.img){ 225 | this.removeImage(true) 226 | }else{ 227 | 228 | } 229 | } 230 | //更改cell的显示隐藏 231 | if(this.data.merge == true && (this.data.row == 0 || this.data.span == 0)){ 232 | this.hide() 233 | }else{ 234 | this.show() 235 | } 236 | this.attr('position',[this.data.xPlace,this.data.yPlace]) 237 | //更改文字 238 | this.setText(this.data.text,true) 239 | //更改字体 240 | this.setFontFamily(this.data.fontFamily,true) 241 | //更改字体大小 242 | this.setFontSize(this.data.fontSize,true) 243 | //更改文字粗细 244 | this.setFontWeight(this.data.fontWeight,true) 245 | //更改文字倾斜 246 | this.setFontItalic(this.data.fontStyle,true) 247 | //更改字体颜色 248 | this.setTextFill(this.data.textFill,true) 249 | //更改背景颜色 250 | this.setFill(this.data.fill,true) 251 | //设置边框 252 | this.setBorder(this.data.border,true) 253 | //设置对齐方式 254 | this.setTextAlign(this.data.textAlign,true) 255 | //设置左上角标 256 | this.setLTIcon(this.data.ltIcon,true) 257 | //设置右上角标 258 | this.setRTIcon(this.data.rtIcon,true) 259 | //设置左下角标 260 | this.setLBIcon(this.data.lbIcon,true) 261 | //设置右下角标 262 | this.setRBIcon(this.data.rbIcon,true) 263 | 264 | this.emit('change',{data:this.data}) 265 | } 266 | //设置左上角标 267 | setLTIcon(data,flag){ 268 | if(data){ 269 | if(data == "none"){ 270 | if(this.ltIcon){ 271 | this.remove(this.ltIcon) 272 | this.ltIcon = null 273 | } 274 | }else{ 275 | if(this.ltIcon){ 276 | this.remove(this.ltIcon) 277 | } 278 | this.ltIcon = new zrender.Image({ 279 | style:{ 280 | image:icons[data], 281 | x:0, 282 | y:0, 283 | width:15, 284 | height:15 285 | }, 286 | z:3 287 | }) 288 | this.add(this.ltIcon) 289 | } 290 | }else{ 291 | if(this.ltIcon){ 292 | this.remove(this.ltIcon) 293 | this.ltIcon = null 294 | } 295 | } 296 | this.data.ltIcon = data 297 | if(!flag){ 298 | this.emit('change',{data:this.data}) 299 | } 300 | } 301 | //设置左下角标 302 | setLBIcon(data,flag){ 303 | if(data){ 304 | if(data == "none"){ 305 | if(this.lbIcon){ 306 | this.remove(this.lbIcon) 307 | this.lbIcon = null 308 | } 309 | }else{ 310 | if(this.lbIcon){ 311 | this.remove(this.lbIcon) 312 | } 313 | this.lbIcon = new zrender.Image({ 314 | style:{ 315 | image:icons[data], 316 | x:0, 317 | y:this.data.cellHeight - 15, 318 | width:15, 319 | height:15 320 | }, 321 | z:3 322 | }) 323 | this.add(this.lbIcon) 324 | } 325 | }else{ 326 | if(this.lbIcon){ 327 | this.remove(this.lbIcon) 328 | this.lbIcon = null 329 | } 330 | } 331 | this.data.lbIcon = data 332 | if(!flag){ 333 | this.emit('change',{data:this.data}) 334 | } 335 | } 336 | //设置右上角标 337 | setRTIcon(data,flag){ 338 | if(data){ 339 | if(data == "none"){ 340 | if(this.rtIcon){ 341 | this.remove(this.rtIcon) 342 | this.rtIcon = null 343 | } 344 | }else{ 345 | if(this.rtIcon){ 346 | this.remove(this.rtIcon) 347 | } 348 | this.rtIcon = new zrender.Image({ 349 | style:{ 350 | image:icons[data], 351 | x:this.data.cellWidth - 15, 352 | y:0, 353 | width:15, 354 | height:15 355 | }, 356 | z:3 357 | }) 358 | this.add(this.rtIcon) 359 | } 360 | }else{ 361 | if(this.rtIcon){ 362 | this.remove(this.rtIcon) 363 | this.rtIcon = null 364 | } 365 | } 366 | this.data.rtIcon = data 367 | if(!flag){ 368 | this.emit('change',{data:this.data}) 369 | } 370 | } 371 | //设置右下角标 372 | setRBIcon(data,flag){ 373 | if(data){ 374 | if(data == "none"){ 375 | if(this.rbIcon){ 376 | this.remove(this.rbIcon) 377 | this.rbIcon = null 378 | } 379 | }else{ 380 | if(this.rbIcon){ 381 | this.remove(this.rbIcon) 382 | } 383 | this.rbIcon = new zrender.Image({ 384 | style:{ 385 | image:icons[data], 386 | x:this.data.cellWidth - 15, 387 | y:this.data.cellHeight - 15, 388 | width:15, 389 | height:15 390 | }, 391 | z:3 392 | }) 393 | this.add(this.rbIcon) 394 | } 395 | }else{ 396 | if(this.rbIcon){ 397 | this.remove(this.rbIcon) 398 | this.rbIcon = null 399 | } 400 | } 401 | this.data.rbIcon = data 402 | if(!flag){ 403 | this.emit('change',{data:this.data}) 404 | } 405 | } 406 | //添加图片 407 | addImage(url,flag){ 408 | this.data.imgUrl = url 409 | if(this.img){ 410 | this.remove(this.img) 411 | this.img = null 412 | } 413 | this.setText("") 414 | this.img = new zrender.Image({ 415 | style:{ 416 | image:url, 417 | x:0, 418 | y:0, 419 | width:this.data.cellWidth, 420 | height:this.data.cellHeight 421 | }, 422 | z:2 423 | }) 424 | this.add(this.img) 425 | if(!flag){ 426 | this.emit('change',{data:this.data}) 427 | } 428 | } 429 | removeImage(flag){ 430 | console.log(this.img) 431 | if(this.img){ 432 | this.remove(this.img) 433 | this.img = null 434 | this.data.imgUrl = "" 435 | } 436 | if(!flag){ 437 | this.emit('change',{data:this.data}) 438 | } 439 | } 440 | clear(){ 441 | let data = { 442 | fontFamily:'微软雅黑', 443 | fontSize:14, 444 | fontStyle:'normal', 445 | fontWeight:'normal', 446 | textFill:'#000000', 447 | fill:'#ffffff', 448 | border:false, 449 | textAlign:'center', 450 | cellWidth:this.data.cellWidth, 451 | cellHeight:this.data.cellHeight, 452 | merge:this.data.merge, 453 | row:this.data.row, 454 | span:this.data.span, 455 | mergeConfig:this.data.mergeConfig, 456 | text:"", 457 | xPlace:this.data.xPlace, 458 | yPlace:this.data.yPlace, 459 | imgUrl:'', 460 | x:this.data.x, 461 | y:this.data.y 462 | } 463 | this.data = data 464 | this.setData(data) 465 | this.emit('change',{data:this.data}) 466 | } 467 | clearFormat(){ 468 | let data = { 469 | fontFamily:'微软雅黑', 470 | fontSize:14, 471 | fontStyle:'normal', 472 | fontWeight:'normal', 473 | textFill:'#000000', 474 | fill:'#ffffff', 475 | border:false, 476 | textAlign:'center', 477 | } 478 | this.setData(data) 479 | this.emit('change',{data:this.data}) 480 | } 481 | } 482 | 483 | export default Cell -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | export const letter = [ 2 | 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z', 3 | ] 4 | 5 | export const defaultTableConfig = { 6 | cellWidth:100, 7 | cellHeight:30, 8 | row:30, 9 | span:26 10 | } 11 | 12 | //表头高度 13 | export const headerHeight = 20 14 | //列头宽度 15 | export const indexWidth = 50 16 | //scrollWidth 17 | export const scrollWidth = 10 -------------------------------------------------------------------------------- /src/contextMenu.js: -------------------------------------------------------------------------------- 1 | class ContextMenu { 2 | constructor(parent){ 3 | this.menus = [ 4 | 5 | ] 6 | this.menuEl = null 7 | this.init(parent) 8 | } 9 | init(parent){ 10 | //创建右键菜单dom 11 | this.menuEl = document.createElement('div') 12 | this.menuEl.id = "daodao_excel_menu" 13 | this.menuEl.style.cssText = ` 14 | position:absolute; 15 | padding:2px; 16 | width:150px; 17 | display:none; 18 | background:#fff; 19 | border:1px solid #d2d2d2; 20 | ` 21 | parent.appendChild(this.menuEl) 22 | } 23 | showMenu(x,y){ 24 | this.menuEl.style.display = "block" 25 | this.menuEl.style.top = y + 'px' 26 | this.menuEl.style.left = x + 'px' 27 | } 28 | hideMenu(){ 29 | this.menuEl.style.display = "none" 30 | } 31 | addButton(name,func){ 32 | let btn = document.createElement('div') 33 | btn.innerText = name 34 | btn.style.cssText = ` 35 | cursor: pointer; 36 | padding: 4px; 37 | color: #6b6b6b; 38 | font-size: 14px; 39 | ` 40 | this.menuEl.appendChild(btn) 41 | this.menus.push(btn) 42 | btn.addEventListener("click",func) 43 | return btn 44 | } 45 | } 46 | 47 | export default ContextMenu -------------------------------------------------------------------------------- /src/copyedCell.js: -------------------------------------------------------------------------------- 1 | import zrender from 'zrender' 2 | import {headerHeight,indexWidth} from './config' 3 | 4 | class CopyedCell extends zrender.Rect{ 5 | constructor(cells){ 6 | let originConfig = { 7 | cursor:'default', 8 | style:{ 9 | stroke: '#4e9fff', 10 | lineWidth:'2', 11 | fill:'none', 12 | lineDash:[5] 13 | }, 14 | z:3 15 | } 16 | let config = { 17 | shape:{ 18 | 19 | } 20 | } 21 | //cells的长度为1的时候 22 | if(cells.length == 1){ 23 | config.shape = { 24 | x:cells[0].data.xPlace, 25 | y:cells[0].data.yPlace, 26 | width:cells[0].data.cellWidth, 27 | height:cells[0].data.cellHeight 28 | } 29 | }else{ 30 | let x = cells[0].data.xPlace 31 | let y = cells[0].data.yPlace 32 | let width = cells[cells.length - 1].data.xPlace + cells[cells.length - 1].data.cellWidth - x 33 | let height = cells[cells.length - 1].data.yPlace + cells[cells.length - 1].data.cellHeight - y 34 | 35 | //下标最小的是起点 36 | config.shape.x = x 37 | config.shape.y = y 38 | //下标最大的是终点 39 | config.shape.width = width 40 | config.shape.height = height 41 | } 42 | let finalConfig = Object.assign({},originConfig,config) 43 | super(finalConfig) 44 | } 45 | //改变选择框的位置和大小 46 | change(cells){ 47 | let shape = {} 48 | if(cells.length == 1){ 49 | shape = { 50 | x:cells[0].data.xPlace, 51 | y:cells[0].data.yPlace, 52 | width:cells[0].data.cellWidth, 53 | height:cells[0].data.cellHeight 54 | } 55 | }else{ 56 | //求最小下标以及最大下标 57 | let x = cells[0].data.xPlace 58 | let y = cells[0].data.yPlace 59 | let width = cells[cells.length - 1].data.xPlace + cells[cells.length - 1].data.cellWidth - x 60 | let height = cells[cells.length - 1].data.yPlace + cells[cells.length - 1].data.cellHeight - y 61 | 62 | //下标最小的是起点 63 | shape.x = x 64 | shape.y = y 65 | //下标最大的是终点 66 | shape.width = width 67 | shape.height = height 68 | } 69 | this.attr('shape',shape) 70 | } 71 | } 72 | 73 | export default CopyedCell -------------------------------------------------------------------------------- /src/edit.js: -------------------------------------------------------------------------------- 1 | import Event from './event.js' 2 | 3 | class Edit extends Event{ 4 | constructor(parent){ 5 | super() 6 | //编辑框的dom元素 7 | this.editEle = null 8 | this.editFlag = false 9 | this.init(parent) 10 | } 11 | init(parent){ 12 | //初始化编辑框 13 | this.editEle = document.createElement('div') 14 | this.editEle.id = "daodao_excel_edit_div" 15 | this.editEle.style.cssText = ` 16 | position:absolute; 17 | text-indent:2px; 18 | outline-color:transparent; 19 | display:none; 20 | background:#fff; 21 | border:1px solid #4e9fff; 22 | ` 23 | this.editEle.setAttribute("contenteditable", "true"); 24 | parent.appendChild(this.editEle) 25 | } 26 | //改变编辑框的位置和大小 27 | setPosition(width,height,positionX,positionY,data){ 28 | this.editEle.style.width = `${width - 2}px` 29 | this.editEle.style.minHeight = `${height - 2}px` 30 | this.editEle.style.top = `${positionY}px` 31 | this.editEle.style.left = `${positionX}px` 32 | this.editEle.textContent = data 33 | this.showEdit() 34 | this.editEle.focus() 35 | } 36 | hideEdit(){ 37 | if(!this.editFlag){ 38 | return false 39 | } 40 | this.emit('update',{ 41 | type:'text', 42 | text:this.editEle.textContent 43 | }) 44 | this.editEle.style.display = "none" 45 | this.editFlag = false 46 | this.clearInput() 47 | } 48 | showEdit(){ 49 | this.editEle.style.display = "block" 50 | this.editFlag = true 51 | } 52 | clearInput(){ 53 | this.editEle.innerHTML = " " 54 | } 55 | } 56 | 57 | export default Edit -------------------------------------------------------------------------------- /src/event.js: -------------------------------------------------------------------------------- 1 | class Event { 2 | constructor(){ 3 | this.handlers = {} 4 | } 5 | on(type,handler){ 6 | if(typeof this.handlers[type] === "undefined"){ 7 | this.handlers[type] = [] 8 | } 9 | this.handlers[type].push(handler) 10 | } 11 | emit(type,event){ 12 | if(!event.target){ 13 | event.target = this 14 | } 15 | if(this.handlers[type] instanceof Array){ 16 | const handlers = this.handlers[type] 17 | handlers.forEach((handler)=>{ 18 | handler(event) 19 | }) 20 | } 21 | } 22 | off(type,handler){ 23 | if(this.handlers[type] instanceof Array){ 24 | const handlers = this.handlers[type] 25 | for(var i = 0,len = handlers.length; i < len; i++){ 26 | if(handlers[i] === handler){ 27 | break; 28 | } 29 | } 30 | handlers.splice(i,1) 31 | } 32 | } 33 | } 34 | 35 | export default Event -------------------------------------------------------------------------------- /src/icon.js: -------------------------------------------------------------------------------- 1 | const icons = { 2 | "upArrow":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAABuklEQVRoQ+3Xu4rVUBjF8d8gKgheGu18BJlyYHTES2Ohj2DlC2gxMI0whSDYaGdpJwha2CiC+AqCNgoWdt5A1EYYROSDE4jhmJOZb0eJ7ECak73Pt/5rrWzIkolfSxPXrwL86wRrAjWBpAO1QkkD09trAgssPIY13MWXtN1z/mDMBC7gPvbgNU7hfWmIsQDO4eFMfKP5DVbxqSTEGABn8LgjvtH8CidLQpQGCPGPsLfH5YA4js8lkigJcAJPF4hvNL+cvRNpiFIAIf4J9rVc3UTccf3EDWy0ngdEnFBfM0mUAJgn/grutI7OANiF6x2I5zidgcgCrOBZx/kQfwsH5wCE2UUhMgBRl7c43KpAIz5++hNAPLuJy619YcLZnVQpC/AOB2aD2+IXAXQhtnAI37cLkQGIWRdxCfdwuzO8L4FmabzY67iGq9sVH+uzAH0zhwDsRPNveypAj4U1gSH9qhWqFRrSk541tUK1QrVCSQdqhZIG1lPof67QfnybAf7A7mRb5m4fs0Ix8AOO4AWWpwhwFOfxAB+nCDCG5r/2RTa6+LG/iSvAEAfGPoWGaEitqQAp+wpsnnwCvwCy1lwxNkQzpAAAAABJRU5ErkJggg==", 3 | "rightArrow":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAABcklEQVRoQ+2YTUoEMRCFvwEFFyK4cCOiVxBlNiKuXagXEDycKw8hgojXkFEQ0bUHkBpaaHDMTJJHfqCyTirvq1ep7mRC52PSuX4coLaD7oA7kJkBL6HMBGYvdwciU7gHnAN3wHfk2oXTSzqwBnwB28ALcAJ85kKUBFgH3oGdQbQEoiSA6Z4CD8CmCqI0gByiBoAUohaADKImwH8QZ8NhX6lB1QZYBPE2tFjrWEtHCwBZEK0A/ELcA1tD2ldyoiUA030IPMZAhACOgKulRaifcAxcjMIGnQgBzIADvb6kiE+Adac/IwTwCuwnbadf9AycxgLsAjfAhl5PMKKV7qWihArrnm8nPcSlAaLFm8BW2miS+FYAksW3AGAXnPHX125p3fzMSW5ntc6ARHytEpKJrwEgFV8aYPwuZHt396wyfheSiC/tgO1nT4vXwC3wofjU1+pCCu3zGA4gS2ViIHcgMXGyZe6ALJWJgdyBxMTJlnXvwA8QdVAx3rcJfAAAAABJRU5ErkJggg==", 4 | "downArrow":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACCklEQVRoQ+2ZsUoDQRCGZy4kryAiWFhEsrtFaosIWgm+gZ0ognZ2PoSNqJVWPoMWgoUiKQTBYhOSCCkDQiBEwUA4GDlI4AiXu927PSEwqQK52/2/mW82R4Kw4C9c8PzAAPM6qJSqENEbAPRHo1G12+0O8+h2bh2QUp4j4ukk9IHW+nbRAK4Q8TgITUQnjUbjmgEiKpCnQtwBE+W4A/OqJKVkhVghkwqwQlmqBJDfwxwPsWFn+HuAh9hQlXmXsUKsECuUsQKsUMYC8inECv2TQiilvEDEdd/3z1qt1nvSvmmeRoUQR57nXRKR9n2/1m63f5L2MZoBIcSO53kPkx+pvolos9lsfsQtbguglNonohtEnGba1VrfOwGoVCrLhULhFQDWTCFsAGbDE9HnYDCo9nq9XycAwSLlcnmlWCzWEXHVBMIUIKLyXQDY0Fp/JYUPPjdSaLqQDYQJQNbw1gDTTpRKpZcknZIAXIRPBRDcpJRaAoB6HEQcgBBiDxHvQgNrpU1YLSuFwjcmQcwDcBk+dQemIHEQUQCuw2cGiNMJEQ/Df3AQ0dCVNk4UStIJEYMvutrkyH0CgC0Xzs8eralnYHahWZ1izvDUAxu1pjOAKJ0iNnQa3skMWHTCefhcAEKdeAaA9QlgLuFzAwhBPAbvx+PxdqfT6Zs829he43QGbDd3cT0DuKhiljX+AOZNFk86C5VQAAAAAElFTkSuQmCC", 5 | "leftArrow":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAB0UlEQVRoQ+3XP6hPcRzG8ddNkjJcGQw2M6PlRiQyK6PRYjTehZgYlMVotBikpJBMBgwG/xY7SUJEJvrWufXrdq57zvc51723vmf+Pp/P836e7+/0O3M2+TO3yf1rAOvdYGugNRAm0K5QGGAsbw3EEYYDWgNhgLE8bWAvDuEWfsVuKgYkAAfwEPO4g5MV+2NJLUAx/xg7OgfvsSd2UzGgBmC5+bL2NG5W7I8lYwH6zJ/DtdhJ5YAxABvOfGEeCrCABzN3vmgv4FJlcJPJhgAU84+wfWbrZSxO5iIYtBpAn/ly38u93xDPvwAu4vwyl+V1+e4/O/+Eq3jat3clgN34MOI3stZMb7BvDMAufMSWtXY2cP5r7B8DUM6exfWZFv7gJb4OXDrVsc+4gudjAcr547iHrZ34N47hyVTu0jmrvYX6IH7iKJ6ly6fQDwHog/jRNbHuEEMBVoI4jBdTJFk7YwzAEsRdbOsWfus+aF7VGkh1YwHKviO4PwPxBQfxNjVTo68B6IMoX2YnagykmlqAJYjb2IkbOJOaqdEnAGVf+Yd6qvsm/l5jINWkAOn+WN8A4gjDAa2BMMBY3hqIIwwHtAbCAGN5ayCOMBzQGggDjOV/AZVbOTEZHY7IAAAAAElFTkSuQmCC", 6 | "filter":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAC9klEQVRYR+2WS2gTURSG/zOTpGlTaxJipaGtiQ8EBV9QdVGU0oWKIoqgCFYRFdoR1y5EFB8bkboQk/oG25UI4gMRbBfiA7X1sVBEBJOmtVLQTmqiqTH3Hplo1aapSWwxC3tXM8M5///NmXPPHUKeF+XZH+MA4xUYUgGvX78EYN3fNiYDDCYtqNmbvL6+OyCqTtGKC6aVIc3eOvg8FeA2gCUAdzPobM4gDClBLSHN/sbj09eAMM/QYLBbAe1I6hGvDtQ7r6UHOB2djET8EUCVgrkupDlbcoZISShv5EKTNXyfCPOYcSCoOfb9HjKsCStOhqepgjuIYBNQa0INJfdGA+Hx6VeIsBqMloDmqEvVSrsLvP7+hQxxG0yx2ukFrQvKLGtdNtUU+8r8Lpro1mN8+kh1wcFUsaPPeRbHRaNJxfxwTAavvYx16ANSY+abQZdjFdaTyArACPI09S8Hy+uuIkVtWDQBxZahrG8/ivddUbniWLWlw4g//CSxuayIztksimrcv+j9iuZnn4zLR2y1Lw1upYF0lfzjHKj09W1SiZrdJSoMCLMyVEIy8OGzGLBbVbNZRdLYWK8/JHD+cRRC8isU0uLgVkd4pM+YcRBN8fftUUCHZk4yYcv8YigZMkJhgZPtESQE9whRUNW1y9bzpx7KCGAke336GRC2VZVbsG520Yh63f0Cp9ojiAuOgE1VAa3kVaYGzgoA+1nxlIYvG928bIYVNVOtw3R7oxL+hx8xkEBcSK4N7XTezWSeHAvZBCVjLrLF+15vM6bbxjk2zC0z/0zti0mceBBB9AtLAtYGNMfVbHWzBwDgOssTir/o7UQ0M52BlLS9c6c9pwmaE4BhWnH8k9tkij8FUPo7BIOPBBucu7N988G4nAGSTenXf5wZv+wkKSs76yfeGAcYr8D/UYHKpsgslRP+739P39c/3QWDpp4T4Q0g2UhE7rwAGCCTL7CtMKrvJUVpC9Tbb/2THsjVZNTH8Vgapmr91SgeS6C8A3wDaS4vMOrG7VAAAAAASUVORK5CYII=", 7 | } 8 | 9 | export default icons 10 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import zrender from 'zrender' 2 | import Cell from './cell.js' 3 | import SelectCell from './selectCell.js' 4 | import CopyedCell from './copyedCell.js' 5 | import TableHeaderCell from './tableHeaderCell.js' 6 | import TableIndexCell from './tableIndexCell.js' 7 | import {defaultTableConfig,headerHeight,indexWidth,scrollWidth} from './config' 8 | import Scroll from './scroll.js' 9 | import Edit from './edit.js' 10 | import Event from "./event.js" 11 | import ContextMenu from './contextMenu.js' 12 | import UploadFile from './uploadFile.js' 13 | import ToolBar from './toolBar.js' 14 | import { mouseWheelDirection, preventDefault,stopPropagation ,generateUUID} from "./utils.js" 15 | 16 | class DaoDaoExcel extends Event { 17 | constructor(obj){ 18 | super() 19 | //默认配置 20 | const defaultObj = defaultTableConfig 21 | this.currentObj = {...defaultObj,...obj} 22 | //zrender实例 23 | this.canvas = null 24 | //整个表格的group 25 | this.table = null 26 | //所有单元格 27 | this.cells = new Array() 28 | //当前激活的单元格 29 | this.activeCell = null 30 | //当前选中的单元格 31 | this.selectCells = [] 32 | //当前在剪贴板中的单元格 33 | this.copyCells = [] 34 | //选中的时候显示的那个蓝色框框 35 | this.selectedCell = null 36 | //copyCells有数据的那个蓝色框框 37 | this.copyedCell = null 38 | //表头a-z 39 | this.tableHeader = null 40 | //表头a-z单元格 41 | this.tableHeaderCell = [] 42 | //列头1-n 43 | this.tableIndex = null 44 | //列头1-n的单元格 45 | this.tableIndexCell = [] 46 | //左上角的那个什么也没有的格子 47 | this.selectAllCell = null 48 | //可编辑的div 49 | this.edit = null 50 | //右键菜单 51 | this.contextMenu = null 52 | //改变宽度的控制柄 53 | this.changeWidthLine = null 54 | //上传图片的组件 55 | this.uploadFile = null 56 | //工具条 57 | this.toolBar = null 58 | //默认文字状态 59 | this.textConfig = { 60 | 'fontFamily':'微软雅黑', 61 | 'fontSize':14, 62 | 'fontStyle':'normal', 63 | 'fontWeight':'normal', 64 | 'textFill':'#000000', 65 | 'fill':'#ffffff', 66 | 'border':false, 67 | 'textAlign':'center' 68 | } 69 | //当前是否正在编辑 70 | this.isActive = false 71 | this.init() 72 | } 73 | init(){ 74 | //parent是canvas的包裹元素 75 | const parent = document.getElementById(this.currentObj.id) 76 | if(!parent){ 77 | //如果没有包裹元素就不在执行,并抛出错误 78 | //JavaScript引擎一旦遇到throw语句,就会停止执行后面的语句,并将throw语句的参数值,返回给用户。 79 | throw new Error("没有找到id为"+this.currentObj.id+"的元素!"); 80 | } 81 | const canvasWrapper = document.createElement('div') 82 | canvasWrapper.id = generateUUID() 83 | canvasWrapper.style.width = '100%' 84 | canvasWrapper.style.height = (parent.clientHeight - 30) + 'px' 85 | parent.appendChild(canvasWrapper) 86 | //新建canvas 87 | this.canvas = zrender.init(canvasWrapper); 88 | this.initCells() 89 | this.initTableHeader() 90 | this.initTableIndex() 91 | //左上角加上一个单元格 92 | this.selectAllCell = new zrender.Rect({ 93 | cursor:'default', 94 | scale:[0.5,0.5], 95 | style:{ 96 | stroke: '#aaa', 97 | fill: 'white', 98 | lineWidth:'1' 99 | }, 100 | shape:{ 101 | x:1, 102 | y:1, 103 | width:indexWidth * 2, 104 | height:headerHeight * 2 105 | }, 106 | z:1001 107 | }) 108 | this.canvas.add(this.selectAllCell) 109 | //初始化滚动条 110 | this.initScroll(canvasWrapper) 111 | //初始化编辑框 112 | this.initEdit(canvasWrapper) 113 | //初始化上传文件 114 | this.initUploadFile(parent) 115 | //初始化右键菜单 116 | this.initContextMenu(canvasWrapper) 117 | //初始化工具条 118 | this.initToolBar(parent) 119 | //绑定事件 120 | this.initEvents() 121 | } 122 | initUploadFile(parent){ 123 | this.uploadFile = new UploadFile(parent) 124 | this.uploadFile.on('changeImage',(event) => { 125 | this.activeCell.addImage(event.url) 126 | }) 127 | } 128 | //取消选择单元格 129 | cancelSelectCell(){ 130 | this.selectCells.forEach(cell => { 131 | cell.unSelectCell() 132 | }) 133 | this.tableHeaderCell.forEach(cell => { 134 | cell.unSelectCell() 135 | }) 136 | this.tableIndexCell.forEach(cell => { 137 | cell.unSelectCell() 138 | }) 139 | } 140 | //更新选择状态 141 | updateSelectState(){ 142 | this.selectCells.forEach(cell => { 143 | cell.selectCell() 144 | }) 145 | //初始化选中的蓝色框框 146 | if(this.selectedCell){ 147 | //如果有选择框了就更新位置 148 | this.selectedCell.change(this.selectCells) 149 | }else{ 150 | //如果没有选择框就创建一个 151 | this.selectedCell = new SelectCell(this.selectCells) 152 | this.canvas.add(this.selectedCell) 153 | } 154 | this.activeCell = this.selectCells[0] 155 | if(this.activeCell){ 156 | this.activeCell.unSelectCell() 157 | } 158 | this.changeHeaderAndIndexState(this.selectCells) 159 | } 160 | initCells(){ 161 | //table是一个group,里面装着cells 162 | this.table = new zrender.Group() 163 | this.canvas.add(this.table) 164 | for(let x = 0;x < this.currentObj.span;x++){ 165 | this.cells[x] = new Array() 166 | for(let y = 0;y < this.currentObj.row;y++){ 167 | this.cells[x][y] = new Cell({...{ 168 | x:x, 169 | y:y, 170 | cellWidth:this.currentObj.cellWidth, 171 | cellHeight:this.currentObj.cellHeight, 172 | row:1, 173 | span:1, 174 | merge:false, 175 | text:"", 176 | },...this.textConfig}) 177 | this.cells[x][y].addEvent('change',(event)=>{ 178 | this.emit("changeCell",event) 179 | }) 180 | this.table.add(this.cells[x][y]) 181 | } 182 | } 183 | this.table.on('mousedown',(event) => { 184 | //将工具条的状态改变得和cell的状态一致 185 | this.toolBar.setConfig(event.target.parent.data) 186 | if(event.event.button != 0){ 187 | //如果点击的不是鼠标左键 188 | return false 189 | } 190 | this.isActive = true 191 | this.emit("clickCell",{data:event.target.parent.data}) 192 | //隐藏输入框 193 | this.edit.hideEdit() 194 | this.cancelSelectCell() 195 | //设置选中单元格为当前单击的单元格 196 | this.selectCells = [event.target.parent] 197 | //设置激活的单元格尾当前单击的单元格 198 | this.activeCell = event.target.parent 199 | //初始化选中的蓝色框框 200 | if(this.selectedCell){ 201 | //如果有选择框了就更新位置 202 | this.selectedCell.change(this.selectCells) 203 | }else{ 204 | //如果没有选择框就创建一个 205 | this.selectedCell = new SelectCell(this.selectCells) 206 | this.canvas.add(this.selectedCell) 207 | } 208 | let positionX = this.table.position[0] 209 | let positionY = this.table.position[1] 210 | this.selectedCell.attr('position',[positionX,positionY]) 211 | //更新一下列和行的选择状态 212 | this.changeHeaderAndIndexState(this.selectCells) 213 | this.table.on('mousemove',this.handleTableMouseMove,this) 214 | }) 215 | //鼠标双击进入编辑模式 216 | this.table.on("dblclick",(event) => { 217 | this.isActive = false 218 | //计算可编辑div的位置 219 | let x = event.target.parent.data.x 220 | let y = event.target.parent.data.y 221 | let width = event.target.parent.data.cellWidth 222 | let height = event.target.parent.data.cellHeight 223 | //得到x方向和y方向的位移 224 | let movex = this.table.position[0] 225 | let movey = this.table.position[1] 226 | let positionX = event.target.parent.data.xPlace + movex 227 | let positionY = event.target.parent.data.yPlace + movey 228 | let data = event.target.parent.data.text 229 | //改变编辑框的状态 230 | this.edit.setPosition(width,height,positionX,positionY,data) 231 | }) 232 | } 233 | setSelect(xstart,ystart,xend,yend){ 234 | if(!xend){ 235 | xend = xstart 236 | } 237 | if(!yend){ 238 | yend = ystart 239 | } 240 | this.cancelSelectCell() 241 | 242 | this.selectCells = this.countSelect(xstart,xend,ystart,yend) 243 | 244 | this.updateSelectState() 245 | } 246 | handleTableMouseMove(event){ 247 | //计算当前拖动到的单元格的下标 248 | const x = event.target.parent.data.x 249 | const y = event.target.parent.data.y 250 | //根据activeCell和当前的下标,动态的改变selectCells 251 | const ax = this.activeCell.data.x 252 | const ay = this.activeCell.data.y 253 | 254 | if(ax == x && ay == y){ 255 | return false 256 | } 257 | 258 | let xstart 259 | let ystart 260 | let xend 261 | let yend 262 | 263 | //判断两种下标的大小,来判定到底是从哪边拖动的 264 | if(x > ax){ 265 | xstart = ax 266 | xend = x 267 | }else{ 268 | xstart = x 269 | xend = ax 270 | } 271 | 272 | if(y > ay){ 273 | ystart = ay 274 | yend = y 275 | }else{ 276 | ystart = y 277 | yend = ay 278 | } 279 | 280 | this.cancelSelectCell() 281 | 282 | this.selectCells = this.countSelect(xstart,xend,ystart,yend) 283 | 284 | this.updateSelectState() 285 | } 286 | countSelect(xs,xe,ys,ye){ 287 | let xstart = xs 288 | let xend = xe 289 | let ystart = ys 290 | let yend = ye 291 | let that = this 292 | let list = [] 293 | getBoundXY() 294 | function getBoundXY(){ 295 | for(let x = xstart;x <= xend;x++){ 296 | for(let y = ystart;y <= yend;y++){ 297 | if(that.cells[x][y].data.merge == true&&that.cells[x][y].data.mergeConfig){ 298 | let flag = false 299 | if(that.cells[x][y].data.mergeConfig.xstart < xstart){ 300 | xstart = that.cells[x][y].data.mergeConfig.xstart 301 | flag = true 302 | } 303 | if(that.cells[x][y].data.mergeConfig.xend > xend){ 304 | xend = that.cells[x][y].data.mergeConfig.xend 305 | flag = true 306 | } 307 | if(that.cells[x][y].data.mergeConfig.ystart < ystart){ 308 | ystart = that.cells[x][y].data.mergeConfig.ystart 309 | flag = true 310 | } 311 | if(that.cells[x][y].data.mergeConfig.yend > yend){ 312 | yend = that.cells[x][y].data.mergeConfig.yend 313 | flag = true 314 | } 315 | if(flag){ 316 | getBoundXY() 317 | } 318 | } 319 | } 320 | } 321 | } 322 | for(let x = xstart;x <= xend;x++){ 323 | for(let y = ystart;y <= yend;y++){ 324 | list.push(this.cells[x][y]) 325 | } 326 | } 327 | return list 328 | } 329 | keydownMethod(event){ 330 | const keyCode = event.keyCode || event.which 331 | //获取到activeCell的下标 332 | if(!this.activeCell){ 333 | return false 334 | } 335 | let x = this.activeCell.data.x 336 | let y = this.activeCell.data.y 337 | switch(keyCode){ 338 | case 38: 339 | if(!this.isActive){ 340 | return false 341 | } 342 | preventDefault(event) 343 | //上 344 | //如果y = 0,就阻止,否则 y - 1 345 | if(y > 0){ 346 | y -= 1 347 | this.edit.hideEdit() 348 | 349 | this.activeCell = this.cells[x][y] 350 | this.cancelSelectCell() 351 | this.selectCells = [this.activeCell] 352 | this.selectedCell.change(this.selectCells,{ 353 | cellWidth:this.currentObj.cellWidth, 354 | cellHeight:this.currentObj.cellHeight 355 | }) 356 | this.emit("moveCell",{data:this.cells[x][y].data}) 357 | } 358 | break; 359 | case 40: 360 | //下 361 | if(!this.isActive){ 362 | return false 363 | } 364 | preventDefault(event) 365 | if(y < this.currentObj.row - 1){ 366 | y += 1 367 | this.edit.hideEdit() 368 | 369 | this.activeCell = this.cells[x][y] 370 | this.cancelSelectCell() 371 | this.selectCells = [this.activeCell] 372 | this.selectedCell.change(this.selectCells,{ 373 | cellWidth:this.currentObj.cellWidth, 374 | cellHeight:this.currentObj.cellHeight 375 | }) 376 | this.emit("moveCell",{data:this.cells[x][y].data}) 377 | } 378 | break; 379 | case 37: 380 | //左 381 | if(!this.isActive){ 382 | return false 383 | } 384 | preventDefault(event) 385 | if(x > 0){ 386 | x -= 1 387 | this.edit.hideEdit() 388 | 389 | this.activeCell = this.cells[x][y] 390 | this.cancelSelectCell() 391 | this.selectCells = [this.activeCell] 392 | this.selectedCell.change(this.selectCells,{ 393 | cellWidth:this.currentObj.cellWidth, 394 | cellHeight:this.currentObj.cellHeight 395 | }) 396 | this.emit("moveCell",{data:this.cells[x][y].data}) 397 | } 398 | break; 399 | case 39: 400 | //右 401 | if(!this.isActive){ 402 | return false 403 | } 404 | preventDefault(event) 405 | if(x < this.currentObj.span - 1){ 406 | x += 1 407 | this.edit.hideEdit() 408 | 409 | this.activeCell = this.cells[x][y] 410 | this.cancelSelectCell() 411 | this.selectCells = [this.activeCell] 412 | this.selectedCell.change(this.selectCells,{ 413 | cellWidth:this.currentObj.cellWidth, 414 | cellHeight:this.currentObj.cellHeight 415 | }) 416 | this.emit("moveCell",{data:this.cells[x][y].data}) 417 | } 418 | break; 419 | case 13: 420 | //回车 421 | this.edit.hideEdit() 422 | break; 423 | case 8: 424 | case 46: 425 | //删除 426 | if(!this.edit.editFlag){ 427 | this.selectCells.forEach(cell => { 428 | cell.clear() 429 | }) 430 | } 431 | break; 432 | case 67: 433 | //ctrl+c 434 | if(event.ctrlKey){ 435 | this.setCopyCell() 436 | } 437 | break; 438 | case 86: 439 | //ctrl+v 440 | if(event.ctrlKey){ 441 | this.pastCopyCell() 442 | } 443 | break; 444 | } 445 | } 446 | removeMethods(event){ 447 | if(event.target != this.edit.editEle){ 448 | this.edit.hideEdit() 449 | } 450 | 451 | if(this.handleTableMouseMove){ 452 | this.table.off('mousemove',this.handleTableMouseMove) 453 | } 454 | if(this.handleHeaderMouseMove && this.canvas){ 455 | this.canvas.off('mousemove',this.handleHeaderMouseMove) 456 | } 457 | if(this.handleIndexMouseMove && this.canvas){ 458 | this.canvas.off('mousemove',this.handleIndexMouseMove) 459 | } 460 | 461 | this.isActive = false 462 | 463 | let list = event.composedPath() 464 | for(let i = 0;i item.x)) 517 | let copyMin = Math.min(...this.copyCells.map(item => item.x)) 518 | let copyYMin = Math.min(...this.copyCells.map(item => item.y)) 519 | let copyYMax = Math.max(...this.copyCells.map(item => item.y)) 520 | let copySpan = copyMax - copyMin + 1 521 | let copyRow = this.copyCells.length / copySpan 522 | 523 | if(this.copyCells.length == 1 && this.copyCells[0].mergeConfig){ 524 | copyMax = this.copyCells[0].mergeConfig.xend 525 | copyMin = this.copyCells[0].mergeConfig.xstart 526 | copyYMin = this.copyCells[0].mergeConfig.ystart 527 | copyYMax = this.copyCells[0].mergeConfig.yend 528 | copySpan = copyMax - copyMin + 1 529 | copyRow = copyYMax - copyYMin + 1 530 | 531 | this.copyCells = [] 532 | for(let i = copyMin;i<=copyMax;i++){ 533 | for(let j = copyYMin;j<=copyYMax;j++){ 534 | let obj = zrender.util.clone(this.cells[i][j].data) 535 | delete obj.cellHeight 536 | delete obj.cellWidth 537 | delete obj.xPlace 538 | delete obj.yPlace 539 | 540 | this.copyCells.push(obj) 541 | } 542 | } 543 | } 544 | 545 | let tempSelect = [] 546 | let selectMax = Math.max(...this.selectCells.map(item => item.data.x)) 547 | let selectMin = Math.min(...this.selectCells.map(item => item.data.x)) 548 | let selectYMin = Math.min(...this.selectCells.map(item => item.data.y)) 549 | let selectYMax = Math.max(...this.selectCells.map(item => item.data.y)) 550 | let selectSpan = selectMax - selectMin + 1 551 | let selectRow = this.selectCells.length / selectSpan 552 | 553 | //如果选择的不是复制的整数倍的话,就去掉几个格子 554 | if(selectRow / copyRow < 1||selectSpan / copySpan < 1){ 555 | alert('复制粘贴的位置不够,请重新选择') 556 | return false 557 | } 558 | 559 | if(selectRow % copyRow != 0){ 560 | selectYMax -= selectRow % copyRow 561 | selectRow -= selectRow % copyRow 562 | } 563 | if(selectSpan % copySpan != 0){ 564 | selectMax -= selectSpan % copySpan 565 | selectSpan -= selectSpan % copySpan 566 | } 567 | 568 | this.selectCells.forEach(cell => { 569 | cell.unSelectCell() 570 | }) 571 | this.selectCells = [] 572 | for(let i = selectMin;i<=selectMax;i++){ 573 | for(let j = selectYMin;j<=selectYMax;j++){ 574 | this.selectCells.push(this.cells[i][j]) 575 | } 576 | } 577 | 578 | for(let i = 0;i < copyRow;i++){ 579 | copyTemp.push([]) 580 | for(let j = 0;j { 639 | cell.selectCell() 640 | }) 641 | this.activeCell.unSelectCell() 642 | this.copyCells = [] 643 | this.canvas.remove(this.copyedCell) 644 | this.copyedCell = null 645 | } 646 | addTableHeaderCell(config){ 647 | let headCell = new TableHeaderCell(config) 648 | this.tableHeaderCell.splice(config.index,0,headCell) 649 | this.tableHeader.add(headCell) 650 | headCell.addEvent('dragLine',(event)=>{ 651 | if(this.changeWidthLine){ 652 | this.changeWidthLine.attr({shape:{ 653 | x1:event.offsetX, 654 | y1:0, 655 | x2:event.offsetX, 656 | y2:this.canvas.getHeight() 657 | }}) 658 | }else{ 659 | this.changeWidthLine = new zrender.Line({ 660 | z:1001, 661 | shape:{ 662 | x1:event.offsetX, 663 | y1:0, 664 | x2:event.offsetX, 665 | y2:this.canvas.getHeight() 666 | }, 667 | style:{ 668 | stroke: '#4e9fff', 669 | fill: 'none', 670 | lineWidth:'1', 671 | } 672 | }) 673 | this.canvas.add(this.changeWidthLine) 674 | } 675 | }) 676 | headCell.addEvent('changeSize',(event) => { 677 | this.canvas.remove(this.changeWidthLine) 678 | this.changeWidthLine = null 679 | this.refreshCell() 680 | }) 681 | } 682 | initTableHeader(){ 683 | this.tableHeader = new zrender.Group() 684 | this.canvas.add(this.tableHeader) 685 | for(let i = 0;i { 693 | //隐藏编辑框 694 | this.edit.hideEdit() 695 | if(event.target.type != 'headerBorder'){ 696 | return false 697 | } 698 | this.cancelSelectCell() 699 | //选中的是哪一列 700 | let index = event.target.parent.data.index 701 | this.selectCells = [] 702 | //更新activeCell和selectCells 703 | for(let x = 0;x < this.cells.length;x++){ 704 | if(x == index){ 705 | this.selectCells = this.cells[x] 706 | } 707 | } 708 | //选择列头 以及 单元格 709 | //设置激活的单元格尾当前单击的单元格 710 | this.activeCell = this.selectCells[0] 711 | this.selectCells.forEach(cell => { 712 | cell.selectCell() 713 | }) 714 | this.activeCell.unSelectCell() 715 | //初始化选中的蓝色框框 716 | if(this.selectedCell){ 717 | //如果有选择框了就更新位置 718 | this.selectedCell.change(this.selectCells,{ 719 | cellWidth:this.currentObj.cellWidth, 720 | cellHeight:this.currentObj.cellHeight 721 | }) 722 | }else{ 723 | //如果没有选择框就创建一个 724 | this.selectedCell = new SelectCell(this.selectCells,{ 725 | cellWidth:this.currentObj.cellWidth, 726 | cellHeight:this.currentObj.cellHeight 727 | }) 728 | this.canvas.add(this.selectedCell) 729 | } 730 | //更新一下列和行的选择状态 731 | this.changeHeaderAndIndexState(this.selectCells) 732 | 733 | //绑定鼠标移动事件 734 | this.canvas.on('mousemove',this.handleHeaderMouseMove,this) 735 | }) 736 | } 737 | handleHeaderMouseMove(event){ 738 | if(event.target){ 739 | let targetIndex 740 | let activeIndex 741 | let start 742 | let end 743 | switch (event.target.type){ 744 | case 'headerBorder': 745 | targetIndex = event.target.parent.data.index 746 | activeIndex = this.activeCell.data.x 747 | if(targetIndex != activeIndex){ 748 | this.cancelSelectCell() 749 | this.selectCells = [] 750 | if(targetIndex > activeIndex){ 751 | start = activeIndex 752 | end = targetIndex 753 | }else{ 754 | start = targetIndex 755 | end = activeIndex 756 | } 757 | for(let x = start;x <= end;x++){ 758 | this.selectCells.push(...this.cells[x]) 759 | } 760 | this.updateSelectState() 761 | } 762 | break; 763 | case 'cellborder': 764 | targetIndex = event.target.parent.data.x 765 | activeIndex = this.activeCell.data.x 766 | if(targetIndex != activeIndex){ 767 | this.cancelSelectCell() 768 | this.selectCells = [] 769 | if(targetIndex > activeIndex){ 770 | start = activeIndex 771 | end = targetIndex 772 | }else{ 773 | start = targetIndex 774 | end = activeIndex 775 | } 776 | for(let x = start;x <= end;x++){ 777 | this.selectCells.push(...this.cells[x]) 778 | } 779 | this.updateSelectState() 780 | } 781 | break; 782 | } 783 | } 784 | } 785 | addTableIndexCell(config){ 786 | let indexCell = new TableIndexCell(config) 787 | this.tableIndexCell.splice(config.index,0,indexCell) 788 | this.tableIndex.add(indexCell) 789 | 790 | indexCell.addEvent('dragLine',(event)=>{ 791 | if(this.changeWidthLine){ 792 | this.changeWidthLine.attr({shape:{ 793 | x1:0, 794 | y1:event.offsetY, 795 | x2:this.canvas.getWidth(), 796 | y2:event.offsetY 797 | }}) 798 | }else{ 799 | this.changeWidthLine = new zrender.Line({ 800 | z:1001, 801 | shape:{ 802 | x1:0, 803 | y1:event.offsetY, 804 | x2:this.canvas.getWidth(), 805 | y2:event.offsetY 806 | }, 807 | style:{ 808 | stroke: '#4e9fff', 809 | fill: 'none', 810 | lineWidth:'1', 811 | } 812 | }) 813 | this.canvas.add(this.changeWidthLine) 814 | } 815 | }) 816 | indexCell.addEvent('changeSize',(event) => { 817 | this.canvas.remove(this.changeWidthLine) 818 | this.changeWidthLine = null 819 | this.refreshCell() 820 | }) 821 | } 822 | initTableIndex(){ 823 | this.tableIndex = new zrender.Group() 824 | this.canvas.add(this.tableIndex) 825 | for(let i = 0;i < this.currentObj.row;i++){ 826 | this.addTableIndexCell({ 827 | cellWidth:indexWidth, 828 | cellHeight:this.currentObj.cellHeight, 829 | index:i 830 | }) 831 | } 832 | this.tableIndex.on('mousedown',(event) => { 833 | //隐藏编辑框 834 | this.edit.hideEdit() 835 | if(event.target.type != 'indexBorder'){ 836 | return false 837 | } 838 | this.cancelSelectCell() 839 | //选中的是哪一行 840 | let index = event.target.parent.data.index 841 | this.selectCells = [] 842 | //更新activeCell和selectCells 843 | for(let x = 0;x < this.cells.length;x++){ 844 | for(let y = 0;y < this.cells[x].length;y++){ 845 | if(y == index){ 846 | this.selectCells.push(this.cells[x][y]) 847 | } 848 | } 849 | } 850 | //选择列头 以及 单元格 851 | //设置激活的单元格尾当前单击的单元格 852 | this.activeCell = this.selectCells[0] 853 | this.selectCells.forEach(cell => { 854 | cell.selectCell() 855 | }) 856 | this.activeCell.unSelectCell() 857 | //初始化选中的蓝色框框 858 | if(this.selectedCell){ 859 | //如果有选择框了就更新位置 860 | this.selectedCell.change(this.selectCells,{ 861 | cellWidth:this.currentObj.cellWidth, 862 | cellHeight:this.currentObj.cellHeight 863 | }) 864 | }else{ 865 | //如果没有选择框就创建一个 866 | this.selectedCell = new SelectCell(this.selectCells,{ 867 | cellWidth:this.currentObj.cellWidth, 868 | cellHeight:this.currentObj.cellHeight 869 | }) 870 | this.canvas.add(this.selectedCell) 871 | } 872 | //更新一下列和行的选择状态 873 | this.changeHeaderAndIndexState(this.selectCells) 874 | 875 | //绑定鼠标移动事件 876 | this.canvas.on('mousemove',this.handleIndexMouseMove,this) 877 | }) 878 | } 879 | handleIndexMouseMove(event){ 880 | if(event.target){ 881 | let targetIndex 882 | let activeIndex 883 | let start 884 | let end 885 | switch (event.target.type){ 886 | case 'indexBorder': 887 | targetIndex = event.target.parent.data.index 888 | activeIndex = this.activeCell.data.y 889 | if(targetIndex != activeIndex){ 890 | this.cancelSelectCell() 891 | this.selectCells = [] 892 | if(targetIndex > activeIndex){ 893 | start = activeIndex 894 | end = targetIndex 895 | }else{ 896 | start = targetIndex 897 | end = activeIndex 898 | } 899 | for(let x = 0; x < this.cells.length;x++){ 900 | for(let y = start;y<=end;y++){ 901 | this.selectCells.push(this.cells[x][y]) 902 | } 903 | } 904 | this.updateSelectState() 905 | } 906 | break; 907 | case 'cellborder': 908 | targetIndex = event.target.parent.data.y 909 | activeIndex = this.activeCell.data.y 910 | if(targetIndex != activeIndex){ 911 | this.cancelSelectCell() 912 | this.selectCells = [] 913 | if(targetIndex > activeIndex){ 914 | start = activeIndex 915 | end = targetIndex 916 | }else{ 917 | start = targetIndex 918 | end = activeIndex 919 | } 920 | for(let x = 0; x < this.cells.length;x++){ 921 | for(let y = start;y<=end;y++){ 922 | this.selectCells.push(this.cells[x][y]) 923 | } 924 | } 925 | this.updateSelectState() 926 | } 927 | break; 928 | } 929 | } 930 | } 931 | changeHeaderAndIndexState(cells){ 932 | if(cells.length == 1){ 933 | this.tableHeaderCell[cells[0].data.x].selectCell() 934 | this.tableIndexCell[cells[0].data.y].selectCell() 935 | }else{ 936 | //计算应该选择的行开始和结束位置 937 | let xstart = cells[0].data.x 938 | let ystart = cells[0].data.y 939 | let xend = cells[cells.length - 1].data.x 940 | let yend = cells[cells.length - 1].data.y 941 | for(let i = xstart;i <= xend;i++){ 942 | this.tableHeaderCell[i].selectCell() 943 | } 944 | for(let i = ystart;i <= yend;i++){ 945 | this.tableIndexCell[i].selectCell() 946 | } 947 | } 948 | } 949 | //初始化滚动条 950 | initScroll(parent){ 951 | //计算纵向的高度 952 | const tableHeight = this.table.getBoundingRect().height 953 | //计算纵向显示高度 954 | const tableWrapperHeight = this.canvas.getHeight() - headerHeight 955 | //计算横向的宽度 956 | const tableWidth = this.table.getBoundingRect().width 957 | //计算横向显示宽度 958 | const tableWrapperWidth = this.canvas.getWidth() - indexWidth 959 | 960 | this.scroll = new Scroll({ 961 | fullHeight:tableHeight, 962 | wrapHeight:tableWrapperHeight, 963 | fullWidth:tableWidth, 964 | wrapWidth:tableWrapperWidth, 965 | parent:parent 966 | }) 967 | this.scroll.on('scrollY',(e) => { 968 | //隐藏编辑框 969 | this.edit.hideEdit() 970 | let positionX = this.table.position[0] 971 | let moveY = e.pageMove 972 | if(moveY > 0){ 973 | moveY = 0 974 | } 975 | if(moveY < -(this.scroll.config.fullHeight - this.scroll.config.wrapHeight + scrollWidth)){ 976 | moveY = -(this.scroll.config.fullHeight - this.scroll.config.wrapHeight + scrollWidth) 977 | } 978 | this.table.attr('position',[positionX, moveY]) 979 | let positionIndexX = this.tableIndex.position[0] 980 | this.tableIndex.attr('position',[positionIndexX,moveY]) 981 | if(this.selectedCell){ 982 | this.selectedCell.attr('position',[positionX,moveY]) 983 | } 984 | if(this.copyedCell){ 985 | this.copyedCell.attr('position',[positionX,moveY]) 986 | } 987 | }) 988 | this.scroll.on('scrollX',(e) => { 989 | //隐藏编辑框 990 | this.edit.hideEdit() 991 | let positionY = this.table.position[1] 992 | let moveX = e.pageMove 993 | if(moveX > 0){ 994 | moveX = 0 995 | } 996 | if(moveX < -(this.scroll.config.fullWidth - this.scroll.config.wrapWidth + scrollWidth)){ 997 | moveX = -(this.scroll.config.fullWidth - this.scroll.config.wrapWidth + scrollWidth) 998 | } 999 | this.table.attr('position',[moveX, positionY]) 1000 | let positionHeaderY = this.tableHeader.position[1] 1001 | this.tableHeader.attr('position',[moveX, positionHeaderY]) 1002 | if(this.selectedCell){ 1003 | this.selectedCell.attr('position',[moveX, positionY]) 1004 | } 1005 | if(this.copyedCell){ 1006 | this.copyedCell.attr('position',[moveX,positionY]) 1007 | } 1008 | }) 1009 | this.canvas.on('mousewheel',(event) => { 1010 | //判断需不需要滚动 1011 | if(this.scroll.config.fullHeight < this.scroll.config.wrapHeight){ 1012 | return false 1013 | } 1014 | //隐藏编辑框 1015 | this.edit.hideEdit() 1016 | if(mouseWheelDirection(event.event)){ 1017 | //table滚动 1018 | //index滚动 1019 | let positionX = this.table.position[0] 1020 | let positionY = this.table.position[1] 1021 | let moveY = positionY + 20 1022 | if(moveY > 0){ 1023 | moveY = 0 1024 | } 1025 | this.scroll.scrollY(moveY) 1026 | this.table.attr('position',[positionX, moveY]) 1027 | let positionIndexX = this.tableIndex.position[0] 1028 | this.tableIndex.attr('position',[positionIndexX,moveY]) 1029 | if(this.selectedCell){ 1030 | this.selectedCell.attr('position',[positionX,moveY]) 1031 | } 1032 | if(this.copyedCell){ 1033 | this.copyedCell.attr('position',[positionX,moveY]) 1034 | } 1035 | //更新滚动条位置 1036 | }else{ 1037 | let positionX = this.table.position[0] 1038 | let positionY = this.table.position[1] 1039 | let moveY = positionY - 20 1040 | if(moveY < -(this.scroll.config.fullHeight - this.scroll.config.wrapHeight + scrollWidth)){ 1041 | moveY = -(this.scroll.config.fullHeight - this.scroll.config.wrapHeight + scrollWidth) 1042 | } 1043 | this.scroll.scrollY(moveY) 1044 | this.table.attr('position',[positionX,moveY]) 1045 | let positionIndexX = this.tableIndex.position[0] 1046 | this.tableIndex.attr('position',[positionIndexX,moveY]) 1047 | if(this.selectedCell){ 1048 | this.selectedCell.attr('position',[positionX,moveY]) 1049 | } 1050 | if(this.copyedCell){ 1051 | this.copyedCell.attr('position',[positionX,moveY]) 1052 | } 1053 | } 1054 | }) 1055 | } 1056 | initEdit(parent){ 1057 | this.edit = new Edit(parent) 1058 | this.edit.editEle.addEventListener('click',(e) => { 1059 | stopPropagation(e) 1060 | }) 1061 | this.edit.on('update',(event) => { 1062 | if(event.type == 'text'){ 1063 | this.activeCell.setText(event.text) 1064 | } 1065 | }) 1066 | } 1067 | initContextMenu(parent){ 1068 | this.contextMenu = new ContextMenu(parent) 1069 | //添加菜单 1070 | 1071 | const addSpan = this.contextMenu.addButton('插入列',()=>{ 1072 | //tableHeaderCell插入列 1073 | const index = this.activeCell.data.x 1074 | this.addTableHeaderCell({ 1075 | cellWidth:this.currentObj.cellWidth, 1076 | cellHeight:headerHeight, 1077 | index:index + 1 1078 | }) 1079 | //更新所有tableHeaderCell的data和位置 1080 | for(let i = index;i{ 1097 | this.emit("changeCell",event) 1098 | }) 1099 | this.table.add(insertArr[y]) 1100 | } 1101 | this.cells.splice(index+1,0,insertArr) 1102 | //更新所有cells的xy 1103 | for(let x = 0;x 0 &&this.cells[x][y].data.span > 0){ 1108 | //如果index在merge的单元格之中 1109 | if(index >= this.cells[x][y].data.mergeConfig.xstart && index < this.cells[x][y].data.mergeConfig.xend){ 1110 | this.cells[x][y].data.mergeConfig.xend += 1 1111 | this.cells[x][y].data.span += 1 1112 | for(let m = this.cells[x][y].data.mergeConfig.ystart;m<=this.cells[x][y].data.mergeConfig.yend;m++){ 1113 | this.cells[index+1][m].setData({ 1114 | row:0, 1115 | span:0, 1116 | merge:true 1117 | }) 1118 | } 1119 | } 1120 | //如果index在merge之前 1121 | if(index < this.cells[x][y].data.mergeConfig.xstart){ 1122 | this.cells[x][y].data.mergeConfig.xstart += 1 1123 | this.cells[x][y].data.mergeConfig.xend += 1 1124 | } 1125 | } 1126 | } 1127 | } 1128 | //更新整个视图 1129 | this.refreshCell() 1130 | }) 1131 | 1132 | const addRow = this.contextMenu.addButton('插入行',()=>{ 1133 | //tableHeaderCell插入列 1134 | const index = this.activeCell.data.y 1135 | this.addTableIndexCell({ 1136 | cellWidth:indexWidth, 1137 | cellHeight:this.currentObj.cellHeight, 1138 | index:index + 1 1139 | }) 1140 | //更新所有tableHeaderCell的data和位置 1141 | for(let i = index;i{ 1157 | this.emit("changeCell",event) 1158 | }) 1159 | this.cells[x].splice(index+1,0,insert) 1160 | this.table.add(insert) 1161 | } 1162 | //更新所有cells的xy 1163 | for(let x = 0;x 0 &&this.cells[x][y].data.span > 0){ 1168 | //如果index在merge的单元格之中 1169 | if(index >= this.cells[x][y].data.mergeConfig.ystart && index < this.cells[x][y].data.mergeConfig.yend){ 1170 | this.cells[x][y].data.mergeConfig.yend += 1 1171 | this.cells[x][y].data.row += 1 1172 | for(let m = this.cells[x][y].data.mergeConfig.xstart;m<=this.cells[x][y].data.mergeConfig.xend;m++){ 1173 | this.cells[m][index + 1].setData({ 1174 | row:0, 1175 | span:0, 1176 | merge:true 1177 | }) 1178 | } 1179 | } 1180 | //如果index在merge之前 1181 | if(index < this.cells[x][y].data.mergeConfig.ystart){ 1182 | this.cells[x][y].data.mergeConfig.ystart += 1 1183 | this.cells[x][y].data.mergeConfig.yend += 1 1184 | } 1185 | } 1186 | } 1187 | } 1188 | //更新整个视图 1189 | this.refreshCell() 1190 | }) 1191 | 1192 | const addImage = this.contextMenu.addButton('插入图片',()=>{ 1193 | this.uploadFile.open() 1194 | }) 1195 | 1196 | const clearInputBtn = this.contextMenu.addButton('清除内容',() => { 1197 | this.selectCells.forEach(cell => { 1198 | cell.clear() 1199 | }) 1200 | }) 1201 | 1202 | //合并单元格 1203 | const mergeCells = this.contextMenu.addButton('合并单元格',()=>{ 1204 | this.mergeCells() 1205 | }) 1206 | 1207 | //取消合并单元格 1208 | const splitCell = this.contextMenu.addButton('取消合并单元格',() => { 1209 | this.splitCell() 1210 | }) 1211 | 1212 | //删除列 1213 | const deleteSpan = this.contextMenu.addButton('删除列',() => { 1214 | //先把列删掉 1215 | const index = this.activeCell.data.x 1216 | for(let y = 0;y 1 &&this.cells[x][y].data.span > 1){ 1226 | //如果index在merge的单元格之中 1227 | if(index >= this.cells[x][y].data.mergeConfig.xstart && index <= this.cells[x][y].data.mergeConfig.xend){ 1228 | this.cells[x][y].data.mergeConfig.xend -= 1 1229 | this.cells[x][y].data.span -= 1 1230 | //如果被删掉的刚好是合并的,那就设置相邻的单元格顶替 1231 | if(index == this.cells[x][y].data.mergeConfig.xstart || index == this.cells[x][y].data.mergeConfig.xend){ 1232 | this.cells[this.cells[x][y].data.mergeConfig.xstart + 1][y].setData({ 1233 | row:this.cells[x][y].data.row, 1234 | span:this.cells[x][y].data.span, 1235 | mergeConfig:{ 1236 | xstart:this.cells[x][y].data.mergeConfig.xstart, 1237 | xend:this.cells[x][y].data.mergeConfig.xend, 1238 | ystart:this.cells[x][y].data.mergeConfig.ystart, 1239 | yend:this.cells[x][y].data.mergeConfig.yend, 1240 | } 1241 | }) 1242 | x++ 1243 | } 1244 | } 1245 | //如果index在merge之前 1246 | if(index < this.cells[x][y].data.mergeConfig.xstart){ 1247 | this.cells[x][y].data.mergeConfig.xstart -= 1 1248 | this.cells[x][y].data.mergeConfig.xend -= 1 1249 | } 1250 | } 1251 | } 1252 | } 1253 | 1254 | this.cells.splice(index,1) 1255 | //更新所有cells的xy 1256 | for(let x = 0;x { 1275 | //先把行删掉 1276 | const index = this.activeCell.data.y 1277 | for(let x = 0;x 1 &&this.cells[x][y].data.span > 1){ 1287 | //如果index在merge的单元格之中 1288 | if(index >= this.cells[x][y].data.mergeConfig.ystart && index <= this.cells[x][y].data.mergeConfig.yend){ 1289 | this.cells[x][y].data.mergeConfig.yend -= 1 1290 | this.cells[x][y].data.row -= 1 1291 | //如果被删掉的刚好是合并的,那就设置相邻的单元格顶替 1292 | if(index == this.cells[x][y].data.mergeConfig.ystart || index == this.cells[x][y].data.mergeConfig.yend){ 1293 | this.cells[x][this.cells[x][y].data.mergeConfig.ystart + 1].setData({ 1294 | row:this.cells[x][y].data.row, 1295 | span:this.cells[x][y].data.span, 1296 | mergeConfig:{ 1297 | xstart:this.cells[x][y].data.mergeConfig.xstart, 1298 | xend:this.cells[x][y].data.mergeConfig.xend, 1299 | ystart:this.cells[x][y].data.mergeConfig.ystart, 1300 | yend:this.cells[x][y].data.mergeConfig.yend, 1301 | } 1302 | }) 1303 | y++ 1304 | } 1305 | } 1306 | //如果index在merge之前 1307 | if(index < this.cells[x][y].data.mergeConfig.ystart){ 1308 | this.cells[x][y].data.mergeConfig.ystart -= 1 1309 | this.cells[x][y].data.mergeConfig.yend -= 1 1310 | } 1311 | } 1312 | } 1313 | } 1314 | 1315 | for(let x = 0;x { 1339 | this.setCopyCell() 1340 | }) 1341 | 1342 | //粘贴 1343 | const paste = this.contextMenu.addButton('粘贴',() => { 1344 | this.pastCopyCell() 1345 | }) 1346 | 1347 | //右键菜单 1348 | this.canvas.on("contextmenu",(event) => { 1349 | preventDefault(event.event) 1350 | //判断当前点的cell有没有在selectCells里面 1351 | if(event.target.parent && event.target.parent.type == 'cell'){ 1352 | if(this.selectCells.includes(event.target.parent)){ 1353 | 1354 | }else{ 1355 | this.edit.hideEdit() 1356 | this.cancelSelectCell() 1357 | this.activeCell = event.target.parent 1358 | this.selectCells = [event.target.parent] 1359 | if(this.selectedCell){ 1360 | //如果有选择框了就更新位置 1361 | this.selectedCell.change(this.selectCells,{ 1362 | cellWidth:this.currentObj.cellWidth, 1363 | cellHeight:this.currentObj.cellHeight 1364 | }) 1365 | }else{ 1366 | //如果没有选择框就创建一个 1367 | this.selectedCell = new SelectCell(this.selectCells,{ 1368 | cellWidth:this.currentObj.cellWidth, 1369 | cellHeight:this.currentObj.cellHeight 1370 | }) 1371 | this.canvas.add(this.selectedCell) 1372 | } 1373 | //更新一下列和行的选择状态 1374 | this.changeHeaderAndIndexState(this.selectCells) 1375 | } 1376 | //判断选中了几个单元格,如果选中了多个就显示合并单元格按钮 1377 | if(this.selectCells.length > 1){ 1378 | addImage.style.display='none' 1379 | if(this.selectCells.every(cell => {return cell.data.merge == false})){ 1380 | mergeCells.style.display = "block" 1381 | splitCell.style.display = "none" 1382 | }else{ 1383 | mergeCells.style.display = "none" 1384 | splitCell.style.display = "block" 1385 | } 1386 | }else{ 1387 | mergeCells.style.display = "none" 1388 | addImage.style.display='block' 1389 | if(this.selectCells[0].data.merge == true){ 1390 | splitCell.style.display = "block" 1391 | }else{ 1392 | splitCell.style.display = "none" 1393 | } 1394 | } 1395 | } 1396 | //如果有就直接弹出menu 1397 | //如果没有就重新设置一下selectCells以及activeCell 1398 | this.contextMenu.showMenu(event.offsetX,event.offsetY) 1399 | }) 1400 | this.hideMenu = this.hideMenu.bind(this) 1401 | document.addEventListener('click',this.hideMenu) 1402 | } 1403 | hideMenu(){ 1404 | this.contextMenu.hideMenu() 1405 | } 1406 | //重新绘制cell 1407 | refreshCell(){ 1408 | //重绘headerCell 1409 | this.refreshTableHeaderCell() 1410 | //重绘indexCell 1411 | this.refreshTableIndexCell() 1412 | //重绘tableCell 1413 | this.refreshTableCell() 1414 | //刷新选择框 1415 | if(this.selectCells.length > 0 && this.selectedCell){ 1416 | this.selectedCell.change(this.selectCells) 1417 | this.selectCells.forEach(cell => { 1418 | cell.selectCell() 1419 | }) 1420 | this.activeCell.unSelectCell() 1421 | } 1422 | //刷新复制框 1423 | if(this.copyedCell){ 1424 | this.copyCells = [] 1425 | this.canvas.remove(this.copyedCell) 1426 | this.copyedCell = null 1427 | } 1428 | //刷新滚动条 1429 | this.refreshScroll() 1430 | } 1431 | refreshTableHeaderCell(){ 1432 | let x = indexWidth 1433 | this.tableHeaderCell.forEach(cell => { 1434 | cell.setData({ 1435 | xPlace:x 1436 | }) 1437 | cell.refresh() 1438 | x += cell.data.width 1439 | }) 1440 | } 1441 | refreshTableIndexCell(){ 1442 | let y = headerHeight 1443 | this.tableIndexCell.forEach(cell => { 1444 | cell.setData({ 1445 | yPlace:y 1446 | }) 1447 | cell.refresh() 1448 | y += cell.data.height 1449 | }) 1450 | } 1451 | refreshTableCell(){ 1452 | for(let x = 0;x {return cell.data.merge == false})){ 1512 | 1513 | }else{ 1514 | return false 1515 | } 1516 | //设置activeCell的row和span 1517 | //求最小下标以及最大下标 1518 | let xstart = this.selectCells[0].data.x 1519 | let ystart = this.selectCells[0].data.y 1520 | let xPlace = this.selectCells[0].data.xPlace 1521 | let yPlace = this.selectCells[0].data.yPlace 1522 | let xend = this.selectCells[this.selectCells.length - 1].data.x 1523 | let yend = this.selectCells[this.selectCells.length - 1].data.y 1524 | 1525 | let row = yend - ystart + 1 1526 | let span = xend - xstart + 1 1527 | 1528 | let width = 0 1529 | let height = 0 1530 | 1531 | for(let i = xstart;i<=xend;i++){ 1532 | width += this.tableHeaderCell[i].data.width 1533 | } 1534 | 1535 | for(let i = ystart;i<=yend;i++){ 1536 | height += this.tableIndexCell[i].data.height 1537 | } 1538 | 1539 | this.activeCell.setData({ 1540 | row:row, 1541 | span:span, 1542 | merge:true, 1543 | cellWidth:width, 1544 | cellHeight:height, 1545 | xPlace:xPlace, 1546 | yPlace:yPlace, 1547 | mergeConfig:{ 1548 | xstart:xstart, 1549 | ystart:ystart, 1550 | xend:xend, 1551 | yend:yend 1552 | } 1553 | }) 1554 | 1555 | //其余的selectCells全部隐藏不显示,被merge 1556 | this.selectCells.forEach(cell => { 1557 | if(cell != this.activeCell){ 1558 | cell.setData({ 1559 | row:0, 1560 | span:0, 1561 | merge:true, 1562 | }) 1563 | } 1564 | }) 1565 | 1566 | //重新设置activeCell和selectCells 1567 | this.selectCells = [this.activeCell] 1568 | } 1569 | splitCell(){ 1570 | //遍历一遍当前合并的单元格,重新设置他们的位置以及大小 1571 | let xstart = this.selectCells[0].data.x 1572 | let ystart = this.selectCells[0].data.y 1573 | let xend = this.selectCells[this.selectCells.length - 1].data.x 1574 | let yend = this.selectCells[this.selectCells.length - 1].data.y 1575 | 1576 | for(let x = xstart;x<=xend;x++){ 1577 | for(let y=ystart;y<=yend;y++){ 1578 | if(this.cells[x][y].data.merge == true && this.cells[x][y].data.row + this.cells[x][y].data.span > 2){ 1579 | let x0 = this.cells[x][y].data.mergeConfig.xstart 1580 | let y0 = this.cells[x][y].data.mergeConfig.ystart 1581 | let x1 = this.cells[x][y].data.mergeConfig.xend 1582 | let y1 = this.cells[x][y].data.mergeConfig.yend 1583 | for(let i = x0;i<=x1;i++){ 1584 | for(let j=y0;j<=y1;j++){ 1585 | this.cells[i][j].setData({ 1586 | xPlace:this.tableHeaderCell[i].data.xPlace, 1587 | yPlace:this.tableIndexCell[j].data.yPlace, 1588 | cellWidth:this.tableHeaderCell[i].data.width, 1589 | cellHeight:this.tableIndexCell[j].data.height, 1590 | merge:false, 1591 | row:1, 1592 | span:1, 1593 | mergeConfig:null 1594 | }) 1595 | this.cells[i][j].show() 1596 | this.selectCells.push(this.cells[i][j]) 1597 | } 1598 | } 1599 | } 1600 | } 1601 | } 1602 | this.selectedCell.change(this.selectCells) 1603 | } 1604 | initToolBar(parent){ 1605 | this.toolBar = new ToolBar(parent) 1606 | this.toolBar.on('copy',(e) => { 1607 | this.setCopyCell() 1608 | }) 1609 | this.toolBar.on('paste',(e) => { 1610 | this.pastCopyCell() 1611 | }) 1612 | this.toolBar.on('clearFormat',(e) => { 1613 | this.selectCells.forEach(cell =>{ 1614 | cell.clearFormat() 1615 | }) 1616 | }) 1617 | this.toolBar.on('changeTypeFace',(e) => { 1618 | //更改selectCells的fontFamily 1619 | this.selectCells.forEach(cell => { 1620 | cell.setFontFamily(e.data) 1621 | }) 1622 | }) 1623 | this.toolBar.on('changeFontSize',(e) => { 1624 | this.selectCells.forEach(cell => { 1625 | cell.setFontSize(e.data) 1626 | }) 1627 | }) 1628 | this.toolBar.on('changeFontWeight',(e) => { 1629 | this.selectCells.forEach(cell => { 1630 | cell.setFontWeight(e.data) 1631 | }) 1632 | }) 1633 | this.toolBar.on('changeFontItalic',(e) => { 1634 | this.selectCells.forEach(cell => { 1635 | cell.setFontItalic(e.data) 1636 | }) 1637 | }) 1638 | this.toolBar.on('changeTextFill',(e) => { 1639 | this.selectCells.forEach(cell => { 1640 | cell.setTextFill(e.data) 1641 | }) 1642 | }) 1643 | this.toolBar.on('changeFill',(e) => { 1644 | this.selectCells.forEach(cell => { 1645 | cell.setFill(e.data) 1646 | }) 1647 | }) 1648 | this.toolBar.on('changeBorder',(e) =>{ 1649 | this.selectCells.forEach(cell => { 1650 | cell.setBorder(e.data) 1651 | }) 1652 | }) 1653 | this.toolBar.on('changeTextAlign',(e) => { 1654 | this.selectCells.forEach(cell => { 1655 | cell.setTextAlign(e.data) 1656 | }) 1657 | }) 1658 | this.toolBar.on('mergeCell',(e) => { 1659 | this.mergeCells() 1660 | }) 1661 | this.toolBar.on('splitCell',(e) => { 1662 | this.splitCell() 1663 | }) 1664 | this.toolBar.on('addImage',(e) => { 1665 | this.uploadFile.open() 1666 | }) 1667 | } 1668 | //改变列数 1669 | setSpanNum(number){ 1670 | //检测有没有这么多列,如果没有的话,就删除单元格和Header,如果没有的话就添加单元格和Header 1671 | if(number <= this.tableHeaderCell.length){ 1672 | while(number < this.tableHeaderCell.length){ 1673 | //tableHeaderCell 1674 | this.tableHeader.remove(this.tableHeaderCell.pop()) 1675 | //删除cells最末尾几列 1676 | for(let i=0;i this.tableHeaderCell.length){ 1686 | const index = this.tableHeaderCell.length 1687 | this.addTableHeaderCell({ 1688 | cellWidth:this.currentObj.cellWidth, 1689 | cellHeight:headerHeight, 1690 | index:index 1691 | }) 1692 | //cells插入列 1693 | let insertArr = new Array() 1694 | for(let y = 0;y < this.tableIndexCell.length;y++){ 1695 | insertArr[y] = new Cell({...{ 1696 | x:index, 1697 | y:y, 1698 | cellWidth:this.currentObj.cellWidth, 1699 | cellHeight:this.currentObj.cellHeight, 1700 | row:1, 1701 | span:1, 1702 | merge:false, 1703 | text:"" 1704 | },...this.textConfig}) 1705 | insertArr[y].addEvent('change',(event)=>{ 1706 | this.emit("changeCell",event) 1707 | }) 1708 | this.table.add(insertArr[y]) 1709 | } 1710 | this.cells.splice(index,0,insertArr) 1711 | } 1712 | //重新绘制 1713 | this.refreshCell() 1714 | } 1715 | } 1716 | //改变行数 1717 | setRowNum(number){ 1718 | //检测有没有这么多行,如果没有的话,就删除单元格和Index,如果没有的话就添加单元格和Index 1719 | if(number <= this.tableIndexCell.length){ 1720 | while(number < this.tableIndexCell.length){ 1721 | //删除tableIndexCell最末尾几个 1722 | this.tableIndex.remove(this.tableIndexCell.pop()) 1723 | //删除cells最末尾几行 1724 | for(let i=0;i this.tableIndexCell.length){ 1733 | //新增tableIndexCell 1734 | //新增cells 1735 | const index = this.tableIndexCell.length 1736 | this.addTableIndexCell({ 1737 | cellWidth:indexWidth, 1738 | cellHeight:this.currentObj.cellHeight, 1739 | index:index 1740 | }) 1741 | //cells插入行 1742 | for(let x = 0;x < this.cells.length;x++){ 1743 | let insert = new Cell({...{ 1744 | x:x, 1745 | y:index, 1746 | cellWidth:this.currentObj.cellWidth, 1747 | cellHeight:this.currentObj.cellHeight, 1748 | row:1, 1749 | span:1, 1750 | merge:false, 1751 | text:"" 1752 | },...this.textConfig}) 1753 | insert.addEvent('change',(event)=>{ 1754 | this.emit("changeCell",event) 1755 | }) 1756 | this.cells[x].splice(index,0,insert) 1757 | this.table.add(insert) 1758 | } 1759 | } 1760 | //重新绘制 1761 | this.refreshCell() 1762 | } 1763 | } 1764 | //获取全部数据 1765 | getTableDatas(){ 1766 | let data = [] 1767 | for(let i = 0;i { 1778 | item.setData({ 1779 | width:this.currentObj.cellWidth, 1780 | }) 1781 | }) 1782 | this.tableIndexCell.forEach(item => { 1783 | item.setData({ 1784 | height:this.currentObj.cellHeight, 1785 | }) 1786 | }) 1787 | for(let x = 0;x 2){ 1792 | let x0 = this.cells[x][y].data.mergeConfig.xstart 1793 | let y0 = this.cells[x][y].data.mergeConfig.ystart 1794 | let x1 = this.cells[x][y].data.mergeConfig.xend 1795 | let y1 = this.cells[x][y].data.mergeConfig.yend 1796 | for(let i = x0;i<=x1;i++){ 1797 | for(let j=y0;j<=y1;j++){ 1798 | this.cells[i][j].setData({ 1799 | xPlace:this.tableHeaderCell[i].data.xPlace, 1800 | yPlace:this.tableIndexCell[j].data.yPlace, 1801 | cellWidth:this.tableHeaderCell[i].data.width, 1802 | cellHeight:this.tableIndexCell[j].data.height, 1803 | merge:false, 1804 | row:1, 1805 | span:1, 1806 | mergeConfig:null 1807 | }) 1808 | this.cells[i][j].show() 1809 | this.selectCells.push(this.cells[i][j]) 1810 | } 1811 | } 1812 | } 1813 | } 1814 | } 1815 | this.refreshCell() 1816 | } 1817 | //批量填充全部数据 1818 | /* 1819 | * config = { 1820 | * data:Array 一维数组 或 二维数组,data中,x和y是必须的 1821 | * clear:Boolean 填充数据时是否清空其他数据 true:清空 false:不清空 1822 | * } 1823 | */ 1824 | setTableDatas(data,clear = false){ 1825 | //先检测data的长度,看有没有data,没有的话提示一下,有的话进行下一步 1826 | if(data){ 1827 | if(data instanceof Array){ 1828 | if(data.length > 0){ 1829 | if(clear){ 1830 | //先清空其他格子 1831 | this.cells.forEach(list => { 1832 | list.forEach(cell => { 1833 | cell.clear() 1834 | cell.clearFormat() 1835 | }) 1836 | }) 1837 | } 1838 | //判断data是一维数组还是二维数组 1839 | if(data[0] instanceof Array){ 1840 | //判断data最大的x,y如果大于现在的,那就添加几行几列 1841 | let arr = data.flat() 1842 | let maxX = Math.max(...arr.map(item => item.x)) 1843 | let maxY = Math.max(...arr.map(item => item.y)) 1844 | if(maxX > this.tableHeaderCell.length){ 1845 | this.setSpanNum(maxX+1) 1846 | } 1847 | if(maxY > this.tableIndexCell.length){ 1848 | this.setRowNum(maxY+1) 1849 | } 1850 | //二维 1851 | for(let i = 0;i 0 && data[i][j].span > 0){ 1855 | //计算正确的宽度,合并过的单元格宽度需要用总宽度减去被合并的宽度 1856 | let xstart = data[i][j].mergeConfig.xstart 1857 | let xend = data[i][j].mergeConfig.xend 1858 | let cellWidth = data[i][j].cellWidth 1859 | for(let x = 0;x= xstart && data[x][j].x <= xend && data[x][j].merge && data[x][j].span == 0){ 1861 | cellWidth -= data[x][j].cellWidth 1862 | } 1863 | } 1864 | if(this.tableHeaderCell[data[i][j].x].data.width < cellWidth){ 1865 | this.tableHeaderCell[data[i][j].x].setData({ 1866 | width:cellWidth 1867 | }) 1868 | } 1869 | }else{ 1870 | if(this.tableHeaderCell[data[i][j].x].data.width < data[i][j].cellWidth){ 1871 | this.tableHeaderCell[data[i][j].x].setData({ 1872 | width:data[i][j].cellWidth 1873 | }) 1874 | } 1875 | } 1876 | } 1877 | if(data[i][j].cellHeight){ 1878 | if(data[i][j].merge && data[i][j].row > 0 && data[i][j].span > 0){ 1879 | //计算正确的高度 1880 | let ystart = data[i][j].mergeConfig.ystart 1881 | let yend = data[i][j].mergeConfig.yend 1882 | let cellHeight = data[i][j].cellHeight 1883 | for(let y = 0;y= ystart && data[i][y].y <= yend && data[i][y].merge && data[i][y].row == 0){ 1885 | cellHeight -= data[i][y].cellHeight 1886 | } 1887 | } 1888 | if(this.tableIndexCell[data[i][j].y].data.height < cellHeight){ 1889 | this.tableIndexCell[data[i][j].y].setData({ 1890 | height:cellHeight 1891 | }) 1892 | } 1893 | }else{ 1894 | if(this.tableIndexCell[data[i][j].y].data.height < data[i][j].cellHeight){ 1895 | this.tableIndexCell[data[i][j].y].setData({ 1896 | height:data[i][j].cellHeight 1897 | }) 1898 | } 1899 | } 1900 | } 1901 | this.cells[data[i][j].x][data[i][j].y].setData({ 1902 | ...this.textConfig,...data[i][j] 1903 | }) 1904 | } 1905 | } 1906 | this.refreshCell() 1907 | }else{ 1908 | //判断data最大的x,y如果大于现在的,那就添加几行几列 1909 | let maxX = Math.max(data.map(item => item.x)) 1910 | let maxY = Math.max(data.map(item => item.y)) 1911 | if(maxX > this.tableHeaderCell.length){ 1912 | this.setSpanNum(maxX) 1913 | } 1914 | if(maxY > this.tableIndexCell.length){ 1915 | this.setRowNum(maxY) 1916 | } 1917 | //一维 1918 | for(let i = 0;i 0 && data[i].span > 0){ 1921 | //计算正确的宽度,合并过的单元格宽度需要用总宽度减去被合并的宽度 1922 | let xstart = data[i].mergeConfig.xstart 1923 | let xend = data[i].mergeConfig.xend 1924 | let cellWidth = data[i].cellWidth 1925 | for(let x = 0;x= xstart && data[x].x <= xend && data[x].merge && data[x].span == 0){ 1927 | cellWidth -= data[x].cellWidth 1928 | } 1929 | } 1930 | if(this.tableHeaderCell[data[i].x].data.width < cellWidth){ 1931 | this.tableHeaderCell[data[i].x].setData({ 1932 | width:cellWidth 1933 | }) 1934 | } 1935 | }else{ 1936 | if(this.tableHeaderCell[data[i].x].data.width < data[i].cellWidth){ 1937 | this.tableHeaderCell[data[i].x].setData({ 1938 | width:data[i].cellWidth 1939 | }) 1940 | } 1941 | } 1942 | } 1943 | if(data[i].cellHeight){ 1944 | if(data[i].merge && data[i].row > 0 && data[i].span > 0){ 1945 | //计算正确的高度 1946 | let ystart = data[i].mergeConfig.ystart 1947 | let yend = data[i].mergeConfig.yend 1948 | let cellHeight = data[i].cellHeight 1949 | for(let y = 0;y= ystart && data[y].y <= yend && data[y].merge && data[y].row == 0){ 1951 | cellHeight -= data[y].cellHeight 1952 | } 1953 | } 1954 | if(this.tableIndexCell[data[i].y].data.height < cellHeight){ 1955 | this.tableIndexCell[data[i].y].setData({ 1956 | height:cellHeight 1957 | }) 1958 | } 1959 | }else{ 1960 | if(this.tableIndexCell[data[i].y].data.height < data[i].cellHeight){ 1961 | this.tableIndexCell[data[i].y].setData({ 1962 | height:data[i].cellHeight 1963 | }) 1964 | } 1965 | } 1966 | } 1967 | this.cells[data[i].x][data[i].y].setData({ 1968 | ...this.textConfig,...data[i] 1969 | }) 1970 | } 1971 | this.refreshCell() 1972 | } 1973 | }else{ 1974 | return false 1975 | } 1976 | }else{ 1977 | return false 1978 | } 1979 | }else{ 1980 | return false 1981 | } 1982 | } 1983 | /**** 1984 | * 注销对象 1985 | * 1986 | */ 1987 | dispose(){ 1988 | this.canvas.dispose() 1989 | this.canvas = null 1990 | const parent = document.getElementById(this.currentObj.id) 1991 | console.log(parent) 1992 | if(parent){ 1993 | parent.innerHTML = "" 1994 | } 1995 | this.scroll.dispose() 1996 | document.removeEventListener('click',this.hideMenu) 1997 | document.removeEventListener('keydown',this.keydownMethod) 1998 | document.removeEventListener('mouseup',this.removeMethods) 1999 | this.currentObj = null 2000 | this.table = null 2001 | this.cells = null 2002 | this.activeCell = null 2003 | this.selectCells = null 2004 | this.copyCells = null 2005 | this.selectedCell = null 2006 | this.copyedCell = null 2007 | this.tableHeader = null 2008 | this.tableHeaderCell = null 2009 | this.tableIndex = null 2010 | this.tableIndexCell = null 2011 | this.selectAllCell = null 2012 | this.edit = null 2013 | this.contextMenu = null 2014 | this.changeWidthLine = null 2015 | this.uploadFile = null 2016 | this.toolBar = null 2017 | this.textConfig = null 2018 | } 2019 | } 2020 | 2021 | window.DaoDaoExcel = DaoDaoExcel 2022 | 2023 | export default DaoDaoExcel 2024 | -------------------------------------------------------------------------------- /src/scroll.js: -------------------------------------------------------------------------------- 1 | import Event from "./event.js" 2 | import {scrollWidth} from './config.js' 3 | 4 | class Scroll extends Event{ 5 | constructor(config){ 6 | const defaultConfig= { 7 | bgColor:'rgba(199,199,199,0.3)', 8 | hoverBgColor:'rgba(199,199,199,0.5)', 9 | scrollColor:'rgba(167,167,167,0.3)', 10 | scrollWidth:scrollWidth, 11 | hoverColor:'rgba(167,167,167,0.6)' 12 | } 13 | super() 14 | this.config = Object.assign({},defaultConfig,config) 15 | //纵向滚动条的包围盒 16 | this.scrollHeight = null 17 | //纵向滚动条的操纵条 18 | this.scrollHeightBtn = null 19 | //横向滚动条的包围盒 20 | this.scrollWidth = null 21 | //横向滚动条的操纵条 22 | this.scrollWidthBtn = null 23 | //鼠标点下去的时候的x,y坐标 24 | this.originMouseXY = [0,0] 25 | this.originScrollXY = [0,0] 26 | this.parent = config.parent 27 | this.init(config.parent) 28 | } 29 | init(parent){ 30 | this.initMethod = this.initMethod.bind(this) 31 | document.addEventListener('mouseup',this.initMethod) 32 | this.addScrollY(parent) 33 | this.addScrollX(parent) 34 | } 35 | initMethod(){ 36 | if(this.mouseMoveY){ 37 | document.removeEventListener('mousemove',this.mouseMoveY) 38 | } 39 | if(this.mouseMoveX){ 40 | document.removeEventListener('mousemove',this.mouseMoveX) 41 | } 42 | } 43 | dispose(){ 44 | document.removeEventListener('mouseup',this.initMethod) 45 | } 46 | scrollY(moveY){ 47 | //将进度换算成进度条的移动 48 | let moveScroll = - moveY / this.config.fullHeight * this.config.wrapHeight 49 | if(moveScroll < 0){ 50 | moveScroll = 0 51 | } 52 | if(moveScroll > this.config.wrapHeight - (this.config.wrapHeight / this.config.fullHeight * this.config.wrapHeight)){ 53 | moveScroll = this.config.wrapHeight - (this.config.wrapHeight / this.config.fullHeight * this.config.wrapHeight) 54 | } 55 | if(this.scrollHeight){ 56 | this.scrollHeightBtn.style.top = moveScroll + 'px' 57 | } 58 | } 59 | mouseMoveY(event){ 60 | //鼠标拖动的距离为 61 | let distance = event.pageY - this.originMouseXY[1] 62 | let positionY = this.originScrollXY[1] + distance 63 | if(positionY < 0){ 64 | positionY = 0 65 | } 66 | if(positionY > this.config.wrapHeight - (this.config.wrapHeight / this.config.fullHeight * this.config.wrapHeight)){ 67 | positionY = this.config.wrapHeight - (this.config.wrapHeight / this.config.fullHeight * this.config.wrapHeight) 68 | } 69 | this.scrollHeightBtn.style.top = positionY + 'px' 70 | //换算成页面需要挪动多少 71 | let pageMoveY = -positionY / this.config.wrapHeight * (this.config.fullHeight + this.config.scrollWidth) 72 | this.emit('scrollY',{ 73 | pageMove:pageMoveY 74 | }) 75 | } 76 | mouseMoveX(event){ 77 | //鼠标拖动的距离为 78 | let distance = event.pageX - this.originMouseXY[0] 79 | let positionX = this.originScrollXY[0] + distance 80 | if(positionX < 0){ 81 | positionX = 0 82 | } 83 | if(positionX > this.config.wrapWidth - (this.config.wrapWidth / this.config.fullWidth * this.config.wrapWidth)){ 84 | positionX = this.config.wrapWidth - (this.config.wrapWidth / this.config.fullWidth * this.config.wrapWidth) 85 | } 86 | this.scrollWidthBtn.style.left = positionX + 'px' 87 | //换算成页面需要挪动多少 88 | let pageMoveX = -positionX / this.config.wrapWidth * (this.config.fullWidth + this.config.scrollWidth) 89 | this.emit('scrollX',{ 90 | pageMove:pageMoveX 91 | }) 92 | } 93 | addScrollY(){ 94 | //判断是否需要加纵向滚动条 95 | if(this.config.wrapHeight < this.config.fullHeight){ 96 | this.scrollHeight = document.createElement('div') 97 | this.scrollHeight.id = "daodao_excel_scroll_height_wrapper" 98 | this.scrollHeightBtn = document.createElement('div') 99 | this.scrollHeightBtn.id = "daodao_excel_scroll_height_btn" 100 | this.scrollHeight.appendChild(this.scrollHeightBtn) 101 | const parent = this.config.parent 102 | this.parent = parent 103 | parent.appendChild(this.scrollHeight) 104 | parent.style.position = "relative" 105 | 106 | //设置纵向滚动条的style 107 | this.scrollHeight.style.cssText = ` 108 | position:absolute; 109 | bottom:0; 110 | right:0; 111 | height:${this.config.wrapHeight}px; 112 | width:${this.config.scrollWidth}px; 113 | background:${this.config.bgColor}; 114 | ` 115 | this.scrollHeightBtn.style.cssText = ` 116 | position:absolute; 117 | cursor:pointer; 118 | border-radius:${this.config.scrollWidth / 2}px; 119 | top:0; 120 | left:1px; 121 | height:${this.config.wrapHeight / this.config.fullHeight * this.config.wrapHeight}px; 122 | width:${this.config.scrollWidth - 2}px; 123 | background:${this.config.scrollColor}; 124 | ` 125 | const style = document.createElement('style') 126 | style.innerHTML = `#daodao_excel_scroll_height_wrapper:hover{ 127 | background:${this.config.hoverBgColor}!important; 128 | } 129 | #daodao_excel_scroll_height_btn:hover{ 130 | background:${this.config.hoverColor}!important; 131 | }` 132 | this.scrollHeight.appendChild(style) 133 | 134 | //给纵向滚动条加事件 135 | this.scrollHeightBtn.addEventListener('mousedown',(event) => { 136 | this.originMouseXY = [event.pageX,event.pageY] 137 | this.originScrollXY = [parseInt(this.scrollWidthBtn.style.left),parseInt(this.scrollHeightBtn.style.top)] 138 | this.mouseMoveY = this.mouseMoveY.bind(this) 139 | document.addEventListener('mousemove',this.mouseMoveY) 140 | }) 141 | } 142 | } 143 | addScrollX(){ 144 | //判断是否需要加横向滚动条 145 | if(this.config.wrapWidth < this.config.fullWidth){ 146 | this.scrollWidth = document.createElement('div') 147 | this.scrollWidth.id = "daodao_excel_scroll_width_wrapper" 148 | this.scrollWidthBtn = document.createElement('div') 149 | this.scrollWidthBtn.id = "daodao_excel_scroll_width_btn" 150 | this.scrollWidth.appendChild(this.scrollWidthBtn) 151 | const parent = this.config.parent 152 | parent.appendChild(this.scrollWidth) 153 | parent.style.position = "relative" 154 | 155 | //设置纵向滚动条的style 156 | this.scrollWidth.style.cssText = ` 157 | position:absolute; 158 | bottom:0; 159 | right:${this.config.scrollWidth}px; 160 | height:${this.config.scrollWidth}px; 161 | width:${this.config.wrapWidth - this.config.scrollWidth}px; 162 | background:${this.config.bgColor}; 163 | ` 164 | this.scrollWidthBtn.style.cssText = ` 165 | position:absolute; 166 | cursor:pointer; 167 | border-radius:${this.config.scrollWidth / 2}px; 168 | top:1px; 169 | left:0; 170 | width:${this.config.wrapWidth / this.config.fullWidth * (this.config.wrapWidth - this.config.scrollWidth)}px; 171 | height:${this.config.scrollWidth - 2}px; 172 | background:${this.config.scrollColor}; 173 | ` 174 | const style = document.createElement('style') 175 | style.innerHTML = `#daodao_excel_scroll_width_wrapper:hover{ 176 | background:${this.config.hoverBgColor}!important; 177 | } 178 | #daodao_excel_scroll_width_btn:hover{ 179 | background:${this.config.hoverColor}!important; 180 | }` 181 | this.scrollWidth.appendChild(style) 182 | 183 | //给横向滚动条加事件 184 | this.scrollWidthBtn.addEventListener('mousedown',(event) => { 185 | this.originMouseXY = [event.pageX,event.pageY] 186 | this.originScrollXY = [parseInt(this.scrollWidthBtn.style.left),parseInt(this.scrollHeightBtn.style.top)] 187 | this.mouseMoveX = this.mouseMoveX.bind(this) 188 | document.addEventListener('mousemove',this.mouseMoveX) 189 | }) 190 | } 191 | } 192 | refresh(data){ 193 | this.config = Object.assign({},this.config,data) 194 | //判断是否要纵向滚动条 195 | if(this.config.wrapHeight < this.config.fullHeight){ 196 | if(this.scrollHeight){ 197 | //改一下纵向滚动条 198 | //设置纵向滚动条的style 199 | this.scrollHeight.style.height = `${this.config.wrapHeight}px` 200 | this.scrollHeightBtn.style.height = `${this.config.wrapHeight / this.config.fullHeight * this.config.wrapHeight}px` 201 | }else{ 202 | this.addScrollY() 203 | } 204 | }else{ 205 | //移除纵向滚动条 206 | if(this.scrollHeight){ 207 | this.parent.removeChild(this.scrollHeight) 208 | this.scrollHeight = null 209 | } 210 | } 211 | //判断是否要横向滚动条 212 | if(this.config.wrapWidth < this.config.fullWidth){ 213 | if(this.scrollWidth){ 214 | //改一下横向滚动条 215 | //设置纵向滚动条的style 216 | this.scrollWidth.style.width = `${this.config.wrapWidth - this.config.scrollWidth}px` 217 | this.scrollWidthBtn.style.width = `${this.config.wrapWidth / this.config.fullWidth * (this.config.wrapWidth - this.config.scrollWidth)}px` 218 | }else{ 219 | this.addScrollX() 220 | } 221 | }else{ 222 | //移除纵向滚动条 223 | if(this.scrollWidth){ 224 | this.parent.removeChild(this.scrollWidth) 225 | this.scrollWidth = null 226 | } 227 | } 228 | } 229 | } 230 | 231 | export default Scroll -------------------------------------------------------------------------------- /src/selectCell.js: -------------------------------------------------------------------------------- 1 | import zrender from 'zrender' 2 | import {headerHeight,indexWidth} from './config' 3 | 4 | class SelectCell extends zrender.Rect{ 5 | constructor(cells){ 6 | let originConfig = { 7 | cursor:'default', 8 | style:{ 9 | stroke: '#4e9fff', 10 | lineWidth:'2', 11 | fill:'none', 12 | }, 13 | z:3 14 | } 15 | let config = { 16 | shape:{ 17 | 18 | } 19 | } 20 | //cells的长度为1的时候 21 | if(cells.length == 1){ 22 | config.shape = { 23 | x:cells[0].data.xPlace, 24 | y:cells[0].data.yPlace, 25 | width:cells[0].data.cellWidth, 26 | height:cells[0].data.cellHeight 27 | } 28 | }else{ 29 | let x = cells[0].data.xPlace 30 | let y = cells[0].data.yPlace 31 | let width = cells[cells.length - 1].data.xPlace + cells[cells.length - 1].data.cellWidth - x 32 | let height = cells[cells.length - 1].data.yPlace + cells[cells.length - 1].data.cellHeight - y 33 | 34 | //下标最小的是起点 35 | config.shape.x = x 36 | config.shape.y = y 37 | //下标最大的是终点 38 | config.shape.width = width 39 | config.shape.height = height 40 | } 41 | let finalConfig = Object.assign({},originConfig,config) 42 | super(finalConfig) 43 | } 44 | //改变选择框的位置和大小 45 | change(cells){ 46 | let shape = {} 47 | if(cells.length == 1){ 48 | shape = { 49 | x:cells[0].data.xPlace, 50 | y:cells[0].data.yPlace, 51 | width:cells[0].data.cellWidth, 52 | height:cells[0].data.cellHeight 53 | } 54 | }else{ 55 | //求最小下标以及最大下标 56 | let x = cells[0].data.xPlace 57 | let y = cells[0].data.yPlace 58 | let width = cells[cells.length - 1].data.xPlace + cells[cells.length - 1].data.cellWidth - x 59 | let height = cells[cells.length - 1].data.yPlace + cells[cells.length - 1].data.cellHeight - y 60 | 61 | //下标最小的是起点 62 | shape.x = x 63 | shape.y = y 64 | //下标最大的是终点 65 | shape.width = width 66 | shape.height = height 67 | } 68 | this.attr('shape',shape) 69 | } 70 | } 71 | 72 | export default SelectCell -------------------------------------------------------------------------------- /src/tableHeaderCell.js: -------------------------------------------------------------------------------- 1 | import zrender from 'zrender' 2 | import {letter,indexWidth} from './config.js' 3 | import { mouseWheelDirection, preventDefault,stopPropagation,generateCode } from "./utils.js" 4 | 5 | class TableHeaderCell extends zrender.Group{ 6 | constructor(config){ 7 | let defaultConfig = { 8 | cursor:'default', 9 | scale:[0.5,0.5] 10 | } 11 | //由于zrender的Rect描边粗细有bug,所以我的做法是先放大两倍然后再缩放0.5倍 12 | let xPlace = config.cellWidth * config.index + indexWidth 13 | let yPlace = 0 14 | let countConfig = { 15 | shape:{ 16 | x:1, 17 | y:1, 18 | width:config.cellWidth * 2, 19 | height:config.cellHeight * 2 20 | }, 21 | style:{ 22 | text:generateCode(config.index), 23 | stroke: '#aaa', 24 | fill: 'white', 25 | lineWidth:'1', 26 | }, 27 | z:10 28 | } 29 | let finnalconfig = Object.assign({},defaultConfig,config,countConfig) 30 | super({position:[xPlace,yPlace],z:1000}) 31 | this.data = { 32 | index:config.index, 33 | width:config.cellWidth, 34 | xPlace:xPlace 35 | } 36 | this.box = new zrender.Rect(finnalconfig) 37 | this.box.type = 'headerBorder' 38 | this.add(this.box) 39 | this.line = new zrender.Line({ 40 | shape:{ 41 | x1:config.cellWidth, 42 | y1:0, 43 | x2:config.cellWidth, 44 | y2:config.cellHeight 45 | }, 46 | cursor:'ew-resize', 47 | style:{ 48 | stroke: '#fff', 49 | opacity:0, 50 | fill: 'white', 51 | lineWidth:'6', 52 | }, 53 | z:11 54 | }) 55 | this.add(this.line) 56 | this.type = 'tableHeaderCell' 57 | this.handlers = {} 58 | this.isDrag = false 59 | this.line.on('mousedown',(event) => { 60 | stopPropagation(event.event) 61 | let width = event.offsetX - this.data.xPlace - this.parent.position[0] 62 | this.emit('dragLine',{width:width,offsetX:event.offsetX}) 63 | this.isDrag = true 64 | this.mousemove = this.mousemove.bind(this) 65 | document.addEventListener('mousemove',this.mousemove) 66 | this.mouseup = this.mouseup.bind(this) 67 | document.addEventListener('mouseup',this.mouseup) 68 | }) 69 | } 70 | mouseup(event){ 71 | if(this.isDrag){ 72 | this.data.width = event.offsetX - this.data.xPlace - this.parent.position[0] 73 | let offsetX = event.offsetX 74 | if(this.data.width < 10){ 75 | this.data.width = 10 76 | offsetX = this.data.xPlace + 10 77 | }else{ 78 | offsetX = event.offsetX 79 | } 80 | this.emit('changeSize',{width:this.data.width,offsetX:offsetX}) 81 | } 82 | this.isDrag = false 83 | document.removeEventListener('mousemove',this.mousemove) 84 | document.removeEventListener('mouseup',this.mouseup) 85 | } 86 | mousemove(event){ 87 | let width = event.offsetX - this.data.xPlace - this.parent.position[0] 88 | let offsetX = event.offsetX 89 | if(width < 10){ 90 | width = 10 91 | offsetX = this.data.xPlace + 10 92 | }else{ 93 | offsetX = event.offsetX 94 | } 95 | this.emit('dragLine',{width:width,offsetX:offsetX}) 96 | } 97 | selectCell(){ 98 | this.box.attr({style:{fill:'rgb(160,212,255)'}}) 99 | } 100 | unSelectCell(){ 101 | this.box.attr({style:{fill:'#fff'}}) 102 | } 103 | addEvent(type,handler){ 104 | if(typeof this.handlers[type] === "undefined"){ 105 | this.handlers[type] = [] 106 | } 107 | this.handlers[type].push(handler) 108 | } 109 | emit(type,event){ 110 | if(!event.target){ 111 | event.target = this 112 | } 113 | if(this.handlers[type] instanceof Array){ 114 | const handlers = this.handlers[type] 115 | handlers.forEach((handler)=>{ 116 | handler(event) 117 | }) 118 | } 119 | } 120 | remove(type,handler){ 121 | if(this.handlers[type] instanceof Array){ 122 | const handlers = this.handlers[type] 123 | for(var i = 0,len = handlers.length; i < len; i++){ 124 | if(handlers[i] === handler){ 125 | break; 126 | } 127 | } 128 | handlers.splice(i,1) 129 | } 130 | } 131 | refresh(){ 132 | this.attr('position',[this.data.xPlace,0]) 133 | //画格子 134 | this.box.attr({ 135 | shape:{ 136 | width:this.data.width * 2 137 | } 138 | }) 139 | //画线线 140 | this.line.attr({ 141 | shape:{ 142 | x1:this.data.width, 143 | x2:this.data.width 144 | } 145 | }) 146 | } 147 | setData(data){ 148 | this.data = Object.assign({},this.data,data) 149 | this.box.attr({style:{ 150 | text:generateCode(this.data.index) 151 | }}) 152 | } 153 | } 154 | 155 | export default TableHeaderCell -------------------------------------------------------------------------------- /src/tableIndexCell.js: -------------------------------------------------------------------------------- 1 | import zrender from 'zrender' 2 | import {headerHeight} from './config.js' 3 | import { mouseWheelDirection, preventDefault,stopPropagation } from "./utils.js" 4 | 5 | class TableHeaderCell extends zrender.Group{ 6 | constructor(config){ 7 | let defaultConfig = { 8 | cursor:'default', 9 | scale:[0.5,0.5] 10 | } 11 | //由于zrender的Rect描边粗细有bug,所以我的做法是先放大两倍然后再缩放0.5倍 12 | let xPlace = 0 13 | let yPlace = config.cellHeight * config.index + headerHeight 14 | let countConfig = { 15 | shape:{ 16 | x:1, 17 | y:1, 18 | width:config.cellWidth * 2, 19 | height:config.cellHeight * 2 20 | }, 21 | style:{ 22 | text:config.index + 1, 23 | stroke: '#aaa', 24 | fill: 'white', 25 | lineWidth:'1' 26 | }, 27 | z:10 28 | } 29 | let finnalconfig = Object.assign({},defaultConfig,config,countConfig) 30 | super({position:[xPlace,yPlace],z:1000}) 31 | this.box = new zrender.Rect(finnalconfig) 32 | this.box.type = 'indexBorder' 33 | this.add(this.box) 34 | this.line = new zrender.Line({ 35 | shape:{ 36 | x1:0, 37 | y1:config.cellHeight, 38 | x2:config.cellWidth, 39 | y2:config.cellHeight 40 | }, 41 | cursor:'ns-resize', 42 | style:{ 43 | stroke: '#aaa', 44 | opacity:0, 45 | fill: 'white', 46 | lineWidth:'6', 47 | }, 48 | z:11 49 | }) 50 | this.add(this.line) 51 | this.type = 'tableHeaderCell' 52 | this.handlers = {} 53 | this.isDrag = false 54 | this.data = { 55 | index:config.index, 56 | height:config.cellHeight, 57 | yPlace:yPlace 58 | } 59 | this.type = 'tableIndexCell' 60 | this.line.on('mousedown',(event) => { 61 | stopPropagation(event.event) 62 | let height = event.offsetY - this.data.yPlace - this.parent.position[1] 63 | this.emit('dragLine',{height:height,offsetY:event.offsetY}) 64 | this.isDrag = true 65 | this.mousemove = this.mousemove.bind(this) 66 | document.addEventListener('mousemove',this.mousemove) 67 | this.mouseup = this.mouseup.bind(this) 68 | document.addEventListener('mouseup',this.mouseup) 69 | }) 70 | } 71 | mouseup(event){ 72 | if(this.isDrag){ 73 | this.data.height = event.offsetY - this.data.yPlace - this.parent.position[1] 74 | let offsetY = event.offsetY 75 | if(this.data.height < 10){ 76 | this.data.height = 10 77 | offsetY = this.data.yPlace + 10 78 | }else{ 79 | offsetY = event.offsetY 80 | } 81 | this.emit('changeSize',{height:this.data.height,offsetY:offsetY}) 82 | } 83 | this.isDrag = false 84 | document.removeEventListener('mousemove',this.mousemove) 85 | document.removeEventListener('mouseup',this.mouseup) 86 | } 87 | mousemove(event){ 88 | let height = event.offsetY - this.data.yPlace - this.parent.position[1] 89 | let offsetY = event.offsetY 90 | if(height < 10){ 91 | height = 10 92 | offsetY = this.data.yPlace + 10 93 | }else{ 94 | offsetY = event.offsetY 95 | } 96 | this.emit('dragLine',{height:height,offsetY:offsetY}) 97 | } 98 | selectCell(){ 99 | this.box.attr({style:{fill:'rgb(160,212,255)'}}) 100 | } 101 | unSelectCell(){ 102 | this.box.attr({style:{fill:'#fff'}}) 103 | } 104 | addEvent(type,handler){ 105 | if(typeof this.handlers[type] === "undefined"){ 106 | this.handlers[type] = [] 107 | } 108 | this.handlers[type].push(handler) 109 | } 110 | emit(type,event){ 111 | if(!event.target){ 112 | event.target = this 113 | } 114 | if(this.handlers[type] instanceof Array){ 115 | const handlers = this.handlers[type] 116 | handlers.forEach((handler)=>{ 117 | handler(event) 118 | }) 119 | } 120 | } 121 | remove(type,handler){ 122 | if(this.handlers[type] instanceof Array){ 123 | const handlers = this.handlers[type] 124 | for(var i = 0,len = handlers.length; i < len; i++){ 125 | if(handlers[i] === handler){ 126 | break; 127 | } 128 | } 129 | handlers.splice(i,1) 130 | } 131 | } 132 | refresh(){ 133 | this.attr('position',[0,this.data.yPlace]) 134 | //画格子 135 | this.box.attr({ 136 | shape:{ 137 | height:this.data.height * 2 138 | } 139 | }) 140 | //画线线 141 | this.line.attr({ 142 | shape:{ 143 | y1:this.data.height, 144 | y2:this.data.height 145 | } 146 | }) 147 | } 148 | setData(data){ 149 | this.data = Object.assign({},this.data,data) 150 | this.box.attr({style:{ 151 | text:this.data.index+1 152 | }}) 153 | } 154 | } 155 | 156 | export default TableHeaderCell -------------------------------------------------------------------------------- /src/toolBar.js: -------------------------------------------------------------------------------- 1 | import Event from "./event.js" 2 | import {preventDefault,stopPropagation ,generateUUID} from "./utils.js" 3 | 4 | class ToolBar extends Event{ 5 | constructor(parent){ 6 | super() 7 | this.el = null 8 | this.parent = parent 9 | this.copyButton = null 10 | this.pasteButton = null 11 | this.clearFormat = null 12 | this.typeFaceButton = null 13 | this.fontSizeButton = null 14 | this.fontWeightButton = null 15 | this.fontItalicButton = null 16 | this.underLineButton = null 17 | this.textFillButton = null 18 | this.fillButton = null 19 | this.borderButton = null 20 | this.alignLiftButton = null 21 | this.alignRightButton = null 22 | this.alignCenterButton = null 23 | this.mergeCellButton = null 24 | this.splitCellButton = null 25 | this.addImageButton = null 26 | this.init(parent) 27 | } 28 | init(parent){ 29 | this.el = document.createElement('div') 30 | this.el.id = 'daodao_excel_scroll_toolbar-wrapper' 31 | this.el.style.cssText = ` 32 | height:30px; 33 | ` 34 | const first = parent.firstChild 35 | parent.insertBefore(this.el,first) 36 | this.initStyle() 37 | this.initCopy() 38 | this.initPaste() 39 | this.initClearFormat() 40 | this.initTypeFace() 41 | this.initFontSize() 42 | this.initFontWeight() 43 | this.initFontItalic() 44 | this.initTextFill() 45 | this.initFill() 46 | this.initBorder() 47 | this.initAlignLeft() 48 | this.initAlignRight() 49 | this.initAlignCenter() 50 | this.initMergeCell() 51 | this.initSplitCell() 52 | this.initAddImage() 53 | } 54 | initStyle(){ 55 | const style = document.createElement('style') 56 | style.innerHTML = ` 57 | #daodao_excel_scroll_toolbar-wrapper .toolbar-item{ 58 | display: inline-block; 59 | position:relative; 60 | border:1px solid transparent; 61 | vertical-align: middle; 62 | cursor: pointer; 63 | margin-right:4px; 64 | } 65 | #daodao_excel_scroll_toolbar-wrapper .toolbar-item .tooltip__down{ 66 | display:none; 67 | position:absolute; 68 | margin-top:10px; 69 | left:50%; 70 | margin-left:-35px; 71 | width:70px; 72 | height:22px; 73 | line-height: 22px; 74 | font-size:12px; 75 | border:1px solid #d0d0d0; 76 | background: #fff; 77 | text-align: center; 78 | z-index:10; 79 | } 80 | #daodao_excel_scroll_toolbar-wrapper .toolbar-item .tooltip__down:before{ 81 | content:''; 82 | width:0; 83 | height:0; 84 | border-bottom:8px solid #d0d0d0; 85 | border-right:5px solid transparent; 86 | border-left:5px solid transparent; 87 | position:absolute; 88 | top:-8px; 89 | left:50%; 90 | margin-left:-5px; 91 | } 92 | #daodao_excel_scroll_toolbar-wrapper .toolbar-item .tooltip__down:after{ 93 | content:''; 94 | width:0; 95 | height:0; 96 | border-bottom:8px solid #ffffff; 97 | border-right:5px solid transparent; 98 | border-left:5px solid transparent; 99 | position:absolute; 100 | top:-6px; 101 | left:50%; 102 | margin-left:-5px; 103 | } 104 | #daodao_excel_scroll_toolbar-wrapper .toolbar-item:hover{ 105 | border:1px solid #d0d0d0; 106 | } 107 | #daodao_excel_scroll_toolbar-wrapper .toolbar-item:hover .tooltip__down{ 108 | display: block; 109 | } 110 | #daodao_excel_scroll_toolbar-wrapper .toolbar-button{ 111 | width:20px; 112 | height:20px; 113 | text-align: center; 114 | background:#fff; 115 | } 116 | #daodao_excel_scroll_toolbar-wrapper .toolbar-button.active{ 117 | background:#e0e0e0 118 | } 119 | ` 120 | this.el.appendChild(style) 121 | } 122 | initCopy(){ 123 | this.copyButton = this.addButton( 124 | '', 125 | '复制', 126 | (e)=>{ 127 | this.emit('copy',{ 128 | data:true 129 | }) 130 | }, 131 | false 132 | ) 133 | } 134 | initPaste(){ 135 | this.copyButton = this.addButton( 136 | '', 137 | '粘贴', 138 | (e)=>{ 139 | this.emit('paste',{ 140 | data:true 141 | }) 142 | }, 143 | false 144 | ) 145 | } 146 | initClearFormat(){ 147 | this.copyButton = this.addButton( 148 | '', 149 | '清除格式', 150 | (e)=>{ 151 | this.emit('clearFormat',{ 152 | data:true 153 | }) 154 | }, 155 | false 156 | ) 157 | } 158 | initTypeFace(){ 159 | const id = generateUUID() 160 | const html = ` 161 | 168 |
169 | 选择字体 170 |
171 | ` 172 | let div = document.createElement('div') 173 | div.classList = ["toolbar-item"] 174 | div.innerHTML = html 175 | 176 | const style = document.createElement('style') 177 | style.innerHTML = ` 178 | #daodao_excel_scroll_toolbar-wrapper select{ 179 | border:none!important; 180 | outline: none!important; 181 | } 182 | ` 183 | this.el.appendChild(style) 184 | 185 | this.el.appendChild(div) 186 | 187 | this.typeFaceButton = document.getElementById(id) 188 | 189 | this.typeFaceButton.addEventListener('change',(e) => { 190 | this.emit('changeTypeFace',{ 191 | data:e.target.value 192 | }) 193 | }) 194 | } 195 | initFontSize(){ 196 | const id = generateUUID() 197 | const html = ` 198 | 211 |
212 | 字体大小 213 |
214 | ` 215 | let div = document.createElement('div') 216 | div.classList = ["toolbar-item"] 217 | div.innerHTML = html 218 | 219 | this.el.appendChild(div) 220 | 221 | this.fontSizeButton = document.getElementById(id) 222 | 223 | this.fontSizeButton.addEventListener('change',(e) => { 224 | this.emit('changeFontSize',{ 225 | data:e.target.value 226 | }) 227 | }) 228 | } 229 | initFontWeight(){ 230 | this.fontWeightButton = this.addButton( 231 | '', 232 | '加粗', 233 | (e)=>{ 234 | if(e){ 235 | this.emit('changeFontWeight',{ 236 | data:'bold' 237 | }) 238 | }else{ 239 | this.emit('changeFontWeight',{ 240 | data:'normal' 241 | }) 242 | } 243 | }, 244 | true 245 | ) 246 | } 247 | initFontItalic(){ 248 | this.fontItalicButton = this.addButton( 249 | '', 250 | '倾斜', 251 | (e)=>{ 252 | if(e){ 253 | this.emit('changeFontItalic',{ 254 | data:'italic' 255 | }) 256 | }else{ 257 | this.emit('changeFontItalic',{ 258 | data:'normal' 259 | }) 260 | } 261 | }, 262 | true 263 | ) 264 | } 265 | initTextFill(){ 266 | const id = generateUUID() 267 | const id1 = generateUUID() 268 | const id2 = generateUUID() 269 | const html = ` 270 |
271 | 272 |
273 |
274 |
275 | 文字颜色 276 |
277 | 331 | ` 332 | let div = document.createElement('div') 333 | div.classList.add("toolbar-item","toolbar-drop-down","toolbar-color") 334 | div.innerHTML = html 335 | 336 | const style = document.createElement('style') 337 | style.innerHTML = ` 338 | #daodao_excel_scroll_toolbar-wrapper .dropdown-icon{ 339 | height:20px; 340 | display: inline-block; 341 | } 342 | #daodao_excel_scroll_toolbar-wrapper .toolbar-icon{ 343 | height:20px; 344 | display: inline-block; 345 | } 346 | #daodao_excel_scroll_toolbar-wrapper .toolbar-color .dropdown-icon .dropdown-card{ 347 | display: none; 348 | position:absolute; 349 | left:50%; 350 | margin-left:-110px; 351 | width:220px; 352 | line-height: 22px; 353 | font-size:12px; 354 | border:1px solid #d0d0d0; 355 | background: #fff; 356 | text-align: center; 357 | z-index:10; 358 | } 359 | #daodao_excel_scroll_toolbar-wrapper .toolbar-color .dropdown-icon .dropdown-card:before{ 360 | content:''; 361 | width:0; 362 | height:0; 363 | border-bottom:8px solid #d0d0d0; 364 | border-right:5px solid transparent; 365 | border-left:5px solid transparent; 366 | position:absolute; 367 | top:-8px; 368 | left:50%; 369 | margin-left:-5px; 370 | } 371 | #daodao_excel_scroll_toolbar-wrapper .toolbar-color .dropdown-icon .dropdown-card:after{ 372 | content:''; 373 | width:0; 374 | height:0; 375 | border-bottom:8px solid #ffffff; 376 | border-right:5px solid transparent; 377 | border-left:5px solid transparent; 378 | position:absolute; 379 | top:-6px; 380 | left:50%; 381 | margin-left:-5px; 382 | } 383 | #daodao_excel_scroll_toolbar-wrapper .toolbar-color .dropdown-card .color-button{ 384 | width:20px; 385 | height:20px; 386 | float:left; 387 | margin:4px 2px; 388 | } 389 | #daodao_excel_scroll_toolbar-wrapper .toolbar-color .dropdown-card .color-button:hover{ 390 | box-shadow:0 0 7px 2px #d0d0d0; 391 | } 392 | 393 | ` 394 | this.el.appendChild(style) 395 | 396 | this.el.appendChild(div) 397 | 398 | this.textFillButton = document.getElementById(id1) 399 | 400 | document.getElementById(id2).addEventListener('click',(event) => { 401 | stopPropagation(event) 402 | document.getElementById(id).style.display = 'block' 403 | parent.addEventListener('click',hide) 404 | }) 405 | 406 | document.getElementById(id).addEventListener('click',(e) => { 407 | let color = e.target.dataset.color 408 | document.getElementById(id).style.display = 'none' 409 | this.emit('changeTextFill',{ 410 | data:color 411 | }) 412 | this.textFillButton.querySelector(".color-line").style.backgroundColor = color 413 | }) 414 | 415 | function hide(){ 416 | document.getElementById(id).style.display = 'none' 417 | parent.removeEventListener('click',hide) 418 | } 419 | } 420 | initFill(){ 421 | const id = generateUUID() 422 | const id1 = generateUUID() 423 | const id2 = generateUUID() 424 | const html = ` 425 |
426 | 427 |
428 |
429 |
430 | 背景颜色 431 |
432 | 486 | ` 487 | let div = document.createElement('div') 488 | div.classList.add("toolbar-item","toolbar-drop-down","toolbar-color") 489 | div.innerHTML = html 490 | 491 | this.el.appendChild(div) 492 | 493 | this.fillButton = document.getElementById(id1) 494 | 495 | document.getElementById(id2).addEventListener('click',(event) => { 496 | stopPropagation(event) 497 | document.getElementById(id).style.display = 'block' 498 | parent.addEventListener('click',hide) 499 | }) 500 | 501 | document.getElementById(id).addEventListener('click',(e) => { 502 | let color = e.target.dataset.color 503 | document.getElementById(id).style.display = 'none' 504 | this.emit('changeFill',{ 505 | data:color 506 | }) 507 | this.fillButton.querySelector(".color-line").style.backgroundColor = color 508 | }) 509 | function hide(){ 510 | document.getElementById(id).style.display = 'none' 511 | parent.removeEventListener('click',hide) 512 | } 513 | } 514 | initBorder(){ 515 | const id = generateUUID() 516 | const id1 = generateUUID() 517 | const id2 = generateUUID() 518 | const html = ` 519 | 520 | 534 | ` 535 | const style = document.createElement('style') 536 | style.innerHTML = ` 537 | #daodao_excel_scroll_toolbar-wrapper .toolbar-border .dropdown-card{ 538 | display: none; 539 | position:absolute; 540 | left:50%; 541 | margin-left:-30px; 542 | width:60px; 543 | line-height: 22px; 544 | font-size:12px; 545 | border:1px solid #d0d0d0; 546 | background: #fff; 547 | text-align: center; 548 | z-index:10; 549 | } 550 | #daodao_excel_scroll_toolbar-wrapper .toolbar-border:hover .dropdown-card{ 551 | display: block; 552 | } 553 | #daodao_excel_scroll_toolbar-wrapper .toolbar-border .dropdown-card:before{ 554 | content:''; 555 | width:0; 556 | height:0; 557 | border-bottom:8px solid #d0d0d0; 558 | border-right:5px solid transparent; 559 | border-left:5px solid transparent; 560 | position:absolute; 561 | top:-8px; 562 | left:50%; 563 | margin-left:-5px; 564 | } 565 | #daodao_excel_scroll_toolbar-wrapper .toolbar-border .dropdown-card:after{ 566 | content:''; 567 | width:0; 568 | height:0; 569 | border-bottom:8px solid #ffffff; 570 | border-right:5px solid transparent; 571 | border-left:5px solid transparent; 572 | position:absolute; 573 | top:-6px; 574 | left:50%; 575 | margin-left:-5px; 576 | } 577 | #daodao_excel_scroll_toolbar-wrapper .toolbar-border .border-button{ 578 | position: relative; 579 | float:left; 580 | margin:4px 2px; 581 | } 582 | #daodao_excel_scroll_toolbar-wrapper .toolbar-border .border-button .tip{ 583 | display: none; 584 | position:absolute; 585 | left:50%; 586 | margin-left:-35px; 587 | width:70px; 588 | line-height: 22px; 589 | font-size:12px; 590 | border:1px solid #d0d0d0; 591 | background: #fff; 592 | text-align: center; 593 | z-index:10; 594 | } 595 | #daodao_excel_scroll_toolbar-wrapper .toolbar-border .border-button:hover .tip{ 596 | display: block; 597 | } 598 | #daodao_excel_scroll_toolbar-wrapper .toolbar-border .border-button .tip:before{ 599 | content:''; 600 | width:0; 601 | height:0; 602 | border-bottom:8px solid #d0d0d0; 603 | border-right:5px solid transparent; 604 | border-left:5px solid transparent; 605 | position:absolute; 606 | top:-8px; 607 | left:50%; 608 | margin-left:-5px; 609 | } 610 | #daodao_excel_scroll_toolbar-wrapper .toolbar-border .border-button .tip:after{ 611 | content:''; 612 | width:0; 613 | height:0; 614 | border-bottom:8px solid #ffffff; 615 | border-right:5px solid transparent; 616 | border-left:5px solid transparent; 617 | position:absolute; 618 | top:-6px; 619 | left:50%; 620 | margin-left:-5px; 621 | } 622 | ` 623 | this.el.appendChild(style) 624 | 625 | let div = document.createElement('div') 626 | div.classList.add("toolbar-item","toolbar-button","toolbar-border") 627 | div.id = id 628 | div.innerHTML = html 629 | 630 | this.el.appendChild(div) 631 | 632 | this.borderButton = document.getElementById(id) 633 | 634 | document.getElementById(id1).addEventListener('click',(event) => { 635 | this.emit('changeBorder',{ 636 | data:document.getElementById(id1).dataset.type 637 | }) 638 | }) 639 | document.getElementById(id2).addEventListener('click',(event) => { 640 | this.emit('changeBorder',{ 641 | data:document.getElementById(id2).dataset.type 642 | }) 643 | }) 644 | } 645 | initAlignLeft(){ 646 | this.alignLeftButton = this.addButton( 647 | '', 648 | '左对齐', 649 | (e)=>{ 650 | this.emit('changeTextAlign',{ 651 | data:'left' 652 | }) 653 | }, 654 | true 655 | ) 656 | } 657 | initAlignRight(){ 658 | this.alignRightButton = this.addButton( 659 | '', 660 | '右对齐', 661 | (e)=>{ 662 | this.emit('changeTextAlign',{ 663 | data:'right' 664 | }) 665 | }, 666 | true 667 | ) 668 | } 669 | initAlignCenter(){ 670 | this.alignCenterButton = this.addButton( 671 | '', 672 | '居中对齐', 673 | (e)=>{ 674 | this.emit('changeTextAlign',{ 675 | data:'center' 676 | }) 677 | }, 678 | true 679 | ) 680 | } 681 | initMergeCell(){ 682 | this.mergeCellButton = this.addButton( 683 | '', 684 | '合并单元格', 685 | (e)=>{ 686 | this.emit('mergeCell',{ 687 | data:true 688 | }) 689 | }, 690 | false 691 | ) 692 | } 693 | initSplitCell(){ 694 | this.mergeCellButton = this.addButton( 695 | '', 696 | '取消合并', 697 | (e)=>{ 698 | this.emit('splitCell',{ 699 | data:true 700 | }) 701 | }, 702 | false 703 | ) 704 | } 705 | initAddImage(){ 706 | this.addImageButton = this.addButton( 707 | '', 708 | '插入图片', 709 | (e)=>{ 710 | this.emit('addImage',{ 711 | data:true 712 | }) 713 | }, 714 | false 715 | ) 716 | } 717 | addButton(innerHTML,tip,func,hasStatus){ 718 | let dom = document.createElement('div') 719 | dom.classList.add("toolbar-item","toolbar-button") 720 | let html = ` 721 | ${innerHTML} 722 |
723 | ${tip} 724 |
725 | ` 726 | dom.innerHTML = html 727 | this.el.appendChild(dom) 728 | dom.addEventListener("click",()=>{ 729 | if(hasStatus){ 730 | if(dom.classList.contains('active')){ 731 | dom.classList.remove("active") 732 | func(false) 733 | }else{ 734 | dom.classList.add("active") 735 | func(true) 736 | } 737 | }else{ 738 | func() 739 | } 740 | }) 741 | return dom 742 | } 743 | setConfig(config){ 744 | this.typeFaceButton.value = config.fontFamily 745 | this.fontSizeButton.value = config.fontSize 746 | this.textFillButton.querySelector(".color-line").style.backgroundColor = config.textFill 747 | this.fillButton.querySelector(".color-line").style.backgroundColor = config.fill 748 | if(config.fontWeight == 'bold'){ 749 | this.fontWeightButton.classList.add('active') 750 | }else{ 751 | this.fontWeightButton.classList.remove('active') 752 | } 753 | if(config.fontStyle == 'italic'){ 754 | this.fontItalicButton.classList.add('active') 755 | }else{ 756 | this.fontItalicButton.classList.remove('active') 757 | } 758 | if(config.textAlign == 'left'){ 759 | this.alignLeftButton.classList.add('active') 760 | this.alignRightButton.classList.remove('active') 761 | this.alignCenterButton.classList.remove('active') 762 | }else if(config.textAlign == 'right'){ 763 | this.alignRightButton.classList.add('active') 764 | this.alignLeftButton.classList.remove('active') 765 | this.alignCenterButton.classList.remove('active') 766 | }else if(config.textAlign == 'center'){ 767 | this.alignCenterButton.classList.add('active') 768 | this.alignRightButton.classList.remove('active') 769 | this.alignLeftButton.classList.remove('active') 770 | } 771 | } 772 | } 773 | 774 | export default ToolBar -------------------------------------------------------------------------------- /src/uploadFile.js: -------------------------------------------------------------------------------- 1 | import Event from "./event.js" 2 | 3 | class UploadFile extends Event{ 4 | constructor(parent){ 5 | super() 6 | this.el = null 7 | this.init(parent) 8 | } 9 | init(parent){ 10 | this.el = document.createElement('input') 11 | this.el.type = 'file' 12 | this.el.accept = 'image/*' 13 | this.el.style.display = 'none' 14 | parent.appendChild(this.el) 15 | this.el.addEventListener('change',(e) => { 16 | let file = this.el.files[0] 17 | let reader = new FileReader() 18 | reader.onload = () => { 19 | this.emit('changeImage',{url:reader.result}) 20 | this.el.value = '' 21 | } 22 | reader.readAsDataURL(file) 23 | }) 24 | } 25 | open(){ 26 | this.el.click() 27 | } 28 | } 29 | 30 | export default UploadFile -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from 'uuid'; 2 | 3 | /* 4 | * 生成唯一id 5 | */ 6 | export function generateUUID(){ 7 | let id = uuidv4() 8 | return id 9 | } 10 | 11 | /* 12 | *判断滚轮方向 return true 为正,false 为负 13 | */ 14 | export function mouseWheelDirection(e){ 15 | e = e || window.event; 16 | if (e.wheelDelta) { //判断浏览器IE,谷歌滑轮事件 17 | if (e.wheelDelta > 0) { //当滑轮向上滚动时 18 | return true 19 | } 20 | if (e.wheelDelta < 0) { //当滑轮向下滚动时 21 | return false 22 | } 23 | } else if (e.detail) { //Firefox滑轮事件 24 | if (e.detail> 0) { //当滑轮向上滚动时 25 | return false 26 | } 27 | if (e.detail< 0) { //当滑轮向下滚动时 28 | return true 29 | } 30 | } 31 | } 32 | 33 | /* 34 | *阻止默认事件 35 | */ 36 | export function preventDefault(event){ 37 | if (event.preventDefault){ 38 | event.preventDefault(); 39 | }else{ 40 | event.returnValue=false; 41 | } 42 | } 43 | 44 | /* 45 | *阻止冒泡和捕获 46 | */ 47 | export function stopPropagation(event){ 48 | if(event && event.stopPropagation) { 49 | event.stopPropagation(); // W3C标准 50 | }else { 51 | event.cancelBubble = true; //ie678 52 | } 53 | } 54 | 55 | /* 56 | *生成26进制的A-F序号 57 | */ 58 | export function generateCode(number){ 59 | let letter = [ 60 | 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z', 61 | ] 62 | let code = '' 63 | let num = number 64 | code += letter[num % 26]; 65 | num = Math.floor(num/26); 66 | while(num > 0){ 67 | code += letter[num % 26 - 1]; 68 | num = Math.floor(num/26); 69 | } 70 | return code.split('').reverse().join('') 71 | } 72 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | web excel test 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 58 | 59 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | // mode:"development", 5 | mode:'production', 6 | entry:["./src/main"], 7 | output:{ 8 | libraryTarget: 'umd', //类库加载方式 9 | path:path.resolve(__dirname,"./dist"), 10 | filename:"daodaoExcel.js" 11 | } 12 | } --------------------------------------------------------------------------------