feat(statistics): 新增课程报表功能
This commit is contained in:
@@ -2,33 +2,60 @@ import { requestClient } from '#/api/request';
|
|||||||
|
|
||||||
export const statisticsApi = {
|
export const statisticsApi = {
|
||||||
/**
|
/**
|
||||||
* 获取天医币报表列表
|
* 获取天医币报表
|
||||||
|
* @param data 请求参数
|
||||||
|
* @param data.month 月份
|
||||||
|
* @param data.year 年份
|
||||||
|
* @returns 天医币报表数据
|
||||||
*/
|
*/
|
||||||
getReportTianyibi: (data: { month?: string; year: number }) => {
|
getReportTianyibi: (data: { month?: string; year: number }) => {
|
||||||
return requestClient.post('common/statistics/pointStatistics', data);
|
return requestClient.post('common/statistics/pointStatistics', data);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取实物报表列表
|
* 获取实物报表
|
||||||
|
* @param data 请求参数
|
||||||
|
* @param data.month 月份
|
||||||
|
* @param data.year 年份
|
||||||
|
* @returns 实物报表数据
|
||||||
*/
|
*/
|
||||||
getPhysicalStatistics: (data: { month?: string; year: number }) => {
|
getPhysicalStatistics: (data: { month?: string; year: number }) => {
|
||||||
return requestClient.post('common/statistics/physicalStatistics', data);
|
return requestClient.post('common/statistics/physicalStatistics', data);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取培训班报表列表
|
* 获取培训班报表
|
||||||
|
* @param data 请求参数
|
||||||
|
* @param data.month 月份
|
||||||
|
* @param data.year 年份
|
||||||
|
* @returns 培训班报表数据
|
||||||
*/
|
*/
|
||||||
getTrainingClassStatistics: (data: { month?: string; year: number }) => {
|
getTrainingClassStatistics: (data: { month?: string; year: number }) => {
|
||||||
return requestClient.post('common/statistics/trainingClassStatistics', data);
|
return requestClient.post('common/statistics/trainingClassStatistics', data);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取VIP报表列表
|
* 获取VIP报表
|
||||||
|
* @param data 请求参数
|
||||||
|
* @param data.month 月份
|
||||||
|
* @param data.year 年份
|
||||||
|
* @returns VIP报表数据
|
||||||
*/
|
*/
|
||||||
getVipStatistics: (data: { month?: string; year: number }) => {
|
getVipStatistics: (data: { month?: string; year: number }) => {
|
||||||
return requestClient.post('common/statistics/vipStatistics', data);
|
return requestClient.post('common/statistics/vipStatistics', data);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取课程报表
|
||||||
|
* @param data 请求参数
|
||||||
|
* @param data.month 月份
|
||||||
|
* @param data.year 年份
|
||||||
|
* @returns 课程报表数据
|
||||||
|
*/
|
||||||
|
getCourseStatistics: (data: { month?: string; year: number }) => {
|
||||||
|
return requestClient.post('common/statistics/courseStatistics', data);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载天医币报表
|
* 下载天医币报表
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -47,6 +47,15 @@ const routes: RouteRecordRaw[] = [
|
|||||||
path: '/statistics/vip-report',
|
path: '/statistics/vip-report',
|
||||||
component: () => import('#/views/statistics/vip/report.vue'),
|
component: () => import('#/views/statistics/vip/report.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
title: '课程报表',
|
||||||
|
keepAlive: true,
|
||||||
|
},
|
||||||
|
name: 'CourseReport',
|
||||||
|
path: '/statistics/course-report',
|
||||||
|
component: () => import('#/views/statistics/course/report.vue'),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
153
apps/finance/src/views/statistics/course/report.vue
Normal file
153
apps/finance/src/views/statistics/course/report.vue
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page, Spinner } from '@vben/common-ui';
|
||||||
|
// import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
|
|
||||||
|
import { Button, Card, DatePicker, Empty } from 'ant-design-vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
import { statisticsApi } from '#/api/statistics';
|
||||||
|
|
||||||
|
const year = ref<Dayjs>(dayjs());
|
||||||
|
const disabledDate = (date: Dayjs) => date.year() > dayjs().year();
|
||||||
|
|
||||||
|
interface Report {
|
||||||
|
incomes: Record<string, number>;
|
||||||
|
notyet: number;
|
||||||
|
already: number;
|
||||||
|
now: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReportItem {
|
||||||
|
type: string;
|
||||||
|
fee: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 报表数据
|
||||||
|
const list = ref<Report[]>([]);
|
||||||
|
const loading = ref<boolean>(false);
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
list.value = [];
|
||||||
|
try {
|
||||||
|
// 计算查询年份中包含哪些月份, 若为今年则只查询到当前月份,往年则查询所有月份
|
||||||
|
const monthList =
|
||||||
|
year.value.year() === dayjs().year()
|
||||||
|
? Array.from({ length: dayjs().month() + 1 }, (_, i) => (i + 1).toString().padStart(2, '0'))
|
||||||
|
: Array.from({ length: 12 }, (_, i) => (i + 1).toString().padStart(2, '0'));
|
||||||
|
for (const month of monthList) {
|
||||||
|
const data = await statisticsApi.getCourseStatistics({
|
||||||
|
year: year.value.year(),
|
||||||
|
month,
|
||||||
|
});
|
||||||
|
list.value.push({
|
||||||
|
incomes: {
|
||||||
|
微信: 0,
|
||||||
|
支付宝: 0,
|
||||||
|
银行: 0,
|
||||||
|
天医币: 0,
|
||||||
|
...Object.fromEntries(data.incomes.map((item: ReportItem) => [item.type, item.fee])),
|
||||||
|
},
|
||||||
|
notyet: data.notyet || 0,
|
||||||
|
already: data.already || 0,
|
||||||
|
now: data.now || 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 下载报表
|
||||||
|
// const downloadReport = async (index: number) => {
|
||||||
|
// const month = index > 9 ? `${index + 1}` : `0${index + 1}`;
|
||||||
|
// const date = `${year.value.year()}-${month}`;
|
||||||
|
// const filename = `天医币报表_${year.value.year()}年${month}月_文件.xlsx`;
|
||||||
|
// const res = await statisticsApi.downloadReportTianyibi({
|
||||||
|
// date,
|
||||||
|
// });
|
||||||
|
// downloadFileFromBlobPart({
|
||||||
|
// source: res.data,
|
||||||
|
// fileName: filename,
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const downloadAllReport = () => {
|
||||||
|
// list.value.forEach((_, index) => {
|
||||||
|
// downloadReport(index);
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<div class="flex h-full flex-col rounded-md bg-white">
|
||||||
|
<div class="search-form p-4">
|
||||||
|
<DatePicker
|
||||||
|
v-model:value="year"
|
||||||
|
picker="year"
|
||||||
|
:disabled-date="disabledDate"
|
||||||
|
@change="getList"
|
||||||
|
/>
|
||||||
|
<Button type="primary" class="ml-2" @click="getList">查询</Button>
|
||||||
|
<!-- <Button type="link" class="ml-2" @click="downloadAllReport">
|
||||||
|
下载 {{ year.year() }} 年全部天医币报表
|
||||||
|
</Button> -->
|
||||||
|
</div>
|
||||||
|
<div class="h-2 bg-gray-100"></div>
|
||||||
|
<Spinner class="content flex-1 px-3 py-4" :spinning="loading">
|
||||||
|
<div
|
||||||
|
v-if="list.length === 0 && !loading"
|
||||||
|
class="col-span-3 flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<Empty />
|
||||||
|
</div>
|
||||||
|
<div v-else class="grid max-h-full grid-cols-3 gap-3 overflow-auto px-1">
|
||||||
|
<Card v-for="(item, index) in list" :key="index" :title="`${index + 1} 月`" size="small">
|
||||||
|
<!-- <template #extra>
|
||||||
|
<Button type="link" @click="downloadReport(index)">下载报表</Button>
|
||||||
|
</template> -->
|
||||||
|
<div class="-m-2 text-[16px]">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-1 bg-[#F6FFF5] px-2 pb-1">
|
||||||
|
<div class="p-1 text-center font-bold">收入</div>
|
||||||
|
<div v-for="(fee, type) in item.incomes" :key="type" class="p-1">
|
||||||
|
<span class="text-gray-500">{{ type }}:</span>
|
||||||
|
<span class="text-black">{{ fee }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 bg-[#FFFBF0] px-2 pb-1">
|
||||||
|
<div class="p-1 text-center font-bold">摊销</div>
|
||||||
|
<div class="p-1">
|
||||||
|
<span class="text-gray-500">已摊销:</span>
|
||||||
|
<span class="text-black">{{ item.already }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-1">
|
||||||
|
<span class="text-gray-500">月摊销:</span>
|
||||||
|
<span class="text-black">{{ item.now }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-1">
|
||||||
|
<span class="text-gray-500">剩余摊销:</span>
|
||||||
|
<span class="text-black">{{ item.notyet }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</Spinner>
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
:deep(.ant-card-head-title) {
|
||||||
|
font-size: 16px !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user