Przeglądaj źródła

feat(web-app): 优化基本面模块与构建配置(GDP/通胀/投资/vite/main/router)

ligb 2 tygodni temu
rodzic
commit
e6fa4ec0aa

+ 1 - 0
web-app/.env.production

@@ -0,0 +1 @@
+VITE_API_BASE=

+ 1 - 38
web-app/src/main.js

@@ -2,42 +2,5 @@ import { createApp } from 'vue'
 import App from './App.vue'
 import router from './router'
 import './assets/main.css'
-import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
-import { restoreOriginalTitle } from '@/composables/useSyncDocumentTitle'
 
-let instance = null
-
-function render(props = {}) {
-  const { container } = props
-  instance = createApp(App)
-  // 通过 props 接收主应用下发的内容(如设备端、token 等)
-  instance.provide('microProps', props)
-  instance.use(router)
-  instance.mount(container ? container.querySelector('#app') : document.getElementById('app'))
-}
-
-// qiankun 生命周期
-renderWithQiankun({
-  bootstrap() {
-    console.log('[web-app] bootstrap')
-  },
-  mount(props) {
-    console.log('[web-app] mount', props)
-    render(props)
-  },
-  update(props) {
-    console.log('[web-app] update', props)
-  },
-  unmount() {
-    console.log('[web-app] unmount')
-    // 卸载前还原宿主页面 title,避免浏览器 tab 残留子应用标题
-    restoreOriginalTitle()
-    instance?.unmount()
-    instance = null
-  },
-})
-
-// 独立运行时直接渲染(便于单独调试该子应用)
-if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
-  render()
-}
+createApp(App).use(router).mount('#app')

+ 2 - 3
web-app/src/router/index.js

@@ -1,10 +1,9 @@
 import { createRouter, createWebHistory } from 'vue-router'
-import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
 import { useAuth } from '@/store/auth'
 
