523 lines
16 KiB
Vue
523 lines
16 KiB
Vue
<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 赠金:¥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>
|