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