1.所有接口需要根据agent_id绑定渠道

2.移除所有记录页面的更新按钮,只能查看数据
3.将所有软删除修改为硬删除
This commit is contained in:
2026-05-19 12:04:34 +08:00
parent b089f302de
commit 1f25280dfd
30 changed files with 325 additions and 592 deletions

View File

@@ -427,6 +427,9 @@
"rewardConfigRecord": "Dice Weight Test Records",
"playRecordTest": "Draw Records (Test Weight)",
"config": "Game Config"
},
"game": {
"title": "Game Management"
}
},
"table": {

View File

@@ -38,8 +38,31 @@
"ruleGameNameRequired": "Chinese name is required",
"ruleGameTypeRequired": "Game type is required"
},
"table": {
"search": {
"providerCode": "Provider Code",
"placeholderProviderCode": "Enter provider code",
"gameCode": "Game Code",
"placeholderGameCode": "Enter game code",
"gameType": "Game Type",
"placeholderGameType": "Enter game type",
"status": "Status",
"placeholderStatus": "Select status",
"statusEnabled": "Enabled",
"statusDisabled": "Disabled"
},
"table": {
"id": "ID",
"provider": "Provider",
"providerCode": "Provider Code",
"gameCode": "Game Code",
"gameKey": "Game Key",
"gameName": "Name (ZH)",
"gameNameEn": "Name (EN)",
"gameType": "Type",
"sort": "Sort",
"status": "Status",
"statusEnabled": "Enabled",
"statusDisabled": "Disabled",
"updateTime": "Update Time"
}
}

View File

@@ -423,6 +423,9 @@
"rewardConfigRecord": "权重测试记录",
"playRecordTest": "抽奖记录(测试权重)",
"config": "游戏配置"
},
"game": {
"title": "游戏管理"
}
},
"table": {

View File

@@ -38,8 +38,31 @@
"ruleGameNameRequired": "请输入中文名称",
"ruleGameTypeRequired": "请输入游戏类型"
},
"table": {
"search": {
"providerCode": "供应商编码",
"placeholderProviderCode": "请输入供应商编码",
"gameCode": "游戏编号",
"placeholderGameCode": "请输入游戏编号",
"gameType": "游戏类型",
"placeholderGameType": "请输入游戏类型",
"status": "状态",
"placeholderStatus": "请选择状态",
"statusEnabled": "启用",
"statusDisabled": "禁用"
},
"table": {
"id": "ID",
"provider": "供应商",
"providerCode": "供应商编码",
"gameCode": "游戏编号",
"gameKey": "游戏唯一值",
"gameName": "中文名",
"gameNameEn": "英文名",
"gameType": "类型",
"sort": "排序",
"status": "状态",
"statusEnabled": "启用",
"statusDisabled": "禁用",
"updateTime": "更新时间"
}
}

View File

