663 lines
18 KiB
Vue
663 lines
18 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>{{ 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>
|
||
<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"
|
||
import {API} from "@/assets/config/API.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(API.Statistics.Base.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(API.Statistics.Base.GetPayOverview, {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(API.Statistics.Base.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: rgba(#4D6DE4,0.5);
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
.active {
|
||
color: #fff !important;
|
||
background: #4D6DE4;
|
||
}
|
||
|
||
.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>
|