PageOrganization.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. <template>
  2. <div class="safety-platform-container">
  3. <div class="safety-platform-container__header">
  4. <div class="breadcrumb-title"> 应急架构体系 </div>
  5. </div>
  6. <div class="safety-platform-container__main" :class="{ 'zoom-mode': isChartZoomed }">
  7. <div class="chart-container" ref="chartContainerRef" v-loading="loadingTeams">
  8. <OrgChart
  9. :treeData="treeData"
  10. @node-click="handleNodeClick"
  11. @canvas-click="handleCanvasClick"
  12. v-if="treeData.id !== '-1'"
  13. />
  14. <div class="no-data" v-else>暂无队伍</div>
  15. <div class="chart-actions">
  16. <el-button v-if="!isChartFullscreen" size="small" @click="toggleChartZoom">
  17. {{ isChartZoomed ? '恢复布局' : '放大模式' }}
  18. </el-button>
  19. <el-button size="small" @click="toggleChartFullscreen">
  20. {{ isChartFullscreen ? '退出全屏' : '全屏查看' }}
  21. </el-button>
  22. </div>
  23. </div>
  24. <div class="detail-container" v-loading="loadingTeamsDetail">
  25. <TeamDetailList :selected-team-id="selectedTeamId" class="team-detail" />
  26. </div>
  27. </div>
  28. <div class="safety-platform-container__footer" v-if="showOperationBar">
  29. <el-button @click="router.push('team-management')"> 编辑 </el-button>
  30. </div>
  31. </div>
  32. </template>
  33. <script setup lang="ts">
  34. import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
  35. import { useRouter } from 'vue-router';
  36. import { storeToRefs } from 'pinia';
  37. import { useUserInfoHook } from '@/views/disaster/hooks';
  38. import useTeamStore from './team-management/store/userTeam';
  39. import { EMERGENCY_PERMISSIONS } from '@/views/emergency/constant';
  40. import OrgChart from '../components/OrgChart.vue';
  41. import TeamDetailList from './components/TeamDetailList.vue';
  42. const { permissions } = useUserInfoHook();
  43. const { loadingTeams, loadingTeamsDetail } = storeToRefs(useTeamStore());
  44. const { getLeaderTeams } = useTeamStore();
  45. type OrganizationTreeType = {
  46. id: string;
  47. data: { name: string };
  48. children?: OrganizationTreeType[];
  49. };
  50. const treeData = ref<OrganizationTreeType>({
  51. id: 'root',
  52. data: { name: '应急领导小组' },
  53. children: [
  54. {
  55. id: 'group1',
  56. data: { name: '应急指挥小组' },
  57. },
  58. {
  59. id: 'group2',
  60. data: { name: '应急响应小组' },
  61. },
  62. {
  63. id: 'group3',
  64. data: { name: '应急支援小组' },
  65. },
  66. ],
  67. });
  68. const router = useRouter();
  69. const showOperationBar = ref(false);
  70. const chartContainerRef = ref<HTMLElement | null>(null);
  71. const isChartFullscreen = ref(false);
  72. const isChartZoomed = ref(false);
  73. const selectedTeamId = ref<number | null>(null);
  74. const handleNodeClick = (nodeData: any) => {
  75. selectedTeamId.value = Number(nodeData.id);
  76. };
  77. const handleCanvasClick = () => {
  78. selectedTeamId.value = null;
  79. };
  80. async function toggleChartFullscreen() {
  81. const el = chartContainerRef.value;
  82. if (!el) return;
  83. if (document.fullscreenElement === el) {
  84. await document.exitFullscreen();
  85. await triggerChartResize();
  86. return;
  87. }
  88. await el.requestFullscreen();
  89. await triggerChartResize();
  90. }
  91. async function toggleChartZoom() {
  92. isChartZoomed.value = !isChartZoomed.value;
  93. await triggerChartResize();
  94. }
  95. function syncFullscreenState() {
  96. isChartFullscreen.value = document.fullscreenElement === chartContainerRef.value;
  97. }
  98. async function triggerChartResize() {
  99. await nextTick();
  100. // G6 组件内部监听 window resize,这里主动触发一次确保尺寸立即更新
  101. window.dispatchEvent(new Event('resize'));
  102. }
  103. function convertData(leaderTeams): OrganizationTreeType {
  104. return {
  105. id: leaderTeams.teamId.toString(),
  106. data: {
  107. name: leaderTeams.teamName,
  108. },
  109. children: leaderTeams.children?.map((child) => convertData(child)),
  110. };
  111. }
  112. onMounted(async () => {
  113. document.addEventListener('fullscreenchange', syncFullscreenState);
  114. showOperationBar.value = Boolean(
  115. permissions.find((item: { code: string }) => item.code === EMERGENCY_PERMISSIONS.ORGANIZATION_MANAGEMENT),
  116. );
  117. const res = await getLeaderTeams();
  118. treeData.value = convertData(res);
  119. });
  120. onBeforeUnmount(() => {
  121. document.removeEventListener('fullscreenchange', syncFullscreenState);
  122. });
  123. </script>
  124. <style lang="scss" scoped>
  125. @use '@/styles/page-details-layout.scss' as *;
  126. .chart-container {
  127. position: relative;
  128. width: 100%;
  129. height: 40%;
  130. box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
  131. border-radius: 8px;
  132. }
  133. .chart-container:fullscreen {
  134. height: 100%;
  135. padding: 12px;
  136. background: #ffffff;
  137. border-radius: 0;
  138. }
  139. .chart-actions {
  140. position: absolute;
  141. display: flex;
  142. gap: 8px;
  143. right: 12px;
  144. bottom: 12px;
  145. z-index: 2;
  146. }
  147. .safety-platform-container__main.zoom-mode {
  148. .chart-container {
  149. height: 75%;
  150. }
  151. .detail-container {
  152. height: 25%;
  153. }
  154. }
  155. .detail-container {
  156. width: 100%;
  157. height: 60%;
  158. padding-top: 20px;
  159. overflow: auto;
  160. .team-detail {
  161. width: 60%;
  162. margin: 0 auto;
  163. }
  164. }
  165. .no-data {
  166. width: 100%;
  167. height: 100%;
  168. display: flex;
  169. justify-content: center;
  170. align-items: center;
  171. font-size: 16px;
  172. color: #666666;
  173. }
  174. </style>