Compare commits
5 Commits
d712f9a5bf
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 1468891bf5 | |||
| 5c2c5b92dd | |||
| 4b9f79192f | |||
| 5c39bd4113 | |||
| be5078ae0d |
@@ -14,6 +14,10 @@ export namespace AuthApi {
|
|||||||
* 最后登录时间
|
* 最后登录时间
|
||||||
*/
|
*/
|
||||||
lastTime: string;
|
lastTime: string;
|
||||||
|
/**
|
||||||
|
* 角色列表
|
||||||
|
*/
|
||||||
|
role: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 登录接口返回值 */
|
/** 登录接口返回值 */
|
||||||
|
|||||||
111
apps/finance/src/api/defaultRequest.ts
Normal file
111
apps/finance/src/api/defaultRequest.ts
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* 该文件可自行根据业务逻辑进行调整
|
||||||
|
*/
|
||||||
|
import type { RequestClientOptions } from '@vben/request';
|
||||||
|
|
||||||
|
import { useAppConfig } from '@vben/hooks';
|
||||||
|
import { preferences } from '@vben/preferences';
|
||||||
|
import {
|
||||||
|
authenticateResponseInterceptor,
|
||||||
|
defaultResponseInterceptor,
|
||||||
|
errorMessageResponseInterceptor,
|
||||||
|
RequestClient,
|
||||||
|
} from '@vben/request';
|
||||||
|
import { useAccessStore } from '@vben/stores';
|
||||||
|
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useAuthStore } from '#/store';
|
||||||
|
|
||||||
|
import { refreshTokenApi } from './core';
|
||||||
|
|
||||||
|
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||||
|
|
||||||
|
function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||||
|
const client = new RequestClient({
|
||||||
|
...options,
|
||||||
|
baseURL,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新认证逻辑
|
||||||
|
*/
|
||||||
|
async function doReAuthenticate() {
|
||||||
|
console.warn('Access token or refresh token is invalid or expired. ');
|
||||||
|
const accessStore = useAccessStore();
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
accessStore.setAccessToken(null);
|
||||||
|
if (preferences.app.loginExpiredMode === 'modal' && accessStore.isAccessChecked) {
|
||||||
|
accessStore.setLoginExpired(true);
|
||||||
|
} else {
|
||||||
|
await authStore.logout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新token逻辑
|
||||||
|
*/
|
||||||
|
async function doRefreshToken() {
|
||||||
|
const accessStore = useAccessStore();
|
||||||
|
const resp = await refreshTokenApi();
|
||||||
|
const newToken = resp.data;
|
||||||
|
accessStore.setAccessToken(newToken);
|
||||||
|
return newToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatToken(token: null | string) {
|
||||||
|
return token ? `${token}` : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 请求头处理
|
||||||
|
client.addRequestInterceptor({
|
||||||
|
fulfilled: async (config) => {
|
||||||
|
const accessStore = useAccessStore();
|
||||||
|
|
||||||
|
config.headers.Token = formatToken(accessStore.accessToken);
|
||||||
|
config.headers['Accept-Language'] = preferences.app.locale;
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理返回的响应数据格式
|
||||||
|
client.addResponseInterceptor(
|
||||||
|
defaultResponseInterceptor({
|
||||||
|
codeField: 'code',
|
||||||
|
dataField: 'data',
|
||||||
|
successCode: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// token过期的处理
|
||||||
|
client.addResponseInterceptor(
|
||||||
|
authenticateResponseInterceptor({
|
||||||
|
client,
|
||||||
|
doReAuthenticate,
|
||||||
|
doRefreshToken,
|
||||||
|
enableRefreshToken: preferences.app.enableRefreshToken,
|
||||||
|
formatToken,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
|
||||||
|
client.addResponseInterceptor(
|
||||||
|
errorMessageResponseInterceptor((msg: string, error) => {
|
||||||
|
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
|
||||||
|
// 当前mock接口返回的错误字段是 error 或者 message
|
||||||
|
const responseData = error?.response?.data ?? {};
|
||||||
|
const errorMessage = responseData?.error ?? responseData?.message ?? '';
|
||||||
|
// 如果没有错误信息,则会根据状态码进行提示
|
||||||
|
message.error(errorMessage || msg);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultRequestClient = createRequestClient(apiURL, {
|
||||||
|
responseReturn: 'data',
|
||||||
|
timeout: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { defaultRequestClient } from '#/api/defaultRequest';
|
||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
export const statisticsApi = {
|
export const statisticsApi = {
|
||||||
@@ -58,10 +59,62 @@ export const statisticsApi = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载天医币报表
|
* 下载天医币报表
|
||||||
|
* @param data 请求参数
|
||||||
|
* @param data.month 月份
|
||||||
|
* @param data.year 年份
|
||||||
|
* @returns 天医币报表数据
|
||||||
*/
|
*/
|
||||||
downloadReportTianyibi: (data: { date: string }) => {
|
downloadReportTianyibi: (data: { month?: string; year: number }) => {
|
||||||
return requestClient.post('/common/import/getImportFile', data, {
|
return defaultRequestClient.download<Blob>('common/statistics/pointInfoExport', {
|
||||||
responseType: 'blob',
|
data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 下载实物报表
|
||||||
|
* @param data 请求参数
|
||||||
|
* @param data.month 月份
|
||||||
|
* @param data.year 年份
|
||||||
|
* @returns 实物报表数据
|
||||||
|
*/
|
||||||
|
downloadReportPhysical: (data: { month?: string; year: number }) => {
|
||||||
|
return defaultRequestClient.download<Blob>('common/statistics/physicalInfoExport', {
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 下载培训班报表
|
||||||
|
* @param data 请求参数
|
||||||
|
* @param data.month 月份
|
||||||
|
* @param data.year 年份
|
||||||
|
* @returns 培训班报表数据
|
||||||
|
*/
|
||||||
|
downloadReportTrainingClass: (data: { month?: string; year: number }) => {
|
||||||
|
return defaultRequestClient.download<Blob>('common/statistics/trainingClassInfoExport', {
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 下载VIP报表
|
||||||
|
* @param data 请求参数
|
||||||
|
* @param data.month 月份
|
||||||
|
* @param data.year 年份
|
||||||
|
* @returns VIP报表数据
|
||||||
|
*/
|
||||||
|
downloadReportVip: (data: { month?: string; year: number }) => {
|
||||||
|
return defaultRequestClient.download<Blob>('common/statistics/vipInfoExport', {
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 下载课程报表
|
||||||
|
* @param data 请求参数
|
||||||
|
* @param data.month 月份
|
||||||
|
* @param data.year 年份
|
||||||
|
* @returns 课程报表数据
|
||||||
|
*/
|
||||||
|
downloadReportCourse: (data: { month?: string; year: number }) => {
|
||||||
|
return defaultRequestClient.download<Blob>('common/statistics/courseInfoExport', {
|
||||||
|
data,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ function setupAccessGuard(router: Router) {
|
|||||||
// 生成路由表
|
// 生成路由表
|
||||||
// 当前登录用户拥有的角色标识列表
|
// 当前登录用户拥有的角色标识列表
|
||||||
const userInfo = userStore.userInfo || (await authStore.fetchUserInfo());
|
const userInfo = userStore.userInfo || (await authStore.fetchUserInfo());
|
||||||
const userRoles = userInfo.roles ?? [];
|
const userRoles = userInfo?.roles ?? [];
|
||||||
|
|
||||||
// 生成菜单和路由
|
// 生成菜单和路由
|
||||||
const { accessibleMenus, accessibleRoutes } = await generateAccess({
|
const { accessibleMenus, accessibleRoutes } = await generateAccess({
|
||||||
@@ -109,7 +109,7 @@ function setupAccessGuard(router: Router) {
|
|||||||
accessStore.setIsAccessChecked(true);
|
accessStore.setIsAccessChecked(true);
|
||||||
const redirectPath = (from.query.redirect ??
|
const redirectPath = (from.query.redirect ??
|
||||||
(to.path === preferences.app.defaultHomePath
|
(to.path === preferences.app.defaultHomePath
|
||||||
? userInfo.homePath || preferences.app.defaultHomePath
|
? userInfo?.homePath || preferences.app.defaultHomePath
|
||||||
: to.fullPath)) as string;
|
: to.fullPath)) as string;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
// ]);
|
// ]);
|
||||||
|
|
||||||
userInfo = userEntity;
|
userInfo = userEntity;
|
||||||
|
userInfo.roles = userEntity.role ? [userEntity.role] : [];
|
||||||
|
|
||||||
userStore.setUserInfo(userInfo);
|
userStore.setUserInfo(userInfo);
|
||||||
// accessStore.setAccessCodes(accessCodes);
|
// accessStore.setAccessCodes(accessCodes);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import type { VbenFormProps } from '#/adapter/form';
|
import type { VbenFormProps } from '#/adapter/form';
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page, SubmitButton } from '@vben/common-ui';
|
||||||
|
|
||||||
import { Button, message, notification } from 'ant-design-vue';
|
import { Button, message, notification } from 'ant-design-vue';
|
||||||
|
|
||||||
@@ -55,6 +55,7 @@ const formOptions: VbenFormProps = {
|
|||||||
},
|
},
|
||||||
fieldName: 'tel',
|
fieldName: 'tel',
|
||||||
label: '手机号',
|
label: '手机号',
|
||||||
|
defaultValue: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
@@ -159,6 +160,8 @@ const gridOptions: VxeTableGridOptions<RowType> = {
|
|||||||
return await tianyibiApi.getPointOrdersList({
|
return await tianyibiApi.getPointOrdersList({
|
||||||
page: page.currentPage,
|
page: page.currentPage,
|
||||||
limit: page.pageSize,
|
limit: page.pageSize,
|
||||||
|
source: params.source || '',
|
||||||
|
type: params.type || '',
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -172,11 +175,11 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function onStartAutoMatch() {
|
async function onStartAutoMatch() {
|
||||||
const hide = message.loading('系统自动匹配消耗中...', 0);
|
const hide = message.loading('系统自动匹配天医币消耗中...', 0);
|
||||||
await tianyibiApi.autoConsumeTianyibi();
|
await tianyibiApi.autoConsumeTianyibi();
|
||||||
hide();
|
hide();
|
||||||
notification.success({
|
notification.success({
|
||||||
message: '自动匹配消耗成功',
|
message: '自动匹配天医币消耗成功',
|
||||||
});
|
});
|
||||||
// 刷新表格数据
|
// 刷新表格数据
|
||||||
gridApi?.query();
|
gridApi?.query();
|
||||||
@@ -198,7 +201,8 @@ async function onConsumption(row: RowType, code: number) {
|
|||||||
<Grid>
|
<Grid>
|
||||||
<template #toolbar-actions>
|
<template #toolbar-actions>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<Button type="primary" @click="onStartAutoMatch()">启动自动匹配消耗</Button>
|
<!-- <Button type="primary" @click="onStartAutoMatch()">启动自动匹配消耗</Button> -->
|
||||||
|
<SubmitButton theme="ant-design" :submit="onStartAutoMatch" text="启动自动匹配消耗" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #action="{ row }">
|
<template #action="{ row }">
|
||||||
|
|||||||
122
apps/finance/src/views/statistics/common/MonthReportView.vue
Normal file
122
apps/finance/src/views/statistics/common/MonthReportView.vue
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { Loading, Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button, Card, DatePicker, Empty } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
contentItemClass?: string;
|
||||||
|
disabledDate: (d: any) => boolean;
|
||||||
|
downloadAll?: () => void;
|
||||||
|
downloadMonth?: (index: number) => void;
|
||||||
|
list: any[];
|
||||||
|
loading: boolean;
|
||||||
|
year: any;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'query'): void;
|
||||||
|
(e: 'update:year', v: any): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const modelYear = computed({
|
||||||
|
get: () => props.year,
|
||||||
|
set: (v) => emit('update:year', v),
|
||||||
|
});
|
||||||
|
|
||||||
|
const downloadingAll = ref(false);
|
||||||
|
const downloadingMonth = ref<boolean[]>([]);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.list.length,
|
||||||
|
(len) => {
|
||||||
|
downloadingMonth.value = Array.from({ length: len }, () => false);
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDownloadAll = async () => {
|
||||||
|
if (!props.downloadAll) return;
|
||||||
|
downloadingAll.value = true;
|
||||||
|
try {
|
||||||
|
await Promise.resolve(props.downloadAll());
|
||||||
|
} finally {
|
||||||
|
downloadingAll.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDownloadMonth = async (index: number) => {
|
||||||
|
if (!props.downloadMonth) return;
|
||||||
|
downloadingMonth.value[index] = true;
|
||||||
|
try {
|
||||||
|
await Promise.resolve(props.downloadMonth(index));
|
||||||
|
} finally {
|
||||||
|
downloadingMonth.value[index] = 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="modelYear"
|
||||||
|
picker="year"
|
||||||
|
:disabled-date="disabledDate"
|
||||||
|
@change="() => emit('query')"
|
||||||
|
/>
|
||||||
|
<Button type="primary" class="ml-2" @click="emit('query')">查询</Button>
|
||||||
|
<Button
|
||||||
|
v-if="props.downloadAll"
|
||||||
|
type="link"
|
||||||
|
class="ml-2"
|
||||||
|
:loading="downloadingAll"
|
||||||
|
@click="handleDownloadAll"
|
||||||
|
>
|
||||||
|
下载全年报表
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div class="h-2 bg-gray-100"></div>
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
<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
|
||||||
|
v-if="props.downloadMonth"
|
||||||
|
type="link"
|
||||||
|
:loading="downloadingMonth[index]"
|
||||||
|
@click="handleDownloadMonth(index)"
|
||||||
|
>
|
||||||
|
下载报表
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
<template v-if="item !== null">
|
||||||
|
<slot :item="item" :index="index" :year="modelYear"></slot>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div
|
||||||
|
v-if="loading"
|
||||||
|
class="flex min-h-12 items-center justify-center"
|
||||||
|
:class="contentItemClass"
|
||||||
|
>
|
||||||
|
努力加载中...
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Loading>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
:deep(.ant-card-head-title) {
|
||||||
|
font-size: 16px !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
67
apps/finance/src/views/statistics/common/useMonthReport.ts
Normal file
67
apps/finance/src/views/statistics/common/useMonthReport.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import type { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
|
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
export function useMonthReport<T>(options: {
|
||||||
|
downloadFile: (p: { month: string; year: number }) => Promise<any>;
|
||||||
|
fetchMonth: (p: { month: string; year: number }) => Promise<any>;
|
||||||
|
fileNameBuilder?: (year: number, month: string) => string;
|
||||||
|
normalize: (resp: any) => T;
|
||||||
|
}) {
|
||||||
|
const year = ref<Dayjs>(dayjs());
|
||||||
|
const disabledDate = (date: Dayjs) => date.year() > dayjs().year();
|
||||||
|
const list = ref<Array<null | T>>([]);
|
||||||
|
const loading = ref<boolean>(false);
|
||||||
|
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
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'));
|
||||||
|
list.value = Array.from({ length: monthList.length }, () => null);
|
||||||
|
let pending = monthList.length;
|
||||||
|
monthList.forEach((month, idx) => {
|
||||||
|
options
|
||||||
|
.fetchMonth({
|
||||||
|
year: year.value.year(),
|
||||||
|
month,
|
||||||
|
})
|
||||||
|
.then((resp) => {
|
||||||
|
list.value[idx] = options.normalize(resp) as any;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
pending -= 1;
|
||||||
|
if (pending === 0) {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 下载报表
|
||||||
|
const downloadReport = async (index: number) => {
|
||||||
|
const month = index > 8 ? `${index + 1}` : `0${index + 1}`;
|
||||||
|
const filename =
|
||||||
|
options.fileNameBuilder?.(year.value.year(), month) ||
|
||||||
|
`报表_${year.value.year()}年${month}月_文件.xlsx`;
|
||||||
|
const Blob = await options.downloadFile({
|
||||||
|
month,
|
||||||
|
year: year.value.year(),
|
||||||
|
});
|
||||||
|
downloadFileFromBlobPart({
|
||||||
|
source: Blob,
|
||||||
|
fileName: filename,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadAllReport = async () => {
|
||||||
|
await Promise.all(list.value.map((_, index) => downloadReport(index)));
|
||||||
|
};
|
||||||
|
|
||||||
|
return { year, disabledDate, list, loading, getList, downloadReport, downloadAllReport };
|
||||||
|
}
|
||||||
@@ -1,18 +1,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Dayjs } from 'dayjs';
|
import { onMounted } from 'vue';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
import { Loading, Page } 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';
|
import { statisticsApi } from '#/api/statistics';
|
||||||
|
import MonthReportView from '#/views/statistics/common/MonthReportView.vue';
|
||||||
|
|
||||||
const year = ref<Dayjs>(dayjs());
|
import { useMonthReport } from '../common/useMonthReport';
|
||||||
const disabledDate = (date: Dayjs) => date.year() > dayjs().year();
|
|
||||||
|
|
||||||
interface Report {
|
interface Report {
|
||||||
incomes: Record<string, number>;
|
incomes: Record<string, number>;
|
||||||
@@ -26,128 +18,74 @@ interface ReportItem {
|
|||||||
fee: number;
|
fee: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 报表数据
|
const { year, disabledDate, list, loading, getList, downloadReport, downloadAllReport } =
|
||||||
const list = ref<Report[]>([]);
|
useMonthReport<Report>({
|
||||||
const loading = ref<boolean>(false);
|
fetchMonth: (p) => statisticsApi.getCourseStatistics(p),
|
||||||
const getList = async () => {
|
downloadFile: (p) => statisticsApi.downloadReportCourse(p),
|
||||||
loading.value = true;
|
fileNameBuilder: (y, m) => `课程报表_${y}年${m}月_文件.xlsx`,
|
||||||
list.value = [];
|
normalize: (data) => ({
|
||||||
try {
|
incomes: {
|
||||||
// 计算查询年份中包含哪些月份, 若为今年则只查询到当前月份,往年则查询所有月份
|
微信: 0,
|
||||||
const monthList =
|
支付宝: 0,
|
||||||
year.value.year() === dayjs().year()
|
银行: 0,
|
||||||
? Array.from({ length: dayjs().month() + 1 }, (_, i) => (i + 1).toString().padStart(2, '0'))
|
天医币: 0,
|
||||||
: Array.from({ length: 12 }, (_, i) => (i + 1).toString().padStart(2, '0'));
|
...Object.fromEntries(data.incomes.map((item: ReportItem) => [item.type, item.fee])),
|
||||||
for (const month of monthList) {
|
},
|
||||||
const data = await statisticsApi.getCourseStatistics({
|
notyet: data.notyet || 0,
|
||||||
year: year.value.year(),
|
already: data.already || 0,
|
||||||
month,
|
now: data.now || 0,
|
||||||
});
|
}),
|
||||||
list.value.push({
|
});
|
||||||
incomes: {
|
|
||||||
微信: 0,
|
const onUpdateYear = (v: any) => {
|
||||||
支付宝: 0,
|
year.value = v;
|
||||||
银行: 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(() => {
|
onMounted(() => {
|
||||||
getList();
|
getList();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<MonthReportView
|
||||||
<Loading class="flex h-full flex-col rounded-md bg-white" :spinning="loading">
|
:year="year"
|
||||||
<div class="search-form p-4">
|
:disabled-date="disabledDate"
|
||||||
<DatePicker
|
:loading="loading"
|
||||||
v-model:value="year"
|
:list="list"
|
||||||
picker="year"
|
:download-all="downloadAllReport"
|
||||||
:disabled-date="disabledDate"
|
:download-month="downloadReport"
|
||||||
@change="getList"
|
content-item-class="h-[150px]"
|
||||||
/>
|
@update:year="onUpdateYear"
|
||||||
<Button type="primary" class="ml-2" @click="getList">查询</Button>
|
@query="getList"
|
||||||
<!-- <Button type="link" class="ml-2" @click="downloadAllReport">
|
>
|
||||||
下载 {{ year.year() }} 年全部天医币报表
|
<template #default="{ item }">
|
||||||
</Button> -->
|
<div class="-m-2 text-[16px]">
|
||||||
</div>
|
<div class="flex">
|
||||||
<div class="h-2 bg-gray-100"></div>
|
<div class="flex-1 bg-[#F6FFF5] px-2 pb-1">
|
||||||
<div class="content relative min-h-2 flex-1 px-3 py-4">
|
<div class="p-1 text-center font-bold">收入</div>
|
||||||
<div
|
<div v-for="(fee, type) in item.incomes" :key="type" class="p-1">
|
||||||
v-if="list.length === 0 && !loading"
|
<span class="text-gray-500">{{ type }}:</span>
|
||||||
class="col-span-3 flex items-center justify-center"
|
<span class="text-black">{{ fee }}</span>
|
||||||
>
|
|
||||||
<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>
|
</div>
|
||||||
</Card>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</Loading>
|
</template>
|
||||||
</Page>
|
</MonthReportView>
|
||||||
</template>
|
</template>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss"></style>
|
||||||
:deep(.ant-card-head-title) {
|
|
||||||
font-size: 16px !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Dayjs } from 'dayjs';
|
import { onMounted } from 'vue';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
import { Loading, Page } from '@vben/common-ui';
|
|
||||||
|
|
||||||
import { VbenIcon } from '@vben-core/shadcn-ui';
|
import { VbenIcon } from '@vben-core/shadcn-ui';
|
||||||
|
|
||||||
import { Button, Card, DatePicker, Empty } from 'ant-design-vue';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
|
|
||||||
import { statisticsApi } from '#/api/statistics';
|
import { statisticsApi } from '#/api/statistics';
|
||||||
|
import MonthReportView from '#/views/statistics/common/MonthReportView.vue';
|
||||||
|
|
||||||
const year = ref<Dayjs>(dayjs());
|
import { useMonthReport } from '../common/useMonthReport';
|
||||||
const disabledDate = (date: Dayjs) => date.year() > dayjs().year();
|
|
||||||
|
|
||||||
interface ReportItem {
|
interface ReportItem {
|
||||||
type: string;
|
type: string;
|
||||||
@@ -25,24 +18,12 @@ interface ReportItem {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 报表数据
|
const { year, disabledDate, list, loading, getList, downloadReport, downloadAllReport } =
|
||||||
const list = ref<ReportItem[][]>([]);
|
useMonthReport<ReportItem[]>({
|
||||||
const loading = ref<boolean>(false);
|
fetchMonth: (p) => statisticsApi.getPhysicalStatistics(p),
|
||||||
const getList = async () => {
|
downloadFile: (p) => statisticsApi.downloadReportPhysical(p),
|
||||||
loading.value = true;
|
fileNameBuilder: (y, m) => `实物报表_${y}年${m}月_文件.xlsx`,
|
||||||
list.value = [];
|
normalize: (data) => {
|
||||||
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.getPhysicalStatistics({
|
|
||||||
year: year.value.year(),
|
|
||||||
month,
|
|
||||||
});
|
|
||||||
// 确保四种支付方式都存在,默认值为0
|
|
||||||
const paymentTypes = [
|
const paymentTypes = [
|
||||||
{ type: '微信', icon: { color: 'text-green-500', icon: 'ant-design:wechat-filled' } },
|
{ type: '微信', icon: { color: 'text-green-500', icon: 'ant-design:wechat-filled' } },
|
||||||
{ type: '支付宝', icon: { color: 'text-blue-500', icon: 'ant-design:alipay-outlined' } },
|
{ type: '支付宝', icon: { color: 'text-blue-500', icon: 'ant-design:alipay-outlined' } },
|
||||||
@@ -52,74 +33,55 @@ const getList = async () => {
|
|||||||
icon: { color: 'text-purple-500', icon: 'ant-design:pay-circle-filled' },
|
icon: { color: 'text-purple-500', icon: 'ant-design:pay-circle-filled' },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const report: ReportItem[] = paymentTypes.map((type) => {
|
return paymentTypes.map((type) => {
|
||||||
const item = data.map.find((item: any) => item.type === type.type);
|
const item = data.map.find((item: any) => item.type === type.type);
|
||||||
return {
|
return {
|
||||||
type: type.type,
|
type: type.type,
|
||||||
fee: item?.fee || 0,
|
fee: item?.fee || 0,
|
||||||
count: item?.count || 0,
|
count: item?.count || 0,
|
||||||
icon: { color: type.icon.color, icon: type.icon.icon },
|
icon: { color: type.icon.color, icon: type.icon.icon },
|
||||||
};
|
} as ReportItem;
|
||||||
});
|
});
|
||||||
list.value.push(report);
|
},
|
||||||
}
|
});
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const onUpdateYear = (v: any) => {
|
||||||
|
year.value = v;
|
||||||
|
};
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList();
|
getList();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<MonthReportView
|
||||||
<Loading class="flex h-full flex-col rounded-md bg-white" :spinning="loading">
|
:year="year"
|
||||||
<div class="search-form p-4">
|
:disabled-date="disabledDate"
|
||||||
<DatePicker
|
:loading="loading"
|
||||||
v-model:value="year"
|
:list="list"
|
||||||
picker="year"
|
:download-all="downloadAllReport"
|
||||||
:disabled-date="disabledDate"
|
:download-month="downloadReport"
|
||||||
@change="getList"
|
@update:year="onUpdateYear"
|
||||||
/>
|
@query="getList"
|
||||||
<Button type="primary" class="ml-2" @click="getList">查询</Button>
|
>
|
||||||
</div>
|
<template #default="{ item }">
|
||||||
<div class="h-2 bg-gray-100"></div>
|
<div class="-m-2 text-[16px]">
|
||||||
<div class="content relative min-h-2 flex-1 px-3 py-4">
|
<div class="flex flex-row py-2">
|
||||||
<div
|
<div v-for="(payment, index2) in item" :key="index2" class="flex-1 text-center">
|
||||||
v-if="list.length === 0 && !loading"
|
<div class="flex items-center justify-center font-bold">
|
||||||
class="col-span-3 flex items-center justify-center"
|
<VbenIcon
|
||||||
>
|
class="size-5"
|
||||||
<Empty />
|
:class="payment.icon.color"
|
||||||
</div>
|
:icon="payment.icon.icon"
|
||||||
<div v-else class="grid max-h-full grid-cols-3 gap-3 overflow-auto px-1">
|
fallback
|
||||||
<Card v-for="(item, index) in list" :key="index" :title="`${index + 1} 月`" size="small">
|
/>
|
||||||
<div class="-m-2 text-[16px]">
|
{{ payment.type }}
|
||||||
<div class="flex flex-row py-2">
|
|
||||||
<div v-for="(payment, index2) in item" :key="index2" class="flex-1 text-center">
|
|
||||||
<div class="flex items-center justify-center font-bold">
|
|
||||||
<VbenIcon
|
|
||||||
class="size-5"
|
|
||||||
:class="payment.icon.color"
|
|
||||||
:icon="payment.icon.icon"
|
|
||||||
fallback
|
|
||||||
/>
|
|
||||||
{{ payment.type }}
|
|
||||||
</div>
|
|
||||||
<!-- <div class="text-gray-500">{{ payment.count }}笔</div> -->
|
|
||||||
<div class="text-black">{{ payment.fee }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
<div class="text-black">{{ payment.fee }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Loading>
|
</template>
|
||||||
</Page>
|
</MonthReportView>
|
||||||
</template>
|
</template>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss"></style>
|
||||||
:deep(.ant-card-head-title) {
|
|
||||||
font-size: 16px !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Dayjs } from 'dayjs';
|
import { onMounted } from 'vue';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
import { Loading, Page } 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';
|
import { statisticsApi } from '#/api/statistics';
|
||||||
|
import MonthReportView from '#/views/statistics/common/MonthReportView.vue';
|
||||||
|
|
||||||
const year = ref<Dayjs>(dayjs());
|
import { useMonthReport } from '../common/useMonthReport';
|
||||||
const disabledDate = (date: Dayjs) => date.year() > dayjs().year();
|
|
||||||
|
interface IncomeItem {
|
||||||
|
fee: number;
|
||||||
|
point: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface Report {
|
interface Report {
|
||||||
incomes: Record<string, number>;
|
incomes: Record<string, IncomeItem>;
|
||||||
consumes: Record<string, number>;
|
consumes: Record<string, number>;
|
||||||
surplus: number;
|
surplus: number;
|
||||||
}
|
}
|
||||||
@@ -23,127 +20,80 @@ interface Report {
|
|||||||
interface ReportItem {
|
interface ReportItem {
|
||||||
type: string;
|
type: string;
|
||||||
fee: number;
|
fee: number;
|
||||||
|
point: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 报表数据
|
const { year, disabledDate, list, loading, getList, downloadReport, downloadAllReport } =
|
||||||
const list = ref<Report[]>([]);
|
useMonthReport<Report>({
|
||||||
const loading = ref<boolean>(false);
|
fetchMonth: (p) => statisticsApi.getReportTianyibi(p),
|
||||||
const getList = async () => {
|
downloadFile: (p) => statisticsApi.downloadReportTianyibi(p),
|
||||||
loading.value = true;
|
fileNameBuilder: (y, m) => `天医币报表_${y}年${m}月_文件.xlsx`,
|
||||||
list.value = [];
|
normalize: (data) => ({
|
||||||
try {
|
incomes: {
|
||||||
// 计算查询年份中包含哪些月份, 若为今年则只查询到当前月份,往年则查询所有月份
|
微信: { fee: 0, point: 0 },
|
||||||
const monthList =
|
支付宝: { fee: 0, point: 0 },
|
||||||
year.value.year() === dayjs().year()
|
银行: { fee: 0, point: 0 },
|
||||||
? Array.from({ length: dayjs().month() + 1 }, (_, i) => (i + 1).toString().padStart(2, '0'))
|
...Object.fromEntries(
|
||||||
: Array.from({ length: 12 }, (_, i) => (i + 1).toString().padStart(2, '0'));
|
data.map.incomes.map((item: ReportItem) => [
|
||||||
for (const month of monthList) {
|
item.type,
|
||||||
const data = await statisticsApi.getReportTianyibi({
|
{ fee: item.fee, point: item.point },
|
||||||
year: year.value.year(),
|
]),
|
||||||
month,
|
),
|
||||||
});
|
},
|
||||||
list.value.push({
|
consumes: {
|
||||||
incomes: {
|
实物: 0,
|
||||||
微信: 0,
|
培训班: 0,
|
||||||
支付宝: 0,
|
vip: 0,
|
||||||
银行: 0,
|
课程: 0,
|
||||||
...Object.fromEntries(data.map.incomes.map((item: ReportItem) => [item.type, item.fee])),
|
...Object.fromEntries(data.map.consumes.map((item: ReportItem) => [item.type, item.fee])),
|
||||||
},
|
},
|
||||||
consumes: {
|
surplus: data.map.surplus,
|
||||||
实物: 0,
|
}),
|
||||||
培训班: 0,
|
});
|
||||||
vip: 0,
|
|
||||||
课程: 0,
|
const onUpdateYear = (v: any) => {
|
||||||
...Object.fromEntries(data.map.consumes.map((item: ReportItem) => [item.type, item.fee])),
|
year.value = v;
|
||||||
},
|
|
||||||
surplus: data.map.surplus,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} 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(() => {
|
onMounted(() => {
|
||||||
getList();
|
getList();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<MonthReportView
|
||||||
<Loading class="flex h-full flex-col rounded-md bg-white" :spinning="loading">
|
:year="year"
|
||||||
<div class="search-form p-4">
|
:disabled-date="disabledDate"
|
||||||
<DatePicker
|
:loading="loading"
|
||||||
v-model:value="year"
|
:list="list"
|
||||||
picker="year"
|
:download-all="downloadAllReport"
|
||||||
:disabled-date="disabledDate"
|
:download-month="downloadReport"
|
||||||
@change="getList"
|
content-item-class="h-[180px]"
|
||||||
/>
|
@update:year="onUpdateYear"
|
||||||
<Button type="primary" class="ml-2" @click="getList">查询</Button>
|
@query="getList"
|
||||||
<!-- <Button type="link" class="ml-2" @click="downloadAllReport">
|
>
|
||||||
下载 {{ year.year() }} 年全部天医币报表
|
<template #default="{ item }">
|
||||||
</Button> -->
|
<div class="-m-2 text-[16px]">
|
||||||
</div>
|
<div class="flex">
|
||||||
<div class="h-2 bg-gray-100"></div>
|
<div class="min-w-[50%] bg-[#F6FFF5] px-2 pb-1">
|
||||||
<div class="content relative min-h-2 flex-1 px-3 py-4">
|
<div class="p-1 text-center font-bold">进项</div>
|
||||||
<div
|
<div v-for="(value, type) in item.incomes" :key="type" class="p-1">
|
||||||
v-if="list.length === 0 && !loading"
|
<span class="text-gray-500">{{ type }}:</span>
|
||||||
class="col-span-3 flex items-center justify-center"
|
<span class="text-black">{{
|
||||||
>
|
`¥${value.fee}${!value.point || value.fee === value.point ? '' : `(天医币${value.point})`}`
|
||||||
<Empty />
|
}}</span>
|
||||||
</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 v-for="(fee, type) in item.consumes" :key="type" class="p-1">
|
|
||||||
<span class="text-gray-500">{{ type }}:</span>
|
|
||||||
<span class="text-black">{{ fee }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="px-2">APP用户剩余天医币 : {{ item.surplus }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
|
<div class="flex-1 bg-[#FFFBF0] px-2 pb-1">
|
||||||
|
<div class="p-1 text-center font-bold">出项</div>
|
||||||
|
<div v-for="(fee, type) in item.consumes" :key="type" class="p-1">
|
||||||
|
<span class="text-gray-500">{{ type }}:</span>
|
||||||
|
<span class="text-black">{{ fee }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="px-2">APP用户剩余天医币 : {{ item.surplus }}</div>
|
||||||
</div>
|
</div>
|
||||||
</Loading>
|
</template>
|
||||||
</Page>
|
</MonthReportView>
|
||||||
</template>
|
</template>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss"></style>
|
||||||
:deep(.ant-card-head-title) {
|
|
||||||
font-size: 16px !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Dayjs } from 'dayjs';
|
import { onMounted } from 'vue';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
import { Loading, Page } from '@vben/common-ui';
|
|
||||||
|
|
||||||
import { VbenIcon } from '@vben-core/shadcn-ui';
|
import { VbenIcon } from '@vben-core/shadcn-ui';
|
||||||
|
|
||||||
import { Button, Card, DatePicker, Empty } from 'ant-design-vue';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
|
|
||||||
import { statisticsApi } from '#/api/statistics';
|
import { statisticsApi } from '#/api/statistics';
|
||||||
|
import MonthReportView from '#/views/statistics/common/MonthReportView.vue';
|
||||||
|
|
||||||
const year = ref<Dayjs>(dayjs());
|
import { useMonthReport } from '../common/useMonthReport';
|
||||||
const disabledDate = (date: Dayjs) => date.year() > dayjs().year();
|
|
||||||
|
|
||||||
interface ReportItem {
|
interface ReportItem {
|
||||||
type: string;
|
type: string;
|
||||||
@@ -25,24 +18,12 @@ interface ReportItem {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 报表数据
|
const { year, disabledDate, list, loading, getList, downloadReport, downloadAllReport } =
|
||||||
const list = ref<ReportItem[][]>([]);
|
useMonthReport<ReportItem[]>({
|
||||||
const loading = ref<boolean>(false);
|
fetchMonth: (p) => statisticsApi.getTrainingClassStatistics(p),
|
||||||
const getList = async () => {
|
downloadFile: (p) => statisticsApi.downloadReportTrainingClass(p),
|
||||||
loading.value = true;
|
fileNameBuilder: (y, m) => `培训班报表_${y}年${m}月_文件.xlsx`,
|
||||||
list.value = [];
|
normalize: (data) => {
|
||||||
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.getTrainingClassStatistics({
|
|
||||||
year: year.value.year(),
|
|
||||||
month,
|
|
||||||
});
|
|
||||||
// 确保四种支付方式都存在,默认值为0
|
|
||||||
const paymentTypes = [
|
const paymentTypes = [
|
||||||
{ type: '微信', icon: { color: 'text-green-500', icon: 'ant-design:wechat-filled' } },
|
{ type: '微信', icon: { color: 'text-green-500', icon: 'ant-design:wechat-filled' } },
|
||||||
{ type: '支付宝', icon: { color: 'text-blue-500', icon: 'ant-design:alipay-outlined' } },
|
{ type: '支付宝', icon: { color: 'text-blue-500', icon: 'ant-design:alipay-outlined' } },
|
||||||
@@ -52,75 +33,56 @@ const getList = async () => {
|
|||||||
icon: { color: 'text-purple-500', icon: 'ant-design:pay-circle-filled' },
|
icon: { color: 'text-purple-500', icon: 'ant-design:pay-circle-filled' },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const report: ReportItem[] = paymentTypes.map((type) => {
|
return paymentTypes.map((type) => {
|
||||||
const item = data.map.find((item: any) => item.type === type.type);
|
const item = data.map.find((item: any) => item.type === type.type);
|
||||||
return {
|
return {
|
||||||
type: type.type,
|
type: type.type,
|
||||||
fee: item?.fee || 0,
|
fee: item?.fee || 0,
|
||||||
count: item?.count || 0,
|
count: item?.count || 0,
|
||||||
icon: { color: type.icon.color, icon: type.icon.icon },
|
icon: { color: type.icon.color, icon: type.icon.icon },
|
||||||
};
|
} as ReportItem;
|
||||||
});
|
});
|
||||||
list.value.push(report);
|
},
|
||||||
}
|
});
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const onUpdateYear = (v: any) => {
|
||||||
|
year.value = v;
|
||||||
|
};
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList();
|
getList();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<MonthReportView
|
||||||
<Loading class="flex h-full flex-col rounded-md bg-white" :spinning="loading">
|
:year="year"
|
||||||
<div class="search-form p-4">
|
:disabled-date="disabledDate"
|
||||||
<DatePicker
|
:loading="loading"
|
||||||
v-model:value="year"
|
:list="list"
|
||||||
picker="year"
|
:download-all="downloadAllReport"
|
||||||
:disabled-date="disabledDate"
|
:download-month="downloadReport"
|
||||||
@change="getList"
|
@update:year="onUpdateYear"
|
||||||
/>
|
@query="getList"
|
||||||
<Button type="primary" class="ml-2" @click="getList">查询</Button>
|
>
|
||||||
</div>
|
<template #default="{ item }">
|
||||||
<div class="h-2 bg-gray-100"></div>
|
<div class="-m-2 text-[16px]">
|
||||||
<div class="content relative min-h-2 flex-1 px-3 py-4">
|
<div class="flex flex-row py-2">
|
||||||
<div
|
<div v-for="(payment, index2) in item" :key="index2" class="flex-1 text-center">
|
||||||
v-if="list.length === 0 && !loading"
|
<div class="flex items-center justify-center font-bold">
|
||||||
class="col-span-3 flex items-center justify-center"
|
<VbenIcon
|
||||||
>
|
class="size-5"
|
||||||
<Empty />
|
:class="payment.icon.color"
|
||||||
</div>
|
:icon="payment.icon.icon"
|
||||||
<div v-else class="grid max-h-full grid-cols-3 gap-3 overflow-auto px-1">
|
fallback
|
||||||
<Card v-for="(item, index) in list" :key="index" :title="`${index + 1} 月`" size="small">
|
/>
|
||||||
<div class="-m-2 text-[16px]">
|
{{ payment.type }}
|
||||||
<div class="flex flex-row py-2">
|
|
||||||
<div v-for="(payment, index2) in item" :key="index2" class="flex-1 text-center">
|
|
||||||
<div class="flex items-center justify-center font-bold">
|
|
||||||
<VbenIcon
|
|
||||||
class="size-5"
|
|
||||||
:class="payment.icon.color"
|
|
||||||
:icon="payment.icon.icon"
|
|
||||||
fallback
|
|
||||||
/>
|
|
||||||
{{ payment.type }}
|
|
||||||
</div>
|
|
||||||
<!-- <div class="text-gray-500">{{ payment.count }}笔</div> -->
|
|
||||||
<div class="text-black">{{ payment.fee }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
<div class="text-black">{{ payment.fee }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Loading>
|
</template>
|
||||||
</Page>
|
</MonthReportView>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss"></style>
|
||||||
:deep(.ant-card-head-title) {
|
|
||||||
font-size: 16px !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,18 +1,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Dayjs } from 'dayjs';
|
import { onMounted } from 'vue';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
import { Loading, Page } 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';
|
import { statisticsApi } from '#/api/statistics';
|
||||||
|
import MonthReportView from '#/views/statistics/common/MonthReportView.vue';
|
||||||
|
|
||||||
const year = ref<Dayjs>(dayjs());
|
import { useMonthReport } from '../common/useMonthReport';
|
||||||
const disabledDate = (date: Dayjs) => date.year() > dayjs().year();
|
|
||||||
|
|
||||||
interface Report {
|
interface Report {
|
||||||
incomes: Record<string, number>;
|
incomes: Record<string, number>;
|
||||||
@@ -26,128 +18,73 @@ interface ReportItem {
|
|||||||
fee: number;
|
fee: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 报表数据
|
const { year, disabledDate, list, loading, getList, downloadReport, downloadAllReport } =
|
||||||
const list = ref<Report[]>([]);
|
useMonthReport<Report>({
|
||||||
const loading = ref<boolean>(false);
|
fetchMonth: (p) => statisticsApi.getVipStatistics(p),
|
||||||
const getList = async () => {
|
downloadFile: (p) => statisticsApi.downloadReportVip(p),
|
||||||
loading.value = true;
|
fileNameBuilder: (y, m) => `VIP报表_${y}年${m}月_文件.xlsx`,
|
||||||
list.value = [];
|
normalize: (data) => ({
|
||||||
try {
|
incomes: {
|
||||||
// 计算查询年份中包含哪些月份, 若为今年则只查询到当前月份,往年则查询所有月份
|
微信: 0,
|
||||||
const monthList =
|
支付宝: 0,
|
||||||
year.value.year() === dayjs().year()
|
银行: 0,
|
||||||
? Array.from({ length: dayjs().month() + 1 }, (_, i) => (i + 1).toString().padStart(2, '0'))
|
天医币: 0,
|
||||||
: Array.from({ length: 12 }, (_, i) => (i + 1).toString().padStart(2, '0'));
|
...Object.fromEntries(data.incomes.map((item: ReportItem) => [item.type, item.fee])),
|
||||||
for (const month of monthList) {
|
},
|
||||||
const data = await statisticsApi.getVipStatistics({
|
notyet: data.notyet || 0,
|
||||||
year: year.value.year(),
|
already: data.already || 0,
|
||||||
month,
|
now: data.now || 0,
|
||||||
});
|
}),
|
||||||
list.value.push({
|
});
|
||||||
incomes: {
|
|
||||||
微信: 0,
|
const onUpdateYear = (v: any) => {
|
||||||
支付宝: 0,
|
year.value = v;
|
||||||
银行: 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(() => {
|
onMounted(() => {
|
||||||
getList();
|
getList();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<MonthReportView
|
||||||
<Loading class="flex h-full flex-col rounded-md bg-white" :spinning="loading">
|
:year="year"
|
||||||
<div class="search-form p-4">
|
:disabled-date="disabledDate"
|
||||||
<DatePicker
|
:loading="loading"
|
||||||
v-model:value="year"
|
:list="list"
|
||||||
picker="year"
|
:download-all="downloadAllReport"
|
||||||
:disabled-date="disabledDate"
|
:download-month="downloadReport"
|
||||||
@change="getList"
|
@update:year="onUpdateYear"
|
||||||
/>
|
@query="getList"
|
||||||
<Button type="primary" class="ml-2" @click="getList">查询</Button>
|
>
|
||||||
<!-- <Button type="link" class="ml-2" @click="downloadAllReport">
|
<template #default="{ item }">
|
||||||
下载 {{ year.year() }} 年全部天医币报表
|
<div class="-m-2 text-[16px]">
|
||||||
</Button> -->
|
<div class="flex">
|
||||||
</div>
|
<div class="flex-1 bg-[#F6FFF5] px-2 pb-1">
|
||||||
<div class="h-2 bg-gray-100"></div>
|
<div class="p-1 text-center font-bold">收入</div>
|
||||||
<div class="content relative min-h-2 flex-1 px-3 py-4">
|
<div v-for="(fee, type) in item.incomes" :key="type" class="p-1">
|
||||||
<div
|
<span class="text-gray-500">{{ type }}:</span>
|
||||||
v-if="list.length === 0 && !loading"
|
<span class="text-black">{{ fee }}</span>
|
||||||
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>
|
</div>
|
||||||
</Card>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</Loading>
|
</template>
|
||||||
</Page>
|
</MonthReportView>
|
||||||
</template>
|
</template>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss"></style>
|
||||||
:deep(.ant-card-head-title) {
|
|
||||||
font-size: 16px !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -55,10 +55,7 @@ export function downloadFileFromBase64({ fileName, source }: DownloadOptions) {
|
|||||||
/**
|
/**
|
||||||
* 通过图片 URL 下载图片文件
|
* 通过图片 URL 下载图片文件
|
||||||
*/
|
*/
|
||||||
export async function downloadFileFromImageUrl({
|
export async function downloadFileFromImageUrl({ fileName, source }: DownloadOptions) {
|
||||||
fileName,
|
|
||||||
source,
|
|
||||||
}: DownloadOptions) {
|
|
||||||
const base64 = await urlToBase64(source);
|
const base64 = await urlToBase64(source);
|
||||||
downloadFileFromBase64({ fileName, source: base64 });
|
downloadFileFromBase64({ fileName, source: base64 });
|
||||||
}
|
}
|
||||||
@@ -87,9 +84,7 @@ export function downloadFileFromBlobPart({
|
|||||||
}: DownloadOptions<BlobPart>): void {
|
}: DownloadOptions<BlobPart>): void {
|
||||||
// 如果 data 不是 Blob,则转换为 Blob
|
// 如果 data 不是 Blob,则转换为 Blob
|
||||||
const blob =
|
const blob =
|
||||||
source instanceof Blob
|
source instanceof Blob ? source : new Blob([source], { type: 'application/octet-stream' });
|
||||||
? source
|
|
||||||
: new Blob([source], { type: 'application/octet-stream' });
|
|
||||||
|
|
||||||
// 创建对象 URL 并触发下载
|
// 创建对象 URL 并触发下载
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
|
|||||||
6
packages/@core/base/typings/src/basic.d.ts
vendored
6
packages/@core/base/typings/src/basic.d.ts
vendored
@@ -27,11 +27,15 @@ interface BasicUserInfo {
|
|||||||
/**
|
/**
|
||||||
* 用户角色
|
* 用户角色
|
||||||
*/
|
*/
|
||||||
role?: string;
|
roles?: string[];
|
||||||
/**
|
/**
|
||||||
* 用户状态
|
* 用户状态
|
||||||
*/
|
*/
|
||||||
state?: string;
|
state?: string;
|
||||||
|
/**
|
||||||
|
* 首页路径
|
||||||
|
*/
|
||||||
|
homePath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClassType = Array<object | string> | object | string;
|
type ClassType = Array<object | string> | object | string;
|
||||||
|
|||||||
@@ -22,13 +22,10 @@ class FileDownloader {
|
|||||||
* @param config 配置信息,可选。
|
* @param config 配置信息,可选。
|
||||||
* @returns 如果config.responseReturn为'body',则返回Blob(默认),否则返回RequestResponse<Blob>
|
* @returns 如果config.responseReturn为'body',则返回Blob(默认),否则返回RequestResponse<Blob>
|
||||||
*/
|
*/
|
||||||
public async download<T = Blob>(
|
public async download<T = Blob>(url: string, config?: DownloadRequestConfig): Promise<T> {
|
||||||
url: string,
|
|
||||||
config?: DownloadRequestConfig,
|
|
||||||
): Promise<T> {
|
|
||||||
const finalConfig: DownloadRequestConfig = {
|
const finalConfig: DownloadRequestConfig = {
|
||||||
responseReturn: 'body',
|
responseReturn: 'body',
|
||||||
method: 'GET',
|
method: 'post',
|
||||||
...config,
|
...config,
|
||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ interface AccessState {
|
|||||||
/**
|
/**
|
||||||
* 用户角色
|
* 用户角色
|
||||||
*/
|
*/
|
||||||
userRoles: string;
|
userRoles: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,12 +22,12 @@ export const useUserStore = defineStore('core-user', {
|
|||||||
// 设置用户信息
|
// 设置用户信息
|
||||||
this.userInfo = userInfo;
|
this.userInfo = userInfo;
|
||||||
// 设置角色信息
|
// 设置角色信息
|
||||||
const roles = userInfo?.role ?? '';
|
const roles = userInfo?.roles ?? [];
|
||||||
// 存储信息到本地
|
// 存储信息到本地
|
||||||
localStorage.setItem('userInfo', JSON.stringify(userInfo));
|
localStorage.setItem('userInfo', JSON.stringify(userInfo));
|
||||||
this.setUserRoles(roles);
|
this.setUserRoles(roles);
|
||||||
},
|
},
|
||||||
setUserRoles(roles: string) {
|
setUserRoles(roles: string[]) {
|
||||||
this.userRoles = roles;
|
this.userRoles = roles;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user