feat(财务统计): 新增月度财务报表功能
添加月度财务报表页面及相关工具函数和类型定义 修改统计接口参数类型以支持字符串格式的月份 调整空状态显示样式
This commit is contained in:
@@ -9,7 +9,7 @@ export const statisticsApi = {
|
||||
* @param data.year 年份
|
||||
* @returns 天医币报表数据
|
||||
*/
|
||||
getReportTianyibi: (data: { month?: string; year: number }) => {
|
||||
getReportTianyibi: (data: { month: string; year: number | string }) => {
|
||||
return requestClient.post('common/statistics/pointStatistics', data);
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@ export const statisticsApi = {
|
||||
* @param data.year 年份
|
||||
* @returns 实物报表数据
|
||||
*/
|
||||
getPhysicalStatistics: (data: { month?: string; year: number }) => {
|
||||
getPhysicalStatistics: (data: { month: string; year: number | string }) => {
|
||||
return requestClient.post('common/statistics/physicalStatistics', data);
|
||||
},
|
||||
|
||||
@@ -31,7 +31,7 @@ export const statisticsApi = {
|
||||
* @param data.year 年份
|
||||
* @returns 培训班报表数据
|
||||
*/
|
||||
getTrainingClassStatistics: (data: { month?: string; year: number }) => {
|
||||
getTrainingClassStatistics: (data: { month: string; year: number | string }) => {
|
||||
return requestClient.post('common/statistics/trainingClassStatistics', data);
|
||||
},
|
||||
|
||||
@@ -42,7 +42,7 @@ export const statisticsApi = {
|
||||
* @param data.year 年份
|
||||
* @returns VIP报表数据
|
||||
*/
|
||||
getVipStatistics: (data: { month?: string; year: number }) => {
|
||||
getVipStatistics: (data: { month: string; year: number | string }) => {
|
||||
return requestClient.post('common/statistics/vipStatistics', data);
|
||||
},
|
||||
|
||||
@@ -53,7 +53,7 @@ export const statisticsApi = {
|
||||
* @param data.year 年份
|
||||
* @returns 课程报表数据
|
||||
*/
|
||||
getCourseStatistics: (data: { month?: string; year: number }) => {
|
||||
getCourseStatistics: (data: { month: string; year: number | string }) => {
|
||||
return requestClient.post('common/statistics/courseStatistics', data);
|
||||
},
|
||||
|
||||
@@ -64,7 +64,7 @@ export const statisticsApi = {
|
||||
* @param data.year 年份
|
||||
* @returns 天医币报表数据
|
||||
*/
|
||||
downloadReportTianyibi: (data: { month?: string; year: number }) => {
|
||||
downloadReportTianyibi: (data: { month: string; year: number | string }) => {
|
||||
return defaultRequestClient.download<Blob>('common/statistics/pointInfoExport', {
|
||||
data,
|
||||
});
|
||||
@@ -76,7 +76,7 @@ export const statisticsApi = {
|
||||
* @param data.year 年份
|
||||
* @returns 实物报表数据
|
||||
*/
|
||||
downloadReportPhysical: (data: { month?: string; year: number }) => {
|
||||
downloadReportPhysical: (data: { month: string; year: number | string }) => {
|
||||
return defaultRequestClient.download<Blob>('common/statistics/physicalInfoExport', {
|
||||
data,
|
||||
});
|
||||
@@ -88,7 +88,7 @@ export const statisticsApi = {
|
||||
* @param data.year 年份
|
||||
* @returns 培训班报表数据
|
||||
*/
|
||||
downloadReportTrainingClass: (data: { month?: string; year: number }) => {
|
||||
downloadReportTrainingClass: (data: { month: string; year: number | string }) => {
|
||||
return defaultRequestClient.download<Blob>('common/statistics/trainingClassInfoExport', {
|
||||
data,
|
||||
});
|
||||
@@ -100,7 +100,7 @@ export const statisticsApi = {
|
||||
* @param data.year 年份
|
||||
* @returns VIP报表数据
|
||||
*/
|
||||
downloadReportVip: (data: { month?: string; year: number }) => {
|
||||
downloadReportVip: (data: { month: string; year: number | string }) => {
|
||||
return defaultRequestClient.download<Blob>('common/statistics/vipInfoExport', {
|
||||
data,
|
||||
});
|
||||
@@ -112,9 +112,31 @@ export const statisticsApi = {
|
||||
* @param data.year 年份
|
||||
* @returns 课程报表数据
|
||||
*/
|
||||
downloadReportCourse: (data: { month?: string; year: number }) => {
|
||||
downloadReportCourse: (data: { month: string; year: number | string }) => {
|
||||
return defaultRequestClient.download<Blob>('common/statistics/courseInfoExport', {
|
||||
data,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取月总结
|
||||
* @param data 月份 格式为YYYY-MM
|
||||
* @returns 月总结数据
|
||||
*/
|
||||
getSumMonthStatistics: (data: string) => {
|
||||
return requestClient.post('common/statistics/getStatistics', { orderTime: data });
|
||||
},
|
||||
|
||||
/**
|
||||
* 汇总导出
|
||||
* @param data 月份 格式为YYYY-MM
|
||||
* @param data.month 月份
|
||||
* @param data.year 年份
|
||||
* @returns 月总结数据
|
||||
*/
|
||||
downloadSumMonthStatistics: (data: { month: string; year: number | string }) => {
|
||||
return defaultRequestClient.download<Blob>('common/statistics/statisticsInfoExport', {
|
||||
data,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -56,6 +56,15 @@ const routes: RouteRecordRaw[] = [
|
||||
path: '/statistics/course-report',
|
||||
component: () => import('#/views/statistics/course/report.vue'),
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
title: '月度财务报表',
|
||||
keepAlive: true,
|
||||
},
|
||||
name: 'FinanceMonthReport',
|
||||
path: '/statistics/finance-month-report',
|
||||
component: () => import('#/views/statistics/summary-month/report.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -81,7 +81,7 @@ const handleDownloadMonth = async (index: number) => {
|
||||
<div class="content relative min-h-2 flex-1 px-3 py-4">
|
||||
<div
|
||||
v-if="list.length === 0 && !loading"
|
||||
class="col-span-3 flex items-center justify-center"
|
||||
class="col-span-3 flex h-full items-center justify-center"
|
||||
>
|
||||
<Empty />
|
||||
</div>
|
||||
|
||||
127
apps/finance/src/views/statistics/summary-month/report-utils.ts
Normal file
127
apps/finance/src/views/statistics/summary-month/report-utils.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import type { IncomeItem, VipAndCourseMonthStatistics } from './types';
|
||||
|
||||
// 处理收款汇总数据以及实物和培训班数据
|
||||
export function handleSumAndNoNeedAmortizateMonthData(
|
||||
responseData: any[],
|
||||
options: {
|
||||
childDictArr: { title: string; type: string }[];
|
||||
typeDictArr: string[];
|
||||
typeKey: string;
|
||||
},
|
||||
) {
|
||||
let sumMonthTotal = 0;
|
||||
const resultData = [] as any[];
|
||||
const { childDictArr, typeDictArr, typeKey } = options;
|
||||
typeDictArr.forEach((type) => {
|
||||
// 查找接口返回数据中是否有当前支付平台的数据
|
||||
const index = responseData.findIndex((item) => item[typeKey] === type);
|
||||
// 取得当前支付平台的数据项,若不存在则用空对象代替
|
||||
const resItem = index === -1 ? {} : responseData[index];
|
||||
// 组装页面渲染需要的数据格式
|
||||
resultData.push({
|
||||
type,
|
||||
sumFee: resItem.sumFee || 0,
|
||||
children: childDictArr.map((childType) => ({
|
||||
title: childType.title,
|
||||
fee: resItem[childType.type] || 0,
|
||||
})),
|
||||
});
|
||||
// 累加总金额
|
||||
// 使用 toFixed 避免精度丢失
|
||||
sumMonthTotal = Number.parseFloat((sumMonthTotal + (resItem.sumFee || 0)).toFixed(3));
|
||||
});
|
||||
return {
|
||||
sumMonthTotal,
|
||||
resultData,
|
||||
};
|
||||
}
|
||||
|
||||
// 获取当前月份和上一个月份的数据
|
||||
async function getTwoMonthData(
|
||||
requestMethod: (params: { month: string; year: number | string }) => Promise<any>,
|
||||
date: string,
|
||||
) {
|
||||
// 处理日期格式,将YYYY-MM转换为year: YYYY, month: MM
|
||||
const [year = '', month = ''] = date.split('-');
|
||||
const params = {
|
||||
year,
|
||||
month,
|
||||
};
|
||||
// 处理跨年情况
|
||||
const lastMonthParams = {
|
||||
year: params.month === '01' ? Number(params.year) - 1 : params.year,
|
||||
month: params.month === '01' ? '12' : (Number(params.month) - 1).toString().padStart(2, '0'),
|
||||
};
|
||||
// 调用接口 查询当前月份数据和上一个月数据
|
||||
// 同时发送两次请求
|
||||
const [currentMonthData, lastMonthData] = await Promise.all([
|
||||
requestMethod(params),
|
||||
requestMethod(lastMonthParams),
|
||||
]);
|
||||
return {
|
||||
currentMonthData,
|
||||
lastMonthData,
|
||||
};
|
||||
}
|
||||
|
||||
// 处理VIP、课程数据的公共方法
|
||||
export async function handleVipAndCourseMonthData(
|
||||
requestMethod: (params: {
|
||||
month: string;
|
||||
year: number | string;
|
||||
}) => Promise<VipAndCourseMonthStatistics>,
|
||||
date: string,
|
||||
incomeTypes: string[],
|
||||
) {
|
||||
const { currentMonthData, lastMonthData } = await getTwoMonthData(requestMethod, date);
|
||||
return {
|
||||
...currentMonthData,
|
||||
incomes: incomeTypes.map((type) => ({
|
||||
type,
|
||||
fee: currentMonthData.incomes.find((item: IncomeItem) => item.type === type)?.fee || 0,
|
||||
})),
|
||||
lastNotyet: lastMonthData?.notyet || 0,
|
||||
incomesTotal: Number.parseFloat(
|
||||
currentMonthData.incomes
|
||||
.reduce((acc: number, cur: IncomeItem) => acc + cur.fee, 0)
|
||||
.toFixed(3),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
// 处理天医币明细数据
|
||||
export async function handleTianyibiDetailData(
|
||||
requestMethod: (params: {
|
||||
month: string;
|
||||
year: number | string;
|
||||
}) => Promise<VipAndCourseMonthStatistics>,
|
||||
date: string,
|
||||
incomeTypes: string[],
|
||||
consumesTypes: string[],
|
||||
) {
|
||||
const { currentMonthData, lastMonthData } = await getTwoMonthData(requestMethod, date);
|
||||
return {
|
||||
...currentMonthData.map,
|
||||
incomes: incomeTypes.map((type) => ({
|
||||
type,
|
||||
fee: currentMonthData.map.incomes.find((item: IncomeItem) => item?.type === type)?.fee || 0,
|
||||
point:
|
||||
currentMonthData.map.incomes.find((item: IncomeItem) => item?.type === type)?.point || 0,
|
||||
})),
|
||||
consumes: consumesTypes.map((type) => ({
|
||||
type,
|
||||
fee: currentMonthData.map.consumes.find((item: IncomeItem) => item?.type === type)?.fee || 0,
|
||||
})),
|
||||
lastSurplus: lastMonthData?.map.surplus || 0,
|
||||
incomesTotal: Number.parseFloat(
|
||||
currentMonthData.map.incomes
|
||||
.reduce((acc: number, cur: IncomeItem) => acc + cur.fee, 0)
|
||||
.toFixed(3),
|
||||
),
|
||||
consumesTotal: Number.parseFloat(
|
||||
currentMonthData.map.consumes
|
||||
.reduce((acc: number, cur: IncomeItem) => acc + cur.fee, 0)
|
||||
.toFixed(3),
|
||||
),
|
||||
};
|
||||
}
|
||||
530
apps/finance/src/views/statistics/summary-month/report.vue
Normal file
530
apps/finance/src/views/statistics/summary-month/report.vue
Normal file
@@ -0,0 +1,530 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
import type {
|
||||
IncomeItem,
|
||||
NoNeedAmortizateMonthStatistics,
|
||||
SumAndNoNeedAmortizateMonthDataItem,
|
||||
SumMonthStatistics,
|
||||
} from './types';
|
||||
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
|
||||
import { Loading, Page } from '@vben/common-ui';
|
||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||
|
||||
import { Button, Card, DatePicker, message } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { statisticsApi } from '#/api/statistics';
|
||||
import { useSysStore } from '#/store/sys';
|
||||
|
||||
import {
|
||||
handleSumAndNoNeedAmortizateMonthData,
|
||||
handleTianyibiDetailData,
|
||||
handleVipAndCourseMonthData,
|
||||
} from './report-utils';
|
||||
|
||||
const sysStore = useSysStore();
|
||||
const loading = ref(false);
|
||||
const date = ref(dayjs().format('YYYY-MM'));
|
||||
const disabledDate = (date: Dayjs) => {
|
||||
const currentYear = dayjs().year();
|
||||
const currentMonth = dayjs().month();
|
||||
|
||||
// 如果是当前年份,超过本月的月份不能选
|
||||
if (date.year() === currentYear) {
|
||||
return date.month() > currentMonth;
|
||||
}
|
||||
|
||||
// 如果是未来年份,所有月份都不能选
|
||||
if (date.year() > currentYear) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 过去年份的所有月份都可以选
|
||||
return false;
|
||||
};
|
||||
|
||||
const platList = computed(() => sysStore.getDictList('payment'));
|
||||
const loadingText = '数据加载中...';
|
||||
const loadingError = '数据加载或解析失败';
|
||||
|
||||
// 获取收款汇总数据和实物和培训班数据
|
||||
const sumAndNoNeedAmortizateMonthData = reactive({
|
||||
paymentSummary: [] as SumMonthStatistics[],
|
||||
physicalAndTraining: [] as NoNeedAmortizateMonthStatistics[],
|
||||
});
|
||||
async function getSumAndNoNeedAmortizateMonth() {
|
||||
const res = await statisticsApi.getSumMonthStatistics(date.value);
|
||||
sumAndNoNeedAmortizateMonthData.paymentSummary = res.paymentSummary || [];
|
||||
sumAndNoNeedAmortizateMonthData.physicalAndTraining = res.physicalAndTraining || [];
|
||||
}
|
||||
|
||||
// 处理收款汇总数据
|
||||
const sumMonthLoading = ref<boolean | string>(loadingText);
|
||||
const sumMonthData = ref<SumAndNoNeedAmortizateMonthDataItem[]>([]);
|
||||
const sumMonthTotal = ref(0);
|
||||
|
||||
function handleSumMonthData() {
|
||||
sumMonthData.value = [];
|
||||
sumMonthTotal.value = 0;
|
||||
try {
|
||||
const paymentSummary = sumAndNoNeedAmortizateMonthData.paymentSummary;
|
||||
const { sumMonthTotal: sumMonthTotalValue, resultData: sumMonthDataValue } =
|
||||
handleSumAndNoNeedAmortizateMonthData(paymentSummary, {
|
||||
childDictArr: [
|
||||
{ title: '实物', type: 'shiwu' },
|
||||
{ title: '培训班', type: 'peixun' },
|
||||
{ title: '课程', type: 'kecheng' },
|
||||
{ title: 'vip', type: 'vip' },
|
||||
{ title: '天医币', type: 'tianyibi' },
|
||||
],
|
||||
typeDictArr: platList.value.map((item) => item.label),
|
||||
typeKey: 'plat',
|
||||
});
|
||||
sumMonthData.value = sumMonthDataValue;
|
||||
sumMonthTotal.value = sumMonthTotalValue;
|
||||
|
||||
sumMonthLoading.value = false;
|
||||
} catch {
|
||||
message.error('收款汇总数据解析失败');
|
||||
sumMonthLoading.value = loadingError;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理实物和培训班数据
|
||||
const noNeedAmortizateMonthLoading = ref<boolean | string>(loadingText);
|
||||
const noNeedAmortizateMonthData = ref<SumAndNoNeedAmortizateMonthDataItem[]>([]);
|
||||
const noNeedAmortizateMonthTotal = ref(0);
|
||||
const noNeedAmortizateList = ['实物', '培训班'];
|
||||
|
||||
function handleNoNeedAmortizateMonthData() {
|
||||
noNeedAmortizateMonthData.value = [];
|
||||
noNeedAmortizateMonthTotal.value = 0;
|
||||
try {
|
||||
const physicalAndTraining = sumAndNoNeedAmortizateMonthData.physicalAndTraining;
|
||||
const { sumMonthTotal: sumMonthTotalValue, resultData: sumMonthDataValue } =
|
||||
handleSumAndNoNeedAmortizateMonthData(physicalAndTraining, {
|
||||
childDictArr: [
|
||||
{ title: '微信', type: 'wx' },
|
||||
{ title: '银行', type: 'bank' },
|
||||
{ title: '支付宝', type: 'zfb' },
|
||||
{ title: '天医币', type: 'tianyibi' },
|
||||
],
|
||||
typeDictArr: noNeedAmortizateList,
|
||||
typeKey: 'type',
|
||||
});
|
||||
noNeedAmortizateMonthData.value = sumMonthDataValue;
|
||||
noNeedAmortizateMonthTotal.value = sumMonthTotalValue;
|
||||
|
||||
noNeedAmortizateMonthLoading.value = false;
|
||||
} catch {
|
||||
message.error('实物和培训班数据解析失败');
|
||||
noNeedAmortizateMonthLoading.value = loadingError;
|
||||
}
|
||||
}
|
||||
|
||||
async function querySumAndNoNeedAmortizateMonthData() {
|
||||
sumMonthLoading.value = loadingText;
|
||||
noNeedAmortizateMonthLoading.value = loadingText;
|
||||
await getSumAndNoNeedAmortizateMonth();
|
||||
handleSumMonthData();
|
||||
handleNoNeedAmortizateMonthData();
|
||||
}
|
||||
|
||||
// 查询和处理VIP数据
|
||||
const vipMonthLoading = ref<boolean | string>(loadingText);
|
||||
const vipMonthData = ref({
|
||||
incomes: [] as IncomeItem[],
|
||||
incomesTotal: 0,
|
||||
notyet: 0,
|
||||
already: 0,
|
||||
now: 0,
|
||||
lastNotyet: 0,
|
||||
});
|
||||
|
||||
// 获取VIP数据
|
||||
async function queryVipMonthData() {
|
||||
vipMonthLoading.value = loadingText;
|
||||
try {
|
||||
const incomeTypes = platList.value.map((item) => item.label);
|
||||
incomeTypes.push('天医币');
|
||||
const res = await handleVipAndCourseMonthData(
|
||||
statisticsApi.getVipStatistics,
|
||||
date.value,
|
||||
incomeTypes,
|
||||
);
|
||||
vipMonthData.value = res;
|
||||
vipMonthLoading.value = false;
|
||||
} catch {
|
||||
message.error('VIP收入和摊销数据解析失败');
|
||||
vipMonthLoading.value = loadingError;
|
||||
}
|
||||
}
|
||||
|
||||
// 查询和处理课程数据
|
||||
const courseMonthLoading = ref<boolean | string>(loadingText);
|
||||
const courseMonthData = ref({
|
||||
incomes: [] as IncomeItem[],
|
||||
incomesTotal: 0,
|
||||
notyet: 0,
|
||||
already: 0,
|
||||
now: 0,
|
||||
lastNotyet: 0,
|
||||
});
|
||||
|
||||
// 获取课程数据
|
||||
async function queryCourseMonthData() {
|
||||
courseMonthLoading.value = loadingText;
|
||||
try {
|
||||
const incomeTypes = platList.value.map((item) => item.label);
|
||||
incomeTypes.push('天医币');
|
||||
const res = await handleVipAndCourseMonthData(
|
||||
statisticsApi.getCourseStatistics,
|
||||
date.value,
|
||||
incomeTypes,
|
||||
);
|
||||
courseMonthData.value = res;
|
||||
courseMonthLoading.value = false;
|
||||
} catch {
|
||||
message.error('课程收入数据解析失败');
|
||||
courseMonthLoading.value = loadingError;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理天医币明细数据
|
||||
const tianyibiMonthLoading = ref<boolean | string>(loadingText);
|
||||
const tianyibiMonthData = ref({
|
||||
incomes: [] as IncomeItem[],
|
||||
consumes: [] as IncomeItem[],
|
||||
incomesTotal: 0,
|
||||
consumesTotal: 0,
|
||||
surplus: 0,
|
||||
lastSurplus: 0,
|
||||
});
|
||||
|
||||
// 获取天医币明细数据
|
||||
async function queryTianyibiMonthData() {
|
||||
tianyibiMonthLoading.value = loadingText;
|
||||
try {
|
||||
const res = await handleTianyibiDetailData(
|
||||
statisticsApi.getReportTianyibi,
|
||||
date.value,
|
||||
platList.value.map((item) => item.label),
|
||||
['培训班', '课程', 'vip', '实物'],
|
||||
);
|
||||
tianyibiMonthData.value = res;
|
||||
tianyibiMonthLoading.value = false;
|
||||
} catch {
|
||||
message.error('天医币明细数据解析失败');
|
||||
tianyibiMonthLoading.value = loadingError;
|
||||
}
|
||||
}
|
||||
|
||||
// 查询方法
|
||||
async function query() {
|
||||
loading.value = true;
|
||||
await Promise.all([
|
||||
querySumAndNoNeedAmortizateMonthData(),
|
||||
queryVipMonthData(),
|
||||
queryCourseMonthData(),
|
||||
queryTianyibiMonthData(),
|
||||
]);
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
// 组件挂载时查询数据
|
||||
onMounted(() => {
|
||||
query();
|
||||
});
|
||||
|
||||
// 下载月总结报表
|
||||
const downloadMonthStatisticsLoading = ref(false);
|
||||
async function downloadMonthStatistics() {
|
||||
downloadMonthStatisticsLoading.value = true;
|
||||
const [year = '', month = ''] = date.value.split('-');
|
||||
const Blob = await statisticsApi.downloadSumMonthStatistics({
|
||||
month,
|
||||
year,
|
||||
});
|
||||
downloadFileFromBlobPart({
|
||||
source: Blob,
|
||||
fileName: `${year}年${month}月财务报表.xlsx`,
|
||||
});
|
||||
downloadMonthStatisticsLoading.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<Loading class="flex h-full flex-col rounded-md bg-white" :spinning="loading">
|
||||
<div class="search-form p-4">
|
||||
<DatePicker
|
||||
v-model:value="date"
|
||||
picker="month"
|
||||
:disabled-date="disabledDate"
|
||||
value-format="YYYY-MM"
|
||||
@change="query"
|
||||
/>
|
||||
<Button type="primary" class="ml-2" @click="query">查询</Button>
|
||||
</div>
|
||||
<div class="h-2 bg-gray-100"></div>
|
||||
<div class="content relative min-h-2 flex-1 px-3 py-4">
|
||||
<div class="max-h-full w-full overflow-auto px-1">
|
||||
<Card :bordered="false" class="h-full !shadow-none">
|
||||
<template #title>
|
||||
<div class="flex items-center justify-between">
|
||||
<div
|
||||
class="absolute left-0 top-0 flex h-full items-center justify-center bg-[#1890FF] pl-5 pr-8 text-lg font-bold text-white"
|
||||
style="clip-path: polygon(0 0, 100% 0, 90% 100%, 0 100%)"
|
||||
>
|
||||
{{ dayjs(date).format('YYYY年M月') }} 财务报表
|
||||
</div>
|
||||
<div class="absolute bottom-0 right-0 h-1 w-full bg-[#1890FF]"></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #extra>
|
||||
<Button
|
||||
:loading="downloadMonthStatisticsLoading"
|
||||
type="link"
|
||||
size="large"
|
||||
class="!px-0"
|
||||
@click="downloadMonthStatistics"
|
||||
>
|
||||
下载报表
|
||||
</Button>
|
||||
</template>
|
||||
<div class="grid grid-cols-4 gap-3">
|
||||
<!-- 收款汇总 -->
|
||||
<div class="col-span-1 row-span-2">
|
||||
<Card
|
||||
class="h-full"
|
||||
title="收款汇总"
|
||||
size="small"
|
||||
:head-style="{
|
||||
backgroundColor: '#1890FF',
|
||||
color: '#fff',
|
||||
borderColor: '#1890FF',
|
||||
}"
|
||||
>
|
||||
<template #extra>
|
||||
<span v-if="!sumMonthLoading"> 合计:{{ sumMonthTotal }}元 </span>
|
||||
</template>
|
||||
<div v-if="sumMonthLoading" class="flex h-full items-center justify-center">
|
||||
{{ sumMonthLoading }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-for="item in sumMonthData" :key="item.type">
|
||||
<div class="font-bold">{{ `${item.type}(合计:${item.sumFee}元)` }}</div>
|
||||
<div
|
||||
v-for="child in item.children"
|
||||
:key="child.title"
|
||||
class="flex h-full items-center"
|
||||
>
|
||||
<div>{{ `${child.title}:${child.fee}元` }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- 天医币明细 -->
|
||||
<div class="col-span-1 row-span-2">
|
||||
<Card
|
||||
class="h-full"
|
||||
title="天医币明细"
|
||||
size="small"
|
||||
:head-style="{
|
||||
backgroundColor: '#1890FF',
|
||||
color: '#fff',
|
||||
borderColor: '#1890FF',
|
||||
}"
|
||||
>
|
||||
<div v-if="tianyibiMonthLoading" class="flex h-full items-center justify-center">
|
||||
{{ tianyibiMonthLoading }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="font-bold">
|
||||
上月天医币剩余 {{ tianyibiMonthData.lastSurplus }}元
|
||||
</div>
|
||||
<div class="font-bold">进项 合计{{ tianyibiMonthData.incomesTotal }}元</div>
|
||||
<div
|
||||
v-for="child in tianyibiMonthData.incomes"
|
||||
:key="child.type"
|
||||
class="flex items-center"
|
||||
>
|
||||
{{
|
||||
`${child.type}:${child.fee}元${child.point !== child.fee ? `(${child.point}天医币)` : ''}`
|
||||
}}
|
||||
</div>
|
||||
<div class="font-bold">出项 合计{{ tianyibiMonthData.consumesTotal }}元</div>
|
||||
<div
|
||||
v-for="child in tianyibiMonthData.consumes"
|
||||
:key="child.type"
|
||||
class="flex items-center"
|
||||
>
|
||||
{{ `${child.type}:${child.fee}元` }}
|
||||
</div>
|
||||
<div class="font-bold">APP用户天医币剩余 {{ tianyibiMonthData.surplus }}元</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- -->
|
||||
<div class="col-span-2">
|
||||
<Card
|
||||
class="h-full"
|
||||
title="实物和培训班收入"
|
||||
size="small"
|
||||
:head-style="{
|
||||
backgroundColor: '#1890FF',
|
||||
color: '#fff',
|
||||
borderColor: '#1890FF',
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-if="noNeedAmortizateMonthLoading"
|
||||
class="flex h-full items-center justify-center"
|
||||
>
|
||||
{{ noNeedAmortizateMonthLoading }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-for="item in noNeedAmortizateMonthData" :key="item.type">
|
||||
<div class="font-bold">{{ `${item.type}` }}</div>
|
||||
<div class="grid grid-cols-3">
|
||||
<div
|
||||
v-for="child in item.children"
|
||||
:key="child.title"
|
||||
class="flex items-center"
|
||||
>
|
||||
{{ `${child.title}:${child.fee}元` }}
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
{{ `合计:${item.sumFee}元` }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- VIP收入及摊销 -->
|
||||
<div class="col-span-1">
|
||||
<Card
|
||||
class="h-full"
|
||||
title="VIP收入及摊销"
|
||||
size="small"
|
||||
:head-style="{
|
||||
backgroundColor: '#1890FF',
|
||||
color: '#fff',
|
||||
borderColor: '#1890FF',
|
||||
}"
|
||||
>
|
||||
<div v-if="vipMonthLoading" class="flex h-full items-center justify-center">
|
||||
{{ vipMonthLoading }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="font-bold">上月剩余摊销 {{ vipMonthData.lastNotyet }}元</div>
|
||||
<div class="font-bold">收入 合计{{ vipMonthData.incomesTotal }}元</div>
|
||||
<div
|
||||
v-for="child in vipMonthData.incomes"
|
||||
:key="child.type"
|
||||
class="flex items-center"
|
||||
>
|
||||
{{ `${child.type}:${child.fee}元` }}
|
||||
</div>
|
||||
<div class="font-bold">月摊销 {{ vipMonthData.now }}元</div>
|
||||
<div class="font-bold">已摊销 {{ vipMonthData.already }}元</div>
|
||||
<div class="font-bold">剩余摊销 {{ vipMonthData.notyet }}元</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
<!-- 课程收入及摊销 -->
|
||||
<div class="col-span-1">
|
||||
<Card
|
||||
class="h-full"
|
||||
title="课程收入及摊销"
|
||||
size="small"
|
||||
:head-style="{
|
||||
backgroundColor: '#1890FF',
|
||||
color: '#fff',
|
||||
borderColor: '#1890FF',
|
||||
}"
|
||||
>
|
||||
<div v-if="courseMonthLoading" class="flex h-full items-center justify-center">
|
||||
{{ courseMonthLoading }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="font-bold">上月剩余摊销 {{ courseMonthData.lastNotyet }}元</div>
|
||||
<div class="font-bold">收入 合计{{ courseMonthData.incomesTotal }}元</div>
|
||||
<div
|
||||
v-for="child in courseMonthData.incomes"
|
||||
:key="child.type"
|
||||
class="flex items-center"
|
||||
>
|
||||
{{ `${child.type}:${child.fee}元` }}
|
||||
</div>
|
||||
<div class="font-bold">月摊销 {{ courseMonthData.now }}元</div>
|
||||
<div class="font-bold">已摊销 {{ courseMonthData.already }}元</div>
|
||||
<div class="font-bold">剩余摊销 {{ courseMonthData.notyet }}元</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 flex justify-end gap-10 bg-gray-50 p-2 text-black">
|
||||
<span>
|
||||
经合计计算后剩余天医币:
|
||||
{{
|
||||
tianyibiMonthLoading
|
||||
? '等待计算'
|
||||
: `${Number.parseFloat(
|
||||
(
|
||||
tianyibiMonthData.lastSurplus +
|
||||
tianyibiMonthData.incomesTotal -
|
||||
tianyibiMonthData.consumesTotal
|
||||
).toFixed(3),
|
||||
)}元`
|
||||
}}
|
||||
</span>
|
||||
<span>
|
||||
经合计计算后VIP剩余摊销:
|
||||
{{
|
||||
vipMonthLoading
|
||||
? '等待计算'
|
||||
: `${Number.parseFloat(
|
||||
(
|
||||
vipMonthData.lastNotyet +
|
||||
vipMonthData.incomesTotal -
|
||||
vipMonthData.now
|
||||
).toFixed(3),
|
||||
)}元`
|
||||
}}
|
||||
</span>
|
||||
<span>
|
||||
经合计计算后课程剩余摊销:
|
||||
{{
|
||||
courseMonthLoading
|
||||
? '等待计算'
|
||||
: `${Number.parseFloat(
|
||||
(
|
||||
courseMonthData.lastNotyet +
|
||||
courseMonthData.incomesTotal -
|
||||
courseMonthData.now
|
||||
).toFixed(3),
|
||||
)}元`
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</Loading>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.ant-card-head) {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
40
apps/finance/src/views/statistics/summary-month/types.d.ts
vendored
Normal file
40
apps/finance/src/views/statistics/summary-month/types.d.ts
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
export interface NoNeedAmortizateMonthStatistics {
|
||||
wx: number;
|
||||
bank: number;
|
||||
zfb: number;
|
||||
tianyibi: number;
|
||||
sumFee: number;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface SumAndNoNeedAmortizateMonthDataItem {
|
||||
type: string;
|
||||
sumFee: number;
|
||||
children: Array<{
|
||||
fee: number;
|
||||
title: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface SumMonthStatistics {
|
||||
peixun: number;
|
||||
shiwu: number;
|
||||
kecheng: number;
|
||||
tianyibi: number;
|
||||
plat: string;
|
||||
sumFee: number;
|
||||
vip: number;
|
||||
}
|
||||
|
||||
export interface IncomeItem {
|
||||
fee: number;
|
||||
type: string;
|
||||
point?: number;
|
||||
}
|
||||
|
||||
export interface VipAndCourseMonthStatistics {
|
||||
incomes: IncomeItem[];
|
||||
notyet: number;
|
||||
already: number;
|
||||
now: number;
|
||||
}
|
||||
Reference in New Issue
Block a user