Compare commits

..

42 Commits

Author SHA1 Message Date
liang.cheng 170dfe720a Merge pull request 'feature/cl' (#17) from feature/cl into master
Reviewed-on: #17
4 months ago
Chengliang 8e8569110e 多语言遗留问题修复 6 months ago
Chengliang dce9480ee9 组织架构图更新根节点 8 months ago
liang.cheng 2b8751d7dd Merge pull request '组织架构图增加版本删除功能' (#16) from feature/cl into master
Reviewed-on: #16
9 months ago
Chengliang c9005b9a08 组织架构图增加版本删除功能 9 months ago
liang.cheng 0bc78d717f Merge pull request 'feature/cl' (#15) from feature/cl into master
Reviewed-on: #15
10 months ago
Chengliang 8a59de812a 修复其它维度展示 10 months ago
Chengliang 6bf43c8150 修复异步传参问题 10 months ago
liang.cheng 0513ad1345 Merge pull request 'feature/cl' (#14) from feature/cl into master
Reviewed-on: #14
10 months ago
Chengliang d2e0da7297 多语言新功能完善 10 months ago
Chengliang 483c34fc05 新功能 10 months ago
Chengliang 55dbade138 组织架构图功能完善 10 months ago
Chengliang 69dc2b3815 多语言修改 10 months ago
Chengliang a99d911db1 表格样式处理 10 months ago
liang.cheng f825a33bb8 Merge pull request '多语言设置' (#11) from feature/cl into master
Reviewed-on: #11
10 months ago
Chengliang b2757dfa11 多语言设置 10 months ago
liang.cheng 68402ffb6e Merge pull request '组织架构图多语言设置' (#10) from feature/cl into master
Reviewed-on: #10
11 months ago
Chengliang 0d702fe1a3 组织架构图多语言设置 11 months ago
liang.cheng 67cfb21f72 Merge pull request '修复已知问题' (#9) from feature/cl into master
Reviewed-on: #9
12 months ago
Chengliang a884e58203 修复已知问题 12 months ago
liang.cheng 2a5628765e Merge pull request 'feature/cl' (#8) from feature/cl into master
Reviewed-on: #8
1 year ago
Chengliang 1a676ab4a8 维度切换 下拉框处理 1 year ago
Chengliang 9f661db803 修复分权参数 1 year ago
Chengliang e0c1b1c3b5 岗位显示修复 2 years ago
Chengliang c01a806c88 修复 2 years ago
liang.cheng 32e1b51632 Merge pull request 'feature/cl' (#7) from feature/cl into master
Reviewed-on: #7
2 years ago
Chengliang dd703a05d4 组织架构图 2 years ago
Chengliang c018b71f13 bug修复 2 years ago
Chengliang ce6591b351 修复bug 2 years ago
Chengliang 662057515c 测试 2 years ago
Chengliang 17e0626e70 增加隐藏部门 2 years ago
Chengliang 08217b0a69 组织快速调整页面优化 2 years ago
Chengliang 5b9788cfc8 修改 2 years ago
liang.cheng 747a625c1c Merge pull request 'feature/cl' (#6) from feature/cl into master
Reviewed-on: #6
2 years ago
Chengliang 12482afb8f 组织调整 2 years ago
Chengliang 303e8e5e50 组织架构图 2 years ago
Chengliang ea3c4a4fee 1 2 years ago
liang.cheng 038c6fef3c Merge pull request '组织快捷调整页面' (#5) from feature/cl into master
Reviewed-on: #5
2 years ago
Chengliang 7747f91160 组织快捷调整页面 2 years ago
liang.cheng 68e26fa992 Merge pull request 'feature/drag' (#4) from feature/drag into master
Reviewed-on: #4
2 years ago
Chengliang 63d0f86805 组织架构图优化 2 years ago
Chengliang 622ebcbcd7 功能迭代 2 years ago

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" native2AsciiForPropertiesFiles="true" defaultCharsetForPropertiesFiles="UTF-8" />
</project>

@ -0,0 +1,36 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="JavaDoc" enabled="true" level="WARNING" enabled_by_default="true">
<option name="TOP_LEVEL_CLASS_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="INNER_CLASS_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="METHOD_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="@return@param@throws or @exception" />
</value>
</option>
<option name="FIELD_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="IGNORE_DEPRECATED" value="false" />
<option name="IGNORE_JAVADOC_PERIOD" value="true" />
<option name="IGNORE_DUPLICATED_THROWS" value="false" />
<option name="IGNORE_POINT_TO_ITSELF" value="false" />
<option name="myAdditionalJavadocTags" value="date" />
</inspection_tool>
</profile>
</component>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="JSX" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="10" project-jdk-type="JavaSDK" />
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/org-chart-frant.iml" filepath="$PROJECT_DIR$/.idea/org-chart-frant.iml" />
</modules>
</component>
</project>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

@ -0,0 +1,593 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="449daba1-1dd3-4ffd-906d-6ef641fb81de" name="Default Changelist" comment="">
<change beforePath="$PROJECT_DIR$/src/d3-org-chart.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/d3-org-chart.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/dragTree.jsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/dragTree.jsx" afterDir="false" />
</list>
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileEditorManager">
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
<file pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/src/d3-org-chart.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-146">
<folding>
<element signature="e#0#49#0" expanded="true" />
<element signature="n#style#0;n#div#0;n#!!top" expanded="true" />
<element signature="n#style#0;n#div#0;n#!!top" expanded="true" />
<element signature="n#style#0;n#div#0;n#!!top" expanded="true" />
<element signature="n#style#0;n#div#0;n#!!top" expanded="true" />
<element signature="n#style#0;n#div#0;n#!!top" expanded="true" />
<element signature="n#style#0;n#div#0;n#!!top" expanded="true" />
<element signature="n#style#0;n#div#0;n#!!top" expanded="true" />
<element signature="n#style#0;n#div#0;n#!!top" expanded="true" />
<element signature="n#style#0;n#div#0;n#!!top" expanded="true" />
<element signature="n#style#0;n#div#0;n#!!top" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
<component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER" />
<component name="JsGulpfileManager">
<detection-done>true</detection-done>
<sorting>DEFINITION_ORDER</sorting>
</component>
<component name="NodeModulesDirectoryManager">
<handled-path value="$PROJECT_DIR$/node_modules" />
</component>
<component name="NodePackageJsonFileManager">
<packageJsonPaths>
<path value="$PROJECT_DIR$/package.json" />
</packageJsonPaths>
</component>
<component name="ProjectFrameBounds" fullScreen="true">
<option name="x" value="1440" />
<option name="width" value="1920" />
<option name="height" value="1080" />
</component>
<component name="ProjectInspectionProfilesVisibleTreeState">
<entry key="Project Default">
<profile-state>
<expanded-state>
<State />
<State>
<id>ActionScript specificJavaScript</id>
</State>
<State>
<id>Android</id>
</State>
<State>
<id>Ant</id>
</State>
<State>
<id>Batch Applications</id>
</State>
<State>
<id>CFML</id>
</State>
<State>
<id>CSS</id>
</State>
<State>
<id>Class structureJava</id>
</State>
<State>
<id>Code quality toolsCSS</id>
</State>
<State>
<id>Code quality toolsJavaScript</id>
</State>
<State>
<id>Code style issuesJava</id>
</State>
<State>
<id>CoffeeScript</id>
</State>
<State>
<id>CorrectnessLintAndroid</id>
</State>
<State>
<id>Cucumber</id>
</State>
<State>
<id>Error handlingJava</id>
</State>
<State>
<id>Faces Model</id>
</State>
<State>
<id>Flow type checkerJavaScript</id>
</State>
<State>
<id>FreeMarker</id>
</State>
<State>
<id>General</id>
</State>
<State>
<id>GeneralCoffeeScript</id>
</State>
<State>
<id>GeneralJavaScript</id>
</State>
<State>
<id>Google Web Toolkit</id>
</State>
<State>
<id>GrailsGroovy</id>
</State>
<State>
<id>Groovy</id>
</State>
<State>
<id>HTML</id>
</State>
<State>
<id>Hibernate</id>
</State>
<State>
<id>IconsUsabilityLintAndroid</id>
</State>
<State>
<id>ImportsJava</id>
</State>
<State>
<id>Inheritance issuesJava</id>
</State>
<State>
<id>Internationalization</id>
</State>
<State>
<id>InternationalizationJava</id>
</State>
<State>
<id>InternationalizationLintAndroid</id>
</State>
<State>
<id>Invalid elementsCSS</id>
</State>
<State>
<id>J2ME issuesJava</id>
</State>
<State>
<id>JSON and JSON5</id>
</State>
<State>
<id>JSP</id>
</State>
<State>
<id>Java</id>
</State>
<State>
<id>Java 8Java language level migration aidsJava</id>
</State>
<State>
<id>Java interop issuesKotlin</id>
</State>
<State>
<id>Java language level migration aidsJava</id>
</State>
<State>
<id>JavaFX</id>
</State>
<State>
<id>JavaScript</id>
</State>
<State>
<id>JavadocJava</id>
</State>
<State>
<id>Kotlin</id>
</State>
<State>
<id>LintAndroid</id>
</State>
<State>
<id>LintLintAndroid</id>
</State>
<State>
<id>Manifest</id>
</State>
<State>
<id>Markdown</id>
</State>
<State>
<id>MessagesCorrectnessLintAndroid</id>
</State>
<State>
<id>Node.jsJavaScript</id>
</State>
<State>
<id>OSGi</id>
</State>
<State>
<id>OtherGroovy</id>
</State>
<State>
<id>Pages Navigation Model</id>
</State>
<State>
<id>Plugin DevKit</id>
</State>
<State>
<id>PortabilityJava</id>
</State>
<State>
<id>Potentially confusing code constructsGroovy</id>
</State>
<State>
<id>Probable bugsJava</id>
</State>
<State>
<id>Properties Files</id>
</State>
<State>
<id>Properties FilesJava</id>
</State>
<State>
<id>RELAX NG</id>
</State>
<State>
<id>RESTful Web Service</id>
</State>
<State>
<id>Resource managementJava</id>
</State>
<State>
<id>SQL</id>
</State>
<State>
<id>Sass/SCSS</id>
</State>
<State>
<id>SecurityLintAndroid</id>
</State>
<State>
<id>SetupSpring CoreSpring</id>
</State>
<State>
<id>Spring</id>
</State>
<State>
<id>Spring BootSpring</id>
</State>
<State>
<id>Spring CoreSpring</id>
</State>
<State>
<id>Struts</id>
</State>
<State>
<id>Struts 1Struts</id>
</State>
<State>
<id>Struts 2Struts</id>
</State>
<State>
<id>Style issuesKotlin</id>
</State>
<State>
<id>TestNGJava</id>
</State>
<State>
<id>Threading issuesJava</id>
</State>
<State>
<id>TypeScript</id>
</State>
<State>
<id>UI Form</id>
</State>
<State>
<id>UsabilityLintAndroid</id>
</State>
<State>
<id>Validity issuesGroovy</id>
</State>
<State>
<id>Velocity</id>
</State>
<State>
<id>Verbose or redundant code constructsJava</id>
</State>
<State>
<id>VisibilityJava</id>
</State>
<State>
<id>Vue</id>
</State>
<State>
<id>Web Services</id>
</State>
<State>
<id>XML</id>
</State>
<State>
<id>XMLSpring CoreSpring</id>
</State>
<State>
<id>XPath</id>
</State>
<State>
<id>YAML</id>
</State>
</expanded-state>
</profile-state>
</entry>
</component>
<component name="ProjectView">
<navigator proportions="" version="1">
<foldersAlwaysOnTop value="true" />
</navigator>
<panes>
<pane id="ProjectPane">
<subPane>
<expand>
<path>
<item name="org-chart-frant" type="b2602c69:ProjectViewProjectNode" />
<item name="org-chart-frant" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="org-chart-frant" type="b2602c69:ProjectViewProjectNode" />
<item name="org-chart-frant" type="462c0819:PsiDirectoryNode" />
<item name="src" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="org-chart-frant" type="b2602c69:ProjectViewProjectNode" />
<item name="org-chart-frant" type="462c0819:PsiDirectoryNode" />
<item name="src" type="462c0819:PsiDirectoryNode" />
<item name="components" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="org-chart-frant" type="b2602c69:ProjectViewProjectNode" />
<item name="org-chart-frant" type="462c0819:PsiDirectoryNode" />
<item name="src" type="462c0819:PsiDirectoryNode" />
<item name="components" type="462c0819:PsiDirectoryNode" />
<item name="dialog" type="462c0819:PsiDirectoryNode" />
</path>
</expand>
<select />
</subPane>
</pane>
<pane id="PackagesPane" />
<pane id="Scope" />
<pane id="CatdeaLogs" />
</panes>
</component>
<component name="PropertiesComponent">
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="aspect.path.notification.shown" value="true" />
<property name="com.android.tools.idea.instantapp.provision.ProvisionBeforeRunTaskProvider.myTimeStamp" value="1690446615685" />
<property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
<property name="nodejs_npm_path_reset_for_default_project" value="true" />
<property name="project.structure.last.edited" value="Project" />
<property name="project.structure.proportion" value="0.0" />
<property name="project.structure.side.proportion" value="0.0" />
<property name="settings.editor.selected.configurable" value="preferences.externalDependencies" />
</component>
<component name="RebelAgentSelection">
<selection>jr</selection>
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="449daba1-1dd3-4ffd-906d-6ef641fb81de" name="Default Changelist" comment="" />
<created>1689842546961</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1689842546961</updated>
<workItem from="1689842557088" duration="205000" />
<workItem from="1690192167781" duration="39000" />
<workItem from="1690341272941" duration="164000" />
<workItem from="1690446551998" duration="218000" />
</task>
<servers />
</component>
<component name="TimeTrackingManager">
<option name="totallyTimeSpent" value="626000" />
</component>
<component name="ToolWindowManager">
<frame x="1440" y="0" width="1920" height="1080" extended-state="0" />
<layout>
<window_info content_ui="combo" id="Project" order="0" visible="true" weight="0.19382322" />
<window_info id="Structure" order="1" side_tool="true" weight="0.25" />
<window_info id="JRebel" order="2" side_tool="true" />
<window_info id="Image Layers" order="3" />
<window_info id="Capture Tool" order="4" />
<window_info id="UI Designer" order="5" />
<window_info id="Favorites" order="6" side_tool="true" />
<window_info id="Designer" order="7" />
<window_info anchor="bottom" id="Message" order="0" />
<window_info anchor="bottom" id="Find" order="1" />
<window_info anchor="bottom" id="Run" order="2" />
<window_info anchor="bottom" id="Debug" order="3" weight="0.4" />
<window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
<window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
<window_info anchor="bottom" id="TODO" order="6" />
<window_info anchor="bottom" id="Event Log" order="7" side_tool="true" />
<window_info anchor="bottom" id="Terminal" order="8" />
<window_info anchor="bottom" id="Docker" order="9" show_stripe_button="false" />
<window_info anchor="bottom" id="Messages" order="10" />
<window_info anchor="bottom" id="Database Changes" order="11" show_stripe_button="false" />
<window_info active="true" anchor="bottom" id="Version Control" order="12" visible="true" weight="0.26039603" />
<window_info anchor="bottom" id="JRebel Console" order="13" side_tool="true" />
<window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
<window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
<window_info anchor="right" id="Palette" order="3" />
<window_info anchor="right" id="Capture Analysis" order="4" />
<window_info anchor="right" id="Maven Projects" order="5" />
<window_info anchor="right" id="aiXcoder" order="6" />
<window_info anchor="right" id="Database" order="7" />
<window_info anchor="right" id="Palette&#9;" order="8" />
<window_info anchor="right" id="Theme Preview" order="9" />
</layout>
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="1" />
</component>
<component name="Vcs.Log.History.Properties">
<option name="COLUMN_ORDER">
<list>
<option value="0" />
<option value="2" />
<option value="3" />
<option value="1" />
</list>
</option>
</component>
<component name="VcsContentAnnotationSettings">
<option name="myLimit" value="2678400000" />
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/src/components/dialog/index.jsx">
<provider selected="true" editor-type-id="text-editor">
<state>
<folding>
<element signature="e#0#26#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/d3-org-chart.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-146">
<folding>
<element signature="e#0#49#0" expanded="true" />
<element signature="n#style#0;n#div#0;n#!!top" expanded="true" />
<element signature="n#style#0;n#div#0;n#!!top" expanded="true" />
<element signature="n#style#0;n#div#0;n#!!top" expanded="true" />
<element signature="n#style#0;n#div#0;n#!!top" expanded="true" />
<element signature="n#style#0;n#div#0;n#!!top" expanded="true" />
<element signature="n#style#0;n#div#0;n#!!top" expanded="true" />
<element signature="n#style#0;n#div#0;n#!!top" expanded="true" />
<element signature="n#style#0;n#div#0;n#!!top" expanded="true" />
<element signature="n#style#0;n#div#0;n#!!top" expanded="true" />
<element signature="n#style#0;n#div#0;n#!!top" expanded="true" />
</folding>
</state>
</provider>
</entry>
</component>
<component name="ideajad">
<property name="annotate" value="false" />
<property name="annotateFully" value="false" />
<property name="braces" value="false" />
<property name="clear" value="false" />
<property name="confirmNavigationTriggeredDecompile" value="true" />
<property name="dead" value="true" />
<property name="defaultInitializers" value="false" />
<property name="dissassemblerOnly" value="false" />
<property name="fieldsFirst" value="true" />
<property name="fileExtension" value="java" />
<property name="fullyQualifiedNames" value="false" />
<property name="indentation" value="4" />
<property name="intRadix" value="10" />
<property name="lineNumbersAsComments" value="true" />
<property name="longRadix" value="10" />
<property name="maxStringLength" value="64" />
<property name="nocast" value="false" />
<property name="noclass" value="false" />
<property name="nocode" value="false" />
<property name="noconv" value="false" />
<property name="noctor" value="false" />
<property name="nodos" value="false" />
<property name="nofd" value="false" />
<property name="noinner" value="false" />
<property name="nolvt" value="false" />
<property name="nonlb" value="false" />
<property name="outputDirectory" value="" />
<property name="packFields" value="3" />
<property name="packImports" value="7" />
<property name="prefixNumericalClasses" value="_cls" />
<property name="prefixNumericalFields" value="_fld" />
<property name="prefixNumericalLocals" value="_lcl" />
<property name="prefixNumericalMethods" value="_mth" />
<property name="prefixNumericalParameters" value="_prm" />
<property name="prefixUnusedExceptions" value="_ex" />
<property name="readonly" value="false" />
<property name="safe" value="false" />
<property name="sort" value="false" />
<property name="spaceAfterKeyword" value="false" />
<property name="splitStringsAtNewline" value="false" />
<property name="useTabs" value="false" />
</component>
<component name="masterDetails">
<states>
<state key="GlobalLibrariesConfigurable.UI">
<settings>
<last-edited>fastdfs-client-java-1.27-RELEASE</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="JdkListConfigurable.UI">
<settings>
<last-edited>10</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="ProjectJDKs.UI">
<settings>
<last-edited>1.8</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="ProjectLibrariesConfigurable.UI">
<settings>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
</states>
</component>
</project>

@ -1,11 +1,3 @@
/*
* @Author: Chengliang 1546584672@qq.com
* @Date: 2022-08-04 10:22:55
* @LastEditors: Chengliang 1546584672@qq.com
* @LastEditTime: 2022-12-16 11:24:29
* @FilePath: /org-chart-frant/.umirc.ts
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import { defineConfig } from 'umi';
export default defineConfig({
@ -20,13 +12,16 @@ export default defineConfig({
routes: [
{ path: '/user', component: '@/pages/user' },
{ path: '/company', component: '@/pages/company' },
{ path: '/dragtree', component: '@/pages/dragTree' },
{ path: '/statistics', component: '@/pages/statisticsTable' },
],
fastRefresh: {},
antd: {},
proxy: {
'/api': {
// 标识需要进行转换的请求的url
target: 'http://127.0.0.1:8686/api', // 服务端域名 / http://localhost:8686
target: 'http://127.0.0.1:8686/api', // 服务端域名
//target: 'http://221.226.25.34:11080/api',
changeOrigin: true, // 允许域名进行转换
pathRewrite: { '^/api': '' }, // 将请求url里的ci去掉
},

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@ -0,0 +1,115 @@
import { Form, Input, Modal, TreeSelect, message, Checkbox } from 'antd';
import React, {
useState,
useEffect,
useRef,
forwardRef,
useImperativeHandle,
} from 'react';
import * as d3 from 'd3';
import './index.less';
import { HomeOutlined } from '@ant-design/icons';
import './index.less';
import { getLabel } from '../../util/i18n.js';
const layout = {
labelCol: { span: 8 },
wrapperCol: { span: 14 },
};
const CopyDialog = forwardRef(
({ open, onCreate, onCancel, labelData }, ref) => {
const [treeData, setData] = useState([]);
const [form] = Form.useForm();
const formRef = useRef(null);
useImperativeHandle(ref, () => ({
getTreeData() {
form.resetFields();
d3.json('/api/bs/hrmorganization/orgchart/getSubCompanyTree').then(
(data) => {
data.companyTree.map((item, index) => {
item.icon = <HomeOutlined />;
});
setData(data.companyTree);
},
);
},
}));
/**
* 根节点树异步加载
* @param {} parentId
* @returns
*/
const onRootLoadData = (treeNode) =>
new Promise((resolve) => {
const { id } = treeNode.props;
setTimeout(() => {
d3.json(
`/api/bs/hrmorganization/orgchart/getSubCompanyTree?subcompany=${id}`,
).then((data) => {
data.companyTree.map((item, index) => {
item.icon = <HomeOutlined />;
});
let arr = [...treeData, ...data.companyTree];
setData(arr);
});
resolve(undefined);
}, 200);
});
return (
<Modal
open={open}
title={getLabel(547505, labelData)}
okText={getLabel(547319, labelData)}
cancelText={getLabel(547318, labelData)}
onCancel={onCancel}
onOk={() => {
form
.validateFields()
.then((values) => {
onCreate(values);
})
.catch((info) => {
console.log('Validate Failed:', info);
});
}}
>
<Form ref={formRef} {...layout} form={form} name="form_in_modal">
<Form.Item
name="company"
label={getLabel(547506, labelData)}
rules={[
{
required: true,
message: `${getLabel(547507, labelData)}`,
},
]}
>
<TreeSelect
className="custom-tree-select"
treeDataSimpleMode
allowClear
style={{ width: '100%' }}
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
loadData={onRootLoadData}
treeData={treeData}
treeIcon
/>
</Form.Item>
<Form.Item
name="copySubDept"
valuePropName="checked"
label={getLabel(547262, labelData)}
>
<Checkbox />
</Form.Item>
</Form>
</Modal>
);
},
);
export default CopyDialog;

@ -0,0 +1,111 @@
import React from 'react';
import { TreeSelect, Modal, message } from 'antd';
export default class OperateDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
treeData: [],
open: false,
confirmLoading: false,
title: '',
operateType: '',
root: '',
type: 1,
};
}
componentDidMount() {}
handleOk = () => {
const { type } = this.state;
if (type === 1) {
this.props.addFolderNode();
} else {
this.props.deleteNode();
}
setTimeout(() => {
this.setState({ open: false });
message.success('操作成功');
}, 200);
};
/**
* 根节点树数据
* @param {} parentId
* @returns
*/
getNodeTreeNode = (parentId) => {
this.setState({ confirmLoading: true });
fetch(`/api/bs/hrmorganization/orgchart/getSubCompanyTree`)
.then((res) => res.json())
.then((data) => {
if (data.api_status) {
let arr = [];
arr = [...this.state.treeData, ...data.companyTree];
this.setState({
treeData: arr,
confirmLoading: false,
});
}
});
};
/**
* 根节点树异步加载
* @param {} parentId
* @returns
*/
onRootLoadData = (treeNode) =>
new Promise((resolve) => {
const { id } = treeNode.props;
setTimeout(() => {
let api = `/api/bs/hrmorganization/orgchart/getSubCompanyTree?subcompany=${id}`;
this.getNodeTreeNode(api);
resolve(undefined);
}, 500);
});
showOperate = (id, title, type) => {
this.setState({
open: true,
confirmLoading: true,
title: title,
type: type,
});
this.getNodeTreeNode(id);
};
onRootChange = (value) => {
this.setState({ root: value });
};
render() {
const { treeData, open, confirmLoading, title } = this.state;
return (
<>
<Modal
title={title}
cancelText="取消"
okText="确定"
open={open}
onOk={this.handleOk}
confirmLoading={confirmLoading}
onCancel={() => this.setState({ open: false })}
>
<TreeSelect
treeDataSimpleMode
allowClear
style={{ width: '100%' }}
value={this.state.root}
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
placeholder="请选择要删除的节点"
onChange={this.onRootChange}
loadData={this.onRootLoadData}
treeData={treeData}
/>
</Modal>
</>
);
}
}

@ -0,0 +1,5 @@
.ant-select-tree
.ant-select-tree-treenode-disabled
.ant-select-tree-node-content-wrapper {
color: rgba(0, 0, 0, 0.85);
}

@ -0,0 +1,126 @@
import { Form, Input, Modal, TreeSelect, message } from 'antd';
import React, {
useState,
useEffect,
useRef,
forwardRef,
useImperativeHandle,
} from 'react';
import * as d3 from 'd3';
import './index.less';
import { HomeOutlined, FolderOutlined } from '@ant-design/icons';
import './index.less';
import { getLabel } from '../../util/i18n.js';
const layout = {
labelCol: { span: 6 },
wrapperCol: { span: 14 },
};
const MergeDialog = forwardRef(
({ open, onCreate, onCancel, labelData }, ref) => {
const [treeData, setData] = useState([]);
const [form] = Form.useForm();
const formRef = useRef(null);
const [treeLine, setTreeLine] = useState(true);
useImperativeHandle(ref, () => ({
getTreeData() {
form.resetFields();
d3.json('/api/bs/hrmorganization/orgchart/getDepartmentTree').then(
(data) => {
data.departmentTree.map((item, index) => {
item.icon = <HomeOutlined />;
});
setData(data.departmentTree);
},
);
},
}));
/**
* 根节点树异步加载
* @param {} parentId
* @returns
*/
const onRootLoadData = (treeNode) =>
new Promise((resolve) => {
const { id } = treeNode.props;
setTimeout(() => {
d3.json(
`/api/bs/hrmorganization/orgchart/getDepartmentTree?subcompany=${id}`,
).then((data) => {
data.departmentTree.map((item, index) => {
if (item.key.indexOf('d') > -1) {
item.icon = <FolderOutlined />;
} else {
item.icon = <HomeOutlined />;
}
});
let arr = [...treeData, ...data.departmentTree];
setData(arr);
});
resolve(undefined);
}, 200);
});
return (
<Modal
open={open}
title={getLabel(547508, labelData)}
okText={getLabel(547319, labelData)}
cancelText={getLabel(547318, labelData)}
onCancel={onCancel}
onOk={() => {
form
.validateFields()
.then((values) => {
onCreate(values);
})
.catch((info) => {
console.log('Validate Failed:', info);
});
}}
>
<Form ref={formRef} {...layout} form={form} name="form_in_modal">
<Form.Item
name="department"
label={getLabel(547264, labelData)}
rules={[
{
required: true,
message: `${getLabel(547509, labelData)}`,
},
]}
>
<TreeSelect
className="custom-tree-select"
treeDataSimpleMode
allowClear
style={{ width: '100%' }}
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
loadData={onRootLoadData}
treeData={treeData}
treeIcon
/>
</Form.Item>
<Form.Item
name="mergeName"
label={getLabel(547265, labelData)}
rules={[
{
required: true,
message: `${getLabel(547510, labelData)}`,
},
]}
>
<Input />
</Form.Item>
</Form>
</Modal>
);
},
);
export default MergeDialog;

@ -8,6 +8,8 @@ import {
Table,
Spin,
Checkbox,
Row,
Col,
} from 'antd';
import { OrgChartComponent } from '@/components/orgChart';
import * as d3 from 'd3';
@ -16,6 +18,7 @@ import { message } from 'antd';
import jsPDF from 'jspdf';
import ExportJsonExcel from 'js-export-excel';
import './index.less';
import { getLabel } from '../../util/i18n.js';
let addNodeChildFunc = null;
let orgChart = null;
@ -31,14 +34,24 @@ export default class DrawerComponents extends React.Component {
dataSource: [],
columns: [],
spinning: true,
showJob: true,
showJob: false,
defaultTypeValue: ['isBelongTo'],
defaultStatusValue: [0, 1, 2, 3],
disabled: false,
};
}
componentDidMount() {}
//
onNodeClick(node) {}
onNodeClick(node) {
if (node.ftype == '4') {
window.open(
`/spa/hrm/index_mobx.html#/main/hrm/card/cardInfo/${node.id}`,
'_blank',
);
}
}
onButtonClick(event, d) {
if (d.children) {
@ -63,7 +76,7 @@ export default class DrawerComponents extends React.Component {
//
getDeatilDatas(params, type = 'chart', showJob = '1') {
this.setState({ spinning: true });
this.setState({ spinning: true, data: [], dataSource: [] });
d3.json(
'/api/bs/hrmorganization/orgchart/getDepartmentDetail?' +
qs.stringify({ detauleType: type, ...params, showJob }),
@ -72,7 +85,11 @@ export default class DrawerComponents extends React.Component {
if (type == 'chart') {
this.setState({ data: data.data, spinning: false });
} else {
this.setState({ dataSource: data.dataSource, columns: data.columns });
this.setState({
dataSource: data.dataSource,
columns: data.columns,
spinning: false,
});
}
});
}
@ -164,6 +181,7 @@ export default class DrawerComponents extends React.Component {
};
handleExport = (e) => {
const { labelData } = this.props;
let type = e.key == '1' ? 'png' : e.key == '1' ? 'pdf' : 'excel';
if (type == 'png') {
orgChart && orgChart.exportImg({ full: true });
@ -181,8 +199,8 @@ export default class DrawerComponents extends React.Component {
工号: dataSource[i].workCode,
姓名: dataSource[i].lastName,
性别: dataSource[i].sex,
部门: dataSource[i].departmentId,
分部: dataSource[i].subcompanyid1,
部门: dataSource[i].departmentName,
分部: dataSource[i].subcompanyName,
岗位: dataSource[i].jobTitle,
手机号: dataSource[i].mobile,
};
@ -190,30 +208,30 @@ export default class DrawerComponents extends React.Component {
}
}
}
option.fileName = '组织信息';
option.fileName = `${getLabel(547468, labelData)}`;
option.datas = [
{
sheetData: dataTable,
sheetName: 'sheet',
sheetFilter: [
'序号',
'工号',
'姓名',
'性别',
'部门',
'分部',
'岗位',
'手机号',
`${getLabel(547327, labelData)}`,
`${getLabel(547328, labelData)}`,
`${getLabel(547329, labelData)}`,
`${getLabel(547330, labelData)}`,
`${getLabel(547331, labelData)}`,
`${getLabel(547332, labelData)}`,
`${getLabel(547333, labelData)}`,
`${getLabel(547334, labelData)}`,
],
sheetHeader: [
'序号',
'工号',
'姓名',
'性别',
'部门',
'分部',
'岗位',
'手机号',
`${getLabel(547327, labelData)}`,
`${getLabel(547328, labelData)}`,
`${getLabel(547329, labelData)}`,
`${getLabel(547330, labelData)}`,
`${getLabel(547331, labelData)}`,
`${getLabel(547332, labelData)}`,
`${getLabel(547333, labelData)}`,
`${getLabel(547334, labelData)}`,
],
},
];
@ -226,6 +244,8 @@ export default class DrawerComponents extends React.Component {
* 节点渲染
*/
nodeContentRender = (d, i, arr, state) => {
const { labelData } = this.props;
if (d.data.ftype == 2) {
return `<div style="position: relative;">
<div style=" height: 152px;background-size: 100% 100%;box-sizing: border-box;padding-top: 30px;position:relative;z-index:2">
@ -239,15 +259,17 @@ export default class DrawerComponents extends React.Component {
<img src="./img/department.png" style="width: 58px; height: 58px;position:absolute;left:29px; border-radius: 50%; margin-top: 16px;position:absolute;left:29px;z-index:999"/>
</div>
<div style="display: inline-block; margin-left: 6px;width: 55%">
<div class="dept-box" style="font-size: 15px;font-family: Microsoft YaHei-Regular, Microsoft YaHei;font-weight: 900;color: #333333;height: 25px;line-height: 25px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">
<div class="dept-box" style="font-size: 15px;font-family: Microsoft YaHei-Regular, Microsoft YaHei;font-weight: 900;color: #333333;height: 25px;line-height: 25px;width:110px,white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">
${d.data.fname}
</div>
<div style="font-size: 13px;font-family: Microsoft YaHei-Bold, Microsoft YaHei;color: #333333;height: 25px;line-height: 25px;">
负责人:${d.data.fleader}
<div style="font-size: 13px;font-family: Microsoft YaHei-Bold, Microsoft YaHei;color: #333333;line-height: 25px;">
${getLabel(547190, labelData)}:${d.data.fleader}
</div>
<div style="display:flex" >
<div style="height: 25px; line-height: 25px; min-width: 80px;">
在岗: ${d.data.fonjob}
${getLabel(547323, labelData)}: ${
d.data.fonjob
} ${getLabel(547525, labelData)}
</div>
</div>
</div>
@ -262,9 +284,13 @@ export default class DrawerComponents extends React.Component {
</div>
<img src="./img/user-card/jobicon-orange.png" style="margin-left: 20px; vertical-align: top;"/>
<div style="display: inline-block; margin-left: 15px;">
<div style="font-size: 15px;height: 25px;line-height: 25px;font-family: Microsoft YaHei-Bold, Microsoft YaHei;font-weight: bold;color: #333333;">${d.data.fname}</div>
<div style="font-size: 15px;height: 25px;line-height: 25px;font-family: Microsoft YaHei-Bold, Microsoft YaHei;font-weight: bold;color: #333333;">${
d.data.fname
}</div>
<div style="font-size: 13px;font-family: Microsoft YaHei-Regular, Microsoft YaHei;font-weight: 400;color: #333333;display: flex;height: 25px;line-height: 25px;">
<span>在岗: ${d.data.fonjob} </span>
<span>${getLabel(547323, labelData)}: ${
d.data.fonjob
} ${getLabel(547525, labelData)}</span>
</div>
</div>
</div>
@ -283,15 +309,19 @@ export default class DrawerComponents extends React.Component {
: './img/default_avator.png'
}" style="width: 58px; height: 58px; border-radius: 50%; margin-top: 16px;margin-left: -6px;z-index:999" />
</div>
<div style="display: inline-block; margin-left: 6px;width: 55%;height:100%">
<div style='display:flex;align-items:center;height: 25px;line-height: 25px;margin-top:15px'>
<div style="font-weight: bold;font-size: 15px;ont-family: Microsoft YaHei-Bold, Microsoft YaHei;color: #333333;">${
d.data.fname
}</div>
<div style="display: inline-block;width: 55%;height:100%">
<div style='display:flex;align-items:center;height: 25px;line-height: 25px;margin-top:2px'>
<div style="font-weight: bold;font-size: 14px;ont-family: Microsoft YaHei-Bold, Microsoft YaHei;color: #333333;">
${d.data.fname}
<span style='color: red;'>(${d.data.accountType})</span>
</div>
</div>
<div style="font-size: 13px;font-family: Microsoft YaHei-Regular, Microsoft YaHei;font-weight: 400;color: #333333;display: flex;height: 25px;line-height: 25px;">
<span>司龄: ${d.data.companyWorkYear} </span>
<div style="font-size: 13px;margin-top: 5px;font-family: Microsoft YaHei-Regular, Microsoft YaHei;font-weight: 400;color: #333333;display: flex;height: 25px;line-height: 25px;">
<span>${getLabel(547324, labelData)}: ${
d.data.companyWorkYear
} ${getLabel(547526, labelData)}</span>
</div>
</div>
</div>
</div>`;
@ -299,23 +329,78 @@ export default class DrawerComponents extends React.Component {
};
showDrawer = (params) => {
const showJob = params.fclass == '0' ? '1' : '0';
this.getDeatilDatas(params, 'chart', showJob);
const { defaultStatusValue, defaultTypeValue } = this.state;
params = {
...params,
typeValue: defaultTypeValue.join(','),
statusValue: defaultStatusValue.join(','),
};
this.getDeatilDatas(params, 'chart', '0');
this.setState({ open: true, params: params });
};
onClose = () => {
this.setState({ open: false, detailType: 'chart', showJob: true });
this.setState({
open: false,
detailType: 'chart',
showJob: false,
defaultStatusValue: [0, 1, 2, 3],
defaultTypeValue: ['isBelongTo'],
disabled: false,
});
};
changeDetail = () => {
const { detailType, params } = this.state;
const { detailType, params, defaultTypeValue, defaultStatusValue } =
this.state;
const newParam = {
...params,
typeValue: defaultTypeValue.join(','),
statusValue: defaultStatusValue.join(','),
};
let type = detailType == 'chart' ? 'table' : 'chart';
const showJob = params.fclass == '0' ? '1' : '0';
const showJob = this.state.showJob ? '1' : '0';
this.setState({
detailType: type,
});
this.getDeatilDatas(params, type, showJob);
this.getDeatilDatas(newParam, type, showJob);
};
onStatusChange = (checkedValues) => {
const { labelData } = this.props;
if (checkedValues.length === 0) {
return message.error(`${getLabel(547728, labelData)}`, 2);
}
this.setState({
defaultStatusValue: checkedValues,
});
const { defaultTypeValue, params, detailType } = this.state;
const showJob = this.state.showJob ? '1' : '0';
const nParam = {
...params,
statusValue: checkedValues.join(','),
typeValue: defaultTypeValue.join(','),
};
this.getDeatilDatas(nParam, detailType, showJob);
};
onTypeChange = (checkedValues) => {
const { labelData } = this.props;
if (this.state.showJob) {
return message.error(`${getLabel(547729, labelData)}`, 2);
}
this.setState({
defaultTypeValue: checkedValues,
disabled: checkedValues.includes('isManager') ? true : false,
});
const { defaultStatusValue, params, detailType } = this.state;
const showJob = this.state.showJob ? '1' : '0';
const nParam = {
...params,
statusValue: defaultStatusValue.join(','),
typeValue: checkedValues.join(','),
};
this.getDeatilDatas(nParam, detailType, showJob);
};
render() {
@ -328,21 +413,43 @@ export default class DrawerComponents extends React.Component {
columns,
spinning,
showJob,
defaultStatusValue,
defaultTypeValue,
disabled,
} = this.state;
let arr = [];
const { labelData } = this.props;
const optionsType = [
{ label: `${getLabel(547715, labelData)}`, value: 'isManager' },
{ label: `${getLabel(547716, labelData)}`, value: 'isBelongTo' },
];
const optionsStatus = [
{ label: `${getLabel(547718, labelData)}`, value: 0 },
{ label: `${getLabel(547719, labelData)}`, value: 1 },
{ label: `${getLabel(547720, labelData)}`, value: 2 },
{ label: `${getLabel(547721, labelData)}`, value: 3 },
{ label: `${getLabel(547722, labelData)}`, value: 4 },
{ label: `${getLabel(547723, labelData)}`, value: 5 },
{ label: `${getLabel(547724, labelData)}`, value: 6 },
{ label: `${getLabel(547725, labelData)}`, value: 7 },
];
if (detailType == 'chart') {
arr.push({ label: '导出图片', key: '1' });
arr.push({ label: `${getLabel(547315, labelData)}`, key: '1' });
//arr.push({ label: 'PDF', key: '2' });
} else {
arr.push({ label: '导出表格', key: '3' });
arr.push({ label: `${getLabel(547448, labelData)}`, key: '3' });
}
const menu = <Menu onClick={this.handleExport.bind(this)} items={arr} />;
return (
<Drawer
title="详细信息"
width={1000}
title={getLabel(547321, labelData)}
width={1200}
onClose={this.onClose}
open={open}
bodyStyle={{
@ -354,51 +461,97 @@ export default class DrawerComponents extends React.Component {
<Checkbox
style={{ marginTop: '5px', marginLeft: 100 }}
checked={showJob}
disabled={disabled}
onChange={(e) => {
this.setState({
showJob: e.target.checked,
});
const newParam = {
...params,
statusValue: defaultStatusValue.join(','),
typeValue: defaultTypeValue.join(','),
};
this.getDeatilDatas(
params,
newParam,
detailType,
e.target.checked ? '1' : '0',
);
}}
>
是否显示岗位
{getLabel(547447, labelData)}
</Checkbox>
)}
<Dropdown overlay={menu}>
<Button type="primary">导出</Button>
<Button type="primary">{getLabel(547314, labelData)}</Button>
</Dropdown>
<img
style={{ width: 36, height: 36, cursor: 'pointer' }}
src="./img/change.png"
onClick={this.changeDetail}
/>
<Button type="primary" onClick={this.changeDetail}>
{getLabel(547326, labelData)}
</Button>
</Space>
}
>
{detailType == 'chart' ? (
data.length > 0 && (
<Spin size="large" spinning={spinning}>
<OrgChartComponent
setChart={(chart) => (orgChart = chart)}
setClick={(click) => (addNodeChildFunc = click)}
onNodeClick={this.onNodeClick}
onButtonClick={this.onButtonClick}
data={data}
buttonContent={this.buttonContentRender}
nodeWidth={this.nodeWidthRender}
nodeHeight={this.nodeHeightRender}
nodeContent={this.nodeContentRender}
<>
{detailType == 'chart' && (
<Row style={{ margin: '20px 20px 0px 20px' }}>
<Col span={2}>{getLabel(547714, labelData)}</Col>
<Col span={22}>
<Checkbox.Group
options={optionsType}
value={defaultTypeValue}
onChange={this.onTypeChange}
/>
</Col>
</Row>
)}
<Row style={{ margin: '10px 20px 10px 20px' }}>
<Col span={2}>{getLabel(547717, labelData)}</Col>
<Col span={22}>
<Checkbox.Group
options={optionsStatus}
value={defaultStatusValue}
onChange={this.onStatusChange}
/>
</Col>
</Row>
{detailType == 'chart' ? (
<div className="svg-container">
<Spin
size="large"
spinning={spinning}
tip={getLabel(547320, labelData)}
className="loading-center"
/>
</Spin>
)
) : (
<Table dataSource={dataSource} columns={columns} />
)}
{data.length > 0 && (
<OrgChartComponent
setChart={(chart) => (orgChart = chart)}
setClick={(click) => (addNodeChildFunc = click)}
onNodeClick={this.onNodeClick}
onButtonClick={this.onButtonClick}
data={data}
buttonContent={this.buttonContentRender}
nodeWidth={this.nodeWidthRender}
nodeHeight={this.nodeHeightRender}
nodeContent={this.nodeContentRender}
/>
)}
</div>
) : (
<div style={{ padding: '0 20px' }}>
<Table
dataSource={dataSource}
columns={columns}
loading={spinning}
pagination={{
showSizeChanger: false,
showTotal: (total) =>
`${getLabel(547523, labelData)} ${
dataSource.length
} ${getLabel(547524, labelData)}`,
}}
/>
</div>
)}
</>
</Drawer>
);
}

@ -2,3 +2,11 @@
width: 500px;
text-align: left;
}
.loading-center {
position: absolute;
top: 50vh;
left: 50%;
margin-left: -8px;
margin-top: -8px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

@ -2,19 +2,24 @@
* @Author: Chengliang 1546584672@qq.com
* @Date: 2023-06-25 16:33:21
* @LastEditors: Chengliang 1546584672@qq.com
* @LastEditTime: 2023-06-29 14:24:04
* @LastEditTime: 2023-09-14 17:48:39
* @FilePath: /org-chart-frant/src/components/timeline/index.jsx
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import React from 'react';
import { Timeline } from 'antd';
import { Timeline, Drawer, Popconfirm, message } from 'antd';
import styles from './index.less';
import leftTreeShow from './img/leftTree-show.png';
import leftHide from './img/leftTree-hide.png';
import { CloseCircleOutlined } from '@ant-design/icons';
import { getLabel } from '../../util/i18n.js';
export default class TimeLine extends React.Component {
constructor(props) {
super(props);
this.state = {
timelineList: [],
open: true,
};
}
@ -32,6 +37,21 @@ export default class TimeLine extends React.Component {
this.props.onClick(data);
}
handleDelete(key) {
const { labelData } = this.props;
let api = `/api/bs/hrmorganization/orgchart/versionDelete?versionId=${key}`;
fetch(api)
.then((res) => res.json())
.then((data) => {
if (data.api_status) {
message.success(`${getLabel(547484, labelData)}`, 2, 3);
window.location.reload(true);
} else {
message.error(`${getLabel(547483, labelData)}`, 2, 3);
}
});
}
componentDidMount() {
this.searchTimeLines(this.props.url);
}
@ -46,26 +66,79 @@ export default class TimeLine extends React.Component {
});
}
setOpen = () => {
this.setState({
open: !this.state.open,
});
};
render() {
const { labelData } = this.props;
let showStyle = {};
let positionStyle = {};
if (this.state.open) {
showStyle = {
display: 'block',
};
positionStyle = {
left: '219px',
background: `url(${leftTreeShow}) no-repeat -2px 0`,
};
} else {
showStyle = {
display: 'none',
};
positionStyle = {
left: '0',
background: `url(${leftHide}) no-repeat -2px 0`,
};
}
return (
<div className={styles.lineWrapper}>
<Timeline>
{this.state.timelineList.map((item) => {
return (
<Timeline.Item
key={item.key}
onClick={this.handleLineClick.bind(this, item)}
className={styles.timeline}
color={item.color}
style={{ color: item.color == 'blue' ? '#1890ff' : 'dimgray' }}
>
<div>{item.title}</div>
<div className={styles.time}>{item.time}</div>
</Timeline.Item>
);
})}
</Timeline>
</div>
<>
<div
className={styles.leftRightLayoutBtn}
style={positionStyle}
onClick={this.setOpen}
></div>
<div className={styles.lineWrapper} style={showStyle}>
<Timeline>
{this.state.timelineList.map((item) => {
return (
<Timeline.Item
key={item.key}
className={styles.timeline}
color={item.color}
>
<div
className={styles.title}
style={{
color: item.color == 'blue' ? '#1890ff' : 'dimgray',
}}
onClick={this.handleLineClick.bind(this, item)}
>
{item.title}
</div>
{item.key != 0 && (
<Popconfirm
title={`${getLabel(547491, labelData)}[${item.title}]?`}
onConfirm={this.handleDelete.bind(this, item.key)}
okText={getLabel(547319, labelData)}
cancelText={getLabel(547318, labelData)}
>
<div className={styles.delete}>
<CloseCircleOutlined />
</div>
</Popconfirm>
)}
<div className={styles.time}>{item.time}</div>
</Timeline.Item>
);
})}
</Timeline>
</div>
</>
);
}
}

@ -15,13 +15,32 @@
cursor: pointer;
}
.timeline :hover {
color: #1890ff;
}
// .timeline .title:hover {
// color: #1890ff;
// }
.time {
color: dimgray;
}
.delete :hover {
color: red;
}
}
.leftRightLayoutBtn {
width: 18px;
height: 60px;
position: fixed;
top: 50%;
// margin-top: -30px;
z-index: 101;
cursor: pointer;
}
.leftRightLayoutBtn :hover {
background-image: url(./img/leftTree-show-hover.png) no-repeat -2px 0 !important;
background-color: #1890ff;
}
.lineWrapper::-webkit-scrollbar {

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

@ -6,6 +6,12 @@ import top from './img/top.png';
import left from './img/left.png';
import topActive from './img/top_active.png';
import leftActive from './img/left_active.png';
import fullscreen from './img/fullscreen.png';
import fit from './img/fit.png';
import compact from './img/compact.png';
import expandAll from './img/expandAll.png';
import deleteNode from './img/folder-remove.png';
import folderAdd from './img/folder-add.png';
export default class ToolBar extends React.Component {
progressBtn = React.createRef();
@ -97,6 +103,48 @@ export default class ToolBar extends React.Component {
this.props.onLeftLayoutClick(this.progressBtn);
}}
/>
{/* <img
className={styles.toolBarItem}
src={folderAdd}
onClick={() => {
this.props.onFolderAddNode(this.progressBtn);
}}
/>
<img
className={styles.toolBarItem}
src={deleteNode}
onClick={() => {
this.props.onDeleteNode(this.progressBtn);
}}
/> */}
<img
className={styles.toolBarItem}
src={fullscreen}
onClick={() => {
this.props.onFullscreen(this.progressBtn);
}}
/>
<img
className={styles.toolBarItem}
src={fit}
onClick={() => {
this.props.onFit(this.progressBtn);
}}
/>
<img
className={styles.toolBarItem}
src={compact}
onClick={() => {
this.props.onCompact(this.progressBtn);
}}
/>
<img
className={styles.toolBarItem}
src={expandAll}
onClick={() => {
this.props.onExpandAll(this.progressBtn);
}}
/>
</div>
);
}

@ -1,38 +1,39 @@
.toolbarWrapper {
width: 68px;
position: fixed;
right: 10px;
z-index: 100;
background: #fff;
border-radius: 20px;
padding-top: 20px;
padding-bottom: 20px;
text-align: center;
.progressWrapper {
position: relative;
padding-top: 5px;
padding-bottom: 5px;
margin-left: 20px;
.progressLine {
height: 100px;
width: 0px;
border-left: 2px solid #C9C9C9;
margin-left: 12px;
}
.progressBtn {
width: 16px;
height: 9px;
background-color: #C9C9C9;
position: absolute;
left: 5px;
top: 0px;
}
width: 68px;
position: fixed;
right: 10px;
z-index: 100;
background: #fff;
border-radius: 20px;
padding-top: 20px;
padding-bottom: 20px;
text-align: center;
.progressWrapper {
position: relative;
padding-top: 5px;
padding-bottom: 5px;
margin-left: 20px;
.progressLine {
height: 100px;
width: 0px;
border-left: 2px solid #c9c9c9;
margin-left: 12px;
}
.toolBarItem {
display: block;
margin-top: 20px;
margin-bottom: 20px;
margin-left: 20px;
cursor: pointer;
.progressBtn {
width: 16px;
height: 9px;
background-color: #c9c9c9;
position: absolute;
left: 5px;
top: 0px;
}
}
.toolBarItem {
display: block;
margin-top: 20px;
margin-bottom: 20px;
margin-left: 26px;
cursor: pointer;
width: 20px;
}
}

@ -15,13 +15,17 @@ import {
Modal,
Input,
message,
notification,
} from 'antd';
const { Option } = Select;
const { TextArea } = Input;
import moment from 'moment';
import 'moment/locale/zh-cn';
import locale from 'antd/lib/date-picker/locale/zh_CN';
import { HomeOutlined, FolderOutlined } from '@ant-design/icons';
moment.locale('zh-cn');
import { getLabel } from '../../util/i18n.js';
import { SmileOutlined } from '@ant-design/icons';
export class TopBar extends React.Component {
constructor(props) {
@ -29,16 +33,20 @@ export class TopBar extends React.Component {
this.state = {
fclasslist: [],
rootTreeData: [], //
treeLoadedKeys: [],
treeExpandedKeys: [],
requestData: {
fclass: '0',
root: undefined,
level: '2',
fisvitual: '0',
hidedept: '0',
},
open: false,
confirmLoading: false,
description: '',
timelineId: '0',
treeKey: 1,
};
}
@ -51,23 +59,39 @@ export class TopBar extends React.Component {
this.setState({ requestData });
}
handleTreeData(treeData = [], timelineId) {
this.setState({
rootTreeData: treeData,
timelineId: timelineId,
treeKey: this.state.treeKey + 1,
});
}
onChange = (e) => {
this.setState({ description: e.target.value });
};
generateKey = (
length = 5,
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
) => {
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
};
/**
* 弹窗确认
*/
handleOk = () => {
const { description, requestData } = this.state;
const { labelData } = this.props;
if (description.length == 0) {
return message.error('请填写描述信息');
return message.error(`${getLabel(547512, labelData)}`);
}
this.setState({ confirmLoading: true });
// setTimeout(() => {
// this.setState({ open: false, confirmLoading: false, description: '' });
// message.success(',');
// }, 2000);
let api =
'/api/bs/hrmorganization/orgchart/versionRecord?fclass=' +
requestData.fclass +
@ -80,11 +104,10 @@ export class TopBar extends React.Component {
this.setState({
open: false,
confirmLoading: false,
description: '',
});
message.success('版本记录成功,请重新刷新页面', 2, 3);
message.success(`${getLabel(547513, labelData)}`, 2, 3);
} else {
message.error('版本记录失败,请联系相关人员处理数据', 2, 3);
message.error(`${getLabel(547514, labelData)}`, 2, 3);
}
});
};
@ -113,6 +136,12 @@ export class TopBar extends React.Component {
} else {
arr = [...data.companyTree];
}
arr.map((item, index) => {
item.icon = <HomeOutlined />;
if (item.type == 2) {
item.icon = <FolderOutlined />;
}
});
this.setState({
rootTreeData: arr,
});
@ -127,14 +156,17 @@ export class TopBar extends React.Component {
*/
onRootLoadData = (treeNode) =>
new Promise((resolve) => {
const { id } = treeNode.props;
const { id } = treeNode;
setTimeout(() => {
const { fclass } = this.state.requestData;
const { timelineId } = this.state;
let api =
'/api/bs/hrmorganization/orgchart/getSubCompanyTree?subcompany=' +
id +
'&fclass=' +
fclass;
fclass +
'&id=' +
timelineId;
this.getNodeTreeNode(api);
resolve(undefined);
}, 500);
@ -159,6 +191,9 @@ export class TopBar extends React.Component {
fetch(url)
.then((res) => res.json())
.then((data) => {
data.companyTree.map((item, index) => {
item.icon = <HomeOutlined />;
});
this.setState({
fclasslist: data.fclasslist,
rootTreeData: data.companyTree,
@ -171,7 +206,7 @@ export class TopBar extends React.Component {
onClick={this.handleExportMenuClick.bind(this)}
items={[
{
label: '导出图片',
label: `${getLabel(547315, this.props.labelData)}`,
key: '1',
},
// {
@ -183,14 +218,15 @@ export class TopBar extends React.Component {
);
render() {
const { disabled, type } = this.props;
const { rootTreeData, open, confirmLoading } = this.state;
const { disabled, type, labelData } = this.props;
const { rootTreeData, open, confirmLoading, treeExpandedKeys, treeKey } =
this.state;
return (
<div className={style.topbarWrapper}>
<Row>
<Col span={6}>
维度
{getLabel(547293, labelData)}
<Select
defaultValue="0"
style={{ width: 140 }}
@ -201,8 +237,12 @@ export class TopBar extends React.Component {
root: undefined,
level: '2',
fisvitual: '0',
hidedept: '0',
};
this.handleFormChange(requestData);
this.setState({
rootTreeData: [],
});
this.getNodeTreeNode(
`/api/bs/hrmorganization/orgchart/getSubCompanyTree?fclass=${value}`,
false,
@ -217,69 +257,117 @@ export class TopBar extends React.Component {
))}
</Select>
</Col>
<Col span={6}>
根节点
<Col span={8}>
{getLabel(547294, labelData)}
<TreeSelect
key={treeKey}
treeDataSimpleMode
allowClear
style={{ width: '75%' }}
style={{ width: '65%' }}
value={this.state.requestData.root}
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
placeholder="请选择根节点"
placeholder={getLabel(547298, labelData)}
onChange={this.onRootChange}
loadData={this.onRootLoadData}
treeData={rootTreeData}
treeIcon
/>
</Col>
<Col span={6}>
<Col span={5}>
<Checkbox
style={{ marginTop: '5px', marginLeft: 100 }}
checked={this.state.requestData.fisvitual == '1'}
checked={this.state.requestData.hidedept == '1'}
onChange={(e) =>
this.handleFormChange({
fisvitual: e.target.checked ? '1' : '0',
hidedept: e.target.checked ? '1' : '0',
})
}
>
显示虚拟组织
{getLabel(547296, labelData)}
</Checkbox>
<Tooltip
title="提示:若启用虚拟组织,需要在分部自定义表增加字段(名称 fblx) 字段类型 下拉框(0实体 1虚拟) 部门自定义表同上(字段名称 bmlx)。"
color="#FF7F00"
title={getLabel(547303, labelData)}
color="#0082fb"
placement="rightTop"
>
<QuestionCircleOutlined
style={{ color: '#FF7F00', cursor: 'pointer', fontSize: 16 }}
style={{ color: '#0082fb', cursor: 'pointer', fontSize: 16 }}
/>
</Tooltip>
</Col>
<Col span={6}>
部门层级
<Col span={5}>
{getLabel(547299, labelData)}
<Select
defaultValue="3"
style={{ width: 140 }}
value={this.state.requestData.level}
onChange={(value) => this.handleFormChange({ level: value })}
>
<Option value="1">全部</Option>
<Option value="2">一级</Option>
<Option value="3">二级</Option>
<Option value="4">三级</Option>
<Option value="1">{getLabel(547301, labelData)}</Option>
<Option value="2">{getLabel(547463, labelData)}</Option>
<Option value="3">{getLabel(547464, labelData)}</Option>
<Option value="4">{getLabel(547465, labelData)}</Option>
</Select>
</Col>
</Row>
<Row style={{ marginTop: '15px' }}>
<Col span={8}>
<Button
type="primary"
style={{ marginRight: '10px' }}
disabled={disabled}
onClick={() => {
this.setState({ open: true });
}}
<Col span={6}>
<Checkbox
style={{ marginTop: '5px' }}
checked={this.state.requestData.fisvitual == '1'}
onChange={(e) =>
this.handleFormChange({
fisvitual: e.target.checked ? '1' : '0',
})
}
>
版本记录
</Button>
{getLabel(547302, labelData)}
</Checkbox>
<Tooltip
title={getLabel(547304, labelData)}
color="#0082fb"
placement="rightTop"
>
<QuestionCircleOutlined
style={{ color: '#0082fb', cursor: 'pointer', fontSize: 16 }}
/>
</Tooltip>
</Col>
<Col span={10}>
{this.state.requestData.fclass == '0' && (
<span>
<Button
type="primary"
style={{ marginRight: '10px' }}
disabled={disabled}
onClick={() => {
this.setState({ open: true });
}}
>
{getLabel(547305, labelData)}
</Button>
<Button
type="primary"
style={{ marginRight: '10px' }}
onClick={() => {
window.open('#/dragtree', 'blank');
}}
>
{getLabel(547310, labelData)}
</Button>
<Button
type="primary"
style={{ marginRight: '10px' }}
onClick={() => {
window.open('#/statistics', 'blank');
}}
>
{getLabel(547313, labelData)}
</Button>
</span>
)}
<Button
type="primary"
@ -288,29 +376,28 @@ export class TopBar extends React.Component {
this.props.onSearch(this.state.requestData);
}}
>
查询
{getLabel(547307, labelData)}
</Button>
<Dropdown overlay={this.menu}>
<Button type="primary">导出</Button>
<Button type="primary">{getLabel(547314, labelData)}</Button>
</Dropdown>
</Col>
</Row>
<Modal
title="版本记录"
cancelText="取消"
okText="确定"
title={getLabel(547305, labelData)}
cancelText={getLabel(547318, labelData)}
okText={getLabel(547319, labelData)}
open={open}
onOk={this.handleOk}
confirmLoading={confirmLoading}
onCancel={() => this.setState({ open: false })}
>
<p style={{ color: 'red' }}>
提示:版本记录耗时较长请谨慎操作仅记录当前维度的数据版本
</p>
<p>主题:</p>
<p style={{ color: 'red' }}>{getLabel(547316, labelData)}</p>
<p>{getLabel(547317, labelData)}:</p>
<TextArea
showCount
maxLength={10}
maxLength={20}
style={{ height: 120, resize: 'none' }}
onChange={this.onChange}
placeholder="please enter"

@ -231,7 +231,7 @@ export class OrgChart {
nodeLeftX: (node) => -node.width / 2,
nodeRightX: (node) => node.width / 2,
nodeTopY: (node) => 0,
// "nodeTopY": node => node.height/0.4,
//nodeTopY: (node) => node.height / 0.3,
nodeBottomY: (node) => node.height,
nodeJoinX: (node) => node.x - node.width / 2,
nodeJoinY: (node) => node.y + node.height,
@ -509,7 +509,9 @@ export class OrgChart {
// Get zooming function
behaviors.zoom = d3
.zoom()
.on('zoom', (event, d) => this.zoomed(event, d))
.on('zoom', (event, d) => {
this.zoomed(event, d);
})
.scaleExtent(attrs.scaleExtent);
attrs.zoomBehavior = behaviors.zoom;
}

@ -1,5 +0,0 @@
import React from 'react'
export default () => {
return <div>Hello</div>
}

@ -6,30 +6,40 @@ import { TopBar } from '../components/topBar';
import ToolBar from '../components/toolBar';
import TimeLine from '../components/timeline';
import DrawerComponents from '../components/drawer';
import OperateDialog from '../components/dialog';
import jsPDF from 'jspdf';
import moment from 'moment';
import qs from 'qs';
import { message, Spin } from 'antd';
import { message, Spin, notification } from 'antd';
import { SmileOutlined } from '@ant-design/icons';
import { getLabel } from '../util/i18n.js';
let active = 'top';
let drawerCom = null;
let operateCom = null;
let timeLine = null;
let orgChart = null;
let topbar = null;
export default function companyPage() {
const [data, setData] = useState(null);
let compact = 0;
let expandAll = 0;
const [sliderProgress, setSliderProgress] = useState(50);
let addNodeChildFunc = null;
let topBarSearchRequest = null;
const [hasRight, setHasRight] = useState('');
const [timelineId, setTimelineId] = useState(0);
const infoRef = useRef();
const [labelData, setLabelData] = useState({});
const [lebelCompleted, setLabelCompleted] = useState(false);
useEffect(() => {
infoRef.current = timelineId;
}, [timelineId]);
const [spinning, setSpinning] = useState(false);
//
@ -66,9 +76,10 @@ export default function companyPage() {
'/api/bs/hrmorganization/orgchart/asyncCompanyData' +
qs.stringify(request, { addQueryPrefix: true });
} else {
let request = { ...topbar.state.requestData, ids: idsStr };
api =
'/api/bs/hrmorganization/orgchart/asyncCompanyData?fclass=0&fisvitual=0&id=0&root=0&ids=' +
idsStr;
'/api/bs/hrmorganization/orgchart/asyncCompanyData' +
qs.stringify(request, { addQueryPrefix: true });
}
fetch(api)
.then((res) => res.json())
@ -93,15 +104,30 @@ export default function companyPage() {
return fisvitual == '0' ? `./img/back/level1.png` : `./img/back/level5.png`;
}
//
//
useEffect(() => {
d3.json(
'/api/bs/hrmorganization/orgchart/companyData?fclass=0&fisvitual=0&root=0&level=2&id=0',
).then((data) => {
setData(data.data);
setHasRight(data?.hasRight);
d3.json('/api/bs/hrmorganization/orgchart/i18n').then((res) => {
setLabelData(res.data);
setLabelCompleted(true);
notification.open({
message: `${getLabel(547283, res.data)}`,
description: `${getLabel(547292, res.data)}`,
icon: <SmileOutlined style={{ color: '#108ee9' }} />,
});
});
}, [true]);
}, []);
//
useEffect(() => {
if (lebelCompleted) {
d3.json(
'/api/bs/hrmorganization/orgchart/companyData?fclass=0&fisvitual=0&hidedept=0&root=0&level=2&id=0',
).then((data) => {
setData(data.data);
setHasRight(data?.hasRight);
});
}
}, [lebelCompleted]);
// ButtonContent
const buttonContentRender = ({ node, state }) => {
@ -115,9 +141,9 @@ export default function companyPage() {
//
const nodeWidthRender = (d) => {
if (d.data.ftype == 0) {
return 1000;
return 220;
} else if (d.data.ftype == 1) {
return 160;
return 144;
} else if (d.data.ftype == 2) {
return 144;
}
@ -136,12 +162,11 @@ export default function companyPage() {
};
const nodeContentRender = (d, i, arr, state) => {
let fclass = topbar.state.requestData.fclass;
let statisticsStyle = fclass == 0 ? 'block' : 'none';
if (d.data.ftype == 0) {
return `<div style="text-align:center">
<div style="display: inline-block; vertical-align: top;">
<img src="./img/company.png" />
</div>
<div style="display: inline-block; text-align: center; margin-left: 5px;">
return `<div style="text-align: center;">
<div style="display: inline-block; margin-left: 5px;">
<div style="
font-size: 24px;
font-family: Microsoft YaHei-Bold, Microsoft YaHei;
@ -161,11 +186,18 @@ export default function companyPage() {
</div>
<div style="width: 144px;height: 80px;top: 35px;position: relative;font-weight: 400;font-size: 14px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;color: #333333;text-align: center;">
<div title=${
d.data.fname
} style="width: 110px;margin: 0 auto;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;-o-text-overflow:ellipsis;
line-height: 18px;word-break: break-all;">${d.data.fname}</div>
</div>`;
<div class="tooltip">
<div class="tooltitle"> ${d.data.fname} </div>
<div class="tooltiptext">${d.data.fname}</div>
</div>
<div style="display: ${statisticsStyle}">
<span style="color:red">${d.data.staffNum}</span> /
<span style="color:green">${d.data.onJobNum}</span>
</div>
</div>
</div>
`;
} else if (d.data.ftype == 2) {
return `
<div style="width: 100%; height: 100%; background-size: 100% 100%;">
@ -176,8 +208,15 @@ export default function companyPage() {
</div>
<div style="width: 144px;height: 80px;top: 35px;position: relative;font-weight: 400;font-size: 14px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;color: #333333;text-align: center;">
<div style="width: 110px;margin: 0 auto;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;
line-height: 18px;word-break: break-all;">${d.data.fname}</div>
<div class="tooltip">
<div class="tooltitle"> ${d.data.fname} </div>
<div class="tooltiptext">${d.data.fname}</div>
</div>
<div style="display: ${statisticsStyle}">
<span style="color:red">${d.data.staffNum}</span> /
<span style="color:green">${d.data.onJobNum}</span>
</div>
</div>
</div>
`;
@ -205,6 +244,56 @@ export default function companyPage() {
active = 'left';
};
const handleFullscreen = (progressBtn) => {
progressBtn.current.style.top = 50 + 'px';
orgChart && orgChart.fullscreen('body');
};
const handleFit = (progressBtn) => {
progressBtn.current.style.top = 50 + 'px';
orgChart && orgChart.fit();
};
const handleFolderAddNode = (progressBtn) => {
progressBtn.current.style.top = 50 + 'px';
operateCom &&
operateCom.showOperate(topbar.state.requestData.root, '新增节点', 1);
};
const addFolderNode = (id) => {
orgChart &&
orgChart.addNode({
id: 'd_10091',
fname: '测试增加节点',
parentId: 's_10',
ftype: '2',
});
};
const handleDeleteNode = (progressBtn) => {
progressBtn.current.style.top = 50 + 'px';
operateCom &&
operateCom.showOperate(topbar.state.requestData.root, '删除节点', 2);
};
const deleteNode = (id) => {
orgChart && orgChart.removeNode('d_10091');
};
const handleCompact = (progressBtn) => {
progressBtn.current.style.top = 50 + 'px';
orgChart &&
orgChart
.compact(!!(compact++ % 2))
.render()
.fit();
};
const handleExpandAll = (progressBtn) => {
progressBtn.current.style.top = 50 + 'px';
orgChart && expandAll++ % 2 ? orgChart.collapseAll() : orgChart.expandAll();
};
const handleZoomIn = (progressBtn) => {
if (progressBtn) {
let top = parseInt(progressBtn.current.style.top) - 10;
@ -262,6 +351,10 @@ export default function companyPage() {
*/
const handleExport = (type) => {
if (type == 'png') {
const hiddenElements = document.querySelectorAll('.tooltitle');
const hiddenElementsArray = Array.from(hiddenElements);
// DOM
hiddenElementsArray.forEach((el) => (el.style.display = 'none'));
orgChart && orgChart.exportImg({ full: true });
} else {
orgChart && downloadPdf(orgChart);
@ -275,8 +368,15 @@ export default function companyPage() {
const timeLineSearch = (timeline) => {
setTimelineId(timeline.id);
const fclass = topbar.state.requestData.fclass;
const resetParams = { root: undefined, level: '2', fisvitual: '0' };
const resetParams = {
root: undefined,
level: '2',
fisvitual: '0',
hidedept: '0',
};
topbar.handleFormChange({ ...resetParams });
//
topbar.handleTreeData([], timeline.id);
topbar.getNodeTreeNode(
`/api/bs/hrmorganization/orgchart/getSubCompanyTree?fclass=${fclass}&id=${timeline.id}`,
false,
@ -304,12 +404,14 @@ export default function companyPage() {
if (data.data) {
if (!data.data.length) {
setData([{}]);
message.warning('暂无数据');
message.warning(`${getLabel(547516, labelData)}`);
} else {
setData(data?.data);
}
}
setSpinning(false);
setTimeout(function () {
setSpinning(false);
}, 200);
});
};
@ -349,13 +451,14 @@ export default function companyPage() {
style={{ display: 'block', margin: '0 auto' }}
src="./img/permission.png"
/>
<p style={{ textAlign: 'center' }}>对不起您暂时没有权限!</p>
<p style={{ textAlign: 'center' }}>{getLabel(547515, labelData)}</p>
</div>
);
}
return (
hasRight && (
hasRight &&
Object.keys(labelData).length != 0 && (
<div className={styles.contentWrapper}>
<TopBar
ref={(r) => (topbar = r)}
@ -369,13 +472,20 @@ export default function companyPage() {
handleChange(requestData);
}}
type="company"
url="/api/bs/hrmorganization/orgchart/getCondition?fclass=0&type=company"
url="/api/bs/hrmorganization/orgchart/getCondition?fclass=0&type=company&id=0"
labelData={labelData}
/>
<ToolBar
onTopLayoutClick={(progressBtn) => handleTopLayoutClick(progressBtn)}
onLeftLayoutClick={(progressBtn) =>
handleLeftLayoutClick(progressBtn)
}
onFullscreen={(progressBtn) => handleFullscreen(progressBtn)}
onFit={(progressBtn) => handleFit(progressBtn)}
onFolderAddNode={(progressBtn) => handleFolderAddNode(progressBtn)}
onDeleteNode={(progressBtn) => handleDeleteNode(progressBtn)}
onCompact={(progressBtn) => handleCompact(progressBtn)}
onExpandAll={(progressBtn) => handleExpandAll(progressBtn)}
onZoomOut={(progressBtn) => handleZoomOut(progressBtn)}
onZoomIn={(progressBtn) => handleZoomIn(progressBtn)}
onZoomBehavior={(value) => handleZoomBehavior(value)}
@ -386,6 +496,7 @@ export default function companyPage() {
timeLineSearch(timeline);
}}
url={'/api/bs/hrmorganization/orgchart/timeLines?fclass=0'}
labelData={labelData}
/>
<Spin size="large" spinning={spinning}>
<OrgChartComponent
@ -400,8 +511,13 @@ export default function companyPage() {
nodeContent={nodeContentRender}
/>
</Spin>
<DrawerComponents ref={(r) => (drawerCom = r)} />
<DrawerComponents ref={(r) => (drawerCom = r)} labelData={labelData} />
<OperateDialog
ref={(r) => (operateCom = r)}
addFolderNode={addFolderNode}
deleteNode={deleteNode}
labelData={labelData}
/>
</div>
)
);

@ -0,0 +1,496 @@
import { Tree, message, Modal, Popconfirm, Spin, Layout, Drawer } from 'antd';
import React, { useEffect, useState, useRef } from 'react';
import * as d3 from 'd3';
import qs from 'qs';
import MergeDialog from '../components/dialog/mergeDialog';
import CopyDialog from '../components/dialog/copyDialog';
import inset from '../../public/img/back/inset.png';
import { getLabel } from '../util/i18n.js';
import {
HomeOutlined,
FolderOutlined,
ClusterOutlined,
ApartmentOutlined,
QuestionCircleOutlined,
} from '@ant-design/icons';
const { Header, Footer, Sider, Content } = Layout;
import './index.less';
const DragTree = () => {
const [gData, setGData] = useState([]);
const [expandedKeys, setExpandedKeys] = useState([undefined]);
const childRef = useRef(null);
const copyChildRef = useRef(null);
const [tip, setTip] = useState('');
const [loading, setLoading] = useState(false);
const [showCanceled, setShowCanceled] = useState(0);
const [drawerOpen, setDrawerOpen] = useState(false);
const [iframe, setIframe] = useState('');
const [open, setOpen] = useState(false);
const [mergeId, setMergeId] = useState(null);
const [copyopen, setCopyOpen] = useState(false);
const [copyId, setCopyId] = useState(null);
const [labelData, setLabelData] = useState({});
useEffect(() => {
d3.json('/api/bs/hrmorganization/orgchart/i18n').then((res) => {
setLabelData(res.data);
setTip(`${getLabel(547473, res.data)}`);
});
getMoveTree(0);
}, [true]);
const getMoveTree = (showCanceled, expandedKeys = '') => {
setLoading(true);
d3.json(
`/api/bs/hrmorganization/orgchart/getMovingTree?showCanceled=${showCanceled}&expandedKeys=${expandedKeys}`,
).then((data) => {
setGData(data.movingTree);
setExpandedKeys(data.expandedKeys);
setLoading(false);
});
};
const onDragEnter = (info) => {
//console.log(info);
// expandedKeys
// setExpandedKeys(info.expandedKeys)
};
const loop = (data, key, callback) => {
for (let i = 0; i < data.length; i++) {
if (data[i].key === key) {
return callback(data[i], i, data);
}
if (data[i].children) {
loop(data[i].children, key, callback);
}
}
};
const onDrop = (info) => {
const dropKey = info.node.key;
const dragKey = info.dragNode.key;
const dropPos = info.node.pos.split('-');
const dropPosition =
info.dropPosition - Number(dropPos[dropPos.length - 1]);
if (dropPosition == -1) {
return message.error(`${getLabel(547475, labelData)}`, 2);
}
let pos =
dropPosition == 0
? `${getLabel(547476, labelData)}`
: `${getLabel(547477, labelData)}`;
let title = `${getLabel(547478, labelData)}${
info.dragNode.title
} ${getLabel(547479, labelData)} ${info.node.title}${pos}`;
Modal.confirm({
title: `${getLabel(547480, labelData)}`,
content: title,
okText: `${getLabel(547319, labelData)}`,
cancelText: `${getLabel(547318, labelData)}`,
onOk() {
setLoading(true);
setTip(`${getLabel(547481, labelData)}`);
fetch('/api/bs/hrmorganization/dept/dragDepartment', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
sourcekey: dragKey,
targetkey: dropKey,
dropPosition: dropPosition,
}),
})
.then((response) => response.json())
.then((res) => {
if (res.code == 200) {
const data = [...gData];
// Find dragObject
let dragObj;
loop(data, dragKey, (item, index, arr) => {
arr.splice(index, 1);
dragObj = item;
});
if (!info.dropToGap) {
// Drop on the content
loop(data, dropKey, (item) => {
item.children = item.children || [];
// where to insert
item.children.unshift(dragObj);
});
} else if (
(info.node.props.children || []).length > 0 &&
// Has children
info.node.props.expanded &&
// Is expanded
dropPosition === 1 // On the bottom gap
) {
loop(data, dropKey, (item) => {
item.children = item.children || [];
// where to insert
item.children.unshift(dragObj);
// in previous version, we use item.children.push(dragObj) to insert the
// item to the tail of the children
});
} else {
let ar = [];
let i;
loop(data, dropKey, (_item, index, arr) => {
ar = arr;
i = index;
});
if (dropPosition === -1) {
ar.splice(i, 0, dragObj);
} else {
ar.splice(i + 1, 0, dragObj);
}
}
setGData(data);
message.success(`${getLabel(547482, labelData)}`, 2);
} else {
message.warning(res.msg, 2);
}
setLoading(false);
})
.catch((error) => {
message.error(`${getLabel(547483, labelData)}`);
});
},
onCancel() {},
});
};
/**
* 删除
* @param {*} nodeData
*/
const onDelete = (nodeData) => {
const extend = nodeData.type == '1' ? 'comp' : 'dept';
fetch(`/api/bs/hrmorganization/${extend}/deleteByIds`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ ids: nodeData.id.substring(1) }),
})
.then((response) => response.json())
.then((res) => {
if (res.code == 200) {
if (res.data.status == '1') {
const data = [...gData];
loop(data, nodeData.key, (item, index, arr) => {
arr.splice(index, 1);
});
setGData(data);
message.success(`${getLabel(547484, labelData)}`, 2);
} else {
message.warning(res.data.message, 2);
}
}
})
.catch((error) => {
message.error(`${getLabel(547483, labelData)}`);
});
};
/**
* 封存
* @param {*} nodeData
*/
const onCancel = (nodeData) => {
setShowCanceled(0);
const extend = nodeData.type == '1' ? 'comp' : 'dept';
fetch(`/api/bs/hrmorganization/${extend}/updateForbiddenTagById`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: nodeData.id.substring(1),
canceled: nodeData.canceled != '0',
}),
})
.then((response) => response.json())
.then((res) => {
if (res.code == 200) {
const data = [...gData];
if (nodeData.canceled != '0') {
message.success(`${getLabel(547485, labelData)}`, 2);
loop(data, nodeData.key, (item, index, arr) => {
arr[index].canceled = '0';
});
} else {
loop(data, nodeData.key, (item, index, arr) => {
arr.splice(index, 1);
});
message.success(`${getLabel(547486, labelData)}`, 2);
}
setGData(data);
} else {
message.warning(res.msg, 2);
}
})
.catch((error) => {
message.error(`${getLabel(547483, labelData)}`);
});
};
/**
* 合并
* @param {*} nodeData
*/
const onMerge = (nodeData) => {
if (childRef.current) {
childRef.current.getTreeData();
}
setMergeId(nodeData.id);
setOpen(true);
};
const onMergeCreate = (values) => {
let params = {
department: values.department.substring(1),
mergeName: values.mergeName,
id: mergeId.substring(1),
};
fetch('/api/bs/hrmorganization/dept/mergeDepartment', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
})
.then((response) => response.json())
.then((res) => {
if (res.code == 200) {
getMoveTree(0, values.department);
message.success(`${getLabel(547487, labelData)}`, 2);
} else {
message.warning(res.msg, 2);
}
setOpen(false);
})
.catch((error) => {
message.error(`${getLabel(547483, labelData)}`);
});
};
/**
* 复制
* @param {*} nodeData
*/
const onCopy = (nodeData) => {
if (copyChildRef.current) {
copyChildRef.current.getTreeData();
}
setCopyOpen(true);
setCopyId(nodeData.id);
};
const onCopyCreate = (values) => {
let params = {
company: values.company,
copySubDept: values.copySubDept ? '1' : '0',
ids: copyId.substring(1),
};
fetch(`/api/bs/hrmorganization/dept/copyDepartment`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
})
.then((response) => response.json())
.then((res) => {
if (res.code == 200) {
setCopyOpen(false);
getMoveTree(0, values.company);
message.success(`${getLabel(547488, labelData)}`, 2);
} else {
message.warning(res.msg, 2);
}
})
.catch((error) => {
message.error(`${getLabel(547483, labelData)}`);
});
};
const onTitleRender = (nodeData) => {
let extend = nodeData.type == '1' ? 'companyExtend' : 'departmentExtend';
let attr =
nodeData.type == '1' || nodeData.canceled == '1'
? 'none'
: 'inline-block';
let icon = nodeData.type == '1' ? <HomeOutlined /> : <FolderOutlined />;
return (
<>
{nodeData.type == 0 ? (
<span>
<ClusterOutlined />
<span style={{ marginLeft: '5px' }}>{nodeData.title}</span>
</span>
) : (
<>
<span>
{icon}
<span style={{ marginLeft: '5px' }}>
{nodeData.title}
{nodeData.canceled == '1' ? (
<span style={{ color: 'red', marginLeft: '5px' }}>
({getLabel(547489, labelData)})
</span>
) : (
''
)}
</span>
</span>
<div id="drag-button-ops">
<span
className="drag-button"
onClick={() => {
setDrawerOpen(true);
setIframe(
`/spa/organization/static/index.html#/main/organization/${extend}/${nodeData.id.substring(
1,
)}`,
);
}}
>
{getLabel(547490, labelData)}
</span>
<Popconfirm
title={`${getLabel(547491, labelData)}[${nodeData.title}]?`}
onConfirm={() => onDelete(nodeData)}
okText={getLabel(547319, labelData)}
cancelText={getLabel(547318, labelData)}
>
<span className="drag-button">
{getLabel(547492, labelData)}
</span>
</Popconfirm>
<Popconfirm
title={`${getLabel(547493, labelData)} [${nodeData.title}]?`}
onConfirm={() => onCancel(nodeData)}
okText={getLabel(547319, labelData)}
cancelText={getLabel(547318, labelData)}
>
<span className="drag-button">
{nodeData.canceled == '0'
? `${getLabel(547494, labelData)}`
: `${getLabel(547495, labelData)}`}
</span>
</Popconfirm>
<span
style={{ display: attr }}
className="drag-button"
onClick={() => onMerge(nodeData)}
>
{getLabel(547194, labelData)}
</span>
<span
style={{ display: attr }}
className="drag-button"
onClick={() => onCopy(nodeData)}
>
{getLabel(547196, labelData)}
</span>
</div>
</>
)}
</>
);
};
const onExpand = (info) => {
setExpandedKeys(info);
};
return (
Object.keys(labelData).length != 0 && (
<div className="drag-wrapper">
<Spin tip={tip} spinning={loading}>
<div className="drag-layout">
<div className="drag-header">
<img src={inset} />
<div>{getLabel(547282, labelData)}</div>
</div>
<div className="drag-content">
<ApartmentOutlined
className="drag-showcanceled"
style={{ color: showCanceled == 0 ? '#000' : '#1890ff' }}
onClick={() => {
const value = showCanceled == 0 ? 1 : 0;
setTip(`${getLabel(547473, labelData)}`);
setShowCanceled(value);
getMoveTree(value);
}}
/>
<Tree
className="draggable-tree"
//defaultExpandedKeys={expandedKeys}
expandedKeys={expandedKeys}
onExpand={onExpand}
draggable
icon={false}
blockNode
onDragEnter={onDragEnter}
onDrop={onDrop}
treeData={gData}
titleRender={onTitleRender}
/>
</div>
<Drawer
width="60%"
placement="right"
closable={false}
onClose={() => setDrawerOpen(false)}
open={drawerOpen}
>
<iframe src={iframe} width="100%" height="100%" />
</Drawer>
<div className="drag-footer">
<p>
<QuestionCircleOutlined />
{getLabel(547283, labelData)}
</p>
<div className="tips">
<div>1.{getLabel(547284, labelData)}</div>
<div>2.{getLabel(547285, labelData)}</div>
<div>3.{getLabel(547286, labelData)}</div>
<div>4.{getLabel(547287, labelData)}</div>
</div>
</div>
</div>
</Spin>
<MergeDialog
ref={childRef}
open={open}
onCreate={onMergeCreate}
onCancel={() => {
setOpen(false);
}}
labelData={labelData}
/>
<CopyDialog
ref={copyChildRef}
open={copyopen}
onCreate={onCopyCreate}
onCancel={() => {
setCopyOpen(false);
}}
labelData={labelData}
/>
</div>
)
);
};
export default DragTree;

@ -1,3 +1,7 @@
#root {
min-height: 100vh;
}
.title {
background: rgb(121, 242, 157);
}
@ -9,3 +13,145 @@
.contentWrapper {
background-color: #f7f9fd;
}
.ant-tree .ant-tree-node-content-wrapper:hover {
background-color: #e6f7ff;
}
.ant-tree .ant-tree-node-content-wrapper:hover #drag-button-ops {
display: inline-block;
}
.ant-tree .ant-tree-treenode-disabled .ant-tree-node-content-wrapper {
color: rgba(0, 0, 0, 0.85);
}
#drag-button-ops {
display: none;
height: 16px;
font-size: 12px;
line-height: 16px;
margin-left: 100px;
.drag-button {
display: inline-block;
padding: 0 10px;
color: #333;
cursor: pointer;
}
.drag-button:hover {
color: #22c8fb;
}
}
.drag-wrapper {
min-height: inherit;
background-image: url('../../public/img/back/background.png') !important;
}
.drag-layout {
min-height: inherit;
padding: 25px;
.drag-header {
text-align: center;
div {
display: inline-block;
font-size: 25px;
font-weight: 400;
}
}
.drag-content {
width: auto;
background: #f8fafc;
border: 1px solid rgba(218, 237, 255, 1);
border-radius: 8px;
margin-top: 20px;
.drag-showcanceled {
font-size: 18px;
line-height: 30px;
margin-left: 5px;
margin-top: 5px;
}
}
.drag-footer {
margin-top: 15px;
background: #f8fafc;
border: 1px solid rgba(218, 237, 255, 1);
border-radius: 8px;
padding: 22px;
p {
span {
color: #178eff;
margin-right: 5px;
}
font-family: PingFangSC-Medium;
font-size: 14px;
color: #596378;
line-height: 24px;
font-weight: 500;
}
.tips {
font-family: PingFangSC-Regular;
font-size: 14px;
color: #596378;
line-height: 21px;
font-weight: 400;
letter-spacing: 0.5px;
}
}
}
.ant-tree {
background: transparent;
}
.ant-drawer-body {
padding: 0;
overflow: hidden;
}
.tooltip {
position: relative;
display: inline-block;
cursor: pointer;
width: 110px;
margin: 0 auto;
line-height: 18px;
word-break: break-all;
}
.tooltip .tooltitle {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 144px;
background-color: #555;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 0;
position: absolute;
z-index: 1;
bottom: 125%;
left: 39%;
margin-left: -60px;
opacity: 0;
transition: opacity 0.3s;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}

@ -0,0 +1,110 @@
/*
* @Author: Chengliang 1546584672@qq.com
* @Date: 2023-09-11 15:33:27
* @LastEditors: Chengliang 1546584672@qq.com
* @LastEditTime: 2023-09-15 11:39:05
* @FilePath: /org-chart-frant/src/pages/statisticsTable.jsx
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import React, { useEffect, useState, useRef } from 'react';
import * as d3 from 'd3';
import qs from 'qs';
import { Table } from 'antd';
import { getLabel } from '../util/i18n.js';
const StatisticsTable = () => {
const [loading, setLoading] = useState(true);
const [dataSource, setDataSource] = useState([]);
const [columns, setColumns] = useState([]);
const [labelData, setLabelData] = useState({});
useEffect(() => {
d3.json('/api/bs/hrmorganization/orgchart/i18n').then((res) => {
setLabelData(res.data);
const columns = [
{
title: `${getLabel(547327, res.data)}`,
dataIndex: 'key',
key: 'key',
},
{
title: `${getLabel(547517, res.data)}`,
dataIndex: 'dataIdName',
key: 'dataIdName',
},
{
title: `${getLabel(547518, res.data)}`,
dataIndex: 'superIdName',
key: 'superIdName',
},
{
title: `${getLabel(547519, res.data)}`,
dataIndex: 'type',
key: 'type',
render(value, row, index) {
if (value == 1) {
return `${getLabel(547332, res.data)}`;
} else {
return `${getLabel(547331, res.data)}`;
}
},
},
{
title: `${getLabel(547346, res.data)}`,
dataIndex: 'onJobNum',
key: 'onJobNum',
},
{
title: `${getLabel(547345, res.data)}`,
dataIndex: 'staffNum',
key: 'staffNum',
},
{
title: `${getLabel(547520, res.data)}`,
dataIndex: 'creator',
key: 'creator',
},
{
title: `${getLabel(547521, res.data)}`,
dataIndex: 'createTime',
key: 'createTime',
},
{
title: `${getLabel(547522, res.data)}`,
dataIndex: 'updateTime',
key: 'updateTime',
},
];
setColumns(columns);
selectData();
});
}, []);
const selectData = () => {
d3.json(`/api/bs/hrmorganization/orgchart/selectStatistics`).then((res) => {
setDataSource(res.data.result);
setLoading(false);
});
};
return (
<>
<div style={{ padding: '50px' }}>
<Table
dataSource={dataSource}
columns={columns}
loading={loading}
pagination={{
showTotal: (total) =>
`${getLabel(547523, labelData)} ${dataSource.length} ${getLabel(
547524,
labelData,
)}`,
}}
/>
;
</div>
</>
);
};
export default StatisticsTable;

@ -99,9 +99,8 @@ export default function userPage() {
//
useEffect(() => {
document.cookie =
'ecology_JSessionid=aaapuc_tMLwUT4NtMrnLy; JSESSIONID=aaapuc_tMLwUT4NtMrnLy; Systemlanguid=7; languageidweaver=7; loginuuids=1; __randcode__=7e56748f-fcc0-41fd-aac7-44d6bce54bad; loginidweaver=sysadmin';
'ecology_JSessionid=aaaLiDqzA0rZHgPBdxGez; JSESSIONID=aaaLiDqzA0rZHgPBdxGez; Systemlanguid=7; languageidweaver=7; loginuuids=1; loginidweaver=sysadmin; __randcode__=7b9b3b9b-a780-4d6a-b8d5-22d0f8aaf11a';
d3.json(
// "/user/data"
'/api/bs/hrmorganization/orgchart/userData?fclass=0&fisvitual=0&root=0&level=3&id=0',
).then((data) => {
setData(data.data);

@ -0,0 +1,15 @@
let languageIdWeaver = document.cookie
.split('; ')
.find((row) => row.startsWith('languageidweaver='))
?.split('=')[1];
if (languageIdWeaver == undefined) {
languageIdWeaver = 7;
}
export const getLabel = (key, labelData) => {
if (labelData[key]) {
return labelData[key].labelName;
} else {
return languageIdWeaver == 7 ? '标签不存在' : 'Label does not exist';
}
};
Loading…
Cancel
Save