ssvfdn 3 月之前
父节点
当前提交
51fe54a0b9

+ 15 - 0
apps/web-antd/src/api/game_control/player_control.ts

@@ -22,4 +22,19 @@ export async function getPlayerControlList(data:any) {
  */
 export async function updatePlayerControlInfo(data:any) {
     return requestBodyClient.post<ApiResultData>('/player_control/update', data);
+}
+
+/**
+ * 取消点控
+ * @param data
+ */
+export async function cancelPlayerControlInfo(data:any) {
+    return requestClient.post<ApiResultData>('/player_control/cancel', data);
+}
+
+/**
+ * 取消所有点控
+ */
+export async function cancelAllPlayerControl() {
+    return requestClient.post<ApiResultData>('/player_control/all_cancel');
 }

+ 3 - 1
apps/web-antd/src/locales/langs/zh-CN/common.json

@@ -11,5 +11,7 @@
     "warn": "警告",
     "warning": "待处理",
     "serial": "序号",
-    "search_submit_button": "查询"
+    "search_submit_button": "查询",
+    "action": "操作",
+    "suc_msg": "操作成功"
 }

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

@@ -4,6 +4,7 @@
     "game": "游戏",
     "play_control_title": "玩家点控",
     "play_control_list_title": "点控列表",
+    "play_control_record_title": "点控记录",
     "game_list": {
         "game_title": "游戏名称",
         "game_en_title": "游戏英文名称",
@@ -21,5 +22,27 @@
     "button": {
         "create_control": "新增点控",
         "all_cancel_control": "一键取消点控"
+    },
+    "player_control": {
+        "uname": "平台ID",
+        "uname_placeholder": "请输入平台ID,多个玩家用英文\",\"隔天",
+        "game_name": "游戏名称",
+        "edit_rtp": "设置RTP",
+        "max_win_multi": "赢取倍数限制",
+        "auto_cancel_rtp": "达到RTP时自动解除控制",
+        "create_player_control_title": "新增玩家点控",
+        "control_rpt": "点控RTP",
+        "old_rtp": "点控时RTP",
+        "now_rtp": "当前RTP",
+        "bet_amount": "点控期间投注额",
+        "bet_count": "点控期间注单数",
+        "total_win_amount": "点控期间输赢",
+        "control_balance": "点控时分数",
+        "balance": "当前分数",
+        "admin_name": "操作账号",
+        "cancel_control": "取消点控",
+        "confirm_cancel_control": "请确认取消【{title}】该点控?",
+        "confirm_all_cancel_control": "确认取消全部点控?",
+        "end_time": "结束时间"
     }
 }

+ 9 - 0
apps/web-antd/src/router/routes/modules/game_control.ts

@@ -41,6 +41,15 @@ const routes: RouteRecordRaw[] = [
                         path: '/game-control/player-control/list',
                         component: () => import('#/views/game_control/player_control/list/index.vue'),
                     },
+                    {
+                        meta: {
+                            title: $t('game_control.play_control_record_title'),
+                            keepAlive: true
+                        },
+                        name: 'GameControlGamePlayerControlRecord',
+                        path: '/game-control/player-control/record',
+                        component: () => import('#/views/game_control/player_control/record/index.vue'),
+                    },
                 ]
             },
         ],

+ 9 - 9
apps/web-antd/src/views/game_control/player_control/list/create_player.vue

