ssvfdn 3 mesi fa
parent
commit
31ee36e4fd

+ 11 - 0
apps/web-antd/src/api/dashboard/analytics.ts

@@ -0,0 +1,11 @@
+import {requestBodyClient, requestClient} from "#/api/request";
+
+interface ApiResultListData {
+    data: Object;
+    status: number;
+    total: number;
+    list: Array<any>;
+}
+export async function getOverviewDashboard() {
+    return requestClient.get<ApiResultListData>('/overview/dashboard');
+}

+ 17 - 0
apps/web-antd/src/api/dashboard/ggr_report.ts

@@ -0,0 +1,17 @@
+import {requestClient} from "#/api/request";
+
+interface ApiResultListData {
+    data: Object;
+    status: number;
+    total: number;
+    list: Array<any>;
+}
+
+/**
+ *
+ */
+export async function getGgrReportList(data:any) {
+    const params = new URLSearchParams(data); // 创建一个新的URLSearchParams对象
+    const queryString = params.toString(); // 转换为查询字符串
+    return requestClient.get<ApiResultListData>('/overview/settlement_report?' + queryString);
+}

+ 23 - 0
apps/web-antd/src/locales/langs/zh-CN/dashboard.json

@@ -0,0 +1,23 @@
+{
+    "analytics_title": "仪表盘",
+    "ggr_report_title": "结算报表",
+    "analytics": {
+        "commission": "今日返佣金额",
+        "bet_users": "投注人数",
+        "bet_count": "注单数",
+        "bet_amount": "注单金额",
+        "game_profit": "今日输赢",
+        "online_users": "在线人数",
+        "login_users": "登录人数",
+        "register_users": "注册人数",
+        "yesterday": "较昨天",
+        "good_morning": "早上好",
+        "good_afternoon": "下午好",
+        "good_evening": "晚上好"
+    },
+    "ggr_report": {
+        "bet_amount": "总投注",
+        "game_profit": "总输赢",
+        "platform_fee": "平台费用"
+    }
+}

+ 18 - 9
apps/web-antd/src/router/routes/modules/dashboard.ts

@@ -19,18 +19,27 @@ const routes: RouteRecordRaw[] = [
         meta: {
           affixTab: true,
           icon: 'lucide:area-chart',
-          title: $t('page.dashboard.analytics'),
+          title: $t('dashboard.analytics_title'),
         },
       },
-      {
-        name: 'Workspace',
-        path: '/workspace',
-        component: () => import('#/views/dashboard/workspace/index.vue'),
-        meta: {
-          icon: 'carbon:workspace',
-          title: $t('page.dashboard.workspace'),
+        {
+            name: 'GgrReport',
+            path: '/ggr-report',
+            component: () => import('#/views/dashboard/ggr_report/index.vue'),
+            meta: {
+                icon: 'solar:align-bottom-outline',
+                title: $t('dashboard.ggr_report_title'),
+            },
         },
-      },
+      // {
+      //   name: 'Workspace',
+      //   path: '/workspace',
+      //   component: () => import('#/views/dashboard/workspace/index.vue'),
+      //   meta: {
+      //     icon: 'carbon:workspace',
+      //     title: $t('page.dashboard.workspace'),
+      //   },
+      // },
     ],
   },
 ];

+ 164 - 81
apps/web-antd/src/views/dashboard/analytics/index.vue

@@ -1,90 +1,173 @@
-<script lang="ts" setup>
-import type { AnalysisOverviewItem } from '@vben/common-ui';
-import type { TabOption } from '@vben/types';
-
+<script setup>
 import {
-  AnalysisChartCard,
-  AnalysisChartsTabs,
-  AnalysisOverview,
+	Page,
+	AnalysisOverview,
 } from '@vben/common-ui';
