feat(api): 更新知识库和文档接口路径

feat(views): 新增广告监控相关页面组件
This commit is contained in:
2026-04-10 14:15:51 +08:00
parent 091a159eec
commit c80f67d2ab
7 changed files with 2475 additions and 17 deletions

View File

@@ -0,0 +1,815 @@
<template>
<div class="ads-summary-monitor">
<el-card shadow="hover">
<template #header
><div class="card-header"><span>监控总表</span></div></template
>
<div class="search-container">
<el-form :model="searchParams" :inline="true" class="search-form">
<el-form-item label="时间范围">
<el-date-picker
v-model="searchParams.dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
:shortcuts="dateShortcuts"
/>
</el-form-item>
<el-form-item label="部门"
><el-select v-model="searchParams.department" placeholder="选择部门"
><el-option v-for="option in departmentOptions" :key="option.value || 'all'" :label="option.label" :value="option.value" /></el-select
></el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</div>
<div class="chart-container">
<el-row :gutter="20">
<el-col :span="12">
<el-card>
<template #header><div class="card-header">预算执行趋势</div></template>
<div ref="trendChartRef" class="chart"></div>
</el-card>
</el-col>
<el-col :span="12">
<el-card>
<template #header><div class="card-header">部门达成率对比</div></template>
<div ref="comparisonChartRef" class="chart"></div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px">
<el-col :span="12">
<el-card>
<template #header><div class="card-header">预算执行分布</div></template>
<div ref="distributionChartRef" class="chart"></div>
</el-card>
</el-col>
<el-col :span="12">
<el-card>
<template #header><div class="card-header">月度预算执行</div></template>
<div ref="monthlyChartRef" class="chart"></div>
</el-card>
</el-col>
</el-row>
</div>
<div class="data-container">
<el-card>
<template #header><div class="card-header">关键指标</div></template>
<div class="stats-grid">
<el-card shadow="hover" class="stats-card">
<div class="stats-item">
<div class="stats-label">总预算</div>
<div class="stats-value">¥{{ totalStats.totalBudget }}</div>
</div>
</el-card>
<el-card shadow="hover" class="stats-card">
<div class="stats-item">
<div class="stats-label">已执行</div>
<div class="stats-value">¥{{ totalStats.executed }}</div>
</div>
</el-card>
<el-card shadow="hover" class="stats-card">
<div class="stats-item">
<div class="stats-label">总达成率</div>
<div class="stats-value">{{ totalStats.overallRate }}%</div>
</div>
</el-card>
<el-card shadow="hover" class="stats-card">
<div class="stats-item">
<div class="stats-label">日均执行</div>
<div class="stats-value">¥{{ totalStats.dailyAverage }}</div>
</div>
</el-card>
</div>
</el-card>
</div>
<div class="table-container">
<el-card>
<template #header><div class="card-header">部门预算执行明细</div></template>
<el-table :data="pagedData" style="width: 100%">
<el-table-column prop="department" label="部门" />
<el-table-column prop="monthlyBudget" label="月度任务" />
<el-table-column prop="executed" label="已达成" />
<el-table-column prop="achievementRate" label="达成率"
><template #default="scope">{{ scope.row.achievementRate }}%</template></el-table-column
>
<el-table-column prop="surplusBudget" label="剩余任务" />
<el-table-column prop="dailyAverage" label="剩余日均" />
<el-table-column prop="yesterday" label="昨日" />
<el-table-column prop="estimatedAchievement" label="预计达成" />
<el-table-column prop="dailyDifference" label="日均差" />
<el-table-column prop="previousDay" label="前日" />
<el-table-column prop="sequentialRatio" label="环比" />
<el-table-column prop="timeProgress" label="对比时间进度" />
<el-table-column prop="januaryAchievement" label="1月达成" />
<el-table-column prop="januarySequentialRatio" label="环比" />
<el-table-column prop="differenceFromTarget" label="目标差" />
</el-table>
<div class="pagination-container">
<el-pagination
v-model:current-page="pagination.currentPage"
v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="pagination.total"
@size-change="handlePageSizeChange"
@current-change="handlePageChange"
/>
</div>
</el-card>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue';
import * as echarts from 'echarts';
interface DepartmentData {
department: string;
monthlyBudget: string;
executed: string;
achievementRate: string;
surplusBudget: string;
surplusDays: string;
dailyAverage: string;
yesterday: string;
estimatedAchievement: string;
dailyDifference: string;
previousDay: string;
sequentialRatio: string;
timeProgress: string;
januaryAchievement: string;
januarySequentialRatio: string;
differenceFromTarget: string;
}
const departmentOptions = [
{ label: '全部', value: 'all' },
{ label: '一区一部', value: '一区一部' },
{ label: '一区二部', value: '一区二部' },
{ label: '一区三部', value: '一区三部' },
{ label: '一区四部', value: '一区四部' },
{ label: '代理运营总', value: '代理运营总' },
{ label: '渠道部', value: '渠道部' },
{ label: '电商一部', value: '电商一部' },
{ label: '电商二部', value: '电商二部' },
{ label: '张哥自营', value: '张哥自营' },
{ label: '电商合计', value: '电商合计' },
{ label: '金牛总任务', value: '金牛总任务' },
];
const searchParams = reactive({
dateRange: [],
department: '',
});
const dateShortcuts = [
{
text: '最近7天',
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
return [start, end];
},
},
{
text: '最近30天',
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
return [start, end];
},
},
{
text: '最近90天',
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
return [start, end];
},
},
];
const trendChartRef = ref();
const comparisonChartRef = ref();
const distributionChartRef = ref();
const monthlyChartRef = ref();
let trendChart: echarts.ECharts | null = null;
let comparisonChart: echarts.ECharts | null = null;
let distributionChart: echarts.ECharts | null = null;
let monthlyChart: echarts.ECharts | null = null;
const totalStats = reactive({
totalBudget: '0',
executed: '0',
overallRate: '0',
dailyAverage: '0',
});
const data = ref<DepartmentData[]>([]);
const pagination = reactive({
currentPage: 1,
pageSize: 10,
total: 0,
});
const pagedData = computed(() => {
const start = (pagination.currentPage - 1) * pagination.pageSize;
const end = start + pagination.pageSize;
return data.value.slice(start, end);
});
const getMockData = (): DepartmentData[] => {
// 根据图片中的数据生成模拟数据
const departments = [
{
name: '一区一部',
monthlyBudget: '1300000',
executed: '937947.12',
achievementRate: '72.15',
surplusBudget: '362052.88',
surplusDays: '21',
dailyAverage: '17240.61',
yesterday: '36129.91',
estimatedAchievement: '130.51%',
dailyDifference: '18889.30',
previousDay: '47003.57',
sequentialRatio: '-10873.66',
timeProgress: '42%',
januaryAchievement: '1547156.813',
januarySequentialRatio: '-16%',
differenceFromTarget: '-7%',
},
{
name: '一区二部',
monthlyBudget: '3500000',
executed: '2338291.84',
achievementRate: '66.81',
surplusBudget: '1161708.16',
surplusDays: '21',
dailyAverage: '55319.44',
yesterday: '106494.91',
estimatedAchievement: '130.71%',
dailyDifference: '51175.47',
previousDay: '105791.93',
sequentialRatio: '702.97',
timeProgress: '37%',
januaryAchievement: '3789374.035',
januarySequentialRatio: '-8%',
differenceFromTarget: '2%',
},
{
name: '一区三部',
monthlyBudget: '2100000',
executed: '1896655.41',
achievementRate: '90.32',
surplusBudget: '203344.59',
surplusDays: '21',
dailyAverage: '9683.07',
yesterday: '55613.03',
estimatedAchievement: '145.93%',
dailyDifference: '45928.43',
previousDay: '53994.08',
sequentialRatio: '1618.95',
timeProgress: '60%',
januaryAchievement: '2861643.583',
januarySequentialRatio: '-27%',
differenceFromTarget: '-19%',
},
{
name: '一区四部',
monthlyBudget: '1600000',
executed: '1960555.94',
achievementRate: '122.53',
surplusBudget: '-360555.94',
surplusDays: '21',
dailyAverage: '-17169.33',
yesterday: '66458.76',
estimatedAchievement: '209.76%',
dailyDifference: '83628.09',
previousDay: '79558.31',
sequentialRatio: '-1309.55',
timeProgress: '93%',
januaryAchievement: '2216583.797',
januarySequentialRatio: '-28%',
differenceFromTarget: '-20%',
},
{
name: '代理运营总',
monthlyBudget: '8500000',
executed: '7134418.26',
achievementRate: '83.93',
surplusBudget: '1365581.74',
surplusDays: '21',
dailyAverage: '65027.70',
yesterday: '264596.76',
estimatedAchievement: '149.32%',
dailyDifference: '199682.28',
previousDay: '286347.89',
sequentialRatio: '-21651.29',
timeProgress: '54%',
januaryAchievement: '10414758.23',
januarySequentialRatio: '-18%',
differenceFromTarget: '-10%',
},
{
name: '渠道部',
monthlyBudget: '3000000',
executed: '29134092.68',
achievementRate: '97.11',
surplusBudget: '865907.32',
surplusDays: '21',
dailyAverage: '41233.68',
yesterday: '806116.365',
estimatedAchievement: '153.54%',
dailyDifference: '764882.68',
previousDay: '844507.14',
sequentialRatio: '-38390.78',
timeProgress: '67%',
januaryAchievement: '39078360.25',
januarySequentialRatio: '-23%',
differenceFromTarget: '-15%',
},
{
name: '电商一部',
monthlyBudget: '4700000',
executed: '3993326.756',
achievementRate: '83.88',
surplusBudget: '706673.24',
surplusDays: '21',
dailyAverage: '33651.11',
yesterday: '125785.982',
estimatedAchievement: '140.02%',
dailyDifference: '89563.45',
previousDay: '132741.39',
sequentialRatio: '-6955.41',
timeProgress: '54%',
januaryAchievement: '4522665.91',
januarySequentialRatio: '4%',
differenceFromTarget: '15%',
},
{
name: '电商二部',
monthlyBudget: '2800000',
executed: '1302679.056',
achievementRate: '46.52',
surplusBudget: '1497320.94',
surplusDays: '21',
dailyAverage: '7130.10',
yesterday: '26049.373',
estimatedAchievement: '66.06%',
dailyDifference: '-45251.62',
previousDay: '27892.62',
sequentialRatio: '-1843.24',
timeProgress: '17%',
januaryAchievement: '2776056.70',
januarySequentialRatio: '1%',
differenceFromTarget: '12%',
},
{
name: '张哥自营',
monthlyBudget: '6000000',
executed: '9770673.61',
achievementRate: '162.84',
surplusBudget: '-3770673.61',
surplusDays: '21',
dailyAverage: '-179555.89',
yesterday: '392448.463',
estimatedAchievement: '300.20%',
dailyDifference: '572004.35',
previousDay: '331376.53',
sequentialRatio: '61071.93',
timeProgress: '133%',
januaryAchievement: '8310458.83',
januarySequentialRatio: '-28%',
differenceFromTarget: '-20%',
},
{
name: '电商合计',
monthlyBudget: '13500000',
executed: '15012801.94',
achievementRate: '111.21',
surplusBudget: '-1512801.94',
surplusDays: '21',
dailyAverage: '-72038.19',
yesterday: '544283.818',
estimatedAchievement: '195.87%',
dailyDifference: '616316.17',
previousDay: '492286.58',
sequentialRatio: '5207.28',
timeProgress: '81%',
januaryAchievement: '15609181.44',
januarySequentialRatio: '-14%',
differenceFromTarget: '-4%',
},
{
name: '金牛总任务',
monthlyBudget: '52000000',
executed: '51280190.4',
achievementRate: '98.62',
surplusBudget: '719809.60',
surplusDays: '21',
dailyAverage: '34276.65',
yesterday: '1615096.78',
estimatedAchievement: '163.84%',
dailyDifference: '1622865.58',
previousDay: '1622865.58',
sequentialRatio: '-7768.79',
timeProgress: '69%',
januaryAchievement: '65108327.29',
januarySequentialRatio: '-20%',
differenceFromTarget: '-12%',
},
];
const mockData: DepartmentData[] = departments.map((dept) => ({
department: dept.name,
monthlyBudget: dept.monthlyBudget,
executed: dept.executed,
achievementRate: dept.achievementRate,
surplusBudget: dept.surplusBudget,
surplusDays: dept.surplusDays,
dailyAverage: dept.dailyAverage,
yesterday: dept.yesterday,
estimatedAchievement: dept.estimatedAchievement,
dailyDifference: dept.dailyDifference,
previousDay: dept.previousDay,
sequentialRatio: dept.sequentialRatio,
timeProgress: dept.timeProgress,
januaryAchievement: dept.januaryAchievement,
januarySequentialRatio: dept.januarySequentialRatio,
differenceFromTarget: dept.differenceFromTarget,
}));
return mockData;
};
const calculateTotalStats = (_data: DepartmentData[]) => {
// 使用图片中的总数据
totalStats.totalBudget = '52000000';
totalStats.executed = '51280190.4';
totalStats.overallRate = '98.62';
totalStats.dailyAverage = '1654199.70';
};
const initTrendChart = (_data: DepartmentData[]) => {
if (!trendChartRef.value) return;
if (trendChart) trendChart.dispose();
trendChart = echarts.init(trendChartRef.value);
// 使用图片中的2026年2月数据
const dates = [
'2026/2/1',
'2026/2/2',
'2026/2/3',
'2026/2/4',
'2026/2/5',
'2026/2/6',
'2026/2/7',
'2026/2/8',
'2026/2/9',
'2026/2/10',
'2026/2/11',
'2026/2/12',
'2026/2/13',
'2026/2/14',
'2026/2/15',
'2026/2/16',
];
const executedData = [
2149518.05, 1871119.64, 2088655.19, 1702786.8, 1770269.31, 1714822.74, 1649665.0, 1622865.58, 1615096.78, 1485551.17, 1407212.21, 1364826.0,
1233123.83, 1089680.69, 1086898.47, 816321.92,
];
const targetData = dates.map(() => 1733333.33); // 52000000 / 30
trendChart.setOption({
title: {
text: '预算执行趋势',
left: 'center',
},
tooltip: {
trigger: 'axis',
formatter: '{b}<br/>{a}: ¥{c}',
},
legend: {
data: ['实际执行', '目标'],
bottom: 0,
},
xAxis: {
type: 'category',
data: dates,
axisLabel: {
rotate: 45,
},
},
yAxis: {
type: 'value',
name: '金额',
axisLabel: {
formatter: '¥{value}',
},
},
series: [
{
name: '实际执行',
type: 'line',
data: executedData,
areaStyle: {},
},
{
name: '目标',
type: 'line',
data: targetData,
lineStyle: {
type: 'dashed',
},
},
],
});
};
const initComparisonChart = (data: DepartmentData[]) => {
if (!comparisonChartRef.value) return;
if (comparisonChart) comparisonChart.dispose();
comparisonChart = echarts.init(comparisonChartRef.value);
const departments = data.map((item) => item.department);
const achievementRates = data.map((item) => parseFloat(item.achievementRate));
comparisonChart.setOption({
title: {
text: '部门达成率对比',
left: 'center',
},
tooltip: {
trigger: 'axis',
formatter: '{b}<br/>{a}: {c}%',
},
xAxis: {
type: 'category',
data: departments,
axisLabel: {
rotate: 45,
},
},
yAxis: {
type: 'value',
name: '达成率',
axisLabel: {
formatter: '{value}%',
},
},
series: [
{
name: '达成率',
type: 'bar',
data: achievementRates,
itemStyle: {
color: function (params: any) {
const rate = params.value;
if (rate >= 80) return '#67c23a';
if (rate >= 60) return '#e6a23c';
return '#f56c6c';
},
},
},
],
});
};
const initDistributionChart = (data: DepartmentData[]) => {
if (!distributionChartRef.value) return;
if (distributionChart) distributionChart.dispose();
distributionChart = echarts.init(distributionChartRef.value);
distributionChart.setOption({
title: {
text: '预算执行分布',
left: 'center',
},
tooltip: {
trigger: 'item',
formatter: '{b}: ¥{c} ({d}%)',
},
legend: {
orient: 'vertical',
left: 'left',
data: data.map((item) => item.department),
},
series: [
{
name: '预算执行',
type: 'pie',
radius: '50%',
data: data.map((item) => ({
value: parseFloat(item.executed),
name: item.department,
})),
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
],
});
};
const initMonthlyChart = (_data: DepartmentData[]) => {
if (!monthlyChartRef.value) return;
if (monthlyChart) monthlyChart.dispose();
monthlyChart = echarts.init(monthlyChartRef.value);
const months = ['1月', '2月', '3月', '4月', '5月', '6月'];
const budgetData = months.map(() => (Math.random() * 10000000 + 5000000).toFixed(2));
const executedData = months.map((_, index) => (parseFloat(budgetData[index]) * (Math.random() * 0.3 + 0.5)).toFixed(2));
monthlyChart.setOption({
title: {
text: '月度预算执行',
left: 'center',
},
tooltip: {
trigger: 'axis',
formatter: '{b}<br/>{a}: ¥{c}',
},
legend: {
data: ['预算', '实际执行'],
bottom: 0,
},
xAxis: {
type: 'category',
data: months,
},
yAxis: {
type: 'value',
name: '金额',
axisLabel: {
formatter: '¥{value}',
},
},
series: [
{
name: '预算',
type: 'bar',
data: budgetData.map(parseFloat),
itemStyle: {
color: '#909399',
},
},
{
name: '实际执行',
type: 'bar',
data: executedData.map(parseFloat),
itemStyle: {
color: '#409eff',
},
},
],
});
};
const handleSearch = () => {
data.value = getMockData();
pagination.total = data.value.length;
calculateTotalStats(data.value);
initTrendChart(data.value);
initComparisonChart(data.value);
initDistributionChart(data.value);
initMonthlyChart(data.value);
};
const handleReset = () => {
searchParams.dateRange = [];
searchParams.department = '';
pagination.currentPage = 1;
handleSearch();
};
const handlePageSizeChange = (size: number) => {
pagination.pageSize = size;
pagination.currentPage = 1;
};
const handlePageChange = (page: number) => {
pagination.currentPage = page;
};
onMounted(() => {
handleSearch();
window.addEventListener('resize', () => {
trendChart?.resize();
comparisonChart?.resize();
distributionChart?.resize();
monthlyChart?.resize();
});
});
</script>
<style scoped>
.ads-summary-monitor {
padding: 20px;
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 16px;
font-weight: 600;
}
.search-container {
margin-bottom: 20px;
}
.search-form {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.search-form :deep(.el-form-item) {
margin-right: 12px;
margin-bottom: 12px;
}
.ads-summary-monitor :deep(.el-select) {
width: 180px;
}
.ads-summary-monitor :deep(.el-select__wrapper),
.ads-summary-monitor :deep(.el-select__selected-item),
.ads-summary-monitor :deep(.el-select__placeholder) {
color: #303133;
}
.granularity-group {
flex-wrap: wrap;
}
.chart-container {
margin-bottom: 20px;
}
.chart {
width: 100%;
height: 400px;
}
.data-container {
margin-bottom: 20px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
}
.stats-card {
text-align: center;
}
.stats-item {
padding: 10px;
}
.stats-label {
font-size: 14px;
color: #606266;
margin-bottom: 8px;
}
.stats-value {
font-size: 24px;
font-weight: 600;
color: #303133;
}
.table-container {
margin-top: 20px;
}
.pagination-container {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
</style>