196 lines
5.4 KiB
Vue
196 lines
5.4 KiB
Vue
<template>
|
||
<div class="page-container">
|
||
<div class="page-header">
|
||
<h2 class="page-title">报表统计</h2>
|
||
<el-select v-model="selectedProject" placeholder="选择项目生成报告" clearable style="width: 260px">
|
||
<el-option v-for="p in projects" :key="p.id" :label="p.name" :value="p.id" />
|
||
</el-select>
|
||
<el-button type="primary" :disabled="!selectedProject" @click="downloadReport">
|
||
<el-icon><Download /></el-icon>下载报告
|
||
</el-button>
|
||
</div>
|
||
|
||
<el-row :gutter="16" class="chart-row">
|
||
<el-col :xs="24" :md="12">
|
||
<div class="chart-card card-shadow">
|
||
<div class="chart-title">数据分级分布</div>
|
||
<v-chart class="chart" :option="levelOption" autoresize />
|
||
</div>
|
||
</el-col>
|
||
<el-col :xs="24" :md="12">
|
||
<div class="chart-card card-shadow">
|
||
<div class="chart-title">项目进度</div>
|
||
<v-chart class="chart" :option="projectOption" autoresize />
|
||
</div>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<el-row :gutter="16" class="chart-row">
|
||
<el-col :xs="24" :md="12">
|
||
<div class="chart-card card-shadow">
|
||
<div class="chart-title">识别来源占比</div>
|
||
<v-chart class="chart" :option="sourceOption" autoresize />
|
||
</div>
|
||
</el-col>
|
||
<el-col :xs="24" :md="12">
|
||
<div class="chart-card card-shadow">
|
||
<div class="chart-title">敏感数据趋势(近7天)</div>
|
||
<v-chart class="chart" :option="trendOption" autoresize />
|
||
</div>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, onMounted } from 'vue'
|
||
import { use } from 'echarts/core'
|
||
import { CanvasRenderer } from 'echarts/renderers'
|
||
import { PieChart, BarChart, LineChart } from 'echarts/charts'
|
||
import { TooltipComponent, LegendComponent, GridComponent } from 'echarts/components'
|
||
import VChart from 'vue-echarts'
|
||
import { Download } from '@element-plus/icons-vue'
|
||
import { getProjects } from '@/api/project'
|
||
|
||
use([CanvasRenderer, PieChart, BarChart, LineChart, TooltipComponent, LegendComponent, GridComponent])
|
||
|
||
const selectedProject = ref<number | undefined>(undefined)
|
||
const projects = ref<any[]>([])
|
||
|
||
const levelOption = ref({
|
||
tooltip: { trigger: 'item' },
|
||
legend: { bottom: '0%', left: 'center' },
|
||
color: ['#67c23a', '#409eff', '#e6a23c', '#f56c6c', '#909399'],
|
||
series: [
|
||
{
|
||
type: 'pie',
|
||
radius: ['40%', '70%'],
|
||
avoidLabelOverlap: false,
|
||
itemStyle: { borderRadius: 6, borderColor: '#fff', borderWidth: 2 },
|
||
label: { show: false },
|
||
data: [
|
||
{ value: 35000, name: 'L1 公开级' },
|
||
{ value: 62000, name: 'L2 内部级' },
|
||
{ value: 48000, name: 'L3 敏感级' },
|
||
{ value: 22000, name: 'L4 重要级' },
|
||
{ value: 6931, name: 'L5 核心级' },
|
||
],
|
||
},
|
||
],
|
||
})
|
||
|
||
const projectOption = ref({
|
||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||
xAxis: { type: 'category', data: ['项目A', '项目B', '项目C', '项目D'] },
|
||
yAxis: { type: 'value', max: 100 },
|
||
series: [
|
||
{
|
||
type: 'bar',
|
||
data: [68, 92, 25, 45],
|
||
itemStyle: { borderRadius: [4, 4, 0, 0], color: '#409eff' },
|
||
label: { show: true, position: 'top', formatter: '{c}%' },
|
||
},
|
||
],
|
||
})
|
||
|
||
const sourceOption = ref({
|
||
tooltip: { trigger: 'item' },
|
||
legend: { bottom: '0%', left: 'center' },
|
||
color: ['#e6a23c', '#67c23a'],
|
||
series: [
|
||
{
|
||
type: 'pie',
|
||
radius: ['40%', '70%'],
|
||
itemStyle: { borderRadius: 6, borderColor: '#fff', borderWidth: 2 },
|
||
label: { show: false },
|
||
data: [
|
||
{ value: 124500, name: '自动识别' },
|
||
{ value: 27840, name: '人工打标' },
|
||
],
|
||
},
|
||
],
|
||
})
|
||
|
||
const trendOption = ref({
|
||
tooltip: { trigger: 'axis' },
|
||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||
xAxis: { type: 'category', data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] },
|
||
yAxis: { type: 'value' },
|
||
series: [
|
||
{
|
||
type: 'line',
|
||
data: [120, 132, 101, 134, 90, 230, 210],
|
||
smooth: true,
|
||
itemStyle: { color: '#f56c6c' },
|
||
areaStyle: { color: 'rgba(245,108,108,0.1)' },
|
||
},
|
||
],
|
||
})
|
||
|
||
function downloadReport() {
|
||
if (!selectedProject.value) return
|
||
const token = localStorage.getItem('pdg_token')
|
||
const url = `/api/v1/reports/projects/${selectedProject.value}/download`
|
||
const a = document.createElement('a')
|
||
a.href = url
|
||
a.download = `report_project_${selectedProject.value}.docx`
|
||
if (token) {
|
||
a.setAttribute('data-token', token)
|
||
}
|
||
document.body.appendChild(a)
|
||
a.click()
|
||
document.body.removeChild(a)
|
||
}
|
||
|
||
async function fetchProjects() {
|
||
try {
|
||
const res: any = await getProjects({ page: 1, page_size: 100 })
|
||
projects.value = res?.data || []
|
||
} catch (e) {
|
||
// ignore
|
||
}
|
||
}
|
||
|
||
onMounted(fetchProjects)
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.page-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-bottom: 20px;
|
||
flex-wrap: wrap;
|
||
|
||
.page-title {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
margin-right: auto;
|
||
}
|
||
}
|
||
|
||
.chart-row {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.chart-card {
|
||
padding: 20px;
|
||
background: #fff;
|
||
border-radius: 8px;
|
||
|
||
.chart-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
margin-bottom: 16px;
|
||
color: #303133;
|
||
}
|
||
|
||
.chart {
|
||
width: 100%;
|
||
height: 300px;
|
||
}
|
||
}
|
||
</style>
|