@@ -46,12 +46,21 @@ export async function loadPageLocale(routePath: string): Promise<void> {
const modules = locale === LanguageEnum.EN ? enModules : zhModules
const tryPaths: string[] = [path]
// 兼容别名路由:例如 /user 实际页面为 /system/user
// 兼容别名路由:菜单 path 为短名但 locale 文件位于模块子目录
// 例如:/user -> system/user/game -> dice/game
if (!path.includes('/')) {
tryPaths.push(`system/${path}`)
}
if (path === 'user') {
tryPaths.push('system/user')
// 兜底:在任意一级子目录下查找同名文件
const suffix = `/${path}.json`
const localePrefix = `./langs/${locale}/`
for (const key of Object.keys(modules)) {
if (key.startsWith(localePrefix) && key.endsWith(suffix)) {
const candidate = key.slice(localePrefix.length, -'.json'.length)
if (!tryPaths.includes(candidate)) {
tryPaths.push(candidate)
}
}
}
}
let matchedPath: string | null = null

View File

@@ -61,6 +61,10 @@ export const MAP_PATH_TO_MENU_I18N_KEY: Record<string, string> = {
'/dice/play_record_test/index': 'menus.dice.playRecordTest',
'/dice/config': 'menus.dice.config',
'/dice/config/index': 'menus.dice.config',
'game': 'menus.game.title',
'game/index': 'menus.game.title',
'/game': 'menus.game.title',
'/game/index': 'menus.game.title',
'/result/success': 'menus.result.success',
'/result/fail': 'menus.result.fail',
'/exception/403': 'menus.exception.forbidden',

View File

@@ -39,18 +39,6 @@ export default {
})
},
/**
* 更新数据
* @param params 数据参数
* @returns 执行结果
*/
update(params: Record<string, any>) {
return request.put<any>({
url: '/core/dice/play_record/DicePlayRecord/update',
data: params
})
},
/**
* 删除数据
* @param id 数据ID

View File

@@ -39,18 +39,6 @@ export default {
})
},
/**
* 更新数据
* @param params 数据参数
* @returns 执行结果
*/
update(params: Record<string, any>) {
return request.put<any>({
url: '/core/dice/play_record_test/DicePlayRecordTest/update',
data: params
})
},
/**
* 删除数据
* @param id 数据ID

View File

@@ -39,18 +39,6 @@ export default {
})
},
/**
* 更新数据
* @param params 数据参数
* @returns 执行结果
*/
update(params: Record<string, any>) {
return request.put<any>({
url: '/core/dice/player_ticket_record/DicePlayerTicketRecord/update',
data: params
})
},
/**
* 删除数据
* @param id 数据ID

View File

@@ -39,18 +39,6 @@ export default {
})
},
/**
* 更新数据
* @param params 数据参数
* @returns 执行结果
*/
update(params: Record<string, any>) {
return request.put<any>({
url: '/core/dice/player_wallet_record/DicePlayerWalletRecord/update',
data: params
})
},
/**
* 删除数据
* @param id 数据ID

View File

@@ -39,18 +39,6 @@ export default {
})
},
/**
* 更新数据
* @param params 数据参数
* @returns 执行结果
*/
update(params: Record<string, any>) {
return request.put<any>({
url: '/core/dice/reward_config_record/DiceRewardConfigRecord/update',
data: params
})
},
/**
* 删除数据
* @param id 数据ID

View File

@@ -10,7 +10,7 @@
<template #icon>
<ArtSvgIcon icon="ri:add-fill" />
</template>
新增
{{ $t('table.actions.add') }}
</ElButton>
<ElButton
v-permission="'dice:game:index:destroy'"
@@ -21,7 +21,7 @@
<template #icon>
<ArtSvgIcon icon="ri:delete-bin-5-line" />
</template>
删除
{{ $t('table.actions.delete') }}
</ElButton>
</ElSpace>
</template>
@@ -108,20 +108,20 @@
apiParams: { limit: 100 },
columnsFactory: () => [
{ type: 'selection', align: 'center' },
{ prop: 'id', label: 'ID', width: 80, align: 'center' },
{ prop: 'provider', label: '供应商', minWidth: 120, align: 'center' },
{ prop: 'provider_code', label: '供应商编码', minWidth: 120, align: 'center' },
{ prop: 'game_code', label: '游戏编号', minWidth: 120, align: 'center' },
{ prop: 'game_key', label: '游戏唯一值', minWidth: 120, align: 'center' },
{ prop: 'game_name', label: '中文名', minWidth: 120, align: 'center' },
{ prop: 'game_name_en', label: '英文名', minWidth: 120, align: 'center' },
{ prop: 'game_type', label: '类型', minWidth: 90, align: 'center' },
{ prop: 'sort', label: '排序', width: 80, align: 'center' },
{ prop: 'status', label: '状态', width: 90, align: 'center', useSlot: true },
{ prop: 'update_time', label: '更新时间', minWidth: 160, align: 'center' },
{ prop: 'id', label: 'page.table.id', width: 80, align: 'center' },
{ prop: 'provider', label: 'page.table.provider', minWidth: 120, align: 'center' },
{ prop: 'provider_code', label: 'page.table.providerCode', minWidth: 120, align: 'center' },
{ prop: 'game_code', label: 'page.table.gameCode', minWidth: 120, align: 'center' },
{ prop: 'game_key', label: 'page.table.gameKey', minWidth: 120, align: 'center' },
{ prop: 'game_name', label: 'page.table.gameName', minWidth: 120, align: 'center' },
{ prop: 'game_name_en', label: 'page.table.gameNameEn', minWidth: 120, align: 'center' },
{ prop: 'game_type', label: 'page.table.gameType', minWidth: 90, align: 'center' },
{ prop: 'sort', label: 'page.table.sort', width: 80, align: 'center' },
{ prop: 'status', label: 'page.table.status', width: 90, align: 'center', useSlot: true },
{ prop: 'update_time', label: 'page.table.updateTime', minWidth: 160, align: 'center' },
{
prop: 'operation',
label: '操作',
label: 'table.actions.operation',
width: 110,
align: 'center',
fixed: 'right',

View File

@@ -8,25 +8,41 @@
@search="handleSearch"
>
<el-col v-bind="setSpan(6)">
<el-form-item label="供应商编码" prop="provider_code">
<el-input v-model="formData.provider_code" placeholder="请输入供应商编码" clearable />
<el-form-item :label="$t('page.search.providerCode')" prop="provider_code">
<el-input
v-model="formData.provider_code"
:placeholder="$t('page.search.placeholderProviderCode')"
clearable
/>
</el-form-item>
</el-col>
<el-col v-bind="setSpan(6)">
<el-form-item label="游戏编号" prop="game_code">
<el-input v-model="formData.game_code" placeholder="请输入游戏编号" clearable />
<el-form-item :label="$t('page.search.gameCode')" prop="game_code">
<el-input
v-model="formData.game_code"
:placeholder="$t('page.search.placeholderGameCode')"
clearable
/>
</el-form-item>
</el-col>
<el-col v-bind="setSpan(6)">
<el-form-item label="游戏类型" prop="game_type">
<el-input v-model="formData.game_type" placeholder="请输入游戏类型" clearable />
<el-form-item :label="$t('page.search.gameType')" prop="game_type">
<el-input
v-model="formData.game_type"
:placeholder="$t('page.search.placeholderGameType')"
clearable
/>
</el-form-item>
</el-col>
<el-col v-bind="setSpan(6)">
<el-form-item label="状态" prop="status">
<el-select v-model="formData.status" placeholder="请选择状态" clearable>
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
<el-form-item :label="$t('page.search.status')" prop="status">
<el-select
v-model="formData.status"
:placeholder="$t('page.search.placeholderStatus')"
clearable
>
<el-option :label="$t('page.search.statusEnabled')" :value="1" />
<el-option :label="$t('page.search.statusDisabled')" :value="0" />
</el-select>
</el-form-item>
</el-col>

View File

@@ -1,13 +1,13 @@
<template>
<el-dialog
v-model="visible"
:title="dialogType === 'add' ? $t('page.form.dialogTitleAdd') : $t('page.form.dialogTitleEdit')"
:title="$t('page.form.dialogTitleEdit')"
width="600px"
align-center
:close-on-click-modal="false"
@close="handleClose"
>
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px">
<el-form ref="formRef" :model="formData" label-width="120px">
<el-form-item :label="$t('page.form.player')" prop="player_id">
<el-select
v-model="formData.player_id"
@@ -15,7 +15,7 @@
clearable
filterable
style="width: 100%"
:disabled="dialogType === 'edit'"
disabled
>
<el-option
v-for="item in playerOptions"
@@ -32,7 +32,7 @@
clearable
filterable
style="width: 100%"
:disabled="dialogType === 'edit'"
disabled
>
<el-option
v-for="item in lotteryConfigOptions"
@@ -48,7 +48,7 @@
:placeholder="$t('form.placeholderSelect')"
clearable
style="width: 100%"
:disabled="dialogType === 'edit'"
disabled
>
<el-option :label="$t('page.form.paid')" :value="0" />
<el-option :label="$t('page.form.free')" :value="1" />
@@ -60,7 +60,7 @@
:placeholder="$t('form.placeholderSelect')"
clearable
style="width: 100%"
:disabled="dialogType === 'edit'"
disabled
>
<el-option :label="$t('page.form.noBigWin')" :value="0" />
<el-option :label="$t('page.form.bigWin')" :value="1" />
@@ -72,7 +72,7 @@
:placeholder="$t('page.form.placeholderWinCoin')"
:precision="0"
style="width: 100%"
:disabled="dialogType === 'edit'"
disabled
/>
</el-form-item>
<el-form-item :label="$t('page.form.superWinCoin')" prop="super_win_coin">
@@ -82,7 +82,7 @@
:precision="0"
:min="0"
style="width: 100%"
:disabled="dialogType === 'edit'"
disabled
/>
</el-form-item>
<el-form-item :label="$t('page.form.rewardWinCoin')" prop="reward_win_coin">
@@ -92,7 +92,7 @@
:precision="0"
:min="0"
style="width: 100%"
:disabled="dialogType === 'edit'"
disabled
/>
</el-form-item>
<el-form-item :label="$t('page.form.direction')" prop="direction">
@@ -101,7 +101,7 @@
:placeholder="$t('page.form.placeholderDirection')"
clearable
style="width: 100%"
:disabled="dialogType === 'edit'"
disabled
>
<el-option :label="$t('page.form.clockwise')" :value="0" />
<el-option :label="$t('page.form.anticlockwise')" :value="1" />
@@ -113,7 +113,7 @@
:placeholder="$t('page.form.placeholderStartIndex')"
:min="0"
style="width: 100%"
:disabled="dialogType === 'edit'"
disabled
/>
</el-form-item>
<el-form-item :label="$t('page.form.targetIndex')" prop="target_index">
@@ -122,7 +122,7 @@
:placeholder="$t('page.form.placeholderTargetIndex')"
:min="0"
style="width: 100%"
:disabled="dialogType === 'edit'"
disabled
/>
</el-form-item>
<el-form-item :label="$t('page.form.rollArray')" prop="rollArrayItems">
@@ -137,7 +137,7 @@
controls-position="right"
placeholder=""
class="roll-array-input"
:disabled="dialogType === 'edit'"
disabled
/>
</div>
<div class="roll-array-hint">{{ $t('page.form.rollArrayHint') }}</div>
@@ -150,7 +150,7 @@
:max="30"
:precision="0"
style="width: 100%"
:disabled="dialogType === 'edit'"
disabled
/>
</el-form-item>
<el-form-item :label="$t('page.form.rewardTier')" prop="reward_tier">
@@ -160,7 +160,7 @@
clearable
filterable
style="width: 100%"
:disabled="true"
disabled
>
<el-option label="T1" value="T1" />
<el-option label="T2" value="T2" />
@@ -172,20 +172,15 @@
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose">{{ dialogType === 'edit' ? $t('form.close') : $t('common.cancel') }}</el-button>
<el-button v-if="dialogType === 'add'" type="primary" @click="handleSubmit">{{ $t('table.form.submit') }}</el-button>
<el-button @click="handleClose">{{ $t('form.close') }}</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import api from '../../../api/play_record/index'
import { useI18n } from 'vue-i18n'
import { getChannelDeptRequestParams, withChannelDeptParams } from '@/composables/useChannelDeptScope'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
const { t } = useI18n()
import { getChannelDeptRequestParams } from '@/composables/useChannelDeptScope'
import type { FormInstance } from 'element-plus'
interface Props {
modelValue: boolean
@@ -200,7 +195,7 @@
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
dialogType: 'add',
dialogType: 'edit',
data: undefined
})
@@ -213,32 +208,6 @@
set: (value) => emit('update:modelValue', value)
})
const rules = computed<FormRules>(() => ({
player_id: [{ required: true, message: t('page.form.rulePlayerRequired'), trigger: 'change' }],
lottery_config_id: [{ required: true, message: t('page.form.ruleLotteryConfigRequired'), trigger: 'change' }],
lottery_type: [{ required: true, message: t('page.form.ruleLotteryTypeRequired'), trigger: 'change' }],
is_win: [{ required: true, message: t('page.form.ruleIsWinRequired'), trigger: 'change' }],
win_coin: [{ required: true, message: t('page.form.ruleWinCoinRequired'), trigger: 'blur' }],
rollArrayItems: [
{
validator: (_rule: any, value: (number | null)[], callback: (e?: Error) => void) => {
if (!value || value.length !== 5) {
callback(new Error(t('page.form.ruleRollArrayLength')))
return
}
const ok = value.every((n) => n != null && n >= 1 && n <= 6)
if (!ok) {
callback(new Error(t('page.form.ruleRollArrayValues')))
return
}
callback()
},
trigger: 'change'
}
],
reward_tier: [{ required: true, message: t('page.form.ruleRewardTierRequired'), trigger: 'change' }]
}))
const playerOptions = ref<Array<{ id: number; username: string }>>([])
const lotteryConfigOptions = ref<Array<{ id: number; name: string }>>([])
@@ -376,46 +345,6 @@
visible.value = false
formRef.value?.resetFields()
}
const handleSubmit = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
const payload = { ...formData } as Record<string, unknown>
// 将 5 个输入值拼成 [1,2,3,4,5] 格式,确保每项为 16 的整数
const items = formData.rollArrayItems
const rollArray = items.map((n) => {
const v = n != null ? Number(n) : 1
return Math.min(6, Math.max(1, Number.isNaN(v) ? 1 : Math.floor(v)))
})
payload.roll_array = rollArray
payload.roll_number = formData.roll_number ?? rollArray.reduce((s, n) => s + n, 0)
delete payload.rollArrayItems
if (props.dialogType === 'add') {
delete payload.id
await api.save(withChannelDeptParams(payload))
ElMessage.success(t('page.form.addSuccess'))
} else {
await api.update(withChannelDeptParams(payload))
ElMessage.success(t('page.form.editSuccess'))
}
emit('success')
handleClose()
} catch (error: any) {
let msg = t('page.form.validateFailed')
if (error?.message) {
msg = error.message
} else if (typeof error === 'string') {
msg = error
} else if (error && typeof error === 'object') {
const first = Object.values(error).find((v: any) => v?.[0]?.message)
if (first && Array.isArray(first)) {
msg = (first[0] as any).message || msg
}
}
ElMessage.warning(msg)
}
}
</script>
<style lang="scss" scoped>

View File

@@ -1,15 +1,15 @@
<template>
<el-dialog
v-model="visible"
:title="dialogType === 'add' ? $t('page.form.titleAdd') : $t('page.form.titleEdit')"
:title="$t('page.form.titleEdit')"
width="600px"
align-center
:close-on-click-modal="false"
@close="handleClose"
>
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px">
<el-form ref="formRef" :model="formData" label-width="120px">
<el-form-item :label="$t('page.form.labelLotteryConfigId')" prop="lottery_config_id">
<el-input v-model="formData.lottery_config_id" :placeholder="$t('page.form.placeholderLotteryConfigId')" />
<el-input v-model="formData.lottery_config_id" :placeholder="$t('page.form.placeholderLotteryConfigId')" disabled />
</el-form-item>
<el-form-item :label="$t('page.form.drawType')" prop="lottery_type">
<el-select
@@ -17,13 +17,20 @@
:placeholder="$t('form.placeholderSelect')"
clearable
style="width: 100%"
disabled
>
<el-option :label="$t('page.search.paid')" :value="0" />
<el-option :label="$t('page.search.free')" :value="1" />
</el-select>
</el-form-item>
<el-form-item :label="$t('page.search.direction')" prop="direction">
<el-select v-model="formData.direction" :placeholder="$t('form.placeholderSelect')" clearable style="width: 100%">
<el-select
v-model="formData.direction"
:placeholder="$t('form.placeholderSelect')"
clearable
style="width: 100%"
disabled
>
<el-option :label="$t('page.search.clockwise')" :value="0" />
<el-option :label="$t('page.search.anticlockwise')" :value="1" />
</el-select>
@@ -35,6 +42,7 @@
:precision="0"
controls-position="right"
style="width: 100%"
disabled
/>
</el-form-item>
<el-form-item :label="$t('page.search.paidAmount')" prop="paid_amount">
@@ -44,10 +52,11 @@
:precision="0"
controls-position="right"
style="width: 100%"
disabled
/>
</el-form-item>
<el-form-item :label="$t('page.search.isBigWin')" prop="is_win">
<el-select v-model="formData.is_win" :placeholder="$t('form.placeholderSelect')" clearable style="width: 100%">
<el-select v-model="formData.is_win" :placeholder="$t('form.placeholderSelect')" clearable style="width: 100%" disabled>
<el-option :label="$t('page.search.noBigWin')" :value="0" />
<el-option :label="$t('page.search.bigWin')" :value="1" />
</el-select>
@@ -59,6 +68,7 @@
:precision="0"
controls-position="right"
style="width: 100%"
disabled
/>
</el-form-item>
<el-form-item :label="$t('page.search.rewardTier')" prop="reward_tier">
@@ -67,6 +77,7 @@
:placeholder="$t('page.form.placeholderRewardTier')"
clearable
style="width: 100%"
disabled
>
<el-option label="T1" value="T1" />
<el-option label="T2" value="T2" />
@@ -77,19 +88,19 @@
</el-select>
</el-form-item>
<el-form-item :label="$t('page.table.startIndex')" prop="start_index">
<el-input v-model="formData.start_index" :placeholder="$t('page.form.placeholderStartIndex')" />
<el-input v-model="formData.start_index" :placeholder="$t('page.form.placeholderStartIndex')" disabled />
</el-form-item>
<el-form-item :label="$t('page.form.labelTargetIndex')" prop="target_index">
<el-input v-model="formData.target_index" :placeholder="$t('page.form.placeholderTargetIndex')" />
<el-input v-model="formData.target_index" :placeholder="$t('page.form.placeholderTargetIndex')" disabled />
</el-form-item>
<el-form-item :label="$t('page.search.rollNumber')" prop="roll_number">
<el-input v-model="formData.roll_number" :placeholder="$t('page.form.placeholderRollNumber')" />
<el-input v-model="formData.roll_number" :placeholder="$t('page.form.placeholderRollNumber')" disabled />
</el-form-item>
<el-form-item :label="$t('page.form.labelRollArray')" prop="roll_array">
<el-input v-model="formData.roll_array" :placeholder="$t('page.form.placeholderRollArray')" />
<el-input v-model="formData.roll_array" :placeholder="$t('page.form.placeholderRollArray')" disabled />
</el-form-item>
<el-form-item :label="$t('page.form.labelStatus')" prop="status">
<sa-radio v-model="formData.status" dict="data_status" />
<sa-radio v-model="formData.status" dict="data_status" disabled />
</el-form-item>
<el-form-item :label="$t('page.table.superWinCoin')" prop="super_win_coin">
<el-input-number
@@ -98,6 +109,7 @@
:precision="0"
controls-position="right"
style="width: 100%"
disabled
/>
</el-form-item>
<el-form-item :label="$t('page.table.rewardWinCoin')" prop="reward_win_coin">
@@ -107,25 +119,21 @@
:precision="0"
controls-position="right"
style="width: 100%"
disabled
/>
</el-form-item>
<el-form-item :label="$t('page.form.labelAdminId')" prop="admin_id">
<el-input v-model="formData.admin_id" :placeholder="$t('page.form.placeholderAdminId')" />
<el-input v-model="formData.admin_id" :placeholder="$t('page.form.placeholderAdminId')" disabled />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose">{{ $t('common.cancel') }}</el-button>
<el-button type="primary" @click="handleSubmit">{{ $t('table.form.submit') }}</el-button>
<el-button @click="handleClose">{{ $t('form.close') }}</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import api from '../../../api/play_record_test/index'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { useI18n } from 'vue-i18n'
import { withChannelDeptParams } from '@/composables/useChannelDeptScope'
import type { FormInstance } from 'element-plus'
interface Props {
modelValue: boolean
@@ -140,12 +148,11 @@
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
dialogType: 'add',
dialogType: 'edit',
data: undefined
})
const emit = defineEmits<Emits>()
const { t } = useI18n()
const formRef = ref<FormInstance>()
@@ -157,18 +164,6 @@
set: (value) => emit('update:modelValue', value)
})
/**
* 表单验证规则
*/
const rules = computed<FormRules>(() => ({
lottery_config_id: [{ required: true, message: t('page.form.ruleLotteryConfigIdRequired'), trigger: 'blur' }],
lottery_type: [{ required: true, message: t('page.form.ruleDrawTypeRequired'), trigger: 'blur' }],
is_win: [{ required: true, message: t('page.form.ruleIsBigWinRequired'), trigger: 'blur' }],
direction: [{ required: true, message: t('page.form.ruleDirectionRequired'), trigger: 'blur' }],
reward_tier: [{ required: true, message: t('page.form.ruleRewardTierRequired'), trigger: 'blur' }],
status: [{ required: true, message: t('page.form.ruleStatusRequired'), trigger: 'blur' }]
}))
/**
* 初始数据
*/
@@ -198,7 +193,7 @@
const formData = reactive({ ...initialFormData })
/**
* 监听弹窗打开,初始化表单数据
* 监听弹窗打开,初始化表单数据(仅查看)
*/
watch(
() => props.modelValue,
@@ -213,9 +208,7 @@
* 初始化页面数据
*/
const initPage = async () => {
// 先重置为初始值
Object.assign(formData, initialFormData)
// 如果有数据,则填充数据
if (props.data) {
await nextTick()
initForm()
@@ -223,7 +216,7 @@
}
/**
* 初始化表单数据
* 回填表单数据
*/
function normalizePlatformCoin(val: unknown): number {
if (val === '' || val === null || val === undefined) return 0
@@ -245,34 +238,11 @@
}
}
/**
* 关闭弹窗并重置表单
* 关闭弹窗
*/
const handleClose = () => {
visible.value = false
formRef.value?.resetFields()
}
/**
* 提交表单
*/
const handleSubmit = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
const payload = { ...formData }
if (props.dialogType === 'add') {
await api.save(withChannelDeptParams(payload))
ElMessage.success(t('page.form.addSuccess'))
} else {
await api.update(withChannelDeptParams(payload))
ElMessage.success(t('page.form.editSuccess'))
}
emit('success')
handleClose()
} catch (error) {
console.log('表单验证失败:', error)
}
}
</script>

