Files
cma-management/frontend/src/views/DataManage.vue
T

203 lines
8.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div>
<h3>数据管理</h3>
<el-tabs v-model="activeTab" class="section-gap-tabs">
<el-tab-pane label="Excel导入" name="import">
<el-card>
<template #header>Excel导入 <el-tag type="success" size="small">🤖 智能识别</el-tag></template>
<el-upload drag :auto-upload="false" :on-change="handleFile" :on-remove="handleRemove" multiple accept=".xlsx,.xls">
<el-icon :size="32"><UploadFilled /></el-icon>
<div>拖拽或点击上传Excel文件</div>
<div class="upload-hint">支持 .xlsx .xls自动识别利润表/现金流量表/资产负债表/KPI数据表</div>
</el-upload>
<div v-if="files.length > 0" class="tag-list">
<el-tag v-for="(f, i) in files" :key="i" closable class="file-tag" @close="removeFile(i)">{{ f.name }}</el-tag>
</div>
<el-button v-if="files.length > 0" type="primary" class="upload-actions" :loading="uploading" @click="doImport">🤖 智能导入 {{ files.length }} 个文件</el-button>
<div v-if="result" class="result-area"><el-alert :title="result" type="success" show-icon /></div>
</el-card>
</el-tab-pane>
<el-tab-pane label="数据源管理" name="sources">
<el-card>
<template #header>
<div class="card-header-flex">
<span>数据源配置</span>
<el-button type="primary" size="small" @click="openSourceForm()">新增数据源</el-button>
</div>
</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">
<template #default="{ row }">
<el-tag :type="sourceTypeTag(row.source_type)" size="small">{{ row.source_type }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="api_endpoint" label="API地址" min-width="200" show-overflow-tooltip />
<el-table-column prop="sync_type" label="同步方式" width="90">
<template #default="{ row }">
<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">
<template #default="{ row }">
<el-tag :type="row.status==='active'?'success':'danger'">{{ row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="last_sync_at" label="上次同步" width="160" />
<el-table-column label="操作" width="150">
<template #default="{ row }">
<el-button size="small" @click="openSourceForm(row)">编辑</el-button>
<el-button size="small" type="danger" @click="doDeleteSource(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-empty v-if="!sourcesLoading && sources.length === 0" description="暂无数据源,请点击「新增数据源」添加" />
</el-card>
</el-tab-pane>
</el-tabs>
<MyDialog v-model="showSourceForm" :title="sourceForm.id ? '编辑数据源' : '新增数据源'" :width="550">
<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-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 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>
<el-form-item label="同步方式">
<el-select v-model="sourceForm.sync_type">
<el-option label="实时" value="realtime" />
<el-option label="定时批量" value="batch" />
<el-option label="手动" value="manual" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showSourceForm=false">取消</el-button>
<el-button type="primary" @click="saveSource" :loading="sourceSaving">保存</el-button>
</template>
</MyDialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { UploadFilled } from '@element-plus/icons-vue'
import { dataApi } from '../api/index'
import MyDialog from '../components/MyDialog.vue'
const activeTab = ref('import')
// === Excel导入 ===
const files = ref<any[]>([])
const uploading = ref(false)
const result = ref('')
function handleFile(f: any) {
if (f.raw && !files.value.some(ex => ex.name === f.raw.name && ex.size === f.raw.size)) {
files.value.push(f.raw)
}
}
function handleRemove(f: any) {
files.value = files.value.filter(ex => !(ex.name === f.name && ex.size === f.size))
}
function removeFile(i: number) {
files.value.splice(i, 1)
}
async function doImport() {
if (files.value.length === 0) return
uploading.value = true
result.value = ''
const msgs: string[] = []
for (const f of files.value) {
try {
const r: any = await dataApi.importExcelSmart(f)
const msg = r.message || ''
msgs.push(`📄 ${f.name}: ${msg}`)
} catch (e: any) {
msgs.push(`❌ ${f.name}: ${e?.response?.data?.detail || '导入失败'}`)
}
}
files.value = []
result.value = msgs.join('\n')
uploading.value = false
}
// === 数据源管理 ===
const sources = ref<any[]>([])
const sourcesLoading = ref(false)
const showSourceForm = ref(false)
const sourceSaving = ref(false)
const sourceForm = ref<any>({ source_type: 'manual', sync_type: 'manual' })
function sourceTypeTag(t: string) {
return ({ erp: 'primary', business: 'warning', excel: 'success', manual: 'info' } as any)[t] || 'info'
}
async function loadSources() {
sourcesLoading.value = true
try {
const r: any = await dataApi.listSources()
sources.value = r.data || []
} catch (e) { ElMessage.error('加载数据源失败') }
sourcesLoading.value = false
}
function openSourceForm(row?: any) {
if (row) {
sourceForm.value = { ...row }
} else {
sourceForm.value = { source_type: 'manual', sync_type: 'manual' }
}
showSourceForm.value = true
}
async function saveSource() {
sourceSaving.value = true
try {
if (sourceForm.value.id) {
await dataApi.updateSource(sourceForm.value.id, sourceForm.value)
ElMessage.success('已更新')
} else {
await dataApi.createSource(sourceForm.value)
ElMessage.success('已创建')
}
showSourceForm.value = false
loadSources()
} catch (e) { ElMessage.error('保存失败') }
sourceSaving.value = false
}
async function doDeleteSource(row: any) {
try {
await ElMessageBox.confirm(`确认删除数据源「${row.name}」?`, '确认')
await dataApi.deleteSource(row.id)
ElMessage.success('已删除')
loadSources()
} catch (e: any) {
if (e !== 'cancel') ElMessage.error('删除失败')
}
}
onMounted(loadSources)
</script>
<style scoped>
.data-page { }
.data-page .card-header-flex { display: flex; justify-content: space-between; align-items: center; }
.upload-hint { color: #999; font-size: 12px; }
.tag-list { margin-top: 8px; }
.tag-list .file-tag { margin: 2px; }
.upload-actions { margin-top: 12px; }
.result-area { margin-top: 12px; white-space: pre-wrap; }
</style>