web/src/components/statistics/over/Revenue.vue

523 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="container">
<div class="revenue">
<Panel title="营收概况">
<template #tools>
<div class="time">
<div class="time_box"
v-for="(item,index) in dataSelector"
:key="index"
:class="{active:curDate?.name==item.name}"
@click="changeCheckDate(item)"
>
<span :style="{color:curDate?.name==item.name?'#000':''}">{{ item.name }}</span>
</div>
<el-date-picker
v-model="selectDate"
type="daterange"
@change="changeDate"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
style="margin-left: 24px"
/>
</div>
</template>
<div class="revenue-content">
<div class="revenue-content-item">
<div class="revenue-content-item-text">
<div class="num">{{ data.totalRevenue || 0 }}<span class="unit">元</span></div>
<div class="name">营业收入</div>
<div class="text">日均:¥{{ (data.totalRevenue / averageNum).toFixed(2) || 0 }}</div>
</div>
<img class="image" src="/static/images/overView/1.png" alt="">
</div>
<div class="revenue-content-item">
<div class="revenue-content-item-text">
<div class="num">{{ data.totalOrderCount || 0 }}<span class="unit color1">人</span></div>
<div class="name color1">消费人次</div>
<div class="text">日均:¥{{ (data.totalOrderCount / averageNum).toFixed(0) || 0 }}</div>
</div>
<img class="image" src="/static/images/overView/2.png" alt="">
</div>
<div class="revenue-content-item">
<div class="revenue-content-item-text">
<div class="num">{{ data.vipRevenue || 0 }}<span class="unit color2">元</span></div>
<div class="name color2">患者贡献收入</div>
<div class="text">日均:¥{{ (data.vipRevenue / averageNum).toFixed(2) || 0 }}</div>
<div class="text"><span>本金¥0.00&nbsp;&nbsp;赠金¥0.00</span></div>
</div>
<img class="image" src="/static/images/overView/3.png" alt="">
</div>
<div class="revenue-content-item">
<div class="revenue-content-item-text">
<div class="num">{{ data.vipOrderCount || 0 }}<span class="unit color3">人</span></div>
<div class="name color3">患者消费人次</div>
<div class="text">日均:¥{{ (data.vipOrderCount / averageNum).toFixed(0) || 0 }}</div>
</div>
<img class="image" src="/static/images/overView/4.png" alt="">
</div>
</div>
</Panel>
</div>
<div class="classification">
<div class="cost">
<Panel title="费用分类">
<div v-if="data.goodsTypeRevenue.length" ref="centerRef" class="center_item_content"
style="width: 100%;height: 100%"></div>
<el-empty v-else description="暂无数据"/>
</Panel>
</div>
<div class="payment">
<Panel title="支付分类">
<div v-if="data.payTypeRevenue.length" ref="centerItemRef" style="width: 100%;height: 100%">
</div>
<el-empty v-else description="暂无数据"/>
</Panel>
</div>
</div>
<div class="statistics-chart">
<div class="business-map">
<Panel title="营业收入趋势">
<div v-if="data.payTypeRevenue.length" ref="chartRef" style="width: 100%;height: 100%"></div>
<el-empty v-else description="暂无数据"/>
</Panel>
</div>
<div class="consumption-map">
<Panel title="消费人次趋势">
<div v-if="data.payTypeRevenue.length" ref="mainRef" style="width: 100%;height: 100%"></div>
<el-empty v-else description="暂无数据"/>
</Panel>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {ref, onMounted} from 'vue'
import * as echarts from 'echarts';
import {post} from "@/utils/request.ts";
import Panel from "@/components/common/Panel.vue";
import {getYesterday, getToday, getTomorrow, getThisWeek, getThisMonth, formatDateArray} from "@/utils/dateUtils.ts"
const changeNum = ref(0)
const data = ref<any>({
goodsTypeRevenue: [0], // 商品类型收入
payTypeRevenue: [0], // 支付类型收入
totalOrderCount: 0, // 总订单数
totalRevenue: null, // 总收入
vipOrderCount: 0, // 患者订单数
vipRevenue: null // 患者收入
})
const beginTime = ref('')
const endTime = ref('')
const timeList = ref([])
const averageNum = ref(1)
const dataSelector = [
{name: '今天', func: getToday()},
{name: '昨天', func: getYesterday()},
{name: '本周', func: getThisWeek()},
{name: '本月', func: getThisMonth()},
]
const changeCheck = (index: number) => {
changeNum.value = index
timeList.value = []
const now = new Date();
const year = now.getFullYear();
let month = String(now.getMonth() + 1).padStart(2, '0'); // 月份从0开始需要加1
const day = String(now.getDate()).padStart(2, '0'); // 获取当前日
if (index == 0) {
const day1 = String(now.getDate() + 1).padStart(2, '0'); // 获取当前日
beginTime.value = `${year}-${month}-${day}`;
endTime.value = `${year}-${month}-${day1}`
}
if (index == 1) {
const yesterday = new Date(now);
yesterday.setDate(now.getDate() - 1);// 设置日期到昨天
const yesterday_year = yesterday.getFullYear();
const yesterday_month = String(yesterday.getMonth() + 1).padStart(2, '0'); // 月份从 0 开始,需要加 1
const yesterday_day = String(yesterday.getDate()).padStart(2, '0');
beginTime.value = `${yesterday_year}-${yesterday_month}-${yesterday_day}`;
endTime.value = `${year}-${month}-${day}`
}
if (index == 2) {
const dayOfWeek = now.getDay(); // 获取今天是周几0 表示周日1 表示周一,以此类推
const diff = now.getDate() - dayOfWeek + (dayOfWeek === 0 ? -6 : 1); // 计算本周一的日期
const startOfWeek = new Date(now.setDate(diff));// 设置日期到本周一
const beginYear = startOfWeek.getFullYear();
const beginMonth = String(startOfWeek.getMonth() + 1).padStart(2, '0'); // 月份从0开始需要加1
const beginDay = String(startOfWeek.getDate()).padStart(2, '0'); // 获取当前日
beginTime.value = `${beginYear}-${beginMonth}-${beginDay}`;
endTime.value = `${year}-${month}-${day}`;
}
if (index == 3) {
// 本月的第一天
let startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
const startYear = startOfMonth.getFullYear();
const startMonth = String(startOfMonth.getMonth() + 1).padStart(2, '0'); // 月份从0开始需要加1
const startDay = String(startOfMonth.getDate()).padStart(2, '0');
beginTime.value = `${startYear}-${startMonth}-${startDay}`;
endTime.value = `${year}-${month}-${day}`;
}
initChart();
}
const formatDate = (dateString: any) => {
const date = new Date(dateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从0开始需要加1
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
const changeTime = () => {
beginTime.value = formatDate(timeList.value[0])
endTime.value = formatDate(timeList.value[1])
initChart()
}
// 创建 ref 引用
const centerRef = ref<HTMLElement | null>(null)
const centerItemRef = ref<HTMLElement | null>(null)
const chartRef = ref<HTMLElement | null>(null)
const mainRef = ref<HTMLElement | null>(null)
onMounted(() => {
initChart()
})
const getDateRange = () => {
let beginTime = ''
let endTime = ''
if (curDate.value == null) {
let dateArray = selectDate.value
beginTime = dateArray[0]
endTime = dateArray[1]
} else {
const date = curDate.value?.func
beginTime = date.start
endTime = date.end
}
return {begin: beginTime, end: endTime}
}
const initChart = () => {
const date = getDateRange()
post('statistics/getRevenueOverview', {
beginTime: date.begin,
endTime: date.end
}).then((res: any) => {
data.value = res
// 初始化 ECharts 实例
if (data.value.goodsTypeRevenue.length) {
if (centerRef.value) {
const myChart = echarts.init(centerRef.value);
myChart.setOption(
{
tooltip: {
trigger: 'item'
},
legend: {
left: '80%',
orient: "vertical",
},
series: [
{
name: '费用分类',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: false,
fontSize: 30,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{value: data.value.goodsTypeRevenue[0]?.totalRevenue, name: data.value.goodsTypeRevenue[0]?.name},
{value: data.value.goodsTypeRevenue[1]?.totalRevenue, name: data.value.goodsTypeRevenue[1]?.name},
{value: data.value.goodsTypeRevenue[2]?.totalRevenue, name: data.value.goodsTypeRevenue[2]?.name},
{value: data.value.goodsTypeRevenue[3]?.totalRevenue, name: data.value.goodsTypeRevenue[3]?.name},
]
}
]
}
)
}
}
if (data.value.payTypeRevenue.length) {
if (centerItemRef.value) {
const myChart = echarts.init(centerItemRef.value);
myChart.setOption(
{
tooltip: {
trigger: 'item'
},
legend: {
left: '80%',
orient: "vertical",
},
series: [
{
name: '支付方式',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: false,
fontSize: 30,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{value: data.value.payTypeRevenue[0]?.totalRevenue, name: data.value.payTypeRevenue[0]?.name},
{value: data.value.payTypeRevenue[1]?.totalRevenue, name: data.value.payTypeRevenue[1]?.name},
{value: data.value.payTypeRevenue[2]?.totalRevenue, name: data.value.payTypeRevenue[2]?.name},
{value: data.value.payTypeRevenue[3]?.totalRevenue, name: data.value.payTypeRevenue[3]?.name},
{value: data.value.payTypeRevenue[4]?.totalRevenue, name: data.value.payTypeRevenue[4]?.name},
]
}
]
}
)
}
}
if (chartRef.value) {
const myChart = echarts.init(chartRef.value);
myChart.setOption(
{
title: {
text: ''
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['患者', '普通',]
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: true,
data: [
'2024-04-01',
'2024-03-30',
'2024-03-26',
'2024-03-22',
'2024-03-18',
'2024-03-14',
'2024-03-10',
'2024-03-06',
'2024-03-02',
]
},
yAxis: {
type: 'value'
},
series: [
{
name: '患者',
type: 'line',
stack: 'Total',
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '普通',
type: 'line',
stack: 'Total',
data: [220, 182, 191, 234, 290, 330, 310]
}
]
}
);
}
if (mainRef.value) {
const myChart = echarts.init(mainRef.value);
myChart.setOption({
title: {
text: ''
},
tooltip: {},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: {},
series: [
{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}
]
});
}
})
}
const disabledDate = (time: Date) => {
return time.getTime() > Date.now()
}
const selectDate = ref();
const curDate = ref<any>(dataSelector[0])
const changeDate = (date: any) => {
curDate.value = null
selectDate.value = formatDateArray(date)
initChart()
}
const changeCheckDate = (dateItem: any) => {
curDate.value = dateItem
selectDate.value = null
initChart()
}
</script>
<style scoped lang="scss">
.time {
display: flex;
align-items: center;
.time_box {
padding: 0 5px;
height: 30px;
border: 1px solid #d7d9da;
color: #d7d9da;
font-size: 15px;
display: flex;
align-items: center;
}
}
.container {
display: flex;
flex-direction: column;
.revenue {
height: 276px;
background: #fff;
padding-bottom: 24px;
&-content {
height: 100%;
display: flex;
padding: 0 24px;
&-item {
flex: 1;
background: #F0F4FD;
margin-right: 8px;
border-radius: 30px;
display: flex;
justify-content: space-between;
&:nth-child(2) {
background: #E5F9FF;
}
&:nth-child(3) {
background: #FFF5EC;
}
&:last-child {
margin-right: 0;
background: #FFEEEE;
}
&-text {
margin: 18px 0 0 34px;
.num {
font-weight: bold;
font-size: 40px;
color: #333333;
font-style: normal;
.unit {
font-weight: bold;
font-size: 16px;
color: #4D6DE4;
font-style: normal;
}
}
.name {
font-weight: bold;
font-size: 18px;
color: #4D6DE4;
font-style: normal;
margin: 8px 0;
}
.text {
font-weight: 500;
font-size: 14px;
color: #999999;
font-style: normal;
margin-bottom: 8px;
}
}
.image {
width: 62px;
height: 58px;
margin: 48px 48px 0 0;
}
}
}
}
.classification {
height: 396px;
margin: 24px 0;
display: flex;
.cost {
flex: 1;
margin-right: 24px;
}
.payment {
flex: 1;
}
}
.statistics-chart {
height: 382px;
display: flex;
.business-map{
flex: 1;
margin-right: 24px;
}
.consumption-map{
flex: 1;
}
}
}
.color1 {
color: #26C8B1 !important;
}
.color2 {
color: #F69C51 !important;
}
.color3 {
color: #FF8686 !important;
}
</style>