refactor(cma-frontend): route all API calls through shared axios instance (cma-frontend-axios-unify-20260915)

api/index.ts: add permissionApi.getModules/getConfig/saveConfig + alignmentApi.config/tree.

views: replace 13 raw fetch calls and 6 local axios.create instances (each hand-building the Authorization header, without the shared 401 redirect / timeout toast) with the shared instance from api/index.ts.

KPIAlignment: fixes a live bug - the header value was the string literal 'Bearer ' + token, so /alignment/tree always returned 401 (reproduced against the production bundle).

Unwrap semantics preserved: the shared response interceptor returns response.data, so call sites that used res.data were adjusted accordingly.

Verified: scan-frontend-tokenless RAW-TOKENLESS=0; vite build ok; headless A/B (new dist vs production bundle, 8 affected pages, real login) -> identical rendered rows and identical pre-existing error profile, /alignment 401->200.

Push not executed: main is ahead 4 with other tickets' gated commits (see cma-main-ahead3-push-gate-notice-20260915).
This commit is contained in:
Hermes CI Fix
2026-09-15 22:21:56 +08:00
parent e5eb092d20
commit 7b74736980
9 changed files with 73 additions and 133 deletions
+9 -16
View File
@@ -271,8 +271,8 @@ import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Loading, ArrowRight } from '@element-plus/icons-vue'
import { dashboardApi, alertApi, aiSuggestionApi } from '../api/index'
import axios from 'axios'
import { dashboardApi, alertApi, aiSuggestionApi, notificationApi } from '../api/index'
import api from '../api/index'
import CountUp from '../components/CountUp.vue'
import GrowthQuality from '../components/GrowthQuality.vue'
import BEPBreakEvenCard from '../components/BEPBreakEvenCard.vue'
@@ -346,12 +346,7 @@ async function loadSuggestions() {
suggestionsLoading.value = false
}
const aiApi = axios.create({ baseURL: '/api/cma', timeout: 30000 })
aiApi.interceptors.request.use((config: any) => {
const token = localStorage.getItem('cma_token')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
// AI 分析等接口统一走 api/index.ts 的共享实例(此前本地 aiApi 手拼 Bearer 已移除)
const dimConfig: Record<string, { icon: string; color: string; name: string }> = {
finance: { icon: '💰', color: '#409eff', name: '财务维度' },
@@ -423,8 +418,8 @@ async function loadKPIAnalysis() {
if (!selectedKPI.value?.id) return
loadingDetail.value = true
try {
const r: any = await aiApi.get(`/ai/kpi-analysis/${selectedKPI.value.id}`)
selectedKPIDetail.value = r.data?.analysis || r.analysis || '暂无分析结果'
const r: any = await api.get(`/ai/kpi-analysis/${selectedKPI.value.id}`)
selectedKPIDetail.value = r?.analysis || '暂无分析结果'
} catch { selectedKPIDetail.value = 'AI分析请求失败' }
loadingDetail.value = false
}
@@ -432,8 +427,8 @@ async function loadKPIAnalysis() {
async function refreshAnalysis() {
aiLoading.value = true
try {
const r: any = await aiApi.get('/ai/dashboard-analysis', { params: { role: userRole.value } })
aiAnalysis.value = r.data?.analysis || r.analysis || '暂无分析'
const r: any = await api.get('/ai/dashboard-analysis', { params: { role: userRole.value } })
aiAnalysis.value = r?.analysis || '暂无分析'
// 建议列表从服务端拉取(含dashboard分析自动生成的规则建议)
await loadSuggestions()
} catch { aiAnalysis.value = 'AI分析暂时不可用' }
@@ -482,10 +477,8 @@ async function loadData() {
finSummary.value = (r as any)?.summary || {}
dataRange.value = (r as any)?.period || ''
} else if (userRole.value === 'it') {
// 鉴权收口(cma-api-auth-surface-audit-20260914):/notifications/channels 已要求 Bearer
const channelRes = await axios.get('/api/cma/notifications/channels', {
headers: { 'Authorization': `Bearer ${localStorage.getItem('cma_token')}` },
}).then(r => r.data)
// 鉴权收口(cma-api-auth-surface-audit-20260914)+ 实例统一(2026-09-15):走共享实例,Bearer 由拦截器注入
const channelRes: any = await notificationApi.list()
enabledChannels.value = channelRes.data?.filter((c: any) => c.enabled).length || 0
}
} catch (e) { console.error('加载数据失败:', e) }