From 3fcb054d2833bca71bab3854c315382060f27b02 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Mon, 11 Dec 2023 14:55:42 +0800 Subject: [PATCH 01/35] feat: add option showGrandTotalsOnTop #650 --- packages/vtable/src/dataset/dataset.ts | 27 ++++++++++++++------ packages/vtable/src/ts-types/new-data-set.ts | 25 +++++++++++++----- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/packages/vtable/src/dataset/dataset.ts b/packages/vtable/src/dataset/dataset.ts index 501ba160c..83d13a28e 100644 --- a/packages/vtable/src/dataset/dataset.ts +++ b/packages/vtable/src/dataset/dataset.ts @@ -234,7 +234,7 @@ export class Dataset { this.rowKeys, this.rows, this.indicatorsAsCol ? undefined : this.indicators, - this?.totals?.row?.showGrandTotals || + this.totals?.row?.showGrandTotals || (!this.indicatorsAsCol && this.columns.length === 0) || (this.indicatorsAsCol && this.rows.length === 0), this.rowGrandTotalLabel @@ -245,9 +245,11 @@ export class Dataset { this.rows, this.indicatorsAsCol ? undefined : this.indicators, this.rowsIsTotal, - this?.totals?.row?.showGrandTotals || (this.indicatorsAsCol && this.rows.length === 0), + this.totals?.row?.showGrandTotals || (this.indicatorsAsCol && this.rows.length === 0), this.rowGrandTotalLabel, - this.rowSubTotalLabel + this.rowSubTotalLabel, + this.totals?.row?.showGrandTotalsOnTop ?? false, + this.totals?.row?.showSubTotalsOnTop ?? false ); } } @@ -264,7 +266,9 @@ export class Dataset { this.colsIsTotal, this.totals?.column?.showGrandTotals || (!this.indicatorsAsCol && this.columns.length === 0), // || this.rows.length === 0,//todo 这里原有逻辑暂时注释掉 this.colGrandTotalLabel, - this.colSubTotalLabel + this.colSubTotalLabel, + this.totals?.column?.showGrandTotalsOnLeft ?? false, + this.totals?.column?.showSubTotalsOnLeft ?? false ); } const t8 = typeof window !== 'undefined' ? window.performance.now() : 0; @@ -1270,7 +1274,9 @@ export class Dataset { subTotalFlags: boolean[], isGrandTotal: boolean, grandTotalLabel: string, - subTotalLabel: string + subTotalLabel: string, + showGrandTotalsOnTop: boolean, + showSubTotalsOnTop: boolean ) { /** * @@ -1335,14 +1341,16 @@ export class Dataset { }) : [] }; + curChild.push(totalChild); + curChild = totalChild.children; } } map.set(flatKey, item); // 存储路径对应的节点 if (node) { //为了确保汇总小计放到最后 使用splice插入到倒数第二个位置。如果小计放前面 直接push就行 - if (subTotalFlags[index - 1]) { + if (subTotalFlags[index - 1] && !showSubTotalsOnTop) { node.children.splice(node.children.length - 1, 0, item); } else { node.children.push(item); @@ -1381,8 +1389,11 @@ export class Dataset { }; }) ?? [] }; - - result.push(node); + if (showGrandTotalsOnTop) { + result.unshift(node); + } else { + result.push(node); + } } return result; } diff --git a/packages/vtable/src/ts-types/new-data-set.ts b/packages/vtable/src/ts-types/new-data-set.ts index 9e87a6435..7a0d03ae7 100644 --- a/packages/vtable/src/ts-types/new-data-set.ts +++ b/packages/vtable/src/ts-types/new-data-set.ts @@ -26,25 +26,36 @@ export interface CalcTotals { } export interface Total { - // 是否显示总计 + /** 是否显示总计; */ showGrandTotals: boolean; - // 是否显示小计 + /** 是否显示小计; */ showSubTotals: boolean; + // // 计算总计方法 // calcGrandTotals?: CalcTotals; // // 计算小计方法 // calcSubTotals?: CalcTotals; - // 小计汇总维度定义 + /** 小计汇总维度定义 */ subTotalsDimensions?: string[]; - // 默认'总计' + /** 汇总节点显示名称 默认'总计' */ grandTotalLabel?: string; - // 默认'小计' + /** 汇总节点显示名称 默认'小计' */ subTotalLabel?: string; } export interface Totals { - row?: Total; - column?: Total; + row?: Total & { + /** 总计显示在上 默认false */ + showGrandTotalsOnTop?: boolean; + /** 小计显示在上 默认false */ + showSubTotalsOnTop?: boolean; + }; + column?: Total & { + /** 总计显示在左 默认false */ + showGrandTotalsOnLeft?: boolean; + /** 小计显示在左 默认false */ + showSubTotalsOnLeft?: boolean; + }; } //#endregion 总计小计 From 08c3becad9545c5f5c4c885a11ac987eb4d39c87 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Mon, 11 Dec 2023 14:56:44 +0800 Subject: [PATCH 02/35] docs: update changlog of rush --- .../650-feature-total-position_2023-12-11-06-56.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/650-feature-total-position_2023-12-11-06-56.json diff --git a/common/changes/@visactor/vtable/650-feature-total-position_2023-12-11-06-56.json b/common/changes/@visactor/vtable/650-feature-total-position_2023-12-11-06-56.json new file mode 100644 index 000000000..269e5f3cf --- /dev/null +++ b/common/changes/@visactor/vtable/650-feature-total-position_2023-12-11-06-56.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "feat: add option showGrandTotalsOnTop #650\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From 642e687213eff4b38e1f82efcbd1b73975012b35 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Mon, 11 Dec 2023 16:33:42 +0800 Subject: [PATCH 03/35] feat: add csv export tool --- common/config/rush/pnpm-lock.yaml | 117 ++++++++- packages/vtable-export/.eslintrc.js | 274 ++++++++++++++++++++ packages/vtable-export/README.md | 93 +++++++ packages/vtable-export/bundler.config.js | 17 ++ packages/vtable-export/demo/README.md | 21 ++ packages/vtable-export/demo/index.html | 33 +++ packages/vtable-export/demo/list/list.ts | 109 ++++++++ packages/vtable-export/demo/main.ts | 156 +++++++++++ packages/vtable-export/demo/menu.ts | 11 + packages/vtable-export/demo/style.css | 106 ++++++++ packages/vtable-export/demo/vite.config.js | 33 +++ packages/vtable-export/package.json | 86 ++++++ packages/vtable-export/setup-mock.js | 2 + packages/vtable-export/src/csv/index.ts | 47 ++++ packages/vtable-export/src/index.ts | 2 + packages/vtable-export/src/util/download.ts | 9 + packages/vtable-export/tscofig.eslint.json | 20 ++ packages/vtable-export/tsconfig.json | 24 ++ rush.json | 9 + 19 files changed, 1159 insertions(+), 10 deletions(-) create mode 100644 packages/vtable-export/.eslintrc.js create mode 100644 packages/vtable-export/README.md create mode 100644 packages/vtable-export/bundler.config.js create mode 100644 packages/vtable-export/demo/README.md create mode 100644 packages/vtable-export/demo/index.html create mode 100644 packages/vtable-export/demo/list/list.ts create mode 100644 packages/vtable-export/demo/main.ts create mode 100644 packages/vtable-export/demo/menu.ts create mode 100644 packages/vtable-export/demo/style.css create mode 100644 packages/vtable-export/demo/vite.config.js create mode 100644 packages/vtable-export/package.json create mode 100644 packages/vtable-export/setup-mock.js create mode 100644 packages/vtable-export/src/csv/index.ts create mode 100644 packages/vtable-export/src/index.ts create mode 100644 packages/vtable-export/src/util/download.ts create mode 100644 packages/vtable-export/tscofig.eslint.json create mode 100644 packages/vtable-export/tsconfig.json diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index c96adda09..a8254532b 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -328,6 +328,103 @@ importers: typescript: 4.9.5 vite: 3.2.6_@types+node@20.10.0 + ../../packages/vtable-export: + specifiers: + '@babel/core': 7.20.12 + '@babel/preset-env': 7.20.2 + '@internal/bundler': workspace:* + '@internal/eslint-config': workspace:* + '@internal/ts-config': workspace:* + '@rushstack/eslint-patch': ~1.1.4 + '@types/chai': 4.2.22 + '@types/jest': ^26.0.0 + '@types/mocha': 9.0.0 + '@types/node': '*' + '@types/offscreencanvas': 2019.6.4 + '@types/react': ^18.0.0 + '@types/react-dom': ^18.0.0 + '@types/react-is': ^17.0.3 + '@visactor/vchart': 1.7.3 + '@visactor/vtable': workspace:* + '@visactor/vutils': ~0.16.10 + '@vitejs/plugin-react': 3.1.0 + axios: ^1.4.0 + chai: 4.3.4 + eslint: ~8.18.0 + file-saver: 2.0.5 + form-data: ~4.0.0 + inversify: 6.0.1 + jest: ^26.0.0 + jest-electron: ^0.1.12 + jest-transform-stub: ^2.0.0 + json-formatter-js: ^2.3.4 + magic-string: ^0.25.7 + markdown-it: ^13.0.0 + mocha: 9.1.3 + node-fetch: 2.6.7 + postcss: 8.4.21 + react: ^18.0.0 + react-dom: ^18.0.0 + rimraf: 3.0.2 + sass: 1.43.5 + ts-jest: ^26.0.0 + ts-loader: 9.2.6 + ts-node: 10.9.0 + tslib: 2.3.1 + ttypescript: 1.5.13 + typescript: 4.9.5 + typescript-transform-paths: 3.3.1 + vite: 3.2.6 + vite-plugin-markdown: ^2.1.0 + dependencies: + '@visactor/vtable': link:../vtable + '@visactor/vutils': 0.16.18 + file-saver: 2.0.5 + devDependencies: + '@babel/core': 7.20.12 + '@babel/preset-env': 7.20.2_@babel+core@7.20.12 + '@internal/bundler': link:../../tools/bundler + '@internal/eslint-config': link:../../share/eslint-config + '@internal/ts-config': link:../../share/ts-config + '@rushstack/eslint-patch': 1.1.4 + '@types/chai': 4.2.22 + '@types/jest': 26.0.24 + '@types/mocha': 9.0.0 + '@types/node': 20.10.0 + '@types/offscreencanvas': 2019.6.4 + '@types/react': 18.2.38 + '@types/react-dom': 18.2.17 + '@types/react-is': 17.0.7 + '@visactor/vchart': 1.7.3 + '@vitejs/plugin-react': 3.1.0_vite@3.2.6 + axios: 1.6.2 + chai: 4.3.4 + eslint: 8.18.0 + form-data: 4.0.0 + inversify: 6.0.1 + jest: 26.6.3_ts-node@10.9.0 + jest-electron: 0.1.12_jest@26.6.3 + jest-transform-stub: 2.0.0 + json-formatter-js: 2.3.4 + magic-string: 0.25.9 + markdown-it: 13.0.2 + mocha: 9.1.3 + node-fetch: 2.6.7 + postcss: 8.4.21 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + rimraf: 3.0.2 + sass: 1.43.5 + ts-jest: 26.5.6_xuote2qreek47x2di7kesslrai + ts-loader: 9.2.6_typescript@4.9.5 + ts-node: 10.9.0_4trkn2ujmhr5pmot7ghg7dhyji + tslib: 2.3.1 + ttypescript: 1.5.13_fxi2xlggroal5l3a4znftvxz2m + typescript: 4.9.5 + typescript-transform-paths: 3.3.1_typescript@4.9.5 + vite: 3.2.6_okf62dhpj5izt2am6hjhv3ud7u + vite-plugin-markdown: 2.1.0_vite@3.2.6 + ../../share/eslint-config: specifiers: '@typescript-eslint/eslint-plugin': 5.30.0 @@ -3309,7 +3406,7 @@ packages: '@visactor/vdataset': 0.16.18 '@visactor/vgrammar-coordinate': 0.9.4 '@visactor/vgrammar-util': 0.9.4 - '@visactor/vrender-components': 0.16.17 + '@visactor/vrender-components': 0.16.20 '@visactor/vrender-core': 0.16.20 '@visactor/vrender-kits': 0.16.20 '@visactor/vscale': 0.16.18 @@ -3365,14 +3462,6 @@ packages: '@visactor/vrender-kits': 0.16.20 '@visactor/vutils': 0.16.18 - /@visactor/vrender-components/0.16.17: - resolution: {integrity: sha512-D7brCgTblbY87kf8DK5OpQADkD51Ez7by7OxH6fMi5+b4NxBYzOCSeEKkKpiF/mWGUWHWSIHO6y38Hn5As5JZA==} - dependencies: - '@visactor/vrender-core': 0.16.17 - '@visactor/vrender-kits': 0.16.17 - '@visactor/vscale': 0.16.18 - '@visactor/vutils': 0.16.18 - /@visactor/vrender-components/0.16.20: resolution: {integrity: sha512-kAiYwoyzahhO32OlkI1J1lGRaJX4sOkaob6H2+sUKO+7Qpj+76iegekrYcVGkR4AjUScLWg/Lv6PxWFSbbjz7Q==} dependencies: @@ -3386,6 +3475,7 @@ packages: dependencies: '@visactor/vutils': 0.16.18 color-convert: 2.0.1 + dev: false /@visactor/vrender-core/0.16.20: resolution: {integrity: sha512-eRNz4l8BniXg0MvnwFOLc+lnYWC+8h6GDZgjIDw7K7QR8hIm2uY/PHBAzM/Yk5jHv3aGQ0RC3v33NlFaaAS+wg==} @@ -3400,6 +3490,7 @@ packages: '@visactor/vrender-core': 0.16.17 '@visactor/vutils': 0.16.18 roughjs: 4.5.2 + dev: false /@visactor/vrender-kits/0.16.20: resolution: {integrity: sha512-XqB7RsrqUxWg9SuyzKWoDRrQZRWVeUPm0Id/w6MsICZqRnZHllVPNezE71FT7trrQ8edpY8nWr5CJaSWyw1ylA==} @@ -3536,6 +3627,7 @@ packages: /abab/2.0.6: resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} + deprecated: Use your platform's native atob() and btoa() methods instead dev: true /abs-svg-path/0.1.1: @@ -5351,6 +5443,7 @@ packages: /domexception/2.0.1: resolution: {integrity: sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==} engines: {node: '>=8'} + deprecated: Use your platform's native DOMException instead dependencies: webidl-conversions: 5.0.0 dev: true @@ -6270,6 +6363,10 @@ packages: dependencies: flat-cache: 3.2.0 + /file-saver/2.0.5: + resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==} + dev: false + /file-source/0.6.1: resolution: {integrity: sha512-1R1KneL7eTXmXfKxC10V/9NeGOdbsAXJ+lQ//fvvcHUgtaZcZDWNJNblxAoVOyV1cj45pOtUrR3vZTBwqcW8XA==} dependencies: @@ -6687,7 +6784,7 @@ packages: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.0.4 + minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 dev: true diff --git a/packages/vtable-export/.eslintrc.js b/packages/vtable-export/.eslintrc.js new file mode 100644 index 000000000..f916cebe0 --- /dev/null +++ b/packages/vtable-export/.eslintrc.js @@ -0,0 +1,274 @@ +/* eslint-disable no-undef */ +require('@rushstack/eslint-patch/modern-module-resolution'); + +module.exports = { + extends: ['@internal/eslint-config/profile/lib'], + parserOptions: { + tsconfigRootDir: __dirname, + project: './tscofig.eslint.json' + }, + env: { + browser: true, + es2021: true, + node: true, + jest: true + }, + globals: { + __DEV__: 'readonly', + __VERSION__: 'readonly', + NodeJS: true + }, + overrides: [ + { + files: ['**/__tests__/**', '**/*.test.ts'], + // 测试文件允许以下规则 + rules: { + '@typescript-eslint/no-empty-function': 'off', + 'no-console': 'off', + 'dot-notation': 'off' + } + } + ], + rules: { + 'prettier/prettier': ['warn'], + // 强制使用 Unix 换行符: \n + 'linebreak-style': ['error', 'unix'], + // 强制换行时操作符在行首 + // 与prettier冲突 + // "operator-linebreak": ["error", "before", { "overrides": { "=": "after" } }], + // 允许给能自动推断出类型的primitive类型变量额外添加类型声明 + '@typescript-eslint/no-inferrable-types': 'off', + // 在类型导入时推荐import type写法 + '@typescript-eslint/consistent-type-imports': 'warn', + // 禁止出现空接口定义 + '@typescript-eslint/no-empty-interface': 'error', + // 禁止出现空函数 + '@typescript-eslint/no-empty-function': 'error', + '@typescript-eslint/no-this-alias': 'off', + // 禁止使用namespace + '@typescript-eslint/no-namespace': 'error', + // 禁止使用for-in Array + '@typescript-eslint/no-for-in-array': 'error', + // 禁止在optional chain语句后加非空断言 + '@typescript-eslint/no-non-null-asserted-optional-chain': 'error', + // 接口定义中使用函数属性而不是对象方法声明 + '@typescript-eslint/method-signature-style': 'error', + // 默认省略除属性以外的public修饰符 + '@typescript-eslint/explicit-member-accessibility': [ + 'warn', + { + overrides: { + accessors: 'off', + constructors: 'no-public', + methods: 'no-public', + properties: 'no-public', + parameterProperties: 'explicit' + } + } + ], + 'no-console': [ + 1, // 开发期间先关闭 + { + // allow: ["warn", "error"] + allow: ['warn', 'error'] + } + ], + // 如果一个变量不会被重新赋值,最好使用const进行声明 + 'prefer-const': 2, + // 禁止在条件中使用常量表达式 + 'no-constant-condition': 0, + 'no-debugger': 2, + // 禁止对象字面量中出现重复的 key + 'no-dupe-keys': 2, + // 禁止在正则表达式中使用空字符集 + 'no-empty-character-class': 2, + // 禁止对 catch 子句的参数重新赋值 + 'no-ex-assign': 2, + 'no-extra-boolean-cast': 0, + // 禁止对 function 声明重新赋值 + 'no-func-assign': 2, + // 禁止在嵌套的块中出现变量声明或 function 声明 + 'no-inner-declarations': 2, + // 禁止 RegExp 构造函数中存在无效的正则表达式字符串 + 'no-invalid-regexp': 2, + // 禁止对关系运算符的左操作数使用否定操作符 + 'no-unsafe-negation': 2, + // 禁止把全局对象作为函数调用 + 'no-obj-calls': 2, + // 禁用稀疏数组 + 'no-sparse-arrays': 2, + // 禁止在 return、throw、continue 和 break 语句之后出现不可达代码 + 'no-unreachable': 2, + // 要求使用 isNaN() 检查 NaN + 'use-isnan': 2, + // 强制 typeof 表达式与有效的字符串进行比较 + 'valid-typeof': 2, + // 要求使用 === 和 !==,除了与 null 字面量进行比较时 + eqeqeq: [ + 'error', + 'always', + { + null: 'ignore' + } + ], + // 允许 if 语句中 return 语句之后有 else 块 + 'no-else-return': 1, + // 禁用标签语句 + 'no-labels': [ + 2, + { + // 忽略循环语句中的标签 + allowLoop: true + } + ], + // 禁用 eval() + 'no-eval': 2, + // 禁止扩展原生类型 + 'no-extend-native': 2, + // 禁止不必要的 .bind() 调用 + 'no-extra-bind': 0, + // 禁止使用类似 eval() 的方法 + 'no-implied-eval': 2, + // 禁用 __iterator__ 属性 + 'no-iterator': 2, + // 禁止不规则的空白 + 'no-irregular-whitespace': 2, + // 禁用不必要的嵌套块 + 'no-lone-blocks': 2, + // 禁止循环中存在函数 + 'no-loop-func': 2, + // 禁止多行字符串 + 'no-multi-str': 2, + // 禁止对原生对象或只读的全局对象进行赋值 + 'no-global-assign': 2, + // 禁止对 String,Number 和 Boolean 使用 new 操作符 + 'no-new-wrappers': 2, + // 禁用八进制字面量 + 'no-octal': 2, + // 禁止在字符串中使用八进制转义序列 + 'no-octal-escape': 2, + // 禁用 __proto__ 属性 + 'no-proto': 2, + // 禁止自身比较 + 'no-self-compare': 2, + // 禁止可以在有更简单的可替代的表达式时使用三元操作符 + 'no-unneeded-ternary': 2, + // 禁用 with 语句 + 'no-with': 2, + // 强制在 parseInt() 使用基数参数 + radix: 2, + // 要求 IIFE 使用括号括起来 + 'wrap-iife': [2, 'any'], + // 禁止删除变量 + 'no-delete-var': 2, + // 禁止 function 定义中出现重名参数 + 'no-dupe-args': 2, + // 禁止出现重复的 case 标签 + 'no-duplicate-case': 2, + // 不允许标签与变量同名 + 'no-label-var': 2, + // 禁止将标识符定义为受限的名字 + 'no-shadow-restricted-names': 2, + // 禁用未声明的变量,除非它们在 /*global */ 注释中被提到 + 'no-undef': 2, + // 禁止将变量初始化为 undefined + 'no-undef-init': 2, + // 允许在变量定义之前使用它们 + 'no-use-before-define': 'off', + '@typescript-eslint/no-use-before-define': 0, + // 强制或禁止调用无参构造函数时有圆括号 + 'new-parens': 2, + // 禁用 Array 构造函数 + 'no-array-constructor': 2, + // 禁用 Object 的构造函数 + 'no-new-object': 2, + // 禁止不必要的括号 + 'no-extra-parens': [2, 'functions'], + // 禁止使用 空格 和 tab 混合缩进 + 'no-mixed-spaces-and-tabs': 2, + // 强制函数中的变量在分开声明 + 'one-var': [2, 'never'], + // 建议回调函数最大嵌套深度不超过5 + 'max-nested-callbacks': [1, 5], + // 建议可嵌套的块的最大深度不超过6 + 'max-depth': [1, 6], + // 强制一行的最大长度不超过120,不包括注释和url + 'max-len': [ + 'error', + { + code: 120, + ignoreUrls: true, + ignoreComments: true + } + ], + // 建议函数定义中最多允许的参数数量不超过15个 + 'max-params': [1, 15], + // 强制非一元操作符周围有空格 + 'space-infix-ops': 2, + // 强制尽可能地使用点号 + 'dot-notation': [ + 2, + { + // 避免对是保留字的属性使用点号 + allowKeywords: true, + allowPattern: '^catch$' + } + ], + // 强制箭头函数的箭头前后使用一致的空格 + 'arrow-spacing': 2, + // 要求在构造函数中有 super() 的调用 + 'constructor-super': 2, + // 禁止在可能与比较操作符相混淆的地方使用箭头函数 + // 与prettier冲突 + // "no-confusing-arrow": [ + // 2, + // { + // // 该规则不那么严格,将括号作为有效防止混淆的语法。 + // "allowParens": true + // } + // ], + // 禁止修改类声明的变量 + 'no-class-assign': 2, + // 禁止修改 const 声明的变量 + 'no-const-assign': 2, + // 允许在构造函数中,在调用 super() 之前使用 this 或 super + 'no-this-before-super': 0, + // 要求使用 let 或 const 而不是 var + 'no-var': 2, + // 重复模块导入 + // "no-duplicate-imports": 1, + '@typescript-eslint/no-duplicate-imports': 1, + // 建议使用剩余参数而不是 arguments + 'prefer-rest-params': 1, + // 禁止 Unicode 字节顺序标记 (BOM) + 'unicode-bom': 2, + // 强制每一行中所允许的最大语句数量为2 + 'max-statements-per-line': 2, + // 允许不必要的构造函数 + 'no-useless-constructor': 0, + // 允许在函数标识符和其调用之间有空格 + 'func-call-spacing': 'off', + '@typescript-eslint/func-call-spacing': 'error', + // 允许出现未使用过的变量 + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': [ + 1, + { + // 仅仅检测本作用域中声明的变量是否使用,允许不使用全局环境中的变量。 + vars: 'local', + // 不检查参数 + args: 'none' + } + ], + // 禁用特定的全局变量 + 'no-restricted-globals': [2, 'event', 'name', 'length', 'orientation', 'top', 'parent', 'location', 'closed'], + // 不允许省略大括号 + curly: 'error', + 'promise/catch-or-return': 'warn', + // indent: [1, 2], + 'no-multi-spaces': 1, + 'no-multiple-empty-lines': [1, { max: 1 }], + 'no-trailing-spaces': 1 + }, + ignorePatterns: ['*.config.ts'] +}; diff --git a/packages/vtable-export/README.md b/packages/vtable-export/README.md new file mode 100644 index 000000000..fbf2b99cb --- /dev/null +++ b/packages/vtable-export/README.md @@ -0,0 +1,93 @@ +
+ + VisActor Logo + +
+ +
+

VTable