@@ -91,13 +91,13 @@ const [Form, formApi] = useVbenForm({
 			component: 'Textarea',
 			// 对应组件的参数
 			componentProps: {
-				placeholder: '请输入平台ID,多个玩家用英文","隔天',
+				placeholder: $t('game_control.player_control.uname_placeholder'),
 				style:"width:70%;height:100px;",
 			},
 			// 字段名
 			fieldName: 'account_ids',
 			// 界面显示的label
-			label: '平台ID',
+			label: $t('game_control.player_control.uname'),
 			rules: 'required',
 		},
 		{
@@ -114,7 +114,7 @@ const [Form, formApi] = useVbenForm({
 				style:"width:70%",
 			},
 			fieldName: 'game_ids',
-			label: '游戏名称',
+			label: $t('game_control.player_control.game_name'),
 			rules: 'required',
 		},
 
@@ -124,13 +124,13 @@ const [Form, formApi] = useVbenForm({
 				allowClear: true,
 				filterOption: true,
 				options: rtpSelectList,
-				placeholder: '请选择',
+				placeholder: $t('common.placeholder_select'),
 				showSearch: true,
 				style:"width:40%",
 			},
 			defaultValue: undefined,
 			fieldName: 'rtp',
-			label: '设置RTP',
+			label: $t('game_control.player_control.edit_rtp'),
 			rules: 'required',
 		},
 		{
@@ -140,7 +140,7 @@ const [Form, formApi] = useVbenForm({
 				style:"width:30%",
 			},
 			fieldName: 'max_win_multi',
-			label: '赢取倍数限制',
+			label: $t('game_control.player_control.max_win_multi'),
 			suffix: () =>  h('span', { style: 'font-size:14px;color:#666;' }, '(玩家在调控期间每局赢奖倍数限制,此倍数与总押注相乘为每局最高可赢取额度)'),
 		},
 		{
@@ -151,7 +151,7 @@ const [Form, formApi] = useVbenForm({
 				"addon-after":"%"
 			},
 			fieldName: 'auto_cancel_rtp',
-			label: '达到RTP时自动解除控制',
+			label: $t('game_control.player_control.auto_cancel_rtp'),
 		},
 	],
 	wrapperClass: 'grid-cols-1',
@@ -160,8 +160,8 @@ const [Form, formApi] = useVbenForm({
 </script>
 
 <template>
-	<Modal :loading="loading"  title="新增玩家点控">
-		<Form border></Form>
+	<Modal :loading="loading"  :title="$t('game_control.player_control.create_player_control_title')">
+		<Form></Form>
 	</Modal>
 </template>
 

+ 57 - 37
apps/web-antd/src/views/game_control/player_control/list/index.vue

@@ -1,13 +1,16 @@
 <script setup>
 import {$t} from "@vben/locales";
-import {Avatar, Button, Card, Input, InputGroup, Select, SelectOption, Tag} from "ant-design-vue";
-import {Page, useVbenModal} from "@vben/common-ui";
+import {Avatar, Button, Card, Input, InputGroup, message, Select, SelectOption, Tag} from "ant-design-vue";
+import {Page, useVbenModal, confirm} from "@vben/common-ui";
 import {reactive, ref} from "vue";
 import {getGameMinList} from "#/api/game_control/game_config.js";
 import dayjs from "dayjs";
 import {useVbenVxeGrid} from "#/adapter/vxe-table.js";
-import {getPlayerControlList} from "#/api/game_control/player_control.js";
-
+import {
+	cancelAllPlayerControl,
+	cancelPlayerControlInfo,
+	getPlayerControlList
+} from "#/api/game_control/player_control.js";
 
 // 列表筛选
 const filterData = reactive({
@@ -107,24 +110,24 @@ const gridOptions = {
 	// },
 	columns: [
 		{ fixed: 'left',  title: $t('common.serial'), type: 'seq', width: 50},
-		{ fixed: 'left', field: 'third_gid', title: $t('player_data.search.third_gid'), width: 230, titlePrefix: {'content':$t('player_data.gameRecords.tips.third_gid')}, treeNode: true  },
-		{ field: 'third_order_id', title:  $t('player_data.fundsChange.uuid'), width: 230},
-		{ field: 'third_round_id', title: $t('player_data.gameRecords.third_round_id'), width: 230, titlePrefix: {'content':$t('player_data.gameRecords.tips.third_round_id')} },
-		{ field: 'nickname', title: $t('player_data.gameRecords.nickname'), width: 150, titlePrefix: {'content':$t('player_data.gameRecords.tips.nickname')}},
+		{ field: 'create_time', title: $t('player_data.gameRecords.create_time'), width: 200, titlePrefix: {'content':$t('player_data.gameRecords.tips.create_time')} },
 		{ field: 'user_id', title: $t('player_data.gameRecords.user_id'), width: 120, titlePrefix: {'content':$t('player_data.gameRecords.tips.user_id')}},
 		{ field: 'uname', title: $t('player_data.gameRecords.uname'), width: 150, titlePrefix: {'content':$t('player_data.gameRecords.tips.uname')}},
-		{ align: 'left', field: 'game_title', title: $t('player_data.gameRecords.game_title'), width: 200, slots: {"default":"game_title"}},
-		{ field: 'amount', title: $t('player_data.fundsChange.change_amount'), width: 140, slots: {'default':'amount'}},
-		{ field: 'prev_amount', title: $t('player_data.gameRecords.prev_amount'), width: 140, titlePrefix: {'content':$t('player_data.gameRecords.tips.prev_amount')}},
-		{ field: 'next_amount', title: $t('player_data.gameRecords.next_amount'), width: 140, titlePrefix: {'content':$t('player_data.gameRecords.tips.next_amount')}},
-		{ field: 'bet', title: $t('player_data.fundsChange.bet_amount'), width: 140, titlePrefix: {'content':$t('player_data.gameRecords.tips.bet_amount')}, slots: {'default':'bet_amount'}},
-		{ field: 'total_win_amount', title: $t('player_data.fundsChange.change_total_amount'), width: 140,  slots: {'default':'total_win_amount'}},
-		{ field: 'total_amount', title: $t('player_data.fundsChange.total_amount'), width: 140,},
-		{ field: 'action_type', title: $t('player_data.fundsChange.order_type'), width: 140, slots: {'default':'action_type'}},
-		{ field: 'status', title: $t('player_data.fundsChange.order_status'), width: 140, slots: {'default':'status'}},
-		{ field: 'reason', title: $t('player_data.fundsChange.reason'), width: 200},
-		{ field: 'create_time', title: $t('player_data.gameRecords.create_time'), width: 200, titlePrefix: {'content':$t('player_data.gameRecords.tips.create_time')} },
-		// { align:"left", title: $t('game_control.game_list.action'), width: 220, slots: {default:'action'}},
+		{ field: 'nickname', title: $t('player_data.gameRecords.nickname'), width: 150, titlePrefix: {'content':$t('player_data.gameRecords.tips.nickname')}},
+		{ align: 'left', field: 'game_title', title: $t('player_data.gameRecords.game_title'), width: 300, slots: {"default":"game_title"}},
+
+		{ field: 'control_rpt', title: $t('game_control.player_control.control_rpt'), width: 150, },
+		{ field: 'auto_cancel_rtp', title: $t('game_control.player_control.auto_cancel_rtp'), width: 200,},
+		{ field: 'max_win_multi', title: $t('game_control.player_control.max_win_multi'), width: 150},
+		{ field: 'old_rtp', title: $t('game_control.player_control.old_rtp'), width: 150},
+		{ field: 'now_rtp', title: $t('game_control.player_control.now_rtp'), width: 150},
+		{ field: 'bet_amount', title: $t('game_control.player_control.bet_amount'), width: 150},
+		{ field: 'bet_count', title: $t('game_control.player_control.bet_count'), width: 150},
+		{ field: 'total_win_amount', title: $t('game_control.player_control.total_win_amount'), width: 150},
+		{ field: 'control_balance', title: $t('game_control.player_control.control_balance'), width: 150},
+		{ field: 'balance', title: $t('game_control.player_control.balance'), width: 150},
+		{ field: 'admin_name', title: $t('game_control.player_control.admin_name'), width: 150},
+		{ fixed: 'right', align: 'left', field: 'action', title: $t('common.action'), width: 150, slots: {"default":"action"} },
 	],
 	exportConfig: {},
 	// height: 'auto', // 如果设置为 auto,则必须确保存在父节点且不允许存在相邻元素,否则会出现高度闪动问题
@@ -154,6 +157,14 @@ const gridOptions = {
 					form[filterData['search_type']] = search.search_text;
 				}
 				const list = await getPlayerControlList(form);
+				list.list.forEach(item => {
+					if(item.auto_cancel_rtp == 0) {
+						item.auto_cancel_rtp =  "-";
+					}
+					if(item.max_win_multi == 0) {
+						item.max_win_multi =  "-";
+					}
+				})
 				return {
 					total: list.total,
 					items: list.list
@@ -197,26 +208,39 @@ const createControl = (row) => {
 	modalApi.open();
 }
 
+const cancelControl = (row) => {
+	confirm($t('game_control.player_control.confirm_cancel_control', {title: row.game_title})).then(async () => {
+		let res = await cancelPlayerControlInfo({'control_id': row.control_id});
+		message.success($t('common.suc_msg'));
+		gridApi.reload();
+		// alert('Confirmed');
+	}).catch(() => {
+		// alert('Canceled');
+	});
+	return false;
+}
+const cancelAllControl = (row) => {
+	confirm($t('game_control.player_control.confirm_all_cancel_control')).then(async () => {
+		let res = await cancelAllPlayerControl();
+		message.success($t('common.suc_msg'));
+		gridApi.reload();
+		// alert('Confirmed');
+	}).catch(() => {
+		// alert('Canceled');
+	});
+}
+
 </script>
 
 <template>
 	<Page>
 		<Grid>
-			<template #bet_amount="{ row }">
-				<span style="color:red">{{row.bet}}</span>
-			</template>
-			<template #amount="{ row }">
-				<span style="color: green" v-if="row.amount >= 0">{{row.amount}}</span>
-				<span style="color: red" v-else>{{row.amount}}</span>
-			</template>
 			<template #total_win_amount="{ row }">
 				<span style="color: green" v-if="row.total_win_amount >= 0">{{row.total_win_amount}}</span>
 				<span style="color: red" v-else>{{row.total_win_amount}}</span>
 			</template>
-			<template #action_type="{ row }">
-				<Tag v-if="row.action_type == 1" color="blue">{{ $t('player_data.fundsChange.bet') }}</Tag>
-				<Tag v-if="row.action_type == 3" color="green">{{ $t('player_data.fundsChange.result_amount') }}</Tag>
-				<Tag v-if="row.action_type == 4" color="red">{{ $t('player_data.fundsChange.check') }}</Tag>
+			<template #action="{ row }">
+				<Button  type="link" danger @click="cancelControl(row)">{{$t('game_control.player_control.cancel_control')}}</Button>
 			</template>
 			<template #game_title="{ row }">
 				<div>
@@ -225,11 +249,7 @@ const createControl = (row) => {
 					<span style="margin-left: .5rem;display: inline-block;">{{row.game_title}}</span>
 				</div>
 			</template>
-			<template #status="{ row }">
-				<Tag v-if="row.status == 2" color="green">{{$t('common.success')}}</Tag>
-				<Tag v-if="row.status == 0" color="red">{{$t('common.abnormal')}}</Tag>
-				<Tag v-if="row.status == 1" color="blue">{{$t('common.warn')}}</Tag>
-			</template>
+
 			<template #form-search_text="slotProps">
 				<Input-Group compact>
 					<Select name="search_type" style="width: 110px;" v-model:value="filterData.search_type">
@@ -243,7 +263,7 @@ const createControl = (row) => {
 
 			<template #toolbar-actions>
 				<Button type="primary" @click="createControl">{{$t('game_control.button.create_control')}}</Button>
-				<Button type="primary" danger style="margin-left:10px;">{{$t('game_control.button.all_cancel_control')}}</Button>
+				<Button type="primary"  danger style="margin-left:10px;" @click="cancelAllControl">{{$t('game_control.button.all_cancel_control')}}</Button>
 			</template>
 		</Grid>
 		<Modal />

+ 229 - 0
apps/web-antd/src/views/game_control/player_control/record/index.vue

@@ -0,0 +1,229 @@
+<script setup>
+import {$t} from "@vben/locales";
+import {Avatar, Button, Card, Input, InputGroup, message, Select, SelectOption, Tag} from "ant-design-vue";
+import {Page, useVbenModal, confirm} from "@vben/common-ui";
+import {reactive, ref} from "vue";
+import {getGameMinList} from "#/api/game_control/game_config.js";
+import dayjs from "dayjs";
+import {useVbenVxeGrid} from "#/adapter/vxe-table.js";
+import {
+	cancelAllPlayerControl,
+	cancelPlayerControlInfo,
+	getPlayerControlList
+} from "#/api/game_control/player_control.js";
+
+// 列表筛选
+const filterData = reactive({
+	'search_type':"uname",
+});
+const filterGameList = ref([]);
+getGameMinList().then((data) => {
+	data.forEach((game) => {
+		filterGameList.value.push({
+			'label': `【${game.game_id}】${game.title}`,
+			'value': game.game_id,
+		})
+	})
+})
+const disabledDate = (current) => {
+	// Can not select days before today and today
+	return current && current > dayjs().endOf('day');
+};
+const formOptions = {
+	// 默认展开
+	collapsed: false,
+	// 所有表单项共用,可单独在表单内覆盖
+	commonConfig: {
+		// 所有表单项
+		componentProps: {
+			class: 'w-full',
+		},
+		labelWidth: 80
+	},
+	// 提交函数
+	// handleSubmit: onSubmit,
+	handleReset: onReset,
+	// 垂直布局,label和input在不同行,值为vertical
+	// 水平布局,label和input在同一行
+	layout: 'horizontal',
+	schema: [
+		{
+			// 组件需要在 #/adapter.ts内注册,并加上类型
+			component: 'Input',
+			// 对应组件的参数
+			// 字段名
+			fieldName: 'search_text',
+			// 界面显示的label
+		},
+		{
+			component: 'Select',
+			componentProps: {
+				allowClear: true,
+				filterOption: true,
+				name:'game_id',
+				options: filterGameList,
+				placeholder: $t('common.placeholder_select'),
+				showSearch: true,
+				mode:"tags",
+				maxTagCount: 3
+			},
+			fieldName: 'game_id',
+			label: $t('game_control.game'),
+		},
+		{
+			label:$t("common.range_time"),
+			component: 'RangePicker',
+			defaultValue: [dayjs(), dayjs()],
+			fieldName: 'range_time',
+			componentProps: {
+				disabledDate: disabledDate,
+			}
+		},
+
+	],
+	showCollapseButton: false,
+	// 是否可展开
+	submitButtonOptions: {
+		content: $t('common.search_submit_button'),
+	},
+	wrapperClass: 'grid-cols-1 md:grid-cols-3',
+};
+function onReset() {
+	gridApi.formApi.resetForm();
+	filterData.search_type = 'uname';
+	gridApi.reload();
+}
+// 列表
+const gridOptions = {
+	border: true,
+	stripe: true,
+	scrollbarConfig: {
+		x: {
+			visible: 'visible'
+		},
+		y: {
+			visible: 'auto'
+		}
+	},
+	// checkboxConfig: {
+	// 	highlight: true,
+	// },
+	columns: [
+		{ fixed: 'left',  title: $t('common.serial'), type: 'seq', width: 50},
+		{ field: 'create_time', title: $t('player_data.gameRecords.create_time'), width: 200, titlePrefix: {'content':$t('player_data.gameRecords.tips.create_time')} },
+		{ field: 'end_time', title: $t('game_control.player_control.end_time'), width: 200 },
+		{ field: 'user_id', title: $t('player_data.gameRecords.user_id'), width: 120, titlePrefix: {'content':$t('player_data.gameRecords.tips.user_id')}},
+		{ field: 'uname', title: $t('player_data.gameRecords.uname'), width: 150, titlePrefix: {'content':$t('player_data.gameRecords.tips.uname')}},
+		{ field: 'nickname', title: $t('player_data.gameRecords.nickname'), width: 150, titlePrefix: {'content':$t('player_data.gameRecords.tips.nickname')}},
+		{ align: 'left', field: 'game_title', title: $t('player_data.gameRecords.game_title'), width: 300, slots: {"default":"game_title"}},
+
+		{ field: 'control_rpt', title: $t('game_control.player_control.control_rpt'), width: 150, },
+		{ field: 'auto_cancel_rtp', title: $t('game_control.player_control.auto_cancel_rtp'), width: 200,},
+		{ field: 'max_win_multi', title: $t('game_control.player_control.max_win_multi'), width: 150},
+		{ field: 'old_rtp', title: $t('game_control.player_control.old_rtp'), width: 150},
+		{ field: 'now_rtp', title: $t('game_control.player_control.now_rtp'), width: 150},
+		{ field: 'bet_amount', title: $t('game_control.player_control.bet_amount'), width: 150},
+		{ field: 'bet_count', title: $t('game_control.player_control.bet_count'), width: 150},
+		{ field: 'total_win_amount', title: $t('game_control.player_control.total_win_amount'), width: 150},
+		{ field: 'control_balance', title: $t('game_control.player_control.control_balance'), width: 150},
+		{ field: 'balance', title: $t('game_control.player_control.balance'), width: 150},
+		{ field: 'admin_name', title: $t('game_control.player_control.admin_name'), width: 150},
+	],
+	exportConfig: {},
+	// height: 'auto', // 如果设置为 auto,则必须确保存在父节点且不允许存在相邻元素,否则会出现高度闪动问题
+	keepSource: true,
+	treeConfig: {
+		transform: true, // 指定表格为树形表格
+		parentField: 'parent_id', // 父节点字段名
+		rowField: 'id', // 行数据字段名
+	},
+	proxyConfig: {
+		ajax: {
+			query: async ({ page }) => {
+				let form = {
+					page: page.currentPage,
+					limit: page.pageSize,
+					compress: 0,
+					is_end_time: 1
+				};
+				const search = await gridApi.formApi.getValues();
+				if(search.game_id) {
+					form.game_id = search.game_id;
+				}
+				if(search.range_time) {
+					form['start_time'] = search.range_time[0].format('YYYY-MM-DD');
+					form['end_time'] = search.range_time[1].format('YYYY-MM-DD');
+				}
+				if(search.search_text) {
+					form[filterData['search_type']] = search.search_text;
+				}
+				const list = await getPlayerControlList(form);
+				list.list.forEach(item => {
+					if(item.auto_cancel_rtp == 0) {
+						item.auto_cancel_rtp =  "-";
+					}
+					if(item.max_win_multi == 0) {
+						item.max_win_multi =  "-";
+					}
+				})
+				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,
+});
+
+</script>
+
+<template>
+	<Page>
+		<Grid>
+			<template #total_win_amount="{ row }">
+				<span style="color: green" v-if="row.total_win_amount >= 0">{{row.total_win_amount}}</span>
+				<span style="color: red" v-else>{{row.total_win_amount}}</span>
+			</template>
+			<template #game_title="{ row }">
+				<div>
+					<Tag  color="blue" size="large">{{row.game_type_text}}</Tag>
+					<Avatar shape="square" :src="row.game_image_url"></Avatar>
+					<span style="margin-left: .5rem;display: inline-block;">{{row.game_title}}</span>
+				</div>
+			</template>
+
+			<template #form-search_text="slotProps">
+				<Input-Group compact>
+					<Select name="search_type" style="width: 110px;" v-model:value="filterData.search_type">
+						<Select-Option value="uname">{{$t('player_data.search.uname')}}</Select-Option>
+						<Select-Option value="nickname">{{$t('player_data.search.nickname')}}</Select-Option>
+						<Select-Option value="user_id">{{$t('player_data.search.user_id')}}</Select-Option>
+					</Select>
+					<Input name="search_text" allowClear :placeholder="$t('player_data.placeholder') + '' + $t('player_data.search[\''+filterData.search_type+'\']')" v-bind="slotProps" style="width:calc(100% - 110px)"></Input>
+				</Input-Group>
+			</template>
+
+		</Grid>
+		<Modal />
+	</Page>
+</template>
+
+<style scoped>
+.ant-input-group.ant-input-group-compact>*:not(:last-child) {
+	border-inline-end-width: 0;
+}
+</style>