View File

@@ -1,13 +1,13 @@
<template>
<el-dialog
v-model="visible"
:title="dialogType === 'add' ? $t('page.form.dialogTitleAdd') : $t('page.form.dialogTitleEdit')"
:title="$t('page.form.dialogTitleEdit')"
width="600px"
align-center
:close-on-click-modal="false"
@close="handleClose"
>
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px">
<el-form ref="formRef" :model="formData" label-width="120px">
<el-form-item :label="$t('page.form.player')" prop="player_id">
<el-select
v-model="formData.player_id"
@@ -15,7 +15,7 @@
clearable
filterable
style="width: 100%"
:disabled="dialogType === 'edit'"
disabled
>
<el-option
v-for="item in playerOptions"
@@ -30,7 +30,7 @@
v-model="formData.use_coins"
:placeholder="$t('page.form.placeholderUseCoins')"
:min="0"
:disabled="dialogType === 'edit'"
disabled
/>
</el-form-item>
<el-form-item :label="$t('page.form.paidDrawCount')" prop="paid_ticket_count">
@@ -38,8 +38,7 @@
v-model="formData.paid_ticket_count"
:placeholder="$t('page.form.placeholderPaidDrawCount')"
:min="0"
@change="onTicketCountChange"
:disabled="dialogType === 'edit'"
disabled
/>
</el-form-item>
<el-form-item :label="$t('page.form.freeDrawCount')" prop="free_ticket_count">
@@ -47,8 +46,7 @@
v-model="formData.free_ticket_count"
:placeholder="$t('page.form.placeholderFreeDrawCount')"
:min="0"
@change="onTicketCountChange"
:disabled="dialogType === 'edit'"
disabled
/>
</el-form-item>
<el-form-item :label="$t('page.form.totalDrawCount')" prop="total_ticket_count">
@@ -68,25 +66,20 @@
maxlength="500"
show-word-limit
style="width: 100%"
:disabled="dialogType === 'edit'"
disabled
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose">{{ $t('common.cancel') }}</el-button>
<el-button type="primary" @click="handleSubmit">{{ $t('table.form.submit') }}</el-button>
<el-button @click="handleClose">{{ $t('form.close') }}</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import api from '../../../api/player_ticket_record/index'
import { useI18n } from 'vue-i18n'
import { getChannelDeptRequestParams, withChannelDeptParams } from '@/composables/useChannelDeptScope'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
const { t } = useI18n()
import { getChannelDeptRequestParams } from '@/composables/useChannelDeptScope'
import type { FormInstance } from 'element-plus'
interface Props {
modelValue: boolean
@@ -101,7 +94,7 @@
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
dialogType: 'add',
dialogType: 'edit',
data: undefined
})
@@ -117,17 +110,6 @@
set: (value) => emit('update:modelValue', value)
})
/**
* 表单验证规则
*/
const rules = reactive<FormRules>({
player_id: [{ required: true, message: t('page.form.rulePlayerRequired'), trigger: 'change' }],
use_coins: [{ required: true, message: t('page.form.ruleUseCoinsRequired'), trigger: 'blur' }],
paid_ticket_count: [{ required: true, message: t('page.form.rulePaidDrawRequired'), trigger: 'blur' }],
free_ticket_count: [{ required: true, message: t('page.form.ruleFreeDrawRequired'), trigger: 'blur' }],
remark: [{ required: true, message: t('page.form.ruleRemarkRequired'), trigger: 'blur' }]
})
/** 玩家下拉选项id、username */
const playerOptions = ref<Array<{ id: number; username: string }>>([])
@@ -138,10 +120,6 @@
return paid + free
})
function onTicketCountChange() {
formData.total_ticket_count = totalTicketCountComputed.value
}
/**
* 初始数据
*/
@@ -161,7 +139,7 @@
const formData = reactive({ ...initialFormData })
/**
* 监听弹窗打开,初始化表单并拉取玩家选项(与 player_wallet_record 一致)
* 监听弹窗打开,初始化表单并拉取玩家选项
*/
watch(
() => props.modelValue,
@@ -182,7 +160,7 @@
)
/**
* 初始化页面数据(仅重置表单、回填编辑数据,不在此处请求玩家列表)
* 初始化页面数据
*/
const initPage = async () => {
Object.assign(formData, { ...initialFormData })
@@ -193,7 +171,7 @@
}
/**
* 初始化表单数据
* 回填表单数据
*/
const initForm = () => {
if (!props.data) return
@@ -215,34 +193,10 @@
}
/**
* 关闭弹窗并重置表单
* 关闭弹窗
*/
const handleClose = () => {
visible.value = false
formRef.value?.resetFields()
}
/**
* 提交表单total_ticket_count 由 paid_ticket_count + free_ticket_count 自动求和,提交前写入)
*/
const handleSubmit = async () => {
if (!formRef.value) return
try {
formData.total_ticket_count = totalTicketCountComputed.value
await formRef.value.validate()
if (props.dialogType === 'add') {
const rest = { ...formData } as Record<string, unknown>
delete rest.id
await api.save(withChannelDeptParams(rest))
ElMessage.success(t('page.form.addSuccess'))
} else {
await api.update(withChannelDeptParams(formData))
ElMessage.success(t('page.form.editSuccess'))
}
emit('success')
handleClose()
} catch (error) {
console.log('表单验证失败:', error)
}
}
</script>

