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

663 lines
18 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>{{ 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.socialRevenue || 0 }}<span class="unit color2">元</span></div>
<div class="name color2">医保收入</div>
<div class="text">日均:¥{{ (data.socialRevenue / 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.socialOrderCount || 0 }}<span class="unit color3">人</span></div>
<div class="name color3">医保消费人次</div>
<div class="text">日均:{{ (data.socialOrderCount / 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="营业收入趋势">
<template #tools>
<div class="time">
<div class="time_box"
v-for="(item,index) in weekAndMonth"
:key="index"
:class="{active:curIncomeDate?.name==item.name}"
@click="changeIncomeDate(item)"
>
<span>{{ item.name }}</span>
</div>
<el-date-picker
v-model="selectIncomeDate"
type="daterange"
@change="changeSelectIncomeDate"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
style="margin-left: 24px"
/>
</div>
</template>
<div v-if="data.payTypeRevenue.length" id="incomeChart" ref="chartRef" style="width: 100%;height: 100%"></div>
<el-empty v-else description="暂无数据"/>
</Panel>
</div>
<div class="consumption-map">
<Panel title="消费人次趋势">
<template #tools>
<div class="time">
<div class="time_box"
v-for="(item,index) in weekAndMonth"
:key="index"
:class="{active:curCountDate?.name==item.name}"
@click="changeCountDate(item)"
>
<span>{{ item.name }}</span>
</div>
<el-date-picker
v-model="selectCountDate"
type="daterange"
@change="changeSelectCountDate"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
style="margin-left: 24px"
/>
</div>
</template>
<div v-if="data.payTypeRevenue.length" id="countChart" 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,
getEndOfDay
} 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 weekAndMonth = [
{name: '本周', func: getThisWeek()},
{name: '本月', func: getThisMonth()},
]
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}`;
};
// 创建 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()
initIncomeChart()
initCountChart()
})
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
}
if (beginTime == endTime) {
endTime = getEndOfDay(endTime)
}
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: incomeDateList.value
},
yAxis: {
type: 'value'
},
series: [
{
name: '普通',
type: 'line',
stack: 'Total',
data: incomeCommonData.value
}
]
}
);
}
if (mainRef.value) {
const myChart = echarts.init(mainRef.value);
myChart.setOption({
title: {
text: ''
},
tooltip: {},
xAxis: {
data: countDateList.value
},
yAxis: {},
series: [
{
name: '销量',
type: 'bar',
data: countDataList.value
}
]
});
}
})
}
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()
}
const incomeDateList = ref([
'2024-04-01',
'2024-03-30',
'2024-03-26',
'2024-03-22',
'2024-03-18',
])
const incomeCommonData = ref([220, 182, 191, 234, 290, 330, 310]);
const initIncomeChart = () => {
let date = {start: null, end: null}
if (curIncomeDate.value) {
date = curIncomeDate.value.func
}
if (selectIncomeDate.value) {
date.start = selectIncomeDate.value[0]
date.end = selectIncomeDate.value[1]
}
post("statistics/getPersonPayOverview", {beginTime: date.start, endTime: date.end}).then((res: any) => {
incomeDateList.value = res.dateList
incomeCommonData.value = res.commonPrice
const newChart = echarts.init(document.getElementById('incomeChart'));
newChart.setOption({
xAxis: {
type: 'category',
boundaryGap: true,
data: incomeDateList.value
},
yAxis: {
type: 'value'
},
series: [
{
name: '普通',
type: 'line',
stack: 'Total',
data: incomeCommonData.value
}
]
});
})
}
const curIncomeDate = ref<any>(weekAndMonth[0]);
const selectIncomeDate = ref();
const changeIncomeDate = (dateItem: any) => {
curIncomeDate.value = dateItem
selectIncomeDate.value = null
initIncomeChart()
}
const changeSelectIncomeDate = (date: any) => {
curIncomeDate.value = null
selectIncomeDate.value = date
let temp = formatDateArray(date)
selectIncomeDate.value[0] = temp[0]
selectIncomeDate.value[1] = getEndOfDay(temp[1])
initIncomeChart()
}
const curCountDate = ref<any>(weekAndMonth[0]);
const selectCountDate = ref();
const changeCountDate = (dateItem: any) => {
curCountDate.value = dateItem
selectCountDate.value = null
initCountChart()
}
const countDateList = ref([
'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',
])
const countDataList = ref([5, 20, 36, 10, 10, 20])
const changeSelectCountDate = (date: any) => {
curCountDate.value = null
selectCountDate.value = date
let temp = formatDateArray(date)
selectCountDate.value[0] = temp[0]
selectCountDate.value[1] = getEndOfDay(temp[1])
initCountChart()
}
const initCountChart = () => {
let date = {start: null, end: null}
if (curCountDate.value) {
date = curCountDate.value.func
}
if (selectCountDate.value) {
date.start = selectCountDate.value[0]
date.end = selectCountDate.value[1]
}
post("statistics/getSalesVolumeOverview", {beginTime: date.start, endTime: date.end}).then((res: any) => {
countDateList.value = res.dateList
countDataList.value = res.countList
const newChart = echarts.init(document.getElementById('countChart'));
newChart.setOption({
xAxis: {
data: countDateList.value
},
yAxis: {
},
series: [
{
name: '销量',
type: 'bar',
data: countDataList.value
}
]
});
})
}
</script>
<style scoped lang="scss">
.time {
display: flex;
align-items: center;
.time_box {
padding: 0 5px;
height: 30px;
border: 1px solid #d7d9da;
color: #000;
font-size: 15px;
display: flex;
align-items: center;
cursor: pointer;
&:hover {
color: #fff;
background: #409EFF;
}
}
}
.active {
color: #fff !important;
background: #409EFF;
}
.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>