feat(ui): 第二轮P1 — scoped样式/mc-dialog暗黑适配/空状态/字号变量

This commit is contained in:
Hermes CI Fix
2026-07-17 16:55:09 +08:00
parent c82bd5b40f
commit c68bdd1e1d
12 changed files with 159 additions and 54 deletions
+28
View File
@@ -83,6 +83,13 @@ html, body, #app {
--bsc-customer: #409EFF;
--bsc-process: #67C23A;
--bsc-learning: #E6A23C;
/* 统一字号 */
--text-sm: 12px;
--text-base: 14px;
--text-lg: 16px;
--text-xl: 18px;
--text-2xl: 24px;
}
/* 暗黑模式覆盖 — 只影响内容区,侧边栏保持不变 */
@@ -259,6 +266,27 @@ html.dark .el-dialog__body {
color: var(--bh-text-regular);
}
/* 自定义弹窗暗黑适配 */
html.dark .mc-dialog-overlay,
html.dark .mc-dialog-overlay-fix {
background: rgba(0,0,0,0.7);
}
html.dark .mc-dialog-box {
background: #1e1e1e;
color: #e0e0e0;
}
html.dark .mc-dialog-header {
border-bottom-color: #333;
}
html.dark .mc-dialog-footer {
border-top-color: #333;
}
html.dark .mc-input {
background: #252525;
border-color: #333;
color: #e0e0e0;
}
/* 自定义弹窗内下拉选择框层级修复 */
.dialog-select-popper {
z-index: 10001 !important;
+20 -8
View File
@@ -14,16 +14,16 @@
<el-row :gutter="16">
<el-col v-for="tpl in templates" :key="tpl.id" :span="24 / templates.length || 1">
<el-card shadow="hover" :body-style="{ padding: '16px', cursor: 'pointer' }" @click="useTemplate(tpl)">
<div style="text-align:center;">
<div style="font-size:32px;margin-bottom:8px;">
<div class="tpl-card-body">
<div class="tpl-icon">
<span v-if="tpl.report_type === 'overview'">📊</span>
<span v-else-if="tpl.report_type === 'trend'">📈</span>
<span v-else-if="tpl.report_type === 'comparison'">📋</span>
<span v-else-if="tpl.report_type === 'topn'">🔝</span>
<span v-else>🔗</span>
</div>
<div style="font-weight:600;">{{ tpl.name }}</div>
<div style="font-size:12px;color:#999;margin-top:4px;">{{ tpl.config?.description || '' }}</div>
<div class="tpl-name">{{ tpl.name }}</div>
<div class="tpl-desc">{{ tpl.config?.description || '' }}</div>
</div>
</el-card>
</el-col>
@@ -33,7 +33,7 @@
<!-- 分析引擎 -->
<el-card v-if="analysisResult" shadow="never" class="section-gap">
<template #header>
<div style="display:flex;justify-content:space-between;align-items:center;">
<div class="card-header-flex">
<span>{{ currentReportName }}</span>
<div>
<el-button size="small" @click="showSaveDialog = true">保存报表</el-button>
@@ -44,10 +44,10 @@
</template>
<!-- 图表区域 -->
<v-chart :option="analysisChartOption" autoresize style="height:350px;width:100%;" />
<v-chart :option="analysisChartOption" autoresize class="bi-chart" />
<!-- 数据表格 -->
<el-table :data="analysisResult.rows" size="small" border style="width:100%;margin-top:12px;" max-height="400">
<el-table :data="analysisResult.rows" size="small" border class="bi-data-table" max-height="400">
<el-table-column prop="kpi_code" label="编码" width="100" />
<el-table-column prop="kpi_name" label="KPI名称" min-width="150" />
<el-table-column prop="dimension" label="维度" width="80">
@@ -77,6 +77,7 @@
</template>
</el-table-column>
</el-table>
<el-empty v-if="!loading && reports.length === 0" description="暂无已保存报表" />
</el-card>
<!-- 模板选择对话框 -->
@@ -110,7 +111,7 @@
</el-checkbox-group>
</el-form-item>
<el-form-item label="图表类型">
<el-select v-model="chartType" style="width:100%">
<el-select v-model="chartType" class="full-width-select">
<el-option label="自动" value="auto" />
<el-option label="柱状图" value="bar" />
<el-option label="折线图" value="line" />
@@ -312,3 +313,14 @@ function exportData() {
onMounted(() => { loadData() })
</script>
<style scoped>
.card-header-flex { display: flex; justify-content: space-between; align-items: center; }
.tpl-card-body { text-align: center; }
.tpl-icon { font-size: 32px; margin-bottom: 8px; }
.tpl-name { font-weight: 600; }
.tpl-desc { font-size: 12px; color: #999; margin-top: 4px; }
.bi-chart { height: 350px; width: 100%; }
.bi-data-table { width: 100%; margin-top: 12px; }
.full-width-select { width: 100%; }
</style>
+17 -8
View File
@@ -1,12 +1,12 @@
<template>
<div style="padding: 30px; max-width: 1000px; margin: 0 auto;">
<h3 style="margin-bottom: 20px;">📊 图表组件测试</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 24px;">
<div class="chart-test-page">
<h3>📊 图表组件测试</h3>
<div class="chart-grid">
<!-- 子弹图测试 -->
<el-card shadow="never">
<template #header><span>🔥 实际 vs 目标子弹图</span></template>
<div style="display: flex; flex-direction: column; gap: 24px;">
<div class="chart-list">
<BulletChart actual="1280000" target="1500000" label="销售额" unit="元" />
<BulletChart actual="85" target="100" label="预算执行率" unit="%" />
<BulletChart actual="309393" target="500000" label="净利润" unit="元" />
@@ -16,7 +16,7 @@
<!-- 仪表盘测试 -->
<el-card shadow="never">
<template #header><span>🎯 达标率仪表盘</span></template>
<div style="display: flex; flex-direction: column; gap: 24px;">
<div class="chart-list">
<GaugeChart actual="95" target="100" label="销售额达标率" unit="%" />
<GaugeChart actual="62" target="100" label="毛利率达标率" unit="%" />
<GaugeChart actual="38" target="100" label="净利润达标率" unit="%" />
@@ -25,9 +25,9 @@
</div>
<!-- 瀑布图测试 -->
<el-card shadow="never" style="margin-top: 24px;">
<el-card shadow="never" class="waterfall-section">
<template #header><span>🌊 瀑布图 利润拆解</span></template>
<div style="height: 300px;">
<div class="chart-container">
<WaterfallChart :data="profitData" totalUnit="元" />
</div>
</el-card>
@@ -47,3 +47,12 @@ const profitData = [
{ name: '净利润', value: 160000 },
]
</script>
<style scoped>
.chart-test-page { padding: 30px; max-width: 1000px; margin: 0 auto; }
.chart-test-page h3 { margin-bottom: 20px; }
.chart-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
.chart-list { display: flex; flex-direction: column; gap: 24px; }
.waterfall-section { margin-top: 24px; }
.chart-container { height: 300px; }
</style>
+2
View File
@@ -34,6 +34,8 @@
</div>
<div v-else ref="trendChartRef" style="width:100%;height:320px;"></div>
</el-card>
<el-empty v-if="!trendLoading && trendData.length === 0" description="暂无趋势数据" />
</div>
</template>
+19 -8
View File
@@ -2,32 +2,32 @@
<div>
<h3>数据管理</h3>
<el-tabs v-model="activeTab" style="margin-top:16px;">
<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 style="color:#999;font-size:12px;">支持 .xlsx .xls自动识别利润表/现金流量表/资产负债表/KPI数据表</div>
<div class="upload-hint">支持 .xlsx .xls自动识别利润表/现金流量表/资产负债表/KPI数据表</div>
</el-upload>
<div v-if="files.length > 0" style="margin-top:8px;">
<el-tag v-for="(f, i) in files" :key="i" closable @close="removeFile(i)" style="margin:2px;">{{ f.name }}</el-tag>
<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" style="margin-top:12px;" :loading="uploading" @click="doImport">🤖 智能导入 {{ files.length }} 个文件</el-button>
<div v-if="result" style="margin-top:12px;white-space:pre-wrap;"><el-alert :title="result" type="success" show-icon /></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 style="display:flex;justify-content:space-between;align-items:center;">
<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" style="width:100%">
<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 }">
@@ -53,6 +53,7 @@
</template>
</el-table-column>
</el-table>
<el-empty v-if="!sourcesLoading && sources.length === 0" description="暂无数据源,请点击「新增数据源」添加" />
</el-card>
</el-tab-pane>
</el-tabs>
@@ -189,3 +190,13 @@ async function doDeleteSource(row: any) {
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>
+21 -12
View File
@@ -12,33 +12,33 @@
<el-row :gutter="16" class="section-gap">
<el-col :span="6">
<el-card shadow="never">
<div style="text-align:center;">
<div style="font-size:28px;font-weight:600;color:#409eff;">{{ stats.total_kpis }}</div>
<div style="font-size:13px;color:#999;">KPI总数</div>
<div class="stat-card-inner">
<div class="stat-value" style="color:#409eff;">{{ stats.total_kpis }}</div>
<div class="stat-label">KPI总数</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="never">
<div style="text-align:center;">
<div style="font-size:28px;font-weight:600;color:var(--bsc-learning);">{{ stats.open_logs }}</div>
<div style="font-size:13px;color:#999;">未解决异常</div>
<div class="stat-card-inner">
<div class="stat-value" style="color:var(--bsc-learning);">{{ stats.open_logs }}</div>
<div class="stat-label">未解决异常</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="never">
<div style="text-align:center;">
<div style="font-size:28px;font-weight:600;color:#f56c6c;">{{ stats.severity_counts?.critical ?? 0 }}</div>
<div style="font-size:13px;color:#999;">严重异常</div>
<div class="stat-card-inner">
<div class="stat-value" style="color:#f56c6c;">{{ stats.severity_counts?.critical ?? 0 }}</div>
<div class="stat-label">严重异常</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="never">
<div style="text-align:center;">
<div style="font-size:28px;font-weight:600;color:#67c23a;">{{ stats.type_counts?.missing_data ?? 0 }}</div>
<div style="font-size:13px;color:#999;">数据缺失</div>
<div class="stat-card-inner">
<div class="stat-value" style="color:#67c23a;">{{ stats.type_counts?.missing_data ?? 0 }}</div>
<div class="stat-label">数据缺失</div>
</div>
</el-card>
</el-col>
@@ -113,6 +113,7 @@
</template>
</el-table-column>
</el-table>
<el-empty v-if="!loading && logs.length === 0" description="暂无质量异常日志" />
</el-card>
</div>
</template>
@@ -189,3 +190,11 @@ onMounted(() => {
loadLogs()
})
</script>
<style scoped>
.quality-page { }
.stat-card-inner { text-align: center; }
.stat-card-inner .stat-value { font-size: 28px; font-weight: 600; }
.stat-card-inner .stat-label { font-size: 13px; color: #999; }
.section-gap-tabs { margin-top: 16px; }
</style>
+20 -9
View File
@@ -6,10 +6,10 @@
</div>
<el-card v-if="kpi" style="margin-top:16px;">
<template #header>
<span style="font-size:16px;font-weight:600;">{{ kpi.kpi_name }}</span>
<el-tag size="small" style="margin-left:8px;">{{ kpi.kpi_code }}</el-tag>
<el-tag v-if="kpi.dimension" :type="dimTagType(kpi.dimension)" size="small" style="margin-left:4px;">{{ dimLabel(kpi.dimension) }}</el-tag>
<el-tag v-if="kpi.category" type="info" size="small" style="margin-left:4px;">{{ catLabel(kpi.category) }}</el-tag>
<span class="header-title">{{ kpi.kpi_name }}</span>
<el-tag size="small" class="header-tag">{{ kpi.kpi_code }}</el-tag>
<el-tag v-if="kpi.dimension" :type="dimTagType(kpi.dimension)" size="small" class="header-tag-gap">{{ dimLabel(kpi.dimension) }}</el-tag>
<el-tag v-if="kpi.category" type="info" size="small" class="header-tag-gap">{{ catLabel(kpi.category) }}</el-tag>
</template>
<el-tabs v-model="activeTab">
@@ -82,8 +82,8 @@
</el-tab-pane>
<el-tab-pane label="历史数据" name="history">
<v-chart :option="chartOption" autoresize style="height:300px;width:100%;" />
<el-table :data="values" size="small" style="width:100%;margin-top:12px;">
<v-chart :option="chartOption" autoresize class="full-width-chart" />
<el-table :data="values" size="small" class="data-table">
<el-table-column prop="period" label="期间" width="120" />
<el-table-column prop="actual_value" label="实际值" width="120" />
<el-table-column prop="source_type" label="来源" width="80" />
@@ -99,7 +99,7 @@
<el-col :span="12">
<el-card shadow="never">
<template #header><b>上游驱动</b>影响本KPI的因子</template>
<div v-if="upstream.length === 0" style="color:#999;text-align:center;padding:20px;">无上游驱动</div>
<div v-if="upstream.length === 0" class="network-empty">无上游驱动</div>
<el-timeline v-else>
<el-timeline-item v-for="item in upstream" :key="item.kpi_id"
:color="item.direction === 'positive' ? 'var(--bsc-process)' : 'var(--bsc-finance)'"
@@ -116,7 +116,7 @@
<el-col :span="12">
<el-card shadow="never">
<template #header><b>下游影响</b>本KPI影响的指标</template>
<div v-if="downstream.length === 0" style="color:#999;text-align:center;padding:20px;">无下游影响</div>
<div v-if="downstream.length === 0" class="network-empty">无下游影响</div>
<el-timeline v-else>
<el-timeline-item v-for="item in downstream" :key="item.kpi_id"
:color="item.direction === 'positive' ? 'var(--bsc-process)' : 'var(--bsc-finance)'"
@@ -196,7 +196,7 @@
<el-dialog v-model="showFullNetwork" title="全局因果网络图" :width="900" top="5vh">
<div v-loading="fullNetworkLoading" style="height:550px;">
<v-chart v-if="fullNetworkNodes.length > 0" :option="fullNetworkChartOption" autoresize style="height:550px;width:100%;" />
<div v-else style="text-align:center;padding:40px;color:#999;">加载中...</div>
<div v-else class="full-network-loading">加载中...</div>
</div>
<template #footer>
<el-button @click="showFullNetwork = false">关闭</el-button>
@@ -534,3 +534,14 @@ async function associateMap() {
function onMapChange(val: any) {}
</script>
<style scoped>
.kpi-detail-page { }
.kpi-detail-page .header-card .header-title { font-size: 16px; font-weight: 600; }
.kpi-detail-page .header-card .header-tag { margin-left: 8px; }
.kpi-detail-page .header-card .header-tag-gap { margin-left: 4px; }
.full-width-chart { height: 300px; width: 100%; }
.data-table { width: 100%; margin-top: 12px; }
.network-empty { color: #999; text-align: center; padding: 20px; }
.full-network-loading { text-align: center; padding: 40px; color: #999; }
</style>
+2
View File
@@ -130,6 +130,8 @@
</el-table-column>
</el-table>
<el-empty v-if="!loading && kpis.length === 0" description="暂无KPI数据请新建或搜索其他条件" />
<!-- 分页 -->
<div class="pagination-bar">
<span class="total-info">共 {{ total }} 条</span>
+5
View File
@@ -45,3 +45,8 @@ const CAPITAL_CATEGORIES = [
{ key: '组织资本', label: '组织资本', icon: '🏛️', subtitle: '战略认知度 / 员工满意度 / 跨部门协作评分', color: '#E6A23C' },
]
</script>
<style scoped>
/* LearningDashboard — 内嵌KPIListView,页面级间距控制 */
.learning-dashboard { padding: 0; }
</style>
+2
View File
@@ -125,6 +125,8 @@
</div>
</template>
<el-empty v-if="!loading && !summary && selectedMap" description="暂无战略回顾数据" />
<!-- KPI详情弹出 -->
<el-dialog v-model="showKpiDialog" :title="selectedObjName" width="600">
<div v-for="k in selectedKpis" :key="k.kpi_code" class="kpi-detail-row" :class="'kpi-' + k.level">
+14 -6
View File
@@ -1,6 +1,6 @@
<template>
<div>
<div style="display:flex;justify-content:space-between;align-items:center;">
<div class="notification-page">
<div class="page-top">
<h3>通知配置</h3>
<el-button type="primary" @click="showAddDialog = true">添加渠道</el-button>
</div>
@@ -17,10 +17,10 @@
</el-table-column>
<el-table-column label="配置摘要" min-width="200">
<template #default="{ row }">
<span v-if="row.channel_type === 'wecom'" style="font-size:12px;color:#999;">
<span v-if="row.channel_type === 'wecom'" class="config-summary">
Webhook: {{ (row.config?.webhook_url || '').substring(0, 40) }}...
</span>
<span v-else-if="row.channel_type === 'mail'" style="font-size:12px;color:#999;">
<span v-else-if="row.channel_type === 'mail'" class="config-summary">
收件人: {{ (row.config?.to || []).join(', ') }}
</span>
</template>
@@ -40,8 +40,8 @@
<el-empty v-if="!loading && channels.length === 0" description="暂无通知渠道,请添加" />
<!-- 通知日志 -->
<h3 style="margin-top:30px;">发送记录</h3>
<!-- 发送记录 -->
<h3 class="section-title">发送记录</h3>
<el-table :data="logs" style="width:100%;margin-top:8px;" size="small">
<el-table-column prop="created_at" label="时间" width="160" />
<el-table-column prop="title" label="标题" min-width="200" />
@@ -199,3 +199,11 @@ async function deleteChannel(row: any) {
onMounted(() => { loadChannels(); loadLogs() })
</script>
<style scoped>
.notification-page { }
.notification-page .page-top { display: flex; justify-content: space-between; align-items: center; }
.notification-page .page-top h3 { margin: 0; }
.notification-page .section-title { margin-top: 30px; }
.config-summary { font-size: 12px; color: #999; }
</style>
+9 -3
View File
@@ -1,10 +1,10 @@
<template>
<div>
<div style="display:flex;justify-content:space-between;margin-bottom:16px;">
<div class="user-page">
<div class="page-top">
<h3>用户管理</h3>
<el-button type="primary" @click="openForm(null)">新建用户</el-button>
</div>
<el-table :data="users" v-loading="loading" style="width:100%" border stripe>
<el-table :data="users" v-loading="loading" class="full-width-table" border stripe>
<el-table-column prop="id" label="ID" width="60" />
<el-table-column prop="username" label="用户名" width="120" />
<el-table-column prop="name" label="姓名" width="120" />
@@ -129,3 +129,9 @@ async function handleDelete(row: any) {
onMounted(load)
</script>
<style scoped>
.user-page { }
.user-page .page-top { display: flex; justify-content: space-between; margin-bottom: 16px; }
.user-page .page-top h3 { margin: 0; }
</style>