Răsfoiți Sursa

全局自动养客

ssvfdn 3 luni în urmă
părinte
comite
8a5c7e6267

+ 29 - 0
apps/web-antd/src/api/game_control/feed_user_global.ts

@@ -0,0 +1,29 @@
+import {requestBodyClient, requestClient} from "#/api/request";
+
+interface ApiResultListData {
+    data: Object;
+    status: number;
+    total: number;
+    list: Array<any>;
+}
+
+/**
+ * 获取点控记录列表
+ */
+export async function getFeedUserGlobalList(data:any) {
+    const params = new URLSearchParams(data); // 创建一个新的URLSearchParams对象
+    const queryString = params.toString(); // 转换为查询字符串
+    return requestClient.get<ApiResultListData>('/improve_user_rtp/global/list?' + queryString);
+}
+
+/**
+ * 增加和更新点控
+ * @param data
+ */
+export async function updateFeedUserGlobalInfo(data:any) {
+    return requestBodyClient.post<ApiResultData>('/improve_user_rtp/global/update', data);
+}
+
+export async function updateFeedUserGlobalStatus(data:any) {
+    return requestBodyClient.post<ApiResultData>('/improve_user_rtp/global/status', data);
+}

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

@@ -15,5 +15,7 @@
     "action": "操作",
     "suc_msg": "操作成功",
     "delete": "删除",
-    "add": "添加"
+    "add": "添加",
+    "status_open": "开启",
+    "status_close": "关闭"
 }

+ 16 - 0
apps/web-antd/src/locales/langs/zh-CN/feed_user.json

@@ -2,6 +2,7 @@
     "feed_user_title": "养客功能",
     "feed_user_list_title": "控制列表",
     "feed_user_record_title": "历史控制记录",
+    "feed_user_global_title": "全局控制",
     "record": {
         "create_time": "控制时间",
         "status": "养客状态",
@@ -12,5 +13,20 @@
         "balance": "玩家当前剩余分数",
         "start_end_time": "生效周期",
         "bet_amount_effective_count": "生效次数/设定次数"
+    },
+    "global": {
+        "user_init_status": "玩家初始状态",
+        "net_income": "玩家游戏盈利值达到后关闭功能",
+        "turnover_multiple": "玩家游戏内流水倍数达到后关闭功能",
+        "evaluation_period": "系统控制功能触发周期",
+        "trigger_interval_rounds": "每次系统控制间隔局数",
+        "status": "功能状态",
+        "confirm_update_status":"此选择的商户的功能-\"全局养客功能\"将会被{status}",
+        "up_rtp": "RTP提升",
+        "number_order": "{number}次",
+        "button_tip": "查看功能场景示例",
+        "desc_1": "功能描述: 本功能旨在通过后台设置特定数值目标(包括盈利值或流水倍数),动态调整玩家在游戏内的RTP(玩家回报率)档位,从而实现对玩家游戏结果的精细化控制。玩家在达到设定的数值目标之一(盈利值或流水倍数)时,控制自动结束。用于营销活动或维护亏损过大玩家的精准控制,提升玩家盈利。",
+        "desc_2": "触发模式: 支持周期性触发规则,包括:每日触发指定次数、每周触发指定次数、每月触发指定次数、终生触发指定次数、自定义时间段内触发指定次数。",
+        "desc_3": "适用范围:全局控制:可同时作用于所有玩家,满足大范围的盈利或流水目标管理需求。 单人控制:支持针对单一玩家进行独立控制,实现个性化干预。"
     }
 }

+ 2 - 1
apps/web-antd/src/locales/langs/zh-CN/game_control.json

@@ -49,7 +49,8 @@
         "confirm_all_cancel_control2": "确认取消全部控制?",
         "end_time": "结束时间",
         "desc":"(玩家在调控期间每局赢奖倍数限制,此倍数与总押注相乘为每局最高可赢取额度)",
