EmergencyProcedure.vue 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. <template>
  2. <div class="emergency-procedure-container">
  3. <div class="container-title">
  4. <span class="line"></span>
  5. <span class="title">应急处置</span>
  6. </div>
  7. <YearSelector class="year-selector" :years="yearsList" :defaultYear="defaultYear" @year-change="handleYearChange" />
  8. <div class="event-counts">
  9. <div v-if="yearSelected === defaultYear">
  10. <span>本月应急事件</span>
  11. <span class="event-count">{{ monthCount }}</span>
  12. </div>
  13. <div>
  14. <span>本年应急事件</span>
  15. <span class="event-count">{{ yearCount }}</span>
  16. </div>
  17. </div>
  18. <EmergencyEventsChart class="events-echarts" />
  19. <div class="container-title">
  20. <span class="line"></span>
  21. <span class="title">应急事件</span>
  22. </div>
  23. <div class="events-list">
  24. <div class="event-item" v-for="(item, index) in eventsList" :key="index">
  25. <div class="event-name">{{ item.name }}</div>
  26. <div class="event-time">{{ item.time }}</div>
  27. <div class="event-status" v-if="item.status">{{ item.status }}</div>
  28. </div>
  29. <!-- 哨兵 -->
  30. <div ref="sentinelRef" style="height: 1px"></div>
  31. <div v-if="isLoading" class="loading">
  32. <span>加载中...</span>
  33. </div>
  34. <div v-if="!hasMore && eventsList.length > 0" class="no-more">
  35. <span>暂无更多应急预案</span>
  36. </div>
  37. </div>
  38. </div>
  39. </template>
  40. <script setup lang="ts">
  41. import { onMounted, ref } from 'vue';
  42. import YearSelector from './YearSelector.vue';
  43. import EmergencyEventsChart from './EmergencyEventsChart.vue';
  44. import { useInfiniteScroll } from '../hooks/useInfiniteScroll';
  45. interface EventItem {
  46. name: string;
  47. time: string;
  48. status?: string; // 是否是启动中状态
  49. }
  50. const defaultYear = ref<number>(new Date().getFullYear()); // 默认为当前年份
  51. const yearsList = ref<number[]>([2021, 2022, 2023, 2024, 2025]);
  52. const yearSelected = ref<number>(defaultYear.value);
  53. const monthCount = ref<number>(0);
  54. const yearCount = ref<number>(0);
  55. const eventsList = ref<EventItem[]>([]);
  56. const page = ref<number>(1);
  57. const pageSize = ref<number>(10);
  58. const hasMore = ref<boolean>(true);
  59. const isLoading = ref<boolean>(false);
  60. const sentinelRef = ref<HTMLElement | null>(null); // 哨兵元素引用
  61. const params = ref({
  62. year: yearSelected.value,
  63. page: page.value,
  64. pageSize: pageSize.value,
  65. });
  66. // 请求后端接口获取数据
  67. const fetchExerciseData = async () => {
  68. if (isLoading.value || !hasMore.value) return;
  69. isLoading.value = true;
  70. try {
  71. // TODO: 替换为实际的 API 请求
  72. // const res = await axios.get('/api/items', params.value);
  73. const res = {
  74. data: {
  75. items: [
  76. { name: '事件1', time: '2023-10-10', status: '启动中' },
  77. { name: '事件2', time: '2023-10-10', status: '启动中' },
  78. { name: '事件3', time: '2023-10-10', status: '启动中' },
  79. { name: '事件4', time: '2023-10-10', status: '启动中' },
  80. { name: '事件5', time: '2023-10-10' },
  81. { name: '事件6', time: '2023-10-10' },
  82. { name: '事件7', time: '2023-10-10' },
  83. { name: '事件8', time: '2023-10-10' },
  84. { name: '事件9', time: '2023-10-10' },
  85. { name: '事件10', time: '2023-10-10' },
  86. { name: '事件11', time: '2023-10-10' },
  87. { name: '事件12', time: '2023-10-10' },
  88. ],
  89. },
  90. };
  91. // TODO: 赋值:本月应急事件,本年应急事件,事件列表,折线图
  92. const newItems = res.data.items as EventItem[];
  93. if (newItems.length === 0) {
  94. hasMore.value = false;
  95. } else {
  96. eventsList.value.push(...newItems);
  97. page.value++;
  98. }
  99. } catch (error) {
  100. console.error('请求失败:', error);
  101. } finally {
  102. isLoading.value = false;
  103. }
  104. };
  105. // 切换年份的状态重置
  106. const resetStateAndFetchData = () => {
  107. hasMore.value = true;
  108. page.value = 1;
  109. eventsList.value = []; // 清空当前列表
  110. monthCount.value = 0;
  111. yearCount.value = 0;
  112. console.log('params', params.value);
  113. fetchExerciseData();
  114. };
  115. const handleYearChange = (year: number) => {
  116. yearSelected.value = year;
  117. resetStateAndFetchData();
  118. };
  119. onMounted(() => {
  120. fetchExerciseData();
  121. useInfiniteScroll(sentinelRef, fetchExerciseData);
  122. });
  123. </script>
  124. <style scoped lang="scss">
  125. .container-title {
  126. display: flex;
  127. align-items: center;
  128. font-weight: 500;
  129. font-size: 16px;
  130. color: #000000;
  131. .line {
  132. width: 3px;
  133. height: 16px;
  134. background: #1777ff;
  135. margin-right: 10px;
  136. }
  137. .title {
  138. margin-right: 12px;
  139. }
  140. }
  141. .emergency-procedure-container {
  142. width: 100%;
  143. height: 100%;
  144. padding: 10px 0;
  145. position: relative;
  146. .year-selector {
  147. position: absolute;
  148. top: 11px;
  149. right: 16px;
  150. }
  151. .event-counts {
  152. display: flex;
  153. justify-content: space-around;
  154. align-items: center;
  155. margin: 20px 7px 10px 7px;
  156. padding: 12px 0px;
  157. background: #e7f1ff;
  158. border-radius: 8px;
  159. font-weight: 400;
  160. font-size: 16px;
  161. color: rgba(0, 0, 0, 0.65);
  162. .event-count {
  163. color: #1777ff;
  164. margin-left: 4px;
  165. }
  166. }
  167. .events-echarts {
  168. margin: 15px 10px;
  169. }
  170. .events-list {
  171. width: 100%;
  172. height: calc(100% - 387px);
  173. margin-top: 15px;
  174. padding: 0 10px;
  175. overflow: auto;
  176. .event-item {
  177. width: 100%;
  178. height: 82px;
  179. margin-bottom: 10px;
  180. background: #dcfaff;
  181. border-radius: 8px;
  182. display: flex;
  183. flex-direction: column;
  184. justify-content: center;
  185. padding: 0 16px;
  186. position: relative;
  187. .event-name {
  188. font-weight: 500;
  189. font-size: 16px;
  190. color: #333333;
  191. margin-bottom: 8px;
  192. }
  193. .event-time {
  194. font-size: 14px;
  195. color: #666666;
  196. }
  197. .event-status {
  198. position: absolute;
  199. top: 0;
  200. right: 0;
  201. background: #ff4d4f;
  202. border-radius: 0px 8px 0px 8px;
  203. font-size: 10px;
  204. color: #ffffff;
  205. padding: 2px 6px;
  206. }
  207. }
  208. }
  209. }
  210. </style>