View File

@@ -1,13 +1,13 @@
<template>
<el-dialog
v-model="visible"
:title="dialogType === 'add' ? $t('page.form.dialogTitleAdd') : $t('page.form.dialogTitleEdit')"
:title="$t('page.form.dialogTitleEdit')"
width="600px"
align-center
:close-on-click-modal="false"
@close="handleClose"
>
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px">
<el-form ref="formRef" :model="formData" label-width="120px">
<el-form-item :label="$t('page.form.user')" prop="player_id">
<el-select
v-model="formData.player_id"
@@ -15,8 +15,7 @@
clearable
filterable
style="width: 100%"
:disabled="dialogType === 'edit'"
@change="onPlayerChange"
disabled
>
<el-option
v-for="item in playerOptions"
@@ -32,7 +31,7 @@
:placeholder="$t('page.form.placeholderType')"
clearable
style="width: 100%"
:disabled="dialogType === 'edit'"
disabled
>
<el-option :label="$t('page.form.typeRecharge')" :value="0" />
<el-option :label="$t('page.form.typeWithdraw')" :value="1" />
@@ -48,8 +47,7 @@
:precision="2"
:step="1"
style="width: 100%"
@change="onCoinChange"
:disabled="dialogType === 'edit'"
disabled
/>
</el-form-item>
<el-form-item :label="$t('page.form.walletBefore')" prop="wallet_before">
@@ -78,25 +76,20 @@
:placeholder="$t('page.form.placeholderRemark')"
maxlength="500"
show-word-limit
:disabled="dialogType === 'edit'"
disabled
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose">{{ $t('common.cancel') }}</el-button>
<el-button type="primary" @click="handleSubmit">{{ $t('table.form.submit') }}</el-button>
<el-button @click="handleClose">{{ $t('form.close') }}</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import api from '../../../api/player_wallet_record/index'
import { useI18n } from 'vue-i18n'
import { getChannelDeptRequestParams, withChannelDeptParams } from '@/composables/useChannelDeptScope'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
const { t } = useI18n()
import { getChannelDeptRequestParams } from '@/composables/useChannelDeptScope'
import type { FormInstance } from 'element-plus'
interface Props {
modelValue: boolean
@@ -111,7 +104,7 @@
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
dialogType: 'add',
dialogType: 'edit',
data: undefined
})
@@ -127,12 +120,6 @@
set: (value) => emit('update:modelValue', value)
})
const rules = reactive<FormRules>({
player_id: [{ required: true, message: t('page.form.ruleUserRequired'), trigger: 'change' }],
coin: [{ required: true, message: t('page.form.ruleCoinRequired'), trigger: 'blur' }],
type: [{ required: true, message: t('page.form.ruleTypeRequired'), trigger: 'change' }]
})
const initialFormData: {
id: number | null
player_id: number | null
@@ -153,36 +140,6 @@
const formData = reactive({ ...initialFormData })
/** 选择用户后拉取当前平台币作为钱包操作前 */
async function onPlayerChange(playerId: number | null) {
if (playerId == null) {
formData.wallet_before = 0
calcWalletAfter()
return
}
try {
const res = await api.getPlayerWalletBefore(playerId)
const before = res?.wallet_before ?? 0
formData.wallet_before = Number(before)
calcWalletAfter()
} catch {
formData.wallet_before = 0
calcWalletAfter()
}
}
/** 平台币变化时重算钱包操作后 */
function onCoinChange() {
calcWalletAfter()
}
/** 钱包操作后 = 钱包操作前 + 平台币变化 */
function calcWalletAfter() {
const before = Number(formData.wallet_before) || 0
const coin = Number(formData.coin) || 0
formData.wallet_after = Number((before + coin).toFixed(2))
}
watch(
() => props.modelValue,
async (open) => {
@@ -230,24 +187,4 @@
visible.value = false
formRef.value?.resetFields()
}
const handleSubmit = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
calcWalletAfter()
const payload = { ...formData }
if (props.dialogType === 'add') {
await api.save(withChannelDeptParams(payload))
ElMessage.success(t('page.form.addSuccess'))
} else {
await api.update(withChannelDeptParams(payload))
ElMessage.success(t('page.form.editSuccess'))
}
emit('success')
handleClose()
} catch (error) {
console.log('表单验证失败:', error)
}
}
</script>