-import {
-  SvgBellIcon,
-  SvgCakeIcon,
-  SvgCardIcon,
-  SvgDownloadIcon,
-} from '@vben/icons';
+import {ref, reactive, computed} from "vue";
+import {getOverviewDashboard} from "#/api/dashboard/analytics.js";
+import {Button, Card, Tag, Avatar, Switch, TypographyTitle} from "ant-design-vue";
+
+import { preferences } from '@vben/preferences';
+
+import { createIconifyIcon } from '@vben-core/icons';
+import {useUserStore} from "@vben/stores";
+import {$t} from "@vben/locales";
+const IconResultAmount = createIconifyIcon('streamline-ultimate-color:cash-payment-coin-dollar');
+const IconBetUsers = createIconifyIcon('streamline-ultimate-color:user-cash-scale');
+const IconBetCount = createIconifyIcon('streamline-ultimate-color:monetization-touch-coin');
+const IconBetAmount = createIconifyIcon('streamline-ultimate-color:saving-money-flower');
+const IconGameProfit = createIconifyIcon('streamline-ultimate-color:accounting-coins');
+const IconLoginUsers = createIconifyIcon('streamline-ultimate-color:network-user');
+const IconRegisterUsers = createIconifyIcon('streamline-ultimate-color:programming-user-chat');
+const IconOnlineUsers = createIconifyIcon('streamline-ultimate-color:multiple-users-network');
+
+
 
-import AnalyticsTrends from './analytics-trends.vue';
-import AnalyticsVisitsData from './analytics-visits-data.vue';
-import AnalyticsVisitsSales from './analytics-visits-sales.vue';
-import AnalyticsVisitsSource from './analytics-visits-source.vue';
-import AnalyticsVisits from './analytics-visits.vue';
+const overviewItems = reactive( [
+	{
+		icon: IconResultAmount,
+		title: $t('dashboard.analytics.commission'),
+		totalTitle: $t('dashboard.analytics.yesterday'),
+		totalValue: 0,
+		value: 0,
+		key: 'commission',
+	},
+	{
+		icon: IconBetUsers,
+		title: $t('dashboard.analytics.bet_users'),
+		totalTitle: $t('dashboard.analytics.yesterday'),
+		totalValue: 0,
+		value: 0,
+		key: 'bet_users'
+	},
+	{
+		icon: IconBetCount,
+		title: $t('dashboard.analytics.bet_count'),
+		totalTitle: $t('dashboard.analytics.yesterday'),
+		totalValue: 0,
+		value: 0,
+		key: 'bet_count'
+	},
+	{
+		icon: IconBetAmount,
+		title: $t('dashboard.analytics.bet_amount'),
+		totalTitle: $t('dashboard.analytics.yesterday'),
+		totalValue: 0,
+		value: 0,
+		key: 'bet_amount'
+	},
+	{
+		icon: IconGameProfit,
+		title: $t('dashboard.analytics.game_profit'),
+		totalTitle: $t('dashboard.analytics.yesterday'),
+		totalValue: 0,
+		value: 0,
+		key: 'game_profit'
+	},
+	{
+		icon: IconOnlineUsers,
+		title: $t('dashboard.analytics.online_users'),
+		totalTitle: $t('dashboard.analytics.yesterday'),
+		totalValue: 0,
+		value: 0,
+		key: 'online_users'
+	},
+	{
+		icon: IconLoginUsers,
+		title: $t('dashboard.analytics.login_users'),
+		totalTitle: $t('dashboard.analytics.yesterday'),
+		totalValue: 0,
+		value: 0,
+		key: 'login_users'
+	},
+	{
+		icon: IconRegisterUsers,
+		title: $t('dashboard.analytics.register_users'),
+		totalTitle: $t('dashboard.analytics.yesterday'),
+		totalValue: 0,
+		value: 0,
+		key: 'register_users'
+	},
+]);
+const getData = async function () {
+	let res = await getOverviewDashboard();
+	overviewItems.forEach(item => {
+		let key = item.key;
+		item['value'] = res?.today[key] || 0;
+		item['totalValue'] = res?.yesterday[key] || 0;
+	})
+}
+getData();
+const userStore = useUserStore();
+const avatar = computed(() => {
+	return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
+});
 
