fix(data): 数据管理模块脱节修复 — Excel导入/sources端点 entity 隔离 + ERP直连通道诚实化(410) + 真实zxt链路展示
- import_excel/import_excel_smart KPI字典按 entity_id 过滤(防跨账套匹配串写,P0安全) - import_excel_smart 自动建KPI编码防冲突(_next_code 循环取号) - sources list/create/update/delete 全部按 entity_id 隔离(create 写入当前账套) - sync-kpis + admin/erp-sync 下线返回410(erp-gateway DB凭据2026-08-31已删,真实通道=zxt智享通注入) - 新增 GET /data/channel-status:展示真实zxt取数通道状态+遗留ERP数据源清单(诚实化) - DataManage.vue:数据源tab顶部展示真实通道(最近注入period/时间/行数/落盘目录mtime)、ERP遗留源标注'非当前通道'、实际取数映射(kpi_value_sources)表格 - 新增 test_data_entity_isolation_20260908.py(8条:双账套导入隔离/CRUD隔离/sync-kpis 410/channel-status)
This commit is contained in:
@@ -144,6 +144,7 @@ export const dataApi = {
|
||||
createSource: (data: any) => api.post('/data/sources', data),
|
||||
updateSource: (id: number, data: any) => api.put(`/data/sources/${id}`, data),
|
||||
deleteSource: (id: number) => api.delete(`/data/sources/${id}`),
|
||||
channelStatus: () => api.get('/data/channel-status'),
|
||||
}
|
||||
|
||||
export const alertApi = {
|
||||
|
||||
@@ -20,6 +20,30 @@
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="数据源管理" name="sources">
|
||||
<!-- ⚠️ 数据链路诚实化:真实取数通道状态(2026-09-08 P0) -->
|
||||
<el-alert v-if="channelLoading" title="加载数据链路状态..." type="info" show-icon :closable="false" class="channel-alert" />
|
||||
<template v-else>
|
||||
<el-alert v-if="realChannel && realChannel.latest_inject_at" type="success" :closable="false" class="channel-alert">
|
||||
<template #title>
|
||||
✅ 真实数据通道(当前在用):{{ realChannel.channel }}
|
||||
<span class="channel-inline">最近注入:{{ realChannel.latest_period }}({{ realChannel.latest_inject_at }},共 {{ realChannel.injected_rows }} 行)</span>
|
||||
<span v-if="realChannel.landing_dir_mtime" class="channel-inline">落盘目录最新文件:{{ realChannel.landing_dir_mtime }}</span>
|
||||
</template>
|
||||
<div class="channel-desc">
|
||||
数据链路:DAMA 从智享通网页读取科目余额表 → 落盘 {{ realChannel.landing_dir }} → 财务Bot 注入 CMA(subjects + kpi_values 双写,source_type='zxt-sync')。
|
||||
该通道为陕西酣客财务原始数据的真实来源,每日/结账后由 DAMA 例行执行。
|
||||
</div>
|
||||
</el-alert>
|
||||
<el-alert v-else type="info" :closable="false" class="channel-alert" title="当前账套暂无 zxt 智享通注入数据(可能为企业数据未接入智享通通道)" />
|
||||
|
||||
<el-alert v-if="erpDirect && erpDirect.legacy_sources_count > 0" type="warning" :closable="false" class="channel-alert">
|
||||
<template #title>
|
||||
⚠️ ERP 直连同步通道已停用(2026-08-31),以下 {{ erpDirect.legacy_sources_count }} 条 ERP 数据源为<strong>遗留配置</strong>,不属于当前数据通道
|
||||
</template>
|
||||
<div class="channel-desc">{{ erpDirect.reason }}</div>
|
||||
</el-alert>
|
||||
</template>
|
||||
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header-flex">
|
||||
@@ -29,7 +53,7 @@
|
||||
</template>
|
||||
<el-table :data="sources" v-loading="sourcesLoading" class="full-width-table">
|
||||
<el-table-column prop="name" label="名称" min-width="140" />
|
||||
<el-table-column prop="source_type" label="类型" width="80">
|
||||
<el-table-column prop="source_type" label="类型" width="90">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="sourceTypeTag(row.source_type)" size="small">{{ row.source_type }}</el-tag>
|
||||
</template>
|
||||
@@ -40,9 +64,11 @@
|
||||
<el-tag size="small" :type="row.sync_type==='realtime'?'success':'info'">{{ row.sync_type }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="70">
|
||||
<el-table-column prop="status" label="状态" width="110">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status==='active'?'success':'danger'">{{ row.status }}</el-tag>
|
||||
<!-- 诚实化:ERP 遗留数据源即使 status=active 也标注"非当前通道",避免误导 -->
|
||||
<el-tag v-if="isLegacyErpSource(row)" type="warning" size="small">遗留·非当前通道</el-tag>
|
||||
<el-tag v-else :type="row.status==='active'?'success':'danger'" size="small">{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="last_sync_at" label="上次同步" width="160" />
|
||||
@@ -54,21 +80,52 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!sourcesLoading && sources.length === 0" description="暂无数据源,请点击「新增数据源」添加" />
|
||||
<div v-if="legacyErpIds.size > 0" class="legacy-note">
|
||||
💡 上表中「ERP」类型数据源为遗留演示配置:其端点指向 erp-gateway 容器(2026-08-31 起 DB 凭据删除、已停用)。
|
||||
新增数据源请选择非 ERP 类型;企业财务数据由上方 zxt 智享通通道真实供给,无需在此配置。
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 真实取数映射(采集器实际使用) -->
|
||||
<el-card v-if="valueSources.length > 0" class="vs-card">
|
||||
<template #header>
|
||||
<div class="card-header-flex">
|
||||
<span>📡 实际取数映射(采集器使用,kpi_value_sources)</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="channel-desc">06:30 定时采集(scripts/kpi_value_collector.py)按此映射写入 kpi_values(source_type='auto_collect'),与上方 ERP 遗留数据源相互独立。</div>
|
||||
<el-table :data="valueSources" class="full-width-table">
|
||||
<el-table-column prop="kpi_code" label="KPI编码" min-width="130" />
|
||||
<el-table-column prop="kpi_name" label="KPI名称" min-width="130" />
|
||||
<el-table-column prop="source_table" label="源表" min-width="140" />
|
||||
<el-table-column prop="source_field" label="源字段" min-width="120" />
|
||||
<el-table-column prop="aggregate" label="聚合" width="90" />
|
||||
<el-table-column prop="status" label="状态" width="90">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status==='active'?'success':'danger'" size="small">{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<MyDialog v-model="showSourceForm" :title="sourceForm.id ? '编辑数据源' : '新增数据源'" :width="550">
|
||||
<el-alert v-if="!sourceForm.id && sourceForm.source_type==='erp'" type="warning" :closable="false" class="form-warn"
|
||||
title="ERP 直连通道已停用(2026-08-31)。ERP 类型仅用于保留/查看遗留配置,企业财务数据由 zxt 智享通通道供给。" />
|
||||
<el-form :model="sourceForm" label-width="120px">
|
||||
<el-form-item label="名称"><el-input v-model="sourceForm.name" /></el-form-item>
|
||||
<el-form-item label="类型">
|
||||
<el-select v-model="sourceForm.source_type">
|
||||
<el-select v-model="sourceForm.source_type" @change="onSourceTypeChange">
|
||||
<el-option label="ERP" value="erp" />
|
||||
<el-option label="业务系统" value="business" />
|
||||
<el-option label="Excel" value="excel" />
|
||||
<el-option label="手工" value="manual" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="sourceForm.source_type==='erp'" label="遗留说明">
|
||||
<el-alert type="warning" :closable="false" show-icon title="该类型数据源不会执行同步(通道已停用),仅作记录" />
|
||||
</el-form-item>
|
||||
<el-form-item label="API地址"><el-input v-model="sourceForm.api_endpoint" placeholder="https://..." /></el-form-item>
|
||||
<el-form-item label="API Key"><el-input v-model="sourceForm.api_key" type="password" show-password /></el-form-item>
|
||||
<el-form-item label="SQL查询"><el-input v-model="sourceForm.query_sql" type="textarea" :rows="3" placeholder="可选,用于查询型数据源" /></el-form-item>
|
||||
@@ -92,7 +149,7 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { UploadFilled } from '@element-plus/icons-vue'
|
||||
import { dataApi } from '../api/index'
|
||||
import { dataApi, budgetApi } from '../api/index'
|
||||
import MyDialog from '../components/MyDialog.vue'
|
||||
|
||||
const activeTab = ref('import')
|
||||
@@ -132,6 +189,38 @@ async function doImport() {
|
||||
uploading.value = false
|
||||
}
|
||||
|
||||
// === 数据链路状态(2026-09-08 诚实化) ===
|
||||
const channelLoading = ref(false)
|
||||
const realChannel = ref<any>(null)
|
||||
const erpDirect = ref<any>(null)
|
||||
const legacyErpIds = ref<Set<number>>(new Set())
|
||||
const valueSources = ref<any[]>([])
|
||||
|
||||
function isLegacyErpSource(row: any): boolean {
|
||||
return row.source_type === 'erp' && legacyErpIds.value.has(Number(row.id))
|
||||
}
|
||||
|
||||
async function loadChannelStatus() {
|
||||
channelLoading.value = true
|
||||
try {
|
||||
const r: any = await dataApi.channelStatus()
|
||||
const d = r.data || r
|
||||
realChannel.value = d.real_channel || null
|
||||
erpDirect.value = d.erp_direct || null
|
||||
const ids = (d.erp_direct?.legacy_sources || []).map((s: any) => Number(s.id))
|
||||
legacyErpIds.value = new Set(ids)
|
||||
} catch (e) { /* 非阻塞:状态卡片加载失败不影响列表 */ }
|
||||
channelLoading.value = false
|
||||
}
|
||||
|
||||
async function loadValueSources() {
|
||||
try {
|
||||
const r: any = await budgetApi.valueSources({ status: 'active' })
|
||||
const d = Array.isArray(r) ? r : (r.data || [])
|
||||
valueSources.value = d
|
||||
} catch (e) { valueSources.value = [] }
|
||||
}
|
||||
|
||||
// === 数据源管理 ===
|
||||
const sources = ref<any[]>([])
|
||||
const sourcesLoading = ref(false)
|
||||
@@ -143,6 +232,11 @@ function sourceTypeTag(t: string) {
|
||||
return ({ erp: 'primary', business: 'warning', excel: 'success', manual: 'info' } as any)[t] || 'info'
|
||||
}
|
||||
|
||||
function onSourceTypeChange() {
|
||||
// 触发表单内 ERP 遗留说明显示
|
||||
sourceForm.value = { ...sourceForm.value }
|
||||
}
|
||||
|
||||
async function loadSources() {
|
||||
sourcesLoading.value = true
|
||||
try {
|
||||
@@ -173,6 +267,7 @@ async function saveSource() {
|
||||
}
|
||||
showSourceForm.value = false
|
||||
loadSources()
|
||||
loadChannelStatus()
|
||||
} catch (e) { ElMessage.error('保存失败') }
|
||||
sourceSaving.value = false
|
||||
}
|
||||
@@ -183,12 +278,17 @@ async function doDeleteSource(row: any) {
|
||||
await dataApi.deleteSource(row.id)
|
||||
ElMessage.success('已删除')
|
||||
loadSources()
|
||||
loadChannelStatus()
|
||||
} catch (e: any) {
|
||||
if (e !== 'cancel') ElMessage.error('删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadSources)
|
||||
onMounted(() => {
|
||||
loadSources()
|
||||
loadChannelStatus()
|
||||
loadValueSources()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -199,4 +299,10 @@ onMounted(loadSources)
|
||||
.tag-list .file-tag { margin: 2px; }
|
||||
.upload-actions { margin-top: 12px; }
|
||||
.result-area { margin-top: 12px; white-space: pre-wrap; }
|
||||
.channel-alert { margin-bottom: 12px; }
|
||||
.channel-inline { margin-left: 12px; color: #606266; font-weight: normal; }
|
||||
.channel-desc { margin-top: 4px; color: #606266; font-size: 12px; line-height: 1.7; }
|
||||
.vs-card { margin-top: 12px; }
|
||||
.legacy-note { margin-top: 8px; color: #b88230; font-size: 12px; line-height: 1.7; }
|
||||
.form-warn { margin-bottom: 12px; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user