工作台第一版

This commit is contained in:
xiaocp2009 2025-07-23 13:21:04 +08:00
parent bd0d2dd6c9
commit 89cb192ded
15 changed files with 990 additions and 67 deletions

View File

@ -0,0 +1,28 @@
import { request } from '@/service/request';
/** 获取营销用户卡片数据 */
export function getMktCardData(startDate: string,endDate: string) {
return request<null>({
url: '/common/statistics/getMktCardData',
method: 'get',
params: {startDate,endDate}
});
}
/** 获取营销用户line数据 */
export function getMktLineData(startDate: string,endDate: string) {
return request<null>({
url: '/common/statistics/getMktLineData',
method: 'get',
params: {startDate,endDate}
});
}
/** 获取营销用户pie数据 */
export function getMktPieData(startDate: string,endDate: string) {
return request<null>({
url: '/common/statistics/getMktPieData',
method: 'get',
params: {startDate,endDate}
});
}

View File

@ -0,0 +1,8 @@
export interface MktCardData {
key: string;
title: string;
value: number;
unit: string;
color: CardColor;
icon: string;
}

View File

@ -1,37 +1,44 @@
/**
* namespace Mps
* Namespace Api
*
* backend api module: "Mps"
* All backend api type
*/
namespace Mps {
declare namespace Api {
/**
* namespace Mps
*
* backend api module: "Mps"
*/
namespace Mps {
/** private test3 */
type PrivateTest3 = Common.CommonRecord<{
/** 主键 */
dataId: CommonType.IdType;
/** 营销人员营销号 */
userId: CommonType.IdType;
/** 客户身份证号 */
custId: CommonType.IdType;
/** 客户姓名 */
custName: string;
/** 客户账号/卡号 */
custAcctNo: string;
/** 客户联系电话 */
custPhoneNo: string;
/** 核对标志0未核对 1核对通过 9核对失败 */
checkFlag: string;
/** 核对时间 */
checkTime: string;
/** 核对人员(人工核对时) */
checkUser: string;
/** 核对方式0系统 1人工 2其他1 3其他2 4其他3 */
checkType: string;
/** 核对结果 */
checkMsg: string;
/** 租户编号 */
tenantId: CommonType.IdType;
/** 删除标志0代表存在 1代表删除 */
delFlag: string;
/** 主键 */
dataId: CommonType.IdType;
/** 营销人员营销号 */
userId: CommonType.IdType;
/** 客户身份证号 */
custId: CommonType.IdType;
/** 客户姓名 */
custName: string;
/** 客户账号/卡号 */
custAcctNo: string;
/** 客户联系电话 */
custPhoneNo: string;
/** 核对标志0未核对 1核对通过 9核对失败 */
checkFlag: string;
/** 核对时间 */
checkTime: string;
/** 核对人员(人工核对时) */
checkUser: string;
/** 核对方式0系统 1人工 2其他1 3其他2 4其他3 */
checkType: string;
/** 核对结果 */
checkMsg: string;
/** 租户编号 */
tenantId: CommonType.IdType;
/** 删除标志0代表存在 1代表删除 */
delFlag: string;
}>;
/** private test3 search params */
@ -49,7 +56,7 @@ namespace Mps {
| 'checkType'
| 'checkMsg'
> &
Api.Common.CommonSearchParams
Api.Common.CommonSearchParams
>;
/** private test3 operate params */
@ -72,4 +79,5 @@ namespace Mps {
/** private test3 list */
type PrivateTest3List = Api.Common.PaginatingQueryRecord<PrivateTest3>;
}
}

View File

@ -1,58 +1,102 @@
<script setup lang="ts">
import { computed } from 'vue';
import { computed, onMounted, ref, watch } from 'vue';
import { useAppStore } from '@/store/modules/app';
import HeaderBanner from './modules/header-banner.vue';
import CardData from './modules/card-data.vue';
import CardDataMkt from './modules/card-data-mkt.vue';
import LineChart from './modules/line-chart.vue';
import LineChartMkt from './modules/line-chart-mkt.vue';
import PieChart from './modules/pie-chart.vue';
import PieChartMkt from './modules/pie-chart-mkt.vue';
import ProjectNews from './modules/project-news.vue';
import CreativityBanner from './modules/creativity-banner.vue';
import { useAuthStore } from "@/store/modules/auth";
import dayjs from 'dayjs';
const appStore = useAppStore();
const { userInfo } = useAuthStore();
const gap = computed(() => (appStore.isMobile ? 0 : 16));
//
const dateRange = ref<[number, number]>([
dayjs().startOf('month').valueOf(), //
dayjs().endOf('month').valueOf() //
]);
//
const handleDateChange = (range: [number, number]) => {
dateRange.value = range;
};
//
const setDateRange = (type: 'today' | 'week' | 'month') => {
let start, end;
switch (type) {
case 'today':
start = dayjs().startOf('day');
end = dayjs().endOf('day');
break;
case 'week':
start = dayjs().startOf('week');
end = dayjs().endOf('week');
break;
case 'month':
start = dayjs().startOf('month');
end = dayjs().endOf('month');
break;
default:
start = dayjs().startOf('day');
end = dayjs().endOf('day');
}
dateRange.value = [start.valueOf(), end.valueOf()];
};
//
onMounted(() => {
console.log(JSON.stringify(userInfo));
console.log("是否营销:" + userInfo.user.userCategory);
console.log("营销编号:" + userInfo.user.mktNo);
});
</script>
<template>
<NSpace vertical :size="16">
<!-- <NAlert title="如遇问题请先看这里" type="warning">
<div class="text-18px">
<div>
开发前请先查看 ReadMe.md 文件中的
<NA
href="https://gitee.com/xlsea/ruoyi-plus-soybean#%E5%BC%80%E5%8F%91%E5%89%8D%E5%BF%85%E7%9C%8B"
target="_blank"
rel="noopener noreferrer"
>
开发前必看
</NA>
</div>
<div>如遇菜单无法点击请检查是否已替换菜单 SQL</div>
<div>如遇代码无法生成请检查是否已替换代码生成模板</div>
</div>
</NAlert>-->
<!-- 欢迎banner -->
<HeaderBanner />
<CardData />
<!-- 数据卡 - 营销人员和其他人员看到的图表不同 -->
<CardDataMkt
v-if="userInfo.user.userCategory === '0'"
:date-range="dateRange"
@date-change="handleDateChange"
@quick-change="setDateRange"
/>
<CardData v-else />
<!-- 线图bar图 -->
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:14">
<NCard :bordered="false" class="card-wrapper">
<LineChart />
<LineChartMkt
v-if="userInfo.user.userCategory === '0'"
:date-range="dateRange"
/>
<LineChart v-else />
</NCard>
</NGi>
<NGi span="24 s:24 m:10">
<NCard :bordered="false" class="card-wrapper">
<PieChart />
<PieChartMkt
v-if="userInfo.user.userCategory === '0'"
:date-range="dateRange"
/>
<PieChart v-else />
</NCard>
</NGi>
</NGrid>
<!-- <NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:14">
<ProjectNews />
</NGi>
<NGi span="24 s:24 m:10">
<CreativityBanner />
</NGi>
</NGrid>-->
</NSpace>
</template>

View File

@ -0,0 +1,299 @@
<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import { createReusableTemplate } from '@vueuse/core';
import { getMktCardData } from '@/service/api/statistics/statistics';
import dayjs from 'dayjs';
import type { MktCardData } from '@/typings/Statistics'; //
defineOptions({
name: 'CardDataMkt'
});
type DateRange = [number, number];
const props = defineProps<{
dateRange: DateRange;
}>();
const emit = defineEmits(['date-change', 'quick-change']);
//
const cardTemplate: Omit<MktCardData, 'value'>[] = [
{
key: 'mpsCount',
title: '总计件量',
unit: '',
color: {
start: '#ec4786',
end: '#b955a4'
},
icon: 'ant-design:bar-chart-outlined'
},
{
key: 'mpsAmt',
title: '总计价额',
unit: '¥',
color: {
start: '#865ec0',
end: '#5144b4'
},
icon: 'ant-design:money-collect-outlined'
},
{
key: 'busiCount',
title: '产品种类',
unit: '',
color: {
start: '#56cdf3',
end: '#719de3'
},
icon: 'carbon:document-download'
},
{
key: 'customerCount',
title: '客户数量',
unit: '',
color: {
start: '#fcbc25',
end: '#f68057'
},
icon: 'ant-design:trademark-circle-outlined'
}
];
const cardDataList = ref<MktCardData[]>([]);
const loading = ref(false);
// API
const fetchCardData = async () => {
try {
loading.value = true;
//
const startDate = dayjs(props.dateRange[0]).format('YYYY-MM-DD');
const endDate = dayjs(props.dateRange[1]).format('YYYY-MM-DD');
//
const { error, data } = await getMktCardData(startDate, endDate);
if (error) {
throw new Error('Failed to fetch card data');
}
// API
return cardTemplate.map(item => {
const apiValue = data[item.key as keyof typeof data];
return {
...item,
value: item.key === 'mpsAmt'
? parseFloat(apiValue) //
: Number(apiValue) //
} as MktCardData;
});
} catch (error) {
console.error('数据获取失败:', error);
// 0退
return cardTemplate.map(item => ({
...item,
value: 0
})) as MktCardData[];
} finally {
loading.value = false;
}
};
//
const queryData = async () => {
cardDataList.value = await fetchCardData();
};
//
watch(() => props.dateRange, queryData, { immediate: true });
const handleDatePickerChange = (range: DateRange | null) => {
if (range && range.length === 2) {
emit('date-change', range);
}
};
const handleQuickChange = (type: 'today' | 'week' | 'month') => {
emit('quick-change', type);
};
const formattedDateRange = computed(() => {
if (!props.dateRange || props.dateRange.length !== 2) return '';
const format = 'YYYY-MM-DD';
const start = dayjs(props.dateRange[0]).format(format);
const end = dayjs(props.dateRange[1]).format(format);
return start === end ? `${start}` : `${start}${end}`;
});
interface GradientBgProps {
gradientColor: string;
}
const [DefineGradientBg, GradientBg] = createReusableTemplate<GradientBgProps>();
function getGradientColor(color: MktCardData['color']) {
return `linear-gradient(to bottom right, ${color.start}, ${color.end})`;
}
</script>
<template>
<NCard :bordered="false" size="small" class="card-wrapper">
<!-- 顶部工具栏 -->
<div class="date-selector-wrapper">
<div class="date-range-display">
<div class="title-container">
<span class="title">营销数据概览</span>
<span class="value">{{ formattedDateRange }} </span>
</div>
</div>
<div class="date-controls">
<!-- 快速日期选项 -->
<div class="quick-date-buttons">
<NButton
size="small"
:type="dateRange[0] === dayjs().startOf('day').valueOf() ? 'primary' : 'default'"
@click="handleQuickChange('today')"
>
本日
</NButton>
<NButton
size="small"
:type="dateRange[0] === dayjs().startOf('week').valueOf() ? 'primary' : 'default'"
@click="handleQuickChange('week')"
>
本周
</NButton>
<NButton
size="small"
:type="dateRange[0] === dayjs().startOf('month').valueOf() ? 'primary' : 'default'"
@click="handleQuickChange('month')"
>
本月
</NButton>
</div>
<!-- 日期选择器 -->
<div class="date-picker-group">
<NDatePicker
:value="dateRange"
type="daterange"
clearable
@update:value="handleDatePickerChange"
class="date-picker"
/>
</div>
</div>
</div>
<DefineGradientBg v-slot="{ $slots, gradientColor }">
<div class="rd-8px px-16px pb-4px pt-8px text-white" :style="{ backgroundImage: gradientColor }">
<component :is="$slots.default" />
</div>
</DefineGradientBg>
<NGrid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
<NGi v-for="item in cardDataList" :key="item.key">
<GradientBg :gradient-color="getGradientColor(item.color)" class="flex-1">
<h3 class="text-16px">{{ item.title }}</h3>
<div class="flex justify-between pt-12px">
<SvgIcon :icon="item.icon" class="text-32px" />
<CountTo
:prefix="item.unit"
:start-value="0"
:end-value="item.value"
:decimals="item.key === 'mpsAmt' ? 2 : 0"
class="text-30px text-white dark:text-dark"
/>
</div>
</GradientBg>
</NGi>
</NGrid>
<!-- 加载状态 -->
<!-- <NSpin :show="loading" size="large" />-->
</NCard>
</template>
<style scoped>
/* 日期选择器容器 - 与卡片风格统一 */
.date-selector-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
margin-bottom: 16px;
border-bottom: 1px dashed #eaecef;
}
/* 标题容器 */
.title-container {
display: flex;
align-items: center;
gap: 8px;
}
/* 标题样式 */
.title {
font-size: 16px;
font-weight: 600;
color: #1d2129;
letter-spacing: 0.5px;
}
/* 日期显示区域 */
.date-range-display {
display: flex;
flex-direction: column;
gap: 4px;
}
.date-range-display .value {
color: #1890ff;
font-weight: 500;
font-size: 13px;
margin-left: 26px;
}
/* 日期控制区域 */
.date-controls {
display: flex;
align-items: center;
gap: 16px;
}
/* 快速日期按钮组 */
.quick-date-buttons {
display: flex;
gap: 8px;
}
/* 日期选择器组 */
.date-picker-group {
display: flex;
align-items: center;
gap: 8px;
}
.date-picker {
width: 280px;
}
/* 卡片样式 */
.card-wrapper {
padding: 16px;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
}
/* 当前选中的快速按钮样式 */
.n-button--primary-type {
background-color: #1890ff;
color: white;
border-color: #1890ff;
}
</style>

View File

@ -22,7 +22,7 @@ interface CardData {
const cardData = computed<CardData[]>(() => [
{
key: 'visitCount',
title: $t('page.home.visitCount'),
title: '总计件量',
value: 9725,
unit: '',
color: {
@ -33,9 +33,9 @@ const cardData = computed<CardData[]>(() => [
},
{
key: 'turnover',
title: $t('page.home.turnover'),
value: 1026,
unit: '$',
title: '总计价额',
value: 1026.12,
unit: '¥',
color: {
start: '#865ec0',
end: '#5144b4'
@ -44,8 +44,8 @@ const cardData = computed<CardData[]>(() => [
},
{
key: 'downloadCount',
title: $t('page.home.downloadCount'),
value: 970925,
title: '产品种类',
value: 16,
unit: '',
color: {
start: '#56cdf3',
@ -55,8 +55,8 @@ const cardData = computed<CardData[]>(() => [
},
{
key: 'dealCount',
title: $t('page.home.dealCount'),
value: 9527,
title: '客户数量',
value: 66,
unit: '',
color: {
start: '#fcbc25',
@ -97,6 +97,7 @@ function getGradientColor(color: CardData['color']) {
:prefix="item.unit"
:start-value="1"
:end-value="item.value"
:decimals="item.key === 'turnover' ? 2 : 0"
class="text-30px text-white dark:text-dark"
/>
</div>

View File

@ -0,0 +1,179 @@
<script setup lang="ts">
import { watch } from 'vue';
import { useAppStore } from '@/store/modules/app';
import { useEcharts } from '@/hooks/common/echarts';
import { $t } from '@/locales';
import dayjs from 'dayjs';
import { getMktLineData } from '@/service/api/statistics/statistics'; // 线API
defineOptions({
name: 'LineChartMkt'
});
const props = defineProps<{
dateRange: [number, number];
}>();
const appStore = useAppStore();
// ECharts
const { domRef, updateOptions } = useEcharts(() => ({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
legend: {
data: ['同比', '环比']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: [] as string[]
},
yAxis: {
type: 'value'
},
series: [
{
color: '#8e9dff',
name: '同比',
type: 'line',
smooth: true,
stack: 'Total',
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0.25,
color: '#8e9dff'
},
{
offset: 1,
color: '#fff'
}
]
}
},
emphasis: {
focus: 'series'
},
data: [] as number[]
},
{
color: '#26deca',
name: '环比',
type: 'line',
smooth: true,
stack: 'Total',
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0.25,
color: '#26deca'
},
{
offset: 1,
color: '#fff'
}
]
}
},
emphasis: {
focus: 'series'
},
data: [] as number[]
}
]
}));
// API线
async function fetchLineData() {
try {
//
const startDate = dayjs(props.dateRange[0]).format('YYYY-MM-DD');
const endDate = dayjs(props.dateRange[1]).format('YYYY-MM-DD');
// API
const { error, data } = await getMktLineData(startDate, endDate);
if (error) {
console.error('获取线图数据失败:', error);
return;
}
//
updateOptions(opts => {
// X
opts.xAxis.data = data.dates.map(date =>
dayjs(date).format('MM/DD')
);
//
opts.series[0].data = data.yoyData;
//
opts.series[1].data = data.momData;
return opts;
});
} catch (err) {
console.error('获取线图数据时出错:', err);
}
}
//
function updateLocale() {
updateOptions((opts, factory) => {
const originOpts = factory();
opts.legend.data = originOpts.legend.data;
opts.series[0].name = originOpts.series[0].name;
opts.series[1].name = originOpts.series[1].name;
return opts;
});
}
//
watch(() => props.dateRange, () => {
fetchLineData();
}, { immediate: true });
//
watch(
() => appStore.locale,
() => {
updateLocale();
}
);
</script>
<template>
<NCard :bordered="false" class="card-wrapper">
<div ref="domRef" class="h-360px overflow-hidden"></div>
</NCard>
</template>
<style scoped></style>

View File

@ -0,0 +1,122 @@
<script setup lang="ts">
import { watch } from 'vue';
import { useAppStore } from '@/store/modules/app';
import { useEcharts } from '@/hooks/common/echarts';
import { $t } from '@/locales';
import dayjs from 'dayjs';
import { getMktPieData } from '@/service/api/statistics/statistics'; // API
defineOptions({
name: 'PieChartMkt'
});
const props = defineProps<{
dateRange: [number, number];
}>();
const appStore = useAppStore();
// ECharts
const { domRef, updateOptions } = useEcharts(() => ({
tooltip: {
trigger: 'item'
},
legend: {
bottom: '1%',
left: 'center',
itemStyle: {
borderWidth: 0
}
},
series: [
{
color: ['#5da8ff', '#8e9dff', '#fedc69', '#26deca'],
name: '业务类别',
type: 'pie',
radius: ['45%', '75%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 1
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '12'
}
},
labelLine: {
show: false
},
data: [] as { name: string; value: number }[]
}
]
}));
// API
async function fetchPieData() {
try {
//
const startDate = dayjs(props.dateRange[0]).format('YYYY-MM-DD');
const endDate = dayjs(props.dateRange[1]).format('YYYY-MM-DD');
// API
const { error, data } = await getMktPieData(startDate, endDate);
if (error) {
console.error('获取饼图数据失败:', error);
return;
}
//
updateOptions(opts => {
opts.series[0].data = data.map(item => ({
name: item.name,
value: item.value
}));
return opts;
});
} catch (err) {
console.error('获取饼图数据时出错:', err);
}
}
//
function updateLocale() {
updateOptions((opts, factory) => {
const originOpts = factory();
//
opts.series[0].name = originOpts.series[0].name;
// fetchPieData
return opts;
});
}
//
watch(() => props.dateRange, () => {
fetchPieData();
}, { immediate: true });
//
watch(
() => appStore.locale,
() => {
updateLocale();
}
);
</script>
<template>
<NCard :bordered="false" class="card-wrapper">
<div ref="domRef" class="h-360px overflow-hidden"></div>
</NCard>
</template>
<style scoped></style>

View File

@ -105,7 +105,7 @@ spring.data:
# 数据库索引
database: 0
# redis 密码必须配置
# password: 123456
password: 123456
# 连接超时时间
timeout: 10s
# 是否开启ssl

View File

@ -21,6 +21,11 @@ import java.util.List;
*/
public interface MpsPrivateEbankNewMapper extends BaseMapperPlus<MpsPrivateEbankNew, MpsPrivateEbankNewVo> {
/*@DataPermission({
@DataColumn(key = "deptName", value = "create_dept"),
@DataColumn(key = "userName", value = "create_by")
})*/
@DataPermission({
@DataColumn(key = "deptName", value = "create_dept"),
@DataColumn(key = "userName", value = "create_by")

View File

@ -0,0 +1,132 @@
package org.dromara.statistics.controller;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.statistics.domain.vo.MktCardVo;
import org.dromara.statistics.domain.vo.MktLineVo;
import org.dromara.statistics.domain.vo.MktPieVo;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* 统计分析控制器
*
* 数据 按需根据业务表自行计算,目前为mock数据
*
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/common/statistics")
public class StatisticsController {
// 日期格式化器
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("MM/dd");
private static final DateTimeFormatter FULL_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
// 营销人员卡片数据
@GetMapping("/getMktCardData")
public R<MktCardVo> getMktCardData(String startDate, String endDate) {
System.out.println(startDate + " " + endDate);
MktCardVo mktCardVo = new MktCardVo();
mktCardVo.setMpsCount(1765); // 总计件量
mktCardVo.setMpsAmt(new BigDecimal(1026.12)); // 总计价额
mktCardVo.setBusiCount(16); // 产品种类
mktCardVo.setCustomerCount(66); // 客户数量
return R.ok(mktCardVo);
}
// 营销线图数据接口
@GetMapping("/getMktLineData")
public R<MktLineVo> getMktLineData(String startDate, String endDate) {
System.out.println("获取线图数据: " + startDate + "" + endDate);
// 解析日期范围
LocalDate start = LocalDate.parse(startDate, FULL_DATE_FORMATTER);
LocalDate end = LocalDate.parse(endDate, FULL_DATE_FORMATTER);
// 生成日期列表
List<String> dates = new ArrayList<>();
List<Double> yoyData = new ArrayList<>(); // 同比数据
List<Double> momData = new ArrayList<>(); // 环比数据
// 随机数生成器
Random random = new Random();
double baseValue = 1000 + random.nextInt(2000); // 基础值范围1000-3000
// 遍历日期范围
for (LocalDate date = start; !date.isAfter(end); date = date.plusDays(1)) {
// 添加格式化日期 (MM/dd)
dates.add(date.format(DATE_FORMATTER));
// 生成模拟数据 - 基于基础值波动
double dailyBase = baseValue * (0.9 + 0.2 * random.nextDouble());
// 同比数据较去年同期的变化
double yoyValue = dailyBase * (0.8 + 0.4 * random.nextDouble());
// 环比数据较前一日的变化
double momValue = dailyBase * (0.85 + 0.3 * random.nextDouble());
yoyData.add(Math.round(yoyValue * 100.0) / 100.0);
momData.add(Math.round(momValue * 100.0) / 100.0);
// 更新基础值用于下一天
baseValue = dailyBase * (0.95 + 0.1 * random.nextDouble());
}
// 创建返回对象
MktLineVo lineVo = new MktLineVo();
lineVo.setDates(dates);
lineVo.setYoyData(yoyData);
lineVo.setMomData(momData);
return R.ok(lineVo);
}
// 营销饼图数据接口
@GetMapping("/getMktPieData")
public R<List<MktPieVo>> getPieData(String startDate, String endDate) {
System.out.println("获取饼图数据: " + startDate + "" + endDate);
// 创建模拟数据
List<MktPieVo> data = new ArrayList<>();
// 产品类型及其基础值
String[] productTypes = {"智E通", "社保类", "公积金", "代收付"};
int[] baseValues = {20, 10, 40, 30};
// 计算日期范围天数
LocalDate start = LocalDate.parse(startDate);
LocalDate end = LocalDate.parse(endDate);
long days = ChronoUnit.DAYS.between(start, end) + 1;
// 根据天数调整比例 (最大不超过2倍)
double multiplier = Math.min(1 + days / 30.0, 2.0);
// 生成模拟数据
Random random = new Random();
for (int i = 0; i < productTypes.length; i++) {
MktPieVo item = new MktPieVo();
item.setName(productTypes[i]);
// 基础值乘以倍数并添加随机波动
int value = (int) Math.round(baseValues[i] * multiplier * (0.9 + 0.2 * random.nextDouble()));
item.setValue(value);
data.add(item);
}
return R.ok(data);
}
}

View File

@ -0,0 +1,21 @@
package org.dromara.statistics.domain.bo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
@Data
public class MktCardBo {
/**
* 开始日期
*/
private String startDate;
/**
* 截止日期
*/
private String endDate;
}

View File

@ -0,0 +1,29 @@
package org.dromara.statistics.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class MktCardVo {
/**
* 总计件量
*/
private int mpsCount;
/**
* 总计价额
*/
private BigDecimal mpsAmt;
/**
* 产品种类(营销产品的种类数量)
*/
private int busiCount;
/**
* 客户数量(服务客户数量)
*/
private int customerCount;
}

View File

@ -0,0 +1,26 @@
package org.dromara.statistics.domain.vo;
import lombok.Data;
import java.util.List;
/**
* 营销线图数据VO
*/
@Data
public class MktLineVo {
/**
* 日期列表 (格式: MM/dd)
*/
private List<String> dates;
/**
* 同比数据 (与去年同期比较)
*/
private List<Double> yoyData;
/**
* 环比数据 (与前一日比较)
*/
private List<Double> momData;
}

View File

@ -0,0 +1,21 @@
package org.dromara.statistics.domain.vo;
import lombok.Data;
import java.util.List;
/**
* 营销线图数据VO
*/
@Data
public class MktPieVo {
/**
* 产品名称
*/
private String name;
/**
* 产品数量/占比值
*/
private Integer value;
}