-const overviewItems: AnalysisOverviewItem[] = [
-  {
-    icon: SvgCardIcon,
-    title: '用户量',
-    totalTitle: '总用户量',
-    totalValue: 120_000,
-    value: 2000,
-  },
-  {
-    icon: SvgCakeIcon,
-    title: '访问量',
-    totalTitle: '总访问量',
-    totalValue: 500_000,
-    value: 20_000,
-  },
-  {
-    icon: SvgDownloadIcon,
-    title: '下载量',
-    totalTitle: '总下载量',
-    totalValue: 120_000,
-    value: 8000,
-  },
-  {
-    icon: SvgBellIcon,
-    title: '使用量',
-    totalTitle: '总使用量',
-    totalValue: 50_000,
-    value: 5000,
-  },
-];
+function getGreeting() {
+	// 获取当前时间的小时数
+	const now = new Date();
+	const hours = now.getHours();
+
+	// 判断时间段并输出问候语
+	if (hours < 12) {
+		return $t('dashboard.analytics.good_morning');
+	} else if (hours >= 12 && hours < 18) {
+		return $t('dashboard.analytics.good_afternoon');
+	} else {
+		return $t('dashboard.analytics.good_evening');
+	}
+}
+// 调用函数
+let timeDesc = getGreeting();
 
-const chartTabs: TabOption[] = [
-  {
-    label: '流量趋势',
-    value: 'trends',
-  },
-  {
-    label: '月访问量',
-    value: 'visits',
-  },
-];
 </script>
 
 <template>
-  <div class="p-5">
-    <AnalysisOverview :items="overviewItems" />
-    <AnalysisChartsTabs :tabs="chartTabs" class="mt-5">
-      <template #trends>
-        <AnalyticsTrends />
-      </template>
-      <template #visits>
-        <AnalyticsVisits />
-      </template>
-    </AnalysisChartsTabs>
-
-    <div class="mt-5 w-full md:flex">
-      <AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问数量">
-        <AnalyticsVisitsData />
-      </AnalysisChartCard>
-      <AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问来源">
-        <AnalyticsVisitsSource />
-      </AnalysisChartCard>
-      <AnalysisChartCard class="mt-5 md:mt-0 md:w-1/3" title="访问来源">
-        <AnalyticsVisitsSales />
-      </AnalysisChartCard>
-    </div>
-  </div>
+<Page>
+	<div class="p-5">
+		<Card class="bottom-4">
+			<div class="flex content-center items-center">
+			<Avatar :size="84" :src="avatar"></Avatar>
+			<TypographyTitle :level="3" style="margin-left: 1rem;">{{timeDesc}} , {{userStore.userInfo?.realName}}, 开始您一天的工作吧!</TypographyTitle>
+			</div>
+		</Card>
+		<AnalysisOverview :items="overviewItems" />
+	</div>
+</Page>
 </template>
+
+<style scoped>
+::v-deep(.size-8) {width:2.6rem;height:2.6rem;}
+::v-deep(.icon-up > div:last-child) {justify-content: normal}
+::v-deep(.icon-down > div:last-child) {justify-content: normal}
+
+::v-deep(.icon-up > div:last-child span:last-child) {position: relative;padding-left:1rem;}
+::v-deep(.icon-down > div:last-child span:last-child) {position: relative;padding-left:1rem;}
+
+::v-deep(.icon-up > div:last-child span:last-child::after) {
+	width: 1rem;
+	height: 1rem;
+	position: absolute;
+	content: " ";
+	display: inline-block;
+	left: .1rem;
+	top: .15rem;
+	background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='%230eaf03' d='M7.03 9.97h4v8.92l2.01.03V9.97h3.99l-5-5Z' stroke-width='0.5' stroke='%230eaf03'/%3E%3C/svg%3E") no-repeat;
+	background-size: 100% 100%;
+	background-position: -.5rem 0;
+	padding-left: 2rem;
+}
+::v-deep(.icon-down > div:last-child span:last-child::after) {
+	width: 1rem;
+	height: 1rem;
+	position: absolute;
+	content: " ";
+	display: inline-block;
+	left: .1rem;
+	top: .15rem;
+	background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='%23af1203' d='M7.03 13.92h4V5l2.01-.03v8.95h3.99l-5 5Z' stroke-width='0.5' stroke='%23af1203'/%3E%3C/svg%3E") no-repeat;
+	background-size: 100% 100%;
+	background-position: -.5rem 0;
+	padding-left: 2rem;
+}
+</style>

