Compare commits
7 Commits
963e2a8d39
...
044db57c67
| Author | SHA1 | Date | |
|---|---|---|---|
| 044db57c67 | |||
| 8971243f23 | |||
| 32afef3e4e | |||
| dab7730216 | |||
| f1c50921de | |||
| dff302aae8 | |||
| 77c1b37f2e |
@@ -39,7 +39,10 @@ export namespace AuthApi {
|
|||||||
* 登录
|
* 登录
|
||||||
*/
|
*/
|
||||||
export async function loginApi(data: AuthApi.LoginParams) {
|
export async function loginApi(data: AuthApi.LoginParams) {
|
||||||
return requestClient.post<AuthApi.LoginResult<AuthApi.UserToken, AuthApi.UserInfo>>('/auth/login', data);
|
return requestClient.post<AuthApi.LoginResult<AuthApi.UserToken, AuthApi.UserInfo>>(
|
||||||
|
'/auth/login',
|
||||||
|
data,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,12 +16,19 @@ export const reconciliateBillsApi = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 人工对账--添加订单后对账
|
* 人工对账--添加订单后对账 -- 单个
|
||||||
*/
|
*/
|
||||||
manualCheckCreated: (data: any) => {
|
manualCheckCreated: (data: any) => {
|
||||||
return requestClient.post('/common/payment/checkoffByAddOrder', { list: data });
|
return requestClient.post('/common/payment/checkoffByAddOrder', { list: data });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 人工对账--添加订单后对账 -- 批量
|
||||||
|
*/
|
||||||
|
manualCheckCreatedBatch: (data: any) => {
|
||||||
|
return requestClient.post('/common/payment/checkoffByBatch', data);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自动对账
|
* 自动对账
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import type { RequestClientOptions } from '@vben/request';
|
import type { RequestClientOptions } from '@vben/request';
|
||||||
|
|
||||||
import { useAppConfig } from '@vben/hooks';
|
import { useAppConfig } from '@vben/hooks';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import {
|
import {
|
||||||
authenticateResponseInterceptor,
|
authenticateResponseInterceptor,
|
||||||
@@ -39,11 +40,6 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
|||||||
if (preferences.app.loginExpiredMode === 'modal' && accessStore.isAccessChecked) {
|
if (preferences.app.loginExpiredMode === 'modal' && accessStore.isAccessChecked) {
|
||||||
accessStore.setLoginExpired(true);
|
accessStore.setLoginExpired(true);
|
||||||
} else {
|
} else {
|
||||||
// 显示登录过期提示
|
|
||||||
message.error({
|
|
||||||
content: '您的登录状态已过期,请重新登录',
|
|
||||||
duration: 3,
|
|
||||||
});
|
|
||||||
// 短暂延迟后跳转,让用户看到提示
|
// 短暂延迟后跳转,让用户看到提示
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
authStore.logout();
|
authStore.logout();
|
||||||
@@ -196,7 +192,7 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
|||||||
|
|
||||||
// 如果是401错误,已经在自定义拦截器中处理了,这里不需要额外提示
|
// 如果是401错误,已经在自定义拦截器中处理了,这里不需要额外提示
|
||||||
if (responseCode === 401) {
|
if (responseCode === 401) {
|
||||||
return Promise.reject(error);
|
return Promise.reject($t('ui.fallback.http.unauthorized'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果有后端返回的错误信息,优先显示
|
// 如果有后端返回的错误信息,优先显示
|
||||||
|
|||||||
67
apps/finance/src/api/statistics/index.ts
Normal file
67
apps/finance/src/api/statistics/index.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export const statisticsApi = {
|
||||||
|
/**
|
||||||
|
* 获取天医币报表
|
||||||
|
* @param data 请求参数
|
||||||
|
* @param data.month 月份
|
||||||
|
* @param data.year 年份
|
||||||
|
* @returns 天医币报表数据
|
||||||
|
*/
|
||||||
|
getReportTianyibi: (data: { month?: string; year: number }) => {
|
||||||
|
return requestClient.post('common/statistics/pointStatistics', data);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取实物报表
|
||||||
|
* @param data 请求参数
|
||||||
|
* @param data.month 月份
|
||||||
|
* @param data.year 年份
|
||||||
|
* @returns 实物报表数据
|
||||||
|
*/
|
||||||
|
getPhysicalStatistics: (data: { month?: string; year: number }) => {
|
||||||
|
return requestClient.post('common/statistics/physicalStatistics', data);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取培训班报表
|
||||||
|
* @param data 请求参数
|
||||||
|
* @param data.month 月份
|
||||||
|
* @param data.year 年份
|
||||||
|
* @returns 培训班报表数据
|
||||||
|
*/
|
||||||
|
getTrainingClassStatistics: (data: { month?: string; year: number }) => {
|
||||||
|
return requestClient.post('common/statistics/trainingClassStatistics', data);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取VIP报表
|
||||||
|
* @param data 请求参数
|
||||||
|
* @param data.month 月份
|
||||||
|
* @param data.year 年份
|
||||||
|
* @returns VIP报表数据
|
||||||
|
*/
|
||||||
|
getVipStatistics: (data: { month?: string; year: number }) => {
|
||||||
|
return requestClient.post('common/statistics/vipStatistics', data);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取课程报表
|
||||||
|
* @param data 请求参数
|
||||||
|
* @param data.month 月份
|
||||||
|
* @param data.year 年份
|
||||||
|
* @returns 课程报表数据
|
||||||
|
*/
|
||||||
|
getCourseStatistics: (data: { month?: string; year: number }) => {
|
||||||
|
return requestClient.post('common/statistics/courseStatistics', data);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载天医币报表
|
||||||
|
*/
|
||||||
|
downloadReportTianyibi: (data: { date: string }) => {
|
||||||
|
return requestClient.post('/common/import/getImportFile', data, {
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -34,16 +34,18 @@ const VNodes = defineComponent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const items = ref<{ value: string }[]>(props.options || []);
|
const items = ref<{ value: string }[]>(props.options || []);
|
||||||
const value = ref(props.value);
|
// const value = computed(() => props.value || items.value[0]?.value);
|
||||||
|
const value = ref(props.value || items.value[0]?.value);
|
||||||
const inputRef = ref();
|
const inputRef = ref();
|
||||||
const inputValue = ref('');
|
const inputValue = ref('');
|
||||||
|
|
||||||
// 监听 props.value 变化,更新本地 value
|
// 监听 props.value 变化,更新本地 value
|
||||||
watch(
|
watch(
|
||||||
() => props.value,
|
() => props.options,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
value.value = newValue;
|
value.value = props.value && props.value !== '' ? props.value : newValue.value[0]?.value;
|
||||||
},
|
},
|
||||||
|
{ immediate: true, deep: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
// 监听本地 value 变化,触发 update:value 事件
|
// 监听本地 value 变化,触发 update:value 事件
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ const renderEditComponent = (column: any, row: any) => {
|
|||||||
const options = ref<any[]>([]);
|
const options = ref<any[]>([]);
|
||||||
|
|
||||||
// 创建一个唯一的缓存键,基于 row 的关键属性
|
// 创建一个唯一的缓存键,基于 row 的关键属性
|
||||||
const cacheKey = `${row.paymentId}_${row.come}_${row.orderType}_${row.courseId}`;
|
const cacheKey = `${row.paymentId}_${row[gridOptions.value?.cardConfig?.keyField || 'id']}`;
|
||||||
|
|
||||||
// 检查缓存中是否已有数据
|
// 检查缓存中是否已有数据
|
||||||
if (selectOptionsCache[cacheKey]) {
|
if (selectOptionsCache[cacheKey]) {
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ export interface CardListPagination {
|
|||||||
export interface CardListOptions<T = any> {
|
export interface CardListOptions<T = any> {
|
||||||
/** 列配置 */
|
/** 列配置 */
|
||||||
columns?: CardListColumn<T>[];
|
columns?: CardListColumn<T>[];
|
||||||
|
/** 卡片配置 */
|
||||||
|
cardConfig?: {
|
||||||
|
/** 卡片主键字段名 */
|
||||||
|
keyField?: string;
|
||||||
|
};
|
||||||
/** 数据源 */
|
/** 数据源 */
|
||||||
data?: T[];
|
data?: T[];
|
||||||
/** 是否显示卡片标题 */
|
/** 是否显示卡片标题 */
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import type { RouteRecordRaw } from 'vue-router';
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
import { $t } from '#/locales';
|
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'lucide:layout-dashboard',
|
icon: 'lucide:layout-dashboard',
|
||||||
order: -1,
|
order: -1,
|
||||||
title: $t('page.dashboard.title'),
|
title: '首页',
|
||||||
},
|
},
|
||||||
name: 'Dashboard',
|
name: 'Dashboard',
|
||||||
path: '/dashboard',
|
path: '/dashboard',
|
||||||
component: () => import('#/views/dashboard/analytics/index.vue'),
|
component: () => import('#/views/dashboard/workspace/index.vue'),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { RouteRecordRaw } from 'vue-router';
|
|||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'ic:baseline-view-in-ar',
|
icon: 'ant-design:book-twotone',
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
order: 1000,
|
order: 1000,
|
||||||
title: '入账管理',
|
title: '入账管理',
|
||||||
|
|||||||
63
apps/finance/src/router/routes/modules/statistics.ts
Normal file
63
apps/finance/src/router/routes/modules/statistics.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
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'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
title: 'VIP报表',
|
||||||
|
keepAlive: true,
|
||||||
|
},
|
||||||
|
name: 'VipReport',
|
||||||
|
path: '/statistics/vip-report',
|
||||||
|
component: () => import('#/views/statistics/vip/report.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
title: '课程报表',
|
||||||
|
keepAlive: true,
|
||||||
|
},
|
||||||
|
name: 'CourseReport',
|
||||||
|
path: '/statistics/course-report',
|
||||||
|
component: () => import('#/views/statistics/course/report.vue'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default routes;
|
||||||
@@ -3,7 +3,7 @@ import type { RouteRecordRaw } from 'vue-router';
|
|||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'ic:baseline-view-in-ar',
|
icon: 'ant-design:setting-twotone',
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
order: 1000,
|
order: 1000,
|
||||||
title: '系统管理',
|
title: '系统管理',
|
||||||
|
|||||||
@@ -1,250 +1,249 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
// import type {
|
||||||
WorkbenchProjectItem,
|
// WorkbenchProjectItem,
|
||||||
WorkbenchQuickNavItem,
|
// WorkbenchQuickNavItem,
|
||||||
WorkbenchTodoItem,
|
// WorkbenchTodoItem,
|
||||||
WorkbenchTrendItem,
|
// WorkbenchTrendItem,
|
||||||
} from '@vben/common-ui';
|
// } from '@vben/common-ui';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
// import { ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
// import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import {
|
// import {
|
||||||
AnalysisChartCard,
|
// AnalysisChartCard,
|
||||||
WorkbenchHeader,
|
// WorkbenchHeader,
|
||||||
WorkbenchProject,
|
// WorkbenchProject,
|
||||||
WorkbenchQuickNav,
|
// WorkbenchQuickNav,
|
||||||
WorkbenchTodo,
|
// WorkbenchTodo,
|
||||||
WorkbenchTrends,
|
// WorkbenchTrends,
|
||||||
} from '@vben/common-ui';
|
// } from '@vben/common-ui';
|
||||||
|
import { WorkbenchHeader } from '@vben/common-ui';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { useUserStore } from '@vben/stores';
|
import { useUserStore } from '@vben/stores';
|
||||||
import { openWindow } from '@vben/utils';
|
// import { openWindow } from '@vben/utils';
|
||||||
|
|
||||||
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
|
// import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
// 这是一个示例数据,实际项目中需要根据实际情况进行调整
|
// 这是一个示例数据,实际项目中需要根据实际情况进行调整
|
||||||
// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转
|
// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转
|
||||||
// 例如:url: /dashboard/workspace
|
// 例如:url: /dashboard/workspace
|
||||||
const projectItems: WorkbenchProjectItem[] = [
|
// const projectItems: WorkbenchProjectItem[] = [
|
||||||
{
|
// {
|
||||||
color: '',
|
// color: '',
|
||||||
content: '不要等待机会,而要创造机会。',
|
// content: '不要等待机会,而要创造机会。',
|
||||||
date: '2021-04-01',
|
// date: '2021-04-01',
|
||||||
group: '开源组',
|
// group: '开源组',
|
||||||
icon: 'carbon:logo-github',
|
// icon: 'carbon:logo-github',
|
||||||
title: 'Github',
|
// title: 'Github',
|
||||||
url: 'https://github.com',
|
// url: 'https://github.com',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
color: '#3fb27f',
|
// color: '#3fb27f',
|
||||||
content: '现在的你决定将来的你。',
|
// content: '现在的你决定将来的你。',
|
||||||
date: '2021-04-01',
|
// date: '2021-04-01',
|
||||||
group: '算法组',
|
// group: '算法组',
|
||||||
icon: 'ion:logo-vue',
|
// icon: 'ion:logo-vue',
|
||||||
title: 'Vue',
|
// title: 'Vue',
|
||||||
url: 'https://vuejs.org',
|
// url: 'https://vuejs.org',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
color: '#e18525',
|
// color: '#e18525',
|
||||||
content: '没有什么才能比努力更重要。',
|
// content: '没有什么才能比努力更重要。',
|
||||||
date: '2021-04-01',
|
// date: '2021-04-01',
|
||||||
group: '上班摸鱼',
|
// group: '上班摸鱼',
|
||||||
icon: 'ion:logo-html5',
|
// icon: 'ion:logo-html5',
|
||||||
title: 'Html5',
|
// title: 'Html5',
|
||||||
url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
|
// url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
color: '#bf0c2c',
|
// color: '#bf0c2c',
|
||||||
content: '热情和欲望可以突破一切难关。',
|
// content: '热情和欲望可以突破一切难关。',
|
||||||
date: '2021-04-01',
|
// date: '2021-04-01',
|
||||||
group: 'UI',
|
// group: 'UI',
|
||||||
icon: 'ion:logo-angular',
|
// icon: 'ion:logo-angular',
|
||||||
title: 'Angular',
|
// title: 'Angular',
|
||||||
url: 'https://angular.io',
|
// url: 'https://angular.io',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
color: '#00d8ff',
|
// color: '#00d8ff',
|
||||||
content: '健康的身体是实现目标的基石。',
|
// content: '健康的身体是实现目标的基石。',
|
||||||
date: '2021-04-01',
|
// date: '2021-04-01',
|
||||||
group: '技术牛',
|
// group: '技术牛',
|
||||||
icon: 'bx:bxl-react',
|
// icon: 'bx:bxl-react',
|
||||||
title: 'React',
|
// title: 'React',
|
||||||
url: 'https://reactjs.org',
|
// url: 'https://reactjs.org',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
color: '#EBD94E',
|
// color: '#EBD94E',
|
||||||
content: '路是走出来的,而不是空想出来的。',
|
// content: '路是走出来的,而不是空想出来的。',
|
||||||
date: '2021-04-01',
|
// date: '2021-04-01',
|
||||||
group: '架构组',
|
// group: '架构组',
|
||||||
icon: 'ion:logo-javascript',
|
// icon: 'ion:logo-javascript',
|
||||||
title: 'Js',
|
// title: 'Js',
|
||||||
url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
|
// url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
|
||||||
},
|
// },
|
||||||
];
|
// ];
|
||||||
|
|
||||||
// 同样,这里的 url 也可以使用以 http 开头的外部链接
|
// 同样,这里的 url 也可以使用以 http 开头的外部链接
|
||||||
const quickNavItems: WorkbenchQuickNavItem[] = [
|
// const quickNavItems: WorkbenchQuickNavItem[] = [
|
||||||
{
|
// {
|
||||||
color: '#1fdaca',
|
// color: '#1fdaca',
|
||||||
icon: 'ion:home-outline',
|
// icon: 'ion:home-outline',
|
||||||
title: '首页',
|
// title: '首页',
|
||||||
url: '/',
|
// url: '/',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
color: '#bf0c2c',
|
// color: '#bf0c2c',
|
||||||
icon: 'ion:grid-outline',
|
// icon: 'ion:grid-outline',
|
||||||
title: '仪表盘',
|
// title: '仪表盘',
|
||||||
url: '/dashboard',
|
// url: '/dashboard',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
color: '#e18525',
|
// color: '#e18525',
|
||||||
icon: 'ion:layers-outline',
|
// icon: 'ion:layers-outline',
|
||||||
title: '组件',
|
// title: '组件',
|
||||||
url: '/demos/features/icons',
|
// url: '/demos/features/icons',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
color: '#3fb27f',
|
// color: '#3fb27f',
|
||||||
icon: 'ion:settings-outline',
|
// icon: 'ion:settings-outline',
|
||||||
title: '系统管理',
|
// title: '系统管理',
|
||||||
url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整
|
// url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
color: '#4daf1bc9',
|
// color: '#4daf1bc9',
|
||||||
icon: 'ion:key-outline',
|
// icon: 'ion:key-outline',
|
||||||
title: '权限管理',
|
// title: '权限管理',
|
||||||
url: '/demos/access/page-control',
|
// url: '/demos/access/page-control',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
color: '#00d8ff',
|
// color: '#00d8ff',
|
||||||
icon: 'ion:bar-chart-outline',
|
// icon: 'ion:bar-chart-outline',
|
||||||
title: '图表',
|
// title: '图表',
|
||||||
url: '/analytics',
|
// url: '/analytics',
|
||||||
},
|
// },
|
||||||
];
|
// ];
|
||||||
|
|
||||||
const todoItems = ref<WorkbenchTodoItem[]>([
|
// const todoItems = ref<WorkbenchTodoItem[]>([
|
||||||
{
|
// {
|
||||||
completed: false,
|
// completed: false,
|
||||||
content: `审查最近提交到Git仓库的前端代码,确保代码质量和规范。`,
|
// content: `审查最近提交到Git仓库的前端代码,确保代码质量和规范。`,
|
||||||
date: '2024-07-30 11:00:00',
|
// date: '2024-07-30 11:00:00',
|
||||||
title: '审查前端代码提交',
|
// title: '审查前端代码提交',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
completed: true,
|
// completed: true,
|
||||||
content: `检查并优化系统性能,降低CPU使用率。`,
|
// content: `检查并优化系统性能,降低CPU使用率。`,
|
||||||
date: '2024-07-30 11:00:00',
|
// date: '2024-07-30 11:00:00',
|
||||||
title: '系统性能优化',
|
// title: '系统性能优化',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
completed: false,
|
// completed: false,
|
||||||
content: `进行系统安全检查,确保没有安全漏洞或未授权的访问。 `,
|
// content: `进行系统安全检查,确保没有安全漏洞或未授权的访问。 `,
|
||||||
date: '2024-07-30 11:00:00',
|
// date: '2024-07-30 11:00:00',
|
||||||
title: '安全检查',
|
// title: '安全检查',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
completed: false,
|
// completed: false,
|
||||||
content: `更新项目中的所有npm依赖包,确保使用最新版本。`,
|
// content: `更新项目中的所有npm依赖包,确保使用最新版本。`,
|
||||||
date: '2024-07-30 11:00:00',
|
// date: '2024-07-30 11:00:00',
|
||||||
title: '更新项目依赖',
|
// title: '更新项目依赖',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
completed: false,
|
// completed: false,
|
||||||
content: `修复用户报告的页面UI显示问题,确保在不同浏览器中显示一致。 `,
|
// content: `修复用户报告的页面UI显示问题,确保在不同浏览器中显示一致。 `,
|
||||||
date: '2024-07-30 11:00:00',
|
// date: '2024-07-30 11:00:00',
|
||||||
title: '修复UI显示问题',
|
// title: '修复UI显示问题',
|
||||||
},
|
// },
|
||||||
]);
|
// ]);
|
||||||
const trendItems: WorkbenchTrendItem[] = [
|
// const trendItems: WorkbenchTrendItem[] = [
|
||||||
{
|
// {
|
||||||
avatar: 'svg:avatar-1',
|
// avatar: 'svg:avatar-1',
|
||||||
content: `在 <a>开源组</a> 创建了项目 <a>Vue</a>`,
|
// content: `在 <a>开源组</a> 创建了项目 <a>Vue</a>`,
|
||||||
date: '刚刚',
|
// date: '刚刚',
|
||||||
title: '威廉',
|
// title: '威廉',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
avatar: 'svg:avatar-2',
|
// avatar: 'svg:avatar-2',
|
||||||
content: `关注了 <a>威廉</a> `,
|
// content: `关注了 <a>威廉</a> `,
|
||||||
date: '1个小时前',
|
// date: '1个小时前',
|
||||||
title: '艾文',
|
// title: '艾文',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
avatar: 'svg:avatar-3',
|
// avatar: 'svg:avatar-3',
|
||||||
content: `发布了 <a>个人动态</a> `,
|
// content: `发布了 <a>个人动态</a> `,
|
||||||
date: '1天前',
|
// date: '1天前',
|
||||||
title: '克里斯',
|
// title: '克里斯',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
avatar: 'svg:avatar-4',
|
// avatar: 'svg:avatar-4',
|
||||||
content: `发表文章 <a>如何编写一个Vite插件</a> `,
|
// content: `发表文章 <a>如何编写一个Vite插件</a> `,
|
||||||
date: '2天前',
|
// date: '2天前',
|
||||||
title: 'Vben',
|
// title: 'Vben',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
avatar: 'svg:avatar-1',
|
// avatar: 'svg:avatar-1',
|
||||||
content: `回复了 <a>杰克</a> 的问题 <a>如何进行项目优化?</a>`,
|
// content: `回复了 <a>杰克</a> 的问题 <a>如何进行项目优化?</a>`,
|
||||||
date: '3天前',
|
// date: '3天前',
|
||||||
title: '皮特',
|
// title: '皮特',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
avatar: 'svg:avatar-2',
|
// avatar: 'svg:avatar-2',
|
||||||
content: `关闭了问题 <a>如何运行项目</a> `,
|
// content: `关闭了问题 <a>如何运行项目</a> `,
|
||||||
date: '1周前',
|
// date: '1周前',
|
||||||
title: '杰克',
|
// title: '杰克',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
avatar: 'svg:avatar-3',
|
// avatar: 'svg:avatar-3',
|
||||||
content: `发布了 <a>个人动态</a> `,
|
// content: `发布了 <a>个人动态</a> `,
|
||||||
date: '1周前',
|
// date: '1周前',
|
||||||
title: '威廉',
|
// title: '威廉',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
avatar: 'svg:avatar-4',
|
// avatar: 'svg:avatar-4',
|
||||||
content: `推送了代码到 <a>Github</a>`,
|
// content: `推送了代码到 <a>Github</a>`,
|
||||||
date: '2021-04-01 20:00',
|
// date: '2021-04-01 20:00',
|
||||||
title: '威廉',
|
// title: '威廉',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
avatar: 'svg:avatar-4',
|
// avatar: 'svg:avatar-4',
|
||||||
content: `发表文章 <a>如何编写使用 Admin Vben</a> `,
|
// content: `发表文章 <a>如何编写使用 Admin Vben</a> `,
|
||||||
date: '2021-03-01 20:00',
|
// date: '2021-03-01 20:00',
|
||||||
title: 'Vben',
|
// title: 'Vben',
|
||||||
},
|
// },
|
||||||
];
|
// ];
|
||||||
|
|
||||||
const router = useRouter();
|
// const router = useRouter();
|
||||||
|
|
||||||
// 这是一个示例方法,实际项目中需要根据实际情况进行调整
|
// 这是一个示例方法,实际项目中需要根据实际情况进行调整
|
||||||
// This is a sample method, adjust according to the actual project requirements
|
// This is a sample method, adjust according to the actual project requirements
|
||||||
function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
|
// function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
|
||||||
if (nav.url?.startsWith('http')) {
|
// if (nav.url?.startsWith('http')) {
|
||||||
openWindow(nav.url);
|
// openWindow(nav.url);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
if (nav.url?.startsWith('/')) {
|
// if (nav.url?.startsWith('/')) {
|
||||||
router.push(nav.url).catch((error) => {
|
// router.push(nav.url).catch((error) => {
|
||||||
console.error('Navigation failed:', error);
|
// console.error('Navigation failed:', error);
|
||||||
});
|
// });
|
||||||
} else {
|
// } else {
|
||||||
console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);
|
// console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="p-5">
|
<div class="p-5">
|
||||||
<WorkbenchHeader
|
<WorkbenchHeader :avatar="userStore.userInfo?.avatar || preferences.app.defaultAvatar">
|
||||||
:avatar="userStore.userInfo?.avatar || preferences.app.defaultAvatar"
|
|
||||||
>
|
|
||||||
<template #title>
|
<template #title>
|
||||||
早安, {{ userStore.userInfo?.realName }}, 开始您一天的工作吧!
|
您好, {{ userStore.userInfo?.name || userStore.userInfo?.account }}, 欢迎登录财务系统!
|
||||||
</template>
|
</template>
|
||||||
<template #description> 今日晴,20℃ - 32℃! </template>
|
<!-- <template #description> 今日晴,20℃ - 32℃! </template> -->
|
||||||
</WorkbenchHeader>
|
</WorkbenchHeader>
|
||||||
|
|
||||||
<div class="mt-5 flex flex-col lg:flex-row">
|
<!-- <div class="mt-5 flex flex-col lg:flex-row">
|
||||||
<div class="mr-4 w-full lg:w-3/5">
|
<div class="mr-4 w-full lg:w-3/5">
|
||||||
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" />
|
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" />
|
||||||
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
|
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
|
||||||
@@ -261,6 +260,6 @@ function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
|
|||||||
<AnalyticsVisitsSource />
|
<AnalyticsVisitsSource />
|
||||||
</AnalysisChartCard>
|
</AnalysisChartCard>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ interface RecommendedUser {
|
|||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
errorItem: CreateOrderType[];
|
errorItem: CreateOrderType[];
|
||||||
|
isBatchMode: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
@@ -73,7 +74,7 @@ const gridOptions = computed(() => ({
|
|||||||
paymentId: row.paymentId,
|
paymentId: row.paymentId,
|
||||||
come: row.come,
|
come: row.come,
|
||||||
orderType: row.orderType,
|
orderType: row.orderType,
|
||||||
courseId: row.courseId,
|
courseId: row.courseId ?? '',
|
||||||
vipType: row.vipType ?? '',
|
vipType: row.vipType ?? '',
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
@@ -88,6 +89,7 @@ const gridOptions = computed(() => ({
|
|||||||
field: 'tel',
|
field: 'tel',
|
||||||
title: '用户手机号',
|
title: '用户手机号',
|
||||||
colSpan: 2,
|
colSpan: 2,
|
||||||
|
show: () => !props.isBatchMode,
|
||||||
},
|
},
|
||||||
{ editRender: { name: 'Input' }, field: 'orderMoney', title: '订单金额' },
|
{ editRender: { name: 'Input' }, field: 'orderMoney', title: '订单金额' },
|
||||||
{ editRender: { name: 'Input' }, field: 'realMoney', title: '实际金额' },
|
{ editRender: { name: 'Input' }, field: 'realMoney', title: '实际金额' },
|
||||||
@@ -99,7 +101,8 @@ const gridOptions = computed(() => ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
// 仅VIP 和课程订单 显示
|
// 仅VIP 和课程订单 显示
|
||||||
show: (row: CreateOrderType) => row.orderType === '1' || row.orderType === '2',
|
show: (row: CreateOrderType) =>
|
||||||
|
!props.isBatchMode && (row.orderType === '1' || row.orderType === '2'),
|
||||||
field: 'startTime',
|
field: 'startTime',
|
||||||
title: '开始时间',
|
title: '开始时间',
|
||||||
},
|
},
|
||||||
@@ -108,13 +111,13 @@ const gridOptions = computed(() => ({
|
|||||||
name: 'Select',
|
name: 'Select',
|
||||||
props: {
|
props: {
|
||||||
options: [
|
options: [
|
||||||
{ label: '1个月', value: '1' },
|
{ label: '1个月', value: '30' },
|
||||||
{ label: '3个月', value: '3' },
|
{ label: '3个月', value: '90' },
|
||||||
{ label: '半年', value: '6' },
|
{ label: '半年', value: '180' },
|
||||||
{ label: '一年', value: '12' },
|
{ label: '一年', value: '365' },
|
||||||
{ label: '两年', value: '24' },
|
{ label: '两年', value: '730' },
|
||||||
{ label: '三年', value: '36' },
|
{ label: '三年', value: '1095' },
|
||||||
{ label: '四年', value: '48' },
|
{ label: '四年', value: '1460' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -124,6 +127,9 @@ const gridOptions = computed(() => ({
|
|||||||
title: '到期时间',
|
title: '到期时间',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
cardConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
},
|
||||||
showTitle: true,
|
showTitle: true,
|
||||||
titleField: 'productName',
|
titleField: 'productName',
|
||||||
gridColumns: 3,
|
gridColumns: 3,
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ const props = withDefaults(
|
|||||||
const emit = defineEmits(['completeCheck', 'deletedChecked']);
|
const emit = defineEmits(['completeCheck', 'deletedChecked']);
|
||||||
|
|
||||||
interface RowType {
|
interface RowType {
|
||||||
productId: number;
|
id: string;
|
||||||
|
productId: string;
|
||||||
year: string;
|
year: string;
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
@@ -34,12 +35,12 @@ function transformData(rows: RowType[]) {
|
|||||||
orderType: '4',
|
orderType: '4',
|
||||||
paymentId: props.payment?.id || '',
|
paymentId: props.payment?.id || '',
|
||||||
productName: row.title,
|
productName: row.title,
|
||||||
productId: row.productId,
|
productId: row.productId.slice(1),
|
||||||
catalogueId: '',
|
catalogueId: '',
|
||||||
orderMoney: '',
|
orderMoney: '',
|
||||||
realMoney: '',
|
realMoney: String(props.payment?.fee) || '',
|
||||||
districtMoney: '',
|
districtMoney: '',
|
||||||
startTime: props.payment?.ctime || '',
|
startTime: '',
|
||||||
endTime: '',
|
endTime: '',
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -35,11 +35,11 @@ function transformData(rows: RowType[]) {
|
|||||||
productId: '',
|
productId: '',
|
||||||
catalogueId: '',
|
catalogueId: '',
|
||||||
orderMoney: '',
|
orderMoney: '',
|
||||||
realMoney: '',
|
realMoney: String(props.payment?.fee) || '',
|
||||||
districtMoney: '',
|
districtMoney: '',
|
||||||
startTime: props.payment?.ctime || '',
|
startTime: props.payment?.ctime || '',
|
||||||
endTime: '',
|
endTime: '',
|
||||||
vipType: row.type,
|
vipType: row.type.slice(1),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ const props = withDefaults(
|
|||||||
const emit = defineEmits(['completeCheck', 'deletedChecked']);
|
const emit = defineEmits(['completeCheck', 'deletedChecked']);
|
||||||
|
|
||||||
interface RowType {
|
interface RowType {
|
||||||
courseId: number;
|
courseId: string;
|
||||||
productId: number;
|
productId: string;
|
||||||
price: number;
|
price: number;
|
||||||
productName: string;
|
productName: string;
|
||||||
}
|
}
|
||||||
@@ -35,10 +35,10 @@ function transformData(rows: RowType[]) {
|
|||||||
orderType: '2',
|
orderType: '2',
|
||||||
paymentId: props.payment?.id || '',
|
paymentId: props.payment?.id || '',
|
||||||
productName: row.productName,
|
productName: row.productName,
|
||||||
productId: row.productId,
|
productId: row.productId.slice(1),
|
||||||
catalogueId: '',
|
catalogueId: '',
|
||||||
orderMoney: String(row.price),
|
orderMoney: String(row.price),
|
||||||
realMoney: '',
|
realMoney: String(props.payment?.fee) || '',
|
||||||
districtMoney: '',
|
districtMoney: '',
|
||||||
startTime: props.payment?.ctime || '',
|
startTime: props.payment?.ctime || '',
|
||||||
endTime: '',
|
endTime: '',
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ function transformData(rows: RowType[]) {
|
|||||||
productId: '',
|
productId: '',
|
||||||
catalogueId: row.catalogueId,
|
catalogueId: row.catalogueId,
|
||||||
orderMoney: '',
|
orderMoney: '',
|
||||||
realMoney: '',
|
realMoney: String(props.payment?.fee) || '',
|
||||||
districtMoney: '',
|
districtMoney: '',
|
||||||
startTime: props.payment?.ctime || '',
|
startTime: props.payment?.ctime || '',
|
||||||
endTime: '',
|
endTime: '',
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const props = withDefaults(
|
|||||||
const emit = defineEmits(['completeCheck', 'deletedChecked']);
|
const emit = defineEmits(['completeCheck', 'deletedChecked']);
|
||||||
|
|
||||||
interface RowType {
|
interface RowType {
|
||||||
id: number;
|
id: string;
|
||||||
price: number;
|
price: number;
|
||||||
productName: string;
|
productName: string;
|
||||||
}
|
}
|
||||||
@@ -34,10 +34,10 @@ function transformData(rows: RowType[]) {
|
|||||||
orderType: '3',
|
orderType: '3',
|
||||||
paymentId: props.payment?.id || '',
|
paymentId: props.payment?.id || '',
|
||||||
productName: row.productName,
|
productName: row.productName,
|
||||||
productId: row.id,
|
productId: row.id.slice(1),
|
||||||
catalogueId: '',
|
catalogueId: '',
|
||||||
orderMoney: String(row.price),
|
orderMoney: String(row.price),
|
||||||
realMoney: '',
|
realMoney: String(props.payment?.fee) || '',
|
||||||
districtMoney: '',
|
districtMoney: '',
|
||||||
startTime: '',
|
startTime: '',
|
||||||
endTime: '',
|
endTime: '',
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ function transformData(rows: RowType[]) {
|
|||||||
productId: '',
|
productId: '',
|
||||||
catalogueId: '',
|
catalogueId: '',
|
||||||
orderMoney: String(row.point),
|
orderMoney: String(row.point),
|
||||||
realMoney: '',
|
realMoney: String(props.payment?.fee) || '',
|
||||||
districtMoney: '',
|
districtMoney: '',
|
||||||
startTime: '',
|
startTime: '',
|
||||||
endTime: '',
|
endTime: '',
|
||||||
@@ -28,7 +28,7 @@ interface RowType {
|
|||||||
function transformData(rows: RowType[]) {
|
function transformData(rows: RowType[]) {
|
||||||
return rows.map((row) => ({
|
return rows.map((row) => ({
|
||||||
id: row.courseId,
|
id: row.courseId,
|
||||||
courseId: row.courseId || '',
|
courseId: row.courseId.slice(1),
|
||||||
tel: '',
|
tel: '',
|
||||||
come: '0',
|
come: '0',
|
||||||
orderType: '2',
|
orderType: '2',
|
||||||
@@ -37,7 +37,7 @@ function transformData(rows: RowType[]) {
|
|||||||
productId: '',
|
productId: '',
|
||||||
catalogueId: '',
|
catalogueId: '',
|
||||||
orderMoney: '',
|
orderMoney: '',
|
||||||
realMoney: '',
|
realMoney: String(props.payment?.fee) || '',
|
||||||
districtMoney: '',
|
districtMoney: '',
|
||||||
startTime: props.payment?.ctime || '',
|
startTime: props.payment?.ctime || '',
|
||||||
endTime: '',
|
endTime: '',
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const props = withDefaults(
|
|||||||
const emit = defineEmits(['completeCheck', 'deletedChecked']);
|
const emit = defineEmits(['completeCheck', 'deletedChecked']);
|
||||||
|
|
||||||
interface RowType {
|
interface RowType {
|
||||||
|
oid: string;
|
||||||
courseFee: string;
|
courseFee: string;
|
||||||
courseId: string;
|
courseId: string;
|
||||||
studyDays: number;
|
studyDays: number;
|
||||||
@@ -29,9 +30,9 @@ interface RowType {
|
|||||||
|
|
||||||
function transformData(rows: RowType[]) {
|
function transformData(rows: RowType[]) {
|
||||||
return rows.map((row) => ({
|
return rows.map((row) => ({
|
||||||
id: row.courseId,
|
id: row.oid,
|
||||||
paymentId: props.payment?.id || 0,
|
paymentId: props.payment?.id || 0,
|
||||||
courseId: row.courseId,
|
courseId: row.courseId.slice(1),
|
||||||
tel: row.cellPhone,
|
tel: row.cellPhone,
|
||||||
come: '0',
|
come: '0',
|
||||||
orderType: '2',
|
orderType: '2',
|
||||||
@@ -39,17 +40,17 @@ function transformData(rows: RowType[]) {
|
|||||||
productId: '',
|
productId: '',
|
||||||
catalogueId: '',
|
catalogueId: '',
|
||||||
orderMoney: '',
|
orderMoney: '',
|
||||||
realMoney: '',
|
realMoney: String(props.payment?.fee) || '',
|
||||||
districtMoney: '',
|
districtMoney: '',
|
||||||
startTime: row.createDate,
|
startTime: props.payment?.ctime || '',
|
||||||
endTime: String(row.studyDays),
|
endTime: '',
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { Grid, cancelCheck, setChecked } = useCardListGrid<RowType, CreateOrderType>({
|
const { Grid, cancelCheck, setChecked } = useCardListGrid<RowType, CreateOrderType>({
|
||||||
payment: computed(() => props.payment),
|
payment: computed(() => props.payment),
|
||||||
selectedData: computed(() => props.selectedData),
|
selectedData: computed(() => props.selectedData),
|
||||||
rowKey: 'courseId',
|
rowKey: 'oid',
|
||||||
rowKeyPrefix: props.tabKey,
|
rowKeyPrefix: props.tabKey,
|
||||||
columns: [
|
columns: [
|
||||||
{ type: 'checkbox', width: 60 },
|
{ type: 'checkbox', width: 60 },
|
||||||
|
|||||||
@@ -34,12 +34,12 @@ function transformData(rows: RowType[]) {
|
|||||||
orderType: '3',
|
orderType: '3',
|
||||||
paymentId: props.payment?.id || '',
|
paymentId: props.payment?.id || '',
|
||||||
productName: row.productName,
|
productName: row.productName,
|
||||||
productId: row.id,
|
productId: row.id.slice(1),
|
||||||
catalogueId: '',
|
catalogueId: '',
|
||||||
orderMoney: String(row.price),
|
orderMoney: String(row.price),
|
||||||
realMoney: '',
|
realMoney: String(props.payment?.fee) || '',
|
||||||
districtMoney: '',
|
districtMoney: '',
|
||||||
startTime: props.payment?.ctime || '',
|
startTime: '',
|
||||||
endTime: '',
|
endTime: '',
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ function transformData(rows: RowType[]) {
|
|||||||
productId: '',
|
productId: '',
|
||||||
catalogueId: '',
|
catalogueId: '',
|
||||||
orderMoney: String(row.point),
|
orderMoney: String(row.point),
|
||||||
realMoney: '',
|
realMoney: String(props.payment?.fee) || '',
|
||||||
districtMoney: '',
|
districtMoney: '',
|
||||||
startTime: props.payment?.ctime || '',
|
startTime: '',
|
||||||
endTime: '',
|
endTime: '',
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,9 @@ import type { CreateOrderType, PaymentRowType } from '../types';
|
|||||||
|
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
import { Button, Card, message, Modal, notification } from 'ant-design-vue';
|
import { VbenIcon } from '@vben-core/shadcn-ui';
|
||||||
|
|
||||||
|
import { Button, Card, Input, message, Modal, notification } from 'ant-design-vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import { reconciliateBillsApi } from '#/api/posting/reconciliate';
|
import { reconciliateBillsApi } from '#/api/posting/reconciliate';
|
||||||
@@ -11,16 +13,16 @@ import { useSysStore } from '#/store';
|
|||||||
|
|
||||||
import Orders from '../components/Orders.vue';
|
import Orders from '../components/Orders.vue';
|
||||||
import Selected from '../components/Selected.vue';
|
import Selected from '../components/Selected.vue';
|
||||||
import TrainingClassProduct from '../components/TrainingClassProduct.vue';
|
import TrainingClass from '../components/TrainingClass.vue';
|
||||||
import VipProduct from '../components/VipProduct.vue';
|
import VipProduct from '../components/VipProduct.vue';
|
||||||
import WumenCourseProduct from '../components/WumenCourseProduct.vue';
|
import WumenCourseProduct from '../components/WumenCourseProduct.vue';
|
||||||
import WumenCourseRecharge from '../components/WumenCourseRecharge.vue';
|
|
||||||
import WumenCourseRecord from '../components/WumenCourseRecord.vue';
|
import WumenCourseRecord from '../components/WumenCourseRecord.vue';
|
||||||
import WumenPhysicalProduct from '../components/WumenPhysicalProduct.vue';
|
import WumenPhysicalProduct from '../components/WumenPhysicalProduct.vue';
|
||||||
|
import WumenRecharge from '../components/WumenRecharge.vue';
|
||||||
import YiluCourseProduct from '../components/YiluCourseProduct.vue';
|
import YiluCourseProduct from '../components/YiluCourseProduct.vue';
|
||||||
import YiluCourseRecharge from '../components/YiluCourseRecharge.vue';
|
|
||||||
import YiluCourseRecord from '../components/YiluCourseRecord.vue';
|
import YiluCourseRecord from '../components/YiluCourseRecord.vue';
|
||||||
import YiluPhysicalProduct from '../components/YiluPhysicalProduct.vue';
|
import YiluPhysicalProduct from '../components/YiluPhysicalProduct.vue';
|
||||||
|
import YiluRecharge from '../components/YiluRecharge.vue';
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -38,40 +40,72 @@ const emit = defineEmits(['update:show']);
|
|||||||
const needCreateRecord: Record<string, any> = {
|
const needCreateRecord: Record<string, any> = {
|
||||||
A: WumenCourseRecord,
|
A: WumenCourseRecord,
|
||||||
B: WumenCourseProduct,
|
B: WumenCourseProduct,
|
||||||
C: WumenCourseRecharge,
|
C: WumenRecharge,
|
||||||
D: WumenPhysicalProduct,
|
D: WumenPhysicalProduct,
|
||||||
E: YiluCourseRecord,
|
E: YiluCourseRecord,
|
||||||
F: YiluCourseProduct,
|
F: YiluCourseProduct,
|
||||||
G: YiluCourseRecharge,
|
G: YiluRecharge,
|
||||||
H: YiluPhysicalProduct,
|
H: YiluPhysicalProduct,
|
||||||
I: VipProduct,
|
I: VipProduct,
|
||||||
J: TrainingClassProduct,
|
J: TrainingClass,
|
||||||
};
|
};
|
||||||
|
|
||||||
const sysStore = useSysStore();
|
const sysStore = useSysStore();
|
||||||
|
|
||||||
|
// 批量模式
|
||||||
|
const isBatchMode = ref(false);
|
||||||
|
|
||||||
const visible = ref(props.show);
|
const visible = ref(props.show);
|
||||||
watch(visible, (val) => {
|
watch(visible, (val) => {
|
||||||
emit('update:show', val);
|
emit('update:show', val);
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentIndex = ref(0);
|
const multipleCurrentIndex = ref<number[]>([0]);
|
||||||
|
const multipleCurrentData = ref<PaymentRowType[]>([]);
|
||||||
|
const selectedAmount = ref();
|
||||||
const pendingData = ref<PaymentRowType[]>([]);
|
const pendingData = ref<PaymentRowType[]>([]);
|
||||||
|
|
||||||
// 监听props.data变化,更新本地pendingData
|
// 监听props.data变化,更新本地pendingData
|
||||||
watch(
|
watch(
|
||||||
() => props.data,
|
() => props.data,
|
||||||
(newData) => {
|
(newData) => {
|
||||||
pendingData.value = [...newData];
|
pendingData.value = newData.toSorted(
|
||||||
currentIndex.value = 0;
|
(a, b) => Number(b.orderFlag === 1) - Number(a.orderFlag === 1),
|
||||||
|
);
|
||||||
|
multipleCurrentIndex.value = [0];
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
function changePendingData(index: number) {
|
function changePendingData(index: number) {
|
||||||
currentIndex.value = index;
|
// 单个模式
|
||||||
|
multipleCurrentIndex.value = [index];
|
||||||
selectedData.value = [];
|
selectedData.value = [];
|
||||||
selectedRef.value?.clearData();
|
selectedRef.value?.clearData();
|
||||||
|
selectedAmount.value = null;
|
||||||
|
isBatchMode.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeBatchSelected() {
|
||||||
|
selectedData.value = [];
|
||||||
|
selectedRef.value?.clearData();
|
||||||
|
|
||||||
|
if (selectedAmount.value || selectedAmount.value === '0') {
|
||||||
|
// 按金额批量选中
|
||||||
|
activeTabKey.value = 'B'; // 批量模式不能选订单和记录,默认到课程商品
|
||||||
|
isBatchMode.value = true;
|
||||||
|
multipleCurrentData.value = pendingData.value.filter(
|
||||||
|
(item) => item.fee === Number.parseFloat(selectedAmount.value),
|
||||||
|
);
|
||||||
|
multipleCurrentIndex.value = multipleCurrentData.value.map((item) =>
|
||||||
|
pendingData.value.indexOf(item),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 取消批量选中
|
||||||
|
activeTabKey.value = '0'; // 切换到订单列表
|
||||||
|
isBatchMode.value = false;
|
||||||
|
changePendingData(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabList = [
|
const tabList = [
|
||||||
@@ -122,6 +156,10 @@ const tabList = [
|
|||||||
];
|
];
|
||||||
const activeTabKey = ref('0');
|
const activeTabKey = ref('0');
|
||||||
function changeRecordTab(key: string) {
|
function changeRecordTab(key: string) {
|
||||||
|
if (isBatchMode.value && ['0', 'A', 'C', 'E', 'G'].includes(key)) {
|
||||||
|
message.warning('批量模式下,不能切换到该选项');
|
||||||
|
return;
|
||||||
|
}
|
||||||
activeTabKey.value = key;
|
activeTabKey.value = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,9 +167,13 @@ function changeRecordTab(key: string) {
|
|||||||
function onCompleteCheck() {
|
function onCompleteCheck() {
|
||||||
notification.success({
|
notification.success({
|
||||||
message: '操作成功',
|
message: '操作成功',
|
||||||
|
duration: 1,
|
||||||
});
|
});
|
||||||
// 删除当前已对账的数据
|
// 删除当前已对账的数据
|
||||||
pendingData.value.splice(currentIndex.value, 1);
|
const currentIndex = multipleCurrentIndex.value[0] || 0;
|
||||||
|
multipleCurrentIndex.value.forEach((index) => {
|
||||||
|
pendingData.value.splice(index, 1);
|
||||||
|
});
|
||||||
|
|
||||||
// 如果没有待对账数据了,关闭弹框
|
// 如果没有待对账数据了,关闭弹框
|
||||||
if (pendingData.value.length === 0) {
|
if (pendingData.value.length === 0) {
|
||||||
@@ -139,10 +181,11 @@ function onCompleteCheck() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果当前索引超出了数据范围,重置到第一条
|
// 清空待处理账单的已选状态
|
||||||
if (currentIndex.value >= pendingData.value.length) {
|
multipleCurrentIndex.value = [];
|
||||||
currentIndex.value = 0;
|
multipleCurrentData.value = [];
|
||||||
}
|
// 如果当前索引超出了数据范围,重置到第一条,否则下一条(删除已处理索引后,currentIndex即是下一条)
|
||||||
|
multipleCurrentIndex.value[0] = Math.min(currentIndex, pendingData.value.length - 1);
|
||||||
}
|
}
|
||||||
// 其他方式--确认对账 --------------
|
// 其他方式--确认对账 --------------
|
||||||
// 已选列表
|
// 已选列表
|
||||||
@@ -172,29 +215,68 @@ function onDeleteChecked(ids: number[]) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function completeSubmitCheck(item: CreateOrderType) {
|
||||||
|
if (!item.realMoney) {
|
||||||
|
message.error(`《${item.productName}》请填写实际金额`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!item.orderMoney) {
|
||||||
|
message.error(`《${item.productName}》请填写订单金额`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 单个模式必须填手机号
|
||||||
|
if (!isBatchMode.value && !item.tel) {
|
||||||
|
message.error(`《${item.productName}》请填写手机号`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 单个模式课程、vip必须填开始时间和结束时间
|
||||||
|
if (!isBatchMode.value && (item.orderType === '1' || item.orderType === '2') && !item.startTime) {
|
||||||
|
message.error(`《${item.productName}》请填写开始时间`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!isBatchMode.value && (item.orderType === '1' || item.orderType === '2') && !item.endTime) {
|
||||||
|
message.error(`《${item.productName}》请填写到期时间`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 批量模式课程、vip必须填到期时间
|
||||||
|
if (isBatchMode.value && (item.orderType === '1' || item.orderType === '2') && !item.endTime) {
|
||||||
|
message.error(`《${item.productName}》请填写到期时间`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// 确认对账
|
// 确认对账
|
||||||
function onCompleteCheckCreated() {
|
async function onCompleteCheckCreated() {
|
||||||
if (selectedData.value.length === 0) {
|
if (multipleCurrentIndex.value.length === 0) {
|
||||||
message.error('请选择数据');
|
message.error('请选择账单');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (selectedData.value.length === 0) {
|
||||||
|
message.error('请选择对账数据');
|
||||||
|
}
|
||||||
|
|
||||||
const selected = selectedRef.value.getData();
|
const selected = selectedRef.value.getData();
|
||||||
|
|
||||||
// 检查已选数据中是否有未填写的表单项
|
// 检查已选数据中是否有未填写的表单项
|
||||||
const emptyItem = selected.find((item: CreateOrderType) => {
|
const emptyItem = selected.find((item: CreateOrderType) => {
|
||||||
if (
|
return completeSubmitCheck(item);
|
||||||
!item.realMoney ||
|
|
||||||
!item.orderMoney ||
|
|
||||||
!item.tel ||
|
|
||||||
((item.orderType === '1' || item.orderType === '2') && (!item.startTime || !item.endTime))
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
if (emptyItem) {
|
if (emptyItem) {
|
||||||
errorData.value = [emptyItem];
|
errorData.value = [emptyItem];
|
||||||
message.error(`《${emptyItem.productName}》有未填写项`);
|
return;
|
||||||
|
} else {
|
||||||
|
errorData.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证订单金额不能小于实际金额
|
||||||
|
const invalidItem = selected.find((item: CreateOrderType) => {
|
||||||
|
return Number.parseFloat(item.orderMoney) < Number.parseFloat(item.realMoney);
|
||||||
|
});
|
||||||
|
if (invalidItem) {
|
||||||
|
errorData.value = [invalidItem];
|
||||||
|
message.error(`《${invalidItem.productName}》订单金额不能小于实际金额`);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
errorData.value = [];
|
errorData.value = [];
|
||||||
@@ -205,7 +287,7 @@ function onCompleteCheckCreated() {
|
|||||||
(acc: number, item: CreateOrderType) => acc + Number.parseFloat(item.realMoney),
|
(acc: number, item: CreateOrderType) => acc + Number.parseFloat(item.realMoney),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
if (totalRealMoney !== Number.parseFloat(pendingData.value?.[currentIndex.value]?.fee || '0')) {
|
if (totalRealMoney !== pendingData.value?.[multipleCurrentIndex.value[0] ?? 0]?.fee) {
|
||||||
message.error('已选数据填写的实际金额总和与对账单金额不一致');
|
message.error('已选数据填写的实际金额总和与对账单金额不一致');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -218,23 +300,35 @@ function onCompleteCheckCreated() {
|
|||||||
).toString(),
|
).toString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// 只有VIP和课程订单才处理开始时间和结束时间
|
// 单选模式且只有VIP和课程订单才处理开始时间和结束时间
|
||||||
if (item.orderType === '1' || item.orderType === '2') {
|
if (!isBatchMode.value && (item.orderType === '1' || item.orderType === '2')) {
|
||||||
result.startTime = dayjs(item.startTime).format('YYYY-MM-DD'); // 格式化开始时间
|
result.startTime = dayjs(item.startTime).format('YYYY-MM-DD'); // 格式化开始时间
|
||||||
result.endTime = dayjs(item.startTime)
|
result.endTime = dayjs(item.startTime)
|
||||||
.add(Number.parseFloat(item.endTime), 'month')
|
.add(Number.parseFloat(item.endTime), 'day')
|
||||||
.format('YYYY-MM-DD'); // 处理结束时间,根据传入的vb时间和课程时长计算
|
.format('YYYY-MM-DD'); // 处理结束时间,根据传入的vb时间和课程时长计算
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果是多选模式,处理相应数据
|
||||||
|
if (isBatchMode.value) {
|
||||||
|
delete result.paymentId;
|
||||||
|
result.startTime = '';
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const paymentIds = multipleCurrentData.value.map((item) => item.id);
|
||||||
// 调用接口
|
// 调用接口
|
||||||
reconciliateBillsApi.manualCheckCreated(data).then(() => {
|
await (isBatchMode.value
|
||||||
onCompleteCheck();
|
? reconciliateBillsApi.manualCheckCreatedBatch({
|
||||||
// 清空已选列表
|
paymentIds: paymentIds.join(','),
|
||||||
selectedData.value = [];
|
orders: data,
|
||||||
selectedRef.value?.clearData();
|
})
|
||||||
});
|
: reconciliateBillsApi.manualCheckCreated(data));
|
||||||
|
onCompleteCheck();
|
||||||
|
// 清空已选列表
|
||||||
|
selectedData.value = [];
|
||||||
|
selectedRef.value?.clearData();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -249,12 +343,22 @@ function onCompleteCheckCreated() {
|
|||||||
>
|
>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<Card title="待对账列表" class="w-1/4" size="small">
|
<Card title="待对账列表" class="w-1/4" size="small">
|
||||||
<div class="flex flex-col gap-2 overflow-y-auto p-1" style="height: calc(100vh - 120px)">
|
<div class="flex items-center justify-between p-1 pb-2">
|
||||||
|
<Input
|
||||||
|
v-model:value="selectedAmount"
|
||||||
|
addon-before="批量选中金额"
|
||||||
|
allow-clear
|
||||||
|
class="w-60"
|
||||||
|
@change="changeBatchSelected"
|
||||||
|
/>
|
||||||
|
<div class="ml-2">已选中:{{ multipleCurrentIndex.length }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2 overflow-y-auto px-1" style="height: calc(100vh - 170px)">
|
||||||
<div
|
<div
|
||||||
v-for="(item, index) in pendingData"
|
v-for="(item, index) in pendingData"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:class="{ active: currentIndex === index }"
|
:class="{ active: multipleCurrentIndex.includes(index) }"
|
||||||
class="pay-order-item"
|
class="pay-order-item relative"
|
||||||
@click="changePendingData(index)"
|
@click="changePendingData(index)"
|
||||||
>
|
>
|
||||||
<div>关联sn号:{{ item.relationSn }}</div>
|
<div>关联sn号:{{ item.relationSn }}</div>
|
||||||
@@ -263,12 +367,19 @@ function onCompleteCheckCreated() {
|
|||||||
<div>金额:{{ item.fee }}</div>
|
<div>金额:{{ item.fee }}</div>
|
||||||
<div>支付方式:{{ sysStore.getDictMap('payment', item.type) }}</div>
|
<div>支付方式:{{ sysStore.getDictMap('payment', item.type) }}</div>
|
||||||
<div>时间:{{ dayjs(item.ctime).format('YYYY-MM-DD HH:mm:ss') }}</div>
|
<div>时间:{{ dayjs(item.ctime).format('YYYY-MM-DD HH:mm:ss') }}</div>
|
||||||
|
|
||||||
|
<VbenIcon
|
||||||
|
v-if="item.orderFlag === 1"
|
||||||
|
icon="ant-design:star-fill"
|
||||||
|
class="absolute right-1 top-1 h-5 w-5 text-red-500"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<div class="flex w-3/4 flex-1 flex-col gap-2">
|
<div class="flex w-3/4 flex-1 flex-col gap-2">
|
||||||
<Card
|
<Card
|
||||||
:tab-list="tabList"
|
:tab-list="tabList"
|
||||||
|
disabled
|
||||||
:active-tab-key="activeTabKey"
|
:active-tab-key="activeTabKey"
|
||||||
@tab-change="changeRecordTab"
|
@tab-change="changeRecordTab"
|
||||||
class="order-card flex-1"
|
class="order-card flex-1"
|
||||||
@@ -276,7 +387,7 @@ function onCompleteCheckCreated() {
|
|||||||
>
|
>
|
||||||
<Orders
|
<Orders
|
||||||
v-if="activeTabKey === '0'"
|
v-if="activeTabKey === '0'"
|
||||||
:payment-id="pendingData[currentIndex]?.id"
|
:payment-id="pendingData[multipleCurrentIndex[0] ?? 0]?.id"
|
||||||
@complete-check="onCompleteCheck"
|
@complete-check="onCompleteCheck"
|
||||||
/>
|
/>
|
||||||
<component
|
<component
|
||||||
@@ -284,7 +395,7 @@ function onCompleteCheckCreated() {
|
|||||||
v-if="activeTabKey !== '0'"
|
v-if="activeTabKey !== '0'"
|
||||||
ref="needCreateRecordRef"
|
ref="needCreateRecordRef"
|
||||||
:tab-key="activeTabKey"
|
:tab-key="activeTabKey"
|
||||||
:payment="pendingData[currentIndex]"
|
:payment="pendingData[multipleCurrentIndex[0] ?? 0]"
|
||||||
:selected-data="selectedData"
|
:selected-data="selectedData"
|
||||||
@complete-check="onSelectRecord"
|
@complete-check="onSelectRecord"
|
||||||
@deleted-checked="onDeleteChecked"
|
@deleted-checked="onDeleteChecked"
|
||||||
@@ -299,7 +410,12 @@ function onCompleteCheckCreated() {
|
|||||||
<template #extra>
|
<template #extra>
|
||||||
<Button type="primary" size="small" @click="onCompleteCheckCreated()">确认对账</Button>
|
<Button type="primary" size="small" @click="onCompleteCheckCreated()">确认对账</Button>
|
||||||
</template>
|
</template>
|
||||||
<Selected ref="selectedRef" :error-item="errorData" @deleted-checked="onDeleteChecked" />
|
<Selected
|
||||||
|
ref="selectedRef"
|
||||||
|
:error-item="errorData"
|
||||||
|
:is-batch-mode="isBatchMode"
|
||||||
|
@deleted-checked="onDeleteChecked"
|
||||||
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -320,6 +436,7 @@ function onCompleteCheckCreated() {
|
|||||||
}
|
}
|
||||||
:deep(.ant-card-body) {
|
:deep(.ant-card-body) {
|
||||||
padding: 5px !important;
|
padding: 5px !important;
|
||||||
|
padding-bottom: 10px !important;
|
||||||
}
|
}
|
||||||
.order-card {
|
.order-card {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
export interface PaymentRowType {
|
export interface PaymentRowType {
|
||||||
checkoff: number;
|
checkoff: number;
|
||||||
ctime: string;
|
ctime: string;
|
||||||
fee: string;
|
fee: number;
|
||||||
|
orderFlag: number;
|
||||||
financeSn: string;
|
financeSn: string;
|
||||||
id: number;
|
id: number;
|
||||||
transactionSn: string;
|
transactionSn: string;
|
||||||
|
|||||||
153
apps/finance/src/views/statistics/course/report.vue
Normal file
153
apps/finance/src/views/statistics/course/report.vue
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page, Spinner } from '@vben/common-ui';
|
||||||
|
// import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
|
|
||||||
|
import { Button, Card, DatePicker, Empty } from 'ant-design-vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
import { statisticsApi } from '#/api/statistics';
|
||||||
|
|
||||||
|
const year = ref<Dayjs>(dayjs());
|
||||||
|
const disabledDate = (date: Dayjs) => date.year() > dayjs().year();
|
||||||
|
|
||||||
|
interface Report {
|
||||||
|
incomes: Record<string, number>;
|
||||||
|
notyet: number;
|
||||||
|
already: number;
|
||||||
|
now: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReportItem {
|
||||||
|
type: string;
|
||||||
|
fee: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 报表数据
|
||||||
|
const list = ref<Report[]>([]);
|
||||||
|
const loading = ref<boolean>(false);
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
list.value = [];
|
||||||
|
try {
|
||||||
|
// 计算查询年份中包含哪些月份, 若为今年则只查询到当前月份,往年则查询所有月份
|
||||||
|
const monthList =
|
||||||
|
year.value.year() === dayjs().year()
|
||||||
|
? Array.from({ length: dayjs().month() + 1 }, (_, i) => (i + 1).toString().padStart(2, '0'))
|
||||||
|
: Array.from({ length: 12 }, (_, i) => (i + 1).toString().padStart(2, '0'));
|
||||||
|
for (const month of monthList) {
|
||||||
|
const data = await statisticsApi.getCourseStatistics({
|
||||||
|
year: year.value.year(),
|
||||||
|
month,
|
||||||
|
});
|
||||||
|
list.value.push({
|
||||||
|
incomes: {
|
||||||
|
微信: 0,
|
||||||
|
支付宝: 0,
|
||||||
|
银行: 0,
|
||||||
|
天医币: 0,
|
||||||
|
...Object.fromEntries(data.incomes.map((item: ReportItem) => [item.type, item.fee])),
|
||||||
|
},
|
||||||
|
notyet: data.notyet || 0,
|
||||||
|
already: data.already || 0,
|
||||||
|
now: data.now || 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 下载报表
|
||||||
|
// const downloadReport = async (index: number) => {
|
||||||
|
// const month = index > 9 ? `${index + 1}` : `0${index + 1}`;
|
||||||
|
// const date = `${year.value.year()}-${month}`;
|
||||||
|
// const filename = `天医币报表_${year.value.year()}年${month}月_文件.xlsx`;
|
||||||
|
// const res = await statisticsApi.downloadReportTianyibi({
|
||||||
|
// date,
|
||||||
|
// });
|
||||||
|
// downloadFileFromBlobPart({
|
||||||
|
// source: res.data,
|
||||||
|
// fileName: filename,
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const downloadAllReport = () => {
|
||||||
|
// list.value.forEach((_, index) => {
|
||||||
|
// downloadReport(index);
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<div class="flex h-full flex-col rounded-md bg-white">
|
||||||
|
<div class="search-form p-4">
|
||||||
|
<DatePicker
|
||||||
|
v-model:value="year"
|
||||||
|
picker="year"
|
||||||
|
:disabled-date="disabledDate"
|
||||||
|
@change="getList"
|
||||||
|
/>
|
||||||
|
<Button type="primary" class="ml-2" @click="getList">查询</Button>
|
||||||
|
<!-- <Button type="link" class="ml-2" @click="downloadAllReport">
|
||||||
|
下载 {{ year.year() }} 年全部天医币报表
|
||||||
|
</Button> -->
|
||||||
|
</div>
|
||||||
|
<div class="h-2 bg-gray-100"></div>
|
||||||
|
<Spinner class="content flex-1 px-3 py-4" :spinning="loading">
|
||||||
|
<div
|
||||||
|
v-if="list.length === 0 && !loading"
|
||||||
|
class="col-span-3 flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<Empty />
|
||||||
|
</div>
|
||||||
|
<div v-else class="grid max-h-full grid-cols-3 gap-3 overflow-auto px-1">
|
||||||
|
<Card v-for="(item, index) in list" :key="index" :title="`${index + 1} 月`" size="small">
|
||||||
|
<!-- <template #extra>
|
||||||
|
<Button type="link" @click="downloadReport(index)">下载报表</Button>
|
||||||
|
</template> -->
|
||||||
|
<div class="-m-2 text-[16px]">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-1 bg-[#F6FFF5] px-2 pb-1">
|
||||||
|
<div class="p-1 text-center font-bold">收入</div>
|
||||||
|
<div v-for="(fee, type) in item.incomes" :key="type" class="p-1">
|
||||||
|
<span class="text-gray-500">{{ type }}:</span>
|
||||||
|
<span class="text-black">{{ fee }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 bg-[#FFFBF0] px-2 pb-1">
|
||||||
|
<div class="p-1 text-center font-bold">摊销</div>
|
||||||
|
<div class="p-1">
|
||||||
|
<span class="text-gray-500">已摊销:</span>
|
||||||
|
<span class="text-black">{{ item.already }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-1">
|
||||||
|
<span class="text-gray-500">月摊销:</span>
|
||||||
|
<span class="text-black">{{ item.now }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-1">
|
||||||
|
<span class="text-gray-500">剩余摊销:</span>
|
||||||
|
<span class="text-black">{{ item.notyet }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</Spinner>
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
:deep(.ant-card-head-title) {
|
||||||
|
font-size: 16px !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
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>
|
||||||
153
apps/finance/src/views/statistics/vip/report.vue
Normal file
153
apps/finance/src/views/statistics/vip/report.vue
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page, Spinner } from '@vben/common-ui';
|
||||||
|
// import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
|
|
||||||
|
import { Button, Card, DatePicker, Empty } from 'ant-design-vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
import { statisticsApi } from '#/api/statistics';
|
||||||
|
|
||||||
|
const year = ref<Dayjs>(dayjs());
|
||||||
|
const disabledDate = (date: Dayjs) => date.year() > dayjs().year();
|
||||||
|
|
||||||
|
interface Report {
|
||||||
|
incomes: Record<string, number>;
|
||||||
|
notyet: number;
|
||||||
|
already: number;
|
||||||
|
now: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReportItem {
|
||||||
|
type: string;
|
||||||
|
fee: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 报表数据
|
||||||
|
const list = ref<Report[]>([]);
|
||||||
|
const loading = ref<boolean>(false);
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
list.value = [];
|
||||||
|
try {
|
||||||
|
// 计算查询年份中包含哪些月份, 若为今年则只查询到当前月份,往年则查询所有月份
|
||||||
|
const monthList =
|
||||||
|
year.value.year() === dayjs().year()
|
||||||
|
? Array.from({ length: dayjs().month() + 1 }, (_, i) => (i + 1).toString().padStart(2, '0'))
|
||||||
|
: Array.from({ length: 12 }, (_, i) => (i + 1).toString().padStart(2, '0'));
|
||||||
|
for (const month of monthList) {
|
||||||
|
const data = await statisticsApi.getVipStatistics({
|
||||||
|
year: year.value.year(),
|
||||||
|
month,
|
||||||
|
});
|
||||||
|
list.value.push({
|
||||||
|
incomes: {
|
||||||
|
微信: 0,
|
||||||
|
支付宝: 0,
|
||||||
|
银行: 0,
|
||||||
|
天医币: 0,
|
||||||
|
...Object.fromEntries(data.incomes.map((item: ReportItem) => [item.type, item.fee])),
|
||||||
|
},
|
||||||
|
notyet: data.notyet || 0,
|
||||||
|
already: data.already || 0,
|
||||||
|
now: data.now || 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 下载报表
|
||||||
|
// const downloadReport = async (index: number) => {
|
||||||
|
// const month = index > 9 ? `${index + 1}` : `0${index + 1}`;
|
||||||
|
// const date = `${year.value.year()}-${month}`;
|
||||||
|
// const filename = `天医币报表_${year.value.year()}年${month}月_文件.xlsx`;
|
||||||
|
// const res = await statisticsApi.downloadReportTianyibi({
|
||||||
|
// date,
|
||||||
|
// });
|
||||||
|
// downloadFileFromBlobPart({
|
||||||
|
// source: res.data,
|
||||||
|
// fileName: filename,
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const downloadAllReport = () => {
|
||||||
|
// list.value.forEach((_, index) => {
|
||||||
|
// downloadReport(index);
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<div class="flex h-full flex-col rounded-md bg-white">
|
||||||
|
<div class="search-form p-4">
|
||||||
|
<DatePicker
|
||||||
|
v-model:value="year"
|
||||||
|
picker="year"
|
||||||
|
:disabled-date="disabledDate"
|
||||||
|
@change="getList"
|
||||||
|
/>
|
||||||
|
<Button type="primary" class="ml-2" @click="getList">查询</Button>
|
||||||
|
<!-- <Button type="link" class="ml-2" @click="downloadAllReport">
|
||||||
|
下载 {{ year.year() }} 年全部天医币报表
|
||||||
|
</Button> -->
|
||||||
|
</div>
|
||||||
|
<div class="h-2 bg-gray-100"></div>
|
||||||
|
<Spinner class="content flex-1 px-3 py-4" :spinning="loading">
|
||||||
|
<div
|
||||||
|
v-if="list.length === 0 && !loading"
|
||||||
|
class="col-span-3 flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<Empty />
|
||||||
|
</div>
|
||||||
|
<div v-else class="grid max-h-full grid-cols-3 gap-3 overflow-auto px-1">
|
||||||
|
<Card v-for="(item, index) in list" :key="index" :title="`${index + 1} 月`" size="small">
|
||||||
|
<!-- <template #extra>
|
||||||
|
<Button type="link" @click="downloadReport(index)">下载报表</Button>
|
||||||
|
</template> -->
|
||||||
|
<div class="-m-2 text-[16px]">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-1 bg-[#F6FFF5] px-2 pb-1">
|
||||||
|
<div class="p-1 text-center font-bold">收入</div>
|
||||||
|
<div v-for="(fee, type) in item.incomes" :key="type" class="p-1">
|
||||||
|
<span class="text-gray-500">{{ type }}:</span>
|
||||||
|
<span class="text-black">{{ fee }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 bg-[#FFFBF0] px-2 pb-1">
|
||||||
|
<div class="p-1 text-center font-bold">摊销</div>
|
||||||
|
<div class="p-1">
|
||||||
|
<span class="text-gray-500">已摊销:</span>
|
||||||
|
<span class="text-black">{{ item.already }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-1">
|
||||||
|
<span class="text-gray-500">月摊销:</span>
|
||||||
|
<span class="text-black">{{ item.now }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-1">
|
||||||
|
<span class="text-gray-500">剩余摊销:</span>
|
||||||
|
<span class="text-black">{{ item.notyet }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</Spinner>
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
:deep(.ant-card-head-title) {
|
||||||
|
font-size: 16px !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,14 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup></script>
|
||||||
import { ref } from 'vue';
|
|
||||||
|
|
||||||
import { TabPane, Tabs } from 'ant-design-vue';
|
|
||||||
|
|
||||||
const activeKey = ref(1);
|
|
||||||
</script>
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div></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>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|||||||
@@ -27,19 +27,12 @@ const subMenu = useSubMenuContext();
|
|||||||
const { parentMenu, parentPaths } = useMenu();
|
const { parentMenu, parentPaths } = useMenu();
|
||||||
|
|
||||||
const active = computed(() => props.path === rootMenu?.activePath);
|
const active = computed(() => props.path === rootMenu?.activePath);
|
||||||
const menuIcon = computed(() =>
|
const menuIcon = computed(() => (active.value ? props.activeIcon || props.icon : props.icon));
|
||||||
active.value ? props.activeIcon || props.icon : props.icon,
|
|
||||||
);
|
|
||||||
|
|
||||||
const isTopLevelMenuItem = computed(
|
const isTopLevelMenuItem = computed(() => parentMenu.value?.type.name === 'Menu');
|
||||||
() => parentMenu.value?.type.name === 'Menu',
|
|
||||||
);
|
|
||||||
|
|
||||||
const collapseShowTitle = computed(
|
const collapseShowTitle = computed(
|
||||||
() =>
|
() => rootMenu.props?.collapseShowTitle && isTopLevelMenuItem.value && rootMenu.props.collapse,
|
||||||
rootMenu.props?.collapseShowTitle &&
|
|
||||||
isTopLevelMenuItem.value &&
|
|
||||||
rootMenu.props.collapse,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const showTooltip = computed(
|
const showTooltip = computed(
|
||||||
@@ -92,11 +85,7 @@ onBeforeUnmount(() => {
|
|||||||
role="menuitem"
|
role="menuitem"
|
||||||
@click.stop="handleClick"
|
@click.stop="handleClick"
|
||||||
>
|
>
|
||||||
<VbenTooltip
|
<VbenTooltip v-if="showTooltip" :content-class="[rootMenu.theme]" side="right">
|
||||||
v-if="showTooltip"
|
|
||||||
:content-class="[rootMenu.theme]"
|
|
||||||
side="right"
|
|
||||||
>
|
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<div :class="[nsMenu.be('tooltip', 'trigger')]">
|
<div :class="[nsMenu.be('tooltip', 'trigger')]">
|
||||||
<VbenIcon :class="nsMenu.e('icon')" :icon="menuIcon" fallback />
|
<VbenIcon :class="nsMenu.e('icon')" :icon="menuIcon" fallback />
|
||||||
@@ -109,11 +98,7 @@ onBeforeUnmount(() => {
|
|||||||
<slot name="title"></slot>
|
<slot name="title"></slot>
|
||||||
</VbenTooltip>
|
</VbenTooltip>
|
||||||
<div v-show="!showTooltip" :class="[e('content')]">
|
<div v-show="!showTooltip" :class="[e('content')]">
|
||||||
<MenuBadge
|
<MenuBadge v-if="rootMenu.props.mode !== 'horizontal'" class="right-2" v-bind="props" />
|
||||||
v-if="rootMenu.props.mode !== 'horizontal'"
|
|
||||||
class="right-2"
|
|
||||||
v-bind="props"
|
|
||||||
/>
|
|
||||||
<VbenIcon :class="nsMenu.e('icon')" :icon="menuIcon" />
|
<VbenIcon :class="nsMenu.e('icon')" :icon="menuIcon" />
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<slot name="title"></slot>
|
<slot name="title"></slot>
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ const props = defineProps<{ class?: any }>();
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div :class="cn('flex flex-col-reverse justify-end gap-x-2', props.class)">
|
||||||
:class="
|
|
||||||
cn('flex flex-row flex-col-reverse justify-end gap-x-2', props.class)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ const props = defineProps<{ class?: any }>();
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div :class="cn('flex flex-col-reverse justify-end gap-x-2', props.class)">
|
||||||
:class="
|
|
||||||
cn('flex flex-row flex-col-reverse justify-end gap-x-2', props.class)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -27,17 +27,20 @@ withDefaults(defineProps<Props>(), {
|
|||||||
<slot name="description"></slot>
|
<slot name="description"></slot>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 flex flex-1 justify-end md:mt-0">
|
<div
|
||||||
<div class="flex flex-col justify-center text-right">
|
v-if="$slots.todo || $slots.project || $slots.team"
|
||||||
|
class="mt-4 flex flex-1 justify-end md:mt-0"
|
||||||
|
>
|
||||||
|
<div v-if="$slots.todo" class="flex flex-col justify-center text-right">
|
||||||
<span class="text-foreground/80"> 待办 </span>
|
<span class="text-foreground/80"> 待办 </span>
|
||||||
<span class="text-2xl">2/10</span>
|
<span class="text-2xl">2/10</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mx-12 flex flex-col justify-center text-right md:mx-16">
|
<div v-if="$slots.project" class="mx-12 flex flex-col justify-center text-right md:mx-16">
|
||||||
<span class="text-foreground/80"> 项目 </span>
|
<span class="text-foreground/80"> 项目 </span>
|
||||||
<span class="text-2xl">8</span>
|
<span class="text-2xl">8</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mr-4 flex flex-col justify-center text-right md:mr-10">
|
<div v-if="$slots.team" class="mr-4 flex flex-col justify-center text-right md:mr-10">
|
||||||
<span class="text-foreground/80"> 团队 </span>
|
<span class="text-foreground/80"> 团队 </span>
|
||||||
<span class="text-2xl">300</span>
|
<span class="text-2xl">300</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,14 +2,7 @@
|
|||||||
import type { Props } from './types';
|
import type { Props } from './types';
|
||||||
|
|
||||||
import { preferences } from '@vben-core/preferences';
|
import { preferences } from '@vben-core/preferences';
|
||||||
import {
|
import { Card, Separator, Tabs, TabsList, TabsTrigger, VbenAvatar } from '@vben-core/shadcn-ui';
|
||||||
Card,
|
|
||||||
Separator,
|
|
||||||
Tabs,
|
|
||||||
TabsList,
|
|
||||||
TabsTrigger,
|
|
||||||
VbenAvatar,
|
|
||||||
} from '@vben-core/shadcn-ui';
|
|
||||||
|
|
||||||
import { Page } from '../../components';
|
import { Page } from '../../components';
|
||||||
|
|
||||||
@@ -29,15 +22,12 @@ const tabsValue = defineModel<string>('modelValue');
|
|||||||
<div class="flex h-full w-full">
|
<div class="flex h-full w-full">
|
||||||
<Card class="w-1/6 flex-none">
|
<Card class="w-1/6 flex-none">
|
||||||
<div class="mt-4 flex h-40 flex-col items-center justify-center gap-4">
|
<div class="mt-4 flex h-40 flex-col items-center justify-center gap-4">
|
||||||
<VbenAvatar
|
<VbenAvatar :src="userInfo?.avatar ?? preferences.app.defaultAvatar" class="size-20" />
|
||||||
:src="userInfo?.avatar ?? preferences.app.defaultAvatar"
|
|
||||||
class="size-20"
|
|
||||||
/>
|
|
||||||
<span class="text-lg font-semibold">
|
<span class="text-lg font-semibold">
|
||||||
{{ userInfo?.realName ?? '' }}
|
{{ userInfo?.name ?? '' }}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-foreground/80 text-sm">
|
<span class="text-foreground/80 text-sm">
|
||||||
{{ userInfo?.username ?? '' }}
|
{{ userInfo?.name ?? '' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Separator class="my-4" />
|
<Separator class="my-4" />
|
||||||
|
|||||||
@@ -219,10 +219,16 @@ const headerSlots = computed(() => {
|
|||||||
:z-index="preferences.app.zIndex"
|
:z-index="preferences.app.zIndex"
|
||||||
@side-mouse-leave="handleSideMouseLeave"
|
@side-mouse-leave="handleSideMouseLeave"
|
||||||
@toggle-sidebar="toggleSidebar"
|
@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-enable="(value: boolean) => updatePreferences({ sidebar: { enable: value } })"
|
||||||
@update:sidebar-expand-on-hover="(value: boolean) => updatePreferences({ sidebar: { expandOnHover: value } })"
|
@update:sidebar-expand-on-hover="
|
||||||
@update:sidebar-extra-collapse="(value: boolean) => updatePreferences({ sidebar: { extraCollapse: value } })"
|
(value: boolean) => updatePreferences({ sidebar: { expandOnHover: value } })
|
||||||
|
"
|
||||||
|
@update:sidebar-extra-collapse="
|
||||||
|
(value: boolean) => updatePreferences({ sidebar: { extraCollapse: value } })
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<!-- logo -->
|
<!-- logo -->
|
||||||
<template #logo>
|
<template #logo>
|
||||||
@@ -330,7 +336,11 @@ const headerSlots = computed(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #tabbar>
|
<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>
|
</template>
|
||||||
|
|
||||||
<!-- 主体内容 -->
|
<!-- 主体内容 -->
|
||||||
@@ -361,7 +371,10 @@ const headerSlots = computed(() => {
|
|||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<template v-if="preferencesButtonPosition.fixed">
|
<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>
|
</template>
|
||||||
<VbenBackTop />
|
<VbenBackTop />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -31,10 +31,18 @@ export const useUserStore = defineStore('core-user', {
|
|||||||
this.userRoles = roles;
|
this.userRoles = roles;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
state: (): AccessState => ({
|
state: (): AccessState => {
|
||||||
userInfo: null,
|
// 从localStorage中读取用户信息
|
||||||
userRoles: '',
|
const storedUserInfo = localStorage.getItem('userInfo');
|
||||||
}),
|
const userInfo = storedUserInfo ? JSON.parse(storedUserInfo) : null;
|
||||||
|
// 从用户信息中提取角色
|
||||||
|
const roles = userInfo?.role ?? '';
|
||||||
|
|
||||||
|
return {
|
||||||
|
userInfo,
|
||||||
|
userRoles: roles,
|
||||||
|
};
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 解决热更新问题
|
// 解决热更新问题
|
||||||
|
|||||||
Reference in New Issue
Block a user