index.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. <template>
  2. <div class="account-warning-container" v-if="platformNames.length > 0 || isLoading">
  3. <a-popover placement="bottom" trigger="hover">
  4. <div class="warning-tags">
  5. <icon-loading v-if="isLoading" style="margin-right: 8px;" />
  6. <a-tag
  7. v-for="platform in platformNames"
  8. :key="platform"
  9. :color="getPlatformStatus(platform).hasOffline ? '#f53f3f' : '#165dff'"
  10. :style="{ marginRight: '8px' }"
  11. :class="{ 'warning-pulse': getPlatformStatus(platform).hasOffline }"
  12. >
  13. {{ getPlatformDisplayName(platform) }}:{{ getPlatformStatus(platform).status }}
  14. <icon-exclamation-circle v-if="getPlatformStatus(platform).hasOffline"
  15. style="margin-left: 4px;"
  16. class="warning-icon" />
  17. </a-tag>
  18. </div>
  19. <template #content>
  20. <div class="warning-detail">
  21. <div v-for="platform in platformNames" :key="platform" class="platform-detail">
  22. <div class="platform-title">{{ getPlatformDisplayName(platform) }}</div>
  23. <div class="platform-stats">
  24. <a-tag
  25. v-for="(stats, type) in accountWarningData[platform]"
  26. :key="type"
  27. :color="stats[1] === 0 ? '#f53f3f' : '#165dff'"
  28. size="small"
  29. :style="{ marginRight: '4px', marginBottom: '4px' }"
  30. >
  31. {{ type }}: {{ stats[1] }}/{{ stats[0] }}
  32. </a-tag>
  33. </div>
  34. </div>
  35. </div>
  36. </template>
  37. </a-popover>
  38. </div>
  39. </template>
  40. <script lang="ts" setup>
  41. import { ref, computed, onMounted, onUnmounted } from 'vue';
  42. import { queryAccountWarning, type AccountWarningStats } from '@/api/message';
  43. const accountWarningData = ref<AccountWarningStats>({});
  44. const isLoading = ref(false);
  45. let intervalId: ReturnType<typeof setInterval> | null = null;
  46. let loadingTimer: ReturnType<typeof setTimeout> | null = null;
  47. const fetchAccountWarning = async () => {
  48. const startTime = Date.now();
  49. isLoading.value = true;
  50. if (loadingTimer) {
  51. clearTimeout(loadingTimer);
  52. }
  53. try {
  54. const { data } = await queryAccountWarning();
  55. console.log(data);
  56. accountWarningData.value = data;
  57. const elapsed = Date.now() - startTime;
  58. const remainingTime = Math.max(500 - elapsed, 0);
  59. loadingTimer = setTimeout(() => {
  60. isLoading.value = false;
  61. }, remainingTime);
  62. } catch (error) {
  63. console.error('获取账户警告数据失败:', error);
  64. const elapsed = Date.now() - startTime;
  65. const remainingTime = Math.max(500 - elapsed, 0);
  66. loadingTimer = setTimeout(() => {
  67. isLoading.value = false;
  68. }, remainingTime);
  69. }
  70. };
  71. const platformNames = computed(() => Object.keys(accountWarningData.value || {}));
  72. const getPlatformDisplayName = (platform: string) => {
  73. const nameMap: Record<string, string> = {
  74. 'tb': '淘宝',
  75. 'jd': '京东'
  76. };
  77. return nameMap[platform] || platform;
  78. };
  79. const getPlatformStatus = (platform: string) => {
  80. const platformData = accountWarningData.value[platform];
  81. if (!platformData) return { status: '无数据', hasOffline: false };
  82. const hasOffline = Object.values(platformData).some(([, onlineCount]) => onlineCount === 0);
  83. if (hasOffline) {
  84. const offlineTypes = Object.entries(platformData)
  85. .filter(([, [, onlineCount]]) => onlineCount === 0)
  86. .map(([type]) => type);
  87. return {
  88. status: `异常(${offlineTypes.join(',')})`,
  89. hasOffline: true
  90. };
  91. }
  92. return { status: '正常', hasOffline: false };
  93. };
  94. onMounted(() => {
  95. fetchAccountWarning();
  96. intervalId = setInterval(fetchAccountWarning, 5000);
  97. });
  98. onUnmounted(() => {
  99. if (intervalId) {
  100. clearInterval(intervalId);
  101. intervalId = null;
  102. }
  103. if (loadingTimer) {
  104. clearTimeout(loadingTimer);
  105. loadingTimer = null;
  106. }
  107. });
  108. </script>
  109. <style scoped lang="less">
  110. .account-warning-container {
  111. display: flex;
  112. align-items: center;
  113. flex-wrap: wrap;
  114. .warning-tags {
  115. display: flex;
  116. align-items: center;
  117. }
  118. }
  119. .warning-detail {
  120. min-width: 200px;
  121. .platform-detail {
  122. margin-bottom: 12px;
  123. &:last-child {
  124. margin-bottom: 0;
  125. }
  126. .platform-title {
  127. font-weight: bold;
  128. margin-bottom: 6px;
  129. color: var(--color-text-1);
  130. }
  131. .platform-stats {
  132. display: flex;
  133. flex-wrap: wrap;
  134. }
  135. }
  136. }
  137. .warning-pulse {
  138. animation: pulse 1s infinite;
  139. box-shadow: 0 0 12px rgba(245, 63, 63, 0.8);
  140. font-weight: bold;
  141. border: 2px solid rgba(245, 63, 63, 0.6);
  142. }
  143. .warning-icon {
  144. animation: shake 1s infinite;
  145. filter: drop-shadow(0 0 4px rgba(245, 63, 63, 0.8));
  146. }
  147. @keyframes pulse {
  148. 0% {
  149. box-shadow: 0 0 12px rgba(245, 63, 63, 0.8);
  150. border-color: rgba(245, 63, 63, 0.6);
  151. transform: scale(1);
  152. }
  153. 50% {
  154. box-shadow: 0 0 24px rgba(245, 63, 63, 1);
  155. border-color: rgba(245, 63, 63, 1);
  156. transform: scale(1.05);
  157. }
  158. 100% {
  159. box-shadow: 0 0 12px rgba(245, 63, 63, 0.8);
  160. border-color: rgba(245, 63, 63, 0.6);
  161. transform: scale(1);
  162. }
  163. }
  164. @keyframes shake {
  165. 0%, 100% {
  166. transform: translateX(0);
  167. }
  168. 10%, 30%, 50%, 70%, 90% {
  169. transform: translateX(-2px);
  170. }
  171. 20%, 40%, 60%, 80% {
  172. transform: translateX(2px);
  173. }
  174. }
  175. </style>