├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── DEVELOP.md ├── LICENSE ├── README.md ├── jsconfig.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── src ├── common │ ├── Settings.ts │ ├── Store.ts │ ├── base.ts │ ├── cache.ts │ ├── constants.ts │ ├── domCache.ts │ ├── events.ts │ ├── framework.ts │ ├── parse.ts │ └── utils.ts ├── css │ ├── animation.less │ ├── gridBase.less │ ├── loading.less │ ├── mixins.less │ └── var.less ├── demo │ ├── css │ │ ├── common.css │ │ └── layout.css │ ├── demo1.html │ ├── demo10.html │ ├── demo2.html │ ├── demo3.html │ ├── demo4.html │ ├── demo5.html │ ├── demo6.html │ ├── demo7.html │ ├── demo8.html │ ├── demo9.html │ ├── index.html │ ├── jquery.html │ ├── js │ │ └── demo1.js │ ├── promise.html │ └── test.html ├── fonts │ ├── iconfont.less │ └── iconfont.woff ├── framework │ ├── angular-1.x │ │ ├── README.md │ │ ├── demo │ │ │ ├── index.html │ │ │ ├── index.js │ │ │ └── style.css │ │ └── js │ │ │ ├── controller.js │ │ │ └── index.js │ ├── react │ │ ├── README.md │ │ ├── demo │ │ │ ├── AppContext.js │ │ │ ├── components.js │ │ │ ├── footer.js │ │ │ ├── index.html │ │ │ ├── index.js │ │ │ ├── search.js │ │ │ └── style.css │ │ └── js │ │ │ └── index.js │ └── vue │ │ ├── README.md │ │ ├── demo │ │ ├── app-router.js │ │ ├── demo-router.html │ │ ├── index.html │ │ ├── index.js │ │ └── style.css │ │ └── js │ │ ├── gridmanager-vue.js │ │ └── index.js ├── jTool │ ├── Ajax.ts │ ├── Animate.ts │ ├── Class.ts │ ├── Css.ts │ ├── Data.ts │ ├── Document.ts │ ├── Element.ts │ ├── Event.ts │ ├── Offset.ts │ ├── Sizzle.ts │ ├── constants.ts │ ├── index.ts │ └── utils.ts └── module │ ├── GridManager.ts │ ├── adjust │ ├── constants.ts │ ├── event.ts │ ├── index.ts │ └── style.less │ ├── ajaxPage │ ├── ajax-page.tpl.html │ ├── event.ts │ ├── index.ts │ ├── style.less │ └── tool.ts │ ├── autoPlay │ └── index.ts │ ├── checkbox │ ├── checkbox.tpl.html │ ├── column.tpl.html │ ├── event.ts │ ├── index.ts │ ├── radio.tpl.html │ ├── style.less │ └── tool.ts │ ├── config │ ├── config.tpl.html │ ├── constants.ts │ ├── event.ts │ ├── index.ts │ └── style.less │ ├── core │ ├── event.ts │ ├── index.ts │ ├── render.ts │ ├── style.less │ ├── template.ts │ ├── th.tpl.html │ ├── tool.ts │ └── wrap.tpl.html │ ├── drag │ ├── constants.ts │ ├── dreamland.tpl.html │ ├── event.ts │ ├── index.ts │ └── style.less │ ├── dropdown │ ├── dropdown.tpl.html │ ├── event.ts │ ├── index.ts │ └── style.less │ ├── exportFile │ └── index.ts │ ├── filter │ ├── constants.ts │ ├── event.ts │ ├── filter.tpl.html │ ├── index.ts │ └── style.less │ ├── fixed │ ├── event.ts │ ├── index.ts │ └── style.less │ ├── fullColumn │ ├── event.ts │ ├── index.ts │ └── style.less │ ├── i18n │ ├── config.ts │ └── index.ts │ ├── index.js │ ├── menu │ ├── event.ts │ ├── index.ts │ ├── style.less │ └── tool.ts │ ├── merge │ ├── constants.ts │ ├── index.ts │ └── style.less │ ├── moveRow │ ├── constants.ts │ ├── dreamland.tpl.html │ ├── event.ts │ ├── index.ts │ └── style.less │ ├── nested │ ├── index.ts │ └── style.less │ ├── order │ ├── index.ts │ └── style.less │ ├── print │ ├── index.ts │ └── style.less │ ├── remind │ ├── event.ts │ ├── index.ts │ ├── remind.tpl.html │ └── style.less │ ├── rowVisible │ ├── index.ts │ └── style.less │ ├── scroll │ ├── index.ts │ └── style.less │ ├── sort │ ├── event.ts │ ├── index.ts │ ├── sort.tpl.html │ └── style.less │ ├── summary │ ├── constants.ts │ ├── index.ts │ └── style.less │ └── tree │ ├── event.ts │ ├── index.ts │ ├── style.less │ └── tool.ts ├── test ├── adjust │ ├── constants_test.js │ ├── event_test.js │ └── index_test.js ├── ajaxPage │ ├── event_test.js │ ├── index_test.js │ └── tool_test.js ├── checkbox │ ├── event_test.js │ ├── index_test.js │ └── tool_test.js ├── common │ ├── Settings_test.js │ ├── Store_test.js │ ├── base_test.js │ ├── cache_test.js │ ├── constants_test.js │ ├── domCache_test.js │ ├── framework_test.js │ ├── parse_test.js │ └── utils_test.js ├── config │ ├── constants_test.js │ ├── event_test.js │ └── index_test.js ├── core │ ├── event_test.js │ ├── template_test.js │ └── tool_test.js ├── doc.md ├── drag │ ├── constants_test.js │ └── event_test.js ├── dropdown │ ├── event_test.js │ └── index_test.js ├── filter │ ├── constants_test.js │ ├── event_test.js │ └── index_test.js ├── fixed │ ├── event_test.js │ └── index_test.js ├── fullColumn │ ├── event_test.js │ └── index_test.js ├── i18n │ ├── I18n_test.js │ └── config_test.js ├── jTool │ ├── Ajax_test.js │ ├── Animate_test.js │ ├── Class_test.js │ ├── Css_test.js │ ├── Data_test.js │ ├── Document_test.js │ ├── Element_test.js │ ├── Event_test.js │ ├── Offset_test.js │ ├── Sizzle_test.js │ └── utils_test.js ├── menu │ ├── event_test.js │ └── tool_test.js ├── merge │ ├── constants_test.js │ └── merge_test.js ├── moveRow │ ├── constants_test.js │ ├── event_test.js │ └── index_test.js ├── nested │ └── index_test.js ├── order │ └── index_test.js ├── remind │ ├── event_test.js │ └── index_test.js ├── rowVisible │ └── index_test.js ├── sort │ ├── event_test.js │ └── index_test.js ├── summary │ └── index_test.js ├── table-config.js ├── table-test.data.js ├── table-test.tpl.html └── tree │ ├── event_test.js │ └── tool_test.js ├── tsconfig.json ├── typings ├── arg.ts ├── data.ts ├── index.d.ts ├── settings.ts └── types.ts ├── version ├── v2.x.md └── v3.x.md ├── webpack-common.loader.js ├── webpack-config.js ├── webpack-dev-config.js └── webpack-loaders ├── html-clear-loader.js └── karma-log-loader.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-react", 4 | [ 5 | "@babel/preset-env", 6 | 7 | { 8 | "targets": { 9 | "chrome": "56", 10 | "firefox": "59" 11 | }, 12 | "useBuiltIns": "entry", 13 | "corejs": 3 14 | } 15 | ] 16 | ], 17 | "plugins": [ 18 | [ 19 | "@babel/plugin-transform-runtime", 20 | { 21 | "corejs": false, 22 | "helpers": true, 23 | "regenerator": true, 24 | "useESModules": true 25 | } 26 | ], 27 | [ 28 | "@babel/plugin-proposal-decorators", 29 | { 30 | "legacy": true 31 | } 32 | ], 33 | "@babel/plugin-proposal-class-properties" 34 | ], 35 | "env": { 36 | "test": { 37 | "plugins": ["istanbul"] 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [**] 5 | # Unix-style newlines with a newline ending every file 6 | end_of_line = lf 7 | insert_final_newline = true 8 | 9 | # 去除行尾空白字符 10 | trim_trailing_whitespace = true 11 | 12 | # 编码格式,编码格式,支持latin1、utf-8、utf-8-bom、utf-16be和utf-16le,不建议使用uft-8-bom。 13 | charset = utf-8 14 | 15 | # 项目中采用tab作为代码缩进样式 16 | indent_style = tab 17 | indent_size = 4 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | 22 | [{package.json,.babelrc,.eslintrc,.esdocrc}] 23 | indent_style = tab 24 | indent_size = 4 25 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /typings 3 | /dist 4 | demo 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | node_modules/ 3 | package-lock.json 4 | npm-debug.log 5 | zip 6 | tempzip 7 | dist 8 | .idea 9 | .DS_Store 10 | js/.DS_Store 11 | /coverage 12 | report.html 13 | test-backup 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 14.0.0 4 | cache: 5 | directories: 6 | - node_modules 7 | before_install: 8 | - export DISPLAY=:99.0 9 | script: 10 | - npm test 11 | - npm run report-coverage 12 | -------------------------------------------------------------------------------- /DEVELOP.md: -------------------------------------------------------------------------------- 1 | # GridManager 开发文档 2 | 3 | ## 测试脚本 4 | ``` 5 | npm run test 6 | ``` 7 | 在当前已启动服务的情况下,可通过`http://127.0.0.1:2015/chart/index.html`查看已生成的测试覆盖率。 8 | 9 | 10 | ## 发布脚本 11 | > 发布时不用提前修改版本号,发布脚本会自动变更 12 | ``` 13 | npm version 14 | ``` 15 | 16 | ### 执行后会自动运行以下操作: 17 | - 检查当前代码是否commit 18 | - 执行测试脚本`npm run test` 19 | - 执行构建脚本`npm run build`, 版本号会根据参数进行递增。`major`: 主版本, `minor`: 次版本, `patch`: 补丁 20 | - 执行发布脚本`npm publish dist` 21 | - 执行提交代码脚本`git push && git push --tags` 22 | 23 | ### 发布后需操作 24 | - 服务器执行installGM.sh 25 | - 更新管理端: 版本、压缩包、发布记录和API 26 | - 发布gm-site (demo存在修改时,需要发布) 27 | - 发布gm-test (依赖的gm.js, gm.css需要重新构建) 28 | - github发布releases 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 拭目以待 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowSyntheticDefaultImports": true, 6 | "experimentalDecorators": true, 7 | "moduleResolution": "node", 8 | "checkJs": true, 9 | "skipLibCheck": true, 10 | "strict": true, 11 | "noImplicitAny": false, 12 | "resolveJsonModule": true, 13 | "baseUrl": ".", 14 | "paths": { 15 | "@common/*": ["./src/common/*"], 16 | "@jTool/*": ["./src/jTool/*"], 17 | "@module/*": ["./src/module/*"] 18 | } 19 | }, 20 | "include": ["./src/**/*", "./test/**/*", "./typings/*"], 21 | "exclude": ["./node_modules", "./dist"] 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gridmanager", 3 | "version": "3.2.4", 4 | "description": "表格管理插件", 5 | "author": "baukh", 6 | "main": "index.js", 7 | "license": "MIT", 8 | "scripts": { 9 | "report-coverage": "codecov", 10 | "start": "webpack serve --config webpack-dev-config.js", 11 | "lint": "eslint src", 12 | "test": "NODE_ENV=test karma start", 13 | "build": "webpack --config webpack-config.js", 14 | "preversion": "npm test", 15 | "version": "npm run build && npm publish dist", 16 | "postversion": "git push && git push --tags" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/baukh789/GridManager.git" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/baukh789/GridManager/issues" 24 | }, 25 | "homepage": "https://gridmanager.lovejavascript.com", 26 | "keywords": [ 27 | "gridmanager", 28 | "table", 29 | "grid", 30 | "component", 31 | "components" 32 | ], 33 | "devDependencies": { 34 | "@babel/cli": "^7.0.0", 35 | "@babel/core": "^7.1.6", 36 | "@babel/plugin-proposal-class-properties": "^7.1.0", 37 | "@babel/plugin-proposal-decorators": "^7.2.2", 38 | "@babel/plugin-transform-runtime": "^7.2.0", 39 | "@babel/preset-env": "^7.1.6", 40 | "@babel/runtime": "^7.3.1", 41 | "@typescript-eslint/parser": "^5.3.0", 42 | "babel-eslint": "^10.0.1", 43 | "babel-loader": "^8.2.2", 44 | "babel-plugin-istanbul": "^6.0.0", 45 | "browserslist": "^4.6.0", 46 | "clean-webpack-plugin": "3.0.0", 47 | "codecov": "^3.7.1", 48 | "copy-webpack-plugin": "^7.0.0", 49 | "core-js": "^3.9.1", 50 | "css-loader": "^5.1.1", 51 | "cssnano": "^4.1.10", 52 | "es6-promise": "^4.2.8", 53 | "eslint": "^5.16.0", 54 | "eslint-friendly-formatter": "^2.0.7", 55 | "eslint-loader": "^2.1.2", 56 | "eslint-plugin-promise": "^4.1.1", 57 | "eslint-plugin-standard": "^4.0.0", 58 | "express": "^4.16.3", 59 | "file-loader": "^6.2.0", 60 | "filemanager-webpack-plugin": "^2.0.5", 61 | "html-loader": "^0.5.5", 62 | "html-webpack-plugin": "^5.2.0", 63 | "istanbul-instrumenter-loader": "3.0.1", 64 | "jasmine-ajax": "4.0.0", 65 | "jasmine-core": "3.7.1", 66 | "karma": "6.3.2", 67 | "karma-babel-preprocessor": "8.0.1", 68 | "karma-chrome-launcher": "3.1.0", 69 | "karma-coverage": "2.0.3", 70 | "karma-coverage-istanbul-reporter": "3.0.3", 71 | "karma-jasmine": "4.0.1", 72 | "karma-jasmine-ajax": "0.1.13", 73 | "karma-sourcemap-loader": "0.3.8", 74 | "karma-webpack": "5.0.0", 75 | "less": "^4.1.1", 76 | "less-loader": "^8.0.0", 77 | "mini-css-extract-plugin": "^1.3.9", 78 | "optimize-css-assets-webpack-plugin": "^5.0.4", 79 | "resolve-url-loader": "^3.1.2", 80 | "sinon": "^1.17.7", 81 | "style-loader": "^0.20.3", 82 | "terser-webpack-plugin": "^5.1.1", 83 | "ts-loader": "^9.2.6", 84 | "typescript": "^4.4.4", 85 | "url-loader": "^1.1.2", 86 | "@babel/preset-react": "^7.0.0", 87 | "react": "^16.8.6", 88 | "react-dom": "^16.8.6", 89 | "vue": "^2.6.14", 90 | "angular": "^1.7.5", 91 | "webpack": "^5.24.3", 92 | "webpack-bundle-analyzer": "^4.4.0", 93 | "webpack-cli": "^4.5.0", 94 | "webpack-dev-server": "^3.11.2" 95 | }, 96 | "dependencies": {} 97 | } 98 | -------------------------------------------------------------------------------- /src/common/Store.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 实例化数据的存储对象 3 | */ 4 | import { GM_VERSION } from '@common/constants'; 5 | import { Row } from 'typings/data'; 6 | 7 | interface StoreInterface { 8 | version: string; 9 | responseData: { 10 | [_: string]: Array 11 | }; 12 | checkedData: any; 13 | settings: any; 14 | } 15 | const Store: StoreInterface = { 16 | // 版本号 17 | version: GM_VERSION, 18 | 19 | // GM使用的数据 20 | responseData: {}, 21 | 22 | // 当前选中的数据列表 23 | checkedData: {}, 24 | 25 | // 表配置信息存储器 26 | settings: { 27 | // columnData: 表配置项, 在宽度、位置等信息变化后 会 即时更新 28 | // columnMap: 是在GridManager.js中通过columnData生成的, 在宽度\位置等信息变化后 会 即时更新 29 | // 其它配置项... 30 | } 31 | }; 32 | 33 | export default Store; 34 | -------------------------------------------------------------------------------- /src/common/domCache.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 通过将在实例销毁前一直存在的dom进行缓存,以减少DOM操作提升性能。 3 | * cacheMap中仅缓存操作较频繁的DOM,操作较少的DOM不需要进行缓存。 4 | * 5 | * 注意: 需要保证这些缓存的DOM获取方法在DOM未存在时不被调用,实例销毁前不被清除。 6 | */ 7 | import jTool from '@jTool'; 8 | import { DIV_KEY, FAKE_TABLE_HEAD_KEY, TABLE_BODY_KEY, TABLE_HEAD_KEY, TABLE_KEY, WRAP_KEY } from '@common/constants'; 9 | 10 | // 缓存map: 操作较少的不需要放在这里 11 | const cacheMap: { 12 | [index:string]: any 13 | } = { 14 | [TABLE_KEY]: {}, 15 | [DIV_KEY]: {}, 16 | [WRAP_KEY]: {}, 17 | [TABLE_HEAD_KEY]: {}, 18 | [FAKE_TABLE_HEAD_KEY]: {}, 19 | [TABLE_BODY_KEY]: {}, 20 | 'allTh': {}, 21 | 'allFakeTh': {} 22 | }; 23 | 24 | /** 25 | * 获取缓存dom 26 | * @param _ 27 | * @param key 28 | * @param querySelector: 为空时,自动使用key + _ 进行拼接 29 | * @returns {*} 30 | */ 31 | export const getCacheDOM = (_: string, key: string, querySelector?: string) => { 32 | const cache = cacheMap[key]; 33 | if (!cache[_]) { 34 | cache[_] = jTool(querySelector || `[${key}="${_}"]`); 35 | } 36 | return cache[_]; 37 | }; 38 | 39 | /** 40 | * 清除指定实例的缓存 41 | * @param _ 42 | */ 43 | export const clearCacheDOM = (_: string): void => { 44 | for (let key in cacheMap) { 45 | delete cacheMap[key][_]; 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /src/common/events.ts: -------------------------------------------------------------------------------- 1 | import { EventObj } from 'typings/types'; 2 | 3 | // 事件: click 4 | export const MOUSE_CLICK = 'click'; 5 | 6 | // 事件: dbclick 7 | export const MOUSE_DBCLICK = 'dblclick'; 8 | 9 | // 事件: mousedown 10 | export const MOUSE_DOWN = 'mousedown'; 11 | 12 | // 事件: mousemove 13 | export const MOUSE_MOVE = 'mousemove'; 14 | 15 | // 事件: mouseup 16 | export const MOUSE_UP = 'mouseup'; 17 | 18 | // 事件: mouseleave 19 | export const MOUSE_LEAVE = 'mouseleave'; 20 | 21 | // 事件: mouseover 22 | export const MOUSE_OVER = 'mouseover'; 23 | 24 | // 事件: contextmenu 25 | export const CONTEXT_MENU = 'contextmenu'; 26 | 27 | // 事件: resize 28 | export const RESIZE = 'resize'; 29 | 30 | // 事件: scroll 31 | export const SCROLL = 'scroll'; 32 | 33 | // 事件: keyup 34 | export const KEY_UP = 'keyup'; 35 | 36 | // 事件名 37 | export const EVENTS = 'events'; 38 | 39 | // 事件绑定对像 40 | export const TARGET = 'target'; 41 | 42 | // 事件选择器 43 | export const SELECTOR = 'selector'; 44 | 45 | // 获取事件对像 46 | export const createEventsObj = (events: string, target: string, selector?: string): EventObj => { 47 | return { 48 | [EVENTS]: events, 49 | [TARGET]: target, 50 | [SELECTOR]: selector 51 | }; 52 | }; 53 | -------------------------------------------------------------------------------- /src/common/parse.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 解析html模板, 该方法为装饰器方法 3 | * @param tpl 4 | * @returns {Function} 5 | */ 6 | /* 仅在该文件内使用 new Function */ 7 | /* eslint no-new-func: "off"*/ 8 | export const parseTpl = (tpl: string) => { 9 | return (target: object, key: string, descriptor: any) => { 10 | const oldValue = descriptor.value; 11 | // params 中如果存在 tpl 则使用 params 中的进行渲染 12 | descriptor.value = (params: any) => { 13 | const vm = oldValue.call(target, params); 14 | 15 | let str = params && params.tpl || tpl; 16 | 17 | return str.replace(/\{\{([^(\}\})]+)\}\}/g, (match: string, evalStr: string) => { 18 | return new Function('vm', 'return ' + evalStr)(vm) || ''; 19 | }); 20 | }; 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /src/common/utils.ts: -------------------------------------------------------------------------------- 1 | import { CONSOLE_ERROR, CONSOLE_INFO, CONSOLE_STYLE, CONSOLE_WARN } from '@common/constants'; 2 | import { isString } from '@jTool/utils'; 3 | /** 4 | * 工具函数 5 | */ 6 | 7 | /** 8 | * 输出日志 9 | * @param s 输出文本 10 | * @param type 输出分类[info,warn,error] 11 | * @returns {*} 12 | */ 13 | const OUT_LOG = (s: string, type: string): void => { 14 | console.log(`%c GridManager ${type} %c ${s} `, ...CONSOLE_STYLE[type]); 15 | }; 16 | 17 | /** 18 | * 输出信息 19 | * @param s 输出文本 20 | * @returns {*} 21 | */ 22 | export const outInfo = (s: string): void => { 23 | OUT_LOG(s, CONSOLE_INFO); 24 | }; 25 | 26 | /** 27 | * 输出警告 28 | * @param s 输出文本 29 | * @returns {*} 30 | */ 31 | export const outWarn = (s: string): void => { 32 | OUT_LOG(s, CONSOLE_WARN); 33 | }; 34 | 35 | /** 36 | * 输出错误 37 | * @param s 输出文本 38 | * @returns {*} 39 | */ 40 | export const outError = (s: string): void => { 41 | OUT_LOG(s, CONSOLE_ERROR); 42 | }; 43 | 44 | /** 45 | * 验证两个Object是否相同 46 | * @param o1 47 | * @param o2 48 | * @param key: 指定精准匹配字段,只要当前字段相同则判定相同 49 | * @returns {boolean} 50 | */ 51 | export const equal = (o1: any, o2: any, key?: string): boolean => { 52 | // 当前指定了精准匹配字段 53 | if (isString(key)) { 54 | return o1[key] === o2[key]; 55 | } 56 | const k1 = Object.keys(o1); 57 | const k2 = Object.keys(o2); 58 | // 全额匹配 59 | if (k1.length !== k2.length) { 60 | return false; 61 | } 62 | 63 | return k1.every(key => { 64 | return JSON.stringify(o1[key]) === JSON.stringify(o2[key]); 65 | }); 66 | }; 67 | 68 | /** 69 | * 获取Array中Object的索引 70 | * @param arr 71 | * @param obj 72 | * @param key: 指定精准匹配字段 73 | * @returns {number} 74 | */ 75 | export const getObjectIndexToArray = (arr: Array, obj: any, key: string): number => { 76 | let index = -1; 77 | let isInclude = false; 78 | arr.some((item, i) => { 79 | isInclude = equal(item, obj, key); 80 | if (isInclude) { 81 | index = i; 82 | } 83 | return isInclude; 84 | }); 85 | return index; 86 | }; 87 | 88 | /** 89 | * clone 对象, 对 JSON.stringify 存在丢失的类型(如function)不作处理。因为GM中不存在这种情况 90 | * @param o 91 | * @returns {any} 92 | */ 93 | export const cloneObject = (o: any): any => { 94 | return JSON.parse(JSON.stringify(o)); 95 | }; 96 | -------------------------------------------------------------------------------- /src/css/animation.less: -------------------------------------------------------------------------------- 1 | /* 2 | * GridManager 所需动画 3 | */ 4 | .table-wrap{ 5 | /*闪烁动画特效*/ 6 | @keyframes opacityChange{ 7 | 0%{ 8 | opacity:0.1; 9 | } 10 | 100%{ 11 | opacity:0.7; 12 | } 13 | } 14 | /* 旋转动画特效 */ 15 | @keyframes rotationMedia{ 16 | 0%{ 17 | transform: rotate(0deg); 18 | } 19 | 100%{ 20 | transform: rotate(360deg); 21 | } 22 | } 23 | 24 | /* loading animation*/ 25 | @keyframes gmCircleRotate { 26 | from { 27 | transform: rotate(0deg); 28 | } 29 | to { 30 | transform: rotate(360deg); 31 | } 32 | } 33 | 34 | 35 | /* 复选框选择动画 */ 36 | @keyframes gmCheckboxEffect { 37 | 0% { 38 | transform: scale(1); 39 | opacity: .5; 40 | } 41 | 42 | to { 43 | transform: scale(1.5); 44 | opacity: 0; 45 | } 46 | } 47 | 48 | /* 单选框选择动画 */ 49 | @keyframes gmRadioEffect { 50 | 0% { 51 | transform: scale(1); 52 | opacity: .5; 53 | } 54 | 55 | to { 56 | transform: scale(1.5); 57 | opacity: 0; 58 | } 59 | } 60 | 61 | /*渐变消失动画特效*/ 62 | @keyframes gmHideEffect{ 63 | 0%{ 64 | opacity:1; 65 | } 66 | 100%{ 67 | opacity:0; 68 | } 69 | } 70 | 71 | /*渐变显示动画特效*/ 72 | @keyframes gmShowEffect{ 73 | 0%{ 74 | opacity:0; 75 | } 76 | 100%{ 77 | opacity:1; 78 | } 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /src/css/gridBase.less: -------------------------------------------------------------------------------- 1 | /* 基础样式 */ 2 | @import './loading'; 3 | 4 | /* 基本样式, 默认样式清除 */ 5 | .table-wrap, .gm-menu{ 6 | font: var(--gm-font-size)/1.5 "Microsoft YaHei", Arial, Helvetica, sans-serif; 7 | box-sizing: border-box !important; 8 | * { 9 | box-sizing: border-box !important; 10 | } 11 | *, *::before, *::after { 12 | box-sizing: border-box !important; 13 | } 14 | 15 | /* 自定义滚动条样式 */ 16 | ::-webkit-scrollbar-track { 17 | border-radius: 5px; 18 | background-color: #f3f3f3; 19 | } 20 | ::-webkit-scrollbar-track-piece { 21 | display: none; 22 | } 23 | ::-webkit-scrollbar { 24 | background: transparent; 25 | width: 10px; 26 | height: 10px; 27 | } 28 | ::-webkit-scrollbar-thumb { 29 | border-radius: 5px; 30 | background-color: #e1e1e1; 31 | width: 6px; 32 | min-height: 50px; 33 | border: 2px solid transparent; 34 | background-clip: content-box; 35 | } 36 | ul, li{ 37 | list-style-type: none; 38 | margin: 0; 39 | padding: 0; 40 | } 41 | 42 | } 43 | .table-div{ 44 | // 火狐修改滚动条: 以下为firefox特有属性 45 | scrollbar-width: thin; 46 | scrollbar-color: #e1e1e1 #f3f3f3; 47 | } 48 | 49 | /* 禁用文字选中 */ 50 | .no-select-text{ 51 | -moz-user-select: none; 52 | -webkit-user-select: none; 53 | user-select: none; 54 | } 55 | /* table配置中样式 */ 56 | [grid-manager]{ 57 | visibility: hidden; 58 | &.gm-ready{ 59 | visibility: inherit; 60 | } 61 | } 62 | 63 | // 文本镜像:用于实时获取th的文本宽度 64 | .text-dreamland { 65 | position: absolute; 66 | visibility: hidden; 67 | z-index: -1; 68 | } 69 | 70 | // 未解析的模板不可见 71 | [data-compile-id] { 72 | visibility: hidden; 73 | } 74 | -------------------------------------------------------------------------------- /src/css/loading.less: -------------------------------------------------------------------------------- 1 | @import './animation'; 2 | 3 | @loading-bg: #fff; 4 | @loading-stroke-width: 3; 5 | @loading-stroke-color: #00aaf1; 6 | /* 加载中动画 */ 7 | .gm-loading { 8 | width: 100%; 9 | height: 100%; 10 | position: absolute; 11 | top: 0; 12 | left: 0; 13 | z-index: 5; 14 | .loader { 15 | width: 100%; 16 | height: 100%; 17 | &:before { 18 | display: block; 19 | background: @loading-bg; 20 | opacity: 0.7; 21 | content: ''; 22 | z-index: 9999; 23 | padding-top: 100%; 24 | } 25 | .circular { 26 | width: 50px; 27 | height: 50px; 28 | position: absolute; 29 | top: calc(50% - 25px); 30 | left: calc(50% - 25px); 31 | animation: gmCircleRotate 1s linear infinite; 32 | transform-origin: center center; 33 | .path { 34 | stroke-width: @loading-stroke-width; 35 | stroke-miterlimit: 10; 36 | stroke: @loading-stroke-color; 37 | stroke-dasharray: 80; 38 | stroke-dashoffset: 0; 39 | stroke-linecap: round; 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/css/mixins.less: -------------------------------------------------------------------------------- 1 | // 文本自动显示... 2 | .text-overflow { 3 | white-space: nowrap; 4 | overflow: hidden; 5 | text-overflow: ellipsis; 6 | } 7 | -------------------------------------------------------------------------------- /src/css/var.less: -------------------------------------------------------------------------------- 1 | .table-wrap, .gm-menu{ 2 | // 字号 3 | --gm-font-size: 12px; 4 | 5 | // 色值 6 | --gm-color: #666; 7 | --gm-color-high: #000; 8 | --gm-color-active: #1890ff; 9 | 10 | // 边框线、分割线 11 | --gm-border: 1px solid #e8e8e8; 12 | --gm-border-high: 1px solid #ccc; 13 | --gm-border-active: 1px solid #aacbe1; 14 | 15 | // 背景色 16 | --gm-bg: #fff; 17 | --gm-bg-odd: #fafafa; 18 | --gm-bg-high: #f2f2f2; 19 | 20 | // tooltip 21 | --gm-remind-bg: #666; 22 | --gm-remind-color: #f8f8f8; 23 | --gm-remind-icon-color: #B9DAF8; 24 | 25 | // cssnano 会删除最后一个分号,在部分webpack的css处理中会报错。所以在这里用一个无用的属性来规避 26 | gm: 1; 27 | } 28 | -------------------------------------------------------------------------------- /src/demo/css/common.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | width: 100%; 3 | overflow-x:hidden; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | .plugin-action{ 8 | display: inline-block; 9 | color: steelblue; 10 | margin-right: 10px; 11 | cursor: pointer; 12 | text-decoration: none; 13 | } 14 | .plugin-action:nth-child(1) { 15 | margin-right: 0; 16 | } 17 | .plugin-action:hover{ 18 | text-decoration: underline; 19 | } 20 | .search-area{ 21 | padding: 10px 20px; 22 | border: 1px solid #ccc; 23 | background: #efefef; 24 | margin-bottom: 15px; 25 | } 26 | .search-area .sa-ele{ 27 | display: inline-block; 28 | margin-right: 20px; 29 | font-size: 12px; 30 | } 31 | .search-area .sa-ele .se-title{ 32 | display: inline-block; 33 | margin-right: 10px; 34 | } 35 | .search-area .sa-ele .se-con{ 36 | display: inline-block; 37 | width:180px; 38 | height: 24px; 39 | border: 1px solid #ccc; 40 | padding: 0 4px; 41 | line-height: 24px; 42 | } 43 | .search-area .sa-ele button{ 44 | display: inline-block; 45 | height: 26px; 46 | border: 1px solid #ccc; 47 | background: #e8e8e8; 48 | padding: 0 14px; 49 | line-height: 26px; 50 | text-align: center; 51 | cursor: pointer; 52 | margin-right: 10px; 53 | } 54 | .search-area .sa-ele button:hover{ 55 | opacity: 0.7; 56 | } 57 | 58 | .bottom-bar{ 59 | height: 30px; 60 | background: #f8f8f8; 61 | padding: 10px; 62 | margin-top: 10px; 63 | } 64 | .fn-select{ 65 | width: 100px; 66 | height: 28px; 67 | } 68 | .fn-code{ 69 | width: 400px; 70 | height: 28px; 71 | border: 0; 72 | padding: 0 10px; 73 | } 74 | .fn-run-info{ 75 | font-size: 12px; 76 | padding: 0 10px; 77 | } 78 | .bottom-bar .fn-run-info a{ 79 | color: #0f88eb; 80 | margin: 0; 81 | } 82 | .success-info{ 83 | color: #008800; 84 | } 85 | .error-info{ 86 | color: #880000; 87 | } 88 | .bottom-bar button{ 89 | padding: 5px 20px; 90 | margin-right: 10px; 91 | border: 1px solid #999; 92 | } 93 | .bottom-bar a{ 94 | font-size: 12px; 95 | margin-right: 10px; 96 | } 97 | .void-template{ 98 | height:300px; 99 | line-height: 300px; 100 | text-align: center; 101 | font-size: 24px; 102 | color: #ccc; 103 | } 104 | .grid-main { 105 | height: calc(100vh - 64px - 60px); 106 | overflow: auto; 107 | } 108 | -------------------------------------------------------------------------------- /src/demo/css/layout.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | width: 100%; 3 | height: 100%; 4 | overflow-x:hidden; 5 | margin: 0; 6 | padding: 0; 7 | font-size: 12px; 8 | box-sizing: border-box; 9 | } 10 | body{ 11 | padding: 0; 12 | margin: 0; 13 | } 14 | p{ 15 | font-size:14px; 16 | padding:10px 30px; 17 | color:#333; 18 | text-indent:2em; 19 | margin: 0; 20 | } 21 | .demo-site{ 22 | display: flex; 23 | height: 100%; 24 | } 25 | 26 | .demo-tabs{ 27 | width: 200px; 28 | height: 100%; 29 | overflow-y: auto; 30 | border-right: 1px solid #ccc; 31 | margin-right: 10px; 32 | } 33 | .demo-tabs .demo-ele{ 34 | display: block; 35 | padding: 10px; 36 | margin-bottom: -1px; 37 | cursor: pointer; 38 | text-align: center; 39 | } 40 | .demo-tabs .demo-ele:nth-child(odd) { 41 | background-color: #eee; 42 | } 43 | .demo-tabs .demo-ele[selected="selected"]{ 44 | background-color: #1890ff; 45 | color: #fff; 46 | } 47 | .demo-tabs .demo-ele:hover{ 48 | opacity: 0.7;; 49 | } 50 | #demo-iframe{ 51 | width: 100%; 52 | height: 100%; 53 | margin: 0; 54 | padding: 0; 55 | border: 0; 56 | } 57 | -------------------------------------------------------------------------------- /src/demo/demo1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | GridManager:使用动态数据渲染 10 | 11 | 12 | 13 |
14 |
15 | 16 | 17 |
18 |
19 | 20 | 24 |
25 |
26 | 27 | 28 |
29 |
30 | 31 | 32 | 33 | 34 |
35 |
36 | 37 |
38 |
39 |
40 | 41 |
42 | 查看源码 43 | 44 | 45 | 46 | 47 |
48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/demo/demo3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | GridManager:多表I18N 8 | 9 | 10 | 11 | 12 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GridManager demo 6 | 7 | 8 | 9 |
10 |
11 | 带搜索功能的动态数据 12 | 简易静态数据 13 | 多表 I18N 14 | 通栏样式 15 | 单选行 16 | 无总页 17 | 延迟总页 18 | 嵌套表头 19 | 数据折叠 20 | 万条不卡 21 | jquery 22 | promise 23 |
24 | 25 |
26 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/fonts/iconfont.less: -------------------------------------------------------------------------------- 1 | /* 图标库来源=> http://www.iconfont.cn 使用github账号*/ 2 | @font-face { 3 | font-family: "gm-iconfont"; 4 | src: url("./iconfont.woff") format('woff'); 5 | } 6 | .gm-icon { 7 | font-family: "gm-iconfont" !important; 8 | font-style: normal; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | .gm-icon-move:before { 14 | content: "\e646"; 15 | } 16 | 17 | .gm-icon-up:before { 18 | content: "\e640"; 19 | } 20 | 21 | .gm-icon-refresh:before { 22 | content: "\e602"; 23 | } 24 | 25 | .gm-icon-help:before { 26 | content: "\e65d"; 27 | } 28 | 29 | .gm-icon-filter:before { 30 | content: "\e6e2"; 31 | } 32 | 33 | .gm-icon-down:before { 34 | content: "\e637"; 35 | } 36 | 37 | .gm-icon-export-checked:before { 38 | content: "\e639"; 39 | } 40 | 41 | .gm-icon-config:before { 42 | content: "\e63e"; 43 | } 44 | 45 | .gm-icon-close:before { 46 | content: "\e6ca"; 47 | } 48 | 49 | .gm-icon-sub:before { 50 | content: "\e655"; 51 | } 52 | 53 | .gm-icon-add:before { 54 | content: "\e65f"; 55 | } 56 | 57 | .gm-icon-export:before { 58 | content: "\e70f"; 59 | } 60 | 61 | .gm-icon-print:before { 62 | content: "\e620"; 63 | } 64 | 65 | .gm-icon-copy:before { 66 | content: "\e6a0"; 67 | } 68 | .gm-icon-hide:before { 69 | content: "\ebe3"; 70 | } 71 | 72 | -------------------------------------------------------------------------------- /src/fonts/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baukh789/GridManager/3b8ec6737416a5a4042b1425c1055a2bf324dcc0/src/fonts/iconfont.woff -------------------------------------------------------------------------------- /src/framework/angular-1.x/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GM-Angular 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 |
17 |
18 | 19 | 20 |
21 |
22 | 23 | 24 | 25 | 26 |
27 |
28 | 29 |
30 |
31 | 32 |
33 |
34 | 35 |
36 | 37 | 38 | 查看源码 39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /src/framework/angular-1.x/demo/style.css: -------------------------------------------------------------------------------- 1 | 2 | html, body{ 3 | width: 100%; 4 | height: 100%; 5 | overflow-x:hidden; 6 | margin: 0; 7 | padding: 0; 8 | } 9 | * { 10 | box-sizing: border-box; 11 | } 12 | .plugin-action{ 13 | display: inline-block; 14 | color: steelblue; 15 | margin-right: 10px; 16 | cursor: pointer; 17 | } 18 | .plugin-action:hover{ 19 | text-decoration:underline; 20 | } 21 | .search-area{ 22 | height: 50px; 23 | padding: 10px 20px; 24 | border: 1px solid #ccc; 25 | background: #efefef; 26 | margin: 0 10px 15px 10px; 27 | } 28 | .search-area .sa-ele{ 29 | display: inline-block; 30 | margin-right: 20px; 31 | font-size: 12px; 32 | } 33 | .search-area .sa-ele .se-title{ 34 | display: inline-block; 35 | margin-right: 10px; 36 | } 37 | .search-area .sa-ele .se-con{ 38 | display: inline-block; 39 | width:180px; 40 | height: 24px; 41 | border: 1px solid #ccc; 42 | padding: 0 4px; 43 | line-height: 24px; 44 | } 45 | .search-area .sa-ele button{ 46 | display: inline-block; 47 | height: 26px; 48 | border: 1px solid #ccc; 49 | background: #e8e8e8; 50 | padding: 0 14px; 51 | line-height: 26px; 52 | text-align: center; 53 | cursor: pointer; 54 | margin-right: 10px; 55 | } 56 | .search-area .sa-ele button:hover{ 57 | opacity: 0.7; 58 | } 59 | 60 | .grid-main { 61 | margin: 10px; 62 | height: calc(100vh - 64px - 60px); 63 | overflow: auto; 64 | } 65 | .bottom-bar{ 66 | height: 50px; 67 | background: #f8f8f8; 68 | padding: 10px; 69 | margin-top: 10px; 70 | } 71 | .bottom-bar button{ 72 | padding: 5px 20px; 73 | margin-right: 10px; 74 | } 75 | -------------------------------------------------------------------------------- /src/framework/angular-1.x/js/controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by baukh on 18/3/8. 3 | */ 4 | export default class GridManagerController { 5 | constructor($scope, $element, $compile, $gridManager) { 6 | this._$element = $element; 7 | this._$compile = $compile; 8 | this._$scope = $scope; 9 | this._$gridManager = $gridManager; 10 | 11 | // 存储 angular scope 12 | this.angularCache = []; 13 | } 14 | 15 | /** 16 | * 清除已经不存在的 angular scope 17 | */ 18 | updateCache() { 19 | this.angularCache = this.angularCache.filter(item => { 20 | const { el, scope } = item; 21 | if (!getComputedStyle(el).display) { 22 | // 清除framework.send 后存在操作的DOM节点 23 | const tree = el.querySelector('[tree-element]'); 24 | tree && el.removeChild(tree); 25 | 26 | // 移除angular node 27 | scope.$destroy(); 28 | el.remove(); 29 | } 30 | return !!getComputedStyle(el).display; 31 | }); 32 | } 33 | $onInit() { 34 | // 当前表格组件所在的域 35 | const _parent = this._$scope.$parent; 36 | 37 | // 获取当前组件的DOM 38 | const table = this._$element[0].querySelector('table'); 39 | 40 | const { _$gridManager, option, callback } = this; 41 | 42 | // 模板解析勾子,这个勾子在原生组件内通过sendCompile进行触发 43 | option.compileAngularjs = compileList => { 44 | this.updateCache(); 45 | return new Promise(resolve => { 46 | let scope = null; 47 | let el = null; 48 | const $new = _parent.$new.bind(_parent); 49 | const $compile = this._$compile; 50 | compileList.forEach(item => { 51 | scope = $new(false); // false 不隔离父级 52 | scope.row = item.row; 53 | scope.index = item.index; 54 | scope.key = item.key; 55 | el = item.el; 56 | 57 | // 将生成的内容进行替换 58 | el.replaceWith($compile(el)(scope)[0]); 59 | 60 | this.angularCache.push({el, scope}); 61 | }); 62 | 63 | // 延时触发angular 脏检查 64 | setTimeout(() => { 65 | _parent.$digest(); 66 | resolve(); 67 | }); 68 | }); 69 | }; 70 | 71 | // 调用原生组件进行实例化 72 | new _$gridManager(table, option, query => { 73 | typeof (callback) === 'function' && callback({query: query}); 74 | // _$gridManager.setScope(table, _parent); 75 | }); 76 | } 77 | 78 | /** 79 | * 销毁钩子 80 | */ 81 | $onDestroy() { 82 | // 销毁实例 83 | this._$gridManager.destroy(this.option.gridManagerName); 84 | } 85 | } 86 | GridManagerController.$inject = ['$scope', '$element', '$compile', '$gridManager']; 87 | -------------------------------------------------------------------------------- /src/framework/angular-1.x/js/index.js: -------------------------------------------------------------------------------- 1 | import controller from './controller'; 2 | import $gridManager, { jTool } from '../../../module/index'; 3 | // import 'gridmanager/css/gm.css'; 4 | 5 | const template = '
'; 6 | const GridManagerComponent = { 7 | controller, 8 | template, 9 | controllerAs: 'vm', 10 | bindings: { 11 | option: '<', 12 | callback: '&' 13 | } 14 | }; 15 | // angular 1.x 无论哪种引入方式都会向 window上挂载,所以可以直接使用window.angular 16 | const gridManagerModuel = window.angular.module('gridManager', []); 17 | 18 | let name = gridManagerModuel 19 | .component('gridManager', GridManagerComponent) 20 | .value('$gridManager', $gridManager) 21 | .value('$jTool', jTool) 22 | .name; 23 | 24 | gridManagerModuel.version = process.env.VERSION; 25 | 26 | export { $gridManager, jTool }; 27 | export default name; 28 | -------------------------------------------------------------------------------- /src/framework/react/demo/AppContext.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const AppContext = React.createContext({gridManagerName: 'testReact'}); 4 | 5 | export default AppContext; 6 | -------------------------------------------------------------------------------- /src/framework/react/demo/components.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { $gridManager } from '../js/index.js'; 3 | 4 | // 组件: 操作列 5 | function ActionInner(props) { 6 | const actionAlert = event => { 7 | alert('操作栏th是由React模板渲染的'); 8 | }; 9 | return {props.text}; 10 | } 11 | 12 | // 组件: th 操作列 13 | export function ActionComponents(props) { 14 | return ; 15 | } 16 | 17 | // 组件: 空模板 18 | export function EmptyTemplate(props) { 19 | return ( 20 |
21 | {props.text} 22 | state 23 |
24 | ); 25 | } 26 | 27 | // 组件: 标题 28 | export function TitleComponents(props) { 29 | return ( 30 | {props.row.title} 31 | ); 32 | } 33 | 34 | // 组件: 类型 35 | export function TypeComponents(props) { 36 | // 博文类型 37 | const TYPE_MAP = { 38 | '1': 'HTML/CSS', 39 | '2': 'nodeJS', 40 | '3': 'javaScript', 41 | '4': '前端鸡汤', 42 | '5': 'PM Coffee', 43 | '6': '前端框架', 44 | '7': '前端相关' 45 | }; 46 | return ( 47 | 48 | ); 49 | } 50 | // 组件: 删除 51 | export function EditComponents(props) { 52 | const { gmkey, index, row } = props; 53 | const editAction = () => { 54 | // window.event.stopPropagation(); 55 | // window.event.preventDefault(); 56 | row.title = row.title + '(编辑于' + new Date().toLocaleDateString() + ')'; 57 | $gridManager.updateRowData(gmkey, 'id', row); 58 | }; 59 | 60 | return ( 61 | 编辑 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /src/framework/react/demo/footer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import AppContext from './AppContext'; 3 | 4 | export default class FooterComponent extends Component { 5 | static contextType = AppContext; 6 | resetTable() { 7 | this.props.resetTable(true); 8 | } 9 | 10 | destroy() { 11 | this.props.resetTable(false); 12 | } 13 | 14 | render() { 15 | return ( 16 |
17 | 18 | 19 |
20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/framework/react/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GM-React 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/framework/react/demo/search.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import GridManager from '../js/index.js'; 3 | import AppContext from './AppContext'; 4 | 5 | export default function SearchComponent(props) { 6 | const [title, setTitle] = useState(''); 7 | const [content, setContent] = useState(''); 8 | const { onAddCol, onRemoveCol } = props; 9 | 10 | function setQuery(gridManagerName) { 11 | GridManager.setQuery(gridManagerName, {title, content}); 12 | } 13 | 14 | function reset() { 15 | setTitle(''); 16 | setContent(''); 17 | } 18 | return ( 19 | 20 | {({gridManagerName}) => ( 21 |
22 |
23 | 24 | setTitle(event.target.value)}/> 25 |
26 |
27 | 28 | setContent(event.target.value)}/> 29 |
30 |
31 | 32 | 33 | 34 | 35 |
36 |
37 | )} 38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/framework/react/demo/style.css: -------------------------------------------------------------------------------- 1 | 2 | html, body{ 3 | width: 100%; 4 | height: 100%; 5 | overflow-x:hidden; 6 | margin: 0; 7 | padding: 0; 8 | } 9 | #app{ 10 | height: 100%; 11 | } 12 | * { 13 | box-sizing: border-box; 14 | } 15 | .plugin-action{ 16 | display: inline-block; 17 | color: steelblue; 18 | margin-right: 10px; 19 | cursor: pointer; 20 | } 21 | .plugin-action:hover{ 22 | text-decoration:underline; 23 | } 24 | .search-area{ 25 | height: 50px; 26 | padding: 10px 20px; 27 | border: 1px solid #ccc; 28 | background: #efefef; 29 | margin: 0 10px 15px 10px; 30 | } 31 | .search-area .sa-ele{ 32 | display: inline-block; 33 | margin-right: 20px; 34 | font-size: 12px; 35 | } 36 | .search-area .sa-ele .se-title{ 37 | display: inline-block; 38 | margin-right: 10px; 39 | } 40 | .search-area .sa-ele .se-con{ 41 | display: inline-block; 42 | width:180px; 43 | height: 24px; 44 | border: 1px solid #ccc; 45 | padding: 0 4px; 46 | line-height: 24px; 47 | } 48 | .search-area .sa-ele button{ 49 | display: inline-block; 50 | height: 26px; 51 | border: 1px solid #ccc; 52 | background: #e8e8e8; 53 | padding: 0 14px; 54 | line-height: 26px; 55 | text-align: center; 56 | cursor: pointer; 57 | margin-right: 10px; 58 | } 59 | .search-area .sa-ele button:hover { 60 | opacity: 0.7; 61 | } 62 | 63 | #example{ 64 | margin: 10px; 65 | height: calc(100vh - 64px - 60px); 66 | overflow: auto; 67 | } 68 | 69 | .bottom-bar{ 70 | height: 50px; 71 | background: #f8f8f8; 72 | padding: 10px; 73 | margin-top: 10px; 74 | } 75 | .bottom-bar button{ 76 | padding: 5px 20px; 77 | margin-right: 10px; 78 | } 79 | -------------------------------------------------------------------------------- /src/framework/vue/demo/app-router.js: -------------------------------------------------------------------------------- 1 | import './style.css'; 2 | // 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter) 3 | import GridManagerVue, { $gridManager } from '../js/index'; 4 | // 模拟的一个promise请求 5 | const getBlogList = function(paramse) { 6 | return new Promise((resolve, reject) => { 7 | const xhr = new XMLHttpRequest(); 8 | xhr.open('POST', 'https://www.lovejavascript.com/blogManager/getBlogList'); 9 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 10 | xhr.onreadystatechange = function() { 11 | if (xhr.readyState !== 4) { 12 | return; 13 | } 14 | if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) { 15 | resolve(xhr.response); 16 | } else { 17 | reject(xhr); 18 | } 19 | }; 20 | 21 | // 一个简单的处理参数的示例 22 | let formData = ''; 23 | for (let key in paramse) { 24 | if(formData !== '') { 25 | formData += '&'; 26 | } 27 | formData += key + '=' + paramse[key]; 28 | } 29 | xhr.send(formData); 30 | }); 31 | }; 32 | Vue.use(GridManagerVue); 33 | const option = { 34 | supportRemind: true, 35 | gridManagerName: 'test', 36 | height: '100%', 37 | supportAjaxPage: true, 38 | isCombSorting: false, 39 | disableCache: true, 40 | ajaxData: (settings, params) => { 41 | return getBlogList(params); 42 | }, 43 | // ajaxData: ajaxData2, 44 | // supportTreeData: true, 45 | ajaxType: 'POST', 46 | query: {test: 22}, 47 | pageSize: 30, 48 | columnData: [ 49 | { 50 | key: 'pic', 51 | remind: 'the pic', 52 | width: '140px', 53 | align: 'center', 54 | text: '缩略图', 55 | template: () => { 56 | console.log(111); 57 | return '' 58 | } 59 | }, { 60 | key: 'title', 61 | remind: 'the title', 62 | align: 'left', 63 | text: '标题' 64 | } 65 | ] 66 | } 67 | // 1. 定义 (路由) 组件。 68 | // 可以从其他文件 import 进来 69 | const Foo = Vue.component('my-component', { 70 | template: '
', 71 | data: function () { 72 | return { 73 | option: option 74 | } 75 | }, 76 | }) 77 | const Bar = { template: '
bar
' } 78 | 79 | // 2. 定义路由 80 | // 每个路由应该映射一个组件。 其中"component" 可以是 81 | // 通过 Vue.extend() 创建的组件构造器, 82 | // 或者,只是一个组件配置对象。 83 | // 我们晚点再讨论嵌套路由。 84 | const routes = [ 85 | { path: '/foo', component: Foo }, 86 | { path: '/bar', component: Bar } 87 | ] 88 | 89 | // 3. 创建 router 实例,然后传 `routes` 配置 90 | // 你还可以传别的配置参数, 不过先这么简单着吧。 91 | const router = new VueRouter({ 92 | routes // (缩写) 相当于 routes: routes 93 | }) 94 | 95 | // 4. 创建和挂载根实例。 96 | // 记得要通过 router 配置参数注入路由, 97 | // 从而让整个应用都有路由功能 98 | const app = new Vue({ 99 | router 100 | }).$mount('#app') 101 | 102 | // 现在,应用已经启动了! 103 | -------------------------------------------------------------------------------- /src/framework/vue/demo/demo-router.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |

Hello App!

8 |

9 | 10 | 11 | 12 | Go to Foo 13 | Go to Bar 14 |

15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/framework/vue/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GM-Vue 6 | 7 | 8 | 9 |
10 |
11 |
12 | 13 | 14 |
15 |
16 | 17 | 18 |
19 |
20 | 21 | 22 | 23 | 24 |
25 |
26 |
27 |
28 | 29 |
30 |
31 | 32 |
33 | 34 | 35 | 查看源码 36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/framework/vue/demo/style.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | width: 100%; 3 | height: 100%; 4 | overflow-x:hidden; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | * { 9 | box-sizing: border-box; 10 | } 11 | table .plugin-action{ 12 | display: inline-block; 13 | color: steelblue; 14 | margin-right: 10px; 15 | cursor: pointer; 16 | text-decoration: none; 17 | } 18 | table .plugin-action:hover{ 19 | text-decoration: underline; 20 | } 21 | .search-area{ 22 | height: 50px; 23 | padding: 10px 20px; 24 | border: 1px solid #ccc; 25 | background: #efefef; 26 | margin: 0 10px 15px 10px; 27 | } 28 | .search-area .sa-ele{ 29 | display: inline-block; 30 | margin-right: 20px; 31 | font-size: 12px; 32 | } 33 | .search-area .sa-ele .se-title{ 34 | display: inline-block; 35 | margin-right: 10px; 36 | } 37 | .search-area .sa-ele .se-con{ 38 | display: inline-block; 39 | width:180px; 40 | height: 24px; 41 | border: 1px solid #ccc; 42 | padding: 0 4px; 43 | line-height: 24px; 44 | } 45 | .search-area .sa-ele button{ 46 | display: inline-block; 47 | height: 26px; 48 | border: 1px solid #ccc; 49 | background: #e8e8e8; 50 | padding: 0 14px; 51 | line-height: 26px; 52 | text-align: center; 53 | cursor: pointer; 54 | margin-right: 10px; 55 | } 56 | .search-area .sa-ele buttonn:hover{ 57 | opacity: 0.7; 58 | } 59 | 60 | .grid-main { 61 | margin: 10px; 62 | height: calc(100vh - 64px - 60px); 63 | overflow: auto; 64 | } 65 | .bottom-bar{ 66 | height: 50px; 67 | background: #f8f8f8; 68 | padding: 10px; 69 | margin-top: 10px; 70 | } 71 | .bottom-bar button{ 72 | padding: 5px 20px; 73 | margin-right: 10px; 74 | } 75 | -------------------------------------------------------------------------------- /src/framework/vue/js/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by baukh on 18/3/8. 3 | */ 4 | import GridManagerVue, { $gridManager, jTool } from './gridmanager-vue'; 5 | 6 | // Vue install, Vue.use 会调用该方法。 7 | GridManagerVue.install = (Vue, opts = {}) => { 8 | // 将构造函数挂载至Vue原型上 9 | // 这样在Vue环境下,可在实例化对像this上使用 this.$gridManager 进行方法调用 10 | Vue.prototype.$gridManager = $gridManager; 11 | Vue.component('grid-manager', GridManagerVue); 12 | }; 13 | 14 | // 通过script标签引入Vue的环境 15 | if (typeof window !== 'undefined' && window.Vue) { 16 | GridManagerVue.install(window.Vue); 17 | } 18 | 19 | // GridManagerVue 的版本号。 需要注意的是: 这仅仅是vue环境的壳, 验证功能需要查看GridManager的版本号 20 | GridManagerVue.version = process.env.VERSION; 21 | 22 | // 将原生方法,挂载至 Vue GridManager 上 23 | const staticList = Object.getOwnPropertyNames($gridManager); 24 | const noExtendsList = ['name', 'length', 'prototype', 'version']; 25 | staticList.forEach(key => { 26 | if (!noExtendsList.includes(key)) { 27 | GridManagerVue[key] = $gridManager[key]; 28 | } 29 | }); 30 | export { $gridManager, jTool }; 31 | export default GridManagerVue; 32 | 33 | -------------------------------------------------------------------------------- /src/jTool/Animate.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * 动画效果 3 | * --动画中的参数对应-- 4 | * styleObj: 元素将要实现的样式, 只允许为Object格式 5 | * time: 执行动画的间隔时间 6 | * callback: 动画执行完成后的回调函数 7 | * --Ex-- 8 | * 无回调参数: jTool('#div1').animate({height: '100px', width: '200px'}, 1000); 9 | * 无时间参数: jTool('#div1').animate({height: '100px', width: '200px'}, callback); 10 | * 完整参数: jTool('#div1').animate({height: '100px', width: '200px'}, 1000, callback); 11 | * --注意事项-- 12 | * show与hide方法只是一个简单的实现,不支持参数及动画效果 13 | * */ 14 | import { each, getStyle, noop, getDomList, rootDocument } from './utils'; 15 | import _Css from './Css'; 16 | 17 | const INLINE_BLOCK = 'inline-block'; 18 | const TABLE_CELL = 'table-cell'; 19 | const DISPLAY_MAP = { 20 | TABLE: 'table', 21 | THEAD: 'table-header-group', 22 | TBODY: 'table-row-group', 23 | TR: 'table-row', 24 | TH: TABLE_CELL, 25 | TD: TABLE_CELL, 26 | SPAN: INLINE_BLOCK, 27 | A: INLINE_BLOCK, 28 | FONT: INLINE_BLOCK, 29 | BUTTON: INLINE_BLOCK, 30 | I: INLINE_BLOCK 31 | }; 32 | 33 | export default { 34 | /** 35 | * 动画效果, 动画样式仅支持以对象类型传入且值需要存在有效的单位 36 | * @param styleObj 37 | * @param time 38 | * @param callback 39 | */ 40 | animate: function (styleObj: object, time = 0, callback = noop): void { 41 | let animateFromText = ''; // 动画执行前样式文本 42 | let animateToText = ''; // 动画执行后样式文本 43 | let node = getDomList(this, 0); 44 | 45 | // 组装动画 keyframes 46 | each(styleObj, (key: string, v: string) => { 47 | animateFromText += key + ':' + getStyle(node, key) + ';'; 48 | animateToText += key + ':' + v + ';'; 49 | }); 50 | // 拼接动画样式文本 51 | const animateText = `@keyframes jToolAnimate {from {${animateFromText}}to {${animateToText}}}`; 52 | 53 | // 引入动画样式至页面 54 | const jToolAnimate = rootDocument.createElement('style'); 55 | jToolAnimate.type = 'text/css'; 56 | rootDocument.head.appendChild(jToolAnimate); 57 | jToolAnimate.textContent = jToolAnimate.textContent + animateText; 58 | 59 | // 启用动画 60 | node.style.animation = `jToolAnimate ${time / 1000}s ease-in-out forwards`; 61 | 62 | // 延时执行回调函数及清理操作 63 | setTimeout(() => { 64 | _Css.css.call(this, styleObj); 65 | node.style.animation = ''; 66 | rootDocument.head.removeChild(jToolAnimate); 67 | callback(); 68 | }, time); 69 | }, 70 | show: function () { 71 | each(this, (v: HTMLElement) => { 72 | v.style.display = DISPLAY_MAP[v.nodeName] || 'block'; 73 | }); 74 | return this; 75 | }, 76 | hide: function () { 77 | each(this, (v: HTMLElement) => { 78 | v.style.display = 'none'; 79 | }); 80 | return this; 81 | } 82 | }; 83 | -------------------------------------------------------------------------------- /src/jTool/Class.ts: -------------------------------------------------------------------------------- 1 | import { each, getDomList } from './utils'; 2 | 3 | /** 4 | * 解析className 将以空格间格的字符串分割为数组 5 | * @param className 6 | * @returns {*} 7 | */ 8 | function parseClassName(className: string): Array { 9 | // 第一个空格不处理,所以indexOf的值为0也算做一个className 10 | return className.indexOf(' ') ? className.split(' ') : [className]; 11 | } 12 | 13 | /** 14 | * 执行指定classList方法 15 | * @param DOMList 16 | * @param className 17 | * @param exeName: 执行方法名 18 | * @returns {changeClass} 19 | */ 20 | function changeClass(DOMList: Array, className: string, exeName: string): void { 21 | const classNameList = parseClassName(className); 22 | each(DOMList, (dom: HTMLElement) => { 23 | each(classNameList, (name: string) => { 24 | dom.classList[exeName](name); 25 | }); 26 | }); 27 | } 28 | export default { 29 | addClass: function (className: string): unknown { 30 | changeClass(getDomList(this), className, 'add'); 31 | return this; 32 | }, 33 | 34 | removeClass: function (className: string): unknown { 35 | changeClass(getDomList(this), className, 'remove'); 36 | return this; 37 | }, 38 | 39 | // 不支持多 className 40 | hasClass: function (className: string): boolean { 41 | return [].some.call(getDomList(this), function (dom: HTMLElement) { 42 | return dom.classList.contains(className); 43 | }); 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /src/jTool/Css.ts: -------------------------------------------------------------------------------- 1 | import {getStyle, isObject, isNumber, isString, each, isUndefined, getDomList, isNull} from './utils'; 2 | 3 | /** 4 | * 当前属性的单位是否为px 5 | * @param name 6 | * @returns {boolean} 7 | */ 8 | const isPxAttr = (name: string): boolean => { 9 | return ['width', 'max-width', 'height', 'top', 'left', 'right', 'bottom', 'padding', 'margin'].some(key => name.indexOf(key) !== -1); 10 | }; 11 | 12 | /** 13 | * 设置样式 14 | * @param DOMList 15 | * @param name 16 | * @param val 17 | */ 18 | function setStyle(DOMList: Array, name: string, val: number | string): void { 19 | const PX = 'px'; 20 | if (isNull(val) || isUndefined(val)) { 21 | return; 22 | } 23 | if (isNumber(val)) { 24 | val = val.toString(); 25 | } 26 | if ((val as string).indexOf(PX) === -1 && isPxAttr(name)) { 27 | val = val + PX; 28 | } 29 | each(DOMList, (v: HTMLElement) => { 30 | v.style[name] = val; 31 | }); 32 | } 33 | export default { 34 | // 如果长度是带 px 的值, 会将其转换成 数字 35 | // 其他情况 不做处理, 返回对应的字符串 36 | css: function (key: string | object, value: string | number): string | number { 37 | const DOMList = getDomList(this); 38 | // getter 39 | if (isString(key) && isUndefined(value)) { 40 | const style = getStyle(DOMList[0], key as string); 41 | if (isPxAttr(key as string)) { 42 | // style = parseInt(style, 10); 43 | return parseFloat(style); 44 | // style = Math.round(parseFloat(style) * 100) / 100; 45 | } 46 | return style; 47 | } 48 | 49 | // setter 50 | // ex: {width:13px, height:10px} 51 | if (isObject(key)) { 52 | for(const k in key as object) { 53 | setStyle(DOMList, k, key[k]); 54 | } 55 | } else { // ex: width, 13px 56 | setStyle(DOMList, key as string, value); 57 | } 58 | return this; 59 | }, 60 | 61 | width: function (value: number | string) { 62 | return this.css('width', value); 63 | }, 64 | 65 | height: function (value: number | string) { 66 | return this.css('height', value); 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /src/jTool/Data.ts: -------------------------------------------------------------------------------- 1 | import { isUndefined, isNull, each, getDomList } from './utils'; 2 | 3 | /** 4 | * 转换值: 当前为null时转换为undefined 5 | * @param value 6 | * @returns {*} 7 | */ 8 | const transformVal = (value: null | string): undefined | string => { 9 | // null => undefined 10 | return isNull(value) ? undefined : value; 11 | }; 12 | 13 | export default { 14 | /** 15 | * 普通属性 16 | * @param key 17 | * @param value 18 | * @returns {*} 19 | */ 20 | attr: function (key: string, value?: string) { 21 | // setter 22 | if (!isUndefined(value)) { 23 | each(this, (v: HTMLElement) => { 24 | v.setAttribute(key, value); 25 | }); 26 | return this; 27 | } 28 | 29 | // getter 30 | return transformVal(getDomList(this, 0).getAttribute(key)); 31 | }, 32 | 33 | /** 34 | * 删除普通属性 35 | * @param key 36 | */ 37 | removeAttr: function (key: string): void { 38 | each(this, (v: HTMLElement) => { 39 | v.removeAttribute(key); 40 | }); 41 | }, 42 | 43 | /** 44 | * 配置固有属性 45 | * @param key 46 | * @param value 47 | * @returns {*} 48 | */ 49 | prop: function (key: string, value: string) { 50 | // setter 51 | if (!isUndefined(value)) { 52 | each(this, (v: HTMLElement) => { 53 | v[key] = value; 54 | }); 55 | return this; 56 | } 57 | 58 | // getter 59 | return transformVal(getDomList(this, 0)[key]); 60 | }, 61 | 62 | /** 63 | * value 64 | * @param value 65 | * @returns {*|string} 66 | */ 67 | val: function (value: string) { 68 | return this.prop('value', value) || ''; 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /src/jTool/Element.ts: -------------------------------------------------------------------------------- 1 | import Sizzle from './Sizzle'; 2 | import { getDomList, isJTool } from '@jTool/utils'; 3 | import { JTool } from 'typings/types'; 4 | 5 | export default { 6 | // 获取指定DOM Element 7 | get: function (index: number): HTMLElement { 8 | return getDomList(this, index); 9 | }, 10 | 11 | // 获取指定索引的jTool对象 12 | eq: function (index: number): JTool { 13 | return new Sizzle(getDomList(this, index)); 14 | }, 15 | 16 | // 返回指定选择器的jTool对象 17 | find: function (selectText: string): JTool { 18 | return new Sizzle(selectText, this); 19 | }, 20 | 21 | // 获取当前元素在指定元素中的索引, 当无参数时为当前同级元素中的索引 22 | index: function (nodeList?: JTool | NodeList): number { 23 | const node = getDomList(this, 0); 24 | // 查找范围参数为空时,找寻同层节点 25 | if (!nodeList) { 26 | nodeList = node.parentNode.children; 27 | } else if (isJTool(nodeList)) { // 查找范围参数为jTool对象,则使用对象的DOMList 28 | nodeList = getDomList(nodeList as JTool); 29 | } 30 | return nodeList ? [].indexOf.call(nodeList, node) : -1; 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/jTool/Offset.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * 位置 3 | * #Offset001: 返回值是ClientRect对象集合,该对象是与该元素相关的CSS边框。 4 | * 每个ClientRect对象包含一组描述该边框的只读属性——left、top、right和bottom,单位为像素,这些属性值是相对于视口的top-left的。 5 | * 即使当表格的标题在表格的边框外面,该标题仍会被计算在内。 6 | * #Offset001: Element.getBoundingClientRect()方法返回元素的大小及其相对于视口的位置。 7 | * 返回值包含了一组用于描述边框的只读属性——left、top、right和bottom,单位为像素。除了 width 和 height 外的属性都是相对于视口的左上角位置而言的。 8 | * 返回如{top: 8, right: 1432, bottom: 548, left: 8, width: 1424…} 9 | * #Offset003: 原先offset()方法使用的是递归形式: 递增寻找position !== static的父级节点offsetTop或offsetLeft的值, 直到node.nodeType !== 1的时候停止 10 | * 这种方法正常情况下没什么问题, 但是当body的position !== static时, 所计算的offset值将不包含scroll卷去的值 11 | * */ 12 | import { getDomList, getStyle, isNumber } from './utils'; 13 | 14 | /** 15 | * 根据参数对位置操作进行get,set分类操作 16 | * @param node 17 | * @param value 18 | * @param type 19 | * @returns {undefined|number|*} 20 | */ 21 | const scrollFN = (node: HTMLElement | Document, value: number, type: string) => { 22 | // scroll 对应 element 属性 23 | const scrollAttr = { 24 | top: 'scrollTop', 25 | left: 'scrollLeft' 26 | }[type]; 27 | 28 | // 转换: node === document 29 | if (node.nodeType === 9) { 30 | node = (node as Document).body; 31 | } 32 | 33 | // setter 34 | if (isNumber(value)) { 35 | node[scrollAttr] = value; 36 | return this; 37 | } 38 | 39 | // getter 40 | return node[scrollAttr]; 41 | }; 42 | export default { 43 | 44 | // #Offset003 45 | // 获取匹配元素在当前视口的相对偏移 46 | offset: function () { 47 | let position = { 48 | top: 0, 49 | left: 0 50 | }; 51 | const node = getDomList(this, 0); 52 | 53 | // #Offset001 54 | // 当前为IE11以下, 直接返回{top: 0, left: 0} 55 | if (!node.getClientRects().length) { 56 | return position; 57 | } 58 | 59 | // 当前DOM节点的 display === 'node' 时, 直接返回{top: 0, left: 0} 60 | if (getStyle(node, 'display') === 'none') { 61 | return position; 62 | } 63 | 64 | // #Offset002 65 | position = node.getBoundingClientRect(); 66 | const docElement = node.ownerDocument.documentElement; 67 | return { 68 | top: position.top + pageYOffset - docElement.clientTop, 69 | left: position.left + pageXOffset - docElement.clientLeft 70 | }; 71 | 72 | }, 73 | // 获取|设置 匹配元素相对滚动条顶部的偏移 value is number 74 | scrollTop: function (value: number) { 75 | return scrollFN(getDomList(this, 0), value, 'top'); 76 | }, 77 | // 获取|设置 匹配元素相对滚动条左部的偏移 value is number 78 | scrollLeft: function (value: number) { 79 | return scrollFN(getDomList(this, 0), value, 'left'); 80 | } 81 | }; 82 | -------------------------------------------------------------------------------- /src/jTool/Sizzle.ts: -------------------------------------------------------------------------------- 1 | import { isWindow, createDOM, each, isString, isNodeList, isElement, isArray, isJTool, rootDocument } from './utils'; 2 | import { DOM_LIST, JTOOL_KEY } from './constants'; 3 | import { JTool } from 'typings/types'; 4 | 5 | const Sizzle = function (selector: JTool | undefined | null | Window | Document | HTMLElement | NodeList | Array | string, 6 | context?: JTool | HTMLElement | NodeList | string): JTool { 7 | let DOMList = (() => { 8 | // selector -> undefined || null 9 | if (!selector) { 10 | selector = null; 11 | return; 12 | } 13 | 14 | // selector: window || document || Element 15 | if (isWindow(selector) || selector === rootDocument || isElement(selector)) { 16 | return [selector]; 17 | } 18 | 19 | // selector: NodeList || Array 20 | if (isNodeList(selector) || isArray(selector)) { 21 | return selector; 22 | } 23 | 24 | // selector: jTool Object 25 | if (isJTool(selector)) { 26 | return selector[DOM_LIST]; 27 | } 28 | 29 | // selector: Html String 30 | if (/<.+>/.test(selector as string)) { 31 | return createDOM((selector as string).trim()); 32 | } 33 | 34 | // 以下的selector都为 css选择器 35 | // selector: css selector, 仅在selector为CSS选择器时,context才会生效 36 | // context -> undefined 37 | if (!context) { 38 | return rootDocument.querySelectorAll(selector as string); 39 | } 40 | 41 | // context: 字符CSS选择器 42 | if (isString(context)) { 43 | context = rootDocument.querySelectorAll(context as string) as NodeList; 44 | } 45 | 46 | // context: DOM 将HTMLElement转换为数组 47 | if (isElement(context)) { 48 | // @ts-ignore 49 | context = [context]; 50 | } 51 | 52 | // context: jTool Object 53 | if (isJTool(context)) { 54 | context = context[DOM_LIST]; 55 | } 56 | 57 | const list: Array = []; 58 | each(context, (v: HTMLElement) => { 59 | // NodeList 只是类数组, 直接使用 concat 并不会将两个数组中的参数边接, 而是会直接将 NodeList 做为一个参数合并成为二维数组 60 | each(v.querySelectorAll(selector as string), (v2: HTMLElement) => { 61 | v2 && list.push(v2); 62 | }); 63 | }); 64 | return list; 65 | })(); 66 | 67 | if (!DOMList || DOMList.length === 0) { 68 | DOMList = undefined; 69 | } 70 | 71 | // 用于确认是否为jTool对象 72 | this[JTOOL_KEY] = true; 73 | 74 | // 用于存储当前选中的节点 75 | this[DOM_LIST] = DOMList; 76 | this.length = DOMList ? DOMList.length : 0; 77 | 78 | // 存储选择器条件 79 | this.querySelector = selector; 80 | 81 | return this; 82 | } as any as { 83 | new (selector: JTool | undefined | null | Window | Document | HTMLElement | NodeList | Array | string, 84 | context?: JTool | HTMLElement | NodeList | string): JTool; 85 | }; 86 | export default Sizzle; 87 | -------------------------------------------------------------------------------- /src/jTool/constants.ts: -------------------------------------------------------------------------------- 1 | // 生成的Sizzle中DOM所存储的字段 2 | export const DOM_LIST = 'DOMList'; 3 | 4 | // 生成的Sizzle中标识对像为jTool的字段 5 | export const JTOOL_KEY = 'jTool'; 6 | 7 | // 生成jtool对像期间临时使用的DOM id 8 | export const JTOOL_DOM_ID = 'jTool-create-dom'; 9 | -------------------------------------------------------------------------------- /src/jTool/index.ts: -------------------------------------------------------------------------------- 1 | import Sizzle from './Sizzle'; 2 | import utils, { extend, each } from './utils'; 3 | import ajax from './Ajax'; 4 | import _Event from './Event'; 5 | import _Css from './Css'; 6 | import _Class from './Class'; 7 | import _Document from './Document'; 8 | import _Offset from './Offset'; 9 | import _Element from './Element'; 10 | import _Animate from './Animate'; 11 | import _Data from './Data'; 12 | import { JTool } from 'typings/types'; 13 | 14 | // 如果需要集成Angular,React,在此处进行集成 15 | const jTool = function (selector: JTool | undefined | null | Window | Document | HTMLElement | NodeList | Array | string, 16 | context?: JTool | HTMLElement | NodeList | string): JTool { 17 | return new Sizzle(selector, context); 18 | }; 19 | 20 | // 把jquery原先的jQuery.fn给省略了.原先的方式是 init = jQuery.fn.init; init.prototype = jQuery.fn; 21 | // @ts-ignore 22 | Sizzle.prototype = jTool.prototype = {}; 23 | // 捆绑jTool 工具 24 | jTool.extend = jTool.prototype.extend = extend; 25 | jTool.extend(utils); 26 | jTool.ajax = ajax; 27 | 28 | // 捆绑jTool 方法 29 | each([_Event, _Css, _Class, _Document, _Offset, _Element, _Animate, _Data], (v: object) => { 30 | jTool.prototype.extend(v); 31 | }); 32 | 33 | // 抛出全局变量jTool 34 | // @ts-ignore 35 | window.jTool = jTool; 36 | 37 | export default jTool; 38 | -------------------------------------------------------------------------------- /src/module/adjust/constants.ts: -------------------------------------------------------------------------------- 1 | // 事件源class name 2 | export const CLASS_ADJUST_ACTION = 'gm-adjust-action'; 3 | 4 | // 正在移动中 class name 5 | export const CLASS_ADJUST_ING = 'gm-adjust-ing'; 6 | -------------------------------------------------------------------------------- /src/module/adjust/event.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 宽度调整功能所需的事件项 3 | * @param gridManagerName 4 | * @param scope: querySelector 域 5 | * 6 | * #001:adjustAbort事件中使用到了两个事件类型,1.mouseup 2.mouseleave 7 | * 其中mouseleave的事件范围超出了querySelector的区域,所以该事件不再代理。 8 | */ 9 | import { DIV_KEY, FAKE_TABLE_HEAD_KEY } from '@common/constants'; 10 | import { CLASS_ADJUST_ACTION } from './constants'; 11 | import { MOUSE_DOWN, MOUSE_MOVE, MOUSE_UP, MOUSE_LEAVE, createEventsObj } from '@common/events'; 12 | import { EventMap } from 'typings/types'; 13 | export function getEvent(_: string, scope: string): EventMap { 14 | return { 15 | // 宽度调整触发 16 | start: createEventsObj(MOUSE_DOWN, scope, `[${FAKE_TABLE_HEAD_KEY}="${_}"] .${CLASS_ADJUST_ACTION}`), 17 | 18 | // 宽度调整中 19 | doing: createEventsObj(MOUSE_MOVE, `[${DIV_KEY}="${_}"]`, scope), 20 | 21 | // 宽度调整停止 #001 22 | abort: createEventsObj(`${MOUSE_UP} ${MOUSE_LEAVE}`, scope) 23 | }; 24 | } 25 | 26 | export const eventMap = {}; 27 | -------------------------------------------------------------------------------- /src/module/adjust/style.less: -------------------------------------------------------------------------------- 1 | /** 2 | * 宽度调整 3 | * style.less 4 | * @author wangbo 5 | * @since 2019-02-20 6 | */ 7 | 8 | // 宽度调整事件源 9 | .gm-adjust-action { 10 | display: block; 11 | width: 6px; 12 | height: 100%; 13 | position: absolute; 14 | top: 0; 15 | right: -4px; 16 | cursor: col-resize; 17 | z-index: 2; 18 | } 19 | .gm-adjust-ing{ 20 | display: block; 21 | width: 0; 22 | height: 100%; 23 | position: absolute; 24 | top: 100%; 25 | border-right: 1px dashed #ccc; 26 | } 27 | // 当处于最后一列时隐藏事件源 28 | th[last-visible] .gm-adjust-action { 29 | display: none; 30 | } 31 | -------------------------------------------------------------------------------- /src/module/ajaxPage/ajax-page.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 |
7 | {{ vm.gotoFirstText }} 8 | 9 | {{ vm.gotoLastText }} 10 |
11 | 12 | 13 |
14 | {{ vm.pageSizeOptionTpl }} 15 |
16 | 17 | 18 |
19 | 20 | 21 |
22 | 23 | 24 | 46 |
47 | -------------------------------------------------------------------------------- /src/module/ajaxPage/event.ts: -------------------------------------------------------------------------------- 1 | import { TOOLBAR_KEY } from '@common/constants'; 2 | import { KEY_UP, MOUSE_CLICK, createEventsObj } from '@common/events'; 3 | import { EventMap } from 'typings/types'; 4 | 5 | /** 6 | * 分页功能所需的事件项 7 | * @param _ 8 | */ 9 | export const getEvent = (_: string): EventMap => { 10 | const target = `[${TOOLBAR_KEY}="${_}"]`; 11 | return { 12 | // 快捷跳转 13 | input: createEventsObj(KEY_UP, target, '.gp-input'), 14 | 15 | // 第一页 16 | first: createEventsObj(MOUSE_CLICK, target, '[pagination-before] .first-page'), 17 | 18 | // 上一页 19 | previous: createEventsObj(MOUSE_CLICK, target, '[pagination-before] .previous-page'), 20 | 21 | // 下一页 22 | next: createEventsObj(MOUSE_CLICK, target, '[pagination-after] .next-page'), 23 | 24 | // 尾页 25 | last: createEventsObj(MOUSE_CLICK, target, '[pagination-after] .last-page'), 26 | 27 | // 页码 28 | num: createEventsObj(MOUSE_CLICK, target, '[pagination-number] li'), 29 | 30 | // 刷新 31 | refresh: createEventsObj(MOUSE_CLICK, target, '.refresh-action') 32 | }; 33 | }; 34 | 35 | export const eventMap = {}; 36 | -------------------------------------------------------------------------------- /src/module/ajaxPage/tool.ts: -------------------------------------------------------------------------------- 1 | import { TOOLBAR_KEY } from '@common/constants'; 2 | import { PageData, SettingObj } from 'typings/types'; 3 | 4 | /** 5 | * 获取选择器 6 | * @param _ 7 | * @returns {string} 8 | */ 9 | export const getQuerySelector = (_: string): string => { 10 | return `[${TOOLBAR_KEY}="${_}"]`; 11 | }; 12 | 13 | /** 14 | * 拼接页码字符串 15 | * @param currentPageKey 16 | * @param pageData 分页数据格式 17 | * @private 18 | */ 19 | export const joinPaginationNumber = (currentPageKey: string, pageData: PageData): string => { 20 | // 当前页 21 | let cPage = Number(pageData[currentPageKey] || 0); 22 | 23 | // 总页数 24 | let tPage = Number(pageData.tPage || 0); 25 | 26 | // 临时存储分页HTML片段 27 | let tHtml = ''; 28 | 29 | // 临时存储末尾页码THML片段 30 | let lHtml = ''; 31 | // 循环开始数 32 | let i = 1; 33 | 34 | // 循环结束数 35 | let maxI = tPage; 36 | 37 | // 配置 first端省略符 38 | if (cPage > 4) { 39 | tHtml += '
  • 1
  • ...
  • '; 40 | i = cPage - 2; 41 | } 42 | // 配置 last端省略符 43 | if ((tPage - cPage) > 4) { 44 | maxI = cPage + 2; 45 | lHtml += `
  • ...
  • ${ tPage }
  • `; 46 | } 47 | 48 | // 配置页码 49 | if (pageData.tSize) { 50 | for (i; i <= maxI; i++) { 51 | if (i === cPage) { 52 | tHtml += `
  • ${ cPage }
  • `; 53 | continue; 54 | } 55 | tHtml += `
  • ${ i }
  • `; 56 | } 57 | } 58 | tHtml += lHtml; 59 | 60 | return tHtml; 61 | }; 62 | 63 | /** 64 | * 计算并返回分页数据 65 | * @param settings 66 | * @param totals 67 | * @param len 本次请求返回的总条数,该参数仅在totals为空时使用 68 | * @returns {{tPage: number, cPage: *, pSize: *, tSize: *}} 69 | * @private 70 | */ 71 | export const getPageData = (settings: SettingObj, totals: number, len: number): PageData => { 72 | const { pageData, pageSizeKey, pageSize, currentPageKey } = settings; 73 | const pSize = pageData[pageSizeKey] || pageSize; 74 | const cPage = pageData[currentPageKey] || 1; 75 | 76 | let tPage = 1; 77 | if (!totals) { 78 | tPage = len < pSize ? cPage : cPage + 1; 79 | } else { 80 | tPage = Math.ceil(totals / pSize); 81 | } 82 | 83 | return { 84 | // 总页数 85 | tPage: tPage, 86 | 87 | // 当前页 88 | [currentPageKey]: cPage > tPage ? 1 : cPage, 89 | 90 | // 每页显示条数 91 | [pageSizeKey]: pSize, 92 | 93 | // 总条数 94 | tSize: totals 95 | }; 96 | }; 97 | -------------------------------------------------------------------------------- /src/module/autoPlay/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 自动轮播 3 | */ 4 | import { getSettings } from '@common/cache'; 5 | import { getDiv } from '@common/base'; 6 | 7 | class AutoPlay { 8 | siv: any; 9 | init(_: string): void { 10 | const { autoPlayConfig } = getSettings(_); 11 | const { interval, step } = autoPlayConfig; 12 | const $div = getDiv(_); 13 | let oldTop: number; 14 | 15 | // 防止用户错误操作,多次执行 16 | if (this.siv) { 17 | clearInterval(this.siv); 18 | } 19 | this.siv = setInterval(() => { 20 | oldTop = $div.scrollTop(); 21 | $div.scrollTop(oldTop + step); 22 | }, interval * 1000); 23 | } 24 | start(_: string): void { 25 | this.init(_); 26 | } 27 | stop(_: string): void { 28 | clearInterval(this.siv); 29 | } 30 | } 31 | 32 | export default new AutoPlay(); 33 | -------------------------------------------------------------------------------- /src/module/checkbox/checkbox.tpl.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/module/checkbox/column.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | {{vm.template}} 3 | 4 | -------------------------------------------------------------------------------- /src/module/checkbox/event.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 选择框功能所需的事件项 3 | * @param gridManagerName 4 | * @param scope: querySelector 域 5 | */ 6 | import { MOUSE_CLICK, createEventsObj } from '@common/events'; 7 | import { TR_CACHE_KEY } from '@common/constants'; 8 | import { EventMap } from 'typings/types'; 9 | export const getEvent = (_: string, scope: string): EventMap => { 10 | return { 11 | // 全选框点击 12 | allChange: createEventsObj(MOUSE_CLICK, scope, 'th[gm-checkbox] .gm-checkbox-wrapper'), 13 | 14 | // 复选框点击 15 | checkboxChange: createEventsObj(MOUSE_CLICK, scope, 'td[gm-checkbox] .gm-checkbox-wrapper'), 16 | 17 | // 单选框点击 18 | radioChange: createEventsObj(MOUSE_CLICK, scope, 'td[gm-checkbox] .gm-radio-wrapper'), 19 | 20 | // tr 点击选中 21 | trChange: createEventsObj(MOUSE_CLICK, scope, `tbody > tr[${TR_CACHE_KEY}]`) 22 | }; 23 | }; 24 | 25 | export const eventMap = {}; 26 | -------------------------------------------------------------------------------- /src/module/checkbox/radio.tpl.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/module/checkbox/tool.ts: -------------------------------------------------------------------------------- 1 | import { CHECKBOX_KEY, ROW_DISABLED_CHECKBOX, TR_CACHE_KEY } from '@common/constants'; 2 | import { getTableData, setTableData, setCheckedData } from '@common/cache'; 3 | import { Row } from 'typings/types'; 4 | 5 | /** 6 | * 重置当前渲染数据中的选择状态 7 | * @param _ 8 | * @param status: 要变更的状态, 单选操作该值无需传递,因为在单选情况下该值永远为true 9 | * @param isAllCheck: 触发源是否为全选操作 10 | * @param cacheKey: 所在行的key 11 | * @param isRadio: 当前事件源为单选 12 | * @returns {*} 13 | */ 14 | export const resetData = (_: string, status: boolean, isAllCheck: boolean, cacheKey?: string | number, isRadio?: boolean): Array => { 15 | const tableData = getTableData(_); 16 | const diffList = []; 17 | // 复选-全选 18 | if (isAllCheck && !cacheKey) { 19 | tableData.forEach(row => { 20 | // 仅选中未禁用的项 21 | if (!row[ROW_DISABLED_CHECKBOX]) { 22 | if (row[CHECKBOX_KEY] !== status) { 23 | diffList.push(row); 24 | } 25 | row[CHECKBOX_KEY] = status; 26 | } 27 | }); 28 | } 29 | 30 | // 复选-单个操作 31 | if (!isAllCheck && !isRadio && cacheKey) { 32 | tableData[cacheKey][CHECKBOX_KEY] = status; 33 | diffList.push(tableData[cacheKey]); 34 | } 35 | 36 | // 单选 37 | if (isRadio) { 38 | tableData.forEach(row => { 39 | if (row[TR_CACHE_KEY] === cacheKey) { 40 | row[CHECKBOX_KEY] = true; 41 | diffList.push(row); 42 | } else { 43 | // 单选状态下会清空原先的数据, 所以单选时不需要将未选中的行数据归类于diffList内 44 | row[CHECKBOX_KEY] = false; 45 | } 46 | }); 47 | } 48 | 49 | // 存储数据 50 | setTableData(_, tableData); 51 | 52 | // 更新选中数据: 单选状态下会清空原先的数据 53 | setCheckedData(_, diffList, isRadio); 54 | 55 | return tableData; 56 | }; 57 | -------------------------------------------------------------------------------- /src/module/config/config.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 |
    {{vm.info}}
    6 |
      7 |
      8 | -------------------------------------------------------------------------------- /src/module/config/constants.ts: -------------------------------------------------------------------------------- 1 | // 禁用点击 class name 2 | export const CLASS_NO_CLICK = 'no-click'; 3 | 4 | // 配置中 class name 5 | export const CLASS_CONFIG_ING = 'gm-config-ing'; 6 | 7 | // 配置区域 class name 8 | export const CLASS_CONFIG = 'gm-config-area'; 9 | -------------------------------------------------------------------------------- /src/module/config/event.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 配置功能所需的事件项 3 | * @param gridManagerName 4 | * @param scope: querySelector 域 5 | */ 6 | import { CONFIG_KEY } from '@common/constants'; 7 | import { MOUSE_CLICK, MOUSE_DOWN, createEventsObj } from '@common/events'; 8 | import { EventMap } from 'typings/types'; 9 | export const getEvent = (_: string): EventMap => { 10 | const target = `[${CONFIG_KEY}="${_}"]`; 11 | return { 12 | // 关闭 13 | closeConfig: createEventsObj(MOUSE_CLICK, target, '.config-action'), 14 | 15 | // 设置 16 | liChange: createEventsObj(MOUSE_CLICK, target, '.config-list li'), 17 | 18 | // 菜单 19 | closeConfigByBody: createEventsObj(`${MOUSE_DOWN}.closeConfig`, 'body') 20 | }; 21 | }; 22 | 23 | export const eventMap = {}; 24 | -------------------------------------------------------------------------------- /src/module/config/style.less: -------------------------------------------------------------------------------- 1 | /** 2 | * style.less 3 | * @author wangbo 4 | * @since 2019-02-20 5 | */ 6 | 7 | // 配置中标识 8 | .gm-config-ing{ 9 | overflow-x: hidden; 10 | } 11 | 12 | // 配置区域样式 13 | .gm-config-area{ 14 | display: none; 15 | width: 260px; 16 | position: absolute; 17 | top: 42px; 18 | right: 0; 19 | cursor: pointer; 20 | z-index: 9999; 21 | padding: 8px 0; 22 | border: 1px solid #ddd; 23 | background: var(--gm-bg); 24 | .config-action{ 25 | display: block; 26 | width: 20px; 27 | height: calc(100% + 2px); 28 | position: absolute; 29 | left: -20px; 30 | top: -1px; 31 | overflow: hidden; 32 | text-align: center; 33 | background-color: #09f; 34 | i{ 35 | display: inline-block; 36 | position: absolute; 37 | left: 2px; 38 | top: calc(50% - 9px); 39 | font-size: 16px; 40 | color: #ddd; 41 | } 42 | &:hover i{ 43 | color: #fff; 44 | } 45 | } 46 | .config-info{ 47 | line-height: 20px; 48 | padding: 0 18px; 49 | color: #666; 50 | } 51 | .config-list{ 52 | display: block; 53 | width: 100%; 54 | list-style-type: none; 55 | margin: 0; 56 | padding: 0 8px; 57 | user-select: none; 58 | overflow-y: auto; 59 | >li{ 60 | display: inline-block; 61 | min-width: calc(50% - 2px); 62 | padding: 4px 10px; 63 | line-height: 20px; 64 | overflow: hidden; 65 | text-overflow:ellipsis; 66 | white-space: nowrap; 67 | .gm-checkbox-input{ 68 | pointer-events: none; 69 | } 70 | &:hover{ 71 | color: #09f; 72 | } 73 | &.no-click{ 74 | cursor: not-allowed; 75 | *{ 76 | cursor: not-allowed; 77 | } 78 | &:hover{ 79 | color:#666; 80 | } 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/module/core/event.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 核心功能所需的事件项 3 | * @param scope: querySelector 域 4 | */ 5 | import { MOUSE_MOVE, MOUSE_CLICK, MOUSE_DBCLICK, createEventsObj, MOUSE_DOWN } from '@common/events'; 6 | import { TR_CACHE_KEY } from '@common/constants'; 7 | import { EventMap } from 'typings/types'; 8 | 9 | export const getEvent = (scope: string): EventMap => { 10 | const tr = `tr[${TR_CACHE_KEY}]`; 11 | const td = `tr[${TR_CACHE_KEY}] td`; 12 | return { 13 | // 行 hover 14 | rowHover: createEventsObj(MOUSE_MOVE, scope, tr), 15 | 16 | // 行 click 17 | rowClick: createEventsObj(MOUSE_CLICK, scope, tr), 18 | 19 | // 行 dbclick 20 | rowDblClick: createEventsObj(MOUSE_DBCLICK, scope, tr), 21 | 22 | // 单元格 hover 23 | cellHover: createEventsObj(MOUSE_MOVE, scope, td), 24 | 25 | // 单元格 click 26 | cellClick: createEventsObj(MOUSE_CLICK, scope, td), 27 | 28 | // 单元格 dbclick 29 | cellDblClick: createEventsObj(MOUSE_DBCLICK, scope, td), 30 | 31 | // 单元格触焦 mousedown 32 | cellFocus: createEventsObj(MOUSE_DOWN, scope, 'td') 33 | }; 34 | }; 35 | 36 | export const eventMap = {}; 37 | -------------------------------------------------------------------------------- /src/module/core/th.tpl.html: -------------------------------------------------------------------------------- 1 | 2 |
      3 | {{vm.thText}} 4 | {{vm.remindHtml}} 5 | {{vm.sortHtml}} 6 | {{vm.filterHtml}} 7 | {{vm.adjustHtml}} 8 |
      9 | 10 | -------------------------------------------------------------------------------- /src/module/core/wrap.tpl.html: -------------------------------------------------------------------------------- 1 |
      2 |
      3 |
      4 | 5 | {{vm.configTpl}} 6 | {{vm.ajaxPageTpl}} 7 |
      8 | -------------------------------------------------------------------------------- /src/module/drag/constants.ts: -------------------------------------------------------------------------------- 1 | // 拖拽事件源 class name 2 | export const CLASS_DRAG_ACTION = 'gm-drag-action'; 3 | 4 | // 拖拽中 class name 5 | export const CLASS_DRAG_ING = 'gm-drag-ongoing'; 6 | 7 | // 镜像 class name 8 | export const CLASS_DREAMLAND = 'gm-dreamland-div'; 9 | -------------------------------------------------------------------------------- /src/module/drag/dreamland.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{vm.th}} 5 | 6 | 7 | 8 | {{vm.tbody}} 9 | 10 |
      11 | -------------------------------------------------------------------------------- /src/module/drag/event.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 拖拽功能所需的事件项 3 | * @param gridManagerName 4 | * @param scope: querySelector 域 5 | */ 6 | import { FAKE_TABLE_HEAD_KEY } from '@common/constants'; 7 | import { MOUSE_DOWN, MOUSE_MOVE, MOUSE_UP, createEventsObj } from '@common/events'; 8 | import { CLASS_DRAG_ACTION } from './constants'; 9 | import { EventMap } from 'typings/types'; 10 | export const getEvent = (_: string, scope: string): EventMap => { 11 | return { 12 | // 开始 13 | start: createEventsObj(MOUSE_DOWN, scope, `[${FAKE_TABLE_HEAD_KEY}="${_}"] .${CLASS_DRAG_ACTION}`), 14 | 15 | // 调整中 16 | doing: createEventsObj(`${MOUSE_MOVE}.gmDrag`, 'body'), 17 | 18 | // 停止 19 | abort: createEventsObj(`${MOUSE_UP}.gmDrag`, 'body') 20 | }; 21 | }; 22 | 23 | export const eventMap = {}; 24 | -------------------------------------------------------------------------------- /src/module/drag/style.less: -------------------------------------------------------------------------------- 1 | /** 2 | * 拖拽换位 3 | * style.less 4 | * @author wangbo 5 | * @since 2019-02-20 6 | */ 7 | @import "../../css/mixins"; 8 | 9 | /* 拖拽换位 */ 10 | .gm-drag-action { 11 | cursor: all-scroll; 12 | } 13 | 14 | /* 拖拽换位中 */ 15 | .table-div .gm-drag-ongoing { 16 | cursor: all-scroll; 17 | opacity: 1; 18 | animation: opacityChange 1s ease-in-out infinite; 19 | } 20 | /* 拖拽换位镜象 */ 21 | .gm-dreamland-div { 22 | display: none; 23 | position: absolute; 24 | padding: 0; 25 | background: var(--gm-bg); 26 | cursor: all-scroll; 27 | z-index: 9999; 28 | border: var(--gm-border); 29 | overflow: hidden; // safari 中获取的宽度比其它浏览器小1,所以需要overflow进行容错 30 | .dreamland-table { 31 | table-layout: fixed; 32 | width: 100%; 33 | margin: 0; 34 | padding: 0; 35 | background-color: #d8d8d8; 36 | border-collapse: separate; 37 | border-spacing: 0; 38 | font-size: var(--gm-font-size); 39 | thead{ 40 | background: var(--gm-bg-high); 41 | } 42 | tr { 43 | th, td{ 44 | border-right: none; 45 | } 46 | td { 47 | background: var(--gm-bg); 48 | padding: 11px; 49 | .text-overflow(); 50 | } 51 | &:nth-child(odd) td{ 52 | background: var(--gm-bg-odd); 53 | } 54 | &:hover td{ 55 | background-color: #F1F8FB; 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/module/dropdown/dropdown.tpl.html: -------------------------------------------------------------------------------- 1 |
      2 | 3 | 4 |
        {{vm.li}}
      5 |
      6 | -------------------------------------------------------------------------------- /src/module/dropdown/event.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * dropdown events 3 | * @param scope: querySelector 域 4 | */ 5 | import { MOUSE_CLICK, createEventsObj } from '@common/events'; 6 | import { EventMap } from 'typings/types'; 7 | 8 | export const getEvent = (scope: string): EventMap => { 9 | return { 10 | // 切换展示状态 11 | open: createEventsObj(MOUSE_CLICK, scope, '.gm-dropdown .gm-dropdown-text'), 12 | 13 | // body关闭事件 14 | close: createEventsObj(MOUSE_CLICK, 'body'), 15 | 16 | // 点选事件 17 | selected: createEventsObj(MOUSE_CLICK, scope, '.gm-dropdown .gm-dropdown-list >li') 18 | }; 19 | }; 20 | 21 | export const eventMap = {}; 22 | -------------------------------------------------------------------------------- /src/module/dropdown/index.ts: -------------------------------------------------------------------------------- 1 | import jTool from '@jTool'; 2 | import { getWrap, clearTargetEvent } from '@common/base'; 3 | import { parseTpl } from '@common/parse'; 4 | import { TOOLBAR_KEY } from '@common/constants'; 5 | import dropdownTpl from './dropdown.tpl.html'; 6 | import './style.less'; 7 | import { getEvent, eventMap } from './event'; 8 | import { EVENTS, TARGET, SELECTOR } from '@common/events'; 9 | 10 | class Dropdown { 11 | /** 12 | * 初始化下拉框 13 | * @param _ 14 | */ 15 | init({ _, defaultValue = '', onChange }: { _: string, defaultValue: string, onChange: any}): void { 16 | eventMap[_] = getEvent(`[${TOOLBAR_KEY}="${_}"]`); 17 | const { open, close, selected } = eventMap[_]; 18 | 19 | const $dropdown = getWrap(_).find('.gm-dropdown'); 20 | const $text = $dropdown.find('.gm-dropdown-text'); 21 | const $ul = $dropdown.find('.gm-dropdown-list'); 22 | 23 | $text.text(defaultValue); 24 | 25 | // 事件: 展示状态 26 | jTool(open[TARGET]).on(open[EVENTS], open[SELECTOR], function (e: MouseEvent) { 27 | e.stopPropagation(); 28 | // 事件: 关闭 29 | const $close = jTool(close[TARGET]); 30 | if ($ul.css('display') === 'block') { 31 | $ul.hide(); 32 | $close.unbind(close[EVENTS]); 33 | return; 34 | } 35 | 36 | // 事件: 打开 37 | $ul.show(); 38 | 39 | const closeEvents = close[EVENTS]; 40 | $close.unbind(closeEvents); 41 | $close.bind(closeEvents, function () { 42 | $close.unbind(closeEvents); 43 | $ul.hide(); 44 | }); 45 | }); 46 | 47 | // 事件: 选中 48 | jTool(selected[TARGET]).on(selected[EVENTS], selected[SELECTOR], function () { 49 | const oldValue = parseInt($text.text(), 10); 50 | const newValue = this.value; 51 | if (oldValue === newValue) { 52 | return; 53 | } 54 | $text.text(newValue); 55 | onChange(newValue, oldValue); 56 | }); 57 | } 58 | 59 | /** 60 | * 生成html 61 | * @param params 62 | * @returns {{liStr: string}} 63 | */ 64 | @parseTpl(dropdownTpl) 65 | createHtml(params: any): string { 66 | const { sizeData } = params; 67 | let liStr = ''; 68 | sizeData.forEach((item: number) => { 69 | liStr += `
    • ${item}
    • `; 70 | }); 71 | 72 | // @ts-ignore 73 | return { 74 | li: liStr 75 | }; 76 | } 77 | 78 | /** 79 | * 消毁 80 | * @param _ 81 | */ 82 | destroy(_: string): void { 83 | clearTargetEvent(eventMap[_]); 84 | } 85 | } 86 | 87 | export default new Dropdown(); 88 | -------------------------------------------------------------------------------- /src/module/dropdown/style.less: -------------------------------------------------------------------------------- 1 | /** 2 | * 宽度调整 3 | * style.less 4 | * @author wangbo 5 | * @since 2019-02-20 6 | */ 7 | @dropdown-align: center; 8 | .gm-dropdown { 9 | width: 100%; 10 | height: 26px; 11 | position: relative; 12 | line-height: 24px; 13 | color: var(--gm-color); 14 | background: var(--gm-bg); 15 | cursor: default; 16 | z-index: 4; 17 | .gm-dropdown-text{ 18 | display: block; 19 | height: 100%; 20 | padding-right: 10px; 21 | text-align: @dropdown-align; 22 | border: var(--gm-border); 23 | } 24 | .gm-dropdown-icon{ 25 | display: block; 26 | width: 0; 27 | height: 0; 28 | position: absolute; 29 | top: 10px; 30 | right: 5px; 31 | border-left: 5px solid transparent; 32 | border-right: 5px solid transparent; 33 | border-top: 6px solid var(--gm-color); 34 | pointer-events: none; 35 | } 36 | &:hover { 37 | .gm-dropdown-text{ 38 | border: var(--gm-border-active); 39 | } 40 | .gm-dropdown-icon{ 41 | border-top-color: #aacbe1; 42 | } 43 | .gm-dropdown-list{ 44 | border: var(--gm-border-active); 45 | border-bottom: none; 46 | } 47 | } 48 | .gm-dropdown-list{ 49 | display: none; 50 | width: 100%; 51 | position: absolute; 52 | bottom: calc(100% - 1px); 53 | left: 0; 54 | border: var(--gm-border); 55 | border-bottom: none; 56 | background: var(--gm-bg); 57 | >li{ 58 | height: 26px; 59 | text-align: @dropdown-align; 60 | &:hover{ 61 | background: var(--gm-bg-odd); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/module/filter/constants.ts: -------------------------------------------------------------------------------- 1 | // filter 区域 class name 2 | export const CLASS_FILTER = 'gm-filter-area'; 3 | 4 | // 存在选中项时的ICON class name 5 | export const CLASS_FILTER_SELECTED = 'filter-selected'; 6 | 7 | // filter 容器 class name 8 | export const CLASS_FILTER_CONTENT = 'fa-con'; 9 | -------------------------------------------------------------------------------- /src/module/filter/event.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 过滤功能所需的事件项 3 | * @param _ 4 | * @param scope: querySelector 域 5 | */ 6 | import { FAKE_TABLE_HEAD_KEY } from '@common/constants'; 7 | import { CLASS_FILTER } from './constants'; 8 | import { MOUSE_DOWN, MOUSE_UP, MOUSE_CLICK, createEventsObj } from '@common/events'; 9 | import { EventMap } from 'typings/types'; 10 | 11 | export const getEvent = (_: string, scope: string): EventMap => { 12 | const filterSign = `[${FAKE_TABLE_HEAD_KEY}="${_}"] .${CLASS_FILTER}`; 13 | return { 14 | // 切换可视状态 15 | toggle: createEventsObj(MOUSE_DOWN, scope, `${filterSign} .fa-icon`), 16 | 17 | // 关闭 18 | close: createEventsObj(`${MOUSE_DOWN}.closeFitler`, 'body'), 19 | 20 | // 提交 21 | submit: createEventsObj(MOUSE_UP, scope, `${filterSign} .filter-submit`), 22 | 23 | // 重置 24 | reset: createEventsObj(MOUSE_UP, scope, `${filterSign} .filter-reset`), 25 | 26 | // 复选框点选 27 | checkboxAction: createEventsObj(MOUSE_CLICK, scope, `${filterSign} .gm-checkbox-input`), 28 | 29 | // 单选框点选 30 | radioAction: createEventsObj(MOUSE_CLICK, scope, `${filterSign} .gm-radio-input`) 31 | }; 32 | }; 33 | 34 | export const eventMap = {}; 35 | -------------------------------------------------------------------------------- /src/module/filter/filter.tpl.html: -------------------------------------------------------------------------------- 1 |
      2 | 3 |
      4 |
        5 | {{vm.list}} 6 |
      7 |
      8 | {{vm.ok}} 9 | {{vm.reset}} 10 |
      11 |
      12 |
      13 | -------------------------------------------------------------------------------- /src/module/filter/style.less: -------------------------------------------------------------------------------- 1 | /** 2 | * 表头的筛选菜单 3 | * style.less 4 | * @author wangbo 5 | * @since 2019-02-20 6 | */ 7 | @import "../../css/mixins"; 8 | 9 | [grid-manager], .gm-dreamland-div{ 10 | th[filter] .th-wrap { 11 | padding-right: 20px; 12 | } 13 | } 14 | .gm-filter-area{ 15 | display: block; 16 | width: 14px; 17 | height: 18px; 18 | position: absolute; 19 | top: calc(50% - 9px); 20 | right: 5px; 21 | cursor: pointer; 22 | color: #444; 23 | .fa-icon { 24 | display: block; 25 | position: absolute; 26 | font-size: var(--gm-font-size); 27 | line-height: 18px; 28 | opacity: .7; 29 | &:hover{ 30 | opacity: 1; 31 | } 32 | &.filter-selected{ 33 | color: var(--gm-color-active); 34 | } 35 | // 解决window环境下未能居中的问题,todo 如后续发现其它图标存在同样的问题,需要抽离至图标样式文件内 36 | &:before{ 37 | vertical-align: middle; 38 | } 39 | } 40 | .fa-con{ 41 | display: none; 42 | min-width: 100px; 43 | position: absolute; 44 | top: 100%; 45 | background: var(--gm-bg); 46 | border: var(--gm-border-high); 47 | z-index: 4; 48 | &.direction-left{ 49 | left: 0; 50 | } 51 | &.direction-right{ 52 | right: 0; 53 | } 54 | .filter-list{ 55 | overflow-y: auto; 56 | li{ 57 | > .gm-radio-wrapper, > .gm-checkbox-wrapper{ 58 | display: flex; 59 | cursor: pointer; 60 | word-break: keep-all; 61 | padding: 9px 12px; 62 | white-space: nowrap; 63 | .gm-checkbox{ 64 | top: 0; 65 | } 66 | } 67 | &:hover{ 68 | background: #e6f7ff; 69 | } 70 | } 71 | } 72 | .filter-bottom{ 73 | display: flex; 74 | border-top: var(--gm-border); 75 | padding: 7px 8px; 76 | text-align: center; 77 | .filter-button{ 78 | display: block; 79 | width: 50%; 80 | height: 100%; 81 | cursor: pointer; 82 | color: #1890ff; 83 | &:hover{ 84 | color: #40a9ff; 85 | } 86 | } 87 | } 88 | } 89 | } 90 | 91 | // 表头的icon图标跟随文本 92 | .gm-icon-follow-text{ 93 | .gm-filter-area{ 94 | display: inline-block; 95 | width: 18px; 96 | position: relative; 97 | top: 0; 98 | left: 0; 99 | vertical-align: middle; 100 | text-align: center; 101 | font-size: var(--gm-font-size);// icon跟随模式下 th-wrap的font-size是0,所以这里需要单独指定 102 | .fa-icon{ 103 | right: 4px; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/module/fixed/event.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 固定列功能所需的事件项 3 | * @param _ 4 | * @param scope: querySelector 域 5 | */ 6 | import { MOUSE_DOWN, createEventsObj } from '@common/events'; 7 | import { EventMap } from 'typings/types'; 8 | 9 | export const getEvent = (_: string, scope: string): EventMap => { 10 | return { 11 | // 触焦事件 12 | fixedFocus: createEventsObj(MOUSE_DOWN, scope, 'td[fixed]') 13 | }; 14 | }; 15 | 16 | export const eventMap = {}; 17 | -------------------------------------------------------------------------------- /src/module/fixed/style.less: -------------------------------------------------------------------------------- 1 | @import "../../css/mixins"; 2 | 3 | @shadow-color: #e8e8e8; 4 | // th样式, 在JS中会动态的变更left | right值 5 | [gm-overflow-x="false"] [grid-manager-mock-thead]{ 6 | padding: 0!important; 7 | [fixed]{ 8 | height: auto!important; 9 | line-height: inherit!important; 10 | position: static; 11 | box-shadow: none!important; 12 | } 13 | } 14 | [gm-overflow-x="true"] [grid-manager-mock-thead]{ 15 | [fixed]{ 16 | position: absolute; 17 | z-index: 3; 18 | background: var(--gm-bg-high); 19 | //border-right: none; 20 | .th-wrap{ 21 | display: flex; 22 | align-items: center; 23 | .th-text{ 24 | height: auto; 25 | } 26 | } 27 | 28 | &:last-child:after{ 29 | display: block; 30 | width: 10px; 31 | height: 100%; 32 | position: absolute; 33 | right: -10px; 34 | top: 0; 35 | content: ""; 36 | background: var(--gm-bg-high); 37 | } 38 | } 39 | } 40 | 41 | // fixed right 42 | th[fixed-previous] .gm-adjust-action{ 43 | display: none; 44 | } 45 | 46 | tbody[grid-manager-tbody] tr td[fixed-focus]{ 47 | z-index: 4 !important; 48 | overflow:visible; 49 | } 50 | -------------------------------------------------------------------------------- /src/module/fullColumn/event.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 通栏功能所需的事件项 3 | * @param scope: querySelector 域 4 | */ 5 | import { createEventsObj, MOUSE_CLICK } from '@common/events'; 6 | import { EventMap } from 'typings/types'; 7 | 8 | export const getEvent = (scope: string, key: string): EventMap => { 9 | return { 10 | // 触发 #001 11 | fold: createEventsObj(MOUSE_CLICK, scope, `i[${key}]`) 12 | }; 13 | }; 14 | 15 | export const eventMap = {}; 16 | -------------------------------------------------------------------------------- /src/module/fullColumn/style.less: -------------------------------------------------------------------------------- 1 | @import "../../css/mixins"; 2 | .table-div[gm-full-column]{ 3 | [grid-manager] tbody{ 4 | // 当前数据充满了tbody 5 | &[filled] tr:last-child{ 6 | &[full-column-interval="0px"] td{ 7 | border-top: none; 8 | } 9 | } 10 | } 11 | [grid-manager] tr{ 12 | &[full-column]{ 13 | td{ 14 | background: var(--gm-bg-odd); 15 | padding: 0; 16 | border: 0; 17 | } 18 | &[full-column-state="false"]{ 19 | display: none; 20 | } 21 | } 22 | 23 | &[full-column-interval]{ 24 | height: auto; 25 | td{ 26 | background: var(--gm-bg); 27 | padding: 0; 28 | border-top: var(--gm-border); 29 | border-bottom: var(--gm-border); 30 | } 31 | &[full-column-interval="0px"] td{ 32 | border-bottom: none; 33 | } 34 | } 35 | 36 | // 清除最后一列(full-column-interval)的底部边框 37 | &:last-child{ 38 | &[full-column-interval] td{ 39 | border-bottom: none; 40 | } 41 | } 42 | 43 | // 常规tr 44 | &[gm-cache-key]{ 45 | td { 46 | background: var(--gm-bg); 47 | border-bottom: none; 48 | &[gm-fold] { 49 | text-align: center; 50 | } 51 | [full-column-fold]{ 52 | color: #00aaf1; 53 | cursor: pointer; 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | .disable-border { 61 | .table-div[gm-full-column]{ 62 | tr[full-column] td .full-column-div{ 63 | border-left: var(--gm-border); 64 | border-right: var(--gm-border); 65 | } 66 | tr:not([full-column]):not([full-column-interval]) td{ 67 | &:first-child{ 68 | border-left: var(--gm-border); 69 | } 70 | &:last-child{ 71 | border-right: var(--gm-border); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/module/i18n/config.ts: -------------------------------------------------------------------------------- 1 | // 表格中使用到的国际化文本信息 2 | export default function () { 3 | const _this = this; 4 | // order 5 | _this['order-text'] = { 6 | 'zh-cn': '序号', 7 | 'zh-tw': '序號', 8 | 'en-us': 'order' 9 | }; 10 | 11 | // ajax page 12 | _this['first-page'] = { 13 | 'zh-cn': '首页', 14 | 'zh-tw': '首頁', 15 | 'en-us': 'first' 16 | }; 17 | _this['previous-page'] = { 18 | 'zh-cn': '上一页', 19 | 'zh-tw': '上一頁', 20 | 'en-us': 'previous' 21 | }; 22 | _this['next-page'] = { 23 | 'zh-cn': '下一页', 24 | 'zh-tw': '下一頁', 25 | 'en-us': 'next' 26 | }; 27 | _this['last-page'] = { 28 | 'zh-cn': '尾页', 29 | 'zh-tw': '尾頁', 30 | 'en-us': 'last' 31 | }; 32 | 33 | // page-info 会传入五个值 34 | // 0: 当前页从多少条开始显示 35 | // 1: 当前页到多少条结束显示 36 | // 2: 总条数 37 | // 3: 当前页 38 | // 4: 总页数 39 | _this['page-info'] = { 40 | 'zh-cn': '此页显示 {0}-{1} 共{2}条', 41 | 'zh-tw': '此頁顯示 {0}-{1} 共{2}條', 42 | 'en-us': 'this page show {0}-{1} count {2}' 43 | }; 44 | 45 | _this['checked-info'] = { 46 | 'zh-cn': '已选 {0} 条', 47 | 'zh-tw': '已選 {0} 條', 48 | 'en-us': 'selected {0}' 49 | }; 50 | _this['goto-first-text'] = { 51 | 'zh-cn': '跳转至', 52 | 'zh-tw': '跳轉至', 53 | 'en-us': 'goto' 54 | }; 55 | _this['goto-last-text'] = { 56 | 'zh-cn': '页', 57 | 'zh-tw': '頁', 58 | 'en-us': 'page' 59 | }; 60 | 61 | _this['refresh'] = { 62 | 'zh-cn': '重新加载', 63 | 'zh-tw': '重新加載', 64 | 'en-us': 'Refresh' 65 | }; 66 | _this['export'] = { 67 | 'zh-cn': '导出', 68 | 'zh-tw': '導出', 69 | 'en-us': 'Export' 70 | }; 71 | _this['export-checked'] = { 72 | 'zh-cn': '导出选中项', 73 | 'zh-tw': '導出選中項', 74 | 'en-us': 'Export selected' 75 | }; 76 | _this['config'] = { 77 | 'zh-cn': '配置表', 78 | 'zh-tw': '配置表', 79 | 'en-us': 'Setting Grid' 80 | }; 81 | _this['print'] = { 82 | 'zh-cn': '打印', 83 | 'zh-tw': '打印', 84 | 'en-us': 'Print' 85 | }; 86 | _this['copy'] = { 87 | 'zh-cn': '复制', 88 | 'zh-tw': '復制', 89 | 'en-us': 'Copy' 90 | }; 91 | _this['hide-row'] = { 92 | 'zh-cn': '隐藏行', 93 | 'zh-tw': '隱藏行', 94 | 'en-us': 'Hidden Row' 95 | }; 96 | _this['ok'] = { 97 | 'zh-cn': '确定', 98 | 'zh-tw': '確定', 99 | 'en-us': 'OK' 100 | }; 101 | _this['reset'] = { 102 | 'zh-cn': '重置', 103 | 'zh-tw': '重置', 104 | 'en-us': 'Reset' 105 | }; 106 | }; 107 | -------------------------------------------------------------------------------- /src/module/i18n/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * i18n: 国际化 3 | * */ 4 | import { isUndefined, isArray } from '@jTool/utils'; 5 | import { outWarn } from '@common/utils'; 6 | import { SettingObj } from 'typings/types'; 7 | 8 | /** 9 | * 指定[表格 键值 语种]获取对应文本 10 | * @param settings 11 | * @param key 键值 12 | * @returns {*|string} 13 | */ 14 | const getText = (settings: SettingObj, key: string): string => { 15 | return settings.textConfig[key][settings.i18n]; 16 | }; 17 | 18 | /** 19 | * 获取与当前配置国际化匹配的文本 20 | * @param settings 21 | * @param key 指向的文本索引 22 | * @param v1 可为空,也存在1至3项,只存在1项时可为数组 23 | * @param v2 可为空,也存在1至3项,只存在1项时可为数组 24 | * @param v3 可为空,也存在1至3项,只存在1项时可为数组 25 | * @returns {string} 26 | */ 27 | /* eslint-disable */ 28 | export default function(settings: SettingObj, key: string, v1?: number | string | Array, v2?: number | string, v3?: number | string) { 29 | let intrusion: Array = []; 30 | const len = arguments.length; 31 | // 处理参数,实现多态化 32 | if (len === 3 && isArray(arguments[2])) { 33 | intrusion = arguments[2]; 34 | } else if (len > 2) { 35 | for (let i = 2; i < len; i++) { 36 | intrusion.push(arguments[i]); 37 | } 38 | } 39 | 40 | try { 41 | let _text = getText(settings, key); 42 | if (!intrusion || !intrusion.length) { 43 | return _text; 44 | } 45 | 46 | // 更换包含{}的文本 47 | return _text.replace(/{\d+}/g, word => { 48 | // @ts-ignore 49 | const _v = intrusion[word.match(/\d+/)]; 50 | return isUndefined(_v) ? '' : _v; 51 | }); 52 | } catch (e) { 53 | outWarn(`not find language matched to ${key}`); 54 | return ''; 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /src/module/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * GridManager: 挂载至Element、window、jQuery 3 | * #001: 如果已经存在,则清除之前的实例,重新进行实例化。原因:如果不清除而直接返回错误,会让使用者存在不便。 4 | * */ 5 | import jTool from '@jTool'; 6 | import { isString, rootWindow } from '@jTool/utils'; 7 | import GridManager from './GridManager'; 8 | import '../css/var.less'; 9 | 10 | /* 11 | * 捆绑至选择器对象 12 | * */ 13 | (() => { 14 | Element.prototype.GM = Element.prototype.GridManager = function () { 15 | // 方法名 16 | let name, 17 | 18 | // 参数 19 | arg, 20 | 21 | // 回调函数 22 | callback, 23 | 24 | // 条件 25 | condition; 26 | 27 | const _ = arguments; 28 | // 格式化参数 29 | if (!isString(_[0])) { 30 | // ex: document.querySelector('table').GridManager({arg}, callback) 31 | name = 'init'; 32 | arg = _[0]; 33 | callback = _[1]; 34 | } else { 35 | // ex: document.querySelector('table').GridManager('get') 36 | // ex: document.querySelector('table').GM('showTh', $th); 37 | // ex: document.querySelector('table').GM('setSort',sortJson,callback, refresh); 38 | name = _[0]; 39 | arg = _[1]; 40 | callback = _[2]; 41 | condition = _[3]; 42 | } 43 | 44 | // no init: 执行 45 | if (name !== 'init') { 46 | return GridManager[name](this, arg, callback, condition) || this; 47 | } 48 | 49 | // init 50 | new GridManager(this, arg, callback); 51 | }; 52 | })(); 53 | 54 | /** 55 | * 将GridManager 对象映射至window 56 | */ 57 | (() => { 58 | // window只存储第一次加载的GM对像, 后续加载的对像将不再向window上挂载 59 | if (!rootWindow.GridManager && !rootWindow.GM) { 60 | rootWindow.GridManager = rootWindow.GM = GridManager; 61 | } 62 | })(); 63 | 64 | /* 65 | * 兼容jQuery 66 | * */ 67 | (jQuery => { 68 | if (!jQuery) { 69 | return; 70 | } 71 | 72 | const runFN = function () { 73 | return this.get(0).GM(...arguments); 74 | }; 75 | 76 | jQuery.fn.extend({ 77 | GridManager: runFN, 78 | 79 | // 提供简捷调用方式 80 | GM: runFN 81 | }); 82 | 83 | // 恢复jTool占用的$变量 84 | rootWindow.$ = jQuery; 85 | })(rootWindow.jQuery); 86 | 87 | export { jTool }; 88 | export default GridManager; 89 | -------------------------------------------------------------------------------- /src/module/menu/event.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 菜单功能所需的事件项 3 | * @param _ 4 | */ 5 | import { WRAP_KEY } from '@common/constants'; 6 | import { MOUSE_DOWN, CONTEXT_MENU, createEventsObj } from '@common/events'; 7 | import { EventMap } from 'typings/types'; 8 | 9 | export const getEvent = (_: string): EventMap => { 10 | return { 11 | // 打开菜单 12 | openMenu: createEventsObj(CONTEXT_MENU, `[${WRAP_KEY}="${_}"]`), 13 | 14 | // 关闭菜单 15 | closeMenu: createEventsObj(`${MOUSE_DOWN}.closeMenu`, 'body') 16 | }; 17 | }; 18 | 19 | export const eventMap = {}; 20 | -------------------------------------------------------------------------------- /src/module/menu/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Menu[右键菜单] 3 | * 参数说明: 4 | * - supportMenu: 是否开启右键菜单 5 | * - menuHandler: 菜单处理程序 6 | * - type: Function 7 | * - default: list => list 8 | * - arguments 9 | * - list: 菜单在实例化时生成的菜单list, 在menuHandler内将修改后的list返回可自定义菜单项。 10 | * 11 | * GridManager自带以下菜单项: 12 | * - 上一页 13 | * - 下一页 14 | * - 导出 15 | * - 导出选中项 16 | * - 重新加载 17 | * - 复制 18 | * - 打印 19 | * - 隐藏行 20 | * - 配置表 21 | * 22 | * 每个菜单对像都有以下属性: 23 | * - content: String 显示文本 24 | * - line: Boolean 是否显示分割线 25 | * - run(gridManagerName, $dom): 每次打开菜单前的执行函数 26 | * - onClick(gridManagerName, $cell): 菜单项点击事件 27 | */ 28 | import jTool from '@jTool'; 29 | import { clearTargetEvent } from '@common/base'; 30 | import { MENU_KEY } from '@common/constants'; 31 | import { getEvent, eventMap } from './event'; 32 | import { TARGET, EVENTS } from '@common/events'; 33 | import { getMenuQuerySelector, createMenuDom, clearMenuDOM, getMenuPosition } from './tool'; 34 | import './style.less'; 35 | 36 | class Menu { 37 | /** 38 | * 初始化 39 | * @param _ 40 | */ 41 | init(_: string): void { 42 | eventMap[_] = getEvent(_); 43 | 44 | const { openMenu, closeMenu } = eventMap[_]; 45 | 46 | // 绑定打开右键菜单栏 47 | jTool(openMenu[TARGET]).on(openMenu[EVENTS], function (e: MouseEvent) { 48 | e.preventDefault(); 49 | e.stopPropagation(); 50 | 51 | const target = e.target as HTMLTableCellElement; 52 | // 验证:如果不是tbody 并且也 不是tbody的子元素,直接跳出 53 | // if (target.nodeName !== 'TBODY' && jTool(target).closest('tbody').length === 0) { 54 | // return; 55 | // } 56 | const $menu = createMenuDom(_, target); 57 | 58 | $menu.show(); 59 | 60 | // 定位 61 | $menu.css(getMenuPosition($menu.width(), $menu.height(), e.clientX, e.clientY)); 62 | 63 | // 禁用菜单区域浏览器默认右键行为 64 | $menu.on(openMenu[EVENTS], function (e1: MouseEvent) { 65 | e1.preventDefault(); 66 | e1.stopPropagation(); 67 | }); 68 | 69 | // 点击空处关闭 70 | const $closeTarget = jTool(closeMenu[TARGET]); 71 | const closeEvents = closeMenu[EVENTS]; 72 | $closeTarget.off(closeEvents); 73 | $closeTarget.on(closeEvents, function (e2: MouseEvent) { 74 | const eventSource = jTool(e2.target); 75 | // 当前为menu自身 76 | if (eventSource.attr(MENU_KEY) || eventSource.closest(`[${MENU_KEY}]`).length === 1) { 77 | return; 78 | } 79 | clearMenuDOM(_); 80 | }); 81 | }); 82 | 83 | } 84 | 85 | /** 86 | * 消毁 87 | * @param _ 88 | */ 89 | destroy(_: string): void { 90 | // 清除事件 91 | clearTargetEvent(eventMap[_]); 92 | 93 | // 删除DOM节点 94 | jTool(getMenuQuerySelector(_)).remove(); 95 | } 96 | } 97 | export default new Menu(); 98 | -------------------------------------------------------------------------------- /src/module/menu/style.less: -------------------------------------------------------------------------------- 1 | /** 2 | * style.less 3 | * @author wangbo 4 | * @since 2019-02-21 5 | */ 6 | 7 | .gm-menu{ 8 | display: none; 9 | width: 200px; 10 | position: absolute; 11 | color: var(--gm-color-high); 12 | border: var(--gm-border-high); 13 | padding: 5px 0; 14 | background: var(--gm-bg); 15 | z-index: 9999; 16 | box-shadow: 0 0 5px #ccc; 17 | [menu-action]{ 18 | display: block; 19 | height: 28px; 20 | position: relative; 21 | padding: 5px 20px; 22 | cursor: default; 23 | line-height: 18px; 24 | &:hover{ 25 | background: #777; 26 | color: #fff; 27 | } 28 | &.disabled{ 29 | color: #ccc; 30 | background: var(--gm-bg); 31 | } 32 | .gm-icon{ 33 | display: block; 34 | font-size: 16px; 35 | position: absolute; 36 | top: 5px; 37 | right: 10px; 38 | } 39 | [gm-fake-copy]{ 40 | width: 5px; 41 | position: absolute; 42 | left: 0; 43 | border: none; 44 | z-index: -99; 45 | background: initial; 46 | } 47 | } 48 | .menu-line{ 49 | display: block; 50 | height: 1px; 51 | background: #e8e8e8; 52 | box-shadow: 0 0 5px #ccc; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/module/merge/constants.ts: -------------------------------------------------------------------------------- 1 | // 列合并属性 2 | export const ROW_SPAN = 'rowspan'; 3 | 4 | // 当前列被合并标识 5 | export const MERGE_TD = 'merge-td'; 6 | 7 | // 列合并最后一个 8 | export const ROW_LAST = 'last-rowspan'; 9 | -------------------------------------------------------------------------------- /src/module/merge/index.ts: -------------------------------------------------------------------------------- 1 | import { getColTd, getTh, getTable, getTbody } from '@common/base'; 2 | import { ROW_HIDE_KEY } from '@common/constants'; 3 | import jTool from '@jTool'; 4 | import { each } from '@jTool/utils'; 5 | import { ROW_SPAN, MERGE_TD, ROW_LAST } from './constants'; 6 | import { Column, ColumnMap } from 'typings/types'; 7 | import './style.less'; 8 | 9 | /** 10 | * 根据配置项[merge]合并行数据相同的单元格 11 | * @param _ 12 | * @param columnMap 13 | */ 14 | export const mergeRow = (_: string, columnMap: ColumnMap): void => { 15 | each(columnMap, (key: string, col: Column) => { 16 | let merge = col.merge; 17 | if (!merge || (merge !== 'text' && merge !== 'html')) { 18 | return true; 19 | } 20 | 21 | // 排除: 汇总行 和 隐藏行 22 | const $tdList = getColTd(getTh(_, key), getTbody(_).find(`tr:not([gm-summary-row]):not([${ROW_HIDE_KEY}])`)); 23 | 24 | let len = $tdList.length; 25 | let index = len; 26 | let mergeSum = 1; 27 | // 倒序进行处理: 添加rowspan需要增加至第一行的单元格,使用倒序可以很好的处理这个问题 28 | while (index) { 29 | const $td = $tdList.eq(index - 1); 30 | $td.removeAttr(ROW_SPAN); 31 | $td.removeAttr(MERGE_TD); 32 | $td.removeAttr(ROW_LAST); 33 | index--; 34 | if (index === 0) { 35 | if (mergeSum > 1) { 36 | $td.attr(ROW_SPAN, mergeSum); 37 | mergeSum = 1; 38 | } 39 | return; 40 | } 41 | const $prve = $tdList.eq(index - 1); 42 | 43 | // 这里比较html而不比较数据的原因: 当前单元格所展示文本可能在template中未完全使用数据 44 | if ($prve[merge]() === $td[merge]()) { 45 | $td.attr(MERGE_TD, ''); 46 | mergeSum++; 47 | } else { 48 | if (mergeSum > 1) { 49 | $td.attr(ROW_SPAN, mergeSum); 50 | // 当前td的rowspan 到达到最后一行 51 | if (index + mergeSum === len) { 52 | $td.attr(ROW_LAST, ''); 53 | } 54 | mergeSum = 1; 55 | } 56 | } 57 | } 58 | }); 59 | }; 60 | 61 | /** 62 | * 清除合并行数据相同的单元格 63 | * @param _ 64 | */ 65 | export const clearMergeRow = (_: string): void => { 66 | const $table = getTable(_); 67 | jTool(`[${ROW_SPAN}]`, $table).removeAttr(ROW_SPAN); 68 | jTool(`[${MERGE_TD}]`, $table).removeAttr(MERGE_TD); 69 | }; 70 | -------------------------------------------------------------------------------- /src/module/merge/style.less: -------------------------------------------------------------------------------- 1 | .table-wrap td[merge-td]{ 2 | display: none; 3 | } 4 | .table-wrap td[last-rowspan]{ 5 | border-bottom: none; 6 | } 7 | -------------------------------------------------------------------------------- /src/module/moveRow/constants.ts: -------------------------------------------------------------------------------- 1 | // 拖拽中 class name 2 | export const CLASS_DRAG_ING = 'gm-move-row-ongoing'; 3 | 4 | // 镜像 class name 5 | export const CLASS_DREAMLAND = 'dreamland-row-div'; 6 | 7 | // td禁止移动标识 8 | export const DISABLE_MOVE = 'disable-move'; 9 | -------------------------------------------------------------------------------- /src/module/moveRow/dreamland.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{vm.tbody}} 4 | 5 |
      6 | -------------------------------------------------------------------------------- /src/module/moveRow/event.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 拖拽功能所需的事件项 3 | * @param scope: querySelector 域 4 | */ 5 | import { EMPTY_TPL_KEY } from '@common/constants'; 6 | import { MOUSE_DOWN, MOUSE_MOVE, MOUSE_UP, createEventsObj } from '@common/events'; 7 | import { EventMap } from 'typings/types'; 8 | 9 | export const getEvent = (scope: string): EventMap => { 10 | const name = 'gmLineDrag'; 11 | return { 12 | // 开始 13 | start: createEventsObj(`${MOUSE_DOWN}.${name}`, scope, `tr:not([${EMPTY_TPL_KEY}])`), 14 | 15 | // 调整中 16 | doing: createEventsObj(`${MOUSE_MOVE}.${name}`, 'body'), 17 | 18 | // 停止 19 | abort: createEventsObj(`${MOUSE_UP}.${name}`, 'body') 20 | }; 21 | }; 22 | 23 | export const eventMap = {}; 24 | -------------------------------------------------------------------------------- /src/module/moveRow/style.less: -------------------------------------------------------------------------------- 1 | @import "../../css/mixins"; 2 | 3 | // 拖拽换位中 4 | .table-div .gm-move-row-ongoing { 5 | cursor: all-scroll; 6 | opacity: 1; 7 | animation: opacityChange 1s ease-in-out infinite; 8 | } 9 | 10 | // 拖拽标识 11 | .table-div[move-row="all"] tr:not([empty-template]) td:not([disable-move]){ 12 | cursor: all-scroll; 13 | } 14 | 15 | // 单列移动模式: 开启后才会存在 th[th-name="gm_moverow"] 和 td[gm-moverow] 节点 16 | .table-div[move-row="single"] { 17 | //th[th-name="gm_moverow"]{ 18 | // border-right-color: transparent; 19 | //} 20 | td[gm-moverow]{ 21 | cursor: all-scroll; 22 | text-align: center; 23 | padding: 0; 24 | i{ 25 | pointer-events: none; 26 | color: #7E7E7E; 27 | } 28 | } 29 | } 30 | 31 | // 拖拽镜像 32 | .dreamland-row-div{ 33 | display: none; 34 | position: absolute; 35 | cursor: all-scroll; 36 | z-index: 4; 37 | border-top: var(--gm-border); 38 | border-bottom: var(--gm-border); 39 | box-shadow:0 0 4px 0 rgba(0,0,0,0.13); 40 | overflow: hidden; // 固定列需要使用到 41 | .dreamland-row { 42 | table-layout: fixed; 43 | width: 100%; 44 | background-color: #d8d8d8; 45 | border-collapse: separate; 46 | border-spacing: 0; 47 | font-size: var(--gm-font-size); 48 | td { 49 | background: var(--gm-bg); 50 | padding: 11px; 51 | border-right: var(--gm-border); 52 | border-bottom: none; 53 | vertical-align: middle; 54 | .text-overflow(); 55 | &[cell-hidden]{ 56 | display: none; 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/module/nested/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 嵌套表头 3 | * - 触发条件: columnData中存在有效的children字段 4 | * - DOM标识: 存在嵌套表头的表格将在 table-div 上增加 gm-nested 属性 5 | */ 6 | import { each, isValidArray } from '@jTool/utils'; 7 | import { getDiv } from '@common/base'; 8 | import { Column, ColumnMap } from 'typings/types'; 9 | import './style.less'; 10 | 11 | /** 12 | * 获取嵌套列所占的列数 13 | * @param col 14 | * @returns {number} 15 | */ 16 | const getNestedLen = (col: Column): number => { 17 | let num = 0; 18 | const getLen = (c: Column) => { 19 | c.children.forEach(item => { 20 | if (isValidArray(item.children)) { 21 | getLen(item); 22 | } else { 23 | num++; 24 | } 25 | }); 26 | }; 27 | getLen(col); 28 | return num; 29 | }; 30 | 31 | /** 32 | * 生成嵌套数据递归函数 33 | * @param columnMap 34 | * @param columnList 35 | * @param list 36 | * @param rowspan 37 | */ 38 | const pushList = (columnMap: ColumnMap, columnList: Array>, list: Array, rowspan: number): void => { 39 | each(list, (item: Column) => { 40 | // 这里不直接使用item而用columnMap的原因: item的children中存储的是初始时的数据,缺失level字段 41 | const col = columnMap[item.key]; 42 | const { level } = col; 43 | if (!columnList[level]) { 44 | columnList[level] = []; 45 | } 46 | if (isValidArray(col.children)) { 47 | col.rowspan = 1; 48 | col.colspan = getNestedLen(col); 49 | pushList(columnMap, columnList, col.children, rowspan - 1); 50 | } else { 51 | col.rowspan = rowspan; 52 | col.colspan = 1; 53 | } 54 | 55 | if (level > 0) { 56 | columnList[level].push(col); 57 | } 58 | }); 59 | }; 60 | class Nested { 61 | /** 62 | * 增加嵌套表头标识: 用于样式文件 63 | * @param _ 64 | */ 65 | addSign(_: string): void { 66 | getDiv(_).attr('gm-nested', ''); 67 | } 68 | 69 | /** 70 | * 生成嵌套数据 71 | * @param columnMap 72 | * @param columnList 73 | */ 74 | push(columnMap: ColumnMap, columnList: Array>): void { 75 | let maxLevel = 0; 76 | const topList = columnList[0]; 77 | each(columnMap, (key: string, col: Column) => { 78 | const { level, index } = col; 79 | // 生成最上层数组 80 | if (level === 0) { 81 | topList[index] = col; 82 | } 83 | 84 | // 最大层层级值 85 | if (maxLevel < level) { 86 | maxLevel = level; 87 | } 88 | }); 89 | pushList(columnMap, columnList, topList, maxLevel + 1); 90 | } 91 | } 92 | export default new Nested(); 93 | -------------------------------------------------------------------------------- /src/module/nested/style.less: -------------------------------------------------------------------------------- 1 | @import "../../css/mixins"; 2 | [gm-nested]{ 3 | thead:after{ 4 | content: " "; 5 | width: 1px; 6 | position: absolute; 7 | top: 0; 8 | right: 0; 9 | background: var(--gm-bg-high); 10 | height: 100%; 11 | } 12 | th:last-child{ 13 | border-right: var(--gm-border) 14 | } 15 | td:last-child{ 16 | border-right: none; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/module/order/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * order: 序号 3 | * */ 4 | import { ORDER_KEY, GM_CREATE, DISABLE_CUSTOMIZE } from '@common/constants'; 5 | import i18n from '../i18n'; 6 | import { SettingObj } from 'typings/types'; 7 | import './style.less'; 8 | class Order { 9 | /** 10 | * 获取序号列对象 11 | * @param settings 12 | */ 13 | getColumn(settings: SettingObj): object { 14 | const { autoOrderConfig } = settings; 15 | return { 16 | key: ORDER_KEY, 17 | text: i18n(settings, 'order-text'), 18 | isAutoCreate: true, 19 | isShow: true, 20 | [DISABLE_CUSTOMIZE]: true, 21 | width: autoOrderConfig.width, 22 | fixed: autoOrderConfig.fixed, 23 | // align: 'center', // 调整为由样式控制 24 | template: (order: string, row: object, index: number, isTop: boolean) => { 25 | return `${isTop ? order : ''}`; 26 | } 27 | }; 28 | } 29 | } 30 | export default new Order(); 31 | -------------------------------------------------------------------------------- /src/module/order/style.less: -------------------------------------------------------------------------------- 1 | th[gm-order]{ 2 | .th-wrap{ 3 | padding: 0; 4 | } 5 | cursor: default; 6 | text-align: center; 7 | } 8 | td[gm-order]{ 9 | text-align: center; 10 | padding: 0; 11 | white-space: normal; 12 | } 13 | -------------------------------------------------------------------------------- /src/module/print/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 打印功能 3 | * @param _ 4 | */ 5 | import { getTable } from '@common/base'; 6 | import { TABLE_HEAD_KEY, FAKE_TABLE_HEAD_KEY, GM_CREATE, CELL_HIDDEN } from '@common/constants'; 7 | import { MERGE_TD } from '@module/merge/constants'; 8 | import { each } from '@jTool/utils'; 9 | export default function print(_: string): void { 10 | const $table = getTable(_).clone(true); 11 | const style = ''; 18 | const printWindow = open(); 19 | // 清除隐藏项 20 | $table.find(`[${CELL_HIDDEN}]`).remove(); 21 | $table.find(`[${MERGE_TD}]`).remove(); 22 | 23 | // 清除表格自动创建项 24 | $table.find(`[${GM_CREATE}]`).remove(); 25 | 26 | const fakeTh = $table.find(`[${FAKE_TABLE_HEAD_KEY}] th`); 27 | // 清除表格样式 28 | const $th = $table.find(`[${TABLE_HEAD_KEY}] th`); 29 | $th.removeAttr('style'); 30 | each($th, (th: HTMLTableCellElement, i: number) => { 31 | th.innerHTML = fakeTh.eq(i).find('.th-text').html(); 32 | }); 33 | $table.removeAttr('style'); 34 | 35 | // 清除mock thhead 36 | $table.find(`[${FAKE_TABLE_HEAD_KEY}]`).remove(); 37 | 38 | printWindow.document.write(style + $table.get(0).outerHTML); 39 | printWindow.document.close(); 40 | printWindow.print(); 41 | printWindow.close(); 42 | } 43 | -------------------------------------------------------------------------------- /src/module/print/style.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baukh789/GridManager/3b8ec6737416a5a4042b1425c1055a2bf324dcc0/src/module/print/style.less -------------------------------------------------------------------------------- /src/module/remind/event.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 表头提醒功能所需的事件项 3 | * @param gridManagerName 4 | * @param scope: querySelector 域 5 | * 6 | * #001: 这里不使用onmouseenter的原因: onmouseenter不支持事件冒泡,无法进行事件委托 7 | */ 8 | import { REMIND_CLASS, FAKE_TABLE_HEAD_KEY } from '@common/constants'; 9 | import { MOUSE_OVER, MOUSE_LEAVE, createEventsObj } from '@common/events'; 10 | import { EventMap } from 'typings/types'; 11 | 12 | export const getEvent = (_: string, scope: string): EventMap => { 13 | return { 14 | // 触发 #001 15 | start: createEventsObj(MOUSE_OVER, scope, `[${FAKE_TABLE_HEAD_KEY}="${_}"] .${REMIND_CLASS}`), 16 | 17 | tooltipLeave: createEventsObj(MOUSE_LEAVE, scope, `[${FAKE_TABLE_HEAD_KEY}="${_}"] .${REMIND_CLASS}`) 18 | }; 19 | }; 20 | 21 | export const eventMap = {}; 22 | -------------------------------------------------------------------------------- /src/module/remind/remind.tpl.html: -------------------------------------------------------------------------------- 1 |
      2 | 3 |
      {{vm.text}}
      4 |
      5 | -------------------------------------------------------------------------------- /src/module/remind/style.less: -------------------------------------------------------------------------------- 1 | /** 2 | * 表头提醒 3 | * style.less 4 | * @author wangbo 5 | * @since 2019-02-20 6 | */ 7 | [grid-manager], .gm-dreamland-div{ 8 | th[remind] .th-wrap { 9 | padding-left: 20px; 10 | } 11 | } 12 | .gm-remind-action { 13 | width: 16px; 14 | height: 18px; 15 | position: absolute; 16 | top: calc(50% - 9px); 17 | left: 4px; 18 | .ra-icon { 19 | width: 16px; 20 | height: 16px; 21 | position: absolute; 22 | top: 0; 23 | left: 0; 24 | font-size: 14px; 25 | line-height: 18px; 26 | //vertical-align: top; 27 | opacity: .7; 28 | cursor: help; 29 | color: var(--gm-remind-icon-color); 30 | } 31 | &:hover { 32 | .ra-icon { 33 | opacity: .3; 34 | color: #1890ff; 35 | } 36 | .ra-area { 37 | display: block; 38 | } 39 | } 40 | } 41 | // 这个样式在thead, tbody中通用 42 | .ra-area { 43 | display: none; 44 | width: 150px; 45 | position: absolute; 46 | top: 24px; 47 | left: 0; 48 | padding: 4px 8px; 49 | z-index: 9999; 50 | border-radius: 2px; 51 | background-color: var(--gm-remind-bg); 52 | line-height: 18px; 53 | color: var(--gm-remind-color); 54 | text-align: center; 55 | &:after { 56 | content: ""; 57 | position: absolute; 58 | border: solid transparent; 59 | top: 0; 60 | left: 4px; 61 | margin-top: -3px; 62 | border-width: 0 3px 3px; 63 | border-bottom-color: var(--gm-remind-bg); 64 | } 65 | &.right-model { 66 | left: auto; 67 | right: 0; 68 | &:after { 69 | top: 0; 70 | left: auto; 71 | right: 4px; 72 | } 73 | } 74 | &.gm-tooltip{ 75 | display: block; 76 | width: auto; 77 | &:after{ 78 | top: calc(100% + 3px); 79 | transform-origin: center; 80 | transform: rotate(-180deg); 81 | transition: transform .3s; 82 | } 83 | } 84 | } 85 | 86 | // 禁用文本的情况下,禁止触发表头提醒。添加后可以解决宽度调整时因为触发了表头提醒,而导致宽度调整中止 87 | .no-select-text{ 88 | .gm-remind-action{ 89 | pointer-events: none; 90 | } 91 | } 92 | 93 | // 表头的icon图标跟随文本 94 | .gm-icon-follow-text{ 95 | .gm-remind-action{ 96 | display: inline-block; 97 | width: 18px; 98 | position: relative; 99 | top: 0; 100 | left: 0; 101 | font-size: var(--gm-font-size);// icon跟随模式下 th-wrap的font-size是0,所以这里需要单独指定 102 | vertical-align: middle; 103 | text-align: center; 104 | .ra-area{ 105 | white-space: normal; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/module/rowVisible/index.ts: -------------------------------------------------------------------------------- 1 | import { ROW_HIDE_KEY, TR_CACHE_KEY, TR_PARENT_KEY } from '@common/constants'; 2 | import { mergeRow } from '@module/merge'; 3 | import './style.less'; 4 | import jTool from '@jTool'; 5 | import { isUndefined } from '@jTool/utils'; 6 | import { JTool, SettingObj } from 'typings/types'; 7 | 8 | // 获取TR列表: cacheKey相匹配的普通行及通栏行、树结构行 9 | const getTrList = (cacheKey: string | number): JTool => { 10 | return jTool(`[${TR_CACHE_KEY}="${cacheKey}"], [${TR_PARENT_KEY}="${cacheKey}"], [${TR_PARENT_KEY}^="${cacheKey}-"]`); 11 | }; 12 | 13 | /** 14 | * 显示已隐藏的行 15 | * @param settings 16 | * @param cacheKey: 行的索引,为空时将显示所有已隐藏的行 17 | */ 18 | export const showRow = (settings: SettingObj, cacheKey: string | number): void => { 19 | let $trList: JTool; 20 | // 为空时将显示所有已隐藏的行 21 | if (isUndefined(cacheKey)) { 22 | $trList = jTool(`[${ROW_HIDE_KEY}]`); 23 | } else { 24 | $trList = getTrList(cacheKey); 25 | } 26 | 27 | $trList.attr(ROW_HIDE_KEY, 'out'); 28 | setTimeout(() => { 29 | $trList.removeAttr(ROW_HIDE_KEY); 30 | mergeRow(settings._, settings.columnMap); 31 | }, 500); 32 | }; 33 | 34 | /** 35 | * 隐藏行 36 | * @param settings 37 | * @param cacheKey: 行的索引,为空时将不执行 38 | */ 39 | export const hideRow = (settings: SettingObj, cacheKey: string | number): void => { 40 | const $trList = getTrList(cacheKey); 41 | $trList.attr(ROW_HIDE_KEY, 'ing'); 42 | setTimeout(() => { 43 | $trList.attr(ROW_HIDE_KEY, 'true'); 44 | mergeRow(settings._, settings.columnMap); 45 | }, 500); 46 | }; 47 | -------------------------------------------------------------------------------- /src/module/rowVisible/style.less: -------------------------------------------------------------------------------- 1 | tbody tr{ 2 | // 隐藏行 3 | &[gm-row-hide="ing"]{ 4 | animation: gmHideEffect .5s ease-in-out 1 forwards; 5 | } 6 | 7 | // 显示行 8 | &[gm-row-hide="out"]{ 9 | animation: gmShowEffect .5s ease-in-out 1 forwards; 10 | } 11 | 12 | // 已隐藏 13 | &[gm-row-hide="true"]{ 14 | display: none; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/module/scroll/style.less: -------------------------------------------------------------------------------- 1 | /* 表头置顶 */ 2 | [grid-manager-mock-thead] { 3 | position: absolute; 4 | left: 0; 5 | top: 0; 6 | } 7 | -------------------------------------------------------------------------------- /src/module/sort/event.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 排序功能所需的事件项 3 | * @param gridManagerName 4 | * @param scope: querySelector 域 5 | */ 6 | import { SORT_CLASS, FAKE_TABLE_HEAD_KEY } from '@common/constants'; 7 | import { createEventsObj, MOUSE_CLICK } from '@common/events'; 8 | import { EventMap } from 'typings/types'; 9 | 10 | export const getEvent = (_: string, scope: string): EventMap => { 11 | return { 12 | // 触发 #001 13 | start: createEventsObj(MOUSE_CLICK, scope, `[${FAKE_TABLE_HEAD_KEY}="${_}"] .${SORT_CLASS}`) 14 | }; 15 | }; 16 | 17 | export const eventMap = {}; 18 | -------------------------------------------------------------------------------- /src/module/sort/sort.tpl.html: -------------------------------------------------------------------------------- 1 |
      2 | 3 | 4 |
      5 | -------------------------------------------------------------------------------- /src/module/sort/style.less: -------------------------------------------------------------------------------- 1 | /** 2 | * 排序 3 | * style.less 4 | * @author wangbo 5 | * @since 2019-02-20 6 | */ 7 | 8 | @import "../../css/mixins"; 9 | 10 | [grid-manager], .gm-dreamland-div{ 11 | th[sorting] .th-wrap { 12 | padding-right: 20px; 13 | } 14 | // 排序与表头筛选同时存在时,需要将样式重置 15 | th[sorting][filter] .th-wrap { 16 | padding-right: 45px; 17 | .gm-sorting-action{ 18 | right: 25px; 19 | } 20 | } 21 | } 22 | .gm-sorting-action{ 23 | display: block; 24 | width: 14px; 25 | height: 18px; 26 | position: absolute; 27 | top: calc(50% - 9px); 28 | right: 5px; 29 | cursor: pointer; 30 | color: #444; 31 | opacity: .7; 32 | &:hover { 33 | color: #000; 34 | opacity: 1; 35 | } 36 | &.sorting-up { 37 | .sa-up { 38 | opacity: 1; 39 | } 40 | .sa-down { 41 | opacity: 0.1; 42 | } 43 | } 44 | &.sorting-down { 45 | .sa-up { 46 | opacity: 0.1; 47 | } 48 | .sa-down { 49 | opacity: 1; 50 | } 51 | } 52 | .sa-icon { 53 | display: block; 54 | height: 10px; 55 | position: absolute; 56 | font-size: var(--gm-font-size); 57 | line-height: 10px; 58 | } 59 | .sa-up { 60 | top: 0; 61 | right: 0; 62 | } 63 | .sa-down { 64 | bottom: 0; 65 | right: 0; 66 | } 67 | } 68 | 69 | // 表头的icon图标跟随文本 70 | .gm-icon-follow-text{ 71 | .gm-sorting-action{ 72 | display: inline-block; 73 | width: 18px; 74 | position: relative; 75 | top: 0; 76 | left: 0; 77 | vertical-align: middle; 78 | text-align: center; 79 | .sa-icon{ 80 | right: 4px; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/module/summary/constants.ts: -------------------------------------------------------------------------------- 1 | // 汇总行在div上的标志 2 | export const SUMMARY_FLAG = 'gm-summary'; 3 | 4 | // 汇总行所在tr上的标志 5 | export const SUMMARY_ROW = `${SUMMARY_FLAG}-row`; 6 | -------------------------------------------------------------------------------- /src/module/summary/index.ts: -------------------------------------------------------------------------------- 1 | import {isElement, isEmptyObject, isNull, isUndefined, each, isFunction} from '@jTool/utils'; 2 | import { compileTd } from '@common/framework'; 3 | import { getDiv } from '@common/base'; 4 | import { DISABLE_MOVE } from '@module/moveRow/constants'; 5 | import { SUMMARY_FLAG, SUMMARY_ROW } from './constants'; 6 | import { SettingObj, Column, TrObject } from 'typings/types'; 7 | import './style.less'; 8 | import { getTableData } from '@common/cache'; 9 | 10 | const querySelector = `[${SUMMARY_ROW}]`; 11 | export const installSummary = (settings: SettingObj, columnList: Array, trObjectList: Array): void => { 12 | const { _, summaryHandler } = settings; 13 | if (!isFunction(summaryHandler)) { 14 | return; 15 | } 16 | const summaryMap = summaryHandler(getTableData(_, true)); 17 | 18 | const $tableDiv = getDiv(_); 19 | // 汇总行是唯一的,如果已存在则清除 20 | $tableDiv.find(querySelector).remove(); 21 | 22 | // 未设置汇总行执行函数: 默认返回的为空对像 23 | if (isEmptyObject(summaryMap)) { 24 | $tableDiv.removeAttr(SUMMARY_FLAG); 25 | return; 26 | } 27 | $tableDiv.attr(SUMMARY_FLAG, ''); 28 | const tdList: Array = []; 29 | 30 | let style = ''; 31 | // 兼容性处理: safari 在处理sticky时,需要减去thead的高度 @baukh20221010: 移除该逻辑,原因是后续的safari修复了该问题(具体版本未知) 32 | // if (browser === 'safari') { 33 | // style = `style="bottom: ${getThead(_).height()}px"`; 34 | // } 35 | each(columnList, (col: Column) => { 36 | const { key, align } = col; 37 | let summary = summaryMap[key]; 38 | if (isNull(summary) || isUndefined(summary)) { 39 | summary = ''; 40 | } 41 | 42 | const alignAttr = align ? `align="${align}"` : ''; 43 | let { text, compileAttr } = compileTd(settings, () => summary, {}, undefined, key); 44 | text = isElement(text) ? text.outerHTML : text; 45 | 46 | tdList.push(`${text}`); 47 | }); 48 | trObjectList.push({ 49 | className: [], 50 | attribute: [[SUMMARY_ROW, '']], 51 | querySelector, 52 | tdList 53 | }); 54 | }; 55 | -------------------------------------------------------------------------------- /src/module/summary/style.less: -------------------------------------------------------------------------------- 1 | .table-div[gm-summary] { 2 | tr[gm-summary-row] td{ 3 | background: #F2F6FC; 4 | position: sticky; 5 | position: -webkit-sticky; // safari 需要 6 | bottom: 0; // safari 下并不会使用该值,而会在js中进行控制 7 | border-top: var(--gm-border); 8 | } 9 | 10 | // 汇总行的上一行: 不显示下边框 11 | tr:nth-last-child(2) td{ 12 | border-bottom: none; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/module/tree/event.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 树折叠功能所需的事件项 3 | */ 4 | import { MOUSE_CLICK, createEventsObj } from '@common/events'; 5 | import { EventMap } from 'typings/types'; 6 | 7 | export const getEvent = (scope: string, key: string): EventMap => { 8 | return { 9 | // 折叠事件 10 | toggle: createEventsObj(MOUSE_CLICK, scope, `[${key}] i`) 11 | }; 12 | }; 13 | 14 | export const eventMap = {}; 15 | -------------------------------------------------------------------------------- /src/module/tree/style.less: -------------------------------------------------------------------------------- 1 | .table-wrap { 2 | [tree-element] { 3 | display: inline-block; 4 | text-align: right; 5 | margin-right: 4px; 6 | line-height: 14px; 7 | vertical-align: middle; 8 | >i{ 9 | text-align: center; 10 | font-size: 14px; 11 | cursor: pointer; 12 | color: #00aaf1; 13 | &:hover{ 14 | color: #1890ff; 15 | } 16 | } 17 | } 18 | .tree-tbody tr { 19 | &:nth-child(odd) td{ 20 | background: var(--gm-bg); 21 | } 22 | &[odd] td{ 23 | background: var(--gm-bg-odd); 24 | } 25 | } 26 | .table-div table tbody { 27 | tr[children-state="false"]{ 28 | display: none; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/module/tree/tool.ts: -------------------------------------------------------------------------------- 1 | // 树的存储结构 2 | interface TreeCache { 3 | cacheKey: string; 4 | level: number; 5 | state: boolean; 6 | hasChildren: boolean; 7 | } 8 | 9 | // tree唯一标识 10 | export const treeElementKey = 'tree-element'; 11 | 12 | // 待添加tree dom存储器 13 | const treeCacheMap = {}; 14 | 15 | // 待添加tree dom存储器: 获取 16 | export const getTreeCache = (_: string): Array => { 17 | return treeCacheMap[_]; 18 | }; 19 | 20 | // 待添加tree dom存储器: 追加 21 | export const addTreeCache = (_: string, data: TreeCache): void => { 22 | if (!treeCacheMap[_]) { 23 | treeCacheMap[_] = []; 24 | } 25 | treeCacheMap[_].push(data); 26 | }; 27 | 28 | // 待添加tree dom存储器: 清除 29 | export const clearTreeCache = (_: string): void => { 30 | delete treeCacheMap[_]; 31 | }; 32 | 33 | // 获取icon class name 34 | export const getIconClass = (state: boolean): string => { 35 | return state ? 'gm-icon-sub' : 'gm-icon-add'; 36 | }; 37 | -------------------------------------------------------------------------------- /test/adjust/constants_test.js: -------------------------------------------------------------------------------- 1 | import { CLASS_ADJUST_ACTION, CLASS_ADJUST_ING } from '../../src/module/adjust/constants'; 2 | describe('constants', () => { 3 | it('CLASS_ADJUST_ACTION', () => { 4 | expect(CLASS_ADJUST_ACTION).toBe('gm-adjust-action'); 5 | }); 6 | it('CLASS_ADJUST_ING', () => { 7 | expect(CLASS_ADJUST_ING).toBe('gm-adjust-ing'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /test/adjust/event_test.js: -------------------------------------------------------------------------------- 1 | import { getEvent, eventMap } from '../../src/module/adjust/event'; 2 | import { CLASS_ADJUST_ACTION } from '../../src/module/adjust/constants'; 3 | import { DIV_KEY, FAKE_TABLE_HEAD_KEY } from '../../src/common/constants'; 4 | describe('adjust', () => { 5 | describe('getEvent', () => { 6 | let events = null; 7 | beforeEach(() => { 8 | }); 9 | afterEach(() => { 10 | events = null; 11 | }); 12 | 13 | it('基础验证', () => { 14 | expect(getEvent).toBeDefined(); 15 | expect(getEvent.length).toBe(2); 16 | }); 17 | 18 | it('执行验证', () => { 19 | events = getEvent('test', '#baukh'); 20 | expect(events.start.events).toBe('mousedown'); 21 | expect(events.start.target).toBe('#baukh'); 22 | expect(events.start.selector).toBe(`[${FAKE_TABLE_HEAD_KEY}="test"] .${CLASS_ADJUST_ACTION}`); 23 | 24 | expect(events.doing.events).toBe('mousemove'); 25 | expect(events.doing.target).toBe(`[${DIV_KEY}="test"]`); 26 | expect(events.doing.selector).toBe('#baukh'); 27 | 28 | expect(events.abort.events).toBe('mouseup mouseleave'); 29 | expect(events.abort.target).toBe('#baukh'); 30 | expect(events.abort.selector).toBeUndefined(); 31 | }); 32 | }); 33 | describe('eventMap', () => { 34 | it('基础验证', () => { 35 | expect(eventMap).toEqual({}); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/adjust/index_test.js: -------------------------------------------------------------------------------- 1 | import adjust from '../../src/module/adjust'; 2 | import { eventMap } from '../../src/module/adjust/event'; 3 | import { CLASS_ADJUST_ACTION } from '../../src/module/adjust/constants'; 4 | describe('adjust', () => { 5 | describe('html', () => { 6 | it('基础验证', () => { 7 | expect(adjust.html).toBe(``); 8 | }); 9 | }); 10 | // describe('destroy', () => { 11 | // it('基础验证', () => { 12 | // expect(eventMap).toEqual({}); 13 | // eventMap.test = { 14 | // a: 1 15 | // }; 16 | // adjust.destroy('test'); 17 | // expect(eventMap).toEqual({}); 18 | // }); 19 | // }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/ajaxPage/event_test.js: -------------------------------------------------------------------------------- 1 | import { getEvent, eventMap } from '../../src/module/ajaxPage/event'; 2 | import { TOOLBAR_KEY } from '../../src/common/constants'; 3 | 4 | describe('ajaxPage event', () => { 5 | describe('getEvent', () => { 6 | let events = null; 7 | beforeEach(() => { 8 | }); 9 | afterEach(() => { 10 | events = null; 11 | }); 12 | 13 | it('基础验证', () => { 14 | expect(getEvent).toBeDefined(); 15 | expect(getEvent.length).toBe(1); 16 | }); 17 | 18 | it('执行验证', () => { 19 | events = getEvent('test'); 20 | expect(events.input.events).toBe('keyup'); 21 | expect(events.input.target).toBe(`[${TOOLBAR_KEY}="test"]`); 22 | expect(events.input.selector).toBe('.gp-input'); 23 | 24 | expect(events.first.events).toBe('click'); 25 | expect(events.first.target).toBe(`[${TOOLBAR_KEY}="test"]`); 26 | expect(events.first.selector).toBe('[pagination-before] .first-page'); 27 | 28 | expect(events.previous.events).toBe('click'); 29 | expect(events.previous.target).toBe(`[${TOOLBAR_KEY}="test"]`); 30 | expect(events.previous.selector).toBe('[pagination-before] .previous-page'); 31 | 32 | expect(events.next.events).toBe('click'); 33 | expect(events.next.target).toBe(`[${TOOLBAR_KEY}="test"]`); 34 | expect(events.next.selector).toBe('[pagination-after] .next-page'); 35 | 36 | expect(events.last.events).toBe('click'); 37 | expect(events.last.target).toBe(`[${TOOLBAR_KEY}="test"]`); 38 | expect(events.last.selector).toBe('[pagination-after] .last-page'); 39 | 40 | expect(events.num.events).toBe('click'); 41 | expect(events.num.target).toBe(`[${TOOLBAR_KEY}="test"]`); 42 | expect(events.num.selector).toBe('[pagination-number] li'); 43 | 44 | expect(events.refresh.events).toBe('click'); 45 | expect(events.refresh.target).toBe(`[${TOOLBAR_KEY}="test"]`); 46 | expect(events.refresh.selector).toBe('.refresh-action'); 47 | }); 48 | }); 49 | 50 | describe('eventMap', () => { 51 | it('基础验证', () => { 52 | expect(eventMap).toEqual({}); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/checkbox/event_test.js: -------------------------------------------------------------------------------- 1 | import { getEvent, eventMap } from '../../src/module/checkbox/event'; 2 | 3 | describe('checkbox event', () => { 4 | describe('getEvent', () => { 5 | let events = null; 6 | beforeEach(() => { 7 | }); 8 | afterEach(() => { 9 | events = null; 10 | }); 11 | 12 | it('基础验证', () => { 13 | expect(getEvent).toBeDefined(); 14 | expect(getEvent.length).toBe(2); 15 | }); 16 | 17 | it('执行验证', () => { 18 | events = getEvent('test', '#baukh'); 19 | expect(events.allChange.events).toBe('click'); 20 | expect(events.allChange.target).toBe('#baukh'); 21 | expect(events.allChange.selector).toBe('th[gm-checkbox] .gm-checkbox-wrapper'); 22 | 23 | expect(events.checkboxChange.events).toBe('click'); 24 | expect(events.checkboxChange.target).toBe('#baukh'); 25 | expect(events.checkboxChange.selector).toBe('td[gm-checkbox] .gm-checkbox-wrapper'); 26 | 27 | expect(events.radioChange.events).toBe('click'); 28 | expect(events.radioChange.target).toBe('#baukh'); 29 | expect(events.radioChange.selector).toBe('td[gm-checkbox] .gm-radio-wrapper'); 30 | 31 | expect(events.trChange.events).toBe('click'); 32 | expect(events.trChange.target).toBe('#baukh'); 33 | expect(events.trChange.selector).toBe('tbody > tr[gm-cache-key]'); 34 | }); 35 | }); 36 | 37 | describe('eventMap', () => { 38 | it('基础验证', () => { 39 | expect(eventMap).toEqual({}); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/checkbox/tool_test.js: -------------------------------------------------------------------------------- 1 | import { resetData } from '../../src/module/checkbox/tool'; 2 | import store from '../../src/common/Store'; 3 | import getTableTestData from '../table-test.data.js'; 4 | import { getColumnMap } from '../table-config'; 5 | import {TR_CACHE_KEY} from '@common/constants'; 6 | 7 | const _ = 'test'; 8 | describe('checkbox tool', () => { 9 | describe('resetData', () => { 10 | let tableData = null; 11 | beforeEach(() => { 12 | tableData = getTableTestData().data; 13 | // 模拟gm-cache-key 14 | tableData.forEach((item, index) => { 15 | item[TR_CACHE_KEY] = `${index}`; 16 | }); 17 | store.checkedData = { 18 | [_]: [] 19 | }; 20 | store.responseData = { 21 | [_]: tableData 22 | }; 23 | store.settings = { 24 | [_]: { 25 | _, 26 | checkboxConfig: { 27 | useRowCheck: false, 28 | useRadio: false 29 | }, 30 | columnMap: getColumnMap() 31 | } 32 | }; 33 | }); 34 | afterEach(() => { 35 | tableData = null; 36 | store.responseData = {}; 37 | store.checkedData = {}; 38 | store.settings = {}; 39 | }); 40 | 41 | it('基础验证', () => { 42 | expect(resetData).toBeDefined(); 43 | expect(resetData.length).toBe(5); 44 | }); 45 | 46 | it('执行验证: 复选-全选', () => { 47 | expect(store.checkedData.test.length).toBe(0); 48 | 49 | // 全选 50 | resetData(_, true, true); 51 | expect(store.checkedData.test.length).toBe(10); 52 | 53 | // 取消全选 54 | resetData(_, false, true); 55 | expect(store.checkedData.test.length).toBe(0); 56 | }); 57 | 58 | it('执行验证: 复选-单个操作', () => { 59 | expect(store.checkedData.test.length).toBe(0); 60 | 61 | // 选中一项 62 | resetData(_, true, false, 3); 63 | expect(store.checkedData.test.length).toBe(1); 64 | 65 | // 再选中一项 66 | resetData(_, true, false, 4); 67 | expect(store.checkedData.test.length).toBe(2); 68 | 69 | // 再选中一项 70 | resetData(_, true, false, 5); 71 | expect(store.checkedData.test.length).toBe(3); 72 | 73 | // 取消一项 74 | resetData(_, false, false, 5); 75 | expect(store.checkedData.test.length).toBe(2); 76 | }); 77 | 78 | it('执行验证: 单选', () => { 79 | expect(store.checkedData.test.length).toBe(0); 80 | 81 | // 选中一项 82 | resetData(_, undefined, true, '3', true); 83 | expect(store.checkedData.test.length).toBe(1); 84 | 85 | // 再选中一项 86 | resetData(_, undefined, true, '4', true); 87 | expect(store.checkedData.test.length).toBe(1); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /test/common/Store_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by baukh on 17/10/24. 3 | */ 4 | import Store from '../../src/common/Store'; 5 | import pak from '../../package.json'; 6 | 7 | const version = pak.version; 8 | describe('Store.js', () => { 9 | it('Store.version', () => { 10 | expect(Store.version).toBe(version); 11 | }); 12 | 13 | it('Store.responseData', () => { 14 | expect(Store.responseData).toBeDefined(); 15 | }); 16 | 17 | it('Store.originalTh', () => { 18 | expect(Store.checkedData).toBeDefined(); 19 | }); 20 | 21 | it('Store.settings', () => { 22 | expect(Store.settings).toBeDefined(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/common/domCache_test.js: -------------------------------------------------------------------------------- 1 | import tableTpl from '../table-test.tpl.html'; 2 | import { getCacheDOM, clearCacheDOM } from '../../src/common/domCache'; 3 | import { TABLE_KEY } from '../../src/common/constants'; 4 | describe('domCache', () => { 5 | beforeEach(() => { 6 | clearCacheDOM('test'); 7 | document.body.innerHTML = tableTpl; 8 | }); 9 | 10 | it('基础验证', () => { 11 | expect(getCacheDOM).toBeDefined(); 12 | expect(getCacheDOM.length).toBe(3); 13 | 14 | expect(clearCacheDOM).toBeDefined(); 15 | expect(clearCacheDOM.length).toBe(1); 16 | }); 17 | 18 | it('执行验证', () => { 19 | expect(getCacheDOM('test', TABLE_KEY).length).toBe(1); 20 | 21 | expect(getCacheDOM('test', TABLE_KEY, `[${TABLE_KEY}="test"]`).length).toBe(1); 22 | 23 | // 触发缓存 24 | expect(getCacheDOM('test', TABLE_KEY).length).toBe(1); 25 | 26 | // 清除缓存 27 | clearCacheDOM('test'); 28 | expect(getCacheDOM('test', TABLE_KEY).length).toBe(1); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test/common/parse_test.js: -------------------------------------------------------------------------------- 1 | import { parseTpl } from '../../src/common/parse'; 2 | 3 | describe('parseTpl', () => { 4 | it('基础验证', () => { 5 | let str = '{{vm.tbodyHtml}}
      '; 6 | let trimStr = 'like kouzi
      '; 7 | let target = {}; 8 | let key = 'testDecorator'; 9 | let descriptor = { 10 | configurable: true, 11 | enumerable: false, 12 | writable: true, 13 | value: params => { 14 | return { 15 | tableClassName: params.name, 16 | tbodyHtml: params.html 17 | }; 18 | } 19 | }; 20 | let params = { 21 | name: 'kouzi', 22 | html: 'like kouzi' 23 | }; 24 | parseTpl(str)(target, key, descriptor); 25 | expect(descriptor.value(params)).toBe(trimStr); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /test/config/constants_test.js: -------------------------------------------------------------------------------- 1 | import { CLASS_NO_CLICK, CLASS_CONFIG_ING, CLASS_CONFIG } from '../../src/module/config/constants'; 2 | describe('constants', () => { 3 | it('CLASS_NO_CLICK', () => { 4 | expect(CLASS_NO_CLICK).toBe('no-click'); 5 | }); 6 | it('CLASS_CONFIG_ING', () => { 7 | expect(CLASS_CONFIG_ING).toBe('gm-config-ing'); 8 | }); 9 | it('CLASS_CONFIG', () => { 10 | expect(CLASS_CONFIG).toBe('gm-config-area'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test/config/event_test.js: -------------------------------------------------------------------------------- 1 | import { getEvent, eventMap } from '../../src/module/config/event'; 2 | import { CONFIG_KEY } from '../../src/common/constants'; 3 | 4 | describe('config', () => { 5 | describe('getEvent', () => { 6 | let events = null; 7 | beforeEach(() => { 8 | }); 9 | afterEach(() => { 10 | events = null; 11 | }); 12 | 13 | it('基础验证', () => { 14 | expect(getEvent).toBeDefined(); 15 | expect(getEvent.length).toBe(1); 16 | }); 17 | 18 | it('执行验证', () => { 19 | events = getEvent('test', '#baukh'); 20 | expect(events.closeConfig.events).toBe('click'); 21 | expect(events.closeConfig.target).toBe(`[${CONFIG_KEY}="test"]`); 22 | expect(events.closeConfig.selector).toBe('.config-action'); 23 | 24 | expect(events.liChange.events).toBe('click'); 25 | expect(events.liChange.target).toBe(`[${CONFIG_KEY}="test"]`); 26 | expect(events.liChange.selector).toBe('.config-list li'); 27 | 28 | expect(events.closeConfigByBody.events).toBe('mousedown.closeConfig'); 29 | expect(events.liChange.target).toBe(`[${CONFIG_KEY}="test"]`); 30 | expect(events.closeConfigByBody.selector).toBeUndefined(); 31 | }); 32 | }); 33 | 34 | describe('eventMap', () => { 35 | it('基础验证', () => { 36 | expect(eventMap).toEqual({}); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/core/event_test.js: -------------------------------------------------------------------------------- 1 | import { getEvent, eventMap } from '../../src/module/core/event'; 2 | 3 | describe('core event', () => { 4 | describe('getEvent', () => { 5 | let events = null; 6 | beforeEach(() => { 7 | }); 8 | afterEach(() => { 9 | events = null; 10 | }); 11 | 12 | it('基础验证', () => { 13 | expect(getEvent).toBeDefined(); 14 | expect(getEvent.length).toBe(1); 15 | }); 16 | 17 | it('rowHover', () => { 18 | events = getEvent('.test'); 19 | expect(events.rowHover.events).toBe('mousemove'); 20 | expect(events.rowHover.target).toBe('.test'); 21 | expect(events.rowHover.selector).toBe('tr[gm-cache-key]'); 22 | }); 23 | 24 | it('rowClick', () => { 25 | events = getEvent('.test'); 26 | expect(events.rowClick.events).toBe('click'); 27 | expect(events.rowClick.target).toBe('.test'); 28 | expect(events.rowClick.selector).toBe('tr[gm-cache-key]'); 29 | }); 30 | 31 | it('rowDblClick', () => { 32 | events = getEvent('.test'); 33 | expect(events.rowDblClick.events).toBe('dblclick'); 34 | expect(events.rowDblClick.target).toBe('.test'); 35 | expect(events.rowDblClick.selector).toBe('tr[gm-cache-key]'); 36 | }); 37 | 38 | it('cellHover', () => { 39 | events = getEvent('.test'); 40 | expect(events.cellHover.events).toBe('mousemove'); 41 | expect(events.cellHover.target).toBe('.test'); 42 | expect(events.cellHover.selector).toBe('tr[gm-cache-key] td'); 43 | }); 44 | 45 | it('cellClick', () => { 46 | events = getEvent('.test'); 47 | expect(events.cellClick.events).toBe('click'); 48 | expect(events.cellClick.target).toBe('.test'); 49 | expect(events.cellClick.selector).toBe('tr[gm-cache-key] td'); 50 | }); 51 | 52 | it('cellDblClick', () => { 53 | events = getEvent('.test'); 54 | expect(events.cellDblClick.events).toBe('dblclick'); 55 | expect(events.cellDblClick.target).toBe('.test'); 56 | expect(events.cellDblClick.selector).toBe('tr[gm-cache-key] td'); 57 | }); 58 | }); 59 | 60 | describe('eventMap', () => { 61 | it('基础验证', () => { 62 | expect(eventMap).toEqual({}); 63 | }); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /test/doc.md: -------------------------------------------------------------------------------- 1 | # 宽度调整功能 2 | - `disableCache:false` 3 | - 操作任意列宽度调整 4 | - 刷新后,调整后的宽度被记忆 5 | - 清除缓存后,恢复为初始状态 6 | 7 | - `disableCache:true` 8 | - 操作任意列宽度调整 9 | - 刷新后,恢复为初始状态 10 | - `disableCache:false` 11 | 12 | # 位置拖拽功能 13 | - `disableCache:false` 14 | - 对任意列进行位置拖拽 15 | - 刷新后,调整的位置被记忆 16 | - 清除缓存后,恢复为初始状态 17 | 18 | - `disableCache:true` 19 | - 对任意列进行位置拖拽 20 | - 刷新后,恢复为初始状态 21 | - `disableCache:false` 22 | 23 | # 显示隐藏功能 24 | - `disableCache:false` 25 | - 对任意两列进行隐藏 26 | - 对其中一列再进行显示 27 | - 刷新后,隐藏的那一列不显示 28 | - 清除缓存后,恢复为初始状态 29 | 30 | - `disableCache:true` 31 | - 对任意两列进行隐藏 32 | - 对其中一列再进行显示 33 | - 刷新后,恢复为初始状态 34 | - `disableCache:false` 35 | 36 | # 排序功能 37 | - `isCombSorting: false` 38 | - 默认排序为: 创建时间=降序 39 | - 点击最后修改时间排序按键,请求参数的排序字段为: 最后修改时间=降序 40 | - 再次点击最后修改时间排序按键,请求参数的排序字段为: 最后修改时间=升序 41 | - 刷新后,恢复为初始状态 42 | 43 | - `isCombSorting: true` 44 | - 默认排序为: 创建时间=降序 45 | - 点击最后修改时间排序按键,请求参数的排序字段为: [创建时间=降序, 最后修改时间=降序] 46 | - 再次点击最后修改时间排序按键,请求参数的排序字段为: [创建时间=降序, 最后修改时间=升序] 47 | - 刷新后,恢复为初始状态 48 | - `isCombSorting: false` 49 | 50 | # 选择功能 51 | - `useRadio: false, useRowCheck: false` 52 | - 不可通过行进行选中,仅可以通过选择框进行选中 53 | - 选择第1行后: 第1条为选中状态,全选框为半选状态,选中条数为1 54 | - 选择第3行后: 第1和第3条为选中状态,全选框为半选状态,选中条数数为1 55 | - 点击全选后: 当前页全部选中,全选框为选中状态,选中条数为当前页总条数. 56 | - 再次点击全选后: 当前页全部未选中,全选框为未选中状态,选中条数为0 57 | - 选择第1行后: 第1条为选中状态,全选框为半选状态,选中条数为1 58 | - 跳转至第二页后: 选中条数为1,但是当前页没有显中状态的行 59 | - 选择第1行后: 第1条为选中状态,全选框为半选状态,选中条数为2 60 | - 通过getCheckedTr查看当前页选中的tr 61 | - 通过getCheckedData查看当前两条数据正确 62 | - 刷新后,恢复为初始状态 63 | 64 | - `useRowCheck: true` 65 | - 可通过行进行选中 66 | 67 | - `useRadio: true, useRowCheck: false` 68 | - 不可通过行进行选中,仅可以通过选择框进行选中 69 | - 选择第1行后: 第1条为选中状态,选中条数为1 70 | - 选择第2行后: 第2条为选中状态,选中条数为1 71 | - 刷新后,恢复为初始状态 72 | 73 | - `useRowCheck: true` 74 | - 可通过行进行选中 75 | - `useRadio: false, useRowCheck: false` 76 | 77 | # 分页功能 78 | - `disableCache:false` 79 | - 页码跳转正常 80 | - 上一页下一页跳转正常 81 | - 切换显示条数,数据请求为切换后的条数 82 | - 刷新后: 显示为刚才配置过的条数 83 | 84 | - `disableCache:true` 85 | - 切换显示条数,数据请求为切换后的条数 86 | - 刷新后: 显示为初始条数 87 | - `disableCache:false` 88 | 89 | # 筛选功能 90 | 91 | # 右键功能 92 | - `supportMenu: true` 93 | - 在区域内点击右键,可正常打开 94 | - 右键功能可用 95 | - `supportMenu: false` 96 | - 在区域内点击右键,不能打开右键 97 | - `supportMenu: true` 98 | 99 | # 模板渲染及内部事件 100 | - 模板解析正常 101 | - 模板内事件可用 102 | 103 | # 公开方法 104 | - 公开方法全部可用 105 | 106 | # 静态数据 `demo2` 107 | - 数据可以展示完整 108 | - destroy 执行正确 109 | - init 执行正确 110 | - reset data 执行正确 111 | 112 | # 多表渲染 `demo3` 113 | - 各表格语言显示正确 114 | - 触发分页事件,对其它表无影响 115 | - 触发排序事件,对其它表无影响 116 | - 触发选择事件,对其它表无影响 117 | 118 | # 通栏功能 `demo4` 119 | - 通栏功能正常 120 | 121 | -------------------------------------------------------------------------------- /test/drag/constants_test.js: -------------------------------------------------------------------------------- 1 | import { CLASS_DRAG_ACTION, CLASS_DRAG_ING, CLASS_DREAMLAND } from '../../src/module/drag/constants'; 2 | describe('constants', () => { 3 | it('CLASS_DRAG_ACTION', () => { 4 | expect(CLASS_DRAG_ACTION).toBe('gm-drag-action'); 5 | }); 6 | it('CLASS_DRAG_ING', () => { 7 | expect(CLASS_DRAG_ING).toBe('gm-drag-ongoing'); 8 | }); 9 | it('CLASS_DREAMLAND', () => { 10 | expect(CLASS_DREAMLAND).toBe('gm-dreamland-div'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test/drag/event_test.js: -------------------------------------------------------------------------------- 1 | import { getEvent, eventMap } from '../../src/module/drag/event'; 2 | import { CLASS_DRAG_ACTION } from '../../src/module/drag/constants'; 3 | import { FAKE_TABLE_HEAD_KEY } from '../../src/common/constants'; 4 | 5 | describe('drag', () => { 6 | describe('getEvent', () => { 7 | let events = null; 8 | beforeEach(() => { 9 | }); 10 | afterEach(() => { 11 | events = null; 12 | }); 13 | 14 | it('基础验证', () => { 15 | expect(getEvent).toBeDefined(); 16 | expect(getEvent.length).toBe(2); 17 | }); 18 | 19 | it('执行验证', () => { 20 | events = getEvent('test', '#baukh'); 21 | expect(events.start.events).toBe('mousedown'); 22 | expect(events.start.target).toBe('#baukh'); 23 | expect(events.start.selector).toBe(`[${FAKE_TABLE_HEAD_KEY}="test"] .${CLASS_DRAG_ACTION}`); 24 | 25 | expect(events.doing.events).toBe('mousemove.gmDrag'); 26 | expect(events.doing.target).toBe('body'); 27 | expect(events.doing.selector).toBeUndefined(); 28 | 29 | expect(events.abort.events).toBe('mouseup.gmDrag'); 30 | expect(events.abort.target).toBe('body'); 31 | expect(events.abort.selector).toBeUndefined(); 32 | }); 33 | }); 34 | 35 | describe('eventMap', () => { 36 | it('基础验证', () => { 37 | expect(eventMap).toEqual({}); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/dropdown/event_test.js: -------------------------------------------------------------------------------- 1 | import { getEvent, eventMap } from '../../src/module/dropdown/event'; 2 | 3 | describe('dropdown', () => { 4 | describe('getEvent', () => { 5 | let events = null; 6 | beforeEach(() => { 7 | }); 8 | afterEach(() => { 9 | events = null; 10 | }); 11 | 12 | it('基础验证', () => { 13 | expect(getEvent).toBeDefined(); 14 | expect(getEvent.length).toBe(1); 15 | }); 16 | 17 | it('执行验证', () => { 18 | events = getEvent('.test'); 19 | expect(events.open.events).toBe('click'); 20 | expect(events.open.target).toBe('.test'); 21 | expect(events.open.selector).toBe('.gm-dropdown .gm-dropdown-text'); 22 | 23 | expect(events.close.events).toBe('click'); 24 | expect(events.close.target).toBe('body'); 25 | 26 | expect(events.selected.events).toBe('click'); 27 | expect(events.selected.target).toBe('.test'); 28 | expect(events.selected.selector).toBe('.gm-dropdown .gm-dropdown-list >li'); 29 | }); 30 | }); 31 | 32 | describe('eventMap', () => { 33 | it('基础验证', () => { 34 | expect(eventMap).toEqual({}); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/dropdown/index_test.js: -------------------------------------------------------------------------------- 1 | import dropdown from '../../src/module/dropdown'; 2 | 3 | describe('dropdown', () => { 4 | describe('createHtml', () => { 5 | let settings = null; 6 | let str = null; 7 | beforeEach(() => { 8 | }); 9 | afterEach(() => { 10 | settings = null; 11 | str = null; 12 | }); 13 | 14 | it('基础验证', () => { 15 | expect(dropdown.createHtml).toBeDefined(); 16 | expect(dropdown.createHtml.length).toBe(1); 17 | }); 18 | 19 | it('执行验证', () => { 20 | settings = { 21 | sizeData: [10, 20, 50, 100] 22 | }; 23 | str = ` 24 |
      25 | 26 | 27 |
        28 |
      • 10
      • 29 |
      • 20
      • 30 |
      • 50
      • 31 |
      • 100
      • 32 |
      33 |
      `; 34 | expect(dropdown.createHtml(settings).replace(/\s/g, '')).toBe(str.replace(/\s/g, '')); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/filter/constants_test.js: -------------------------------------------------------------------------------- 1 | import { CLASS_FILTER, CLASS_FILTER_SELECTED, CLASS_FILTER_CONTENT } from '../../src/module/filter/constants'; 2 | describe('CLASS_FILTER', () => { 3 | it('CLASS_FILTER', () => { 4 | expect(CLASS_FILTER).toBe('gm-filter-area'); 5 | }); 6 | it('CLASS_FILTER_SELECTED', () => { 7 | expect(CLASS_FILTER_SELECTED).toBe('filter-selected'); 8 | }); 9 | it('CLASS_FILTER_CONTENT', () => { 10 | expect(CLASS_FILTER_CONTENT).toBe('fa-con'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test/filter/event_test.js: -------------------------------------------------------------------------------- 1 | import { getEvent, eventMap } from '../../src/module/filter/event'; 2 | import { CLASS_FILTER } from '../../src/module/filter/constants'; 3 | import { FAKE_TABLE_HEAD_KEY } from '../../src/common/constants'; 4 | 5 | describe('filter', () => { 6 | describe('getEvent', () => { 7 | let events = null; 8 | beforeEach(() => { 9 | }); 10 | afterEach(() => { 11 | events = null; 12 | }); 13 | 14 | it('基础验证', () => { 15 | expect(getEvent).toBeDefined(); 16 | expect(getEvent.length).toBe(2); 17 | }); 18 | 19 | it('执行验证', () => { 20 | events = getEvent('test', '#baukh'); 21 | const filterSign = `[${FAKE_TABLE_HEAD_KEY}="test"] .${CLASS_FILTER}`; 22 | expect(events.toggle.events).toBe('mousedown'); 23 | expect(events.toggle.target).toBe('#baukh'); 24 | expect(events.toggle.selector).toBe(`${filterSign} .fa-icon`); 25 | 26 | expect(events.close.events).toBe('mousedown.closeFitler'); 27 | expect(events.close.target).toBe('body'); 28 | expect(events.close.selector).toBeUndefined(); 29 | 30 | expect(events.submit.events).toBe('mouseup'); 31 | expect(events.submit.target).toBe('#baukh'); 32 | expect(events.submit.selector).toBe(`${filterSign} .filter-submit`); 33 | 34 | expect(events.reset.events).toBe('mouseup'); 35 | expect(events.reset.target).toBe('#baukh'); 36 | expect(events.reset.selector).toBe(`${filterSign} .filter-reset`); 37 | 38 | expect(events.checkboxAction.events).toBe('click'); 39 | expect(events.checkboxAction.target).toBe('#baukh'); 40 | expect(events.checkboxAction.selector).toBe(`${filterSign} .gm-checkbox-input`); 41 | 42 | expect(events.radioAction.events).toBe('click'); 43 | expect(events.radioAction.target).toBe('#baukh'); 44 | expect(events.radioAction.selector).toBe(`${filterSign} .gm-radio-input`); 45 | }); 46 | }); 47 | 48 | describe('eventMap', () => { 49 | it('基础验证', () => { 50 | expect(eventMap).toEqual({}); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/fixed/event_test.js: -------------------------------------------------------------------------------- 1 | import { getEvent, eventMap } from '../../src/module/fixed/event'; 2 | 3 | describe('fixed', () => { 4 | describe('getEvent', () => { 5 | let events = null; 6 | beforeEach(() => { 7 | }); 8 | afterEach(() => { 9 | events = null; 10 | }); 11 | 12 | it('基础验证', () => { 13 | expect(getEvent).toBeDefined(); 14 | expect(getEvent.length).toBe(2); 15 | }); 16 | 17 | it('执行验证', () => { 18 | events = getEvent('test', '#baukh'); 19 | expect(events.fixedFocus.events).toBe('mousedown'); 20 | expect(events.fixedFocus.target).toBe('#baukh'); 21 | expect(events.fixedFocus.selector).toBe('td[fixed]'); 22 | }); 23 | }); 24 | 25 | describe('eventMap', () => { 26 | it('基础验证', () => { 27 | expect(eventMap).toEqual({}); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test/fixed/index_test.js: -------------------------------------------------------------------------------- 1 | import fixed from '../../src/module/fixed'; 2 | 3 | describe('fixed', () => { 4 | describe('destroy', () => { 5 | let settings = null; 6 | beforeEach(() => { 7 | }); 8 | afterEach(() => { 9 | settings = null; 10 | }); 11 | it('基础验证', () => { 12 | }); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/fullColumn/event_test.js: -------------------------------------------------------------------------------- 1 | import { getEvent, eventMap } from '../../src/module/fullColumn/event'; 2 | describe('fullColumn event', () => { 3 | describe('getEvent', () => { 4 | let events = null; 5 | beforeEach(() => { 6 | }); 7 | afterEach(() => { 8 | events = null; 9 | }); 10 | 11 | it('基础验证', () => { 12 | expect(getEvent).toBeDefined(); 13 | expect(getEvent.length).toBe(2); 14 | }); 15 | 16 | it('执行验证', () => { 17 | events = getEvent('.test', 'full-column-fold'); 18 | expect(events.fold.events).toBe('click'); 19 | expect(events.fold.target).toBe('.test'); 20 | expect(events.fold.selector).toBe('i[full-column-fold]'); 21 | }); 22 | }); 23 | 24 | describe('eventMap', () => { 25 | it('基础验证', () => { 26 | expect(eventMap).toEqual({}); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/i18n/I18n_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by baukh on 17/3/5. 3 | */ 4 | 'use strict'; 5 | import i18n from '../../src/module/i18n'; 6 | import { Settings } from '../../src/common/Settings'; 7 | import TextConfig from '../../src/module/i18n/config'; 8 | import { CONSOLE_STYLE, CONSOLE_WARN } from '../../src/common/constants'; 9 | 10 | /** 11 | * 实例化方法验证 12 | */ 13 | describe('i18n(settings, key, v1, v2, v3)', () => { 14 | let settings = null; 15 | beforeEach(() => { 16 | settings = new Settings(); 17 | settings.textConfig = new TextConfig(); 18 | 19 | // 存储console, 用于在测方式完成后原还console对象 20 | console._log = console.log; 21 | console.log = jasmine.createSpy('log'); 22 | }); 23 | 24 | afterEach(() => { 25 | settings = null; 26 | 27 | // 还原console 28 | console.log = console._log; 29 | }); 30 | it('基础验证', () => { 31 | expect(i18n).toBeDefined(); 32 | expect(i18n.length).toBe(5); 33 | }); 34 | 35 | it('返回值验证', () => { 36 | 37 | // 未指定{}内容的 38 | expect(i18n(settings, 'order-text')).toBe('序号'); 39 | 40 | // 指定1个{}内容的 41 | expect(i18n(settings, 'checked-info', 1)).toBe('已选 1 条'); 42 | 43 | // 指定2个{}内容的 44 | expect(i18n(settings, 'page-info', 1, 2)).toBe('此页显示 1-2 共条'); 45 | 46 | // 指定3个{}内容的 47 | expect(i18n(settings, 'page-info', 1, 2, 3)).toBe('此页显示 1-2 共3条'); 48 | 49 | // 指定1个{}内容的- 数组 50 | expect(i18n(settings, 'page-info', [1, 2, 3])).toBe('此页显示 1-2 共3条'); 51 | 52 | // 指定错误的, 并验证错误打印信息 53 | expect(i18n(settings, 'undefinedKey', [1, 2, 3])).toBe(''); 54 | expect(console.log).toHaveBeenCalledWith('%c GridManager Warn %c not find language matched to undefinedKey ', ...CONSOLE_STYLE[CONSOLE_WARN]); 55 | }); 56 | }); 57 | 58 | -------------------------------------------------------------------------------- /test/jTool/Animate_test.js: -------------------------------------------------------------------------------- 1 | import _Animate from '../../src/jTool/Animate'; 2 | import Sizzle from '../../src/jTool/Sizzle'; 3 | import { extend } from '../../src/jTool/utils'; 4 | describe('Animate', () => { 5 | 6 | let jTool = null; 7 | beforeEach(() => { 8 | document.body.innerHTML = ''; 9 | jTool = function (selector, context) { 10 | return new Sizzle(selector, context); 11 | }; 12 | document.body.innerHTML = '
      '; 13 | 14 | Sizzle.prototype = jTool.prototype = {}; 15 | 16 | jTool.extend = jTool.prototype.extend = extend; 17 | jTool.prototype.extend(_Animate); 18 | 19 | }); 20 | 21 | afterEach(() => { 22 | document.body.innerHTML = ''; 23 | jTool = null; 24 | }); 25 | 26 | it('animate', () => { 27 | let divEle = document.getElementById('div1'); 28 | jasmine.clock().install(); 29 | jTool('#div1').animate({height: '100px', width: '200px'}, 1000); 30 | jasmine.clock().tick(1000); 31 | expect(divEle.style.height).toBe('100px'); 32 | expect(divEle.style.width).toBe('200px'); 33 | jasmine.clock().uninstall(); 34 | divEle = null; 35 | }); 36 | 37 | it('animate回调函数', () => { 38 | let animateCallbackHandler = jasmine.createSpy('callback'); 39 | jasmine.clock().install(); 40 | jTool('#div1').animate({height: '100px', width: '200px'}, 1000, animateCallbackHandler); 41 | jasmine.clock().tick(1000); 42 | 43 | expect(animateCallbackHandler).toHaveBeenCalled(); 44 | animateCallbackHandler = null; 45 | jasmine.clock().uninstall(); 46 | }); 47 | 48 | it('show', () => { 49 | jTool('#div1').show(); 50 | expect(window.getComputedStyle(document.getElementById('div1')).display).toBe('block'); 51 | }); 52 | 53 | it('hide', () => { 54 | jTool('#div1').hide(); 55 | expect(window.getComputedStyle(document.getElementById('div1')).display).toBe('none'); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/jTool/Class_test.js: -------------------------------------------------------------------------------- 1 | import _Class from '../../src/jTool/Class'; 2 | import Sizzle from '../../src/jTool/Sizzle'; 3 | import { extend } from '../../src/jTool/utils'; 4 | describe('Class', () => { 5 | 6 | var divEle = null; 7 | var divEle2 = null; 8 | var jTool = null; 9 | 10 | beforeEach(() => { 11 | document.body.innerHTML = ''; 12 | jTool = function (selector, context) { 13 | return new Sizzle(selector, context); 14 | }; 15 | 16 | Sizzle.prototype = jTool.prototype = {}; 17 | 18 | jTool.extend = jTool.prototype.extend = extend; 19 | 20 | jTool.prototype.extend(_Class); 21 | 22 | 23 | divEle = document.createElement('div'); 24 | divEle.id = 'div1'; 25 | divEle.classList.add('class1'); 26 | document.body.appendChild(divEle); 27 | 28 | divEle2 = document.createElement('div'); 29 | divEle2.id = 'div2'; 30 | divEle2.classList.add('class21', 'class22'); 31 | document.body.appendChild(divEle2); 32 | }); 33 | 34 | afterEach(() => { 35 | document.body.innerHTML = ''; 36 | divEle = null; 37 | divEle2 = null; 38 | jTool = null; 39 | }); 40 | 41 | it('addClass', () => { 42 | jTool('#div1').addClass('class2'); 43 | expect(divEle.classList.contains('class2')).toBe(true); 44 | 45 | jTool('#div2').addClass('class23 class24'); 46 | expect(divEle2.classList.toString()).toEqual('class21 class22 class23 class24'); 47 | }); 48 | 49 | it('removeClass', () => { 50 | jTool('#div1').removeClass('class1'); 51 | expect(divEle.classList.contains('class1')).toBe(false); 52 | jTool('#div2').removeClass('class22 class24'); 53 | expect(divEle2.classList.toString()).toEqual('class21'); 54 | }); 55 | 56 | it('hasClass', () => { 57 | expect(jTool('#div1').hasClass('class1')).toBe(true); 58 | expect(jTool('#div2').hasClass('class24')).toBe(false); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/jTool/Css_test.js: -------------------------------------------------------------------------------- 1 | import _Css from '../../src/jTool/Css'; 2 | import Sizzle from '../../src/jTool/Sizzle'; 3 | import { extend } from '../../src/jTool/utils'; 4 | describe('Css', () => { 5 | let divEle = null; 6 | let divEle2 = null; 7 | let divEle3 = null; 8 | let jTool = null; 9 | 10 | beforeEach(() => { 11 | document.body.innerHTML = ''; 12 | jTool = function (selector, context) { 13 | return new Sizzle(selector, context); 14 | }; 15 | 16 | Sizzle.prototype = jTool.prototype = {}; 17 | 18 | jTool.extend = jTool.prototype.extend = extend; 19 | 20 | jTool.prototype.extend(_Css); 21 | 22 | 23 | divEle = document.createElement('div'); 24 | divEle.id = 'div1'; 25 | document.body.appendChild(divEle); 26 | 27 | divEle2 = document.createElement('div'); 28 | divEle2.id = 'div2'; 29 | document.body.appendChild(divEle2); 30 | 31 | divEle3 = document.createElement('div'); 32 | divEle3.id = 'div3'; 33 | divEle3.style.left = '60px'; 34 | divEle3.style.top = '80px'; 35 | divEle3.style.position = 'relative'; 36 | divEle2.appendChild(divEle3); 37 | 38 | }); 39 | 40 | afterEach(() => { 41 | document.body.innerHTML = ''; 42 | divEle = null; 43 | divEle2 = null; 44 | divEle3 = null; 45 | jTool = null; 46 | }); 47 | 48 | it('获取 css', () => { 49 | divEle.style.height = '300px'; 50 | expect(jTool('#div1').css('height')).toBe(300); 51 | 52 | divEle.style.color = '#444444'; 53 | expect(jTool('#div1').css('color')).toBe('rgb(68, 68, 68)'); 54 | 55 | divEle.style.fontStyle = 'italic'; 56 | 57 | expect(jTool('#div1').css('font-style')).toBe('italic'); 58 | 59 | divEle.style.border = '1px solid #fff'; 60 | expect(jTool('#div1').css('border')).toBe('1px solid rgb(255, 255, 255)'); 61 | }); 62 | 63 | it('设置 css', () => { 64 | jTool('#div1').css('height', 0); 65 | expect(divEle.style.height).toBe('0px'); 66 | 67 | jTool('#div1').css('height', 12); 68 | expect(divEle.style.height).toBe('12px'); 69 | 70 | 71 | jTool('#div1').css('height', '16px'); 72 | expect(divEle.style.height).toBe('16px'); 73 | 74 | 75 | jTool('#div1').css('color', '#444444'); 76 | expect(divEle.style.color).toBe('rgb(68, 68, 68)'); 77 | 78 | jTool('#div1').css('font-style', 'italic'); 79 | expect(divEle.style.fontStyle).toBe('italic'); 80 | 81 | jTool('#div1').css('border', '1px solid #fff'); 82 | expect(divEle.style.border).toBe('1px solid rgb(255, 255, 255)'); 83 | }); 84 | 85 | it('设置 css 值是对象', () => { 86 | jTool('#div2').css({height: 0, width: '100px'}); 87 | expect(divEle2.style.height).toBe('0px'); 88 | expect(divEle2.style.width).toBe('100px'); 89 | 90 | jTool('#div3').css({color: '#fff', 'font-size': '18px'}); 91 | expect(divEle3.style.color).toBe('rgb(255, 255, 255)'); 92 | expect(divEle3.style.fontSize).toBe('18px'); 93 | }); 94 | 95 | it('设置 css 为无效值', () => { 96 | jTool('#div2').css({height: undefined, width: null}); 97 | expect(divEle2.style.height).toBe(''); 98 | expect(divEle2.style.width).toBe(''); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /test/jTool/Data_test.js: -------------------------------------------------------------------------------- 1 | import _Data from '../../src/jTool/Data'; 2 | import Sizzle from '../../src/jTool/Sizzle'; 3 | import { extend } from '../../src/jTool/utils'; 4 | 5 | describe('Data', () => { 6 | 7 | let divEle = null; 8 | let jTool = null; 9 | 10 | beforeEach(() => { 11 | document.body.innerHTML = ''; 12 | jTool = function (selector, context) { 13 | return new Sizzle(selector, context); 14 | }; 15 | 16 | Sizzle.prototype = jTool.prototype = {}; 17 | 18 | jTool.extend = jTool.prototype.extend = extend; 19 | jTool.prototype.extend(_Data); 20 | 21 | divEle = document.createElement('div'); 22 | divEle.id = 'div1'; 23 | document.body.appendChild(divEle); 24 | }); 25 | 26 | afterEach(() => { 27 | document.body.innerHTML = ''; 28 | divEle = null; 29 | jTool = null; 30 | }); 31 | 32 | it('attr', () => { 33 | expect(jTool('#div1').attr()).toBeUndefined(); 34 | 35 | expect(jTool('#div1').attr('jtool')).toBeUndefined(); 36 | 37 | jTool('#div1').attr('jtool', 'baukh'); 38 | expect(jTool('#div1').attr('jtool')).toBe('baukh'); 39 | 40 | jTool('#div1').removeAttr('jtool'); 41 | expect(jTool('#div1').attr('jtool')).toBeUndefined(); 42 | }); 43 | 44 | it('prop', () => { 45 | expect(jTool('#div1').prop('class')).toBeUndefined(); 46 | jTool('#div1').prop('class', 'baukh'); 47 | expect(jTool('#div1').prop('class')).toBe('baukh'); 48 | // jTool('#div1').removeProp('class'); 49 | // expect(jTool('#div1').prop('class')).toBe(undefined); 50 | }); 51 | 52 | it('val', () => { 53 | jTool('#div1').val('baukh'); 54 | expect(jTool('#div1').val()).toBe('baukh'); 55 | jTool('#div1').val(''); 56 | expect(jTool('#div1').val()).toBe(''); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/jTool/Element_test.js: -------------------------------------------------------------------------------- 1 | import _Element from '../../src/jTool/Element'; 2 | import Sizzle from '../../src/jTool/Sizzle'; 3 | import { extend } from '../../src/jTool/utils'; 4 | 5 | describe('Element', () => { 6 | let divEle = null; 7 | let divEle2 = null; 8 | let divEle3 = null; 9 | let pEle1 = null; 10 | let jTool = null; 11 | 12 | beforeEach(() => { 13 | document.body.innerHTML = ''; 14 | jTool = function (selector, context) { 15 | return new Sizzle(selector, context); 16 | }; 17 | 18 | Sizzle.prototype = jTool.prototype = {}; 19 | 20 | jTool.extend = jTool.prototype.extend = extend; 21 | 22 | jTool.prototype.extend(_Element); 23 | 24 | 25 | divEle = document.createElement('div'); 26 | divEle.id = 'div1'; 27 | document.body.appendChild(divEle); 28 | 29 | divEle2 = document.createElement('div'); 30 | divEle2.id = 'div2'; 31 | document.body.appendChild(divEle2); 32 | 33 | divEle3 = document.createElement('div'); 34 | divEle3.id = 'div3'; 35 | document.body.appendChild(divEle3); 36 | 37 | pEle1 = document.createElement('p'); 38 | pEle1.id = 'p1'; 39 | pEle1.innerHTML = ''; 40 | document.body.appendChild(pEle1); 41 | }); 42 | 43 | afterEach(() => { 44 | document.body.innerHTML = ''; 45 | divEle = null; 46 | divEle2 = null; 47 | divEle3 = null; 48 | jTool = null; 49 | }); 50 | 51 | it('get', () => { 52 | expect(jTool('div').get(0).id).toBe('div1'); 53 | expect(jTool('div').get(1).id).toBe('div2'); 54 | expect(jTool('div').get(3)).toBe(undefined); 55 | }); 56 | 57 | it('eq', () => { 58 | expect(jTool('div').eq(0).DOMList[0].id).toBe('div1'); 59 | expect(jTool('div').eq(1).DOMList[0].id).toBe('div2'); 60 | expect(jTool('div').eq(3).DOMList).toBe(undefined); 61 | }); 62 | 63 | it('find', () => { 64 | expect(jTool('body').find('#div1').DOMList[0].id).toBe('div1'); 65 | expect(jTool('body').find('#div2').DOMList[0].id).toBe('div2'); 66 | expect(jTool('body').find('#div11').DOMList).toBe(undefined); 67 | }); 68 | 69 | it('index', () => { 70 | expect(jTool('#p1 .span1').index()).toBe(0); 71 | expect(jTool('#p1 .span2').index()).toBe(1); 72 | expect(jTool('#p1 .span1').index(jTool('#p1 span'))).toBe(0); 73 | expect(jTool('#p1 .span3').index(jTool('#p1 span'))).toBe(2); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/menu/event_test.js: -------------------------------------------------------------------------------- 1 | import { getEvent, eventMap } from '../../src/module/menu/event'; 2 | import { WRAP_KEY } from '../../src/common/constants'; 3 | 4 | describe('menu event', () => { 5 | describe('getEvent', () => { 6 | let events = null; 7 | beforeEach(() => { 8 | }); 9 | afterEach(() => { 10 | events = null; 11 | }); 12 | 13 | it('基础验证', () => { 14 | expect(getEvent).toBeDefined(); 15 | expect(getEvent.length).toBe(1); 16 | }); 17 | 18 | it('执行验证', () => { 19 | events = getEvent('test'); 20 | expect(events.openMenu.events).toBe('contextmenu'); 21 | expect(events.openMenu.target).toBe(`[${WRAP_KEY}="test"]`); 22 | 23 | expect(events.closeMenu.events).toBe('mousedown.closeMenu'); 24 | expect(events.closeMenu.target).toBe('body'); 25 | }); 26 | }); 27 | 28 | describe('eventMap', () => { 29 | it('基础验证', () => { 30 | expect(eventMap).toEqual({}); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/merge/constants_test.js: -------------------------------------------------------------------------------- 1 | import { ROW_SPAN, MERGE_TD } from '../../src/module/merge/constants'; 2 | describe('constants', () => { 3 | it('ROW_SPAN', () => { 4 | expect(ROW_SPAN).toBe('rowspan'); 5 | }); 6 | it('MERGE_TD', () => { 7 | expect(MERGE_TD).toBe('merge-td'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /test/moveRow/constants_test.js: -------------------------------------------------------------------------------- 1 | import { CLASS_DRAG_ING, CLASS_DREAMLAND, DISABLE_MOVE } from '../../src/module/moveRow/constants'; 2 | describe('constants', () => { 3 | it('CLASS_DRAG_ING', () => { 4 | expect(CLASS_DRAG_ING).toBe('gm-move-row-ongoing'); 5 | }); 6 | it('CLASS_DREAMLAND', () => { 7 | expect(CLASS_DREAMLAND).toBe('dreamland-row-div'); 8 | }); 9 | it('DISABLE_MOVE', () => { 10 | expect(DISABLE_MOVE).toBe('disable-move'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test/moveRow/event_test.js: -------------------------------------------------------------------------------- 1 | import { getEvent, eventMap } from '../../src/module/moveRow/event'; 2 | import { EMPTY_TPL_KEY } from '../../src/common/constants'; 3 | 4 | describe('drag', () => { 5 | describe('getEvent', () => { 6 | let events = null; 7 | beforeEach(() => { 8 | }); 9 | afterEach(() => { 10 | events = null; 11 | }); 12 | 13 | it('基础验证', () => { 14 | expect(getEvent).toBeDefined(); 15 | expect(getEvent.length).toBe(1); 16 | }); 17 | 18 | it('执行验证', () => { 19 | events = getEvent('test'); 20 | expect(events.start.events).toBe('mousedown.gmLineDrag'); 21 | expect(events.start.target).toBe('test'); 22 | expect(events.start.selector).toBe(`tr:not([${EMPTY_TPL_KEY}])`); 23 | 24 | expect(events.doing.events).toBe('mousemove.gmLineDrag'); 25 | expect(events.doing.target).toBe('body'); 26 | expect(events.doing.selector).toBeUndefined(); 27 | 28 | expect(events.abort.events).toBe('mouseup.gmLineDrag'); 29 | expect(events.abort.target).toBe('body'); 30 | expect(events.abort.selector).toBeUndefined(); 31 | }); 32 | }); 33 | 34 | describe('eventMap', () => { 35 | it('基础验证', () => { 36 | expect(eventMap).toEqual({}); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/order/index_test.js: -------------------------------------------------------------------------------- 1 | import order from '../../src/module/order'; 2 | import { ORDER_KEY, GM_CREATE } from '../../src/common/constants'; 3 | import { Settings } from '../../src/common/Settings'; 4 | import TextConfig from '../../src/module/i18n/config'; 5 | 6 | describe('order', () => { 7 | let settings = null; 8 | beforeEach(() => { 9 | settings = new Settings(); 10 | settings.textConfig = new TextConfig(); 11 | }); 12 | 13 | afterEach(() => { 14 | settings = null; 15 | }); 16 | describe('getColumn', () => { 17 | it('执行验证', () => { 18 | let column = order.getColumn(settings); 19 | expect(column.key).toBe(ORDER_KEY); 20 | expect(column.text).toBe('序号'); 21 | expect(column.isAutoCreate).toBe(true); 22 | expect(column.isShow).toBe(true); 23 | expect(column.disableCustomize).toBe(true); 24 | expect(column.width).toBe(50); 25 | expect(column.fixed).toBe(undefined); 26 | expect(column.template()).toBe(``); 27 | expect(column.template(1, {}, 1, true)).toBe(`1`); 28 | column = null; 29 | 30 | // 验证不同的语言 31 | settings.i18n = 'zh-cn'; 32 | expect(order.getColumn(settings).text).toBe('序号'); 33 | settings.i18n = 'zh-tw'; 34 | expect(order.getColumn(settings).text).toBe('序號'); 35 | settings.i18n = 'en-us'; 36 | expect(order.getColumn(settings).text).toBe('order'); 37 | }); 38 | }); 39 | 40 | }); 41 | -------------------------------------------------------------------------------- /test/remind/event_test.js: -------------------------------------------------------------------------------- 1 | import { getEvent, eventMap } from '../../src/module/remind/event'; 2 | import { REMIND_CLASS, FAKE_TABLE_HEAD_KEY } from '../../src/common/constants'; 3 | 4 | describe('remind', () => { 5 | describe('getEvent', () => { 6 | let events = null; 7 | beforeEach(() => { 8 | }); 9 | afterEach(() => { 10 | events = null; 11 | }); 12 | 13 | it('基础验证', () => { 14 | expect(getEvent).toBeDefined(); 15 | expect(getEvent.length).toBe(2); 16 | }); 17 | 18 | it('执行验证', () => { 19 | events = getEvent('test', '#baukh'); 20 | expect(events.start.events).toBe('mouseover'); 21 | expect(events.start.target).toBe('#baukh'); 22 | expect(events.start.selector).toBe(`[${FAKE_TABLE_HEAD_KEY}="test"] .${REMIND_CLASS}`); 23 | }); 24 | }); 25 | 26 | describe('eventMap', () => { 27 | it('基础验证', () => { 28 | expect(eventMap).toEqual({}); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/rowVisible/index_test.js: -------------------------------------------------------------------------------- 1 | import jTool from '@jTool'; 2 | import { showRow, hideRow } from '../../src/module/rowVisible'; 3 | import {TABLE_BODY_KEY, TABLE_HEAD_KEY, TR_CACHE_KEY, ROW_HIDE_KEY} from '@common/constants'; 4 | 5 | // 获取指定类型的DOM长度 6 | const getTrLen = type => { 7 | return jTool(`[${TABLE_BODY_KEY}="test"] tr[${ROW_HIDE_KEY}="${type}"]`).length; 8 | }; 9 | describe('rowVisible', () => { 10 | let $tr = null; 11 | let settings = null; 12 | beforeEach(() => { 13 | document.body.innerHTML = ` 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
      usernamecreateDatelastDate
      张三2019-11-112019-12-11
      李四2019-11-112019-12-12
      王五2019-11-122019-12-11
      赵六2019-11-132019-12-11
      统计共4人
      26 | `; 27 | $tr = jTool(`[${TABLE_BODY_KEY}="test"] tr`).eq(2); 28 | settings = { 29 | _: 'test', 30 | columnMap: { 31 | username: { 32 | key: 'username', 33 | text: '作者' 34 | }, 35 | createDate: { 36 | key: 'createDate', 37 | text: '创建时间' 38 | }, 39 | lastDate: { 40 | key: 'lastDate', 41 | text: '最后修改时间' 42 | } 43 | } 44 | }; 45 | }); 46 | 47 | afterEach(() => { 48 | document.body.innerHTML = ''; 49 | $tr = null; 50 | settings = null; 51 | }); 52 | 53 | it('执行前验证', () => { 54 | expect(getTrLen('out')).toBe(0); 55 | expect(getTrLen('ing')).toBe(0); 56 | expect(getTrLen('true')).toBe(0); 57 | }); 58 | it('执行hideRow后验证', done => { 59 | hideRow(settings, 2); 60 | expect(getTrLen('out')).toBe(0); 61 | expect(getTrLen('ing')).toBe(1); 62 | expect(getTrLen('true')).toBe(0); 63 | 64 | setTimeout(() => { 65 | expect(getTrLen('out')).toBe(0); 66 | expect(getTrLen('ing')).toBe(0); 67 | expect(getTrLen('true')).toBe(1); 68 | done(); 69 | }, 500); 70 | }); 71 | it('执行showRow后验证', done => { 72 | $tr.attr('ROW_HIDE_KEY', true); 73 | showRow(settings, 2); 74 | expect(getTrLen('out')).toBe(1); 75 | expect(getTrLen('ing')).toBe(0); 76 | expect(getTrLen('true')).toBe(0); 77 | 78 | setTimeout(() => { 79 | expect(getTrLen('out')).toBe(0); 80 | expect(getTrLen('ing')).toBe(0); 81 | expect(getTrLen('true')).toBe(0); 82 | done(); 83 | }, 500); 84 | }); 85 | 86 | }); 87 | -------------------------------------------------------------------------------- /test/sort/event_test.js: -------------------------------------------------------------------------------- 1 | import { getEvent, eventMap } from '../../src/module/sort/event'; 2 | import { SORT_CLASS, FAKE_TABLE_HEAD_KEY } from '../../src/common/constants'; 3 | 4 | describe('sort', () => { 5 | describe('getEvent', () => { 6 | let events = null; 7 | beforeEach(() => { 8 | }); 9 | afterEach(() => { 10 | events = null; 11 | }); 12 | 13 | it('基础验证', () => { 14 | expect(getEvent).toBeDefined(); 15 | expect(getEvent.length).toBe(2); 16 | }); 17 | 18 | it('执行验证', () => { 19 | events = getEvent('test', '#baukh'); 20 | expect(events.start.events).toBe('click'); 21 | expect(events.start.target).toBe('#baukh'); 22 | expect(events.start.selector).toBe(`[${FAKE_TABLE_HEAD_KEY}="test"] .${SORT_CLASS}`); 23 | }); 24 | }); 25 | 26 | describe('eventMap', () => { 27 | it('基础验证', () => { 28 | expect(eventMap).toEqual({}); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/sort/index_test.js: -------------------------------------------------------------------------------- 1 | import sort from '../../src/module/sort'; 2 | 3 | describe('sort', () => { 4 | describe('createHtml', () => { 5 | let htmlStr = null; 6 | beforeEach(() => { 7 | }); 8 | afterEach(() => { 9 | htmlStr = null; 10 | }); 11 | 12 | it('执行验证: 无排序', () => { 13 | htmlStr = `
      14 | 15 | 16 |
      `.replace(/\s/g, ''); 17 | expect(sort.createHtml({type: '', sortUpText: 'ASC', sortDownText: 'DESC'}).replace(/\s/g, '')).toBe(htmlStr); 18 | }); 19 | it('执行验证: 向上排序', () => { 20 | htmlStr = `
      21 | 22 | 23 |
      `.replace(/\s/g, ''); 24 | expect(sort.createHtml({type: 'ASC', sortUpText: 'ASC', sortDownText: 'DESC'}).replace(/\s/g, '')).toBe(htmlStr); 25 | }); 26 | it('执行验证: 向下排序', () => { 27 | htmlStr = `
      28 | 29 | 30 |
      `.replace(/\s/g, ''); 31 | expect(sort.createHtml({type: 'DESC', sortUpText: 'ASC', sortDownText: 'DESC'}).replace(/\s/g, '')).toBe(htmlStr); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/tree/event_test.js: -------------------------------------------------------------------------------- 1 | import { getEvent, eventMap } from '../../src/module/tree/event'; 2 | describe('tree event', () => { 3 | describe('getEvent', () => { 4 | let events = null; 5 | beforeEach(() => { 6 | }); 7 | afterEach(() => { 8 | events = null; 9 | }); 10 | 11 | it('基础验证', () => { 12 | expect(getEvent).toBeDefined(); 13 | expect(getEvent.length).toBe(2); 14 | }); 15 | 16 | it('执行验证', () => { 17 | events = getEvent('.test', 'tree-element'); 18 | expect(events.toggle.events).toBe('click'); 19 | expect(events.toggle.target).toBe('.test'); 20 | expect(events.toggle.selector).toBe('[tree-element] i'); 21 | }); 22 | }); 23 | 24 | describe('eventMap', () => { 25 | it('基础验证', () => { 26 | expect(eventMap).toEqual({}); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/tree/tool_test.js: -------------------------------------------------------------------------------- 1 | import { treeElementKey, getTreeCache, addTreeCache, clearTreeCache, getIconClass } from '../../src/module/tree/tool'; 2 | 3 | describe('tree tool', () => { 4 | describe('treeElementKey', () => { 5 | it('基础验证', () => { 6 | expect(treeElementKey).toBe('tree-element'); 7 | }); 8 | }); 9 | 10 | describe('treeCache', () => { 11 | let _ = null; 12 | beforeEach(() => { 13 | _ = 'test'; 14 | }); 15 | afterEach(() => { 16 | _ = null; 17 | }); 18 | 19 | it('基础验证', () => { 20 | expect(getTreeCache).toBeDefined(); 21 | expect(getTreeCache.length).toBe(1); 22 | 23 | expect(addTreeCache).toBeDefined(); 24 | expect(addTreeCache.length).toBe(2); 25 | 26 | expect(clearTreeCache).toBeDefined(); 27 | expect(clearTreeCache.length).toBe(1); 28 | }); 29 | 30 | it('执行验证', () => { 31 | expect(getTreeCache(_)).toBeUndefined(); 32 | 33 | addTreeCache(_, { trNode: 1, level: 2, hasChildren: 3 }); 34 | expect(getTreeCache(_)).toEqual([{ trNode: 1, level: 2, hasChildren: 3 }]); 35 | 36 | addTreeCache(_, { trNode: 1, level: 2, hasChildren: 3 }); 37 | expect(getTreeCache(_)).toEqual([{ trNode: 1, level: 2, hasChildren: 3 }, { trNode: 1, level: 2, hasChildren: 3 }]); 38 | 39 | clearTreeCache(_); 40 | expect(getTreeCache(_)).toBeUndefined(); 41 | }); 42 | }); 43 | 44 | describe('getIconClass', () => { 45 | it('基础验证', () => { 46 | expect(getIconClass).toBeDefined(); 47 | expect(getIconClass.length).toBe(1); 48 | }); 49 | it('执行验证', () => { 50 | expect(getIconClass(true)).toBe('gm-icon-sub'); 51 | expect(getIconClass(false)).toBe('gm-icon-add'); 52 | }); 53 | }); 54 | 55 | }); 56 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // API: https://www.tslang.cn/docs/handbook/compiler-options.html 3 | "compilerOptions": { 4 | "experimentalDecorators": true, // 启用实验性的ES装饰器。 5 | "suppressImplicitAnyIndexErrors": true, // 阻止 --noImplicitAny对缺少索引签名的索引对象报错 6 | "noImplicitAny": true, // 在表达式和声明上有隐含的 any类型时报错。 7 | "noEmitOnError": true, // 发生错误时不输出文件 8 | "outDir": "./dist/", // 重定向输出目录。 9 | "target": "ES2017", // 指定 ECMAScript 的目标版本: 10 | "module": "ES2015", // 指定模块代码的生成方式 11 | "allowJs": true, // 允许编译javascript文件。 12 | "moduleResolution": "node", // 决定如何处理模块。 13 | "sourceMap": true, // 生成相应的 .map文件。 14 | "baseUrl": "./", 15 | "paths": { 16 | "@*": ["./src/*", "./typings/*"] 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /typings/arg.ts: -------------------------------------------------------------------------------- 1 | // 渲染参数 列 2 | export interface ArgColumn { 3 | key: string; 4 | text?: any; // string | function 5 | index?: number; 6 | width?: number | string; 7 | isShow?: boolean; 8 | children?: Array; 9 | template?(cell: object, row: object, rowIndex: number, key: string | boolean): any; // 自动生成列没有key, 只有isTop 10 | align?: string; 11 | fixed?: string; 12 | merge?: string; 13 | filter?: { 14 | option: Array<{ 15 | value: string; 16 | text: string; 17 | }>; 18 | selected: string; 19 | isMultiple: boolean 20 | }; 21 | disableMoveRow?: boolean; 22 | remind?: string | object; 23 | sorting?: string; 24 | } 25 | 26 | export interface ArgObj{ 27 | gridManagerName: string; 28 | columnData: Array; 29 | [index:string]: any; 30 | } 31 | -------------------------------------------------------------------------------- /typings/data.ts: -------------------------------------------------------------------------------- 1 | // 分页 2 | export interface PageData { 3 | tPage: number; 4 | tSize: number; 5 | cPage?: number; // 动态取值[currentPageKey] 6 | pSize?: number; // 动态取值[pageSizeKey] 7 | // 动态取值 8 | [key: string]: number; 9 | } 10 | 11 | // 排序 12 | export interface SortData { 13 | // 动态取值 14 | [key: string]: number | string; 15 | } 16 | // 行数据 17 | export interface Row { 18 | 'gm-cache-key'?: string; 19 | 'gm-level-key'?: number; 20 | 'gm-row-index'?: number; 21 | gm_checkbox?: boolean; 22 | gm_checkbox_disabled?: boolean; 23 | gm_order?: number; 24 | [key:string]: any; 25 | } 26 | -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.html'; 2 | -------------------------------------------------------------------------------- /typings/settings.ts: -------------------------------------------------------------------------------- 1 | // 过滤: 与arg的格式相同 2 | export interface FilterObject{ 3 | option: Array<{ 4 | value: string; 5 | text: string; 6 | }>; 7 | selected: string; 8 | isMultiple: boolean 9 | } 10 | interface ThTemplate { 11 | (): string; 12 | } 13 | // 表格实列 列 14 | export interface Column { 15 | key?: string; 16 | text?: string | ThTemplate; // 自动创建的列不会转换为函数 17 | index?: number; 18 | __index?: number; 19 | width?: number; 20 | __width?: number | undefined; 21 | isShow?: boolean; 22 | __isShow?: boolean; 23 | pk?: string; 24 | children?: Array; 25 | template?(cell: object, row: object, rowIndex: number, key: string | boolean): any; // 自动生成列没有key, 只有isTop 26 | isAutoCreate?: boolean; 27 | align?: string; 28 | fixed?: string; 29 | merge?: string; 30 | filter?: FilterObject; 31 | disableMoveRow?: boolean; 32 | level?: number; 33 | rowspan?: number; 34 | colspan?: number; 35 | remind?: string | object; 36 | sorting?: string; 37 | disableRowCheck?: boolean; 38 | pl?: number; // 仅在fixed中使用 39 | pr?: number; // 仅在fixed中使用 40 | } 41 | 42 | // 表格实例 列集 43 | export interface ColumnMap { 44 | [index:string]: Column 45 | } 46 | 47 | export interface SettingObj { 48 | _?: string; 49 | gridManagerName: string; 50 | columnMap: ColumnMap; 51 | columnData: Array; 52 | [index:string]: any; 53 | } 54 | -------------------------------------------------------------------------------- /typings/types.ts: -------------------------------------------------------------------------------- 1 | import { ArgColumn, ArgObj } from './arg'; 2 | import { Column, ColumnMap, SettingObj, FilterObject } from './settings'; 3 | import { Row, PageData, SortData } from './data'; 4 | 5 | export { ArgColumn, ArgObj, Column, ColumnMap, SettingObj, Row, PageData, SortData, FilterObject }; 6 | 7 | // 生成过程中的tr对像存储器 8 | export interface TrObject { 9 | className: Array; 10 | attribute: Array>; 11 | querySelector: string; 12 | row?: Row; 13 | tdList: Array; 14 | } 15 | 16 | // 配置区域模板参数 17 | export interface ConfigHtmlParams { 18 | _: string; 19 | configInfo: object; 20 | } 21 | 22 | // 列模板参数 23 | export interface ColHtmlParams { 24 | key: string; 25 | isShow: boolean; 26 | label: string; 27 | } 28 | 29 | // 移动行配置 30 | export interface MoveRowConfig { 31 | key: string; 32 | useSingleMode: boolean; 33 | fixed?: string; 34 | handler?(list: Array, tableData: Array): void; 35 | } 36 | 37 | // jtool对象 38 | export interface JTool { 39 | jTool: boolean; 40 | [index:string]: any; 41 | } 42 | 43 | // 事件存储器(单个事件) 44 | export interface EventObj { 45 | events: string; 46 | target: string; 47 | selector: string; 48 | } 49 | // 事件存储器(事件集) 50 | export interface EventMap { 51 | [index:string]: EventObj 52 | } 53 | 54 | // td模板函数 55 | export interface TdTemplate { 56 | (col: Column, row: Row, index: number, key: string): string; 57 | } 58 | 59 | // th模板函数 60 | export interface ThTemplate { 61 | (): string; 62 | } 63 | 64 | // 通栏模板函数 65 | export interface FullColumnTemplate { 66 | (row: Row, index: number): string; 67 | } 68 | 69 | // 为空模板函数 70 | export interface EmptyTemplate { 71 | (settings: SettingObj): string; 72 | } 73 | 74 | // diff data 75 | export interface DiffData { 76 | diffList: Array; 77 | diffFirst: Row; 78 | diffLast: Row; 79 | } 80 | -------------------------------------------------------------------------------- /version/v3.x.md: -------------------------------------------------------------------------------- 1 | # 待开发 2 | - demo1需要支持编辑功能 3 | - 考虑添加插件机制 4 | - 行移动时需要考虑下图片重新加载的问题,尝试下 5 | - 考虑使用previousElementSibling 替换位置拖拽中的向前查找 6 | - 支持甘特图 7 | - 宽度需支持百分比 8 | - 汇总行以两行模式展示 9 | - .target .getAttribute 等原生方法考虑进行封装 10 | - 增加实例后的表格宽度、位置调整, updateCol 11 | - 宽度需要考虑的事项: 所调宽度增加时,需要同时增加总宽;宽度减小时,需要同时减小总宽或增大其它列的宽 12 | - todo 位置更换需要有col增加链表特性 13 | - jTool需要考虑取消链式操作 14 | - getRowData与getTableData: 在不会修改的情况下,应该尽量使用非克隆的数据; 在存在修改的情况下,使用源数据直接修改,而不用再次存储 15 | - 解决flex布局中宽度无限长的问题 16 | ``` 17 |
      18 |
      11
      19 |
      20 |
      21 |
      22 |
      23 | ``` 24 | 原因是内部的Y滚轴宽度,在计算过程中一直会增加导致的 25 | 在flex布局的情况下,renderTable会触发ResizeObserver 26 | 尝试找一下父容器是否为flex布局,能否指定容器不会被表格撑大 27 | - iconfont字体调研下ttf类型 28 | - fullColumn 折叠功能增加指定折叠按纽所在的位置,可以考虑与移动行按纽相似 29 | - columnData考虑使用链表,以便于提升数据查找性能: 存在隐藏列时,index应该占有,并提供函数查找下一个可视的元素 30 | - 将皮肤进行自带 31 | - 分页区域支持框架语法 32 | - import Vue from 'vue/dist/vue.js'; // todo 这里不应该用这个,需要手动去解决 template 向render替换, 目前该文件内有两处 33 | - max-height仅在height为100%时生效,且表格所在容器需声明height 34 | - filter支持自定义 35 | ajaxPage模板改为render(初步调试通了,需要将模块改动为fun),config和右键也可以这么改。可以在初次渲染的时候,把各个功能块的壳用原生JS生成,其它的内部由框架或原生自行生成 36 | - 增加表格轮播功能,参考: https://www.jowostudio.com/docs/component/Table/CarouselTable/ 37 | - 右键 在表头区时增加: 删除列、隐藏列功能 38 | - gridManagerName 更名为key 39 | - 右键菜单中增加: 删除列、隐藏列功能0 40 | - 数据撑不满时,汇总行需要调整 0 41 | - 隐藏行需要有相应的显示行功能 0 42 | - 嵌套表头和列隐藏功能一起使用数据会显示错位(github issues) 43 | - 增加全屏功能 44 | - 存在多个fixed的场景下,使用showTH后,fixed所在th不能正确计算(在设计时使用fixed的列会被定义为固定列,固定列是不会允许隐藏、显示的,config中对其也进行了排除,只是showTh,hideTh没有处理) 45 | - getColTd需要验证是否仅传入的是th,而没有td,如果是的话可以进行改写,通过td-name=key进行获取 46 | 47 | # v3.2.4 48 | - 新增 49 | - 导出功能新增参数exportConfig.disableLoading: 禁止在导出时使用loading动画, 当配置项disableAutoLoading为true时,disableLoading不再生效 50 | 51 | # v3.2.1 52 | - 新增 53 | - 存在一列是fixed='left'时,自动生成列自动添加fixed 54 | - 生成的td是增加td-name属性 55 | # v3.2.0 56 | - 新增 57 | - 表格轮播功能 58 | - 修复 59 | - 虚拟滚动开启状态下tooltip定位问题 60 | 61 | # v3.1.1 62 | - 优化 63 | - 移除汇总行功能对低版本safari的兼容 64 | 65 | # v3.1.0 66 | - 新增 67 | - 单元格双击事件cellDblClick 68 | - 行双击事件rowDblClick 69 | 70 | # v3.0.6 71 | - 修复 72 | - 树型结构差异化更新时的显示BUG 73 | 74 | # v3.0.3 75 | - 新增 76 | - 通栏功能支持配置表头文本、宽度、文本方向、表头提醒 77 | - 修复 78 | - 折叠功能在数据更新后,状态未能重置问题 79 | # v3.0.2 80 | - 优化 81 | - 右键菜单的事件扩大到整个tbody区域 82 | 83 | # v3.0.1 84 | - 优化 85 | - 在React版本中,对样式加载缓慢引起的表头错位进行容错 86 | 87 | # v3.0.0 88 | - 新增 89 | - 支持修改columnData 90 | - renderGrid(gridManagerName, columnData), 增加第二个参数[columnData],通过该参数可以修改表格的列信息 91 | - 修复 92 | - 虚拟滚动条件下,行移动结束动画异常问题 93 | - ES6 引入方式变更 94 | - css: import 'gridmanager/index.css'; 95 | - js: import GridManager from 'gridmanager'; 96 | - react: import GridManager from 'gridmanager/react' 97 | - vue2: import GridManager from 'gridmanager/vue2' 98 | - angular-1.x: import GridManager from 'gridmanager/angular-1.x' 99 | -------------------------------------------------------------------------------- /webpack-dev-config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 4 | const getRules = require('./webpack-common.loader'); 5 | const { version } = require('./package.json'); 6 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 7 | // const HtmlWebpackPlugin = require('html-webpack-plugin'); 8 | 9 | const resolve = dir => path.resolve(__dirname, dir); 10 | const devServer = { 11 | clientLogLevel: 'info', 12 | disableHostCheck: true, 13 | port: '2015', 14 | host: '0.0.0.0', 15 | hot: false, 16 | contentBase: [ 17 | path.join(__dirname, './src'), 18 | path.join(__dirname, './coverage') 19 | ], 20 | compress: true, 21 | overlay: { 22 | warnings: false, 23 | errors: true 24 | } 25 | }; 26 | 27 | console.info('[GridManager] Demo is running at: http://localhost:2015/demo/index.html'); 28 | console.info('[GridManager React] Demo is running at: http://localhost:2015/framework/react/demo/index.html'); 29 | console.info('[GridManager Vue 2] Demo is running at: http://localhost:2015/framework/vue/demo/index.html'); 30 | console.info('[GridManager angular-1.x] Demo is running at: http://localhost:2015/framework/angular-1.x/demo/index.html'); 31 | console.info('[GridManager] Coverage is running at: http://localhost:2015/chart'); 32 | const config = { 33 | mode: 'development', 34 | // map 35 | 36 | devtool: 'eval-cheap-source-map', 37 | 38 | devServer, 39 | 40 | // 入口文件配置 41 | context: path.join(__dirname, 'src/'), 42 | 43 | // 入口文件配置 44 | entry: { 45 | index: './module/index.js', 46 | 'angular-1.x': './framework/angular-1.x/demo/index.js', 47 | 'react': './framework/react/demo/index.js', 48 | 'vue2': './framework/vue/demo/index.js' // 目前将vue2定为默认的版本,后续增加vue3后需要更名为gm-vue2 49 | }, 50 | // externals: { 51 | // 'angular': 'angular', 52 | // 'react': 'React', 53 | // 'react-dom': 'ReactDOM', 54 | // 'vue': { 55 | // root: 'Vue', 56 | // commonjs: 'vue', 57 | // commonjs2: 'vue', 58 | // amd: 'vue' 59 | // } 60 | // }, 61 | // 配置模块如何解析 62 | resolve: { 63 | extensions: ['.js', '.ts'], // 当requrie的模块找不到时,添加这些后缀 64 | alias: { 65 | 'vue$': 'vue/dist/vue.esm.js', 66 | '@common': resolve('src/common'), 67 | '@jTool': resolve('src/jTool'), 68 | '@module': resolve('src/module') 69 | } 70 | }, 71 | 72 | // 文件导出的配置 73 | output: { 74 | path: '/', 75 | filename: '[name].js', 76 | // publicPath 对于热替换(HMR)是必须的,让webpack知道在哪里载入热更新的模块(chunk) 77 | publicPath: '/' 78 | }, 79 | 80 | // 以插件形式定制webpack构建过程 81 | plugins: [ 82 | // 将样式文件 抽取至独立文件内 83 | new MiniCssExtractPlugin({ 84 | filename: '[name].css', 85 | chunkFilename: '[id].css' 86 | }), 87 | 88 | 89 | // 配置环境变量 90 | new webpack.DefinePlugin({ 91 | 'process.env': { 92 | VERSION: JSON.stringify(version) 93 | } 94 | }), 95 | 96 | // 使用交互式可缩放树形图可视化webpack输出文件的大小 97 | // https://www.npmjs.com/package/webpack-bundle-analyzer 98 | new BundleAnalyzerPlugin({ 99 | // 是否启动后打开窗口 100 | openAnalyzer: false 101 | }) 102 | ], 103 | 104 | // 处理项目中的不同类型的模块 105 | module: { 106 | rules: getRules() 107 | } 108 | }; 109 | 110 | module.exports = config; 111 | -------------------------------------------------------------------------------- /webpack-loaders/html-clear-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 清除tmp.html文件中的空格、换行符、注释 3 | * 清除以 > 开头或以 < 结尾的匹配空字符,如: {{vm.thListTpl}} 4 | * 标签内的属性空字符不清除,如: 5 | * 清除标签间的空格,要清除的原因是jTool会将这个空格当作一个text node插入dom,从而导致在使用jTool时出错。如index()方法。 6 | * @param source 7 | * @returns {string} 8 | */ 9 | module.exports = function (source) { 10 | return source.trim() 11 | // 清除注释 12 | .replace(//g, '') 13 | 14 | // 清除换行符 15 | .replace(/\\n/g, '') 16 | 17 | // 清除多余空字符 18 | .replace(/(\S)(\s)+(\S)/g, (match, p1, p2, p3) => { 19 | // 清除以 > 开头或以 < 结尾的匹配空字符,如: {{vm.thListTpl}} 20 | if (p1 === '>' || p3 === '<') { 21 | return p1 + p3; 22 | } 23 | 24 | // 标签内的属性空字符不清除,如: 25 | return p1 + p2 + p3; 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /webpack-loaders/karma-log-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 在测试文件导入后,打印导入成功信息 3 | * @param source 4 | * @returns {string} 5 | */ 6 | module.exports = function (source) { 7 | const importLog = `console.info('test file import success: ${this.resourcePath}');\n`; 8 | return importLog + source; 9 | }; 10 | --------------------------------------------------------------------------------