View File

@@ -1,36 +1,31 @@
<template>
<el-dialog
v-model="visible"
:title="dialogType === 'add' ? $t('page.form.titleAdd') : $t('page.form.titleEdit')"
:title="$t('page.form.titleEdit')"
width="600px"
align-center
:close-on-click-modal="false"
@close="handleClose"
>
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px">
<el-form ref="formRef" :model="formData" label-width="120px">
<el-form-item :label="$t('page.form.labelTestCount')" prop="test_count">
<el-input v-model="formData.test_count" :placeholder="$t('page.form.placeholderTestCount')" />
<el-input v-model="formData.test_count" :placeholder="$t('page.form.placeholderTestCount')" disabled />
</el-form-item>
<el-form-item :label="$t('page.form.labelWeightSnapshot')" prop="weight_config_snapshot">
<el-input v-model="formData.weight_config_snapshot" :placeholder="$t('page.form.placeholderWeightSnapshot')" />
<el-input v-model="formData.weight_config_snapshot" :placeholder="$t('page.form.placeholderWeightSnapshot')" disabled />
</el-form-item>
<el-form-item :label="$t('page.form.labelResultCounts')" prop="result_counts">
<el-input v-model="formData.result_counts" :placeholder="$t('page.form.placeholderResultCounts')" />
<el-input v-model="formData.result_counts" :placeholder="$t('page.form.placeholderResultCounts')" disabled />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose">{{ $t('common.cancel') }}</el-button>
<el-button type="primary" @click="handleSubmit">{{ $t('table.form.submit') }}</el-button>
<el-button @click="handleClose">{{ $t('form.close') }}</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import api from '../../../api/reward_config_record/index'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { useI18n } from 'vue-i18n'
import { withChannelDeptParams } from '@/composables/useChannelDeptScope'
import type { FormInstance } from 'element-plus'
interface Props {
modelValue: boolean
@@ -45,12 +40,11 @@
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
dialogType: 'add',
dialogType: 'edit',
data: undefined
})
const emit = defineEmits<Emits>()
const { t } = useI18n()
const formRef = ref<FormInstance>()
@@ -62,13 +56,6 @@
set: (value) => emit('update:modelValue', value)
})
/**
* 表单验证规则
*/
const rules = computed<FormRules>(() => ({
test_count: [{ required: true, message: t('page.form.ruleTestCountRequired'), trigger: 'blur' }]
}))
/**
* 初始数据
*/
@@ -76,7 +63,7 @@
id: null,
test_count: 100,
weight_config_snapshot: '',
result_counts: '',
result_counts: ''
}
/**
@@ -85,7 +72,7 @@
const formData = reactive({ ...initialFormData })
/**
* 监听弹窗打开,初始化表单数据
* 监听弹窗打开,初始化表单数据(仅查看)
*/
watch(
() => props.modelValue,
@@ -100,9 +87,7 @@
* 初始化页面数据
*/
const initPage = async () => {
// 先重置为初始值
Object.assign(formData, initialFormData)
// 如果有数据,则填充数据
if (props.data) {
await nextTick()
initForm()
@@ -110,7 +95,7 @@
}
/**
* 初始化表单数据
* 回填表单数据
*/
const initForm = () => {
if (props.data) {
@@ -123,31 +108,10 @@
}
/**
* 关闭弹窗并重置表单
* 关闭弹窗
*/
const handleClose = () => {
visible.value = false
formRef.value?.resetFields()
}
/**
* 提交表单
*/
const handleSubmit = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
if (props.dialogType === 'add') {
await api.save(withChannelDeptParams(formData))
ElMessage.success(t('page.form.addSuccess'))
} else {
await api.update(withChannelDeptParams(formData))
ElMessage.success(t('page.form.editSuccess'))
}
emit('success')
handleClose()
} catch (error) {
console.log('表单验证失败:', error)
}
}
</script>

View File

@@ -6,6 +6,7 @@ namespace app\api\controller\v1;
use app\api\cache\AuthTokenCache;
use app\api\controller\BaseController;
use app\api\util\ReturnCode;
use plugin\saiadmin\app\model\system\SystemUser;
use support\Request;
use support\Response;
use Tinywan\Jwt\JwtToken;
@@ -54,6 +55,14 @@ class AuthTokenController extends BaseController
return $this->fail('Signature verification failed', ReturnCode::FORBIDDEN);
}
$agent = SystemUser::where('agent_id', $agentId)->find();
if (!$agent || (int) ($agent->status ?? 0) !== 1) {
return $this->fail('Invalid agent_id', ReturnCode::FORBIDDEN);
}
if (empty($agent->dept_id) || (int) $agent->dept_id <= 0) {
return $this->fail('Agent channel is not configured', ReturnCode::FORBIDDEN);
}
$exp = (int) config('api.auth_token_exp', 86400);
$tokenResult = JwtToken::generateToken([
'id' => 0,

View File

@@ -7,7 +7,6 @@ use app\api\logic\UserLogic;
use app\api\util\ReturnCode;
use app\dice\model\game\DiceGame;
use app\dice\model\player\DicePlayer;
use plugin\saiadmin\app\model\system\SystemUser;
use app\dice\model\play_record\DicePlayRecord;
use app\dice\model\player_wallet_record\DicePlayerWalletRecord;
use app\dice\model\player_ticket_record\DicePlayerTicketRecord;
@@ -60,7 +59,7 @@ class GameController extends BaseController
public function getGameList(Request $request): Response
{
$lang = $this->resolveLang($request->post('lang', 'zh'));
$games = $this->buildPublicGameList($lang);
$games = $this->buildPublicGameList($lang, $this->agentDeptId($request));
return $this->success([
'game_list' => $games,
]);
@@ -73,7 +72,7 @@ class GameController extends BaseController
public function getGameHall(Request $request): Response
{
$lang = $this->resolveLang($request->post('lang', 'zh'));
$games = $this->buildPublicGameList($lang);
$games = $this->buildPublicGameList($lang, $this->agentDeptId($request));
$hallUrl = '';
if (!empty($games)) {
$hallUrl = $games[0]['hall_url'] ?? '';
@@ -106,23 +105,16 @@ class GameController extends BaseController
$time = (string) time();
}
$adminId = null;
$adminIdsInTopDept = null;
$agentId = trim((string) ($request->agent_id ?? ''));
if ($agentId !== '') {
$systemUser = SystemUser::where('agent_id', $agentId)->find();
if ($systemUser) {
$adminId = (int) $systemUser->id;
$adminIdsInTopDept = UserLogic::getAdminIdsByAgentIdTopDept($agentId);
}
}
$deptId = $this->agentDeptId($request);
$adminId = $this->agentAdminId($request);
$adminIdsInTopDept = UserLogic::getAdminIdsByAgentIdTopDept(trim((string) ($request->agent_id ?? '')));
$lang = trim((string) ($request->post('lang', 'zh')));
$lang = in_array($lang, ['en', 'zh'], true) ? $lang : 'zh';
try {
$logic = new UserLogic();
$result = $logic->loginByUsername($username, $password, $lang, 0.0, $time, $adminId, $adminIdsInTopDept);
$result = $logic->loginByUsername($username, $password, $lang, 0.0, $time, $adminId, $adminIdsInTopDept, $deptId);
} catch (\plugin\saiadmin\exception\ApiException $e) {
return $this->fail($e->getMessage(), ReturnCode::PARAMS_ERROR);
}
@@ -145,24 +137,25 @@ class GameController extends BaseController
{
$usernameRaw = $request->input('username', '');
$username = is_string($usernameRaw) ? trim($usernameRaw) : '';
$deptId = $this->agentDeptId($request);
if ($username === '') {
return $this->fail('username is required', ReturnCode::PARAMS_ERROR);
}
$cached = UserCache::getPlayerInfoSnapshotByUsername($username);
$cached = UserCache::getPlayerInfoSnapshotByUsername($this->scopedUsername($deptId, $username));
if ($cached !== null) {
return $this->success($cached);
}
$player = DicePlayer::field(self::PLAYER_INFO_DB_FIELDS)->where('username', $username)->find();
$player = DicePlayer::field(self::PLAYER_INFO_DB_FIELDS)->where('username', $username)->where('dept_id', $deptId)->find();
if (!$player) {
return $this->fail('User not found', ReturnCode::NOT_FOUND);
return $this->fail('User not found', ReturnCode::PARAMS_ERROR);
}
$hidden = ['password', 'lottery_config_id', 't1_weight', 't2_weight', 't3_weight', 't4_weight', 't5_weight', 'delete_time'];
$info = $player->hidden($hidden)->toArray();
UserCache::setPlayerInfoSnapshotByUsername($username, $info);
UserCache::setPlayerInfoSnapshotByUsername($this->scopedUsername($deptId, $username), $info);
return $this->success($info);
}
@@ -276,6 +269,7 @@ class GameController extends BaseController
public function getPlayerGameRecord(Request $request): Response
{
$username = trim((string) ($request->post('username', '')));
$deptId = $this->agentDeptId($request);
$startCreateTime = trim((string) ($request->post('start_create_time', '')));
$endCreateTime = trim((string) ($request->post('end_create_time', '')));
$window = $this->resolvePullRecordTimeWindow($startCreateTime, $endCreateTime);
@@ -284,10 +278,10 @@ class GameController extends BaseController
}
$limit = $this->resolvePullRecordLimit($request);
$query = DicePlayRecord::order('id', 'desc');
$query = DicePlayRecord::where('dept_id', $deptId)->order('id', 'desc');
if ($username !== '') {
$player = DicePlayer::where('username', $username)->find();
$player = $this->findPlayerByUsername($username, $deptId);
if (!$player) {
return $this->success([]);
}
@@ -300,7 +294,7 @@ class GameController extends BaseController
$list = $query->limit($limit)->select()->toArray();
$playerIds = array_unique(array_column($list, 'player_id'));
if (!empty($playerIds)) {
$players = DicePlayer::whereIn('id', $playerIds)->field('id,username,phone')->select()->toArray();
$players = DicePlayer::whereIn('id', $playerIds)->where('dept_id', $deptId)->field('id,username,phone')->select()->toArray();
$playerMap = [];
foreach ($players as $p) {
$playerMap[(int) ($p['id'] ?? 0)] = $p;
@@ -321,6 +315,7 @@ class GameController extends BaseController
public function getPlayerWalletRecord(Request $request): Response
{
$username = trim((string) ($request->post('username', '')));
$deptId = $this->agentDeptId($request);
$startCreateTime = trim((string) ($request->post('start_create_time', '')));
$endCreateTime = trim((string) ($request->post('end_create_time', '')));
$window = $this->resolvePullRecordTimeWindow($startCreateTime, $endCreateTime);
@@ -329,10 +324,10 @@ class GameController extends BaseController
}
$limit = $this->resolvePullRecordLimit($request);
$query = DicePlayerWalletRecord::order('id', 'desc');
$query = DicePlayerWalletRecord::where('dept_id', $deptId)->order('id', 'desc');
if ($username !== '') {
$player = DicePlayer::where('username', $username)->find();
$player = $this->findPlayerByUsername($username, $deptId);
if (!$player) {
return $this->success([]);
}
@@ -357,6 +352,7 @@ class GameController extends BaseController
public function getPlayerTicketRecord(Request $request): Response
{
$username = trim((string) ($request->post('username', '')));
$deptId = $this->agentDeptId($request);
$startCreateTime = trim((string) ($request->post('start_create_time', '')));
$endCreateTime = trim((string) ($request->post('end_create_time', '')));
$window = $this->resolvePullRecordTimeWindow($startCreateTime, $endCreateTime);
@@ -365,10 +361,10 @@ class GameController extends BaseController
}
$limit = $this->resolvePullRecordLimit($request);
$query = DicePlayerTicketRecord::order('id', 'desc');
$query = DicePlayerTicketRecord::where('dept_id', $deptId)->order('id', 'desc');
if ($username !== '') {
$player = DicePlayer::where('username', $username)->find();
$player = $this->findPlayerByUsername($username, $deptId);
if (!$player) {
return $this->success([]);
}
@@ -394,6 +390,7 @@ class GameController extends BaseController
public function setPlayerWallet(Request $request): Response
{
$username = trim((string) ($request->post('username', '')));
$deptId = $this->agentDeptId($request);
$coin = $request->post('coin');
if ($username === '') {
@@ -408,9 +405,9 @@ class GameController extends BaseController
return $this->fail('coin cannot be 0', ReturnCode::PARAMS_ERROR);
}
$player = DicePlayer::where('username', $username)->find();
$player = $this->findPlayerByUsername($username, $deptId);
if (!$player) {
return $this->fail('User not found', ReturnCode::NOT_FOUND);
return $this->fail('User not found', ReturnCode::PARAMS_ERROR);
}
$walletBefore = (float) ($player->coin ?? 0);
@@ -430,6 +427,7 @@ class GameController extends BaseController
$adminId = ($player->admin_id ?? null) ? (int) $player->admin_id : null;
$record = DicePlayerWalletRecord::create([
'dept_id' => $deptId,
'player_id' => (int) $player->id,
'admin_id' => $adminId,
'coin' => $coinVal,
@@ -452,6 +450,7 @@ class GameController extends BaseController
UserCache::deleteUser($player->id);
if ($player->username !== '') {
UserCache::deletePlayerByUsername($player->username);
UserCache::deletePlayerByUsername($this->scopedUsername($deptId, (string) $player->username));
}
$recordArr = $record->toArray();
@@ -471,13 +470,14 @@ class GameController extends BaseController
return $langValue;
}
private function buildPublicGameList(string $lang): array
private function buildPublicGameList(string $lang, int $deptId): array
{
$rows = DiceGame::where('status', 1)
->orderBy('sort', 'asc')
->orderBy('id', 'asc')
->select(array_merge(self::GAME_PUBLIC_FIELDS, ['game_name', 'game_name_en']))
->get()
->where('dept_id', $deptId)
->order('sort', 'asc')
->order('id', 'asc')
->field(array_merge(self::GAME_PUBLIC_FIELDS, ['game_name', 'game_name_en']))
->select()
->toArray();
if (empty($rows)) {
return [];
@@ -495,4 +495,26 @@ class GameController extends BaseController
}
return $games;
}
private function agentDeptId(Request $request): int
{
return (int) ($request->agent_dept_id ?? 0);
}
private function agentAdminId(Request $request): ?int
{
$adminId = (int) ($request->agent_admin_id ?? 0);
return $adminId > 0 ? $adminId : null;
}
private function scopedUsername(int $deptId, string $username): string
{
return $deptId . ':' . $username;
}
private function findPlayerByUsername(string $username, int $deptId): ?DicePlayer
{
$player = DicePlayer::where('username', $username)->where('dept_id', $deptId)->find();
return $player ?: null;
}
}

View File

@@ -76,7 +76,7 @@ class UserLogic
* @param int|null $adminId 创建新用户时关联的后台管理员IDsa_system_user.id可选
* @param int[]|null $adminIdsInTopDept 当前管理员顶级部门下的所有管理员ID用于按部门范围查找玩家为空时退化为仅按 username 查找
*/
public function loginByUsername(string $username, string $password, string $lang, float $coin, string $time, ?int $adminId = null, ?array $adminIdsInTopDept = null): array
public function loginByUsername(string $username, string $password, string $lang, float $coin, string $time, ?int $adminId = null, ?array $adminIdsInTopDept = null, ?int $deptId = null): array
{
$username = trim($username);
if ($username === '') {
@@ -84,6 +84,9 @@ class UserLogic
}
$query = DicePlayer::where('username', $username);
if ($deptId !== null && $deptId > 0) {
$query->where('dept_id', $deptId);
}
if ($adminIdsInTopDept !== null && !empty($adminIdsInTopDept)) {
$query->whereIn('admin_id', $adminIdsInTopDept);
}
@@ -106,10 +109,13 @@ class UserLogic
$player->password = $this->hashPassword($password);
$player->status = self::STATUS_NORMAL;
$player->coin = $coin;
if ($deptId !== null && $deptId > 0) {
$player->dept_id = $deptId;
}
if ($adminId !== null && $adminId > 0) {
$player->admin_id = $adminId;
$adminUser = SystemUser::find($adminId);
if ($adminUser && !empty($adminUser->dept_id)) {
if (($deptId === null || $deptId <= 0) && $adminUser && !empty($adminUser->dept_id)) {
$player->dept_id = $adminUser->dept_id;
}
}
@@ -125,6 +131,7 @@ class UserLogic
]);
$token = $tokenResult['access_token'];
UserCache::setSessionByUsername($username, $token);
UserCache::setCurrentUserToken((int) $player->id, $token);
$userArr = $player->hidden(['password', 'lottery_config_id', 't1_weight', 't2_weight', 't3_weight', 't4_weight', 't5_weight'])->toArray();
UserCache::setUser((int) $player->id, $userArr);

View File

@@ -5,6 +5,7 @@ namespace app\api\middleware;
use app\api\cache\AuthTokenCache;
use app\api\util\ReturnCode;
use plugin\saiadmin\app\model\system\SystemUser;
use plugin\saiadmin\exception\ApiException;
use Tinywan\Jwt\JwtToken;
use Tinywan\Jwt\Exception\JwtTokenException;
@@ -53,7 +54,17 @@ class AuthTokenMiddleware implements MiddlewareInterface
throw new ApiException('auth-token invalid or expired', ReturnCode::TOKEN_INVALID);
}
$agent = SystemUser::where('agent_id', $agentId)->find();
if (!$agent || (int) ($agent->status ?? 0) !== 1) {
throw new ApiException('Invalid agent_id', ReturnCode::FORBIDDEN);
}
if (empty($agent->dept_id) || (int) $agent->dept_id <= 0) {
throw new ApiException('Agent channel is not configured', ReturnCode::FORBIDDEN);
}
$request->agent_id = $agentId;
$request->agent_admin_id = (int) $agent->id;
$request->agent_dept_id = (int) $agent->dept_id;
return $handler($request);
}
}

View File

@@ -53,10 +53,14 @@ class TokenMiddleware implements MiddlewareInterface
if ($username === '') {
throw new ApiException('Invalid or expired token', ReturnCode::TOKEN_INVALID);
}
$userId = (int) ($extend['id'] ?? 0);
if ($userId <= 0) {
throw new ApiException('Invalid or expired token', ReturnCode::TOKEN_INVALID);
}
$currentToken = UserCache::getSessionTokenByUsername($username);
$currentToken = UserCache::getCurrentUserToken($userId);
if ($currentToken === null || $currentToken === '') {
$player = DicePlayer::where('username', $username)->find();
$player = DicePlayer::find($userId);
if (!$player) {
throw new ApiException('Please register', ReturnCode::TOKEN_INVALID);
}
@@ -68,17 +72,17 @@ class TokenMiddleware implements MiddlewareInterface
// 优先从 Redis 缓存取玩家,避免每次请求都查库
$player = null;
$cached = UserCache::getPlayerByUsername($username);
if ($cached !== null && isset($cached['id'])) {
$cached = UserCache::getUser($userId);
if (!empty($cached) && isset($cached['id']) && (int) $cached['id'] === $userId) {
$player = (new DicePlayer())->data($cached, true);
}
if ($player === null) {
$player = DicePlayer::where('username', $username)->find();
$player = DicePlayer::find($userId);
if (!$player) {
UserCache::deleteSessionByUsername($username);
throw new ApiException('Please login again', ReturnCode::TOKEN_INVALID);
}
UserCache::setPlayerByUsername($username, $player->hidden(['password'])->toArray());
UserCache::setUser($userId, $player->hidden(['password'])->toArray());
}
$request->player_id = (int) $player->id;
$request->player = $player;

View File

@@ -144,31 +144,6 @@ class DicePlayRecordController extends BaseController
}
}
/**
* 更新数据
* @param Request $request
* @return Response
*/
#[Permission('玩家抽奖记录修改', 'dice:play_record:index:update')]
public function update(Request $request): Response
{
$data = $request->post();
$this->validate('update', $data);
$model = $this->logic->read($data['id'] ?? 0);
if ($model) {
$recordDeptId = is_array($model) ? ($model['dept_id'] ?? null) : ($model->dept_id ?? null);
if (! AdminScopeHelper::canAccessDept($this->adminInfo ?? null, $recordDeptId, $request->input('dept_id'))) {
return $this->fail('no permission to update this record');
}
}
$result = $this->logic->edit($data['id'], $data);
if ($result) {
return $this->success('update success');
} else {
return $this->fail('update failed');
}
}
/**
* 删除数据
* @param Request $request

View File

@@ -103,31 +103,6 @@ class DicePlayRecordTestController extends BaseController
}
}
/**
* 更新数据
* @param Request $request
* @return Response
*/
#[Permission('玩家抽奖记录(测试数据)修改', 'dice:play_record_test:index:update')]
public function update(Request $request): Response
{
$data = $request->post();
$this->validate('update', $data);
$model = $this->logic->read($data['id'] ?? 0);
if ($model) {
$recordDeptId = is_array($model) ? ($model['dept_id'] ?? null) : ($model->dept_id ?? null);
if (! AdminScopeHelper::canAccessDept($this->adminInfo ?? null, $recordDeptId, $request->input('dept_id'))) {
return $this->fail('no permission to update this record');
}
}
$result = $this->logic->edit($data['id'], $data);
if ($result) {
return $this->success('update success');
} else {
return $this->fail('update failed');
}
}
/**
* 删除数据
* @param Request $request

View File

@@ -117,31 +117,6 @@ class DicePlayerTicketRecordController extends BaseController
}
}
/**
* 更新数据
* @param Request $request
* @return Response
*/
#[Permission('抽奖券获取记录修改', 'dice:player_ticket_record:index:update')]
public function update(Request $request): Response
{
$data = $request->post();
$this->validate('update', $data);
$model = $this->logic->read($data['id'] ?? 0);
if ($model) {
$recordDeptId = is_array($model) ? ($model['dept_id'] ?? null) : ($model->dept_id ?? null);
if (! AdminScopeHelper::canAccessDept($this->adminInfo ?? null, $recordDeptId, $request->input('dept_id'))) {
return $this->fail('no permission to update this record');
}
}
$result = $this->logic->edit($data['id'], $data);
if ($result) {
return $this->success('update success');
} else {
return $this->fail('update failed');
}
}
/**
* 删除数据
* @param Request $request

View File

@@ -200,28 +200,4 @@ class DicePlayerWalletRecordController extends BaseController
return $this->fail('add failed');
}
/**
* 更新数据
* @param Request $request
* @return Response
*/
#[Permission('玩家钱包流水修改', 'dice:player_wallet_record:index:update')]
public function update(Request $request): Response
{
$data = $request->post();
$this->validate('update', $data);
$model = $this->logic->read($data['id'] ?? 0);
if ($model) {
$recordDeptId = is_array($model) ? ($model['dept_id'] ?? null) : ($model->dept_id ?? null);
if (! AdminScopeHelper::canAccessDept($this->adminInfo ?? null, $recordDeptId, $request->input('dept_id'))) {
return $this->fail('no permission to update this record');
}
}
$result = $this->logic->edit($data['id'], $data);
if ($result) {
return $this->success('update success');
} else {
return $this->fail('update failed');
}
}
}

View File

@@ -107,31 +107,6 @@ class DiceRewardConfigRecordController extends BaseController
}
}
/**
* 更新数据
* @param Request $request
* @return Response
*/
#[Permission('奖励配置权重测试记录修改', 'dice:reward_config_record:index:update')]
public function update(Request $request): Response
{
$data = $request->post();
$this->validate('update', $data);
$model = $this->logic->read($data['id'] ?? 0);
if ($model) {
$recordDeptId = is_array($model) ? ($model['dept_id'] ?? null) : ($model->dept_id ?? null);
if (! AdminScopeHelper::canAccessDept($this->adminInfo ?? null, $recordDeptId, $request->input('dept_id'))) {
return $this->fail('no permission to update this record');
}
}
$result = $this->logic->edit($data['id'], $data);
if ($result) {
return $this->success('update success');
} else {
return $this->fail('update failed');
}
}
/**
* 删除数据
* @param Request $request

View File

@@ -7,6 +7,13 @@ use plugin\saiadmin\basic\think\BaseModel as SaiBaseModel;
/**
* 大富翁模块模型基类:删除均为硬删除(物理删除)
*
* 注意:
* - 不要在此重写实例方法 delete(),否则与 trait/父类的 delete() 相互覆盖,
* 在调用 $this->force()->delete() 时会无限递归force() 返回 $this
* 导致内存爆栈、HTTP 500。
* - 物理删除一律通过静态 destroy() 入口(强制 $force=true完成
* SoftDelete::destroy() 内部会按硬删除分支执行。
*/
abstract class DiceModel extends SaiBaseModel
{
@@ -17,9 +24,4 @@ abstract class DiceModel extends SaiBaseModel
{
return parent::destroy($data, true);
}
public function delete(): bool
{
return $this->force()->delete();
}
}

View File

@@ -12,10 +12,18 @@ use plugin\saiadmin\basic\contracts\ModelInterface;
/**
* ThinkORM 模型基类
*
* 全局策略:所有删除一律为硬删除(物理删除)。
* - 保留 SoftDelete trait 仅是为了兼容历史字段(如 delete_time与查询作用域
* 实际删除方法delete/destroy均通过 trait 别名重写为强制 force=true。
* - 项目中不使用 withTrashed/onlyTrashed/restore() 等软删除恢复接口。
*/
class BaseModel extends Model implements ModelInterface
{
use SoftDelete;
use SoftDelete {
delete as protected softDeleteCascadeOriginal;
destroy as protected softDeleteDestroyOriginal;
}
/**
* 删除时间字段
@@ -99,6 +107,25 @@ class BaseModel extends Model implements ModelInterface
}
}
/**
* 删除记录(静态入口):一律强制硬删除(物理删除)。
* @param mixed $data 主键、闭包或条件
* @param bool $force 兼容签名,内部一律按 true 处理
*/
public static function destroy($data, bool $force = true): bool
{
return static::softDeleteDestroyOriginal($data, true);
}
/**
* 删除记录(实例方法):一律强制硬删除。
*/
public function delete(): bool
{
$this->force(true);
return $this->softDeleteCascadeOriginal();
}
/**
* 新增前事件:自动写入 create_time有后台登录信息时写入 created_by
* @param Model $model