├── packages ├── excel-node-generator │ ├── .npmignore │ ├── src │ │ ├── index.ts │ │ ├── __test__ │ │ │ ├── .gitignore │ │ │ ├── 樱.xlsx │ │ │ ├── simple.test.ts │ │ │ └── data.ts │ │ └── utils.ts │ ├── .eslintrc.js │ ├── tslint.json │ ├── .prettierrc.js │ ├── tsconfig.test.json │ ├── scripts │ │ ├── jest │ │ │ └── jest.config.js │ │ └── webpack │ │ │ └── webpack.config.umd.js │ ├── tsconfig.json │ ├── .eslintignore │ ├── tsconfig.cjs.json │ ├── babel.config.js │ ├── tsconfig.es.json │ ├── .vscode │ │ └── setting.json │ └── package.json ├── excel-canvas-renderer │ ├── .eslintignore │ ├── .gitignore │ ├── .babelrc │ ├── test │ │ ├── index_test.js │ │ ├── helper_test.js │ │ └── core │ │ │ ├── formula_test.js │ │ │ ├── font_test.js │ │ │ ├── format_test.js │ │ │ ├── alphabet_test.js │ │ │ └── cell_test.js │ ├── src │ │ ├── component │ │ │ ├── toolbar │ │ │ │ ├── redo.js │ │ │ │ ├── undo.js │ │ │ │ ├── freeze.js │ │ │ │ ├── print.js │ │ │ │ ├── textwrap.js │ │ │ │ ├── bold.js │ │ │ │ ├── clearformat.js │ │ │ │ ├── strike.js │ │ │ │ ├── italic.js │ │ │ │ ├── underline.js │ │ │ │ ├── autofilter.js │ │ │ │ ├── paintformat.js │ │ │ │ ├── merge.js │ │ │ │ ├── border.js │ │ │ │ ├── font.js │ │ │ │ ├── format.js │ │ │ │ ├── icon_item.js │ │ │ │ ├── formula.js │ │ │ │ ├── font_size.js │ │ │ │ ├── align.js │ │ │ │ ├── fill_color.js │ │ │ │ ├── text_color.js │ │ │ │ ├── valign.js │ │ │ │ ├── dropdown_item.js │ │ │ │ ├── toggle_item.js │ │ │ │ ├── more.js │ │ │ │ └── item.js │ │ │ ├── button.js │ │ │ ├── icon.js │ │ │ ├── dropdown_border.js │ │ │ ├── dropdown_font.js │ │ │ ├── dropdown_fontsize.js │ │ │ ├── dropdown_formula.js │ │ │ ├── form_input.js │ │ │ ├── dropdown_color.js │ │ │ ├── dropdown_align.js │ │ │ ├── tooltip.js │ │ │ ├── message.js │ │ │ ├── datepicker.js │ │ │ ├── form_select.js │ │ │ ├── dropdown_format.js │ │ │ ├── scrollbar.js │ │ │ ├── modal.js │ │ │ ├── form_field.js │ │ │ ├── dropdown.js │ │ │ ├── color_palette.js │ │ │ ├── border_palette.js │ │ │ ├── dropdown_linetype.js │ │ │ ├── event.js │ │ │ ├── contextmenu.js │ │ │ ├── calendar.js │ │ │ ├── resizer.js │ │ │ ├── suggest.js │ │ │ ├── sort_filter.js │ │ │ └── bottombar.js │ │ ├── config.js │ │ ├── core │ │ │ ├── scroll.js │ │ │ ├── selector.js │ │ │ ├── _.prototypes.js │ │ │ ├── clipboard.js │ │ │ ├── history.js │ │ │ ├── col.js │ │ │ ├── font.js │ │ │ ├── formula.js │ │ │ ├── alphabet.js │ │ │ ├── merge.js │ │ │ ├── format.js │ │ │ ├── validation.js │ │ │ ├── validator.js │ │ │ ├── helper.js │ │ │ └── auto_filter.js │ │ ├── algorithm │ │ │ ├── bitmap.js │ │ │ └── expression.js │ │ ├── locale │ │ │ ├── locale.js │ │ │ ├── de.js │ │ │ ├── nl.js │ │ │ ├── zh-cn.js │ │ │ └── en.js │ │ ├── canvas │ │ │ └── draw2.js │ │ ├── index.js │ │ └── index.d.ts │ ├── .eslintrc.js │ ├── .travis.yml │ ├── package.json │ └── index.html ├── excel-schema │ ├── .eslintrc.js │ ├── tslint.json │ ├── .prettierrc.js │ ├── tsconfig.json │ ├── tsconfig.test.json │ ├── scripts │ │ ├── jest │ │ │ └── jest.config.js │ │ └── webpack │ │ │ └── webpack.config.umd.js │ ├── .eslintignore │ ├── tsconfig.cjs.json │ ├── babel.config.js │ ├── tsconfig.es.json │ ├── src │ │ ├── index.ts │ │ ├── WorksheetCellCommentDO.ts │ │ ├── constants.ts │ │ ├── WorkbookDO.ts │ │ ├── value.ts │ │ ├── WorksheetCellDO.ts │ │ ├── WorksheetDO.ts │ │ └── style.ts │ ├── .vscode │ │ └── setting.json │ └── package.json └── excel-jexcel-render │ ├── .eslintrc.js │ ├── tslint.json │ ├── .prettierrc.js │ ├── tsconfig.json │ ├── src │ ├── func │ │ ├── __test__ │ │ │ └── sum.test.ts │ │ └── sum.ts │ ├── utils │ │ └── log.ts │ ├── index.ts │ ├── registry │ │ └── Registry.ts │ └── constant │ │ └── types.ts │ ├── tsconfig.test.json │ ├── scripts │ ├── jest │ │ └── jest.config.js │ └── webpack │ │ └── webpack.config.umd.js │ ├── .eslintignore │ ├── tsconfig.cjs.json │ ├── babel.config.js │ ├── tsconfig.es.json │ ├── .vscode │ └── setting.json │ └── package.json ├── .huskyrc.js ├── .lintstagedrc.js ├── .postcssrc.js ├── jsconfig.json ├── .prettierrc.js ├── scripts ├── jest │ └── jest.config.js ├── docker │ ├── Dockerfile.local │ ├── Dockerfile.gitlab │ ├── build-locally-dev.sh │ ├── build-locally-release.sh │ └── build-on-gitlab.sh ├── tools │ ├── upgrade_pkgs.sh │ └── lint_pkgs.sh └── webpack │ └── webpack.config.js ├── .prettierignore ├── tsconfig.test.json ├── .eslintignore ├── .editorconfig ├── .gitignore ├── .eslintrc.js ├── .vscode └── setting.json ├── tsconfig.json ├── tslint.json ├── .gitlab-ci.yml ├── .github └── FUNDING.yml ├── LICENSE ├── index.html ├── package.json └── README.md /packages/excel-node-generator/.npmignore: -------------------------------------------------------------------------------- 1 | big.json 2 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | dist -------------------------------------------------------------------------------- /.huskyrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@m-fe/husky-config'); 2 | -------------------------------------------------------------------------------- /.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@m-fe/lint-staged'); 2 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@m-fe/postcss-config'); 2 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@m-fe/tsconfig/jsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/excel-node-generator/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './exceljs'; 2 | -------------------------------------------------------------------------------- /packages/excel-schema/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require("../../.eslintrc.js") -------------------------------------------------------------------------------- /packages/excel-jexcel-render/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require("../../.eslintrc.js") -------------------------------------------------------------------------------- /packages/excel-node-generator/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require("../../.eslintrc.js") -------------------------------------------------------------------------------- /packages/excel-schema/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../tslint.json"] 3 | } 4 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require('@m-fe/prettier-config/semi'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/excel-jexcel-render/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../tslint.json"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/excel-node-generator/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../tslint.json"] 3 | } 4 | -------------------------------------------------------------------------------- /scripts/jest/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@m-fe/jest-config/jest.config'); 2 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | 4 | .nyc_output/* 5 | -------------------------------------------------------------------------------- /packages/excel-schema/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require('../../.prettierrc.js'), 3 | }; 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | !/.vscode/settings.json 2 | *.min.* 3 | *.production.* 4 | coverage 5 | dist 6 | node_modules 7 | -------------------------------------------------------------------------------- /packages/excel-jexcel-render/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require('../../.prettierrc.js'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/excel-node-generator/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require('../../.prettierrc.js'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/excel-schema/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/excel-node-generator/src/__test__/.gitignore: -------------------------------------------------------------------------------- 1 | adapter.test.ts 2 | test.xlsx 3 | 樱.xlsx 4 | *.xlsx 5 | big.json 6 | -------------------------------------------------------------------------------- /packages/excel-jexcel-render/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /scripts/docker/Dockerfile.local: -------------------------------------------------------------------------------- 1 | FROM abiosoft/caddy 2 | COPY . /srv 3 | RUN find . -name '*.map' -type f -exec rm -f {} \; 4 | EXPOSE 2015 5 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "plugins": ["@babel/plugin-proposal-class-properties"], 4 | } -------------------------------------------------------------------------------- /packages/excel-schema/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/excel-node-generator/src/__test__/樱.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/excel.ts/master/packages/excel-node-generator/src/__test__/樱.xlsx -------------------------------------------------------------------------------- /packages/excel-jexcel-render/src/func/__test__/sum.test.ts: -------------------------------------------------------------------------------- 1 | import { sum } from '../sum'; 2 | 3 | test('sum', () => { 4 | expect(sum(1, 2)).toBe(3); 5 | }); 6 | -------------------------------------------------------------------------------- /packages/excel-jexcel-render/src/utils/log.ts: -------------------------------------------------------------------------------- 1 | export function logError(...args: object[]) { 2 | // tslint:disable-next-line 3 | console.log(...args); 4 | } 5 | -------------------------------------------------------------------------------- /packages/excel-jexcel-render/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/excel-node-generator/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/excel-schema/scripts/jest/jest.config.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../../../scripts/jest/jest.config'); 2 | 3 | module.exports = baseConfig; 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | !/.*.js 2 | *.min.* 3 | *.production.* 4 | *.md 5 | *.js 6 | *.json 7 | 8 | coverage 9 | dist 10 | node_modules 11 | build 12 | scripts 13 | -------------------------------------------------------------------------------- /packages/excel-jexcel-render/scripts/jest/jest.config.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../../../scripts/jest/jest.config'); 2 | 3 | module.exports = baseConfig; 4 | -------------------------------------------------------------------------------- /packages/excel-node-generator/scripts/jest/jest.config.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../../../scripts/jest/jest.config'); 2 | 3 | module.exports = baseConfig; 4 | -------------------------------------------------------------------------------- /packages/excel-node-generator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*"], 4 | "compilerOptions": { 5 | "sourceMap": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/excel-schema/.eslintignore: -------------------------------------------------------------------------------- 1 | !/.*.js 2 | *.min.* 3 | *.production.* 4 | *.md 5 | *.js 6 | *.json 7 | 8 | coverage 9 | dist 10 | node_modules 11 | build 12 | scripts 13 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/test/index_test.js: -------------------------------------------------------------------------------- 1 | // import assert from 'assert'; 2 | // import { describe, it } from 'mocha'; 3 | // import alphabet from '../../src/index'; 4 | // 5 | // 6 | -------------------------------------------------------------------------------- /packages/excel-jexcel-render/.eslintignore: -------------------------------------------------------------------------------- 1 | !/.*.js 2 | *.min.* 3 | *.production.* 4 | *.md 5 | *.js 6 | *.json 7 | 8 | coverage 9 | dist 10 | node_modules 11 | build 12 | scripts 13 | -------------------------------------------------------------------------------- /packages/excel-jexcel-render/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './constant/types'; 2 | export { sum } from './func/sum'; 3 | export * from './utils/log'; 4 | 5 | export const library = 'rtwCore'; 6 | -------------------------------------------------------------------------------- /packages/excel-node-generator/.eslintignore: -------------------------------------------------------------------------------- 1 | !/.*.js 2 | *.min.* 3 | *.production.* 4 | *.md 5 | *.js 6 | *.json 7 | 8 | coverage 9 | dist 10 | node_modules 11 | build 12 | scripts 13 | -------------------------------------------------------------------------------- /packages/excel-jexcel-render/src/func/sum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 计算两个值之和 3 | * @param num1 4 | * @param num2 5 | */ 6 | export function sum(num1: number, num2: number) { 7 | return num1 + num2; 8 | } 9 | -------------------------------------------------------------------------------- /packages/excel-schema/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "dist/cjs", 6 | "declaration": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/excel-jexcel-render/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "dist/cjs", 6 | "declaration": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/redo.js: -------------------------------------------------------------------------------- 1 | import IconItem from './icon_item'; 2 | 3 | export default class Redo extends IconItem { 4 | constructor() { 5 | super('redo', 'Ctrl+Y'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/undo.js: -------------------------------------------------------------------------------- 1 | import IconItem from './icon_item'; 2 | 3 | export default class Undo extends IconItem { 4 | constructor() { 5 | super('undo', 'Ctrl+Z'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/config.js: -------------------------------------------------------------------------------- 1 | /* global window */ 2 | export const cssPrefix = 'x-spreadsheet'; 3 | export const dpr = window.devicePixelRatio || 1; 4 | export default { 5 | cssPrefix, 6 | dpr, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/excel-node-generator/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "dist/cjs", 6 | "declaration": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | tab_width = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/freeze.js: -------------------------------------------------------------------------------- 1 | import ToggleItem from './toggle_item'; 2 | 3 | export default class Freeze extends ToggleItem { 4 | constructor() { 5 | super('freeze'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/print.js: -------------------------------------------------------------------------------- 1 | import IconItem from './icon_item'; 2 | 3 | export default class Print extends IconItem { 4 | constructor() { 5 | super('print', 'Ctrl+P'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node files 2 | node_modules 3 | 4 | # OS junk files 5 | .DS_Store 6 | .stylelintcache 7 | .eslintcache 8 | 9 | # Project specific stuff 10 | .cache-loader 11 | @coverage 12 | *.log 13 | dist 14 | build 15 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "airbnb-base", 3 | "rules": { 4 | "no-param-reassign": ["error", { "props": false }], 5 | "class-methods-use-this": "off", 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/textwrap.js: -------------------------------------------------------------------------------- 1 | import ToggleItem from './toggle_item'; 2 | 3 | export default class Textwrap extends ToggleItem { 4 | constructor() { 5 | super('textwrap'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/bold.js: -------------------------------------------------------------------------------- 1 | import ToggleItem from './toggle_item'; 2 | 3 | export default class Bold extends ToggleItem { 4 | constructor() { 5 | super('font-bold', 'Ctrl+B'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/clearformat.js: -------------------------------------------------------------------------------- 1 | import IconItem from './icon_item'; 2 | 3 | export default class Clearformat extends IconItem { 4 | constructor() { 5 | super('clearformat'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/strike.js: -------------------------------------------------------------------------------- 1 | import ToggleItem from './toggle_item'; 2 | 3 | export default class Strike extends ToggleItem { 4 | constructor() { 5 | super('strike', 'Ctrl+U'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/italic.js: -------------------------------------------------------------------------------- 1 | import ToggleItem from './toggle_item'; 2 | 3 | export default class Italic extends ToggleItem { 4 | constructor() { 5 | super('font-italic', 'Ctrl+I'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/underline.js: -------------------------------------------------------------------------------- 1 | import ToggleItem from './toggle_item'; 2 | 3 | export default class Underline extends ToggleItem { 4 | constructor() { 5 | super('underline', 'Ctrl+U'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/core/scroll.js: -------------------------------------------------------------------------------- 1 | export default class Scroll { 2 | constructor() { 3 | this.x = 0; // left 4 | this.y = 0; // top 5 | this.ri = 0; // cell row-index 6 | this.ci = 0; // cell col-index 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/excel-schema/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@m-fe', 5 | { 6 | import: true, 7 | react: false, 8 | typescript: true, 9 | }, 10 | ], 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /packages/excel-schema/tsconfig.es.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "es6", 5 | "outDir": "dist/es", 6 | "declaration": true, 7 | "declarationDir": "dist/types" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: '@m-fe/eslint-config/base', 3 | parserOptions: { 4 | ecmaVersion: 2018, 5 | sourceType: 'module', 6 | project: './tsconfig.json', 7 | tsconfigRootDir: __dirname, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /packages/excel-jexcel-render/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@m-fe', 5 | { 6 | import: true, 7 | react: false, 8 | typescript: true, 9 | }, 10 | ], 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /packages/excel-jexcel-render/tsconfig.es.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "es6", 5 | "outDir": "dist/es", 6 | "declaration": true, 7 | "declarationDir": "dist/types" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/excel-node-generator/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@m-fe', 5 | { 6 | import: true, 7 | react: false, 8 | typescript: true, 9 | }, 10 | ], 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /packages/excel-node-generator/tsconfig.es.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "es6", 5 | "outDir": "dist/es", 6 | "declaration": true, 7 | "declarationDir": "dist/types" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/autofilter.js: -------------------------------------------------------------------------------- 1 | import ToggleItem from './toggle_item'; 2 | 3 | export default class Autofilter extends ToggleItem { 4 | constructor() { 5 | super('autofilter'); 6 | } 7 | 8 | setState() {} 9 | } 10 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/paintformat.js: -------------------------------------------------------------------------------- 1 | import ToggleItem from './toggle_item'; 2 | 3 | export default class Paintformat extends ToggleItem { 4 | constructor() { 5 | super('paintformat'); 6 | } 7 | 8 | setState() {} 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/setting.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.validate": [ 3 | "javascript", 4 | "javascriptreact", 5 | { 6 | "language": "typescript", 7 | "autoFix": true 8 | }, 9 | { 10 | "language": "typescriptreact", 11 | "autoFix": true 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/excel-jexcel-render/src/registry/Registry.ts: -------------------------------------------------------------------------------- 1 | /** 默认的核心注册类 */ 2 | export class Registry { 3 | localMap: Map = new Map(); 4 | 5 | set(key: string, value: object) { 6 | this.localMap.set(key, value); 7 | } 8 | } 9 | 10 | export default new Registry(); 11 | -------------------------------------------------------------------------------- /packages/excel-schema/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './constants'; 2 | export * from './style'; 3 | export * from './value'; 4 | export * from './WorkbookDO'; 5 | export * from './WorksheetCellCommentDO'; 6 | export * from './WorksheetCellDO'; 7 | export * from './WorksheetDO'; 8 | export * from './WorksheetProps'; 9 | -------------------------------------------------------------------------------- /packages/excel-schema/.vscode/setting.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.validate": [ 3 | "javascript", 4 | "javascriptreact", 5 | { 6 | "language": "typescript", 7 | "autoFix": true 8 | }, 9 | { 10 | "language": "typescriptreact", 11 | "autoFix": true 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/algorithm/bitmap.js: -------------------------------------------------------------------------------- 1 | /* eslint no-bitwise: "off" */ 2 | /* 3 | v: int value 4 | digit: bit len of v 5 | flag: true or false 6 | */ 7 | const bitmap = (v, digit, flag) => { 8 | const b = 1 << digit; 9 | return flag ? (v | b) : (v ^ b); 10 | }; 11 | export default bitmap; 12 | -------------------------------------------------------------------------------- /scripts/tools/upgrade_pkgs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | cd ./packages 5 | 6 | for file in *; do 7 | [ -d "$file" ] || continue 8 | 9 | if [ "$file" == "config" ]; then 10 | continue 11 | fi 12 | 13 | echo "cd $file"; 14 | cd ./$file 15 | ncu -u 16 | cd .. 17 | done 18 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/merge.js: -------------------------------------------------------------------------------- 1 | import ToggleItem from './toggle_item'; 2 | 3 | export default class Merge extends ToggleItem { 4 | constructor() { 5 | super('merge'); 6 | } 7 | 8 | setState(active, disabled) { 9 | this.el.active(active).disabled(disabled); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/excel-jexcel-render/.vscode/setting.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.validate": [ 3 | "javascript", 4 | "javascriptreact", 5 | { 6 | "language": "typescript", 7 | "autoFix": true 8 | }, 9 | { 10 | "language": "typescriptreact", 11 | "autoFix": true 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/excel-node-generator/.vscode/setting.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.validate": [ 3 | "javascript", 4 | "javascriptreact", 5 | { 6 | "language": "typescript", 7 | "autoFix": true 8 | }, 9 | { 10 | "language": "typescriptreact", 11 | "autoFix": true 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /scripts/tools/lint_pkgs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | cd ./packages 5 | 6 | for file in *; do 7 | [ -d "$file" ] || continue 8 | 9 | if [ "$file" == "config" ]; then 10 | continue 11 | fi 12 | 13 | echo "cd $file"; 14 | cd ./$file 15 | yarn run lint 16 | cd .. 17 | done 18 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/border.js: -------------------------------------------------------------------------------- 1 | import DropdownItem from './dropdown_item'; 2 | import DropdownBorder from '../dropdown_border'; 3 | 4 | export default class Border extends DropdownItem { 5 | constructor() { 6 | super('border'); 7 | } 8 | 9 | dropdown() { 10 | return new DropdownBorder(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@m-fe/tsconfig/tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "@/*": ["packages/rtw-host-app/src/*"] 7 | }, 8 | "strictFunctionTypes": false, 9 | "strictNullChecks": false, 10 | "suppressImplicitAnyIndexErrors": true 11 | }, 12 | "include": ["**/*.ts", "**/*.tsx"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 10.12.0 5 | 6 | branches: 7 | only: 8 | - master 9 | 10 | install: 11 | - npm install -g istanbul 12 | - npm install 13 | 14 | before_script: 15 | 16 | script: 17 | - npm run build 18 | - npm run test 19 | - npm run coverage 20 | 21 | after_script: 22 | - cp ./dist/* ./docs/ -r 23 | 24 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/button.js: -------------------------------------------------------------------------------- 1 | import { Element } from './element'; 2 | import { cssPrefix } from '../config'; 3 | import { t } from '../locale/locale'; 4 | 5 | export default class Button extends Element { 6 | // type: primary 7 | constructor(title, type = '') { 8 | super('div', `${cssPrefix}-button ${type}`); 9 | this.child(t(`button.${title}`)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/font.js: -------------------------------------------------------------------------------- 1 | import DropdownItem from './dropdown_item'; 2 | import DropdownFont from '../dropdown_font'; 3 | 4 | export default class Font extends DropdownItem { 5 | constructor() { 6 | super('font-name'); 7 | } 8 | 9 | getValue(it) { 10 | return it.key; 11 | } 12 | 13 | dropdown() { 14 | return new DropdownFont(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/format.js: -------------------------------------------------------------------------------- 1 | import DropdownItem from './dropdown_item'; 2 | import DropdownFormat from '../dropdown_format'; 3 | 4 | export default class Format extends DropdownItem { 5 | constructor() { 6 | super('format'); 7 | } 8 | 9 | getValue(it) { 10 | return it.key; 11 | } 12 | 13 | dropdown() { 14 | return new DropdownFormat(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/icon_item.js: -------------------------------------------------------------------------------- 1 | import Item from './item'; 2 | import Icon from '../icon'; 3 | 4 | export default class IconItem extends Item { 5 | element() { 6 | return super.element() 7 | .child(new Icon(this.tag)) 8 | .on('click', () => this.change(this.tag)); 9 | } 10 | 11 | setState(disabled) { 12 | this.el.disabled(disabled); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/formula.js: -------------------------------------------------------------------------------- 1 | import DropdownItem from './dropdown_item'; 2 | import DropdownFormula from '../dropdown_formula'; 3 | 4 | export default class Format extends DropdownItem { 5 | constructor() { 6 | super('formula'); 7 | } 8 | 9 | getValue(it) { 10 | return it.key; 11 | } 12 | 13 | dropdown() { 14 | return new DropdownFormula(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/font_size.js: -------------------------------------------------------------------------------- 1 | import DropdownItem from './dropdown_item'; 2 | import DropdownFontsize from '../dropdown_fontsize'; 3 | 4 | export default class Format extends DropdownItem { 5 | constructor() { 6 | super('font-size'); 7 | } 8 | 9 | getValue(it) { 10 | return it.pt; 11 | } 12 | 13 | dropdown() { 14 | return new DropdownFontsize(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/align.js: -------------------------------------------------------------------------------- 1 | import DropdownItem from './dropdown_item'; 2 | import DropdownAlign from '../dropdown_align'; 3 | 4 | export default class Align extends DropdownItem { 5 | constructor(value) { 6 | super('align', '', value); 7 | } 8 | 9 | dropdown() { 10 | const { value } = this; 11 | return new DropdownAlign(['left', 'center', 'right'], value); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/fill_color.js: -------------------------------------------------------------------------------- 1 | import DropdownItem from './dropdown_item'; 2 | import DropdownColor from '../dropdown_color'; 3 | 4 | export default class FillColor extends DropdownItem { 5 | constructor(color) { 6 | super('bgcolor', undefined, color); 7 | } 8 | 9 | dropdown() { 10 | const { tag, value } = this; 11 | return new DropdownColor(tag, value); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/text_color.js: -------------------------------------------------------------------------------- 1 | import DropdownItem from './dropdown_item'; 2 | import DropdownColor from '../dropdown_color'; 3 | 4 | export default class TextColor extends DropdownItem { 5 | constructor(color) { 6 | super('color', undefined, color); 7 | } 8 | 9 | dropdown() { 10 | const { tag, value } = this; 11 | return new DropdownColor(tag, value); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/valign.js: -------------------------------------------------------------------------------- 1 | import DropdownItem from './dropdown_item'; 2 | import DropdownAlign from '../dropdown_align'; 3 | 4 | export default class Valign extends DropdownItem { 5 | constructor(value) { 6 | super('valign', '', value); 7 | } 8 | 9 | dropdown() { 10 | const { value } = this; 11 | return new DropdownAlign(['top', 'middle', 'bottom'], value); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/excel-schema/scripts/webpack/webpack.config.umd.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const merge = require('webpack-merge'); 3 | 4 | const umdConfig = require('../../../../scripts/webpack/webpack.config') 5 | .umdConfig; 6 | 7 | const rootPath = process.cwd(); 8 | 9 | module.exports = merge(umdConfig, { 10 | output: { 11 | library: 'rtwCore', 12 | }, 13 | entry: { 14 | index: path.resolve(rootPath, './src/index.ts'), 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /packages/excel-jexcel-render/scripts/webpack/webpack.config.umd.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const merge = require('webpack-merge'); 3 | 4 | const umdConfig = require('../../../../scripts/webpack/webpack.config') 5 | .umdConfig; 6 | 7 | const rootPath = process.cwd(); 8 | 9 | module.exports = merge(umdConfig, { 10 | output: { 11 | library: 'rtwCore', 12 | }, 13 | entry: { 14 | index: path.resolve(rootPath, './src/index.ts'), 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /packages/excel-node-generator/scripts/webpack/webpack.config.umd.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const merge = require('webpack-merge'); 3 | 4 | const umdConfig = require('../../../../scripts/webpack/webpack.config') 5 | .umdConfig; 6 | 7 | const rootPath = process.cwd(); 8 | 9 | module.exports = merge(umdConfig, { 10 | output: { 11 | library: 'rtwCore', 12 | }, 13 | entry: { 14 | index: path.resolve(rootPath, './src/index.ts'), 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /packages/excel-jexcel-render/src/constant/types.ts: -------------------------------------------------------------------------------- 1 | export interface IBasicModule { 2 | // 模块编号 3 | id: string; 4 | // 模块的加载文件路径 5 | module: string; 6 | // 版本 7 | version?: string; 8 | } 9 | 10 | export interface IAppModule extends IBasicModule { 11 | // 模块标题 12 | name: string; 13 | // 引入的 CSS 路径 14 | css?: string | string[]; 15 | } 16 | 17 | /** 初始化参数 */ 18 | export interface IInitOption { 19 | apps: IAppModule[]; 20 | vendors?: IBasicModule[]; 21 | } 22 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/icon.js: -------------------------------------------------------------------------------- 1 | import { Element, h } from './element'; 2 | import { cssPrefix } from '../config'; 3 | 4 | export default class Icon extends Element { 5 | constructor(name) { 6 | super('div', `${cssPrefix}-icon`); 7 | this.iconNameEl = h('div', `${cssPrefix}-icon-img ${name}`); 8 | this.child(this.iconNameEl); 9 | } 10 | 11 | setName(name) { 12 | this.iconNameEl.className(`${cssPrefix}-icon-img ${name}`); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/core/selector.js: -------------------------------------------------------------------------------- 1 | import { CellRange } from './cell_range'; 2 | 3 | export default class Selector { 4 | constructor() { 5 | this.range = new CellRange(0, 0, 0, 0); 6 | this.ri = 0; 7 | this.ci = 0; 8 | } 9 | 10 | multiple() { 11 | return this.range.multiple(); 12 | } 13 | 14 | setIndexes(ri, ci) { 15 | this.ri = ri; 16 | this.ci = ci; 17 | } 18 | 19 | size() { 20 | return this.range.size(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/dropdown_border.js: -------------------------------------------------------------------------------- 1 | import Dropdown from './dropdown'; 2 | import Icon from './icon'; 3 | import BorderPalette from './border_palette'; 4 | 5 | export default class DropdownBorder extends Dropdown { 6 | constructor() { 7 | const icon = new Icon('border-all'); 8 | const borderPalette = new BorderPalette(); 9 | borderPalette.change = (v) => { 10 | this.change(v); 11 | this.hide(); 12 | }; 13 | super(icon, 'auto', false, 'bottom-left', borderPalette.el); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /scripts/docker/Dockerfile.gitlab: -------------------------------------------------------------------------------- 1 | FROM registry.mybiz.com/ufc/ufc-fe-builder:latest as builder 2 | 3 | RUN apk upgrade --update-cache --available \\ 4 | && apk add --upgrade autoconf automake build-base libtool nasm pkgconfig zlib-dev libpng-dev 5 | 6 | # 安装与编译代码 7 | COPY . /app 8 | WORKDIR /app 9 | 10 | RUN yarn install --frozen-lockfile --registry https://registry.npm.taobao.org/ 11 | RUN yarn build 12 | RUN find . -name '*.map' -type f -exec rm -f {} \; 13 | 14 | # 最终的应用 15 | FROM abiosoft/caddy 16 | COPY --from=builder /app/build /srv 17 | EXPOSE 2015 18 | -------------------------------------------------------------------------------- /scripts/docker/build-locally-dev.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # 本地构建并推送项目镜像 4 | # 5 | # Globals: 6 | # DOCKER_REGISTRY_SERVER 7 | # TAG 8 | 9 | set -e 10 | 11 | cd $(dirname $0)/../.. 12 | 13 | DOCKER_REGISTRY_SERVER=${DOCKER_REGISTRY_SERVER:=registry.mybiz.com} 14 | IMAGE=${DOCKER_REGISTRY_SERVER}/m-fe-web-client 15 | TAG=${TAG:=latest} 16 | 17 | yarn build 18 | 19 | echo "[*] Finished building" 20 | 21 | docker build --tag $IMAGE:$TAG -f scripts/docker/Dockerfile.local ./build 22 | 23 | echo "[*] Pushing $IMAGE:$TAG" 24 | 25 | docker push $IMAGE:$TAG 26 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/dropdown_item.js: -------------------------------------------------------------------------------- 1 | import Item from './item'; 2 | 3 | export default class DropdownItem extends Item { 4 | dropdown() {} 5 | 6 | getValue(v) { 7 | return v; 8 | } 9 | 10 | element() { 11 | const { tag } = this; 12 | this.dd = this.dropdown(); 13 | this.dd.change = it => this.change(tag, this.getValue(it)); 14 | return super.element().child( 15 | this.dd, 16 | ); 17 | } 18 | 19 | setState(v) { 20 | if (v) { 21 | this.value = v; 22 | this.dd.setTitle(v); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/excel-schema/src/WorksheetCellCommentDO.ts: -------------------------------------------------------------------------------- 1 | import { RichText } from './value'; 2 | 3 | export interface CommentMargins { 4 | insetmode: 'auto' | 'custom'; 5 | inset: number[]; 6 | } 7 | 8 | export interface CommentProtection { 9 | locked: 'True' | 'False'; 10 | lockText: 'True' | 'False'; 11 | } 12 | 13 | export type CommentEditAs = 'twoCells' | 'oneCells' | 'absolute'; 14 | 15 | export interface WorksheetCellCommentDO { 16 | texts?: RichText[]; 17 | margins?: Partial; 18 | protection?: Partial; 19 | editAs?: CommentEditAs; 20 | } 21 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/dropdown_font.js: -------------------------------------------------------------------------------- 1 | import Dropdown from './dropdown'; 2 | import { h } from './element'; 3 | import { baseFonts } from '../core/font'; 4 | import { cssPrefix } from '../config'; 5 | 6 | export default class DropdownFont extends Dropdown { 7 | constructor() { 8 | const nfonts = baseFonts.map(it => h('div', `${cssPrefix}-item`) 9 | .on('click', () => { 10 | this.setTitle(it.title); 11 | this.change(it); 12 | }) 13 | .child(it.title)); 14 | super(baseFonts[0].title, '160px', true, 'bottom-left', ...nfonts); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/dropdown_fontsize.js: -------------------------------------------------------------------------------- 1 | import Dropdown from './dropdown'; 2 | import { h } from './element'; 3 | import { fontSizes } from '../core/font'; 4 | import { cssPrefix } from '../config'; 5 | 6 | export default class DropdownFontSize extends Dropdown { 7 | constructor() { 8 | const nfontSizes = fontSizes.map(it => h('div', `${cssPrefix}-item`) 9 | .on('click', () => { 10 | this.setTitle(`${it.pt}`); 11 | this.change(it); 12 | }) 13 | .child(`${it.pt}`)); 14 | super('10', '60px', true, 'bottom-left', ...nfontSizes); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@m-fe/tslint-config/react", "tslint-config-eslint/prettier"], 3 | "rules": { 4 | "jsx-no-lambda": false, 5 | "ordered-imports": [ 6 | true, 7 | { 8 | "grouped-imports": true, 9 | "import-sources-order": "lowercase-first", 10 | "named-imports-order": "lowercase-last", 11 | "groups": [ 12 | "^(?!rtw-)(@[^/]|[^@.])", 13 | "^(@/|rtw-)", 14 | "^(../)+", 15 | "^(?!\\./.*\\.less)" 16 | ] 17 | } 18 | ] 19 | }, 20 | "linterOptions": { 21 | "exclude": ["**/*.d.ts"] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/dropdown_formula.js: -------------------------------------------------------------------------------- 1 | import Dropdown from './dropdown'; 2 | import Icon from './icon'; 3 | import { h } from './element'; 4 | import { baseFormulas } from '../core/formula'; 5 | import { cssPrefix } from '../config'; 6 | 7 | export default class DropdownFormula extends Dropdown { 8 | constructor() { 9 | const nformulas = baseFormulas.map(it => h('div', `${cssPrefix}-item`) 10 | .on('click', () => { 11 | this.hide(); 12 | this.change(it); 13 | }) 14 | .child(it.key)); 15 | super(new Icon('formula'), '180px', true, 'bottom-left', ...nformulas); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/toggle_item.js: -------------------------------------------------------------------------------- 1 | import Item from './item'; 2 | import Icon from '../icon'; 3 | 4 | export default class ToggleItem extends Item { 5 | element() { 6 | const { tag } = this; 7 | return super.element() 8 | .child(new Icon(tag)) 9 | .on('click', () => this.click()); 10 | } 11 | 12 | click() { 13 | this.change(this.tag, this.toggle()); 14 | } 15 | 16 | setState(active) { 17 | this.el.active(active); 18 | } 19 | 20 | toggle() { 21 | return this.el.toggle(); 22 | } 23 | 24 | active() { 25 | return this.el.hasClass('active'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/core/_.prototypes.js: -------------------------------------------------------------------------------- 1 | // font.js 2 | /** 3 | * @typedef {number} fontsizePX px for fontSize 4 | */ 5 | /** 6 | * @typedef {number} fontsizePT pt for fontSize 7 | */ 8 | /** 9 | * @typedef {object} BaseFont 10 | * @property {string} key inner key 11 | * @property {string} title title for display 12 | */ 13 | 14 | /** 15 | * @typedef {object} FontSize 16 | * @property {fontsizePT} pt 17 | * @property {fontsizePX} px 18 | */ 19 | 20 | // alphabet.js 21 | /** 22 | * @typedef {string} tagA1 A1 tag for XY-tag (0, 0) 23 | * @example "A1" 24 | */ 25 | /** 26 | * @typedef {[number, number]} tagXY 27 | * @example [0, 0] 28 | */ 29 | -------------------------------------------------------------------------------- /packages/excel-schema/src/constants.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/camelcase */ 2 | export enum RelationshipType { 3 | None = 0, 4 | OfficeDocument = 1, 5 | Worksheet = 2, 6 | CalcChain = 3, 7 | SharedStrings = 4, 8 | Styles = 5, 9 | Theme = 6, 10 | Hyperlink = 7, 11 | } 12 | 13 | export enum DocumentType { 14 | Xlsx = 1, 15 | } 16 | 17 | export enum PaperSize { 18 | Legal = 5, 19 | Executive = 7, 20 | A4 = 9, 21 | A5 = 11, 22 | B5 = 13, 23 | Envelope_10 = 20, 24 | Envelope_DL = 27, 25 | Envelope_C5 = 28, 26 | Envelope_B5 = 34, 27 | Envelope_Monarch = 37, 28 | Double_Japan_Postcard_Rotated = 82, 29 | K16_197x273_mm = 119, 30 | } 31 | -------------------------------------------------------------------------------- /scripts/docker/build-locally-release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # 本地构建并推送项目镜像 4 | # 5 | # Globals: 6 | # DOCKER_REGISTRY_SERVER 7 | # TAG 8 | 9 | set -e 10 | 11 | cd $(dirname $0)/../.. 12 | 13 | PACKAGE_VERSION=$(cat package.json | grep version | head -1 | awk -F= "{ print $2 }" | sed 's/[version:,\",]//g' | tr -d '[[:space:]]') 14 | 15 | DOCKER_REGISTRY_SERVER=${DOCKER_REGISTRY_SERVER:=registry.mybiz.com} 16 | IMAGE=${DOCKER_REGISTRY_SERVER}/m-fe-web-client 17 | TAG=v${PACKAGE_VERSION:=latest}-rc 18 | 19 | yarn build 20 | 21 | echo "[*] Finished building" 22 | 23 | docker build --tag $IMAGE:$TAG -f scripts/docker/Dockerfile.local ./build 24 | 25 | echo "[*] Pushing $IMAGE:$TAG" 26 | 27 | docker push $IMAGE:$TAG 28 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/form_input.js: -------------------------------------------------------------------------------- 1 | import { h } from './element'; 2 | import { cssPrefix } from '../config'; 3 | 4 | export default class FormInput { 5 | constructor(width, hint) { 6 | this.vchange = () => {}; 7 | this.el = h('div', `${cssPrefix}-form-input`); 8 | this.input = h('input', '').css('width', width) 9 | .on('input', evt => this.vchange(evt)) 10 | .attr('placeholder', hint); 11 | this.el.child(this.input); 12 | } 13 | 14 | focus() { 15 | setTimeout(() => { 16 | this.input.el.focus(); 17 | }, 10); 18 | } 19 | 20 | hint(v) { 21 | this.input.attr('placeholder', v); 22 | } 23 | 24 | val(v) { 25 | return this.input.val(v); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/core/clipboard.js: -------------------------------------------------------------------------------- 1 | export default class Clipboard { 2 | constructor() { 3 | this.range = null; // CellRange 4 | this.state = 'clear'; 5 | } 6 | 7 | copy(cellRange) { 8 | this.range = cellRange; 9 | this.state = 'copy'; 10 | return this; 11 | } 12 | 13 | cut(cellRange) { 14 | this.range = cellRange; 15 | this.state = 'cut'; 16 | return this; 17 | } 18 | 19 | isCopy() { 20 | return this.state === 'copy'; 21 | } 22 | 23 | isCut() { 24 | return this.state === 'cut'; 25 | } 26 | 27 | isClear() { 28 | return this.state === 'clear'; 29 | } 30 | 31 | clear() { 32 | this.range = null; 33 | this.state = 'clear'; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/dropdown_color.js: -------------------------------------------------------------------------------- 1 | import Dropdown from './dropdown'; 2 | import Icon from './icon'; 3 | import ColorPalette from './color_palette'; 4 | 5 | export default class DropdownColor extends Dropdown { 6 | constructor(iconName, color) { 7 | const icon = new Icon(iconName) 8 | .css('height', '16px') 9 | .css('border-bottom', `3px solid ${color}`); 10 | const colorPalette = new ColorPalette(); 11 | colorPalette.change = (v) => { 12 | this.setTitle(v); 13 | this.change(v); 14 | }; 15 | super(icon, 'auto', false, 'bottom-left', colorPalette.el); 16 | } 17 | 18 | setTitle(color) { 19 | this.title.css('border-color', color); 20 | this.hide(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | DOCKER_DRIVER: overlay2 3 | EFF_NO_LINK_RULES: 'true' 4 | GIT_CLEAN_FLAGS: -ffdx -e /dist/ 5 | NODE_OPTIONS: --max-old-space-size=2048 6 | PARSER_NO_WATCH: 'true' 7 | DOCKER_TLS_CERTDIR: '' 8 | DOCKER_HOST: tcp://localhost:2375 9 | DOCKER_VERSION: 19.03.5 10 | 11 | build image: 12 | only: 13 | - dev 14 | - master 15 | stage: build 16 | image: docker:${DOCKER_VERSION} 17 | services: 18 | - name: docker:${DOCKER_VERSION}-dind 19 | before_script: 20 | - docker info 21 | - docker login ${DOCKER_REGISTRY_SERVER} -u ${DOCKER_REGISTRY_USER} -p ${DOCKER_REGISTRY_PASSWORD} 22 | script: 23 | - export IMAGE=${DOCKER_REGISTRY_SERVER}/m-fe-web-client 24 | - sh ./scripts/docker/build-on-gitlab.sh 25 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: x-spreadsheet # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/dropdown_align.js: -------------------------------------------------------------------------------- 1 | import Dropdown from './dropdown'; 2 | import { h } from './element'; 3 | import Icon from './icon'; 4 | import { cssPrefix } from '../config'; 5 | 6 | function buildItemWithIcon(iconName) { 7 | return h('div', `${cssPrefix}-item`).child(new Icon(iconName)); 8 | } 9 | 10 | export default class DropdownAlign extends Dropdown { 11 | constructor(aligns, align) { 12 | const icon = new Icon(`align-${align}`); 13 | const naligns = aligns.map(it => buildItemWithIcon(`align-${it}`) 14 | .on('click', () => { 15 | this.setTitle(it); 16 | this.change(it); 17 | })); 18 | super(icon, 'auto', true, 'bottom-left', ...naligns); 19 | } 20 | 21 | setTitle(align) { 22 | this.title.setName(`align-${align}`); 23 | this.hide(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/more.js: -------------------------------------------------------------------------------- 1 | import Dropdown from '../dropdown'; 2 | import DropdownItem from './dropdown_item'; 3 | 4 | import { cssPrefix } from '../../config'; 5 | import { h } from '../element'; 6 | import Icon from '../icon'; 7 | 8 | class DropdownMore extends Dropdown { 9 | constructor() { 10 | const icon = new Icon('ellipsis'); 11 | const moreBtns = h('div', `${cssPrefix}-toolbar-more`); 12 | super(icon, 'auto', false, 'bottom-right', moreBtns); 13 | this.moreBtns = moreBtns; 14 | this.contentEl.css('max-width', '420px'); 15 | } 16 | } 17 | 18 | export default class More extends DropdownItem { 19 | constructor() { 20 | super('more'); 21 | this.el.hide(); 22 | } 23 | 24 | dropdown() { 25 | return new DropdownMore(); 26 | } 27 | 28 | show() { 29 | this.el.show(); 30 | } 31 | 32 | hide() { 33 | this.el.hide(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/core/history.js: -------------------------------------------------------------------------------- 1 | // import helper from '../helper'; 2 | 3 | export default class History { 4 | constructor() { 5 | this.undoItems = []; 6 | this.redoItems = []; 7 | } 8 | 9 | add(data) { 10 | this.undoItems.push(JSON.stringify(data)); 11 | this.redoItems = []; 12 | } 13 | 14 | canUndo() { 15 | return this.undoItems.length > 0; 16 | } 17 | 18 | canRedo() { 19 | return this.redoItems.length > 0; 20 | } 21 | 22 | undo(currentd, cb) { 23 | const { undoItems, redoItems } = this; 24 | if (this.canUndo()) { 25 | redoItems.push(JSON.stringify(currentd)); 26 | cb(JSON.parse(undoItems.pop())); 27 | } 28 | } 29 | 30 | redo(currentd, cb) { 31 | const { undoItems, redoItems } = this; 32 | if (this.canRedo()) { 33 | undoItems.push(JSON.stringify(currentd)); 34 | cb(JSON.parse(redoItems.pop())); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /scripts/webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@m-fe/webpack-config')({ 2 | themeVars: { 3 | 'primary-color': '#5d4bff', 4 | }, 5 | extendedBaseConfig: { 6 | module: { 7 | rules: [ 8 | // svg 的加载交于应用自身决定 9 | { 10 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 11 | oneOf: [ 12 | { 13 | issuer: /\.[jt]sx?$/, 14 | use: [ 15 | { 16 | loader: '@svgr/webpack', 17 | // loader: 'svg-inline-loader', 18 | }, 19 | ], 20 | }, 21 | { 22 | loader: 'url-loader', 23 | }, 24 | ], 25 | }, 26 | ], 27 | }, 28 | resolve: { 29 | alias: { 30 | dayjs: 'dayjs/esm', 31 | moment$: 'dayjs/esm', 32 | systemjs$: 'systemjs/dist/system.js', 33 | }, 34 | }, 35 | }, 36 | }); 37 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/toolbar/item.js: -------------------------------------------------------------------------------- 1 | import { cssPrefix } from '../../config'; 2 | import tooltip from '../tooltip'; 3 | import { h } from '../element'; 4 | import { t } from '../../locale/locale'; 5 | 6 | export default class Item { 7 | // tooltip 8 | // tag: the subclass type 9 | // shortcut: shortcut key 10 | constructor(tag, shortcut, value) { 11 | this.tip = t(`toolbar.${tag.replace(/-[a-z]/g, c => c[1].toUpperCase())}`); 12 | if (shortcut) this.tip += ` (${shortcut})`; 13 | this.tag = tag; 14 | this.shortcut = shortcut; 15 | this.value = value; 16 | this.el = this.element(); 17 | this.change = () => {}; 18 | } 19 | 20 | element() { 21 | const { tip } = this; 22 | return h('div', `${cssPrefix}-toolbar-btn`) 23 | .on('mouseenter', (evt) => { 24 | tooltip(tip, evt.target); 25 | }) 26 | .attr('data-tooltip', tip); 27 | } 28 | 29 | setState() {} 30 | } 31 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/tooltip.js: -------------------------------------------------------------------------------- 1 | /* global document */ 2 | import { h } from './element'; 3 | import { bind } from './event'; 4 | import { cssPrefix } from '../config'; 5 | 6 | export default function tooltip(html, target) { 7 | if (target.classList.contains('active')) { 8 | return; 9 | } 10 | const { 11 | left, top, width, height, 12 | } = target.getBoundingClientRect(); 13 | const el = h('div', `${cssPrefix}-tooltip`).html(html).show(); 14 | document.body.appendChild(el.el); 15 | const elBox = el.box(); 16 | // console.log('elBox:', elBox); 17 | el.css('left', `${left + (width / 2) - (elBox.width / 2)}px`) 18 | .css('top', `${top + height + 2}px`); 19 | 20 | bind(target, 'mouseleave', () => { 21 | if (document.body.contains(el.el)) { 22 | document.body.removeChild(el.el); 23 | } 24 | }); 25 | 26 | bind(target, 'click', () => { 27 | if (document.body.contains(el.el)) { 28 | document.body.removeChild(el.el); 29 | } 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/message.js: -------------------------------------------------------------------------------- 1 | /* global document */ 2 | import { h } from './element'; 3 | import Icon from './icon'; 4 | import { cssPrefix } from '../config'; 5 | 6 | export function xtoast(title, content) { 7 | const el = h('div', `${cssPrefix}-toast`); 8 | const dimmer = h('div', `${cssPrefix}-dimmer active`); 9 | const remove = () => { 10 | document.body.removeChild(el.el); 11 | document.body.removeChild(dimmer.el); 12 | }; 13 | 14 | el.children( 15 | h('div', `${cssPrefix}-toast-header`).children( 16 | new Icon('close').on('click.stop', () => remove()), 17 | title, 18 | ), 19 | h('div', `${cssPrefix}-toast-content`).html(content), 20 | ); 21 | document.body.appendChild(el.el); 22 | document.body.appendChild(dimmer.el); 23 | // set offset 24 | const { width, height } = el.box(); 25 | const { clientHeight, clientWidth } = document.documentElement; 26 | el.offset({ 27 | left: (clientWidth - width) / 2, 28 | top: (clientHeight - height) / 3, 29 | }); 30 | } 31 | 32 | export default {}; 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 王下邀月熊 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/datepicker.js: -------------------------------------------------------------------------------- 1 | import Calendar from './calendar'; 2 | import { h } from './element'; 3 | import { cssPrefix } from '../config'; 4 | 5 | export default class Datepicker { 6 | constructor() { 7 | this.calendar = new Calendar(new Date()); 8 | this.el = h('div', `${cssPrefix}-datepicker`).child( 9 | this.calendar.el, 10 | ).hide(); 11 | } 12 | 13 | setValue(date) { 14 | // console.log(':::::::', date, typeof date, date instanceof string); 15 | const { calendar } = this; 16 | if (typeof date === 'string') { 17 | // console.log(/^\d{4}-\d{1,2}-\d{1,2}$/.test(date)); 18 | if (/^\d{4}-\d{1,2}-\d{1,2}$/.test(date)) { 19 | calendar.setValue(new Date(date.replace(new RegExp('-', 'g'), '/'))); 20 | } 21 | } else if (date instanceof Date) { 22 | calendar.setValue(date); 23 | } 24 | return this; 25 | } 26 | 27 | change(cb) { 28 | this.calendar.selectChange = (d) => { 29 | cb(d); 30 | this.hide(); 31 | }; 32 | } 33 | 34 | show() { 35 | this.el.show(); 36 | } 37 | 38 | hide() { 39 | this.el.hide(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/locale/locale.js: -------------------------------------------------------------------------------- 1 | /* global window */ 2 | import en from './en'; 3 | 4 | let $lang = 'en'; 5 | const $messages = { 6 | en, 7 | }; 8 | 9 | function translate(key, messages) { 10 | if (messages && messages[$lang]) { 11 | let message = messages[$lang]; 12 | const keys = key.split('.'); 13 | for (let i = 0; i < keys.length; i += 1) { 14 | const property = keys[i]; 15 | const value = message[property]; 16 | if (i === keys.length - 1) return value; 17 | if (!value) return undefined; 18 | message = value; 19 | } 20 | } 21 | return undefined; 22 | } 23 | 24 | function t(key) { 25 | let v = translate(key, $messages); 26 | if (!v && window && window.x_spreadsheet && window.x_spreadsheet.$messages) { 27 | v = translate(key, window.x_spreadsheet.$messages); 28 | } 29 | return v || ''; 30 | } 31 | 32 | function tf(key) { 33 | return () => t(key); 34 | } 35 | 36 | function locale(lang, message) { 37 | $lang = lang; 38 | if (message) { 39 | $messages[lang] = message; 40 | } 41 | } 42 | 43 | export default { 44 | t, 45 | }; 46 | 47 | export { 48 | locale, 49 | t, 50 | tf, 51 | }; 52 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/form_select.js: -------------------------------------------------------------------------------- 1 | import { h } from './element'; 2 | import Suggest from './suggest'; 3 | import { cssPrefix } from '../config'; 4 | 5 | export default class FormSelect { 6 | constructor(key, items, width, getTitle = it => it, change = () => {}) { 7 | this.key = key; 8 | this.getTitle = getTitle; 9 | this.vchange = () => {}; 10 | this.el = h('div', `${cssPrefix}-form-select`); 11 | this.suggest = new Suggest(items.map(it => ({ key: it, title: this.getTitle(it) })), (it) => { 12 | this.itemClick(it.key); 13 | change(it.key); 14 | this.vchange(it.key); 15 | }, width, this.el); 16 | this.el.children( 17 | this.itemEl = h('div', 'input-text').html(this.getTitle(key)), 18 | this.suggest.el, 19 | ).on('click', () => this.show()); 20 | } 21 | 22 | show() { 23 | this.suggest.search(''); 24 | } 25 | 26 | itemClick(it) { 27 | this.key = it; 28 | this.itemEl.html(this.getTitle(it)); 29 | } 30 | 31 | val(v) { 32 | if (v !== undefined) { 33 | this.key = v; 34 | this.itemEl.html(this.getTitle(v)); 35 | return this; 36 | } 37 | return this.key; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/algorithm/expression.js: -------------------------------------------------------------------------------- 1 | // src: include chars: [0-9], +, -, *, / 2 | // // 9+(3-1)*3+10/2 => 9 3 1-3*+ 10 2/+ 3 | const infix2suffix = (src) => { 4 | const operatorStack = []; 5 | const stack = []; 6 | for (let i = 0; i < src.length; i += 1) { 7 | const c = src.charAt(i); 8 | if (c !== ' ') { 9 | if (c >= '0' && c <= '9') { 10 | stack.push(c); 11 | } else if (c === ')') { 12 | let c1 = operatorStack.pop(); 13 | while (c1 !== '(') { 14 | stack.push(c1); 15 | c1 = operatorStack.pop(); 16 | } 17 | } else { 18 | // priority: */ > +- 19 | if (operatorStack.length > 0 && (c === '+' || c === '-')) { 20 | const last = operatorStack[operatorStack.length - 1]; 21 | if (last === '*' || last === '/') { 22 | while (operatorStack.length > 0) { 23 | stack.push(operatorStack.pop()); 24 | } 25 | } 26 | } 27 | operatorStack.push(c); 28 | } 29 | } 30 | } 31 | while (operatorStack.length > 0) { 32 | stack.push(operatorStack.pop()); 33 | } 34 | return stack; 35 | }; 36 | 37 | export default { 38 | infix2suffix, 39 | }; 40 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/dropdown_format.js: -------------------------------------------------------------------------------- 1 | import Dropdown from './dropdown'; 2 | import { h } from './element'; 3 | import { baseFormats } from '../core/format'; 4 | import { cssPrefix } from '../config'; 5 | 6 | export default class DropdownFormat extends Dropdown { 7 | constructor() { 8 | let nformats = baseFormats.slice(0); 9 | nformats.splice(2, 0, { key: 'divider' }); 10 | nformats.splice(8, 0, { key: 'divider' }); 11 | nformats = nformats.map((it) => { 12 | const item = h('div', `${cssPrefix}-item`); 13 | if (it.key === 'divider') { 14 | item.addClass('divider'); 15 | } else { 16 | item.child(it.title()) 17 | .on('click', () => { 18 | this.setTitle(it.title()); 19 | this.change(it); 20 | }); 21 | if (it.label) item.child(h('div', 'label').html(it.label)); 22 | } 23 | return item; 24 | }); 25 | super('Normal', '220px', true, 'bottom-left', ...nformats); 26 | } 27 | 28 | setTitle(key) { 29 | for (let i = 0; i < baseFormats.length; i += 1) { 30 | if (baseFormats[i].key === key) { 31 | this.title.html(baseFormats[i].title()); 32 | } 33 | } 34 | this.hide(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/excel-schema/src/WorkbookDO.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, isValidArray } from '@m-fe/utils'; 2 | 3 | import { WorksheetDO } from './WorksheetDO'; 4 | 5 | export interface WorkbookProperties { 6 | /** 7 | * Set workbook dates to 1904 date system 8 | */ 9 | date1904: boolean; 10 | } 11 | 12 | /** Excel 文件对象 */ 13 | export class WorkbookDO extends BaseEntity { 14 | /** 基础属性 */ 15 | creator: string; 16 | lastModifiedBy: string; 17 | lastPrinted: string; 18 | created: string; 19 | modified: string; 20 | company: string; 21 | manager: string; 22 | title: string; 23 | subject: string; 24 | keywords: string; 25 | category: string; 26 | description: string; 27 | language: string; 28 | revision: string; 29 | contentStatus: string; 30 | themes: string[]; 31 | 32 | properties: WorkbookProperties; 33 | 34 | /** 内容属性 */ 35 | sheets: Partial[] = []; 36 | 37 | optimize() { 38 | // 优化 Sheet 39 | for (const s of this.sheets) { 40 | s.optimize(); 41 | } 42 | } 43 | 44 | constructor(data: Partial = {}) { 45 | super(data); 46 | 47 | Object.assign(this, data); 48 | 49 | if (isValidArray(data.sheets)) { 50 | this.sheets = data.sheets.map(s => new WorksheetDO(s)); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/excel-node-generator/src/utils.ts: -------------------------------------------------------------------------------- 1 | import * as U from '@m-fe/utils'; 2 | import axios from 'axios'; 3 | import { Cell, Column, Row, Style } from 'exceljs'; 4 | 5 | /** 从 URL 中获取图片的 Base64 */ 6 | export async function getImageAsBase64(url: string) { 7 | return ( 8 | axios 9 | // 这里仅对中文和特殊字符编码 10 | .get(U.encodeUri(url), { 11 | responseType: 'arraybuffer', 12 | }) 13 | .then(response => Buffer.from(response.data, 'binary').toString('base64')) 14 | ); 15 | } 16 | 17 | /** 合并样式对象 */ 18 | export function mergeStyle( 19 | obj: Cell | Partial | Partial, 20 | style: Partial 33 | 42 |
43 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/core/col.js: -------------------------------------------------------------------------------- 1 | import helper from './helper'; 2 | 3 | class Cols { 4 | constructor({ 5 | len, width, indexWidth, minWidth, 6 | }) { 7 | this._ = {}; 8 | this.len = len; 9 | this.width = width; 10 | this.indexWidth = indexWidth; 11 | this.minWidth = minWidth; 12 | } 13 | 14 | setData(d) { 15 | if (d.len) { 16 | this.len = d.len; 17 | delete d.len; 18 | } 19 | this._ = d; 20 | } 21 | 22 | getData() { 23 | const { len } = this; 24 | return Object.assign({ len }, this._); 25 | } 26 | 27 | getWidth(i) { 28 | if (this.isHide(i)) return 0; 29 | const col = this._[i]; 30 | if (col && col.width) { 31 | return col.width; 32 | } 33 | return this.width; 34 | } 35 | 36 | getOrNew(ci) { 37 | this._[ci] = this._[ci] || {}; 38 | return this._[ci]; 39 | } 40 | 41 | setWidth(ci, width) { 42 | const col = this.getOrNew(ci); 43 | col.width = width; 44 | } 45 | 46 | unhide(idx) { 47 | let index = idx; 48 | while (index > 0) { 49 | index -= 1; 50 | if (this.isHide(index)) { 51 | this.setHide(index, false); 52 | } else break; 53 | } 54 | } 55 | 56 | isHide(ci) { 57 | const col = this._[ci]; 58 | return col && col.hide; 59 | } 60 | 61 | setHide(ci, v) { 62 | const col = this.getOrNew(ci); 63 | if (v === true) col.hide = true; 64 | else delete col.hide; 65 | } 66 | 67 | setStyle(ci, style) { 68 | const col = this.getOrNew(ci); 69 | col.style = style; 70 | } 71 | 72 | sumWidth(min, max) { 73 | return helper.rangeSum(min, max, i => this.getWidth(i)); 74 | } 75 | 76 | totalWidth() { 77 | return this.sumWidth(0, this.len); 78 | } 79 | } 80 | 81 | export default {}; 82 | export { 83 | Cols, 84 | }; 85 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/canvas/draw2.js: -------------------------------------------------------------------------------- 1 | class Draw { 2 | constructor(el) { 3 | this.el = el; 4 | this.ctx = el.getContext('2d'); 5 | } 6 | 7 | clear() { 8 | const { width, height } = this.el; 9 | this.ctx.clearRect(0, 0, width, height); 10 | return this; 11 | } 12 | 13 | attr(m) { 14 | Object.assign(this.ctx, m); 15 | return this; 16 | } 17 | 18 | save() { 19 | this.ctx.save(); 20 | this.ctx.beginPath(); 21 | return this; 22 | } 23 | 24 | restore() { 25 | this.ctx.restore(); 26 | return this; 27 | } 28 | 29 | beginPath() { 30 | this.ctx.beginPath(); 31 | return this; 32 | } 33 | 34 | closePath() { 35 | this.ctx.closePath(); 36 | return this; 37 | } 38 | 39 | measureText(text) { 40 | return this.ctx.measureText(text); 41 | } 42 | 43 | rect(x, y, width, height) { 44 | this.ctx.rect(x, y, width, height); 45 | return this; 46 | } 47 | 48 | scale(x, y) { 49 | this.ctx.scale(x, y); 50 | return this; 51 | } 52 | 53 | rotate(angle) { 54 | this.ctx.rotate(angle); 55 | return this; 56 | } 57 | 58 | translate(x, y) { 59 | this.ctx.translate(x, y); 60 | return this; 61 | } 62 | 63 | transform(a, b, c, d, e) { 64 | this.ctx.transform(a, b, c, d, e); 65 | return this; 66 | } 67 | 68 | fillRect(x, y, w, h) { 69 | this.ctx.fillRect(x, y, w, h); 70 | return this; 71 | } 72 | 73 | strokeRect(x, y, w, h) { 74 | this.ctx.strokeRect(x, y, w, h); 75 | return this; 76 | } 77 | 78 | fillText(text, x, y, maxWidth) { 79 | this.ctx.fillText(text, x, y, maxWidth); 80 | return this; 81 | } 82 | 83 | strokeText(text, x, y, maxWidth) { 84 | this.ctx.strokeText(text, x, y, maxWidth); 85 | return this; 86 | } 87 | } 88 | 89 | export default {}; 90 | export { Draw }; 91 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/form_field.js: -------------------------------------------------------------------------------- 1 | import { h } from './element'; 2 | import { cssPrefix } from '../config'; 3 | import { t } from '../locale/locale'; 4 | 5 | const patterns = { 6 | number: /(^\d+$)|(^\d+(\.\d{0,4})?$)/, 7 | date: /^\d{4}-\d{1,2}-\d{1,2}$/, 8 | }; 9 | 10 | // rule: { required: false, type, pattern: // } 11 | export default class FormField { 12 | constructor(input, rule, label, labelWidth) { 13 | this.label = ''; 14 | this.rule = rule; 15 | if (label) { 16 | this.label = h('label', 'label').css('width', `${labelWidth}px`).html(label); 17 | } 18 | this.tip = h('div', 'tip').child('tip').hide(); 19 | this.input = input; 20 | this.input.vchange = () => this.validate(); 21 | this.el = h('div', `${cssPrefix}-form-field`) 22 | .children(this.label, input.el, this.tip); 23 | } 24 | 25 | isShow() { 26 | return this.el.css('display') !== 'none'; 27 | } 28 | 29 | show() { 30 | this.el.show(); 31 | } 32 | 33 | hide() { 34 | this.el.hide(); 35 | return this; 36 | } 37 | 38 | val(v) { 39 | return this.input.val(v); 40 | } 41 | 42 | hint(hint) { 43 | this.input.hint(hint); 44 | } 45 | 46 | validate() { 47 | const { 48 | input, rule, tip, el, 49 | } = this; 50 | const v = input.val(); 51 | if (rule.required) { 52 | if (/^\s*$/.test(v)) { 53 | tip.html(t('validation.required')); 54 | el.addClass('error'); 55 | return false; 56 | } 57 | } 58 | if (rule.type || rule.pattern) { 59 | const pattern = rule.pattern || patterns[rule.type]; 60 | if (!pattern.test(v)) { 61 | tip.html(t('validation.notMatch')); 62 | el.addClass('error'); 63 | return false; 64 | } 65 | } 66 | el.removeClass('error'); 67 | return true; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/test/helper_test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import { describe, it } from 'mocha'; 3 | import helper from '../src/core/helper'; 4 | 5 | describe('helper', () => { 6 | describe('.cloneDeep()', () => { 7 | it('The modification of the returned value does not affect the original value', () => { 8 | const obj = { k: { k1: 'v' } }; 9 | const obj1 = helper.cloneDeep(obj); 10 | obj1.k.k1 = 'v1'; 11 | assert.equal(obj.k.k1, 'v'); 12 | }); 13 | }); 14 | describe('.merge()', () => { 15 | it('should return { a: \'a\' } where the value is { a: \'a\' }', () => { 16 | const merge = helper.merge({ a: 'a' }); 17 | assert.equal(merge.a, 'a'); 18 | }); 19 | it('should return {a: \'a\', b: \'b\'} where the value is {a: \'a\'}, {b: \'b\'}', () => { 20 | const merge = helper.merge({ a: 'a' }, { b: 'b' }); 21 | assert.equal(merge.a, 'a'); 22 | assert.equal(merge.b, 'b'); 23 | }); 24 | it('should return { a: { a1: \'a2\' }, b: \'b\' } where the value is {a: {a1: \'a1\'}, b: \'b\'}, {a: {a1: \'b\'}}', () => { 25 | const obj = { a: { a1: 'a1' }, b: 'b' }; 26 | const merge = helper.merge(obj, { a: { a1: 'a2' } }); 27 | assert.equal(obj.a.a1, 'a1'); 28 | assert.equal(merge.a.a1, 'a2'); 29 | assert.equal(merge.b, 'b'); 30 | }); 31 | }); 32 | // sum 33 | describe('.sum()', () => { 34 | it('should return [50, 3] where the value is [10, 20, 20]', () => { 35 | const [total, size] = helper.sum([10, 20, 20]); 36 | assert.equal(total, 50); 37 | assert.equal(size, 3); 38 | }); 39 | it('should return [50, 3] where the value is {k1: 10, k2: 20, k3: 20}', () => { 40 | const [total, size] = helper.sum({ k1: 10, k2: 20, k3: 20 }); 41 | assert.equal(total, 50); 42 | assert.equal(size, 3); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/core/font.js: -------------------------------------------------------------------------------- 1 | // docs 2 | import './_.prototypes'; 3 | 4 | /** default font list 5 | * @type {BaseFont[]} 6 | */ 7 | const baseFonts = [ 8 | { key: 'Arial', title: 'Arial' }, 9 | { key: 'Helvetica', title: 'Helvetica' }, 10 | { key: 'Source Sans Pro', title: 'Source Sans Pro' }, 11 | { key: 'Comic Sans MS', title: 'Comic Sans MS' }, 12 | { key: 'Courier New', title: 'Courier New' }, 13 | { key: 'Verdana', title: 'Verdana' }, 14 | { key: 'Lato', title: 'Lato' }, 15 | ]; 16 | 17 | /** default fontSize list 18 | * @type {FontSize[]} 19 | */ 20 | const fontSizes = [ 21 | { pt: 7.5, px: 10 }, 22 | { pt: 8, px: 11 }, 23 | { pt: 9, px: 12 }, 24 | { pt: 10, px: 13 }, 25 | { pt: 10.5, px: 14 }, 26 | { pt: 11, px: 15 }, 27 | { pt: 12, px: 16 }, 28 | { pt: 14, px: 18.7 }, 29 | { pt: 15, px: 20 }, 30 | { pt: 16, px: 21.3 }, 31 | { pt: 18, px: 24 }, 32 | { pt: 22, px: 29.3 }, 33 | { pt: 24, px: 32 }, 34 | { pt: 26, px: 34.7 }, 35 | { pt: 36, px: 48 }, 36 | { pt: 42, px: 56 }, 37 | // { pt: 54, px: 71.7 }, 38 | // { pt: 63, px: 83.7 }, 39 | // { pt: 72, px: 95.6 }, 40 | ]; 41 | 42 | /** map pt to px 43 | * @date 2019-10-10 44 | * @param {fontsizePT} pt 45 | * @returns {fontsizePX} 46 | */ 47 | function getFontSizePxByPt(pt) { 48 | for (let i = 0; i < fontSizes.length; i += 1) { 49 | const fontSize = fontSizes[i]; 50 | if (fontSize.pt === pt) { 51 | return fontSize.px; 52 | } 53 | } 54 | return pt; 55 | } 56 | 57 | /** transform baseFonts to map 58 | * @date 2019-10-10 59 | * @param {BaseFont[]} [ary=[]] 60 | * @returns {object} 61 | */ 62 | function fonts(ary = []) { 63 | const map = {}; 64 | baseFonts.concat(ary).forEach((f) => { 65 | map[f.key] = f; 66 | }); 67 | return map; 68 | } 69 | 70 | export default {}; 71 | export { 72 | fontSizes, 73 | fonts, 74 | baseFonts, 75 | getFontSizePxByPt, 76 | }; 77 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/dropdown.js: -------------------------------------------------------------------------------- 1 | import { Element, h } from './element'; 2 | import { bindClickoutside, unbindClickoutside } from './event'; 3 | import { cssPrefix } from '../config'; 4 | 5 | export default class Dropdown extends Element { 6 | constructor(title, width, showArrow, placement, ...children) { 7 | super('div', `${cssPrefix}-dropdown ${placement}`); 8 | this.title = title; 9 | this.change = () => {}; 10 | this.headerClick = () => {}; 11 | if (typeof title === 'string') { 12 | this.title = h('div', `${cssPrefix}-dropdown-title`).child(title); 13 | } else if (showArrow) { 14 | this.title.addClass('arrow-left'); 15 | } 16 | this.contentEl = h('div', `${cssPrefix}-dropdown-content`) 17 | .css('width', width) 18 | .hide(); 19 | 20 | this.setContentChildren(...children); 21 | 22 | this.headerEl = h('div', `${cssPrefix}-dropdown-header`); 23 | this.headerEl.on('click', () => { 24 | if (this.contentEl.css('display') !== 'block') { 25 | this.show(); 26 | } else { 27 | this.hide(); 28 | } 29 | }).children( 30 | this.title, 31 | showArrow ? h('div', `${cssPrefix}-icon arrow-right`).child( 32 | h('div', `${cssPrefix}-icon-img arrow-down`), 33 | ) : '', 34 | ); 35 | this.children(this.headerEl, this.contentEl); 36 | } 37 | 38 | setContentChildren(...children) { 39 | this.contentEl.html(''); 40 | if (children.length > 0) { 41 | this.contentEl.children(...children); 42 | } 43 | } 44 | 45 | setTitle(title) { 46 | this.title.html(title); 47 | this.hide(); 48 | } 49 | 50 | show() { 51 | const { contentEl } = this; 52 | contentEl.show(); 53 | this.parent().active(); 54 | bindClickoutside(this.parent(), () => { 55 | this.hide(); 56 | }); 57 | } 58 | 59 | hide() { 60 | this.parent().active(false); 61 | this.contentEl.hide(); 62 | unbindClickoutside(this.parent()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/color_palette.js: -------------------------------------------------------------------------------- 1 | import { h } from './element'; 2 | import { cssPrefix } from '../config'; 3 | 4 | const themeColorPlaceHolders = ['#ffffff', '#000100', '#e7e5e6', '#445569', '#5b9cd6', '#ed7d31', '#a5a5a5', '#ffc001', '#4371c6', '#71ae47']; 5 | 6 | const themeColors = [ 7 | ['#f2f2f2', '#7f7f7f', '#d0cecf', '#d5dce4', '#deeaf6', '#fce5d5', '#ededed', '#fff2cd', '#d9e2f3', '#e3efd9'], 8 | ['#d8d8d8', '#595959', '#afabac', '#adb8ca', '#bdd7ee', '#f7ccac', '#dbdbdb', '#ffe59a', '#b3c6e7', '#c5e0b3'], 9 | ['#bfbfbf', '#3f3f3f', '#756f6f', '#8596b0', '#9cc2e6', '#f4b184', '#c9c9c9', '#fed964', '#8eaada', '#a7d08c'], 10 | ['#a5a5a5', '#262626', '#3a3839', '#333f4f', '#2e75b5', '#c45a10', '#7b7b7b', '#bf8e01', '#2f5596', '#538136'], 11 | ['#7f7f7f', '#0c0c0c', '#171516', '#222a35', '#1f4e7a', '#843c0a', '#525252', '#7e6000', '#203864', '#365624'], 12 | ]; 13 | 14 | const standardColors = ['#c00000', '#fe0000', '#fdc101', '#ffff01', '#93d051', '#00b04e', '#01b0f1', '#0170c1', '#012060', '#7030a0']; 15 | 16 | function buildTd(bgcolor) { 17 | return h('td', '').child( 18 | h('div', `${cssPrefix}-color-palette-cell`) 19 | .on('click.stop', () => this.change(bgcolor)) 20 | .css('background-color', bgcolor), 21 | ); 22 | } 23 | 24 | export default class ColorPalette { 25 | constructor() { 26 | this.el = h('div', `${cssPrefix}-color-palette`); 27 | this.change = () => {}; 28 | const table = h('table', '').children( 29 | h('tbody', '').children( 30 | h('tr', `${cssPrefix}-theme-color-placeholders`).children( 31 | ...themeColorPlaceHolders.map(color => buildTd.call(this, color)), 32 | ), 33 | ...themeColors.map(it => h('tr', `${cssPrefix}-theme-colors`).children( 34 | ...it.map(color => buildTd.call(this, color)), 35 | )), 36 | h('tr', `${cssPrefix}-standard-colors`).children( 37 | ...standardColors.map(color => buildTd.call(this, color)), 38 | ), 39 | ), 40 | ); 41 | this.el.child(table); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/test/core/formula_test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import { describe, it } from 'mocha'; 3 | import { formulam } from '../../src/core/formula'; 4 | 5 | const gformulas = formulam; 6 | describe('formula', () => { 7 | describe('#render()', () => { 8 | it('SUM: should return 36 when the value is [\'12\', \'12\', 12]', () => { 9 | assert.equal(gformulas.SUM.render(['12', '12', 12]), 36); 10 | }); 11 | it('AVERAGE: should return 13 when the value is [\'12\', \'13\', 14]', () => { 12 | assert.equal(gformulas.AVERAGE.render(['12', '13', 14]), 13); 13 | }); 14 | it('MAX: should return 14 when the value is [\'12\', \'13\', 14]', () => { 15 | assert.equal(gformulas.MAX.render(['12', '13', 14]), 14); 16 | }); 17 | it('MIN: should return 12 when the value is [\'12\', \'13\', 14]', () => { 18 | assert.equal(gformulas.MIN.render(['12', '13', 14]), 12); 19 | }); 20 | it('IF: should return 12 when the value is [12 > 11, 12, 11]', () => { 21 | assert.equal(gformulas.IF.render([12 > 11, 12, 11]), 12); 22 | }); 23 | it('AND: should return true when the value is ["a", true, "ok"]', () => { 24 | assert.equal(gformulas.AND.render(['a', true, 'ok']), true); 25 | }); 26 | it('AND: should return false when the value is ["a", false, "ok"]', () => { 27 | assert.equal(gformulas.AND.render(['a', false, 'ok']), false); 28 | }); 29 | it('OR: should return true when the value is ["a", true]', () => { 30 | assert.equal(gformulas.OR.render(['a', true]), true); 31 | }); 32 | it('OR: should return true when the value is ["a", false]', () => { 33 | assert.equal(gformulas.OR.render(['a', false]), true); 34 | }); 35 | it('OR: should return false when the value is [0, false]', () => { 36 | assert.equal(gformulas.OR.render([0, false]), false); 37 | }); 38 | it('CONCAT: should return 1200USD when the value is [\'1200\', \'USD\']', () => { 39 | assert.equal(gformulas.CONCAT.render(['1200', 'USD']), '1200USD'); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/border_palette.js: -------------------------------------------------------------------------------- 1 | import { h } from './element'; 2 | import Icon from './icon'; 3 | import DropdownColor from './dropdown_color'; 4 | import DropdownLineType from './dropdown_linetype'; 5 | import { cssPrefix } from '../config'; 6 | 7 | function buildTable(...trs) { 8 | return h('table', '').child( 9 | h('tbody', '').children(...trs), 10 | ); 11 | } 12 | 13 | function buildTd(iconName) { 14 | return h('td', '').child( 15 | h('div', `${cssPrefix}-border-palette-cell`).child( 16 | new Icon(`border-${iconName}`), 17 | ).on('click', () => { 18 | this.mode = iconName; 19 | const { mode, style, color } = this; 20 | this.change({ mode, style, color }); 21 | }), 22 | ); 23 | } 24 | 25 | export default class BorderPalette { 26 | constructor() { 27 | this.color = '#000'; 28 | this.style = 'thin'; 29 | this.mode = 'all'; 30 | this.change = () => {}; 31 | this.ddColor = new DropdownColor('line-color', this.color); 32 | this.ddColor.change = (color) => { 33 | this.color = color; 34 | }; 35 | this.ddType = new DropdownLineType(this.style); 36 | this.ddType.change = ([s]) => { 37 | this.style = s; 38 | }; 39 | this.el = h('div', `${cssPrefix}-border-palette`); 40 | const table = buildTable( 41 | h('tr', '').children( 42 | h('td', `${cssPrefix}-border-palette-left`).child( 43 | buildTable( 44 | h('tr', '').children( 45 | ...['all', 'inside', 'horizontal', 'vertical', 'outside'].map(it => buildTd.call(this, it)), 46 | ), 47 | h('tr', '').children( 48 | ...['left', 'top', 'right', 'bottom', 'none'].map(it => buildTd.call(this, it)), 49 | ), 50 | ), 51 | ), 52 | h('td', `${cssPrefix}-border-palette-right`).children( 53 | h('div', `${cssPrefix}-toolbar-btn`).child(this.ddColor.el), 54 | h('div', `${cssPrefix}-toolbar-btn`).child(this.ddType.el), 55 | ), 56 | ), 57 | ); 58 | this.el.child(table); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/excel-schema/src/value.ts: -------------------------------------------------------------------------------- 1 | import { Font } from './style'; 2 | 3 | /** 值类型 */ 4 | export enum ErrorValue { 5 | NotApplicable = '#N/A', 6 | Ref = '#REF!', 7 | Name = '#NAME?', 8 | DivZero = '#DIV/0!', 9 | Null = '#NULL!', 10 | Value = '#VALUE!', 11 | Num = '#NUM!', 12 | } 13 | 14 | export interface CellImageValue { 15 | src: string; 16 | width?: number; 17 | height?: number; 18 | tl?: { col: number; row: number }; 19 | br?: { col: number; row: number }; 20 | } 21 | 22 | export interface CellQrcodeValue extends Omit { 23 | qrcodeText: string; 24 | width?: number; 25 | height?: number; 26 | } 27 | 28 | export interface CellErrorValue { 29 | error: 30 | | '#N/A' 31 | | '#REF!' 32 | | '#NAME?' 33 | | '#DIV/0!' 34 | | '#NULL!' 35 | | '#VALUE!' 36 | | '#NUM!'; 37 | } 38 | 39 | export interface RichText { 40 | text: string; 41 | font?: Partial; 42 | } 43 | 44 | export interface CellRichTextValue { 45 | richText: RichText[]; 46 | } 47 | 48 | export interface CellHyperlinkValue { 49 | text: string; 50 | hyperlink: string; 51 | } 52 | 53 | export interface CellFormulaValue { 54 | formula: string; 55 | result?: number | string | Date | { error: CellErrorValue }; 56 | date1904: boolean; 57 | } 58 | 59 | export interface CellSharedFormulaValue { 60 | sharedFormula: string; 61 | readonly formula?: string; 62 | result?: number | string | Date | { error: CellErrorValue }; 63 | date1904: boolean; 64 | } 65 | 66 | export type DataValidationOperator = 67 | | 'between' 68 | | 'notBetween' 69 | | 'equal' 70 | | 'notEqual' 71 | | 'greaterThan' 72 | | 'lessThan' 73 | | 'greaterThanOrEqual' 74 | | 'lessThanOrEqual'; 75 | 76 | export interface DataValidation { 77 | type: 'list' | 'whole' | 'decimal' | 'date' | 'textLength' | 'custom'; 78 | formulae: any[]; 79 | allowBlank?: boolean; 80 | operator?: DataValidationOperator; 81 | error?: string; 82 | errorTitle?: string; 83 | errorStyle?: string; 84 | prompt?: string; 85 | promptTitle?: string; 86 | showErrorMessage?: boolean; 87 | showInputMessage?: boolean; 88 | } 89 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/test/core/font_test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import { describe, it } from 'mocha'; 3 | import { 4 | fontSizes, 5 | fonts, 6 | baseFonts, 7 | getFontSizePxByPt, 8 | } from '../../src/core/font'; 9 | 10 | describe('baseFonts', () => { 11 | it('should be Array of "{ key: string, key: string }"', () => { 12 | const result = baseFonts.find((i) => { 13 | const keyType = typeof i.key; 14 | const titleType = typeof i.title; 15 | return keyType !== 'string' || titleType !== 'string'; 16 | }); 17 | assert.equal(result, undefined); 18 | }); 19 | }); 20 | 21 | describe('fontSizes', () => { 22 | it('should be Array of "{ pt: number, px: number }"', () => { 23 | const result = fontSizes.find((i) => { 24 | const ptType = typeof i.pt; 25 | const pxType = typeof i.px; 26 | return ptType !== 'number' || pxType !== 'number'; 27 | }); 28 | assert.equal(result, undefined); 29 | }); 30 | }); 31 | 32 | describe('getFontSizePxByPt()', () => { 33 | const fontsizeItem = { pt: 7.5, px: 10 }; 34 | // not include pt 35 | const notIncludePT = 6.5; 36 | 37 | it(`should be return ${fontsizeItem.px} when the value is ${fontsizeItem.pt}`, () => { 38 | assert.equal(getFontSizePxByPt(fontsizeItem.pt), fontsizeItem.px); 39 | }); 40 | it(`should be return ${notIncludePT} when the value is ${notIncludePT} (same as input arg)`, () => { 41 | assert.equal(getFontSizePxByPt(notIncludePT), notIncludePT); 42 | }); 43 | }); 44 | 45 | describe('fonts()', () => { 46 | const fontItem = baseFonts[0]; 47 | it(`should include { ${fontItem.key}: ${JSON.stringify(fontItem)} } when the value is not provide.`, () => { 48 | const f = fonts(); 49 | assert.equal(f[fontItem.key], fontItem); 50 | }); 51 | 52 | /** @type {BaseFont} */ 53 | const appendItem = [{ 54 | key: 'test', 55 | title: 'test title', 56 | }]; 57 | const appendItems = [appendItem]; 58 | it(`should include { ${appendItems[0].key}: ${JSON.stringify(appendItems[0])} } when the value is ${JSON.stringify(appendItems)}`, () => { 59 | const f = fonts(appendItems); 60 | assert.equal(f[appendItem.key], appendItem); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@m-fe/stl-viewer", 3 | "version": "0.0.1", 4 | "description": "Micro-Frontend Libs, with React & TS & Webpack", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/wx-chevalier/m-fe-libs" 8 | }, 9 | "author": "wx-chevalier@github", 10 | "license": "MIT", 11 | "private": true, 12 | "workspaces": [ 13 | "packages/*" 14 | ], 15 | "keywords": [ 16 | "react", 17 | "redux", 18 | "mobx", 19 | "webpack", 20 | "typescript" 21 | ], 22 | "scripts": { 23 | "bootstrap": "yarn install && yarn run build", 24 | "build": "npm run clean && yarn workspaces run build && cp -r ./packages/app-lib/build/ ./build", 25 | "clean": "rm -rf ./build && yarn workspaces run clean", 26 | "clean:cov": "yarn workspaces run clean:cov", 27 | "dev": "npm start", 28 | "lint": "./scripts/tools/lint_pkgs.sh", 29 | "lint-staged": "lint-staged", 30 | "postinstall": "node ./node_modules/husky/lib/installer/bin install", 31 | "prettier-all": "prettier --write 'packages/**/src/**/*.{ts,tsx}' '!src/{assets,datas}/**'", 32 | "start": "(cd packages/app-lib && npm start)", 33 | "test": "yarn workspaces run test", 34 | "test:cov": "yarn workspaces run test:cov", 35 | "test:watch": "yarn workspaces run test:watch", 36 | "upgrade": "./scripts/tools/upgrade_pkgs.sh" 37 | }, 38 | "dependencies": { 39 | "core-js": "^3.6.5" 40 | }, 41 | "devDependencies": { 42 | "@m-fe/app-config": "^0.5.2", 43 | "@svgr/webpack": "^5.4.0", 44 | "react-dom": "^16.13.1" 45 | }, 46 | "browserslist": [ 47 | "extends @m-fe/browserslist-config" 48 | ], 49 | "commitlint": { 50 | "extends": [ 51 | "@m-fe" 52 | ] 53 | }, 54 | "remarkConfig": { 55 | "plugins": [ 56 | "@m-fe/remark-config" 57 | ] 58 | }, 59 | "stylelint": { 60 | "extends": [ 61 | "@m-fe/stylelint-config", 62 | "@m-fe/stylelint-config/modules" 63 | ], 64 | "rules": { 65 | "font-family-no-missing-generic-family-keyword": null, 66 | "no-descending-specificity": null, 67 | "plugin/no-unsupported-browser-features": null, 68 | "plugin/no-low-performance-animation-properties": null 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/excel-jexcel-render/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@m-fe/excel-jexcel-render", 3 | "version": "0.0.1", 4 | "description": "@m-fe/ts-lib", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/wx-chevalier/excel.ts" 8 | }, 9 | "author": "wx-chevalier@github", 10 | "license": "MIT", 11 | "main": "dist/cjs/index.js", 12 | "module": "dist/es/index.js", 13 | "types": "dist/types/index.d.ts", 14 | "files": [ 15 | "dist/" 16 | ], 17 | "keywords": [ 18 | "webpack", 19 | "react" 20 | ], 21 | "scripts": { 22 | "build": "npm run build:es && npm run build:cjs && npm run build:umd", 23 | "build:cjs": "tsc --project ./tsconfig.cjs.json", 24 | "build:es": "tsc --project ./tsconfig.es.json", 25 | "build:umd": "cross-env NODE_ENV=production webpack -p --config ./scripts/webpack/webpack.config.umd.js", 26 | "clean": "rimraf dist", 27 | "clean:r": "rimraf ./dist/*.map && rimraf ./dist/**/*.map && rimraf ./dist/**/*.tsbuildinfo", 28 | "dev": "tsc -w --project ./tsconfig.cjs.json", 29 | "lint": "run-p lint:*", 30 | "lint:es": "cross-env PARSER_NO_WATCH=true eslint . --cache --ext js,md,ts,tsx -f friendly --max-warnings 10", 31 | "lint:ts": "tslint -p . -t stylish", 32 | "lint:tsc": "tsc -p tsconfig.json --incremental false --noEmit", 33 | "prepublish": "npm run clean:r", 34 | "test": "jest --config ./scripts/jest/jest.config.js", 35 | "test:cov": "npm run cleanCov && npm test -- --coverage", 36 | "test:watch": "npm test -- --watch" 37 | }, 38 | "dependencies": {}, 39 | "devDependencies": { 40 | "@m-fe/app-config": "^0.5.2", 41 | "cross-env": "^7.0.2", 42 | "webpack": "^4.43.0" 43 | }, 44 | "browserslist": [ 45 | "extends @m-fe/browserslist-config" 46 | ], 47 | "commitlint": { 48 | "extends": [ 49 | "@m-fe" 50 | ] 51 | }, 52 | "remarkConfig": { 53 | "plugins": [ 54 | "@m-fe/remark-config" 55 | ] 56 | }, 57 | "stylelint": { 58 | "extends": [ 59 | "@m-fe/stylelint-config", 60 | "@m-fe/stylelint-config/modules" 61 | ], 62 | "rules": { 63 | "font-family-no-missing-generic-family-keyword": null, 64 | "no-descending-specificity": null, 65 | "plugin/no-unsupported-browser-features": null, 66 | "plugin/no-low-performance-animation-properties": null 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/dropdown_linetype.js: -------------------------------------------------------------------------------- 1 | import Dropdown from './dropdown'; 2 | import { h } from './element'; 3 | import Icon from './icon'; 4 | import { cssPrefix } from '../config'; 5 | 6 | const lineTypes = [ 7 | ['thin', ''], 8 | ['medium', ''], 9 | ['thick', ''], 10 | ['dashed', ''], 11 | ['dotted', ''], 12 | // ['double', ''], 13 | ]; 14 | 15 | export default class DropdownLineType extends Dropdown { 16 | constructor(type) { 17 | const icon = new Icon('line-type'); 18 | let beforei = 0; 19 | const lineTypeEls = lineTypes.map((it, iti) => h('div', `${cssPrefix}-item state ${type === it[0] ? 'checked' : ''}`) 20 | .on('click', () => { 21 | lineTypeEls[beforei].toggle('checked'); 22 | lineTypeEls[iti].toggle('checked'); 23 | beforei = iti; 24 | this.hide(); 25 | this.change(it); 26 | }) 27 | .child( 28 | h('div', `${cssPrefix}-line-type`).html(it[1]), 29 | )); 30 | 31 | super(icon, 'auto', false, 'bottom-left', ...lineTypeEls); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/excel-schema/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@m-fe/excel-schema", 3 | "version": "0.0.11", 4 | "description": "@m-fe/excel-schema", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/wx-chevalier/excel.ts" 8 | }, 9 | "author": "wx-chevalier@github", 10 | "license": "MIT", 11 | "main": "dist/cjs/index.js", 12 | "module": "dist/es/index.js", 13 | "types": "dist/types/index.d.ts", 14 | "files": [ 15 | "dist/" 16 | ], 17 | "keywords": [ 18 | "webpack", 19 | "react" 20 | ], 21 | "scripts": { 22 | "build": "npm run build:es && npm run build:cjs && npm run build:umd", 23 | "build:cjs": "tsc --project ./tsconfig.cjs.json", 24 | "build:es": "tsc --project ./tsconfig.es.json", 25 | "build:umd": "cross-env NODE_ENV=production webpack -p --config ./scripts/webpack/webpack.config.umd.js", 26 | "clean": "rimraf dist", 27 | "clean:r": "rimraf ./dist/*.map && rimraf ./dist/**/*.map && rimraf ./dist/**/*.tsbuildinfo", 28 | "dev": "tsc -w --project ./tsconfig.cjs.json", 29 | "lint": "run-p lint:*", 30 | "lint:es": "cross-env PARSER_NO_WATCH=true eslint . --cache --ext js,md,ts,tsx -f friendly --max-warnings 10", 31 | "lint:ts": "tslint -p . -t stylish", 32 | "lint:tsc": "tsc -p tsconfig.json --incremental false --noEmit", 33 | "prepublish": "npm run clean:r", 34 | "test": "jest --config ./scripts/jest/jest.config.js", 35 | "test:cov": "npm run cleanCov && npm test -- --coverage", 36 | "test:watch": "npm test -- --watch" 37 | }, 38 | "dependencies": { 39 | "@m-fe/utils": "^0.6.0" 40 | }, 41 | "devDependencies": { 42 | "@m-fe/app-config": "^0.9.0", 43 | "cross-env": "^7.0.2", 44 | "webpack": "^4.43.0" 45 | }, 46 | "browserslist": [ 47 | "extends @m-fe/browserslist-config" 48 | ], 49 | "commitlint": { 50 | "extends": [ 51 | "@m-fe" 52 | ] 53 | }, 54 | "remarkConfig": { 55 | "plugins": [ 56 | "@m-fe/remark-config" 57 | ] 58 | }, 59 | "stylelint": { 60 | "extends": [ 61 | "@m-fe/stylelint-config", 62 | "@m-fe/stylelint-config/modules" 63 | ], 64 | "rules": { 65 | "font-family-no-missing-generic-family-keyword": null, 66 | "no-descending-specificity": null, 67 | "plugin/no-unsupported-browser-features": null, 68 | "plugin/no-low-performance-animation-properties": null 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@m-fe/excel-canvas-render", 3 | "version": "1.1.5", 4 | "description": "a javascript xpreadsheet", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/myliang/x-spreadsheet.git" 8 | }, 9 | "author": "myliang ", 10 | "license": "MIT", 11 | "main": "src/index.js", 12 | "types": "src/index.d.ts", 13 | "files": [ 14 | "assets", 15 | "dist", 16 | "src" 17 | ], 18 | "keywords": [ 19 | "javascript", 20 | "spreadsheet", 21 | "canvas" 22 | ], 23 | "scripts": { 24 | "build": "webpack --config build/webpack.prod.js", 25 | "build-locale": "webpack --config build/webpack.locale.js", 26 | "coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov -t 31ecdb12-8ecb-46f7-a486-65c2516307dd", 27 | "dev": "webpack-dev-server --open --config build/webpack.dev.js", 28 | "lint": "./node_modules/eslint/bin/eslint.js src", 29 | "postinstall": "opencollective-postinstall", 30 | "test": "nyc ./node_modules/mocha/bin/mocha --require @babel/register test/*" 31 | }, 32 | "dependencies": { 33 | "opencollective": "^1.0.3", 34 | "opencollective-postinstall": "^2.0.2" 35 | }, 36 | "devDependencies": { 37 | "@babel/core": "^7.3.4", 38 | "@babel/plugin-proposal-class-properties": "^7.4.4", 39 | "@babel/preset-env": "^7.3.4", 40 | "@babel/register": "^7.0.0", 41 | "babel-loader": "^8.0.5", 42 | "clean-webpack-plugin": "^0.1.19", 43 | "codecov": "^3.3.0", 44 | "css-loader": "^1.0.0", 45 | "eslint": "^6.8.0", 46 | "eslint-config-airbnb-base": "^13.1.0", 47 | "eslint-plugin-import": "^2.14.0", 48 | "file-loader": "^2.0.0", 49 | "html-webpack-plugin": "^3.2.0", 50 | "less": "^3.8.1", 51 | "less-loader": "^4.1.0", 52 | "mini-css-extract-plugin": "^0.4.4", 53 | "mocha": "^5.2.0", 54 | "nyc": "^13.3.0", 55 | "style-loader": "^0.23.0", 56 | "webpack": "^4.29.6", 57 | "webpack-cli": "^3.1.0", 58 | "webpack-dev-server": "^3.10.2", 59 | "webpack-merge": "^4.1.4" 60 | }, 61 | "collective": { 62 | "type": "opencollective", 63 | "url": "https://opencollective.com/x-spreadsheet" 64 | }, 65 | "nyc": { 66 | "all": true, 67 | "include": [ 68 | "src/core/*.js" 69 | ], 70 | "exclude": [ 71 | "**/*.spec.js" 72 | ] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/core/formula.js: -------------------------------------------------------------------------------- 1 | /** 2 | formula: 3 | key 4 | title 5 | render 6 | */ 7 | /** 8 | * @typedef {object} Formula 9 | * @property {string} key 10 | * @property {function} title 11 | * @property {function} render 12 | */ 13 | import { tf } from '../locale/locale'; 14 | import { numberCalc } from './helper'; 15 | 16 | /** @type {Formula[]} */ 17 | const baseFormulas = [ 18 | { 19 | key: 'SUM', 20 | title: tf('formula.sum'), 21 | render: ary => ary.reduce((a, b) => numberCalc('+', a, b), 0), 22 | }, 23 | { 24 | key: 'AVERAGE', 25 | title: tf('formula.average'), 26 | render: ary => ary.reduce((a, b) => Number(a) + Number(b), 0) / ary.length, 27 | }, 28 | { 29 | key: 'MAX', 30 | title: tf('formula.max'), 31 | render: ary => Math.max(...ary.map(v => Number(v))), 32 | }, 33 | { 34 | key: 'MIN', 35 | title: tf('formula.min'), 36 | render: ary => Math.min(...ary.map(v => Number(v))), 37 | }, 38 | { 39 | key: 'IF', 40 | title: tf('formula._if'), 41 | render: ([b, t, f]) => (b ? t : f), 42 | }, 43 | { 44 | key: 'AND', 45 | title: tf('formula.and'), 46 | render: ary => ary.every(it => it), 47 | }, 48 | { 49 | key: 'OR', 50 | title: tf('formula.or'), 51 | render: ary => ary.some(it => it), 52 | }, 53 | { 54 | key: 'CONCAT', 55 | title: tf('formula.concat'), 56 | render: ary => ary.join(''), 57 | }, 58 | /* support: 1 + A1 + B2 * 3 59 | { 60 | key: 'DIVIDE', 61 | title: tf('formula.divide'), 62 | render: ary => ary.reduce((a, b) => Number(a) / Number(b)), 63 | }, 64 | { 65 | key: 'PRODUCT', 66 | title: tf('formula.product'), 67 | render: ary => ary.reduce((a, b) => Number(a) * Number(b),1), 68 | }, 69 | { 70 | key: 'SUBTRACT', 71 | title: tf('formula.subtract'), 72 | render: ary => ary.reduce((a, b) => Number(a) - Number(b)), 73 | }, 74 | */ 75 | ]; 76 | 77 | const formulas = baseFormulas; 78 | 79 | // const formulas = (formulaAry = []) => { 80 | // const formulaMap = {}; 81 | // baseFormulas.concat(formulaAry).forEach((f) => { 82 | // formulaMap[f.key] = f; 83 | // }); 84 | // return formulaMap; 85 | // }; 86 | const formulam = {}; 87 | baseFormulas.forEach((f) => { 88 | formulam[f.key] = f; 89 | }); 90 | 91 | export default { 92 | }; 93 | 94 | export { 95 | formulam, 96 | formulas, 97 | baseFormulas, 98 | }; 99 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/core/alphabet.js: -------------------------------------------------------------------------------- 1 | import './_.prototypes'; 2 | 3 | const alphabets = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; 4 | 5 | /** index number 2 letters 6 | * @example stringAt(26) ==> 'AA' 7 | * @date 2019-10-10 8 | * @export 9 | * @param {number} index 10 | * @returns {string} 11 | */ 12 | export function stringAt(index) { 13 | let str = ''; 14 | let cindex = index; 15 | while (cindex >= alphabets.length) { 16 | cindex /= alphabets.length; 17 | cindex -= 1; 18 | str += alphabets[parseInt(cindex, 10) % alphabets.length]; 19 | } 20 | const last = index % alphabets.length; 21 | str += alphabets[last]; 22 | return str; 23 | } 24 | 25 | /** translate letter in A1-tag to number 26 | * @date 2019-10-10 27 | * @export 28 | * @param {string} str "AA" in A1-tag "AA1" 29 | * @returns {number} 30 | */ 31 | export function indexAt(str) { 32 | let ret = 0; 33 | for (let i = 0; i < str.length - 1; i += 1) { 34 | const cindex = str.charCodeAt(i) - 65; 35 | const exponet = str.length - 1 - i; 36 | ret += (alphabets.length ** exponet) + (alphabets.length * cindex); 37 | } 38 | ret += str.charCodeAt(str.length - 1) - 65; 39 | return ret; 40 | } 41 | 42 | // B10 => x,y 43 | /** translate A1-tag to XY-tag 44 | * @date 2019-10-10 45 | * @export 46 | * @param {tagA1} src 47 | * @returns {tagXY} 48 | */ 49 | export function expr2xy(src) { 50 | let x = ''; 51 | let y = ''; 52 | for (let i = 0; i < src.length; i += 1) { 53 | if (src.charAt(i) >= '0' && src.charAt(i) <= '9') { 54 | y += src.charAt(i); 55 | } else { 56 | x += src.charAt(i); 57 | } 58 | } 59 | return [indexAt(x), parseInt(y, 10) - 1]; 60 | } 61 | 62 | /** translate XY-tag to A1-tag 63 | * @example x,y => B10 64 | * @date 2019-10-10 65 | * @export 66 | * @param {number} x 67 | * @param {number} y 68 | * @returns {tagA1} 69 | */ 70 | export function xy2expr(x, y) { 71 | return `${stringAt(x)}${y + 1}`; 72 | } 73 | 74 | /** translate A1-tag src by (xn, yn) 75 | * @date 2019-10-10 76 | * @export 77 | * @param {tagA1} src 78 | * @param {number} xn 79 | * @param {number} yn 80 | * @returns {tagA1} 81 | */ 82 | export function expr2expr(src, xn, yn, condition = () => true) { 83 | if (xn === 0 && yn === 0) return src; 84 | const [x, y] = expr2xy(src); 85 | if (!condition(x, y)) return src; 86 | return xy2expr(x + xn, y + yn); 87 | } 88 | 89 | export default { 90 | stringAt, 91 | indexAt, 92 | expr2xy, 93 | xy2expr, 94 | expr2expr, 95 | }; 96 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/core/merge.js: -------------------------------------------------------------------------------- 1 | import { CellRange } from './cell_range'; 2 | 3 | class Merges { 4 | constructor(d = []) { 5 | this._ = d; 6 | } 7 | 8 | forEach(cb) { 9 | this._.forEach(cb); 10 | } 11 | 12 | deleteWithin(cr) { 13 | this._ = this._.filter(it => !it.within(cr)); 14 | } 15 | 16 | getFirstIncludes(ri, ci) { 17 | for (let i = 0; i < this._.length; i += 1) { 18 | const it = this._[i]; 19 | if (it.includes(ri, ci)) { 20 | return it; 21 | } 22 | } 23 | return null; 24 | } 25 | 26 | filterIntersects(cellRange) { 27 | return new Merges(this._.filter(it => it.intersects(cellRange))); 28 | } 29 | 30 | intersects(cellRange) { 31 | for (let i = 0; i < this._.length; i += 1) { 32 | const it = this._[i]; 33 | if (it.intersects(cellRange)) { 34 | // console.log('intersects'); 35 | return true; 36 | } 37 | } 38 | return false; 39 | } 40 | 41 | union(cellRange) { 42 | let cr = cellRange; 43 | this._.forEach((it) => { 44 | if (it.intersects(cr)) { 45 | cr = it.union(cr); 46 | } 47 | }); 48 | return cr; 49 | } 50 | 51 | add(cr) { 52 | this.deleteWithin(cr); 53 | this._.push(cr); 54 | } 55 | 56 | // type: row | column 57 | shift(type, index, n, cbWithin) { 58 | this._.forEach((cellRange) => { 59 | const { 60 | sri, sci, eri, eci, 61 | } = cellRange; 62 | const range = cellRange; 63 | if (type === 'row') { 64 | if (sri >= index) { 65 | range.sri += n; 66 | range.eri += n; 67 | } else if (sri < index && index <= eri) { 68 | range.eri += n; 69 | cbWithin(sri, sci, n, 0); 70 | } 71 | } else if (type === 'column') { 72 | if (sci >= index) { 73 | range.sci += n; 74 | range.eci += n; 75 | } else if (sci < index && index <= eci) { 76 | range.eci += n; 77 | cbWithin(sri, sci, 0, n); 78 | } 79 | } 80 | }); 81 | } 82 | 83 | move(cellRange, rn, cn) { 84 | this._.forEach((it1) => { 85 | const it = it1; 86 | if (it.within(cellRange)) { 87 | it.eri += rn; 88 | it.sri += rn; 89 | it.sci += cn; 90 | it.eci += cn; 91 | } 92 | }); 93 | } 94 | 95 | setData(merges) { 96 | this._ = merges.map(merge => CellRange.valueOf(merge)); 97 | return this; 98 | } 99 | 100 | getData() { 101 | return this._.map(merge => merge.toString()); 102 | } 103 | } 104 | 105 | export default {}; 106 | export { 107 | Merges, 108 | }; 109 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/core/format.js: -------------------------------------------------------------------------------- 1 | import { tf } from '../locale/locale'; 2 | 3 | const formatStringRender = v => v; 4 | 5 | const formatNumberRender = (v) => { 6 | // match "-12.1" or "12" or "12.1" 7 | if (/^(-?\d*.?\d*)$/.test(v)) { 8 | const v1 = Number(v).toFixed(2).toString(); 9 | const [first, ...parts] = v1.split('\\.'); 10 | return [first.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,'), ...parts]; 11 | } 12 | return v; 13 | }; 14 | 15 | const baseFormats = [ 16 | { 17 | key: 'normal', 18 | title: tf('format.normal'), 19 | type: 'string', 20 | render: formatStringRender, 21 | }, 22 | { 23 | key: 'text', 24 | title: tf('format.text'), 25 | type: 'string', 26 | render: formatStringRender, 27 | }, 28 | { 29 | key: 'number', 30 | title: tf('format.number'), 31 | type: 'number', 32 | label: '1,000.12', 33 | render: formatNumberRender, 34 | }, 35 | { 36 | key: 'percent', 37 | title: tf('format.percent'), 38 | type: 'number', 39 | label: '10.12%', 40 | render: v => `${v}%`, 41 | }, 42 | { 43 | key: 'rmb', 44 | title: tf('format.rmb'), 45 | type: 'number', 46 | label: '¥10.00', 47 | render: v => `¥${formatNumberRender(v)}`, 48 | }, 49 | { 50 | key: 'usd', 51 | title: tf('format.usd'), 52 | type: 'number', 53 | label: '$10.00', 54 | render: v => `$${formatNumberRender(v)}`, 55 | }, 56 | { 57 | key: 'eur', 58 | title: tf('format.eur'), 59 | type: 'number', 60 | label: '€10.00', 61 | render: v => `€${formatNumberRender(v)}`, 62 | }, 63 | { 64 | key: 'date', 65 | title: tf('format.date'), 66 | type: 'date', 67 | label: '26/09/2008', 68 | render: formatStringRender, 69 | }, 70 | { 71 | key: 'time', 72 | title: tf('format.time'), 73 | type: 'date', 74 | label: '15:59:00', 75 | render: formatStringRender, 76 | }, 77 | { 78 | key: 'datetime', 79 | title: tf('format.datetime'), 80 | type: 'date', 81 | label: '26/09/2008 15:59:00', 82 | render: formatStringRender, 83 | }, 84 | { 85 | key: 'duration', 86 | title: tf('format.duration'), 87 | type: 'date', 88 | label: '24:01:00', 89 | render: formatStringRender, 90 | }, 91 | ]; 92 | 93 | // const formats = (ary = []) => { 94 | // const map = {}; 95 | // baseFormats.concat(ary).forEach((f) => { 96 | // map[f.key] = f; 97 | // }); 98 | // return map; 99 | // }; 100 | const formatm = {}; 101 | baseFormats.forEach((f) => { 102 | formatm[f.key] = f; 103 | }); 104 | 105 | export default { 106 | }; 107 | export { 108 | formatm, 109 | baseFormats, 110 | }; 111 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/test/core/format_test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import { describe, it } from 'mocha'; 3 | import { 4 | formatm, 5 | baseFormats, 6 | } from '../../src/core/format'; 7 | 8 | const gformats = formatm; 9 | describe('formatm', () => { 10 | describe('#render()', () => { 11 | it('normal: should return AC when the value is AC', () => { 12 | assert.equal(gformats.normal.render('AC'), 'AC'); 13 | }); 14 | it('text: should return abc when the value is abc', () => { 15 | assert.equal(gformats.text.render('abc'), 'abc'); 16 | }); 17 | it('number: should return 11,000.20 when the value is 11000.20', () => { 18 | assert.equal(gformats.number.render('11000.20'), '11,000.20'); 19 | }); 20 | it('number: should return 110,00.20 (NOT MODIFIED when encounter ileagal input) when the value is 110,00.20', () => { 21 | assert.equal(gformats.number.render('110,00.20'), '110,00.20'); 22 | }); 23 | it('percent: should return 50.456% when the value is 50.456', () => { 24 | assert.equal(gformats.percent.render('50.456'), '50.456%'); 25 | }); 26 | it('RMB: should return ¥1,200.33 when the value is 1200.333', () => { 27 | assert.equal(gformats.rmb.render('1200.333'), '¥1,200.33'); 28 | }); 29 | it('USD: should return $1,200.33 when the value is 1200.333', () => { 30 | assert.equal(gformats.usd.render('1200.333'), '$1,200.33'); 31 | }); 32 | it('EUR: should return €1,200.33 when the value is 1200.333', () => { 33 | assert.equal(gformats.eur.render('1200.333'), '€1,200.33'); 34 | }); 35 | }); 36 | }); 37 | 38 | describe('baseFormats', () => { 39 | // item.key 40 | it('typeof item.key should be "string"', 41 | () => { 42 | const KEY = 'key'; 43 | assert.equal(baseFormats.find(i => typeof i[KEY] !== 'string'), undefined); 44 | }); 45 | // item.title 46 | it('typeof item.title should be "function"', 47 | () => { 48 | const KEY = 'title'; 49 | assert.equal(baseFormats.find(i => typeof i[KEY] !== 'function'), undefined); 50 | }); 51 | // item.type 52 | it('typeof item.type should be "string"', 53 | () => { 54 | const KEY = 'type'; 55 | assert.equal(baseFormats.find(i => typeof i[KEY] !== 'string'), undefined); 56 | }); 57 | // item.render 58 | it('typeof item.render should be "function"', 59 | () => { 60 | const KEY = 'render'; 61 | assert.equal(baseFormats.find(i => typeof i[KEY] !== 'function'), undefined); 62 | }); 63 | // item.label 64 | it('typeof item.label should be "string" or "undefined"', 65 | () => { 66 | const KEY = 'label'; 67 | assert.equal(baseFormats.find(i => typeof i[KEY] !== 'string' && typeof i[KEY] !== 'undefined'), undefined); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /packages/excel-node-generator/src/__test__/simple.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | WorkbookDO, 3 | WorksheetCellDO, 4 | WorksheetDO, 5 | WorksheetRowDO, 6 | } from '@m-fe/excel-schema'; 7 | import * as S from '@m-fe/utils'; 8 | import dayjs from 'dayjs'; 9 | import _ from 'lodash'; 10 | import path from 'path'; 11 | 12 | import { ExcelGenerator } from '../exceljs'; 13 | 14 | jest.setTimeout(60 * 1000 * 1000); 15 | 16 | describe('Test adapter', () => { 17 | it('对账单', async () => { 18 | const rows: Partial[] = [ 19 | { number: 1, height: 100 }, 20 | { number: 2, height: 25 }, 21 | ]; 22 | 23 | // 添加所有的 Cell 24 | const cells: WorksheetCellDO[] = [ 25 | new WorksheetCellDO({ 26 | address: 'A1', 27 | value: { 28 | src: 29 | 'https://ufc-prod-image.oss-cn-shanghai.aliyuncs.com/sync/e6/df3c8857c34a3035e22dfe3c182004/decompressed-1-UTR6800-JP-数量5-12-1软胶打印.stl.thumbnail.PNG?x-oss-process=image%2Fresize%2Cw_100', 30 | tl: { col: 1.5, row: 1.5 }, 31 | width: 100, 32 | height: 500, 33 | }, 34 | }), 35 | new WorksheetCellDO({ 36 | address: 'A1', 37 | mergedCellAddress: 'H1', 38 | style: { alignment: { horizontal: 'left', wrapText: true } }, 39 | value: { 40 | richText: [ 41 | { 42 | font: { 43 | size: 16, 44 | name: '宋体', 45 | }, 46 | text: `名字 \n`, 47 | }, 48 | { 49 | text: '\n', 50 | }, 51 | { 52 | text: '\n', 53 | }, 54 | 55 | { 56 | font: { 57 | size: 12, 58 | }, 59 | text: `导出时间:${dayjs().format('YYYY/MM/DD HH:mm')} \n`, 60 | }, 61 | ], 62 | }, 63 | }), 64 | ]; 65 | 66 | const sheet = new WorksheetDO({ 67 | name: '对账单', 68 | properties: { 69 | defaultColWidth: 22, 70 | defaultRowHeight: 20, 71 | defaultAlignment: { 72 | vertical: 'middle', 73 | horizontal: 'left', 74 | wrapText: true, 75 | }, 76 | }, 77 | columns: [ 78 | { 79 | key: 'E', 80 | width: 20, 81 | }, 82 | ], 83 | rows, 84 | cells, 85 | }); 86 | 87 | const workbook = new WorkbookDO({ 88 | creator: 'Unionfab', 89 | lastModifiedBy: 'Unionfab', 90 | sheets: [sheet], 91 | }); 92 | 93 | const g = new ExcelGenerator( 94 | workbook, 95 | path.resolve(__dirname, 'test.xlsx'), 96 | ); 97 | 98 | await g.generateByExcelJs(); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/test/core/alphabet_test.js: -------------------------------------------------------------------------------- 1 | // const = require('../../src/data/); 2 | import assert from 'assert'; 3 | import { describe, it } from 'mocha'; 4 | import { 5 | indexAt, 6 | stringAt, 7 | expr2xy, 8 | expr2expr, 9 | } from '../../src/core/alphabet'; 10 | 11 | describe('alphabet', () => { 12 | describe('.indexAt()', () => { 13 | it('should return 0 when the value is A', () => { 14 | assert.equal(indexAt('A'), 0); 15 | }); 16 | it('should return 25 when the value is Z', () => { 17 | assert.equal(indexAt('Z'), 25); 18 | }); 19 | it('should return 26 when the value is AA', () => { 20 | assert.equal(indexAt('AA'), 26); 21 | }); 22 | it('should return 52 when the value is BA', () => { 23 | assert.equal(indexAt('BA'), 52); 24 | }); 25 | it('should return 54 when the value is BC', () => { 26 | assert.equal(indexAt('BC'), 54); 27 | }); 28 | it('should return 78 when the value is CA', () => { 29 | assert.equal(indexAt('CA'), 78); 30 | }); 31 | it('should return 26 * 26 when the value is ZA', () => { 32 | assert.equal(indexAt('ZA'), 26 * 26); 33 | }); 34 | it('should return 26 * 26 + 26 when the value is AAA', () => { 35 | assert.equal(indexAt('AAA'), (26 * 26) + 26); 36 | }); 37 | }); 38 | describe('.stringAt()', () => { 39 | it('should return A when the value is 0', () => { 40 | assert.equal(stringAt(0), 'A'); 41 | }); 42 | it('should return Z when the value is 25', () => { 43 | assert.equal(stringAt(25), 'Z'); 44 | }); 45 | it('should return AA when the value is 26', () => { 46 | assert.equal(stringAt(26), 'AA'); 47 | }); 48 | it('should return BC when the value is 54', () => { 49 | assert.equal(stringAt(54), 'BC'); 50 | }); 51 | it('should return CB when the value is 78', () => { 52 | assert.equal(stringAt(78), 'CA'); 53 | }); 54 | it('should return ZA when the value is 26 * 26', () => { 55 | assert.equal(stringAt(26 * 26), 'ZA'); 56 | }); 57 | it('should return Z when the value is 26 * 26 + 1', () => { 58 | assert.equal(stringAt((26 * 26) + 1), 'ZB'); 59 | }); 60 | it('should return AAA when the value is 26 * 26 + 26', () => { 61 | assert.equal(stringAt((26 * 26) + 26), 'AAA'); 62 | }); 63 | }); 64 | describe('.expr2xy()', () => { 65 | it('should return 0 when the value is A1', () => { 66 | assert.equal(expr2xy('A1')[0], 0); 67 | assert.equal(expr2xy('A1')[1], 0); 68 | }); 69 | }); 70 | describe('.expr2expr()', () => { 71 | it('should return B2 when the value is A1, 1, 1', () => { 72 | assert.equal(expr2expr('A1', 1, 1), 'B2'); 73 | }); 74 | it('should return C4 when the value is A1, 2, 3', () => { 75 | assert.equal(expr2expr('A1', 2, 3), 'C4'); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/event.js: -------------------------------------------------------------------------------- 1 | /* global window */ 2 | export function bind(target, name, fn) { 3 | target.addEventListener(name, fn); 4 | } 5 | export function unbind(target, name, fn) { 6 | target.removeEventListener(name, fn); 7 | } 8 | export function unbindClickoutside(el) { 9 | if (el.xclickoutside) { 10 | unbind(window.document.body, 'click', el.xclickoutside); 11 | delete el.xclickoutside; 12 | } 13 | } 14 | 15 | // the left mouse button: mousedown → mouseup → click 16 | // the right mouse button: mousedown → contenxtmenu → mouseup 17 | // the right mouse button in firefox(>65.0): mousedown → contenxtmenu → mouseup → click on window 18 | export function bindClickoutside(el, cb) { 19 | el.xclickoutside = (evt) => { 20 | // ignore double click 21 | // console.log('evt:', evt); 22 | if (evt.detail === 2 || el.contains(evt.target)) return; 23 | if (cb) cb(el); 24 | else { 25 | el.hide(); 26 | unbindClickoutside(el); 27 | } 28 | }; 29 | bind(window.document.body, 'click', el.xclickoutside); 30 | } 31 | export function mouseMoveUp(target, movefunc, upfunc) { 32 | bind(target, 'mousemove', movefunc); 33 | const t = target; 34 | t.xEvtUp = (evt) => { 35 | // console.log('mouseup>>>'); 36 | unbind(target, 'mousemove', movefunc); 37 | unbind(target, 'mouseup', target.xEvtUp); 38 | upfunc(evt); 39 | }; 40 | bind(target, 'mouseup', target.xEvtUp); 41 | } 42 | 43 | function calTouchDirection(spanx, spany, evt, cb) { 44 | let direction = ''; 45 | // console.log('spanx:', spanx, ', spany:', spany); 46 | if (Math.abs(spanx) > Math.abs(spany)) { 47 | // horizontal 48 | direction = spanx > 0 ? 'right' : 'left'; 49 | cb(direction, spanx, evt); 50 | } else { 51 | // vertical 52 | direction = spany > 0 ? 'down' : 'up'; 53 | cb(direction, spany, evt); 54 | } 55 | } 56 | // cb = (direction, distance) => {} 57 | export function bindTouch(target, { move, end }) { 58 | let startx = 0; 59 | let starty = 0; 60 | bind(target, 'touchstart', (evt) => { 61 | const { pageX, pageY } = evt.touches[0]; 62 | startx = pageX; 63 | starty = pageY; 64 | }); 65 | bind(target, 'touchmove', (evt) => { 66 | if (!move) return; 67 | const { pageX, pageY } = evt.changedTouches[0]; 68 | const spanx = pageX - startx; 69 | const spany = pageY - starty; 70 | if (Math.abs(spanx) > 10 || Math.abs(spany) > 10) { 71 | // console.log('spanx:', spanx, ', spany:', spany); 72 | calTouchDirection(spanx, spany, evt, move); 73 | startx = pageX; 74 | starty = pageY; 75 | } 76 | evt.preventDefault(); 77 | }); 78 | bind(target, 'touchend', (evt) => { 79 | if (!end) return; 80 | const { pageX, pageY } = evt.changedTouches[0]; 81 | const spanx = pageX - startx; 82 | const spany = pageY - starty; 83 | calTouchDirection(spanx, spany, evt, end); 84 | }); 85 | } 86 | -------------------------------------------------------------------------------- /packages/excel-node-generator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@m-fe/excel-node-generator", 3 | "version": "0.1.1", 4 | "description": "@m-fe/excel-node-generator", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/wx-chevalier/excel.ts" 8 | }, 9 | "author": "wx-chevalier@github", 10 | "license": "MIT", 11 | "main": "dist/cjs/index.js", 12 | "module": "dist/es/index.js", 13 | "types": "dist/types/index.d.ts", 14 | "files": [ 15 | "dist/" 16 | ], 17 | "keywords": [ 18 | "webpack", 19 | "react" 20 | ], 21 | "scripts": { 22 | "build": "npm run clean && npm run build:es && npm run build:cjs", 23 | "build:cjs": "tsc --project ./tsconfig.cjs.json", 24 | "build:es": "tsc --project ./tsconfig.es.json", 25 | "build:umd": "cross-env NODE_ENV=production webpack -p --config ./scripts/webpack/webpack.config.umd.js", 26 | "clean": "rimraf dist", 27 | "clean:r": "rimraf ./dist/*.map && rimraf ./dist/**/*.map && rimraf ./dist/**/*.tsbuildinfo", 28 | "dev": "tsc -w --project ./tsconfig.cjs.json", 29 | "lint": "run-p lint:*", 30 | "lint:es": "cross-env PARSER_NO_WATCH=true eslint . --cache --ext js,md,ts,tsx -f friendly --max-warnings 10", 31 | "lint:ts": "tslint -p . -t stylish", 32 | "lint:tsc": "tsc -p tsconfig.json --incremental false --noEmit", 33 | "prepublish": "npm run clean:r", 34 | "test": "jest --config ./scripts/jest/jest.config.js", 35 | "test:cov": "npm run cleanCov && npm test -- --coverage", 36 | "test:watch": "npm test -- --watch" 37 | }, 38 | "peerDependencies": { 39 | "@m-fe/excel-schema": "*" 40 | }, 41 | "dependencies": { 42 | "@m-fe/utils": "^0.4.8", 43 | "axios": "^0.20.0", 44 | "dayjs": "^1.9.3", 45 | "exceljs": "^4.1.1", 46 | "fs-extra": "^9.0.1", 47 | "qrcode": "^1.4.4", 48 | "urijs": "^1.19.2" 49 | }, 50 | "devDependencies": { 51 | "@m-fe/app-config": "^0.5.2", 52 | "@types/fs-extra": "^9.0.2", 53 | "@types/qrcode": "^1.3.5", 54 | "@types/urijs": "^1.19.12", 55 | "cross-env": "^7.0.2", 56 | "enzyme-adapter-react-16": "^1.15.5", 57 | "lodash": "^4.17.20", 58 | "react": "^16.13.1", 59 | "react-dom": "^16.13.1", 60 | "ufc-schema": "^0.5.46", 61 | "webpack": "^4.43.0" 62 | }, 63 | "browserslist": [ 64 | "extends @m-fe/browserslist-config" 65 | ], 66 | "commitlint": { 67 | "extends": [ 68 | "@m-fe" 69 | ] 70 | }, 71 | "remarkConfig": { 72 | "plugins": [ 73 | "@m-fe/remark-config" 74 | ] 75 | }, 76 | "stylelint": { 77 | "extends": [ 78 | "@m-fe/stylelint-config", 79 | "@m-fe/stylelint-config/modules" 80 | ], 81 | "rules": { 82 | "font-family-no-missing-generic-family-keyword": null, 83 | "no-descending-specificity": null, 84 | "plugin/no-unsupported-browser-features": null, 85 | "plugin/no-low-performance-animation-properties": null 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= htmlWebpackPlugin.options.title %> 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/locale/zh-cn.js: -------------------------------------------------------------------------------- 1 | export default { 2 | toolbar: { 3 | undo: '撤销', 4 | redo: '恢复', 5 | print: '打印', 6 | paintformat: '格式刷', 7 | clearformat: '清除格式', 8 | format: '数据格式', 9 | fontName: '字体', 10 | fontSize: '字号', 11 | fontBold: '加粗', 12 | fontItalic: '倾斜', 13 | underline: '下划线', 14 | strike: '删除线', 15 | color: '字体颜色', 16 | bgcolor: '填充颜色', 17 | border: '边框', 18 | merge: '合并单元格', 19 | align: '水平对齐', 20 | valign: '垂直对齐', 21 | textwrap: '自动换行', 22 | freeze: '冻结', 23 | autofilter: '自动筛选', 24 | formula: '函数', 25 | more: '更多', 26 | }, 27 | contextmenu: { 28 | copy: '复制', 29 | cut: '剪切', 30 | paste: '粘贴', 31 | pasteValue: '粘贴数据', 32 | pasteFormat: '粘贴格式', 33 | hide: '隐藏', 34 | insertRow: '插入行', 35 | insertColumn: '插入列', 36 | deleteSheet: '删除', 37 | deleteRow: '删除行', 38 | deleteColumn: '删除列', 39 | deleteCell: '删除', 40 | deleteCellText: '删除数据', 41 | validation: '数据验证', 42 | cellprintable: '可打印', 43 | cellnonprintable: '不可打印', 44 | celleditable: '可编辑', 45 | cellnoneditable: '不可编辑', 46 | }, 47 | print: { 48 | size: '纸张大小', 49 | orientation: '方向', 50 | orientations: ['横向', '纵向'], 51 | }, 52 | format: { 53 | normal: '正常', 54 | text: '文本', 55 | number: '数值', 56 | percent: '百分比', 57 | rmb: '人民币', 58 | usd: '美元', 59 | eur: '欧元', 60 | date: '短日期', 61 | time: '时间', 62 | datetime: '长日期', 63 | duration: '持续时间', 64 | }, 65 | formula: { 66 | sum: '求和', 67 | average: '求平均值', 68 | max: '求最大值', 69 | min: '求最小值', 70 | concat: '字符拼接', 71 | _if: '条件判断', 72 | and: '和', 73 | or: '或', 74 | }, 75 | validation: { 76 | required: '此值必填', 77 | notMatch: '此值不匹配验证规则', 78 | between: '此值应在 {} 和 {} 之间', 79 | notBetween: '此值不应在 {} 和 {} 之间', 80 | notIn: '此值不在列表中', 81 | equal: '此值应该等于 {}', 82 | notEqual: '此值不应该等于 {}', 83 | lessThan: '此值应该小于 {}', 84 | lessThanEqual: '此值应该小于等于 {}', 85 | greaterThan: '此值应该大于 {}', 86 | greaterThanEqual: '此值应该大于等于 {}', 87 | }, 88 | error: { 89 | pasteForMergedCell: '无法对合并的单元格执行此操作', 90 | }, 91 | calendar: { 92 | weeks: ['日', '一', '二', '三', '四', '五', '六'], 93 | months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], 94 | }, 95 | button: { 96 | next: '下一步', 97 | cancel: '取消', 98 | remove: '删除', 99 | save: '保存', 100 | ok: '确认', 101 | }, 102 | sort: { 103 | desc: '降序', 104 | asc: '升序', 105 | }, 106 | filter: { 107 | empty: '空白', 108 | }, 109 | dataValidation: { 110 | mode: '模式', 111 | range: '单元区间', 112 | criteria: '条件', 113 | modeType: { 114 | cell: '单元格', 115 | column: '列模式', 116 | row: '行模式', 117 | }, 118 | type: { 119 | list: '列表', 120 | number: '数字', 121 | date: '日期', 122 | phone: '手机号', 123 | email: '电子邮件', 124 | }, 125 | operator: { 126 | be: '在区间', 127 | nbe: '不在区间', 128 | lt: '小于', 129 | lte: '小于等于', 130 | gt: '大于', 131 | gte: '大于等于', 132 | eq: '等于', 133 | neq: '不等于', 134 | }, 135 | }, 136 | }; 137 | -------------------------------------------------------------------------------- /scripts/docker/build-on-gitlab.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Gitlab CI 项目镜像构建 4 | # 5 | # Gitlab CI 预定义变量: 6 | # https://docs.gitlab.com/ee/ci/variables/predefined_variables.html 7 | # 8 | # Globals: 9 | # CI_COMMIT_TAG: 当前提交的 tag 10 | # CI_COMMIT_SHA: 当前提交的 commit sha 11 | # CI_COMMIT_REF_NAME: 当前提交的 ref name (branch/tag) 12 | # 13 | # 测试: 14 | # CI_COMMIT_TAG= ./build-on-gitlab.sh 15 | # CI_COMMIT_SHA= CI_COMMIT_REF_NAME=dev ./build-on-gitlab.sh 16 | 17 | # 切入项目根目录 18 | cd $(dirname $0)/../.. 19 | 20 | DOCKERFILE=${DOCKERFILE:=scripts/docker/Dockerfile.gitlab} 21 | IMAGE=${IMAGE} 22 | PACKAGE_VERSION=$(cat package.json | grep version | head -1 | awk -F= "{ print $2 }" | sed 's/[version:,\",]//g' | tr -d '[[:space:]]') 23 | 24 | ####################################### 25 | # 构建 tag 了的 commit 的镜像并推送 26 | # 27 | # Globals: 28 | # CI_COMMIT_TAG 29 | # IMAGE 30 | # DOCKERFILE 31 | ####################################### 32 | build_tag() { 33 | echo "[1/2] Building ${IMAGE}:${CI_COMMIT_TAG}" 34 | docker build -t ${IMAGE}:${CI_COMMIT_TAG} -f ${DOCKERFILE} . 35 | 36 | echo "[2/2] Pushing ${IMAGE}:${CI_COMMIT_TAG}" 37 | docker push ${IMAGE}:${CI_COMMIT_TAG} 38 | } 39 | 40 | ####################################### 41 | # 在 master 分支构建 rc 镜像,tag 为 package.json 版本 42 | # 43 | # Globals: 44 | # CHART_VERSION 45 | # IMAGE 46 | # DOCKERFILE 47 | ####################################### 48 | build_master() { 49 | local image_tag=v${PACKAGE_VERSION}-rc 50 | echo "[1/2] Building ${IMAGE}:${image_tag}" 51 | docker build -t ${IMAGE}:${image_tag} -f ${DOCKERFILE} . 52 | 53 | echo "[2/2] Pushing ${IMAGE}:${image_tag}" 54 | docker push ${IMAGE}:${image_tag} 55 | } 56 | 57 | ####################################### 58 | # 构建 dev 分支镜像,将构建 COMMIT_SHA 和 latest 两个 tag 的镜像 59 | # 60 | # Globals: 61 | # CI_COMMIT_SHA 62 | # IMAGE 63 | # DOCKERFILE 64 | ####################################### 65 | build_dev() { 66 | echo "[1/3] Building ${IMAGE}:${CI_COMMIT_SHA} $IMAGE:latest" 67 | docker build -t ${IMAGE}:${CI_COMMIT_SHA} -t ${IMAGE}:latest -f ${DOCKERFILE} . || return $? 68 | 69 | echo "[2/3] Pushing ${IMAGE}:latest" 70 | docker push ${IMAGE}:latest || return $? 71 | 72 | # echo "[3/3] Pushing ${IMAGE}:${CI_COMMIT_SHA}" 73 | # docker push ${IMAGE}:${CI_COMMIT_SHA} || return $? 74 | } 75 | 76 | ####################################### 77 | # 非 dev/master 且未 tag 的 commit 生成该分支的镜像 78 | # 79 | # Globals: 80 | # CI_COMMIT_REF_NAME 81 | # IMAGE 82 | # DOCKERFILE 83 | ####################################### 84 | build_other_branch() { 85 | local tag=$(echo ${CI_COMMIT_REF_NAME} | sed 's/\//-/g') 86 | 87 | echo "[1/2] Building ${IMAGE}:${tag}" 88 | docker build -t ${IMAGE}:${tag} -f ${DOCKERFILE} . || return $? 89 | 90 | echo "[2/2] Pushing ${IMAGE}:${tag}" 91 | docker push ${IMAGE}:${tag} || return $? 92 | } 93 | 94 | case ${CI_COMMIT_REF_NAME} in 95 | dev) 96 | echo "Build on dev branch" 97 | build_dev 98 | ;; 99 | master) 100 | echo "Build on master branch" 101 | build_master 102 | ;; 103 | *) 104 | if [[ ! -z ${CI_COMMIT_TAG} ]]; then 105 | echo "Build tag ${CI_COMMIT_TAG}" 106 | build_tag 107 | else 108 | if [[ ! -z ${CI_COMMIT_REF_NAME} ]]; then 109 | echo "Build on ${CI_COMMIT_REF_NAME} branch" 110 | build_other_branch 111 | fi 112 | fi 113 | ;; 114 | esac 115 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/contextmenu.js: -------------------------------------------------------------------------------- 1 | import { h } from './element'; 2 | import { bindClickoutside, unbindClickoutside } from './event'; 3 | import { cssPrefix } from '../config'; 4 | import { tf } from '../locale/locale'; 5 | 6 | const menuItems = [ 7 | { key: 'copy', title: tf('contextmenu.copy'), label: 'Ctrl+C' }, 8 | { key: 'cut', title: tf('contextmenu.cut'), label: 'Ctrl+X' }, 9 | { key: 'paste', title: tf('contextmenu.paste'), label: 'Ctrl+V' }, 10 | { key: 'paste-value', title: tf('contextmenu.pasteValue'), label: 'Ctrl+Shift+V' }, 11 | { key: 'paste-format', title: tf('contextmenu.pasteFormat'), label: 'Ctrl+Alt+V' }, 12 | { key: 'divider' }, 13 | { key: 'insert-row', title: tf('contextmenu.insertRow') }, 14 | { key: 'insert-column', title: tf('contextmenu.insertColumn') }, 15 | { key: 'divider' }, 16 | { key: 'delete-row', title: tf('contextmenu.deleteRow') }, 17 | { key: 'delete-column', title: tf('contextmenu.deleteColumn') }, 18 | { key: 'delete-cell-text', title: tf('contextmenu.deleteCellText') }, 19 | { key: 'hide', title: tf('contextmenu.hide') }, 20 | { key: 'divider' }, 21 | { key: 'validation', title: tf('contextmenu.validation') }, 22 | { key: 'divider' }, 23 | { key: 'cell-printable', title: tf('contextmenu.cellprintable') }, 24 | { key: 'cell-non-printable', title: tf('contextmenu.cellnonprintable') }, 25 | { key: 'divider' }, 26 | { key: 'cell-editable', title: tf('contextmenu.celleditable') }, 27 | { key: 'cell-non-editable', title: tf('contextmenu.cellnoneditable') }, 28 | ]; 29 | 30 | function buildMenuItem(item) { 31 | if (item.key === 'divider') { 32 | return h('div', `${cssPrefix}-item divider`); 33 | } 34 | return h('div', `${cssPrefix}-item`) 35 | .on('click', () => { 36 | this.itemClick(item.key); 37 | this.hide(); 38 | }) 39 | .children( 40 | item.title(), 41 | h('div', 'label').child(item.label || ''), 42 | ); 43 | } 44 | 45 | function buildMenu() { 46 | return menuItems.map(it => buildMenuItem.call(this, it)); 47 | } 48 | 49 | export default class ContextMenu { 50 | constructor(viewFn, isHide = false) { 51 | this.menuItems = buildMenu.call(this); 52 | this.el = h('div', `${cssPrefix}-contextmenu`) 53 | .children(...this.menuItems) 54 | .hide(); 55 | this.viewFn = viewFn; 56 | this.itemClick = () => {}; 57 | this.isHide = isHide; 58 | this.setMode('range'); 59 | } 60 | 61 | // row-col: the whole rows or the whole cols 62 | // range: select range 63 | setMode(mode) { 64 | const hideEl = this.menuItems[12]; 65 | if (mode === 'row-col') { 66 | hideEl.show(); 67 | } else { 68 | hideEl.hide(); 69 | } 70 | } 71 | 72 | hide() { 73 | const { el } = this; 74 | el.hide(); 75 | unbindClickoutside(el); 76 | } 77 | 78 | setPosition(x, y) { 79 | if (this.isHide) return; 80 | const { el } = this; 81 | const { width } = el.show().offset(); 82 | const view = this.viewFn(); 83 | const vhf = view.height / 2; 84 | let left = x; 85 | if (view.width - x <= width) { 86 | left -= width; 87 | } 88 | el.css('left', `${left}px`); 89 | if (y > vhf) { 90 | el.css('bottom', `${view.height - y}px`) 91 | .css('max-height', `${y}px`) 92 | .css('top', 'auto'); 93 | } else { 94 | el.css('top', `${y}px`) 95 | .css('max-height', `${view.height - y}px`) 96 | .css('bottom', 'auto'); 97 | } 98 | bindClickoutside(el); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/calendar.js: -------------------------------------------------------------------------------- 1 | import { h } from './element'; 2 | import Icon from './icon'; 3 | import { t } from '../locale/locale'; 4 | 5 | function addMonth(date, step) { 6 | date.setMonth(date.getMonth() + step); 7 | } 8 | 9 | function weekday(date, index) { 10 | const d = new Date(date); 11 | d.setDate(index - date.getDay() + 1); 12 | return d; 13 | } 14 | 15 | function monthDays(year, month, cdate) { 16 | // the first day of month 17 | const startDate = new Date(year, month, 1, 23, 59, 59); 18 | const datess = [[], [], [], [], [], []]; 19 | for (let i = 0; i < 6; i += 1) { 20 | for (let j = 0; j < 7; j += 1) { 21 | const index = i * 7 + j; 22 | const d = weekday(startDate, index); 23 | const disabled = d.getMonth() !== month; 24 | // console.log('d:', d, ', cdate:', cdate); 25 | const active = d.getMonth() === cdate.getMonth() && d.getDate() === cdate.getDate(); 26 | datess[i][j] = { d, disabled, active }; 27 | } 28 | } 29 | return datess; 30 | } 31 | 32 | export default class Calendar { 33 | constructor(value) { 34 | this.value = value; 35 | this.cvalue = new Date(value); 36 | 37 | this.headerLeftEl = h('div', 'calendar-header-left'); 38 | this.bodyEl = h('tbody', ''); 39 | this.buildAll(); 40 | this.el = h('div', 'x-spreadsheet-calendar') 41 | .children( 42 | h('div', 'calendar-header').children( 43 | this.headerLeftEl, 44 | h('div', 'calendar-header-right').children( 45 | h('a', 'calendar-prev') 46 | .on('click.stop', () => this.prev()) 47 | .child(new Icon('chevron-left')), 48 | h('a', 'calendar-next') 49 | .on('click.stop', () => this.next()) 50 | .child(new Icon('chevron-right')), 51 | ), 52 | ), 53 | h('table', 'calendar-body').children( 54 | h('thead', '').child( 55 | h('tr', '').children( 56 | ...t('calendar.weeks').map(week => h('th', 'cell').child(week)), 57 | ), 58 | ), 59 | this.bodyEl, 60 | ), 61 | ); 62 | this.selectChange = () => {}; 63 | } 64 | 65 | setValue(value) { 66 | this.value = value; 67 | this.cvalue = new Date(value); 68 | this.buildAll(); 69 | } 70 | 71 | prev() { 72 | const { value } = this; 73 | addMonth(value, -1); 74 | this.buildAll(); 75 | } 76 | 77 | next() { 78 | const { value } = this; 79 | addMonth(value, 1); 80 | this.buildAll(); 81 | } 82 | 83 | buildAll() { 84 | this.buildHeaderLeft(); 85 | this.buildBody(); 86 | } 87 | 88 | buildHeaderLeft() { 89 | const { value } = this; 90 | this.headerLeftEl.html(`${t('calendar.months')[value.getMonth()]} ${value.getFullYear()}`); 91 | } 92 | 93 | buildBody() { 94 | const { value, cvalue, bodyEl } = this; 95 | const mDays = monthDays(value.getFullYear(), value.getMonth(), cvalue); 96 | const trs = mDays.map((it) => { 97 | const tds = it.map((it1) => { 98 | let cls = 'cell'; 99 | if (it1.disabled) cls += ' disabled'; 100 | if (it1.active) cls += ' active'; 101 | return h('td', '').child( 102 | h('div', cls) 103 | .on('click.stop', () => { 104 | this.selectChange(it1.d); 105 | }) 106 | .child(it1.d.getDate().toString()), 107 | ); 108 | }); 109 | return h('tr', '').children(...tds); 110 | }); 111 | bodyEl.html('').children(...trs); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/core/validation.js: -------------------------------------------------------------------------------- 1 | import Validator from './validator'; 2 | import { CellRange } from './cell_range'; 3 | 4 | class Validation { 5 | constructor(mode, refs, validator) { 6 | this.refs = refs; 7 | this.mode = mode; // cell 8 | this.validator = validator; 9 | } 10 | 11 | includes(ri, ci) { 12 | const { refs } = this; 13 | for (let i = 0; i < refs.length; i += 1) { 14 | const cr = CellRange.valueOf(refs[i]); 15 | if (cr.includes(ri, ci)) return true; 16 | } 17 | return false; 18 | } 19 | 20 | addRef(ref) { 21 | this.remove(CellRange.valueOf(ref)); 22 | this.refs.push(ref); 23 | } 24 | 25 | remove(cellRange) { 26 | const nrefs = []; 27 | this.refs.forEach((it) => { 28 | const cr = CellRange.valueOf(it); 29 | if (cr.intersects(cellRange)) { 30 | const crs = cr.difference(cellRange); 31 | crs.forEach(it1 => nrefs.push(it1.toString())); 32 | } else { 33 | nrefs.push(it); 34 | } 35 | }); 36 | this.refs = nrefs; 37 | } 38 | 39 | getData() { 40 | const { refs, mode, validator } = this; 41 | const { 42 | type, required, operator, value, 43 | } = validator; 44 | return { 45 | refs, mode, type, required, operator, value, 46 | }; 47 | } 48 | 49 | static valueOf({ 50 | refs, mode, type, required, operator, value, 51 | }) { 52 | return new Validation(mode, refs, new Validator(type, required, value, operator)); 53 | } 54 | } 55 | class Validations { 56 | constructor() { 57 | this._ = []; 58 | // ri_ci: errMessage 59 | this.errors = new Map(); 60 | } 61 | 62 | getError(ri, ci) { 63 | return this.errors.get(`${ri}_${ci}`); 64 | } 65 | 66 | validate(ri, ci, text) { 67 | const v = this.get(ri, ci); 68 | const key = `${ri}_${ci}`; 69 | const { errors } = this; 70 | if (v !== null) { 71 | const [flag, message] = v.validator.validate(text); 72 | if (!flag) { 73 | errors.set(key, message); 74 | } else { 75 | errors.delete(key); 76 | } 77 | } else { 78 | errors.delete(key); 79 | } 80 | return true; 81 | } 82 | 83 | // type: date|number|phone|email|list 84 | // validator: { required, value, operator } 85 | add(mode, ref, { 86 | type, required, value, operator, 87 | }) { 88 | const validator = new Validator( 89 | type, required, value, operator, 90 | ); 91 | const v = this.getByValidator(validator); 92 | if (v !== null) { 93 | v.addRef(ref); 94 | } else { 95 | this._.push(new Validation(mode, [ref], validator)); 96 | } 97 | } 98 | 99 | getByValidator(validator) { 100 | for (let i = 0; i < this._.length; i += 1) { 101 | const v = this._[i]; 102 | if (v.validator.equals(validator)) { 103 | return v; 104 | } 105 | } 106 | return null; 107 | } 108 | 109 | get(ri, ci) { 110 | for (let i = 0; i < this._.length; i += 1) { 111 | const v = this._[i]; 112 | if (v.includes(ri, ci)) return v; 113 | } 114 | return null; 115 | } 116 | 117 | remove(cellRange) { 118 | this.each((it) => { 119 | it.remove(cellRange); 120 | }); 121 | } 122 | 123 | each(cb) { 124 | this._.forEach(it => cb(it)); 125 | } 126 | 127 | getData() { 128 | return this._.filter(it => it.refs.length > 0).map(it => it.getData()); 129 | } 130 | 131 | setData(d) { 132 | this._ = d.map(it => Validation.valueOf(it)); 133 | } 134 | } 135 | 136 | export default {}; 137 | export { 138 | Validations, 139 | }; 140 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/resizer.js: -------------------------------------------------------------------------------- 1 | /* global window */ 2 | import { h } from './element'; 3 | import { mouseMoveUp } from './event'; 4 | import { cssPrefix } from '../config'; 5 | 6 | export default class Resizer { 7 | constructor(vertical = false, minDistance) { 8 | this.moving = false; 9 | this.vertical = vertical; 10 | this.el = h('div', `${cssPrefix}-resizer ${vertical ? 'vertical' : 'horizontal'}`).children( 11 | this.unhideHoverEl = h('div', `${cssPrefix}-resizer-hover`) 12 | .on('dblclick.stop', evt => this.mousedblclickHandler(evt)) 13 | .css('position', 'absolute').hide(), 14 | this.hoverEl = h('div', `${cssPrefix}-resizer-hover`) 15 | .on('mousedown.stop', evt => this.mousedownHandler(evt)), 16 | this.lineEl = h('div', `${cssPrefix}-resizer-line`).hide(), 17 | ).hide(); 18 | // cell rect 19 | this.cRect = null; 20 | this.finishedFn = null; 21 | this.minDistance = minDistance; 22 | this.unhideFn = () => {}; 23 | } 24 | 25 | showUnhide(index) { 26 | this.unhideIndex = index; 27 | this.unhideHoverEl.show(); 28 | } 29 | 30 | hideUnhide() { 31 | this.unhideHoverEl.hide(); 32 | } 33 | 34 | // rect : {top, left, width, height} 35 | // line : {width, height} 36 | show(rect, line) { 37 | const { 38 | moving, vertical, hoverEl, lineEl, el, 39 | unhideHoverEl, 40 | } = this; 41 | if (moving) return; 42 | this.cRect = rect; 43 | const { 44 | left, top, width, height, 45 | } = rect; 46 | el.offset({ 47 | left: vertical ? left + width - 5 : left, 48 | top: vertical ? top : top + height - 5, 49 | }).show(); 50 | hoverEl.offset({ 51 | width: vertical ? 5 : width, 52 | height: vertical ? height : 5, 53 | }); 54 | lineEl.offset({ 55 | width: vertical ? 0 : line.width, 56 | height: vertical ? line.height : 0, 57 | }); 58 | unhideHoverEl.offset({ 59 | left: vertical ? 5 - width : left, 60 | top: vertical ? top : 5 - height, 61 | width: vertical ? 5 : width, 62 | height: vertical ? height : 5, 63 | }); 64 | } 65 | 66 | hide() { 67 | this.el.offset({ 68 | left: 0, 69 | top: 0, 70 | }).hide(); 71 | this.hideUnhide(); 72 | } 73 | 74 | mousedblclickHandler() { 75 | if (this.unhideIndex) this.unhideFn(this.unhideIndex); 76 | } 77 | 78 | mousedownHandler(evt) { 79 | let startEvt = evt; 80 | const { 81 | el, lineEl, cRect, vertical, minDistance, 82 | } = this; 83 | let distance = vertical ? cRect.width : cRect.height; 84 | // console.log('distance:', distance); 85 | lineEl.show(); 86 | mouseMoveUp(window, (e) => { 87 | this.moving = true; 88 | if (startEvt !== null && e.buttons === 1) { 89 | // console.log('top:', top, ', left:', top, ', cRect:', cRect); 90 | if (vertical) { 91 | distance += e.movementX; 92 | if (distance > minDistance) { 93 | el.css('left', `${cRect.left + distance}px`); 94 | } 95 | } else { 96 | distance += e.movementY; 97 | if (distance > minDistance) { 98 | el.css('top', `${cRect.top + distance}px`); 99 | } 100 | } 101 | startEvt = e; 102 | } 103 | }, () => { 104 | startEvt = null; 105 | lineEl.hide(); 106 | this.moving = false; 107 | this.hide(); 108 | if (this.finishedFn) { 109 | if (distance < minDistance) distance = minDistance; 110 | this.finishedFn(cRect, distance); 111 | } 112 | }); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/core/validator.js: -------------------------------------------------------------------------------- 1 | import { t } from '../locale/locale'; 2 | import helper from './helper'; 3 | 4 | const rules = { 5 | phone: /^[1-9]\d{10}$/, 6 | email: /w+([-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*/, 7 | }; 8 | 9 | function returnMessage(flag, key, ...arg) { 10 | let message = ''; 11 | if (!flag) { 12 | message = t(`validation.${key}`, ...arg); 13 | } 14 | return [flag, message]; 15 | } 16 | 17 | export default class Validator { 18 | // operator: b|nb|eq|neq|lt|lte|gt|gte 19 | // type: date|number|list|phone|email 20 | constructor(type, required, value, operator) { 21 | this.required = required; 22 | this.value = value; 23 | this.type = type; 24 | this.operator = operator; 25 | this.message = ''; 26 | } 27 | 28 | parseValue(v) { 29 | const { type } = this; 30 | if (type === 'date') { 31 | return new Date(v); 32 | } 33 | if (type === 'number') { 34 | return Number(v); 35 | } 36 | return v; 37 | } 38 | 39 | equals(other) { 40 | let flag = this.type === other.type 41 | && this.required === other.required 42 | && this.operator === other.operator; 43 | if (flag) { 44 | if (Array.isArray(this.value)) { 45 | flag = helper.arrayEquals(this.value, other.value); 46 | } else { 47 | flag = this.value === other.value; 48 | } 49 | } 50 | return flag; 51 | } 52 | 53 | values() { 54 | return this.value.split(','); 55 | } 56 | 57 | validate(v) { 58 | const { 59 | required, operator, value, type, 60 | } = this; 61 | if (required && /^\s*$/.test(v)) { 62 | return returnMessage(false, 'required'); 63 | } 64 | if (/^\s*$/.test(v)) return [true]; 65 | if (rules[type] && !rules[type].test(v)) { 66 | return returnMessage(false, 'notMatch'); 67 | } 68 | if (type === 'list') { 69 | return returnMessage(this.values().includes(v), 'notIn'); 70 | } 71 | if (operator) { 72 | const v1 = this.parseValue(v); 73 | if (operator === 'be') { 74 | const [min, max] = value; 75 | return returnMessage( 76 | v1 >= this.parseValue(min) && v1 <= this.parseValue(max), 77 | 'between', 78 | min, 79 | max, 80 | ); 81 | } 82 | if (operator === 'nbe') { 83 | const [min, max] = value; 84 | return returnMessage( 85 | v1 < this.parseValue(min) || v1 > this.parseValue(max), 86 | 'notBetween', 87 | min, 88 | max, 89 | ); 90 | } 91 | if (operator === 'eq') { 92 | return returnMessage( 93 | v1 === this.parseValue(value), 94 | 'equal', 95 | value, 96 | ); 97 | } 98 | if (operator === 'neq') { 99 | return returnMessage( 100 | v1 !== this.parseValue(value), 101 | 'notEqual', 102 | value, 103 | ); 104 | } 105 | if (operator === 'lt') { 106 | return returnMessage( 107 | v1 < this.parseValue(value), 108 | 'lessThan', 109 | value, 110 | ); 111 | } 112 | if (operator === 'lte') { 113 | return returnMessage( 114 | v1 <= this.parseValue(value), 115 | 'lessThanEqual', 116 | value, 117 | ); 118 | } 119 | if (operator === 'gt') { 120 | return returnMessage( 121 | v1 > this.parseValue(value), 122 | 'greaterThan', 123 | value, 124 | ); 125 | } 126 | if (operator === 'gte') { 127 | return returnMessage( 128 | v1 >= this.parseValue(value), 129 | 'greaterThanEqual', 130 | value, 131 | ); 132 | } 133 | } 134 | return [true]; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/index.js: -------------------------------------------------------------------------------- 1 | /* global window, document */ 2 | import { h } from "./component/element"; 3 | import DataProxy from "./core/data_proxy"; 4 | import Sheet from "./component/sheet"; 5 | import Bottombar from "./component/bottombar"; 6 | import { cssPrefix } from "./config"; 7 | import { locale } from "./locale/locale"; 8 | import "./index.less"; 9 | 10 | class Spreadsheet { 11 | constructor(selectors, options = {}) { 12 | let targetEl = selectors; 13 | this.options = options; 14 | this.sheetIndex = 1; 15 | this.datas = []; 16 | if (typeof selectors === "string") { 17 | targetEl = document.querySelector(selectors); 18 | } 19 | this.bottombar = new Bottombar( 20 | () => { 21 | const d = this.addSheet(); 22 | this.sheet.resetData(d); 23 | }, 24 | (index) => { 25 | const d = this.datas[index]; 26 | this.sheet.resetData(d); 27 | }, 28 | () => { 29 | this.deleteSheet(); 30 | }, 31 | (index, value) => { 32 | this.datas[index].name = value; 33 | } 34 | ); 35 | this.data = this.addSheet(); 36 | const rootEl = h("div", `${cssPrefix}`).on("contextmenu", (evt) => 37 | evt.preventDefault() 38 | ); 39 | // create canvas element 40 | targetEl.appendChild(rootEl.el); 41 | this.sheet = new Sheet(rootEl, this.data); 42 | rootEl.child(this.bottombar.el); 43 | } 44 | 45 | addSheet(name, active = true) { 46 | const n = name || `sheet${this.sheetIndex}`; 47 | const d = new DataProxy(n, this.options); 48 | d.change = (...args) => { 49 | this.sheet.trigger("change", ...args); 50 | }; 51 | this.datas.push(d); 52 | // console.log('d:', n, d, this.datas); 53 | this.bottombar.addItem(n, active); 54 | this.sheetIndex += 1; 55 | return d; 56 | } 57 | 58 | deleteSheet() { 59 | const [oldIndex, nindex] = this.bottombar.deleteItem(); 60 | if (oldIndex >= 0) { 61 | this.datas.splice(oldIndex, 1); 62 | if (nindex >= 0) this.sheet.resetData(this.datas[nindex]); 63 | } 64 | } 65 | 66 | loadData(data) { 67 | const ds = Array.isArray(data) ? data : [data]; 68 | this.bottombar.clear(); 69 | this.datas = []; 70 | if (ds.length > 0) { 71 | for (let i = 0; i < ds.length; i += 1) { 72 | const it = ds[i]; 73 | const nd = this.addSheet(it.name, i === 0); 74 | nd.setData(it); 75 | if (i === 0) { 76 | this.sheet.resetData(nd); 77 | } 78 | } 79 | } 80 | return this; 81 | } 82 | 83 | getData() { 84 | return this.datas.map((it) => it.getData()); 85 | } 86 | 87 | cellText(ri, ci, text, sheetIndex = 0) { 88 | this.datas[sheetIndex].setCellText(ri, ci, text, "finished"); 89 | return this; 90 | } 91 | 92 | cell(ri, ci, sheetIndex = 0) { 93 | return this.datas[sheetIndex].getCell(ri, ci); 94 | } 95 | 96 | cellStyle(ri, ci, sheetIndex = 0) { 97 | return this.datas[sheetIndex].getCellStyle(ri, ci); 98 | } 99 | 100 | reRender() { 101 | this.sheet.table.render(); 102 | return this; 103 | } 104 | 105 | on(eventName, func) { 106 | this.sheet.on(eventName, func); 107 | return this; 108 | } 109 | 110 | validate() { 111 | const { validations } = this.data; 112 | return validations.errors.size <= 0; 113 | } 114 | 115 | change(cb) { 116 | this.sheet.on("change", cb); 117 | return this; 118 | } 119 | 120 | static locale(lang, message) { 121 | locale(lang, message); 122 | } 123 | } 124 | 125 | const spreadsheet = (el, options = {}) => new Spreadsheet(el, options); 126 | 127 | if (window) { 128 | window.x_spreadsheet = spreadsheet; 129 | window.x_spreadsheet.locale = (lang, message) => locale(lang, message); 130 | } 131 | 132 | export default Spreadsheet; 133 | export { spreadsheet }; 134 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/component/suggest.js: -------------------------------------------------------------------------------- 1 | import { h } from './element'; 2 | import { bindClickoutside, unbindClickoutside } from './event'; 3 | import { cssPrefix } from '../config'; 4 | 5 | function inputMovePrev(evt) { 6 | evt.preventDefault(); 7 | evt.stopPropagation(); 8 | const { filterItems } = this; 9 | if (filterItems.length <= 0) return; 10 | if (this.itemIndex >= 0) filterItems[this.itemIndex].toggle(); 11 | this.itemIndex -= 1; 12 | if (this.itemIndex < 0) { 13 | this.itemIndex = filterItems.length - 1; 14 | } 15 | filterItems[this.itemIndex].toggle(); 16 | } 17 | 18 | function inputMoveNext(evt) { 19 | evt.stopPropagation(); 20 | const { filterItems } = this; 21 | if (filterItems.length <= 0) return; 22 | if (this.itemIndex >= 0) filterItems[this.itemIndex].toggle(); 23 | this.itemIndex += 1; 24 | if (this.itemIndex > filterItems.length - 1) { 25 | this.itemIndex = 0; 26 | } 27 | filterItems[this.itemIndex].toggle(); 28 | } 29 | 30 | function inputEnter(evt) { 31 | evt.preventDefault(); 32 | const { filterItems } = this; 33 | if (filterItems.length <= 0) return; 34 | evt.stopPropagation(); 35 | if (this.itemIndex < 0) this.itemIndex = 0; 36 | filterItems[this.itemIndex].el.click(); 37 | this.hide(); 38 | } 39 | 40 | function inputKeydownHandler(evt) { 41 | const { keyCode } = evt; 42 | if (evt.ctrlKey) { 43 | evt.stopPropagation(); 44 | } 45 | switch (keyCode) { 46 | case 37: // left 47 | evt.stopPropagation(); 48 | break; 49 | case 38: // up 50 | inputMovePrev.call(this, evt); 51 | break; 52 | case 39: // right 53 | evt.stopPropagation(); 54 | break; 55 | case 40: // down 56 | inputMoveNext.call(this, evt); 57 | break; 58 | case 13: // enter 59 | inputEnter.call(this, evt); 60 | break; 61 | case 9: 62 | inputEnter.call(this, evt); 63 | break; 64 | default: 65 | evt.stopPropagation(); 66 | break; 67 | } 68 | } 69 | 70 | export default class Suggest { 71 | constructor(items, itemClick, width = '200px') { 72 | this.filterItems = []; 73 | this.items = items; 74 | this.el = h('div', `${cssPrefix}-suggest`).css('width', width).hide(); 75 | this.itemClick = itemClick; 76 | this.itemIndex = -1; 77 | } 78 | 79 | setOffset(v) { 80 | this.el.cssRemoveKeys('top', 'bottom') 81 | .offset(v); 82 | } 83 | 84 | hide() { 85 | const { el } = this; 86 | this.filterItems = []; 87 | this.itemIndex = -1; 88 | el.hide(); 89 | unbindClickoutside(this.el.parent()); 90 | } 91 | 92 | setItems(items) { 93 | this.items = items; 94 | // this.search(''); 95 | } 96 | 97 | search(word) { 98 | let { items } = this; 99 | if (!/^\s*$/.test(word)) { 100 | items = items.filter(it => (it.key || it).startsWith(word.toUpperCase())); 101 | } 102 | items = items.map((it) => { 103 | let { title } = it; 104 | if (title) { 105 | if (typeof title === 'function') { 106 | title = title(); 107 | } 108 | } else { 109 | title = it; 110 | } 111 | const item = h('div', `${cssPrefix}-item`) 112 | .child(title) 113 | .on('click.stop', () => { 114 | this.itemClick(it); 115 | this.hide(); 116 | }); 117 | if (it.label) { 118 | item.child(h('div', 'label').html(it.label)); 119 | } 120 | return item; 121 | }); 122 | this.filterItems = items; 123 | if (items.length <= 0) { 124 | return; 125 | } 126 | const { el } = this; 127 | // items[0].toggle(); 128 | el.html('').children(...items).show(); 129 | bindClickoutside(el.parent(), () => { this.hide(); }); 130 | } 131 | 132 | bindInputEvents(input) { 133 | input.on('keydown', evt => inputKeydownHandler.call(this, evt)); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/locale/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | toolbar: { 3 | undo: 'Undo', 4 | redo: 'Redo', 5 | print: 'Print', 6 | paintformat: 'Paint format', 7 | clearformat: 'Clear format', 8 | format: 'Format', 9 | fontName: 'Font', 10 | fontSize: 'Font size', 11 | fontBold: 'Font bold', 12 | fontItalic: 'Font italic', 13 | underline: 'Underline', 14 | strike: 'Strike', 15 | color: 'Text color', 16 | bgcolor: 'Fill color', 17 | border: 'Borders', 18 | merge: 'Merge cells', 19 | align: 'Horizontal align', 20 | valign: 'Vertical align', 21 | textwrap: 'Text wrapping', 22 | freeze: 'Freeze cell', 23 | autofilter: 'Filter', 24 | formula: 'Functions', 25 | more: 'More', 26 | }, 27 | contextmenu: { 28 | copy: 'Copy', 29 | cut: 'Cut', 30 | paste: 'Paste', 31 | pasteValue: 'Paste values only', 32 | pasteFormat: 'Paste format only', 33 | hide: 'Hide', 34 | insertRow: 'Insert row', 35 | insertColumn: 'Insert column', 36 | deleteSheet: 'Delete', 37 | deleteRow: 'Delete row', 38 | deleteColumn: 'Delete column', 39 | deleteCell: 'Delete cell', 40 | deleteCellText: 'Delete cell text', 41 | validation: 'Data validations', 42 | cellprintable: 'Enable export', 43 | cellnonprintable: 'Disable export', 44 | celleditable: 'Enable editing', 45 | cellnoneditable: 'Disable editing', 46 | }, 47 | print: { 48 | size: 'Paper size', 49 | orientation: 'Page orientation', 50 | orientations: ['Landscape', 'Portrait'], 51 | }, 52 | format: { 53 | normal: 'Normal', 54 | text: 'Plain Text', 55 | number: 'Number', 56 | percent: 'Percent', 57 | rmb: 'RMB', 58 | usd: 'USD', 59 | eur: 'EUR', 60 | date: 'Date', 61 | time: 'Time', 62 | datetime: 'Date time', 63 | duration: 'Duration', 64 | }, 65 | formula: { 66 | sum: 'Sum', 67 | average: 'Average', 68 | max: 'Max', 69 | min: 'Min', 70 | _if: 'IF', 71 | and: 'AND', 72 | or: 'OR', 73 | concat: 'Concat', 74 | }, 75 | validation: { 76 | required: 'it must be required', 77 | notMatch: 'it not match its validation rule', 78 | between: 'it is between {} and {}', 79 | notBetween: 'it is not between {} and {}', 80 | notIn: 'it is not in list', 81 | equal: 'it equal to {}', 82 | notEqual: 'it not equal to {}', 83 | lessThan: 'it less than {}', 84 | lessThanEqual: 'it less than or equal to {}', 85 | greaterThan: 'it greater than {}', 86 | greaterThanEqual: 'it greater than or equal to {}', 87 | }, 88 | error: { 89 | pasteForMergedCell: 'Unable to do this for merged cells', 90 | }, 91 | calendar: { 92 | weeks: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], 93 | months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], 94 | }, 95 | button: { 96 | next: 'Next', 97 | cancel: 'Cancel', 98 | remove: 'Remove', 99 | save: 'Save', 100 | ok: 'OK', 101 | }, 102 | sort: { 103 | desc: 'Sort Z -> A', 104 | asc: 'Sort A -> Z', 105 | }, 106 | filter: { 107 | empty: 'empty', 108 | }, 109 | dataValidation: { 110 | mode: 'Mode', 111 | range: 'Cell Range', 112 | criteria: 'Criteria', 113 | modeType: { 114 | cell: 'Cell', 115 | column: 'Colun', 116 | row: 'Row', 117 | }, 118 | type: { 119 | list: 'List', 120 | number: 'Number', 121 | date: 'Date', 122 | phone: 'Phone', 123 | email: 'Email', 124 | }, 125 | operator: { 126 | be: 'between', 127 | nbe: 'not betwwen', 128 | lt: 'less than', 129 | lte: 'less than or equal to', 130 | gt: 'greater than', 131 | gte: 'greater than or equal to', 132 | eq: 'equal to', 133 | neq: 'not equal to', 134 | }, 135 | }, 136 | }; 137 | -------------------------------------------------------------------------------- /packages/excel-node-generator/src/__test__/data.ts: -------------------------------------------------------------------------------- 1 | import * as S from 'ufc-schema'; 2 | 3 | const inquiryOrderStatements: any[] = [ 4 | { 5 | id: 'INQUIRY_ORDER-535', 6 | code: 'XJ20200815-374815-4', 7 | payMethod: '月结', 8 | createdAt: '2020-08-15T22:33:09+08:00', 9 | switchWaitProductAt: '2020-09-01T16:21:51+08:00', 10 | price: { 11 | orderId: 'INQUIRY_ORDER-535', 12 | handleFee: 0, 13 | postage: 0, 14 | packingFee: 0, 15 | surcharge: 0, 16 | materialFee: 1010.91, 17 | taxRate: 130, 18 | totalPriceWithTax: 1010.91, 19 | totalPriceWithoutTax: 879.49, 20 | }, 21 | saleManId: 'USER-6', 22 | saleMan: { 23 | userId: 'USER-6', 24 | username: 'XXXX', 25 | nickname: 'XXXX', 26 | }, 27 | printInfo: [ 28 | { 29 | orderId: 'INQUIRY_ORDER-535', 30 | orderItemId: 'INQUIRY_ORDER_ITEM-641', 31 | fileId: 'FILE-30283', 32 | handle: { 33 | method: '', 34 | desc: '', 35 | }, 36 | price: 1010.91, 37 | printCount: 1, 38 | fileName: '巴基碗_巴基碗1.stl', 39 | previewUrl: { 40 | url: 41 | 'https://gateway.test.unionfab.com/file/md5/8db70b5d99c74d66d3d527f06d862ec2/download?&token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJhY2Nlc3NpYmxlTWQ1cyI6WyI4ZGI3MGI1ZDk5Yzc0ZDY2ZDNkNTI3ZjA2ZDg2MmVjMiJdLCJjYW5BY2Nlc3NBbnkiOnRydWUsImlzcyI6InVmYyIsInJlYWRPbmx5Ijp0cnVlLCJleHAiOjE2MDMxNzUwMDAsImlhdCI6MTYwMjU3MDIwMH0.EHRcRpISqa7m4uQl8DBFIiuHYDy3RWHZBBCOBf29B6CK2OfomkI4MVTxN1yZVl7ZhNCEA8tOXcitpSiRrLAMvw&name=decompressed-巴基碗_巴基碗1.stl.thumbnail.PNG', 42 | expiresAt: '2020-10-20T06:23:20.995Z', 43 | }, 44 | thumbnailFileId: 'FILE-30285', 45 | materialName: '测试材料二', 46 | }, 47 | ], 48 | }, 49 | { 50 | id: 'INQUIRY_ORDER-657', 51 | code: 'XJ20200824-454878-4', 52 | payMethod: '月结', 53 | createdAt: '2020-08-24T20:50:53+08:00', 54 | switchWaitProductAt: '2020-09-07T16:58:41+08:00', 55 | price: { 56 | orderId: 'INQUIRY_ORDER-657', 57 | handleFee: 100, 58 | postage: 0, 59 | packingFee: 0, 60 | surcharge: 0, 61 | materialFee: 245, 62 | taxRate: 130, 63 | totalPriceWithTax: 345, 64 | totalPriceWithoutTax: 300.15, 65 | }, 66 | saleManId: 'USER-5', 67 | saleMan: { 68 | userId: 'USER-5', 69 | username: 'tangfeng', 70 | nickname: 'YYYY', 71 | }, 72 | printInfo: [ 73 | { 74 | orderId: 'INQUIRY_ORDER-657', 75 | orderItemId: 'INQUIRY_ORDER_ITEM-1330', 76 | fileId: 'FILE-31529', 77 | handle: { 78 | method: '丝印', 79 | desc: '7894456123123123123', 80 | }, 81 | price: 245, 82 | printCount: 1, 83 | fileName: '猫或狗碗1.stl', 84 | previewUrl: { 85 | url: 86 | 'https://gateway.test.unionfab.com/file/md5/ee61b24b802a9ba46eb8919b47ea4c84/download?&token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJhY2Nlc3NpYmxlTWQ1cyI6WyJlZTYxYjI0YjgwMmE5YmE0NmViODkxOWI0N2VhNGM4NCJdLCJjYW5BY2Nlc3NBbnkiOnRydWUsImlzcyI6InVmYyIsInJlYWRPbmx5Ijp0cnVlLCJleHAiOjE2MDMxNzUwMDAsImlhdCI6MTYwMjU3MDIwMH0.25Vtd9RuXMMR1SqCArpJzvhedUFeM5TCf8Qt79PALRrVNJG0CC-glwNTfccfU0ywNr9czkySOUNHb2W0J2ff3Q&name=猫或狗碗1.stl.thumbnail.PNG', 87 | expiresAt: '2020-10-20T06:23:20.996Z', 88 | }, 89 | thumbnailFileId: 'FILE-31530', 90 | materialName: '测试材料五', 91 | }, 92 | ], 93 | }, 94 | ]; 95 | 96 | export interface TestData { 97 | totalPrice: number; 98 | date: string; 99 | customerName: string; 100 | inquiryOrderStatements: S.InquiryOrderStatement[]; 101 | } 102 | 103 | export const testData: TestData = { 104 | date: '2020-09-01~2020-10-13', 105 | totalPrice: 1355.91, 106 | customerName: '客户AAA1', 107 | inquiryOrderStatements: inquiryOrderStatements.map( 108 | s => new S.InquiryOrderStatement(s), 109 | ), 110 | }; 111 | -------------------------------------------------------------------------------- /packages/excel-canvas-renderer/src/core/helper.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-param-reassign */ 2 | function cloneDeep(obj) { 3 | return JSON.parse(JSON.stringify(obj)); 4 | } 5 | 6 | const mergeDeep = (object = {}, ...sources) => { 7 | sources.forEach((source) => { 8 | Object.keys(source).forEach((key) => { 9 | const v = source[key]; 10 | // console.log('k:', key, ', v:', source[key], typeof v, v instanceof Object); 11 | if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') { 12 | object[key] = v; 13 | } else if (typeof v !== 'function' && !Array.isArray(v) && v instanceof Object) { 14 | object[key] = object[key] || {}; 15 | mergeDeep(object[key], v); 16 | } else { 17 | object[key] = v; 18 | } 19 | }); 20 | }); 21 | // console.log('::', object); 22 | return object; 23 | }; 24 | 25 | function equals(obj1, obj2) { 26 | const keys = Object.keys(obj1); 27 | if (keys.length !== Object.keys(obj2).length) return false; 28 | for (let i = 0; i < keys.length; i += 1) { 29 | const k = keys[i]; 30 | const v1 = obj1[k]; 31 | const v2 = obj2[k]; 32 | if (v2 === undefined) return false; 33 | if (typeof v1 === 'string' || typeof v1 === 'number' || typeof v1 === 'boolean') { 34 | if (v1 !== v2) return false; 35 | } else if (Array.isArray(v1)) { 36 | if (v1.length !== v2.length) return false; 37 | for (let ai = 0; ai < v1.length; ai += 1) { 38 | if (!equals(v1[ai], v2[ai])) return false; 39 | } 40 | } else if (typeof v1 !== 'function' && !Array.isArray(v1) && v1 instanceof Object) { 41 | if (!equals(v1, v2)) return false; 42 | } 43 | } 44 | return true; 45 | } 46 | 47 | /* 48 | objOrAry: obejct or Array 49 | cb: (value, index | key) => { return value } 50 | */ 51 | const sum = (objOrAry, cb = value => value) => { 52 | let total = 0; 53 | let size = 0; 54 | Object.keys(objOrAry).forEach((key) => { 55 | total += cb(objOrAry[key], key); 56 | size += 1; 57 | }); 58 | return [total, size]; 59 | }; 60 | 61 | function deleteProperty(obj, property) { 62 | const oldv = obj[`${property}`]; 63 | delete obj[`${property}`]; 64 | return oldv; 65 | } 66 | 67 | function rangeReduceIf(min, max, inits, initv, ifv, getv) { 68 | let s = inits; 69 | let v = initv; 70 | let i = min; 71 | for (; i < max; i += 1) { 72 | if (s > ifv) break; 73 | v = getv(i); 74 | s += v; 75 | } 76 | return [i, s - v, v]; 77 | } 78 | 79 | function rangeSum(min, max, getv) { 80 | let s = 0; 81 | for (let i = min; i < max; i += 1) { 82 | s += getv(i); 83 | } 84 | return s; 85 | } 86 | 87 | function rangeEach(min, max, cb) { 88 | for (let i = min; i < max; i += 1) { 89 | cb(i); 90 | } 91 | } 92 | 93 | function arrayEquals(a1, a2) { 94 | if (a1.length === a2.length) { 95 | for (let i = 0; i < a1.length; i += 1) { 96 | if (a1[i] !== a2[i]) return false; 97 | } 98 | } else return false; 99 | return true; 100 | } 101 | 102 | function digits(a) { 103 | const v = `${a}`; 104 | let ret = 0; 105 | let flag = false; 106 | for (let i = 0; i < v.length; i += 1) { 107 | if (flag === true) ret += 1; 108 | if (v.charAt(i) === '.') flag = true; 109 | } 110 | return ret; 111 | } 112 | 113 | export function numberCalc(type, a1, a2) { 114 | if (Number.isNaN(a1) || Number.isNaN(a2)) { 115 | return a1 + type + a2; 116 | } 117 | const al1 = digits(a1); 118 | const al2 = digits(a2); 119 | const num1 = Number(a1); 120 | const num2 = Number(a2); 121 | let ret = 0; 122 | if (type === '-') { 123 | ret = num1 - num2; 124 | } else if (type === '+') { 125 | ret = num1 + num2; 126 | } else if (type === '*') { 127 | ret = num1 * num2; 128 | } else if (type === '/') { 129 | ret = num1 / num2; 130 | } 131 | return ret.toFixed(Math.max(al1, al2)); 132 | } 133 | 134 | export default { 135 | cloneDeep, 136 | merge: (...sources) => mergeDeep({}, ...sources), 137 | equals, 138 | arrayEquals, 139 | sum, 140 | rangeEach, 141 | rangeSum, 142 | rangeReduceIf, 143 | deleteProperty, 144 | numberCalc, 145 | }; 146 | -------------------------------------------------------------------------------- /packages/excel-schema/src/WorksheetCellDO.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '@m-fe/utils'; 2 | 3 | import { Style } from './style'; 4 | import { 5 | CellErrorValue, 6 | CellFormulaValue, 7 | CellHyperlinkValue, 8 | CellImageValue, 9 | CellQrcodeValue, 10 | CellRichTextValue, 11 | CellSharedFormulaValue, 12 | DataValidation, 13 | } from './value'; 14 | import { WorksheetCellCommentDO } from './WorksheetCellCommentDO'; 15 | 16 | export enum CellValueType { 17 | Null = 0, 18 | Merge = 1, 19 | Number = 2, 20 | String = 3, 21 | Date = 4, 22 | Hyperlink = 5, 23 | Formula = 6, 24 | SharedString = 7, 25 | RichText = 8, 26 | Boolean = 9, 27 | Error = 10, 28 | Image = 11, 29 | Qrcode = 12, 30 | } 31 | 32 | export type CellValue = 33 | | null 34 | | number 35 | | string 36 | | boolean 37 | | Date 38 | | CellErrorValue 39 | | CellRichTextValue 40 | | CellHyperlinkValue 41 | | CellFormulaValue 42 | | CellSharedFormulaValue 43 | | CellImageValue 44 | | CellQrcodeValue; 45 | 46 | export class WorksheetCellDO extends BaseEntity { 47 | address: string; 48 | 49 | // 该 Cell 合并到的目标 50 | mergedCellAddress?: string; 51 | 52 | type: CellValueType; 53 | value: CellValue; 54 | 55 | style: Partial