+ 0 - 0
apps/web-antd/src/views/dashboard/analytics/analytics-trends.vue → apps/web-antd/src/views/dashboard/analytics_old/analytics-trends.vue


+ 0 - 0
apps/web-antd/src/views/dashboard/analytics/analytics-visits-data.vue → apps/web-antd/src/views/dashboard/analytics_old/analytics-visits-data.vue


+ 0 - 0
apps/web-antd/src/views/dashboard/analytics/analytics-visits-sales.vue → apps/web-antd/src/views/dashboard/analytics_old/analytics-visits-sales.vue


+ 0 - 0
apps/web-antd/src/views/dashboard/analytics/analytics-visits-source.vue → apps/web-antd/src/views/dashboard/analytics_old/analytics-visits-source.vue


+ 0 - 0
apps/web-antd/src/views/dashboard/analytics/analytics-visits.vue → apps/web-antd/src/views/dashboard/analytics_old/analytics-visits.vue


+ 90 - 0
apps/web-antd/src/views/dashboard/analytics_old/index.vue

@@ -0,0 +1,90 @@
+<script lang="ts" setup>
+import type { AnalysisOverviewItem } from '@vben/common-ui';
+import type { TabOption } from '@vben/types';
+
+import {
+  AnalysisChartCard,
+  AnalysisChartsTabs,
+  AnalysisOverview,
+} from '@vben/common-ui';
+import {
+  SvgBellIcon,
+  SvgCakeIcon,
+  SvgCardIcon,
+  SvgDownloadIcon,
+} from '@vben/icons';
+
+import AnalyticsTrends from './analytics-trends.vue';
+import AnalyticsVisitsData from './analytics-visits-data.vue';
+import AnalyticsVisitsSales from './analytics-visits-sales.vue';
+import AnalyticsVisitsSource from './analytics-visits-source.vue';
+import AnalyticsVisits from './analytics-visits.vue';
+
+const overviewItems: AnalysisOverviewItem[] = [
+  {
+    icon: SvgCardIcon,
+    title: '用户量',
+    totalTitle: '总用户量',
+    totalValue: 120_000,
+    value: 2000,
+  },
+  {
+    icon: SvgCakeIcon,
+    title: '访问量',
+    totalTitle: '总访问量',
+    totalValue: 500_000,
+    value: 20_000,
+  },
+  {
+    icon: SvgDownloadIcon,
+    title: '下载量',
+    totalTitle: '总下载量',
+    totalValue: 120_000,
+    value: 8000,
+  },
+  {
+    icon: SvgBellIcon,
+    title: '使用量',
+    totalTitle: '总使用量',
+    totalValue: 50_000,
+    value: 5000,
+  },
+];
+
+const chartTabs: TabOption[] = [
+  {
+    label: '流量趋势',
+    value: 'trends',
+  },
+  {
+    label: '月访问量',
+    value: 'visits',
+  },
+];
+</script>
+
+<template>
+  <div class="p-5">
+    <AnalysisOverview :items="overviewItems" />
+    <AnalysisChartsTabs :tabs="chartTabs" class="mt-5">
+      <template #trends>
+        <AnalyticsTrends />
+      </template>
+      <template #visits>
+        <AnalyticsVisits />
+      </template>
+    </AnalysisChartsTabs>
+
+    <div class="mt-5 w-full md:flex">
+      <AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问数量">
+        <AnalyticsVisitsData />
+      </AnalysisChartCard>
+      <AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问来源">
+        <AnalyticsVisitsSource />
+      </AnalysisChartCard>
+      <AnalysisChartCard class="mt-5 md:mt-0 md:w-1/3" title="访问来源">
+        <AnalyticsVisitsSales />
+      </AnalysisChartCard>
+    </div>
+  </div>
+</template>

