diff --git a/src/plugins/axios.config.js b/src/plugins/axios.config.js
index 6e4874ab..f4e2ce61 100644
--- a/src/plugins/axios.config.js
+++ b/src/plugins/axios.config.js
@@ -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) => {
diff --git a/src/store/modules/app.js b/src/store/modules/app.js
index 499828b8..1ff1deca 100644
--- a/src/store/modules/app.js
+++ b/src/store/modules/app.js
@@ -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
diff --git a/src/utils/bettingChips.js b/src/utils/bettingChips.js
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/views/play.vue b/src/views/play.vue
index 2707165e..fd8072fa 100644
--- a/src/views/play.vue
+++ b/src/views/play.vue
@@ -55,11 +55,28 @@
+
+
+
+ {{ subtitle }}
+
+
+
+
+
+
@@ -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);
+}