feat: 登录失效统一弹框 + 筹码越南K格式持久化 + play.vue 优化

- axios.config: 拦截登录过期响应,统一弹框后清除用户信息,防多次弹框
- app.js: chooseChip 改为越南 K 格式默认 10k,支持 localStorage 恢复
- bettingChips.js: 移除(逻辑已合并)
This commit is contained in:
testadmin
2026-04-20 10:55:51 +08:00
parent b326cffa95
commit 833d76e88c
4 changed files with 204 additions and 11 deletions
+42
View File
@@ -1,8 +1,48 @@
import axios from "axios"
import $store from "@/store"
import { encrypt, decrypt } from "@/utils/secret"
import { showDialog, closeDialog } from "vant"
let api = ""
// 登录失效统一处理(防止多 API 同时失败弹多个框)
let _authExpiredHandling = false
const handleAuthExpired = (res) => {
const msg = res && res.Msg
const isAuthExpired =
res &&
res.Success == 0 &&
(msg === "获取信息失败" ||
msg === "登录已过期" ||
msg === "token失效" ||
msg === "请重新登录")
if (!isAuthExpired) return false
if (_authExpiredHandling) return true
_authExpiredHandling = true
const cfg = $store.state.config
const Lang = cfg.$lang && cfg.$Type ? cfg.$lang[cfg.$Type] : {}
const title = Lang.tips || "提示"
const message = Lang.login_expired || "登录已过期,请重新登录"
let autoClose = null
showDialog({ title, message })
.then(() => {
clearTimeout(autoClose)
_authExpiredHandling = false
$store.commit("app/removeUserInfo")
})
.catch(() => {
_authExpiredHandling = false
$store.commit("app/removeUserInfo")
})
autoClose = setTimeout(() => {
closeDialog()
_authExpiredHandling = false
$store.commit("app/removeUserInfo")
}, 4000)
return true
}
const instance = axios.create({
timeout: 6000,
headers: {
@@ -103,6 +143,7 @@ const get = (url, params, option = {}) => {
encryptData
})
.then((res) => {
handleAuthExpired(res)
resolve(res)
})
.catch((err) => {
@@ -127,6 +168,7 @@ const post = (url, params, option = {}) => {
})
.then((res) => {
res.Data = res.Data ? JSON.parse(decrypt(res.Data)) : {}
handleAuthExpired(res)
resolve(res)
})
.catch((err) => {
+33 -3
View File
@@ -27,10 +27,28 @@ try {
console.log(err)
}
// 默认筹码(必须和 store/modules/config.js::chipArry 里的 key 对齐)
// 2026-04 改用越南 K 格式(10k/20k/.../100000k),旧 "1w" 已废弃
const DEFAULT_CHOOSE_CHIP = { key: "10k", show: true, value: 10000 }
// 从 localStorage 恢复上次选中的筹码,若不存在 / key 已下架则回退默认
let chooseChipInit = DEFAULT_CHOOSE_CHIP
try {
const saved = localStorage.getItem("chooseChip")
if (saved) {
const obj = JSON.parse(saved)
if (obj && typeof obj.key === "string" && typeof obj.value === "number" && obj.value > 0) {
chooseChipInit = { key: obj.key, show: obj.show !== false, value: obj.value }
}
}
} catch (err) {
console.log(err)
}
const state = {
game_id: 1,
gameUrl: "",
chooseChip: { key: "1s", value: 10 },
chooseChip: chooseChipInit,
userInfo: userData,
allTableData: [],
routerStack: routerStack,
@@ -67,9 +85,21 @@ const mutations = {
}
window.location.href = url
},
// 更新选择下注筹码
// 更新选择下注筹码(强类型 + 持久化到 localStorage
updateChooseChip: (state, data) => {
state.chooseChip = data
if (!data || typeof data !== "object") return
const safe = {
key: String(data.key || ""),
show: data.show !== false,
value: Number(data.value) || 0
}
if (!safe.key || safe.value <= 0) return
state.chooseChip = safe
try {
localStorage.setItem("chooseChip", JSON.stringify(safe))
} catch (err) {
console.log(err)
}
},
updateGameId(state, id) {
state.game_id = id
View File
+129 -8
View File
@@ -55,11 +55,28 @@
<!-- Subtitle Overlay -->
<Transition name="subtitle-fade">
<div class="subtitle-overlay" v-if="subtitle" :key="subtitle">
<span class="subtitle-text">{{ subtitle }}</span>
</div>
</Transition>
<!-- Back Button (Top Left) -->
<div class="video-btn-back" @click="handleBack"></div>
</div>
<!-- Notice Bar -->
<div class="notice-bar" v-if="userInfo.memo && userInfo.memo.length">
<div class="notice-icon">📢</div>
<div class="notice-wrap">
<div class="notice-content">
<span v-for="(item, i) in userInfo.memo" :key="i">{{ item.title }}&nbsp;&nbsp;&nbsp;&nbsp;</span>
</div>
</div>
</div>
<!-- Top Navigation Bar (Moved below video) -->
<div class="nav">
<!-- 1. User Info -->
@@ -317,6 +334,13 @@ export default {
const showDiceResult = ref(false)
const showRouletteResult = ref(false)
const winArray = ref([])
const subtitle = ref("")
let subtitleTimer = null
const showSubtitle = (text, duration = 3000) => {
clearTimeout(subtitleTimer)
subtitle.value = text
subtitleTimer = setTimeout(() => { subtitle.value = "" }, duration)
}
const router = useRouter()
const route = useRoute()
const store = useStore()
@@ -593,7 +617,7 @@ export default {
win.push("banker_pair", "player_pair")
mp3list.push("baccarat_b_pair", "baccarat_p_pair")
}
showToast(text)
showSubtitle(text, 5000)
winArray.value = win
audioMp3(mp3list).Play()
setTimeout(() => {
@@ -627,7 +651,7 @@ export default {
mp3list.push("lh_tie")
}
audioMp3(mp3list).Play()
showToast(text)
showSubtitle(text, 5000)
winArray.value = win
setTimeout(() => {
winArray.value = []
@@ -678,7 +702,7 @@ export default {
}
}
audioMp3(mp3list).Play()
showToast(text)
showSubtitle(text, 5000)
winArray.value = win
setTimeout(() => {
winArray.value = []
@@ -726,7 +750,7 @@ export default {
break
}
audioMp3(mp3list).Play()
showToast(text)
showSubtitle(text, 5000)
winArray.value = win
setTimeout(() => {
winArray.value = []
@@ -758,7 +782,7 @@ export default {
mp3list.push(`${totle}_point`)
audioMp3(mp3list).Play()
showToast(text)
showSubtitle(text, 5000)
winArray.value = data.round.result_parse
setTimeout(() => {
winArray.value = []
@@ -776,7 +800,7 @@ export default {
text = result
mp3list.push(`${result}_point`)
// console.log(result, mp3list)
showToast(text)
showSubtitle(text, 3000)
winArray.value = data.round.result_parse
setTimeout(() => {
winArray.value = []
@@ -1099,7 +1123,7 @@ export default {
store.commit("config/rouletteLockTable", false)
}
closeDialog()
showToast(Lang.value[Type.value].msg_start_bet)
showSubtitle(Lang.value[Type.value].msg_start_bet)
if (data.game_id != 5) {
audioMp3(["start_betting"]).Play()
} else {
@@ -1120,6 +1144,9 @@ export default {
case "startBetCountDown": //倒计时
if (data.count_down == 10) {
audioMp3(["last_10_seconds"]).Play()
} else if (data.count_down <= 5 && data.count_down > 0) {
showSubtitle(Lang.value[Type.value].endBet, 1000)
audioMp3(["time"]).Play()
} else if (data.count_down < 9) {
audioMp3(["time"]).Play()
}
@@ -1173,7 +1200,7 @@ export default {
) {
hideVideo.value = false
}
showToast(Lang.value[Type.value].endBet)
showSubtitle(Lang.value[Type.value].endBet)
audioMp3(["stop_betting"]).Play()
break
case "openingBaccaratResult":
@@ -1226,6 +1253,7 @@ export default {
audio,
videoConfig,
hideVideo,
subtitle,
userInfo,
nnGame,
tableData,
@@ -1292,6 +1320,60 @@ $gold: #c5a059;
/* Notice Bar - Marquee Style */
.notice-bar {
width: 100%;
height: 24px;
display: flex;
align-items: center;
background: rgba(0, 0, 0, 0.85);
border-bottom: 1px solid #222;
flex-shrink: 0;
overflow: hidden;
z-index: 100;
.notice-icon {
flex-shrink: 0;
width: 30px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
}
.notice-wrap {
flex: 1;
overflow: hidden;
position: relative;
height: 100%;
display: flex;
align-items: center;
.notice-content {
display: inline-flex;
white-space: nowrap;
animation: noticeScroll 20s linear infinite;
color: $gold;
font-size: 12px;
font-weight: 500;
letter-spacing: 0.5px;
span {
flex-shrink: 0;
}
}
}
}
@keyframes noticeScroll {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(-100%);
}
}
/* Navigation Bar - Redesigned */
.nav {
position: relative;
@@ -1839,4 +1921,43 @@ $gold: #c5a059;
}
}
}
/* Subtitle Overlay */
.subtitle-overlay {
position: absolute;
bottom: 20%;
left: 50%;
transform: translateX(-50%);
z-index: 3500;
pointer-events: none;
.subtitle-text {
display: inline-block;
padding: 8px 24px;
background: rgba(0, 0, 0, 0.75);
color: #ffd700;
font-size: 18px;
font-weight: bold;
border-radius: 6px;
letter-spacing: 2px;
text-shadow: 0 0 8px rgba(255, 215, 0, 0.6);
white-space: nowrap;
}
}
/* Subtitle Transition */
.subtitle-fade-enter-active {
transition: all 0.3s ease-out;
}
.subtitle-fade-leave-active {
transition: all 0.5s ease-in;
}
.subtitle-fade-enter-from {
opacity: 0;
transform: translateX(-50%) translateY(10px);
}
.subtitle-fade-leave-to {
opacity: 0;
transform: translateX(-50%) translateY(-10px);
}
</style>