From 63060e98760e60fd185933b0728beb07544a74e9 Mon Sep 17 00:00:00 2001 From: twp0217 Date: Wed, 8 Sep 2021 18:22:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=8A=E4=BC=A0=E7=89=88=E6=9C=AC1.0?= =?UTF-8?q?.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 16 ++++ .fatherrc.ts | 4 + .gitignore | 25 +++++++ .prettierignore | 7 ++ .prettierrc | 11 +++ .umirc.ts | 11 +++ CHANGELOG.md | 0 README.md | 61 +++++++++++++++ docs/changelog.md | 1 + docs/demo/basic.md | 3 + docs/demo/event.md | 3 + docs/examples/basic.tsx | 33 +++++++++ docs/examples/data.json | 26 +++++++ docs/examples/event.tsx | 21 ++++++ docs/index.md | 5 ++ package.json | 55 ++++++++++++++ public/images/logo.svg | 1 + src/OrgChart.module.less | 54 ++++++++++++++ src/OrgChart.tsx | 20 +++++ src/components/DefaultOrgChart.tsx | 114 +++++++++++++++++++++++++++++ src/index.ts | 5 ++ src/interface.ts | 23 ++++++ tsconfig.json | 29 ++++++++ typings.d.ts | 2 + 24 files changed, 530 insertions(+) create mode 100644 .editorconfig create mode 100644 .fatherrc.ts create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 .umirc.ts create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 docs/changelog.md create mode 100644 docs/demo/basic.md create mode 100644 docs/demo/event.md create mode 100644 docs/examples/basic.tsx create mode 100644 docs/examples/data.json create mode 100644 docs/examples/event.tsx create mode 100644 docs/index.md create mode 100644 package.json create mode 100644 public/images/logo.svg create mode 100644 src/OrgChart.module.less create mode 100644 src/OrgChart.tsx create mode 100644 src/components/DefaultOrgChart.tsx create mode 100644 src/index.ts create mode 100644 src/interface.ts create mode 100644 tsconfig.json create mode 100644 typings.d.ts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7e3649a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/.fatherrc.ts b/.fatherrc.ts new file mode 100644 index 0000000..cc3a27b --- /dev/null +++ b/.fatherrc.ts @@ -0,0 +1,4 @@ +export default { + esm: 'rollup', + cjs: 'rollup', +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..82cff45 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/npm-debug.log* +/yarn-error.log +/yarn.lock +/package-lock.json + +# production +/dist +/docs-dist + +# misc +.DS_Store + +# umi +.umi +.umi-production +.umi-test +.env.local + +# ide +/.vscode +/.idea diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..ecb24d3 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,7 @@ +**/*.svg +**/*.ejs +**/*.html +package.json +.umi +.umi-production +.umi-test diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..94beb14 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,11 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "printWidth": 80, + "overrides": [ + { + "files": ".prettierrc", + "options": { "parser": "json" } + } + ] +} diff --git a/.umirc.ts b/.umirc.ts new file mode 100644 index 0000000..c2fe667 --- /dev/null +++ b/.umirc.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'dumi'; + +export default defineConfig({ + title: 'react-org-chart', + favicon: '/images/logo.svg', + logo: '/images/logo.svg', + outputPath: 'docs-dist', + base: '/react-org-chart', + publicPath: '/', + // more config: https://d.umijs.org/config +}); diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..591684e --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# react-org-chart - 组织结构图 + +## 使用 + +```typescript +import React from 'react'; +import OrgChart, { NodeDataType } from '@twp0217/react-org-chart'; + +export default () => { + const data: NodeDataType = { + key: 0, + label: '科技有限公司', + children: [ + { + key: 1, + label: '研发部', + children: [ + { key: 11, label: '开发-前端' }, + { key: 12, label: '开发-后端' }, + { key: 13, label: 'UI设计' }, + { key: 14, label: '产品经理' }, + ], + }, + { + key: 2, + label: '销售部', + children: [ + { key: 21, label: '销售一部' }, + { key: 22, label: '销售二部' }, + ], + }, + { key: 3, label: '财务部' }, + { key: 4, label: '人事部' }, + ], + }; + + return ; +}; +``` + +## API + +### NodeDataType + +| 名称 | 类型 | 默认值 | 说明 | +| --------- | ------------------- | ------ | ---------- | +| key | string \| number | - | key | +| label | number | - | label | +| children | NodeDataType[] | - | 子节点集合 | +| className | string | - | 类名 | +| style | React.CSSProperties | - | 样式 | + +### OrgChartProps + +| 名称 | 类型 | 默认值 | 说明 | +| ---------- | --------------------------------------------------------------------- | ------ | -------------- | +| data | NodeDataType | - | 数据 | +| className | string | - | 类名 | +| style | React.CSSProperties | - | 样式 | +| renderNode | (node: NodeDataType, originNode: React.ReactNode) => React.ReactNode; | - | 自定义渲染节点 | +| onClick | (node: NodeDataType) => void | - | 点击事件 | diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 0000000..beb2edd --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1 @@ + diff --git a/docs/demo/basic.md b/docs/demo/basic.md new file mode 100644 index 0000000..94cc536 --- /dev/null +++ b/docs/demo/basic.md @@ -0,0 +1,3 @@ +## basic + + diff --git a/docs/demo/event.md b/docs/demo/event.md new file mode 100644 index 0000000..9715115 --- /dev/null +++ b/docs/demo/event.md @@ -0,0 +1,3 @@ +## event + + diff --git a/docs/examples/basic.tsx b/docs/examples/basic.tsx new file mode 100644 index 0000000..80f2e2d --- /dev/null +++ b/docs/examples/basic.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import OrgChart, { NodeDataType } from '@twp0217/react-org-chart'; + +export default () => { + const data: NodeDataType = { + key: 0, + label: '科技有限公司', + children: [ + { + key: 1, + label: '研发部', + children: [ + { key: 11, label: '开发-前端' }, + { key: 12, label: '开发-后端' }, + { key: 13, label: 'UI设计' }, + { key: 14, label: '产品经理' }, + ], + }, + { + key: 2, + label: '销售部', + children: [ + { key: 21, label: '销售一部' }, + { key: 22, label: '销售二部' }, + ], + }, + { key: 3, label: '财务部' }, + { key: 4, label: '人事部' }, + ], + }; + + return ; +}; diff --git a/docs/examples/data.json b/docs/examples/data.json new file mode 100644 index 0000000..8ec8b3f --- /dev/null +++ b/docs/examples/data.json @@ -0,0 +1,26 @@ +{ + "key": 0, + "label": "科技有限公司", + "children": [ + { + "key": 1, + "label": "研发部", + "children": [ + { "key": 11, "label": "开发-前端" }, + { "key": 12, "label": "开发-后端" }, + { "key": 13, "label": "UI设计" }, + { "key": 14, "label": "产品经理" } + ] + }, + { + "key": 2, + "label": "销售部", + "children": [ + { "key": 21, "label": "销售一部" }, + { "key": 22, "label": "销售二部" } + ] + }, + { "key": 3, "label": "财务部" }, + { "key": 4, "label": "人事部" } + ] +} diff --git a/docs/examples/event.tsx b/docs/examples/event.tsx new file mode 100644 index 0000000..5201035 --- /dev/null +++ b/docs/examples/event.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import OrgChart, { NodeDataType } from '@twp0217/react-org-chart'; + +export default () => { + const data = require('./data.json'); + + const onClick = (node: NodeDataType) => { + console.log('onClick', node); + }; + + return ( +
+ +
+ ); +}; diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..f3eef61 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,5 @@ +--- +title: react-org-chart +--- + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..be882fb --- /dev/null +++ b/package.json @@ -0,0 +1,55 @@ +{ + "name": "@twp0217/react-org-chart", + "version": "1.0.0", + "scripts": { + "start": "dumi dev", + "docs:build": "dumi build", + "docs:deploy": "gh-pages -d docs-dist", + "build": "father-build", + "deploy": "npm run docs:build && npm run docs:deploy", + "release": "npm run build && npm publish --access public", + "prettier": "prettier --write \"**/*.{js,jsx,tsx,ts,less,md,json}\"", + "test": "umi-test", + "test:coverage": "umi-test --coverage", + "commit": "git-cz", + "version": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md" + }, + "main": "dist/index.js", + "module": "dist/index.esm.js", + "typings": "dist/index.d.ts", + "gitHooks": { + "pre-commit": "lint-staged" + }, + "lint-staged": { + "*.{js,jsx,less,md,json}": [ + "prettier --write" + ], + "*.ts?(x)": [ + "prettier --parser=typescript --write" + ] + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "dependencies": { + "classnames": "^2.3.1" + }, + "devDependencies": { + "@umijs/test": "^3.0.5", + "commitizen": "^4.2.4", + "conventional-changelog-cli": "^2.1.1", + "cz-conventional-changelog": "^3.3.0", + "dumi": "^1.0.16", + "father-build": "^1.17.2", + "gh-pages": "^3.0.0", + "lint-staged": "^10.0.7", + "prettier": "^2.2.1", + "yorkie": "^2.0.0" + }, + "config": { + "commitizen": { + "path": "./node_modules/cz-conventional-changelog" + } + } +} diff --git a/public/images/logo.svg b/public/images/logo.svg new file mode 100644 index 0000000..67c6eb3 --- /dev/null +++ b/public/images/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/OrgChart.module.less b/src/OrgChart.module.less new file mode 100644 index 0000000..a7d5750 --- /dev/null +++ b/src/OrgChart.module.less @@ -0,0 +1,54 @@ +@node-color: #1890ff; +@line-width: 1px; +@line-color: @node-color; + +.orgChartContainer { + display: inline-block; + + :global { + table { + border-collapse: separate; + border-spacing: 0; + line-height: 1.5715; + + tr { + &.lines td { + height: 20px; + + .vertical { + width: @line-width; + height: 100%; + background-color: @line-color; + display: inline-block; + } + + &.left { + border-right: @line-width solid @line-color; + } + + &.right { + border-left: @line-width solid transparent; + } + + &.top { + border-top: @line-width solid @line-color; + } + } + } + + td { + text-align: center; + padding: 0; + vertical-align: top; + + .node { + display: inline-block; + border: 1px solid @node-color; + padding: 0 0.5rem; + margin: 0 5px; + cursor: pointer; + } + } + } + } +} diff --git a/src/OrgChart.tsx b/src/OrgChart.tsx new file mode 100644 index 0000000..6ddac7f --- /dev/null +++ b/src/OrgChart.tsx @@ -0,0 +1,20 @@ +import classNames from 'classnames'; +import React from 'react'; +import DefaultOrgChart from './components/DefaultOrgChart'; +import { OrgChartProps } from './interface'; +import styles from './OrgChart.module.less'; + +const OrgChart = (props: OrgChartProps) => { + const { data, className, style, renderNode, onClick } = props; + + return !!data ? ( +
+ +
+ ) : null; +}; + +export default OrgChart; diff --git a/src/components/DefaultOrgChart.tsx b/src/components/DefaultOrgChart.tsx new file mode 100644 index 0000000..ac6c042 --- /dev/null +++ b/src/components/DefaultOrgChart.tsx @@ -0,0 +1,114 @@ +import { NodeDataType, OrgChartComponentProps, RenderNode } from '../interface'; +import classNames from 'classnames'; +import React from 'react'; + +const DefaultOrgChart = (props: OrgChartComponentProps) => { + const { data, renderNode: customRenderNode, onClick } = props; + + const childrenLength = data.children?.length || 0; + const colSpan: number = childrenLength * 2; + + /** + * 渲染节点 + * @param data + * @returns + */ + const renderNode = (data: NodeDataType): React.ReactNode => { + const contentNode: React.ReactNode = ( +
+ {data.label} +
+ ); + return ( + + +
onClick && onClick(data)} + > + {!!customRenderNode + ? customRenderNode(data, contentNode) + : contentNode} +
+ + + ); + }; + + /** + * 渲染垂直线 + * @returns + */ + const renderVerticalLine = (): React.ReactNode => { + return ( + +
+ + ); + }; + + /** + * 渲染连接线 + * @returns + */ + const renderConnectLines = (): React.ReactNode[] => { + const lines: React.ReactNode[] = []; + for (let index = 0; index < colSpan; index++) { + lines.push( + +   + , + ); + } + return lines; + }; + + /** + * 渲染子节点 + * @param datas + * @returns + */ + const renderChildren = (datas: NodeDataType[] = []): React.ReactNode => { + if (datas.length > 0) { + return ( + <> + {renderVerticalLine()} + {renderConnectLines()} + + {datas.map((data) => { + return ( + + + + ); + })} + + + ); + } + return; + }; + + return ( + + + {renderNode(data)} + {renderChildren(data.children)} + +
+ ); +}; + +export default DefaultOrgChart; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..5c58e7b --- /dev/null +++ b/src/index.ts @@ -0,0 +1,5 @@ +import { NodeDataType, OrgChartProps } from './interface'; +import OrgChart from './OrgChart'; + +export { NodeDataType, OrgChartProps }; +export default OrgChart; diff --git a/src/interface.ts b/src/interface.ts new file mode 100644 index 0000000..6681111 --- /dev/null +++ b/src/interface.ts @@ -0,0 +1,23 @@ +export interface NodeDataType { + key: string | number; + label: string; + children?: NodeDataType[]; + className?: string; + style?: React.CSSProperties; +} + +export type RenderNode = ( + node: NodeDataType, + originNode: React.ReactNode, +) => React.ReactNode; + +export interface OrgChartComponentProps { + data: NodeDataType; + renderNode?: RenderNode; + onClick?: (node: NodeDataType) => void; +} + +export interface OrgChartProps extends Partial { + className?: string; + style?: React.CSSProperties; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..de15e0a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "importHelpers": true, + "jsx": "react", + "esModuleInterop": true, + "sourceMap": true, + "baseUrl": "./", + "strict": true, + "paths": { + "@/*": ["src/*"], + "@@/*": ["src/.umi/*"] + }, + "allowSyntheticDefaultImports": true + }, + "exclude": [ + "node_modules", + "lib", + "es", + "dist", + "typings", + "**/__test__", + "test", + "docs", + "tests" + ] +} diff --git a/typings.d.ts b/typings.d.ts new file mode 100644 index 0000000..71e0e9f --- /dev/null +++ b/typings.d.ts @@ -0,0 +1,2 @@ +declare module '*.css'; +declare module '*.less';