工作台第一版
This commit is contained in:
parent
bd0d2dd6c9
commit
89cb192ded
28
cds-fontend-2025.V1/src/service/api/statistics/statistics.ts
Normal file
28
cds-fontend-2025.V1/src/service/api/statistics/statistics.ts
Normal 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}
|
||||
});
|
||||
}
|
8
cds-fontend-2025.V1/src/typings/Statistics.ts
Normal file
8
cds-fontend-2025.V1/src/typings/Statistics.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export interface MktCardData {
|
||||
key: string;
|
||||
title: string;
|
||||
value: number;
|
||||
unit: string;
|
||||
color: CardColor;
|
||||
icon: string;
|
||||
}
|
@ -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>;
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
299
cds-fontend-2025.V1/src/views/home/modules/card-data-mkt.vue
Normal file
299
cds-fontend-2025.V1/src/views/home/modules/card-data-mkt.vue
Normal 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>
|
@ -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>
|
||||
|
179
cds-fontend-2025.V1/src/views/home/modules/line-chart-mkt.vue
Normal file
179
cds-fontend-2025.V1/src/views/home/modules/line-chart-mkt.vue
Normal 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>
|
122
cds-fontend-2025.V1/src/views/home/modules/pie-chart-mkt.vue
Normal file
122
cds-fontend-2025.V1/src/views/home/modules/pie-chart-mkt.vue
Normal 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>
|
@ -105,7 +105,7 @@ spring.data:
|
||||
# 数据库索引
|
||||
database: 0
|
||||
# redis 密码必须配置
|
||||
# password: 123456
|
||||
password: 123456
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
# 是否开启ssl
|
||||
|
@ -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")
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user