+ 170 - 0
apps/web-antd/src/views/dashboard/ggr_report/index.vue

@@ -0,0 +1,170 @@
+<script setup>
+import {Page} from "@vben/common-ui";
+import dayjs from "dayjs";
+import {$t} from "@vben/locales";
+import {useVbenVxeGrid} from "#/adapter/vxe-table.js";
+import {Button, Card, Tag, Avatar, Switch, Space, TypographyTitle} from "ant-design-vue";
+import {getAgentList} from "#/api/data_statistics/agent.js";
+import {getGgrReportList} from "#/api/dashboard/ggr_report.js";
+import {ref} from "vue";
+
+const disabledDate = (current) => {
+	return current && current > dayjs().endOf('day');
+};
+const formOptions = {
+	// 默认展开
+	collapsed: false,
+	// 所有表单项共用,可单独在表单内覆盖
+	commonConfig: {
+		// 所有表单项
+		componentProps: {
+			class: 'w-full',
+		},
+	},
+	// 垂直布局,label和input在不同行,值为vertical
+	// 水平布局,label和input在同一行
+	layout: 'horizontal',
+	schema: [
+		{
+			label:$t("common.day_range_time"),
+			component: 'RangePicker',
+			defaultValue: [dayjs(), dayjs()],
+			fieldName: 'range_time',
+			componentProps: {
+				disabledDate: disabledDate,
+			}
+		},
+	],
+	// 是否可展开
+	submitButtonOptions: {
+		content: $t('common.search_submit_button'),
+	},
+	showCollapseButton: false,
+	wrapperClass: 'grid-cols-1 md:grid-cols-3',
+}
+
+// 列表
+const gridOptions = {
+	border: true,
+	stripe: true,
+	scrollbarConfig: {
+		x: {
+			visible: 'visible'
+		},
+		y: {
+			visible: 'auto'
+		}
+	},
+	columns: [
+		{ fixed: 'left',  title: $t('common.serial'), type: 'seq', width: 50},
+		{ field: 'date', title: $t('data_statistics.daily_agent.date'), minWidth: 200},
+		{ field: 'bet_users', title: $t('data_statistics.daily_agent.bet_users'), minWidth: 160},
+		{ field: 'bet_amount', title: $t('dashboard.ggr_report.bet_amount'), minWidth: 160},
+		{ field: 'game_profit', title: $t('dashboard.ggr_report.game_profit'), minWidth:160, slots:{'default':'game_profit'}},
+		{ field: 'platform_fee', title: $t('dashboard.ggr_report.platform_fee'), minWidth: 160},
+	],
+	keepSource: true,
+	proxyConfig: {
+		ajax: {
+			query: async ({ page }, formValues) => {
+				let form = {
+					page: page.currentPage,
+					limit: page.pageSize,
+					compress: 0
+				};
+				const search = await gridApi.formApi.getValues();
+				if(search.range_time) {
+					if(form.range_time) {
+						delete form.range_time;
+					}
+					form['start_time'] = search.range_time[0].format('YYYY-MM-DD');
+					form['end_time'] = search.range_time[1].format('YYYY-MM-DD');
+				}
+				const list = await getGgrReportList(form);
+				return {
+					total: list.total,
+					items: list.list
+				}
+			},
+		},
+	},
+	rowConfig: {
+		isHover: true,
+	},
+	toolbarConfig: {
+		custom: true,
+		export: true,
+		// import: true,
+		refresh: true,
+		zoom: true,
+	},
+};
+const [Grid, gridApi] = useVbenVxeGrid({
+	formOptions,
+	gridOptions,
+});
+
+let typeButton = ref(1);
+const changeTime = function ($timeType) {
+	let start_time, end_time;
+	switch ($timeType) {
+		case 1:
+			start_time = dayjs().startOf('day');
+			end_time = dayjs().endOf('day');
+			break;
+		case 2:
+			start_time = dayjs().subtract(1, 'day').startOf('day');
+			end_time = dayjs().subtract(1, 'day').endOf('day');
+			break;
+		case 3:
+			start_time = dayjs().startOf('week');
+			end_time = dayjs().endOf('week');
+			break;
+		case 4:
+			start_time = dayjs().subtract(1, 'week').startOf('week')
+			end_time = dayjs().subtract(1, 'week').endOf('week')
+			break
+		case 5:
+			start_time = dayjs().startOf('month');
+			end_time = dayjs().endOf('month');
+			break;
+		case 6:
+			start_time = dayjs().subtract(1, 'month').startOf('month');
+			end_time = dayjs().subtract(1, 'month').endOf('month');
+			break;
+	}
+	if(start_time) {
+		gridApi.formApi.setValues({
+			'range_time': [start_time, end_time],
+		})
+		typeButton.value = $timeType;
+		gridApi.reload();
+	}
+}
+
+</script>
+
+<template>
+	<Page>
+		<Grid>
+			<template #game_profit="{ row }">
+				<span style="color:green" v-if="row.game_profit >= 0">{{row.game_profit}}</span>
+				<span style="color:red" v-else>{{row.game_profit}}</span>
+			</template>
+			<template #toolbar-tools>
+				<Space>
+				<Button :type="typeButton == 1 ? 'primary' : 'default'" @click="changeTime(1)">今 日</Button>
+				<Button :type="typeButton == 2 ? 'primary' : 'default'" @click="changeTime(2)">昨 日</Button>
+				<Button :type="typeButton == 3 ? 'primary' : 'default'" @click="changeTime(3)">本 周</Button>
+				<Button :type="typeButton == 4 ? 'primary' : 'default'" @click="changeTime(4)">上 周</Button>
+				<Button :type="typeButton == 5 ? 'primary' : 'default'" @click="changeTime(5)">本 月</Button>
+				<Button :type="typeButton == 6 ? 'primary' : 'default'" @click="changeTime(6)">上 月</Button>
+				</Space>
+			</template>
+		</Grid>
+	</Page>
+</template>
+
+<style scoped>
+
+</style>

+ 1 - 1
apps/web-antd/src/views/game_control/auto_rtp/config/info.vue

@@ -247,5 +247,5 @@ const addKill = function () {
 
 <style scoped>
 .grid-class {width: 100%}
-.grid-form-class-diy ::v-deep .ant-select {width: 25%}
+.grid-form-class-diy ::v-deep(.ant-select) {width: 25%}
 </style>

+ 2 - 2
packages/effects/common-ui/src/ui/dashboard/analysis/analysis-overview.vue

@@ -27,7 +27,7 @@ withDefaults(defineProps<Props>(), {
 <template>
   <div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
     <template v-for="item in items" :key="item.title">
-      <Card :title="item.title" class="w-full">
+      <Card :title="item.title" class="w-full" :class="item.value >= item.totalValue ? 'icon-up' : 'icon-down'">
         <CardHeader>
           <CardTitle class="text-xl">{{ item.title }}</CardTitle>
         </CardHeader>
@@ -35,7 +35,7 @@ withDefaults(defineProps<Props>(), {
         <CardContent class="flex items-center justify-between">
           <VbenCountToAnimator
             :end-val="item.value"
-            :start-val="1"
+            :start-val="0"
             class="text-xl"
             prefix=""
           />