-// 子应用在 qiankun 环境下运行时,路由 base 采用其部署前缀 /web/
+// 单独部署:根路径
 const router = createRouter({
-  history: createWebHistory(qiankunWindow.__POWERED_BY_QIANKUN__ ? '/web/' : '/'),
+  history: createWebHistory('/'),
   routes: [
     {
       path: '/login',

+ 35 - 13
web-app/src/views/fundamental/gdp/index.vue

@@ -20,7 +20,8 @@
           {{ formatNum(k.value) }}<span class="unit"> {{ k.unit }}</span>
         </div>
         <div :class="['chg', chgCls(k.value, k.sign)]">
-          <span class="arrow">{{ arrow(k.value) }}</span> {{ k.date }} · {{ k.tag }}
+          {{ k.date }}
+          <!-- <span>{{ arrow(k.value) }}</span> {{ k.date }} · {{ k.tag }} -->
         </div>
       </div>
     </section>
@@ -41,12 +42,12 @@
               <span class="ic">1.1</span>
               <span class="title-text">现价 GDP · 同比 / 累计走势</span>
             </div>
-            <div class="acts">
+            <!-- <div class="acts">
               <div class="pill-group">
                 <a :class="{ on: nomPill === 'yoy' }" @click="nomPill = 'yoy'">同比</a>
                 <a :class="{ on: nomPill === 'trend' }" @click="nomPill = 'trend'">趋势线</a>
               </div>
-            </div>
+            </div> -->
           </div>
           <div class="card-body">
             <Chart :option="gdpNominalOpt" height="300px" />
@@ -68,9 +69,9 @@
               <div class="kv"><span class="k">最近观测</span><span class="v">{{ gdpNominalMeta?.data_end || '—' }}</span></div>
               <div class="kv"><span class="k">最新读数</span><span class="v strong">{{ formatPct(gdpNominalLatest) }}</span></div>
             </div>
-            <div class="note">
-              现价 GDP 同比反映名义增长(含价格因素)。近 4 季度呈温和回升态势,{{ gdpNominalComment }}。
-            </div>
+<div class="note">
+                现价 GDP 同比反映名义增长(含价格因素)。{{ gdpNominalTrend }},{{ gdpNominalComment }}。
+              </div>
           </div>
         </div>
       </div>
@@ -124,7 +125,7 @@
       <div class="section-hd">
         <span class="dot"></span>
         <h2>§1.3 GDP 周度预测 (MM6)</h2>
-        <span class="meta">周频 · 自研模型 · MM6 · R² ≈ 0.91</span>
+        <span class="meta">周频 · 自研模型 · MM6 </span>
         <span class="meta-spacer"></span>
         <span class="meta">最近预测 {{ formatPct(mm6ForecastLatest) }} · {{ formatDate(mm6ForecastMeta?.data_end) }}</span>
       </div>
@@ -151,7 +152,7 @@
               <div class="name">🔮 MM6 下期预测</div>
               <div style="font-size:11px;color:var(--mut);margin-top:3px">基于 22 个高频指标</div>
             </div>
-            <span class="badge" v-if="mm6R2">R²={{ mm6R2 }}</span>
+            <!-- <span class="badge" v-if="mm6R2">R²={{ mm6R2 }}</span> -->
           </div>
           <div class="pred">
             <span class="v" style="color:var(--ink)">{{ mm6ForecastLatest?.value?.toFixed?.(2) || '—' }}<span style="font-size:13px;color:var(--mut)">%</span></span>
@@ -159,7 +160,7 @@
           </div>
           <div class="range">
             <span>{{ mm6RangeLow?.toFixed?.(2) }}%</span>
-            <span>{{ formatDate(mm6ForecastLatest?.date) }} 预测 ± {{ mm6Range?.toFixed?.(2) }}pct</span>
+            <!-- <span>{{ formatDate(mm6ForecastLatest?.date) }} 预测 ± {{ mm6Range?.toFixed?.(2) }}pct</span> -->
             <span>{{ mm6RangeHigh?.toFixed?.(2) }}%</span>
           </div>
           <div class="hrange"><div class="pin" :style="{ left: mm6PinLeft + '%' }"></div></div>
@@ -177,7 +178,7 @@
         </div>
       </div>
 
-      <div class="card-grid cols-1" style="margin-top:14px">
+      <!-- <div class="card-grid cols-1" style="margin-top:14px">
         <div class="card">
           <div class="card-head">
             <div class="title">
@@ -192,7 +193,7 @@
             <Chart :option="mm6ForecastOpt" height="320px" />
           </div>
         </div>
-      </div>
+      </div> -->
     </section>
 
     <!-- §1.4 GDP 平减指数 -->
@@ -360,9 +361,9 @@ onMounted(reload)
 const kpis = computed(() => ([
   { label: '现价 GDP 同比', icon: '📈', value: gdpNominalLatest.value?.value, unit: '%', date: formatDate(gdpNominalLatest.value?.date), tag: '名义增速', sign: true, forecast: false },
   { label: '不变价 GDP 同比', icon: '📊', value: gdpRealLatest.value?.value, unit: '%', date: formatDate(gdpRealLatest.value?.date), tag: '实际增速', sign: true, forecast: false },
-  { label: 'GDP 平减指数', icon: '🧮', value: gdpDeflatorLatest.value?.value, unit: '%', date: formatDate(gdpDeflatorLatest.value?.date), tag: gdpDeflatorLatest.value?.value >= 0 ? '价格因素为正' : '价格因素为负', sign: false, forecast: false },
-  { label: 'MM6 周频预测', icon: '🔮', value: mm6ForecastLatest.value?.value, unit: '%', date: formatDate(mm6ForecastLatest.value?.date), tag: '下期预测', sign: true, forecast: true },
+  { label: 'GDP 平减指数', icon: '🧮', value: gdpDeflatorLatest.value?.value, unit: '%', date: formatDate(gdpDeflatorLatest.value?.date), tag: gdpDeflatorLatest.value?.value >= 0 ? '价格因素为正' : '价格因素为负', sign: true, forecast: false },
   { label: 'MM6 实际增速', icon: '🎯', value: mm6ActualLatest.value?.value, unit: '%', date: formatDate(mm6ActualLatest.value?.date), tag: '季度内最新', sign: true, forecast: false },
+  { label: 'MM6 周频预测', icon: '🔮', value: mm6ForecastLatest.value?.value, unit: '%', date: formatDate(mm6ForecastLatest.value?.date), tag: '下期预测', sign: true, forecast: true },
 ]))
 
 // ===== Charts =====
@@ -411,6 +412,7 @@ const mm6Range = computed(() => {
     const lv = l.get(d)
     return lv != null ? uv - lv : null
   }).filter((x) => x != null)
+
   if (!widths.length) return 0.3
   return widths[widths.length - 1] / 2
 })
@@ -450,6 +452,26 @@ const gdpNominalComment = computed(() => {
   if (v >= 0) return '名义增长偏弱'
   return '名义增长承压'
 })
+
+// 近 4 季度趋势判断:基于 gdpNominalSeries 末 4 个数据点的斜率
+const gdpNominalTrend = computed(() => {
+  const arr = gdpNominalSeries.value
+  if (!arr || arr.length < 2) return '增长动能持续观察'
+  const win = arr.slice(-4).map(x => x[1]).filter(v => typeof v === 'number')
+  if (win.length < 2) return '增长动能持续观察'
+  const first = win[0]
+  const last = win[win.length - 1]
+  const diff = last - first
+  // 绝对阈值 0.3pct,比例阈值 5%
+  const rel = Math.abs(diff) / (Math.abs(first) || 1)
+  if (rel < 0.05 && Math.abs(diff) < 0.3) return '近 4 季度走势平稳'
+  if (diff >= 0) {
+    if (rel >= 0.15 || diff >= 1.0) return '近 4 季度明显回升'
+    return '近 4 季度呈温和回升态势'
+  }
+  if (rel >= 0.15 || diff <= -1.0) return '近 4 季度明显回落'
+  return '近 4 季度呈温和回落态势'
+})
 </script>
 
 <style scoped>

+ 3 - 2
web-app/src/views/fundamental/inflation/data.js

@@ -275,15 +275,16 @@ export async function loadPpiForecast() {
 }
 
 export async function loadLatestAll() {
-  const [cpiY, cpiM, coreY, ppiY, cpiF, ppiF] = await Promise.all([
+  const [cpiY, cpiM, coreY, coreM, ppiY, cpiF, ppiF] = await Promise.all([
     loadLatestWithFallback(24, 'cpiYoy', 'CPI:当月同比'),
     loadLatestWithFallback(25, 'cpiMom', 'CPI:环比'),
     loadLatestWithFallback(26, 'coreCpiYoy', '核心CPI:当月同比'),
+    loadLatestWithFallback(27, 'coreCpiMom', '核心CPI:环比'),
     loadLatestWithFallback(28, 'ppiYoy', 'PPI:当月同比'),
     loadLatestWithFallback(50, 'cpiFcForecast', 'CPI预测'),
     loadLatestWithFallback(51, 'ppiFcForecast', 'PPI预测'),
   ])
-  return { cpiYoy: cpiY, cpiMom: cpiM, coreCpiYoy: coreY, ppiYoy: ppiY, cpiForecast: cpiF, ppiForecast: ppiF }
+  return { cpiYoy: cpiY, cpiMom: cpiM, coreCpiYoy: coreY, coreCpiMom: coreM, ppiYoy: ppiY, cpiForecast: cpiF, ppiForecast: ppiF }
 }
 
 export async function loadInflationMeta() {

+ 11 - 6
web-app/src/views/fundamental/inflation/index.vue

@@ -211,7 +211,7 @@
     </section>
 
     <!-- §5.5 PPI 结构 (占位) -->
-    <section class="section">
+    <!-- <section class="section">
       <div class="section-hd">
         <span class="dot"></span>
         <h2>§5.5 PPI 结构 · 待补充</h2>
@@ -231,7 +231,7 @@
           </div>
         </div>
       </div>
-    </section>
+    </section> -->
 
     <!-- §5.6 通胀预测 -->
     <section class="section">
@@ -249,11 +249,11 @@
               <span class="ic">5.6a</span>
               <span class="title-text">CPI 实际 vs 预测</span>
             </div>
-            <div class="acts">
+            <!-- <div class="acts">
               <span class="meta tag-r" v-if="cpiForecastLatest && cpiYoyLatest">
                 Δ {{ cpiFcDelta.text }}
               </span>
-            </div>
+            </div> -->
           </div>
           <div class="card-body">
             <Chart :option="cpiForecastOpt" height="300px" />
@@ -265,11 +265,11 @@
               <span class="ic">5.6b</span>
               <span class="title-text">PPI 实际 vs 预测</span>
             </div>
-            <div class="acts">
+            <!-- <div class="acts">
               <span class="meta tag-r" v-if="ppiForecastLatest && ppiYoyLatest">
                 Δ {{ ppiFcDelta.text }}
               </span>
-            </div>
+            </div> -->
           </div>
           <div class="card-body">
             <Chart :option="ppiForecastOpt" height="300px" />
@@ -313,6 +313,7 @@ const ppiFc = vRef({ actual: [], forecast: [] })
 const cpiYoyMeta = vRef(null)
 const cpiMomMeta = vRef(null)
 const coreCpiYoyMeta = vRef(null)
+const coreCpiMomMeta = vRef(null)
 const ppiYoyMeta = vRef(null)
 const ppiMomMeta = vRef(null)
 const cpiFcActualMeta = vRef(null)
@@ -323,6 +324,7 @@ const ppiFcForecastMeta = vRef(null)
 const cpiYoyLatest = vRef(null)
 const cpiMomLatest = vRef(null)
 const coreCpiYoyLatest = vRef(null)
+const coreCpiMomLatest = vRef(null)
 const ppiYoyLatest = vRef(null)
 const ppiMomLatest = vRef(null)
 const cpiForecastLatest = vRef(null)
@@ -379,6 +381,8 @@ async function reload() {
     cpiMomMeta.value = cpiM.meta
     coreCpiYoySeries.value = coreY.series
     coreCpiYoyMeta.value = coreY.meta
+    coreCpiMomSeries.value = coreM.series
+    coreCpiMomMeta.value = coreM.meta
     ppiYoySeries.value = ppiY.series
     ppiYoyMeta.value = ppiY.meta
     ppiMomSeries.value = ppiM.series
@@ -395,6 +399,7 @@ async function reload() {
     cpiYoyLatest.value = pickLatest(cpiY.series)
     cpiMomLatest.value = pickLatest(cpiM.series)
     coreCpiYoyLatest.value = pickLatest(coreY.series)
+    coreCpiMomLatest.value = pickLatest(coreM.series)
     ppiYoyLatest.value = pickLatest(ppiY.series)
     ppiMomLatest.value = pickLatest(ppiM.series)
     cpiForecastLatest.value = pickLatest(cpiF.forecast)

+ 2 - 2
web-app/src/views/fundamental/investment/index.vue

@@ -245,7 +245,7 @@
     </section>
 
     <!-- §2.6 民间投资 -->
-    <section class="section">
+    <!-- <section class="section">
       <div class="section-hd">
         <span class="dot"></span>
         <h2>§2.6 民间投资 · 累计增速</h2>
@@ -290,7 +290,7 @@
           </div>
         </div>
       </div>
-    </section>
+    </section> -->
 
     <footer class="footer">
       数据源:国家统计局 · {{ updatedAt }} · 投资看板 v1.0

+ 10 - 19
web-app/vite.config.js

@@ -1,21 +1,13 @@
 import { fileURLToPath, URL } from 'node:url'
 import { defineConfig } from 'vite'
 import vue from '@vitejs/plugin-vue'
-import qiankun from 'vite-plugin-qiankun'
 
-const SUB_APP_NAME = 'web-app'
-
-// qiankun 子应用:借助 vite-plugin-qiankun 暴露微前端生命周期
 export default defineConfig(({ mode }) => {
   const isProd = mode === 'production'
 
   return {
-    // 生产环境部署在 /web/ 子路径下;开发环境用根路径便于独立调试
-    base: isProd ? '/web/' : '/',
-    plugins: [
-      vue(),
-      qiankun(SUB_APP_NAME, { useDevMode: !isProd }),
-    ],
+    base: '/',
+    plugins: [vue()],
     resolve: {
       alias: {
         '@': fileURLToPath(new URL('./src', import.meta.url)),
@@ -25,14 +17,7 @@ export default defineConfig(({ mode }) => {
       host: '0.0.0.0',
       port: 7100,
       strictPort: true,
-      cors: true,
-      origin: 'http://localhost:7100',
-      headers: {
-        // qiankun 通过 fetch 拉取子应用资源,须允许跨域
-        'Access-Control-Allow-Origin': '*',
-      },
       proxy: {
-        // 开发环境将 /api 代理到后端,避免浏览器跨域
         '/api': {
           target: process.env.VITE_API_TARGET || 'http://localhost:8080',
           changeOrigin: true,
@@ -41,12 +26,18 @@ export default defineConfig(({ mode }) => {
     },
     build: {
       target: 'es2015',
-      // 每个子应用产出去独立的 chunk,便于 qiankun 沙箱加载
+      sourcemap: false,
+      outDir: 'dist',
+      assetsDir: 'assets',
       rollupOptions: {
         output: {
           chunkFileNames: 'assets/[name]-[hash].js',
+          manualChunks: {
+            echarts: ['echarts'],
+            vue: ['vue', 'vue-router'],
+          },
         },
       },
     },
   }
-})
+})