-        "view_info": "查看配置"
+        "view_info": "查看配置",
+        "edit_info": "编辑配置"
     },
     "auto_rtp": {
         "new_user_number": "定义新手玩家的游戏局数",

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

@@ -81,6 +81,15 @@ const routes: RouteRecordRaw[] = [
                 name: 'FeedUser',
                 path: '/game-control/feed-user',
                 children:[
+                    {
+                        meta: {
+                            title: $t('feed_user.feed_user_global_title'),
+                            keepAlive: true
+                        },
+                        name: 'FeedUserGlobal',
+                        path: '/game-control/feed-user/global',
+                        component: () => import('#/views/game_control/feed_user/global/index.vue'),
+                    },
                     {
                         meta: {
                             title: $t('feed_user.feed_user_list_title'),

+ 255 - 0
apps/web-antd/src/views/game_control/feed_user/global/create.vue

@@ -0,0 +1,255 @@
+<script setup>
+import {InputNumber, InputGroup, message, Select, SelectOption, RangePicker, DescriptionsItem, Row, Space, Col} from "ant-design-vue";
+
+import {useVbenForm} from "#/adapter/form.js";
+import {$t} from "@vben/locales";
+import {h, reactive, ref, toRaw, toRefs} from "vue";
+import {Page, useVbenModal, confirm} from "@vben/common-ui";
+import {updateFeedUserInfo} from "#/api/game_control/feed_user_list.js";
+import dayjs from "dayjs";
+import {updateFeedUserGlobalInfo, updateFeedUserGlobalStatus} from "#/api/game_control/feed_user_global.js";
+const title = ref('查看');
+
+let netIncomeList = [10, 20, 30, 50, 70, 100, 200, 500, 1000, 1500, 2000, 3000, 5000, 10000, 50000, 100000, 500000, 1000000];
+let turnoverMultipleList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 50, 100, 500, 1000, 5000];
+let evaluationPeriodList = [
+	{'label':'每日','value':1},
+	{'label':'每周','value':2},
+	{'label':'每月','value':3},
+	{'label':'终生','value':4},
+	{'label':'自选时间段','value':5},
+];
+
+let effectiveCountList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 50, 100, 1000];
+let triggerIntervalRoundsList = [5, 10, 15, 20, 30, 40, 50, 60, 80, 100, 150, 200, 300];
+
+// 添加或更新 {"uname":"115","net_income":100,"turnover_multiple":5,"evaluation_period":4,"effective_count":6,"trigger_interval_rounds":300}
+
+let form = reactive({
+	'net_income': 100,
+	'turnover_multiple': 5,
+	'evaluation_period': 4,
+	'effective_count': 1,
+	'trigger_interval_rounds': 300,
+	'day_time': [],
+	'day_time_error': false,
+	'custom_time_start':"",
+	'custom_time_end':"",
+})
+let is_view = ref(false);
+const [Modal, modalApi] = useVbenModal({
+	draggable: true,
+	async onOpenChange(isOpen) {
+		if (isOpen) {
+			form = reactive({
+				'net_income': 100,
+				'turnover_multiple': 5,
+				'evaluation_period': 4,
+				'effective_count': 1,
+				'trigger_interval_rounds': 300,
+				'day_time': [],
+				'day_time_error' : false,
+			})
+			let data = await modalApi.getData();
+
+			if(data.data) {
+				let _data = data.data;
+				form.net_income = _data.net_income;
+				form.turnover_multiple = _data.turnover_multiple;
+				form.evaluation_period = _data.evaluation_period;
+				form.effective_count = _data.effective_count;
+				form.trigger_interval_rounds = _data.trigger_interval_rounds;
+				if(form.evaluation_period == 5) {
+					form.day_time = [dayjs(data.custom_time_start), dayjs(data.custom_time_end)];
+				}
+			}
+
+			if(data.is_edit) {
+				is_view.value = false
+				title.value = "新增点控";
+				modalApi.setState({
+					footer:true
+				})
+
+			}else {
+				is_view.value = true;
+				title.value = "查看";
+				modalApi.setState({
+					footer:false
+				})
+			}
+		}
+	},
+	onConfirm:async function () {
+		const status = await formApi.validate();
+		if(!status.valid) {
+			// 验证不通过
+			return false;
+		}
+
+		const _form = await formApi.getValues();
+		form.day_time_error = false;
+		form.custom_time_start = "";
+		form.custom_time_end = "";
+		if(form.evaluation_period == 5) {
+			if(form.day_time.length == 0) {
+				form.day_time_error = true;
+			}else {
+				form.custom_time_start = form.day_time[0].format('YYYY-MM-DD');
+				form.custom_time_end = form.day_time[1].format('YYYY-MM-DD');
+			}
+		}
+		modalApi.lock();
+		const res = await updateFeedUserGlobalInfo(toRaw(form));
+		modalApi.unlock();
+		if(res.state) {
+			modalApi.setData({
+				'is_reload': true
+			});
+			message.success(res.message);
+			await modalApi.close();
+		}else {
+			message.error(res.message);
+		}
+	},
+});
+const [Form, formApi] = useVbenForm({
+	// 所有表单项共用,可单独在表单内覆盖
+	commonConfig: {
+		// 所有表单项
+		componentProps: {
+			class: 'w-full',
+		},
+		labelWidth: 130,
+		colon: true,
+	},
+	submitButtonOptions: {
+		show: false
+	},
+	resetButtonOptions: {
+		show: false
+	},
+	// 提交函数
+	// handleSubmit: onSubmit,
+	// 垂直布局,label和input在不同行,值为vertical
+	// 水平布局,label和input在同一行
+	scrollToFirstError: true,
+	layout: 'horizontal',
+	schema: [
+		{
+			component: 'Select',
+			defaultValue: undefined,
+			fieldName: 'net_income',
+			hideLabel: true,
+		},
+	],
+	wrapperClass: 'grid-cols-1',
+});
+</script>
+
+<template>
+	<Modal :title="title">
+		<Form>
+			<template #net_income>
+				<Space class="border w-full" style="display:block;border-bottom:0;">
+					<Row class="border-b h-[60px]" align="middle">
+						<Col :span="7" class="required label"><label>设定玩家在游戏内盈利值达到后关闭功能</label></Col>
+						<Col :span="5" class="content">
+							<div class="mb-0 w-full pl-4 pr-4">
+								<Select class="w-full" v-model:value="form.net_income" name="net_income" :disabled="is_view">
+									<SelectOption v-for="value in netIncomeList" :value="value">{{value}}</SelectOption>
+								</Select>
+							</div>
+						</Col>
+						<Col :span="1" class="label warning"><label>或</label></Col>
+						<Col :span="6" class="required label"><label>达到设定流水倍数关闭提升状态</label></Col>
+						<Col :span="5" class="content">
+							<div class="mb-0 w-full pl-4 pr-4">
+								<Select class="w-full" v-model:value="form.turnover_multiple" name="turnover_multiple" :disabled="is_view">
+									<SelectOption v-for="value in turnoverMultipleList" :value="value">{{value}}倍</SelectOption>
+								</Select>
+							</div>
+						</Col>
+					</Row>
+					<Row  class="border-b h-[60px]" align="middle">
+						<Col :span="24">
+							<div style="text-align:center;">(设定玩家自动取消系统控制的条件,以上条件 <span style="color:red;">其二达到任一即取消</span> 系统控制,并消耗一次控制次数)</div>
+						</Col>
+					</Row>
+					<Row  class="border-b h-[60px]" align="middle">
+						<Col :span="7" class="required label"><label>系统控制的触发周期选择</label></Col>
+						<Col :span="5" class="content">
+							<div class="mb-0 w-full pl-4 pr-4">
+								<Select class="w-full" v-model:value="form.evaluation_period" name="evaluation_period" :disabled="is_view">
+									<SelectOption v-for="item in evaluationPeriodList" :value="item.value">{{item.label}}</SelectOption>
+								</Select>
+							</div>
+						</Col>
+						<Col :span="7" class="required label"><label>周期内可被系统控制的次数</label></Col>
+						<Col :span="5" class="content">
+							<div class="mb-0 w-full pl-4 pr-4">
+								<Select class="w-full" v-model:value="form.effective_count" name="effective_count" :disabled="is_view">
+									<SelectOption v-for="value in effectiveCountList" :value="value">{{value}}次</SelectOption>
+								</Select>
+							</div>
+						</Col>
+					</Row>
+					<Row  class="border-b h-[60px]" align="middle">
+						<Col :span="24">
+							<div style="text-align:center;">(设定玩家被系统控制的周期及控制次数, <span style="color:red;">控制次数消耗完,在此周期内玩家不再被控制。</span> 自选时间段为自由选择日期区间)</div>
+						</Col>
+					</Row>
+
+					<Row  class="border-b h-[60px]" align="middle" v-if="form.evaluation_period == 5">
+						<Col :span="7" class="required label"><label>自选周期时间段</label></Col>
+						<Col :span="17" class="content">
+							<div class="mb-0 w-full pl-4 pr-4">
+								<RangePicker class="w-full" v-model:value="form.day_time" name="day_time" show-time :status="form.day_time_error ? 'error' : ''"  :disabled="is_view" />
+								<p class="text-destructive text-[0.8rem] bottom-1" v-if="form.day_time_error">请选择时间段</p>
+							</div>
+						</Col>
+					</Row>
+
+					<Row  class="border-b h-[60px]" align="middle">
+						<Col :span="7" class="required label"><label>每次系统控制间隔局数</label></Col>
+						<Col :span="5" class="content">
+							<div class="mb-0 w-full pl-4 pr-4">
+								<Select class="w-full" v-model:value="form.trigger_interval_rounds" name="trigger_interval_rounds" :disabled="is_view">
+									<SelectOption v-for="value in triggerIntervalRoundsList" :value="value">每{{value}}局</SelectOption>
+								</Select>
+							</div>
+						</Col>
+						<Col :span="12" class="flex-start">周期内可被系统控制的次数</Col>
+					</Row>
+				</Space>
+			</template>
+		</Form>
+	</Modal>
+</template>
+
+<style scoped>
+.label.required {
+	position: relative;
+}
+.label.required:before {
+	color: var(--vxe-ui-status-danger-color);
+	content: "*";
+}
+.label {
+	align-items: center;
+	background-color: hsl(var(--border));
+	box-shadow: 0 0 2px 0 var(--vxe-ui-layout-background-color);
+	display: flex;
+	font-weight: 550;
+	height: 60px;
+	justify-content: center;
+}
+.content {
+	align-items: center;
+	display: flex;
+	justify-content: center;
+}
+.label.warning {
+	background-color: hsl(var(--warning));
+}
+</style>

+ 170 - 0
apps/web-antd/src/views/game_control/feed_user/global/index.vue

@@ -0,0 +1,170 @@
+<script setup>
+import {useVbenVxeGrid} from "#/adapter/vxe-table.js";
+import {$t} from "@vben/locales";
+import {Avatar, Button, Switch, Row, Col, message, Select, SelectOption, Tag} from "ant-design-vue";
+import {Page, useVbenModal, confirm} from "@vben/common-ui";
+
+// 列表
+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},
+		{ title: $t('feed_user.global.user_init_status'), minWidth: 200, slots: {'default': 'user_init_status'} },
+		{ field: 'net_income', title: $t('feed_user.global.net_income'), minWidth: 250},
+		{ field: 'turnover_multiple', title: $t('feed_user.global.turnover_multiple'), minWidth: 300},
+		{ field: 'evaluation_period', title: $t('feed_user.global.evaluation_period'), minWidth: 200, slots: {'default': 'evaluation_period'} },
+		{ field: 'trigger_interval_rounds', title: $t('feed_user.global.trigger_interval_rounds'), minWidth: 200},
+		{ field: 'status', title: $t('feed_user.global.status'), minWidth: 150, slots: {'default':'status'}},
+		{ fixed: 'right', align: 'left', field: 'action', title: $t('common.action'), minWidth: 200, slots: {"default":"action"} },
+	],
+	exportConfig: {},
+	// height: 'auto', // 如果设置为 auto,则必须确保存在父节点且不允许存在相邻元素,否则会出现高度闪动问题
+	keepSource: true,
+	treeConfig: {
+		transform: true, // 指定表格为树形表格
+	},
+	proxyConfig: {
+		ajax: {
+			query: async ({ page }) => {
+				let form = {
+					page: page.currentPage,
+					limit: page.pageSize,
+					compress: 0
+				};
+				const list = await getFeedUserGlobalList(form);
+				list.list.forEach(item => {
+					item.status = item.status == 1;
+				})
+				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({
+	gridOptions,
+});
+
+
+
+// 查看玩家详情
+import ExtraModal from './create.vue';
+import {getFeedUserGlobalList, updateFeedUserGlobalStatus} from "#/api/game_control/feed_user_global.js";
+const [Modal, modalApi] = useVbenModal({
+	// 连接抽离的组件
+	connectedComponent: ExtraModal,
+	class:'w-[65%]',
+	onClosed: function () {
+		const _data = modalApi.getData();
+		if(_data.is_reload){
+			gridApi.reload();
+		}
+	}
+});
+const editControl = (row) => {
+	modalApi.setData({
+		data: row,
+		is_edit: true
+	}).open();
+}
+
+const updateStatus = async (row) => {
+	confirm($t('feed_user.global.confirm_update_status', {'status': row.status == 1 ? $t('common.status_close') : $t('common.status_open')})  ).then(async () => {
+		let res = await updateFeedUserGlobalStatus({'status': row.status ? 0 : 1});
+		message.success(res.message);
+		await gridApi.reload();
+	}).catch(() => {
+	});
+}
+
+</script>
+
+<template>
+	<Page>
+		<Grid>
+
+			<template #action="{ row }">
+				<Button  type="link" danger @click="editControl(row)">{{$t('game_control.player_control.edit_info')}}</Button>
+			</template>
+
+			<template #status="{ row }">
+				<Switch :checked="row.status" @click="updateStatus(row)" />
+			</template>
+
+			<template #user_init_status="{ row }">
+				<Tag color="green">{{$t('feed_user.global.up_rtp')}}</Tag>
+			</template>
+
+			<template #evaluation_period="{ row }">
+				{{row.evaluation_period_text}} ({{ $t('feed_user.global.number_order', {'number': row.effective_count}) }})
+				<template v-if="row.evaluation_period == 5"><br/>{{row.custom_time_start}} ~ {{row.custom_time_end}}</template>
+			</template>
+
+
+<!--			<template #toolbar-actions>-->
+<!--				<Row>-->
+<!--					<Col :span="24">-->
+<!--						<Button>{{$t('feed_user.global.button_tip')}}</Button>-->
+<!--					</Col>-->
+<!--				</Row>-->
+<!--			</template>-->
+			<template #top>
+				<Row>
+					<Col :span="24">
+						<div class="ant-alert-warning">
+							<p>{{$t('feed_user.global.desc_1')}}</p>
+							<p>{{$t('feed_user.global.desc_2')}}</p>
+							<p>{{$t('feed_user.global.desc_3')}}</p>
+						</div>
+					</Col>
+				</Row>
+			</template>
+		</Grid>
+		<Modal />
+	</Page>
+</template>
+
+<style scoped>
+.ant-alert-warning {
+	background-color: #fffdf0;
+	border: 1px solid #fff4c7;
+	box-sizing: border-box;
+	margin: 0 0 10px;
+	padding: 8px 12px;
+	color: rgba(50, 54, 57, 0.88);
+	font-size: 14px;
+	line-height: 1.5714285714285714;
+	list-style: none;
+	font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
+	position: relative;
+	//display: flex;
+	//align-items: center;
+	//word-wrap: break-word;
+	border-radius: 10px;
+	color: #ff3860;
+}
+</style>

+ 2 - 2
apps/web-antd/vite.config.mts

@@ -9,8 +9,8 @@ export default defineConfig(async () => {
           '/api': {
             changeOrigin: true,
             rewrite: (path) => path.replace(/^\/api/, ''),
-              target: 'https://merchant.w115.net',
-              // target: 'http://merchant.uwigb.mynatapp.cc',
+              // target: 'https://merchant.w115.net',
+              target: 'http://merchant.uwigb.mynatapp.cc',
             // ws: true,
           },
         },