fix: 企业切换数据同步(8/10页面+shared组件)

This commit is contained in:
Hermes CI Fix
2026-07-27 16:13:03 +08:00
parent b593e0383f
commit f85b18ce6d
8 changed files with 51 additions and 34 deletions
+2 -1
View File
@@ -375,7 +375,8 @@ function openCreate() {
function loadData() {
loading.value = true
kpis.value = []
api.get('/kpis', { params: { dimension: props.dimension, page_size: 100 } })
const eid = Number(localStorage.getItem('cma_entity_id') || 1)
api.get('/kpis', { params: { dimension: props.dimension, page_size: 100, entity_id: eid } })
.then((r: any) => {
kpis.value = (r.data || []).map((k: any) => ({
...k,
+17 -12
View File
@@ -437,6 +437,10 @@ import DriverFactorBudget from '../components/DriverFactorBudget.vue'
const activeTab = ref('input')
const currentYear = new Date().getFullYear()
const currentMonth = new Date().getMonth() + 1
function getEntityId() {
return Number(localStorage.getItem('cma_entity_id') || 1)
}
const yearOptions = computed(() => { const y: number[] = []; for (let i = currentYear - 2; i <= currentYear + 2; i++) y.push(i); return y })
const filterYear = ref(currentYear); const filterMonth = ref(0); const searchKpi = ref('')
const loading = ref(false); const budgetList = ref<any[]>([]); const page = ref(1); const pageSize = ref(20); const total = ref(0); const noMap = ref(false)
@@ -494,7 +498,7 @@ const addForm = ref({ kpi_id: null as any, year: currentYear, month: new Date().
const addKpiOptions = ref<any[]>([]); const addKpiLoading = ref(false); const addSaving = ref(false)
async function searchKpiForAdd(query: string) {
addKpiLoading.value = true
try { const r: any = await kpiApi.list({ keyword: query, page_size: 20 }); const d = r.data || r || []; addKpiOptions.value = Array.isArray(d) ? d : (d.items || []) } catch { }
try { const r: any = await kpiApi.list({ keyword: query, page_size: 20, entity_id: getEntityId() }); const d = r.data || r || []; addKpiOptions.value = Array.isArray(d) ? d : (d.items || []) } catch { }
addKpiLoading.value = false
}
async function doAddBudget() {
@@ -516,11 +520,11 @@ async function loadBudget() {
if (mapCodes.size === 0) { budgetList.value = []; total.value = 0; return }
noMap.value = false; loading.value = true
try {
const params: any = { page: page.value, page_size: pageSize.value }; if (filterYear.value) params.year = filterYear.value; if (filterMonth.value > 0) params.budget_month = filterMonth.value; if (searchKpi.value) params.keyword = searchKpi.value
const params: any = { page: page.value, page_size: pageSize.value, entity_id: getEntityId() }; if (filterYear.value) params.year = filterYear.value; if (filterMonth.value > 0) params.budget_month = filterMonth.value; if (searchKpi.value) params.keyword = searchKpi.value
const r: any = await budgetApi.list(params); const d = r.data || r || []; const allPlans = (Array.isArray(d) ? d : (d.items || [])) as any[]
// 只保留选中地图关联KPI的预算记录
const plans = allPlans.filter((p: any) => mapCodes.has(p.kpi_code))
const kr: any = await kpiApi.list({ page_size: 100, keyword: searchKpi.value || undefined }); const kd = kr.data || kr || []; const ak = Array.isArray(kd) ? kd : (kd.items || [])
const kr: any = await kpiApi.list({ page_size: 100, keyword: searchKpi.value || undefined, entity_id: getEntityId() }); const kd = kr.data || kr || []; const ak = Array.isArray(kd) ? kd : (kd.items || [])
const mk = ak.filter((k: any) => mapCodes.has(k.kpi_code)); const pk = new Set(plans.map((p: any) => p.kpi_code))
const ek = mk.filter((k: any) => !pk.has(k.kpi_code)).map((k: any) => ({ kpi_id: k.id, kpi_code: k.kpi_code, kpi_name: k.kpi_name, dimension: k.dimension, unit: k.unit || '', period: `${filterYear.value || currentYear}-${String(filterMonth.value || 1).padStart(2, '0')}`, budget_value: null, version: 'v1.0', status: '', precision: 0, step: 1 }))
budgetList.value = [...plans.map((item: any) => initRow({ ...item, _empty: false })), ...ek.map(item => initRow({ ...item, _empty: true }))]
@@ -596,9 +600,9 @@ const strategyTree = ref<any[]>([]); const strategyFilterYear = ref(currentYear)
async function loadStrategyBudget() {
if (!selectedMap.value) return
try { const p: any = { budget_year: strategyFilterYear.value }; if (strategyFilterMonth.value > 0) p.budget_month = strategyFilterMonth.value; const r: any = await budgetApi.list(p); const d = r.data || r || []; const plans = (Array.isArray(d) ? d : (d.items || [])) as any[]; strategyAllBudgetPlans.value = plans
try { const p: any = { budget_year: strategyFilterYear.value, entity_id: getEntityId() }; if (strategyFilterMonth.value > 0) p.budget_month = strategyFilterMonth.value; const r: any = await budgetApi.list(p); const d = r.data || r || []; const plans = (Array.isArray(d) ? d : (d.items || [])) as any[]; strategyAllBudgetPlans.value = plans
let dims = selectedMap.value.dimensions; if (typeof dims === 'string') { try { dims = JSON.parse(dims) } catch { dims = [] } }; if (!Array.isArray(dims) || dims.length === 0) { strategyTree.value = []; return }
const kr: any = await kpiApi.list({ page_size: 100 }); const kd = kr.data || kr || []; const km = new Map<string, any>(); (Array.isArray(kd) ? kd : (kd.items || [])).forEach((k: any) => km.set(k.kpi_code, k))
const kr: any = await kpiApi.list({ page_size: 100, entity_id: getEntityId() }); const kd = kr.data || kr || []; const km = new Map<string, any>(); (Array.isArray(kd) ? kd : (kd.items || [])).forEach((k: any) => km.set(k.kpi_code, k))
const tree: any[] = []
for (const dim of dims) { const dt = { budget: 0, kpiCount: 0 }; const objs = (dim.objectives || []).map((obj: any) => { const kpis = (obj.kpis || []).map((code: string) => { const kpi = km.get(code); const plan = plans.find((p: any) => p.kpi_code === code); const bv = plan ? plan.budget_value : null; dt.kpiCount++; if (bv) dt.budget += bv; return { kpi_code: code, kpi_id: kpi?.id || null, kpi_name: kpi?.kpi_name || code, unit: kpi?.unit || '', precision: kpi?.precision || 0, step: kpi?.step || 1, dimension: dim.key, period: plan?.period || `${strategyFilterYear.value}-${String(strategyFilterMonth.value || 1).padStart(2, '0')}`, version: plan?.version || 'v1.0', budget_value: bv, plan_id: plan?.id || null, _editValue: bv ?? 0, _changed: false, _saving: false, _dimKey: dim.key } }).filter(Boolean); return { name: obj.name, kpis } }); tree.push({ key: dim.key, name: dim.name, icon: dim.icon, color: dim.color, kpiCount: dt.kpiCount, totalBudget: dt.budget, objectives: objs }) }
strategyTree.value = tree
@@ -625,20 +629,20 @@ async function approveVersion(row: any, action: string) { const l = action === '
async function resubmitVersion(row: any) { try { await ElMessageBox.confirm(`确认重新提交版本「${row.version}」?`, '确认'); await budgetApi.versionSubmit({ version: row.version }); ElMessage.success('已重新提交'); loadVersions() } catch { } }
const versionYear = ref(currentYear); const versionsList = ref<any[]>([]); const versionsLoading = ref(false); const selectedVersions = ref<any[]>([]); const showVersionDiff = ref(false); const diffLoading = ref(false); const diffData = ref<any>(null); const currentVersion = ref('')
async function loadVersions() { versionsLoading.value = true; try { const r: any = await budgetApi.versions({ year: versionYear.value }); const d = r.data || []; versionsList.value = d.map((v: any) => ({ ...v, _selected: false })); currentVersion.value = d[0]?.version || '' } catch (e) { ElMessage.error('加载版本列表失败') }; versionsLoading.value = false }
async function loadVersions() { versionsLoading.value = true; try { const r: any = await budgetApi.versions({ year: versionYear.value, entity_id: getEntityId() }); const d = r.data || []; versionsList.value = d.map((v: any) => ({ ...v, _selected: false })); currentVersion.value = d[0]?.version || '' } catch (e) { ElMessage.error('加载版本列表失败') }; versionsLoading.value = false }
function onVersionSelect(row: any) { selectedVersions.value = versionsList.value.filter((v: any) => v._selected); if (selectedVersions.value.length > 2) { row._selected = false; selectedVersions.value = versionsList.value.filter((v: any) => v._selected) } }
async function loadVersionDiff() { if (selectedVersions.value.length !== 2) return; diffLoading.value = true; try { const [a, b] = selectedVersions.value; const r: any = await budgetApi.versionDiff({ version_a: a.version, version_b: b.version, year: versionYear.value }); diffData.value = r; showVersionDiff.value = true } catch (e) { ElMessage.error('加载版本对比失败') }; diffLoading.value = false }
const execFilterYear = ref(currentYear); const execFilterMonth = ref(new Date().getMonth()); const execFilterDim = ref(''); const execLoading = ref(false); const execReport = ref<any[]>([]); const execSummaryCards = ref<any[]>([])
async function loadExecutionReport() {
execLoading.value = true
try { const r: any = await budgetApi.deviationReport({ year: execFilterYear.value, month: execFilterMonth.value, dimension: execFilterDim.value || undefined, hierarchical: true }); const items: any[] = r.items || []; const en = items.map((i: any) => ({ ...i, execution_rate: i.budget_value && i.budget_value > 0 ? Math.round((i.actual_value || 0) / i.budget_value * 100) : 0, deviation_value: (i.actual_value || 0) - (i.budget_value || 0) })); execReport.value = en; const hb = en.filter((i: any) => i.budget_value != null); const ob = en.filter((i: any) => i.is_over_budget); const ar = hb.length > 0 ? Math.round(hb.reduce((s, i) => s + (i.execution_rate || 0), 0) / hb.length) : 0; const tb = hb.reduce((s, i) => s + (i.budget_value || 0), 0); const ta = hb.reduce((s, i) => s + (i.actual_value || 0), 0); execSummaryCards.value = [{ label: '有预算KPI', value: `${hb.length}/${en.length}`, color: '#409eff' }, { label: '超预算KPI', value: ob.length.toString(), color: '#f56c6c' }, { label: '平均执行率', value: `${ar}%`, color: ar > 100 ? '#f56c6c' : '#67c23a' }, { label: '预算执行进度', value: tb > 0 ? `${Math.round(ta / tb * 100)}%` : '-', color: '#e6a23c' }] } catch (e) { ElMessage.error('加载执行报告失败') }
try { const r: any = await budgetApi.deviationReport({ year: execFilterYear.value, month: execFilterMonth.value, dimension: execFilterDim.value || undefined, hierarchical: true, entity_id: getEntityId() }); const items: any[] = r.items || []; const en = items.map((i: any) => ({ ...i, execution_rate: i.budget_value && i.budget_value > 0 ? Math.round((i.actual_value || 0) / i.budget_value * 100) : 0, deviation_value: (i.actual_value || 0) - (i.budget_value || 0) })); execReport.value = en; const hb = en.filter((i: any) => i.budget_value != null); const ob = en.filter((i: any) => i.is_over_budget); const ar = hb.length > 0 ? Math.round(hb.reduce((s, i) => s + (i.execution_rate || 0), 0) / hb.length) : 0; const tb = hb.reduce((s, i) => s + (i.budget_value || 0), 0); const ta = hb.reduce((s, i) => s + (i.actual_value || 0), 0); execSummaryCards.value = [{ label: '有预算KPI', value: `${hb.length}/${en.length}`, color: '#409eff' }, { label: '超预算KPI', value: ob.length.toString(), color: '#f56c6c' }, { label: '平均执行率', value: `${ar}%`, color: ar > 100 ? '#f56c6c' : '#67c23a' }, { label: '预算执行进度', value: tb > 0 ? `${Math.round(ta / tb * 100)}%` : '-', color: '#e6a23c' }] } catch (e) { ElMessage.error('加载执行报告失败') }
execLoading.value = false
}
async function doDecompose() {
decomposing.value = true; decomposeResult.value = ''; decomposeDetails.value = []
try { const r: any = await budgetApi.autoDecompose({ year: decomposeForm.value.year, method: decomposeForm.value.method }); decomposeResult.value = r.message || '分解成功'; if (r.results) decomposeDetails.value = r.results.map((res: any) => ({ kpi_code: res.kpi_code, kpi_name: res.kpi_name, annual_budget: res.annual_budget, method: res.method === 'equal' ? '均分' : '加权', monthly_count: res.monthly.length })); ElMessage.success('年度预算分解完成'); loadBudget() } catch (e: any) { ElMessage.error(e.detail || e.message || '分解失败') }
try { const r: any = await budgetApi.autoDecompose({ year: decomposeForm.value.year, method: decomposeForm.value.method, entity_id: getEntityId() }); decomposeResult.value = r.message || '分解成功'; if (r.results) decomposeDetails.value = r.results.map((res: any) => ({ kpi_code: res.kpi_code, kpi_name: res.kpi_name, annual_budget: res.annual_budget, method: res.method === 'equal' ? '均分' : '加权', monthly_count: res.monthly.length })); ElMessage.success('年度预算分解完成'); loadBudget() } catch (e: any) { ElMessage.error(e.detail || e.message || '分解失败') }
decomposing.value = false
}
@@ -657,7 +661,7 @@ function loadMethodDetail(id: string) {
async function refreshMethodComparison() {
try {
const r: any = await budgetApi.methodComparison({ entity: 'hanke' })
const r: any = await budgetApi.methodComparison({ entity: 'hanke', entity_id: getEntityId() })
budgetMethods.value = r.methods || []
selectedMethod.value = r.recommended || 'zero_based'
loadMethodDetail(selectedMethod.value)
@@ -721,7 +725,7 @@ let comparisonChart: any = null
async function loadComparisonKpiOptions() {
try {
const r: any = await kpiApi.list({ page_size: 200 })
const r: any = await kpiApi.list({ page_size: 200, entity_id: getEntityId() })
const d = r.data || r || []
comparisonKpiOptions.value = Array.isArray(d) ? d : (d.items || [])
} catch { comparisonKpiOptions.value = [] }
@@ -730,7 +734,7 @@ async function loadComparisonKpiOptions() {
async function loadComparison() {
comparisonLoading.value = true
try {
const params: any = { year: comparisonYear.value }
const params: any = { year: comparisonYear.value, entity_id: getEntityId() }
if (comparisonKpiId.value) params.kpi_id = comparisonKpiId.value
const r: any = await budgetApi.getComparison(params)
comparisonData.value = r.months_data || []
@@ -846,7 +850,7 @@ const alertPeriodOptions = computed(() => {
async function loadDeviationAlerts() {
alertLoading.value = true
try {
const params: any = {}
const params: any = { entity_id: getEntityId() }
if (alertFilterPeriod.value) params.period = alertFilterPeriod.value
if (alertFilterLevel.value) params.alert_level = alertFilterLevel.value
if (alertFilterStatus.value) params.status = alertFilterStatus.value
@@ -863,6 +867,7 @@ async function doDeviationCheck() {
const r: any = await budgetApi.deviationCheck({
period: alertFilterPeriod.value,
threshold: 20,
entity_id: getEntityId(),
})
if (r.alerts_generated > 0) {
deviationAlertMessage.value = `偏差检查完成:生成了 ${r.alerts_generated} 条预警`
+9 -5
View File
@@ -390,9 +390,13 @@ const overviewData = ref<any>({})
const productOptions = ref<string[]>([])
const maxTrend = ref(0)
function getEntityId() {
return Number(localStorage.getItem('cma_entity_id') || 1)
}
async function loadOverview() {
try {
const r: any = await costApi.dashboard({ period: overviewPeriod.value })
const r: any = await costApi.dashboard({ period: overviewPeriod.value, entity_id: getEntityId() })
overviewData.value = r.data || r
//
const products = overviewData.value.products || []
@@ -423,7 +427,7 @@ const stdProductFilter = ref('')
async function loadStandardCosts() {
stdLoading.value = true
try {
const params: any = {}
const params: any = { entity_id: getEntityId() }
if (stdProductFilter.value) params.product_code = stdProductFilter.value
const r: any = await costApi.listStandardCosts(params)
standardCosts.value = r.data || []
@@ -474,7 +478,7 @@ const actualSaving = ref(false)
async function loadActualCosts() {
actualLoading.value = true
try {
const params: any = { period: entryPeriod.value }
const params: any = { period: entryPeriod.value, entity_id: getEntityId() }
const r: any = await costApi.listActualCosts(params)
actualCosts.value = r.data || []
} catch (e) { ElMessage.error('加载实际成本失败') }
@@ -555,7 +559,7 @@ const allocSaving = ref(false)
async function loadAllocations() {
allocLoading.value = true
try {
const r: any = await costApi.listAbcAllocations({ period: allocPeriod.value })
const r: any = await costApi.listAbcAllocations({ period: allocPeriod.value, entity_id: getEntityId() })
abcAllocations.value = r.data || []
} catch (e) { /* ignore */ }
allocLoading.value = false
@@ -652,7 +656,7 @@ const compInsights = computed(() => compData.value.insights || [])
async function loadComparison() {
compLoading.value = true
try {
const r: any = await costApi.comparison({ entity: compEntity.value })
const r: any = await costApi.comparison({ entity: compEntity.value, entity_id: getEntityId() })
compData.value = r.data || r
} catch (e) { ElMessage.error('加载对比数据失败') }
compLoading.value = false
+3 -1
View File
@@ -69,8 +69,9 @@ async function loadTrend() {
// KPIListView kpis
// KPIListView API 5KPI
trendLoading.value = true
const eid = Number(localStorage.getItem('cma_entity_id') || 1)
try {
const r: any = await api.get('/kpis', { params: { dimension: 'customer', page_size: 5 } })
const r: any = await api.get('/kpis', { params: { dimension: 'customer', page_size: 5, entity_id: eid } })
const kpis = (r.data || []).slice(0, 5)
const kpiIds = kpis.map((k: any) => k.id).filter(Boolean)
if (kpiIds.length === 0) { trendLoading.value = false; return }
@@ -78,6 +79,7 @@ async function loadTrend() {
kpi_ids: kpiIds,
dimension: 'customer',
period_type: trendType.value,
entity_id: eid,
})
trendData.value = trendRes.data || []
trendLoading.value = false
+6 -5
View File
@@ -408,13 +408,14 @@ function onCustomRangeChange(val: any) {
async function loadData() {
try {
const params = getPeriodParams()
const entityId = Number(localStorage.getItem('cma_entity_id') || 1)
const params = { ...getPeriodParams(), entity_id: entityId }
if (userRole.value === 'ceo') {
const [kpiRes, alertRes, sumRes, predRes] = await Promise.all([
dashboardApi.kpis({ role: 'ceo', ...params }),
alertApi.list({ limit: 10 }),
alertApi.list({ limit: 10, entity_id: entityId }),
dashboardApi.summary({ role: 'ceo', ...params }),
dashboardApi.predict({}).catch(() => ({ data: [] })),
dashboardApi.predict({ entity_id: entityId }).catch(() => ({ data: [] })),
])
kpis.value = (kpiRes as any).data || []
alerts.value = (alertRes as any).data?.filter((a: any) => a.status === 'pending') || []
@@ -422,11 +423,11 @@ async function loadData() {
predictions.value = (predRes as any)?.predictions || []
dataRange.value = (kpiRes as any).range?.start + ' ~ ' + (kpiRes as any).range?.end
} else if (userRole.value === 'business') {
const r: any = await dashboardApi.myKpis?.(params)
const r: any = await dashboardApi.myKpis?.({ ...params })
myKpis.value = (r as any)?.data || []
dataRange.value = (r as any)?.period || ''
} else if (userRole.value === 'finance') {
const r: any = await dashboardApi.financeAnalysis?.(params)
const r: any = await dashboardApi.financeAnalysis?.({ ...params })
finKpis.value = (r as any)?.kpis || []
finSummary.value = (r as any)?.summary || {}
dataRange.value = (r as any)?.period || ''
+1 -1
View File
@@ -289,7 +289,7 @@ function onRowClick(row: any) {
async function loadReport() {
loading.value = true
try {
const params: any = { hierarchical: true }
const params: any = { hierarchical: true, entity_id: Number(localStorage.getItem('cma_entity_id') || 1) }
if (filterYear.value) params.year = filterYear.value
if (filterMonth.value) params.month = filterMonth.value
if (filterDimension.value) params.dimension = filterDimension.value
+2 -2
View File
@@ -374,8 +374,8 @@ const searchStatus = ref('active')
//
const entities = ref<any[]>([])
const currentEntityId = ref(1)
const currentEntityName = ref('酣客')
const currentEntityId = ref(Number(localStorage.getItem('cma_entity_id') || 1))
const currentEntityName = ref(localStorage.getItem('cma_entity_name') || '酣客')
//
const categoryTree = ref<any[]>([])
+11 -7
View File
@@ -374,6 +374,10 @@ const loading = ref(false)
const activeTab = ref('profit')
const reportPeriod = ref('2026-06')
function getEntityId() {
return Number(localStorage.getItem('cma_entity_id') || 1)
}
// API
const api = axios.create({ baseURL: '/api/cma', timeout: 30000 })
api.interceptors.request.use((config: any) => {
@@ -411,7 +415,7 @@ async function loadNew30() {
loadingNew30.value = true
dualComparisonData.value = null
try {
const params: any = { period: reportPeriod.value }
const params: any = { period: reportPeriod.value, entity_id: getEntityId() }
if (new30Format.value === 'dual') {
params.format = 'dual'
} else {
@@ -497,7 +501,7 @@ const restatementNetProfit = ref<any>(null)
async function loadRestatement() {
loadingRestatement.value = true
try {
const r = await api.get('/reports/restatement', { params: { period: reportPeriod.value } })
const r = await api.get('/reports/restatement', { params: { period: reportPeriod.value, entity_id: getEntityId() } })
const d = (r as any).data || {}
restatementItems.value = d.items || []
restatementSummary.value = d.summary || null
@@ -549,14 +553,14 @@ function onPeriodChange() {
//
async function loadProfit() {
try {
const r = await api.get('/reports/profit-summary', { params: { period: reportPeriod.value } })
const r = await api.get('/reports/profit-summary', { params: { period: reportPeriod.value, entity_id: getEntityId() } })
profitItems.value = (r as any).data?.items || []
} catch { profitItems.value = [] }
}
async function loadBudget() {
try {
const params: any = { period: reportPeriod.value }
const params: any = { period: reportPeriod.value, entity_id: getEntityId() }
if (budgetDim.value) params.dimension = budgetDim.value
if (budgetLevel.value) params.alert_level = budgetLevel.value
const r = await api.get('/reports/budget-execution', { params })
@@ -568,7 +572,7 @@ async function loadBudget() {
async function loadTrends() {
try {
const params: any = { months: trendMonths.value }
const params: any = { months: trendMonths.value, entity_id: getEntityId() }
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 })
@@ -579,14 +583,14 @@ async function loadTrends() {
async function loadBsc() {
try {
const r = await api.get('/reports/bsc-scorecard', { params: { period: reportPeriod.value } })
const r = await api.get('/reports/bsc-scorecard', { params: { period: reportPeriod.value, entity_id: getEntityId() } })
bscData.value = (r as any).data || {}
} catch { bscData.value = {} }
}
async function loadKpiOptions() {
try {
const r = await api.get('/kpis', { params: { page_size: 100 } })
const r = await api.get('/kpis', { params: { page_size: 100, entity_id: getEntityId() } })
kpiOptions.value = (r as any).data?.data || []
} catch {}
}