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';