GamePortrait/src/views/play.vue

1739 lines
53 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div
class="play"
:class="[
{ offcamera: !switchVideo },
{ no_bg: tableData && (tableData.game_id == 7 || tableData.game_id == 8) }
]"
>
<!-- Top Navigation Bar -->
<!-- Main View Container: Vertical Flex Stack -->
<div class="view">
<!-- 1. Video & Game Area (Top Section, Flex Grow to fill space) -->
<div class="video-container" ref="videoDom" @click.stop="closeSwitchView(false)">
<!-- Video Iframe -->
<iframe
v-if="switchVideo && tableData"
class="iframe"
:class="[
{
scale:
videoConfig.zoom &&
tableData.sendMode &&
(tableData.sendMode == 'endBet' ||
tableData.sendMode == 'sendScanResult')
}
]"
:src="videoUrl"
></iframe>
<!-- Overlays inside Top Section -->
<SwitchTableButton v-if="!switchtabshow" @click="showSwitchtab(true)" />
<!-- Status/Countdown Overlay (Moved to absolute end for visibility) -->
<div class="status-overlay" v-if="tableData">
<!-- Dealing Status -->
<div class="status-circle dealing" v-if="['endBet', 'sendScanResult', 'openingBaccaratResult', 'openingDtResult', 'openingNnResult', 'openingTcResult', 'openingToningResult', 'openingDiceResult', 'openingRouletteResult'].includes(tableData.sendMode)">
<span>发牌中</span>
</div>
<!-- Countdown -->
<div class="status-circle countdown" v-else-if="tableData.sendMode === 'startBetCountDown' || tableData.sendMode === 'toBet'">
<span>{{ circle.num }}</span>
</div>
<!-- Wait/Shuffle/Other -->
<div class="status-circle wait" v-else-if="!tableData.sendMode || tableData.sendMode === 'changeBoot'">
<span style="font-size: 12px;">洗牌中</span>
</div>
</div>
<!-- Back Button (Top Left) -->
<div class="video-btn-back" @click="handleBack"></div>
</div>
<!-- Top Navigation Bar (Moved below video) -->
<div class="nav">
<!-- 1. User Info -->
<div class="nav-item user-info">
<div class="icon user-icon"></div>
<div class="text">{{ userInfo.username }}</div>
</div>
<!-- 2. Balance -->
<div class="nav-item balance-info">
<div class="icon money-icon"></div>
<div class="text">{{ userInfo.money }}</div>
</div>
<!-- 3. Table Limit -->
<div class="nav-item limit-info">
<div class="icon limit-icon"></div>
<div class="text">{{ tableData && tableData.limit_money }}</div>
</div>
<!-- 4. Right Controls (Camera, etc.) -->
<div class="nav-item right-controls">
<!-- Camera moved to video overlay -->
<!-- <div class="btn muise" @click="toggleAplayer"></div> -->
<div class="btn menu" @click="showMenu"></div>
</div>
</div>
<!-- Game Betting Area (Moved below Nav) -->
<div class="game-area-block" :class="{ 'nn-mode': tableData && (tableData.game_id == 4 || tableData.game_id == 5) }">
<div class="game-area-content">
<template
v-if="tableData && (tableData.game_id == 7 || tableData.game_id == 8)"
>
<Transition
name="custom-classes"
enter-active-class="animate__animated animate__faster animate__fadeInRight"
leave-active-class="animate__animated animate__faster animate__fadeOutRight"
>
<PlayTable
v-if="hideVideo"
class="dice-table"
ref="chipTable"
:game_id="tableData?.game_id"
:sendMode="tableData?.sendMode"
:table_id="tableData?.id"
:number_tab_id="tableData?.number_tab_id"
:is_scavenging="tableData?.is_scavenging"
:winArray="winArray"
:bet_amount_msg="tableData?.bet_amount_msg"
:can_bet_big_small="tableData?.can_bet_big_small"
:can_bet_luck_six="tableData?.can_bet_luck_six"
:limit_money="tableData?.limit_money"
:limit_money_pair="tableData?.limit_money_pair"
:limit_money_tie="tableData?.limit_money_tie"
:tableData="tableData"
></PlayTable>
</Transition>
</template>
<template v-else>
<PlayTable
ref="chipTable"
:game_id="tableData?.game_id"
:sendMode="tableData?.sendMode"
:table_id="tableData?.id"
:number_tab_id="tableData?.number_tab_id"
:is_scavenging="tableData?.is_scavenging"
:winArray="winArray"
:bet_amount_msg="tableData?.bet_amount_msg"
:can_bet_big_small="tableData?.can_bet_big_small"
:can_bet_luck_six="tableData?.can_bet_luck_six"
:limit_money="tableData?.limit_money"
:limit_money_pair="tableData?.limit_money_pair"
:limit_money_tie="tableData?.limit_money_tie"
:tableData="tableData"
:class="[
{
hideTable:
tableData &&
(tableData.sendMode == 'sendScanResult' ||
tableData.sendMode == 'endBet') &&
(tableData.game_id == 4 || tableData.game_id == 5)
}
]"
></PlayTable>
</template>
<RushVillage
v-if="tableData && tableData.is_rob == 1"
:thisData="tableData"
></RushVillage>
</div>
</div>
<!-- 2. Chip Control Bar (Middle Section, Fixed Height) -->
<PlayChip class="bet-chip-bar"></PlayChip>
<!-- 3. Poker Cards for NN/TC (Separate section above roadmap) -->
<div
class="poker-section"
v-if="tableData && (tableData.game_id == 4 || tableData.game_id == 5) && tableData.is_scavenging == 1"
>
<Poker
class="pokerView nn-compact"
:thisData="tableData"
></Poker>
</div>
<!-- 4. Roadmap (Bottom Section, Fixed Height or Flex Basis) -->
<div class="roadmap-container" :class="{ 'nn-mode': tableData && (tableData.game_id == 4 || tableData.game_id == 5) }" @click.stop="closeSwitchView(false)">
<PlayWay :tableData="tableData"></PlayWay>
<!-- Poker for Baccarat/Dragon Tiger (overlay style) -->
<Poker
class="pokerView"
:thisData="tableData"
v-if="tableData && tableData.is_scavenging == 1 && tableData.game_id != 4 && tableData.game_id != 5"
></Poker>
<ToningResult
v-if="showToningResult"
:result="tableData.round && tableData.round.result"
:sendMode="tableData.sendMode"
></ToningResult>
<DiceResult
v-if="showDiceResult"
:result="tableData.round && tableData.round.result"
:sendMode="tableData.sendMode"
></DiceResult>
<RouletteResult
v-if="showRouletteResult"
:resultInfo="tableData.round"
:sendMode="tableData.sendMode"
></RouletteResult>
</div>
</div>
<!-- Moved Switch Views to Root View Level for Full Height Overlay -->
<van-popup
v-model:show="switchtabshow"
position="bottom"
:style="{ height: '100%' }"
:overlay="true"
:close-on-click-overlay="false"
@click.stop
>
<SwitchTab
@showSwitchtab="showSwitchtab"
:tabInfo="{
game_id: tableData && tableData.game_id,
table_id: tableData && tableData.id
}"
></SwitchTab>
</van-popup>
<div
class="switchView camera animate__fadeIn animated0"
v-if="switchCameraShow"
>
<setCamera @showSwitchCamera="showSwitchCamera"></setCamera>
</div>
<!-- Popups -->
<PlayTypePop class="play-type-pop"></PlayTypePop>
<TableInfoPop class="table-info-pop" :tableData="tableData"></TableInfoPop>
<OnLinePop class="online-pop"></OnLinePop>
</div>
</template>
<script>
import { ref, computed, watch, nextTick, onUnmounted } from "vue"
import { useRouter, useRoute } from "vue-router"
import { useStore } from "vuex"
import { showToast, showDialog, closeDialog, closeToast } from "vant"
import PlayTable from "@/components/PlayTable/Index.vue"
import PlayWay from "@/components/PlayWay.vue"
import PlayChip from "@/components/PlayChip.vue"
import Poker from "@/components/Poker"
import RushVillage from "@/components/RushVillage"
import ToningResult from "@/components/ToningResult"
import DiceResult from "@/components/DiceResult"
import RouletteResult from "@/components/RouletteResult"
import SwitchTab from "@/components/SwitchTab2"
import SwitchTableButton from "@/components/SwitchTableButton"
import setCamera from "@/components/setCamera"
import PlayTypePop from "@/components/PlayTypePop"
import TableInfoPop from "@/components/TableInfoPop"
import OnLinePop from "@/components/OnLinePop"
import { audioMp3 } from "@/assets/js/sound.js"
import {
getUserBetBaccarat,
getUserBetDt,
getUserBetNn,
getUserBetTc,
getUserBetToning,
getUserBetDice,
getUserBetRoulette
} from "@/utils/api"
export default {
name: "playView",
components: {
PlayTable,
PlayWay,
PlayChip,
Poker,
SwitchTab,
SwitchTableButton,
setCamera,
PlayTypePop,
TableInfoPop,
OnLinePop,
RushVillage,
ToningResult,
DiceResult,
RouletteResult
},
setup() {
const audio = ref(null)
const videoDom = ref(null)
const foxVideo = ref({ w: 0, h: 0, mt: 0, ml: 0 })
const hideVideo = ref(true)
const table_id = ref(null)
const chipTable = ref(null)
const currentRate = ref(0)
const switchtabshow = ref(false)
const isSwitchtab = ref(false)
const switchCameraShow = ref(false)
const isSwitchCamera = ref(false)
const showToningResult = ref(false)
const showDiceResult = ref(false)
const showRouletteResult = ref(false)
const winArray = ref([])
const router = useRouter()
const route = useRoute()
const store = useStore()
const baccaratType = computed(() => store.state.config.baccaratType)
const rouletteType = computed(() => store.state.config.roulette_type)
const rouletteLockTable = computed(
() => store.state.config.rouletteLockTable
)
const tableData = computed(() => store.getters.getTableById(table_id.value))
const circle = computed(() => {
const data = { num: 0, rate: 0 }
if (tableData.value) {
const { count_down, wait_time, sendMode } = tableData.value
if (!sendMode) {
data.num = wait_time
data.rate = (data.num / wait_time) * 100
} else {
data.num = count_down || 0
data.rate = (data.num / wait_time) * 100
}
}
return data
})
const Type = computed(() => store.state.config.$Type)
const Lang = computed(() => store.state.config.$lang)
const routerStack = computed(() => store.state.app.routerStack)
const phoneModel = computed(() => store.state.config.phoneModel)
const phoneScreen = computed(() => store.state.config.phoneScreen)
const switchVideo = computed(() => store.state.config.switchVideo)
const videoConfig = computed(() => store.state.config.video)
const videoUrl = computed(() => {
let src = "",
media_url = ""
if (tableData.value && videoConfig.value) {
// line决定使用近景(near)还是远景(far)definition决定使用flv还是ws
if (videoConfig.value.line == 1) {
// 使用近景
if (videoConfig.value.definition == 1) {
media_url = tableData.value.media_near_flv
} else {
media_url = tableData.value.media_near_ws
}
} else {
// 使用远景
if (videoConfig.value.definition == 1) {
media_url = tableData.value.media_far_flv
} else {
media_url = tableData.value.media_far_ws
}
}
src = `${videoConfig.value.player}?url=${media_url}`
} else {
src = `static/video.html?url=${media_url}`
}
// const src = `static/video.html?url=https://al2-flv.live.huajiao.com/live_huajiao_h265/_LC_AL2_non_h265_SD_26820950716932293510114149_OX.flv`
return src
})
const closeSwitchView = () => {
showSwitchtab(false)
showSwitchCamera(false)
}
// 处理返回按钮
const handleBack = () => {
// 检查是否从 Portal 跳转过来
const urlParams = new URLSearchParams(window.location.search)
const from = urlParams.get('from')
const returnUrl = urlParams.get('returnUrl')
const language = urlParams.get('language')
if (from === 'portal' && returnUrl) {
// 返回到 Portal保留语言设置
let finalUrl = decodeURIComponent(returnUrl)
if (language) {
// 如果 URL 中已有参数,用 & 连接,否则用 ?
const separator = finalUrl.includes('?') ? '&' : '?'
finalUrl = `${finalUrl}${separator}language=${language}`
}
window.location.href = finalUrl
} else {
// 返回到游戏大厅
router.replace({ name: routerStack.value })
}
}
// 显示快捷换台列表
const showSwitchtab = (type) => {
store.commit("app/standbyTime")
if (type == false) {
switchtabshow.value = false
} else {
switchtabshow.value = true
}
}
// 切换视频线路
const showSwitchCamera = (type) => {
store.commit("app/standbyTime")
// isSwitchCamera.value = type
if (type == false) {
switchCameraShow.value = false
} else {
switchCameraShow.value = true
}
}
const userInfo = computed(() => store.state.app.userInfo)
// 视频开关
const offCamera = () => {
store.commit("config/switchVideo", !switchVideo.value)
}
// 显示音乐播放器
const toggleAplayer = () => {
store.commit("config/showAplayer")
}
// 显示隐藏视频
const toggleVide = () => {
hideVideo.value = !hideVideo.value
}
// 获取单台数据
let getSingletableTimer = null,
getSingletableNun = 0
const getSingletable = (id) => {
getSingletableNun++
clearTimeout(getSingletableTimer)
if (tableData.value && tableData.value.id) {
store.dispatch("socket/updateSingletable", {
type: "update",
table_id: id
})
let num = Math.floor(Math.random() * 6) + 1
if (num == 4) {
num = 1
}
let name = ""
switch (tableData.value.game_id) {
case 1:
name = "baccarat"
break
case 2:
name = "lh"
break
case 3:
break
case 4:
name = "nn"
break
case 5:
name = "tc"
break
case 6:
name = "toning"
break
case 7:
name = "dice"
break
case 8:
name = "lp"
break
}
audioMp3([`${name}_w_p${num}`]).Play()
} else {
if (getSingletableNun < 10) {
getSingletableTimer = setTimeout(() => {
getSingletable(id)
}, 1000)
}
}
}
const clearChip = (state) => {
nextTick(() => {
chipTable.value.resetChip(state)
})
}
const cancelChip = () => {
nextTick(() => {
chipTable.value.cancelChip()
})
}
// 显示菜单
const showMenu = () => {
store.commit("config/showMenu", true)
}
// 显示免佣设置
const showSetFree = () => {
store.commit("config/showSetFree")
}
// 显示玩法
const showBaccaratPlayType = () => {
store.commit("config/showBaccaratPlayType")
}
// 显示桌台信息
const showTableInfo = () => {
store.commit("config/showTableInfo", true)
}
// 显示在线人数
const showOnLine = () => {
store.commit("config/showOnLine")
}
// 切换轮盘 玩法
const switchRouletteType = () => {
if (rouletteLockTable.value) {
showToast("已下注")
} else {
store.commit("config/switchRouletteType")
}
}
// 百家乐结果
const baccaratResult = (data) => {
store.dispatch("socket/getGoodTabData")
getwinResult(1)
let mp3list = [],
win = [],
text = ""
mp3list.push(
"baccarat_banker",
`${data.round.banker}_point`,
"baccarat_player",
`${data.round.player}_point`
)
if (data.round.opening == 1) {
text = Lang.value[Type.value].msg_banker_win
win.push("banker")
mp3list.push("baccarat_b_win")
} else if (data.round.opening == 2) {
text = Lang.value[Type.value].msg_player_win
win.push("player")
mp3list.push("baccarat_p_win")
} else if (data.round.opening == 3) {
text = Lang.value[Type.value].msg_tie_win
win.push("tie")
mp3list.push("baccarat_tie")
}
// 大小
if (data.can_bet_big_small == 1) {
if (data.round.big_small == 1) {
text = text + "、" + Lang.value[Type.value].big
win.push("big")
} else if (data.round.big_small == 2) {
text = text + "、" + Lang.value[Type.value].small
win.push("small")
}
}
// 幸运6
if (data.can_bet_luck_six == 1) {
if (data.round.luck_six == 2) {
text = text + "、" + Lang.value[Type.value].luckSix + "X2"
win.push("luck_six")
} else if (data.round.luck_six == 3) {
text = text + "、" + Lang.value[Type.value].luckSix + "X3"
win.push("luck_six")
}
}
// 对子
if (data.round.pair == 1) {
text = text + "、" + Lang.value[Type.value].msg_banker_pair
win.push("banker_pair")
mp3list.push("baccarat_b_pair")
} else if (data.round.pair == 2) {
text = text + "、" + Lang.value[Type.value].msg_player_pair
win.push("player_pair")
mp3list.push("baccarat_p_pair")
} else if (data.round.pair == 3) {
text =
text +
"、" +
Lang.value[Type.value].msg_banker_pair +
"、" +
Lang.value[Type.value].msg_player_pair
win.push("banker_pair", "player_pair")
mp3list.push("baccarat_b_pair", "baccarat_p_pair")
}
showToast(text)
winArray.value = win
audioMp3(mp3list).Play()
setTimeout(() => {
winArray.value = []
clearChip()
}, 7000)
}
// 龙虎结果
const longhuResult = (data) => {
getwinResult(2)
let mp3list = [],
win = [],
text = ""
mp3list.push(
"lh_dragon",
`${data.round.banker}_point`,
"lh_tiger",
`${data.round.player}_point`
)
if (data.round.opening == 1) {
text = Lang.value[Type.value].msg_dragon_win
win.push("banker")
mp3list.push("lh_dragon_win")
} else if (data.round.opening == 2) {
text = Lang.value[Type.value].msg_tiger_win
win.push("player")
mp3list.push("lh_tiger_win")
} else if (data.round.opening == 3) {
text = Lang.value[Type.value].msg_tie_win
win.push("tie")
mp3list.push("lh_tie")
}
audioMp3(mp3list).Play()
showToast(text)
winArray.value = win
setTimeout(() => {
winArray.value = []
clearChip()
}, 7000)
}
// 牛牛
const nnResult = (data) => {
if (data.game_id == 4) {
getwinResult(4)
} else {
getwinResult(5)
}
let mp3list = [],
win = [],
text = ""
if (
data.round.win_player_1 == 0 &&
data.round.win_player_2 == 0 &&
data.round.win_player_3 == 0
) {
text = Lang.value[Type.value].msg_banker_win
mp3list.push("nn_banker")
win.push("player_1_banker", "player_2_banker", "player_3_banker")
win
} else {
if (data.round.win_player_1 == 1) {
text = text + Lang.value[Type.value].player1
mp3list.push("nn_player_1")
win.push("player_1")
} else {
win.push("player_1_banker")
}
if (data.round.win_player_2 == 1) {
text = text + Lang.value[Type.value].player2
mp3list.push("nn_player_2")
win.push("player_2")
} else {
win.push("player_2_banker")
}
if (data.round.win_player_3 == 1) {
text = text + Lang.value[Type.value].player3
mp3list.push("nn_player_3")
win.push("player_3")
} else {
win.push("player_3_banker")
}
}
audioMp3(mp3list).Play()
showToast(text)
winArray.value = win
setTimeout(() => {
winArray.value = []
clearChip()
}, 5000)
}
// 色碟
const toningResult = (data) => {
const language = Lang.value[Type.value]
getwinResult(6)
showToningResult.value = true
let mp3list = [],
win = [],
text = ""
switch (data.round.result) {
case 0:
win.push("toning_zero", "toning_plural", "toning_small")
mp3list.push("toning_4_white", "toning_even", "toning_small")
// text = "四白,双,小"
text = `${language.four} ${language.white}${language.even}${language.small}`
break
case 1:
win.push("toning_one", "toning_singular", "toning_small")
mp3list.push("toning_3_w_1_r", "toning_odd", "toning_small")
// text = "三白一红,单,小"
text = `${language.three} ${language.white} ${language.one} ${language.red}${language.odd}${language.small}`
break
case 2:
win.push("toning_plural")
mp3list.push("toning_2_w_2_r", "toning_even")
// text = "二红二白,双"
text = `${language.two} ${language.red} ${language.two} ${language.white}${language.even}`
break
case 3:
win.push("toning_three", "toning_singular", "toning_big")
mp3list.push("toning_1_w_3_r", "toning_odd", "toning_big")
// text = "三红一白,单,大"
text = `${language.three} ${language.red} ${language.one} ${language.white}${language.odd}${language.big}`
break
case 4:
win.push("toning_four", "toning_plural", "toning_big")
mp3list.push("toning_4_red", "toning_even", "toning_big")
// text = "四红,双,大"
text = `${language.four} ${language.red}${language.odd}${language.big}`
break
}
audioMp3(mp3list).Play()
showToast(text)
winArray.value = win
setTimeout(() => {
winArray.value = []
showToningResult.value = false
clearChip()
}, 5000)
}
// 骰宝
const diceResult = (data) => {
showDiceResult.value = true
getwinResult(7)
let mp3list = [],
text = ""
const result = data.round.result
const first = parseInt(result[0])
const second = parseInt(result[1])
const third = parseInt(result[2])
mp3list.push(
`dice_num_${first}`,
`dice_num_${second}`,
`dice_num_${third}`
)
if (first == second && second == third) {
text = "豹子,"
mp3list.push("dice_any_triple")
}
const totle = first + second + third
text = `${text}${totle}`
mp3list.push(`${totle}_point`)
audioMp3(mp3list).Play()
showToast(text)
winArray.value = data.round.result_parse
setTimeout(() => {
winArray.value = []
showDiceResult.value = false
clearChip()
}, 5000)
}
// 轮盘
const rouletteResult = (data) => {
showRouletteResult.value = true
getwinResult(7)
let mp3list = [],
text = ""
const result = data.round.result
text = result
mp3list.push(`${result}_point`)
// console.log(result, mp3list)
showToast(text)
winArray.value = data.round.result_parse
setTimeout(() => {
winArray.value = []
showRouletteResult.value = false
clearChip()
}, 3000)
}
const getwinResult = (game) => {
let Api = ""
if (game == 1) {
Api = getUserBetBaccarat
} else if (game == 2) {
Api = getUserBetDt
} else if (game == 4) {
Api = getUserBetNn
} else if (game == 5) {
Api = getUserBetTc
} else if (game == 6) {
Api = getUserBetToning
} else if (game == 7) {
Api = getUserBetDice
} else if (game == 8) {
Api = getUserBetRoulette
}
const params = {
user_id: userInfo.value.id,
api_token: userInfo.value.api_token,
number_tab_id: tableData.value?.previous_number_tab_id,
table_id: tableData.value.id
}
Api(params)
.then((response) => {
setTimeout(() => {
if (response.Success == 1) {
const data = response.Data
// console.log("输赢金额", data)
if (
game == 4 ||
game == 5 ||
game == 6 ||
game == 7 ||
game == 8
) {
showDialog({
allowHtml: true,
title: Lang.value[Type.value].msg_win_lose,
message:
'<div class="item title"><span>' +
Lang.value[Type.value].msg_bet_total +
"</span><span>" +
Lang.value[Type.value].msg_win_lose +
"</span><span>" +
Lang.value[Type.value].msg_balance +
"</span></div>" +
'<div class="item"><span>' +
data.amount +
"</span><span>" +
returnFloat(data.win_total) +
"</span><span>" +
data.end_money +
"</span></div>",
className: "alert",
confirmButtonText: Lang.value[Type.value].Confirm
})
} else {
popResult(data)
}
} else {
// console.log(response)
}
tableData.value.sendMode = ""
}, 3500)
})
.catch((error) => {
console.log(error)
})
}
const popResult = (data) => {
let html = ""
let text = ""
data.returnData.forEach((v) => {
if (v.amount > 0) {
if (v.win_amount > 0) {
text = Lang.value[Type.value].win
} else if (v.win_amount < 0) {
text = Lang.value[Type.value].lose
} else {
text = Lang.value[Type.value].tie
}
html =
html +
'<div class="item"><span>' +
v.name +
"</span><span>" +
v.amount +
"</span><span>" +
text +
"</span><span>" +
v.win_amount +
"</span></div>"
}
})
html =
'<div class="item title"><span>' +
Lang.value[Type.value].bet_type +
"</span><span>" +
Lang.value[Type.value].bet_amount +
"</span><span>" +
Lang.value[Type.value].note_result +
"</span><span>" +
Lang.value[Type.value].note_win_lose +
"</span></div>" +
html
showDialog({
allowHtml: true,
title:
Lang.value[Type.value].msg_win_lose +
"" +
returnFloat(data.win_total),
message: html,
className: "alert",
confirmButtonText: Lang.value[Type.value].Confirm
})
}
const returnFloat = (num) => {
let value = Math.round(parseFloat(num) * 100) / 100
let xsd = value.toString().split(".")
if (xsd.length == 1) {
value = value.toString() + ".00"
return value
}
if (xsd.length > 1) {
if (xsd[1].length < 2) {
value = value.toString() + "0"
}
return value
}
}
const foxVideoFn = () => {
nextTick(() => {
if (!videoDom.value) return;
const containerH = videoDom.value.clientHeight;
const containerW = videoDom.value.clientWidth;
// Default to 16:9 if missing
const proportion = videoConfig.value.proportion || [16, 9];
const videoRatio = proportion[0] / proportion[1];
const screenRatio = containerW / containerH; // Inverse for calculation check
let w, h, ml = 0, mt = 0;
if (screenRatio > videoRatio) {
// Screen is wider than video (or video is taller than screen) -> Height constrained
// e.g. Landscape or wide container
h = containerH;
w = h * videoRatio;
ml = (containerW - w) / 2;
} else {
// Screen is narrower than video (Standard Mobile Portrait) -> Width constrained
w = containerW;
h = w / videoRatio;
mt = (containerH - h) / 2;
}
// Apply specific offsets if needed, though usually centering is best for 'contain'
if (phoneModel.value == "ios") {
if (phoneScreen.value == "landscape") {
mt += videoConfig.value.offset.iosLandscape || 0;
} else {
mt += videoConfig.value.offset.iosPortrait || 0;
}
} else if (phoneModel.value == "android") {
mt += videoConfig.value.offset.android || 0;
} else {
mt += videoConfig.value.offset.pc || 0;
}
// Ensure values are safe
if (isNaN(w)) w = containerW;
if (isNaN(h)) h = containerW * 0.56; // Fallback 16:9
foxVideo.value.w = `${w}px`
foxVideo.value.h = `${h}px`
foxVideo.value.mt = `${mt}px`
foxVideo.value.ml = `${ml}px`
foxVideo.value.model = phoneModel.value
foxVideo.value.screen = phoneScreen.value
})
}
// 牛牛点数声音
const playNNsound = (data) => {
const position = parseInt(data.round.order_num)
if (data.game_id == 4) {
if ([15, 25, 35, 45].includes(position)) {
soundType(data.round.result)
} else {
audioMp3(["show_card"]).Play()
}
} else if (data.game_id == 5) {
if ([13, 23, 33, 43].includes(position)) {
soundType(data.round.result)
} else {
audioMp3(["show_card"]).Play()
}
}
function soundType(result) {
let name = ""
switch (result) {
case "无牛":
case "N0":
name = "no_bull"
break
case "牛1":
case "N1":
name = "bull_1"
break
case "牛2":
case "N2":
name = "bull_2"
break
case "牛3":
case "N3":
name = "bull_3"
break
case "牛4":
case "N4":
name = "bull_4"
break
case "牛5":
case "N5":
name = "bull_5"
break
case "牛6":
case "N6":
name = "bull_6"
break
case "牛7":
case "N7":
name = "bull_7"
break
case "牛8":
case "N8":
name = "bull_8"
break
case "牛9":
case "N9":
name = "bull_9"
break
case "牛牛":
case "NN":
name = "bull_bull"
break
case "五公":
name = "five_pictur_bull"
break
case "豹子":
name = "any_triple"
break
case "同花顺":
name = "straight_flush"
break
case "皇家同花順":
name = "royal_flush"
break
}
audioMp3([`${data.game_id == 4 ? "nn" : "tc"}_${name}`]).Play()
}
}
// 离开销毁
onUnmounted(() => {
closeDialog()
closeToast()
audioMp3().Pause()
})
// 监听机型和横竖屏
watch(
() => [phoneModel, phoneScreen],
() => {
foxVideoFn()
},
{ immediate: true, deep: true }
)
// switchVideo
watch(
() => [switchVideo.value],
([state]) => {
if (state == false) {
hideVideo.value = true
}
},
{ immediate: true, deep: true }
)
// 监听路由切换
watch(
() => [route.query],
([query]) => {
winArray.value = []
table_id.value = query.id
getSingletable(query.id)
const { game_id = 1 } = tableData.value
store.commit("app/updateGameId", game_id)
},
{ immediate: true, deep: true }
)
watch(
() => [tableData.value],
([data]) => {
const sendMode = (data && data.sendMode) || null
switch (sendMode) {
case "startBet": //开始游戏
if (data.game_id == 6) {
showToningResult.value = false
}
if (data.game_id == 7) {
hideVideo.value = true
}
if (data.game_id == 8) {
hideVideo.value = true
store.commit("config/rouletteLockTable", false)
}
closeDialog()
showToast(Lang.value[Type.value].msg_start_bet)
if (data.game_id != 5) {
audioMp3(["start_betting"]).Play()
} else {
audioMp3(["tc_banker_stop", "start_betting"]).Play()
}
break
case "startRob": //开始抢庄
showToast(Lang.value[Type.value].msg_accept_grab)
audioMp3(["tc_start_banker"]).Play()
break
case "toRobResult": //抢庄后通知
if (data.RobMsg.rob_banker_id == userInfo.value.id) {
audioMp3(["tc_banker_success"]).Play()
}
break
case "startRobCountDown":
break
case "startBetCountDown": //倒计时
if (data.count_down == 10) {
audioMp3(["last_10_seconds"]).Play()
} else if (data.count_down < 9) {
audioMp3(["time"]).Play()
}
break
case "toBet":
showToast({
message: Lang.value[Type.value].msg_bet_success,
position: "top"
})
if (data.game_id == 8) {
store.commit("config/rouletteLockTable", true)
}
break
case "toBetFlase":
audioMp3(["alert"]).Play()
cancelChip()
break
case "sendScanResult":
if (data.game_id == 1) {
const position = parseInt(data.round.position)
if (position == 23) {
audioMp3(["baccarat_b_draw"]).Play()
} else if (position == 13) {
audioMp3(["baccarat_p_draw"]).Play()
} else {
audioMp3(["show_card"]).Play()
}
} else if (data.game_id == 2) {
audioMp3(["show_card"]).Play()
} else if (data.game_id == 4 || data.game_id == 5) {
playNNsound(data)
} else {
audioMp3(["show_card"]).Play()
}
break
case "cancelBet":
showToast(Lang.value[Type.value].msg_cancel_success)
break
case "endBet":
if (data.game_id == 6) {
showToningResult.value = true
} else if (data.game_id == 7) {
showDiceResult.value = true
} else if (data.game_id == 8) {
showRouletteResult.value = true
}
if (
(data.game_id == 7 || data.game_id == 8) &&
switchVideo.value == true
) {
hideVideo.value = false
}
showToast(Lang.value[Type.value].endBet)
audioMp3(["stop_betting"]).Play()
break
case "openingBaccaratResult":
baccaratResult(data)
break
case "openingDtResult":
longhuResult(data)
break
case "openingNnResult":
nnResult(data)
break
case "openingTcResult":
nnResult(data)
break
case "openingToningResult":
toningResult(data)
break
case "openingDiceResult":
diceResult(data)
if (data.game_id == 7) {
hideVideo.value = true
}
break
case "openingRouletteResult":
rouletteResult(data)
if (data.game_id == 8) {
hideVideo.value = true
}
break
case "retreated":
break
case "changeBoot":
showToast(Lang.value[Type.value].changeBoot)
clearChip()
break
case "resetBoot":
showToast(Lang.value[Type.value].resetBoot)
clearChip()
break
case "resetNumberTab":
showToast(Lang.value[Type.value].msg_council)
clearChip()
break
}
},
{ immediate: true, deep: true }
)
return {
Type,
audio,
videoConfig,
hideVideo,
userInfo,
tableData,
switchVideo,
router,
circle,
currentRate,
switchtabshow,
isSwitchtab,
switchCameraShow,
isSwitchCamera,
chipTable,
baccaratType,
rouletteType,
foxVideo,
videoDom,
phoneModel,
routerStack,
winArray,
showToningResult,
showDiceResult,
showRouletteResult,
showSwitchtab,
showSwitchCamera,
offCamera,
toggleAplayer,
clearChip,
handleBack,
cancelChip,
showMenu,
showSetFree,
showBaccaratPlayType,
showTableInfo,
showOnLine,
closeSwitchView,
toggleVide,
switchRouletteType,
videoUrl
}
}
}
</script>
<style lang="scss" scoped>
/* 深色豪华主题配色 */
$dark-bg: #0d0d0d;
$dark-bg-secondary: #000;
$card-bg: #000;
$border-color: #333;
$gold: #c5a059;
.play {
width: 100%;
height: 100%;
background: $dark-bg;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
box-sizing: border-box;
overflow: hidden;
/* Navigation Bar - Redesigned */
.nav {
position: relative;
z-index: 100;
width: 100%;
height: 36px; /* Slightly compact */
display: flex;
align-items: center;
justify-content: space-between;
background: #000; /* Pure black as per ref */
border-bottom: 1px solid #222;
flex-shrink: 0;
padding: 0 10px;
box-sizing: border-box;
.nav-item {
display: flex;
align-items: center;
height: 100%;
color: #fff;
font-size: 13px;
.icon {
width: 16px;
height: 16px;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
margin-right: 6px;
}
&.user-info {
.user-icon { background-image: url("~@/assets/images/icon/user.png"); }
.text { font-weight: bold; }
}
&.balance-info {
.money-icon { background-image: url("~@/assets/images/icon/money.png"); }
.text { color: $gold; font-family: monospace; font-size: 14px; }
}
&.limit-info {
/* Use switch_tab icon as placeholder for limit arrows if specific icon invalid,
or styling css borders for arrows */
.limit-icon {
width: 12px; height: 12px;
background-image: url("~@/assets/images/icon/switch_tab.png"); /* Placeholder */
transform: rotate(90deg); /* Make it look like up/down sort */
opacity: 0.7;
}
.text { color: #aaa; font-size: 12px; }
}
&.right-controls {
display: flex;
gap: 15px;
.btn {
width: 24px;
height: 24px;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
&.camera {
background-image: url("~@/assets/images/icon/camera.png");
&.off { background-image: url("~@/assets/images/icon/camera_off.png"); }
}
&.muise { background-image: url("~@/assets/images/icon/musie.png"); }
&.menu { background-image: url("~@/assets/images/icon/meun.png"); }
}
}
}
}
/* Main View Container - Vertical Flex */
.view {
display: flex;
flex-direction: column;
width: 100%;
height: 100%; /* Full height, Nav is now inside */
position: relative;
overflow: hidden;
/* 1. Video Container (Top) - Flex Grow */
.video-container {
flex: 1; /* Grow to fill available space */
position: relative;
width: 100%;
background: #000;
overflow: hidden;
.iframe {
border: none;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 1;
object-fit: cover; /* This won't affect iframe content but is good for semantics if it were a video tag. */
}
.game-area {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 10;
pointer-events: none;
/* Enable clicks on children */
::v-deep .playtable, ::v-deep .rushvillage {
pointer-events: auto;
}
}
/* Switch Views (Camera/Tables)Overlay */
.switchView {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 2010;
background: $dark-bg-secondary;
&.camera { width: 40%; position: absolute; bottom: 0; right: 0; border-top-left-radius: 0.3rem; }
}
/* Separated Video Controls */
.video-btn-back {
position: absolute;
top: 10px;
left: 10px;
z-index: 2005; /* Highest priority */
width: 30px;
height: 30px;
background-image: url("~@/assets/images/icon/back.png");
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
/* Overlay Camera Icon */
.video-overlay-camera {
position: absolute;
top: 10px;
right: 60px; /* Switch is at 10px, so this is left of it */
z-index: 2001;
width: 30px;
height: 30px;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
background-image: url("~@/assets/images/icon/camera.png");
&.off {
background-image: url("~@/assets/images/icon/camera_off.png");
}
}
/* Status Overlay Styles */
.status-overlay {
position: absolute;
top: 10px;
right: 50px; /* Left of switch button (10px + 30px + 10px gap) */
z-index: 3000;
display: flex;
pointer-events: none;
align-items: center;
justify-content: center;
.status-circle {
width: 40px; /* Slightly smaller for corner */
height: 40px;
border-radius: 50%;
background: rgba(0,0,0,0.6);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: bold;
font-size: 14px;
border: 2px solid rgba(255, 255, 255, 0.2);
backdrop-filter: blur(4px);
&.dealing {
background: rgba(46, 204, 113, 0.8); /* Green */
border-color: #2ecc71;
font-size: 12px;
}
&.countdown {
background: rgba(231, 76, 60, 0.8); /* Red/Orange for countdown */
border-color: #e74c3c;
font-size: 20px;
}
&.wait {
background: rgba(52, 152, 219, 0.8);
border-color: #3498db;
}
}
}
}
/* 4. Game Table Area (New Flex Item) */
.game-area-block {
width: 100%;
height: 200px; /* Fixed height for table area */
flex-shrink: 0;
position: relative;
background: transparent;
z-index: 10;
overflow: hidden;
/* NN/TC mode - same as other games */
&.nn-mode {
height: 200px;
}
/* Content wrapper that mimics old absolute positioning for internal components */
.game-area-content {
width: 100%;
height: 100%;
position: relative;
/* Ensure PlayTable fits inside this block */
::v-deep .playtable {
.view {
bottom: 0 !important; /* Anchor to bottom of this block */
top: auto !important;
height: 100% !important; /* Fill the block */
transform: none !important; /* Reset 3D transform for flat view if needed, or keep if 3D desired within this block */
}
/* Adjust portrait mode specifically if needed */
@media screen and (orientation: portrait) {
.view {
height: 100% !important;
border-radius: 0 !important;
}
}
}
}
}
/* 2. Chip Control Bar (Middle) - Fixed Height */
.bet-chip-bar {
width: 100%;
height: 60px;
flex-shrink: 0;
position: relative;
z-index: 20;
background: #111;
border-top: 1px solid #333;
}
/* 3. Poker Section for NN/TC - Compact horizontal layout */
.poker-section {
width: 100%;
height: 40px; /* 减小高度 */
flex-shrink: 0;
position: relative;
z-index: 20;
background: rgba(0, 0, 0, 0.85);
border-top: 1px solid #333;
overflow: hidden;
::v-deep .nn-compact {
/* Override default poker-box styles for compact mode */
.poker-box.nn {
position: relative !important;
height: 100% !important;
width: 100% !important;
display: flex !important;
flex-direction: row !important;
align-items: center !important;
justify-content: space-around !important;
padding: 2px 4px !important;
box-sizing: border-box !important;
background: transparent !important;
.list {
flex: 1 !important;
height: 100% !important;
min-width: 0 !important;
padding: 0 2px !important;
border-left: 1px solid #444 !important;
display: flex !important;
flex-direction: row !important;
align-items: center !important;
justify-content: center !important;
position: relative !important;
&:first-child {
border-left: none !important;
}
.title {
position: relative !important;
display: block !important;
font-size: 8px !important;
padding: 2px 3px !important;
border-radius: 2px !important;
white-space: nowrap !important;
margin-right: 2px !important;
flex-shrink: 0 !important;
/* 确保颜色样式正确应用 */
&.red {
background: #ff494b !important;
color: #fff !important;
}
&.blue {
background: #00a8ff !important;
color: #fff !important;
}
}
.role {
position: absolute !important;
width: auto !important;
left: 50% !important;
top: auto !important;
bottom: 2px !important;
transform: translateX(-50%) !important;
font-size: 10px !important;
padding: 1px 4px !important;
margin: 0 !important;
background: rgba(0, 0, 0, 0.7) !important;
border-radius: 2px !important;
color: #ffd700 !important;
}
.item {
width: 16px !important;
height: 22px !important;
margin: 0 -3px !important;
margin-top: 0 !important;
padding: 0 !important;
.card {
background-size: 100% 100% !important;
}
}
}
}
/* Position card (定位牌) - show on video area left side for NN/TC */
.nn_position {
display: flex !important;
position: fixed !important;
top: 15% !important; /* 在视频区域内 */
left: 8px !important;
transform: translateY(-50%) !important;
z-index: 2000 !important;
flex-direction: column !important;
align-items: center !important;
justify-content: center !important;
background: transparent !important;
padding: 0 !important;
pointer-events: none !important; /* 让点击穿透到下注区域 */
strong {
font-size: 10px !important;
color: #fff !important;
margin-bottom: 4px !important;
padding: 0 !important;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8) !important;
}
.item {
width: 36px !important;
height: 52px !important;
transform: none !important;
margin: 0 !important;
padding: 0 !important;
.card {
display: block !important;
width: 100% !important;
height: 100% !important;
background-size: 100% 100% !important;
border-radius: 3px !important;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5) !important;
}
}
}
/* 三卡牛牛的定位牌在 poker-box 内部,需要额外选择器 */
.poker-box.nn .nn_position {
display: flex !important;
position: fixed !important;
top: 15% !important; /* 在视频区域内 */
left: 8px !important;
transform: translateY(-50%) !important;
z-index: 2000 !important;
flex-direction: column !important;
align-items: center !important;
justify-content: center !important;
background: transparent !important;
padding: 0 !important;
strong {
font-size: 10px !important;
color: #fff !important;
margin-bottom: 4px !important;
padding: 0 !important;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8) !important;
}
.item {
width: 36px !important;
height: 52px !important;
transform: none !important;
margin: 0 !important;
padding: 0 !important;
.card {
display: block !important;
width: 100% !important;
height: 100% !important;
background-size: 100% 100% !important;
border-radius: 3px !important;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5) !important;
}
}
}
}
}
/* 4. Roadmap Container (Bottom) - Auto Height for Aspect Ratio */
.roadmap-container {
width: 100%;
height: 160px;
flex-shrink: 0;
background: #fff;
position: relative;
z-index: 20;
overflow: hidden;
/* NN/TC mode - larger roadmap */
&.nn-mode {
height: 130px;
}
/* Deep overrides to ensure standard layout and white background support */
::v-deep .playway {
height: 100% !important;
background: transparent !important;
&.white-theme {
background: #fff !important;
}
}
::v-deep .baccarat-view {
height: 100% !important;
}
::v-deep .road-box {
background: transparent !important;
}
::v-deep .canvas {
width: 100% !important;
height: 100% !important;
display: block !important;
}
}
}
}
</style>