+
+ +
+ +VTable is not just a high-performance multidimensional data analysis table, but also a grid artist that creates art between rows and columns.React-VTable is a React wrapper of VTable. + +[![npm Version](https://img.shields.io/npm/v/@visactor/vtable.svg)](https://www.npmjs.com/package/@visactor/react-vtable) +[![npm Download](https://img.shields.io/npm/dm/@visactor/vtable.svg)](https://www.npmjs.com/package/@visactor/react-vvtable) +[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/visactor/vtable/blob/main/LICENSE) + +
+ +# Usage + +## Installation + +[npm package](https://www.npmjs.com/package/@visactor/react-vtable) + +```bash +// npm +npm install @visactor/react-vtable + +// yarn +yarn add @visactor/react-vtable +``` + +## Quick Start + +```jsx +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { ListTable } from "@visactor/react-vtable"; + +const option = { + header: [ + { + field: "0", + caption: "name", + }, + { + field: "1", + caption: "age", + }, + { + field: "2", + caption: "gender", + }, + { + field: "3", + caption: "hobby", + }, + ], + records: new Array(1000).fill(["John", 18, "male", "🏀"]), +}; + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + +); +``` + +## + +[More demos and detailed tutorials](https://visactor.io/vtable) + +# Related Links + +- [Official website](https://visactor.io/vtable) + +# Ecosystem + +| Project | Description | +| ----------------------------------------------------------- | -------------------------------------------------------------------------------------- | +| [AI-generated Components](https://visactor.io/ai-vtable) | AI-generated table component. | + +# Contribution + +If you would like to contribute, please read the [Code of Conduct ](./CODE_OF_CONDUCT.md) 和 [ Guide](./CONTRIBUTING.zh-CN.md) first。 + +Small streams converge to make great rivers and seas! + + + +# License + +[MIT License](./LICENSE) diff --git a/packages/vtable-export/bundler.config.js b/packages/vtable-export/bundler.config.js new file mode 100644 index 000000000..6c01068ad --- /dev/null +++ b/packages/vtable-export/bundler.config.js @@ -0,0 +1,17 @@ +/** + * @type {Partial} + */ +module.exports = { + formats: ['cjs', 'es', 'umd'], + noEmitOnError: false, + copy: ['css'], + name: 'VTable', + umdOutputFilename: 'react-vtable', + rollupOptions: { + treeshake: true + }, + globals: { + '@visactor/vtable': 'VTable' + }, + external: ['@visactor/vtable'] +}; diff --git a/packages/vtable-export/demo/README.md b/packages/vtable-export/demo/README.md new file mode 100644 index 000000000..a51678304 --- /dev/null +++ b/packages/vtable-export/demo/README.md @@ -0,0 +1,21 @@ +# 图表示例开发说明 + +1. 先决条件 + +全局安装 [@microsoft/rush](https://rushjs.io/pages/intro/get_started/) + +```bash +npm i --global @microsoft/rush +``` + +2. 在根目录下安装依赖 + +运行:`rush update` + +3. 启动demo + +进入到项目中 +```shell +cd packages/vtable/ +rushx demo +``` \ No newline at end of file diff --git a/packages/vtable-export/demo/index.html b/packages/vtable-export/demo/index.html new file mode 100644 index 000000000..e2e88912a --- /dev/null +++ b/packages/vtable-export/demo/index.html @@ -0,0 +1,33 @@ + + + + + + + + + + VTable demos + + + +
+ +
+
+
+
+
+
+
+ + +
+
+
+ + + + + diff --git a/packages/vtable-export/demo/list/list.ts b/packages/vtable-export/demo/list/list.ts new file mode 100644 index 000000000..692c09a32 --- /dev/null +++ b/packages/vtable-export/demo/list/list.ts @@ -0,0 +1,109 @@ +import * as VTable from '@visactor/vtable'; +const CONTAINER_ID = 'vTable'; +const generatePersons = count => { + return Array.from(new Array(count)).map((_, i) => ({ + id: i + 1, + email1: `${i + 1}@xxx.com`, + name: `小明${i + 1}`, + lastName: '王', + date1: '2022年9月1日', + tel: '000-0000-0000', + sex: i % 2 === 0 ? 'boy' : 'girl', + work: i % 2 === 0 ? 'back-end engineer' : 'front-end engineer', + city: 'beijing' + })); +}; + +export function createTable() { + const records = generatePersons(10); + const columns: VTable.ColumnsDefine = [ + { + field: '', + title: '行号', + width: 80, + fieldFormat(data, col, row, table) { + return row - 1; + } + }, + { + field: 'id', + title: 'ID', + width: '1%', + minWidth: 200, + sort: true + }, + { + field: 'email1', + title: 'email', + width: 200, + sort: true + }, + { + title: 'full name', + columns: [ + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + } + ] + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + }, + { + field: 'tel', + title: 'telephone', + width: 150 + }, + { + field: 'work', + title: 'job', + width: 200 + }, + { + field: 'city', + title: 'city', + width: 150 + } + ]; + const option = { + container: document.getElementById(CONTAINER_ID), + records, + columns, + tooltip: { + isShowOverflowTextTooltip: true + } + // frozenColCount: 1, + // bottomFrozenRowCount: 2, + // rightFrozenColCount: 2, + // overscrollBehavior: 'none' + // autoWrapText: true, + // heightMode: 'autoHeight', + // widthMode: 'adaptive' + }; + const tableInstance = new VTable.ListTable(option); + window.tableInstance = tableInstance; + // tableInstance.on('sort_click', args => { + // tableInstance.updateSortState( + // { + // field: args.field, + // order: Date.now() % 3 === 0 ? 'desc' : Date.now() % 3 === 1 ? 'asc' : 'normal' + // }, + // false + // ); + // return false; //return false代表不执行内部排序逻辑 + // }); +} diff --git a/packages/vtable-export/demo/main.ts b/packages/vtable-export/demo/main.ts new file mode 100644 index 000000000..e0621d617 --- /dev/null +++ b/packages/vtable-export/demo/main.ts @@ -0,0 +1,156 @@ +import { menus } from './menu'; +// import * as VTable from '../src'; +import { default as MarkdownIt } from 'markdown-it'; +import { downloadCsv, exportVTableToCsv } from '../src'; +// window.VTable = VTable; +// window.PivotTable = PivotTable; +// window.PivotTable = PivotTable; +// window.CONTAINER_ID = 'vTable'; + +const md = new MarkdownIt(); + +const ACTIVE_ITEM_CLS = 'menu-item-active'; +const MENU_TITLE_CLS = 'menu-title'; +const LOCAL_STORAGE_KEY = 'VTABLE_DEMO'; + +const evaluateCode = (code: string) => { + // eslint-disable-next-line no-console + if (!code) { + return; + } + try { + Function(code)(window); + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + } +}; + +const handleClick = (e: { target: any }, isInit?: boolean) => { + const triggerNode = e.target; + + if (!triggerNode || triggerNode.classList.contains(MENU_TITLE_CLS)) { + return; + } + + if (triggerNode) { + const path = triggerNode.dataset.path; + const name = triggerNode.dataset.name; + + if (path && name) { + const prevActiveItems = document.getElementsByClassName(ACTIVE_ITEM_CLS); + + if (prevActiveItems && prevActiveItems.length) { + for (let i = 0; i < prevActiveItems.length; i++) { + const element = prevActiveItems[i]; + + element.classList.remove(ACTIVE_ITEM_CLS); + } + } + triggerNode.classList.add(ACTIVE_ITEM_CLS); + if (!isInit) { + localStorage.setItem(LOCAL_STORAGE_KEY, name); + } + + if (window.tableInstance) { + window.tableInstance.release(); + document.getElementById('vTable').innerHTML = null; + } + + let fileType = 'ts'; + if (path === 'custom-layout-jsx') { + fileType = 'tsx'; + } + import(`./${path}/${name}.${fileType}`) + .then(module => { + // eslint-disable-next-line no-console + console.info('%c %s', 'color: #1890ff;font-weight: bold', `当前 demo 路径:./examples/${path}/${name}.md`); + // document.getElementById('article').innerHTML = module.html; + // const jsCode = document.getElementsByClassName('language-ts')[0].innerHTML; + // evaluateCode(md.utils.unescapeAll(jsCode)); + + if (module.createTable) { + module.createTable(); + } + }) + .catch(err => { + // eslint-disable-next-line no-console + console.log(err); + }); + } + } +}; + +const initSidebarEvent = (node: HTMLDivElement) => { + node.addEventListener('click', handleClick); +}; + +const createSidebar = (node: HTMLDivElement) => { + const specsHtml = menus.map(entry => { + if (entry.menu && entry.children && entry.children.length) { + const childrenItems = entry.children.map(child => { + return ``; + }); + + return `${childrenItems.join('')}`; + } + + return ``; + }); + + node.innerHTML = ` +
+ + +
+ `; +}; + +const run = () => { + const sidebarNode = document.querySelector('#sidebar')!; + const prevActivePath = localStorage.getItem(LOCAL_STORAGE_KEY); + + createSidebar(sidebarNode); + initSidebarEvent(sidebarNode); + + const menuItemNodes = document.getElementsByClassName('menu-item'); + + handleClick( + { + target: + menuItemNodes && + menuItemNodes.length && + ([...menuItemNodes].find(node => { + return prevActivePath && node.dataset.name === prevActivePath; + }) || + menuItemNodes[0]) + }, + true + ); +}; + +run(); + +function bindExport() { + const exportCsvButton = document.getElementById('export-csv'); + const exportExcelButton = document.getElementById('export-excel'); + + if (!exportCsvButton || !exportExcelButton) { + return; + } + + exportCsvButton.addEventListener('click', () => { + if (window.tableInstance) { + downloadCsv(exportVTableToCsv(window.tableInstance), 'export'); + } + }); + + exportExcelButton.addEventListener('click', () => { + if (window.tableInstance) { + } + }); +} + +bindExport(); diff --git a/packages/vtable-export/demo/menu.ts b/packages/vtable-export/demo/menu.ts new file mode 100644 index 000000000..3626490c5 --- /dev/null +++ b/packages/vtable-export/demo/menu.ts @@ -0,0 +1,11 @@ +export const menus = [ + { + menu: 'listTable', + children: [ + { + path: 'list', + name: 'list' + } + ] + } +]; diff --git a/packages/vtable-export/demo/style.css b/packages/vtable-export/demo/style.css new file mode 100644 index 000000000..2166ca647 --- /dev/null +++ b/packages/vtable-export/demo/style.css @@ -0,0 +1,106 @@ +body { + margin: 0; + + --primary-color: #1890ff; + --primary-color-hover: #40a9ff; + --primary-color-active: #096dd9; + --primary-color-outline: rgba(24, 144, 255, 0.2); + --primary-1: #e6f7ff; + --primary-2: #bae7ff; + --primary-3: #91d5ff; + --primary-4: #69c0ff; + --primary-5: #40a9ff; + --primary-6: #1890ff; + --primary-7: #096dd9; +} +body p { + margin: 0; +} +.container { + font-family: Avenir, Helvetica, Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + /* text-align: center; */ + color: #2c3e50; + display: flex; + height: 100vh; +} + +.container .sidebar { + width: 200px; + /* height: 100%; */ + /* border-right: 1px solid rgba(0, 0, 0, 0.06); */ +} + +.container .sidebar > div { + height: 100%; +} + +.container .sidebar .sidebar-title { + font-weight: 700; + line-height: 5em; +} + +.container .sidebar .menu-list { + height: calc(100% - 5em); + overflow-y: scroll; +} + +.container .sidebar .menu-item { + line-height: 3em; + text-align: left; + padding: 0 1em; + cursor: pointer; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.container .sidebar .menu-item.menu-title { + line-height: 1.4em; + margin-left: -0.5em; + color: #999; + cursor: default; +} + +.container .sidebar .menu-item.menu-item-active { + background-color: var(--primary-1); + color: var(--primary-color); + font-weight: 700; +} + +.container .content { + flex: 1; + /* margin: 0 1em; */ + /* height: 100%; */ + min-width: 500px; +} + +#header { + height: 40px; + line-height: 40px; + border-bottom: 1px solid #d7d7d7; + margin-bottom: 20px; +} + +#chartContainer { + /* height: 500px; */ + height: calc(100% - 2em); + width: 100%; + /* margin: 1em 0em; */ +} + +pre { + border: 1px solid var(--primary-3); + padding: 1em; +} + +.language-ts { + color: var(--primary-6); +} + +#export-buttom { + position: absolute; + bottom: 0; + right: 0; +} \ No newline at end of file diff --git a/packages/vtable-export/demo/vite.config.js b/packages/vtable-export/demo/vite.config.js new file mode 100644 index 000000000..7ccf59431 --- /dev/null +++ b/packages/vtable-export/demo/vite.config.js @@ -0,0 +1,33 @@ +const { plugin: mdPlugin, Mode } = require('vite-plugin-markdown'); +const react = require('@vitejs/plugin-react'); +const path = require('path'); +module.exports = { + server: { + host: '0.0.0.0', + port: 3013, + https: !!process.env.HTTPS + }, + define: { + __DEV__: true, + __VERSION__: JSON.stringify(require('../package.json').version) + }, + resolve: { + alias: { '@visactor/vtable': path.resolve(__dirname, '../../vtable/src/index.ts') } + }, + plugins: [ + mdPlugin({ mode: [Mode.HTML, Mode.MARKDOWN, Mode.TOC, Mode.REACT] }), + react({ + babel: { + plugins: [ + [ + '@babel/plugin-transform-react-jsx', + { + pragma: 'jsx', + pragmaFrag: 'Fragment' + } + ] + ] + } + }) + ] +}; diff --git a/packages/vtable-export/package.json b/packages/vtable-export/package.json new file mode 100644 index 000000000..88a1a3137 --- /dev/null +++ b/packages/vtable-export/package.json @@ -0,0 +1,86 @@ +{ + "name": "@visactor/vtable-export", + "version": "0.16.1", + "description": "The export util of VTable", + "author": { + "name": "VisActor", + "url": "https://VisActor.io/" + }, + "license": "MIT", + "sideEffects": false, + "main": "cjs/index.js", + "module": "es/index.js", + "types": "es/index.d.ts", + "files": [ + "cjs", + "es", + "dist" + ], + "exports": { + ".": { + "require": "./cjs/index.js", + "import": "./es/index.js" + } + }, + "scripts": { + "start": "vite ./demo", + "build": "bundle --clean" + }, + "unpkg": "latest", + "unpkgFiles": [ + "dist/vtable-exporter.js" + ], + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@visactor/vtable": "workspace:*", + "@visactor/vutils": "~0.16.10", + "file-saver": "2.0.5" + }, + "devDependencies": { + "@visactor/vchart": "1.7.3", + "@internal/bundler": "workspace:*", + "@internal/eslint-config": "workspace:*", + "@internal/ts-config": "workspace:*", + "@rushstack/eslint-patch": "~1.1.4", + "react": "^18.0.0", + "react-dom": "^18.0.0", + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", + "@vitejs/plugin-react": "3.1.0", + "eslint": "~8.18.0", + "vite": "3.2.6", + "typescript": "4.9.5", + "@babel/core": "7.20.12", + "@babel/preset-env": "7.20.2", + "@types/chai": "4.2.22", + "@types/jest": "^26.0.0", + "@types/mocha": "9.0.0", + "@types/node": "*", + "@types/offscreencanvas": "2019.6.4", + "chai": "4.3.4", + "jest": "^26.0.0", + "jest-electron": "^0.1.12", + "jest-transform-stub": "^2.0.0", + "magic-string": "^0.25.7", + "mocha": "9.1.3", + "postcss": "8.4.21", + "rimraf": "3.0.2", + "sass": "1.43.5", + "ts-jest": "^26.0.0", + "ts-loader": "9.2.6", + "ts-node": "10.9.0", + "tslib": "2.3.1", + "ttypescript": "1.5.13", + "typescript-transform-paths": "3.3.1", + "json-formatter-js": "^2.3.4", + "inversify": "6.0.1", + "vite-plugin-markdown": "^2.1.0", + "markdown-it": "^13.0.0", + "node-fetch": "2.6.7", + "form-data": "~4.0.0", + "axios": "^1.4.0", + "@types/react-is": "^17.0.3" + } +} \ No newline at end of file diff --git a/packages/vtable-export/setup-mock.js b/packages/vtable-export/setup-mock.js new file mode 100644 index 000000000..b0df380be --- /dev/null +++ b/packages/vtable-export/setup-mock.js @@ -0,0 +1,2 @@ +global.__DEV__ = true; +global.__VERSION__ = true; diff --git a/packages/vtable-export/src/csv/index.ts b/packages/vtable-export/src/csv/index.ts new file mode 100644 index 000000000..a8ab20702 --- /dev/null +++ b/packages/vtable-export/src/csv/index.ts @@ -0,0 +1,47 @@ +import type * as VTable from '@visactor/vtable'; + +type IVTable = VTable.ListTable | VTable.PivotTable | VTable.PivotChart; +type CellRange = VTable.TYPES.CellRange; + +const newLine = '\r\n'; +const separator = ','; + +export function exportVTableToCsv(tableInstance: IVTable): string { + const minRow = 0; + const maxRow = tableInstance.rowCount - 1; + const minCol = 0; + const maxCol = tableInstance.colCount - 1; + + let copyValue = ''; + for (let row = minRow; row <= maxRow; row++) { + for (let col = minCol; col <= maxCol; col++) { + const copyCellValue = getCopyCellValue(col, row, tableInstance); + if (typeof Promise !== 'undefined' && copyCellValue instanceof Promise) { + // not support async + } else { + const strCellValue = `${copyCellValue}`; + if (/^\[object .*\]$/.exec(strCellValue)) { + // ignore object + } else { + copyValue += strCellValue; + } + } + copyValue += separator; + } + copyValue += newLine; + } + return copyValue; +} + +function getCopyCellValue(col: number, row: number, tableInstance: IVTable): string | Promise | void { + const cellRange: CellRange = tableInstance.getCellRange(col, row); + const copyStartCol = cellRange.start.col; + const copyStartRow = cellRange.start.row; + + if (copyStartCol !== col || copyStartRow !== row) { + return ''; + } + + const value = tableInstance.getCellValue(col, row); + return value; +} diff --git a/packages/vtable-export/src/index.ts b/packages/vtable-export/src/index.ts new file mode 100644 index 000000000..7e31a35c1 --- /dev/null +++ b/packages/vtable-export/src/index.ts @@ -0,0 +1,2 @@ +export { exportVTableToCsv } from './csv'; +export { downloadCsv } from './util/download'; diff --git a/packages/vtable-export/src/util/download.ts b/packages/vtable-export/src/util/download.ts new file mode 100644 index 000000000..bf1fa20d3 --- /dev/null +++ b/packages/vtable-export/src/util/download.ts @@ -0,0 +1,9 @@ +import { saveAs } from 'file-saver'; + +export function downloadCsv(str: string, name: string) { + const blob = new Blob([`\ufeff${str}`], { + type: 'text/csv;charset=utf-8' + }); + + saveAs(blob, `${name}.csv`); +} diff --git a/packages/vtable-export/tscofig.eslint.json b/packages/vtable-export/tscofig.eslint.json new file mode 100644 index 000000000..a8b2b56da --- /dev/null +++ b/packages/vtable-export/tscofig.eslint.json @@ -0,0 +1,20 @@ +{ + "extends": "@internal/ts-config/tsconfig.base.json", + "compilerOptions": { + "types": [ + "jest", + "offscreencanvas", + "node" + ], + "lib": [ + "DOM", + "ESNext" + ], + "baseUrl": "./", + "rootDir": "./" + }, + "include": [ + "src", + "demo" + ] +} \ No newline at end of file diff --git a/packages/vtable-export/tsconfig.json b/packages/vtable-export/tsconfig.json new file mode 100644 index 000000000..75891b766 --- /dev/null +++ b/packages/vtable-export/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "@internal/ts-config/tsconfig.base.json", + "compilerOptions": { + "jsx": "react", + "types": ["jest", "offscreencanvas", "node"], + "lib": ["DOM", "ESNext"], + "baseUrl": "./", + "rootDir": "./src", + "paths": { + } + }, + "ts-node": { + "transpileOnly": true, + "compilerOptions": { + "module": "commonjs" + } + }, + "references": [ + { + "path": "../vtable" + } + ], + "include": ["src"] +} \ No newline at end of file diff --git a/rush.json b/rush.json index a4c452735..8b058d162 100644 --- a/rush.json +++ b/rush.json @@ -82,6 +82,15 @@ ], "shouldPublish": true, "versionPolicyName": "vtableMain" + }, + { + "packageName": "@visactor/vtable-export", + "projectFolder": "packages/vtable-export", + "tags": [ + "package" + ], + "shouldPublish": true, + "versionPolicyName": "vtableMain" } ] } \ No newline at end of file From a480f4b0373d0c9a65fe51cc88a21e6ef6479806 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Mon, 11 Dec 2023 17:08:55 +0800 Subject: [PATCH 04/35] fix: multiple level totals in pivot table compute value loss #702 --- packages/vtable/src/dataset/dataset.ts | 53 ++++++++++--------- .../vtable/src/layout/pivot-layout-helper.ts | 24 ++++++++- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/packages/vtable/src/dataset/dataset.ts b/packages/vtable/src/dataset/dataset.ts index 83d13a28e..a4e934919 100644 --- a/packages/vtable/src/dataset/dataset.ts +++ b/packages/vtable/src/dataset/dataset.ts @@ -1319,33 +1319,36 @@ export class Dataset { }; if (subTotalFlags[index]) { let curChild = item.children; - for (let i = index; i < list.length - 1; i++) { - const totalChild: { value: string; dimensionKey: string; children: any[] } = { - value: subTotalLabel, - dimensionKey: rows[index + 1], - // id: `${flatKey}${concatStr}${subTotalLabel}`, // getId(item?.id, 1), - //树的叶子节点补充指标 - children: - index + 1 === list.length - 1 && indicators?.length >= 1 - ? indicators?.map(indicator => { - if (typeof indicator === 'string') { - return { - indicatorKey: indicator, - value: indicator - }; - } + // for (let i = index; i < list.length - 1; i++) { + const totalChild: { value: string; dimensionKey: string; children: any[]; levelSpan: number } = { + value: subTotalLabel, + dimensionKey: rows[index + 1], + levelSpan: subTotalFlags.length - index - 1, + // id: `${flatKey}${concatStr}${subTotalLabel}`, // getId(item?.id, 1), + //树的叶子节点补充指标 + children: + // i + 1 === list.length - 1 && + indicators?.length >= 1 + ? indicators?.map(indicator => { + if (typeof indicator === 'string') { return { - indicatorKey: indicator.indicatorKey, - value: indicator.title + indicatorKey: indicator, + value: indicator }; - }) - : [] - }; + } + return { + indicatorKey: indicator.indicatorKey, + value: indicator.title + }; + }) + : [] + }; - curChild.push(totalChild); + curChild.push(totalChild); - curChild = totalChild.children; - } + curChild = totalChild.children; + + // } } map.set(flatKey, item); // 存储路径对应的节点 if (node) { @@ -1371,10 +1374,10 @@ export class Dataset { } //最后将总计的节点加上 if (isGrandTotal && arr?.length) { - const node: { value: string; dimensionKey: string; children: any[]; rowSpan: number } = { + const node: { value: string; dimensionKey: string; children: any[]; levelSpan: number } = { value: grandTotalLabel, // getId(item?.id, 1), dimensionKey: rows[0], - rowSpan: subTotalFlags.length, + levelSpan: subTotalFlags.length, children: indicators?.map(indicator => { if (typeof indicator === 'string') { diff --git a/packages/vtable/src/layout/pivot-layout-helper.ts b/packages/vtable/src/layout/pivot-layout-helper.ts index 27cfe15fd..1b5520044 100644 --- a/packages/vtable/src/layout/pivot-layout-helper.ts +++ b/packages/vtable/src/layout/pivot-layout-helper.ts @@ -27,6 +27,8 @@ interface IPivotLayoutBaseHeadNode { value: string; children: IPivotLayoutHeadNode[] | undefined; level: number; + /** 节点跨占层数 如汇总节点跨几层维度 */ + levelSpan: number; startIndex: number; size: number; //对应到colSpan或者rowSpan // parsing?: 'img' | 'link' | 'video' | 'templateLink'; @@ -424,14 +426,34 @@ export function dealHeader( for (let r = row - 1; r >= 0; r--) { _headerCellIds[r][layoutMap.colIndex] = roots[r]; } + + // 处理汇总小计跨维度层级的情况 + if ((hd as any).levelSpan > 1) { + for (let i = 1; i < (hd as any).levelSpan; i++) { + if (!_headerCellIds[row + i]) { + _headerCellIds[row + i] = []; + } + _headerCellIds[row + i][layoutMap.colIndex] = id; + } + } + if ((hd as IPivotLayoutHeadNode).children?.length >= 1) { layoutMap - ._addHeaders(_headerCellIds, row + 1, (hd as IPivotLayoutHeadNode).children ?? [], [...roots, id]) + ._addHeaders(_headerCellIds, row + ((hd as any).levelSpan ?? 1), (hd as IPivotLayoutHeadNode).children ?? [], [ + ...roots, + ...Array((hd as any).levelSpan ?? 1).fill(id) + ]) .forEach(c => results.push(c)); } else { // columns.push([""])//代码一个路径 for (let r = row + 1; r < _headerCellIds.length; r++) { _headerCellIds[r][layoutMap.colIndex] = id; + + // if ((hd as any).levelSpan > 1) { + // for (let i = 1; i < (hd as any).levelSpan; i++) { + // _headerCellIds[r + i][layoutMap.colIndex] = id; + // } + // } } layoutMap.colIndex++; } From 98a3db944eff20d0569ef1184d59637a26a7d55c Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Mon, 11 Dec 2023 17:10:32 +0800 Subject: [PATCH 05/35] fix: multiple level totals in pivot table compute value loss #702 --- packages/vtable/src/layout/pivot-layout-helper.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/vtable/src/layout/pivot-layout-helper.ts b/packages/vtable/src/layout/pivot-layout-helper.ts index 1b5520044..73d682de5 100644 --- a/packages/vtable/src/layout/pivot-layout-helper.ts +++ b/packages/vtable/src/layout/pivot-layout-helper.ts @@ -64,6 +64,7 @@ export class DimensionTree { value: '', children: [], level: -1, + levelSpan: 1, startIndex: 0, size: 0, startInTotal: 0, From cb47bed8191f138575479e341f7b1c5d6c8fce16 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Mon, 11 Dec 2023 21:21:13 +0800 Subject: [PATCH 06/35] feat: add basic excel export tool --- common/config/rush/pnpm-lock.yaml | 122 ++++++++++++++++++++ packages/vtable-export/demo/main.ts | 3 +- packages/vtable-export/package.json | 4 +- packages/vtable-export/src/excel/index.ts | 102 ++++++++++++++++ packages/vtable-export/src/index.ts | 3 + packages/vtable-export/src/util/download.ts | 21 ++++ packages/vtable-export/src/util/type.ts | 4 + 7 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 packages/vtable-export/src/excel/index.ts create mode 100644 packages/vtable-export/src/util/type.ts diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index a8254532b..3cbb996d2 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -376,10 +376,14 @@ importers: typescript-transform-paths: 3.3.1 vite: 3.2.6 vite-plugin-markdown: ^2.1.0 + xlsx: 0.18.5 + xlsx-js-style: 1.2.0 dependencies: '@visactor/vtable': link:../vtable '@visactor/vutils': 0.16.18 file-saver: 2.0.5 + xlsx: 0.18.5 + xlsx-js-style: 1.2.0 devDependencies: '@babel/core': 7.20.12 '@babel/preset-env': 7.20.2_@babel+core@7.20.12 @@ -3690,6 +3694,20 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + /adler-32/1.2.0: + resolution: {integrity: sha512-/vUqU/UY4MVeFsg+SsK6c+/05RZXIHZMGJA+PX5JyWI0ZRcBpupnRuPLU/NXXoFwMYCPCoxIfElM2eS+DUXCqQ==} + engines: {node: '>=0.8'} + hasBin: true + dependencies: + exit-on-epipe: 1.0.1 + printj: 1.1.2 + dev: false + + /adler-32/1.3.1: + resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==} + engines: {node: '>=0.8'} + dev: false + /agent-base/6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -4512,6 +4530,14 @@ packages: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} dev: true + /cfb/1.2.2: + resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==} + engines: {node: '>=0.8'} + dependencies: + adler-32: 1.3.1 + crc-32: 1.2.2 + dev: false + /chai/4.3.10: resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==} engines: {node: '>=4'} @@ -4761,6 +4787,20 @@ packages: engines: {node: '>=0.10.0'} dev: false + /codepage/1.14.0: + resolution: {integrity: sha512-iz3zJLhlrg37/gYRWgEPkaFTtzmnEv1h+r7NgZum2lFElYQPi0/5bnmuDfODHxfp0INEfnRqyfyeIJDbb7ahRw==} + engines: {node: '>=0.8'} + hasBin: true + dependencies: + commander: 2.14.1 + exit-on-epipe: 1.0.1 + dev: false + + /codepage/1.15.0: + resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==} + engines: {node: '>=0.8'} + dev: false + /collect-v8-coverage/1.0.2: resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} dev: true @@ -4831,6 +4871,14 @@ packages: dependencies: delayed-stream: 1.0.0 + /commander/2.14.1: + resolution: {integrity: sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==} + dev: false + + /commander/2.17.1: + resolution: {integrity: sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==} + dev: false + /commander/2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -4946,6 +4994,12 @@ packages: /core-util-is/1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + /crc-32/1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + dev: false + /create-require/1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -6199,6 +6253,11 @@ packages: strip-final-newline: 2.0.0 dev: true + /exit-on-epipe/1.0.1: + resolution: {integrity: sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==} + engines: {node: '>=0.8'} + dev: false + /exit/0.1.2: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} @@ -6357,6 +6416,10 @@ packages: pend: 1.2.0 dev: true + /fflate/0.3.11: + resolution: {integrity: sha512-Rr5QlUeGN1mbOHlaqcSYMKVpPbgLy0AWT/W0EHxA6NGI12yO1jpoui2zBBvU2G824ltM6Ut8BFgfHSBGfkmS0A==} + dev: false + /file-entry-cache/6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -6547,6 +6610,11 @@ packages: combined-stream: 1.0.8 mime-types: 2.1.35 + /frac/1.1.2: + resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==} + engines: {node: '>=0.8'} + dev: false + /fraction.js/4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} dev: false @@ -10528,6 +10596,12 @@ packages: engines: {node: '>= 0.8'} dev: false + /printj/1.1.2: + resolution: {integrity: sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==} + engines: {node: '>=0.8'} + hasBin: true + dev: false + /process-nextick-args/2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -11594,6 +11668,13 @@ packages: dev: true optional: true + /ssf/0.11.2: + resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==} + engines: {node: '>=0.8'} + dependencies: + frac: 1.1.2 + dev: false + /sshpk/1.18.0: resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} engines: {node: '>=0.10.0'} @@ -13082,11 +13163,21 @@ packages: stackback: 0.0.2 dev: true + /wmf/1.0.2: + resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==} + engines: {node: '>=0.8'} + dev: false + /word-wrap/1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} dev: true + /word/0.3.0: + resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==} + engines: {node: '>=0.8'} + dev: false + /workerpool/6.1.5: resolution: {integrity: sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==} dev: true @@ -13165,6 +13256,37 @@ packages: optional: true dev: true + /xlsx-js-style/1.2.0: + resolution: {integrity: sha512-DDT4FXFSWfT4DXMSok/m3TvmP1gvO3dn0Eu/c+eXHW5Kzmp7IczNkxg/iEPnImbG9X0Vb8QhROda5eatSR/97Q==} + engines: {node: '>=0.8'} + hasBin: true + dependencies: + adler-32: 1.2.0 + cfb: 1.2.2 + codepage: 1.14.0 + commander: 2.17.1 + crc-32: 1.2.2 + exit-on-epipe: 1.0.1 + fflate: 0.3.11 + ssf: 0.11.2 + wmf: 1.0.2 + word: 0.3.0 + dev: false + + /xlsx/0.18.5: + resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==} + engines: {node: '>=0.8'} + hasBin: true + dependencies: + adler-32: 1.3.1 + cfb: 1.2.2 + codepage: 1.15.0 + crc-32: 1.2.2 + ssf: 0.11.2 + wmf: 1.0.2 + word: 0.3.0 + dev: false + /xml-name-validator/3.0.0: resolution: {integrity: sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==} dev: true diff --git a/packages/vtable-export/demo/main.ts b/packages/vtable-export/demo/main.ts index e0621d617..4fa3a813a 100644 --- a/packages/vtable-export/demo/main.ts +++ b/packages/vtable-export/demo/main.ts @@ -1,7 +1,7 @@ import { menus } from './menu'; // import * as VTable from '../src'; import { default as MarkdownIt } from 'markdown-it'; -import { downloadCsv, exportVTableToCsv } from '../src'; +import { downloadCsv, exportVTableToCsv, downloadExcel, exportVTableToExcel } from '../src'; // window.VTable = VTable; // window.PivotTable = PivotTable; // window.PivotTable = PivotTable; @@ -149,6 +149,7 @@ function bindExport() { exportExcelButton.addEventListener('click', () => { if (window.tableInstance) { + downloadExcel(exportVTableToExcel(window.tableInstance), 'export'); } }); } diff --git a/packages/vtable-export/package.json b/packages/vtable-export/package.json index 88a1a3137..15da66e20 100644 --- a/packages/vtable-export/package.json +++ b/packages/vtable-export/package.json @@ -36,7 +36,9 @@ "dependencies": { "@visactor/vtable": "workspace:*", "@visactor/vutils": "~0.16.10", - "file-saver": "2.0.5" + "file-saver": "2.0.5", + "xlsx": "0.18.5", + "xlsx-js-style": "1.2.0" }, "devDependencies": { "@visactor/vchart": "1.7.3", diff --git a/packages/vtable-export/src/excel/index.ts b/packages/vtable-export/src/excel/index.ts new file mode 100644 index 000000000..bf35d56e2 --- /dev/null +++ b/packages/vtable-export/src/excel/index.ts @@ -0,0 +1,102 @@ +import type * as VTable from '@visactor/vtable'; +import XLSX from 'xlsx-js-style'; + +type IVTable = VTable.ListTable | VTable.PivotTable | VTable.PivotChart; +type CellRange = VTable.TYPES.CellRange; + +export function exportVTableToExcel(tableInstance: IVTable) { + const workSheet = exportVTableToWorkSheet(tableInstance); + const workBook = createWorkBook(workSheet); + + const workBookExport = XLSX.write(workBook, { + bookType: 'xlsx', + bookSST: false, + type: 'binary' + }); + + return workBookExport; +} + +function createWorkBook(workSheet: any, name: string = 'sheet') { + const workBook = { SheetNames: [] as any, Sheets: {} }; + + workBook.SheetNames.push(name); + workBook.Sheets[name] = workSheet; + + return workBook; +} + +function exportVTableToWorkSheet(tableInstance: IVTable) { + const minRow = 0; + const maxRow = tableInstance.rowCount - 1; + const minCol = 0; + const maxCol = tableInstance.colCount - 1; + + const workSheet = {}; + const mergeCells = []; + const mergeCellSet = new Set(); + const columnsWidth = []; + const rowsWidth = []; + + for (let col = minCol; col <= maxCol; col++) { + const colWith = tableInstance.getColWidth(col); + columnsWidth[col] = { wpx: colWith }; + for (let row = minRow; row <= maxRow; row++) { + if (!rowsWidth[row]) { + const rowHeight = tableInstance.getRowHeight(row); + rowsWidth[row] = { hpx: rowHeight }; + } + + const cellValue = tableInstance.getCellValue(col, row); + workSheet[encodeCellAddress(col, row)] = { + v: cellValue, + t: typeof cellValue === 'number' ? 'n' : 's', + s: {} + }; + + const cellRange = tableInstance.getCellRange(col, row); + if (cellRange.start.col !== cellRange.end.col || cellRange.start.row !== cellRange.end.row) { + const key = `${cellRange.start.col},${cellRange.start.row}:${cellRange.end.col},${cellRange.end.row}}`; + if (!mergeCellSet.has(key)) { + mergeCellSet.add(key); + mergeCells.push({ + s: { c: cellRange.start.col, r: cellRange.start.row }, + e: { c: cellRange.end.col, r: cellRange.end.row } + }); + } + } + } + } + + const tableRange = encodeCellRange({ + start: { + col: 0, + row: 0 + }, + end: { + col: tableInstance.colCount - 1, + row: tableInstance.rowCount - 1 + } + }); + + workSheet['!ref'] = tableRange; + workSheet['!merges'] = mergeCells; + workSheet['!cols'] = columnsWidth; + workSheet['!rows'] = rowsWidth; + + return workSheet; +} + +function encodeCellRange(cellRange: CellRange) { + const start = encodeCellAddress(cellRange.start.col, cellRange.start.row); + const end = encodeCellAddress(cellRange.end.col, cellRange.end.row); + return `${start}:${end}`; +} + +function encodeCellAddress(col: number, row: number) { + let s = ''; + for (let column = col + 1; column > 0; column = Math.floor((column - 1) / 26)) { + s = String.fromCharCode(((column - 1) % 26) + 65) + s; + } + return s + (row + 1); +} diff --git a/packages/vtable-export/src/index.ts b/packages/vtable-export/src/index.ts index 7e31a35c1..02a9639c1 100644 --- a/packages/vtable-export/src/index.ts +++ b/packages/vtable-export/src/index.ts @@ -1,2 +1,5 @@ export { exportVTableToCsv } from './csv'; export { downloadCsv } from './util/download'; + +export { exportVTableToExcel } from './excel'; +export { downloadExcel } from './util/download'; diff --git a/packages/vtable-export/src/util/download.ts b/packages/vtable-export/src/util/download.ts index bf1fa20d3..e4367a3ec 100644 --- a/packages/vtable-export/src/util/download.ts +++ b/packages/vtable-export/src/util/download.ts @@ -7,3 +7,24 @@ export function downloadCsv(str: string, name: string) { saveAs(blob, `${name}.csv`); } + +export function downloadExcel(workSheetStr: string, name: string) { + // debugger; + const arrayBuffer = workSheetStr2ArrayBuffer(workSheetStr); + const blob = new Blob([arrayBuffer], { + type: 'application/octet-stream' + }); + + saveAs(blob, `${name}.xlsx`); +} + +function workSheetStr2ArrayBuffer(workSheetStr: string) { + const buffer = new ArrayBuffer(workSheetStr.length); + const arrayBuffer = new Uint8Array(buffer); + + for (let i = 0; i < workSheetStr.length; ++i) { + arrayBuffer[i] = workSheetStr.charCodeAt(i) & 0xff; + } + + return buffer; +} diff --git a/packages/vtable-export/src/util/type.ts b/packages/vtable-export/src/util/type.ts new file mode 100644 index 000000000..a4b2c0c50 --- /dev/null +++ b/packages/vtable-export/src/util/type.ts @@ -0,0 +1,4 @@ +import type * as VTable from '@visactor/vtable'; + +export type IVTable = VTable.ListTable | VTable.PivotTable | VTable.PivotChart; +export type CellRange = VTable.TYPES.CellRange; From 6e5fd9a00e732dc4697e5e94d23deb332a2f49d3 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Tue, 12 Dec 2023 15:40:56 +0800 Subject: [PATCH 07/35] feat: add basic style in excel export tool --- packages/vtable-export/src/excel/index.ts | 26 ++++++++--- packages/vtable-export/src/excel/style.ts | 53 +++++++++++++++++++++++ 2 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 packages/vtable-export/src/excel/style.ts diff --git a/packages/vtable-export/src/excel/index.ts b/packages/vtable-export/src/excel/index.ts index bf35d56e2..7118532b5 100644 --- a/packages/vtable-export/src/excel/index.ts +++ b/packages/vtable-export/src/excel/index.ts @@ -1,8 +1,6 @@ -import type * as VTable from '@visactor/vtable'; import XLSX from 'xlsx-js-style'; - -type IVTable = VTable.ListTable | VTable.PivotTable | VTable.PivotChart; -type CellRange = VTable.TYPES.CellRange; +import { getCellStyle } from './style'; +import type { CellRange, IVTable } from '../util/type'; export function exportVTableToExcel(tableInstance: IVTable) { const workSheet = exportVTableToWorkSheet(tableInstance); @@ -48,11 +46,14 @@ function exportVTableToWorkSheet(tableInstance: IVTable) { } const cellValue = tableInstance.getCellValue(col, row); - workSheet[encodeCellAddress(col, row)] = { + const cell: XLSX.CellObject = { v: cellValue, - t: typeof cellValue === 'number' ? 'n' : 's', - s: {} + t: typeof cellValue === 'number' ? 'n' : 's' }; + if (cellValue) { + cell.s = getCellStyle(col, row, tableInstance); + } + workSheet[encodeCellAddress(col, row)] = cell; const cellRange = tableInstance.getCellRange(col, row); if (cellRange.start.col !== cellRange.end.col || cellRange.start.row !== cellRange.end.row) { @@ -87,12 +88,23 @@ function exportVTableToWorkSheet(tableInstance: IVTable) { return workSheet; } +/** + * @description: comvert cell range to code + * @param {CellRange} cellRange + * @return {*} + */ function encodeCellRange(cellRange: CellRange) { const start = encodeCellAddress(cellRange.start.col, cellRange.start.row); const end = encodeCellAddress(cellRange.end.col, cellRange.end.row); return `${start}:${end}`; } +/** + * @description: convert cell address to code + * @param {number} col + * @param {number} row + * @return {*} + */ function encodeCellAddress(col: number, row: number) { let s = ''; for (let column = col + 1; column > 0; column = Math.floor((column - 1) / 26)) { diff --git a/packages/vtable-export/src/excel/style.ts b/packages/vtable-export/src/excel/style.ts new file mode 100644 index 000000000..ed9cbc178 --- /dev/null +++ b/packages/vtable-export/src/excel/style.ts @@ -0,0 +1,53 @@ +import type { IVTable } from '../util/type'; +import type XLSX from 'xlsx-js-style'; +import type * as VTable from '@visactor/vtable'; + +export function getCellStyle(col: number, row: number, tableInstance: IVTable): XLSX.CellStyle { + const cellStyle = tableInstance.getCellStyle(col, row); + return { + alignment: getCellAlignment(col, row, cellStyle), + border: getCellBorder(col, row, cellStyle), + fill: getCellFill(col, row, cellStyle), + font: getCellFont(col, row, cellStyle) + }; +} + +function getCellAlignment(col: number, row: number, cellStyle: VTable.TYPES.CellStyle): XLSX.CellStyle['alignment'] { + return { + horizontal: cellStyle.textAlign || 'left', + vertical: 'center', // no middle, use center + wrapText: cellStyle.autoWrapText || false + }; +} + +function getCellBorder(col: number, row: number, cellStyle: VTable.TYPES.CellStyle): XLSX.CellStyle['border'] { + return { + top: { color: getColor(cellStyle.borderColor) }, + left: { color: getColor(cellStyle.borderColor) }, + bottom: { color: getColor(cellStyle.borderColor) }, + right: { color: getColor(cellStyle.borderColor) } + }; +} + +function getCellFill(col: number, row: number, cellStyle: VTable.TYPES.CellStyle): XLSX.CellStyle['fill'] { + return { + fgColor: getColor(cellStyle.bgColor) + }; +} + +function getCellFont(col: number, row: number, cellStyle: VTable.TYPES.CellStyle): XLSX.CellStyle['font'] { + return { + name: cellStyle.fontFamily || 'Arial', // only one font familt name + sz: cellStyle.fontSize || 10, + bold: cellStyle.fontWeight === 'bold', // only bold or not + italic: cellStyle.fontStyle === 'italic', // only italic or not + color: getColor(cellStyle.color), + underline: cellStyle.underline + }; +} + +function getColor(color: string) { + return { + rgb: color.substring(1) + }; +} From 76b304e1a43184363405525041a34d880ca34686 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Tue, 12 Dec 2023 20:26:01 +0800 Subject: [PATCH 08/35] feat: change xlsx-js into exceljs --- common/config/rush/pnpm-lock.yaml | 328 +++++++++++++++++- packages/vtable-export/demo/main.ts | 4 +- packages/vtable-export/package.json | 3 +- .../vtable-export/src/excel/index-xlsx-js.ts | 114 ++++++ packages/vtable-export/src/excel/index.ts | 157 ++++----- packages/vtable-export/src/util/download.ts | 5 +- packages/vtable-export/src/util/encode.ts | 26 ++ packages/vtable-export/src/util/type.ts | 1 + 8 files changed, 547 insertions(+), 91 deletions(-) create mode 100644 packages/vtable-export/src/excel/index-xlsx-js.ts create mode 100644 packages/vtable-export/src/util/encode.ts diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 3cbb996d2..36cdcfb27 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -351,6 +351,7 @@ importers: axios: ^1.4.0 chai: 4.3.4 eslint: ~8.18.0 + exceljs: 4.4.0 file-saver: 2.0.5 form-data: ~4.0.0 inversify: 6.0.1 @@ -381,6 +382,7 @@ importers: dependencies: '@visactor/vtable': link:../vtable '@visactor/vutils': 0.16.18 + exceljs: 4.4.0 file-saver: 2.0.5 xlsx: 0.18.5 xlsx-js-style: 1.2.0 @@ -1940,6 +1942,29 @@ packages: transitivePeerDependencies: - supports-color + /@fast-csv/format/4.3.5: + resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==} + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.isboolean: 3.0.3 + lodash.isequal: 4.5.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + dev: false + + /@fast-csv/parse/4.3.6: + resolution: {integrity: sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==} + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.groupby: 4.6.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + lodash.isundefined: 3.0.1 + lodash.uniq: 4.5.0 + dev: false + /@gulp-sourcemaps/identity-map/2.0.1: resolution: {integrity: sha512-Tb+nSISZku+eQ4X1lAkevcQa+jknn/OVUgZ3XCxEKIsLsqYuPoJwJOPQeaOk75X3WPftb29GWY1eqE7GLsXb1Q==} engines: {node: '>= 0.10'} @@ -3055,6 +3080,10 @@ packages: resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} dev: true + /@types/node/14.18.63: + resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} + dev: false + /@types/node/20.10.0: resolution: {integrity: sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==} dependencies: @@ -3849,6 +3878,51 @@ packages: buffer-equal: 1.0.1 dev: false + /archiver-utils/2.1.0: + resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} + engines: {node: '>= 6'} + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 2.3.8 + dev: false + + /archiver-utils/3.0.4: + resolution: {integrity: sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==} + engines: {node: '>= 10'} + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + dev: false + + /archiver/5.3.2: + resolution: {integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==} + engines: {node: '>= 10'} + dependencies: + archiver-utils: 2.1.0 + async: 3.2.5 + buffer-crc32: 0.2.13 + readable-stream: 3.6.2 + readdir-glob: 1.1.3 + tar-stream: 2.2.0 + zip-stream: 4.1.1 + dev: false + /archy/1.0.0: resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} dev: false @@ -4066,6 +4140,10 @@ packages: async-done: 1.3.2 dev: false + /async/3.2.5: + resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} + dev: false + /asynckit/0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -4317,12 +4395,21 @@ packages: mixin-deep: 1.3.2 pascalcase: 0.1.1 + /base64-js/1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: false + /bcrypt-pbkdf/1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} dependencies: tweetnacl: 0.14.5 dev: true + /big-integer/1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + dev: false + /binary-extensions/1.13.1: resolution: {integrity: sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==} engines: {node: '>=0.10.0'} @@ -4333,6 +4420,13 @@ packages: engines: {node: '>=8'} dev: true + /binary/0.3.0: + resolution: {integrity: sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==} + dependencies: + buffers: 0.1.1 + chainsaw: 0.1.0 + dev: false + /binaryextensions/2.3.0: resolution: {integrity: sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==} engines: {node: '>=0.8'} @@ -4344,6 +4438,18 @@ packages: file-uri-to-path: 1.0.0 optional: true + /bl/4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: false + + /bluebird/3.4.7: + resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} + dev: false + /blueimp-md5/2.19.0: resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} dev: true @@ -4429,7 +4535,6 @@ packages: /buffer-crc32/0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - dev: true /buffer-equal/1.0.1: resolution: {integrity: sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==} @@ -4439,6 +4544,23 @@ packages: /buffer-from/1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + /buffer-indexof-polyfill/1.0.2: + resolution: {integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==} + engines: {node: '>=0.10'} + dev: false + + /buffer/5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: false + + /buffers/0.1.1: + resolution: {integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==} + engines: {node: '>=0.2.0'} + dev: false + /builtin-modules/1.1.1: resolution: {integrity: sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ==} engines: {node: '>=0.10.0'} @@ -4563,6 +4685,12 @@ packages: type-detect: 4.0.8 dev: true + /chainsaw/0.1.0: + resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==} + dependencies: + traverse: 0.3.9 + dev: false + /chalk/1.1.3: resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} engines: {node: '>=0.10.0'} @@ -4898,6 +5026,16 @@ packages: /component-emitter/1.3.1: resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + /compress-commons/4.1.2: + resolution: {integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==} + engines: {node: '>= 10'} + dependencies: + buffer-crc32: 0.2.13 + crc32-stream: 4.0.3 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + dev: false + /compute-scroll-into-view/1.0.11: resolution: {integrity: sha512-uUnglJowSe0IPmWOdDtrlHXof5CTIJitfJEyITHBW6zDVOGu9Pjk5puaLM73SLcwak0L4hEjO7Td88/a6P5i7A==} dev: false @@ -5000,6 +5138,14 @@ packages: hasBin: true dev: false + /crc32-stream/4.0.3: + resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==} + engines: {node: '>= 10'} + dependencies: + crc-32: 1.2.2 + readable-stream: 3.6.2 + dev: false + /create-require/1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -5522,6 +5668,12 @@ packages: domelementtype: 2.3.0 domhandler: 4.3.1 + /duplexer2/0.1.4: + resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + dependencies: + readable-stream: 2.3.8 + dev: false + /duplexer3/0.1.5: resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} dev: true @@ -6206,6 +6358,21 @@ packages: /eventemitter3/4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + /exceljs/4.4.0: + resolution: {integrity: sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==} + engines: {node: '>=8.3.0'} + dependencies: + archiver: 5.3.2 + dayjs: 1.11.10 + fast-csv: 4.3.6 + jszip: 3.10.1 + readable-stream: 3.6.2 + saxes: 5.0.1 + tmp: 0.2.1 + unzipper: 0.10.14 + uuid: 8.3.2 + dev: false + /exec-sh/0.3.6: resolution: {integrity: sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==} dev: true @@ -6373,6 +6540,14 @@ packages: time-stamp: 1.1.0 dev: false + /fast-csv/4.3.6: + resolution: {integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==} + engines: {node: '>=10.0.0'} + dependencies: + '@fast-csv/format': 4.3.5 + '@fast-csv/parse': 4.3.6 + dev: false + /fast-deep-equal/3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -6631,6 +6806,10 @@ packages: js-yaml: 3.14.1 dev: true + /fs-constants/1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + dev: false + /fs-extra/10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} @@ -6677,6 +6856,16 @@ packages: requiresBuild: true optional: true + /fstream/1.0.12: + resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==} + engines: {node: '>=0.6'} + dependencies: + graceful-fs: 4.2.11 + inherits: 2.0.4 + mkdirp: 0.5.6 + rimraf: 2.7.1 + dev: false + /function-bind/1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -7332,6 +7521,10 @@ packages: requiresBuild: true optional: true + /immediate/3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + dev: false + /import-cwd/3.0.0: resolution: {integrity: sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==} engines: {node: '>=8'} @@ -8807,6 +9000,15 @@ packages: object.values: 1.1.7 dev: false + /jszip/3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + dev: false + /just-debounce/1.1.0: resolution: {integrity: sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==} dev: false @@ -8924,6 +9126,12 @@ packages: prelude-ls: 1.2.1 type-check: 0.4.0 + /lie/3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + dependencies: + immediate: 3.0.6 + dev: false + /liftoff/3.1.0: resolution: {integrity: sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==} engines: {node: '>= 0.8'} @@ -8986,6 +9194,10 @@ packages: - enquirer dev: true + /listenercount/1.0.1: + resolution: {integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==} + dev: false + /listr2/4.0.5: resolution: {integrity: sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==} engines: {node: '>=12'} @@ -9065,6 +9277,50 @@ packages: /lodash.debounce/4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + /lodash.defaults/4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + dev: false + + /lodash.difference/4.5.0: + resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} + dev: false + + /lodash.escaperegexp/4.1.2: + resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + dev: false + + /lodash.flatten/4.4.0: + resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + dev: false + + /lodash.groupby/4.6.0: + resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} + dev: false + + /lodash.isboolean/3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + dev: false + + /lodash.isequal/4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + dev: false + + /lodash.isfunction/3.0.9: + resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} + dev: false + + /lodash.isnil/4.0.0: + resolution: {integrity: sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==} + dev: false + + /lodash.isplainobject/4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + dev: false + + /lodash.isundefined/3.0.1: + resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==} + dev: false + /lodash.memoize/4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} dev: false @@ -9076,6 +9332,10 @@ packages: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} dev: true + /lodash.union/4.6.0: + resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} + dev: false + /lodash.uniq/4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} dev: false @@ -9375,7 +9635,6 @@ packages: /minimist/1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - dev: true /mixin-deep/1.3.2: resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} @@ -9389,7 +9648,6 @@ packages: hasBin: true dependencies: minimist: 1.2.8 - dev: true /mkdirp/1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} @@ -9864,6 +10122,10 @@ packages: engines: {node: '>=6'} dev: true + /pako/1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + dev: false + /parent-module/1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -10878,6 +11140,12 @@ packages: string_decoder: 1.3.0 util-deprecate: 1.0.2 + /readdir-glob/1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + dependencies: + minimatch: 5.1.6 + dev: false + /readdirp/2.2.1: resolution: {integrity: sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==} engines: {node: '>=0.10'} @@ -11331,7 +11599,6 @@ packages: engines: {node: '>=10'} dependencies: xmlchars: 2.2.0 - dev: true /scheduler/0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} @@ -11427,6 +11694,10 @@ packages: is-plain-object: 2.0.4 split-string: 3.1.0 + /setimmediate/1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + dev: false + /shallowequal/1.1.0: resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} dev: false @@ -12016,6 +12287,17 @@ packages: engines: {node: '>=6'} dev: true + /tar-stream/2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: false + /terminal-link/2.1.1: resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} engines: {node: '>=8'} @@ -12144,6 +12426,13 @@ packages: engines: {node: '>=14.0.0'} dev: true + /tmp/0.2.1: + resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} + engines: {node: '>=8.17.0'} + dependencies: + rimraf: 3.0.2 + dev: false + /tmpl/1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} dev: true @@ -12247,6 +12536,10 @@ packages: punycode: 2.3.1 dev: true + /traverse/0.3.9: + resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} + dev: false + /ts-jest/26.5.6_xuote2qreek47x2di7kesslrai: resolution: {integrity: sha512-rua+rCP8DxpA8b4DQD/6X2HQS8Zy/xzViVYfEs2OQu68tkCuKLV0Md8pmX55+W24uRIyAsf/BajRfxOs+R2MKA==} engines: {node: '>= 10'} @@ -12603,6 +12896,21 @@ packages: has-value: 0.3.1 isobject: 3.0.1 + /unzipper/0.10.14: + resolution: {integrity: sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==} + dependencies: + big-integer: 1.6.52 + binary: 0.3.0 + bluebird: 3.4.7 + buffer-indexof-polyfill: 1.0.2 + duplexer2: 0.1.4 + fstream: 1.0.12 + graceful-fs: 4.2.11 + listenercount: 1.0.1 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + dev: false + /upath/1.2.0: resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==} engines: {node: '>=4'} @@ -12700,8 +13008,6 @@ packages: /uuid/8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true - dev: true - optional: true /v8-compile-cache-lib/3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -13293,7 +13599,6 @@ packages: /xmlchars/2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - dev: true /xtend/4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} @@ -13465,3 +13770,12 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} dev: true + + /zip-stream/4.1.1: + resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} + engines: {node: '>= 10'} + dependencies: + archiver-utils: 3.0.4 + compress-commons: 4.1.2 + readable-stream: 3.6.2 + dev: false diff --git a/packages/vtable-export/demo/main.ts b/packages/vtable-export/demo/main.ts index 4fa3a813a..80f9319c8 100644 --- a/packages/vtable-export/demo/main.ts +++ b/packages/vtable-export/demo/main.ts @@ -147,9 +147,9 @@ function bindExport() { } }); - exportExcelButton.addEventListener('click', () => { + exportExcelButton.addEventListener('click', async () => { if (window.tableInstance) { - downloadExcel(exportVTableToExcel(window.tableInstance), 'export'); + downloadExcel(await exportVTableToExcel(window.tableInstance), 'export'); } }); } diff --git a/packages/vtable-export/package.json b/packages/vtable-export/package.json index 15da66e20..6ae18e7f3 100644 --- a/packages/vtable-export/package.json +++ b/packages/vtable-export/package.json @@ -38,7 +38,8 @@ "@visactor/vutils": "~0.16.10", "file-saver": "2.0.5", "xlsx": "0.18.5", - "xlsx-js-style": "1.2.0" + "xlsx-js-style": "1.2.0", + "exceljs": "4.4.0" }, "devDependencies": { "@visactor/vchart": "1.7.3", diff --git a/packages/vtable-export/src/excel/index-xlsx-js.ts b/packages/vtable-export/src/excel/index-xlsx-js.ts new file mode 100644 index 000000000..7118532b5 --- /dev/null +++ b/packages/vtable-export/src/excel/index-xlsx-js.ts @@ -0,0 +1,114 @@ +import XLSX from 'xlsx-js-style'; +import { getCellStyle } from './style'; +import type { CellRange, IVTable } from '../util/type'; + +export function exportVTableToExcel(tableInstance: IVTable) { + const workSheet = exportVTableToWorkSheet(tableInstance); + const workBook = createWorkBook(workSheet); + + const workBookExport = XLSX.write(workBook, { + bookType: 'xlsx', + bookSST: false, + type: 'binary' + }); + + return workBookExport; +} + +function createWorkBook(workSheet: any, name: string = 'sheet') { + const workBook = { SheetNames: [] as any, Sheets: {} }; + + workBook.SheetNames.push(name); + workBook.Sheets[name] = workSheet; + + return workBook; +} + +function exportVTableToWorkSheet(tableInstance: IVTable) { + const minRow = 0; + const maxRow = tableInstance.rowCount - 1; + const minCol = 0; + const maxCol = tableInstance.colCount - 1; + + const workSheet = {}; + const mergeCells = []; + const mergeCellSet = new Set(); + const columnsWidth = []; + const rowsWidth = []; + + for (let col = minCol; col <= maxCol; col++) { + const colWith = tableInstance.getColWidth(col); + columnsWidth[col] = { wpx: colWith }; + for (let row = minRow; row <= maxRow; row++) { + if (!rowsWidth[row]) { + const rowHeight = tableInstance.getRowHeight(row); + rowsWidth[row] = { hpx: rowHeight }; + } + + const cellValue = tableInstance.getCellValue(col, row); + const cell: XLSX.CellObject = { + v: cellValue, + t: typeof cellValue === 'number' ? 'n' : 's' + }; + if (cellValue) { + cell.s = getCellStyle(col, row, tableInstance); + } + workSheet[encodeCellAddress(col, row)] = cell; + + const cellRange = tableInstance.getCellRange(col, row); + if (cellRange.start.col !== cellRange.end.col || cellRange.start.row !== cellRange.end.row) { + const key = `${cellRange.start.col},${cellRange.start.row}:${cellRange.end.col},${cellRange.end.row}}`; + if (!mergeCellSet.has(key)) { + mergeCellSet.add(key); + mergeCells.push({ + s: { c: cellRange.start.col, r: cellRange.start.row }, + e: { c: cellRange.end.col, r: cellRange.end.row } + }); + } + } + } + } + + const tableRange = encodeCellRange({ + start: { + col: 0, + row: 0 + }, + end: { + col: tableInstance.colCount - 1, + row: tableInstance.rowCount - 1 + } + }); + + workSheet['!ref'] = tableRange; + workSheet['!merges'] = mergeCells; + workSheet['!cols'] = columnsWidth; + workSheet['!rows'] = rowsWidth; + + return workSheet; +} + +/** + * @description: comvert cell range to code + * @param {CellRange} cellRange + * @return {*} + */ +function encodeCellRange(cellRange: CellRange) { + const start = encodeCellAddress(cellRange.start.col, cellRange.start.row); + const end = encodeCellAddress(cellRange.end.col, cellRange.end.row); + return `${start}:${end}`; +} + +/** + * @description: convert cell address to code + * @param {number} col + * @param {number} row + * @return {*} + */ +function encodeCellAddress(col: number, row: number) { + let s = ''; + for (let column = col + 1; column > 0; column = Math.floor((column - 1) / 26)) { + s = String.fromCharCode(((column - 1) % 26) + 65) + s; + } + return s + (row + 1); +} diff --git a/packages/vtable-export/src/excel/index.ts b/packages/vtable-export/src/excel/index.ts index 7118532b5..d855665bf 100644 --- a/packages/vtable-export/src/excel/index.ts +++ b/packages/vtable-export/src/excel/index.ts @@ -1,114 +1,115 @@ -import XLSX from 'xlsx-js-style'; -import { getCellStyle } from './style'; -import type { CellRange, IVTable } from '../util/type'; +import ExcelJS from 'exceljs'; +import { encodeCellAddress } from '../util/encode'; +import type { CellStyle, IVTable } from '../util/type'; -export function exportVTableToExcel(tableInstance: IVTable) { - const workSheet = exportVTableToWorkSheet(tableInstance); - const workBook = createWorkBook(workSheet); +export async function exportVTableToExcel(tableInstance: IVTable) { + const workbook = new ExcelJS.Workbook(); + const worksheet = workbook.addWorksheet('sheet1'); + const columns = []; - const workBookExport = XLSX.write(workBook, { - bookType: 'xlsx', - bookSST: false, - type: 'binary' - }); - - return workBookExport; -} - -function createWorkBook(workSheet: any, name: string = 'sheet') { - const workBook = { SheetNames: [] as any, Sheets: {} }; - - workBook.SheetNames.push(name); - workBook.Sheets[name] = workSheet; - - return workBook; -} - -function exportVTableToWorkSheet(tableInstance: IVTable) { const minRow = 0; const maxRow = tableInstance.rowCount - 1; const minCol = 0; const maxCol = tableInstance.colCount - 1; - const workSheet = {}; const mergeCells = []; const mergeCellSet = new Set(); - const columnsWidth = []; - const rowsWidth = []; for (let col = minCol; col <= maxCol; col++) { const colWith = tableInstance.getColWidth(col); - columnsWidth[col] = { wpx: colWith }; + columns[col] = { width: colWith / 7 }; for (let row = minRow; row <= maxRow; row++) { - if (!rowsWidth[row]) { + if (col === minCol) { const rowHeight = tableInstance.getRowHeight(row); - rowsWidth[row] = { hpx: rowHeight }; + const worksheetRow = worksheet.getRow(row + 1); + worksheetRow.height = rowHeight * 0.75; } const cellValue = tableInstance.getCellValue(col, row); - const cell: XLSX.CellObject = { - v: cellValue, - t: typeof cellValue === 'number' ? 'n' : 's' - }; - if (cellValue) { - cell.s = getCellStyle(col, row, tableInstance); - } - workSheet[encodeCellAddress(col, row)] = cell; + const cellStyle = tableInstance.getCellStyle(col, row); + + const cell = worksheet.getCell(encodeCellAddress(col, row)); + cell.value = cellValue; + cell.font = getCellFont(cellStyle); + cell.fill = getCellFill(cellStyle); + cell.border = getCellBorder(cellStyle); + cell.alignment = getCellAlignment(cellStyle); const cellRange = tableInstance.getCellRange(col, row); if (cellRange.start.col !== cellRange.end.col || cellRange.start.row !== cellRange.end.row) { const key = `${cellRange.start.col},${cellRange.start.row}:${cellRange.end.col},${cellRange.end.row}}`; if (!mergeCellSet.has(key)) { mergeCellSet.add(key); - mergeCells.push({ - s: { c: cellRange.start.col, r: cellRange.start.row }, - e: { c: cellRange.end.col, r: cellRange.end.row } - }); + mergeCells.push(cellRange); } } } } - const tableRange = encodeCellRange({ - start: { - col: 0, - row: 0 - }, - end: { - col: tableInstance.colCount - 1, - row: tableInstance.rowCount - 1 - } + worksheet.columns = columns; + mergeCells.forEach(mergeCell => { + worksheet.mergeCells( + mergeCell.start.row + 1, + mergeCell.start.col + 1, + mergeCell.end.row + 1, + mergeCell.end.col + 1 + ); }); + const buffer = await workbook.xlsx.writeBuffer(); + return buffer; +} - workSheet['!ref'] = tableRange; - workSheet['!merges'] = mergeCells; - workSheet['!cols'] = columnsWidth; - workSheet['!rows'] = rowsWidth; +function getCellFont(cellStyle: CellStyle): Partial { + return { + // name: cellStyle.fontFamily || 'Arial', // only one font familt name + name: 'Arial', + size: cellStyle.fontSize || 10, + bold: cellStyle.fontWeight === 'bold', // only bold or not + italic: cellStyle.fontStyle === 'italic', // only italic or not + color: getColor('#000000'), + underline: cellStyle.underline + }; +} + +function getCellFill(cellStyle: CellStyle): ExcelJS.Fill { + return { + type: 'pattern', + pattern: 'solid', + fgColor: getColor(cellStyle.bgColor) + }; +} - return workSheet; +function getCellBorder(cellStyle: CellStyle): Partial { + return { + top: { + style: 'medium', + color: getColor(cellStyle.borderColor) + }, + left: { + style: 'medium', + color: getColor(cellStyle.borderColor) + }, + bottom: { + style: 'medium', + color: getColor(cellStyle.borderColor) + }, + right: { + style: 'medium', + color: getColor(cellStyle.borderColor) + } + }; } -/** - * @description: comvert cell range to code - * @param {CellRange} cellRange - * @return {*} - */ -function encodeCellRange(cellRange: CellRange) { - const start = encodeCellAddress(cellRange.start.col, cellRange.start.row); - const end = encodeCellAddress(cellRange.end.col, cellRange.end.row); - return `${start}:${end}`; +function getCellAlignment(cellStyle: CellStyle): Partial { + return { + horizontal: cellStyle.textAlign || 'left', + vertical: cellStyle.textBaseline, + wrapText: cellStyle.autoWrapText || false + }; } -/** - * @description: convert cell address to code - * @param {number} col - * @param {number} row - * @return {*} - */ -function encodeCellAddress(col: number, row: number) { - let s = ''; - for (let column = col + 1; column > 0; column = Math.floor((column - 1) / 26)) { - s = String.fromCharCode(((column - 1) % 26) + 65) + s; - } - return s + (row + 1); +function getColor(color: string) { + return { + argb: 'FF' + color.substring(1).toUpperCase() + }; } diff --git a/packages/vtable-export/src/util/download.ts b/packages/vtable-export/src/util/download.ts index e4367a3ec..f5a7770d6 100644 --- a/packages/vtable-export/src/util/download.ts +++ b/packages/vtable-export/src/util/download.ts @@ -8,9 +8,8 @@ export function downloadCsv(str: string, name: string) { saveAs(blob, `${name}.csv`); } -export function downloadExcel(workSheetStr: string, name: string) { - // debugger; - const arrayBuffer = workSheetStr2ArrayBuffer(workSheetStr); +export function downloadExcel(arrayBuffer: ArrayBuffer, name: string) { + // const arrayBuffer = workSheetStr2ArrayBuffer(workSheetStr); const blob = new Blob([arrayBuffer], { type: 'application/octet-stream' }); diff --git a/packages/vtable-export/src/util/encode.ts b/packages/vtable-export/src/util/encode.ts new file mode 100644 index 000000000..06c13078d --- /dev/null +++ b/packages/vtable-export/src/util/encode.ts @@ -0,0 +1,26 @@ +import type { CellRange } from './type'; + +/** + * @description: comvert cell range to code + * @param {CellRange} cellRange + * @return {*} + */ +export function encodeCellRange(cellRange: CellRange) { + const start = encodeCellAddress(cellRange.start.col, cellRange.start.row); + const end = encodeCellAddress(cellRange.end.col, cellRange.end.row); + return `${start}:${end}`; +} + +/** + * @description: convert cell address to code + * @param {number} col + * @param {number} row + * @return {*} + */ +export function encodeCellAddress(col: number, row: number) { + let s = ''; + for (let column = col + 1; column > 0; column = Math.floor((column - 1) / 26)) { + s = String.fromCharCode(((column - 1) % 26) + 65) + s; + } + return s + (row + 1); +} diff --git a/packages/vtable-export/src/util/type.ts b/packages/vtable-export/src/util/type.ts index a4b2c0c50..1cc08245c 100644 --- a/packages/vtable-export/src/util/type.ts +++ b/packages/vtable-export/src/util/type.ts @@ -2,3 +2,4 @@ import type * as VTable from '@visactor/vtable'; export type IVTable = VTable.ListTable | VTable.PivotTable | VTable.PivotChart; export type CellRange = VTable.TYPES.CellRange; +export type CellStyle = VTable.TYPES.CellStyle; From 305ee71a1b53a971f38ab82b6b7f5e406a401533 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Wed, 13 Dec 2023 20:22:57 +0800 Subject: [PATCH 09/35] feat: fix export table style --- packages/vtable-export/demo/list/link.ts | 114 ++++ packages/vtable-export/demo/menu.ts | 13 + .../vtable-export/demo/pivot/pivot-basic.ts | 503 ++++++++++++++++++ .../vtable-export/src/excel/index-xlsx-js.ts | 2 +- packages/vtable-export/src/excel/index.ts | 73 +-- .../vtable-export/src/excel/style-xlsx-js.ts | 53 ++ packages/vtable-export/src/excel/style.ts | 118 ++-- packages/vtable-export/src/util/color.ts | 223 ++++++++ packages/vtable-export/src/util/type.ts | 2 + packages/vtable/src/core/BaseTable.ts | 16 +- packages/vtable/src/ts-types/base-table.ts | 1 + packages/vtable/src/ts-types/style-define.ts | 4 + 12 files changed, 1031 insertions(+), 91 deletions(-) create mode 100644 packages/vtable-export/demo/list/link.ts create mode 100644 packages/vtable-export/demo/pivot/pivot-basic.ts create mode 100644 packages/vtable-export/src/excel/style-xlsx-js.ts create mode 100644 packages/vtable-export/src/util/color.ts diff --git a/packages/vtable-export/demo/list/link.ts b/packages/vtable-export/demo/list/link.ts new file mode 100644 index 000000000..d9b3b5c9d --- /dev/null +++ b/packages/vtable-export/demo/list/link.ts @@ -0,0 +1,114 @@ +import * as VTable from '@visactor/vtable'; +// import { bindDebugTool } from '../../src/scenegraph/debug-tool'; +const ListTable = VTable.ListTable; +const CONTAINER_ID = 'vTable'; + +export function createTable() { + const personsDataSource = [ + { + progress: 100, + id: 1, + name: 'a', + link: 'http://' + window.location.host + '/mock-data/custom-render/flower.jpg' + }, + { + progress: 80, + id: 2, + name: 'b', + link: 'http://' + window.location.host + '/mock-data/custom-render/wolf.jpg' + }, + { + progress: 1, + id: 3, + name: 'c', + link: 'http://' + window.location.host + '/mock-data/custom-render/rabbit.jpg' + }, + { + progress: 55, + id: 4, + name: 'd', + link: 'http://' + window.location.host + '/mock-data/custom-render/cat.jpg' + }, + { + progress: 28, + id: 5, + name: 'e', + link: 'http://' + window.location.host + '/mock-data/custom-render/bear.jpg' + } + ]; + const option: VTable.ListTableConstructorOptions = { + container: document.getElementById(CONTAINER_ID), + columns: [ + { + field: 'progress', + fieldFormat(rec) { + return `已完成${rec.progress}%`; + }, + title: 'progress', + description: '这是一个标题的详细描述', + width: 150, + showSort: true //显示VTable内置排序图标 + }, + { + field: 'id', + title: 'ID', + sort: (v1, v2, order) => { + if (order === 'desc') { + return v1 === v2 ? 0 : v1 > v2 ? -1 : 1; + } + return v1 === v2 ? 0 : v1 > v2 ? 1 : -1; + }, + width: 100 + }, + { + field: 'id', + fieldFormat(rec) { + return `这是第${rec.id}号`; + }, + title: 'ID说明', + description: '这是一个ID详细描述', + sort: (v1, v2, order) => { + if (order === 'desc') { + return v1 === v2 ? 0 : v1 > v2 ? -1 : 1; + } + return v1 === v2 ? 0 : v1 > v2 ? 1 : -1; + }, + width: 150 + }, + { + title: 'Link', + headerStyle: { + textAlign: 'center', + fontWeight: 'bold', + fontSize: 13, + fontFamily: 'sans-serif' + }, + style: { + autoWrapText: true + }, + field: 'link', + width: 300, + cellType: 'link' + } + ], + showFrozenIcon: true, //显示VTable内置冻结列图标 + widthMode: 'standard', + allowFrozenColCount: 2, + heightMode: 'autoHeight' + }; + + const instance = new ListTable(option); + + //设置表格数据 + instance.setRecords(personsDataSource, { + field: 'progress', + order: 'desc' + }); + + // bindDebugTool(instance.scenegraph.stage as any, { + // customGrapicKeys: ['role', '_updateTag'] + // }); + + // 只为了方便控制太调试用,不要拷贝 + window.tableInstance = instance; +} diff --git a/packages/vtable-export/demo/menu.ts b/packages/vtable-export/demo/menu.ts index 3626490c5..c9eabc808 100644 --- a/packages/vtable-export/demo/menu.ts +++ b/packages/vtable-export/demo/menu.ts @@ -5,6 +5,19 @@ export const menus = [ { path: 'list', name: 'list' + }, + { + path: 'list', + name: 'link' + } + ] + }, + { + menu: 'pivotTable', + children: [ + { + path: 'pivot', + name: 'pivot-basic' } ] } diff --git a/packages/vtable-export/demo/pivot/pivot-basic.ts b/packages/vtable-export/demo/pivot/pivot-basic.ts new file mode 100644 index 000000000..217a49345 --- /dev/null +++ b/packages/vtable-export/demo/pivot/pivot-basic.ts @@ -0,0 +1,503 @@ +import * as VTable from '@visactor/vtable'; +// import { bindDebugTool } from '../../src/scenegraph/debug-tool'; +const PivotTable = VTable.PivotTable; +const CONTAINER_ID = 'vTable'; + +function generatePivotDataSource(num, colCount) { + const array = new Array(num); + for (let i = 0; i < num; i++) { + const data = new Array(colCount); + for (let j = 0; j < colCount; j++) { + data[j] = i + j; + } + array[i] = data; + } + return array; +} +const DEFAULT_BAR_COLOR = data => { + const num = (data.percentile ?? 0) * 100; + if (num > 80) { + return '#20a8d8'; + } + if (num > 50) { + return '#4dbd74'; + } + if (num > 20) { + return '#ffc107'; + } + return '#f86c6b'; +}; + +export function createTable() { + const records = generatePivotDataSource(19, 18); + const theme: VTable.TYPES.ITableThemeDefine = { + underlayBackgroundColor: '#F6F6F6', + defaultStyle: { + borderColor: '#000', + color: '#000', + bgColor: '#F6F6F6' + }, + headerStyle: { + bgColor: '#F5F6FA', + frameStyle: { + borderColor: '#00ffff', + borderLineWidth: 2 + } + }, + selectionStyle: { + cellBgColor: 'rgba(130,178,245, 0.2)', + cellBorderColor: '#003fff', + cellBorderLineWidth: 2 + }, + rowHeaderStyle: { + bgColor: '#F3F8FF', + frameStyle: { + borderColor: '#ff00ff', + borderLineWidth: 2 + } + }, + cornerHeaderStyle: { + bgColor: '#CCE0FF', + fontSize: 20, + fontFamily: 'sans-serif', + frameStyle: { + borderColor: '#00ff00', + borderLineWidth: 2 + } + }, + bodyStyle: { + hover: { + cellBgColor: '#CCE0FF', + inlineRowBgColor: '#F3F8FF', + inlineColumnBgColor: '#F3F8FF' + }, + frameStyle: { + borderColor: '#ffff00', + borderLineWidth: 5 + } + }, + frameStyle: { + borderColor: '#000', + borderLineWidth: 1, + borderLineDash: [] + }, + columnResize: { + lineWidth: 1, + lineColor: '#416EFF', + bgColor: '#D9E2FF', + width: 3 + }, + frozenColumnLine: { + shadow: { + width: 24, + startColor: 'rgba(00, 24, 47, 0.06)', + endColor: 'rgba(00, 24, 47, 0)' + } + } + // menuStyle: { + // color: '#000', + // highlightColor: '#2E68CF', + // font: '12px sans-serif', + // highlightFont: '12px sans-serif', + // hoverBgColor: '#EEE' + // } + }; + const option: VTable.PivotTableConstructorOptions = { + columnHeaderTitle: { + title: true, + headerStyle: { + textStick: true + } + }, + columns: [ + { + dimensionKey: '地区', + title: '地区', + headerFormat(value) { + return `${value}地区`; + }, + description(args) { + return args.value; + }, + cornerDescription: '地区维度', + headerStyle: { + textAlign: 'center', + borderColor: 'blue', + color: 'pink', + textStick: true, + bgColor(arg) { + if (arg.cellHeaderPaths.colHeaderPaths && arg.cellHeaderPaths.colHeaderPaths[0].value === '东北') { + return '#bd422a'; + } + if (arg.cellHeaderPaths.colHeaderPaths && arg.cellHeaderPaths.colHeaderPaths[0].value === '华北') { + return '#ff9900'; + } + return 'gray'; + } + }, + // 指标菜单 + dropDownMenu: ['升序排序I', '降序排序I', '冻结列I'], + // corner菜单 + cornerDropDownMenu: ['升序排序C', '降序排序C', '冻结列C'], + drillDown: true + }, + { + dimensionKey: '邮寄方式', + title: '邮寄方式11', + headerFormat(value) { + return `${value}邮寄方式`; + }, + headerStyle: { + textAlign: 'left', + borderColor: 'blue', + color: 'pink', + // lineHeight: '2em', + fontSize: 16, + fontStyle: 'bold', + fontFamily: 'sans-serif', + underline: true, + textStick: true, + bgColor(arg) { + if (arg.cellHeaderPaths.colHeaderPaths && arg.cellHeaderPaths.colHeaderPaths[0].value === '东北') { + return '#bd422a'; + } + if (arg.cellHeaderPaths.colHeaderPaths && arg.cellHeaderPaths.colHeaderPaths[0].value === '华北') { + return '#ff9900'; + } + return 'gray'; + } + }, + drillUp: false + } + ], + rows: [ + { + dimensionKey: '类别', + title: '类别', + drillUp: true, + width: 'auto', + headerStyle: { + textAlign: 'center', + borderColor: 'blue', + color: 'purple', + textBaseline: 'top', + textStick: true, + bgColor: '#6cd26f' + } + }, + { + dimensionKey: '子类别', + title: '子类别', + headerStyle: { + textAlign: 'center', + color: 'blue', + bgColor: '#45b89f' + }, + width: 'auto', + dropDownMenu: ['升序排序I', '降序排序I', '冻结列I'] + // headerType: 'MULTILINETEXT', + } + ], + indicators: [ + { + indicatorKey: '1', + title: '销售额', + format(rec) { + return `${rec.dataValue}%`; + }, + headerStyle: { + color: 'red', + bgColor(arg) { + if (arg.cellHeaderPaths.colHeaderPaths && arg.cellHeaderPaths.colHeaderPaths[0].value === '东北') { + return '#bd422a'; + } + if (arg.cellHeaderPaths.colHeaderPaths && arg.cellHeaderPaths.colHeaderPaths[0].value === '华北') { + return '#ff9900'; + } + return 'gray'; + } + }, + // style: { + // barHeight: '100%', + // // barBgColor: '#aaa', + // // barColor: '#444', + // barBgColor: data => { + // return `rgb(${100 + 100 * (1 - (data.percentile ?? 0))},${100 + 100 * (1 - (data.percentile ?? 0))},${ + // 255 * (1 - (data.percentile ?? 0)) + // })`; + // }, + // barColor: 'transparent' + // }, + // cellType: 'progressbar', + showSort: true + // headerType: 'MULTILINETEXT', + }, + { + indicatorKey: '2', + title: '利润', + format(rec) { + // if (rec.rowDimensions[0].value === '东北') return `${rec.dataValue}%`; + return rec.dataValue; + }, + // cellType: 'progressbar', + // style: { + // barHeight: '50%', + // barBottom: 20, + // barColor: DEFAULT_BAR_COLOR + // }, + showSort: true, + dropDownMenu: ['利润升序排序I', '利润降序排序I', '利润冻结列I'] + } + ], + columnTree: [ + { + dimensionKey: '地区', + value: '东北', + children: [ + { + dimensionKey: '邮寄方式', + value: '一级', + children: [ + { + // dimensionKey: '指标名称', + indicatorKey: '1', + value: '销售额' + }, + { + // dimensionKey: '指标名称', + indicatorKey: '2', + value: '利润' + } + ] + }, + { + dimensionKey: '邮寄方式', + value: '二级', + children: [ + { + // dimensionKey: '指标名称', + indicatorKey: '1', + value: '销售额' + }, + { + // dimensionKey: '指标名称', + indicatorKey: '2', + value: '利润' + } + ] + }, + { + dimensionKey: '邮寄方式', + value: '三级', + children: [ + { + // dimensionKey: '指标名称', + indicatorKey: '1', + value: '销售额' + }, + { + // dimensionKey: '指标名称', + indicatorKey: '2', + value: '利润' + } + ] + } + ] + }, + { + dimensionKey: '地区', + value: '华北', + children: [ + { + dimensionKey: '邮寄方式', + value: '一级', + children: [ + { + indicatorKey: '1', + value: '销售额' + }, + { + indicatorKey: '2', + value: '利润' + } + ] + }, + { + dimensionKey: '邮寄方式', + value: '二级', + children: [ + { + indicatorKey: '1', + value: '销售额' + }, + { + indicatorKey: '2', + value: '利润' + } + ] + }, + { + dimensionKey: '邮寄方式', + value: '三级', + children: [ + { + // dimensionKey: '指标名称', + indicatorKey: '1', + value: '销售额' + }, + { + // dimensionKey: '指标名称', + indicatorKey: '2', + value: '利润' + } + ] + } + ] + }, + { + dimensionKey: '地区', + value: '中南', + children: [ + { + dimensionKey: '邮寄方式', + value: '一级', + children: [ + { + // dimensionKey: '指标名称', + indicatorKey: '1', + value: '销售额' + }, + { + // dimensionKey: '指标名称', + indicatorKey: '2', + value: '利润' + } + ] + }, + { + dimensionKey: '邮寄方式', + value: '二级', + children: [ + { + // dimensionKey: '指标名称', + indicatorKey: '1', + value: '销售额' + }, + { + // dimensionKey: '指标名称', + indicatorKey: '2', + value: '利润' + } + ] + }, + { + dimensionKey: '邮寄方式', + value: '三级', + children: [ + { + // dimensionKey: '指标名称', + indicatorKey: '1', + value: '销售额' + }, + { + // dimensionKey: '指标名称', + indicatorKey: '2', + value: '利润' + } + ] + } + ] + } + ], + rowTree: [ + { + dimensionKey: '类别', + value: '办公用品', + children: [ + { dimensionKey: '子类别', value: '电脑' }, + { dimensionKey: '子类别', value: '装订机' }, + { dimensionKey: '子类别', value: '签字笔' }, + { dimensionKey: '子类别', value: '标签' }, + { dimensionKey: '子类别', value: '收纳柜' }, + { dimensionKey: '子类别', value: '纸张' }, + { dimensionKey: '子类别', value: '电灯' } + ] + }, + { + dimensionKey: '类别', + value: '家具', + children: [ + { dimensionKey: '子类别', value: '衣柜' }, + { dimensionKey: '子类别', value: '沙发' }, + { dimensionKey: '子类别', value: '餐桌' }, + { dimensionKey: '子类别', value: '椅子' }, + { dimensionKey: '子类别', value: '桌子' } + ] + }, + { + dimensionKey: '类别', + value: '餐饮', + children: [ + { dimensionKey: '子类别', value: '锅具' }, + { + dimensionKey: '子类别', + value: '油盐酱醋' + }, + { dimensionKey: '子类别', value: '米面' } + ] + }, + { + dimensionKey: '类别', + value: '技术', + children: [ + { dimensionKey: '子类别', value: '设备' }, + { dimensionKey: '子类别', value: '配件' }, + { dimensionKey: '子类别', value: '电话' }, + { dimensionKey: '子类别', value: '复印机' } + ] + } + ], + corner: { + titleOnDimension: 'column', + headerStyle: { + textAlign: 'center', + borderColor: 'red', + color: 'yellow', + underline: true, + fontSize: 16, + fontStyle: 'bold', + fontFamily: 'sans-serif' + // lineHeight: '20px' + } + }, + indicatorTitle: '指标名称', + // indicatorsAsCol: false, + container: document.getElementById(CONTAINER_ID), + records, + theme, + showFrozenIcon: false, //显示VTable内置冻结列图标 + allowFrozenColCount: 2, + widthMode: 'autoWidth', // 宽度模式:standard 标准模式; adaptive 自动填满容器 + defaultRowHeight: 80, + columnResizeType: 'indicator', // 'column' | 'indicator' | 'all' + tooltip: { + isShowOverflowTextTooltip: true + } + }; + + const instance = new PivotTable(option); + window.tableInstance = instance; + + const { PIVOT_SORT_CLICK } = VTable.PivotTable.EVENT_TYPE; + instance.on(PIVOT_SORT_CLICK, e => { + const order = e.order === 'asc' ? 'desc' : e.order === 'desc' ? 'normal' : 'asc'; + instance.updatePivotSortState([{ dimensions: e.dimensionInfo, order }]); + }); + + // bindDebugTool(instance.scenegraph.stage as any, { + // customGrapicKeys: ['role', '_updateTag'] + // }); + + // 只为了方便控制太调试用,不要拷贝 + window.tableInstance = instance; +} diff --git a/packages/vtable-export/src/excel/index-xlsx-js.ts b/packages/vtable-export/src/excel/index-xlsx-js.ts index 7118532b5..9b1c6ef74 100644 --- a/packages/vtable-export/src/excel/index-xlsx-js.ts +++ b/packages/vtable-export/src/excel/index-xlsx-js.ts @@ -1,5 +1,5 @@ import XLSX from 'xlsx-js-style'; -import { getCellStyle } from './style'; +import { getCellStyle } from './style-xlsx-js'; import type { CellRange, IVTable } from '../util/type'; export function exportVTableToExcel(tableInstance: IVTable) { diff --git a/packages/vtable-export/src/excel/index.ts b/packages/vtable-export/src/excel/index.ts index d855665bf..4c25a288a 100644 --- a/packages/vtable-export/src/excel/index.ts +++ b/packages/vtable-export/src/excel/index.ts @@ -1,17 +1,17 @@ import ExcelJS from 'exceljs'; import { encodeCellAddress } from '../util/encode'; -import type { CellStyle, IVTable } from '../util/type'; +import type { CellType, IVTable } from '../util/type'; +import { getCellAlignment, getCellBorder, getCellFill, getCellFont } from './style'; export async function exportVTableToExcel(tableInstance: IVTable) { const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet('sheet1'); - const columns = []; + const columns = []; const minRow = 0; const maxRow = tableInstance.rowCount - 1; const minCol = 0; const maxCol = tableInstance.colCount - 1; - const mergeCells = []; const mergeCellSet = new Set(); @@ -27,10 +27,11 @@ export async function exportVTableToExcel(tableInstance: IVTable) { const cellValue = tableInstance.getCellValue(col, row); const cellStyle = tableInstance.getCellStyle(col, row); + const cellType = tableInstance.getCellType(col, row); const cell = worksheet.getCell(encodeCellAddress(col, row)); - cell.value = cellValue; - cell.font = getCellFont(cellStyle); + cell.value = getCellValue(cellValue, cellType); + cell.font = getCellFont(cellStyle, cellType); cell.fill = getCellFill(cellStyle); cell.border = getCellBorder(cellStyle); cell.alignment = getCellAlignment(cellStyle); @@ -59,57 +60,13 @@ export async function exportVTableToExcel(tableInstance: IVTable) { return buffer; } -function getCellFont(cellStyle: CellStyle): Partial { - return { - // name: cellStyle.fontFamily || 'Arial', // only one font familt name - name: 'Arial', - size: cellStyle.fontSize || 10, - bold: cellStyle.fontWeight === 'bold', // only bold or not - italic: cellStyle.fontStyle === 'italic', // only italic or not - color: getColor('#000000'), - underline: cellStyle.underline - }; -} - -function getCellFill(cellStyle: CellStyle): ExcelJS.Fill { - return { - type: 'pattern', - pattern: 'solid', - fgColor: getColor(cellStyle.bgColor) - }; -} - -function getCellBorder(cellStyle: CellStyle): Partial { - return { - top: { - style: 'medium', - color: getColor(cellStyle.borderColor) - }, - left: { - style: 'medium', - color: getColor(cellStyle.borderColor) - }, - bottom: { - style: 'medium', - color: getColor(cellStyle.borderColor) - }, - right: { - style: 'medium', - color: getColor(cellStyle.borderColor) - } - }; -} - -function getCellAlignment(cellStyle: CellStyle): Partial { - return { - horizontal: cellStyle.textAlign || 'left', - vertical: cellStyle.textBaseline, - wrapText: cellStyle.autoWrapText || false - }; -} - -function getColor(color: string) { - return { - argb: 'FF' + color.substring(1).toUpperCase() - }; +function getCellValue(cellValue: string, cellType: CellType) { + if (cellType === 'link') { + return { + text: cellValue, + hyperlink: cellValue, + tooltip: cellValue + }; + } + return cellValue; } diff --git a/packages/vtable-export/src/excel/style-xlsx-js.ts b/packages/vtable-export/src/excel/style-xlsx-js.ts new file mode 100644 index 000000000..ed9cbc178 --- /dev/null +++ b/packages/vtable-export/src/excel/style-xlsx-js.ts @@ -0,0 +1,53 @@ +import type { IVTable } from '../util/type'; +import type XLSX from 'xlsx-js-style'; +import type * as VTable from '@visactor/vtable'; + +export function getCellStyle(col: number, row: number, tableInstance: IVTable): XLSX.CellStyle { + const cellStyle = tableInstance.getCellStyle(col, row); + return { + alignment: getCellAlignment(col, row, cellStyle), + border: getCellBorder(col, row, cellStyle), + fill: getCellFill(col, row, cellStyle), + font: getCellFont(col, row, cellStyle) + }; +} + +function getCellAlignment(col: number, row: number, cellStyle: VTable.TYPES.CellStyle): XLSX.CellStyle['alignment'] { + return { + horizontal: cellStyle.textAlign || 'left', + vertical: 'center', // no middle, use center + wrapText: cellStyle.autoWrapText || false + }; +} + +function getCellBorder(col: number, row: number, cellStyle: VTable.TYPES.CellStyle): XLSX.CellStyle['border'] { + return { + top: { color: getColor(cellStyle.borderColor) }, + left: { color: getColor(cellStyle.borderColor) }, + bottom: { color: getColor(cellStyle.borderColor) }, + right: { color: getColor(cellStyle.borderColor) } + }; +} + +function getCellFill(col: number, row: number, cellStyle: VTable.TYPES.CellStyle): XLSX.CellStyle['fill'] { + return { + fgColor: getColor(cellStyle.bgColor) + }; +} + +function getCellFont(col: number, row: number, cellStyle: VTable.TYPES.CellStyle): XLSX.CellStyle['font'] { + return { + name: cellStyle.fontFamily || 'Arial', // only one font familt name + sz: cellStyle.fontSize || 10, + bold: cellStyle.fontWeight === 'bold', // only bold or not + italic: cellStyle.fontStyle === 'italic', // only italic or not + color: getColor(cellStyle.color), + underline: cellStyle.underline + }; +} + +function getColor(color: string) { + return { + rgb: color.substring(1) + }; +} diff --git a/packages/vtable-export/src/excel/style.ts b/packages/vtable-export/src/excel/style.ts index ed9cbc178..af6368a25 100644 --- a/packages/vtable-export/src/excel/style.ts +++ b/packages/vtable-export/src/excel/style.ts @@ -1,53 +1,109 @@ -import type { IVTable } from '../util/type'; -import type XLSX from 'xlsx-js-style'; -import type * as VTable from '@visactor/vtable'; +import type ExcelJS from 'exceljs'; +import { colorStringToRGB, rgbaToHex } from '../util/color'; +import type { CellStyle, CellType, LineDashsDef } from '../util/type'; -export function getCellStyle(col: number, row: number, tableInstance: IVTable): XLSX.CellStyle { - const cellStyle = tableInstance.getCellStyle(col, row); +export function getCellFont(cellStyle: CellStyle, cellType: CellType): Partial { return { - alignment: getCellAlignment(col, row, cellStyle), - border: getCellBorder(col, row, cellStyle), - fill: getCellFill(col, row, cellStyle), - font: getCellFont(col, row, cellStyle) + name: getFirstFontFromFontFamily(cellStyle.fontFamily as string) || 'Arial', // only one font family name + size: cellStyle.fontSize || 10, + bold: cellStyle.fontWeight === 'bold', // only bold or not + italic: cellStyle.fontStyle === 'italic', // only italic or not + color: getColor(cellType === 'link' ? (cellStyle._linkColor as string) : (cellStyle.color as string)), + underline: cellStyle.underline }; } -function getCellAlignment(col: number, row: number, cellStyle: VTable.TYPES.CellStyle): XLSX.CellStyle['alignment'] { - return { - horizontal: cellStyle.textAlign || 'left', - vertical: 'center', // no middle, use center - wrapText: cellStyle.autoWrapText || false - }; +function getFirstFontFromFontFamily(fontFamily: string) { + const fonts = fontFamily.split(',').map(font => font.trim()); + return fonts[0]; } -function getCellBorder(col: number, row: number, cellStyle: VTable.TYPES.CellStyle): XLSX.CellStyle['border'] { +export function getCellFill(cellStyle: CellStyle): ExcelJS.Fill { return { - top: { color: getColor(cellStyle.borderColor) }, - left: { color: getColor(cellStyle.borderColor) }, - bottom: { color: getColor(cellStyle.borderColor) }, - right: { color: getColor(cellStyle.borderColor) } + type: 'pattern', + pattern: 'solid', + fgColor: getColor(cellStyle.bgColor as string) }; } -function getCellFill(col: number, row: number, cellStyle: VTable.TYPES.CellStyle): XLSX.CellStyle['fill'] { +export function getCellBorder(cellStyle: CellStyle): Partial { + const { borderColor, borderLineWidth, borderLineDash, _strokeArrayWidth, _strokeArrayColor } = cellStyle; + if (_strokeArrayColor || _strokeArrayWidth) { + const border: Partial = {}; + if (!((_strokeArrayColor && !_strokeArrayColor[0]) || (_strokeArrayWidth && !_strokeArrayWidth[0]))) { + border.top = { + style: getBorderStyle((_strokeArrayWidth?.[0] as number) ?? (borderLineWidth as number), borderLineDash), + color: getColor((_strokeArrayColor?.[0] as string) ?? (borderColor as string)) + }; + } + if (!((_strokeArrayColor && !_strokeArrayColor[1]) || (_strokeArrayWidth && !_strokeArrayWidth[1]))) { + border.right = { + style: getBorderStyle((_strokeArrayWidth?.[1] as number) ?? (borderLineWidth as number), borderLineDash), + color: getColor(_strokeArrayColor?.[1] as string) + }; + } + if (!((_strokeArrayColor && !_strokeArrayColor[2]) || (_strokeArrayWidth && !_strokeArrayWidth[2]))) { + border.bottom = { + style: getBorderStyle((_strokeArrayWidth?.[2] as number) ?? (borderLineWidth as number), borderLineDash), + color: getColor(_strokeArrayColor?.[2] as string) + }; + } + if (!((_strokeArrayColor && !_strokeArrayColor[3]) || (_strokeArrayWidth && !_strokeArrayWidth[3]))) { + border.left = { + style: getBorderStyle((_strokeArrayWidth?.[3] as number) ?? (borderLineWidth as number), borderLineDash), + color: getColor(_strokeArrayColor?.[3] as string) + }; + } + return border; + } + + if (borderLineWidth === 0) { + return {}; + } + const border = { + style: getBorderStyle(borderLineWidth as number, borderLineDash), + color: getColor(borderColor as string) + }; return { - fgColor: getColor(cellStyle.bgColor) + top: border, + left: border, + bottom: border, + right: border }; } -function getCellFont(col: number, row: number, cellStyle: VTable.TYPES.CellStyle): XLSX.CellStyle['font'] { +function getBorderStyle(lineWidth: number, borderLineDash: LineDashsDef): ExcelJS.BorderStyle { + // hair:0.5 + // thin:1 + // medium:2.0 + // thick:3.0 + if (borderLineDash && borderLineDash.length) { + if (lineWidth <= 2) { + return 'dashed'; + } + return 'mediumDashed'; + } + if (lineWidth <= 0.5) { + return 'hair'; + } else if (lineWidth <= 1) { + return 'thin'; + } else if (lineWidth <= 2) { + return 'medium'; + } + return 'thick'; +} + +export function getCellAlignment(cellStyle: CellStyle): Partial { return { - name: cellStyle.fontFamily || 'Arial', // only one font familt name - sz: cellStyle.fontSize || 10, - bold: cellStyle.fontWeight === 'bold', // only bold or not - italic: cellStyle.fontStyle === 'italic', // only italic or not - color: getColor(cellStyle.color), - underline: cellStyle.underline - }; + horizontal: cellStyle.textAlign || 'left', + vertical: cellStyle.textBaseline || 'middle', + wrapText: cellStyle.autoWrapText || false + } as any; } function getColor(color: string) { + // to do: support gradient color return { - rgb: color.substring(1) + argb: rgbaToHex(colorStringToRGB(color)) }; } diff --git a/packages/vtable-export/src/util/color.ts b/packages/vtable-export/src/util/color.ts new file mode 100644 index 000000000..25be6cc00 --- /dev/null +++ b/packages/vtable-export/src/util/color.ts @@ -0,0 +1,223 @@ +export function colorStringToRGB(colorString: string) { + if (colorString.startsWith('#')) { + // 处理十六进制颜色值(例如:#RRGGBB 或 #RGB) + let hex = colorString.substring(1); + + // 处理缩写的十六进制颜色值(例如:#RGB) + if (hex.length === 3) { + hex = hex.replace(/(.)/g, '$1$1'); + } + + const r = parseInt(hex.substring(0, 2), 16); + const g = parseInt(hex.substring(2, 4), 16); + const b = parseInt(hex.substring(4, 6), 16); + return [r, g, b]; + } else if (colorString.startsWith('rgb(')) { + // 处理RGB颜色值(例如:rgb(R, G, B)) + const values = colorString + .substring(4, colorString.length - 1) + .split(',') + .map(Number); + return values; + } else if (colorString.startsWith('rgba(')) { + // 处理RGBA颜色值(例如:rgba(R, G, B, A)) + const values = colorString + .substring(5, colorString.length - 1) + .split(',') + .map(Number); + return values; + } else if (DEFAULT_COLORS[colorString]) { + return rgb(DEFAULT_COLORS[colorString]); + } else if (DEFAULT_COLORS_OPACITY[colorString]) { + return rgba(DEFAULT_COLORS_OPACITY[colorString]); + } + + throw new Error('Unsupported color format'); +} + +export function rgbaToHex(rgbaArray: number[]) { + if (rgbaArray.length === 3) { + rgbaArray.push(1); + } + + if (rgbaArray.length !== 4) { + throw new Error('Invalid RGBA array'); + } + + const [r, g, b, a] = rgbaArray.map(Math.round); + const alphaHex = Math.round(a * 255) + .toString(16) + .padStart(2, '0'); + + return `${alphaHex}${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b + .toString(16) + .padStart(2, '0')}`; +} + +function rgb(value: number) { + return [(value as number) >> 16, ((value as number) >> 8) & 0xff, (value as number) & 0xff, 1]; +} + +function rgba(value: number) { + return [ + (value as number) >>> 24, + ((value as number) >>> 16) & 0xff, + ((value as number) >>> 8) & 0xff, + (value as number) & 0xff + ]; +} + +const DEFAULT_COLORS_OPACITY = { + transparent: 0xffffff00 +}; + +export const DEFAULT_COLORS = { + aliceblue: 0xf0f8ff, + antiquewhite: 0xfaebd7, + aqua: 0x00ffff, + aquamarine: 0x7fffd4, + azure: 0xf0ffff, + beige: 0xf5f5dc, + bisque: 0xffe4c4, + black: 0x000000, + blanchedalmond: 0xffebcd, + blue: 0x0000ff, + blueviolet: 0x8a2be2, + brown: 0xa52a2a, + burlywood: 0xdeb887, + cadetblue: 0x5f9ea0, + chartreuse: 0x7fff00, + chocolate: 0xd2691e, + coral: 0xff7f50, + cornflowerblue: 0x6495ed, + cornsilk: 0xfff8dc, + crimson: 0xdc143c, + cyan: 0x00ffff, + darkblue: 0x00008b, + darkcyan: 0x008b8b, + darkgoldenrod: 0xb8860b, + darkgray: 0xa9a9a9, + darkgreen: 0x006400, + darkgrey: 0xa9a9a9, + darkkhaki: 0xbdb76b, + darkmagenta: 0x8b008b, + darkolivegreen: 0x556b2f, + darkorange: 0xff8c00, + darkorchid: 0x9932cc, + darkred: 0x8b0000, + darksalmon: 0xe9967a, + darkseagreen: 0x8fbc8f, + darkslateblue: 0x483d8b, + darkslategray: 0x2f4f4f, + darkslategrey: 0x2f4f4f, + darkturquoise: 0x00ced1, + darkviolet: 0x9400d3, + deeppink: 0xff1493, + deepskyblue: 0x00bfff, + dimgray: 0x696969, + dimgrey: 0x696969, + dodgerblue: 0x1e90ff, + firebrick: 0xb22222, + floralwhite: 0xfffaf0, + forestgreen: 0x228b22, + fuchsia: 0xff00ff, + gainsboro: 0xdcdcdc, + ghostwhite: 0xf8f8ff, + gold: 0xffd700, + goldenrod: 0xdaa520, + gray: 0x808080, + green: 0x008000, + greenyellow: 0xadff2f, + grey: 0x808080, + honeydew: 0xf0fff0, + hotpink: 0xff69b4, + indianred: 0xcd5c5c, + indigo: 0x4b0082, + ivory: 0xfffff0, + khaki: 0xf0e68c, + lavender: 0xe6e6fa, + lavenderblush: 0xfff0f5, + lawngreen: 0x7cfc00, + lemonchiffon: 0xfffacd, + lightblue: 0xadd8e6, + lightcoral: 0xf08080, + lightcyan: 0xe0ffff, + lightgoldenrodyellow: 0xfafad2, + lightgray: 0xd3d3d3, + lightgreen: 0x90ee90, + lightgrey: 0xd3d3d3, + lightpink: 0xffb6c1, + lightsalmon: 0xffa07a, + lightseagreen: 0x20b2aa, + lightskyblue: 0x87cefa, + lightslategray: 0x778899, + lightslategrey: 0x778899, + lightsteelblue: 0xb0c4de, + lightyellow: 0xffffe0, + lime: 0x00ff00, + limegreen: 0x32cd32, + linen: 0xfaf0e6, + magenta: 0xff00ff, + maroon: 0x800000, + mediumaquamarine: 0x66cdaa, + mediumblue: 0x0000cd, + mediumorchid: 0xba55d3, + mediumpurple: 0x9370db, + mediumseagreen: 0x3cb371, + mediumslateblue: 0x7b68ee, + mediumspringgreen: 0x00fa9a, + mediumturquoise: 0x48d1cc, + mediumvioletred: 0xc71585, + midnightblue: 0x191970, + mintcream: 0xf5fffa, + mistyrose: 0xffe4e1, + moccasin: 0xffe4b5, + navajowhite: 0xffdead, + navy: 0x000080, + oldlace: 0xfdf5e6, + olive: 0x808000, + olivedrab: 0x6b8e23, + orange: 0xffa500, + orangered: 0xff4500, + orchid: 0xda70d6, + palegoldenrod: 0xeee8aa, + palegreen: 0x98fb98, + paleturquoise: 0xafeeee, + palevioletred: 0xdb7093, + papayawhip: 0xffefd5, + peachpuff: 0xffdab9, + peru: 0xcd853f, + pink: 0xffc0cb, + plum: 0xdda0dd, + powderblue: 0xb0e0e6, + purple: 0x800080, + rebeccapurple: 0x663399, + red: 0xff0000, + rosybrown: 0xbc8f8f, + royalblue: 0x4169e1, + saddlebrown: 0x8b4513, + salmon: 0xfa8072, + sandybrown: 0xf4a460, + seagreen: 0x2e8b57, + seashell: 0xfff5ee, + sienna: 0xa0522d, + silver: 0xc0c0c0, + skyblue: 0x87ceeb, + slateblue: 0x6a5acd, + slategray: 0x708090, + slategrey: 0x708090, + snow: 0xfffafa, + springgreen: 0x00ff7f, + steelblue: 0x4682b4, + tan: 0xd2b48c, + teal: 0x008080, + thistle: 0xd8bfd8, + tomato: 0xff6347, + turquoise: 0x40e0d0, + violet: 0xee82ee, + wheat: 0xf5deb3, + white: 0xffffff, + whitesmoke: 0xf5f5f5, + yellow: 0xffff00, + yellowgreen: 0x9acd32 +}; diff --git a/packages/vtable-export/src/util/type.ts b/packages/vtable-export/src/util/type.ts index 1cc08245c..55937afb6 100644 --- a/packages/vtable-export/src/util/type.ts +++ b/packages/vtable-export/src/util/type.ts @@ -3,3 +3,5 @@ import type * as VTable from '@visactor/vtable'; export type IVTable = VTable.ListTable | VTable.PivotTable | VTable.PivotChart; export type CellRange = VTable.TYPES.CellRange; export type CellStyle = VTable.TYPES.CellStyle; +export type LineDashsDef = VTable.TYPES.LineDashsDef; +export type CellType = VTable.TYPES.ColumnTypeOption; diff --git a/packages/vtable/src/core/BaseTable.ts b/packages/vtable/src/core/BaseTable.ts index 73efb4efa..5320680f3 100644 --- a/packages/vtable/src/core/BaseTable.ts +++ b/packages/vtable/src/core/BaseTable.ts @@ -2501,6 +2501,17 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { const cellType = this.internalProps.layoutMap.getBody(col, row).cellType; return getProp('cellType', { cellType }, col, row, this); } + + getCellType(col: number, row: number): ColumnTypeOption { + let cellType; + if (this.isHeader(col, row)) { + cellType = this.internalProps.layoutMap.getHeader(col, row).headerType; + } else { + cellType = this.internalProps.layoutMap.getBody(col, row).cellType; + } + return getProp('cellType', { cellType }, col, row, this); + } + /** * 根据行列号获取对应的字段名 * @param {number} col column index. @@ -3112,7 +3123,10 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { // lineThroughDash: (theme.text as any).lineThroughDash padding: theme._vtable.padding, underlineWidth: theme.text.underline, - lineThroughLineWidth: theme.text.lineThrough + lineThroughLineWidth: theme.text.lineThrough, + _strokeArrayWidth: (theme.group as any).strokeArrayWidth, + _strokeArrayColor: (theme.group as any).strokeArrayColor, + _linkColor: getProp('linkColor', actStyle, col, row, this) }; } /** diff --git a/packages/vtable/src/ts-types/base-table.ts b/packages/vtable/src/ts-types/base-table.ts index 11a1effca..fb8ed49db 100644 --- a/packages/vtable/src/ts-types/base-table.ts +++ b/packages/vtable/src/ts-types/base-table.ts @@ -561,6 +561,7 @@ export interface BaseTableAPI { _getBodyLayoutMap: (col: number, row: number) => ColumnData | IndicatorData; getBodyColumnDefine: (col: number, row: number) => ColumnDefine; getBodyColumnType: (col: number, row: number) => ColumnTypeOption; + getCellType: (col: number, row: number) => ColumnTypeOption; fireListeners: ( type: TYPE, event: TableEventHandlersEventArgumentMap[TYPE] diff --git a/packages/vtable/src/ts-types/style-define.ts b/packages/vtable/src/ts-types/style-define.ts index 8809e1a62..d569e87c3 100644 --- a/packages/vtable/src/ts-types/style-define.ts +++ b/packages/vtable/src/ts-types/style-define.ts @@ -104,4 +104,8 @@ export type CellStyle = { // lineThroughColor: CanvasRenderingContext2D['strokeStyle']; // lineThroughDash: number[]; lineThroughLineWidth: number; + + _strokeArrayWidth: number[]; + _strokeArrayColor: string[]; + _linkColor: CanvasRenderingContext2D['fillStyle']; }; From 536f63e546b6928321033f0f7aee0d025b79d947 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Wed, 13 Dec 2023 21:11:11 +0800 Subject: [PATCH 10/35] feat: add excel table frozen --- packages/vtable-export/demo/list/list.ts | 2 +- packages/vtable-export/src/excel/index.ts | 30 +++++++++++++++++-- .../vtable/__tests__/listTable-1W.test.ts | 5 +++- packages/vtable/__tests__/listTable.test.ts | 5 +++- .../options/listTable-autoRowHeight.test.ts | 5 +++- packages/vtable/__tests__/pivotTable.test.ts | 10 +++++-- 6 files changed, 49 insertions(+), 8 deletions(-) diff --git a/packages/vtable-export/demo/list/list.ts b/packages/vtable-export/demo/list/list.ts index 692c09a32..89e31ac52 100644 --- a/packages/vtable-export/demo/list/list.ts +++ b/packages/vtable-export/demo/list/list.ts @@ -15,7 +15,7 @@ const generatePersons = count => { }; export function createTable() { - const records = generatePersons(10); + const records = generatePersons(100); const columns: VTable.ColumnsDefine = [ { field: '', diff --git a/packages/vtable-export/src/excel/index.ts b/packages/vtable-export/src/excel/index.ts index 4c25a288a..332b96940 100644 --- a/packages/vtable-export/src/excel/index.ts +++ b/packages/vtable-export/src/excel/index.ts @@ -6,6 +6,7 @@ import { getCellAlignment, getCellBorder, getCellFill, getCellFont } from './sty export async function exportVTableToExcel(tableInstance: IVTable) { const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet('sheet1'); + worksheet.properties.defaultRowHeight = 40; const columns = []; const minRow = 0; @@ -17,12 +18,13 @@ export async function exportVTableToExcel(tableInstance: IVTable) { for (let col = minCol; col <= maxCol; col++) { const colWith = tableInstance.getColWidth(col); - columns[col] = { width: colWith / 7 }; + columns[col] = { width: colWith / 6 }; for (let row = minRow; row <= maxRow; row++) { if (col === minCol) { const rowHeight = tableInstance.getRowHeight(row); const worksheetRow = worksheet.getRow(row + 1); - worksheetRow.height = rowHeight * 0.75; + // worksheetRow.height = rowHeight * 0.75; + worksheetRow.height = rowHeight; } const cellValue = tableInstance.getCellValue(col, row); @@ -56,6 +58,30 @@ export async function exportVTableToExcel(tableInstance: IVTable) { mergeCell.end.col + 1 ); }); + + // frozen + const frozenView: ExcelJS.WorksheetViewFrozen[] = []; + // top frozen + if (tableInstance.frozenRowCount > 0) { + frozenView.push({ + state: 'frozen', + ySplit: tableInstance.frozenRowCount, + // activeCell: 'A1', + topLeftCell: encodeCellAddress(0, tableInstance.frozenRowCount) + }); + } + // left frozen + if (tableInstance.frozenColCount > 0) { + frozenView.push({ + state: 'frozen', + xSplit: tableInstance.frozenColCount, + // activeCell: 'A1', + topLeftCell: encodeCellAddress(tableInstance.frozenColCount, 0) + }); + } + // not support bottom&right frozen + worksheet.views = frozenView; + const buffer = await workbook.xlsx.writeBuffer(); return buffer; } diff --git a/packages/vtable/__tests__/listTable-1W.test.ts b/packages/vtable/__tests__/listTable-1W.test.ts index 3b696fec5..aa5219851 100644 --- a/packages/vtable/__tests__/listTable-1W.test.ts +++ b/packages/vtable/__tests__/listTable-1W.test.ts @@ -203,7 +203,10 @@ describe('listTable-1W init test', () => { textBaseline: 'middle', textOverflow: 'ellipsis', underline: false, - underlineWidth: undefined + underlineWidth: undefined, + _linkColor: '#3772ff', + _strokeArrayColor: undefined, + _strokeArrayWidth: undefined }); }); test('listTable-1W getRowHeight colWidth', () => { diff --git a/packages/vtable/__tests__/listTable.test.ts b/packages/vtable/__tests__/listTable.test.ts index 5c98492ea..20f1a5eb0 100644 --- a/packages/vtable/__tests__/listTable.test.ts +++ b/packages/vtable/__tests__/listTable.test.ts @@ -138,7 +138,10 @@ describe('listTable init test', () => { underlineWidth: undefined, lineThrough: false, lineThroughLineWidth: undefined, - padding: [10, 16, 10, 16] + padding: [10, 16, 10, 16], + _linkColor: '#3772ff', + _strokeArrayColor: undefined, + _strokeArrayWidth: undefined }); }); test('listTable updateOption records&autoWidth&widthMode', () => { diff --git a/packages/vtable/__tests__/options/listTable-autoRowHeight.test.ts b/packages/vtable/__tests__/options/listTable-autoRowHeight.test.ts index a7d181d34..02b49b204 100644 --- a/packages/vtable/__tests__/options/listTable-autoRowHeight.test.ts +++ b/packages/vtable/__tests__/options/listTable-autoRowHeight.test.ts @@ -119,7 +119,10 @@ describe('listTable-autoRowHeight init test', () => { // lineThroughDash: undefined, underline: false, // underlineDash: undefined - padding: [10, 0, 10, 60] + padding: [10, 0, 10, 60], + _linkColor: '#3772ff', + _strokeArrayColor: undefined, + _strokeArrayWidth: undefined }); }); diff --git a/packages/vtable/__tests__/pivotTable.test.ts b/packages/vtable/__tests__/pivotTable.test.ts index 7fa6e3f2f..90449c380 100644 --- a/packages/vtable/__tests__/pivotTable.test.ts +++ b/packages/vtable/__tests__/pivotTable.test.ts @@ -495,7 +495,10 @@ describe('pivotTable init test', () => { // lineThroughDash: undefined, underline: false, // underlineDash: undefined - padding: [10, 16, 10, 16] + padding: [10, 16, 10, 16], + _linkColor: '#3772ff', + _strokeArrayColor: undefined, + _strokeArrayWidth: undefined }); }); test('pivotTable API getCellStyle', () => { @@ -520,7 +523,10 @@ describe('pivotTable init test', () => { // lineThroughDash: undefined, underline: false, // underlineDash: undefined - padding: [10, 16, 10, 16] + padding: [10, 16, 10, 16], + _linkColor: '#3772ff', + _strokeArrayColor: undefined, + _strokeArrayWidth: undefined }); }); test('pivotTable getCellRange', () => { From e9ac5188b975163104a41dc4ef95d883db534068 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Thu, 14 Dec 2023 11:57:56 +0800 Subject: [PATCH 11/35] feat: optimize diffCellIndices in toggleHierarchyState() --- .../vtable/develop_2023-12-14-03-48.json | 10 +++ .../vtable/examples/list/list-tree-20000.ts | 74 +++++++++++++++++++ packages/vtable/examples/menu.ts | 4 + packages/vtable/src/data/DataSource.ts | 8 +- packages/vtable/src/tools/diff-cell.ts | 59 ++++++++++++++- 5 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 common/changes/@visactor/vtable/develop_2023-12-14-03-48.json create mode 100644 packages/vtable/examples/list/list-tree-20000.ts diff --git a/common/changes/@visactor/vtable/develop_2023-12-14-03-48.json b/common/changes/@visactor/vtable/develop_2023-12-14-03-48.json new file mode 100644 index 000000000..a091db2ef --- /dev/null +++ b/common/changes/@visactor/vtable/develop_2023-12-14-03-48.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vtable", + "comment": "feat: optimize diffCellIndices in toggleHierarchyState()", + "type": "none" + } + ], + "packageName": "@visactor/vtable" +} \ No newline at end of file diff --git a/packages/vtable/examples/list/list-tree-20000.ts b/packages/vtable/examples/list/list-tree-20000.ts new file mode 100644 index 000000000..22bd509d0 --- /dev/null +++ b/packages/vtable/examples/list/list-tree-20000.ts @@ -0,0 +1,74 @@ +/* eslint-disable */ +import * as VTable from '../../src'; +import VChart from '@visactor/vchart'; +import { bindDebugTool } from '../../src/scenegraph/debug-tool'; + +const CONTAINER_ID = 'vTable'; +VTable.register.chartModule('vchart', VChart); +export function createTable() { + let tableInstance; + fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/company_struct.json') + .then(res => res.json()) + .then(data => { + const record = []; + for (let i = 0; i < 20000; i++) { + record.push(data[0]); + } + + const columns = [ + { + field: 'group', + title: 'department', + width: 'auto', + tree: true, + fieldFormat(rec) { + return rec['department'] ?? rec['group'] ?? rec['name']; + } + }, + { + field: 'total_children', + title: 'memebers count', + width: 'auto', + fieldFormat(rec) { + if (rec?.['position']) { + return `position: ${rec['position']}`; + } else return rec?.['total_children']; + } + }, + { + field: 'monthly_expense', + title: 'monthly expense', + width: 'auto', + fieldFormat(rec) { + if (rec?.['salary']) { + return `salary: ${rec['salary']}`; + } else return rec?.['monthly_expense']; + } + }, + { + field: 'new_hires_this_month', + title: 'new hires this month', + width: 'auto' + }, + { + field: 'resignations_this_month', + title: 'resignations this month', + width: 'auto' + }, + { + field: 'complaints_and_suggestions', + title: 'recived complaints counts', + width: 'auto' + } + ]; + + const option = { + // records:data, + records: record, + columns, + widthMode: 'standard' + }; + tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); + window['tableInstance'] = tableInstance; + }); +} diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index fa41d44f1..f59cd5163 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -43,6 +43,10 @@ export const menus = [ path: 'list', name: 'list-tree' }, + { + path: 'list', + name: 'list-tree-20000' + }, { path: 'list', name: 'TODO-list-tree-checkbox' diff --git a/packages/vtable/src/data/DataSource.ts b/packages/vtable/src/data/DataSource.ts index 7bd5b5d44..940405485 100644 --- a/packages/vtable/src/data/DataSource.ts +++ b/packages/vtable/src/data/DataSource.ts @@ -14,7 +14,7 @@ import { HierarchyState } from '../ts-types'; import { applyChainSafe, getOrApply, obj, isPromise, emptyFn } from '../tools/helper'; import { EventTarget } from '../event/EventTarget'; import { getValueByPath, isAllDigits } from '../tools/util'; -import { diffCellIndices } from '../tools/diff-cell'; +import { calculateArrayDiff } from '../tools/diff-cell'; import { cloneDeep, isValid } from '@visactor/vutils'; import type { BaseTableAPI } from '../ts-types/base-table'; import { TABLE_EVENT_TYPE } from '../core/TABLE_EVENT_TYPE'; @@ -390,7 +390,11 @@ export class DataSource extends EventTarget implements DataSourceAPI { // 变更了pagerConfig所以需要更新分页数据 TODO待定 因为只关注根节点的数量的话 可能不会影响到 this.updatePagerData(); - return diffCellIndices(oldIndexedData, this.currentIndexedData); + const newDiff = calculateArrayDiff(oldIndexedData, this.currentIndexedData); + // const oldDiff = diffCellIndices(oldIndexedData, this.currentIndexedData); + + // return oldDiff; + return newDiff; } /** * 某个节点状态由折叠变为展开,往this.currentIndexedData中插入展开后的新增节点,注意需要递归,因为展开节点下面的子节点也能是展开状态 diff --git a/packages/vtable/src/tools/diff-cell.ts b/packages/vtable/src/tools/diff-cell.ts index 8beca0fd6..10ba75ccc 100644 --- a/packages/vtable/src/tools/diff-cell.ts +++ b/packages/vtable/src/tools/diff-cell.ts @@ -49,7 +49,7 @@ export function diffCellAddress( } // find diff between two arrays -export function diffCellIndices(oldIndexedData: (number | number[])[], currentIndexedData: (number | number[])[]) { +function diffCellIndices(oldIndexedData: (number | number[])[], currentIndexedData: (number | number[])[]) { const add = []; const remove = []; // find removed indices @@ -99,3 +99,60 @@ function checkIndex(oldIndex: number | number[], newIndex: number | number[]): b } return true; } + +export function calculateArrayDiff(originalArray: (number | number[])[], targetArray: (number | number[])[]) { + const add = []; + const remove = []; + + const originalMap = new Map(); + for (let i = 0; i < originalArray.length; i++) { + const element = originalArray[i]; + const key = JSON.stringify(element); + if (originalMap.has(key)) { + originalMap.get(key).push(i); + } else { + originalMap.set(key, [i]); + } + } + + for (let i = 0; i < targetArray.length; i++) { + const element = targetArray[i]; + const key = JSON.stringify(element); + if (!originalMap.has(key)) { + add.push(i); + } else { + const indices = originalMap.get(key); + indices.shift(); // Remove the first index + if (indices.length === 0) { + originalMap.delete(key); + } + } + } + + for (let i = 0; i < originalArray.length; i++) { + const element = originalArray[i]; + if (!targetArray.some(item => isEqual(item, element))) { + remove.push(i); + } + } + + return { add, remove }; +} + +function isEqual(arr1: any, arr2: any) { + if (arr1 === arr2) { + return true; + } + + if (arr1.length !== arr2.length) { + return false; + } + + for (let i = 0; i < arr1.length; i++) { + if (arr1[i] !== arr2[i]) { + return false; + } + } + + return true; +} From 2ea8a2ef85145f892fb8edab3dafa1da9c359d04 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Thu, 14 Dec 2023 14:15:51 +0800 Subject: [PATCH 12/35] fix: fix disableHover bottom error --- packages/vtable/src/state/hover/is-cell-hover.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vtable/src/state/hover/is-cell-hover.ts b/packages/vtable/src/state/hover/is-cell-hover.ts index 18c3a0596..aa8f60c86 100644 --- a/packages/vtable/src/state/hover/is-cell-hover.ts +++ b/packages/vtable/src/state/hover/is-cell-hover.ts @@ -83,10 +83,10 @@ export function isCellHover(state: StateManager, col: number, row: number): stri let cellDisable; if (isHeader) { const define = table.getHeaderDefine(col, row); - cellDisable = define.disableHeaderHover; + cellDisable = define?.disableHeaderHover; } else { const define = table.getBodyColumnDefine(col, row); - cellDisable = define.disableHover; + cellDisable = define?.disableHover; } if (cellDisable) { From 74198fca6a26296aebb3d3f6d7f8464de6117f1f Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Thu, 14 Dec 2023 14:26:04 +0800 Subject: [PATCH 13/35] chore: add rush change --- .../fix-fix-disable-hover-error_2023-12-14-06-25.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 common/changes/@visactor/vtable/fix-fix-disable-hover-error_2023-12-14-06-25.json diff --git a/common/changes/@visactor/vtable/fix-fix-disable-hover-error_2023-12-14-06-25.json b/common/changes/@visactor/vtable/fix-fix-disable-hover-error_2023-12-14-06-25.json new file mode 100644 index 000000000..371b56956 --- /dev/null +++ b/common/changes/@visactor/vtable/fix-fix-disable-hover-error_2023-12-14-06-25.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vtable", + "comment": "fix: fix disableHover bottom frozen hover error", + "type": "none" + } + ], + "packageName": "@visactor/vtable" +} \ No newline at end of file From 69bdcbe4023073fe9c51c7eaeb9d49c0cf17a465 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Thu, 14 Dec 2023 15:01:22 +0800 Subject: [PATCH 14/35] fix: selectionStyle consider lineWidth set array --- .../scenegraph/select/update-select-border.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/vtable/src/scenegraph/select/update-select-border.ts b/packages/vtable/src/scenegraph/select/update-select-border.ts index 5f49642ef..54ef8f57c 100644 --- a/packages/vtable/src/scenegraph/select/update-select-border.ts +++ b/packages/vtable/src/scenegraph/select/update-select-border.ts @@ -219,24 +219,39 @@ function updateComponent(selectComp: { rect: IRect; role: CellSubLocation }, key //#endregion //#region 处理边缘被截问题 - const diffSize = Math.ceil(selectComp.rect.attribute.lineWidth / 2); + let diffSize = 0; + if (typeof selectComp.rect.attribute.lineWidth === 'number') { + diffSize = Math.ceil(selectComp.rect.attribute.lineWidth / 2); + } if (endCol === scene.table.colCount - 1) { + if (Array.isArray(selectComp.rect.attribute.lineWidth)) { + diffSize = Math.ceil((selectComp.rect.attribute.lineWidth[1] ?? 0) / 2); + } selectComp.rect.setAttributes({ width: selectComp.rect.attribute.width - diffSize }); } if (startCol === 0) { + if (Array.isArray(selectComp.rect.attribute.lineWidth)) { + diffSize = Math.ceil((selectComp.rect.attribute.lineWidth[3] ?? 0) / 2); + } selectComp.rect.setAttributes({ x: selectComp.rect.attribute.x + diffSize, width: selectComp.rect.attribute.width - diffSize }); } if (endRow === scene.table.rowCount - 1) { + if (Array.isArray(selectComp.rect.attribute.lineWidth)) { + diffSize = Math.ceil((selectComp.rect.attribute.lineWidth[2] ?? 0) / 2); + } selectComp.rect.setAttributes({ height: selectComp.rect.attribute.height - diffSize }); } if (startRow === 0) { + if (Array.isArray(selectComp.rect.attribute.lineWidth)) { + diffSize = Math.ceil((selectComp.rect.attribute.lineWidth[0] ?? 0) / 2); + } selectComp.rect.setAttributes({ y: selectComp.rect.attribute.y + diffSize, height: selectComp.rect.attribute.height - diffSize From 455444779634023e3b1d94ea36610a5417b8b595 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Thu, 14 Dec 2023 17:02:19 +0800 Subject: [PATCH 15/35] feat: add disableAxisHover config --- .../feat-disable-axis-hover_2023-12-14-09-02.json | 10 ++++++++++ packages/vtable/examples/pivot-chart/horizontal.ts | 10 +++++----- .../vtable/src/scenegraph/layout/compute-row-height.ts | 6 +++++- packages/vtable/src/state/hover/is-cell-hover.ts | 10 +++++++--- packages/vtable/src/ts-types/base-table.ts | 2 ++ 5 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 common/changes/@visactor/vtable/feat-disable-axis-hover_2023-12-14-09-02.json diff --git a/common/changes/@visactor/vtable/feat-disable-axis-hover_2023-12-14-09-02.json b/common/changes/@visactor/vtable/feat-disable-axis-hover_2023-12-14-09-02.json new file mode 100644 index 000000000..45cf6be25 --- /dev/null +++ b/common/changes/@visactor/vtable/feat-disable-axis-hover_2023-12-14-09-02.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vtable", + "comment": "feat: add disableAxisHover config", + "type": "none" + } + ], + "packageName": "@visactor/vtable" +} \ No newline at end of file diff --git a/packages/vtable/examples/pivot-chart/horizontal.ts b/packages/vtable/examples/pivot-chart/horizontal.ts index 860b72e73..c53023d2e 100644 --- a/packages/vtable/examples/pivot-chart/horizontal.ts +++ b/packages/vtable/examples/pivot-chart/horizontal.ts @@ -9245,11 +9245,11 @@ export function createTable() { } }), widthMode: 'adaptive', - heightMode: 'adaptive' - // hover: { - // disableHeaderHover:false, - // disableHover: true - // }, + heightMode: 'adaptive', + hover: { + highlightMode: 'cell', + disableAxisHover: true + } // select: { // disableSelect: true // } diff --git a/packages/vtable/src/scenegraph/layout/compute-row-height.ts b/packages/vtable/src/scenegraph/layout/compute-row-height.ts index b5f0f3d22..0a7ac5db9 100644 --- a/packages/vtable/src/scenegraph/layout/compute-row-height.ts +++ b/packages/vtable/src/scenegraph/layout/compute-row-height.ts @@ -69,7 +69,11 @@ export function computeRowsHeight( for (let row = rowStart; row < table.columnHeaderLevelCount; row++) { let startCol = 0; let endCol = table.colCount - 1; - if ((table.isPivotTable() || table.isPivotChart()) && checkPivotFixedStyleAndNoWrap(table, row)) { + if ( + ((table.isPivotTable() && !table.isPivotChart()) || + (table.isPivotChart() && !(table.internalProps.layoutMap as PivotHeaderLayoutMap).indicatorsAsCol)) && // no top axis + checkPivotFixedStyleAndNoWrap(table, row) + ) { // 列表头样式一致,只计算第一列行高,作为整行行高 startCol = 0; endCol = table.rowHeaderLevelCount; diff --git a/packages/vtable/src/state/hover/is-cell-hover.ts b/packages/vtable/src/state/hover/is-cell-hover.ts index aa8f60c86..cdbffd589 100644 --- a/packages/vtable/src/state/hover/is-cell-hover.ts +++ b/packages/vtable/src/state/hover/is-cell-hover.ts @@ -16,14 +16,14 @@ export function getCellHoverColor(cellGroup: Group, table: BaseTableAPI): string ) { for (let col = cellGroup.mergeStartCol; col <= cellGroup.mergeEndCol; col++) { for (let row = cellGroup.mergeStartRow; row <= cellGroup.mergeEndRow; row++) { - const key = isCellHover(table.stateManager, col, row); + const key = isCellHover(table.stateManager, col, row, cellGroup); if (key && (!colorKey || key === 'cellBgColor')) { colorKey = key; } } } } else if (cellGroup.role === 'cell') { - colorKey = isCellHover(table.stateManager, cellGroup.col, cellGroup.row); + colorKey = isCellHover(table.stateManager, cellGroup.col, cellGroup.row, cellGroup); } if (!colorKey) { @@ -53,7 +53,7 @@ export function getCellHoverColor(cellGroup: Group, table: BaseTableAPI): string return fillColor; } -export function isCellHover(state: StateManager, col: number, row: number): string | undefined { +export function isCellHover(state: StateManager, col: number, row: number, cellGroup: Group): string | undefined { const { highlightScope, disableHeader, cellPos } = state.hover; const table = state.table; @@ -84,6 +84,10 @@ export function isCellHover(state: StateManager, col: number, row: number): stri if (isHeader) { const define = table.getHeaderDefine(col, row); cellDisable = define?.disableHeaderHover; + + if (cellGroup.firstChild && cellGroup.firstChild.name === 'axis' && table.options.hover?.disableAxisHover) { + cellDisable = true; + } } else { const define = table.getBodyColumnDefine(col, row); cellDisable = define?.disableHover; diff --git a/packages/vtable/src/ts-types/base-table.ts b/packages/vtable/src/ts-types/base-table.ts index 11a1effca..72d4af8b9 100644 --- a/packages/vtable/src/ts-types/base-table.ts +++ b/packages/vtable/src/ts-types/base-table.ts @@ -270,6 +270,8 @@ export interface BaseTableConstructorOptions { disableHover?: boolean; /** 单独设置表头不响应鼠标hover交互 */ disableHeaderHover?: boolean; + /** 单独设置坐标轴不响应鼠标hover交互 */ + disableAxisHover?: boolean; }; /** 选择单元格交互配置 */ select?: { From 869dcab8c02af6e9b104be573746858a7bf0b1a4 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Thu, 14 Dec 2023 18:30:53 +0800 Subject: [PATCH 16/35] docs: add AggregationType NONE --- .../Pivot_table/pivot_table_dataAnalysis.md | 11 ++++++++++- .../Pivot_table/pivot_table_dataAnalysis.md | 15 ++++++++++++--- docs/assets/option/en/table/pivotTable.md | 3 ++- docs/assets/option/zh/table/pivotTable.md | 3 ++- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/docs/assets/guide/en/table_type/Pivot_table/pivot_table_dataAnalysis.md b/docs/assets/guide/en/table_type/Pivot_table/pivot_table_dataAnalysis.md index c98e6d7ef..d3958d4d5 100644 --- a/docs/assets/guide/en/table_type/Pivot_table/pivot_table_dataAnalysis.md +++ b/docs/assets/guide/en/table_type/Pivot_table/pivot_table_dataAnalysis.md @@ -131,7 +131,16 @@ Configuration example: indicatorKey: 'AverageOrderSales', //Indicator name field: 'Sales', //Indicator based on field aggregationType: VTable.TYPES.AggregationType.AVG, //Computation type - formatFun: sumNumberFormat + }, + { + indicatorKey: 'MaxOrderSales', //Indicator name + field: 'Sales', //Indicator based on field + aggregationType: VTable.TYPES.AggregationType.MAX, //Computation type , caculate max value + }, + { + indicatorKey: 'OrderSalesValue', //Indicator name + field: 'Sales', //Indicator based on field + aggregationType: VTable.TYPES.AggregationType.NONE, //don't aggregate } ] ``` diff --git a/docs/assets/guide/zh/table_type/Pivot_table/pivot_table_dataAnalysis.md b/docs/assets/guide/zh/table_type/Pivot_table/pivot_table_dataAnalysis.md index 3722d17bc..f22b7c057 100644 --- a/docs/assets/guide/zh/table_type/Pivot_table/pivot_table_dataAnalysis.md +++ b/docs/assets/guide/zh/table_type/Pivot_table/pivot_table_dataAnalysis.md @@ -125,14 +125,23 @@ filterRules: [ { indicatorKey: 'OrderCount', //指标名称 field: 'Sales', //指标依据字段 - aggregationType: VTable.TYPES.AggregationType.COUNT, //计算类型 + aggregationType: VTable.TYPES.AggregationType.COUNT, //计算类型 求数量 formatFun: countNumberFormat }, { indicatorKey: 'AverageOrderSales', //指标名称 field: 'Sales', //指标依据字段 - aggregationType: VTable.TYPES.AggregationType.AVG, //计算类型 - formatFun: sumNumberFormat + aggregationType: VTable.TYPES.AggregationType.AVG, //计算类型 求平均 + }, + { + indicatorKey: 'MaxOrderSales', //指标名称 + field: 'Sales', //指标依据字段 + aggregationType: VTable.TYPES.AggregationType.MAX, //计算类型 求最大 + }, + { + indicatorKey: 'OrderSalesValue', //指标名称 + field: 'Sales', //指标依据字段 + aggregationType: VTable.TYPES.AggregationType.NONE, //不做聚合 匹配到其中对应数据获取其对应field的值 } ] ``` diff --git a/docs/assets/option/en/table/pivotTable.md b/docs/assets/option/en/table/pivotTable.md index 40bdcd709..51c8502e0 100644 --- a/docs/assets/option/en/table/pivotTable.md +++ b/docs/assets/option/en/table/pivotTable.md @@ -96,7 +96,8 @@ export enum AggregationType { MIN = 'MIN', MAX = 'MAX', AVG = 'AVG', - COUNT = 'COUNT' + COUNT = 'COUNT', + NONE = 'NONE' } ``` ### sortRules(SortRules) diff --git a/docs/assets/option/zh/table/pivotTable.md b/docs/assets/option/zh/table/pivotTable.md index 25d46670a..9b42b68c9 100644 --- a/docs/assets/option/zh/table/pivotTable.md +++ b/docs/assets/option/zh/table/pivotTable.md @@ -97,7 +97,8 @@ export enum AggregationType { MIN = 'MIN', MAX = 'MAX', AVG = 'AVG', - COUNT = 'COUNT' + COUNT = 'COUNT', + NONE = 'NONE' } ``` ### sortRules(SortRules) From 9b7fc832a6a20455c7019334dfa43d0cf0e8ade6 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Thu, 14 Dec 2023 18:51:07 +0800 Subject: [PATCH 17/35] docs: add showGrandTotalsOnTop --- .../Pivot_table/pivot_table_dataAnalysis.md | 3 ++- .../Pivot_table/pivot_table_dataAnalysis.md | 3 ++- docs/assets/option/en/table/pivotTable.md | 10 ++++++++-- docs/assets/option/zh/table/pivotTable.md | 14 ++++++++++++-- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/docs/assets/guide/en/table_type/Pivot_table/pivot_table_dataAnalysis.md b/docs/assets/guide/en/table_type/Pivot_table/pivot_table_dataAnalysis.md index d3958d4d5..33e8f5368 100644 --- a/docs/assets/guide/en/table_type/Pivot_table/pivot_table_dataAnalysis.md +++ b/docs/assets/guide/en/table_type/Pivot_table/pivot_table_dataAnalysis.md @@ -68,7 +68,8 @@ dataConfig: { showSubTotals: true, subTotalsDimensions: ['province'], grandTotalLabel: 'row total', - subTotalLabel: 'Subtotal' + subTotalLabel: 'Subtotal', + showGrandTotalsOnTop: true //totals show on top }, column: { showGrandTotals: true, diff --git a/docs/assets/guide/zh/table_type/Pivot_table/pivot_table_dataAnalysis.md b/docs/assets/guide/zh/table_type/Pivot_table/pivot_table_dataAnalysis.md index f22b7c057..c103e2fad 100644 --- a/docs/assets/guide/zh/table_type/Pivot_table/pivot_table_dataAnalysis.md +++ b/docs/assets/guide/zh/table_type/Pivot_table/pivot_table_dataAnalysis.md @@ -69,7 +69,8 @@ dataConfig: { showSubTotals: true, subTotalsDimensions: ['province'], grandTotalLabel: '行总计', - subTotalLabel: '小计' + subTotalLabel: '小计', + showGrandTotalsOnTop: true //汇总值显示在上 }, column: { showGrandTotals: true, diff --git a/docs/assets/option/en/table/pivotTable.md b/docs/assets/option/en/table/pivotTable.md index 51c8502e0..22e43eed4 100644 --- a/docs/assets/option/en/table/pivotTable.md +++ b/docs/assets/option/en/table/pivotTable.md @@ -169,8 +169,14 @@ export interface FilterRule { Set up totals, subtotals, and grand totals. ``` export interface Totals { - row?: Total; - column?: Total; + row?: Total & { + showGrandTotalsOnTop?: boolean; + showSubTotalsOnTop?: boolean; + }; + column?: Total & { + showGrandTotalsOnLeft?: boolean; + showSubTotalsOnLeft?: boolean; + }; } ``` diff --git a/docs/assets/option/zh/table/pivotTable.md b/docs/assets/option/zh/table/pivotTable.md index 9b42b68c9..dc47befe2 100644 --- a/docs/assets/option/zh/table/pivotTable.md +++ b/docs/assets/option/zh/table/pivotTable.md @@ -170,8 +170,18 @@ export interface FilterRule { 设置汇总,小计总计。 ``` export interface Totals { - row?: Total; - column?: Total; + row?: Total & { + /** 总计显示在上 默认false */ + showGrandTotalsOnTop?: boolean; + /** 小计显示在上 默认false */ + showSubTotalsOnTop?: boolean; + }; + column?: Total & { + /** 总计显示在左 默认false */ + showGrandTotalsOnLeft?: boolean; + /** 小计显示在左 默认false */ + showSubTotalsOnLeft?: boolean; + }; } ``` From 95aae6c0c36be783ab21064b8f6fe4d666772632 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Thu, 14 Dec 2023 19:07:33 +0800 Subject: [PATCH 18/35] fix: fix rowUpdatePos update in updateRow() --- .../vtable/fix-update-row_2023-12-14-11-07.json | 10 ++++++++++ docs/assets/option/en/table/listTable.md | 8 ++++++++ docs/assets/option/zh/table/listTable.md | 8 ++++++++ packages/vtable/src/scenegraph/layout/update-row.ts | 2 +- 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 common/changes/@visactor/vtable/fix-update-row_2023-12-14-11-07.json diff --git a/common/changes/@visactor/vtable/fix-update-row_2023-12-14-11-07.json b/common/changes/@visactor/vtable/fix-update-row_2023-12-14-11-07.json new file mode 100644 index 000000000..b3d15a2e7 --- /dev/null +++ b/common/changes/@visactor/vtable/fix-update-row_2023-12-14-11-07.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vtable", + "comment": "fix: fix rowUpdatePos update in updateRow()", + "type": "none" + } + ], + "packageName": "@visactor/vtable" +} \ No newline at end of file diff --git a/docs/assets/option/en/table/listTable.md b/docs/assets/option/en/table/listTable.md index c2fcf502d..2d98460cb 100644 --- a/docs/assets/option/en/table/listTable.md +++ b/docs/assets/option/en/table/listTable.md @@ -83,3 +83,11 @@ Among them, IEditor is the editor interface defined in @visactor/vtable-editors. tableType = 'listTable' ) }} ``` + +## hierarchyIndent(number) + +When displayed as a tree structure, the indentation value of each layer of content. + +## hierarchyExpandLevel(number) + +When displayed as a tree structure, the number of levels is expanded by default. The default value is 1, which only displays the root node. If configured to `Infinity`, all nodes will be expanded. \ No newline at end of file diff --git a/docs/assets/option/zh/table/listTable.md b/docs/assets/option/zh/table/listTable.md index 052d4c72c..71dca0e04 100644 --- a/docs/assets/option/zh/table/listTable.md +++ b/docs/assets/option/zh/table/listTable.md @@ -81,3 +81,11 @@ editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => s prefix = '#', tableType = 'listTable' ) }} + +## hierarchyIndent(number) + +展示为树形结构时,每层内容缩进值。 + +## hierarchyExpandLevel(number) + +展示为树形结构时,默认展开层数。默认为1只显示根节点,配置为`Infinity`则全部展开。 diff --git a/packages/vtable/src/scenegraph/layout/update-row.ts b/packages/vtable/src/scenegraph/layout/update-row.ts index 597636e66..55ae982e5 100644 --- a/packages/vtable/src/scenegraph/layout/update-row.ts +++ b/packages/vtable/src/scenegraph/layout/update-row.ts @@ -79,7 +79,7 @@ export function updateRow( if (addRows.length) { if (!isNumber(updateAfter)) { const minRow = Math.min(...addRows); - scene.proxy.rowUpdatePos = minRow; + scene.proxy.rowUpdatePos = Math.min(minRow, scene.proxy.rowUpdatePos); } scene.proxy.rowUpdateDirection = 'up'; scene.proxy.updateCellGroups(scene.proxy.screenRowCount * 2); From 071b0369965ea0d9e9d7e3d510fdab963c509b88 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Thu, 14 Dec 2023 19:09:52 +0800 Subject: [PATCH 19/35] docs: add showGrandTotalsOnTop --- docs/assets/demo/en/data-analysis/pivot-analysis-total.md | 2 +- docs/assets/demo/zh/data-analysis/pivot-analysis-total.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/assets/demo/en/data-analysis/pivot-analysis-total.md b/docs/assets/demo/en/data-analysis/pivot-analysis-total.md index 0a5f8d14a..27674ba86 100644 --- a/docs/assets/demo/en/data-analysis/pivot-analysis-total.md +++ b/docs/assets/demo/en/data-analysis/pivot-analysis-total.md @@ -9,7 +9,7 @@ option: PivotTable#dataConfig.totals # Pivot Analysis - Subtotal Total -To summarize table data in pivot analysis, configure totals in dataConfig to set the total subtotal of the row and column dimensions. +To summarize table data in pivot analysis, configure totals in dataConfig to set the total subtotal of the row and column dimensions.This example summary is always displayed to the bottom, and can also be displayed to the top through total.showGrandTotalsOnTop. [Configuration reference](../../option/PivotTable#dataConfig.totals) ## Key Configurations diff --git a/docs/assets/demo/zh/data-analysis/pivot-analysis-total.md b/docs/assets/demo/zh/data-analysis/pivot-analysis-total.md index 8a53d296f..6670c3ae2 100644 --- a/docs/assets/demo/zh/data-analysis/pivot-analysis-total.md +++ b/docs/assets/demo/zh/data-analysis/pivot-analysis-total.md @@ -9,7 +9,7 @@ option: PivotTable#dataConfig.totals # 透视分析——小计总计 -透视分析表格数据汇总,dataConfig中配置totals来设置行列维度的小计总计。 +透视分析表格数据汇总,dataConfig中配置totals来设置行列维度的小计总计。该示例汇总总显示到底部,也可以通过total.showGrandTotalsOnTop来显示到顶部。[配置参考](../../option/PivotTable#dataConfig.totals) ## 关键配置 From 22494b117b0f586c1bd58685b15eb581e8d72e01 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Thu, 14 Dec 2023 20:08:05 +0800 Subject: [PATCH 20/35] fix: fix right frozen adaptive problem --- .../vtable/develop_2023-12-14-12-04.json | 10 ++ .../scenegraph/layout/compute-col-width.ts | 4 +- .../scenegraph/layout/compute-row-height.ts | 2 +- packages/vtable/src/scenegraph/scenegraph.ts | 91 ++++++++----------- 4 files changed, 50 insertions(+), 57 deletions(-) create mode 100644 common/changes/@visactor/vtable/develop_2023-12-14-12-04.json diff --git a/common/changes/@visactor/vtable/develop_2023-12-14-12-04.json b/common/changes/@visactor/vtable/develop_2023-12-14-12-04.json new file mode 100644 index 000000000..ad0289fcf --- /dev/null +++ b/common/changes/@visactor/vtable/develop_2023-12-14-12-04.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vtable", + "comment": "fix: fix right frozen adaptive problem", + "type": "none" + } + ], + "packageName": "@visactor/vtable" +} \ No newline at end of file diff --git a/packages/vtable/src/scenegraph/layout/compute-col-width.ts b/packages/vtable/src/scenegraph/layout/compute-col-width.ts index 4ad8ae00b..dd279fe64 100644 --- a/packages/vtable/src/scenegraph/layout/compute-col-width.ts +++ b/packages/vtable/src/scenegraph/layout/compute-col-width.ts @@ -131,7 +131,7 @@ export function computeColsWidth(table: BaseTableAPI, colStart?: number, colEnd? let actualWidth = 0; for (let col = 0; col < table.colCount; col++) { const colWidth = update ? newWidths[col] : table.getColWidth(col); - if (col < table.frozenColCount || col >= table.colCount - table.rightFrozenColCount) { + if (col < table.frozenColCount || (table.isPivotChart() && col >= table.colCount - table.rightFrozenColCount)) { actualHeaderWidth += colWidth; } actualWidth += colWidth; @@ -608,7 +608,7 @@ function getColWidthDefinedWidthResizedWidth(col: number, table: BaseTableAPI) { return widthDefined; } -function getAdaptiveWidth( +export function getAdaptiveWidth( totalDrawWidth: number, startCol: number, endColPlus1: number, diff --git a/packages/vtable/src/scenegraph/layout/compute-row-height.ts b/packages/vtable/src/scenegraph/layout/compute-row-height.ts index b5f0f3d22..1d955a0e2 100644 --- a/packages/vtable/src/scenegraph/layout/compute-row-height.ts +++ b/packages/vtable/src/scenegraph/layout/compute-row-height.ts @@ -213,7 +213,7 @@ export function computeRowsHeight( let actualHeaderHeight = 0; for (let row = 0; row < table.rowCount; row++) { const rowHeight = update ? newHeights[row] : table.getRowHeight(row); - if (row < table.frozenRowCount || row >= table.rowCount - table.bottomFrozenRowCount) { + if (row < table.frozenRowCount || (table.isPivotChart() && row >= table.rowCount - table.bottomFrozenRowCount)) { actualHeaderHeight += rowHeight; } diff --git a/packages/vtable/src/scenegraph/scenegraph.ts b/packages/vtable/src/scenegraph/scenegraph.ts index 782f9c4f4..26ac92500 100644 --- a/packages/vtable/src/scenegraph/scenegraph.ts +++ b/packages/vtable/src/scenegraph/scenegraph.ts @@ -25,7 +25,7 @@ import { getFunctionalProp, getProp } from './utils/get-prop'; import { dealWithIcon } from './utils/text-icon-layout'; import { SceneProxy } from './group-creater/progress/proxy'; import type { TooltipOptions } from '../ts-types/tooltip'; -import { computeColWidth, computeColsWidth } from './layout/compute-col-width'; +import { computeColWidth, computeColsWidth, getAdaptiveWidth } from './layout/compute-col-width'; import { moveHeaderPosition } from './layout/move-cell'; import { updateCell } from './group-creater/cell-helper'; import type { BaseTableAPI } from '../ts-types/base-table'; @@ -1084,56 +1084,36 @@ export class Scenegraph { dealWidthMode() { const table = this.table; if (table.widthMode === 'adaptive') { - // 处理adaptive宽度 - // table._colRangeWidthsMap = new Map(); - // const canvasWidth = this.internalProps.canvas.width; - const totalDrawWidth = table.tableNoFrameWidth - table.getFrozenColsWidth() - table.getRightFrozenColsWidth(); - let actualWidth = 0; - for (let col = table.frozenColCount; col < table.colCount - table.rightFrozenColCount; col++) { - actualWidth += table.getColWidth(col); - } - const factor = totalDrawWidth / actualWidth; - for (let col = table.frozenColCount; col < table.colCount - table.rightFrozenColCount; col++) { - let colWidth; - if (col === table.colCount - table.rightFrozenColCount - 1) { - colWidth = - totalDrawWidth - table.getColsWidth(table.frozenColCount, table.colCount - table.rightFrozenColCount - 2); - } else { - colWidth = Math.round(table.getColWidth(col) * factor); + table._clearColRangeWidthsMap(); + const canvasWidth = table.tableNoFrameWidth; + let actualHeaderWidth = 0; + for (let col = 0; col < table.colCount; col++) { + const colWidth = table.getColWidth(col); + if (col < table.frozenColCount || col >= table.colCount - table.rightFrozenColCount) { + actualHeaderWidth += colWidth; } - this.setColWidth(col, colWidth); } + const startCol = table.frozenColCount; + const endCol = table.isPivotChart() ? table.colCount - table.rightFrozenColCount : table.colCount; + getAdaptiveWidth(canvasWidth - actualHeaderWidth, startCol, endCol, false, [], table); } else if (table.autoFillWidth) { // 处理风神列宽特殊逻辑 - // table._colRangeWidthsMap = new Map(); + table._clearColRangeWidthsMap(); const canvasWidth = table.tableNoFrameWidth; - let actualWidth = 0; let actualHeaderWidth = 0; + let actualWidth = 0; for (let col = 0; col < table.colCount; col++) { const colWidth = table.getColWidth(col); - if (col < table.frozenColCount || col >= table.colCount - table.rightFrozenColCount) { + if (col < table.frozenColCount || (table.isPivotChart() && col >= table.colCount - table.rightFrozenColCount)) { actualHeaderWidth += colWidth; } - actualWidth += colWidth; } - // 如果内容宽度小于canvas宽度,执行adaptive放大 - if (actualWidth < canvasWidth && actualWidth - actualHeaderWidth > 0) { - const factor = (canvasWidth - actualHeaderWidth) / (actualWidth - actualHeaderWidth); - for (let col = table.frozenColCount; col < table.colCount - table.rightFrozenColCount; col++) { - // this.setColWidth(col, table.getColWidth(col) * factor); - let colWidth; - if (col === table.colCount - table.rightFrozenColCount - 1) { - colWidth = - canvasWidth - - actualHeaderWidth - - table.getColsWidth(table.frozenColCount, table.colCount - table.rightFrozenColCount - 2); - } else { - colWidth = Math.round(table.getColWidth(col) * factor); - } - this.setColWidth(col, colWidth); - } + if (actualWidth < canvasWidth && actualWidth > actualHeaderWidth) { + const startCol = table.frozenColCount; + const endCol = table.isPivotChart() ? table.colCount - table.rightFrozenColCount : table.colCount; + getAdaptiveWidth(canvasWidth - actualHeaderWidth, startCol, endCol, false, [], table); } } @@ -1177,44 +1157,47 @@ export class Scenegraph { if (table.heightMode === 'adaptive') { table._clearRowRangeHeightsMap(); // const canvasWidth = table.internalProps.canvas.width; - const totalDrawHeight = - table.tableNoFrameHeight - table.getFrozenRowsHeight() - table.getBottomFrozenRowsHeight(); + const columnHeaderHeight = table.getRowsHeight(0, table.columnHeaderLevelCount - 1); + const bottomHeaderHeight = table.isPivotChart() ? table.getBottomFrozenRowsHeight() : 0; + const totalDrawHeight = table.tableNoFrameHeight - columnHeaderHeight - bottomHeaderHeight; + const startRow = table.columnHeaderLevelCount; + const endRow = table.isPivotChart() ? table.rowCount - table.bottomFrozenRowCount : table.rowCount; let actualHeight = 0; - for (let row = table.frozenRowCount; row < table.rowCount - table.bottomFrozenRowCount; row++) { + for (let row = startRow; row < endRow; row++) { actualHeight += table.getRowHeight(row); } const factor = totalDrawHeight / actualHeight; - for (let row = table.frozenRowCount; row < table.rowCount - table.bottomFrozenRowCount; row++) { + for (let row = startRow; row < endRow; row++) { let rowHeight; - if (row === table.rowCount - table.bottomFrozenRowCount - 1) { - rowHeight = - totalDrawHeight - - table.getRowsHeight(table.frozenRowCount, table.rowCount - table.bottomFrozenRowCount - 2); + if (row === endRow - 1) { + rowHeight = totalDrawHeight - table.getRowsHeight(startRow, endRow - 2); } else { rowHeight = Math.round(table.getRowHeight(row) * factor); } - this.setRowHeight(row, rowHeight); + + table._setRowHeight(row, rowHeight, false); } } else if (table.autoFillHeight) { + table._clearRowRangeHeightsMap(); const canvasHeight = table.tableNoFrameHeight; let actualHeight = 0; let actualHeaderHeight = 0; for (let row = 0; row < table.rowCount; row++) { const rowHeight = table.getRowHeight(row); - if (row < table.frozenRowCount || row >= table.rowCount - table.bottomFrozenRowCount) { + if ( + row < table.frozenRowCount || + (table.isPivotChart() && row >= table.rowCount - table.bottomFrozenRowCount) + ) { actualHeaderHeight += rowHeight; } actualHeight += rowHeight; } + table.scenegraph._dealAutoFillHeightOriginRowsHeight = actualHeight; // 如果内容高度小于canvas高度,执行adaptive放大 - if ( - (this._dealAutoFillHeightOriginRowsHeight ?? actualHeight) < canvasHeight && - actualHeight - actualHeaderHeight > 0 - ) { + if (actualHeight < canvasHeight && actualHeight - actualHeaderHeight > 0) { const factor = (canvasHeight - actualHeaderHeight) / (actualHeight - actualHeaderHeight); for (let row = table.frozenRowCount; row < table.rowCount - table.bottomFrozenRowCount; row++) { - // this.setRowHeight(row, table.getRowHeight(row) * factor); let rowHeight; if (row === table.rowCount - table.bottomFrozenRowCount - 1) { rowHeight = @@ -1224,7 +1207,7 @@ export class Scenegraph { } else { rowHeight = Math.round(table.getRowHeight(row) * factor); } - this.setRowHeight(row, rowHeight); + table._setRowHeight(row, rowHeight, false); } } } From 48988a7cc6916faee222297e61ca222e7d7b11f8 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Thu, 14 Dec 2023 20:47:29 +0800 Subject: [PATCH 21/35] fix: fix dealWidthMode&dealHeightMode in scenegraph --- .../src/scenegraph/layout/compute-col-width.ts | 5 ++++- packages/vtable/src/scenegraph/scenegraph.ts | 15 +++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/vtable/src/scenegraph/layout/compute-col-width.ts b/packages/vtable/src/scenegraph/layout/compute-col-width.ts index dd279fe64..bdfc55dd1 100644 --- a/packages/vtable/src/scenegraph/layout/compute-col-width.ts +++ b/packages/vtable/src/scenegraph/layout/compute-col-width.ts @@ -614,7 +614,8 @@ export function getAdaptiveWidth( endColPlus1: number, update: boolean, newWidths: number[], - table: BaseTableAPI + table: BaseTableAPI, + fromScenegraph?: boolean ) { let actualWidth = 0; const adaptiveColumns: number[] = []; @@ -650,6 +651,8 @@ export function getAdaptiveWidth( } if (update) { newWidths[col] = table._adjustColWidth(col, colWidth); + } else if (fromScenegraph) { + table.scenegraph.setColWidth(col, table._adjustColWidth(col, colWidth)); } else { table._setColWidth(col, table._adjustColWidth(col, colWidth), false, true); } diff --git a/packages/vtable/src/scenegraph/scenegraph.ts b/packages/vtable/src/scenegraph/scenegraph.ts index 26ac92500..095bdd29f 100644 --- a/packages/vtable/src/scenegraph/scenegraph.ts +++ b/packages/vtable/src/scenegraph/scenegraph.ts @@ -1095,7 +1095,7 @@ export class Scenegraph { } const startCol = table.frozenColCount; const endCol = table.isPivotChart() ? table.colCount - table.rightFrozenColCount : table.colCount; - getAdaptiveWidth(canvasWidth - actualHeaderWidth, startCol, endCol, false, [], table); + getAdaptiveWidth(canvasWidth - actualHeaderWidth, startCol, endCol, false, [], table, true); } else if (table.autoFillWidth) { // 处理风神列宽特殊逻辑 table._clearColRangeWidthsMap(); @@ -1113,7 +1113,7 @@ export class Scenegraph { if (actualWidth < canvasWidth && actualWidth > actualHeaderWidth) { const startCol = table.frozenColCount; const endCol = table.isPivotChart() ? table.colCount - table.rightFrozenColCount : table.colCount; - getAdaptiveWidth(canvasWidth - actualHeaderWidth, startCol, endCol, false, [], table); + getAdaptiveWidth(canvasWidth - actualHeaderWidth, startCol, endCol, false, [], table, true); } } @@ -1175,7 +1175,7 @@ export class Scenegraph { rowHeight = Math.round(table.getRowHeight(row) * factor); } - table._setRowHeight(row, rowHeight, false); + this.setRowHeight(row, rowHeight); } } else if (table.autoFillHeight) { table._clearRowRangeHeightsMap(); @@ -1193,9 +1193,12 @@ export class Scenegraph { actualHeight += rowHeight; } - table.scenegraph._dealAutoFillHeightOriginRowsHeight = actualHeight; + // table.scenegraph._dealAutoFillHeightOriginRowsHeight = actualHeight; // 如果内容高度小于canvas高度,执行adaptive放大 - if (actualHeight < canvasHeight && actualHeight - actualHeaderHeight > 0) { + if ( + (this._dealAutoFillHeightOriginRowsHeight ?? actualHeight) < canvasHeight && + actualHeight - actualHeaderHeight > 0 + ) { const factor = (canvasHeight - actualHeaderHeight) / (actualHeight - actualHeaderHeight); for (let row = table.frozenRowCount; row < table.rowCount - table.bottomFrozenRowCount; row++) { let rowHeight; @@ -1207,7 +1210,7 @@ export class Scenegraph { } else { rowHeight = Math.round(table.getRowHeight(row) * factor); } - table._setRowHeight(row, rowHeight, false); + this.setRowHeight(row, rowHeight); } } } From f261d3b16988460e828fce9303e691a4e0a12e06 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Fri, 15 Dec 2023 14:36:50 +0800 Subject: [PATCH 22/35] feat: optimize computeTextWidth() in pivot table --- packages/vtable/src/core/BaseTable.ts | 17 +++++++- .../vtable/src/layout/pivot-header-layout.ts | 36 ++++++++++++++-- .../vtable/src/layout/pivot-layout-helper.ts | 42 ++++++++++++++----- .../scenegraph/layout/compute-col-width.ts | 14 ++++++- .../scenegraph/layout/compute-row-height.ts | 13 +++++- 5 files changed, 103 insertions(+), 19 deletions(-) diff --git a/packages/vtable/src/core/BaseTable.ts b/packages/vtable/src/core/BaseTable.ts index 03c37380b..6334aa2dc 100644 --- a/packages/vtable/src/core/BaseTable.ts +++ b/packages/vtable/src/core/BaseTable.ts @@ -2815,7 +2815,20 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { const { layoutMap } = this.internalProps; const isHeader = layoutMap.isHeader(col, row); if (isHeader) { - let cacheStyle = this.headerStyleCache.get(`${col}-${row}`); + // const cacheKey = `${col}-${row}`; + let cacheKey; + if (this.isPivotTable()) { + // use dimensionKey&indicatorKey to cache style object in pivot table + const define = this.getHeaderDefine(col, row) as any; + cacheKey = define?.dimensionKey + ? `dim-${define.dimensionKey}` + : define?.indicatorKey + ? `ind-${define.indicatorKey}` + : `${col}-${row}`; + } else { + cacheKey = `${col}-${row}`; + } + let cacheStyle = this.headerStyleCache.get(cacheKey); if (cacheStyle) { return cacheStyle; } @@ -2901,7 +2914,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { this.options.autoWrapText ); } - this.headerStyleCache.set(`${col}-${row}`, cacheStyle); + this.headerStyleCache.set(cacheKey, cacheStyle); return cacheStyle; } let cacheKey; diff --git a/packages/vtable/src/layout/pivot-header-layout.ts b/packages/vtable/src/layout/pivot-header-layout.ts index 1b5211d85..27b84bbff 100644 --- a/packages/vtable/src/layout/pivot-header-layout.ts +++ b/packages/vtable/src/layout/pivot-header-layout.ts @@ -105,6 +105,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { indicatorDimensionKey: string = IndicatorDimensionKeyPlaceholder; // 缓存行号列号对应的cellRange 需要注意当表头位置拖拽后 这个缓存的行列号已不准确 进行重置 // private _cellRangeMap: Map; //存储单元格的行列号范围 针对解决是否为合并单元格情况 + private _largeCellRangeCache: CellRange[]; // 缓存行号列号对应的headerPath,注意树形结构展开需要清除! 需要注意当表头位置拖拽后 这个缓存的行列号已不准确 进行重置 private _CellHeaderPathMap: Map; _table: PivotTable | PivotChart; @@ -142,6 +143,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { } this.dataset = dataset; // this._cellRangeMap = new Map(); + this._largeCellRangeCache = []; this._CellHeaderPathMap = new Map(); // this.showHeader = showHeader; // this.pivotLayout = pivotLayoutObj; @@ -1302,10 +1304,15 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { if (this.isRightFrozenColumn(col, row) || this.isBottomFrozenRow(col, row)) { return result; } - // if (this._cellRangeMap.has(`$${col}$${row}`)) return this._cellRangeMap.get(`$${col}$${row}`); - // if (this._cellRangeMap.has(`${col}-${row}`)) { - // return this._cellRangeMap.get(`${col}-${row}`); + // if (this._cellRangeMap.has(`$${col}$${row}`)) { + // return this._cellRangeMap.get(`$${col}$${row}`); // } + for (let i = 0; i < this._largeCellRangeCache.length; i++) { + const range = this._largeCellRangeCache[i]; + if (col >= range.start.col && col <= range.end.col && row >= range.start.row && row <= range.end.row) { + return range; + } + } if (this.isHeader(col, row) && col !== -1 && row !== -1) { //in header const id = this.getCellId(col, row); @@ -1343,6 +1350,10 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { } } // this._cellRangeMap.set(`${col}-${row}`, result); + if (result.end.col - result.start.col > 100 || result.end.row - result.start.row > 100) { + // only cache large range to avoid long col&row search + this._largeCellRangeCache.push(result); + } return result; } isCellRangeEqual(col: number, row: number, targetCol: number, targetRow: number): boolean { @@ -1411,6 +1422,21 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { if (this._CellHeaderPathMap.has(`${col}-${row}`)) { return this._CellHeaderPathMap.get(`${col}-${row}`); } + let _largeCellRangeCacheIndex = -1; + for (let i = 0; i < this._largeCellRangeCache.length; i++) { + const range = this._largeCellRangeCache[i]; + if (col >= range.start.col && col <= range.end.col && row >= range.start.row && row <= range.end.row) { + _largeCellRangeCacheIndex = i; + break; + } + } + // if (_largeCellRangeCacheIndex !== -1) { + // const range = this._largeCellRangeCache[_largeCellRangeCacheIndex]; + // if (this._CellHeaderPathMap.has(`${range.start.col}-${range.start.row}`)) { + // return this._CellHeaderPathMap.get(`${range.start.col}-${range.start.row}`); + // } + // } + // console.log(`${col}-${row}`); const recordCol = this.getBodyIndexByCol(col); const recordRow = this.getBodyIndexByRow(row) + this.currentPageStartIndex; let colPath: IPivotLayoutHeadNode[] = []; @@ -1666,6 +1692,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { }, {} as { [key: LayoutObjectId]: HeaderData }); this._CellHeaderPathMap = new Map(); // this._cellRangeMap = new Map(); + this._largeCellRangeCache.length = 0; const diffCell: { addCellPositions: CellAddress[]; removeCellPositions: CellAddress[]; @@ -1970,6 +1997,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { this.columnDimensionTree.reset(this.columnDimensionTree.tree.children, true); this._CellHeaderPathMap = new Map(); // this._cellRangeMap = new Map(); + this._largeCellRangeCache.length = 0; return { sourceIndex: sourceCellRange.start.col, targetIndex, @@ -2024,6 +2052,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { this.rowDimensionTree.reset(this.rowDimensionTree.tree.children, true); this._CellHeaderPathMap = new Map(); // this._cellRangeMap = new Map(); + this._largeCellRangeCache.length = 0; return { sourceIndex: sourceCellRange.start.row, targetIndex: targetIndex + this.columnHeaderLevelCount, @@ -2390,6 +2419,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { clearCellRangeMap() { // this._cellRangeMap.clear(); + this._largeCellRangeCache.length = 0; this._CellHeaderPathMap = new Map(); } diff --git a/packages/vtable/src/layout/pivot-layout-helper.ts b/packages/vtable/src/layout/pivot-layout-helper.ts index 9ff0867de..0fc417965 100644 --- a/packages/vtable/src/layout/pivot-layout-helper.ts +++ b/packages/vtable/src/layout/pivot-layout-helper.ts @@ -195,22 +195,44 @@ export class DimensionTree { } searchPath(index: number, node: IPivotLayoutHeadNode, path: Array, maxDeep: number) { if (!node) { - return false; + return; } if (index < node.startIndex || index >= node.startIndex + node.size) { - return false; + return; } path.push(node); - if (!node.children || node.children.length === 0) { - return true; - } - if (node.level >= maxDeep) { - return true; + if (!node.children || node.children.length === 0 || node.level >= maxDeep) { + return; } - // 这里按照 数据填充时,序号时同级序号 还是全局序号 + + // const cIndex = index - node.startIndex; + // for (let i = 0; i < node.children.length; i++) { + // const element = node.children[i]; + // if (cIndex >= element.startIndex && cIndex < element.startIndex + element.size) { + // this.searchPath(cIndex, element, path, maxDeep); + // break; + // } + // } + + // use dichotomy to optimize search performance const cIndex = index - node.startIndex; - node.children.some(n => this.searchPath(cIndex, n, path, maxDeep)); - return true; + let left = 0; + let right = node.children.length - 1; + + while (left <= right) { + const middle = Math.floor((left + right) / 2); + const element = node.children[middle]; + + if (cIndex >= element.startIndex && cIndex < element.startIndex + element.size) { + this.searchPath(cIndex, element, path, maxDeep); + break; + } else if (cIndex < element.startIndex) { + right = middle - 1; + } else { + left = middle + 1; + } + } + return; } /** * 将该树中 层级为level 的sourceIndex处的节点移动到targetIndex位置 diff --git a/packages/vtable/src/scenegraph/layout/compute-col-width.ts b/packages/vtable/src/scenegraph/layout/compute-col-width.ts index bdfc55dd1..b67cac70f 100644 --- a/packages/vtable/src/scenegraph/layout/compute-col-width.ts +++ b/packages/vtable/src/scenegraph/layout/compute-col-width.ts @@ -518,8 +518,17 @@ function computeTextWidth(col: number, row: number, cellType: ColumnTypeOption, // const dataValue = table.getCellOriginValue(col, row); const actStyle = table._getCellStyle(col, row); let iconWidth = 0; - const define = table.getBodyColumnDefine(col, row); - const mayHaveIcon = table.getCellLocation(col, row) !== 'body' ? true : !!define?.icon || !!define?.tree; + + // const define = table.getBodyColumnDefine(col, row); + // const mayHaveIcon = table.getCellLocation(col, row) !== 'body' ? true : !!define?.icon || !!define?.tree; + + let mayHaveIcon = false; + if (table.getCellLocation(col, row) !== 'body') { + mayHaveIcon = true; + } else { + const define = table.getBodyColumnDefine(col, row); + mayHaveIcon = !!define?.icon || !!define?.tree; + } if (mayHaveIcon) { const icons = table.getCellIcons(col, row); icons?.forEach(icon => { @@ -528,6 +537,7 @@ function computeTextWidth(col: number, row: number, cellType: ColumnTypeOption, } }); } + let spanCol = 1; if (table.isHeader(col, row) || (table.getBodyColumnDefine(col, row) as TextColumnDefine)?.mergeCell) { const cellRange = table.getCellRange(col, row); diff --git a/packages/vtable/src/scenegraph/layout/compute-row-height.ts b/packages/vtable/src/scenegraph/layout/compute-row-height.ts index 792388266..14c32b812 100644 --- a/packages/vtable/src/scenegraph/layout/compute-row-height.ts +++ b/packages/vtable/src/scenegraph/layout/compute-row-height.ts @@ -530,8 +530,17 @@ function computeTextHeight(col: number, row: number, cellType: ColumnTypeOption, let iconInlineFrontHeight = 0; const iconInlineEnd: ColumnIconOption[] = []; let iconInlineEndHeight = 0; - const define = table.getBodyColumnDefine(col, row); - const mayHaveIcon = table.getCellLocation(col, row) !== 'body' ? true : !!define?.icon || !!define?.tree; + // const define = table.getBodyColumnDefine(col, row); + // const mayHaveIcon = table.getCellLocation(col, row) !== 'body' ? true : !!define?.icon || !!define?.tree; + + let mayHaveIcon = false; + if (table.getCellLocation(col, row) !== 'body') { + mayHaveIcon = true; + } else { + const define = table.getBodyColumnDefine(col, row); + mayHaveIcon = !!define?.icon || !!define?.tree; + } + if (mayHaveIcon) { const icons = table.getCellIcons(col, row); icons?.forEach(icon => { From e351fe92578223a0a462c92cc55c0d6b1aaabfad Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Fri, 15 Dec 2023 14:53:51 +0800 Subject: [PATCH 23/35] fix: fix bottom&right frozen header style cache --- .../vtable/feat-pivot-perf_2023-12-15-06-53.json | 10 ++++++++++ packages/vtable/src/core/BaseTable.ts | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 common/changes/@visactor/vtable/feat-pivot-perf_2023-12-15-06-53.json diff --git a/common/changes/@visactor/vtable/feat-pivot-perf_2023-12-15-06-53.json b/common/changes/@visactor/vtable/feat-pivot-perf_2023-12-15-06-53.json new file mode 100644 index 000000000..327e72d4d --- /dev/null +++ b/common/changes/@visactor/vtable/feat-pivot-perf_2023-12-15-06-53.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vtable", + "comment": "feat: optimize computeTextWidth() in pivot table", + "type": "none" + } + ], + "packageName": "@visactor/vtable" +} \ No newline at end of file diff --git a/packages/vtable/src/core/BaseTable.ts b/packages/vtable/src/core/BaseTable.ts index 6334aa2dc..ac8bac22a 100644 --- a/packages/vtable/src/core/BaseTable.ts +++ b/packages/vtable/src/core/BaseTable.ts @@ -2817,7 +2817,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { if (isHeader) { // const cacheKey = `${col}-${row}`; let cacheKey; - if (this.isPivotTable()) { + if (this.isPivotTable() && !this.isBottomFrozenRow(row) && !this.isRightFrozenColumn(col)) { // use dimensionKey&indicatorKey to cache style object in pivot table const define = this.getHeaderDefine(col, row) as any; cacheKey = define?.dimensionKey From a1be42f6facbd0dc47c2f40d3b0666260587e31e Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Fri, 15 Dec 2023 16:33:05 +0800 Subject: [PATCH 24/35] fix: fix seqId error in PivotHeaderLayoutMap --- packages/vtable/src/ListTable.ts | 1 + packages/vtable/src/PivotChart.ts | 1 + packages/vtable/src/PivotTable.ts | 1 + packages/vtable/src/core/BaseTable.ts | 13 +++++-------- packages/vtable/src/layout/pivot-header-layout.ts | 3 +++ 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index f6abbbc63..77dfa75c6 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -825,6 +825,7 @@ export class ListTable extends BaseTable implements ListTableAPI { this.stateManager.initCheckedState(records); // this.internalProps.frozenColCount = this.options.frozenColCount || this.rowHeaderLevelCount; // 生成单元格场景树 + this.clearCellStyleCache(); this.scenegraph.createSceneGraph(); this.stateManager.updateHoverPos(oldHoverState.col, oldHoverState.row); if (this.internalProps.title && !this.internalProps.title.isReleased) { diff --git a/packages/vtable/src/PivotChart.ts b/packages/vtable/src/PivotChart.ts index 14e1aa4b3..7d6b4d363 100644 --- a/packages/vtable/src/PivotChart.ts +++ b/packages/vtable/src/PivotChart.ts @@ -1167,6 +1167,7 @@ export class PivotChart extends BaseTable implements PivotChartAPI { this.scenegraph.clearCells(); // this.internalProps.frozenColCount = this.options.frozenColCount || this.rowHeaderLevelCount; // 生成单元格场景树 + this.clearCellStyleCache(); this.scenegraph.createSceneGraph(); this.stateManager.updateHoverPos(oldHoverState.col, oldHoverState.row); if (this.internalProps.title && !this.internalProps.title.isReleased) { diff --git a/packages/vtable/src/PivotTable.ts b/packages/vtable/src/PivotTable.ts index 6830f3e1e..3f8a46e15 100644 --- a/packages/vtable/src/PivotTable.ts +++ b/packages/vtable/src/PivotTable.ts @@ -943,6 +943,7 @@ export class PivotTable extends BaseTable implements PivotTableAPI { this.scenegraph.clearCells(); // this.internalProps.frozenColCount = this.options.frozenColCount || this.rowHeaderLevelCount; // 生成单元格场景树 + this.clearCellStyleCache(); this.scenegraph.createSceneGraph(); this.stateManager.updateHoverPos(oldHoverState.col, oldHoverState.row); if (this.internalProps.title && !this.internalProps.title.isReleased) { diff --git a/packages/vtable/src/core/BaseTable.ts b/packages/vtable/src/core/BaseTable.ts index ac8bac22a..4a8ce40da 100644 --- a/packages/vtable/src/core/BaseTable.ts +++ b/packages/vtable/src/core/BaseTable.ts @@ -1992,8 +1992,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { if (internalProps.menu.renderMode === 'html' && !internalProps.menuHandler) { internalProps.menuHandler = new MenuHandler(this); } - this.headerStyleCache = new Map(); - this.bodyStyleCache = new Map(); + this.clearCellStyleCache(); this.clearColWidthCache(); this.clearRowHeightCache(); } @@ -2004,8 +2003,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { const oldHoverState = { col: this.stateManager.hover.cellPos.col, row: this.stateManager.hover.cellPos.row }; this.refreshHeader(); this.scenegraph.clearCells(); - this.headerStyleCache = new Map(); - this.bodyStyleCache = new Map(); + this.clearCellStyleCache(); this.scenegraph.createSceneGraph(); this.stateManager.updateHoverPos(oldHoverState.col, oldHoverState.row); this.render(); @@ -2444,8 +2442,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { this.options.autoWrapText = autoWrapText; // if (this.heightMode === 'autoHeight' || this.heightMode === 'adaptive') { this.scenegraph.clearCells(); - this.headerStyleCache = new Map(); - this.bodyStyleCache = new Map(); + this.clearCellStyleCache(); this.scenegraph.createSceneGraph(); this.render(); // } @@ -2470,8 +2467,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { this.options.theme = theme; this.scenegraph.updateStageBackground(); this.scenegraph.clearCells(); - this.headerStyleCache = new Map(); - this.bodyStyleCache = new Map(); + this.clearCellStyleCache(); this.scenegraph.createSceneGraph(); this.stateManager.updateHoverPos(oldHoverState.col, oldHoverState.row); this.render(); @@ -2956,6 +2952,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { } clearCellStyleCache() { this.headerStyleCache.clear(); + this.bodyStyleCache.clear(); } /** * 清除行高度缓存对象 diff --git a/packages/vtable/src/layout/pivot-header-layout.ts b/packages/vtable/src/layout/pivot-header-layout.ts index 27b84bbff..4891a8d1d 100644 --- a/packages/vtable/src/layout/pivot-header-layout.ts +++ b/packages/vtable/src/layout/pivot-header-layout.ts @@ -227,6 +227,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { } // if (typeof this.showColumnHeader !== 'boolean') { if (this.columnHeaderTitle) { + this.sharedVar.seqId = Math.max(this.sharedVar.seqId, this._headerObjects.length); const id = ++this.sharedVar.seqId; const firstRowIds = Array(this.colCount - this.rowHeaderLevelCount).fill(id); this._columnHeaderCellIds.unshift(firstRowIds); @@ -273,6 +274,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { } // if (typeof this.showRowHeader !== 'boolean') { if (this.rowHeaderTitle) { + this.sharedVar.seqId = Math.max(this.sharedVar.seqId, this._headerObjects.length); const id = ++this.sharedVar.seqId; const firstColIds = Array(this._rowHeaderCellIds_FULL[0]?.length ?? this.rowDimensionTree.tree.size).fill(id); this._rowHeaderCellIds_FULL.unshift(firstColIds); @@ -337,6 +339,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { }); } + this.sharedVar.seqId = Math.max(this.sharedVar.seqId, this._headerObjects.length); //生成cornerHeaderObjs及_cornerHeaderCellIds if (this.cornerSetting.titleOnDimension === 'column') { this.cornerHeaderObjs = this._addCornerHeaders( From 596a4112801e5a33947a47fa9d168ea54dc74950 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Fri, 15 Dec 2023 17:20:13 +0800 Subject: [PATCH 25/35] feat: add vtable-export docs --- common/config/rush/pnpm-lock.yaml | 116 ------------------ docs/assets/guide/en/export/csv.md | 24 ++++ docs/assets/guide/en/export/excel.md | 25 ++++ docs/assets/guide/menu.json | 23 ++++ docs/assets/guide/zh/export/csv.md | 24 ++++ docs/assets/guide/zh/export/excel.md | 25 ++++ packages/vtable-export/README.md | 22 ++-- packages/vtable-export/bundler.config.js | 2 +- packages/vtable-export/package.json | 2 - .../vtable-export/src/excel/index-xlsx-js.ts | 114 ----------------- .../vtable-export/src/excel/style-xlsx-js.ts | 53 -------- 11 files changed, 135 insertions(+), 295 deletions(-) create mode 100644 docs/assets/guide/en/export/csv.md create mode 100644 docs/assets/guide/en/export/excel.md create mode 100644 docs/assets/guide/zh/export/csv.md create mode 100644 docs/assets/guide/zh/export/excel.md delete mode 100644 packages/vtable-export/src/excel/index-xlsx-js.ts delete mode 100644 packages/vtable-export/src/excel/style-xlsx-js.ts diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 36cdcfb27..f2d97c21e 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -377,15 +377,11 @@ importers: typescript-transform-paths: 3.3.1 vite: 3.2.6 vite-plugin-markdown: ^2.1.0 - xlsx: 0.18.5 - xlsx-js-style: 1.2.0 dependencies: '@visactor/vtable': link:../vtable '@visactor/vutils': 0.16.18 exceljs: 4.4.0 file-saver: 2.0.5 - xlsx: 0.18.5 - xlsx-js-style: 1.2.0 devDependencies: '@babel/core': 7.20.12 '@babel/preset-env': 7.20.2_@babel+core@7.20.12 @@ -3723,20 +3719,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - /adler-32/1.2.0: - resolution: {integrity: sha512-/vUqU/UY4MVeFsg+SsK6c+/05RZXIHZMGJA+PX5JyWI0ZRcBpupnRuPLU/NXXoFwMYCPCoxIfElM2eS+DUXCqQ==} - engines: {node: '>=0.8'} - hasBin: true - dependencies: - exit-on-epipe: 1.0.1 - printj: 1.1.2 - dev: false - - /adler-32/1.3.1: - resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==} - engines: {node: '>=0.8'} - dev: false - /agent-base/6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -4652,14 +4634,6 @@ packages: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} dev: true - /cfb/1.2.2: - resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==} - engines: {node: '>=0.8'} - dependencies: - adler-32: 1.3.1 - crc-32: 1.2.2 - dev: false - /chai/4.3.10: resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==} engines: {node: '>=4'} @@ -4915,20 +4889,6 @@ packages: engines: {node: '>=0.10.0'} dev: false - /codepage/1.14.0: - resolution: {integrity: sha512-iz3zJLhlrg37/gYRWgEPkaFTtzmnEv1h+r7NgZum2lFElYQPi0/5bnmuDfODHxfp0INEfnRqyfyeIJDbb7ahRw==} - engines: {node: '>=0.8'} - hasBin: true - dependencies: - commander: 2.14.1 - exit-on-epipe: 1.0.1 - dev: false - - /codepage/1.15.0: - resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==} - engines: {node: '>=0.8'} - dev: false - /collect-v8-coverage/1.0.2: resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} dev: true @@ -4999,14 +4959,6 @@ packages: dependencies: delayed-stream: 1.0.0 - /commander/2.14.1: - resolution: {integrity: sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==} - dev: false - - /commander/2.17.1: - resolution: {integrity: sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==} - dev: false - /commander/2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -6420,11 +6372,6 @@ packages: strip-final-newline: 2.0.0 dev: true - /exit-on-epipe/1.0.1: - resolution: {integrity: sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==} - engines: {node: '>=0.8'} - dev: false - /exit/0.1.2: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} @@ -6591,10 +6538,6 @@ packages: pend: 1.2.0 dev: true - /fflate/0.3.11: - resolution: {integrity: sha512-Rr5QlUeGN1mbOHlaqcSYMKVpPbgLy0AWT/W0EHxA6NGI12yO1jpoui2zBBvU2G824ltM6Ut8BFgfHSBGfkmS0A==} - dev: false - /file-entry-cache/6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -6785,11 +6728,6 @@ packages: combined-stream: 1.0.8 mime-types: 2.1.35 - /frac/1.1.2: - resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==} - engines: {node: '>=0.8'} - dev: false - /fraction.js/4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} dev: false @@ -10858,12 +10796,6 @@ packages: engines: {node: '>= 0.8'} dev: false - /printj/1.1.2: - resolution: {integrity: sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==} - engines: {node: '>=0.8'} - hasBin: true - dev: false - /process-nextick-args/2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -11939,13 +11871,6 @@ packages: dev: true optional: true - /ssf/0.11.2: - resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==} - engines: {node: '>=0.8'} - dependencies: - frac: 1.1.2 - dev: false - /sshpk/1.18.0: resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} engines: {node: '>=0.10.0'} @@ -13469,21 +13394,11 @@ packages: stackback: 0.0.2 dev: true - /wmf/1.0.2: - resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==} - engines: {node: '>=0.8'} - dev: false - /word-wrap/1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} dev: true - /word/0.3.0: - resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==} - engines: {node: '>=0.8'} - dev: false - /workerpool/6.1.5: resolution: {integrity: sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==} dev: true @@ -13562,37 +13477,6 @@ packages: optional: true dev: true - /xlsx-js-style/1.2.0: - resolution: {integrity: sha512-DDT4FXFSWfT4DXMSok/m3TvmP1gvO3dn0Eu/c+eXHW5Kzmp7IczNkxg/iEPnImbG9X0Vb8QhROda5eatSR/97Q==} - engines: {node: '>=0.8'} - hasBin: true - dependencies: - adler-32: 1.2.0 - cfb: 1.2.2 - codepage: 1.14.0 - commander: 2.17.1 - crc-32: 1.2.2 - exit-on-epipe: 1.0.1 - fflate: 0.3.11 - ssf: 0.11.2 - wmf: 1.0.2 - word: 0.3.0 - dev: false - - /xlsx/0.18.5: - resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==} - engines: {node: '>=0.8'} - hasBin: true - dependencies: - adler-32: 1.3.1 - cfb: 1.2.2 - codepage: 1.15.0 - crc-32: 1.2.2 - ssf: 0.11.2 - wmf: 1.0.2 - word: 0.3.0 - dev: false - /xml-name-validator/3.0.0: resolution: {integrity: sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==} dev: true diff --git a/docs/assets/guide/en/export/csv.md b/docs/assets/guide/en/export/csv.md new file mode 100644 index 000000000..6579fbcc5 --- /dev/null +++ b/docs/assets/guide/en/export/csv.md @@ -0,0 +1,24 @@ +# CSV export + +The `@visactor/vtable-export` package is a tool packaged for VTable table export. It supports export in both CSV and Excel formats. + +## Usage +First, you need to install the `@visactor/vtable` and `@visactor/vtable-export` packages in your application, then introduce them in your code to generate a table instance and export it: + +```js +import * as VTable from '@visactor/vtable'; +import { downloadCsv, exportVTableToCsv } from '@visactor/vtable-export'; + +//config option +//...... +const tableInstance = new VTable.ListTable(option); + +// donload csv file +downloadCsv(exportVTableToCsv(tableInstance), 'export-csv'); +``` + +* `exportVTableToCsv`: Table output tool, outputs table instances as a string in CSV format +* `downloadCsv`: Download tool to download CSV format strings as files in a browser environment +* If it is a server environment, you can process the CSV format string converted by `exportVTableToCsv` yourself. + +Reference[demo](https://codesandbox.io/p/sandbox/react-vtable-wjrvpq) \ No newline at end of file diff --git a/docs/assets/guide/en/export/excel.md b/docs/assets/guide/en/export/excel.md new file mode 100644 index 000000000..2979e4111 --- /dev/null +++ b/docs/assets/guide/en/export/excel.md @@ -0,0 +1,25 @@ +# Excel export + +The `@visactor/vtable-export` package is a tool packaged for VTable table export. It supports export in both CSV and Excel formats. + +## Usage +First, you need to install the `@visactor/vtable` and `@visactor/vtable-export` packages in your application, then introduce them in your code to generate a table instance and export it: + +```js +import * as VTable from '@visactor/vtable'; +import { downloadExcel, exportVTableToExcel } from '@visactor/vtable-export'; + +//config option +//...... +const tableInstance = new VTable.ListTable(option); + +// donload csv file +downloadExcel(exportVTableToExcel(tableInstance), 'export-csv'); +``` + +* `exportVTableToExcel`: Table output tool, outputs table instances as an ArrayBuffer in Excel format +* `downloadExcel`: Download tool to download the ArrayBuffer in Excel format as a file in the browser environment +* If it is a server environment, you can process the Excel format ArrayBuffer converted by `exportVTableToExcel` yourself. +* The excel export function is currently being improved. Currently, it only supports the export of text-type cells, and will support more types such as sparkline in the future. + +Reference[demo](https://codesandbox.io/p/sandbox/react-vtable-wjrvpq) \ No newline at end of file diff --git a/docs/assets/guide/menu.json b/docs/assets/guide/menu.json index 8f9db4ddc..49abfad20 100644 --- a/docs/assets/guide/menu.json +++ b/docs/assets/guide/menu.json @@ -433,6 +433,29 @@ } ] }, + { + "path": "export", + "title": { + "zh": "表格导出", + "en": "table export" + }, + "children": [ + { + "path": "csv", + "title": { + "zh": "csv", + "en": "csv" + } + }, + { + "path": "excel", + "title": { + "zh": "excel", + "en": "excel" + } + } + ] + }, { "path": "Developer_Ecology", "title": { diff --git a/docs/assets/guide/zh/export/csv.md b/docs/assets/guide/zh/export/csv.md new file mode 100644 index 000000000..8140071e3 --- /dev/null +++ b/docs/assets/guide/zh/export/csv.md @@ -0,0 +1,24 @@ +# CSV导出 + +`@visactor/vtable-export`包是为了 VTable 表格导出所封装的工具,它支持CSV和Excel两种格式的导出。 + +## 使用方式 +首先,你需要在你的应用中安装`@visactor/vtable`和`@visactor/vtable-export`包,然后在你的代码中引入它们生成一个表格实例,并导出: + +```js +import * as VTable from '@visactor/vtable'; +import { downloadCsv, exportVTableToCsv } from '@visactor/vtable-export'; + +// config option +// ...... +const tableInstance = new VTable.ListTable(option); + +// donload csv file +downloadCsv(exportVTableToCsv(tableInstance), 'export-csv'); +``` + +* `exportVTableToCsv`:表格输出工具,将表格实例输出为一个CSV格式的字符串 +* `downloadCsv`:下载工具,在浏览器环境中将CSV格式的字符串下载为文件 +* 如果是服务端环境,可以自行处理`exportVTableToCsv`转换出的CSV格式的字符串 + +参考[demo](https://codesandbox.io/p/sandbox/react-vtable-wjrvpq) \ No newline at end of file diff --git a/docs/assets/guide/zh/export/excel.md b/docs/assets/guide/zh/export/excel.md new file mode 100644 index 000000000..b2b85a72d --- /dev/null +++ b/docs/assets/guide/zh/export/excel.md @@ -0,0 +1,25 @@ +# Excel导出 + +`@visactor/vtable-export`包是为了 VTable 表格导出所封装的工具,它支持CSV和Excel两种格式的导出。 + +## 使用方式 +首先,你需要在你的应用中安装`@visactor/vtable`和`@visactor/vtable-export`包,然后在你的代码中引入它们生成一个表格实例,并导出: + +```js +import * as VTable from '@visactor/vtable'; +import { downloadExcel, exportVTableToExcel } from '@visactor/vtable-export'; + +// config option +// ...... +const tableInstance = new VTable.ListTable(option); + +// donload csv file +downloadExcel(exportVTableToExcel(tableInstance), 'export-csv'); +``` + +* `exportVTableToExcel`:表格输出工具,将表格实例输出为一个Excel格式的ArrayBuffer +* `downloadExcel`:下载工具,在浏览器环境中将Excel格式的ArrayBuffer下载为文件 +* 如果是服务端环境,可以自行处理`exportVTableToExcel`转换出的Excel格式的ArrayBuffer +* 目前excel导出功能正在完善中,目前只支持文字类型的单元格导出,后续会支持迷你图等更多类型。 + +参考[demo](https://codesandbox.io/p/sandbox/react-vtable-wjrvpq) \ No newline at end of file diff --git a/packages/vtable-export/README.md b/packages/vtable-export/README.md index fbf2b99cb..c18f8c068 100644 --- a/packages/vtable-export/README.md +++ b/packages/vtable-export/README.md @@ -22,22 +22,21 @@ VTable is not just a high-performance multidimensional data analysis table, but ## Installation -[npm package](https://www.npmjs.com/package/@visactor/react-vtable) +[npm package](https://www.npmjs.com/package/@visactor/vtable-export) ```bash // npm -npm install @visactor/react-vtable +npm install @visactor/vtable-export // yarn -yarn add @visactor/react-vtable +yarn add @visactor/vtable-export ``` ## Quick Start ```jsx -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import { ListTable } from "@visactor/react-vtable"; +import * as VTable from '@visactor/vtable'; +import { downloadCsv, exportVTableToCsv, downloadExcel, exportVTableToExcel } from '@visactor/vtable-export'; const option = { header: [ @@ -61,9 +60,14 @@ const option = { records: new Array(1000).fill(["John", 18, "male", "🏀"]), }; -ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( - -); +const tableInstance = new VTable.ListTable(option); + +// donload csv file +downloadCsv(exportVTableToCsv(tableInstance), 'export-csv'); + +// donload excel file +downloadExcel(await exportVTableToExcel(tableInstance), 'export-excel'); + ``` ## diff --git a/packages/vtable-export/bundler.config.js b/packages/vtable-export/bundler.config.js index 6c01068ad..3b1f203fa 100644 --- a/packages/vtable-export/bundler.config.js +++ b/packages/vtable-export/bundler.config.js @@ -6,7 +6,7 @@ module.exports = { noEmitOnError: false, copy: ['css'], name: 'VTable', - umdOutputFilename: 'react-vtable', + umdOutputFilename: 'vtable-export', rollupOptions: { treeshake: true }, diff --git a/packages/vtable-export/package.json b/packages/vtable-export/package.json index 6ae18e7f3..eb313db5c 100644 --- a/packages/vtable-export/package.json +++ b/packages/vtable-export/package.json @@ -37,8 +37,6 @@ "@visactor/vtable": "workspace:*", "@visactor/vutils": "~0.16.10", "file-saver": "2.0.5", - "xlsx": "0.18.5", - "xlsx-js-style": "1.2.0", "exceljs": "4.4.0" }, "devDependencies": { diff --git a/packages/vtable-export/src/excel/index-xlsx-js.ts b/packages/vtable-export/src/excel/index-xlsx-js.ts deleted file mode 100644 index 9b1c6ef74..000000000 --- a/packages/vtable-export/src/excel/index-xlsx-js.ts +++ /dev/null @@ -1,114 +0,0 @@ -import XLSX from 'xlsx-js-style'; -import { getCellStyle } from './style-xlsx-js'; -import type { CellRange, IVTable } from '../util/type'; - -export function exportVTableToExcel(tableInstance: IVTable) { - const workSheet = exportVTableToWorkSheet(tableInstance); - const workBook = createWorkBook(workSheet); - - const workBookExport = XLSX.write(workBook, { - bookType: 'xlsx', - bookSST: false, - type: 'binary' - }); - - return workBookExport; -} - -function createWorkBook(workSheet: any, name: string = 'sheet') { - const workBook = { SheetNames: [] as any, Sheets: {} }; - - workBook.SheetNames.push(name); - workBook.Sheets[name] = workSheet; - - return workBook; -} - -function exportVTableToWorkSheet(tableInstance: IVTable) { - const minRow = 0; - const maxRow = tableInstance.rowCount - 1; - const minCol = 0; - const maxCol = tableInstance.colCount - 1; - - const workSheet = {}; - const mergeCells = []; - const mergeCellSet = new Set(); - const columnsWidth = []; - const rowsWidth = []; - - for (let col = minCol; col <= maxCol; col++) { - const colWith = tableInstance.getColWidth(col); - columnsWidth[col] = { wpx: colWith }; - for (let row = minRow; row <= maxRow; row++) { - if (!rowsWidth[row]) { - const rowHeight = tableInstance.getRowHeight(row); - rowsWidth[row] = { hpx: rowHeight }; - } - - const cellValue = tableInstance.getCellValue(col, row); - const cell: XLSX.CellObject = { - v: cellValue, - t: typeof cellValue === 'number' ? 'n' : 's' - }; - if (cellValue) { - cell.s = getCellStyle(col, row, tableInstance); - } - workSheet[encodeCellAddress(col, row)] = cell; - - const cellRange = tableInstance.getCellRange(col, row); - if (cellRange.start.col !== cellRange.end.col || cellRange.start.row !== cellRange.end.row) { - const key = `${cellRange.start.col},${cellRange.start.row}:${cellRange.end.col},${cellRange.end.row}}`; - if (!mergeCellSet.has(key)) { - mergeCellSet.add(key); - mergeCells.push({ - s: { c: cellRange.start.col, r: cellRange.start.row }, - e: { c: cellRange.end.col, r: cellRange.end.row } - }); - } - } - } - } - - const tableRange = encodeCellRange({ - start: { - col: 0, - row: 0 - }, - end: { - col: tableInstance.colCount - 1, - row: tableInstance.rowCount - 1 - } - }); - - workSheet['!ref'] = tableRange; - workSheet['!merges'] = mergeCells; - workSheet['!cols'] = columnsWidth; - workSheet['!rows'] = rowsWidth; - - return workSheet; -} - -/** - * @description: comvert cell range to code - * @param {CellRange} cellRange - * @return {*} - */ -function encodeCellRange(cellRange: CellRange) { - const start = encodeCellAddress(cellRange.start.col, cellRange.start.row); - const end = encodeCellAddress(cellRange.end.col, cellRange.end.row); - return `${start}:${end}`; -} - -/** - * @description: convert cell address to code - * @param {number} col - * @param {number} row - * @return {*} - */ -function encodeCellAddress(col: number, row: number) { - let s = ''; - for (let column = col + 1; column > 0; column = Math.floor((column - 1) / 26)) { - s = String.fromCharCode(((column - 1) % 26) + 65) + s; - } - return s + (row + 1); -} diff --git a/packages/vtable-export/src/excel/style-xlsx-js.ts b/packages/vtable-export/src/excel/style-xlsx-js.ts deleted file mode 100644 index ed9cbc178..000000000 --- a/packages/vtable-export/src/excel/style-xlsx-js.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { IVTable } from '../util/type'; -import type XLSX from 'xlsx-js-style'; -import type * as VTable from '@visactor/vtable'; - -export function getCellStyle(col: number, row: number, tableInstance: IVTable): XLSX.CellStyle { - const cellStyle = tableInstance.getCellStyle(col, row); - return { - alignment: getCellAlignment(col, row, cellStyle), - border: getCellBorder(col, row, cellStyle), - fill: getCellFill(col, row, cellStyle), - font: getCellFont(col, row, cellStyle) - }; -} - -function getCellAlignment(col: number, row: number, cellStyle: VTable.TYPES.CellStyle): XLSX.CellStyle['alignment'] { - return { - horizontal: cellStyle.textAlign || 'left', - vertical: 'center', // no middle, use center - wrapText: cellStyle.autoWrapText || false - }; -} - -function getCellBorder(col: number, row: number, cellStyle: VTable.TYPES.CellStyle): XLSX.CellStyle['border'] { - return { - top: { color: getColor(cellStyle.borderColor) }, - left: { color: getColor(cellStyle.borderColor) }, - bottom: { color: getColor(cellStyle.borderColor) }, - right: { color: getColor(cellStyle.borderColor) } - }; -} - -function getCellFill(col: number, row: number, cellStyle: VTable.TYPES.CellStyle): XLSX.CellStyle['fill'] { - return { - fgColor: getColor(cellStyle.bgColor) - }; -} - -function getCellFont(col: number, row: number, cellStyle: VTable.TYPES.CellStyle): XLSX.CellStyle['font'] { - return { - name: cellStyle.fontFamily || 'Arial', // only one font familt name - sz: cellStyle.fontSize || 10, - bold: cellStyle.fontWeight === 'bold', // only bold or not - italic: cellStyle.fontStyle === 'italic', // only italic or not - color: getColor(cellStyle.color), - underline: cellStyle.underline - }; -} - -function getColor(color: string) { - return { - rgb: color.substring(1) - }; -} From 0d59df69bd99a5640718ff88062dfc01d10c6f4a Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Fri, 15 Dec 2023 17:24:44 +0800 Subject: [PATCH 26/35] chore: add rush change --- .../vtable/feat-table-export_2023-12-15-09-21.json | 10 ++++++++++ packages/vtable-export/package.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 common/changes/@visactor/vtable/feat-table-export_2023-12-15-09-21.json diff --git a/common/changes/@visactor/vtable/feat-table-export_2023-12-15-09-21.json b/common/changes/@visactor/vtable/feat-table-export_2023-12-15-09-21.json new file mode 100644 index 000000000..32c3d56f3 --- /dev/null +++ b/common/changes/@visactor/vtable/feat-table-export_2023-12-15-09-21.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vtable-export", + "comment": "feat: add table-export tools", + "type": "none" + } + ], + "packageName": "@visactor/vtable" +} \ No newline at end of file diff --git a/packages/vtable-export/package.json b/packages/vtable-export/package.json index eb313db5c..b55e83d6c 100644 --- a/packages/vtable-export/package.json +++ b/packages/vtable-export/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-export", - "version": "0.16.1", + "version": "0.16.3", "description": "The export util of VTable", "author": { "name": "VisActor", From 36889fd3298ffd5a1990ed5efdbb877836ea9215 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Fri, 15 Dec 2023 17:45:41 +0800 Subject: [PATCH 27/35] refactor: dropdownMenu hide #727 --- packages/vtable/src/event/listener/table-group.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/vtable/src/event/listener/table-group.ts b/packages/vtable/src/event/listener/table-group.ts index 9e42a9695..859e9dbc0 100644 --- a/packages/vtable/src/event/listener/table-group.ts +++ b/packages/vtable/src/event/listener/table-group.ts @@ -285,6 +285,10 @@ export function bindTableGroupListener(eventManager: EventManager) { table.scenegraph.tableGroup.addEventListener('pointerupoutside', (e: FederatedPointerEvent) => { // 同pointerup中的逻辑 + const eventArgsSet: SceneEvent = getCellEventArgsSet(e); + if (stateManager.menu.isShow && (eventArgsSet.eventArgs?.target as any) !== stateManager.residentHoverIcon?.icon) { + stateManager.hideMenu(); + } if (stateManager.isResizeCol()) { endResizeCol(table); } else if (stateManager.isMoveCol()) { @@ -350,11 +354,7 @@ export function bindTableGroupListener(eventManager: EventManager) { table.scenegraph.updateChartState(null); } // 处理menu - if ( - stateManager.menu.isShow && - eventArgsSet.eventArgs && - (eventArgsSet.eventArgs.target as any) !== stateManager.residentHoverIcon?.icon - ) { + if (stateManager.menu.isShow && (eventArgsSet.eventArgs?.target as any) !== stateManager.residentHoverIcon?.icon) { // 点击在menu外,且不是下拉菜单的icon,移除menu stateManager.hideMenu(); } @@ -596,6 +596,10 @@ export function bindTableGroupListener(eventManager: EventManager) { // click outside table.scenegraph.stage.addEventListener('pointertap', (e: FederatedPointerEvent) => { + const eventArgsSet: SceneEvent = getCellEventArgsSet(e); + if (stateManager.menu.isShow && (eventArgsSet.eventArgs?.target as any) !== stateManager.residentHoverIcon?.icon) { + stateManager.hideMenu(); + } const target = e.target; if ( // 如果是鼠标点击到canvas空白区域 则取消选中状态 From 1851748669b4008eaed69d02d509c2e30883370a Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Fri, 15 Dec 2023 17:45:58 +0800 Subject: [PATCH 28/35] docs: update changlog of rush --- .../refactor-dropdownMenu_hide_2023-12-15-09-45.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/refactor-dropdownMenu_hide_2023-12-15-09-45.json diff --git a/common/changes/@visactor/vtable/refactor-dropdownMenu_hide_2023-12-15-09-45.json b/common/changes/@visactor/vtable/refactor-dropdownMenu_hide_2023-12-15-09-45.json new file mode 100644 index 000000000..7c9ab7d03 --- /dev/null +++ b/common/changes/@visactor/vtable/refactor-dropdownMenu_hide_2023-12-15-09-45.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "refactor: dropdownMenu hide #727\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From d96a428e1b895490168ef96cc2ada553d2cf8c78 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Fri, 15 Dec 2023 18:09:01 +0800 Subject: [PATCH 29/35] feat: change export demo url --- docs/assets/guide/en/export/csv.md | 2 +- docs/assets/guide/zh/export/excel.md | 2 +- packages/vtable-export/src/util/color.ts | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/assets/guide/en/export/csv.md b/docs/assets/guide/en/export/csv.md index 6579fbcc5..16289f7fd 100644 --- a/docs/assets/guide/en/export/csv.md +++ b/docs/assets/guide/en/export/csv.md @@ -21,4 +21,4 @@ downloadCsv(exportVTableToCsv(tableInstance), 'export-csv'); * `downloadCsv`: Download tool to download CSV format strings as files in a browser environment * If it is a server environment, you can process the CSV format string converted by `exportVTableToCsv` yourself. -Reference[demo](https://codesandbox.io/p/sandbox/react-vtable-wjrvpq) \ No newline at end of file +Reference[demo](https://codesandbox.io/p/sandbox/vtable-export-j7k9j4) \ No newline at end of file diff --git a/docs/assets/guide/zh/export/excel.md b/docs/assets/guide/zh/export/excel.md index b2b85a72d..e2ffa16c2 100644 --- a/docs/assets/guide/zh/export/excel.md +++ b/docs/assets/guide/zh/export/excel.md @@ -22,4 +22,4 @@ downloadExcel(exportVTableToExcel(tableInstance), 'export-csv'); * 如果是服务端环境,可以自行处理`exportVTableToExcel`转换出的Excel格式的ArrayBuffer * 目前excel导出功能正在完善中,目前只支持文字类型的单元格导出,后续会支持迷你图等更多类型。 -参考[demo](https://codesandbox.io/p/sandbox/react-vtable-wjrvpq) \ No newline at end of file +参考[demo](https://codesandbox.io/p/sandbox/vtable-export-j7k9j4) \ No newline at end of file diff --git a/packages/vtable-export/src/util/color.ts b/packages/vtable-export/src/util/color.ts index 25be6cc00..f3e4ddeb5 100644 --- a/packages/vtable-export/src/util/color.ts +++ b/packages/vtable-export/src/util/color.ts @@ -1,3 +1,5 @@ +import { isNumber } from '@visactor/vutils'; + export function colorStringToRGB(colorString: string) { if (colorString.startsWith('#')) { // 处理十六进制颜色值(例如:#RRGGBB 或 #RGB) @@ -26,9 +28,9 @@ export function colorStringToRGB(colorString: string) { .split(',') .map(Number); return values; - } else if (DEFAULT_COLORS[colorString]) { + } else if (isNumber(DEFAULT_COLORS[colorString])) { return rgb(DEFAULT_COLORS[colorString]); - } else if (DEFAULT_COLORS_OPACITY[colorString]) { + } else if (isNumber(DEFAULT_COLORS_OPACITY[colorString])) { return rgba(DEFAULT_COLORS_OPACITY[colorString]); } From 6e35ce48268c854b2f6344776ef73f265a0a389f Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Fri, 15 Dec 2023 18:24:18 +0800 Subject: [PATCH 30/35] refactor: dropdownMenu hide #727 --- packages/vtable/src/event/listener/table-group.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/vtable/src/event/listener/table-group.ts b/packages/vtable/src/event/listener/table-group.ts index 859e9dbc0..fcf7e7ddd 100644 --- a/packages/vtable/src/event/listener/table-group.ts +++ b/packages/vtable/src/event/listener/table-group.ts @@ -282,13 +282,18 @@ export function bindTableGroupListener(eventManager: EventManager) { }); } }); - + // table.scenegraph.tableGroup.addEventListener('pointerdownoutside', (e: FederatedPointerEvent) => { + // const eventArgsSet: SceneEvent = getCellEventArgsSet(e); + // if (stateManager.menu.isShow && (eventArgsSet.eventArgs?.target as any) !== stateManager.residentHoverIcon?.icon) { + // stateManager.hideMenu(); + // } + // }); table.scenegraph.tableGroup.addEventListener('pointerupoutside', (e: FederatedPointerEvent) => { - // 同pointerup中的逻辑 const eventArgsSet: SceneEvent = getCellEventArgsSet(e); if (stateManager.menu.isShow && (eventArgsSet.eventArgs?.target as any) !== stateManager.residentHoverIcon?.icon) { stateManager.hideMenu(); } + // 同pointerup中的逻辑 if (stateManager.isResizeCol()) { endResizeCol(table); } else if (stateManager.isMoveCol()) { @@ -593,13 +598,15 @@ export function bindTableGroupListener(eventManager: EventManager) { } } }); - // click outside - table.scenegraph.stage.addEventListener('pointertap', (e: FederatedPointerEvent) => { + table.scenegraph.stage.addEventListener('pointerdown', (e: FederatedPointerEvent) => { const eventArgsSet: SceneEvent = getCellEventArgsSet(e); if (stateManager.menu.isShow && (eventArgsSet.eventArgs?.target as any) !== stateManager.residentHoverIcon?.icon) { stateManager.hideMenu(); } + }); + // click outside + table.scenegraph.stage.addEventListener('pointertap', (e: FederatedPointerEvent) => { const target = e.target; if ( // 如果是鼠标点击到canvas空白区域 则取消选中状态 From 15751d9182ba11ae8143107d75c8d3a17d768ec7 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Fri, 15 Dec 2023 18:33:48 +0800 Subject: [PATCH 31/35] refactor: dropdownMenu hide #727 --- packages/vtable/src/event/listener/table-group.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/vtable/src/event/listener/table-group.ts b/packages/vtable/src/event/listener/table-group.ts index fcf7e7ddd..69c01b7f1 100644 --- a/packages/vtable/src/event/listener/table-group.ts +++ b/packages/vtable/src/event/listener/table-group.ts @@ -282,12 +282,6 @@ export function bindTableGroupListener(eventManager: EventManager) { }); } }); - // table.scenegraph.tableGroup.addEventListener('pointerdownoutside', (e: FederatedPointerEvent) => { - // const eventArgsSet: SceneEvent = getCellEventArgsSet(e); - // if (stateManager.menu.isShow && (eventArgsSet.eventArgs?.target as any) !== stateManager.residentHoverIcon?.icon) { - // stateManager.hideMenu(); - // } - // }); table.scenegraph.tableGroup.addEventListener('pointerupoutside', (e: FederatedPointerEvent) => { const eventArgsSet: SceneEvent = getCellEventArgsSet(e); if (stateManager.menu.isShow && (eventArgsSet.eventArgs?.target as any) !== stateManager.residentHoverIcon?.icon) { From e9cb0bd5c335823b6a10f4a1e89099b53ecb73f1 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Fri, 15 Dec 2023 18:48:47 +0800 Subject: [PATCH 32/35] feat: add table export demo --- common/config/rush/pnpm-lock.yaml | 2 + docs/assets/demo/en/export/table-export.md | 169 ++++++++++++++++++++ docs/assets/demo/menu.json | 25 +++ docs/assets/demo/zh/export/table-export.md | 171 +++++++++++++++++++++ docs/package.json | 1 + docs/src/main.tsx | 12 ++ docs/style.css | 1 + packages/react-vtable/README.md | 2 +- packages/react-vtable/bundler.config.js | 2 +- packages/vtable-export/README.md | 2 +- packages/vtable-export/bundler.config.js | 2 +- 11 files changed, 385 insertions(+), 4 deletions(-) create mode 100644 docs/assets/demo/en/export/table-export.md create mode 100644 docs/assets/demo/zh/export/table-export.md diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index f2d97c21e..37564549d 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -17,6 +17,7 @@ importers: '@visactor/vchart': 1.7.3 '@visactor/vtable': workspace:* '@visactor/vtable-editors': workspace:* + '@visactor/vtable-export': workspace:* '@vitejs/plugin-react': 3.1.0 axios: ^1.4.0 chalk: ^3.0.0 @@ -39,6 +40,7 @@ importers: '@visactor/vchart': 1.7.3 '@visactor/vtable': link:../packages/vtable '@visactor/vtable-editors': link:../packages/vtable-editors + '@visactor/vtable-export': link:../packages/vtable-export axios: 1.6.2 highlight.js: 11.9.0 lodash: 4.17.21 diff --git a/docs/assets/demo/en/export/table-export.md b/docs/assets/demo/en/export/table-export.md new file mode 100644 index 000000000..beb110f7c --- /dev/null +++ b/docs/assets/demo/en/export/table-export.md @@ -0,0 +1,169 @@ +--- +category: examples +group: export +title: table export +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/pivot-table.png +order: 4-6 +link: '../guide/components/dropdown' +option: ListTable#menu.contextMenuItems +--- + +# table export + +Using the `@visactor/table-export` tool, the table export function can be implemented very simply. The following is a simple example that demonstrates how to use VTable to implement the table export function. + +## Code demo + +```javascript livedemo template=vtable +// You need to introduce the plug-in package when using it `@visactor/vtable-export` +// import { +// downloadCsv, +// exportVTableToCsv, +// downloadExcel, +// exportVTableToExcel, +// } from "@visactor/vtable-export"; +// When umd is introduced, the export tool will be mounted to VTable.export + +let tableInstance; +fetch( + "https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/North_American_Superstore_Pivot_data.json" +) + .then((res) => res.json()) + .then((data) => { + const option = { + records: data, + rows: [ + { + dimensionKey: "City", + title: "City", + headerStyle: { + textStick: true, + }, + width: "auto", + }, + ], + columns: [ + { + dimensionKey: "Category", + title: "Category", + headerStyle: { + textStick: true, + }, + width: "auto", + }, + ], + indicators: [ + { + indicatorKey: "Quantity", + title: "Quantity", + width: "auto", + showSort: false, + headerStyle: { + fontWeight: "normal", + }, + style: { + padding: [16, 28, 16, 28], + color(args) { + if (args.dataValue >= 0) return "#000000"; + return "red"; + }, + }, + }, + { + indicatorKey: "Sales", + title: "Sales", + width: "auto", + showSort: false, + headerStyle: { + fontWeight: "normal", + }, + format: (rec) => { + return "$" + Number(rec).toFixed(2); + }, + style: { + padding: [16, 28, 16, 28], + color(args) { + if (args.dataValue >= 0) return "#000000"; + return "red"; + }, + }, + }, + { + indicatorKey: "Profit", + title: "Profit", + width: "auto", + showSort: false, + headerStyle: { + fontWeight: "normal", + }, + format: (rec) => { + return "$" + Number(rec).toFixed(2); + }, + style: { + padding: [16, 28, 16, 28], + color(args) { + if (args.dataValue >= 0) return "#000000"; + return "red"; + }, + }, + }, + ], + corner: { + titleOnDimension: "row", + headerStyle: { + textStick: true, + }, + }, + dataConfig: { + sortRules: [ + { + sortField: "Category", + sortBy: ["Office Supplies", "Technology", "Furniture"], + }, + ], + }, + enableDataAnalysis: true, + widthMode: "standard", + }; + tableInstance = new VTable.PivotTable( + document.getElementById(CONTAINER_ID), + option + ); + window["tableInstance"] = tableInstance; + + bindExport(); + }); + +function bindExport() { + let exportContainer = document.getElementById("export-buttom"); + if (exportContainer) { + exportContainer.parentElement.removeChild(exportContainer); + } + + exportContainer = document.createElement("div"); + exportContainer.id = "export-buttom"; + exportContainer.style.position = "absolute"; + exportContainer.style.bottom = "0"; + exportContainer.style.right = "0"; + + window["tableInstance"].getContainer().appendChild(exportContainer); + + const exportCsvButton = document.createElement("button"); + const exportExcelButton = document.createElement("button"); + exportContainer.appendChild(exportCsvButton); + exportContainer.appendChild(exportExcelButton); + + exportCsvButton.addEventListener("click", () => { + if (window.tableInstance) { + downloadCsv(exportVTableToCsv(window.tableInstance), "export"); + } + }); + + exportExcelButton.addEventListener("click", async () => { + if (window.tableInstance) { + downloadExcel(await exportVTableToExcel(window.tableInstance), "export"); + } + }); +} +``` + diff --git a/docs/assets/demo/menu.json b/docs/assets/demo/menu.json index 5ae9a4fa5..5db41dcc7 100644 --- a/docs/assets/demo/menu.json +++ b/docs/assets/demo/menu.json @@ -1042,6 +1042,31 @@ } ] }, + { + "path": "export", + "title": { + "zh": "表格导出", + "en": "table export" + }, + "children": [ + { + "path": "table-export", + "title": { + "zh": "表格导出", + "en": "table export" + }, + "meta": { + "title": "table export", + "keywords": "", + "category": "demo", + "group": "Export", + "cover": "https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/pivot-table.png", + "link": "'../../guide/export/excel'", + "option": "" + } + } + ] + }, { "path": "business", "title": { diff --git a/docs/assets/demo/zh/export/table-export.md b/docs/assets/demo/zh/export/table-export.md new file mode 100644 index 000000000..b2e6a03c1 --- /dev/null +++ b/docs/assets/demo/zh/export/table-export.md @@ -0,0 +1,171 @@ +--- +category: examples +group: export +title: 表格导出 +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/pivot-table.png +order: 4-6 +link: '../guide/components/dropdown' +option: ListTable#menu.contextMenuItems +--- + +# 表格导出 + +使用`@visactor/table-export`工具,可以非常简单的实现表格的导出功能。以下是一个简单的例子,演示了如何使用VTable来实现表格的导出功能。 + +## 代码演示 + +```javascript livedemo template=vtable +// 使用时需要引入插件包@visactor/vtable-export +// import { +// downloadCsv, +// exportVTableToCsv, +// downloadExcel, +// exportVTableToExcel, +// } from "@visactor/vtable-export"; +// umd引入时导出工具会挂载到VTable.export + +let tableInstance; +fetch( + "https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/North_American_Superstore_Pivot_data.json" +) + .then((res) => res.json()) + .then((data) => { + const option = { + records: data, + rows: [ + { + dimensionKey: "City", + title: "City", + headerStyle: { + textStick: true, + }, + width: "auto", + }, + ], + columns: [ + { + dimensionKey: "Category", + title: "Category", + headerStyle: { + textStick: true, + }, + width: "auto", + }, + ], + indicators: [ + { + indicatorKey: "Quantity", + title: "Quantity", + width: "auto", + showSort: false, + headerStyle: { + fontWeight: "normal", + }, + style: { + padding: [16, 28, 16, 28], + color(args) { + if (args.dataValue >= 0) return "#000000"; + return "red"; + }, + }, + }, + { + indicatorKey: "Sales", + title: "Sales", + width: "auto", + showSort: false, + headerStyle: { + fontWeight: "normal", + }, + format: (rec) => { + return "$" + Number(rec).toFixed(2); + }, + style: { + padding: [16, 28, 16, 28], + color(args) { + if (args.dataValue >= 0) return "#000000"; + return "red"; + }, + }, + }, + { + indicatorKey: "Profit", + title: "Profit", + width: "auto", + showSort: false, + headerStyle: { + fontWeight: "normal", + }, + format: (rec) => { + return "$" + Number(rec).toFixed(2); + }, + style: { + padding: [16, 28, 16, 28], + color(args) { + if (args.dataValue >= 0) return "#000000"; + return "red"; + }, + }, + }, + ], + corner: { + titleOnDimension: "row", + headerStyle: { + textStick: true, + }, + }, + dataConfig: { + sortRules: [ + { + sortField: "Category", + sortBy: ["Office Supplies", "Technology", "Furniture"], + }, + ], + }, + enableDataAnalysis: true, + widthMode: "standard", + }; + tableInstance = new VTable.PivotTable( + document.getElementById(CONTAINER_ID), + option + ); + window["tableInstance"] = tableInstance; + + bindExport(); + }); + +function bindExport() { + let exportContainer = document.getElementById("export-buttom"); + if (exportContainer) { + exportContainer.parentElement.removeChild(exportContainer); + } + + exportContainer = document.createElement("div"); + exportContainer.id = "export-buttom"; + exportContainer.style.position = "absolute"; + exportContainer.style.bottom = "0"; + exportContainer.style.right = "0"; + + window["tableInstance"].getContainer().appendChild(exportContainer); + + const exportCsvButton = document.createElement("button"); + exportCsvButton.innerHTML = "CSV-export"; + const exportExcelButton = document.createElement("button"); + exportExcelButton.innerHTML = "Excel-export"; + exportContainer.appendChild(exportCsvButton); + exportContainer.appendChild(exportExcelButton); + + exportCsvButton.addEventListener("click", () => { + if (window.tableInstance) { + downloadCsv(exportVTableToCsv(window.tableInstance), "export"); + } + }); + + exportExcelButton.addEventListener("click", async () => { + if (window.tableInstance) { + downloadExcel(await exportVTableToExcel(window.tableInstance), "export"); + } + }); +} +``` + diff --git a/docs/package.json b/docs/package.json index a74a538d9..1992a24a9 100644 --- a/docs/package.json +++ b/docs/package.json @@ -12,6 +12,7 @@ "@arco-design/web-react": "2.46.1", "@visactor/vtable": "workspace:*", "@visactor/vtable-editors": "workspace:*", + "@visactor/vtable-export": "workspace:*", "@visactor/vchart": "1.7.3", "markdown-it": "^13.0.0", "highlight.js": "^11.8.0", diff --git a/docs/src/main.tsx b/docs/src/main.tsx index d71d8d15b..8a24f4bbc 100644 --- a/docs/src/main.tsx +++ b/docs/src/main.tsx @@ -2,6 +2,12 @@ import ReactDOM from 'react-dom/client'; import * as VTable from '@visactor/vtable'; import * as VChart from '@visactor/vchart'; import * as VTableEditors from '@visactor/vtable-editors'; +import { + downloadCsv, + exportVTableToCsv, + downloadExcel, + exportVTableToExcel, +} from "@visactor/vtable-export"; import { App } from './app'; import '@arco-design/web-react/dist/css/arco.css'; @@ -9,6 +15,12 @@ import '@arco-design/web-react/dist/css/arco.css'; (window as any).VTable = VTable; (window as any).VTable_editors= VTableEditors; (window as any).VChart = VChart.VChart; + +(window as any).downloadCsv = downloadCsv; +(window as any).exportVTableToCsv = exportVTableToCsv; +(window as any).downloadExcel = downloadExcel; +(window as any).exportVTableToExcel = exportVTableToExcel; + (window as any).CONTAINER_ID = 'chart'; ReactDOM.createRoot(document.getElementById('root')!).render( diff --git a/docs/style.css b/docs/style.css index 4bafc5969..456027cdc 100644 --- a/docs/style.css +++ b/docs/style.css @@ -92,6 +92,7 @@ .markdown-demo { width: 100%; height: 400px; + position: relative; } .markdown-container table { diff --git a/packages/react-vtable/README.md b/packages/react-vtable/README.md index fbf2b99cb..71ebf936d 100644 --- a/packages/react-vtable/README.md +++ b/packages/react-vtable/README.md @@ -5,7 +5,7 @@
-

VTable

+

React-VTable

diff --git a/packages/react-vtable/bundler.config.js b/packages/react-vtable/bundler.config.js index 6c01068ad..c5bdbc801 100644 --- a/packages/react-vtable/bundler.config.js +++ b/packages/react-vtable/bundler.config.js @@ -5,7 +5,7 @@ module.exports = { formats: ['cjs', 'es', 'umd'], noEmitOnError: false, copy: ['css'], - name: 'VTable', + name: 'ReactVTable', umdOutputFilename: 'react-vtable', rollupOptions: { treeshake: true diff --git a/packages/vtable-export/README.md b/packages/vtable-export/README.md index c18f8c068..969fd10dd 100644 --- a/packages/vtable-export/README.md +++ b/packages/vtable-export/README.md @@ -5,7 +5,7 @@
-

VTable

+

VTable-Export

diff --git a/packages/vtable-export/bundler.config.js b/packages/vtable-export/bundler.config.js index 3b1f203fa..ec5044f2e 100644 --- a/packages/vtable-export/bundler.config.js +++ b/packages/vtable-export/bundler.config.js @@ -5,7 +5,7 @@ module.exports = { formats: ['cjs', 'es', 'umd'], noEmitOnError: false, copy: ['css'], - name: 'VTable', + name: 'VTable.export', umdOutputFilename: 'vtable-export', rollupOptions: { treeshake: true From 65036c977f191e61e1cef94d53c39a5e77287f2f Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Fri, 15 Dec 2023 19:03:05 +0800 Subject: [PATCH 33/35] docs: change table export demo url --- docs/assets/guide/en/export/csv.md | 2 +- docs/assets/guide/en/export/excel.md | 2 +- docs/assets/guide/zh/export/csv.md | 2 +- docs/assets/guide/zh/export/excel.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/assets/guide/en/export/csv.md b/docs/assets/guide/en/export/csv.md index 16289f7fd..7d0b9bff6 100644 --- a/docs/assets/guide/en/export/csv.md +++ b/docs/assets/guide/en/export/csv.md @@ -21,4 +21,4 @@ downloadCsv(exportVTableToCsv(tableInstance), 'export-csv'); * `downloadCsv`: Download tool to download CSV format strings as files in a browser environment * If it is a server environment, you can process the CSV format string converted by `exportVTableToCsv` yourself. -Reference[demo](https://codesandbox.io/p/sandbox/vtable-export-j7k9j4) \ No newline at end of file +Reference[demo](../../demo/export/table-export) \ No newline at end of file diff --git a/docs/assets/guide/en/export/excel.md b/docs/assets/guide/en/export/excel.md index 2979e4111..30e072b37 100644 --- a/docs/assets/guide/en/export/excel.md +++ b/docs/assets/guide/en/export/excel.md @@ -22,4 +22,4 @@ downloadExcel(exportVTableToExcel(tableInstance), 'export-csv'); * If it is a server environment, you can process the Excel format ArrayBuffer converted by `exportVTableToExcel` yourself. * The excel export function is currently being improved. Currently, it only supports the export of text-type cells, and will support more types such as sparkline in the future. -Reference[demo](https://codesandbox.io/p/sandbox/react-vtable-wjrvpq) \ No newline at end of file +Reference[demo](../../demo/export/table-export) \ No newline at end of file diff --git a/docs/assets/guide/zh/export/csv.md b/docs/assets/guide/zh/export/csv.md index 8140071e3..9e88c3899 100644 --- a/docs/assets/guide/zh/export/csv.md +++ b/docs/assets/guide/zh/export/csv.md @@ -21,4 +21,4 @@ downloadCsv(exportVTableToCsv(tableInstance), 'export-csv'); * `downloadCsv`:下载工具,在浏览器环境中将CSV格式的字符串下载为文件 * 如果是服务端环境,可以自行处理`exportVTableToCsv`转换出的CSV格式的字符串 -参考[demo](https://codesandbox.io/p/sandbox/react-vtable-wjrvpq) \ No newline at end of file +参考[demo](../../demo/export/table-export) \ No newline at end of file diff --git a/docs/assets/guide/zh/export/excel.md b/docs/assets/guide/zh/export/excel.md index e2ffa16c2..8997b81a0 100644 --- a/docs/assets/guide/zh/export/excel.md +++ b/docs/assets/guide/zh/export/excel.md @@ -22,4 +22,4 @@ downloadExcel(exportVTableToExcel(tableInstance), 'export-csv'); * 如果是服务端环境,可以自行处理`exportVTableToExcel`转换出的Excel格式的ArrayBuffer * 目前excel导出功能正在完善中,目前只支持文字类型的单元格导出,后续会支持迷你图等更多类型。 -参考[demo](https://codesandbox.io/p/sandbox/vtable-export-j7k9j4) \ No newline at end of file +参考[demo](../../demo/export/table-export) \ No newline at end of file From 0f5f5cf6a03c529896b35138b8c41a85e8a16334 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Fri, 15 Dec 2023 19:10:32 +0800 Subject: [PATCH 34/35] chore: change rush change type --- .../@visactor/vtable/feat-table-export_2023-12-15-09-21.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/changes/@visactor/vtable/feat-table-export_2023-12-15-09-21.json b/common/changes/@visactor/vtable/feat-table-export_2023-12-15-09-21.json index 32c3d56f3..20ed95e74 100644 --- a/common/changes/@visactor/vtable/feat-table-export_2023-12-15-09-21.json +++ b/common/changes/@visactor/vtable/feat-table-export_2023-12-15-09-21.json @@ -3,7 +3,7 @@ { "packageName": "@visactor/vtable-export", "comment": "feat: add table-export tools", - "type": "none" + "type": "minor" } ], "packageName": "@visactor/vtable" From 58c991bffb8bd25344b9b1ec2b79ae9011d4ffc6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 15 Dec 2023 11:28:53 +0000 Subject: [PATCH 35/35] build: prelease version 0.17.0 --- ...ature-total-position_2023-12-11-06-56.json | 11 ------- .../vtable/develop_2023-12-14-03-48.json | 10 ------ .../vtable/develop_2023-12-14-12-04.json | 10 ------ ...t-disable-axis-hover_2023-12-14-09-02.json | 10 ------ .../feat-pivot-perf_2023-12-15-06-53.json | 10 ------ ...-disable-hover-error_2023-12-14-06-25.json | 10 ------ .../fix-update-row_2023-12-14-11-07.json | 10 ------ ...or-dropdownMenu_hide_2023-12-15-09-45.json | 11 ------- common/config/rush/version-policies.json | 2 +- packages/react-vtable/package.json | 2 +- packages/vtable-editors/package.json | 2 +- packages/vtable-export/package.json | 4 +-- packages/vtable/CHANGELOG.json | 33 +++++++++++++++++++ packages/vtable/CHANGELOG.md | 20 ++++++++++- packages/vtable/package.json | 2 +- 15 files changed, 58 insertions(+), 89 deletions(-) delete mode 100644 common/changes/@visactor/vtable/650-feature-total-position_2023-12-11-06-56.json delete mode 100644 common/changes/@visactor/vtable/develop_2023-12-14-03-48.json delete mode 100644 common/changes/@visactor/vtable/develop_2023-12-14-12-04.json delete mode 100644 common/changes/@visactor/vtable/feat-disable-axis-hover_2023-12-14-09-02.json delete mode 100644 common/changes/@visactor/vtable/feat-pivot-perf_2023-12-15-06-53.json delete mode 100644 common/changes/@visactor/vtable/fix-fix-disable-hover-error_2023-12-14-06-25.json delete mode 100644 common/changes/@visactor/vtable/fix-update-row_2023-12-14-11-07.json delete mode 100644 common/changes/@visactor/vtable/refactor-dropdownMenu_hide_2023-12-15-09-45.json diff --git a/common/changes/@visactor/vtable/650-feature-total-position_2023-12-11-06-56.json b/common/changes/@visactor/vtable/650-feature-total-position_2023-12-11-06-56.json deleted file mode 100644 index 269e5f3cf..000000000 --- a/common/changes/@visactor/vtable/650-feature-total-position_2023-12-11-06-56.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "feat: add option showGrandTotalsOnTop #650\n\n", - "type": "none", - "packageName": "@visactor/vtable" - } - ], - "packageName": "@visactor/vtable", - "email": "892739385@qq.com" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/develop_2023-12-14-03-48.json b/common/changes/@visactor/vtable/develop_2023-12-14-03-48.json deleted file mode 100644 index a091db2ef..000000000 --- a/common/changes/@visactor/vtable/develop_2023-12-14-03-48.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "changes": [ - { - "packageName": "@visactor/vtable", - "comment": "feat: optimize diffCellIndices in toggleHierarchyState()", - "type": "none" - } - ], - "packageName": "@visactor/vtable" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/develop_2023-12-14-12-04.json b/common/changes/@visactor/vtable/develop_2023-12-14-12-04.json deleted file mode 100644 index ad0289fcf..000000000 --- a/common/changes/@visactor/vtable/develop_2023-12-14-12-04.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "changes": [ - { - "packageName": "@visactor/vtable", - "comment": "fix: fix right frozen adaptive problem", - "type": "none" - } - ], - "packageName": "@visactor/vtable" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/feat-disable-axis-hover_2023-12-14-09-02.json b/common/changes/@visactor/vtable/feat-disable-axis-hover_2023-12-14-09-02.json deleted file mode 100644 index 45cf6be25..000000000 --- a/common/changes/@visactor/vtable/feat-disable-axis-hover_2023-12-14-09-02.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "changes": [ - { - "packageName": "@visactor/vtable", - "comment": "feat: add disableAxisHover config", - "type": "none" - } - ], - "packageName": "@visactor/vtable" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/feat-pivot-perf_2023-12-15-06-53.json b/common/changes/@visactor/vtable/feat-pivot-perf_2023-12-15-06-53.json deleted file mode 100644 index 327e72d4d..000000000 --- a/common/changes/@visactor/vtable/feat-pivot-perf_2023-12-15-06-53.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "changes": [ - { - "packageName": "@visactor/vtable", - "comment": "feat: optimize computeTextWidth() in pivot table", - "type": "none" - } - ], - "packageName": "@visactor/vtable" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/fix-fix-disable-hover-error_2023-12-14-06-25.json b/common/changes/@visactor/vtable/fix-fix-disable-hover-error_2023-12-14-06-25.json deleted file mode 100644 index 371b56956..000000000 --- a/common/changes/@visactor/vtable/fix-fix-disable-hover-error_2023-12-14-06-25.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "changes": [ - { - "packageName": "@visactor/vtable", - "comment": "fix: fix disableHover bottom frozen hover error", - "type": "none" - } - ], - "packageName": "@visactor/vtable" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/fix-update-row_2023-12-14-11-07.json b/common/changes/@visactor/vtable/fix-update-row_2023-12-14-11-07.json deleted file mode 100644 index b3d15a2e7..000000000 --- a/common/changes/@visactor/vtable/fix-update-row_2023-12-14-11-07.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "changes": [ - { - "packageName": "@visactor/vtable", - "comment": "fix: fix rowUpdatePos update in updateRow()", - "type": "none" - } - ], - "packageName": "@visactor/vtable" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/refactor-dropdownMenu_hide_2023-12-15-09-45.json b/common/changes/@visactor/vtable/refactor-dropdownMenu_hide_2023-12-15-09-45.json deleted file mode 100644 index 7c9ab7d03..000000000 --- a/common/changes/@visactor/vtable/refactor-dropdownMenu_hide_2023-12-15-09-45.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "refactor: dropdownMenu hide #727\n\n", - "type": "none", - "packageName": "@visactor/vtable" - } - ], - "packageName": "@visactor/vtable", - "email": "892739385@qq.com" -} \ No newline at end of file diff --git a/common/config/rush/version-policies.json b/common/config/rush/version-policies.json index 5e7ff4bef..e964346ce 100644 --- a/common/config/rush/version-policies.json +++ b/common/config/rush/version-policies.json @@ -1 +1 @@ -[{"definitionName":"lockStepVersion","policyName":"vtableMain","version":"0.16.3","mainProject":"@visactor/vtable","nextBump":"patch"}] +[{"definitionName":"lockStepVersion","policyName":"vtableMain","version":"0.17.0","mainProject":"@visactor/vtable","nextBump":"minor"}] diff --git a/packages/react-vtable/package.json b/packages/react-vtable/package.json index e3e31ab42..c1abd423d 100644 --- a/packages/react-vtable/package.json +++ b/packages/react-vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/react-vtable", - "version": "0.16.3", + "version": "0.17.0", "description": "The react version of VTable", "keywords": [ "react", diff --git a/packages/vtable-editors/package.json b/packages/vtable-editors/package.json index 32341ac03..da0dc9596 100644 --- a/packages/vtable-editors/package.json +++ b/packages/vtable-editors/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-editors", - "version": "0.16.3", + "version": "0.17.0", "description": "", "sideEffects": false, "main": "cjs/index.js", diff --git a/packages/vtable-export/package.json b/packages/vtable-export/package.json index b55e83d6c..1714a266e 100644 --- a/packages/vtable-export/package.json +++ b/packages/vtable-export/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-export", - "version": "0.16.3", + "version": "0.17.0", "description": "The export util of VTable", "author": { "name": "VisActor", @@ -84,4 +84,4 @@ "axios": "^1.4.0", "@types/react-is": "^17.0.3" } -} \ No newline at end of file +} diff --git a/packages/vtable/CHANGELOG.json b/packages/vtable/CHANGELOG.json index bd82c6334..5c1ce56f3 100644 --- a/packages/vtable/CHANGELOG.json +++ b/packages/vtable/CHANGELOG.json @@ -1,6 +1,39 @@ { "name": "@visactor/vtable", "entries": [ + { + "version": "0.17.0", + "tag": "@visactor/vtable_v0.17.0", + "date": "Fri, 15 Dec 2023 11:23:08 GMT", + "comments": { + "none": [ + { + "comment": "feat: add option showGrandTotalsOnTop #650\n\n" + }, + { + "comment": "feat: optimize diffCellIndices in toggleHierarchyState()" + }, + { + "comment": "fix: fix right frozen adaptive problem" + }, + { + "comment": "feat: add disableAxisHover config" + }, + { + "comment": "feat: optimize computeTextWidth() in pivot table" + }, + { + "comment": "fix: fix disableHover bottom frozen hover error" + }, + { + "comment": "fix: fix rowUpdatePos update in updateRow()" + }, + { + "comment": "refactor: dropdownMenu hide #727\n\n" + } + ] + } + }, { "version": "0.16.3", "tag": "@visactor/vtable_v0.16.3", diff --git a/packages/vtable/CHANGELOG.md b/packages/vtable/CHANGELOG.md index 93b372d8c..f53f910f3 100644 --- a/packages/vtable/CHANGELOG.md +++ b/packages/vtable/CHANGELOG.md @@ -1,6 +1,24 @@ # Change Log - @visactor/vtable -This log was last generated on Wed, 13 Dec 2023 11:43:30 GMT and should not be manually modified. +This log was last generated on Fri, 15 Dec 2023 11:23:08 GMT and should not be manually modified. + +## 0.17.0 +Fri, 15 Dec 2023 11:23:08 GMT + +### Updates + +- feat: add option showGrandTotalsOnTop #650 + + +- feat: optimize diffCellIndices in toggleHierarchyState() +- fix: fix right frozen adaptive problem +- feat: add disableAxisHover config +- feat: optimize computeTextWidth() in pivot table +- fix: fix disableHover bottom frozen hover error +- fix: fix rowUpdatePos update in updateRow() +- refactor: dropdownMenu hide #727 + + ## 0.16.3 Wed, 13 Dec 2023 11:43:30 GMT diff --git a/packages/vtable/package.json b/packages/vtable/package.json index d7b4705d0..35083c86d 100644 --- a/packages/vtable/package.json +++ b/packages/vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable", - "version": "0.16.3", + "version": "0.17.0", "description": "canvas table width high performance", "keywords": [ "grid",