[update]GameRtp页面接入后端
This commit is contained in:
94
web/src/api/backend/embed.ts
Normal file
94
web/src/api/backend/embed.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import createAxios from '/@/utils/axios'
|
||||
|
||||
export interface GameRtpParams {
|
||||
provider: string
|
||||
game_name: string
|
||||
page: number
|
||||
}
|
||||
|
||||
export function gameRtp(params: GameRtpParams) {
|
||||
return createAxios({
|
||||
url: '/admin/embed.Embed/gameRtp',
|
||||
method: 'get',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
export interface AddGamePayload {
|
||||
provider_site: string
|
||||
game_name: string
|
||||
image_url: string
|
||||
rtp: number
|
||||
status: 0 | 1
|
||||
}
|
||||
|
||||
export function addGame(data: AddGamePayload) {
|
||||
return createAxios(
|
||||
{
|
||||
url: '/admin/embed.Embed/addGame',
|
||||
method: 'post',
|
||||
data,
|
||||
},
|
||||
{
|
||||
showSuccessMessage: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export interface EditGamePayload extends AddGamePayload {
|
||||
id: number | string
|
||||
}
|
||||
|
||||
export function editGame(data: EditGamePayload) {
|
||||
return createAxios(
|
||||
{
|
||||
url: '/admin/embed.Embed/editGame',
|
||||
method: 'post',
|
||||
data,
|
||||
},
|
||||
{
|
||||
showSuccessMessage: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export function del(data: { ids: (number | string)[] }) {
|
||||
return createAxios(
|
||||
{
|
||||
url: '/admin/embed.Embed/del',
|
||||
method: 'post',
|
||||
data,
|
||||
},
|
||||
{
|
||||
showSuccessMessage: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export interface SaveGameRtpPayload {
|
||||
auto_game_rtp: 0 | 1
|
||||
auto_rtp_amount: Record<string, string | number>
|
||||
auto_frequency: number
|
||||
provider_display: string
|
||||
custom_config: string
|
||||
header_image_url: string
|
||||
text_color: string
|
||||
button_text_color: string
|
||||
base_color: string
|
||||
button_bg_color: string
|
||||
outline_color: string
|
||||
progress_bar_bg_color: string
|
||||
}
|
||||
|
||||
export function saveGameRtp(data: SaveGameRtpPayload) {
|
||||
return createAxios(
|
||||
{
|
||||
url: '/admin/embed.Embed/saveGameRtp',
|
||||
method: 'post',
|
||||
data,
|
||||
},
|
||||
{
|
||||
showSuccessMessage: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -45,6 +45,7 @@ export default {
|
||||
nameRequired: 'Please enter the game name',
|
||||
urlRequired: 'Please enter the game image URL',
|
||||
saved: 'Settings saved',
|
||||
invalidAutoAmount: 'Auto RTP Amount must be a valid JSON object',
|
||||
copied: 'Embed code copied',
|
||||
copyFailed: 'Unable to copy the embed code',
|
||||
operationSuccess: 'Operation completed',
|
||||
|
||||
@@ -45,6 +45,7 @@ export default {
|
||||
nameRequired: '请输入游戏名称',
|
||||
urlRequired: '请输入游戏图片 URL',
|
||||
saved: '设置已保存',
|
||||
invalidAutoAmount: '自动 RTP 范围必须是有效的 JSON 对象',
|
||||
copied: '嵌入代码已复制',
|
||||
copyFailed: '无法复制嵌入代码',
|
||||
operationSuccess: '操作成功',
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<span>{{ settings[item.key] }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-button type="primary" class="save-button" @click="saveSettings">
|
||||
<el-button type="primary" class="save-button" :loading="savingSettings" @click="saveSettings">
|
||||
{{ t('embed.gameRtp.save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -87,8 +87,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-wrap">
|
||||
<el-table :data="pagedRows" border stripe>
|
||||
<div v-loading="loading" class="table-wrap">
|
||||
<el-table :data="rows" border stripe>
|
||||
<el-table-column prop="id" :label="t('embed.gameRtp.gameId')" width="100" />
|
||||
<el-table-column prop="provider" :label="t('embed.gameRtp.provider')" min-width="140" />
|
||||
<el-table-column prop="name" :label="t('embed.gameRtp.gameName')" min-width="180" />
|
||||
@@ -115,13 +115,13 @@
|
||||
</div>
|
||||
|
||||
<div class="table-footer">
|
||||
<span>{{ t('embed.gameRtp.totalRecords', { total: filteredRows.length }) }}</span>
|
||||
<span>{{ t('embed.gameRtp.totalRecords', { total }) }}</span>
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
background
|
||||
layout="prev, pager, next"
|
||||
:page-size="pageSize"
|
||||
:total="filteredRows.length"
|
||||
:page-count="lastPage"
|
||||
@current-change="loadGameRtp"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
@@ -153,13 +153,13 @@
|
||||
<el-form-item :label="t('embed.gameRtp.status')" prop="status">
|
||||
<el-select v-model="form.status">
|
||||
<el-option :label="t('embed.gameRtp.active')" :value="1" />
|
||||
<el-option :label="t('embed.gameRtp.inactive')" :value="2" />
|
||||
<el-option :label="t('embed.gameRtp.inactive')" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">{{ t('embed.gameRtp.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submitForm">{{ t('embed.gameRtp.confirm') }}</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="submitForm">{{ t('embed.gameRtp.confirm') }}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -168,8 +168,9 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { addGame, del, editGame, gameRtp, saveGameRtp } from '/@/api/backend/embed'
|
||||
|
||||
defineOptions({
|
||||
name: 'embed/gameRtp',
|
||||
@@ -180,10 +181,44 @@ interface GameRtpRow {
|
||||
provider: string
|
||||
name: string
|
||||
imageUrl: string
|
||||
rtp: number
|
||||
rtp: number | null
|
||||
status: number
|
||||
}
|
||||
|
||||
interface GameRtpSourceRow {
|
||||
id?: number | string
|
||||
game_id?: number | string
|
||||
provider?: string
|
||||
game_provider?: string
|
||||
provider_site?: string
|
||||
type?: string
|
||||
name?: string
|
||||
game_name?: string
|
||||
image_url?: string
|
||||
game_image_url?: string
|
||||
url?: string
|
||||
rtp?: number | string
|
||||
game_rtp?: number | string
|
||||
status?: number | string
|
||||
game_status?: number | string
|
||||
}
|
||||
|
||||
interface GameRtpSetting {
|
||||
auto_game_rtp?: number | string
|
||||
auto_rtp_amount?: string | Record<string, string | number> | null
|
||||
auto_frequency?: number | string
|
||||
provider_display?: string | null
|
||||
custom_config?: string | null
|
||||
header_image_url?: string | null
|
||||
text_color?: string | null
|
||||
button_text_color?: string | null
|
||||
base_color?: string | null
|
||||
button_bg_color?: string | null
|
||||
outline_color?: string | null
|
||||
progress_bar_bg_color?: string | null
|
||||
update_time?: number | string | null
|
||||
}
|
||||
|
||||
type ColorSettingKey = 'textColor' | 'baseColor' | 'outlineColor' | 'buttonTextColor' | 'buttonBgColor' | 'progressBgColor'
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -229,9 +264,9 @@ const colorFields: { key: ColorSettingKey; label: string }[] = [
|
||||
{ key: 'progressBgColor', label: 'embed.gameRtp.progressBgColor' },
|
||||
]
|
||||
|
||||
const lastRefresh = ref('2026-06-11 18:23:54')
|
||||
const lastRefresh = ref('')
|
||||
const embedCode = ref(`<div style="position: relative;max-width: 1000px;margin: 0 auto;overflow: hidden;border: none;">
|
||||
<iframe id="gameRtpIframe" src="https://1xaud.em7bd7.co/embed/game-rtp/game-rtp.asp" style="width: 100%;overflow: hidden;border: none;"></iframe>
|
||||
<iframe id="gameRtpIframe" src="http://192.168.0.46:8000/index.php/api/external/gameRtp" style="width: 100%;overflow: hidden;border: none;"></iframe>
|
||||
<script>
|
||||
(() => {
|
||||
const merchantId = (typeof _ !== "undefined" && typeof _.getVar === "function") ? (_.getVar("merchantId") ?? "") : "";
|
||||
@@ -264,61 +299,156 @@ const embedCode = ref(`<div style="position: relative;max-width: 1000px;margin:
|
||||
}
|
||||
}, 1000);
|
||||
})();
|
||||
<\/script>
|
||||
/script>
|
||||
</div>`)
|
||||
|
||||
const sourceRows: GameRtpRow[] = [
|
||||
{ id: 15996, provider: 'SCRATCH', name: 'Spiritual Fortune', imageUrl: 'https://bonzagame.example/thumb_xl_spiritualfortune.png', rtp: 33, status: 1 },
|
||||
{ id: 15995, provider: 'SCRATCH', name: 'Monster Mayhem', imageUrl: 'https://bonzagame.example/thumb_xl_monstermayhem.png', rtp: 70, status: 1 },
|
||||
{ id: 15994, provider: 'SCRATCH', name: 'The Fiery Tales', imageUrl: 'https://bonzagame.example/thumb_xl_thefierytales.png', rtp: 76, status: 1 },
|
||||
{ id: 15993, provider: 'SCRATCH', name: 'The Chronicles', imageUrl: 'https://bonzagame.example/thumb_xl_thechronicles.png', rtp: 52, status: 1 },
|
||||
{ id: 15992, provider: 'SCRATCH', name: 'Spiritual King', imageUrl: 'https://bonzagame.example/thumb_xl_spiritualking.png', rtp: 31, status: 1 },
|
||||
{ id: 15991, provider: 'SCRATCH', name: 'Enchanted Forest', imageUrl: 'https://bonzagame.example/thumb_xl_enchantedforest.png', rtp: 66, status: 1 },
|
||||
{ id: 15990, provider: 'SCRATCH', name: 'Fortune Dino', imageUrl: 'https://bonzagame.example/thumb_xl_fortunedino.png', rtp: 69, status: 1 },
|
||||
{ id: 15989, provider: 'SCRATCH', name: 'Gold Hunter', imageUrl: 'https://bonzagame.example/thumb_xl_goldhunter.png', rtp: 42, status: 1 },
|
||||
{ id: 15988, provider: 'SCRATCH', name: 'Space Hunter', imageUrl: 'https://bonzagame.example/thumb_xl_spacehunter.png', rtp: 58, status: 1 },
|
||||
{ id: 15987, provider: 'SCRATCH', name: 'Prosperity Play', imageUrl: 'https://bonzagame.example/thumb_xl_prosperityplay.png', rtp: 73, status: 1 },
|
||||
{ id: 15986, provider: 'SCRATCH', name: 'Wonderful Alice', imageUrl: 'https://bonzagame.example/thumb_xl_wonderfulalice.png', rtp: 32, status: 1 },
|
||||
{ id: 15985, provider: 'SCRATCH', name: 'Galactic Trooper', imageUrl: 'https://bonzagame.example/thumb_xl_galactictrooper.png', rtp: 59, status: 2 },
|
||||
]
|
||||
|
||||
const rows = ref<GameRtpRow[]>(sourceRows)
|
||||
const rows = ref<GameRtpRow[]>([])
|
||||
const filters = reactive({ provider: '', name: '' })
|
||||
const appliedFilters = reactive({ provider: '', name: '' })
|
||||
const providers = computed(() => [...new Set(rows.value.map((row) => row.provider))].sort())
|
||||
const filteredRows = computed(() =>
|
||||
rows.value.filter(
|
||||
(row) =>
|
||||
(!appliedFilters.provider || row.provider === appliedFilters.provider) &&
|
||||
(!appliedFilters.name || row.name.toLowerCase().includes(appliedFilters.name.toLowerCase()))
|
||||
)
|
||||
)
|
||||
const providerOptions = ref<string[]>([])
|
||||
const providers = computed(() => [...new Set([...providerOptions.value, ...rows.value.map((row) => row.provider)].filter(Boolean))].sort())
|
||||
const currentPage = ref(1)
|
||||
const pageSize = 10
|
||||
const pagedRows = computed(() => filteredRows.value.slice((currentPage.value - 1) * pageSize, currentPage.value * pageSize))
|
||||
const lastPage = ref(1)
|
||||
const pageSize = ref(50)
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
|
||||
watch(filteredRows, () => {
|
||||
const lastPage = Math.max(1, Math.ceil(filteredRows.value.length / pageSize))
|
||||
if (currentPage.value > lastPage) currentPage.value = lastPage
|
||||
})
|
||||
|
||||
const search = () => {
|
||||
Object.assign(appliedFilters, filters)
|
||||
currentPage.value = 1
|
||||
const toNumber = (value: unknown) => {
|
||||
const number = Number(value)
|
||||
return Number.isFinite(number) ? number : 0
|
||||
}
|
||||
|
||||
const clearSearch = () => {
|
||||
const normalizeStatus = (value: unknown) => {
|
||||
if (typeof value === 'string') {
|
||||
const normalized = value.trim().toUpperCase()
|
||||
if (normalized === 'ACTIVE') return 1
|
||||
if (normalized === 'INACTIVE') return 0
|
||||
}
|
||||
return toNumber(value) === 1 ? 1 : 0
|
||||
}
|
||||
|
||||
const formatUpdateTime = (value: unknown) => {
|
||||
if (!value) return ''
|
||||
if (typeof value === 'string' && !/^\d+$/.test(value)) return value
|
||||
const timestamp = toNumber(value)
|
||||
if (!timestamp) return ''
|
||||
return new Date(timestamp * (timestamp < 1e12 ? 1000 : 1)).toLocaleString()
|
||||
}
|
||||
|
||||
const formatJsonSetting = (value: string | Record<string, unknown> | null | undefined) => {
|
||||
if (!value) return ''
|
||||
return typeof value === 'string' ? value : JSON.stringify(value)
|
||||
}
|
||||
|
||||
const applyRtpSetting = (setting?: GameRtpSetting) => {
|
||||
if (!setting) return
|
||||
settings.autoRtp = toNumber(setting.auto_game_rtp) === 1 ? 1 : 0
|
||||
settings.autoAmount = formatJsonSetting(setting.auto_rtp_amount)
|
||||
settings.frequency = String(setting.auto_frequency ?? '')
|
||||
settings.providerDisplay = setting.provider_display ?? ''
|
||||
settings.customConfig = setting.custom_config ?? ''
|
||||
settings.headerImage = setting.header_image_url ?? ''
|
||||
settings.textColor = setting.text_color ?? ''
|
||||
settings.buttonTextColor = setting.button_text_color ?? ''
|
||||
settings.baseColor = setting.base_color ?? ''
|
||||
settings.buttonBgColor = setting.button_bg_color ?? ''
|
||||
settings.outlineColor = setting.outline_color ?? ''
|
||||
settings.progressBgColor = setting.progress_bar_bg_color ?? ''
|
||||
lastRefresh.value = formatUpdateTime(setting.update_time)
|
||||
}
|
||||
|
||||
const normalizeResponse = (responseData: any) => {
|
||||
const source = responseData?.data ?? responseData ?? {}
|
||||
const list = Array.isArray(source.list) ? source.list : []
|
||||
|
||||
const normalizedRows: GameRtpRow[] = list.map((row: GameRtpSourceRow) => ({
|
||||
id: toNumber(row.id ?? row.game_id),
|
||||
provider: String(row.provider_site ?? row.game_provider ?? row.provider ?? row.type ?? ''),
|
||||
name: String(row.game_name ?? row.name ?? ''),
|
||||
imageUrl: String(row.game_image_url ?? row.image_url ?? row.url ?? ''),
|
||||
rtp: row.game_rtp == null && row.rtp == null ? null : toNumber(row.game_rtp ?? row.rtp),
|
||||
status: normalizeStatus(row.game_status ?? row.status),
|
||||
}))
|
||||
|
||||
if (currentPage.value === 1 && normalizedRows.length) pageSize.value = normalizedRows.length
|
||||
const normalizedTotal = toNumber(source.total ?? normalizedRows.length)
|
||||
|
||||
return {
|
||||
rows: normalizedRows,
|
||||
total: normalizedTotal,
|
||||
lastPage: Math.max(1, Math.ceil(normalizedTotal / pageSize.value)),
|
||||
providers: Array.isArray(source.provider) ? source.provider.map(String) : [],
|
||||
setting: source.rtp_setting as GameRtpSetting | undefined,
|
||||
}
|
||||
}
|
||||
|
||||
const loadGameRtp = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await gameRtp({
|
||||
provider: appliedFilters.provider,
|
||||
game_name: appliedFilters.name,
|
||||
page: currentPage.value,
|
||||
})
|
||||
const normalized = normalizeResponse(response.data)
|
||||
rows.value = normalized.rows
|
||||
total.value = normalized.total
|
||||
lastPage.value = normalized.lastPage
|
||||
providerOptions.value = normalized.providers
|
||||
applyRtpSetting(normalized.setting)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const search = async () => {
|
||||
Object.assign(appliedFilters, filters)
|
||||
currentPage.value = 1
|
||||
await loadGameRtp()
|
||||
}
|
||||
|
||||
const clearSearch = async () => {
|
||||
Object.assign(filters, { provider: '', name: '' })
|
||||
search()
|
||||
await search()
|
||||
}
|
||||
|
||||
const loadTemplate = () => {
|
||||
settings.customConfig = configTemplate
|
||||
}
|
||||
|
||||
const saveSettings = () => {
|
||||
lastRefresh.value = new Date().toLocaleString()
|
||||
ElMessage.success(t('embed.gameRtp.saved'))
|
||||
const savingSettings = ref(false)
|
||||
|
||||
const saveSettings = async () => {
|
||||
let autoRtpAmount: Record<string, string | number>
|
||||
try {
|
||||
const parsed = JSON.parse(settings.autoAmount)
|
||||
if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') throw new Error()
|
||||
autoRtpAmount = parsed
|
||||
} catch {
|
||||
ElMessage.error(t('embed.gameRtp.invalidAutoAmount'))
|
||||
return
|
||||
}
|
||||
|
||||
savingSettings.value = true
|
||||
try {
|
||||
await saveGameRtp({
|
||||
auto_game_rtp: settings.autoRtp as 0 | 1,
|
||||
auto_rtp_amount: autoRtpAmount,
|
||||
auto_frequency: toNumber(settings.frequency),
|
||||
provider_display: settings.providerDisplay,
|
||||
custom_config: settings.customConfig,
|
||||
header_image_url: settings.headerImage,
|
||||
text_color: settings.textColor,
|
||||
button_text_color: settings.buttonTextColor,
|
||||
base_color: settings.baseColor,
|
||||
button_bg_color: settings.buttonBgColor,
|
||||
outline_color: settings.outlineColor,
|
||||
progress_bar_bg_color: settings.progressBgColor,
|
||||
})
|
||||
await loadGameRtp()
|
||||
} finally {
|
||||
savingSettings.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const copyEmbedCode = async () => {
|
||||
@@ -333,6 +463,7 @@ const copyEmbedCode = async () => {
|
||||
const dialogVisible = ref(false)
|
||||
const dialogMode = ref<'create' | 'edit'>('create')
|
||||
const editingId = ref<number>()
|
||||
const submitting = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const form = reactive({
|
||||
provider: '',
|
||||
@@ -358,14 +489,39 @@ const submitForm = async () => {
|
||||
if (!(await formRef.value?.validate().catch(() => false))) return
|
||||
|
||||
if (dialogMode.value === 'create') {
|
||||
const nextId = Math.max(0, ...rows.value.map((row) => row.id)) + 1
|
||||
rows.value.unshift({ id: nextId, ...form })
|
||||
submitting.value = true
|
||||
try {
|
||||
await addGame({
|
||||
provider_site: form.provider,
|
||||
game_name: form.name,
|
||||
image_url: form.imageUrl,
|
||||
rtp: form.rtp,
|
||||
status: form.status as 0 | 1,
|
||||
})
|
||||
dialogVisible.value = false
|
||||
currentPage.value = 1
|
||||
await loadGameRtp()
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
} else {
|
||||
const index = rows.value.findIndex((row) => row.id === editingId.value)
|
||||
if (index >= 0) rows.value[index] = { id: editingId.value!, ...form }
|
||||
if (editingId.value == null) return
|
||||
submitting.value = true
|
||||
try {
|
||||
await editGame({
|
||||
id: editingId.value,
|
||||
provider_site: form.provider,
|
||||
game_name: form.name,
|
||||
image_url: form.imageUrl,
|
||||
rtp: form.rtp,
|
||||
status: form.status as 0 | 1,
|
||||
})
|
||||
dialogVisible.value = false
|
||||
await loadGameRtp()
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
dialogVisible.value = false
|
||||
ElMessage.success(t('embed.gameRtp.operationSuccess'))
|
||||
}
|
||||
|
||||
const removeRow = async (row: GameRtpRow) => {
|
||||
@@ -375,8 +531,9 @@ const removeRow = async (row: GameRtpRow) => {
|
||||
confirmButtonText: t('embed.gameRtp.confirm'),
|
||||
cancelButtonText: t('embed.gameRtp.cancel'),
|
||||
})
|
||||
rows.value = rows.value.filter((item) => item.id !== row.id)
|
||||
ElMessage.success(t('embed.gameRtp.deleted'))
|
||||
await del({ ids: [row.id] })
|
||||
if (rows.value.length === 1 && currentPage.value > 1) currentPage.value -= 1
|
||||
await loadGameRtp()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
@@ -395,6 +552,8 @@ const importGames = async () => {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadGameRtp)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
Reference in New Issue
Block a user