view.vue 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. <template>
  2. <div v-if="showMissions" class="mission-container">
  3. <el-row :gutter="20" class="grid-container">
  4. <el-col v-for="mission in currentPageData" :key="mission.id" :xs="24" :sm="12" :md="8" :lg="6">
  5. <el-tooltip :content="mission.description" placement="bottom" popper-class="mission-tooltip">
  6. <el-card class="mission-card" @click="handleCardClick(mission.id)">
  7. <!-- 状态指示灯 -->
  8. <div class="status-indicator">
  9. <el-tooltip effect="dark" :content="statusText[mission.status]" placement="top">
  10. <div class="status-light" :style="{ backgroundColor: statusColor[mission.status] }" />
  11. </el-tooltip>
  12. </div>
  13. <div class="card-content">
  14. <h3 class="title">{{ mission.name }}</h3>
  15. <div class="meta">
  16. <el-icon>
  17. <Calendar />
  18. </el-icon>
  19. <span>{{ formatTime(mission.createTime) }}</span>
  20. </div>
  21. <div class="stats">
  22. <!-- 节点行 -->
  23. <div class="stat-item node-row">
  24. <div class="label-box">
  25. <span class="label">节点</span>
  26. </div>
  27. <div class="node-stats">
  28. <span class="node-count">S:{{ mission.nodesInfo.S }}</span>
  29. <span class="node-count">D:{{ mission.nodesInfo.D }}</span>
  30. <span class="node-count">I:{{ mission.nodesInfo.I }}</span>
  31. </div>
  32. </div>
  33. <!-- 边行 -->
  34. <div class="stat-item edge-row">
  35. <div class="label-box">
  36. <span class="label">边</span>
  37. </div>
  38. <span class="edge-count">{{ mission.edgesInfo.num }}</span>
  39. </div>
  40. </div>
  41. </div>
  42. </el-card>
  43. </el-tooltip>
  44. </el-col>
  45. </el-row>
  46. <!-- 分页控件 -->
  47. <el-pagination background layout="total, sizes, prev, pager, next" :page-sizes="[8, 16, 24]"
  48. :current-page="pagination.currentPage" :page-size="pagination.pageSize" :total="pagination.total"
  49. @size-change="handleSizeChange" @current-change="handleCurrentChange" class="pagination" />
  50. </div>
  51. <div v-else>
  52. <router-view></router-view>
  53. </div>
  54. </template>
  55. <script setup>
  56. import { ref, reactive, computed, onMounted, inject, onActivated, watch } from 'vue'
  57. import { useRouter, useRoute } from 'vue-router'
  58. import { Calendar, User, Connection } from '@element-plus/icons-vue'
  59. import { postData, getData } from '@/api/axios'
  60. import { ElMessage } from 'element-plus'
  61. // 保存选中的mission
  62. const useAnalyzeInfo = inject('analyzeInfo')
  63. // 任务数据
  64. const missions = ref([])
  65. // 任务状态对应颜色
  66. const statusColor = {
  67. 'init': '#ff4d4f', // 未开始或暂停、停止
  68. 'pause': '#ff4d4f', // 未开始或暂停、停止
  69. 'calculating': '#faad14', // 计算中
  70. 'done': '#52c41a' // 已完成
  71. }
  72. // 任务状态提示语句
  73. const statusText = {
  74. 'init': '尚未执行计算',
  75. 'pause': '计算暂停中',
  76. 'calculating': '计算进行中',
  77. 'done': '计算已完成',
  78. }
  79. // 用于控制点击后跳转到plan页面
  80. const showMissions = ref(false)
  81. // 分页配置
  82. const pagination = ref({
  83. currentPage: 1,
  84. pageSize: 8,
  85. total: missions.value.length
  86. })
  87. // 当前页数据计算
  88. const currentPageData = computed(() => {
  89. const start = (pagination.value.currentPage - 1) * pagination.value.pageSize
  90. return missions.value.slice(start, start + pagination.value.pageSize)
  91. })
  92. // 分页事件处理
  93. const handleSizeChange = (size) => {
  94. pagination.pageSize = size
  95. pagination.value.currentPage = 1
  96. }
  97. const handleCurrentChange = (page) => {
  98. pagination.value.currentPage = page
  99. }
  100. // 时间格式化
  101. const formatTime = (isoString) => {
  102. return new Date(isoString).toLocaleDateString('zh-CN', {
  103. year: 'numeric',
  104. month: '2-digit',
  105. day: '2-digit'
  106. })
  107. }
  108. // 路由跳转
  109. const router = useRouter()
  110. const handleCardClick = (id) => {
  111. // 检查mission的状态,如果还未计算,则应进入plan界面
  112. // 如果已经计算,应进入calculate界面
  113. let mission = missions.value.find(mission => mission.id == id)
  114. preparePlan(mission)
  115. if(mission.status === "init"){
  116. // 初始状态,应进入流程规划
  117. // 切换显示plan的routeview
  118. showMissions.value = false
  119. router.push(`/dashboard/analyze/plan`)
  120. }else{
  121. showMissions.value = false
  122. // 否则进入calculate界面,可查看计算过程或结果
  123. router.push(`/dashboard/analyze/plan/calculate`)
  124. }
  125. }
  126. const preparePlan = (mission) => {
  127. useAnalyzeInfo.analyzeInfo.value.nodeFile.amount = mission.nodesInfo.S + mission.nodesInfo.D + mission.nodesInfo.I
  128. useAnalyzeInfo.analyzeInfo.value.nodeFile.sNodes = mission.nodesInfo.S
  129. useAnalyzeInfo.analyzeInfo.value.nodeFile.dNodes = mission.nodesInfo.D
  130. useAnalyzeInfo.analyzeInfo.value.nodeFile.iNodes = mission.nodesInfo.I
  131. useAnalyzeInfo.analyzeInfo.value.edgeFile.amount = mission.edgesInfo.num
  132. useAnalyzeInfo.analyzeInfo.value.mission = {
  133. id: mission.id,
  134. name: mission.name,
  135. createTime: mission.createTime,
  136. status: mission.status,
  137. }
  138. console.log(useAnalyzeInfo.analyzeInfo.value)
  139. // 将获取到的数据写入页面缓存,防止刷新丢失
  140. sessionStorage.setItem('analyze-info', JSON.stringify(useAnalyzeInfo.analyzeInfo.value))
  141. }
  142. //加载数据
  143. const loadMissions = async () => {
  144. missions.value = [];
  145. showMissions.value = true;
  146. try {
  147. const response = await getData('/missions/');
  148. missions.value = response.data.map(mission => ({
  149. id: mission.id,
  150. name: mission.name,
  151. createTime: mission.createTime,
  152. nodesInfo: mission.nodesInfo,
  153. edgesInfo: mission.edgesInfo,
  154. status: mission.status,
  155. // 需要后续完善描述输入
  156. description: "This is description",
  157. })).sort((a, b) => b.id - a.id);
  158. pagination.value.total = missions.value.length;
  159. } catch (error) {
  160. console.error(error);
  161. ElMessage.error("获取任务列表错误");
  162. }
  163. }
  164. const route = useRoute();
  165. // 监听路由变化
  166. watch(
  167. () => route.path,
  168. (newPath) => {
  169. if (newPath === '/dashboard/view' || newPath === '/dashboard/taskfile') {
  170. showMissions.value = true;
  171. }
  172. },
  173. { immediate: true }
  174. );
  175. // 首次加载
  176. onMounted(loadMissions);
  177. // 通过后退按钮返回页面时重新加载
  178. onActivated(loadMissions);
  179. </script>
  180. <style scoped lang="scss">
  181. .mission-container {
  182. padding: 20px;
  183. max-width: 1400px;
  184. margin: 0 auto;
  185. background: #f8fafc;
  186. .grid-container {
  187. min-height: 60vh;
  188. }
  189. .mission-card {
  190. position: relative;
  191. border: 1px solid #e2e8f0;
  192. border-radius: 12px;
  193. box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
  194. transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  195. background: white;
  196. cursor: pointer;
  197. overflow: hidden;
  198. &:hover {
  199. transform: translateY(-3px);
  200. box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.08);
  201. .title {
  202. color: var(--el-color-primary);
  203. }
  204. }
  205. .label-box {
  206. display: inline-flex;
  207. align-items: center;
  208. justify-content: center;
  209. width: 48px;
  210. height: 24px;
  211. background: #f1f5f9;
  212. border: 1px solid #e2e8f0;
  213. border-radius: 4px;
  214. margin-right: 8px;
  215. .label {
  216. color: #475569;
  217. font-size: 0.85rem;
  218. font-weight: 500;
  219. transform: scale(0.9);
  220. }
  221. }
  222. .status-indicator {
  223. position: absolute;
  224. right: 16px;
  225. top: 16px;
  226. z-index: 2;
  227. .status-light {
  228. width: 12px;
  229. height: 12px;
  230. border-radius: 50%;
  231. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  232. position: relative;
  233. &::after {
  234. content: '';
  235. position: absolute;
  236. inset: 0;
  237. border-radius: 50%;
  238. background: inherit;
  239. filter: brightness(1.2);
  240. opacity: 0.3;
  241. }
  242. }
  243. }
  244. .card-content {
  245. .title {
  246. font-family: "Microsoft YaHei", system-ui;
  247. font-weight: 600;
  248. color: #1e293b;
  249. margin: 0 0 8px; // 减少上下间距
  250. font-size: 1.15rem;
  251. line-height: 1.3;
  252. position: relative;
  253. padding-left: 24px;
  254. &::before {
  255. content: '📌';
  256. position: absolute;
  257. left: -4px;
  258. top: -2px;
  259. font-size: 0.9em;
  260. opacity: 0.8;
  261. }
  262. }
  263. .meta {
  264. display: flex;
  265. align-items: center;
  266. margin-bottom: 12px;
  267. color: #64748b;
  268. font-size: 0.9rem;
  269. .el-icon {
  270. margin-right: 8px;
  271. font-size: 1.1em;
  272. color: #94a3b8;
  273. }
  274. }
  275. .stats {
  276. .stat-item {
  277. display: flex;
  278. align-items: center;
  279. margin-bottom: 8px;
  280. &.node-row {
  281. .node-stats {
  282. display: flex;
  283. gap: 12px;
  284. align-items: center;
  285. }
  286. }
  287. &.edge-row {
  288. .edge-count {
  289. color: #334155;
  290. font-weight: 500;
  291. }
  292. }
  293. }
  294. }
  295. }
  296. .pagination {
  297. margin-top: 30px;
  298. justify-content: center;
  299. ::v-deep(.el-pagination__total) {
  300. color: #64748b;
  301. }
  302. }
  303. }
  304. @media (max-width: 768px) {
  305. .mission-card {
  306. margin: 8px 0;
  307. .card-content {
  308. padding: 12px;
  309. .title {
  310. font-size: 1.1rem;
  311. padding-left: 20px;
  312. }
  313. .node-stats {
  314. flex-wrap: wrap;
  315. gap: 4px !important;
  316. span::after {
  317. display: none !important;
  318. }
  319. }
  320. }
  321. }
  322. }
  323. }
  324. </style>