├── .prettierignore
├── pnpm-workspace.yaml
├── .stylelintrc
├── vv-react-table
├── src
│ ├── vite-env.d.ts
│ ├── ReactTable
│ │ ├── global.d.ts
│ │ ├── utils
│ │ │ ├── log.ts
│ │ │ ├── full.ts
│ │ │ ├── index.ts
│ │ │ └── onContextMenu.ts
│ │ ├── components
│ │ │ ├── TableHeader.tsx
│ │ │ └── SelectInput.tsx
│ │ ├── style
│ │ │ └── index.scss
│ │ ├── props.ts
│ │ ├── columns.tsx
│ │ ├── types
│ │ │ └── index.ts
│ │ └── index.tsx
│ ├── main.tsx
│ └── App.tsx
├── vite.config.ts
├── README.md
├── tsconfig.node.json
├── index.html
├── package.pro.json
├── tsconfig.json
├── scripts
│ ├── tsconfig.json
│ └── build.ts
├── public
│ └── vite.svg
├── package.json
└── rollup.config.js
├── public
├── logo.png
├── rwm.jpeg
├── logo2.png
├── logo3.png
└── index.css
├── .husky
├── pre-commit
└── commit-msg
├── .eslintrc.js
├── .gitignore
├── .fatherrc.ts
├── .editorconfig
├── tsconfig.json
├── .prettierrc.js
├── docs
├── index.md
└── guide
│ └── index.md
├── .dumirc.ts
├── LICENSE
├── README.md
├── package.json
└── src
└── vv-react-table
└── index.md
/.prettierignore:
--------------------------------------------------------------------------------
1 | /dist
2 | *.yaml
3 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - vv-react-table
3 |
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@umijs/lint/dist/config/stylelint"
3 | }
4 |
--------------------------------------------------------------------------------
/vv-react-table/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyh0371/vv-react-table/HEAD/public/logo.png
--------------------------------------------------------------------------------
/public/rwm.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyh0371/vv-react-table/HEAD/public/rwm.jpeg
--------------------------------------------------------------------------------
/public/logo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyh0371/vv-react-table/HEAD/public/logo2.png
--------------------------------------------------------------------------------
/public/logo3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyh0371/vv-react-table/HEAD/public/logo3.png
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/vv-react-table/src/ReactTable/global.d.ts:
--------------------------------------------------------------------------------
1 | declare type Obj = {
2 | [key: string]: any;
3 | };
4 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: require.resolve('@umijs/lint/dist/config/eslint'),
3 | };
4 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx commitlint --edit "${1}"
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /dist
2 | .dumi/tmp
3 | .dumi/tmp-test
4 | .dumi/tmp-production
5 | docs-dist
6 | node_modules
7 | vv-react-table/node_modules
8 | vv-react-table/dist
9 | .DS_Store
10 |
--------------------------------------------------------------------------------
/.fatherrc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'father';
2 |
3 | export default defineConfig({
4 | // more father config: https://github.com/umijs/father/blob/master/docs/config.md
5 | esm: { output: 'dist' },
6 | });
7 |
--------------------------------------------------------------------------------
/vv-react-table/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/vv-react-table/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import App from './App';
4 |
5 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render();
6 |
--------------------------------------------------------------------------------
/vv-react-table/src/ReactTable/utils/log.ts:
--------------------------------------------------------------------------------
1 | const name = 'vv-react-table';
2 |
3 | /**
4 | * @name 封装 console.error
5 | * @param info
6 | */
7 | export const errorLog = (info: string) => {
8 | console.error(`${name}:${info}`);
9 | };
10 |
--------------------------------------------------------------------------------
/vv-react-table/README.md:
--------------------------------------------------------------------------------
1 | ## 基于 `rsuite-table` 封装的数据驱动虚拟列表
2 |
3 | https://juejin.cn/post/7034451277878657055#heading-7
4 |
5 | ## 固定列支持 ✅
6 |
7 | 支持 checkbox 和 radio ✅
8 |
9 | ## 合并单元格支持
10 |
11 | ## 右键菜单
12 |
13 | ## 编辑行
14 |
15 | ## 列拖拽
16 |
--------------------------------------------------------------------------------
/vv-react-table/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/public/index.css:
--------------------------------------------------------------------------------
1 | h1.dumi-default-hero-title {
2 | font-size: 100px;
3 | }
4 |
5 | #root .dumi-default-logo img {
6 | height: 50px;
7 | }
8 |
9 | #root .dumi-default-hero {
10 | height: 700px;
11 | }
12 |
13 | #root .dumi-default-features .dumi-default-features-item {
14 | text-align: center;
15 | }
16 |
17 | .dumi-default-search-bar-svg {
18 | left: 10px;
19 | }
20 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "declaration": true,
5 | "skipLibCheck": true,
6 | "esModuleInterop": true,
7 | "jsx": "react",
8 | "baseUrl": "./",
9 | "paths": {
10 | "@@/*": [".dumi/tmp/*"],
11 | "src": ["src"],
12 | "src/*": ["src/*", "*"]
13 | }
14 | },
15 | "include": [".dumi/**/*", ".dumirc.ts", "src/**/*"]
16 | }
17 |
--------------------------------------------------------------------------------
/vv-react-table/src/ReactTable/components/TableHeader.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { HeaderCell } from 'rsuite-table';
3 |
4 | interface TableHeaderPropsType {
5 | children: React.ReactNode;
6 | }
7 | const TableHeader: React.FC = (props) => {
8 | // console.log('children', children);
9 |
10 | return {props.children};
11 | };
12 |
13 | export default TableHeader;
14 |
--------------------------------------------------------------------------------
/vv-react-table/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/vv-react-table/src/ReactTable/style/index.scss:
--------------------------------------------------------------------------------
1 | @import './theme.scss';
2 |
3 | .rs-table-cell-header .rs-table-cell-content {
4 | line-height: unset;
5 | }
6 | .vv-table-isFull {
7 | position: fixed;
8 | top: 0;
9 | bottom: 0;
10 | right: 0;
11 | left: 0;
12 | background-color: #fff;
13 | }
14 | .rs-table .vv-bg-active .rs-table-cell {
15 | background-color: pink;
16 | }
17 |
18 | .lyh-react-table-wrapper {
19 | height: 100%;
20 | }
21 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | pluginSearchDirs: false,
3 | plugins: [
4 | require.resolve('prettier-plugin-organize-imports'),
5 | require.resolve('prettier-plugin-packagejson'),
6 | ],
7 | printWidth: 80,
8 | proseWrap: 'never',
9 | singleQuote: true,
10 | trailingComma: 'all',
11 | overrides: [
12 | {
13 | files: '*.md',
14 | options: {
15 | proseWrap: 'preserve',
16 | },
17 | },
18 | ],
19 | };
20 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | hero:
3 | title: vv-react-table
4 | description:
5 | actions:
6 | - text: 开始使用
7 | link: /components/vv-react-table
8 | - text: 查看源码
9 | link: https://github.com/lyh0371/vv-react-table
10 | features:
11 | - title: TypeScript
12 | emoji: ✌
13 | description: 友好的类型提示
14 | - title: 丰富的案例说明
15 | emoji: ✊
16 | description: 支持大多数使用场景
17 | - title: 可扩展
18 | emoji: 🤝
19 | description: 超强的扩展性
20 | ---
21 |
--------------------------------------------------------------------------------
/.dumirc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'dumi';
2 |
3 | export default defineConfig({
4 | outputPath: 'docs-dist',
5 | base: '/',
6 | themeConfig: {
7 | name: '首页',
8 | logo: '/logo.png',
9 | prefersColor: {
10 | default: 'light',
11 | switch: false,
12 | },
13 | footer:
14 | '刘小灰出品',
15 | },
16 |
17 | styles: [`/index.css`],
18 | });
19 |
--------------------------------------------------------------------------------
/vv-react-table/src/ReactTable/utils/full.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @name 使table全屏
3 | * @param target table DOM
4 | */
5 |
6 | let _index = 2000;
7 | export const fullCore = (target: HTMLDivElement) => {
8 | const className = 'vv-table-isFull';
9 | // 判断是不是全屏状态
10 | const tableStatus = target.classList.contains(className);
11 |
12 | if (!tableStatus) {
13 | // 放大
14 | target.style.zIndex = `${_index + 1}`;
15 | target.classList.add(className);
16 | } else {
17 | target.classList.remove(className);
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/vv-react-table/package.pro.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vv-react-table",
3 | "version": "0.0.5",
4 | "type": "module",
5 | "main": "index.esm.js",
6 | "module": "index.esm.js",
7 | "types": "type/index.d.ts",
8 | "style": "index.esm.css",
9 | "keywords": ["vv-react-table", "table"],
10 | "license": "MIT",
11 | "peerDependencies": {
12 | "rsuite-table": "^5.8.1"
13 | },
14 | "dependencies": {
15 | "ahooks": "^3.7.4",
16 | "lodash": "^4.17.21",
17 | "react": "^18.2.0",
18 | "react-dom": "^18.2.0",
19 | "prop-types": "^15.8.1",
20 | "rsuite-table": "^5.8.1"
21 | },
22 | "browserslist": ["> 1%", "not ie 11", "not op_mini all"]
23 | }
24 |
--------------------------------------------------------------------------------
/vv-react-table/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "target": "ESNext",
5 | "baseUrl": ".",
6 | "useDefineForClassFields": true,
7 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
8 | "allowJs": true,
9 | "skipLibCheck": true,
10 | "esModuleInterop": true,
11 | "allowSyntheticDefaultImports": true,
12 | "strict": true,
13 | "forceConsistentCasingInFileNames": true,
14 | "module": "ESNext",
15 | "moduleResolution": "Node",
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "noEmit": true,
19 | "jsx": "react-jsx"
20 | },
21 | "include": ["src"],
22 | "references": [{ "path": "./tsconfig.node.json" }]
23 | }
24 |
--------------------------------------------------------------------------------
/vv-react-table/scripts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "target": "ESNext",
5 | "baseUrl": ".",
6 | "useDefineForClassFields": true,
7 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
8 | "allowJs": true,
9 | "skipLibCheck": true,
10 | "esModuleInterop": true,
11 | "allowSyntheticDefaultImports": true,
12 | "strict": true,
13 | "forceConsistentCasingInFileNames": true,
14 | "module": "commonjs",
15 | "moduleResolution": "Node",
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "noEmit": true,
19 | "jsx": "react-jsx"
20 | },
21 | "include": ["src"],
22 | "references": [{ "path": "../tsconfig.node.json" }]
23 | }
24 |
--------------------------------------------------------------------------------
/vv-react-table/src/ReactTable/props.ts:
--------------------------------------------------------------------------------
1 | import { cloneDeep } from 'lodash';
2 |
3 | import { columnsType, ReactTableType } from './types';
4 |
5 | export const columnProps = (props: columnsType) => {
6 | type itemType = Partial;
7 | const propsItem: itemType = cloneDeep(props);
8 | delete propsItem.dataIndex;
9 | delete propsItem.title;
10 | delete propsItem.render;
11 | delete propsItem.rightClickMenu;
12 | delete propsItem.contextMenu;
13 |
14 | return propsItem;
15 | };
16 |
17 | export const tableProps = (props: ReactTableType) => {
18 | type propsType = Partial;
19 | const propsItem: propsType = cloneDeep(props);
20 | delete propsItem.columns;
21 | delete propsItem.rowSelection;
22 | delete propsItem.dbClickFull;
23 | delete propsItem.rightClickMenu;
24 | return propsItem;
25 | };
26 |
--------------------------------------------------------------------------------
/vv-react-table/src/ReactTable/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './log';
2 | export function flatMap(array: T[], callback: (value: T, index: number, array: T[]) => U[]): U[] {
3 | const result: U[] = [];
4 |
5 | array.forEach((value, index) => {
6 | result.push(...callback(value, index, array));
7 | });
8 |
9 | return result;
10 | }
11 |
12 | export function fromEntries(entries: Iterable) {
13 | const result: { [k in PropertyKey]: T } = {};
14 | for (const [key, value] of entries) {
15 | result[key as any] = value;
16 | }
17 | return result;
18 | }
19 |
20 | export const arrayUtils = {
21 | diff(arr1: string[], arr2: Iterable) {
22 | const set = new Set(arr2);
23 | return arr1.filter((x) => !set.has(x));
24 | },
25 | merge(arr1: string[], arr2: string[]) {
26 | const set = new Set(arr1);
27 | return arr1.concat(arr2.filter((x) => !set.has(x)));
28 | },
29 | } as const;
30 |
31 | export function always(value: T) {
32 | return (...args: any[]) => value;
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c)
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 |
--------------------------------------------------------------------------------
/docs/guide/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav:
3 | title: 指南
4 | order: -1
5 | group:
6 | title: 介绍
7 | order: -1
8 | ---
9 |
10 | # 什么是 vv-react-table
11 |
12 | vv-react-table 是一款基于 [rsuite-table](https://github.com/rsuite/rsuite-table)二次封装的虚拟表格组件。支持通过 JSON 配置生成虚拟表格,并对 `rsuite-table` 进行了扩展
13 |
14 | ## 功能
15 |
16 | vv-react-table 主要支持以下功能:
17 |
18 | - 🚀 **表格宽度拖拽** 需设置固定宽度即可实现宽度拖拽
19 | - 🔍 **拖拽行和列**:表格行和列可自由拖拽
20 | - 🎨 **右键菜单**:支持给每一列单独设置右键菜单,右键菜单样式可自定义
21 | - 🚥 **表头及列的合并**:通过简单配置即可时间表头和列的合并
22 | - 💡 **行内编辑**:对行内元素进行编辑
23 | - 💎 更多高级用法请看[示例](/components/vv-react-table)
24 |
25 | ## 使用
26 |
27 | 1. 下载依赖
28 |
29 | ```shell
30 |
31 | npm install vv-react-table -S
32 | # or
33 | yarn add vv-react-table -S
34 | # or
35 | pnpm install vv-react-table -S
36 |
37 | ```
38 |
39 | 2. 引入组件及样式
40 |
41 | ```js
42 | import ReactTable from 'vv-react-table';
43 | import 'vv-react-table/index.esm.css';
44 | ```
45 |
46 | > 未了支持 ESM 实现大统,`vv-react-table` 只支持 ESM 规范
47 |
48 | ## 问题反馈
49 |
50 | 如果在使用过程中发现任何问题、或者有改善建议,欢迎在 GitHub Issues 进行反馈:https://github.com/lyh0371/vv-react-table/issues
51 |
52 | 或加我微信(lyh1347635797)直接反馈(加微信请备注虚拟表格):
53 |
54 |
55 |

56 |
57 |
--------------------------------------------------------------------------------
/vv-react-table/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
vv-react-table
5 |
6 |
7 |
8 |
9 |
10 |
11 | # 什么是 vv-react-table
12 |
13 | vv-react-table 是一款基于 [rsuite-table](https://github.com/rsuite/rsuite-table)二次封装的虚拟表格组件。支持通过 JSON 配置生成虚拟表格,并对 `rsuite-table` 进行了扩展
14 |
15 | ## 功能
16 |
17 | vv-react-table 主要支持以下功能:
18 |
19 | - 🚀 **表格宽度拖拽** 需设置固定宽度即可实现宽度拖拽
20 | - 🔍 **拖拽行和列**:表格行和列可自由拖拽
21 | - 🎨 **右键菜单**:支持给每一列单独设置右键菜单,右键菜单样式可自定义
22 | - 🚥 **表头及列的合并**:通过简单配置即可时间表头和列的合并
23 | - 💡 **行内编辑**:对行内元素进行编辑
24 | - 💎 更多高级用法请看[示例](http://www.h5love.cn/components/vv-react-table)
25 |
26 | ## 使用
27 |
28 | 1. 下载依赖
29 |
30 | ```shell
31 |
32 | yarn install vv-react-table -S
33 |
34 | ```
35 |
36 | 2. 引入组件及样式
37 |
38 | ```js
39 | import ReactTable from 'vv-react-table';
40 | import 'vv-react-table/index.esm.css';
41 | ```
42 |
43 | ## 问题反馈
44 |
45 | 如果在使用过程中发现任何问题、或者有改善建议,欢迎在 GitHub Issues 进行反馈:https://github.com/lyh0371/vv-react-table/issues
46 |
47 | 或加我微信(lyh1347635797)直接反馈(加微信请备注虚拟表格):
48 |
49 |
50 |

51 |
52 |
--------------------------------------------------------------------------------
/vv-react-table/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vv-react-table/vv-react-table",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "main": "./src/ReactTable",
7 | "scripts": {
8 | "build": "rimraf dist/ && esno ./scripts/build.ts",
9 | "build:web": "tsc && vite build",
10 | "d": "rimraf dist/ && rollup --config rollup.config.js",
11 | "dev": "vite",
12 | "preview": "vite preview"
13 | },
14 | "dependencies": {
15 | "ahooks": "^3.7.4",
16 | "lodash": "^4.17.21",
17 | "react": "^18.2.0",
18 | "react-dom": "^18.2.0",
19 | "rsuite-table": "^5.8.1"
20 | },
21 | "devDependencies": {
22 | "@babel/core": "^7.20.12",
23 | "@babel/preset-env": "^7.20.2",
24 | "@babel/preset-react": "^7.18.6",
25 | "@babel/preset-typescript": "^7.18.6",
26 | "@rollup/plugin-babel": "^6.0.3",
27 | "@rollup/plugin-commonjs": "^24.0.0",
28 | "@rollup/plugin-json": "^6.0.0",
29 | "@rollup/plugin-node-resolve": "^15.0.1",
30 | "@types/lodash": "^4.14.191",
31 | "@types/node-sass": "^4.11.3",
32 | "@types/react": "^18.0.26",
33 | "@types/react-dom": "^18.0.9",
34 | "@vitejs/plugin-react": "^3.0.0",
35 | "esno": "^0.16.3",
36 | "less": "^4.1.3",
37 | "node-sass": "^8.0.0",
38 | "prop-types": "^15.8.1",
39 | "rimraf": "^3.0.2",
40 | "rollup": "^3.9.1",
41 | "rollup-plugin-dts": "^5.1.1",
42 | "rollup-plugin-postcss": "^4.0.2",
43 | "rollup-plugin-typescript2": "^0.34.1",
44 | "sass": "^1.57.1",
45 | "scss": "^0.2.4",
46 | "ts-node": "^10.9.1",
47 | "typescript": "^4.9.3",
48 | "vite": "^4.0.0"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/vv-react-table/src/ReactTable/columns.tsx:
--------------------------------------------------------------------------------
1 | import { Cell, Column, ColumnGroup, HeaderCell } from 'rsuite-table';
2 | import { columnProps } from './props';
3 | import { columnsType } from './types';
4 |
5 | const TableColumns = (columns: columnsType[]) => {
6 | const baseColums = (item: columnsType, index: number) => {
7 | return (
8 |
9 | {item.title}
10 | {item.render && typeof item.render === 'function' ? (
11 |
12 | {(rowData, index) => {
13 | return item.render!(rowData as columnsType, index);
14 | }}
15 | |
16 | ) : (
17 | |
18 | )}
19 |
20 | );
21 | };
22 |
23 | return (
24 | <>
25 | {columns
26 | .filter((item) => !item.hidden)
27 | .map((item, index) => {
28 | console.log(item.columnChildren);
29 |
30 | if (item.columnChildren) {
31 | return (
32 |
39 | {item.columnChildren.map((citem: any) =>
40 | baseColums(citem, index),
41 | )}
42 |
43 | );
44 | }
45 | return baseColums(item, index);
46 | })}
47 | >
48 | );
49 | };
50 | export default TableColumns;
51 |
--------------------------------------------------------------------------------
/vv-react-table/src/ReactTable/types/index.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ColumnProps, RowDataType, TableProps } from 'rsuite-table';
3 |
4 | export type RowSelection = {
5 | type?: 'checkbox' | 'radio'; // 类型
6 | key: string; // 唯一标识
7 | defaultSelectedAllRows?: boolean; // 是否默认选中全部
8 | defaultSelectedKeys?: string[]; // 默认选中
9 | onChange: (
10 | ids: string[],
11 | rows: Obj[],
12 | id: string | undefined,
13 | row: Obj,
14 | ) => void; // 点击选择的执行函数
15 | selectRender?: () => void; // 可以自定义选择逻辑 比如样式 是否可以选中
16 | fixed?: 'left';
17 | width?: number;
18 | };
19 | //每一列的类型
20 | export interface columnsType extends ColumnProps {
21 | /**
22 | * 每一列的类型
23 | */
24 | title: string; // 头部标题
25 | dataIndex?: string; // 字段
26 | hidden?: boolean; // 是否隐藏
27 | contextMenu?: boolean; // 是否开启右键菜单
28 | rightClickMenu?: {
29 | render: (row: any, index: number) => React.ReactNode;
30 | }; // 自定义此列右键菜单
31 | render?: (row: any, index?: number) => JSX.Element;
32 | columnChildren?: columnsType[]; // 合并头部单元格
33 | groupHeaderHeight?: number;
34 | }
35 |
36 | // table 类型
37 | export interface ReactTableType
38 | extends Omit {
39 | /**
40 | * table props 类型
41 | */
42 | columns: columnsType[];
43 | rowSelection?: RowSelection; // 是否支持选择行
44 | data: RowDataType[];
45 | dbClickFull?: boolean;
46 | rightClickMenu?: {
47 | render: (row: any, index: number) => React.ReactNode;
48 | }; // 右键菜单
49 | }
50 |
51 | export interface TColumn extends Omit {
52 | /**
53 | * column 类型
54 | */
55 | dataIndex?: keyof T;
56 | render?: (row: T, index?: number) => JSX.Element;
57 | }
58 |
--------------------------------------------------------------------------------
/vv-react-table/src/ReactTable/utils/onContextMenu.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import { RowDataType } from 'rsuite-table';
4 | import { columnsType } from '../types';
5 | import { errorLog } from './log';
6 |
7 | const onContextMenu = (
8 | e: React.MouseEvent,
9 | rowData: RowDataType,
10 | index: number,
11 | item: columnsType,
12 | rightClickMenu?: {
13 | render: (row: any, index: number) => React.ReactNode;
14 | },
15 | ) => {
16 | if (item.contextMenu) {
17 | // 说明开启了右键菜单
18 |
19 | // 先关闭所有的右键菜单
20 | const vvMenu = document.querySelector('#vv-menu') as HTMLDivElement;
21 | if (vvMenu) {
22 | vvMenu.style.display = 'none';
23 | document.body.removeChild(vvMenu);
24 | }
25 | const divWarp = document.createElement('div');
26 | divWarp.style.position = 'absolute';
27 | divWarp.setAttribute('id', 'vv-menu');
28 | divWarp.style.zIndex = '1000';
29 | divWarp.style.top = `${e.clientY}px`;
30 | divWarp.style.left = `${e.clientX}px`;
31 | divWarp.style.background = '#fff';
32 | divWarp.style.boxShadow = '1px 1px 10px #ccc';
33 | document.body.appendChild(divWarp);
34 |
35 | let renderDome = rightClickMenu?.render(rowData, index);
36 | if (item.rightClickMenu) {
37 | if (!item.rightClickMenu.render) {
38 | errorLog('请提供 render函数');
39 | return false;
40 | }
41 | renderDome = item.rightClickMenu?.render(rowData, index);
42 | }
43 | ReactDOM.createRoot(divWarp as HTMLElement).render(renderDome);
44 | document.body.addEventListener('click', (e) => {
45 | const vvMenu = document.querySelector('#vv-menu') as HTMLDivElement;
46 | if (vvMenu) {
47 | vvMenu.style.display = 'none';
48 | document.body.removeChild(vvMenu);
49 | }
50 | });
51 |
52 | e.preventDefault();
53 | }
54 | };
55 |
56 | export default onContextMenu;
57 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vv-react-table",
3 | "version": "0.0.1",
4 | "description": "",
5 | "homepage": "http://www.h5love.cn/",
6 | "license": "MIT",
7 | "module": "dist/index.js",
8 | "types": "dist/index.d.ts",
9 | "files": [
10 | "dist"
11 | ],
12 | "scripts": {
13 | "build": "father build",
14 | "build:watch": "father dev",
15 | "dev": "dumi dev",
16 | "docs:build": "dumi build",
17 | "doctor": "father doctor",
18 | "lint": "npm run lint:es && npm run lint:css",
19 | "lint:css": "stylelint \"{src,test}/**/*.{css,less}\"",
20 | "lint:es": "eslint \"{src,test}/**/*.{js,jsx,ts,tsx}\"",
21 | "prepare": "husky install && dumi setup",
22 | "prepublishOnly": "father doctor && npm run build",
23 | "start": "npm run dev"
24 | },
25 | "commitlint": {
26 | "extends": [
27 | "@commitlint/config-conventional"
28 | ]
29 | },
30 | "lint-staged": {
31 | "*.{md,json}": [
32 | "prettier --write --no-error-on-unmatched-pattern"
33 | ],
34 | "*.{css,less}": [
35 | "stylelint --fix",
36 | "prettier --write"
37 | ],
38 | "*.{js,jsx}": [
39 | "eslint --fix",
40 | "prettier --write"
41 | ],
42 | "*.{ts,tsx}": [
43 | "eslint --fix",
44 | "prettier --parser=typescript --write"
45 | ]
46 | },
47 | "dependencies": {
48 | "@vv-react-table/vv-react-table": "workspace:^0.0.0"
49 | },
50 | "devDependencies": {
51 | "@commitlint/cli": "^17.1.2",
52 | "@commitlint/config-conventional": "^17.1.0",
53 | "@types/react": "^18.0.26",
54 | "@umijs/lint": "^4.0.0",
55 | "dumi": "^2.0.2",
56 | "eslint": "^8.23.0",
57 | "father": "^4.1.0",
58 | "husky": "^8.0.1",
59 | "lint-staged": "^13.0.3",
60 | "prettier": "^2.7.1",
61 | "prettier-plugin-organize-imports": "^3.0.0",
62 | "prettier-plugin-packagejson": "^2.2.18",
63 | "react": "^18.2.0",
64 | "react-dom": "^18.0.0",
65 | "stylelint": "^14.9.1"
66 | },
67 | "peerDependencies": {
68 | "react": ">=16.9.0",
69 | "react-dom": ">=16.9.0"
70 | },
71 | "publishConfig": {
72 | "access": "public"
73 | },
74 | "authors": []
75 | }
76 |
--------------------------------------------------------------------------------
/vv-react-table/scripts/build.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @name 使用 rollup 对 vv-react-table 打包
3 | */
4 | import babel from '@rollup/plugin-babel';
5 | import commonjs from '@rollup/plugin-commonjs';
6 | import json from '@rollup/plugin-json';
7 | import resolve from '@rollup/plugin-node-resolve';
8 | import fs from 'fs';
9 | import { OutputOptions, rollup, RollupOptions } from 'rollup';
10 | import dts from 'rollup-plugin-dts';
11 | import postcss from 'rollup-plugin-postcss';
12 | import typescript from 'rollup-plugin-typescript2';
13 | const external = ['lodash', 'rsuite-table', 'react', 'react-dom'];
14 | const inputConfig: RollupOptions = {
15 | input: 'src/ReactTable/index.tsx',
16 | plugins: [
17 | typescript({
18 | tsconfig: 'tsconfig.json',
19 | }),
20 | commonjs(),
21 | resolve({
22 | extensions: ['.mjs', '.js', '.jsx', '.json', '.node'],
23 | }),
24 |
25 | json(),
26 | babel({
27 | presets: ['@babel/preset-env'],
28 | extensions: ['.js', '.jsx', '.ts', '.tsx'],
29 | exclude: '**/node_modules/**',
30 | babelHelpers: 'bundled',
31 | }),
32 | postcss({
33 | extract: true,
34 | }),
35 | ],
36 | external,
37 | treeshake: {
38 | moduleSideEffects: false,
39 | },
40 | };
41 |
42 | // 打包组件
43 | const buildCore = async () => {
44 | const build = await rollup(inputConfig);
45 | return build.write({
46 | dir: 'dist',
47 | format: 'esm',
48 | entryFileNames: '[name].esm.js',
49 | } as OutputOptions);
50 | };
51 |
52 | // 生成.d.ts文件
53 | const dtsCore = async () => {
54 | const build = await rollup({
55 | input: 'src/ReactTable/index.tsx',
56 | plugins: [
57 | postcss({
58 | extract: true,
59 | }),
60 | dts({
61 | compilerOptions: {
62 | allowJs: true,
63 | },
64 | }),
65 | ],
66 | });
67 | return build.write({
68 | filename: 'index.d.ts',
69 | dir: 'dist/type',
70 | } as OutputOptions);
71 | };
72 |
73 | // 处理package.json
74 |
75 | const pkgCore = async () => {
76 | let pkg = fs.readFileSync('package.pro.json', 'utf-8');
77 | fs.writeFileSync('dist/package.json', pkg);
78 | };
79 |
80 | // 复制 README
81 |
82 | const copyReadme = () => {
83 | fs.copyFile('../README.md', 'dist/README.md', () => {});
84 | };
85 |
86 | // 打包所有
87 | const buildAll = async () => {
88 | await buildCore();
89 | await dtsCore();
90 | await pkgCore();
91 | copyReadme();
92 | };
93 | buildAll();
94 |
--------------------------------------------------------------------------------
/vv-react-table/rollup.config.js:
--------------------------------------------------------------------------------
1 | import resolve from '@rollup/plugin-node-resolve';
2 | import typescript from 'rollup-plugin-typescript2';
3 | import json from '@rollup/plugin-json';
4 | import commonjs from '@rollup/plugin-commonjs';
5 | import babel from '@rollup/plugin-babel';
6 | import dts from 'rollup-plugin-dts';
7 | import postcss from 'rollup-plugin-postcss';
8 | import sass from 'node-sass';
9 | const external = ['lodash', 'rsuite-table', 'react', 'react-dom'];
10 | const babelOptions = {
11 | presets: ['@babel/preset-env'],
12 | extensions: ['.js', '.jsx', '.ts', '.tsx', '.scss'],
13 | exclude: '**/node_modules/**',
14 | };
15 |
16 | const processScss = function (context) {
17 | return new Promise((resolve, reject) => {
18 | sass.compile(
19 | {
20 | file: context,
21 | },
22 | function (err, result) {
23 | if (!err) {
24 | resolve(result);
25 | } else {
26 | reject(result);
27 | }
28 | }
29 | );
30 | sass.compile(context, {}).then(
31 | function (output) {
32 | if (output && output.css) {
33 | resolve(output.css);
34 | } else {
35 | reject({});
36 | }
37 | },
38 | function (err) {
39 | reject(err);
40 | }
41 | );
42 | });
43 | };
44 | const config = (arg) => ({
45 | plugins: [
46 | typescript({
47 | tsconfig: 'tsconfig.json',
48 | }),
49 | commonjs(),
50 | resolve({
51 | extensions: ['.mjs', '.js', '.jsx', '.json', '.node'],
52 | }),
53 | postcss({
54 | extract: true,
55 | process: processScss,
56 | }),
57 | json(),
58 | babel(babelOptions),
59 | ],
60 | external,
61 | treeshake: {
62 | moduleSideEffects: false,
63 | },
64 |
65 | ...arg,
66 | });
67 |
68 | const input = 'src/ReactTable/index.tsx';
69 |
70 | export default [
71 | config({
72 | input: input,
73 | output: {
74 | dir: 'dist',
75 | format: 'esm',
76 | entryFileNames: '[name].esm.js',
77 | chunkFileNames: 'chunks/ali-react-table-[name]-[hash].esm.js',
78 | },
79 | }),
80 | config({
81 | input: input,
82 | output: {
83 | dir: 'dist',
84 | format: 'cjs',
85 | entryFileNames: '[name].js',
86 | chunkFileNames: 'chunks/ali-react-table-[name]-[hash].js',
87 | },
88 | }),
89 | {
90 | input: input,
91 | output: [{ filename: 'index.d.ts', dir: 'dist/es/type', format: 'esm' }],
92 | plugins: [
93 | postcss({
94 | extract: true,
95 | process: processScss,
96 | }),
97 | dts({
98 | exclude: ['*.scss', '*.css'],
99 | }),
100 | ],
101 | },
102 | ];
103 |
--------------------------------------------------------------------------------
/vv-react-table/src/ReactTable/components/SelectInput.tsx:
--------------------------------------------------------------------------------
1 | import { useUpdateEffect } from 'ahooks';
2 | import React, { useRef, useState } from 'react';
3 | import { Cell, Column, HeaderCell, RowDataType } from 'rsuite-table';
4 | import { RowSelection } from '../types';
5 | import { errorLog } from '../utils';
6 | const SelectInput: React.FC<{
7 | rowSelection: RowSelection;
8 | data: RowDataType[];
9 | headerHeight?: number;
10 | rowHeight?: number | ((rowData?: RowDataType) => number);
11 | }> = ({
12 | rowSelection: {
13 | width = 30,
14 | type = 'checkbox',
15 | fixed = 'left',
16 | key = 'id',
17 | onChange,
18 | },
19 | data,
20 | headerHeight = 40,
21 | rowHeight = 46,
22 | }) => {
23 | const [ids, setIds] = useState([]);
24 | const keyId = useRef('');
25 | const rowItem = useRef({});
26 | const inputChange = (e: React.ChangeEvent, rowData: Obj) => {
27 | console.log('rowData', rowData);
28 |
29 | if (rowData[key] === undefined) {
30 | errorLog(`data里需要包含${key}`);
31 | return false;
32 | }
33 | const checked = (e.target as HTMLInputElement).checked;
34 | const id = rowData[key];
35 | if (checked) {
36 | setIds([...ids, id]);
37 | rowItem.current = rowData;
38 | } else {
39 | setIds((ids) => ids.filter((idItem) => idItem != id));
40 | rowItem.current = {};
41 | }
42 | keyId.current = id;
43 | };
44 |
45 | // 点击全选按钮执行
46 | const allInputChange = (e: React.ChangeEvent) => {
47 | const checked = (e.target as HTMLInputElement).checked;
48 | // 全选了
49 | if (checked) {
50 | setIds(() =>
51 | data.map((item) => {
52 | return item[key];
53 | }),
54 | );
55 | } else {
56 | // 不全选
57 | setIds([]);
58 | }
59 | keyId.current = undefined;
60 | rowItem.current = {};
61 | };
62 |
63 | useUpdateEffect(() => {
64 | const rows = data.filter((item: Obj) => ids.includes(item[key]));
65 | onChange(ids, rows, keyId.current, rowItem.current);
66 | }, [ids]);
67 |
68 | return (
69 |
70 |
71 | {type === 'checkbox' && (
72 | allInputChange(e)}
76 | />
77 | )}
78 |
79 |
85 | {(rowData) => {
86 | return (
87 | id === rowData[key])}
91 | onChange={(e) => inputChange(e, rowData)}
92 | />
93 | );
94 | }}
95 | |
96 |
97 | );
98 | };
99 |
100 | export default SelectInput;
101 |
--------------------------------------------------------------------------------
/vv-react-table/src/ReactTable/index.tsx:
--------------------------------------------------------------------------------
1 | import { memo, useRef } from 'react';
2 | import { Cell, Column, ColumnGroup, HeaderCell, Table } from 'rsuite-table';
3 | import SelectInput from './components/SelectInput';
4 | import { columnProps, tableProps } from './props';
5 | import './style/index.scss';
6 | import { columnsType, ReactTableType } from './types';
7 | import { fullCore } from './utils/full';
8 | import onContextMenu from './utils/onContextMenu';
9 | const ReactTable = (props: ReactTableType) => {
10 | const {
11 | columns,
12 | rowSelection,
13 | data,
14 | headerHeight = 40,
15 | rowHeight,
16 | dbClickFull,
17 | rightClickMenu,
18 | } = props;
19 | const vvTable = useRef(null);
20 | const vvTableWapper = useRef(null);
21 | // 把设置为 hidden 的过滤掉
22 | const noHiddenColumns = columns.filter((item) => !item.hidden);
23 | // 生成 Column props
24 | const showSelectInput = rowSelection && typeof rowSelection === 'object';
25 |
26 | const baseColums = (item: columnsType, index: number, deep: 1 | 2) => {
27 | return (
28 |
33 | {/* 头部 */}
34 | {
37 | if (!dbClickFull) return false;
38 | fullCore(vvTableWapper.current as unknown as HTMLDivElement);
39 | }}
40 | >
41 | {item.title}
42 |
43 | {/* 对应的数据 */}
44 |
45 |
52 | {(rowData, index) => {
53 | const renderItem =
54 | item.render && typeof item.render === 'function'
55 | ? item.render!(rowData as columnsType, index!)
56 | : rowData[item.dataIndex!];
57 |
58 | return (
59 | {
66 | onContextMenu(e, rowData, index!, item, rightClickMenu);
67 | }}
68 | >
69 | {renderItem}
70 |
71 | );
72 | }}
73 | |
74 |
75 | );
76 | };
77 | return (
78 |
79 |
86 | {showSelectInput &&
87 | data.length > 0 &&
88 | SelectInput({ rowSelection, data, headerHeight, rowHeight })}
89 | {noHiddenColumns.map((item, index) => {
90 | if (item.columnChildren && item.columnChildren.length > 0) {
91 | return (
92 |
100 | {/* TODO: 目前只支持两成嵌套 */}
101 | {item.columnChildren.map((citem: any) =>
102 | baseColums(citem, index, 2),
103 | )}
104 |
105 | );
106 | }
107 | return baseColums(item, index, 1);
108 | })}
109 |
110 |
111 | );
112 | };
113 |
114 | export * from './types';
115 |
116 | export default memo(ReactTable);
117 |
--------------------------------------------------------------------------------
/vv-react-table/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { SortType } from 'rsuite-table';
3 | import ReactTable, { TColumn } from './ReactTable';
4 | function App() {
5 | const [sortColumn, setSortColumn] = React.useState('id');
6 | const [sortType, setSortType] = React.useState('asc');
7 | const [editingRow, setEditingRow] = useState(new Map());
8 | type Item = {
9 | aaa: string;
10 | bbb: string;
11 | ccc: string;
12 | name: string;
13 | id: string;
14 | };
15 |
16 | let columns: TColumn- [] = [
17 | {
18 | title: '价格工厂1',
19 | dataIndex: 'name',
20 | contextMenu: true,
21 | minWidth: 100,
22 | sortable: true,
23 | rowSpan: (rowData) => {
24 | return rowData.rowSpan;
25 | },
26 | },
27 | {
28 | title: '价格工厂2',
29 | contextMenu: true,
30 | dataIndex: 'bbb',
31 | minWidth: 100,
32 | render: (row: Item, index) => {
33 | return editingRow.has(row.id) ? (
34 | {
38 | const t = [...tableData];
39 | t[index!].bbb = Number(e.target.value);
40 | setTableData(t);
41 | }}
42 | />
43 | ) : (
44 | {row.bbb}
45 | );
46 | },
47 | },
48 | {
49 | title: '价格工厂3',
50 | dataIndex: 'bbb',
51 | minWidth: 100,
52 | contextMenu: true,
53 | rightClickMenu: {
54 | render(row, index) {
55 | return (
56 |
{
58 | alert(JSON.stringify(row));
59 | }}
60 | style={{
61 | padding: '5px 10px',
62 | cursor: 'pointer',
63 | }}
64 | >
65 |
我是自定义
66 |
67 | );
68 | },
69 | },
70 | },
71 | ];
72 |
73 | const columns2: TColumn- [] = [
74 | {
75 | title: '工厂 title',
76 | dataIndex: 'aaa',
77 | minWidth: 100,
78 | columnChildren: [
79 | {
80 | title: '配方代码 child',
81 | dataIndex: 'ccc',
82 | minWidth: 100,
83 | contextMenu: true,
84 | },
85 | {
86 | title: '工厂 child',
87 | dataIndex: 'aaa',
88 | minWidth: 100,
89 | contextMenu: true,
90 | },
91 | ],
92 | },
93 | {
94 | title: '操作',
95 | align: 'center',
96 |
97 | width: 200,
98 | render: (row: Item, index) => {
99 | return editingRow.has(row.id) ? (
100 |
101 | {
104 | setEditingRow((map) => {
105 | const newMap = new Map(map);
106 | newMap.delete(row.id);
107 | return newMap;
108 | });
109 | }}
110 | >
111 | 保存
112 |
113 | {
115 | const t = [...tableData];
116 | const oldRow = editingRow.get(row.id);
117 | t[index!] = oldRow;
118 | setTableData(t);
119 | setEditingRow((map) => {
120 | const newMap = new Map(map);
121 | newMap.delete(row.id);
122 | return newMap;
123 | });
124 | }}
125 | >
126 | 取消编辑
127 |
128 |
129 | ) : (
130 |
133 | setEditingRow((map) => new Map(map.set(row.id, row)))
134 | }
135 | >
136 | 编辑
137 |
138 | );
139 | },
140 | },
141 | ];
142 |
143 | // const tableData = useMemo(
144 | // () =>
145 | // new Array(4).fill(undefined).map((item, index) => ({
146 | // aaa: 1,
147 | // bbb: 2,
148 | // ccc: 3,
149 | // id: index.toString(),
150 | // })),
151 | // [columns],
152 | // );
153 | // setAaa(() => aaa.set(1, 1));
154 |
155 | const [tableData, setTableData] = useState([
156 | {
157 | aaa: 1,
158 | bbb: 2,
159 | ccc: 3,
160 | id: '1',
161 | },
162 | {
163 | aaa: 1,
164 | bbb: 2,
165 | ccc: 3,
166 | id: '2',
167 | },
168 | {
169 | rowSpan: 3,
170 | aaa: 111,
171 | bbb: 2222,
172 | name: 'a',
173 | ccc: 333,
174 | id: '22',
175 | },
176 | {
177 | aaa: 1,
178 | name: 'a',
179 | bbb: 2,
180 | ccc: 3,
181 | id: '3',
182 | },
183 | {
184 | aaa: 1,
185 | name: 'a',
186 | bbb: 2,
187 | ccc: 3,
188 | id: '4',
189 | },
190 | {
191 | aaa: 11,
192 | bbb: 22,
193 | ccc: 33,
194 | id: '5',
195 | },
196 | ]);
197 |
198 | const tableDom = () => {
199 | setTimeout(() => {
200 | columns = [...columns, ...columns2];
201 | }, 1000);
202 | return (
203 |
204 |
{
209 | console.log(sortColumn, sortType);
210 | sortType && setSortType(sortType);
211 | setSortColumn(sortColumn);
212 | }}
213 | rowSelection={{
214 | key: 'id',
215 | type: 'checkbox',
216 | onChange: (ids, rows, id, row) => {
217 | console.log('ids', ids);
218 | console.log('rows', rows);
219 | console.log('id', id);
220 | console.log('row', row);
221 | },
222 | }}
223 | renderTreeToggle={(icon, rowData: any) => {
224 | if (rowData.children && rowData.children.length === 0) {
225 | return 'loading';
226 | }
227 | return icon;
228 | }}
229 | headerHeight={60}
230 | rowHeight={30}
231 | bordered
232 | dbClickFull
233 | rightClickMenu={{
234 | render(row, index) {
235 | return (
236 | {
238 | alert(JSON.stringify(row));
239 | }}
240 | style={{
241 | padding: '5px 10px',
242 | cursor: 'pointer',
243 | }}
244 | >
245 |
第一列
246 |
第二列
247 |
第三列
248 |
249 | );
250 | },
251 | }}
252 | columns={columns}
253 | data={tableData}
254 | >
255 |
256 | );
257 | };
258 | return (
259 |
260 | {/* */}
261 | {tableDom()}
262 |
263 | );
264 | }
265 |
266 | export default App;
267 |
--------------------------------------------------------------------------------
/src/vv-react-table/index.md:
--------------------------------------------------------------------------------
1 | # 示例
2 |
3 | ## 基础用法
4 |
5 | 通过 `data` 设置表格的数据源,通过 `columns` 设置表格的列即可得到一个表格
6 |
7 | ```jsx
8 | import ReactTable, { TColumn } from '@vv-react-table/vv-react-table';
9 | import React, { useMemo } from 'react';
10 | function Dome() {
11 | type Item = {
12 | column1: string,
13 | column2: string,
14 | column3: string,
15 | column4: string,
16 | };
17 |
18 | const columns: TColumn- [] = [
19 | {
20 | title: '第一列',
21 | dataIndex: 'column1',
22 | minWidth: 100,
23 | align: 'center',
24 | },
25 |
26 | {
27 | title: '第二列',
28 | dataIndex: 'column2',
29 | minWidth: 100,
30 | align: 'center',
31 | },
32 | {
33 | title: '第三列',
34 | dataIndex: 'column3',
35 | minWidth: 100,
36 | align: 'center',
37 | },
38 | {
39 | title: '第四列',
40 | dataIndex: 'column4',
41 | align: 'center',
42 | minWidth: 100,
43 | },
44 | ];
45 |
46 | const tableData = useMemo(
47 | () =>
48 | new Array(100).fill(undefined).map((item, index) => ({
49 | column1: 1,
50 | column2: 2,
51 | column3: 3,
52 | column4: 4,
53 | })),
54 | [columns],
55 | );
56 |
57 | return (
58 |
59 |
65 |
66 | );
67 | }
68 |
69 | export default Dome;
70 | ```
71 |
72 | ## 可选择
73 |
74 | 设置 `rowSelection` 使表格可被配置,配置 `type = checkbox | radio` 支持多选/单选
75 |
76 | **设置 `rowSelection` 需要给列表配置唯一标识 通过 key 进行配置**
77 |
78 | ```jsx
79 | import ReactTable, { TColumn } from '@vv-react-table/vv-react-table';
80 | import React, { useMemo } from 'react';
81 | function Dome() {
82 | type Item = {
83 | column1: string,
84 | column2: string,
85 | column3: string,
86 | column4: string,
87 | };
88 |
89 | const columns: TColumn- [] = [
90 | {
91 | title: '第一列',
92 | dataIndex: 'column1',
93 | minWidth: 100,
94 | align: 'center',
95 | },
96 |
97 | {
98 | title: '第二列',
99 | dataIndex: 'column2',
100 | minWidth: 100,
101 | align: 'center',
102 | },
103 | {
104 | title: '第三列',
105 | dataIndex: 'column3',
106 | minWidth: 100,
107 | align: 'center',
108 | },
109 | {
110 | title: '第四列',
111 | dataIndex: 'column4',
112 | align: 'center',
113 | minWidth: 100,
114 | },
115 | ];
116 |
117 | const tableData = useMemo(
118 | () =>
119 | new Array(100).fill(undefined).map((item, index) => ({
120 | column1: 1,
121 | column2: 2,
122 | column3: 3,
123 | column4: 4,
124 | id: index.toString(), // 唯一标识
125 | })),
126 | [columns],
127 | );
128 |
129 | return (
130 |
131 | {
136 | console.log('ids', ids);
137 | console.log('rows', rows);
138 | console.log('id', id);
139 | console.log('row', row);
140 | },
141 | }}
142 | height={300}
143 | bordered
144 | columns={columns}
145 | data={tableData}
146 | >
147 |
148 | );
149 | }
150 |
151 | export default Dome;
152 | ```
153 |
154 | ## 固定列
155 |
156 | 给某一列配置 `fixed` 属性可实现固定列,支持 `right | left` 固定到 ` 右边 | 左边`
157 |
158 | ```jsx
159 | import ReactTable, { TColumn } from '@vv-react-table/vv-react-table';
160 | import React, { useMemo } from 'react';
161 | function Dome() {
162 | type Item = {
163 | column1: string,
164 | column2: string,
165 | column3: string,
166 | column4: string,
167 | };
168 |
169 | const columns: TColumn- [] = [
170 | {
171 | title: '第一列',
172 | dataIndex: 'column1',
173 | width: 100,
174 | align: 'center',
175 | fixed: 'left',
176 | },
177 |
178 | {
179 | title: '第二列',
180 | dataIndex: 'column2',
181 | width: 300,
182 | align: 'center',
183 | },
184 | {
185 | title: '第三列',
186 | dataIndex: 'column3',
187 | width: 300,
188 | align: 'center',
189 | },
190 | {
191 | title: '第四列',
192 | dataIndex: 'column4',
193 | align: 'center',
194 | width: 300,
195 | },
196 | {
197 | title: '第五列',
198 | dataIndex: 'column5',
199 | align: 'center',
200 | width: 300,
201 | },
202 | {
203 | title: '第六列',
204 | dataIndex: 'column6',
205 | align: 'center',
206 | width: 100,
207 | fixed: 'right',
208 | },
209 | ];
210 |
211 | const tableData = useMemo(
212 | () =>
213 | new Array(100).fill(undefined).map((item, index) => ({
214 | column1: 1,
215 | column2: 2,
216 | column3: 3,
217 | column4: 4,
218 | column5: 5,
219 | column6: 6,
220 | })),
221 | [columns],
222 | );
223 |
224 | return (
225 |
226 |
232 |
233 | );
234 | }
235 |
236 | export default Dome;
237 | ```
238 |
239 | ## 合并头部
240 |
241 | 通过配置 `columnChildren` 即可实现
242 |
243 | ```jsx
244 | import ReactTable, { TColumn } from '@vv-react-table/vv-react-table';
245 | import React, { useMemo } from 'react';
246 | function Dome() {
247 | type Item = {
248 | column1: string,
249 | column2: string,
250 | column3: string,
251 | column4: string,
252 | column2_1: string,
253 | column2_2: string,
254 | };
255 |
256 | const columns: TColumn- [] = [
257 | {
258 | title: '第一列',
259 | dataIndex: 'column1',
260 | minWidth: 100,
261 | align: 'center',
262 | },
263 |
264 | {
265 | title: '第二列',
266 | dataIndex: 'column2',
267 | minWidth: 100,
268 | align: 'center',
269 | columnChildren: [
270 | {
271 | title: '第二列_1',
272 | dataIndex: 'column2_1',
273 | minWidth: 100,
274 | },
275 | {
276 | title: '第二列_2',
277 | dataIndex: 'column2_2',
278 | minWidth: 100,
279 | },
280 | ],
281 | },
282 | {
283 | title: '第三列',
284 | dataIndex: 'column3',
285 | minWidth: 100,
286 | align: 'center',
287 | },
288 | {
289 | title: '第四列',
290 | dataIndex: 'column4',
291 | align: 'center',
292 | minWidth: 100,
293 | },
294 | {
295 | title: '第五列',
296 | dataIndex: 'column5',
297 | align: 'center',
298 | minWidth: 100,
299 | },
300 | {
301 | title: '第六列',
302 | dataIndex: 'column6',
303 | align: 'center',
304 | minWidth: 100,
305 | },
306 | ];
307 |
308 | const tableData = useMemo(
309 | () =>
310 | new Array(100).fill(undefined).map((item, index) => ({
311 | column1: 1,
312 | column2: 2,
313 | column3: 3,
314 | column4: 4,
315 | column5: 5,
316 | column6: 6,
317 | column2_1: '2_1',
318 | column2_2: '2_2',
319 | })),
320 | [columns],
321 | );
322 |
323 | return (
324 |
325 |
331 |
332 | );
333 | }
334 |
335 | export default Dome;
336 | ```
337 |
338 | ## 合并列
339 |
340 | 1. 在 `columns`中某一列配置 `rowSpan: (rowData) => {
341 | return rowData.rowSpan;
342 | }`说明合并行在哪一列发生
343 |
344 | 2. 通过给 `data` 配置 `rowSpan` 来控制要合并几行
345 |
346 | ```jsx
347 | import ReactTable, { TColumn } from '@vv-react-table/vv-react-table';
348 | import React, { useMemo } from 'react';
349 | function Dome() {
350 | type Item = {
351 | column1: string,
352 | column2: string,
353 | column3: string,
354 | column4: string,
355 | };
356 |
357 | const columns: TColumn- [] = [
358 | {
359 | title: '第一列',
360 | dataIndex: 'column1',
361 | minWidth: 100,
362 | align: 'center',
363 | rowSpan: (rowData) => {
364 | return rowData.rowSpan;
365 | },
366 | },
367 |
368 | {
369 | title: '第二列',
370 | dataIndex: 'column2',
371 | minWidth: 100,
372 | align: 'center',
373 | },
374 | {
375 | title: '第三列',
376 | dataIndex: 'column3',
377 | minWidth: 100,
378 | align: 'center',
379 | },
380 | {
381 | title: '第四列',
382 | dataIndex: 'column4',
383 | align: 'center',
384 | minWidth: 100,
385 | },
386 | ];
387 |
388 | const tableData = [
389 | {
390 | column1: 1,
391 | column2: 2,
392 | column3: 3,
393 | column4: 4,
394 | },
395 | {
396 | column1: 1,
397 | column2: 2,
398 | column3: 3,
399 | column4: 4,
400 | rowSpan: 2,
401 | },
402 | {
403 | column1: 1,
404 | column2: 2,
405 | column3: 3,
406 | column4: 4,
407 | },
408 | {
409 | column1: 1,
410 | column2: 2,
411 | column3: 3,
412 | column4: 4,
413 | },
414 | {
415 | column1: 1,
416 | column2: 2,
417 | column3: 3,
418 | column4: 4,
419 | },
420 | ];
421 |
422 | return (
423 |
424 |
430 |
431 | );
432 | }
433 |
434 | export default Dome;
435 | ```
436 |
437 | ## 列宽拖拽
438 |
439 | 给 `columns` 配置 `resizable=true` 即可支持列宽拖拽。**列宽拖拽需要配置固定宽度**`width`
440 |
441 | ```jsx
442 | import ReactTable, { TColumn } from '@vv-react-table/vv-react-table';
443 | import React, { useMemo } from 'react';
444 | function Dome() {
445 | type Item = {
446 | column1: string,
447 | column2: string,
448 | column3: string,
449 | column4: string,
450 | };
451 |
452 | const columns: TColumn- [] = [
453 | {
454 | title: '第一列',
455 | dataIndex: 'column1',
456 | width: 200,
457 | resizable: true,
458 | align: 'center',
459 | },
460 |
461 | {
462 | title: '第二列',
463 | dataIndex: 'column2',
464 | width: 200,
465 | resizable: true,
466 | align: 'center',
467 | },
468 | {
469 | title: '第三列',
470 | dataIndex: 'column3',
471 | width: 200,
472 | resizable: true,
473 | align: 'center',
474 | },
475 | {
476 | title: '第四列',
477 | dataIndex: 'column4',
478 | align: 'center',
479 | width: 200,
480 | resizable: true,
481 | },
482 | ];
483 |
484 | const tableData = useMemo(
485 | () =>
486 | new Array(100).fill(undefined).map((item, index) => ({
487 | column1: 1,
488 | column2: 2,
489 | column3: 3,
490 | column4: 4,
491 | })),
492 | [columns],
493 | );
494 |
495 | return (
496 |
497 |
503 |
504 | );
505 | }
506 |
507 | export default Dome;
508 | ```
509 |
510 | ## 双击头部放大
511 |
512 | 设置 `dbClickFull`
513 |
514 | ```jsx
515 | import ReactTable, { TColumn } from '@vv-react-table/vv-react-table';
516 | import React, { useMemo } from 'react';
517 | function Dome() {
518 | type Item = {
519 | column1: string,
520 | column2: string,
521 | column3: string,
522 | column4: string,
523 | };
524 |
525 | const columns: TColumn- [] = [
526 | {
527 | title: '第一列',
528 | dataIndex: 'column1',
529 | minWidth: 100,
530 | align: 'center',
531 | },
532 |
533 | {
534 | title: '第二列',
535 | dataIndex: 'column2',
536 | minWidth: 100,
537 | align: 'center',
538 | },
539 | {
540 | title: '第三列',
541 | dataIndex: 'column3',
542 | minWidth: 100,
543 | align: 'center',
544 | },
545 | {
546 | title: '第四列',
547 | dataIndex: 'column4',
548 | align: 'center',
549 | minWidth: 100,
550 | },
551 | ];
552 |
553 | const tableData = useMemo(
554 | () =>
555 | new Array(100).fill(undefined).map((item, index) => ({
556 | column1: 1,
557 | column2: 2,
558 | column3: 3,
559 | column4: 4,
560 | })),
561 | [columns],
562 | );
563 |
564 | return (
565 |
566 |
573 |
574 | );
575 | }
576 |
577 | export default Dome;
578 | ```
579 |
580 | ## 行内编辑
581 |
582 | 行内编辑功能可通过 `render` 函数自行扩展,如下所示:
583 |
584 | ### 场景一
585 |
586 | ```jsx
587 | import ReactTable, { TColumn } from '@vv-react-table/vv-react-table';
588 | import React, { useState } from 'react';
589 | function Dome() {
590 | type Item = {
591 | column1: string,
592 | column2: string,
593 | column3: string,
594 | column4: string,
595 | };
596 |
597 | const columns: TColumn- [] = [
598 | {
599 | title: '第一列',
600 | dataIndex: 'column1',
601 | minWidth: 100,
602 | align: 'center',
603 | },
604 |
605 | {
606 | title: '第二列',
607 | dataIndex: 'column2',
608 | minWidth: 100,
609 | align: 'center',
610 | render: (row: Item, index) => {
611 | return (
612 | {
616 | const t = [...tableData];
617 | t[index!].column2 = Number(e.target.value);
618 | setTableData(t);
619 | }}
620 | />
621 | );
622 | },
623 | },
624 | {
625 | title: '第三列',
626 | dataIndex: 'column3',
627 | minWidth: 100,
628 | align: 'center',
629 | render: (row: Item, index) => {
630 | return (
631 | {
635 | const t = [...tableData];
636 | t[index!].column3 = Number(e.target.value);
637 | setTableData(t);
638 | }}
639 | />
640 | );
641 | },
642 | },
643 | {
644 | title: '第四列',
645 | dataIndex: 'column4',
646 | align: 'center',
647 | minWidth: 100,
648 | },
649 | ];
650 | const [tableData, setTableData] = useState([
651 | {
652 | column1: 1,
653 | column2: 2,
654 | column3: 3,
655 | column4: 4,
656 | },
657 | {
658 | column1: 11,
659 | column2: 22,
660 | column3: 33,
661 | column4: 44,
662 | },
663 | ]);
664 |
665 | return (
666 |
667 |
679 |
680 |
686 |
687 | );
688 | }
689 |
690 | export default Dome;
691 | ```
692 |
693 | ### 场景二
694 |
695 | ```jsx
696 | import ReactTable, { TColumn } from '@vv-react-table/vv-react-table';
697 | import React, { useState } from 'react';
698 |
699 | function Dome() {
700 | const [editingRow, setEditingRow] = useState(new Map()); // 储存要编辑的行
701 | type Item = {
702 | column1: string,
703 | column2: string,
704 | column3: string,
705 | column4: string,
706 | };
707 |
708 | const columns: TColumn- [] = [
709 | {
710 | title: '第一列',
711 | dataIndex: 'column1',
712 | minWidth: 100,
713 | align: 'center',
714 | },
715 |
716 | {
717 | title: '第二列',
718 | dataIndex: 'column2',
719 | minWidth: 100,
720 | align: 'center',
721 | render: (row: Item, index) => {
722 | return editingRow.has(row.id) ? (
723 | {
727 | const t = [...tableData];
728 | t[index!].column2 = Number(e.target.value);
729 | setTableData(t);
730 | }}
731 | />
732 | ) : (
733 | {row.column2}
734 | );
735 | },
736 | },
737 | {
738 | title: '第三列',
739 | dataIndex: 'column3',
740 | minWidth: 100,
741 | align: 'center',
742 | render: (row: Item, index) => {
743 | return editingRow.has(row.id) ? (
744 | {
748 | const t = [...tableData];
749 | t[index!].column3 = Number(e.target.value);
750 | setTableData(t);
751 | }}
752 | />
753 | ) : (
754 | {row.column3}
755 | );
756 | },
757 | },
758 | {
759 | title: '第四列',
760 | dataIndex: 'column4',
761 | align: 'center',
762 | minWidth: 100,
763 | },
764 | {
765 | title: '操作',
766 | align: 'center',
767 | width: 200,
768 | render: (row: Item, index) => {
769 | return editingRow.has(row.id) ? (
770 |
771 | {
774 | setEditingRow((map) => {
775 | const newMap = new Map(map);
776 | newMap.delete(row.id);
777 | return newMap;
778 | });
779 | }}
780 | >
781 | 保存
782 |
783 | {
785 | const t = [...tableData];
786 | const oldRow = editingRow.get(row.id);
787 | t[index!] = oldRow;
788 | setTableData(t);
789 | setEditingRow((map) => {
790 | const newMap = new Map(map);
791 | newMap.delete(row.id);
792 | return newMap;
793 | });
794 | }}
795 | >
796 | 取消编辑
797 |
798 |
799 | ) : (
800 |
803 | setEditingRow((map) => new Map(map.set(row.id, row)))
804 | }
805 | >
806 | 编辑
807 |
808 | );
809 | },
810 | },
811 | ];
812 | const [tableData, setTableData] = useState([
813 | {
814 | column1: 1,
815 | column2: 2,
816 | column3: 3,
817 | column4: 4,
818 | },
819 | {
820 | column1: 11,
821 | column2: 22,
822 | column3: 33,
823 | column4: 44,
824 | },
825 | ]);
826 |
827 | return (
828 |
829 |
841 |
842 |
848 |
849 | );
850 | }
851 |
852 | export default Dome;
853 | ```
854 |
855 | ## 右键菜单
856 |
857 | - 通过给 `table` 设置 `rightClickMenu` 来控制右键菜单的 `UI`
858 | - 通过给 `colums` 设置 `contextMenu` 控制是否开启此列的右键菜单
859 | - 如果 `colums` 设置 `rightClickMenu` 则会覆盖全局 `rightClickMenu`
860 |
861 | > 右键菜单效果需点击下面“在独立页面打开”查看效果
862 |
863 | ```jsx
864 | import ReactTable, { TColumn } from '@vv-react-table/vv-react-table';
865 | import React, { useMemo } from 'react';
866 | function Dome() {
867 | type Item = {
868 | column1: string,
869 | column2: string,
870 | column3: string,
871 | column4: string,
872 | };
873 |
874 | const columns: TColumn- [] = [
875 | {
876 | title: '第一列',
877 | dataIndex: 'column1',
878 | minWidth: 100,
879 | align: 'center',
880 | contextMenu: true,
881 | },
882 |
883 | {
884 | title: '第二列',
885 | dataIndex: 'column2',
886 | minWidth: 100,
887 | align: 'center',
888 | contextMenu: true,
889 | },
890 | {
891 | title: '第三列',
892 | dataIndex: 'column3',
893 | minWidth: 100,
894 | align: 'center',
895 | contextMenu: true,
896 | },
897 | {
898 | title: '自定义右键',
899 | dataIndex: 'column4',
900 | align: 'center',
901 | minWidth: 100,
902 | contextMenu: true,
903 | rightClickMenu: {
904 | render(row, index) {
905 | return (
906 |
{
908 | alert(JSON.stringify(row));
909 | }}
910 | style={{
911 | padding: '5px 10px',
912 | cursor: 'pointer',
913 | }}
914 | >
915 |
我是自定义
916 |
917 | );
918 | },
919 | },
920 | },
921 | ];
922 |
923 | const tableData = useMemo(
924 | () =>
925 | new Array(100).fill(undefined).map((item, index) => ({
926 | column1: 1,
927 | column2: 2,
928 | column3: 3,
929 | column4: 4,
930 | })),
931 | [columns],
932 | );
933 |
934 | return (
935 |
936 |
{
946 | alert(JSON.stringify(row));
947 | }}
948 | style={{
949 | padding: '5px 10px',
950 | cursor: 'pointer',
951 | }}
952 | >
953 | 第一列
954 | 第二列
955 | 第三列
956 |
957 | );
958 | },
959 | }}
960 | >
961 |
962 | );
963 | }
964 |
965 | export default Dome;
966 | ```
967 |
968 | ## 单独改变某一列样式
969 |
970 | 通过 `rowClassName` 可对某一行设置单独样式
971 |
972 | ```jsx
973 | import ReactTable, { TColumn } from '@vv-react-table/vv-react-table';
974 | import React, { useMemo } from 'react';
975 | function Dome() {
976 | type Item = {
977 | column1: string,
978 | column2: string,
979 | column3: string,
980 | column4: string,
981 | };
982 |
983 | const columns: TColumn- [] = [
984 | {
985 | title: '第一列',
986 | dataIndex: 'column1',
987 | minWidth: 100,
988 | align: 'center',
989 | },
990 |
991 | {
992 | title: '第二列',
993 | dataIndex: 'column2',
994 | minWidth: 100,
995 | align: 'center',
996 | },
997 | {
998 | title: '第三列',
999 | dataIndex: 'column3',
1000 | minWidth: 100,
1001 | align: 'center',
1002 | },
1003 | {
1004 | title: '第四列',
1005 | dataIndex: 'column4',
1006 | align: 'center',
1007 | minWidth: 100,
1008 | },
1009 | ];
1010 |
1011 | const tableData = useMemo(
1012 | () =>
1013 | new Array(100).fill(undefined).map((item, index) => ({
1014 | column1: 1,
1015 | column2: 2,
1016 | column3: 3,
1017 | column4: 4,
1018 | status: Math.random() > 0.5,
1019 | })),
1020 | [columns],
1021 | );
1022 |
1023 | return (
1024 |
1025 | {
1031 | if (row && row.status) {
1032 | return 'vv-bg-active';
1033 | }
1034 | }}
1035 | >
1036 |
1037 | );
1038 | }
1039 |
1040 | export default Dome;
1041 | ```
1042 |
1043 | ## 改变行高度
1044 |
1045 | - `rowHeight` 设置每一行高度
1046 | - `headerHeight` 设置表头高度
1047 |
1048 | ```jsx
1049 | import ReactTable, { TColumn } from '@vv-react-table/vv-react-table';
1050 | import React, { useMemo } from 'react';
1051 | function Dome() {
1052 | type Item = {
1053 | column1: string,
1054 | column2: string,
1055 | column3: string,
1056 | column4: string,
1057 | };
1058 |
1059 | const columns: TColumn- [] = [
1060 | {
1061 | title: '第一列',
1062 | dataIndex: 'column1',
1063 | minWidth: 100,
1064 | align: 'center',
1065 | },
1066 |
1067 | {
1068 | title: '第二列',
1069 | dataIndex: 'column2',
1070 | minWidth: 100,
1071 | align: 'center',
1072 | },
1073 | {
1074 | title: '第三列',
1075 | dataIndex: 'column3',
1076 | minWidth: 100,
1077 | align: 'center',
1078 | },
1079 | {
1080 | title: '第四列',
1081 | dataIndex: 'column4',
1082 | align: 'center',
1083 | minWidth: 100,
1084 | },
1085 | ];
1086 |
1087 | const tableData = useMemo(
1088 | () =>
1089 | new Array(100).fill(undefined).map((item, index) => ({
1090 | column1: 1,
1091 | column2: 2,
1092 | column3: 3,
1093 | column4: 4,
1094 | })),
1095 | [columns],
1096 | );
1097 |
1098 | return (
1099 |
1100 |
1108 |
1109 | );
1110 | }
1111 |
1112 | export default Dome;
1113 | ```
1114 |
--------------------------------------------------------------------------------