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:
@@ -339,10 +339,20 @@ export const roleApi = {
|
||||
|
||||
export const permissionApi = {
|
||||
getMenuTree: () => api.get('/permissions/menu-tree'),
|
||||
getModules: () => api.get('/permissions/modules'),
|
||||
getConfig: () => api.get('/permissions/config'),
|
||||
saveConfig: (data: any) => api.put('/permissions/config', data),
|
||||
getUserRoles: () => api.get('/user-roles'),
|
||||
setUserRoles: (data: any) => api.post('/user-roles', data),
|
||||
}
|
||||
|
||||
// ── KPI目标对齐(/alignment/* 由路由级 require_auth 保护,统一走本实例)──
|
||||
export const alignmentApi = {
|
||||
getConfig: () => api.get('/alignment/config'),
|
||||
saveConfig: (data: any) => api.post('/alignment/config', data),
|
||||
getTree: (params?: any) => api.get('/alignment/tree', { params }),
|
||||
}
|
||||
|
||||
export const kpiCausalityApi = {
|
||||
list: (params?: any) => api.get('/kpi-causality', { params }),
|
||||
get: (id: number) => api.get(`/kpi-causality/${id}`),
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -176,14 +176,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import axios from 'axios'
|
||||
|
||||
const api = axios.create({ baseURL: '/api/cma', timeout: 30000 })
|
||||
api.interceptors.request.use((config: any) => {
|
||||
const token = localStorage.getItem('cma_token')
|
||||
if (token) config.headers.Authorization = `Bearer ${token}`
|
||||
return config
|
||||
})
|
||||
// 统一走 api/index.ts 的 axios 实例(拦截器自动带 Bearer / 401 跳登录)
|
||||
import api from '../api/index'
|
||||
|
||||
const userRole = computed(() => (localStorage.getItem('cma_role') || 'finance'))
|
||||
const canWrite = computed(() => ['ceo', 'finance', 'it'].includes(userRole.value))
|
||||
@@ -221,7 +215,7 @@ function dimLabel(d: string) {
|
||||
async function loadStats() {
|
||||
try {
|
||||
const r = await api.get('/data-classification/stats')
|
||||
stats.value = (r as any).data || {}
|
||||
stats.value = (r as any) || {}
|
||||
} catch (e) {
|
||||
stats.value = {}
|
||||
}
|
||||
@@ -236,7 +230,7 @@ async function loadInventory() {
|
||||
if (onlyImportant.value) params.important = 1
|
||||
if (keyword.value) params.keyword = keyword.value
|
||||
const r = await api.get('/data-classification/inventory', { params })
|
||||
const d = (r as any).data || {}
|
||||
const d = (r as any) || {}
|
||||
items.value = d.items || []
|
||||
totalItems.value = d.total || 0
|
||||
markedCount.value = (d.stats || {}).marked || 0
|
||||
@@ -251,7 +245,7 @@ async function loadInventory() {
|
||||
async function loadIndustryRef() {
|
||||
try {
|
||||
const r = await api.get('/data-classification/industry-reference')
|
||||
industryRef.value = (r as any).data?.items || []
|
||||
industryRef.value = (r as any)?.items || []
|
||||
} catch (e) {
|
||||
industryRef.value = []
|
||||
}
|
||||
@@ -322,7 +316,7 @@ async function doExport() {
|
||||
if (filterLevel.value) params.data_level = filterLevel.value
|
||||
if (onlyImportant.value) params.important = 1
|
||||
const r: any = await api.get('/data-classification/export', { params, responseType: 'blob' })
|
||||
const blob = r.data
|
||||
const blob = r
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
|
||||
@@ -112,21 +112,14 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { sessionEntityName } from '../utils/entity'
|
||||
// 统一走 api/index.ts 的 axios 实例(拦截器自动带 Bearer / 401 跳登录;语义同为「已剥层」)
|
||||
import api from '../api/index'
|
||||
|
||||
const loading = ref(false)
|
||||
const entityLabel = ref(sessionEntityName())
|
||||
const data = ref<any>({})
|
||||
|
||||
const api = axios.create({ baseURL: '/api/cma', timeout: 15000 })
|
||||
api.interceptors.request.use((config: any) => {
|
||||
const token = localStorage.getItem('cma_token')
|
||||
if (token) config.headers.Authorization = `Bearer ${token}`
|
||||
return config
|
||||
})
|
||||
api.interceptors.response.use((r: any) => r.data, (e: any) => Promise.reject(e))
|
||||
|
||||
function formatPct(val: number): string {
|
||||
if (val === null || val === undefined) return '-'
|
||||
return val.toFixed(2)
|
||||
|
||||
@@ -137,6 +137,8 @@
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
// 统一走 api/index.ts 的 axios 实例(拦截器自动带 Bearer / 401 跳登录)
|
||||
import { alignmentApi, mapApi } from '../api/index'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -156,10 +158,8 @@ const selectedMapId = ref<number | null>(null)
|
||||
|
||||
async function loadMapList() {
|
||||
try {
|
||||
const token = localStorage.getItem('cma_token')
|
||||
const r = await fetch('/api/cma/maps', {
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
}).then(r => r.json())
|
||||
// 统一走 api 实例(Bearer 由拦截器注入,不再手拼 header)
|
||||
const r: any = await mapApi.list()
|
||||
mapList.value = r.data || []
|
||||
} catch {}
|
||||
}
|
||||
@@ -271,11 +271,8 @@ const devClass = (d: any) => {
|
||||
|
||||
async function loadConfig() {
|
||||
try {
|
||||
// 鉴权收口(cma-api-auth-surface-audit-20260914):/alignment/* 已要求 Bearer,补 token
|
||||
const token = localStorage.getItem('cma_token')
|
||||
const r = await fetch('/api/cma/alignment/config', {
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
}).then(r => r.json())
|
||||
// 统一走 api 实例(Bearer 由拦截器注入)
|
||||
const r: any = await alignmentApi.getConfig()
|
||||
alignModes.value = r.modes || []
|
||||
alignConfigured.value = r.configured || false
|
||||
if (r.configured && r.mode) {
|
||||
@@ -291,12 +288,8 @@ async function saveAlignConfig() {
|
||||
if (!selectedMode.value) return
|
||||
saving.value = true
|
||||
try {
|
||||
const token = localStorage.getItem('cma_token')
|
||||
await fetch('/api/cma/alignment/config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Authorization': `'Bearer ' + token` },
|
||||
body: JSON.stringify({ mode: selectedMode.value }),
|
||||
}).then(r => r.json())
|
||||
// 修复:原 `'Bearer ' + token` 模板字面量 bug(header 值被写死成字面量 → 必 401)
|
||||
await alignmentApi.saveConfig({ mode: selectedMode.value })
|
||||
ElMessage.success('对齐模式已设置')
|
||||
currentMode.value = alignModes.value.find(m => m.key === selectedMode.value) || null
|
||||
alignConfigured.value = true
|
||||
@@ -309,12 +302,7 @@ async function saveAlignConfig() {
|
||||
async function loadTree() {
|
||||
loading.value = true
|
||||
try {
|
||||
const token = localStorage.getItem('cma_token')
|
||||
let url = '/api/cma/alignment/tree'
|
||||
if (selectedMapId.value) url += `?map_id=${selectedMapId.value}`
|
||||
const r = await fetch(url, {
|
||||
headers: { 'Authorization': `'Bearer ' + token` },
|
||||
}).then(r => r.json())
|
||||
const r: any = await alignmentApi.getTree(selectedMapId.value ? { map_id: selectedMapId.value } : undefined)
|
||||
treeData.value = r
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e.message || '加载失败')
|
||||
|
||||
@@ -155,14 +155,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import axios from 'axios'
|
||||
|
||||
const api = axios.create({ baseURL: '/api/cma', timeout: 30000 })
|
||||
api.interceptors.request.use((config: any) => {
|
||||
const token = localStorage.getItem('cma_token')
|
||||
if (token) config.headers.Authorization = `Bearer ${token}`
|
||||
return config
|
||||
})
|
||||
// 统一走 api/index.ts 的 axios 实例(拦截器自动带 Bearer / 401 跳登录)
|
||||
import api from '../api/index'
|
||||
|
||||
// ── 状态 ──
|
||||
const period = ref('2026-06')
|
||||
@@ -243,7 +237,7 @@ async function calculate() {
|
||||
})),
|
||||
}
|
||||
const r = await api.post('/reports/mpm-calculate', payload)
|
||||
result.value = (r as any).data || {}
|
||||
result.value = (r as any) || {}
|
||||
} catch (e) {
|
||||
ElMessage.error('计算失败,请检查参数')
|
||||
result.value = null
|
||||
|
||||
@@ -619,7 +619,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import axios from 'axios'
|
||||
// 统一走 api/index.ts 的 axios 实例(拦截器自动带 Bearer / 统一 401 跳登录 / 超时提示)
|
||||
import api from '../api/index'
|
||||
import { getFixedEntityId } from '../utils/entity'
|
||||
|
||||
// ── 状态 ──
|
||||
@@ -644,13 +645,7 @@ function getEntityId() {
|
||||
return Number(localStorage.getItem('cma_entity_id') || currentEntityId.value || 1)
|
||||
}
|
||||
|
||||
// API 配置
|
||||
const api = axios.create({ baseURL: '/api/cma', timeout: 30000 })
|
||||
api.interceptors.request.use((config: any) => {
|
||||
const token = localStorage.getItem('cma_token')
|
||||
if (token) config.headers.Authorization = `Bearer ${token}`
|
||||
return config
|
||||
})
|
||||
// API 配置:统一使用 api/index.ts 的共享实例(本地 axios 实例 + 手拼 Bearer 已移除)
|
||||
|
||||
// ── 报表1:管理利润表 ──
|
||||
const profitItems = ref<any[]>([])
|
||||
@@ -688,7 +683,7 @@ async function loadNew30() {
|
||||
params.format = 'new'
|
||||
}
|
||||
const r = await api.get('/reports/profit-statement', { params })
|
||||
const d = (r as any).data || {}
|
||||
const d = (r as any) || {}
|
||||
|
||||
if (new30Format.value === 'dual') {
|
||||
dualComparisonData.value = d
|
||||
@@ -749,7 +744,7 @@ async function loadStatutory() {
|
||||
statutoryLoading.value = true
|
||||
try {
|
||||
const r = await api.get('/reports/statutory', { params: { period: reportPeriod.value, entity_id: getEntityId() } })
|
||||
const d = (r as any).data || {}
|
||||
const d = (r as any) || {}
|
||||
statutoryData.value = d
|
||||
statutoryDemoMode.value = !d.cash_flow?.all_items_have_data
|
||||
} catch (e) {
|
||||
@@ -762,11 +757,11 @@ async function loadStatutory() {
|
||||
async function exportStatutory() {
|
||||
exporting.value = true
|
||||
try {
|
||||
const token = localStorage.getItem('cma_token') || ''
|
||||
const url = `/api/cma/reports/statutory/export?period=${reportPeriod.value}&entity_id=${getEntityId()}`
|
||||
const resp = await fetch(url, { headers: { 'Authorization': `Bearer ${token}` } })
|
||||
if (!resp.ok) throw new Error('导出失败')
|
||||
const blob = await resp.blob()
|
||||
// 统一走 api 实例(responseType=blob;Bearer 由拦截器注入,非 2xx 直接 reject)
|
||||
const blob: any = await api.get('/reports/statutory/export', {
|
||||
params: { period: reportPeriod.value, entity_id: getEntityId() },
|
||||
responseType: 'blob',
|
||||
})
|
||||
const a = document.createElement('a')
|
||||
a.href = URL.createObjectURL(blob)
|
||||
a.download = `对外法定报表_${reportPeriod.value}.xlsx`
|
||||
@@ -811,7 +806,7 @@ async function loadRestatement() {
|
||||
loadingRestatement.value = true
|
||||
try {
|
||||
const r = await api.get('/reports/restatement', { params: { period: reportPeriod.value, entity_id: getEntityId() } })
|
||||
const d = (r as any).data || {}
|
||||
const d = (r as any) || {}
|
||||
restatementItems.value = d.items || []
|
||||
restatementSummary.value = d.summary || null
|
||||
restatementNetProfit.value = d.net_profit_comparison || null
|
||||
@@ -864,7 +859,7 @@ function onPeriodChange() {
|
||||
async function loadProfit() {
|
||||
try {
|
||||
const r = await api.get('/reports/profit-summary', { params: { period: reportPeriod.value, entity_id: getEntityId() } })
|
||||
profitItems.value = (r as any).data?.items || []
|
||||
profitItems.value = (r as any)?.items || []
|
||||
} catch { profitItems.value = [] }
|
||||
}
|
||||
|
||||
@@ -874,7 +869,7 @@ async function loadBudget() {
|
||||
if (budgetDim.value) params.dimension = budgetDim.value
|
||||
if (budgetLevel.value) params.alert_level = budgetLevel.value
|
||||
const r = await api.get('/reports/budget-execution', { params })
|
||||
const d = (r as any).data || {}
|
||||
const d = (r as any) || {}
|
||||
budgetSummary.value = d.summary || {}
|
||||
budgetItems.value = d.items || []
|
||||
} catch { budgetItems.value = [] }
|
||||
@@ -925,7 +920,7 @@ async function loadProforma() {
|
||||
cash: '/reports/proforma/cash-flow',
|
||||
}
|
||||
const r = await api.get(map[proformaType.value], { params: { period: reportPeriod.value, entity_id: getEntityId() } })
|
||||
proformaData.value = (r as any).data || {}
|
||||
proformaData.value = (r as any) || {}
|
||||
} catch (e) {
|
||||
proformaData.value = null
|
||||
ElMessage.error('加载预编报表失败')
|
||||
@@ -953,7 +948,7 @@ async function loadTrends() {
|
||||
if (trendKpiId.value) params.kpi_id = trendKpiId.value
|
||||
else if (trendDim.value) params.dimension = trendDim.value
|
||||
const r = await api.get('/reports/kpi-trends', { params })
|
||||
trendData.value = (r as any).data?.data || []
|
||||
trendData.value = (r as any)?.data || []
|
||||
nextTick(() => renderTrendCharts())
|
||||
} catch { trendData.value = [] }
|
||||
}
|
||||
@@ -961,14 +956,14 @@ async function loadTrends() {
|
||||
async function loadBsc() {
|
||||
try {
|
||||
const r = await api.get('/reports/bsc-scorecard', { params: { period: reportPeriod.value, entity_id: getEntityId() } })
|
||||
bscData.value = (r as any).data || {}
|
||||
bscData.value = (r as any) || {}
|
||||
} catch { bscData.value = {} }
|
||||
}
|
||||
|
||||
async function loadKpiOptions() {
|
||||
try {
|
||||
const r = await api.get('/kpis', { params: { page_size: 100, entity_id: getEntityId() } })
|
||||
kpiOptions.value = (r as any).data?.data || []
|
||||
kpiOptions.value = (r as any)?.data || []
|
||||
} catch {}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,6 +141,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
// 统一走 api/index.ts 的 axios 实例(拦截器自动带 Bearer / 401 跳登录)
|
||||
import { permissionApi, alignmentApi } from '../api/index'
|
||||
|
||||
// ── Tab 控制 ──
|
||||
const activeTab = ref('permissions')
|
||||
@@ -200,14 +202,10 @@ function toggleAction(role: string, actionKey: string, value: boolean) {
|
||||
async function savePermConfig() {
|
||||
saving.value = true
|
||||
try {
|
||||
const token = localStorage.getItem('cma_token')
|
||||
await fetch('/api/cma/permissions/config', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
|
||||
body: JSON.stringify({
|
||||
route_permissions: routePerms.value,
|
||||
action_permissions: actionPerms.value,
|
||||
}),
|
||||
// 统一走 api 实例(Bearer 由拦截器注入,不再手拼 header)
|
||||
await permissionApi.saveConfig({
|
||||
route_permissions: routePerms.value,
|
||||
action_permissions: actionPerms.value,
|
||||
})
|
||||
ElMessage.success('权限配置已保存')
|
||||
permSaved.value = true
|
||||
@@ -217,13 +215,8 @@ async function savePermConfig() {
|
||||
|
||||
async function resetPermConfig() {
|
||||
try { await ElMessageBox.confirm('确定恢复默认权限配置?', '确认', { center: true }) } catch { return }
|
||||
const token = localStorage.getItem('cma_token')
|
||||
try {
|
||||
await fetch('/api/cma/permissions/config', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
|
||||
body: JSON.stringify({})
|
||||
})
|
||||
await permissionApi.saveConfig({})
|
||||
ElMessage.success('已恢复默认')
|
||||
loadPermData()
|
||||
} catch { ElMessage.error('恢复失败') }
|
||||
@@ -231,12 +224,9 @@ async function resetPermConfig() {
|
||||
|
||||
async function loadPermData() {
|
||||
try {
|
||||
// 鉴权收口(cma-api-auth-surface-audit-20260914):/permissions/* 已要求 Bearer,补 token
|
||||
const token = localStorage.getItem('cma_token')
|
||||
const authHeaders = { 'Authorization': `Bearer ${token}` }
|
||||
const [modRes, cfgRes] = await Promise.all([
|
||||
fetch('/api/cma/permissions/modules', { headers: authHeaders }).then(r => r.json()),
|
||||
fetch('/api/cma/permissions/config', { headers: authHeaders }).then(r => r.json()),
|
||||
const [modRes, cfgRes]: any[] = await Promise.all([
|
||||
permissionApi.getModules(),
|
||||
permissionApi.getConfig(),
|
||||
])
|
||||
modules.value = modRes.modules || []
|
||||
actions.value = modRes.actions || []
|
||||
@@ -275,11 +265,8 @@ function feedsLabel(feeds: string[] | null) {
|
||||
|
||||
async function loadAlignmentConfig() {
|
||||
try {
|
||||
// 鉴权收口(cma-api-auth-surface-audit-20260914):/alignment/* 已要求 Bearer,补 token
|
||||
const token = localStorage.getItem('cma_token')
|
||||
const r = await fetch('/api/cma/alignment/config', {
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
}).then(r => r.json())
|
||||
// 统一走 api 实例(Bearer 由拦截器注入)
|
||||
const r: any = await alignmentApi.getConfig()
|
||||
alignmentModes.value = r.modes || []
|
||||
alignConfigured.value = r.configured || false
|
||||
currentMode.value = null
|
||||
@@ -295,12 +282,7 @@ async function saveAlignmentConfig() {
|
||||
if (!selectedMode.value) return
|
||||
savingAlign.value = true
|
||||
try {
|
||||
const token = localStorage.getItem('cma_token')
|
||||
const r = await fetch('/api/cma/alignment/config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
|
||||
body: JSON.stringify({ mode: selectedMode.value }),
|
||||
}).then(r => r.json())
|
||||
const r: any = await alignmentApi.saveConfig({ mode: selectedMode.value })
|
||||
ElMessage.success(r.message || '对齐模式已设置')
|
||||
loadAlignmentConfig()
|
||||
showSelector.value = false
|
||||
@@ -311,10 +293,7 @@ async function saveAlignmentConfig() {
|
||||
async function previewTree() {
|
||||
loadingTree.value = true
|
||||
try {
|
||||
const token = localStorage.getItem('cma_token')
|
||||
const r = await fetch('/api/cma/alignment/tree', {
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
}).then(r => r.json())
|
||||
const r: any = await alignmentApi.getTree()
|
||||
treeData.value = r
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e.message || '加载失败')
|
||||
|
||||
@@ -99,14 +99,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import axios from 'axios'
|
||||
|
||||
const api = axios.create({ baseURL: '/api/cma', timeout: 30000 })
|
||||
api.interceptors.request.use((config: any) => {
|
||||
const token = localStorage.getItem('cma_token')
|
||||
if (token) config.headers.Authorization = `Bearer ${token}`
|
||||
return config
|
||||
})
|
||||
// 统一走 api/index.ts 的 axios 实例(拦截器自动带 Bearer / 401 跳登录)
|
||||
import api from '../api/index'
|
||||
|
||||
const loading = ref(false)
|
||||
const subjects = ref<any[]>([])
|
||||
@@ -127,7 +121,7 @@ async function loadSubjects() {
|
||||
if (filterCategory.value) params.category = filterCategory.value
|
||||
if (keyword.value) params.keyword = keyword.value
|
||||
const r = await api.get('/subjects', { params })
|
||||
const d = (r as any).data || {}
|
||||
const d = (r as any) || {}
|
||||
subjects.value = d.data || []
|
||||
totalSubjects.value = d.total || 0
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user