App.vue 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. <script setup lang="ts">
  2. /**
  3. * 观澜前端外壳(P11)—— Vue 3 + Vite + TypeScript。
  4. *
  5. * 两种模式:
  6. * · 工作台:与重构前 `/detail/v2` 同构(页签清单一致),数据取 `/api/fleet?win=`(与 app.js 的 load() 同一个接口);
  7. * · 契约自检:把冻契约里的关键接口各取一次,显示 状态/耗时/顶层键。
  8. *
  9. * 迁移纪律(用户令 2026-09-29):页签逐个搬,**与重构前页面对拍通过后**才删对应 Python 前端源码。
  10. */
  11. import { onMounted, ref, watch } from 'vue'
  12. import ContractSmoke from './views/ContractSmoke.vue'
  13. import Workbench from './views/Workbench.vue'
  14. import { callApi } from './api/contract'
  15. type Mode = 'workbench' | 'smoke' | 'about'
  16. const mode = ref<Mode>('workbench')
  17. const tab = ref<string>('overview')
  18. const sub = ref<string | null>(null)
  19. const d = ref<any>(null)
  20. const win = ref<string>('')
  21. const loading = ref(false)
  22. const err = ref('')
  23. async function load() {
  24. loading.value = true
  25. err.value = ''
  26. const q = win.value ? `?win=${encodeURIComponent(win.value)}` : ''
  27. const r = await callApi<any>('fleet', { method: 'GET' })
  28. // callApi 走的是契约里的固定路径;窗参数在这里补上(契约只约定路径与方法)
  29. if (q) {
  30. try {
  31. const resp = await fetch('/api/fleet' + q)
  32. d.value = await resp.json()
  33. } catch (e) {
  34. err.value = e instanceof Error ? e.message : String(e)
  35. }
  36. } else if (r.ok) {
  37. d.value = r.data
  38. if (d.value?.win) win.value = d.value.win
  39. } else {
  40. err.value = r.error || `HTTP ${r.status}`
  41. }
  42. loading.value = false
  43. }
  44. watch(win, (v) => { if (v && v !== d.value?.win) void load() })
  45. onMounted(load)
  46. </script>
  47. <template>
  48. <header class="top">
  49. <div class="brand">观澜 · 风电数据分析与决策支持</div>
  50. <nav>
  51. <button :class="{ on: mode === 'workbench' }" @click="mode = 'workbench'">工作台</button>
  52. <button :class="{ on: mode === 'smoke' }" @click="mode = 'smoke'">契约自检</button>
  53. <button :class="{ on: mode === 'about' }" @click="mode = 'about'">关于本前端</button>
  54. </nav>
  55. </header>
  56. <main>
  57. <template v-if="mode === 'workbench'">
  58. <p v-if="loading" class="hint" style="padding:0 18px">取数中(/api/fleet)…</p>
  59. <p v-else-if="err" class="hint" style="padding:0 18px;color:var(--bad)">取数失败:{{ err }}</p>
  60. <Workbench v-else-if="d" :d="d" :tab="tab" :sub="sub" @tab="tab = $event" @sub="sub = $event" />
  61. </template>
  62. <ContractSmoke v-else-if="mode === 'smoke'" />
  63. <section v-else class="card">
  64. <h2>关于本前端</h2>
  65. <ul>
  66. <li>技术栈:<b>Vue 3 + Vite + TypeScript + Node.js</b>(用户令 2026-09-29)</li>
  67. <li>与后端交互:只走网关一个源站,路径与形状以 <code>app_backEnd/app_backEnd_guanlan/contract/http_api_v1.json</code> 冻契约为准</li>
  68. <li>数据来源:与重构前 <code>/detail/v2</code> 同一个接口 <code>/api/fleet?win=</code></li>
  69. <li>迁移纪律:页签逐个搬,与重构前页面对拍通过后才删对应 Python 源码</li>
  70. </ul>
  71. </section>
  72. </main>
  73. </template>