feat(statistics): 添加统计分析模块:天医币、实物、培训班。
- 新增天医币、实物和培训班报表页面 - 添加统计相关API接口 - 配置统计分析路由 - 优化SheetFooter和DialogFooter组件样式
This commit is contained in:
33
apps/finance/src/api/statistics/index.ts
Normal file
33
apps/finance/src/api/statistics/index.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export const statisticsApi = {
|
||||
/**
|
||||
* 获取天医币报表列表
|
||||
*/
|
||||
getReportTianyibi: (data: { month?: string; year: number }) => {
|
||||
return requestClient.post('common/statistics/pointStatistics', data);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取实物报表列表
|
||||
*/
|
||||
getPhysicalStatistics: (data: { month?: string; year: number }) => {
|
||||
return requestClient.post('common/statistics/physicalStatistics', data);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取培训班报表列表
|
||||
*/
|
||||
getTrainingClassStatistics: (data: { month?: string; year: number }) => {
|
||||
return requestClient.post('common/statistics/trainingClassStatistics', data);
|
||||
},
|
||||
|
||||
/**
|
||||
* 下载天医币报表
|
||||
*/
|
||||
downloadReportTianyibi: (data: { date: string }) => {
|
||||
return requestClient.post('/common/import/getImportFile', data, {
|
||||
responseType: 'blob',
|
||||
});
|
||||
},
|
||||
};
|
||||
45
apps/finance/src/router/routes/modules/statistics.ts
Normal file
45
apps/finance/src/router/routes/modules/statistics.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
meta: {
|
||||
icon: 'mdi:chart-areaspline',
|
||||
keepAlive: true,
|
||||
order: 1000,
|
||||
title: '统计分析',
|
||||
},
|
||||
name: 'Statistics',
|
||||
path: '/statistics',
|
||||
children: [
|
||||
{
|
||||
meta: {
|
||||
title: '天医币报表',
|
||||
keepAlive: true,
|
||||
},
|
||||
name: 'TianyibiReport',
|
||||
path: '/statistics/tianyibi-report',
|
||||
component: () => import('#/views/statistics/tianyibi/report.vue'),
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
title: '实物报表',
|
||||
keepAlive: true,
|
||||
},
|
||||
name: 'PhysicalReport',
|
||||
path: '/statistics/physical-report',
|
||||
component: () => import('#/views/statistics/physical/report.vue'),
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
title: '培训班报表',
|
||||
keepAlive: true,
|
||||
},
|
||||
name: 'TrainingClassReport',
|
||||
path: '/statistics/training-class-report',
|
||||
component: () => import('#/views/statistics/trainingClass/report.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
125
apps/finance/src/views/statistics/physical/report.vue
Normal file
125
apps/finance/src/views/statistics/physical/report.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { Page, Spinner } from '@vben/common-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';
|
||||
|
||||
const year = ref<Dayjs>(dayjs());
|
||||
const disabledDate = (date: Dayjs) => date.year() > dayjs().year();
|
||||
|
||||
interface ReportItem {
|
||||
type: string;
|
||||
fee: number;
|
||||
count: number;
|
||||
icon: {
|
||||
color: string;
|
||||
icon: string;
|
||||
};
|
||||
}
|
||||
|
||||
// 报表数据
|
||||
const list = ref<ReportItem[][]>([]);
|
||||
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.getPhysicalStatistics({
|
||||
year: year.value.year(),
|
||||
month,
|
||||
});
|
||||
// 确保四种支付方式都存在,默认值为0
|
||||
const paymentTypes = [
|
||||
{ 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-yellow-500', icon: 'ant-design:credit-card-filled' } },
|
||||
{
|
||||
type: '天医币',
|
||||
icon: { color: 'text-purple-500', icon: 'ant-design:pay-circle-filled' },
|
||||
},
|
||||
];
|
||||
const report: ReportItem[] = paymentTypes.map((type) => {
|
||||
const item = data.map.find((item: any) => item.type === type.type);
|
||||
return {
|
||||
type: type.type,
|
||||
fee: item?.fee || 0,
|
||||
count: item?.count || 0,
|
||||
icon: { color: type.icon.color, icon: type.icon.icon },
|
||||
};
|
||||
});
|
||||
list.value.push(report);
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
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>
|
||||
</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">
|
||||
<div class="-m-2 text-[16px]">
|
||||
<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>
|
||||
</Card>
|
||||
</div>
|
||||
</Spinner>
|
||||
</div>
|
||||
</Page>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
:deep(.ant-card-head-title) {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
</style>
|
||||
149
apps/finance/src/views/statistics/tianyibi/report.vue
Normal file
149
apps/finance/src/views/statistics/tianyibi/report.vue
Normal file
@@ -0,0 +1,149 @@
|
||||
<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>;
|
||||
consumes: Record<string, number>;
|
||||
surplus: 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.getReportTianyibi({
|
||||
year: year.value.year(),
|
||||
month,
|
||||
});
|
||||
list.value.push({
|
||||
incomes: {
|
||||
微信: 0,
|
||||
支付宝: 0,
|
||||
银行: 0,
|
||||
...Object.fromEntries(data.map.incomes.map((item: ReportItem) => [item.type, item.fee])),
|
||||
},
|
||||
consumes: {
|
||||
实物: 0,
|
||||
培训班: 0,
|
||||
vip: 0,
|
||||
课程: 0,
|
||||
...Object.fromEntries(data.map.consumes.map((item: ReportItem) => [item.type, item.fee])),
|
||||
},
|
||||
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(() => {
|
||||
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 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>
|
||||
</Card>
|
||||
</div>
|
||||
</Spinner>
|
||||
</div>
|
||||
</Page>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
:deep(.ant-card-head-title) {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
</style>
|
||||
126
apps/finance/src/views/statistics/trainingClass/report.vue
Normal file
126
apps/finance/src/views/statistics/trainingClass/report.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { Page, Spinner } from '@vben/common-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';
|
||||
|
||||
const year = ref<Dayjs>(dayjs());
|
||||
const disabledDate = (date: Dayjs) => date.year() > dayjs().year();
|
||||
|
||||
interface ReportItem {
|
||||
type: string;
|
||||
fee: number;
|
||||
count: number;
|
||||
icon: {
|
||||
color: string;
|
||||
icon: string;
|
||||
};
|
||||
}
|
||||
|
||||
// 报表数据
|
||||
const list = ref<ReportItem[][]>([]);
|
||||
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.getTrainingClassStatistics({
|
||||
year: year.value.year(),
|
||||
month,
|
||||
});
|
||||
// 确保四种支付方式都存在,默认值为0
|
||||
const paymentTypes = [
|
||||
{ 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-yellow-500', icon: 'ant-design:credit-card-filled' } },
|
||||
{
|
||||
type: '天医币',
|
||||
icon: { color: 'text-purple-500', icon: 'ant-design:pay-circle-filled' },
|
||||
},
|
||||
];
|
||||
const report: ReportItem[] = paymentTypes.map((type) => {
|
||||
const item = data.map.find((item: any) => item.type === type.type);
|
||||
return {
|
||||
type: type.type,
|
||||
fee: item?.fee || 0,
|
||||
count: item?.count || 0,
|
||||
icon: { color: type.icon.color, icon: type.icon.icon },
|
||||
};
|
||||
});
|
||||
list.value.push(report);
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
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>
|
||||
</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">
|
||||
<div class="-m-2 text-[16px]">
|
||||
<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>
|
||||
</Card>
|
||||
</div>
|
||||
</Spinner>
|
||||
</div>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.ant-card-head-title) {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,14 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
import { TabPane, Tabs } from 'ant-design-vue';
|
||||
|
||||
const activeKey = ref(1);
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<Tabs v-model:active-key="activeKey" :style="{ height: '200px' }">
|
||||
<TabPane v-for="i in 30" :key="i" :tab="`Tab-${i}`">Content of tab {{ i }}</TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@@ -27,19 +27,12 @@ const subMenu = useSubMenuContext();
|
||||
const { parentMenu, parentPaths } = useMenu();
|
||||
|
||||
const active = computed(() => props.path === rootMenu?.activePath);
|
||||
const menuIcon = computed(() =>
|
||||
active.value ? props.activeIcon || props.icon : props.icon,
|
||||
);
|
||||
const menuIcon = computed(() => (active.value ? props.activeIcon || props.icon : props.icon));
|
||||
|
||||
const isTopLevelMenuItem = computed(
|
||||
() => parentMenu.value?.type.name === 'Menu',
|
||||
);
|
||||
const isTopLevelMenuItem = computed(() => parentMenu.value?.type.name === 'Menu');
|
||||
|
||||
const collapseShowTitle = computed(
|
||||
() =>
|
||||
rootMenu.props?.collapseShowTitle &&
|
||||
isTopLevelMenuItem.value &&
|
||||
rootMenu.props.collapse,
|
||||
() => rootMenu.props?.collapseShowTitle && isTopLevelMenuItem.value && rootMenu.props.collapse,
|
||||
);
|
||||
|
||||
const showTooltip = computed(
|
||||
@@ -92,11 +85,7 @@ onBeforeUnmount(() => {
|
||||
role="menuitem"
|
||||
@click.stop="handleClick"
|
||||
>
|
||||
<VbenTooltip
|
||||
v-if="showTooltip"
|
||||
:content-class="[rootMenu.theme]"
|
||||
side="right"
|
||||
>
|
||||
<VbenTooltip v-if="showTooltip" :content-class="[rootMenu.theme]" side="right">
|
||||
<template #trigger>
|
||||
<div :class="[nsMenu.be('tooltip', 'trigger')]">
|
||||
<VbenIcon :class="nsMenu.e('icon')" :icon="menuIcon" fallback />
|
||||
@@ -109,11 +98,7 @@ onBeforeUnmount(() => {
|
||||
<slot name="title"></slot>
|
||||
</VbenTooltip>
|
||||
<div v-show="!showTooltip" :class="[e('content')]">
|
||||
<MenuBadge
|
||||
v-if="rootMenu.props.mode !== 'horizontal'"
|
||||
class="right-2"
|
||||
v-bind="props"
|
||||
/>
|
||||
<MenuBadge v-if="rootMenu.props.mode !== 'horizontal'" class="right-2" v-bind="props" />
|
||||
<VbenIcon :class="nsMenu.e('icon')" :icon="menuIcon" />
|
||||
<slot></slot>
|
||||
<slot name="title"></slot>
|
||||
|
||||
@@ -5,11 +5,7 @@ const props = defineProps<{ class?: any }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
cn('flex flex-row flex-col-reverse justify-end gap-x-2', props.class)
|
||||
"
|
||||
>
|
||||
<div :class="cn('flex flex-col-reverse justify-end gap-x-2', props.class)">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -5,11 +5,7 @@ const props = defineProps<{ class?: any }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
cn('flex flex-row flex-col-reverse justify-end gap-x-2', props.class)
|
||||
"
|
||||
>
|
||||
<div :class="cn('flex flex-col-reverse justify-end gap-x-2', props.class)">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -2,14 +2,7 @@
|
||||
import type { Props } from './types';
|
||||
|
||||
import { preferences } from '@vben-core/preferences';
|
||||
import {
|
||||
Card,
|
||||
Separator,
|
||||
Tabs,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
VbenAvatar,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
import { Card, Separator, Tabs, TabsList, TabsTrigger, VbenAvatar } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { Page } from '../../components';
|
||||
|
||||
@@ -29,15 +22,12 @@ const tabsValue = defineModel<string>('modelValue');
|
||||
<div class="flex h-full w-full">
|
||||
<Card class="w-1/6 flex-none">
|
||||
<div class="mt-4 flex h-40 flex-col items-center justify-center gap-4">
|
||||
<VbenAvatar
|
||||
:src="userInfo?.avatar ?? preferences.app.defaultAvatar"
|
||||
class="size-20"
|
||||
/>
|
||||
<VbenAvatar :src="userInfo?.avatar ?? preferences.app.defaultAvatar" class="size-20" />
|
||||
<span class="text-lg font-semibold">
|
||||
{{ userInfo?.realName ?? '' }}
|
||||
{{ userInfo?.name ?? '' }}
|
||||
</span>
|
||||
<span class="text-foreground/80 text-sm">
|
||||
{{ userInfo?.username ?? '' }}
|
||||
{{ userInfo?.name ?? '' }}
|
||||
</span>
|
||||
</div>
|
||||
<Separator class="my-4" />
|
||||
|
||||
@@ -219,10 +219,16 @@ const headerSlots = computed(() => {
|
||||
:z-index="preferences.app.zIndex"
|
||||
@side-mouse-leave="handleSideMouseLeave"
|
||||
@toggle-sidebar="toggleSidebar"
|
||||
@update:sidebar-collapse="(value: boolean) => updatePreferences({ sidebar: { collapsed: value } })"
|
||||
@update:sidebar-collapse="
|
||||
(value: boolean) => updatePreferences({ sidebar: { collapsed: value } })
|
||||
"
|
||||
@update:sidebar-enable="(value: boolean) => updatePreferences({ sidebar: { enable: value } })"
|
||||
@update:sidebar-expand-on-hover="(value: boolean) => updatePreferences({ sidebar: { expandOnHover: value } })"
|
||||
@update:sidebar-extra-collapse="(value: boolean) => updatePreferences({ sidebar: { extraCollapse: value } })"
|
||||
@update:sidebar-expand-on-hover="
|
||||
(value: boolean) => updatePreferences({ sidebar: { expandOnHover: value } })
|
||||
"
|
||||
@update:sidebar-extra-collapse="
|
||||
(value: boolean) => updatePreferences({ sidebar: { extraCollapse: value } })
|
||||
"
|
||||
>
|
||||
<!-- logo -->
|
||||
<template #logo>
|
||||
@@ -330,7 +336,11 @@ const headerSlots = computed(() => {
|
||||
</template>
|
||||
|
||||
<template #tabbar>
|
||||
<LayoutTabbar v-if="preferences.tabbar.enable" :show-icon="preferences.tabbar.showIcon" :theme="theme" />
|
||||
<LayoutTabbar
|
||||
v-if="preferences.tabbar.enable"
|
||||
:show-icon="preferences.tabbar.showIcon"
|
||||
:theme="theme"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 主体内容 -->
|
||||
@@ -361,7 +371,10 @@ const headerSlots = computed(() => {
|
||||
</Transition>
|
||||
|
||||
<template v-if="preferencesButtonPosition.fixed">
|
||||
<Preferences class="z-100 fixed bottom-20 right-0" @clear-preferences-and-logout="clearPreferencesAndLogout" />
|
||||
<Preferences
|
||||
class="z-100 fixed bottom-20 right-0"
|
||||
@clear-preferences-and-logout="clearPreferencesAndLogout"
|
||||
/>
|
||||
</template>
|
||||
<VbenBackTop />
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user