├── .DS_Store ├── .bitmap ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc.js ├── .umirc.ts ├── LEGAL.md ├── LICENSE ├── README.md ├── package.json ├── public └── assets │ └── favicon.png ├── src ├── app.tsx ├── assets │ ├── icons │ │ ├── demo.css │ │ ├── demo_index.html │ │ ├── iconfont.css │ │ ├── iconfont.js │ │ ├── iconfont.json │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.woff2 │ └── node-icons │ │ ├── demo.css │ │ ├── demo_index.html │ │ ├── iconfont.css │ │ ├── iconfont.js │ │ ├── iconfont.json │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.woff2 ├── components │ ├── color-picker │ │ ├── constant │ │ │ └── index.ts │ │ ├── index.less │ │ └── index.tsx │ ├── console-menu │ │ ├── index.less │ │ └── index.tsx │ ├── console │ │ ├── auth-manager │ │ │ ├── account-manager │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ ├── edit-role │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ ├── edit-user │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ ├── index.module.less │ │ │ ├── index.tsx │ │ │ └── role-manager │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ ├── constant │ │ │ └── index.ts │ │ ├── database-info │ │ │ ├── index.module.less │ │ │ └── index.tsx │ │ ├── hooks │ │ │ ├── useDataInfo.ts │ │ │ ├── useGraph.ts │ │ │ ├── useRole.ts │ │ │ └── useUser.ts │ │ ├── index.ts │ │ ├── interface │ │ │ ├── role.ts │ │ │ └── user.ts │ │ ├── mock.ts │ │ └── utils │ │ │ ├── localStorage.ts │ │ │ ├── query.ts │ │ │ ├── request.tsx │ │ │ └── timeFormatter.ts │ ├── custom-color-picker │ │ ├── index.less │ │ └── index.tsx │ ├── guidance │ │ ├── assets │ │ │ └── index.ts │ │ ├── download.tsx │ │ ├── end.tsx │ │ ├── filter.tsx │ │ ├── index.module.less │ │ ├── index.tsx │ │ ├── query.tsx │ │ ├── spread.tsx │ │ ├── style.tsx │ │ ├── types.ts │ │ └── welcome.tsx │ ├── icon-font │ │ ├── index.less │ │ └── index.tsx │ ├── icon-loader │ │ └── index.ts │ ├── icon-picker │ │ ├── constant │ │ │ ├── defaultClassIcons.ts │ │ │ └── index.ts │ │ ├── index.less │ │ └── index.tsx │ └── studio │ │ ├── components │ │ ├── async-table │ │ │ └── index.tsx │ │ ├── auth-item │ │ │ └── index.tsx │ │ ├── collapsable-steps │ │ │ ├── index.module.less │ │ │ └── index.tsx │ │ ├── demo-card │ │ │ ├── index.module.less │ │ │ └── index.tsx │ │ ├── edit-password │ │ │ ├── index.module.less │ │ │ └── index.tsx │ │ ├── edit-table │ │ │ ├── index.module.less │ │ │ └── index.tsx │ │ ├── garph-canvas │ │ │ └── index.tsx │ │ ├── graph-canvas-layout │ │ │ ├── constant │ │ │ │ └── index.ts │ │ │ ├── index.module.less │ │ │ └── index.tsx │ │ ├── graph-canvas-tools │ │ │ ├── components │ │ │ │ ├── auto-zoom.tsx │ │ │ │ ├── real-zoom.tsx │ │ │ │ ├── zoom-in.tsx │ │ │ │ └── zoom-out.tsx │ │ │ ├── index.module.less │ │ │ └── index.tsx │ │ ├── icon-font │ │ │ └── index.tsx │ │ ├── icon-item │ │ │ ├── index.module.less │ │ │ └── index.tsx │ │ ├── search-input │ │ │ ├── index.module.less │ │ │ └── index.tsx │ │ ├── split-panle │ │ │ └── index.tsx │ │ ├── switch-drawer │ │ │ ├── index.module.less │ │ │ └── index.tsx │ │ ├── text-tabs │ │ │ ├── index.module.less │ │ │ └── index.tsx │ │ └── tooltip-text │ │ │ ├── index.module.less │ │ │ └── index.tsx │ │ ├── constant │ │ ├── demo-json │ │ │ └── schema-demo.json │ │ └── index.tsx │ │ ├── domain-core │ │ ├── graph-construct │ │ │ ├── add-nodes-edges │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ ├── edit-nodes-edges │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ ├── file-uploader │ │ │ │ └── index.tsx │ │ │ ├── import-data-config │ │ │ │ ├── data-map.tsx │ │ │ │ ├── data-mapping.tsx │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ ├── import-data-result │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ ├── import-data │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ ├── index.module.less │ │ │ ├── index.tsx │ │ │ └── nodes-edges-list │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ ├── graph-query │ │ │ ├── components │ │ │ │ ├── excecute-result-panle │ │ │ │ │ ├── index.module.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── excecute-result │ │ │ │ │ ├── index.module.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── model-overview │ │ │ │ │ ├── components │ │ │ │ │ │ ├── edge-node-list │ │ │ │ │ │ │ ├── index.module.less │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ └── graph-canvas-mini │ │ │ │ │ │ │ ├── index.module.less │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── index.module.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── node-query │ │ │ │ │ ├── index.module.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── path-query │ │ │ │ │ ├── index.module.less │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── path-modal │ │ │ │ │ │ ├── index.module.less │ │ │ │ │ │ └── index.tsx │ │ │ │ ├── statement-query-list │ │ │ │ │ ├── index.module.less │ │ │ │ │ └── index.tsx │ │ │ │ └── stored-procedure │ │ │ │ │ ├── index.module.less │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── stored-checkout │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── stored-download │ │ │ │ │ ├── index.module.less │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── stored-khop-panle │ │ │ │ │ ├── index.module.less │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── stored-list │ │ │ │ │ ├── index.module.less │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── stored-form │ │ │ │ │ │ ├── index.module.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── stored-result │ │ │ │ │ ├── index.module.less │ │ │ │ │ └── index.tsx │ │ │ ├── constant │ │ │ │ └── index.tsx │ │ │ ├── cypherEditor.less │ │ │ ├── cypherEditor.tsx │ │ │ ├── index.module.less │ │ │ ├── index.tsx │ │ │ ├── interface │ │ │ │ └── excecute.ts │ │ │ └── utils │ │ │ │ ├── editEdgeParamsTransform.ts │ │ │ │ └── getConnectOptions.ts │ │ └── project │ │ │ └── project-list │ │ │ ├── components │ │ │ ├── add-tugraph │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ ├── edit-form │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ ├── edit-tugraph │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ ├── empty-project │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ └── project-card │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ └── translator │ │ │ └── index.tsx │ │ ├── graph-list │ │ ├── index.module.less │ │ └── index.tsx │ │ ├── hooks │ │ ├── useGraph.ts │ │ ├── useGraphData.ts │ │ ├── useImport.ts │ │ ├── useProcedure.ts │ │ ├── useQuery.ts │ │ ├── useSchema.ts │ │ └── useVisible.ts │ │ ├── index.md │ │ ├── index.ts │ │ ├── interface │ │ ├── graph.ts │ │ ├── graphData.ts │ │ ├── import.ts │ │ ├── procedure.ts │ │ ├── query.ts │ │ └── schema.ts │ │ ├── login │ │ ├── index.module.less │ │ ├── index.tsx │ │ └── particles-config.ts │ │ ├── mock.ts │ │ ├── services │ │ ├── ProcedureController.ts │ │ └── SchemaController.ts │ │ ├── styles │ │ └── mixin.less │ │ ├── user-center │ │ ├── index.module.less │ │ └── index.tsx │ │ └── utils │ │ ├── bigNumberTransform.ts │ │ ├── dataImportTransform.ts │ │ ├── downloadFile.ts │ │ ├── getDefaultDemoList.ts │ │ ├── getFileSizeTransform.ts │ │ ├── getZhPeriod.ts │ │ ├── graphTranslator.ts │ │ ├── index.ts │ │ ├── localStorage.ts │ │ ├── nodesEdgesListTranslator.ts │ │ ├── objectOper.ts │ │ ├── parseCsv.ts │ │ ├── processEdges.ts │ │ ├── request.tsx │ │ ├── routeParams.ts │ │ ├── schemaTransform.ts │ │ ├── uploadFile.ts │ │ └── url.ts ├── constants │ ├── demo_data │ │ ├── TheThreeBody │ │ │ ├── import.json │ │ │ ├── organization-organization-relationship.csv │ │ │ ├── organization-plan-relationship.csv │ │ │ ├── organizes.csv │ │ │ ├── person-organization-relationship.csv │ │ │ ├── person-person-relationship.csv │ │ │ ├── person-plan-relationship.csv │ │ │ ├── persons.csv │ │ │ ├── plans.csv │ │ │ ├── timeRelationship.csv │ │ │ └── timeline.csv │ │ ├── ThreeKingdoms │ │ │ ├── battle.csv │ │ │ ├── civil-servant.csv │ │ │ ├── civilServant_to_lord.csv │ │ │ ├── civilServant_to_state.csv │ │ │ ├── general.csv │ │ │ ├── general_father_lord.csv │ │ │ ├── general_to_battle.csv │ │ │ ├── general_to_lord.csv │ │ │ ├── general_to_state.csv │ │ │ ├── import.json │ │ │ ├── lord.csv │ │ │ ├── lord_brother_lord.csv │ │ │ ├── lord_father_lord.csv │ │ │ ├── lord_to_battle.csv │ │ │ ├── lord_to_lord.csv │ │ │ ├── lord_to_state.csv │ │ │ └── state.csv │ │ ├── WanderingEarth │ │ │ ├── celestialFsacilities.csv │ │ │ ├── event_celestialFacilities.csv │ │ │ ├── event_organization_celestialFacilities.csv │ │ │ ├── event_organization_role.csv │ │ │ ├── event_role_celestialFacility.csv │ │ │ ├── event_role_organization.csv │ │ │ ├── event_role_role.csv │ │ │ ├── import.json │ │ │ ├── organizations.csv │ │ │ ├── relationship_role_celestialFacility.csv │ │ │ ├── relationship_role_organization.csv │ │ │ ├── relationship_role_role.csv │ │ │ └── roles.csv │ │ └── movie │ │ │ ├── edge_acted_in.csv │ │ │ ├── edge_directed.csv │ │ │ ├── edge_has_genre.csv │ │ │ ├── edge_has_keyword.csv │ │ │ ├── edge_is_friend.csv │ │ │ ├── edge_produce.csv │ │ │ ├── edge_rate.csv │ │ │ ├── edge_write.csv │ │ │ ├── import.json │ │ │ ├── vertex_genre.csv │ │ │ ├── vertex_keyword.csv │ │ │ ├── vertex_movie.csv │ │ │ ├── vertex_person.csv │ │ │ └── vertex_user.csv │ └── index.ts ├── domains-core │ └── graph-analysis │ │ └── graph-schema │ │ ├── components │ │ ├── array-tabs │ │ │ └── index.tsx │ │ ├── attibutes-filter │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── attributes-filter-form │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── canvas-container │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── canvas-legend-pie-chart │ │ │ └── index.tsx │ │ ├── canvas-legend │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── canvas-sider │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── canvas-toolbar-segmented │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── config-query │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── context-menu │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── download-canvas │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── drawer-handler │ │ │ └── index.tsx │ │ ├── element-info │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── filter-segmented │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── graph-canvas │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── graph-filter │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── graph-json-view │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── graph-style-setting │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── graph-table-view │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── language-query │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── lasso-select │ │ │ └── index.tsx │ │ ├── layout-form-slider │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── layout-form-xy-input │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── layout-form │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── layout-style-segmented │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── page-back-arrow │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── page-layout-segmented │ │ │ └── index.tsx │ │ ├── page-title │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── path-chart │ │ │ └── index.tsx │ │ ├── path-query │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── query-filter-segmented │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── query-segmented │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── schema-field │ │ │ └── index.tsx │ │ ├── statistics-filter-attribute-select │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── statistics-filter-column-chart │ │ │ └── index.tsx │ │ ├── statistics-filter-form │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── statistics-filter-histogram-chart │ │ │ └── index.tsx │ │ ├── statistics-filter-line-chart │ │ │ └── index.tsx │ │ ├── statistics-filter-pie-chart │ │ │ └── index.tsx │ │ ├── statistics-filter-world-cloud-chart │ │ │ └── index.tsx │ │ ├── statistics-filter │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── view-select │ │ │ ├── index.less │ │ │ └── index.tsx │ │ └── zoom-in-out │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── constants │ │ ├── action-bar.ts │ │ ├── config-center.ts │ │ ├── graph-style.ts │ │ ├── index.ts │ │ ├── layout.tsx │ │ └── schema.ts │ │ ├── contexts │ │ └── index.ts │ │ ├── hooks │ │ ├── use-schema-form-value │ │ │ └── index.ts │ │ └── use-schema-tab-container │ │ │ └── index.ts │ │ ├── interfaces │ │ └── index.ts │ │ ├── registers │ │ ├── breathing-node │ │ │ └── index.ts │ │ └── cluster-dagre-layout │ │ │ └── index.ts │ │ ├── root │ │ └── index.ts │ │ ├── translators │ │ ├── env-graph-language-translator │ │ │ └── index.ts │ │ ├── graph-schema-translator │ │ │ └── index.ts │ │ └── properties-translator │ │ │ └── index.ts │ │ └── utils │ │ ├── download-json │ │ └── index.ts │ │ ├── download-png │ │ └── index.tsx │ │ ├── filter-by-property-condition │ │ └── index.ts │ │ ├── filter-by-top-rule │ │ └── index.ts │ │ ├── filter-element-by-property-value │ │ └── index.ts │ │ ├── filter-graph-data-by-value │ │ └── index.ts │ │ ├── filter-graph-data │ │ └── index.ts │ │ ├── generate-edge-type-map │ │ └── index.ts │ │ ├── get-chart-data │ │ └── index.ts │ │ ├── get-engine-type-by-env │ │ └── index.ts │ │ ├── get-filtered-element-style │ │ └── index.ts │ │ ├── get-histogram-data │ │ └── index.ts │ │ ├── get-label-text-by-style-config │ │ └── index.ts │ │ ├── get-node-icon-by-style-config │ │ └── index.ts │ │ ├── get-node-size-by-style-config │ │ └── index.ts │ │ ├── get-operator-list-by-value-type │ │ └── index.ts │ │ ├── get-property-value-ranks │ │ └── index.ts │ │ ├── get-schema-by-env │ │ └── index.ts │ │ ├── get-styled-graph-data │ │ └── index.ts │ │ ├── get-styles-from-schema │ │ └── index.ts │ │ ├── graph-data-2-property-graph │ │ └── index.ts │ │ ├── group-and-count-nodes-by-label │ │ └── index.ts │ │ ├── hex-to-rgba │ │ └── index.ts │ │ ├── highlight-sub-graph │ │ └── index.ts │ │ ├── merge-graph-data │ │ └── index.ts │ │ ├── pie-chart-edges-translator │ │ └── index.ts │ │ ├── reset-graph-active-status │ │ └── index.ts │ │ ├── resize-canvas │ │ └── index.ts │ │ ├── set-graph-active-status │ │ └── index.ts │ │ ├── unique-elements-by │ │ └── index.ts │ │ └── update-graph-style-options │ │ └── index.ts ├── hooks │ └── useAnalysis.ts ├── layouts │ ├── index.less │ ├── index.tsx │ └── nav.tsx ├── pages │ ├── console.tsx │ ├── construct.tsx │ ├── graph-schema │ │ ├── index.less │ │ └── index.tsx │ ├── index.tsx │ ├── login.tsx │ ├── query.tsx │ ├── resetPassword.less │ ├── resetPassword.tsx │ └── studio.tsx ├── queries │ ├── analysis.ts │ ├── data.ts │ ├── graph.ts │ ├── info.ts │ ├── procedure.ts │ ├── query.ts │ ├── schema.ts │ └── security.ts ├── services │ ├── analysisi.ts │ ├── data.ts │ ├── info.ts │ ├── procedure.ts │ ├── query.ts │ ├── request.ts │ └── schema │ │ ├── IndexSchema.ts │ │ ├── index.ts │ │ ├── label.ts │ │ └── schema.ts ├── translator │ ├── graph-data-tanslator │ │ └── index.ts │ └── index.ts ├── types │ ├── services │ │ └── index.ts │ ├── studio │ │ └── procedure.ts │ └── typings.d.ts └── utils │ ├── common.ts │ ├── filterTitleEllipsis.ts │ ├── getEditorTotalCountByLineNumber.ts │ ├── getId.ts │ ├── history.ts │ ├── index.ts │ ├── originUtils.ts │ ├── parseHash.ts │ ├── parseSearch.ts │ ├── query.ts │ ├── safeParseJSON.ts │ ├── schema.ts │ └── transformHexToRgb.ts ├── tsconfig.json └── workspace.jsonc /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TuGraph-family/tugraph-db-browser/38400298e6594a0601f97bcaa5a433f2f2398879/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bit 2 | /node_modules 3 | /.env.local 4 | /.umirc.local.ts 5 | /config/config.local.ts 6 | /src/.umi 7 | /dist 8 | package-lock.json 9 | /src/.umi-production -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com/ 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/.git 2 | **/.svn 3 | **/.hg 4 | **/node_modules 5 | 6 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 80, 6 | arrowParens: 'avoid', 7 | }; 8 | -------------------------------------------------------------------------------- /.umirc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'umi'; 2 | 3 | /** 判断是不是 cosmos 私有化部署环境 */ 4 | const customPublicPath = process.argv[3]; 5 | 6 | export default defineConfig({ 7 | hash: true, 8 | title: 'TuGraph DB', 9 | history: { 10 | type: 'hash', 11 | }, 12 | outputPath: './dist/resource', 13 | publicPath: customPublicPath ? customPublicPath : '/resource/', 14 | theme: { 15 | '@primary-color': '#1650FF', 16 | '@border-radius-base': '6px', 17 | '@collapse-panel-border-radius': '6px', 18 | '@checkbox-border-radius': '4px', 19 | '@tag-border-radius': '4px', 20 | '@text-color': '#363740', 21 | '@heading-color': '#363740', 22 | '@text-color-dark': '#363740', 23 | }, 24 | routes: [ 25 | { path: '/', component: 'studio', title: '图项目' }, 26 | { path: '/login', component: 'login', title: '登录' }, 27 | { path: '/home', component: 'studio', title: '图项目' }, 28 | { path: '/console', component: 'console', title: '控制台' }, 29 | { path: '/construct', component: 'construct', title: '图构建' }, 30 | { path: '/analysis', component: 'graph-schema/index', title: '图分析' }, 31 | { path: '/query', component: 'query', title: '图查询' }, 32 | { path: '/reset', component: 'resetPassword', title: '重置密码' } 33 | ], 34 | npmClient: 'npm', 35 | favicons: ['/resource/assets/favicon.png'], 36 | esbuildMinifyIIFE: true, 37 | plugins: [ 38 | '@umijs/plugins/dist/initial-state', 39 | '@umijs/plugins/dist/model', 40 | '@umijs/plugins/dist/antd', 41 | ], 42 | initialState: {}, 43 | model: {}, 44 | antd: { 45 | import: true, 46 | }, 47 | }); 48 | -------------------------------------------------------------------------------- /LEGAL.md: -------------------------------------------------------------------------------- 1 | Legal Disclaimer 2 | 3 | Within this source code, the comments in Chinese shall be the original, governing version. Any comment in other languages are for reference only. In the event of any conflict between the Chinese language version comments and other language version comments, the Chinese language version shall prevail. 4 | 5 | 法律免责声明 6 | 7 | 关于代码注释部分,中文注释为官方版本,其它语言注释仅做参考。中文注释可能与其它语言注释存在不一致,当中文注释与其它语言注释存在不一致时,请以中文注释为准。 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TuGraph DB BROWSER 🔗 2 | 3 | TuGraph DB BROWSER 是 TuGraph 图数据库的可视化平台。可以完成图谱、模型、数据等的创建和导入。同时可用使用 TuGraph Cypher 进行数据的操作。 4 | 5 | ## 0. 环境准备 6 | 7 | - node.js >= 16 8 | 9 | ## 1. 安装项目依赖 10 | 11 | ```bash 12 | npm install --force 13 | ``` 14 | 15 | ## 2. 本地研发 16 | 17 | ```bash 18 | npm run dev 19 | ``` 20 | 21 | 浏览器访问 http://localhost:8000 22 | 23 | ## 3. 编译构建 24 | 25 | 通用环境编译命令 26 | ```bash 27 | npm run build 28 | ``` 29 | 30 | 31 | cosmos 私有化部署环境编译命令, 其中 xxx 可以指定拼接的 prefixPath 32 | ```bash 33 | npm run custom-build xxx 34 | ``` 35 | -------------------------------------------------------------------------------- /public/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TuGraph-family/tugraph-db-browser/38400298e6594a0601f97bcaa5a433f2f2398879/public/assets/favicon.png -------------------------------------------------------------------------------- /src/assets/icons/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TuGraph-family/tugraph-db-browser/38400298e6594a0601f97bcaa5a433f2f2398879/src/assets/icons/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/icons/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TuGraph-family/tugraph-db-browser/38400298e6594a0601f97bcaa5a433f2f2398879/src/assets/icons/iconfont.woff -------------------------------------------------------------------------------- /src/assets/icons/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TuGraph-family/tugraph-db-browser/38400298e6594a0601f97bcaa5a433f2f2398879/src/assets/icons/iconfont.woff2 -------------------------------------------------------------------------------- /src/assets/node-icons/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TuGraph-family/tugraph-db-browser/38400298e6594a0601f97bcaa5a433f2f2398879/src/assets/node-icons/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/node-icons/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TuGraph-family/tugraph-db-browser/38400298e6594a0601f97bcaa5a433f2f2398879/src/assets/node-icons/iconfont.woff -------------------------------------------------------------------------------- /src/assets/node-icons/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TuGraph-family/tugraph-db-browser/38400298e6594a0601f97bcaa5a433f2f2398879/src/assets/node-icons/iconfont.woff2 -------------------------------------------------------------------------------- /src/components/color-picker/constant/index.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_NODE_ACTIVE_COLOR = '#1890ff'; 2 | -------------------------------------------------------------------------------- /src/components/color-picker/index.less: -------------------------------------------------------------------------------- 1 | .color-picker { 2 | display: flex; 3 | 4 | &-item { 5 | width: 20px; 6 | border-radius: 4px; 7 | height: 20px; 8 | cursor: pointer; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | color: rgb(106 107 113 / 100%); 13 | 14 | &:hover { 15 | box-shadow: #f2f2f2 0 0 0 3px; 16 | } 17 | 18 | &:not(:last-child) { 19 | margin-right: 16px; 20 | } 21 | 22 | &__ellipsis { 23 | color: rgb(106 107 113 / 100%); 24 | border: 1px solid #dddddf; 25 | } 26 | } 27 | } 28 | 29 | .color-picker-item-popover { 30 | .item-popover-content { 31 | display: flex; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/console-menu/index.less: -------------------------------------------------------------------------------- 1 | .consoleMenu { 2 | display: flex; 3 | justify-content: flex-start; 4 | flex-direction: column; 5 | width: 100%; 6 | height: 100%; 7 | padding: 8px; 8 | background: rgb(228, 235, 255); 9 | border-right: 1px solid rgba(26, 27, 37, 0.06); 10 | 11 | .consoleMenuItem { 12 | color: rgba(26, 27, 37, 0.65); 13 | padding: 0 16px 0 24px; 14 | margin-bottom: 8px; 15 | height: 40px; 16 | line-height: 40px; 17 | cursor: pointer; 18 | } 19 | .consoleMenuItemSelected { 20 | background-color: rgba(26, 27, 37, 0.04); 21 | color: rgba(54, 55, 64, 1); 22 | padding: 0 16px 0 24px; 23 | margin-bottom: 8px; 24 | height: 40px; 25 | line-height: 40px; 26 | cursor: pointer; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/console-menu/index.tsx: -------------------------------------------------------------------------------- 1 | import { CONSOLE_LINKS } from '@/constants'; 2 | import styles from './index.less'; 3 | 4 | const ConsoleMenu = ({ 5 | onMenuChange, 6 | menuKey, 7 | }: { 8 | onMenuChange: (value: any) => void; 9 | menuKey: string; 10 | }) => { 11 | return ( 12 |
13 | {CONSOLE_LINKS.map(({ title, key }) => ( 14 |
{ 22 | window.location.hash = '/console?menu=' + key; 23 | onMenuChange && onMenuChange(key); 24 | }} 25 | > 26 | {title} 27 |
28 | ))} 29 |
30 | ); 31 | }; 32 | 33 | export default ConsoleMenu; 34 | -------------------------------------------------------------------------------- /src/components/console/auth-manager/account-manager/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-permissions{ 3 | &-btn { 4 | padding: 4px; 5 | } 6 | &-permissions { 7 | display: flex; 8 | align-items: center; 9 | } 10 | &-dot { 11 | font-weight: 400; 12 | font-size: 12px; 13 | color: rgba(0, 0, 0, 0.45); 14 | margin: 0 16px 0 4px; 15 | } 16 | } 17 | .@{perfix-cls}-permissions-tr{ 18 | display: inline-block; 19 | } 20 | -------------------------------------------------------------------------------- /src/components/console/auth-manager/edit-role/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-modal { 3 | :global { 4 | .ant-modal-body { 5 | padding: 0 60px; 6 | } 7 | .ant-table-wrapper { 8 | margin-bottom: 32px; 9 | } 10 | } 11 | &-user { 12 | margin-bottom: 0; 13 | } 14 | &-text { 15 | height: 22px; 16 | width: 216px; 17 | font-weight: 400; 18 | font-size: 12px; 19 | color: #98989d; 20 | line-height: 22px; 21 | margin-bottom: 24px; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/console/auth-manager/edit-user/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-modal { 3 | :global { 4 | .ant-modal-body { 5 | padding: 0 60px; 6 | } 7 | } 8 | &-user { 9 | margin-bottom: 0; 10 | } 11 | &-text { 12 | height: 22px; 13 | width: 216px; 14 | font-weight: 400; 15 | font-size: 12px; 16 | color: #98989d; 17 | line-height: 22px; 18 | margin-bottom: 24px; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/console/auth-manager/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-permissions-container { 3 | height: 100%; 4 | width: 100%; 5 | background-color: #ffffff; 6 | border-radius: 8px; 7 | padding: 25px; 8 | box-sizing: border-box; 9 | .@{perfix-cls}-table-header { 10 | display: flex; 11 | justify-content: space-between; 12 | align-items: center; 13 | margin-bottom: 11px; 14 | .@{perfix-cls}-change-btn { 15 | margin-right: 12px; 16 | } 17 | } 18 | :global { 19 | .ant-form-item { 20 | width: 350px; 21 | display: inline-block; 22 | margin-bottom: 0; 23 | .ant-select { 24 | width: 264px; 25 | } 26 | } 27 | .ant-tabs-tab { 28 | &:hover { 29 | color: rgba(54, 55, 64, 1); 30 | } 31 | } 32 | .ant-tabs-nav { 33 | &:before { 34 | border: none; 35 | } 36 | } 37 | .ant-tabs-tab-btn { 38 | color: rgba(54, 55, 64, 1) !important; 39 | } 40 | .ant-tabs-ink-bar { 41 | background-color: rgba(26, 27, 37, 0.88); 42 | } 43 | } 44 | &-btn { 45 | padding: 4px; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/components/console/auth-manager/role-manager/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-permissions{ 3 | &-btn { 4 | padding: 4px; 5 | } 6 | &-permissions { 7 | display: flex; 8 | align-items: center; 9 | } 10 | &-dot { 11 | font-weight: 400; 12 | font-size: 12px; 13 | color: rgba(0, 0, 0, 0.45); 14 | margin: 0 16px 0 4px; 15 | } 16 | } 17 | .@{perfix-cls}-permissions-tr{ 18 | display: inline-block; 19 | } 20 | -------------------------------------------------------------------------------- /src/components/console/constant/index.ts: -------------------------------------------------------------------------------- 1 | export const PUBLIC_PERFIX_CLASS = 'ant-tugraph'; 2 | export enum PERSSIONS_ENUM { 3 | READ = 'READ', 4 | WRITE = 'WRITE', 5 | FULL = 'FULL', 6 | NONE = 'NONE', 7 | } 8 | export enum PERSSION_COlOR { 9 | READ = 'gold', 10 | WRITE = 'green', 11 | FULL = 'blue', 12 | NONE = '#D9D9D9', 13 | } 14 | export const PERSSIONS_ENUM_TEXT = [ 15 | { label: '全部', value: PERSSIONS_ENUM.FULL }, 16 | { label: '读写', value: PERSSIONS_ENUM.WRITE }, 17 | { label: '只读', value: PERSSIONS_ENUM.READ }, 18 | { label: '无', value: PERSSIONS_ENUM.NONE }, 19 | ]; 20 | -------------------------------------------------------------------------------- /src/components/console/database-info/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-base-info { 3 | :global { 4 | .ant-card { 5 | margin-bottom: 24px; 6 | } 7 | } 8 | 9 | .@{perfix-cls}-card-item { 10 | width: 19.2%; 11 | margin-bottom: 24px; 12 | .@{perfix-cls}-item-label { 13 | font-weight: 400; 14 | font-size: 14px; 15 | color: rgba(0, 0, 0, 0.45); 16 | line-height: 30px; 17 | } 18 | .@{perfix-cls}-item-val { 19 | line-height: 30px; 20 | color: rgba(0, 0, 0, 0.85); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/console/hooks/useDataInfo.ts: -------------------------------------------------------------------------------- 1 | import { useModel } from 'umi'; 2 | 3 | import { useRequest } from 'ahooks'; 4 | import { InitialState } from '@/app'; 5 | import { getDatabaseInfo, getSystemInfo } from '@/queries/info'; 6 | import { request } from '@/services/request'; 7 | 8 | export const useDataInfo = () => { 9 | const { initialState } = useModel('@@initialState'); 10 | const { driver } = initialState as InitialState; 11 | const { 12 | runAsync: onGetDatabaseInfo, 13 | loading: getDatabaseInfoLoading, 14 | error: getDatabaseInfoError, 15 | } = useRequest(() => request({ driver, cypher: getDatabaseInfo() }), { 16 | manual: true, 17 | }); 18 | 19 | const { 20 | runAsync: onGetSystemInfo, 21 | loading: getSystemInfoLoading, 22 | error: getSystemInfoError, 23 | } = useRequest(() => request({ driver, cypher: getSystemInfo() }), { 24 | manual: true, 25 | }); 26 | 27 | return { 28 | onGetDatabaseInfo, 29 | getDatabaseInfoLoading, 30 | getDatabaseInfoError, 31 | onGetSystemInfo, 32 | getSystemInfoLoading, 33 | getSystemInfoError, 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /src/components/console/index.ts: -------------------------------------------------------------------------------- 1 | export { AuthManager } from './auth-manager'; 2 | export { DatabaseInfo } from './database-info'; 3 | -------------------------------------------------------------------------------- /src/components/console/interface/role.ts: -------------------------------------------------------------------------------- 1 | export interface RoleProps { 2 | description?: string; 3 | disabled?: boolean; 4 | field_permissions?: string; 5 | permissions?: Record; 6 | role?: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/console/interface/user.ts: -------------------------------------------------------------------------------- 1 | export interface UserProps { 2 | username?: string; 3 | password?: string; 4 | description?: string; 5 | disabled?: boolean; 6 | roles?: Array; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/console/mock.ts: -------------------------------------------------------------------------------- 1 | export const mockDatabaseInfo = { 2 | baseInfo: [ 3 | { label: '服务器版本号', val: '3.3.3' }, 4 | { label: '运行时间', val: '24小时40分4秒' }, 5 | { label: '服务器代码版本', val: '“Master”' }, 6 | { label: '前端代码版本号', val: '“aef85doster”' }, 7 | { label: 'CPP编译器版本号', val: '“ef85sdjter”' }, 8 | { label: 'Python版本号', val: '“3.5.6”' }, 9 | { label: 'CPP编辑器ID', val: '“GNU”' }, 10 | ], 11 | configInfo: [ 12 | { label: 'bind_host', val: '0.0.0.0' }, 13 | { label: 'bind_host', val: '0.0.0.0' }, 14 | { label: 'bind_host', val: '0.0.0.0' }, 15 | { label: 'bind_host', val: '0.0.0.0' }, 16 | { label: 'bind_host', val: '0.0.0.0' }, 17 | { label: 'bind_host', val: '0.0.0.0' }, 18 | { label: 'bind_host', val: '0.0.0.0' }, 19 | { label: 'bind_host', val: '0.0.0.0' }, 20 | { label: 'bind_host', val: '0.0.0.0' }, 21 | { label: 'bind_host', val: '0.0.0.0' }, 22 | { label: 'bind_host', val: '0.0.0.0' }, 23 | { label: 'bind_host', val: '0.0.0.0' }, 24 | { label: 'bind_host', val: '0.0.0.0' }, 25 | { label: 'bind_host', val: '0.0.0.0' }, 26 | { label: 'bind_host', val: '0.0.0.0' }, 27 | { label: 'bind_host', val: '0.0.0.0' }, 28 | ], 29 | }; 30 | -------------------------------------------------------------------------------- /src/components/console/utils/localStorage.ts: -------------------------------------------------------------------------------- 1 | export const getLocalData = (key: string) => { 2 | if (!key) { 3 | return; 4 | } 5 | try { 6 | const data = JSON.parse(localStorage.getItem(key) || '{}'); 7 | return data; 8 | } catch (e) { 9 | console.error(`tugraph ${key} %d ${e}`); 10 | } 11 | }; 12 | 13 | export const setLocalData = (key: string, data: any) => { 14 | if (!key) { 15 | return; 16 | } 17 | localStorage.setItem(key, JSON.stringify(data)); 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/console/utils/request.tsx: -------------------------------------------------------------------------------- 1 | import { message } from 'antd'; 2 | import { extend } from 'umi-request'; 3 | import { PROXY_HOST } from '@/constants'; 4 | import { getLocalData } from './localStorage'; 5 | 6 | const request = extend({ 7 | headers: { 8 | 'Content-Type': 'application/json', 9 | Authorization: getLocalData('TUGRAPH_TOKEN'), 10 | }, 11 | prefix: PROXY_HOST, 12 | withCredentials: true, 13 | credentials: 'include', 14 | // 默认错误处理 15 | crossOrigin: true, // 开启CORS跨域 16 | }); 17 | // 中间件 18 | request.interceptors.response.use(async response => { 19 | const data = await response.clone().json(); 20 | if (data.errorCode === 401) { 21 | message.warning('登录过期,请重新登录'); 22 | window.location.href = '/login'; 23 | } 24 | if (data.errorCode == 400 || data.errorCode == 500) { 25 | message.error('请求失败' + data.errorMessage); 26 | } 27 | return response; 28 | }); 29 | 30 | export default request; 31 | -------------------------------------------------------------------------------- /src/components/console/utils/timeFormatter.ts: -------------------------------------------------------------------------------- 1 | import { compact, join } from 'lodash'; 2 | export const formatTime = (seconds: number | undefined) => { 3 | if(!seconds){ 4 | return seconds 5 | } 6 | const days = Math.floor(seconds / (24 * 60 * 60)); 7 | const hours = Math.floor((seconds % (24 * 60 * 60)) / (60 * 60)); 8 | const minutes = Math.floor((seconds % (60 * 60)) / 60); 9 | const secs = Math.floor(seconds % 60); 10 | 11 | const formattedTime = join( 12 | compact([ 13 | days && `${days}天`, 14 | hours && `${hours}小时`, 15 | minutes && `${minutes}分`, 16 | secs && `${secs}秒`, 17 | ]), 18 | '' 19 | ); 20 | 21 | return formattedTime; 22 | }; 23 | -------------------------------------------------------------------------------- /src/components/custom-color-picker/index.less: -------------------------------------------------------------------------------- 1 | .geamaker-color-input-popover { 2 | :global { 3 | .ant-popover-inner-content { 4 | padding: 0; 5 | } 6 | } 7 | } 8 | 9 | .color-input { 10 | .color-picker-item { 11 | width: 20px; 12 | border-radius: 4px; 13 | height: 20px; 14 | cursor: pointer; 15 | display: flex; 16 | justify-content: center; 17 | align-items: center; 18 | 19 | &__ellipsis { 20 | border: 1px solid #dddddf; 21 | background-color: #fff; 22 | 23 | :global { 24 | .anticon { 25 | color: rgb(106 107 113 / 100%); 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/guidance/download.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StepUIProps } from './types'; 3 | import { Assets } from './assets'; 4 | // @ts-ignore 5 | import styles from './index.module.less'; 6 | 7 | /** 8 | * 新手引导 - 下载 9 | * @returns 10 | */ 11 | export const Download: React.FC = ({ 12 | prev, 13 | next, 14 | end, 15 | x = 0, 16 | y = 0, 17 | canvasX = 0, 18 | canvasY = 0, 19 | }) => { 20 | return ( 21 |
22 |
23 | 28 | 33 |
34 |
35 | 36 |
37 | 下一步 38 |
39 |
40 |
41 |
46 | 47 |
48 |
49 |
50 | ); 51 | }; 52 | -------------------------------------------------------------------------------- /src/components/guidance/end.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StepUIProps } from './types'; 3 | import { Assets } from './assets'; 4 | // @ts-ignore 5 | import styles from './index.module.less'; 6 | 7 | /** 8 | * 新手引导 - 结束页 9 | * @returns 10 | */ 11 | export const End: React.FC = ({ prev, next, end }) => { 12 | return ( 13 |
14 |
15 | 20 |
21 | 26 |
27 | 进入图分析自由探索 28 |
29 |
30 |
31 |
32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /src/components/guidance/query.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StepUIProps } from './types'; 3 | import { Assets } from './assets'; 4 | // @ts-ignore 5 | import styles from './index.module.less'; 6 | 7 | /** 8 | * 新手引导 - 数据查询 9 | * @returns 10 | */ 11 | export const Query: React.FC = ({ prev, next, end, x = 0, y = 0 }) => { 12 | return ( 13 |
14 |
15 | 16 | 17 |
18 |
19 | 20 |
下一步
21 |
22 |
23 |
24 | 25 |
26 |
27 |
28 | ); 29 | }; 30 | 31 | -------------------------------------------------------------------------------- /src/components/guidance/spread.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StepUIProps } from './types'; 3 | import { Assets } from './assets'; 4 | // @ts-ignore 5 | import styles from './index.module.less'; 6 | 7 | /** 8 | * 新手引导 - 扩散 9 | * @returns 10 | */ 11 | export const Spread: React.FC = ({ 12 | prev, 13 | next, 14 | end, 15 | x = 0, 16 | y = 0, 17 | }) => { 18 | return ( 19 |
20 |
21 | 26 | 31 |
32 |
33 | 34 |
35 | 下一步 36 |
37 |
38 |
39 |
44 | 45 |
46 |
47 |
48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /src/components/guidance/types.ts: -------------------------------------------------------------------------------- 1 | export type StepUIProps = { 2 | prev: () => void; 3 | next: () => void; 4 | end: () => void; 5 | x?: number; 6 | y?: number; 7 | canvasX?: number; 8 | canvasY?: number; 9 | } -------------------------------------------------------------------------------- /src/components/guidance/welcome.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StepUIProps } from './types'; 3 | import { Assets } from './assets'; 4 | // @ts-ignore 5 | import styles from './index.module.less'; 6 | 7 | /** 8 | * 新手引导 - 欢迎页 9 | * @returns 10 | */ 11 | export const Welcome: React.FC = ({ prev, next, end }) => { 12 | return ( 13 |
14 |
15 | 16 |
17 | 18 |
19 | 开启破案之旅 20 |
21 |
22 | 23 |
24 | 25 |
26 |
27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/components/icon-font/index.less: -------------------------------------------------------------------------------- 1 | .icon { 2 | width: 1em; 3 | height: 1em; 4 | fill: currentcolor; 5 | overflow: hidden; 6 | } 7 | -------------------------------------------------------------------------------- /src/components/icon-font/index.tsx: -------------------------------------------------------------------------------- 1 | // https://www.iconfont.cn/manage/index?spm=a313x.7781069.1998910419.20&manage_type=myprojects&projectId=3146710&keyword=&project_type=&page= 2 | import '@/assets/icons/iconfont'; 3 | import '@/assets/node-icons/iconfont'; 4 | import React from 'react'; 5 | import styles from './index.less'; 6 | 7 | interface IconFontProps extends React.HTMLProps { 8 | rotate?: number; 9 | type: string; 10 | } 11 | 12 | const IconFont: React.FC = (props) => { 13 | const { type, rotate, style, className, ...others } = props; 14 | return ( 15 | 24 | 27 | 28 | ); 29 | }; 30 | 31 | export default IconFont; 32 | -------------------------------------------------------------------------------- /src/components/icon-loader/index.ts: -------------------------------------------------------------------------------- 1 | import '@/assets/node-icons/iconfont.css'; 2 | import fonts from '@/assets/node-icons/iconfont.json'; 3 | 4 | export const fontFamily = 'geamaker-iconfont'; 5 | 6 | const icons = fonts.glyphs.map((icon) => { 7 | return { 8 | name: icon.name, 9 | unicode: String.fromCodePoint(icon.unicode_decimal), // `\\u${icon.unicode}`, 10 | }; 11 | }); 12 | export const iconLoader = (type: string) => { 13 | const matchIcon = icons.find((icon) => { 14 | return icon.name === type; 15 | }) || { unicode: '', name: 'default' }; 16 | return matchIcon.unicode; 17 | }; 18 | -------------------------------------------------------------------------------- /src/components/icon-picker/index.less: -------------------------------------------------------------------------------- 1 | .icon-picker { 2 | display: flex; 3 | align-items: center; 4 | 5 | &-item { 6 | width: 24px; 7 | height: 24px; 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | cursor: pointer; 12 | border-radius: 4px; 13 | 14 | &:hover { 15 | background: #f2f2f2; 16 | } 17 | 18 | &__active { 19 | border-radius: 12px; 20 | } 21 | 22 | &:not(:last-child) { 23 | margin-right: 11px; 24 | font-size: 20px; 25 | } 26 | 27 | &:last-child { 28 | color: rgb(106 107 113 / 100%); 29 | border: 1px solid #dddddf; 30 | border-radius: 4px; 31 | width: 20px; 32 | height: 20px; 33 | background-color: #fff; 34 | } 35 | } 36 | } 37 | 38 | .icon-picker-item-popover { 39 | display: flex; 40 | flex-direction: column; 41 | 42 | .popover-content { 43 | display: flex; 44 | flex-wrap: wrap; 45 | width: 340px; 46 | 47 | &-item { 48 | font-size: 20px; 49 | cursor: pointer; 50 | height: 24px; 51 | width: 24px; 52 | display: flex; 53 | justify-content: center; 54 | align-items: center; 55 | margin: 10px 10px 0 0; 56 | border-radius: 4px; 57 | 58 | &:hover { 59 | background: #f2f2f2; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/components/studio/components/auth-item/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface AuthItemProps { 4 | code: string; 5 | menuAuths?: any; 6 | children?: React.ReactDOM; 7 | } 8 | 9 | export const linkToApplyAuth = () => window.open('/personal/group'); 10 | 11 | export const hasModuleAuth = (code: string, menuAuths?: any) => 12 | menuAuths && menuAuths.some((item) => item.code === code); 13 | 14 | export const AuthItem: React.FunctionComponent = ({ 15 | children, 16 | code, 17 | menuAuths, 18 | }) => { 19 | return hasModuleAuth(code, menuAuths) ? <>{children} : null; 20 | }; 21 | 22 | export default AuthItem; 23 | -------------------------------------------------------------------------------- /src/components/studio/components/collapsable-steps/index.module.less: -------------------------------------------------------------------------------- 1 | @collasible-prefix-cls: ant-tugraph; 2 | 3 | .@{collasible-prefix-cls}-collasible-steps { 4 | display: flex; 5 | transition: all 0.2s; 6 | overflow: hidden; 7 | 8 | .@{collasible-prefix-cls}-step { 9 | display: flex; 10 | align-items: center; 11 | 12 | &-arrow { 13 | font-size: 12px; 14 | color: rgba(26, 27, 37, 0.25); 15 | margin: 0 6px; 16 | } 17 | 18 | .@{collasible-prefix-cls}-step-content { 19 | display: flex; 20 | justify-items: center; 21 | width: 100%; 22 | background-color: #d0dcff; 23 | border-radius: 8px; 24 | padding: 20px 14px; 25 | 26 | .@{collasible-prefix-cls}-title { 27 | font-weight: 500; 28 | font-size: 16px; 29 | color: #363740; 30 | text-align: left; 31 | } 32 | 33 | .@{collasible-prefix-cls}-desc { 34 | font-weight: 400; 35 | font-size: 14px; 36 | color: #6a6b71; 37 | } 38 | 39 | .@{collasible-prefix-cls}-icon-text-title { 40 | margin-right: 8px; 41 | font-size: 42px; 42 | margin-top: 3px; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/components/studio/components/demo-card/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .card-styles { 3 | width: 233px; 4 | height: 233px; 5 | border-radius: 8px; 6 | cursor: pointer; 7 | padding: 16px; 8 | box-sizing: border-box; 9 | } 10 | .@{perfix-cls}-card-container { 11 | border: 1px solid rgba(0, 0, 0, 0.06); 12 | position: relative; 13 | .card-styles(); 14 | 15 | .@{perfix-cls}-card-img { 16 | height: 127px; 17 | width: 201px; 18 | background-color: rgba(22, 80, 255, 0.15); 19 | border-radius: 4px; 20 | overflow: hidden; 21 | img { 22 | width: 100%; 23 | } 24 | } 25 | .@{perfix-cls}-card-title { 26 | height: 22px; 27 | font-weight: 400; 28 | font-size: 14px; 29 | color: #363740; 30 | line-height: 22px; 31 | margin: 8px 0; 32 | } 33 | .@{perfix-cls}-card-desc { 34 | height: 40px; 35 | font-weight: 400; 36 | font-size: 12px; 37 | color: #98989d; 38 | line-height: 20px; 39 | } 40 | } 41 | .@{perfix-cls}-active-card { 42 | border: 1px solid #1650ff; 43 | background-color: rgba(22, 80, 255, 0.08); 44 | } 45 | .@{perfix-cls}-card-horn { 46 | position: absolute; 47 | width: 0; 48 | height: 0; 49 | border: 6px solid transparent; 50 | border-top: 6px solid rgba(22, 80, 255, 1); 51 | border-right: 6px solid rgba(22, 80, 255, 1); 52 | border-radius: 0 4px 0; 53 | top: 2px; 54 | right: 2px; 55 | } 56 | -------------------------------------------------------------------------------- /src/components/studio/components/demo-card/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PUBLIC_PERFIX_CLASS } from '@/components/studio/constant'; 3 | 4 | import styles from './index.module.less'; 5 | 6 | type Prop = { 7 | isActive: boolean; 8 | onClick?: () => void; 9 | detail: { 10 | graph_name: string; 11 | description: string; 12 | imgUrl?: string; 13 | graph_demo_name: string; 14 | }; 15 | }; 16 | const DemoCard: React.FC = ({ isActive, onClick, detail }) => { 17 | return ( 18 |
24 |
27 |
28 | 29 |
30 |
31 | {detail.graph_demo_name || detail.graph_name} 32 |
33 |
34 | {detail.description} 35 |
36 |
37 | ); 38 | }; 39 | export default DemoCard; 40 | -------------------------------------------------------------------------------- /src/components/studio/components/edit-password/index.module.less: -------------------------------------------------------------------------------- 1 | .ant-tugraph-edit-password-modal { 2 | :global { 3 | .ant-modal-body { 4 | height: 357px; 5 | .ant-form { 6 | padding: 0 36px; 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/studio/components/edit-password/index.tsx: -------------------------------------------------------------------------------- 1 | import { Form, Input, Modal } from 'antd'; 2 | import React from 'react'; 3 | import { PUBLIC_PERFIX_CLASS } from '@/components/studio/constant'; 4 | 5 | import styles from './index.module.less'; 6 | 7 | type Prop = { open: boolean; onCancel: () => void }; 8 | const { Item } = Form; 9 | const EditPasswordModal: React.FC = ({ open, onCancel }) => { 10 | return ( 11 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 | ); 33 | }; 34 | export default EditPasswordModal; 35 | -------------------------------------------------------------------------------- /src/components/studio/components/edit-table/index.module.less: -------------------------------------------------------------------------------- 1 | .table-container { 2 | :global { 3 | .ant-form-item { 4 | margin-bottom: 0; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/studio/components/graph-canvas-layout/constant/index.ts: -------------------------------------------------------------------------------- 1 | import type { Layout } from "@antv/graphin"; 2 | 3 | export const CANVAS_LAYOUT: { 4 | layout: Layout; 5 | title: string; 6 | icon: string; 7 | }[] = [ 8 | { 9 | layout: { 10 | type: "graphin-force", 11 | animation: false, 12 | }, 13 | title: "力导布局", 14 | icon: "icon-yuanxingbuju", 15 | }, 16 | { 17 | layout: { 18 | type: "concentric", 19 | preventOverlap: true, 20 | nodeSize: 150, 21 | }, 22 | title: "同心圆布局", 23 | icon: "icon-tongxinyuanbuju", 24 | }, 25 | { 26 | layout: { 27 | type: "grid", 28 | }, 29 | title: "栅格布局", 30 | icon: "icon-jingdianlidaoxiangbuju", 31 | }, 32 | ]; 33 | -------------------------------------------------------------------------------- /src/components/studio/components/graph-canvas-layout/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-graph-canvas-layout { 3 | display: flex; 4 | align-items: center; 5 | 6 | :global { 7 | .anticon { 8 | font-size: 22px; 9 | } 10 | } 11 | 12 | .@{perfix-cls}-types { 13 | background-color: rgba(26, 27, 37, 0.04); 14 | border-radius: 6px; 15 | padding: 2px; 16 | display: flex; 17 | margin-left: 10px; 18 | 19 | &-item { 20 | width: 32px; 21 | height: 28px; 22 | display: flex; 23 | align-items: center; 24 | cursor: pointer; 25 | justify-content: center; 26 | border-radius: 4px; 27 | 28 | &-active { 29 | background-color: #fff; 30 | } 31 | 32 | &:hover { 33 | color: #1650ff; 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/components/studio/components/graph-canvas-layout/index.tsx: -------------------------------------------------------------------------------- 1 | import type { Layout } from '@antv/graphin'; 2 | import { Popover } from 'antd'; 3 | import { join } from 'lodash'; 4 | import React from 'react'; 5 | import { PUBLIC_PERFIX_CLASS } from '../../constant'; 6 | import IconFont from '../icon-font'; 7 | import { CANVAS_LAYOUT } from './constant'; 8 | 9 | import styles from './index.module.less'; 10 | 11 | interface GraphCanvasLayoutProps { 12 | onLayoutChange: (layout: Layout) => void; 13 | currentLayout: Layout; 14 | } 15 | 16 | export const GraphCanvasLayout: React.FC = ({ 17 | onLayoutChange, 18 | currentLayout, 19 | }) => { 20 | return ( 21 |
30 |
31 | {CANVAS_LAYOUT.map((item) => { 32 | const { icon, title, layout } = item; 33 | return ( 34 | 35 |
48 | onLayoutChange(layout)} /> 49 |
50 |
51 | ); 52 | })} 53 |
54 |
55 | ); 56 | }; 57 | -------------------------------------------------------------------------------- /src/components/studio/components/graph-canvas-tools/components/auto-zoom.tsx: -------------------------------------------------------------------------------- 1 | import { GraphinContext } from '@antv/graphin'; 2 | import { Popover } from 'antd'; 3 | import React, { useCallback, useContext } from 'react'; 4 | import { useGraphinContext } from '@/components/studio/components/garph-canvas'; 5 | import IconFont from '@/components/studio/components/icon-font/index'; 6 | 7 | const AutoZoom: React.FC = () => { 8 | const { apis } = useGraphinContext(); 9 | const { apis: contextApis } = useContext(GraphinContext); 10 | 11 | const onClick = useCallback(() => { 12 | if (apis) { 13 | apis.handleAutoZoom(); 14 | } 15 | if (contextApis.handleAutoZoom) { 16 | contextApis.handleAutoZoom(); 17 | } 18 | }, [apis, contextApis]); 19 | return ( 20 | 21 |
22 | 23 |
24 |
25 | ); 26 | }; 27 | 28 | export default AutoZoom; 29 | -------------------------------------------------------------------------------- /src/components/studio/components/graph-canvas-tools/components/real-zoom.tsx: -------------------------------------------------------------------------------- 1 | import { GraphinContext } from '@antv/graphin'; 2 | import { Popover } from 'antd'; 3 | import React, { useCallback, useContext } from 'react'; 4 | import { useGraphinContext } from '@/components/studio/components/garph-canvas'; 5 | import IconFont from '@/components/studio/components/icon-font/index'; 6 | 7 | const RealZoom: React.FC = () => { 8 | const { apis } = useGraphinContext(); 9 | const { apis: contextApis } = useContext(GraphinContext); 10 | 11 | const onClick = useCallback(() => { 12 | if (apis) { 13 | apis.handleRealZoom(); 14 | } 15 | if (contextApis.handleRealZoom) { 16 | contextApis.handleRealZoom(); 17 | } 18 | }, [apis, contextApis]); 19 | return ( 20 | 21 |
22 | 23 |
24 |
25 | ); 26 | }; 27 | 28 | export default RealZoom; 29 | -------------------------------------------------------------------------------- /src/components/studio/components/graph-canvas-tools/components/zoom-in.tsx: -------------------------------------------------------------------------------- 1 | import { GraphinContext } from '@antv/graphin'; 2 | import { Popover } from 'antd'; 3 | import React, { useCallback, useContext } from 'react'; 4 | import { useGraphinContext } from '@/components/studio/components/garph-canvas'; 5 | import IconFont from '@/components/studio/components/icon-font/index'; 6 | 7 | const ZoomIn: React.FC = () => { 8 | const { apis } = useGraphinContext(); 9 | const { apis: contextApis } = useContext(GraphinContext); 10 | 11 | const onClick = useCallback(() => { 12 | if (apis) { 13 | apis.handleZoomIn(); 14 | } 15 | if (contextApis.handleZoomIn) { 16 | contextApis.handleZoomIn(); 17 | } 18 | }, [apis, contextApis]); 19 | return ( 20 | 21 |
22 | 23 |
24 |
25 | ); 26 | }; 27 | 28 | export default ZoomIn; 29 | -------------------------------------------------------------------------------- /src/components/studio/components/graph-canvas-tools/components/zoom-out.tsx: -------------------------------------------------------------------------------- 1 | import { GraphinContext } from '@antv/graphin'; 2 | import { Popover } from 'antd'; 3 | import React, { useCallback, useContext } from 'react'; 4 | import { useGraphinContext } from '@/components/studio/components/garph-canvas'; 5 | import IconFont from '@/components/studio/components/icon-font/index'; 6 | const ZoomOut: React.FC = () => { 7 | const { apis } = useGraphinContext(); 8 | const { apis: contextApis } = useContext(GraphinContext); 9 | const onClick = useCallback(() => { 10 | if (apis) { 11 | apis.handleZoomOut(); 12 | } 13 | if (contextApis.handleZoomOut) { 14 | contextApis.handleZoomOut(); 15 | } 16 | }, [apis, contextApis]); 17 | return ( 18 | 19 |
20 | 21 |
22 |
23 | ); 24 | }; 25 | 26 | export default ZoomOut; 27 | -------------------------------------------------------------------------------- /src/components/studio/components/graph-canvas-tools/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-graph-canvas-tools { 3 | display: flex; 4 | background-color: #fff; 5 | padding: 5px 9px; 6 | border-radius: 6px; 7 | align-items: center; 8 | 9 | .@{perfix-cls}-divider { 10 | height: 16px; 11 | width: 1px; 12 | background-color: #dddddf; 13 | margin-left: 9px; 14 | } 15 | 16 | &-item { 17 | cursor: pointer; 18 | 19 | &:not(:first-child) { 20 | padding-left: 9px; 21 | } 22 | 23 | &:hover { 24 | color: #1650ff; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/studio/components/graph-canvas-tools/index.tsx: -------------------------------------------------------------------------------- 1 | import { join } from 'lodash'; 2 | import React from 'react'; 3 | import { PUBLIC_PERFIX_CLASS } from '@/components/studio/constant'; 4 | import AutoZoom from './components/auto-zoom'; 5 | import RealZoom from './components/real-zoom'; 6 | import ZoomIn from './components/zoom-in'; 7 | import ZoomOut from './components/zoom-out'; 8 | 9 | import styles from './index.module.less'; 10 | 11 | export const GraphCanvasTools: React.FC = () => { 12 | return ( 13 |
22 |
23 | 24 |
25 |
26 | 27 |
28 |
29 |
30 | 31 |
32 |
33 | 34 |
35 |
36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /src/components/studio/components/icon-font/index.tsx: -------------------------------------------------------------------------------- 1 | // https://www.iconfont.cn/manage/index?spm=a313x.7781069.1998910419.20&manage_type=myprojects&projectId=3146710&keyword=&project_type=&page= 2 | import { createFromIconfontCN } from '@ant-design/icons'; 3 | const IconFont = createFromIconfontCN({ 4 | scriptUrl: '//at.alicdn.com/t/a/font_3146710_a0jqa3k8j79.css', // 在 iconfont.cn 上生成 5 | }); 6 | 7 | export default IconFont; 8 | 9 | export const NodeIcon = createFromIconfontCN({ 10 | scriptUrl: 'https://at.alicdn.com/t/a/font_3146710_a0jqa3k8j79.js', // 在 iconfont.cn 上生成 11 | }); 12 | -------------------------------------------------------------------------------- /src/components/studio/components/icon-item/index.module.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixin.less'; 2 | @perfix-cls: ant-tugraph; 3 | .@{perfix-cls}-icon-item, 4 | .@{perfix-cls}-icon-item-disabled { 5 | display: flex; 6 | flex-direction: column; 7 | align-items: center; 8 | justify-content: center; 9 | cursor: pointer; 10 | 11 | &-name { 12 | font-size: @font-size-small; 13 | line-height: 20px; 14 | white-space: nowrap; 15 | } 16 | } 17 | 18 | .@{perfix-cls}-icon-item { 19 | color: #6a6b71; 20 | 21 | &__horizontal { 22 | flex-direction: row; 23 | 24 | :global { 25 | .anticon { 26 | margin-right: 8px; 27 | } 28 | } 29 | } 30 | } 31 | 32 | .@{perfix-cls}-icon-item-disabled { 33 | color: rgba(0, 10, 26, 0.26); 34 | cursor: not-allowed; 35 | } 36 | -------------------------------------------------------------------------------- /src/components/studio/components/icon-item/index.tsx: -------------------------------------------------------------------------------- 1 | import { join } from 'lodash'; 2 | import type { HTMLAttributes, ReactNode } from 'react'; 3 | import React from 'react'; 4 | import { PUBLIC_PERFIX_CLASS } from '@/components/studio/constant'; 5 | import IconFont from '../icon-font'; 6 | 7 | import styles from './index.module.less'; 8 | 9 | interface IconItemProps extends HTMLAttributes { 10 | icon?: ReactNode; 11 | name?: ReactNode; 12 | onClick?: () => void; 13 | disabled?: boolean; 14 | iconProps?: { 15 | style?: React.CSSProperties; 16 | }; 17 | direction?: 'horizontal'; 18 | } 19 | 20 | const IconItem: React.FC = ({ 21 | icon, 22 | name, 23 | onClick, 24 | disabled, 25 | iconProps, 26 | direction, 27 | ...others 28 | }) => { 29 | return ( 30 |
46 | {typeof icon === 'string' ? ( 47 | 48 | ) : ( 49 | icon 50 | )} 51 | {name && ( 52 |
58 | {name} 59 |
60 | )} 61 |
62 | ); 63 | }; 64 | 65 | export default IconItem; 66 | -------------------------------------------------------------------------------- /src/components/studio/components/search-input/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-search-ipt { 3 | :global { 4 | .ant-input-affix-wrapper { 5 | color: rgba(0, 10, 26, 0.26); 6 | border-radius: 8px !important; 7 | border: 1px solid rgba(26, 27, 37, 0.15); 8 | } 9 | 10 | .ant-input-suffix { 11 | cursor: pointer; 12 | } 13 | 14 | .ant-select { 15 | width: 100%; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/studio/components/split-panle/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactSplitPane, { SplitPaneProps } from 'react-split-pane'; 3 | 4 | interface ISplitPaneProps extends SplitPaneProps { 5 | children: React.ReactNode; 6 | } 7 | 8 | export const SplitPane: React.FC = (props) => { 9 | return ; 10 | }; 11 | -------------------------------------------------------------------------------- /src/components/studio/components/tooltip-text/index.module.less: -------------------------------------------------------------------------------- 1 | .tooltip-text { 2 | display: inline-block; 3 | overflow: hidden; 4 | white-space: nowrap; 5 | text-overflow: ellipsis; 6 | cursor: pointer; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/studio/components/tooltip-text/index.tsx: -------------------------------------------------------------------------------- 1 | import { message, Tooltip, TooltipProps } from 'antd'; 2 | import copy from 'copy-to-clipboard'; 3 | import { join } from 'lodash'; 4 | import type { FC, ReactNode } from 'react'; 5 | import React from 'react'; 6 | 7 | import styles from './index.module.less'; 8 | 9 | interface TooltipTextProps 10 | extends React.DetailedHTMLProps< 11 | React.HTMLAttributes, 12 | HTMLDivElement 13 | > { 14 | maxWidth: number; 15 | text: ReactNode; 16 | tooltipProps?: TooltipProps; 17 | } 18 | 19 | export const TooltipText: FC = ({ 20 | maxWidth, 21 | text, 22 | style, 23 | tooltipProps, 24 | ...others 25 | }) => { 26 | return ( 27 |
{ 29 | if (typeof text === 'string') { 30 | copy(text); 31 | message.success('复制成功'); 32 | } 33 | }} 34 | {...others} 35 | > 36 | 46 | 53 | {text} 54 | 55 | 56 |
57 | ); 58 | }; 59 | -------------------------------------------------------------------------------- /src/components/studio/constant/demo-json/schema-demo.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "label": "person", 4 | "type": "VERTEX", 5 | "properties": [ 6 | { 7 | "name": "name", 8 | "type": "STRING", 9 | "optional": false, 10 | "unique": true, 11 | "index": true 12 | }, 13 | { "name": "age", "type": "INT8", "optional": false }, 14 | { "name": "birthday", "type": "STRING", "optional": false }, 15 | { "name": "gender", "type": "STRING", "optional": true }, 16 | { "name": "dsd", "type": "STRING", "optional": false } 17 | ], 18 | "primary": "name" 19 | }, 20 | { 21 | "label": "company", 22 | "type": "VERTEX", 23 | "properties": [ 24 | { 25 | "name": "name", 26 | "type": "STRING", 27 | "optional": false, 28 | "unique": true, 29 | "index": true 30 | } 31 | ], 32 | "primary": "name" 33 | }, 34 | { 35 | "label": "employment", 36 | "type": "EDGE", 37 | "properties": [], 38 | "constraints": [["person", "company"]] 39 | } 40 | ] 41 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/graph-construct/add-nodes-edges/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-container { 3 | :global { 4 | .ant-select-selection-item-remove { 5 | display: none; 6 | } 7 | .anticon-question-circle { 8 | margin-left: 4px; 9 | } 10 | .ant-table-container { 11 | border-radius: 8px 0 0 8px; 12 | overflow: hidden; 13 | table { 14 | border-radius: 8px; 15 | overflow: hidden; 16 | } 17 | } 18 | .ant-table-cell { 19 | font-weight: 400; 20 | font-size: 14px; 21 | color: #6a6b71; 22 | } 23 | .ant-form-item-required { 24 | color: #363740; 25 | } 26 | } 27 | &-content { 28 | padding: 0 24px 24px; 29 | } 30 | &-header { 31 | height: 44px; 32 | display: flex; 33 | justify-content: space-between; 34 | align-items: center; 35 | font-weight: 500; 36 | font-size: 14px; 37 | color: rgba(54, 55, 64, 1); 38 | div { 39 | color: rgba(106, 107, 113, 0.85); 40 | font-weight: 400; 41 | a { 42 | color: rgba(22, 80, 255, 1); 43 | font-weight: 400; 44 | cursor: pointer; 45 | } 46 | } 47 | } 48 | &-addbtn { 49 | margin: 8px 0 10px 0; 50 | &:hover { 51 | border: 1px dashed #1650ff; 52 | } 53 | } 54 | &-name:focus { 55 | border: 1px solid #1650ff; 56 | } 57 | &-attr { 58 | margin-bottom: 16px; 59 | } 60 | &-title { 61 | font-weight: 400; 62 | font-size: 14px; 63 | color: rgba(54, 55, 64, 1); 64 | } 65 | &-attributes { 66 | background-color: #1650ff; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/graph-construct/edit-nodes-edges/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-container { 3 | 4 | :global { 5 | .ant-select-selection-item-remove { 6 | display: none; 7 | } 8 | .anticon-question-circle { 9 | margin-left: 4px; 10 | } 11 | .ant-table-container { 12 | border-radius: 8px 0 0 8px; 13 | overflow: hidden; 14 | table { 15 | border-radius: 8px; 16 | overflow: hidden; 17 | } 18 | } 19 | } 20 | &-content { 21 | padding: 0 24px 24px; 22 | } 23 | &-header { 24 | height: 44px; 25 | display: flex; 26 | justify-content: space-between; 27 | align-items: center; 28 | font-weight: 500; 29 | font-size: 14px; 30 | color: rgba(54, 55, 64, 1); 31 | div { 32 | color: rgba(106, 107, 113, 0.85); 33 | font-weight: 400; 34 | a { 35 | color: rgba(22, 80, 255, 1); 36 | font-weight: 400; 37 | cursor: pointer; 38 | } 39 | } 40 | } 41 | &-addbtn { 42 | margin: 8px 0 10px 0; 43 | } 44 | &-attr { 45 | margin-bottom: 16px; 46 | } 47 | &-title { 48 | font-weight: 400; 49 | font-size: 14px; 50 | color: rgba(54, 55, 64, 1); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/graph-construct/import-data-result/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-result { 3 | background-color: #fff; 4 | height: 100%; 5 | padding-top: 120px; 6 | } 7 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/graph-query/components/model-overview/components/graph-canvas-mini/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-view-graph-model-canvas { 3 | position: relative; 4 | height: 100%; 5 | 6 | :global { 7 | .@{perfix-cls}-graph-canvas-tools { 8 | position: absolute; 9 | bottom: 4px; 10 | right: 0px; 11 | } 12 | .graphin-core { 13 | min-height: auto !important; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/graph-query/components/model-overview/components/graph-canvas-mini/index.tsx: -------------------------------------------------------------------------------- 1 | import { GraphinContext } from '@antv/graphin'; 2 | import { join } from 'lodash'; 3 | import { useContext, useEffect } from 'react'; 4 | import { 5 | GraphCanvas, 6 | GraphCanvasContext, 7 | useGraphinContext, 8 | } from '@/components/studio/components/garph-canvas'; 9 | import { GraphCanvasTools } from '@/components/studio/components/graph-canvas-tools'; 10 | import { PUBLIC_PERFIX_CLASS } from '@/components/studio/constant'; 11 | 12 | import styles from './index.module.less'; 13 | 14 | const GraphCanvasMini = ({ 15 | getGraphCanvasContextValue, 16 | graphData, 17 | graphCanvasContextValue, 18 | }: GraphCanvasMiniProps) => { 19 | const { apis } = useGraphinContext(); 20 | const { apis: contextApis } = useContext(GraphinContext); 21 | useEffect(() => { 22 | if (apis) { 23 | apis.handleAutoZoom(); 24 | } 25 | if (contextApis.handleAutoZoom) { 26 | contextApis.handleAutoZoom(); 27 | } 28 | }, [apis, contextApis]); 29 | return ( 30 | 31 |
40 | 47 | 48 |
49 |
50 | ); 51 | }; 52 | 53 | export default GraphCanvasMini; 54 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/graph-query/components/model-overview/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-model-overview { 3 | position: relative; 4 | background: #fff; 5 | width: 100%; 6 | border-left: 1px solid rgba(0, 0, 0, 0.06); 7 | height: 100%; 8 | 9 | &__open { 10 | width: 350px; 11 | } 12 | 13 | transition: all 0.3s ease; 14 | 15 | :global { 16 | .@{perfix-cls}-text-tabs { 17 | height: 36px !important; 18 | overflow: hidden; 19 | } 20 | 21 | .@{perfix-cls}-text-tabs-card .@{perfix-cls}-text-tabs-item { 22 | padding: 7px !important; 23 | } 24 | } 25 | 26 | &-content { 27 | padding: 20px 24px; 28 | height: calc(100% - 36px); 29 | overflow: auto; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/graph-query/components/path-query/path-modal/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-path-modal { 3 | .@{perfix-cls}-div-select { 4 | width: 138px; 5 | margin-right: 8px; 6 | :global { 7 | .ant-select-selector { 8 | border-radius: 6px; 9 | } 10 | } 11 | } 12 | .@{perfix-cls}-div-ipt { 13 | width: 208px; 14 | border-radius: 6px; 15 | :global { 16 | .ant-select-selector { 17 | border-radius: 6px; 18 | } 19 | } 20 | } 21 | :global { 22 | .ant-col-12 { 23 | display: flex; 24 | justify-content: flex-end; 25 | } 26 | .ant-input-group-compact { 27 | width: 380px; 28 | } 29 | .ant-input-number { 30 | width: 146px; 31 | border-radius: 8px; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/graph-query/components/stored-procedure/stored-checkout/index.tsx: -------------------------------------------------------------------------------- 1 | import { cpp } from '@codemirror/lang-cpp'; 2 | import { java } from '@codemirror/lang-java'; 3 | import { python } from '@codemirror/lang-python'; 4 | import CodeMirror from '@uiw/react-codemirror'; 5 | import { Drawer } from 'antd'; 6 | import React from 'react'; 7 | type Prop = { 8 | visible: boolean; 9 | onClose: () => void; 10 | value: string; 11 | }; 12 | export const StoredCheckoutDrawer: React.FC = ({ 13 | visible, 14 | onClose, 15 | value, 16 | }) => { 17 | return ( 18 | 25 | 30 | 31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/graph-query/components/stored-procedure/stored-download/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-form-radio { 3 | :global { 4 | .ant-radio-group { 5 | width: 100%; 6 | display: flex; 7 | justify-content: space-between; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/graph-query/components/stored-procedure/stored-khop-panle/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-container { 3 | height: 100%; 4 | padding-bottom: 8px; 5 | display: flex; 6 | 7 | &-box { 8 | &-text { 9 | color: rgba(54, 55, 64, 1); 10 | font-size: 12px; 11 | line-height: 20px; 12 | margin-bottom: 8px; 13 | display: flex; 14 | align-items: center; 15 | justify-content: space-between; 16 | &-right { 17 | display: flex; 18 | align-items: center; 19 | :global { 20 | .ant-input-number-input { 21 | height: 22px; 22 | } 23 | } 24 | } 25 | } 26 | padding: 16px; 27 | height: 100%; 28 | width: 50%; 29 | &-content { 30 | height: calc(100% - 28px); 31 | :global { 32 | .ant-input { 33 | height: 100%; 34 | } 35 | } 36 | &-text { 37 | border: 1px solid #d9d9d9; 38 | border-radius: 8px; 39 | height: 100%; 40 | padding: 16px 24px; 41 | } 42 | } 43 | } 44 | } 45 | .@{perfix-cls}-container-empty { 46 | justify-content: center; 47 | :global { 48 | .ant-empty-normal { 49 | position: absolute; 50 | height: 70px; 51 | top: 50%; 52 | transform: translateY(-50%); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/graph-query/components/stored-procedure/stored-list/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-list { 3 | width: 290px; 4 | padding: 16px 24px; 5 | border-right: 1px solid #f2f2f2; 6 | &-search { 7 | display: flex; 8 | align-items: center; 9 | gap: 12px; 10 | margin-bottom: 12px; 11 | :global { 12 | .ant-select { 13 | width: 170px; 14 | } 15 | .anticon-plus-squar { 16 | font-size: 16px; 17 | color: #1650ff; 18 | } 19 | .ant-btn { 20 | color: #1650ff; 21 | padding: 0; 22 | } 23 | } 24 | } 25 | :global { 26 | .ant-select-selector { 27 | background: #f6f6f6; 28 | } 29 | .ant-select-selection-search-input { 30 | border: none; 31 | } 32 | .ant-collapse { 33 | background-color: #fff !important; 34 | .ant-collapse-item:nth-of-type(2) { 35 | .ant-collapse-header { 36 | margin-top: 28px; 37 | } 38 | } 39 | .ant-collapse-header { 40 | background-color: #fff; 41 | padding: 6px 16px; 42 | } 43 | .ant-collapse-content-box { 44 | background-color: #fff; 45 | padding: 4px 0 34px 0; 46 | } 47 | } 48 | } 49 | &-item { 50 | height: 32px; 51 | display: flex; 52 | align-items: center; 53 | cursor: pointer; 54 | background-color: #fff; 55 | border-radius: 8px; 56 | padding: 16px; 57 | &:hover { 58 | background-color: #f6f6f6; 59 | } 60 | :global { 61 | .anticon { 62 | margin-right: 8px; 63 | color: rgba(106, 107, 113, 1); 64 | } 65 | } 66 | } 67 | &-active { 68 | background-color: #f6f6f6; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/graph-query/components/stored-procedure/stored-list/stored-form/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-form-radio { 3 | :global { 4 | .ant-radio-group { 5 | width: 100%; 6 | display: flex; 7 | justify-content: space-between; 8 | } 9 | } 10 | } 11 | .@{perfix-cls}-readonly { 12 | margin-bottom: 11px; 13 | } 14 | :global { 15 | .ant-select-item-option-content { 16 | justify-content: space-between; 17 | } 18 | } 19 | .@{perfix-cls}-readonly-horizontal { 20 | margin-bottom: 11px; 21 | :global { 22 | .ant-form-item-control { 23 | width: auto !important; 24 | } 25 | .ant-form-item-label { 26 | line-height: 32px; 27 | margin-right: 12px; 28 | } 29 | } 30 | } 31 | 32 | .@{perfix-cls}-popover-img { 33 | width: 285px; 34 | height: 241.5px; 35 | user-select: none; 36 | image-rendering: -moz-crisp-edges; 37 | image-rendering: -o-crisp-edges; 38 | image-rendering: -webkit-optimize-contrast; 39 | image-rendering: crisp-edges; 40 | -ms-interpolation-mode: nearest-neighbor; 41 | } 42 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/graph-query/components/stored-procedure/stored-result/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-container { 3 | height: 100%; 4 | &-header { 5 | line-height: 36px; 6 | background-color: #f7f8ff; 7 | font-weight: 400; 8 | padding: 0 22px; 9 | color: rgba(54, 55, 64, 1); 10 | } 11 | :global { 12 | .ant-empty-normal { 13 | top: 50%; 14 | left: 50%; 15 | position: absolute; 16 | transform: translate(-50%, -50%); 17 | } 18 | } 19 | } 20 | .@{perfix-cls}-container-content { 21 | flex: 1; 22 | height: 100%; 23 | display: flex; 24 | flex-direction: column; 25 | overflow: auto; 26 | padding: 12px; 27 | } 28 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/graph-query/components/stored-procedure/stored-result/index.tsx: -------------------------------------------------------------------------------- 1 | import { Empty } from 'antd'; 2 | import React from 'react'; 3 | import ReactJson from 'react-json-view'; 4 | import { PUBLIC_PERFIX_CLASS } from '@/components/studio/constant'; 5 | 6 | import styles from './index.module.less'; 7 | 8 | type Prop = { 9 | result: any; 10 | }; 11 | export const StoredResult: React.FC = ({ result }) => { 12 | return ( 13 |
14 |
15 | 执行结果 16 |
17 |
18 | {result ? ( 19 | 20 | ) : ( 21 | 22 | )} 23 |
24 |
25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/graph-query/cypherEditor.less: -------------------------------------------------------------------------------- 1 | .monaco-editor .margin-view-overlays .line-numbers { 2 | text-align: center; 3 | } -------------------------------------------------------------------------------- /src/components/studio/domain-core/graph-query/interface/excecute.ts: -------------------------------------------------------------------------------- 1 | export enum ExcecuteHistoryTabEnum { 2 | plan = '执行计划', 3 | efficiency = '时效分析', 4 | history = '执行历史', 5 | } 6 | export type ExcecuteHistoryTabKey = keyof typeof ExcecuteHistoryTabEnum; 7 | export interface ExcecuteHistoryTabDesc { 8 | text: string; 9 | key: ExcecuteHistoryTabKey; 10 | } 11 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/graph-query/utils/editEdgeParamsTransform.ts: -------------------------------------------------------------------------------- 1 | import { find } from 'lodash'; 2 | export const editEdgeParamsTransform = (sourceModel, targetModel, nodes) => { 3 | const sourceLabel = sourceModel?.label; 4 | const targetLabel = targetModel?.label; 5 | const sourceNode = find(nodes, (node) => node.labelName === sourceLabel); 6 | const targetNode = find(nodes, (node) => node.labelName === targetLabel); 7 | const sourcePrimaryKey = sourceNode?.primaryField; 8 | const targetPrimaryKey = targetNode?.primaryField; 9 | const sourceValue = sourceModel.properties[sourcePrimaryKey]; 10 | const targetValue = targetModel.properties[targetPrimaryKey]; 11 | return { sourceLabel, targetLabel, sourcePrimaryKey, targetPrimaryKey, sourceValue, targetValue }; 12 | }; 13 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/graph-query/utils/getConnectOptions.ts: -------------------------------------------------------------------------------- 1 | import { includes } from 'lodash'; 2 | import { CONNECT, CONNECT_STR_TYPE } from '@/components/studio/constant'; 3 | export const getConnectOptions = (type: string) => { 4 | if (includes(CONNECT_STR_TYPE, type)) { 5 | return CONNECT['string']; 6 | } 7 | return CONNECT['number']; 8 | }; 9 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/project/project-list/components/add-tugraph/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-add-modal-container { 3 | :global { 4 | .ant-modal-body { 5 | padding: 0 69px; 6 | .ant-steps { 7 | padding: 0 155px 40px; 8 | } 9 | .ant-pagination { 10 | margin: 25px 0; 11 | display: inline-block; 12 | position: absolute; 13 | right: 15px; 14 | bottom: -75px; 15 | } 16 | .ant-col { 17 | margin-bottom: 16px; 18 | } 19 | } 20 | } 21 | .@{perfix-cls}-stencil-container { 22 | &-row { 23 | margin-left: 16px !important; 24 | } 25 | display: flex; 26 | flex-wrap: wrap; 27 | justify-content: space-between; 28 | height: 482px; 29 | align-content: space-between; 30 | margin-bottom: 75px; 31 | position: relative; 32 | } 33 | .@{perfix-cls}-creat-step { 34 | height: 482px; 35 | padding: 0 155px; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/project/project-list/components/edit-form/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-input { 3 | margin-bottom: 0; 4 | } 5 | .@{perfix-cls}-input-text { 6 | height: 22px; 7 | width: 216px; 8 | font-weight: 400; 9 | font-size: 12px; 10 | color: #98989d; 11 | line-height: 22px; 12 | margin-bottom: 24px; 13 | } 14 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/project/project-list/components/edit-tugraph/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-modal-container { 3 | :global { 4 | .ant-modal-body { 5 | padding: 0 36px; 6 | min-height: 350px; 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/project/project-list/components/edit-tugraph/index.tsx: -------------------------------------------------------------------------------- 1 | import { Form, Modal, message } from 'antd'; 2 | import React, { useEffect } from 'react'; 3 | import { PUBLIC_PERFIX_CLASS } from '@/components/studio/constant'; 4 | import { useGraph } from '@/components/studio/hooks/useGraph'; 5 | import EditForm from '../edit-form'; 6 | 7 | import styles from './index.module.less'; 8 | 9 | type Prop = { 10 | open: boolean; 11 | onClose: () => void; 12 | detail: { graphName: string; description: string; maxSizeGB: number }; 13 | onRefreshProjectList?: () => void; 14 | }; 15 | const EditTuGraphMoadl: React.FC = ({ 16 | open, 17 | onClose, 18 | detail, 19 | onRefreshProjectList, 20 | }) => { 21 | const [form] = Form.useForm(); 22 | useEffect(() => { 23 | form.setFieldsValue({ 24 | graphName: detail.graphName, 25 | description: detail.description, 26 | maxSizeGB: detail.maxSizeGB, 27 | }); 28 | }, []); 29 | const { onEditGraph } = useGraph(); 30 | return ( 31 | { 37 | form.validateFields().then((values) => { 38 | const { graphName, description, maxSizeGB } = values; 39 | onEditGraph({ graphName, config: { description, maxSizeGB } }).then( 40 | (res) => { 41 | if (res.success) { 42 | message.success('修改成功'); 43 | onRefreshProjectList?.(); 44 | onClose(); 45 | } 46 | } 47 | ); 48 | }); 49 | }} 50 | width={560} 51 | okText="确认" 52 | cancelText="取消" 53 | > 54 | 55 | 56 | ); 57 | }; 58 | export default EditTuGraphMoadl; 59 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/project/project-list/components/empty-project/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | .@{perfix-cls}-empty-project { 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | flex-direction: column; 7 | background-color: #fff; 8 | border-radius: 8px; 9 | height: 553px; 10 | margin-top: 25px; 11 | 12 | .@{perfix-cls}-desc { 13 | color: #000; 14 | font-size: 14px; 15 | font-family: PingFangSC-Regular; 16 | opacity: 0.45; 17 | } 18 | 19 | .@{perfix-cls}-btn { 20 | margin-top: 24px; 21 | 22 | :global { 23 | .ant-btn { 24 | width: 102px; 25 | height: 36px; 26 | border-radius: 6px; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/studio/domain-core/project/project-list/components/empty-project/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Button } from "antd"; 3 | import { PUBLIC_PERFIX_CLASS } from "@/components/studio/constant"; 4 | 5 | import styles from "./index.module.less"; 6 | 7 | interface EmptyProjectProps { 8 | onCreateProject: () => void; 9 | } 10 | 11 | const EmptyProject: React.FC = ({ onCreateProject }) => { 12 | return ( 13 |
14 | 15 |
16 | 你当前没有图项目,请先新建图项目 17 |
18 |
19 | 22 |
23 |
24 | ); 25 | }; 26 | export default EmptyProject; 27 | -------------------------------------------------------------------------------- /src/components/studio/hooks/useImport.ts: -------------------------------------------------------------------------------- 1 | import { useRequest } from 'ahooks'; 2 | import { importSchemaMod } from '@/services/schema'; 3 | import { useModel } from 'umi'; 4 | import { InitialState } from '@/app'; 5 | import { importData } from '@/services/info'; 6 | 7 | export const useImport = () => { 8 | const { initialState } = useModel('@@initialState'); 9 | const { driver } = initialState as InitialState; 10 | 11 | const { 12 | runAsync: onImportData, 13 | loading: importDataLoading, 14 | error: importDataError, 15 | } = useRequest(params => importData({ driver, ...params }), { manual: true }); 16 | 17 | 18 | 19 | const { 20 | runAsync: onImportGraphSchema, 21 | loading: ImportGraphSchemaLoading, 22 | error: ImportGraphSchemaError, 23 | } = useRequest(params => importSchemaMod(driver, params), { manual: true }); 24 | 25 | return { 26 | useImport, 27 | importDataLoading, 28 | onImportData, 29 | importDataError, 30 | onImportGraphSchema, 31 | ImportGraphSchemaLoading, 32 | ImportGraphSchemaError, 33 | }; 34 | }; 35 | -------------------------------------------------------------------------------- /src/components/studio/hooks/useQuery.ts: -------------------------------------------------------------------------------- 1 | import { useRequest } from 'ahooks'; 2 | import { queryByGraphLanguage, queryByNode, queryByPath } from '@/services/query'; 3 | import { useModel } from 'umi'; 4 | import { InitialState } from '@/app'; 5 | 6 | export const useQuery = () => { 7 | const { initialState } = useModel('@@initialState'); 8 | const { driver } = initialState as InitialState; 9 | 10 | const { 11 | runAsync: onStatementQuery, 12 | loading: StatementQueryLoading, 13 | error: StatementQueryError, 14 | } = useRequest(params=>queryByGraphLanguage(driver,params), { manual: true }); 15 | 16 | const { 17 | runAsync: onNodeQuery, 18 | loading: NodeQueryLoading, 19 | error: NodeQueryError, 20 | } = useRequest(params=>queryByNode(driver,params), { manual: true }); 21 | 22 | const { 23 | runAsync: onPathQuery, 24 | loading: PathQueryLoading, 25 | error: PathQueryError, 26 | } = useRequest(params=>queryByPath(driver,params), { manual: true }); 27 | 28 | return { 29 | onStatementQuery, 30 | StatementQueryLoading, 31 | StatementQueryError, 32 | onNodeQuery, 33 | NodeQueryLoading, 34 | NodeQueryError, 35 | onPathQuery, 36 | PathQueryLoading, 37 | PathQueryError, 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /src/components/studio/hooks/useVisible.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from "react"; 2 | 3 | export const useVisible = (options?: { 4 | afterShow?: () => void; 5 | afterClose?: () => void; 6 | defaultVisible?: boolean; 7 | }) => { 8 | const { afterClose, afterShow, defaultVisible } = options || {}; 9 | const [visible, setVisible] = useState(!!defaultVisible); 10 | const onShow = useCallback(() => { 11 | setVisible(true); 12 | if (afterShow) { 13 | afterShow(); 14 | } 15 | }, []); 16 | const onClose = useCallback(() => { 17 | setVisible(false); 18 | if (afterClose) { 19 | afterClose(); 20 | } 21 | }, []); 22 | return { 23 | visible, 24 | onShow, 25 | onClose, 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /src/components/studio/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 图研发 3 | nav: 4 | title: 图研发 5 | order: 1 6 | group: 7 | title: TuGraph 8 | order: 1 9 | --- 10 | 11 | ```jsx 12 | import React from 'react'; 13 | import { GraphConstruct } from './domain-core/graph-construct'; 14 | import { GraphQuery } from './domain-core/graph-query'; 15 | // import { GraphList } from './graph-list'; 16 | 17 | export default () => ; 18 | ``` 19 | -------------------------------------------------------------------------------- /src/components/studio/index.ts: -------------------------------------------------------------------------------- 1 | export { GraphConstruct } from './domain-core/graph-construct'; 2 | export { GraphQuery } from './domain-core/graph-query'; 3 | export { GraphList } from './graph-list'; 4 | export { Login } from './login'; 5 | export { UserCenter } from './user-center'; 6 | export * from './utils'; 7 | -------------------------------------------------------------------------------- /src/components/studio/interface/graph.ts: -------------------------------------------------------------------------------- 1 | export interface SubGraph { 2 | graph_name: string; 3 | configuration: { 4 | description?: string; 5 | max_size_GB?: number; 6 | }; 7 | } 8 | 9 | export interface SubGraphInfo { 10 | isStatistics: boolean; 11 | description: string; 12 | maxSizeGB?: number; 13 | graphName: string; 14 | nodeEdgeStats?: any; 15 | isNodeEdgeObj?: boolean; 16 | } 17 | -------------------------------------------------------------------------------- /src/components/studio/interface/graphData.ts: -------------------------------------------------------------------------------- 1 | type CreateNode = { 2 | graphName: string; 3 | labelName: string; 4 | properties: Record; 5 | }; 6 | 7 | type CreateEdge = { 8 | graphName: string; 9 | sourceLabel: string; 10 | targetLabel: string; 11 | sourcePrimaryKey: string; 12 | sourceValue: string; 13 | targetPrimaryKey: string; 14 | targetValue: string; 15 | labelName: string; 16 | properties: Record; 17 | }; 18 | 19 | type EditNode = { 20 | graphName: string; 21 | primaryKey: string; 22 | primaryValue: string; 23 | labelName: string; 24 | properties: Record; 25 | }; 26 | 27 | type EditEdge = { 28 | graphName?: string; 29 | sourceLabel?: string; 30 | targetLabel?: string; 31 | sourcePrimaryKey?: string; 32 | sourceValue?: string; 33 | targetPrimaryKey?: string; 34 | targetValue?: string; 35 | labelName?: string; 36 | properties?: Record; 37 | }; 38 | type DeleteNode = { 39 | labelName: string; 40 | graphName: string; 41 | primaryKey: string; 42 | primaryValue: string; 43 | }; 44 | type DeleteEdge = { 45 | graphName: string; 46 | sourceLabel: string; 47 | targetLabel: string; 48 | sourcePrimaryKey: string; 49 | sourceValue: string; 50 | targetPrimaryKey: string; 51 | targetValue: string; 52 | labelName: string; 53 | }; 54 | -------------------------------------------------------------------------------- /src/components/studio/interface/import.ts: -------------------------------------------------------------------------------- 1 | import { ColumnsType } from 'antd/lib/table'; 2 | import { RcFile } from 'antd/lib/upload'; 3 | 4 | export interface CheckFileParams { 5 | fileName: string; 6 | flag: string; 7 | checkSum?: string; // 文件的md5值 8 | fileSize?: string; 9 | } 10 | 11 | export interface UploadFileParams { 12 | 'File-Name': string; 13 | 'Begin-Pos': string; //开始位置在文件内的偏移 14 | Size: string; 15 | } 16 | 17 | export interface SchemaProperty { 18 | name: string; 19 | type: string; 20 | optional?: boolean; 21 | unique?: boolean; 22 | index?: boolean; 23 | } 24 | 25 | export interface Schema { 26 | label: string; 27 | type: string; 28 | properties?: SchemaProperty[]; 29 | primary?: string; 30 | constraints?: string[][]; 31 | } 32 | 33 | 34 | export interface FileSchema { 35 | path: string; 36 | columns: string[]; 37 | label: string; 38 | format: 'CSV' | 'JSON'; 39 | header?: number; 40 | SRC_ID?: string; 41 | DST_ID?: string; 42 | } 43 | 44 | export interface ImportDataParams { 45 | graph: string; 46 | schema: { 47 | files: FileSchema[]; 48 | }; 49 | delimiter: string; 50 | continueOnError?: boolean; 51 | skipPackages?: string; 52 | taskId?: string; 53 | flag?: string; 54 | } 55 | 56 | export interface ImportSchemaParams { 57 | graph: string; 58 | schema: Schema[]; 59 | } 60 | 61 | export interface LabelOption { 62 | value: string | number; 63 | label: string; 64 | children?: LabelOption[]; 65 | } 66 | 67 | export interface FileData { 68 | fileName: string; 69 | formateSize: string; 70 | status: 'success' | 'error' | 'processing'; 71 | file?: RcFile; 72 | fileSchema: FileSchema; 73 | data?: { 74 | columns: ColumnsType; 75 | dataSource: DataType[]; 76 | } | null; 77 | labelOptions?: LabelOption[]; 78 | selectedValue?: string[] 79 | } 80 | 81 | export type DataType = Record; 82 | -------------------------------------------------------------------------------- /src/components/studio/interface/procedure.ts: -------------------------------------------------------------------------------- 1 | export type UploadProcedureParams = { 2 | graphName: string; 3 | procedureType: 'cpp' | 'python'; 4 | procedureName: string; 5 | content: string; 6 | codeType: string; 7 | description: string; 8 | readonly: string; 9 | version: string; 10 | }; 11 | export type ProcedureParams = { 12 | graphName: string; 13 | procedureType: 'cpp' | 'python' | 'any'; 14 | version: string; 15 | }; 16 | export type ProcedureItemParams = { 17 | name?: string; 18 | version?: 'v1' | 'v2'; 19 | description?: string; 20 | read_only?: string; 21 | signature?: string; 22 | type: 'cpp' | 'python'; 23 | }; 24 | export type ProcedureCode = { 25 | graphName: string; 26 | procedureType: string; 27 | procedureName: string; 28 | }; 29 | export type DeleteProcedure = { 30 | graphName: string; 31 | procedureType: string; 32 | procedureName: string; 33 | }; 34 | export type CallProcedure = { 35 | graphName: string; 36 | procedureType: string; 37 | procedureName: string; 38 | timeout: number; 39 | inProcess: boolean; 40 | param: string; 41 | version: 'v1' | 'v2'; 42 | }; 43 | -------------------------------------------------------------------------------- /src/components/studio/interface/query.ts: -------------------------------------------------------------------------------- 1 | export type Condition = { 2 | property: string; 3 | value: string; 4 | operator: string; 5 | }; 6 | export type StatementParams = { 7 | graphName: string; 8 | script?: string; 9 | }; 10 | export type NodeQueryParams = { 11 | graphName: string; 12 | nodes?: Array; 13 | limit?: number; 14 | conditions?: Array; 15 | }; 16 | export type PathQueryParams = { 17 | graphName: string; 18 | limit?: number; 19 | conditions?: Array; 20 | path: string; 21 | }; 22 | export type Edgeproperties = { 23 | end_time: number; 24 | start_time: number; 25 | }; 26 | export type FormatDataEdgeProp = { 27 | direction: string; 28 | id: string; 29 | label: string; 30 | properties: Array; 31 | source: string; 32 | target: string; 33 | }; 34 | export type Nodeproperties = { 35 | confirmed_at: string; 36 | id: string; 37 | is_confirmed: boolean; 38 | name: string; 39 | }; 40 | export type FormatDataNodeProp = { 41 | id: string; 42 | label: string; 43 | properties: Array; 44 | }; 45 | export type FormatData = { 46 | nodes: Array; 47 | edges: Array; 48 | }; 49 | export type ExcecuteResultProp = { 50 | data: { 51 | formatData?: FormatData; 52 | originalData?: any; 53 | }; 54 | success?: boolean; 55 | script: string; 56 | }; 57 | -------------------------------------------------------------------------------- /src/components/studio/login/particles-config.ts: -------------------------------------------------------------------------------- 1 | import type { ISourceOptions } from 'tsparticles-engine'; 2 | const particlesOptions: ISourceOptions = { 3 | style: { 4 | width: '100%', 5 | height: '100%', 6 | }, 7 | fpsLimit: 120, 8 | fullScreen: { 9 | enable: false, 10 | }, 11 | interactivity: { 12 | events: { 13 | onClick: { 14 | enable: true, 15 | mode: 'push', 16 | }, 17 | onHover: { 18 | enable: true, 19 | mode: 'grab', 20 | }, 21 | resize: true, 22 | }, 23 | modes: { 24 | push: { 25 | quantity: 4, 26 | }, 27 | grab: { 28 | distance: '200', 29 | }, 30 | }, 31 | }, 32 | particles: { 33 | color: { 34 | value: '#7999fa', 35 | }, 36 | links: { 37 | color: '#D8DCE9', 38 | distance: 150, 39 | enable: true, 40 | width: 1, 41 | }, 42 | collisions: { 43 | enable: false, 44 | }, 45 | move: { 46 | direction: 'none', 47 | enable: true, 48 | random: false, 49 | speed: 2, 50 | straight: false, 51 | }, 52 | number: { 53 | density: { 54 | enable: true, 55 | area: 800, 56 | }, 57 | value: 80, 58 | }, 59 | opacity: { 60 | value: 0.9, 61 | }, 62 | shape: { 63 | type: 'circle', 64 | }, 65 | size: { 66 | value: { min: 1, max: 8 }, 67 | }, 68 | }, 69 | detectRetina: true, 70 | }; 71 | 72 | export default particlesOptions; 73 | -------------------------------------------------------------------------------- /src/components/studio/services/ProcedureController.ts: -------------------------------------------------------------------------------- 1 | import request from 'umi-request'; 2 | 3 | import { PROXY_HOST } from '@/constants'; 4 | import { getLocalData } from '../utils/localStorage'; 5 | 6 | 7 | 8 | 9 | /* 获取procedure demo */ 10 | export async function getProcedureDemo(params: { type: string }) { 11 | return request(`${PROXY_HOST}/api/get_procedure_demo`, { 12 | method: 'POST', 13 | headers: { 14 | Authorization: getLocalData('TUGRAPH_TOKEN'), 15 | }, 16 | data: { 17 | ...params, 18 | }, 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /src/components/studio/services/SchemaController.ts: -------------------------------------------------------------------------------- 1 | import { LabelSchema, } from '../interface/schema'; 2 | import { createSchema } from '@/services/schema'; 3 | import { Driver } from 'neo4j-driver'; 4 | 5 | 6 | export async function createLabelSchema(driver:Driver,params: LabelSchema) { 7 | const param = { 8 | graphName: params?.graphName, 9 | labelType: params?.labelType, 10 | labelName: params?.labelName, 11 | properties: params?.properties, 12 | ...(params?.labelType === 'node' 13 | ? { primaryField: params?.primaryField, indexs: params?.indexs } 14 | : { edgeConstraints: params?.edgeConstraints }), 15 | }; 16 | return createSchema(driver,param); 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/components/studio/styles/mixin.less: -------------------------------------------------------------------------------- 1 | // color 2 | // border 3 | @border-color-base: #d6d8da; 4 | @border-radius: 6px; 5 | 6 | // bg 7 | @bg-color-page: #fff; 8 | 9 | // font 10 | @font-size-normal: 14px; 11 | @font-size-large: 20px; 12 | @font-size-small: 12px; 13 | -------------------------------------------------------------------------------- /src/components/studio/user-center/index.module.less: -------------------------------------------------------------------------------- 1 | @perfix-cls: ant-tugraph; 2 | 3 | .@{perfix-cls}-help { 4 | 5 | cursor: pointer; 6 | 7 | &:hover { 8 | opacity: 0.8; 9 | } 10 | 11 | &-tips { 12 | margin-left: 6px; 13 | } 14 | } -------------------------------------------------------------------------------- /src/components/studio/utils/bigNumberTransform.ts: -------------------------------------------------------------------------------- 1 | export const bigNumberTransform = (value: number | undefined) => { 2 | if (!value) { 3 | return ""; 4 | } 5 | let numberValue; 6 | let unit: string = ""; 7 | let minNumber: number = 10000; 8 | let sizes: string[] = ["", "万", "亿", "万亿"]; 9 | let i: number; 10 | if (value < minNumber) { 11 | numberValue = value; 12 | } else { 13 | i = Math.floor(Math.log(value) / Math.log(minNumber)); 14 | numberValue = (value / Math.pow(minNumber, i)).toFixed(2); 15 | unit = sizes[i]; 16 | } 17 | return numberValue + unit; 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/studio/utils/downloadFile.ts: -------------------------------------------------------------------------------- 1 | export const downloadFile = (content: any, filename?: string) => { 2 | const eleLink = document.createElement('a'); 3 | eleLink.download = filename || 'file'; 4 | eleLink.style.display = 'none'; 5 | const blob = new Blob([content]); 6 | eleLink.href = URL.createObjectURL(blob); 7 | document.body.appendChild(eleLink); 8 | eleLink.click(); 9 | document.body.removeChild(eleLink); 10 | }; 11 | 12 | export const downloadRemoteFile = (url: string, input?: RequestInit) => { 13 | return fetch(url, { ...input, credentials: 'include' }).then((res) => { 14 | return res.blob().then((blob) => { 15 | // blob用来创建URL的File对象 16 | const a = document.createElement('a'); 17 | // createObjectURL将一个媒体元素的src属性关联到一个 MediaSource 对象 18 | const downloadUrl = window.URL.createObjectURL(blob); 19 | const cd = res.headers.get('Content-disposition') || ''; 20 | const filename = cd.split('=')[1]; 21 | a.href = downloadUrl; 22 | a.download = decodeURIComponent(filename); 23 | a.click(); 24 | // revokeObjectURL使这个潜在的对象保留在原来的地方,允许平台在合适的时机进行垃圾收集。 25 | // window.URL.revokeObjectURL(url); 26 | return { success: true } as { success: boolean; message: string }; 27 | }); 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /src/components/studio/utils/getDefaultDemoList.ts: -------------------------------------------------------------------------------- 1 | import { filter, includes } from 'lodash'; 2 | import { TUGRAPH_DEOM_NAME } from '../constant'; 3 | export const getDefaultDemoList = (list) => { 4 | return [ 5 | ...filter(list, (item) => includes(TUGRAPH_DEOM_NAME, item.graphName)), 6 | ...filter(list, (item) => !includes(TUGRAPH_DEOM_NAME, item.graphName)), 7 | ]; 8 | }; 9 | -------------------------------------------------------------------------------- /src/components/studio/utils/getFileSizeTransform.ts: -------------------------------------------------------------------------------- 1 | export const getFileSizeTransform = (size: number) => { 2 | let fileSize; 3 | if (size / 1024 < 1) { 4 | fileSize = `${size} B`; 5 | } 6 | if (size / 1024 >= 1 && size / 1024 ** 2 < 1) { 7 | fileSize = `${(size / 1024).toFixed(2)} KB`; 8 | } 9 | if (size / 1024 ** 2 >= 1 && size / 1024 ** 3 < 1) { 10 | fileSize = `${(size / 1024 ** 2).toFixed(2)} MB`; 11 | } 12 | if (size / 1024 ** 3 >= 1) { 13 | fileSize = `${(size / 1024 ** 3).toFixed(2)} GB`; 14 | } 15 | return fileSize || ''; 16 | }; 17 | -------------------------------------------------------------------------------- /src/components/studio/utils/getZhPeriod.ts: -------------------------------------------------------------------------------- 1 | export const getZhPeriod = () => { 2 | const timeNow = new Date(); 3 | const hours = timeNow.getHours(); 4 | let text = ``; 5 | if (hours >= 0 && hours <= 10) { 6 | text = `早上好`; 7 | } else if (hours > 10 && hours <= 14) { 8 | text = `中午好`; 9 | } else if (hours > 14 && hours <= 18) { 10 | text = `下午好`; 11 | } else if (hours > 18 && hours <= 24) { 12 | text = `晚上好`; 13 | } 14 | return text; 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/studio/utils/graphTranslator.ts: -------------------------------------------------------------------------------- 1 | import { map } from 'lodash'; 2 | import { SubGraph } from '../interface/graph'; 3 | 4 | export const getGraphListTranslator = (graphList: SubGraph[]) => { 5 | if (graphList?.length === 0) { 6 | return []; 7 | } 8 | return map(graphList, (graph: SubGraph) => { 9 | return { 10 | isStatistics: false, 11 | description: graph.configuration?.description, 12 | maxSizeGB: graph.configuration?.max_size_GB, 13 | graphName: graph.graph_name, 14 | nodeEdgeStats: {}, 15 | isNodeEdgeObj: false, 16 | }; 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/studio/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './bigNumberTransform'; 2 | export * from './localStorage'; 3 | export * from './getZhPeriod'; 4 | -------------------------------------------------------------------------------- /src/components/studio/utils/localStorage.ts: -------------------------------------------------------------------------------- 1 | export const getLocalData = (key: string) => { 2 | if (!key) { 3 | return; 4 | } 5 | try { 6 | const data = JSON.parse(localStorage.getItem(key) || ''); 7 | return data; 8 | } catch (e) { 9 | console.error(`tugraph ${key} %d ${e}`); 10 | } 11 | }; 12 | 13 | export const setLocalData = (key: string, data: any) => { 14 | if (!key) { 15 | return; 16 | } 17 | localStorage.setItem(key, JSON.stringify(data)); 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/studio/utils/objectOper.ts: -------------------------------------------------------------------------------- 1 | export const batchSecureDeletion = (obj: any, delKeys: string[]) => { 2 | if (Array.isArray(delKeys) && delKeys?.length) { 3 | return delKeys.every(key => { 4 | if (new Object(obj).hasOwnProperty(key)) { 5 | delete obj[key]; 6 | return true; 7 | } else { 8 | return false; 9 | } 10 | }); 11 | } 12 | return false; 13 | }; 14 | -------------------------------------------------------------------------------- /src/components/studio/utils/parseCsv.ts: -------------------------------------------------------------------------------- 1 | import type { ColumnsType } from 'antd/es/table'; 2 | import Papa from 'papaparse'; 3 | import { DataType } from '../interface/import'; 4 | 5 | export const tableDataTranslator = () => {}; 6 | 7 | export const parseCsv = (file: any, delimiter = ',') => { 8 | return new Promise((resolve, reject) => { 9 | Papa.parse(file, { 10 | header: false, 11 | complete: results => { 12 | resolve(results.data); 13 | }, 14 | error: error => { 15 | reject(error); 16 | }, 17 | }); 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/studio/utils/request.tsx: -------------------------------------------------------------------------------- 1 | import { message } from 'antd'; 2 | import { extend } from 'umi-request'; 3 | import { getLocalData } from './localStorage'; 4 | import { PROXY_HOST } from '@/constants'; 5 | 6 | export const HOLD_TIME = 2.5; 7 | 8 | const request = extend({ 9 | headers: { 10 | 'Content-Type': 'application/json', 11 | Authorization: getLocalData('TUGRAPH_TOKEN'), 12 | }, 13 | prefix: PROXY_HOST, 14 | withCredentials: true, 15 | credentials: 'include', 16 | // 默认错误处理 17 | crossOrigin: true, // 开启CORS跨域 18 | }); 19 | // 中间件 20 | request.interceptors.response.use(async response => { 21 | const data = await response.clone().json(); 22 | const TUGRAPH_TOKEN = getLocalData('TUGRAPH_TOKEN'); 23 | if ((!TUGRAPH_TOKEN && data.errorCode == 400) || data.errorCode == 401) { 24 | new Promise(resolve => { 25 | setTimeout(() => { 26 | message.warning('登录过期,请重新登录', HOLD_TIME); 27 | resolve(); 28 | }, 2500); 29 | }).then(() => { 30 | // 清除掉之前的缓存 31 | localStorage.removeItem('TUGRAPH_TOKEN'); 32 | window.location.href = '/login'; 33 | }); 34 | } 35 | if (data.errorCode == 500) { 36 | message.error('请求失败' + data.errorMessage, HOLD_TIME); 37 | } 38 | return response; 39 | }); 40 | 41 | export default request; 42 | -------------------------------------------------------------------------------- /src/components/studio/utils/routeParams.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * file: handle url string 3 | * author: Allen 4 | */ 5 | 6 | import queryString from 'query-string'; 7 | 8 | /** 9 | * parse search string 10 | * @param search Search string 11 | * @returns Parsed object 12 | */ 13 | export const parseSearch = (search: string) => { 14 | return queryString.parse(search?.trim().replace(/^[?]+/g, '')); 15 | }; 16 | 17 | /** 18 | * generate search str 19 | * @param params Obj 20 | * @returns Formatted string 21 | */ 22 | export const searchFy =

>(params: P): string => { 23 | const path = location.pathname.replace('/v4', ''); 24 | const query = queryString.stringify(params); 25 | return `${path}?${query}`; 26 | }; 27 | 28 | /** 29 | * Get the value of a specified parameter from the URL 30 | * @param name String 31 | * @returns String 32 | */ 33 | export const getQueryString = (name: string) => { 34 | const reg = new RegExp(`(^|&|\\?)${name}=([^(&|#)]*)`, 'i'); 35 | const match = location.href.match(reg); 36 | if (match) { 37 | return decodeURIComponent(match[2]); 38 | } 39 | return ''; 40 | }; 41 | -------------------------------------------------------------------------------- /src/components/studio/utils/schemaTransform.ts: -------------------------------------------------------------------------------- 1 | import { map, find } from 'lodash'; 2 | import { Schema, SchemaProperty } from '../interface/import'; 3 | import { GraphData, NodeIndexProp, SchemaProperties } from '../interface/schema'; 4 | 5 | export const indexMergeToProperties = (index: NodeIndexProp[], properties: SchemaProperties[]): SchemaProperty[] => { 6 | return map(properties, (property) => { 7 | const { name, type, optional } = property; 8 | const indexMatch = find(index, (indexProp) => indexProp.propertyName === name); 9 | if (indexMatch) { 10 | return { 11 | name, 12 | type, 13 | optional, 14 | unique: indexMatch.isUnique, 15 | index: true, 16 | }; 17 | } 18 | return { 19 | name, 20 | type, 21 | optional, 22 | }; 23 | }); 24 | }; 25 | 26 | export const schemaTransform = (schema: GraphData): Schema[] => { 27 | const { nodes, edges } = schema; 28 | // 针对节点数据的转换 29 | const nodeSchemas = map(nodes, (node) => { 30 | const { labelName, primaryField, properties, index } = node; 31 | 32 | return { 33 | label: labelName, 34 | type: 'VERTEX', 35 | properties: indexMergeToProperties(index, properties), 36 | primary: primaryField, 37 | }; 38 | }); 39 | 40 | // 针对边数据的转换 41 | const edgeSchemas = edges.map((edge) => { 42 | const { labelName, properties } = edge; 43 | return { 44 | label: labelName, 45 | type: 'EDGE', 46 | properties, 47 | constraints: edge.edgeConstraints, 48 | }; 49 | }); 50 | 51 | return [...nodeSchemas, ...edgeSchemas]; 52 | }; 53 | -------------------------------------------------------------------------------- /src/components/studio/utils/uploadFile.ts: -------------------------------------------------------------------------------- 1 | import { FileData } from '../interface/import'; 2 | import { map, find, compact, isEmpty } from 'lodash'; 3 | import { GraphData } from '../interface/schema'; 4 | 5 | export const mergeFileDataList = (fileList: FileData[], file: FileData) => { 6 | if (!find(fileList, fileItem => fileItem.fileName === file.fileName)) { 7 | return [...fileList, file]; 8 | } 9 | const result = map(fileList, fileItem => { 10 | if (fileItem.fileName === file.fileName) { 11 | return file; 12 | } else { 13 | return fileItem; 14 | } 15 | }); 16 | return result; 17 | }; 18 | 19 | const mapToOption = (item: { labelName?: string }) => { 20 | if (item?.labelType == 'edge') { 21 | return item.labelName 22 | ? { value: item.labelName, label: item.labelName, } 23 | : null; 24 | } else { 25 | return item.labelName 26 | ? { value: item.labelName, label: item.labelName } 27 | : null; 28 | } 29 | }; 30 | 31 | export const cascaderOptionsTransform = (data: GraphData) => { 32 | const { nodes, edges } = data; 33 | const nodeOptions = compact(map(nodes, mapToOption)); 34 | const edgeOptions = compact(map(edges, mapToOption)); 35 | const nodeItem = isEmpty(nodeOptions) 36 | ? null 37 | : { value: 'node', label: '点', children: nodeOptions }; 38 | const edgeItem = isEmpty(edgeOptions) 39 | ? null 40 | : { value: 'edge', label: '边', children: edgeOptions }; 41 | return compact([nodeItem, edgeItem]); 42 | }; 43 | -------------------------------------------------------------------------------- /src/components/studio/utils/url.ts: -------------------------------------------------------------------------------- 1 | import queryString from 'query-string'; 2 | 3 | // 改路由参数 4 | export function addQueryParam(key: string, value: string) { 5 | const location = window.location; 6 | const hashList = location.hash?.split('?'); 7 | const query = queryString.parse((hashList?.[1] || '')?.trim()); 8 | 9 | query[key] = value; 10 | location.hash = `${hashList[0]}?${queryString.stringify(query)}`; 11 | } 12 | 13 | export function getQueryParam(key: string) { 14 | const urlParams = new URLSearchParams(window.location.search); 15 | return urlParams.get(key); 16 | } 17 | -------------------------------------------------------------------------------- /src/constants/demo_data/TheThreeBody/organization-organization-relationship.csv: -------------------------------------------------------------------------------- 1 | "水滴","舰队国际","秒杀" 2 | "水滴","太阳系","封锁" 3 | "二向箔","太阳系","清理" 4 | "终极规律号","自然选择号","攻击" 5 | "终极规律号","企业号","攻击" 6 | "终极规律号","深空号","攻击" 7 | "终极规律号","蓝色空间号","攻击" 8 | "蓝色空间号","终极规律号","攻击" 9 | "星舰地球","自然选择号","包括" 10 | "星舰地球","深空号","包括" 11 | "星舰地球","蓝色空间号","包括" 12 | "星舰地球","终极规律号","包括" 13 | "星舰地球","企业号","包括" 14 | "青铜时代号","量子号","攻击" 15 | "万有引力号","蓝色空间号","追击、合并" 16 | -------------------------------------------------------------------------------- /src/constants/demo_data/TheThreeBody/organization-plan-relationship.csv: -------------------------------------------------------------------------------- 1 | "ETO","破壁计划","提出" 2 | "PDC","面壁计划","提出" 3 | "PDC","群星计划","提出" -------------------------------------------------------------------------------- /src/constants/demo_data/TheThreeBody/organizes.csv: -------------------------------------------------------------------------------- 1 | "ETO","全称是Earth-Threebody Organization(地球三体组织),原本以“借助外来力量拯救腐朽的人类社会”为最高宗旨,其目的在后期转变为“消灭人类暴政”,希望三体文明接管地球。精神领袖为叶文洁,实际领导人为伊文斯。" 2 | "PDC","全称是Planet Defense Council(行星防御理事会),利用三体人思维透明的致命缺陷,制订“面壁计划”并选出四位面壁者" 3 | "PIA","全称Planetary Defense Council(行星防御理事会战略情报局),以三体舰队和母星为侦察目标的情报机构,在行政上由行星防御理事会(PDC)领导" 4 | "舰队国际","随着三体危机的到来,所有的国家都衰落了,但国家仍然存在。人类分为两大国际:地球国际、舰队国际。地球国际是原有的联合国,而舰队国际则是由新组建的欧洲、北美、亚洲三大舰队组成" 5 | "星环集团","程心在艾AA帮助下建立星环集团,扶持托马斯·维德等人的光速飞船研究" 6 | "星舰地球","章北海带领逃离的自然选择号,以及追击他们的4艘战舰(终极规律号、企业号、深空号、蓝色空间号),最终成立了星舰地球。" 7 | "智子","质子经过二维展开改造后的智能微观粒子,可以进行通讯、侦查、干扰粒子高能加速器等任务;二、受三体世界智能粒子(也就是上面所讲的智子)控制的拟人机器人,可视作三体人驻地球的大使" 8 | "自然选择号","亚洲舰队第三分舰队的旗舰,舰长是东方延绪,因为要排查舰队中的钢印族,让冬眠了200多年章北海复苏担任执行舰长暂时接任东方延绪,直到调查结束。章北海刚拿到“自然选择”号的控制权后利用重置口令的时机劫持战舰逃离。" 9 | "深空号","追击自然选择号。后因联合舰队全军覆没,归属章北海和星舰地球,并起航向NH588J2飞行。后在黑暗战役中因终极规律号的次声波氢弹被摧毁。" 10 | "蓝色空间号","恒星级战舰。黑暗战役中,在“终极规律”号抢先对其他战舰发动次声波氢弹攻击后,对“终极规律”号发起了反击,“蓝色空间”夺取了除已被完全摧毁的终极规律号其他三艘战舰的全部资源后离去。" 11 | "终极规律号","它向星舰地球的其他四艘飞船发射了十二枚装载着次声波氢弹弹头的隐形导弹,向二十万公里外的自然选择号发射的三枚比其他九枚提前了一段时间,以使其和向附近三艘飞船发射的导弹同时到达起爆位置。" 12 | "企业号","追击自然选择号。后因联合舰队全军覆没,归属章北海和星舰地球,并起航向NH588J2飞行。后在黑暗战役中因终极规律号的次声波氢弹被摧毁。" 13 | "量子号","炮灰" 14 | "青铜时代号","在太空军遭受水滴打击的时刻,早已进入推进四准备的青铜时代号和量子号成功避免了水滴的打击,迅速逃离了太阳系。在之前的五艘星舰进行黑暗战役的过程中,青铜时代号也对量子号发动了袭击,获取了量子号中的所有资源。当罗辑成功在地球与三体之间建立威胁后,青铜时代号的官兵被人类骗回了地球,然后星舰上的全体人员被迫接受了审判。" 15 | "水滴","由三体文明使用强互作用力材料(SIM)所制成的宇宙探测器,因为其外形与水滴相似,所以被人类称之为水滴。它对电磁波的反射率为100%,且绝对光滑,温度处于绝对零度,强度高达太阳系中最坚硬的材料的百倍以上的坚硬外壳。" 16 | "二向箔","首次出现于一艘来自歌者“母世界”的宇宙飞船。由于宇宙战争愈演愈烈,二向箔对于高等文明而言已不够格作为武器使用,但其仍作为一种廉价的清理工具用于清除隐藏在结构较复杂的恒星系统中的弱小文明。并且对它的滥用极大加速了宇宙的死亡。" 17 | "万有引力号","万有引力号是太空军遭受水滴打击后地球建造的第一艘恒星级战舰,最大的特点是星舰本身具备了引力波发射器的能力。但由于青铜时代星舰上的一名指挥官对蓝色空间号进行了警告,所以蓝色空间没有回归。最后在得知三体人向地球发动新的打击后,蓝色空间和万有引力两舰通过协商,向宇宙发出了决定三体和地球命运的引力波。最后两星舰一起带着人类文明向远方驶去。" 18 | "太阳系", 19 | "三体第一舰队", 20 | "光速飞船", 21 | "蓝星", 22 | "DX3906恒星","云天明身患绝症,知自己将不久于人世,于是花了大学同学胡文报恩的300万,购买了编号为“DX3906”的恒星,送给大学时期暗恋的女孩程心。" 23 | "5公斤生态球", -------------------------------------------------------------------------------- /src/constants/demo_data/TheThreeBody/person-organization-relationship.csv: -------------------------------------------------------------------------------- 1 | "伊文斯","ETO","创办" 2 | "叶文洁","ETO","精神领袖" 3 | "潘寒","ETO","降临派" 4 | "申玉菲","ETO","拯救派" 5 | "萨伊","PDC","轮值主席" 6 | "于维民","PIA","PIA副局长" 7 | "米哈伊尔·瓦季姆","PIA","PIA主任" 8 | "柯曼琳","PIA","PIA高级顾问" 9 | "丁仪","水滴","近距离接触" 10 | "丁仪","量子号","提前深海" 11 | "丁仪","青铜时代号","提前深海" 12 | "东方延绪","自然选择号","舰长" 13 | "汪淼","智子","被干扰" 14 | "云天明","三体第一舰队","被复活" 15 | "程心","PIA","加入" 16 | "褚岩","蓝色空间号","舰长" 17 | "艾AA","星环集团","辅助建立" 18 | "程心","星环集团","建立" 19 | "托马斯·维德","光速飞船","推进计划" 20 | "艾AA","DX3906恒星","乘坐“星环号”光速飞船" 21 | "程心","DX3906恒星","乘坐“星环号”光速飞船" 22 | "程心","蓝星","前往" 23 | "关一帆","蓝星","前往" 24 | "关一帆","万有引力号","随舰学者" 25 | "约瑟夫·莫沃维奇","万有引力号","舰长" 26 | "韦斯特","万有引力号","心理医生" 27 | "戴文","万有引力号","宪兵指挥官" 28 | "伊万","万有引力号","维护工程师" 29 | "薇拉","万有引力号","维护工程师" 30 | "程心","5公斤生态球","保留" 31 | "章北海","自然选择号","成为舰长后叛逃" 32 | -------------------------------------------------------------------------------- /src/constants/demo_data/TheThreeBody/person-person-relationship.csv: -------------------------------------------------------------------------------- 1 | "绍琳","叶文洁","母亲" 2 | "绍琳","叶文雪","母亲" 3 | "绍琳","叶哲泰","妻子" 4 | "叶哲泰","叶文洁","父亲" 5 | "叶哲泰","叶文雪","父亲" 6 | "叶文雪","叶文洁","姐妹" 7 | "杨卫宁","叶文洁","丈夫" 8 | "叶文洁","杨卫宁","杀死" 9 | "叶文洁","雷志成","杀死" 10 | "叶文洁","沙瑞山","学生" 11 | "叶文洁","罗辑","宇宙文明公理" 12 | "叶文洁","潘寒","护卫扭断了脖子" 13 | "沙瑞山","汪淼","帮助观察到整个宇宙在闪烁" 14 | "杨冬","叶文洁","女儿" 15 | "杨冬","丁仪","恋人" 16 | "白沐霖","叶文洁","诬陷" 17 | "汪淼","杨冬","同学" 18 | "汪淼","潘寒","卧底" 19 | "汪淼","申玉菲","卧底" 20 | "申玉菲","魏成","夫妻" 21 | "潘寒","申玉菲","刺杀" 22 | "史强","汪淼","拯救" 23 | "史强","常伟思","同事" 24 | "史强","罗辑","保护、纯洁的工作关系" 25 | "三体人","罗辑","下令刺杀" 26 | "1379号监听员","叶文洁","警告不要回答" 27 | "三体人","1379号监听员","惩罚" 28 | "罗辑","庄颜","丈夫" 29 | "罗辑","程心","执剑人交接" 30 | "冯·诺依曼","弗雷德里克·泰勒","破壁" 31 | "山杉惠子","比尔·希恩斯","破壁" 32 | "墨子","曼努尔·雷迪亚兹","破壁" 33 | "比尔·希恩斯","山杉惠子","丈夫" 34 | "丁仪","章北海","间接启发" 35 | "托马斯·维德","程心","领导-暗杀-全权代理-旅行承诺" 36 | "云天明","程心","曾经暗恋,赠予DX3906恒星" 37 | "云天明","艾AA","度过幸福一生" 38 | "智子","程心","服务" 39 | "智子","关一帆","服务" 40 | "关一帆","程心","落入黑域,启动飞船一起前往蓝星" -------------------------------------------------------------------------------- /src/constants/demo_data/TheThreeBody/person-plan-relationship.csv: -------------------------------------------------------------------------------- 1 | "史强","古筝行动","提出" 2 | "汪淼","古筝行动","提供纳米飞刃材料" 3 | "常伟思","古筝行动","总指挥官" 4 | "伊文斯","古筝行动","在本次行动中被杀" 5 | "罗辑","雪地计划","提出" 6 | "弗雷德里克·泰勒","量子舰队计划","提出" 7 | "曼努尔·雷迪亚兹","水星坠落连锁反应计划","提出" 8 | "比尔·希恩斯","失败主义钢印计划","提出" 9 | "罗辑","面壁计划","真实战略意图:建立黑暗森林威慑" 10 | "弗雷德里克·泰勒","面壁计划","真实战略意图:利用球状闪电武器对人类舰队发动突然攻击使其量子化,用量子舰队来对抗三体。" 11 | "曼努尔·雷迪亚兹","面壁计划","真实战略意图:在水星地层中埋藏大量的氢弹,一旦引爆会使整个太阳系变成比三体更加恶劣的地狱,以全人类的生命为筹码要挟三体。" 12 | "比尔·希恩斯","面壁计划","研究思想钢印并向人类植入绝对失败主义的理念,让人类尽快逃离太阳系以避免与三体的正面接触。" 13 | "章北海","增援未来计划","提出" 14 | "三体人","染色计划","提出" 15 | "三体人","神迹计划","提出" 16 | "云天明","黑域计划","三个童话" 17 | "云天明","掩体计划","三个童话" 18 | "云天明","光速飞船计划","三个童话" 19 | "云天明","阶梯计划","大脑被送至三体世界" 20 | "程心","阶梯计划","送出云天明大脑" 21 | -------------------------------------------------------------------------------- /src/constants/demo_data/TheThreeBody/timeRelationship.csv: -------------------------------------------------------------------------------- 1 | "魔法时代","人类时代" 2 | "人类时代","黄金时代" 3 | "黄金时代","危机纪元" 4 | "危机纪元","威慑纪元" 5 | "威慑纪元","威慑后" 6 | "威慑后","广播纪元" 7 | "广播纪元","掩体纪元" 8 | "掩体纪元","银河纪元" 9 | "银河纪元","DX3906星系黑域纪元" 10 | "DX3906星系黑域纪元","647号宇宙预备时间线" -------------------------------------------------------------------------------- /src/constants/demo_data/TheThreeBody/timeline.csv: -------------------------------------------------------------------------------- 1 | "魔法时代","公元1453年5月3日-1453年5月29日","魔法师狄奥伦娜死完,拜占庭陷落" 2 | "人类时代","公元1453年-1980年","叶文洁出生、红岸基地成立" 3 | "黄金时代","公元1980年-201X年","人类意识到智子的存在,叶文洁在杨冬墓前向罗辑告知了宇宙社会学两个公理和两个概念" 4 | "危机纪元","公元201x年-2208年","罗辑被选为面壁人,罗辑领悟黑暗森林法则" 5 | "威慑纪元","公元2208年-2270年","水滴攻击“蓝色空间”与追击的“万有引力" 6 | "威慑后","公元2270年-2272年","全人类开始向澳大利亚移民" 7 | "广播纪元","公元2272年-2332年","三体世界被毁灭" 8 | "掩体纪元","公元2333年-2400年","星环集团宣布研制曲率驱动飞船的计划,太阳开始二维化" 9 | "银河纪元","公元2273年-不明","程心与艾AA到达DX3906,程心与关一帆进入死域" 10 | "DX3906星系黑域纪元","公元2687年-2731年","程心与关一帆发现他们留下的礼物-647号小宇宙" 11 | "647号宇宙预备时间线","公元2687年-公元18906416年","程心、关一帆走出黑域,进入647号小宇宙,遇见智子,程心写回忆录" -------------------------------------------------------------------------------- /src/constants/demo_data/ThreeKingdoms/battle.csv: -------------------------------------------------------------------------------- 1 | name,start_time,end_time 2 | 群雄讨董,190,190 3 | 界桥之战,191,191 4 | 兖州之战,192,195 5 | 徐州之战,194,198 6 | 平定江东,195,196 7 | 宛城之战,197,197 8 | 官渡之战,200,200 9 | 赤壁之战,208,208 10 | 刘备入蜀,212,214 11 | 汉中之战,217,219 12 | 襄樊之战,219,219 13 | 夷陵之战,222,222 14 | 诸葛北伐,228,234 15 | 淮南三叛,251,258 16 | 司马昭灭蜀,263,263 -------------------------------------------------------------------------------- /src/constants/demo_data/ThreeKingdoms/civil-servant.csv: -------------------------------------------------------------------------------- 1 | name,camp,hometown,family 2 | 荀彧,魏,豫州,颍川荀氏 3 | 荀攸,魏,豫州,颍川荀氏 4 | 贾诩,魏,凉州,武威贾氏 5 | 郭嘉,魏,豫州, 6 | 程昱,魏,兖州, 7 | 陈群,魏,豫州,颍川陈氏 8 | 钟繇,魏,豫州,颍川钟氏 9 | 钟会,魏,豫州,颍川钟氏 10 | 诸葛亮,蜀,徐州,琅琊诸葛氏 11 | 孙乾,蜀,青州, 12 | 简庸,蜀,幽州, 13 | 糜竺,蜀,徐州, 14 | 庞统,蜀,荆州, 15 | 刘巴,蜀,荆州,零陵刘氏 16 | 法正,蜀,司州, 17 | 李严,蜀,荆州, 18 | 蒋琬,蜀,荆州, 19 | 费祎,蜀,荆州, 20 | 董允,蜀,荆州, 21 | 张昭,吴,徐州, 22 | 顾雍,吴,扬州,吴郡顾氏 23 | 诸葛瑾,吴,徐州, 24 | 步骘,吴,扬州, -------------------------------------------------------------------------------- /src/constants/demo_data/ThreeKingdoms/civilServant_to_lord.csv: -------------------------------------------------------------------------------- 1 | slaver,master 2 | 荀彧,袁绍 3 | 荀彧,曹操 4 | 荀攸,曹操 5 | 贾诩,董卓 6 | 贾诩,曹操 7 | 郭嘉,袁绍 8 | 郭嘉,曹操 9 | 程昱,曹操 10 | 陈群,曹操 11 | 钟繇,曹操 12 | 钟会,曹丕 13 | 诸葛亮,刘备 14 | 孙乾,刘备 15 | 简庸,刘备 16 | 糜竺,刘备 17 | 庞统,刘备 18 | 刘巴,刘备 19 | 法正,刘璋 20 | 法正,刘备 21 | 李严,刘璋 22 | 李严,刘备 23 | 蒋琬,刘备 24 | 费祎,刘备 25 | 董允,刘备 26 | 张昭,孙策 27 | 顾雍,孙权 28 | 诸葛瑾,孙权 29 | 步骘,孙权 -------------------------------------------------------------------------------- /src/constants/demo_data/ThreeKingdoms/civilServant_to_state.csv: -------------------------------------------------------------------------------- 1 | name,hometown 2 | 荀彧,豫州 3 | 荀攸,豫州 4 | 贾诩,凉州 5 | 郭嘉,豫州 6 | 程昱,兖州 7 | 陈群,豫州 8 | 钟繇,豫州 9 | 钟会,豫州 10 | 诸葛亮,徐州 11 | 孙乾,青州 12 | 简庸,幽州 13 | 糜竺,徐州 14 | 庞统,荆州 15 | 刘巴,荆州 16 | 法正,司州 17 | 李严,荆州 18 | 蒋琬,荆州 19 | 费祎,荆州 20 | 董允,荆州 21 | 张昭,徐州 22 | 顾雍,扬州 23 | 诸葛瑾,徐州 24 | 步骘,扬州 -------------------------------------------------------------------------------- /src/constants/demo_data/ThreeKingdoms/general.csv: -------------------------------------------------------------------------------- 1 | name,camp,hometown,family 2 | 夏侯惇,魏,豫州,谯县夏侯氏 3 | 曹仁,魏,豫州,谯县曹氏 4 | 曹真,魏,豫州,谯县曹氏 5 | 曹洪,魏,豫州,谯县曹氏 6 | 曹纯,魏,豫州,谯县曹氏 7 | 曹休,魏,豫州,谯县曹氏 8 | 夏侯尚,魏,豫州,谯县夏侯氏 9 | 夏侯渊,魏,豫州,谯县夏侯氏 10 | 许褚,魏,豫州, 11 | 典韦,魏,兖州, 12 | 张郃,魏,冀州,河间张氏 13 | 徐晃,魏,司州, 14 | 张辽,魏,并州, 15 | 乐进,魏,兖州, 16 | 于禁,魏,兖州, 17 | 张绣,魏,凉州, 18 | 关羽,蜀,司州, 19 | 张飞,蜀,幽州, 20 | 赵云,蜀,冀州, 21 | 马超,蜀,司州, 22 | 黄忠,蜀,荆州, 23 | 黄权,蜀,益州, 24 | 魏延,蜀,荆州, 25 | 姜维,蜀,凉州, 26 | 周瑜,吴,扬州,庐江周氏 27 | 鲁肃,吴,徐州, 28 | 吕蒙,吴,豫州, 29 | 陆逊,吴,扬州,吴郡陆氏 30 | 陆抗,吴,扬州,吴郡陆氏 31 | 程普,吴,幽州, 32 | 韩当,吴,幽州, 33 | 周泰,吴,扬州, 34 | 黄盖,吴,荆州, 35 | 蒋钦,吴,扬州, 36 | 甘宁,吴,益州, 37 | 凌统,吴,扬州, 38 | 徐盛,吴,徐州, 39 | 潘璋,吴,兖州, 40 | 丁奉,吴,扬州, 41 | 太史慈,吴,青州, -------------------------------------------------------------------------------- /src/constants/demo_data/ThreeKingdoms/general_father_lord.csv: -------------------------------------------------------------------------------- 1 | son,father 2 | 马超,马腾 -------------------------------------------------------------------------------- /src/constants/demo_data/ThreeKingdoms/general_to_battle.csv: -------------------------------------------------------------------------------- 1 | name,war 2 | 张绣,宛城之战 -------------------------------------------------------------------------------- /src/constants/demo_data/ThreeKingdoms/general_to_lord.csv: -------------------------------------------------------------------------------- 1 | slaver,master 2 | 夏侯惇,曹操 3 | 曹仁,曹操 4 | 曹真,曹操 5 | 曹洪,曹操 6 | 曹纯,曹操 7 | 曹休,曹操 8 | 夏侯尚,曹操 9 | 夏侯渊,曹操 10 | 许褚,曹操 11 | 典韦,曹操 12 | 张郃,袁绍 13 | 张郃,曹操 14 | 徐晃,曹操 15 | 张辽,吕布 16 | 张辽,曹操 17 | 乐进,曹操 18 | 于禁,曹操 19 | 关羽,刘备 20 | 张飞,刘备 21 | 赵云,刘备 22 | 马超,刘备 23 | 黄忠,刘备 24 | 黄权,刘备 25 | 魏延,刘备 26 | 姜维,刘禅 27 | 周瑜,孙策 28 | 鲁肃,孙权 29 | 吕蒙,孙权 30 | 陆逊,孙权 31 | 陆抗,孙权 32 | 程普,孙坚 33 | 韩当,孙坚 34 | 周泰,孙策 35 | 黄盖,孙坚 36 | 蒋钦,孙策 37 | 甘宁,孙权 38 | 凌统,孙权 39 | 徐盛,孙权 40 | 潘璋,孙权 41 | 丁奉,孙权 42 | 太史慈,孙策 -------------------------------------------------------------------------------- /src/constants/demo_data/ThreeKingdoms/general_to_state.csv: -------------------------------------------------------------------------------- 1 | name,hometown 2 | 夏侯惇,豫州 3 | 曹仁,豫州 4 | 曹真,豫州 5 | 曹洪,豫州 6 | 曹休,豫州 7 | 夏侯尚,豫州 8 | 夏侯渊,豫州 9 | 许褚,豫州 10 | 典韦,兖州 11 | 张郃,冀州 12 | 徐晃,司州 13 | 张辽,并州 14 | 乐进,兖州 15 | 于禁,兖州 16 | 张绣,凉州 17 | 关羽,司州 18 | 张飞,幽州 19 | 赵云,冀州 20 | 马超,司州 21 | 黄忠,荆州 22 | 黄权,益州 23 | 魏延,荆州 24 | 姜维,凉州 25 | 周瑜,扬州 26 | 鲁肃,徐州 27 | 吕蒙,豫州 28 | 陆逊,扬州 29 | 陆抗,扬州 30 | 程普,幽州 31 | 韩当,幽州 32 | 周泰,扬州 33 | 黄盖,荆州 34 | 蒋钦,扬州 35 | 甘宁,益州 36 | 凌统,扬州 37 | 徐盛,徐州 38 | 潘璋,兖州 39 | 丁奉,扬州 40 | 太史慈,青州 -------------------------------------------------------------------------------- /src/constants/demo_data/ThreeKingdoms/lord.csv: -------------------------------------------------------------------------------- 1 | name,camp,hometown,family,father_position,position 2 | 曹操,魏,豫州,谯县曹氏,太尉,魏武帝 3 | 曹丕,魏,豫州,谯县曹氏,丞相,魏文帝 4 | 曹叡,魏,豫州,谯县曹氏,皇帝,魏明帝 5 | 刘备,蜀,幽州,涿郡刘氏,,汉昭烈帝 6 | 刘禅,蜀,幽州,涿郡刘氏,皇帝,安乐公 7 | 孙坚,吴,扬州,吴郡孙氏,商人,破虏将军 8 | 孙策,吴,扬州,吴郡孙氏,破虏将军,讨逆将军 9 | 孙权,吴,扬州,吴郡孙氏,破虏将军,吴大帝 10 | 袁绍,汉,豫州,汝南袁氏,司空,汉大将军 11 | 袁术,汉,豫州,汝南袁氏,司空,左将军 12 | 司马懿,晋,司州,河内司马氏,京兆尹,晋宣帝 13 | 司马师,晋,司州,河内司马氏,太傅,大将军 14 | 司马昭,晋,司州,河内司马氏,太傅,相国 15 | 董卓,汉,凉州,凉州豪强,县尉,汉相国 16 | 吕布,汉,并州,并州豪强,,平东将军 17 | 公孙瓒,汉,幽州,辽西公孙氏,两千石,前将军 18 | 刘焉,汉,荆州,竟陵刘氏,太守,益州牧 19 | 刘璋,汉,荆州,竟陵刘氏,益州牧,益州牧 20 | 刘表,汉,兖州,山阳刘氏,汉朝宗亲,荆州牧 21 | 刘协,汉,司州,大汉皇族,皇帝,汉献帝 22 | 马腾,汉,凉州,扶风马氏,县尉,卫尉 23 | 刘繇,汉,青州,东莱刘氏,太守,扬州牧 -------------------------------------------------------------------------------- /src/constants/demo_data/ThreeKingdoms/lord_brother_lord.csv: -------------------------------------------------------------------------------- 1 | ybrother,ebrother 2 | 孙权,孙策 3 | 司马昭,司马师 4 | 袁术,袁绍 -------------------------------------------------------------------------------- /src/constants/demo_data/ThreeKingdoms/lord_father_lord.csv: -------------------------------------------------------------------------------- 1 | son,father 2 | 刘禅,刘备 3 | 曹丕,曹操 4 | 孙策,孙坚 5 | 孙权,孙坚 -------------------------------------------------------------------------------- /src/constants/demo_data/ThreeKingdoms/lord_to_battle.csv: -------------------------------------------------------------------------------- 1 | name,war 2 | 曹操,群雄讨董 3 | 袁绍,群雄讨董 4 | 董卓,群雄讨董 5 | 袁术,群雄讨董 6 | 孙坚,群雄讨董 7 | 刘备,群雄讨董 8 | 吕布,群雄讨董 9 | 公孙瓒,群雄讨董 10 | 袁绍,界桥之战 11 | 公孙瓒,界桥之战 12 | 曹操,兖州之战 13 | 吕布,兖州之战 14 | 曹操,徐州之战 15 | 吕布,徐州之战 16 | 刘备,徐州之战 17 | 袁术,徐州之战 18 | 孙策,平定江东 19 | 袁术,平定江东 20 | 刘繇,平定江东 21 | 曹操,宛城之战 22 | 袁绍,官渡之战 23 | 曹操,官渡之战 24 | 曹操,赤壁之战 25 | 刘备,赤壁之战 26 | 孙权,赤壁之战 27 | 刘备,刘备入蜀 28 | 刘璋,刘备入蜀 29 | 刘备,汉中之战 30 | 曹操,汉中之战 31 | 曹操,襄樊之战 32 | 孙权,襄樊之战 33 | 刘备,襄樊之战 34 | 刘备,夷陵之战 35 | 孙权,夷陵之战 36 | 刘禅,诸葛北伐 37 | 曹叡,诸葛北伐 38 | 司马懿,淮南三叛 39 | 司马师,淮南三叛 40 | 司马昭,淮南三叛 41 | 司马昭,司马昭灭蜀 42 | 刘禅,司马昭灭蜀 -------------------------------------------------------------------------------- /src/constants/demo_data/ThreeKingdoms/lord_to_lord.csv: -------------------------------------------------------------------------------- 1 | slaver,master 2 | 司马懿,曹操 -------------------------------------------------------------------------------- /src/constants/demo_data/ThreeKingdoms/lord_to_state.csv: -------------------------------------------------------------------------------- 1 | name,hometown 2 | 曹操,豫州 3 | 曹丕,豫州 4 | 曹叡,豫州 5 | 刘备,幽州 6 | 刘禅,幽州 7 | 孙坚,扬州 8 | 孙策,扬州 9 | 孙权,扬州 10 | 袁绍,豫州 11 | 袁术,豫州 12 | 司马懿,司州 13 | 司马师,司州 14 | 司马昭,司州 15 | 董卓,凉州 16 | 吕布,并州 17 | 公孙瓒,幽州 18 | 刘焉,荆州 19 | 刘璋,荆州 20 | 刘表,兖州 21 | 刘协,司州 22 | 马腾,凉州 23 | 刘繇,青州 -------------------------------------------------------------------------------- /src/constants/demo_data/ThreeKingdoms/state.csv: -------------------------------------------------------------------------------- 1 | state,caption 2 | 司州,洛阳 3 | 青州,临淄 4 | 徐州,郯县 5 | 兖州,昌邑 6 | 豫州,谯县 7 | 幽州,蓟县 8 | 冀州,高邑 9 | 并州,晋阳 10 | 荆州,汉寿 11 | 扬州,寿春 12 | 凉州,姑臧 13 | 益州,成都 14 | 交州,龙编 -------------------------------------------------------------------------------- /src/constants/demo_data/WanderingEarth/celestialFsacilities.csv: -------------------------------------------------------------------------------- 1 | "地球","" 2 | "月球","" 3 | "木星","" 4 | 5 | "方舟空间站","太空电梯上" 6 | "国际空间站","近地轨道上,在流浪旅途中领航" 7 | "太空电梯","" 8 | "太空电梯基地","" 9 | "互联网北京根服务器","" 10 | "杭州行星发动机","" 11 | "苏拉威西行星发动机","" -------------------------------------------------------------------------------- /src/constants/demo_data/WanderingEarth/event_celestialFacilities.csv: -------------------------------------------------------------------------------- 1 | "方舟空间站","地球",6,"6 方舟空间站坠毁至地面","太空电梯危机",2044 2 | 3 | "月球","地球",4,"4 月球即将坠向地球","月球危机",2058 4 | 5 | "地球","木星",5,"5 距离过近导致地震,木星危机开始","木星危机",2075 6 | "国际空间站","木星",20,"20 爆炸后点燃了混合了地球空气的木星","木星危机",2075 7 | "木星","地球",21,"21 燃烧后的冲击波推开地球,木星危机解除,地球继续流浪","木星危机",2075 8 | -------------------------------------------------------------------------------- /src/constants/demo_data/WanderingEarth/event_organization_celestialFacilities.csv: -------------------------------------------------------------------------------- 1 | "联合政府","月球",5,"5 决定用相控阵核弹炸毁月球","月球危机",2058 2 | "联合政府","地球",6,"6 决定重启互联网提前流浪地球","月球危机",2058 3 | "联合政府","地球",15,"15 成功点火推理地球开始流浪","月球危机",2058 4 | 5 | "救援小队","杭州行星发动机",8,"8 护送火石前往,遭遇二次地震","木星危机",2075 6 | "救援小队","杭州行星发动机",9,"9 运输车在地震中损毁,弃车步行前往,路上韩子昂牺牲","木星危机",2075 7 | "救援小队","苏拉威西行星发动机",10,"10 在杭州任务失败后,运送火石前往苏拉威西","木星危机",2075 -------------------------------------------------------------------------------- /src/constants/demo_data/WanderingEarth/event_organization_role.csv: -------------------------------------------------------------------------------- 1 | "联合政府","刘启",3,"3 被查出违法开车,被关进监狱","木星危机",2075 2 | "救援小队","韩子昂",6,"6 在韩子昂逃难时紧急征用其车辆","木星危机",2075 -------------------------------------------------------------------------------- /src/constants/demo_data/WanderingEarth/event_role_celestialFacility.csv: -------------------------------------------------------------------------------- 1 | "MOSS","太空电梯基地",2,"2 出动无人机攻击","太空电梯危机",2044 2 | "反叛者","方舟空间站",4,"4 引爆随太空电梯上升带来的炸弹","太空电梯危机",2044 3 | "MOSS","太空电梯基地",5,"5 覆写550A控制基地","太空电梯危机",2044 4 | 5 | "MOSS","月球",3,"3 MOSS控制下的月球发动机爆炸","月球危机",2058 6 | "刘培强","月球",7,"7 前往部署核弹,后返回空间站","月球危机",2058 7 | "张鹏","月球",8,"8.1 前往引爆核弹","月球危机",2058 8 | "安德烈·戈拉希诺夫","月球",8,"8.2 前往引爆核弹","月球危机",2058 9 | "刘培强","国际空间站",8,"8.3 在张鹏指引下返回空间站","月球危机",2058 10 | "马兆","互联网北京根服务器",9,"9.1 前往重启根服务器","月球危机",2058 11 | "图恒宇","互联网北京根服务器",9,"9.2 前往重启根服务器","月球危机",2058 12 | "图丫丫","互联网北京根服务器",14,"14 背出密钥完成了服务器重启","月球危机",2058 13 | 14 | "刘启","地球",2,"2 和妹妹一起偷偷跑到地表","木星危机",2075 15 | "刘培强","国际空间站",12,"12 前往总控室停止空间站","木星危机",2075 16 | "刘启","苏拉威西行星发动机",15,"15 送入火石","木星危机",2075 17 | "李一一","苏拉威西行星发动机",18,"18 修改程序让尾焰点燃木星,但还差一点到极限距离","木星危机",2075 18 | "刘培强","国际空间站",19,"19 操控飞向行星发动机尾焰","木星危机",2075 -------------------------------------------------------------------------------- /src/constants/demo_data/WanderingEarth/event_role_organization.csv: -------------------------------------------------------------------------------- 1 | "刘培强","领航员面试组",1,"1 面试领航员","月球危机",2058 2 | "周喆直","联合政府",13,"13 强行命令启动点火行星发动机","月球危机",2058 -------------------------------------------------------------------------------- /src/constants/demo_data/WanderingEarth/event_role_role.csv: -------------------------------------------------------------------------------- 1 | "反叛者","MOSS",1,"1 入侵控制系统","太空电梯危机",2044 2 | "反叛者","刘培强",3,"3.1 发生战斗","太空电梯危机",2044 3 | "反叛者","韩朵朵大",3,"3.2 发生战斗","太空电梯危机",2044 4 | 5 | "图恒宇","MOSS",2,"2 上传丫丫的意识","月球危机",2058 6 | "马兆","图恒宇",10,"10 被电缆缠住淹死在洪水前将密钥交给图恒宇","月球危机",2058 7 | "图恒宇","图丫丫",11,"11 在被洪水淹死前上传了自己的意识并让丫丫记住密钥","月球危机",2058 8 | "图恒宇","图丫丫",12,"12 在数字生命世界和丫丫重逢","月球危机",2058 9 | 10 | "韩朵朵小","刘启",1,"1 地下城停电,找到哥哥","木星危机",2075 11 | "韩子昂","刘启",4,"4 营救刘启和韩朵朵失败,也被关进监狱","木星危机",2075 12 | "刘培强","MOSS",7,"7 与王磊通话后被强制休眠","木星危机",2075 13 | "刘培强","MOSS",11,"11 从休眠中苏醒,认为MOSS叛逃","木星危机",2075 14 | "马卡洛夫","刘培强",13,"13 帮助刘培强前往总控室的路上牺牲","木星危机",2075 15 | "MOSS","刘培强",14,"14 在总控室对峙,MOSS解释流浪地球计划失败,其在执行火种计划","木星危机",2075 16 | "刘培强","MOSS",16,"16 夺取控制权后与刘启通话","木星危机",2075 17 | "刘启","李一一",17,"17 讨论后决定点燃木星推开地球","木星危机",2075 -------------------------------------------------------------------------------- /src/constants/demo_data/WanderingEarth/organizations.csv: -------------------------------------------------------------------------------- 1 | "救援小队","执行火石运送任务" 2 | "领航员面试组","面试领航员" 3 | "联合政府","地球上各国成立的联合政府" -------------------------------------------------------------------------------- /src/constants/demo_data/WanderingEarth/relationship_role_celestialFacility.csv: -------------------------------------------------------------------------------- 1 | "刘培强","太空电梯","守卫" 2 | "韩朵朵大","太空电梯","守卫" 3 | "张鹏","太空电梯","守卫" 4 | "MOSS","太空电梯基地","控制" -------------------------------------------------------------------------------- /src/constants/demo_data/WanderingEarth/relationship_role_organization.csv: -------------------------------------------------------------------------------- 1 | "周喆直","联合政府","代表中国" 2 | "郝晓晞","联合政府","代表中国" 3 | "马兆","领航员面试组","成员" 4 | "图恒宇","领航员面试组","成员" 5 | "刘培强","领航员面试组","面试" 6 | "MOSS","领航员面试组","面试辅助" 7 | "王磊","救援小队","队长" 8 | "韩子昂","救援小队","队长" 9 | "刘启","救援小队","队员" 10 | "韩朵朵小","救援小队","队员" 11 | "李一一","救援小队","队员" 12 | "Tim","救援小队","队员" 13 | "何连科","救援小队","队员" 14 | "赵志刚","救援小队","队员" 15 | "张小强","救援小队","队员" -------------------------------------------------------------------------------- /src/constants/demo_data/WanderingEarth/relationship_role_role.csv: -------------------------------------------------------------------------------- 1 | "韩子昂","韩朵朵大","父女" 2 | "韩子昂","刘培强","女婿" 3 | "韩子昂","韩朵朵小","收养" 4 | "韩朵朵小","刘启","兄妹" 5 | "王磊","韩子昂","队长" 6 | "王磊","刘启","队长" 7 | "王磊","韩朵朵小","队长" 8 | "王磊","李一一","队长" 9 | "王磊","Tim","队长" 10 | "王磊","何连科","队长" 11 | "王磊","赵志刚","队长" 12 | "王磊","张小强","队长" 13 | "刘启","李一一","合作" 14 | 15 | "周喆直","郝晓晞","领导" 16 | "郝晓晞","李一一","母子" 17 | "马兆","图恒宇","师徒" 18 | "马兆","图恒宇","领导" 19 | "马兆","刘培强","观察" 20 | "图恒宇","图丫丫","父女" 21 | "图恒宇","刘培强","观察" 22 | "图恒宇","丫丫母亲","夫妻" 23 | "丫丫母亲","图丫丫","母女" 24 | "刘培强","韩朵朵大","夫妻" 25 | "刘培强","刘启","父子" 26 | "刘培强","赫伯特","战友" 27 | "刘培强","马卡洛夫","战友" 28 | "韩朵朵大","刘启","母子" 29 | "张鹏","刘培强","师徒" 30 | "张鹏","安德烈·戈拉希诺夫","战友" 31 | "安德烈·戈拉希诺夫","马卡洛夫","师徒" -------------------------------------------------------------------------------- /src/constants/demo_data/WanderingEarth/roles.csv: -------------------------------------------------------------------------------- 1 | 1999,"韩子昂","运输车驾驶员" 2 | 2023,"刘培强","航天员" 3 | 0,"韩朵朵大","航天员" 4 | 2060,"韩朵朵小","初中生" 5 | 2050,"刘启","工程车辆维修员" 6 | 2048,"李一一","技术观察员" 7 | 0,"Tim","中美混血儿" 8 | 0,"何连科","救援队队员" 9 | 2020,"王磊","救援队队长" 10 | 0,"赵志刚","救援队队员" 11 | 0,"张小强","救援队队员" 12 | 0,"MOSS","量子计算机AI,550A、550C、550W" 13 | 0,"反叛者","数字生命计划的信徒,强烈反对移山计划" 14 | 0,"郝晓晞","联合政府中国代表" 15 | 0,"周喆直","联合政府中国代表" 16 | 1997,"马兆","研究员" 17 | 2010,"图恒宇","研究员" 18 | 0,"丫丫母亲","" 19 | 0,"图丫丫","" 20 | 0,"张鹏","航天员" 21 | 0,"赫伯特","航天员" 22 | 0,"安德烈·戈拉希诺夫","航天员" 23 | 0,"马卡洛夫","航天员" -------------------------------------------------------------------------------- /src/constants/demo_data/movie/edge_directed.csv: -------------------------------------------------------------------------------- 1 | 13,130 2 | 13,1 3 | 13,28 4 | 13,68 5 | 14,130 6 | 14,1 7 | 14,28 8 | 14,68 9 | 100,82 10 | 145,130 11 | 466,457 12 | 490,3915 13 | 491,471 14 | 491,496 15 | 524,517 16 | 549,737 17 | 549,532 18 | 549,564 19 | 607,598 20 | 627,615 21 | 664,641 22 | 664,698 23 | 664,797 24 | 690,681 25 | 724,710 26 | 728,805 27 | 761,750 28 | 776,770 29 | 776,2498 30 | 791,781 31 | 791,2885 32 | 1077,1096 33 | 1107,1097 34 | 1133,1118 35 | 1252,1241 36 | 1254,1241 37 | 1295,1356 38 | 1297,1293 39 | 1379,1366 40 | 1382,1366 41 | 1421,1412 42 | 1557,1550 43 | 1604,1596 44 | 1705,1693 45 | 1766,1768 46 | 1869,1856 47 | 1942,1935 48 | 1943,1935 49 | 2020,2008 50 | 2073,2055 51 | 2243,2232 52 | 2409,2398 53 | 2410,2398 54 | 2439,2422 55 | 2440,2422 56 | 2664,2654 57 | 2779,2767 58 | 3112,3106 59 | 3113,3106 60 | 3267,3252 61 | 3398,3390 62 | 3464,3459 63 | 3630,3624 64 | 3688,3680 65 | 3760,3753 66 | -------------------------------------------------------------------------------- /src/constants/demo_data/movie/edge_produce.csv: -------------------------------------------------------------------------------- 1 | 13,130 2 | 14,130 3 | 15,1 4 | 15,28 5 | 15,68 6 | 106,82 7 | 108,2498 8 | 109,82 9 | 145,130 10 | 490,3915 11 | 491,496 12 | 491,3915 13 | 549,737 14 | 549,532 15 | 549,564 16 | 607,598 17 | 609,598 18 | 616,615 19 | 664,641 20 | 664,698 21 | 664,797 22 | 665,641 23 | 665,698 24 | 665,797 25 | 665,3390 26 | 666,641 27 | 666,698 28 | 666,797 29 | 666,3390 30 | 683,1693 31 | 725,710 32 | 725,805 33 | 728,710 34 | 728,805 35 | 764,750 36 | 776,2498 37 | 791,2885 38 | 793,781 39 | 1119,1118 40 | 1253,1241 41 | 1295,1356 42 | 1297,1293 43 | 1298,1293 44 | 1365,1356 45 | 1371,2232 46 | 1557,1550 47 | 1705,1693 48 | 1767,1768 49 | 2021,2008 50 | 2068,2055 51 | 2074,2055 52 | 2075,2055 53 | 2243,2232 54 | 2244,2232 55 | 2779,2767 56 | 2895,2885 57 | 3113,3106 58 | 3392,3390 59 | 3464,3459 60 | 3689,3680 61 | -------------------------------------------------------------------------------- /src/constants/demo_data/movie/edge_write.csv: -------------------------------------------------------------------------------- 1 | 13,130 2 | 13,1 3 | 13,28 4 | 13,68 5 | 14,130 6 | 14,1 7 | 14,28 8 | 14,68 9 | 100,82 10 | 108,82 11 | 108,2498 12 | 145,130 13 | 490,3915 14 | 491,471 15 | 491,496 16 | 492,471 17 | 492,496 18 | 495,457 19 | 524,517 20 | 525,517 21 | 549,737 22 | 549,532 23 | 549,564 24 | 550,532 25 | 550,564 26 | 551,532 27 | 551,564 28 | 608,598 29 | 628,615 30 | 664,641 31 | 664,698 32 | 664,797 33 | 667,641 34 | 667,698 35 | 667,797 36 | 668,641 37 | 668,698 38 | 668,797 39 | 726,710 40 | 727,710 41 | 728,710 42 | 728,805 43 | 762,750 44 | 763,750 45 | 777,770 46 | 778,770 47 | 791,781 48 | 792,781 49 | 907,681 50 | 1094,1096 51 | 1095,1096 52 | 1106,1097 53 | 1108,1097 54 | 1133,1118 55 | 1134,1118 56 | 1252,1241 57 | 1254,1241 58 | 1255,1241 59 | 1295,1356 60 | 1297,1293 61 | 1299,1293 62 | 1365,1356 63 | 1379,1366 64 | 1380,1366 65 | 1381,1366 66 | 1382,1366 67 | 1422,1412 68 | 1423,1412 69 | 1557,1550 70 | 1605,1596 71 | 1606,1596 72 | 1706,1693 73 | 1766,1768 74 | 1870,1856 75 | 1871,1856 76 | 1872,1856 77 | 1873,1856 78 | 1874,1856 79 | 1942,1935 80 | 1943,1935 81 | 1943,2422 82 | 1944,1935 83 | 2022,2008 84 | 2073,2055 85 | 2076,2055 86 | 2243,2232 87 | 2245,2232 88 | 2411,2398 89 | 2412,2398 90 | 2441,2422 91 | 2442,2422 92 | 2443,2422 93 | 2444,2422 94 | 2510,2498 95 | 2664,2654 96 | 2780,2767 97 | 2896,2885 98 | 2897,2885 99 | 3112,3106 100 | 3113,3106 101 | 3268,3252 102 | 3269,3252 103 | 3399,3390 104 | 3464,3459 105 | 3631,3624 106 | 3690,3680 107 | 3761,3753 108 | -------------------------------------------------------------------------------- /src/constants/demo_data/movie/vertex_genre.csv: -------------------------------------------------------------------------------- 1 | 16,Action 2 | 17,Adventure 3 | 18,Sci-Fi 4 | 19,Thriller 5 | 110,Crime 6 | 129,Documentary 7 | 146,Drama 8 | 526,Western 9 | 610,History 10 | 611,War 11 | 669,Fantasy 12 | 746,Mystery 13 | 779,Comedy 14 | 780,Romance 15 | 1256,Animation 16 | 1257,Musical 17 | 1258,Family 18 | 2246,Horror 19 | 3270,Suspense 20 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const APP_ENTRY = 'TU_GRAPH'; 2 | export const APP_LINKS = [ 3 | { 4 | title: '图项目', 5 | key: APP_ENTRY + '_STUDIO', 6 | path: '/home', 7 | }, 8 | { 9 | title: '控制台', 10 | key: APP_ENTRY + '_CONSOLE', 11 | path: '/console', 12 | }, 13 | ]; 14 | 15 | export const CONSOLE_LINKS = [ 16 | { 17 | title: '账户管理', 18 | key: APP_ENTRY + '_AUTH', 19 | path: '/auth', 20 | }, 21 | { 22 | title: '数据库信息', 23 | key: APP_ENTRY + '_DATABASE', 24 | path: '/database', 25 | }, 26 | ]; 27 | export const PROXY_HOST = `http://${window.location.hostname}:7001`; 28 | 29 | export const TUGRAPH_USER_NAME = 'TUGRAPH_USER_NAME'; 30 | export const TUGRAPH_PASSWORD = 'TUGRAPH_PASSWORD'; 31 | export const TUGRAPH_URI = 'TUGRAPH_URI'; 32 | export const TUGRPAH_USER_LOGIN_TIME = 'TUGRPAH_USER_LOGIN_TIME'; 33 | export const TUGRAPH_HISTORY_URI = 'TUGRAPH_HISTORY_URI'; 34 | 35 | 36 | /* 数据类型 */ 37 | export const DATA_TYPE = [ 38 | { label: 'INT8', value: 'INT8', default: 0 }, 39 | { label: 'INT16', value: 'INT16', default: 0 }, 40 | { label: 'INT32', value: 'INT32', default: 0 }, 41 | { label: 'INT64', value: 'INT64', default: 0 }, 42 | { label: 'DOUBLE', value: 'DOUBLE', default: '0.0' }, 43 | { label: 'STRING', value: 'STRING', default: '' }, 44 | { label: 'DATE', value: 'DATE', default: '0000-01-01' }, 45 | { label: 'DATETIME', value: 'DATETIME', default: '0000-01-01 00:00:00' }, 46 | // { label: 'BLOB', value: 'BLOB', default: '' }, 47 | { label: 'BOOL', value: 'BOOL', default: false }, 48 | ]; 49 | 50 | /** style prefix */ 51 | export const PUBLIC_PERFIX_CLASS = 'ant-tugraph'; 52 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/attibutes-filter/index.less: -------------------------------------------------------------------------------- 1 | .attribute-filter-container { 2 | .attribute-filter-container-form { 3 | overflow: auto; 4 | max-height: calc(100vh - 260px); 5 | 6 | .img { 7 | width: 16px; 8 | height: 16px; 9 | margin-right: 8px; 10 | } 11 | } 12 | 13 | .attribute-button-group { 14 | margin-top: 20px; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/attributes-filter-form/index.less: -------------------------------------------------------------------------------- 1 | .attributes-filter-form { 2 | .nodes-edges-options { 3 | .img { 4 | width: 16px; 5 | height: 16px; 6 | margin-right: 8px; 7 | } 8 | } 9 | 10 | .formList { 11 | display: flex; 12 | flex-direction: row; 13 | align-items: center; 14 | justify-content: center; 15 | margin-bottom: 8px; 16 | margin-left: -2px; 17 | } 18 | 19 | .conditionIcon { 20 | margin-bottom: 8px; 21 | } 22 | 23 | .conditionIcon::before { 24 | display: inline-block; 25 | margin-right: 4px; 26 | color: #ff4d4f; 27 | font-size: 14px; 28 | font-family: SimSun, sans-serif; 29 | line-height: 1; 30 | content: '*'; 31 | } 32 | 33 | :global { 34 | .ant-collapse-extra, 35 | .anticon-delete { 36 | color: #000a1a; 37 | opacity: 0.45; 38 | margin-left: 8px; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/canvas-container/index.less: -------------------------------------------------------------------------------- 1 | .canvas-container { 2 | height: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | position: relative; 6 | 7 | :global { 8 | .ant-spin-nested-loading, 9 | .ant-spin-container { 10 | height: 100%; 11 | display: flex; 12 | flex-direction: column; 13 | 14 | .canvas-center { 15 | flex: 1; 16 | overflow: hidden; 17 | } 18 | } 19 | 20 | .ant-spin-spinning { 21 | max-height: 100% !important; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/canvas-sider/index.less: -------------------------------------------------------------------------------- 1 | .canvas-sider { 2 | height: 100%; 3 | transition: all ease 1s; 4 | 5 | &-content { 6 | height: 100%; 7 | overflow-y: auto; 8 | } 9 | 10 | &-resizable { 11 | background-color: #f7f9fc; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/canvas-toolbar-segmented/index.less: -------------------------------------------------------------------------------- 1 | .canvas-toolbar { 2 | display: flex; 3 | align-items: center; 4 | 5 | &::before { 6 | content: ''; 7 | display: inline-block; 8 | width: 1px; 9 | height: 12px; 10 | background-color: rgba(0, 0, 0, 6%); 11 | margin: 0 10px; 12 | } 13 | 14 | &-title { 15 | color: rgba(152, 152, 157, 100%); 16 | margin-right: 6px; 17 | white-space: nowrap; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/canvas-toolbar-segmented/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 图分析头部工具栏-画布操作 3 | */ 4 | import IconFont from '@/components/icon-font'; 5 | import { useSchemaTabContainer } from '@/domains-core/graph-analysis/graph-schema/hooks/use-schema-tab-container'; 6 | import { Button } from 'antd'; 7 | import type { SegmentedProps } from 'antd/lib/segmented'; 8 | import { default as React } from 'react'; 9 | import styles from './index.less'; 10 | 11 | interface CanvasToolbarSegmentedProps extends SegmentedProps { 12 | activeOptions?: string[]; 13 | } 14 | 15 | const CanvasToolbarSegmented: React.FC = ({ 16 | ...props 17 | }) => { 18 | const { setTabContainerGraphData } = useSchemaTabContainer(); 19 | 20 | const onClearCanvas = () => { 21 | setTabContainerGraphData({ 22 | data: { graphData: {}, originQueryData: [] }, 23 | ifClearGraphData: true, 24 | }); 25 | }; 26 | 27 | return ( 28 |

29 |
画布操作
30 | 35 |
36 | ); 37 | }; 38 | 39 | export default CanvasToolbarSegmented; 40 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/config-query/index.less: -------------------------------------------------------------------------------- 1 | .config-query { 2 | position: relative; 3 | } 4 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/context-menu/index.less: -------------------------------------------------------------------------------- 1 | .context-menu { 2 | position: absolute; 3 | z-index: 1; 4 | box-shadow: rgba(0, 0, 0, 15%) 0 4px 12px; 5 | border-radius: 6px; 6 | overflow: hidden; 7 | } 8 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/download-canvas/index.less: -------------------------------------------------------------------------------- 1 | .download { 2 | position: absolute; 3 | right: 12px; 4 | width: max-content; 5 | 6 | :global { 7 | .ant-dropdown-menu-item { 8 | width: 56px; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/element-info/index.less: -------------------------------------------------------------------------------- 1 | .element-info { 2 | &-title { 3 | font-size: 16px; 4 | font-weight: bold; 5 | margin-bottom: 10px; 6 | 7 | :global { 8 | .ant-typography { 9 | margin: 0; 10 | } 11 | } 12 | } 13 | 14 | &-content { 15 | background-color: #fff; 16 | border-radius: 8px; 17 | padding: 10px 20px; 18 | 19 | &-item { 20 | display: flex; 21 | align-items: baseline; 22 | margin-bottom: 10px; 23 | 24 | .item-label { 25 | font-size: 14px; 26 | color: rgba(26, 27, 37, 88%); 27 | } 28 | 29 | .item-value { 30 | color: rgb(152, 152, 157); 31 | font-size: 12px; 32 | word-break: break-word; 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/element-info/index.tsx: -------------------------------------------------------------------------------- 1 | import { Typography } from 'antd'; 2 | import React, { useMemo } from 'react'; 3 | import styles from './index.less'; 4 | 5 | const { Paragraph } = Typography; 6 | 7 | interface ElementInfoProps { 8 | value: any; 9 | } 10 | 11 | const ElementInfo: React.FC = ({ value }) => { 12 | if (!value) { 13 | return null; 14 | } 15 | const { label, properties, itemType, id } = value; 16 | const isNode = itemType === 'NODE'; 17 | 18 | const propertyKeys = useMemo(() => Object.keys(properties), [properties]); 19 | return ( 20 |
21 |
22 | 23 | {isNode ? '点' : '边'}ID:{id} 24 | 25 |
26 |
27 | {isNode ? '点' : '边'}类型:{label} 28 |
29 | { 30 | !isNode && 31 | <> 32 |
33 | 起点ID:{value?.source} 34 |
35 |
36 | 终点ID:{value?.target} 37 |
38 | 39 | } 40 | {propertyKeys.length ? ( 41 |
42 | {Object.keys(properties).map((item) => { 43 | return ( 44 |
45 |
{item}:
46 |
{`${properties[item]}`}
47 |
48 | ); 49 | })} 50 |
51 | ) : null} 52 |
53 | ); 54 | }; 55 | 56 | export default ElementInfo; 57 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/filter-segmented/index.less: -------------------------------------------------------------------------------- 1 | .filter-segmented { 2 | margin-bottom: 16px; 3 | 4 | :global { 5 | .ant-segmented, 6 | .ant-segmented-group { 7 | width: 100%; 8 | } 9 | 10 | .ant-segmented-item { 11 | flex: 1; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/filter-segmented/index.tsx: -------------------------------------------------------------------------------- 1 | import { Segmented, SegmentedProps } from 'antd'; 2 | import React from 'react'; 3 | import styles from './index.less'; 4 | 5 | const FilterSegmentedOptions = [ 6 | { 7 | label: '属性筛选', 8 | value: 'ATTRIBUTES_FILTER', 9 | }, 10 | { 11 | label: '统计筛选', 12 | value: 'STATISTICS_FILTER', 13 | }, 14 | ]; 15 | 16 | const FilterSegmented: React.FC = (props) => { 17 | return ( 18 |
19 | 20 |
21 | ); 22 | }; 23 | 24 | export default FilterSegmented; 25 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/graph-canvas/index.less: -------------------------------------------------------------------------------- 1 | .graph-canvas { 2 | position: relative; 3 | height: 100%; 4 | width: 100%; 5 | } 6 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/graph-filter/index.less: -------------------------------------------------------------------------------- 1 | .graph-filter { 2 | position: absolute; 3 | top: 8px; 4 | left: 8px; 5 | flex-direction: column; 6 | width: 480px; 7 | padding-left: 4px; 8 | overflow: hidden; 9 | z-index: 2; 10 | 11 | .quickQueryForm { 12 | padding: 12px 16px; 13 | 14 | .formContainer { 15 | padding: 16px; 16 | background: #fff; 17 | border-radius: 8px; 18 | } 19 | 20 | .otherContainer { 21 | margin-top: 24px; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/graph-json-view/index.less: -------------------------------------------------------------------------------- 1 | .graph-json-view { 2 | width: 100%; 3 | overflow-y: auto; 4 | 5 | :global { 6 | .json-container { 7 | padding: 10px 0; 8 | background-color: #eee; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/graph-json-view/index.tsx: -------------------------------------------------------------------------------- 1 | import { cloneDeep } from 'lodash'; 2 | import { onFieldValueChange } from '@formily/core'; 3 | import { useFormEffects } from '@formily/react'; 4 | import React, { useEffect } from 'react'; 5 | import { allExpanded, defaultStyles, JsonView } from 'react-json-view-lite'; 6 | import 'react-json-view-lite/dist/index.css'; 7 | import { useImmer } from 'use-immer'; 8 | import { useSchemaTabContainer } from '@/domains-core/graph-analysis/graph-schema/hooks/use-schema-tab-container'; 9 | import { OriginGraphData } from '@/domains-core/graph-analysis/graph-schema/interfaces'; 10 | import styles from './index.less'; 11 | 12 | const GraphJsonView: React.FC = () => { 13 | const { getTabContainerValue, tabContainerIndex } = useSchemaTabContainer(); 14 | const [state, setState] = useImmer<{ originGraphData: OriginGraphData }>({ 15 | originGraphData: {}, 16 | }); 17 | const { originQueryData } = state.originGraphData; 18 | useFormEffects(() => { 19 | onFieldValueChange( 20 | `CanvasList.${tabContainerIndex}.originGraphData`, 21 | (field) => { 22 | setState((draft) => { 23 | draft.originGraphData = cloneDeep(field.value); 24 | }); 25 | }, 26 | ); 27 | }); 28 | 29 | useEffect(() => { 30 | const originGraphData = getTabContainerValue('originGraphData'); 31 | setState((draft) => { 32 | draft.originGraphData = cloneDeep(originGraphData); 33 | }); 34 | }, []); 35 | return ( 36 |
37 | 43 |
44 | ); 45 | }; 46 | 47 | export default GraphJsonView; 48 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/graph-style-setting/index.less: -------------------------------------------------------------------------------- 1 | .graph-style-setting { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100%; 5 | 6 | .node-edge-form { 7 | flex: 1; 8 | overflow-y: auto; 9 | } 10 | 11 | .footer { 12 | margin-top: 10px; 13 | } 14 | 15 | .collapse-header { 16 | display: flex; 17 | justify-content: space-between; 18 | } 19 | 20 | :global { 21 | .ant-segmented { 22 | width: 100%; 23 | 24 | .ant-segmented-item { 25 | flex: 1; 26 | } 27 | } 28 | 29 | .ant-collapse-header { 30 | display: flex; 31 | align-items: center; 32 | 33 | .ant-collapse-header-text { 34 | flex: 1; 35 | 36 | .anticon { 37 | font-size: 20px; 38 | z-index: 1; 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/graph-table-view/index.less: -------------------------------------------------------------------------------- 1 | .graph-table-view { 2 | width: 100%; 3 | padding: 24px; 4 | overflow-y: auto; 5 | 6 | .table { 7 | margin-bottom: 24px; 8 | 9 | &-title { 10 | margin-bottom: 10px; 11 | font-weight: bold; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/language-query/index.less: -------------------------------------------------------------------------------- 1 | .language-query-container { 2 | position: relative; 3 | z-index: 3; 4 | display: flex; 5 | flex-direction: column; 6 | border-radius: 6px; 7 | padding: 0 4px; 8 | transition: height 0.3s ease; 9 | 10 | .button-container { 11 | :global { 12 | .ant-btn + .ant-btn { 13 | margin-left: 8px; 14 | } 15 | } 16 | } 17 | 18 | &__full { 19 | position: fixed; 20 | width: 100vw; 21 | height: 100vh; 22 | left: 0; 23 | right: 0; 24 | top: 0; 25 | bottom: 0; 26 | z-index: 1000; 27 | } 28 | 29 | .content-container { 30 | flex: 1; 31 | overflow-y: auto; 32 | color: rgba(0, 0, 0, 85%); 33 | font-size: 14px; 34 | border: 1px solid #fff; 35 | border-radius: 8px; 36 | border-image: linear-gradient(#fff, rgba(255, 255, 255, 60%)); 37 | opacity: 0.85; 38 | margin-bottom: 16px; 39 | 40 | .title-group { 41 | display: flex; 42 | justify-content: space-between; 43 | margin-bottom: 8px; 44 | 45 | .text-label-icon { 46 | &::before { 47 | content: '*'; 48 | display: inline-block; 49 | width: 4px; 50 | height: 4px; 51 | margin-right: 8px; 52 | color: #f5222d; 53 | } 54 | } 55 | 56 | .full-handler { 57 | cursor: pointer; 58 | 59 | :global { 60 | .anticon { 61 | font-size: 24px; 62 | } 63 | } 64 | } 65 | } 66 | 67 | .block-container { 68 | border: 1px solid #d9d9d9; 69 | margin-bottom: 24px; 70 | border-radius: 8px; 71 | height: 320px; 72 | overflow: hidden; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/layout-form-slider/index.less: -------------------------------------------------------------------------------- 1 | .inputSlider { 2 | padding: 0 8px; 3 | width: 100%; 4 | overflow: hidden; 5 | 6 | .ant-slider-track { 7 | background-color: #1650ff; 8 | } 9 | 10 | .ant-slider-handle { 11 | border: solid 2px #1650ff; 12 | } 13 | 14 | .ant-slider-dot-active { 15 | border-color: #1650ff; 16 | } 17 | 18 | .ant-switch-checked { 19 | background-color: #1650ff; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/layout-form-slider/index.tsx: -------------------------------------------------------------------------------- 1 | import { Slider } from 'antd'; 2 | import React from 'react'; 3 | import { DEFAULT_MARKS } from '@/domains-core/graph-analysis/graph-schema/constants/layout'; 4 | 5 | interface LayoutFormSliderProps { 6 | onChange?: (value: number) => void; 7 | defaultValue?: number; 8 | value?: number; 9 | marks?: Record; 10 | min?: number; 11 | max?: number; 12 | } 13 | 14 | const LayoutFormSlider: React.FC = ({ 15 | marks = DEFAULT_MARKS, 16 | min = 5, 17 | max = 100, 18 | ...others 19 | }) => { 20 | return ; 21 | }; 22 | 23 | export default LayoutFormSlider; 24 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/layout-form-xy-input/index.less: -------------------------------------------------------------------------------- 1 | .contain { 2 | :global { 3 | .ant-form-item-extra { 4 | text-indent: 8px; 5 | color: rgba(152, 152, 157); 6 | } 7 | 8 | .ant-form-item { 9 | display: inline-block; 10 | margin-bottom: 0; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/layout-form-xy-input/index.tsx: -------------------------------------------------------------------------------- 1 | import { Form, InputNumber, Space } from 'antd'; 2 | import React from 'react'; 3 | import styles from './index.less'; 4 | 5 | const LayoutFormXyInput: React.FC = () => { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ); 16 | }; 17 | 18 | export default LayoutFormXyInput; 19 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/layout-style-segmented/index.less: -------------------------------------------------------------------------------- 1 | .layout-style { 2 | display: flex; 3 | align-items: center; 4 | 5 | &::before { 6 | content: ''; 7 | display: inline-block; 8 | width: 1px; 9 | height: 12px; 10 | background-color: rgba(0, 0, 0, 6%); 11 | margin: 0 10px; 12 | } 13 | 14 | &-title { 15 | color: rgba(152, 152, 157, 100%); 16 | margin-right: 6px; 17 | white-space: nowrap; 18 | } 19 | } 20 | 21 | .popoverContent { 22 | display: flex; 23 | flex-wrap: wrap; 24 | align-items: center; 25 | justify-content: flex-start; 26 | width: 472px; 27 | height: 292px; 28 | margin-top: -8px; 29 | margin-right: -8px; 30 | background-color: #fff; 31 | border-radius: 8px; 32 | 33 | .popoverItem { 34 | width: 110px; 35 | height: 128px; 36 | margin-right: 8px; 37 | border: 1px solid #f2f2f2; 38 | border-radius: 8px; 39 | cursor: pointer; 40 | 41 | &:hover { 42 | border: 1px solid #1754c5; 43 | box-shadow: 0 2px 8px rgba(22, 84, 197, 17%); 44 | } 45 | 46 | .pic { 47 | display: block; 48 | width: 100%; 49 | height: 94px; 50 | padding: 12px; 51 | background-image: linear-gradient( 52 | 179deg, 53 | rgba(222, 232, 255, 50%) 1%, 54 | #dce7ff 96% 55 | ); 56 | border-top-left-radius: 8px; 57 | border-top-right-radius: 8px; 58 | opacity: 0.7; 59 | } 60 | 61 | .text { 62 | width: 110px; 63 | height: 28px; 64 | color: #6a6b71; 65 | font-weight: 400; 66 | font-size: 12px; 67 | line-height: 28px; 68 | text-align: center; 69 | } 70 | } 71 | } 72 | 73 | .popoverContainer { 74 | position: absolute; 75 | top: 8px; 76 | left: 64px; 77 | z-index: 2; 78 | border-radius: 8px; 79 | box-shadow: 0 2px 8px rgba(22, 84, 197, 17%); 80 | } 81 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/page-back-arrow/index.less: -------------------------------------------------------------------------------- 1 | .page-back-arrow { 2 | font-size: 14x; 3 | height: 24px; 4 | line-height: 24px; 5 | margin-right: 10px; 6 | cursor: pointer; 7 | 8 | &:hover { 9 | opacity: 0.5; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/page-back-arrow/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * author: Allen 3 | * file: back arrow 4 | */ 5 | import React from 'react'; 6 | import { LeftOutlined } from '@ant-design/icons'; 7 | import styles from './index.less'; 8 | 9 | const PageBackArrow: React.FC = () => { 10 | 11 | /** hash不支持原生回退,后续支持localStorage来存放hash栈信息来实现 */ 12 | const onBack = () => { 13 | location.hash = '/home' 14 | }; 15 | 16 | return ( 17 |
18 | 19 |
20 | ); 21 | }; 22 | 23 | export default PageBackArrow; 24 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/page-layout-segmented/index.tsx: -------------------------------------------------------------------------------- 1 | import { Segmented, SegmentedProps } from 'antd'; 2 | import type { SegmentedValue } from 'antd/lib/segmented'; 3 | import React from 'react'; 4 | 5 | const PageLayoutSegmented: React.FC = (props) => { 6 | const onChange = (value: SegmentedValue) => { 7 | props.onChange?.(value); 8 | }; 9 | 10 | return ( 11 | 25 | ); 26 | }; 27 | 28 | export default PageLayoutSegmented; 29 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/page-title/index.less: -------------------------------------------------------------------------------- 1 | .page-title { 2 | font-weight: 500; 3 | font-size: 16px; 4 | color: #363740; 5 | display: inline-block; 6 | max-width: 150px; 7 | overflow: hidden; 8 | white-space: nowrap; 9 | text-overflow: ellipsis; 10 | word-wrap: break-word; 11 | } 12 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/page-title/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * author: Allen 3 | * file: graph analyze title 4 | */ 5 | 6 | import React from 'react'; 7 | import styles from './index.less'; 8 | import { parseHashRouterParams } from '@/utils/parseHash'; 9 | 10 | interface PageTitleProps { 11 | value: string; 12 | } 13 | 14 | const PageTitle: React.FC = ({ value }) => { 15 | const { graphName } = parseHashRouterParams(location.hash); 16 | 17 | return
{graphName || value}
; 18 | }; 19 | 20 | export default PageTitle; 21 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/query-filter-segmented/index.less: -------------------------------------------------------------------------------- 1 | .query-filter { 2 | display: flex; 3 | align-items: center; 4 | 5 | &::before { 6 | content: ''; 7 | display: inline-block; 8 | width: 1px; 9 | height: 12px; 10 | background-color: rgba(0, 0, 0, 6%); 11 | margin: 0 10px; 12 | } 13 | 14 | &-title { 15 | color: rgba(152, 152, 157, 100%); 16 | margin-right: 6px; 17 | white-space: nowrap; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/query-segmented/index.less: -------------------------------------------------------------------------------- 1 | .query-segmented { 2 | margin-bottom: 16px; 3 | 4 | :global { 5 | .ant-segmented, 6 | .ant-segmented-group { 7 | width: 100%; 8 | } 9 | 10 | .ant-segmented-item { 11 | flex: 1; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/query-segmented/index.tsx: -------------------------------------------------------------------------------- 1 | import { Segmented, SegmentedProps } from 'antd'; 2 | import React from 'react'; 3 | import styles from './index.less'; 4 | 5 | const QuerySegmentedOptions = [ 6 | { 7 | label: '配置查询', 8 | value: 'CONFIG_QUERY', 9 | }, 10 | { 11 | label: '语句查询', 12 | value: 'LANGUAGE_QUERY', 13 | }, 14 | // { 15 | // label: '路径查询', 16 | // value: 'PATH_QUERY', 17 | // }, 18 | // { 19 | // label: '模版查询', 20 | // value: 'TEMPLATE_QUERY', 21 | // }, 22 | ]; 23 | 24 | const QuerySegmented: React.FC = (props) => { 25 | return ( 26 |
27 | 32 |
33 | ); 34 | }; 35 | 36 | export default QuerySegmented; 37 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/statistics-filter-attribute-select/index.less: -------------------------------------------------------------------------------- 1 | .space-statistic-panel { 2 | position: relative; 3 | 4 | :global { 5 | .ant-space-item { 6 | width: 100%; 7 | } 8 | } 9 | } 10 | 11 | .add-attr { 12 | position: absolute; 13 | top: -33px; 14 | right: -8px; 15 | } 16 | 17 | .tugraph-filter-panel-value { 18 | margin-top: 6px; 19 | } 20 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/statistics-filter-form/index.less: -------------------------------------------------------------------------------- 1 | .tugraph-filter-panel { 2 | padding: 10px; 3 | 4 | .tugraph-filter-panel-criteria-container { 5 | display: flex; 6 | flex-direction: column; 7 | grid-gap: 10px; 8 | margin-top: 15px; 9 | 10 | .tugraph-filter-panel-group { 11 | padding: 8px; 12 | border-radius: 4px; 13 | box-shadow: var(--box-shadow-light, 0 0 4px #ddd); 14 | 15 | .tugraph-filter-panel-prop { 16 | display: flex; 17 | align-items: center; 18 | justify-content: space-between; 19 | } 20 | 21 | .tugraph-filter-panel-recommend-tip { 22 | color: #aaa; 23 | font-size: 10px; 24 | } 25 | } 26 | } 27 | } 28 | 29 | .img { 30 | width: 16px; 31 | height: 16px; 32 | margin-right: 8px; 33 | } 34 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/statistics-filter/index.less: -------------------------------------------------------------------------------- 1 | .statictics-filter-container { 2 | .statictics-filter-container-form { 3 | overflow: auto; 4 | max-height: calc(100vh - 260px); 5 | 6 | .img { 7 | width: 16px; 8 | height: 16px; 9 | margin-right: 8px; 10 | } 11 | } 12 | 13 | .statictics-button-group { 14 | margin-top: 20px; 15 | display: flex; 16 | 17 | :global { 18 | .ant-btn { 19 | width: 80px; 20 | } 21 | } 22 | } 23 | } 24 | 25 | .conditionIcon::before { 26 | display: inline-block; 27 | margin-right: 4px; 28 | color: #ff4d4f; 29 | font-size: 14px; 30 | font-family: SimSun, sans-serif; 31 | line-height: 1; 32 | content: '*'; 33 | } 34 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/components/view-select/index.less: -------------------------------------------------------------------------------- 1 | .view-select { 2 | position: relative; 3 | } 4 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/constants/action-bar.ts: -------------------------------------------------------------------------------- 1 | //数字类型 2 | export const NUMBER_TYPES = [ 3 | 'INT8', 4 | 'INT16', 5 | 'INT32', 6 | 'INT64', 7 | 'DOUBLE', 8 | ] 9 | 10 | //日期类型 11 | export const DATA_TYPE = ['DATETIME','DATE'] -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/constants/graph-style.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_NODE_STYLE = { 2 | size: 30, 3 | color: '#1890ff', 4 | }; 5 | export const DEFAULT_EDGE_STYLE = { 6 | color: '#87e8de', 7 | width: 1, 8 | }; 9 | 10 | export const EDGE_WIDTH_MARKS = { 11 | 1: '最细', 12 | 2: '细', 13 | 5: '中等', 14 | 10: '粗', 15 | }; 16 | 17 | export const NODE_SIZE_MARKS = { 18 | 5: '最小', 19 | 30: '小', 20 | 60: '中等', 21 | 100: '大', 22 | }; 23 | 24 | export const DEFAULT_NODE_SIZE = 30; 25 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const SPLITTER = '|PGSPLIPTER|'; 2 | export const OPERATOR_MAPPING = { 3 | CT: 'CONTAINS', 4 | NC: 'CONTAINS', 5 | EQ: '=', 6 | NE: '<>', 7 | GT: '>', 8 | LT: '<', 9 | GE: '>=', 10 | LE: '<=', 11 | }; 12 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/contexts/index.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from '@antv/g6'; 2 | import { createContext, useContext } from 'react'; 3 | import { Updater } from 'use-immer'; 4 | 5 | export interface GraphSchemaContextValue { 6 | graph?: Graph; 7 | updateContextValue?: Updater; 8 | } 9 | 10 | export const SchemaGraphContext = createContext({}); 11 | 12 | export const useSchemaGraphContext = () => useContext(SchemaGraphContext); 13 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/hooks/use-schema-form-value/index.ts: -------------------------------------------------------------------------------- 1 | import { SchemaFormValue } from '@/domains-core/graph-analysis/graph-schema/interfaces'; 2 | import { useForm } from '@formily/react'; 3 | 4 | export const useSchemaFormValue = () => { 5 | const form = useForm(); 6 | const values: SchemaFormValue = form.getValuesIn('*'); 7 | const { 8 | graphSchemaStyle, 9 | graphSchema, 10 | graphProjectInfo, 11 | htapOrderDetail, 12 | graphEngineType, 13 | graphLanguageList, 14 | } = values; 15 | return { 16 | graphSchema, 17 | graphSchemaStyle, 18 | graphProjectInfo, 19 | form, 20 | htapOrderDetail, 21 | graphEngineType, 22 | graphLanguageList, 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/hooks/use-schema-tab-container/index.ts: -------------------------------------------------------------------------------- 1 | import { OriginGraphData } from '@/domains-core/graph-analysis/graph-schema/interfaces'; 2 | import { mergeGraphData } from '@/domains-core/graph-analysis/graph-schema/utils/merge-graph-data'; 3 | import { useField, useForm } from '@formily/react'; 4 | 5 | export const useSchemaTabContainer = () => { 6 | const field = useField(); 7 | const form = useForm(); 8 | const tabContainerIndex = field.path.toArr()?.[1]; 9 | const tabContainerField = 10 | form.fields[`CanvasList.${tabContainerIndex}.CanvasContainer`]; 11 | const setTabContainerValue = (name: string, value: any) => { 12 | form.setValuesIn(`CanvasList.${tabContainerIndex}.${name}`, value); 13 | }; 14 | const getTabContainerValue = (name: string) => { 15 | return form.getValuesIn(`CanvasList.${tabContainerIndex}.${name}`); 16 | }; 17 | const setTabContainerGraphData = (options: { 18 | data: OriginGraphData; 19 | ifClearGraphData?: boolean; 20 | }) => { 21 | 22 | const { data, ifClearGraphData } = options; 23 | 24 | const { graphData, originQueryData } = data; 25 | const originGraphData = getTabContainerValue('originGraphData'); 26 | // 清空数据 27 | if (ifClearGraphData) { 28 | setTabContainerValue('originGraphData', { graphData, originQueryData }); 29 | } else { 30 | const newData = mergeGraphData(originGraphData.graphData, graphData); 31 | setTabContainerValue('originGraphData', { 32 | graphData: newData, 33 | originQueryData, 34 | }); 35 | } 36 | }; 37 | return { 38 | tabContainerIndex, 39 | tabContainerField, 40 | setTabContainerValue, 41 | getTabContainerValue, 42 | setTabContainerGraphData, 43 | }; 44 | }; 45 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/registers/breathing-node/index.ts: -------------------------------------------------------------------------------- 1 | import { Circle, ExtensionCategory, register } from '@antv/g6'; 2 | 3 | class BreathingNode extends Circle { 4 | onCreate() { 5 | const halo = this.shapeMap.halo; 6 | if (halo) { 7 | halo.animate([{ lineWidth: 0 }, { lineWidth: 20 }], { 8 | duration: 1000, 9 | iterations: Infinity, 10 | direction: 'alternate', 11 | }); 12 | } 13 | } 14 | onUpdate() { 15 | const halo = this.shapeMap.halo; 16 | if (halo) { 17 | halo.animate([{ lineWidth: 0 }, { lineWidth: 20 }], { 18 | duration: 1000, 19 | iterations: Infinity, 20 | direction: 'alternate', 21 | }); 22 | } 23 | } 24 | } 25 | 26 | export const registerBreathingNode = () => { 27 | register(ExtensionCategory.NODE, 'breathing-node', BreathingNode); 28 | }; 29 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/root/index.ts: -------------------------------------------------------------------------------- 1 | import { SchemaField } from '@/domains-core/graph-analysis/graph-schema/components/schema-field'; 2 | import { GRAPH_SCHEMA } from '@/domains-core/graph-analysis/graph-schema/constants/schema'; 3 | // import GraphInfoService from '@/domains-core/graph-analysis/graph-schema/services/graph-info'; 4 | // import { envGraphLanguageTranslator } from '@/domains-core/graph-analysis/graph-schema/translators/env-graph-language-translator'; 5 | import { getEngineTypeByEnv } from '@/domains-core/graph-analysis/graph-schema/utils/get-engine-type-by-env'; 6 | import { getSchemaByEnv } from '@/domains-core/graph-analysis/graph-schema/utils/get-schema-by-env'; 7 | import { getStylesFromSchema } from '@/domains-core/graph-analysis/graph-schema/utils/get-styles-from-schema'; 8 | 9 | export { 10 | GRAPH_SCHEMA, 11 | SchemaField, 12 | // envGraphLanguageTranslator, 13 | getEngineTypeByEnv, 14 | getSchemaByEnv, 15 | getStylesFromSchema, 16 | // GraphInfoService, 17 | }; 18 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/translators/env-graph-language-translator/index.ts: -------------------------------------------------------------------------------- 1 | // import { find } from '@alipay/bigfish/utils/lodash'; 2 | 3 | // export const envGraphLanguageTranslator = ( 4 | // data: Array, 5 | // env: API.GraphDeployEnvEnum, 6 | // ) => { 7 | // const resList = find( 8 | // data, 9 | // (item) => item?.graphDeployEnvEnum === env, 10 | // )?.graphLanguageTypeEnums; 11 | // const graphLanguageList = 12 | // resList 13 | // ?.filter((item: string) => item !== 'GQL') 14 | // ?.map((item) => ({ label: item, value: item })) || []; 15 | // return graphLanguageList; 16 | // }; 17 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/translators/graph-schema-translator/index.ts: -------------------------------------------------------------------------------- 1 | import { AnalysisSchema, Schema } from '@/types/services'; 2 | import { propertiesTranslator } from '@/domains-core/graph-analysis/graph-schema/translators/properties-translator'; 3 | /* Graph analysis schema data conversion */ 4 | export const graphSchemaTranslator = (schema: Schema[]) => { 5 | const nodes: AnalysisSchema[] = []; 6 | const edges: AnalysisSchema[] = []; 7 | schema?.forEach(item => { 8 | if (item?.type === 'VERTEX') { 9 | nodes.push({ 10 | ...item, 11 | nodeType: item?.label, 12 | properties: propertiesTranslator(item?.properties), 13 | }); 14 | } else if (item?.type === 'EDGE') { 15 | edges.push({ 16 | ...item, 17 | edgeType: item?.label, 18 | properties: propertiesTranslator(item?.properties), 19 | }); 20 | } 21 | }); 22 | 23 | return { 24 | nodes, 25 | edges, 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/translators/properties-translator/index.ts: -------------------------------------------------------------------------------- 1 | import { SchemaProperty } from "@/types/services"; 2 | 3 | /* properties conversion */ 4 | export const propertiesTranslator = (properties?: SchemaProperty[]) => { 5 | const newProperties = {}; 6 | 7 | properties?.forEach(item => { 8 | newProperties[item.name] = { 9 | schemaType: item?.type, 10 | }; 11 | }); 12 | 13 | return newProperties; 14 | }; 15 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/download-json/index.ts: -------------------------------------------------------------------------------- 1 | import { GraphData } from '@antv/g6'; 2 | import { isEmpty } from '@antv/util'; 3 | 4 | export const downLoadJson = (graphData: GraphData) => { 5 | const { nodes = [], edges = [] } = graphData; 6 | if (isEmpty([...edges, ...nodes])) return; 7 | try { 8 | const data = { 9 | nodes, 10 | edges, 11 | }; 12 | const element = document.createElement('a'); 13 | const file = new Blob([JSON.stringify(data)], { type: 'text/json' }); 14 | element.href = URL.createObjectURL(file); 15 | element.download = 'data.json'; 16 | document.body.appendChild(element); 17 | element.click(); 18 | document.body.removeChild(element); 19 | URL.revokeObjectURL(element.href); 20 | } catch (error) { 21 | console.log(error); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/download-png/index.tsx: -------------------------------------------------------------------------------- 1 | import { Graph } from '@antv/g6'; 2 | 3 | export const downloadFullImage = async (graph: Graph) => { 4 | if (!graph) return; 5 | const dataURL = await graph.toDataURL(); 6 | const [head, content] = dataURL.split(','); 7 | const contentType = head.match(/:(.*?);/)![1]; 8 | const bstr = atob(content); 9 | let length = bstr.length; 10 | const u8arr = new Uint8Array(length); 11 | 12 | while (length--) { 13 | u8arr[length] = bstr.charCodeAt(length); 14 | } 15 | 16 | const blob = new Blob([u8arr], { type: contentType }); 17 | 18 | const url = URL.createObjectURL(blob); 19 | const a = document.createElement('a'); 20 | a.href = url; 21 | a.download = 'graph.png'; 22 | a.click(); 23 | }; 24 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/filter-by-top-rule/index.ts: -------------------------------------------------------------------------------- 1 | import { TypePropertyCondition } from '@/domains-core/graph-analysis/graph-schema/interfaces'; 2 | import { filterByPropertyCondition } from '@/domains-core/graph-analysis/graph-schema/utils/filter-by-property-condition'; 3 | 4 | export const filterByTopRule = ( 5 | data: Record, 6 | rule: TypePropertyCondition, 7 | ): boolean => { 8 | const { type, conditions } = rule; 9 | // 未配置规则一律通过 10 | if (conditions.length === 0) { 11 | return true; 12 | } 13 | 14 | return conditions.some( 15 | (item) => 16 | data.label === type && filterByPropertyCondition(data.properties, item), 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/filter-graph-data-by-value/index.ts: -------------------------------------------------------------------------------- 1 | /** 根据值筛选画布数据 */ 2 | export const filterGraphDataByValue = ( 3 | value: string, 4 | arr: Record[], 5 | parentKey = '', 6 | ) => { 7 | const result: Record[] = []; 8 | arr.forEach((obj) => { 9 | for (let key in obj) { 10 | if (typeof obj[key] === 'object') { 11 | let innerResult = filterGraphDataByValue(value, [obj[key]], key); 12 | if (innerResult.length > 0) { 13 | result.push({ 14 | id: obj.id, 15 | key: innerResult[0].key, 16 | value: obj, 17 | }); 18 | break; 19 | } 20 | } else if ( 21 | /** 需要支持 number 和 string 都能查询,把统一转成字符来匹配 */ 22 | (typeof obj[key] === 'string' || typeof obj[key] === 'number') && 23 | obj[key].toString().includes(value) && 24 | key !== 'color' 25 | ) { 26 | let fullKey = parentKey ? `${parentKey}.${key}` : key; 27 | result.push({ 28 | id: obj.id, 29 | key: fullKey, 30 | value: obj, 31 | }); 32 | break; 33 | } 34 | } 35 | }); 36 | return result; 37 | }; 38 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/generate-edge-type-map/index.ts: -------------------------------------------------------------------------------- 1 | const generateEdgeTypeMap = ({ 2 | nodes, 3 | edges, 4 | }: Record): Map => { 5 | const edgesKeys: string[] = []; 6 | const edgesTypeMap = new Map(); 7 | edges.forEach((item: Record) => { 8 | const node1: Record = nodes.find( 9 | (i: Record) => item?.source === i?.id, 10 | ); 11 | const node2: Record = nodes.find( 12 | (i: Record) => item?.target === i?.id, 13 | ); 14 | 15 | const type = `+${item?.label}+<${node1?.label}=>${node2?.label}>`; 16 | if (edgesTypeMap && Array.isArray(edgesTypeMap.get(type))) { 17 | if (item?.label) edgesTypeMap.get(type).push(item); 18 | } else { 19 | if (item?.label) { 20 | edgesKeys.push(type); 21 | edgesTypeMap.set(type, [item]); 22 | } 23 | } 24 | }); 25 | return edgesTypeMap; 26 | }; 27 | 28 | export default generateEdgeTypeMap; 29 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/get-chart-data/index.ts: -------------------------------------------------------------------------------- 1 | import { GraphData, NodeData } from '@antv/g6'; 2 | 3 | /** 4 | * 5 | * @param graphData 画布数据 6 | * @param prop 节点/边属性 7 | * @param elementType 元素类型 8 | * @returns 图表数据 9 | */ 10 | export const getChartData = ( 11 | graphData: GraphData, 12 | prop: string, 13 | elementType: 'node' | 'edge', 14 | currentValue?: string, 15 | ) => { 16 | let elements = elementType === 'node' ? graphData.nodes : graphData.edges; 17 | if (currentValue) { 18 | elements = elements?.filter( 19 | (item) => item.label === currentValue, 20 | ) as NodeData[]; 21 | } 22 | 23 | const chartData = new Map(); 24 | elements?.forEach((e) => { 25 | if (e.properties && e.properties[prop] !== undefined) { 26 | const value: any = e.properties[prop]; 27 | chartData.set( 28 | value, 29 | chartData.has(value) ? chartData.get(value)! + 1 : 1, 30 | ); 31 | } 32 | }); 33 | return chartData; 34 | }; 35 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/get-engine-type-by-env/index.ts: -------------------------------------------------------------------------------- 1 | export const getEngineTypeByEnv = ( 2 | graphProjectInfo: API.GeaMakerProjectVO, 3 | env: API.GraphDeployEnvEnum, 4 | ): API.SchemaEngineTypeEnum => { 5 | switch (env) { 6 | case 'OFFLINE_TEST': 7 | return graphProjectInfo?.devEngineTypeEnum || 'geamaker_geabase'; 8 | case 'ONLINE_GRAY': 9 | return graphProjectInfo?.grayEngineTypeEnum || 'geamaker_geabase'; 10 | case 'ONLINE_PRODUCTION': 11 | return graphProjectInfo?.prodEngineTypeEnum || 'geamaker_geabase'; 12 | default: 13 | return 'geamaker_geabase'; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/get-filtered-element-style/index.ts: -------------------------------------------------------------------------------- 1 | import { GraphStyleSettingValue } from '@/domains-core/graph-analysis/graph-schema/components/graph-style-setting'; 2 | import { filterElementByPropertyValue } from '@/domains-core/graph-analysis/graph-schema/utils/filter-element-by-property-value'; 3 | 4 | export const getFilteredElementStyle = (options: { 5 | styles: GraphStyleSettingValue; 6 | elementData: any; 7 | }) => { 8 | const { styles, elementData } = options; 9 | const styleList = styles.elementStyleList; 10 | const { label, properties } = elementData; 11 | const style = styleList.find((item) => { 12 | const { propertyFilters, elementType } = item; 13 | return ( 14 | item[`${elementType}Type` as 'nodeType'] === label && 15 | (propertyFilters 16 | ? propertyFilters.every((filter) => 17 | filterElementByPropertyValue(properties, filter), 18 | ) 19 | : true) 20 | ); 21 | }); 22 | return style; 23 | }; 24 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/get-histogram-data/index.ts: -------------------------------------------------------------------------------- 1 | import { GraphData } from '@antv/g6'; 2 | 3 | /** 4 | * 5 | * @param graphData 画布数据 6 | * @param prop 节点/边属性 7 | * @param elementType 元素类型 8 | * @returns 直方图图表数据 9 | */ 10 | export const getHistogramData = ( 11 | graphData: GraphData, 12 | prop: string, 13 | elementType: 'node' | 'edge', 14 | ) => { 15 | const elements = elementType === 'node' ? graphData.nodes : graphData.edges; 16 | const data = elements 17 | ?.filter((e) => e.properties && e.properties[prop]) 18 | .map((e) => ({ value: e.properties?.[prop] as number })); 19 | data?.sort((a, b) => a.value! - b.value!); 20 | return data; 21 | }; 22 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/get-label-text-by-style-config/index.ts: -------------------------------------------------------------------------------- 1 | import { GraphStyleSettingValue } from '@/domains-core/graph-analysis/graph-schema/components/graph-style-setting'; 2 | import { GraphSchema } from '@/domains-core/graph-analysis/graph-schema/interfaces'; 3 | import { getFilteredElementStyle } from '@/domains-core/graph-analysis/graph-schema/utils/get-filtered-element-style'; 4 | 5 | export const getLabelTextByStyleConfig = (options: { 6 | styles: GraphStyleSettingValue; 7 | elementData: any; 8 | graphSchema?: GraphSchema; 9 | elementType: 'node' | 'edge'; 10 | }) => { 11 | const { elementData, graphSchema, elementType } = options; 12 | const style = getFilteredElementStyle(options); 13 | const schema = graphSchema?.[`${elementType}s`].find( 14 | (item) => item[`${elementType}Type` as 'nodeType'] === elementData.label, 15 | ); 16 | let labelList: string[] = []; 17 | 18 | if (style) { 19 | const { displayLabels, showProperty, showTypeAlias } = style; 20 | if (showProperty) { 21 | displayLabels?.forEach((key) => { 22 | if (key === 'ID') { 23 | labelList.push(elementData.id); 24 | } else { 25 | labelList.push(elementData.properties[key] || '-'); 26 | } 27 | }); 28 | } 29 | if (schema && schema.typeAlias && showTypeAlias) { 30 | labelList.push(schema.typeAlias); 31 | } 32 | 33 | return labelList.join('\n'); 34 | } else { 35 | return ''; 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/get-node-icon-by-style-config/index.ts: -------------------------------------------------------------------------------- 1 | import { GraphStyleSettingValue } from '@/domains-core/graph-analysis/graph-schema/components/graph-style-setting'; 2 | import { getFilteredElementStyle } from '@/domains-core/graph-analysis/graph-schema/utils/get-filtered-element-style'; 3 | import { iconLoader } from '@/components/icon-loader'; 4 | 5 | export const getNodeIconByStyleConfig = (options: { 6 | styles: GraphStyleSettingValue; 7 | elementData: any; 8 | }) => { 9 | const { styles, elementData } = options; 10 | const style = getFilteredElementStyle({ styles, elementData }); 11 | if (style && style.nodeIcon) { 12 | return { 13 | iconFontFamily: style.nodeIcon.fontFamily, 14 | iconText: iconLoader(style.nodeIcon.name), 15 | }; 16 | } else { 17 | return { 18 | iconFontFamily: elementData.style?.iconFontFamily, 19 | iconText: elementData.style?.iconText, 20 | }; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/get-node-size-by-style-config/index.ts: -------------------------------------------------------------------------------- 1 | import { GraphStyleSettingValue } from '@/domains-core/graph-analysis/graph-schema/components/graph-style-setting'; 2 | import { 3 | DEFAULT_EDGE_STYLE, 4 | DEFAULT_NODE_SIZE, 5 | DEFAULT_NODE_STYLE, 6 | } from '@/domains-core/graph-analysis/graph-schema/constants/graph-style'; 7 | import { getFilteredElementStyle } from '@/domains-core/graph-analysis/graph-schema/utils/get-filtered-element-style'; 8 | 9 | export const getNodeSizeByStyleConfig = (options: { 10 | styles: GraphStyleSettingValue; 11 | elementData: any; 12 | }) => { 13 | const { styles, elementData } = options; 14 | const style = getFilteredElementStyle({ styles, elementData }); 15 | if (style) { 16 | return style.nodeSize; 17 | } else { 18 | return elementData.style?.size || DEFAULT_NODE_SIZE; 19 | } 20 | }; 21 | export const getElementFillByStyleConfig = (options: { 22 | styles: GraphStyleSettingValue; 23 | elementData: any; 24 | elementType: 'node' | 'edge'; 25 | }) => { 26 | const { styles, elementData, elementType } = options; 27 | const style = getFilteredElementStyle({ styles, elementData }); 28 | if (style) { 29 | return style[`${elementType}Color` as 'nodeColor']; 30 | } else { 31 | return ( 32 | elementData.style?.fill || 33 | (elementType === 'node' 34 | ? DEFAULT_NODE_STYLE.color 35 | : DEFAULT_EDGE_STYLE.color) 36 | ); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/get-operator-list-by-value-type/index.ts: -------------------------------------------------------------------------------- 1 | /** 根据数据类型获得比较符选项 */ 2 | export const getOperatorListByValueType = (valueType: string = '') => { 3 | const type = valueType.toLowerCase(); 4 | 5 | if (type === 'string') { 6 | return [ 7 | { 8 | value: '=', 9 | label: '=', 10 | }, 11 | { 12 | value: '<>', 13 | label: '≠', 14 | }, 15 | ]; 16 | } 17 | 18 | if (['int8', 'int16', 'int32', 'int64', 'double', 'date', 'datetime'].includes(type)) { 19 | return [ 20 | { 21 | value: '>', 22 | label: '>', 23 | }, 24 | { 25 | value: '<', 26 | label: '<', 27 | }, 28 | { 29 | value: '=', 30 | label: '=', 31 | }, 32 | { 33 | value: '<>', 34 | label: '≠', 35 | }, 36 | { 37 | value: '>=', 38 | label: '≥', 39 | }, 40 | { 41 | value: '<=', 42 | label: '≤', 43 | }, 44 | ]; 45 | } 46 | 47 | /* boolean */ 48 | if (type === 'bool') { 49 | return [ 50 | { 51 | value: '=', 52 | label: '=', 53 | }, 54 | { 55 | value: '<>', 56 | label: '≠', 57 | }, 58 | ]; 59 | } 60 | return []; 61 | }; 62 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/get-property-value-ranks/index.ts: -------------------------------------------------------------------------------- 1 | import { SPLITTER } from '@/domains-core/graph-analysis/graph-schema/constants'; 2 | import { GraphData } from '@antv/g6'; 3 | 4 | /** 5 | * 筛选出计算一个属性的所有属性值的排序,若一个属性值是 outlier,则 rank 为 0,即最重要 6 | * @param propertyGraphData 属性图数据 7 | * @param itemType 图元素类型,节点或边 8 | * @param propertyName 属性名称 9 | * @returns 10 | */ 11 | export const getPropertyValueRanks = ( 12 | propertyGraphData: GraphData | undefined, 13 | itemType: 'node' | 'edge', 14 | propertyName: string, 15 | ): { 16 | propertyValue: unknown; 17 | rank: number; 18 | isOutlier?: boolean; 19 | }[] => { 20 | if (!propertyGraphData) return []; 21 | const valueRanks: { 22 | propertyValue: unknown; 23 | rank: number; 24 | isOutlier?: boolean; 25 | }[] = []; 26 | propertyGraphData.nodes?.forEach((propertyValueNode) => { 27 | const { id, rank, isOutlier } = propertyValueNode as any; 28 | const [iType, pName, pValue] = id.split(SPLITTER); 29 | if (propertyName === 'id' || iType !== itemType || pName !== propertyName) 30 | return []; 31 | valueRanks.push({ 32 | propertyValue: pValue, 33 | rank: isOutlier ? 0 : rank, 34 | isOutlier, 35 | }); 36 | }); 37 | return valueRanks; 38 | }; 39 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/get-schema-by-env/index.ts: -------------------------------------------------------------------------------- 1 | /** 根据环境获得schema */ 2 | export const getSchemaByEnv = ( 3 | allEnvSchema: API.PlatformModelSchemaVO, 4 | env: API.GraphDeployEnvEnum, 5 | ) => { 6 | switch (env) { 7 | case 'OFFLINE_TEST': 8 | return allEnvSchema?.devVisualModelDataVO || {}; 9 | case 'ONLINE_GRAY': 10 | return allEnvSchema?.grayVisualModelDataVO || {}; 11 | case 'ONLINE_PRODUCTION': 12 | return allEnvSchema?.prodVisualModelDataVO || {}; 13 | default: 14 | return { nodes: [], edges: [] }; 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/get-styled-graph-data/index.ts: -------------------------------------------------------------------------------- 1 | import { GraphData, NodeData } from '@antv/g6'; 2 | 3 | /** 注入点边的schema样式 */ 4 | export const getStyledGraphData = (options: { 5 | graphData?: GraphData | API.AggregatedResultVO; 6 | graphSchemaStyle: Record; 7 | }) => { 8 | const { graphData, graphSchemaStyle } = options; 9 | const { nodes, edges } = graphData || {}; 10 | if (graphSchemaStyle) { 11 | return { 12 | nodes: nodes?.map((item) => { 13 | return { 14 | ...item, 15 | style: graphSchemaStyle?.[item.label as string], 16 | }; 17 | }), 18 | edges: edges?.map((item) => { 19 | return { 20 | ...item, 21 | style: graphSchemaStyle?.[item.label as string], 22 | }; 23 | }), 24 | } as GraphData; 25 | } else { 26 | return graphData as GraphData; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/get-styles-from-schema/index.ts: -------------------------------------------------------------------------------- 1 | import { iconLoader } from '@/components/icon-loader'; 2 | import { DEFAULT_NODE_STYLE } from '@/domains-core/graph-analysis/graph-schema/constants/graph-style'; 3 | import { NodeData } from '@antv/g6'; 4 | 5 | /** 获得schema中的点边样式 */ 6 | export const getStylesFromSchema = (schemaData?: API.VisualModelDataVO) => { 7 | const style: Record = {}; 8 | schemaData?.nodes?.forEach(node => { 9 | const { nodeType, color } = node; 10 | const activeColor = color || '#1890ff'; 11 | let icon = node.icon || ({} as any); 12 | if (typeof icon === 'string') { 13 | try { 14 | icon = JSON.parse(icon.replace(/"/g, '"')); 15 | } catch (e) { 16 | // eslint-disable-next-line no-console 17 | console.error(e); 18 | } 19 | } 20 | style[nodeType!] = { 21 | ...DEFAULT_NODE_STYLE, 22 | fill: activeColor, 23 | iconFontFamily: icon.fontFamily, 24 | iconText: iconLoader(icon.name), 25 | }; 26 | }); 27 | schemaData?.edges?.forEach(edge => { 28 | const { color, edgeType } = edge; 29 | const activeColor = color || '#1890ff'; 30 | style[edgeType!] = { 31 | stroke: activeColor, 32 | }; 33 | }); 34 | return style; 35 | }; 36 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/group-and-count-nodes-by-label/index.ts: -------------------------------------------------------------------------------- 1 | const groupAndCountNodesByLabel = ({ 2 | nodes, 3 | typeMap, 4 | }: Record): Record[] => { 5 | const result: Record[] = []; 6 | const keys: string[] = []; 7 | const num: Record = {}; 8 | nodes.forEach((i: Record) => { 9 | if (num[i?.label]) { 10 | num[i?.label] = num[i?.label] + 1; 11 | } else { 12 | num[i?.label] = 1; 13 | } 14 | }); 15 | nodes.forEach((item: Record) => { 16 | if (keys.includes(item?.label)) { 17 | if (typeMap[item?.label]) { 18 | if (Array.isArray(typeMap[item?.label])) { 19 | typeMap[item?.label].push(item?.id); 20 | } 21 | } else { 22 | typeMap[item?.label] = [item?.id]; 23 | } 24 | } else { 25 | typeMap[item?.label] = [item?.id]; 26 | keys.push(item?.label); 27 | result.push({ 28 | ...item, 29 | num: num[item?.label] || 0, 30 | }); 31 | } 32 | }); 33 | 34 | return result; 35 | }; 36 | 37 | export default groupAndCountNodesByLabel; 38 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/hex-to-rgba/index.ts: -------------------------------------------------------------------------------- 1 | /** hex 转 rgba */ 2 | export const hexToRGBA = (hex: string, alpha: number) => { 3 | let r = parseInt(hex.slice(1, 3), 16); 4 | let g = parseInt(hex.slice(3, 5), 16); 5 | let b = parseInt(hex.slice(5, 7), 16); 6 | 7 | if (alpha) { 8 | return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')'; 9 | } else { 10 | return 'rgb(' + r + ', ' + g + ', ' + b + ')'; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/highlight-sub-graph/index.ts: -------------------------------------------------------------------------------- 1 | import { Graph, GraphData } from '@antv/g6'; 2 | 3 | /** 4 | *高亮选中的节点和边 5 | * @param graph G6 graph 实例 6 | * @param data 子图数据 7 | * @returns 8 | */ 9 | export const highlightSubGraph = (graph: Graph, data: GraphData) => { 10 | if (!graph || graph.destroyed) return {}; 11 | const source = graph.getData() as GraphData; 12 | 13 | const nodeIds = data.nodes?.map((node) => node.id); 14 | const edgeIds: string[] = []; 15 | /** 需要考虑聚合边的情况,需要构造全量的边 */ 16 | data.edges?.forEach((edge) => { 17 | edgeIds.push(edge.id!); 18 | }); 19 | 20 | const sourceNodesCount = source.nodes?.length; 21 | const sourceEdgesCount = edgeIds.length; //考虑聚合边 22 | const nodesCount = data.nodes?.length; 23 | const edgesCount = data.edges?.length; 24 | const isEmpty = nodesCount === 0 && edgesCount === 0; 25 | const isFull = 26 | nodesCount === sourceNodesCount && edgesCount === sourceEdgesCount; 27 | // 如果是空或者全部图数据,则恢复到画布原始状态,取消高亮 28 | if (isEmpty || isFull) { 29 | source.nodes?.forEach(function (node) { 30 | graph.setElementState(node.id, []); 31 | }); 32 | source.edges?.forEach(function (edge) { 33 | graph.setElementState(edge.id!, []); 34 | }); 35 | return { isEmpty, isFull }; 36 | } 37 | 38 | source.nodes?.forEach((node) => { 39 | const hasMatch = nodeIds?.includes(node.id); 40 | if (hasMatch) { 41 | graph.setElementState(node.id, ['active'], true); 42 | } 43 | }); 44 | source.edges?.forEach((edge) => { 45 | const { id } = edge; 46 | 47 | /** 考虑聚合边的情况,aggregate 数据中的 edgeId 匹配上一个就可以高亮整个聚合边 */ 48 | 49 | const hasMatch = edgeIds.includes(id!); 50 | 51 | if (hasMatch) { 52 | graph.setElementState(edge.id!, ['active'], true); 53 | } 54 | }); 55 | return { 56 | isEmpty, 57 | isFull, 58 | }; 59 | }; 60 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/merge-graph-data/index.ts: -------------------------------------------------------------------------------- 1 | import { uniqueElementsBy } from '@/domains-core/graph-analysis/graph-schema/utils/unique-elements-by'; 2 | import { GraphData } from '@antv/g6'; 3 | 4 | /** 合并画布和接口数据 */ 5 | export const mergeGraphData = ( 6 | data: GraphData = { nodes: [], edges: [] }, 7 | responseData: any = { nodes: [], edges: [] }, 8 | ) => { 9 | const { nodes = [], edges = [] } = responseData; 10 | // TODO 旧逻辑,暂时注释 11 | // if ( 12 | // ( data.nodes?.length || 13 | // data.edges?.length) && 14 | // (responseData?.nodes?.length || 15 | // responseData?.edges?.length) 16 | // ) { 17 | const graphData: GraphData = { 18 | ...data, 19 | nodes: uniqueElementsBy([...data.nodes!, ...nodes], (a, b) => { 20 | return a.id === b.id; 21 | }), 22 | edges: uniqueElementsBy([...data.edges!, ...edges], (a, b) => { 23 | if (a.id && b.id) { 24 | return a.id === b.id; 25 | } 26 | return a.source === b.source && a.target === b.target; 27 | }), 28 | }; 29 | return graphData; 30 | // } else { 31 | // return { ...responseData }; 32 | // } 33 | }; 34 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/pie-chart-edges-translator/index.ts: -------------------------------------------------------------------------------- 1 | const PieChartEdgesTranslator = (edges: Record) => { 2 | const num: Record = {}; 3 | if (Array.isArray(edges)) { 4 | edges.forEach((i: Record) => { 5 | if (num[i?.label] || num['other']) { 6 | num[i?.label] = num[i?.label] + 1; 7 | } else { 8 | num[i?.label || 'other'] = 1; 9 | } 10 | }); 11 | } 12 | 13 | return Object.keys(num).map((k: string) => ({ 14 | label: k, 15 | num: num[k], 16 | ...num[k], 17 | id: k, 18 | })); 19 | }; 20 | export default PieChartEdgesTranslator; 21 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/reset-graph-active-status/index.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from '@antv/g6'; 2 | 3 | /** 清空画布点边状态 */ 4 | export const resetGraphActiveStatus = (graph?: Graph) => { 5 | if (graph) { 6 | graph.getNodeData().forEach((node) => { 7 | graph.setElementState(node.id, []); 8 | }); 9 | graph.getEdgeData().forEach((edge) => { 10 | graph.setElementState(edge.id!, []); 11 | }); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/resize-canvas/index.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from '@antv/g6'; 2 | 3 | export const resizeCanvas = (options: { graph?: Graph }) => { 4 | const { graph } = options; 5 | if (graph) { 6 | const canvasDom = document.getElementById( 7 | graph.getOptions().container as string, 8 | ); 9 | if (canvasDom) { 10 | graph.resize(canvasDom.offsetWidth, canvasDom.offsetHeight); 11 | graph.fitCenter(); 12 | } 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/set-graph-active-status/index.ts: -------------------------------------------------------------------------------- 1 | import { EdgeData, Graph, NodeData } from '@antv/g6'; 2 | 3 | /** 设置指定点边状态 */ 4 | export const setGraphActiveStatus = (options: { 5 | graph?: Graph; 6 | nodes: NodeData[]; 7 | edges: (EdgeData | string)[]; 8 | }) => { 9 | const { graph, nodes, edges } = options; 10 | if (graph) { 11 | graph.getNodeData().forEach((node) => { 12 | graph.setElementState(node.id, 'inactive'); 13 | }); 14 | graph.getEdgeData().forEach((edge) => { 15 | graph.setElementState(edge.id!, 'inactive'); 16 | }); 17 | nodes?.forEach((nodeItem) => { 18 | graph.setElementState(nodeItem.id!, 'active'); 19 | }); 20 | edges.forEach((edge) => { 21 | graph.setElementState( 22 | typeof edge === 'string' ? edge : edge.id!, 23 | 'active', 24 | ); 25 | }); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /src/domains-core/graph-analysis/graph-schema/utils/unique-elements-by/index.ts: -------------------------------------------------------------------------------- 1 | /** 数组去重 */ 2 | export const uniqueElementsBy = ( 3 | arr: any[], 4 | fn: (arg0: any, arg1: any) => any, 5 | ) => 6 | arr.reduce((acc, v) => { 7 | if (!acc.some((x: any) => fn(v, x))) acc.push(v); 8 | return acc; 9 | }, []); 10 | -------------------------------------------------------------------------------- /src/layouts/index.less: -------------------------------------------------------------------------------- 1 | .umiContainer { 2 | background: #e4ebff; 3 | flex: 1; 4 | } 5 | .appLayout { 6 | display: flex; 7 | width: 100%; 8 | height: 100%; 9 | min-height: 100vh; 10 | min-width: 100vw; 11 | overflow: hidden; 12 | } 13 | 14 | .nav { 15 | height: 54px; 16 | line-height: 54px; 17 | width: 100%; 18 | .logo { 19 | padding: 0 24px; 20 | object-fit: contain; 21 | width: 160px; 22 | height: 100%; 23 | } 24 | .links { 25 | display: flex; 26 | justify-content: flex-start; 27 | align-items: center; 28 | width: 100%; 29 | .link { 30 | margin: 0 12px; 31 | cursor: pointer; 32 | height: 48px; 33 | background-color: #e4ebff; 34 | color: rgba(26, 27, 37, 0.65); 35 | font-size: 16px; 36 | } 37 | .linked { 38 | margin: 0 12px; 39 | cursor: pointer; 40 | height: 48px; 41 | font-size: 16px; 42 | background-color: #e4ebff; 43 | color: rgba(0, 0, 0, 0.85); 44 | font-weight: bold; 45 | border-bottom: 2px solid rgba(0, 0, 0, 0.85); 46 | } 47 | } 48 | } 49 | .consoleContainer { 50 | display: flex; 51 | width: 100%; 52 | height: 100%; 53 | min-height: calc(100vh - 54px); 54 | min-width: 100vw; 55 | } 56 | 57 | .consoleContentContainer { 58 | margin: 24px; 59 | height: calc(100% - 120px); 60 | overflow: hidden; 61 | } 62 | 63 | .userInfo { 64 | display: flex; 65 | height: 54px; 66 | justify-content: flex-end; 67 | align-items: center; 68 | line-height: 54px; 69 | margin-right: 24px; 70 | } 71 | -------------------------------------------------------------------------------- /src/layouts/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * file: container 相关tsx定义 3 | * author: Allen 4 | */ 5 | import { Col, Row } from 'antd'; 6 | import React from 'react'; 7 | import { Outlet } from 'umi'; 8 | 9 | // style 10 | import styles from './index.less'; 11 | 12 | interface IContainerProps { 13 | children?: React.ReactNode; 14 | style?: React.CSSProperties; 15 | } 16 | 17 | interface IConsoleContainerProps { 18 | sidebar: React.ReactNode; 19 | content: React.ReactNode; 20 | } 21 | 22 | export const Container: React.FC = props => { 23 | return ( 24 |
29 | {props?.children} 30 |
31 | ); 32 | }; 33 | 34 | export const ConsoleContainer: React.FC = props => { 35 | const { sidebar, content } = props; 36 | 37 | return ( 38 | 39 | {sidebar} 40 | {content} 41 | 42 | ); 43 | }; 44 | 45 | export const ConsoleContentContainer: React.FC = props => { 46 | return ( 47 |
48 | {props?.children || null} 49 |
50 | ); 51 | }; 52 | 53 | export default function Layout() { 54 | return ( 55 |
56 | 57 |
58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /src/layouts/nav.tsx: -------------------------------------------------------------------------------- 1 | import { UserCenter } from '@/components/studio'; 2 | import { APP_LINKS } from '@/constants'; 3 | // @ts-nocheck 4 | import { Col, Row } from 'antd'; 5 | import { useState } from 'react'; 6 | import { useLocation } from 'umi'; 7 | import styles from './index.less'; 8 | const Nav = ({ linkView = true }) => { 9 | const location = useLocation(); 10 | const [curPath, setPath] = useState( 11 | location?.pathname === '/' ? '/home' : location?.pathname, 12 | ); 13 | 14 | return ( 15 | 16 | 17 | 21 | 22 | 23 | {linkView && ( 24 |
25 | {APP_LINKS.map(({ title, key, path }) => { 26 | return ( 27 |
{ 31 | window.location.hash = path; 32 | }} 33 | > 34 | {title} 35 |
36 | ); 37 | })} 38 |
39 | )} 40 | 41 | 42 |
43 | 44 |
45 | 46 |
47 | ); 48 | }; 49 | 50 | export default Nav; 51 | -------------------------------------------------------------------------------- /src/pages/console.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * file: console entry, include auth and database 3 | * author: Allen 4 | */ 5 | import { useState } from 'react'; 6 | 7 | import ConsoleMenu from '@/components/console-menu'; 8 | import Auth from '@/components/console/auth-manager'; 9 | import Database from '@/components/console/database-info'; 10 | import { 11 | ConsoleContainer, 12 | ConsoleContentContainer, 13 | Container, 14 | } from '@/layouts'; 15 | import Nav from '@/layouts/nav'; 16 | 17 | import { getQueryString } from '@/components/studio/utils/routeParams'; 18 | 19 | const components: any = { 20 | TU_GRAPH_AUTH: , 21 | TU_GRAPH_DATABASE: , 22 | }; 23 | 24 | const ConsolePage = () => { 25 | const key: string = getQueryString('menu') || 'TU_GRAPH_AUTH'; 26 | const [menuKey, setMenuKey] = useState(key); 27 | return ( 28 | 29 |