555 lines
18 KiB
Vue
555 lines
18 KiB
Vue
<template>
|
|
<view class="page-container">
|
|
<!-- Top Header Area -->
|
|
<view class="top-header">
|
|
<image class="header-bg" src="/static/images/login_bg.png" mode="aspectFill"></image>
|
|
<view class="status-bar"></view>
|
|
<!-- User Info Row -->
|
|
<view class="user-bar">
|
|
<view class="left-info" @click="goPage('/pages/user/index')">
|
|
<u-icon name="arrow-left" color="#f4c46f" size="20" v-if="false"></u-icon>
|
|
<view class="user-avatar">
|
|
<u-icon name="account-fill" color="#fff" size="24"></u-icon>
|
|
</view>
|
|
<text class="username">{{ userInfo.username || $lang.guest }}</text>
|
|
<view class="balance-pill">
|
|
<u-icon name="rmb-circle-fill" color="#f4c46f" size="16"></u-icon>
|
|
<text class="balance-text">{{ userInfo.money || '0.00' }}</text>
|
|
</view>
|
|
</view>
|
|
<view class="right-actions">
|
|
<u-icon name="server-fill" color="#fff" size="24" @click="goService" style="margin-right: 15px;"></u-icon>
|
|
<u-icon name="grid-fill" color="#fff" size="24" @click="goPage('/pages/user/index')"></u-icon>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Banner Area -->
|
|
<view class="banner-area">
|
|
<u-swiper :list="bannerList" height="140" radius="8" indicator indicatorMode="line" circular></u-swiper>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Main Content Layout -->
|
|
<view class="main-body">
|
|
<!-- Left Sidebar -->
|
|
<scroll-view scroll-y class="sidebar">
|
|
<view
|
|
class="sidebar-item"
|
|
v-for="(cat, index) in categories"
|
|
:key="index"
|
|
:class="{ active: currentCategory === cat.id }"
|
|
@click="switchCategory(cat.id)"
|
|
>
|
|
<view class="icon-box">
|
|
<!-- Use local images or u-icons based on category -->
|
|
<image :src="cat.icon" mode="widthFix" style="width: 24px; height: 24px;" v-if="cat.icon" />
|
|
<u-icon :name="cat.uIcon" color="#ccc" size="28" v-else></u-icon>
|
|
</view>
|
|
<text class="cat-name">{{ $lang[cat.nameKey] }}</text>
|
|
<text class="cat-count">({{ getCategoryCount(cat.id) }})</text>
|
|
</view>
|
|
|
|
<!-- Online Count Bottom -->
|
|
<view class="online-count-box">
|
|
<text class="label">{{ $lang.onlineCount }}</text>
|
|
<text class="count">2506</text>
|
|
</view>
|
|
</scroll-view>
|
|
|
|
<!-- Right Content Area -->
|
|
<scroll-view scroll-y class="content-area">
|
|
<view class="table-list">
|
|
<view class="loading-state" v-if="loading">
|
|
<u-loading-icon color="#f4c46f" vertical :text="$lang.loading"></u-loading-icon>
|
|
</view>
|
|
|
|
<view
|
|
class="game-card"
|
|
v-for="(item, index) in filteredTables"
|
|
:key="index"
|
|
@click="enterGame(item)"
|
|
>
|
|
<!-- Card Header -->
|
|
<view class="card-header">
|
|
<view class="header-left">
|
|
<text class="table-name">{{ item.table_name }}</text>
|
|
</view>
|
|
<view class="header-right">
|
|
<text class="limit-info">{{ $lang.maxLimit }}:50000 {{ $lang.minLimit }}:20</text>
|
|
<view class="status-dot" :class="{ 'is-live': item.status===1 }"></view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Roadmap Visualization (Body) -->
|
|
<view class="card-body">
|
|
<!-- Roadmap Grid (Zhupan / Bead Plate) -->
|
|
<!-- Assuming 6 rows x N columns -->
|
|
<view class="roadmap-grid">
|
|
<!-- Render grid cells -->
|
|
<view
|
|
v-for="(cell, cIdx) in item.beadRoad"
|
|
:key="cIdx"
|
|
class="road-cell"
|
|
:style="{
|
|
left: ((cell.show_x - 1) * cellWidth) + 'px',
|
|
top: ((cell.show_y - 1) * cellHeight) + 'px'
|
|
}"
|
|
:class="getCellClass(cell.result)"
|
|
>
|
|
<text class="cell-text" v-if="item.game_id !== 4 && item.game_id !== 5">{{ getResultText(cell.result) }}</text>
|
|
<!-- NN/ThreeCard specialized display could go here -->
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Center Overlay Status -->
|
|
<view class="center-overlay" v-if="true">
|
|
|
|
<view class="status-badge-lg" :class="getStatusClass(item.bet_status)">
|
|
<u-icon name="hourglass" size="20" color="#fff" v-if="item.bet_status == 1"></u-icon>
|
|
<u-icon name="lock" size="20" color="#fff" v-else-if="item.bet_status == 2"></u-icon>
|
|
<u-icon name="play-circle" size="20" color="#fff" v-else></u-icon>
|
|
|
|
<text class="status-text-lg">
|
|
{{ getStatusText(item.bet_status) }}
|
|
</text>
|
|
<text class="countdown-text" v-if="item.countdown > 0">{{ item.countdown }}s</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view style="height: 60px;"></view>
|
|
</view>
|
|
</scroll-view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import { mapState } from "vuex";
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
userInfo: {},
|
|
bannerList: [
|
|
'/static/images/new_banner_1.jpg',
|
|
'/static/images/new_banner_2.jpg',
|
|
],
|
|
currentCategory: 0, // 0 = All or default category
|
|
categories: [
|
|
// Mapping based on common IDs: 1=Baccarat, 2=DT, 4=Bull, 5=ThreeCard
|
|
{ id: 1, nameKey: 'baccarat', uIcon: 'chrome-circle-fill', gameIds: [1] },
|
|
{ id: 2, nameKey: 'longhu', uIcon: 'heart-fill', gameIds: [2] },
|
|
{ id: 4, nameKey: 'niuniu', uIcon: 'thumb-up-fill', gameIds: [4] },
|
|
],
|
|
allTables: [],
|
|
loading: false,
|
|
timer: null,
|
|
|
|
// Roadmap config
|
|
cellWidth: 16.5, // slightly adjusted for mobile fit
|
|
cellHeight: 17,
|
|
};
|
|
},
|
|
computed: {
|
|
...mapState({
|
|
storeUserInfo: (state) => state.app.userInfo,
|
|
language: (state) => state.app.language,
|
|
$lang: (state) => state.app.lang[state.app.language],
|
|
}),
|
|
filteredTables() {
|
|
if (!this.allTables.length) return [];
|
|
const currentCatParams = this.categories.find(c => c.id === this.currentCategory);
|
|
if (!currentCatParams) return this.allTables;
|
|
|
|
return this.allTables.filter(t => currentCatParams.gameIds.includes(t.game_id));
|
|
}
|
|
},
|
|
onShow() {
|
|
// Sync userInfo
|
|
this.userInfo = this.storeUserInfo || {};
|
|
this.startPolling();
|
|
},
|
|
onHide() {
|
|
this.stopPolling();
|
|
},
|
|
methods: {
|
|
goPage(url) {
|
|
uni.navigateTo({ url });
|
|
},
|
|
goService() {
|
|
// Mock service
|
|
const url = this.$store.state.app.customServiceUrl;
|
|
if(url) window.open(url, '_blank');
|
|
},
|
|
switchCategory(id) {
|
|
this.currentCategory = id;
|
|
},
|
|
getCategoryCount(id) {
|
|
if (!this.allTables) return 0;
|
|
const cat = this.categories.find(c => c.id === id);
|
|
if (!cat) return 0;
|
|
return this.allTables.filter(t => cat.gameIds.includes(t.game_id)).length;
|
|
},
|
|
startPolling() {
|
|
this.loadData();
|
|
this.timer = setInterval(() => {
|
|
this.loadData();
|
|
}, 3000);
|
|
},
|
|
stopPolling() {
|
|
if(this.timer) clearInterval(this.timer);
|
|
},
|
|
async loadData() {
|
|
if(!this.userInfo.id) return;
|
|
try {
|
|
const res = await this.$api.getAllTable({
|
|
user_id: this.userInfo.id,
|
|
api_token: this.userInfo.api_token
|
|
});
|
|
if (res.Success && res.Data) {
|
|
this.processData(res.Data);
|
|
}
|
|
} catch(e) {
|
|
console.error(e);
|
|
}
|
|
},
|
|
processData(data) {
|
|
// Transform data for UI
|
|
this.allTables = data.map(item => {
|
|
// Extract Bead Road (showRoad usually)
|
|
// Note: In helper.php, waybill() returns { showRoad, bigRoad ... }
|
|
let beadRoad = [];
|
|
if (item.ludan && item.ludan.waybill && item.ludan.waybill.showRoad) {
|
|
beadRoad = item.ludan.waybill.showRoad;
|
|
}
|
|
|
|
// Adjust roadmap to keep latest on screen (simple truncation logic for demo)
|
|
// Real logic might need scrolling or windowing
|
|
// For card display, we usually show last X columns
|
|
|
|
return {
|
|
...item,
|
|
beadRoad: beadRoad,
|
|
// Mock countdown if not provided
|
|
countdown: item.countdown || 15
|
|
};
|
|
});
|
|
},
|
|
// Visual Helpers
|
|
getCellClass(result) {
|
|
// 1=Banker(Red), 2=Player(Blue), 3=Tie(Green)
|
|
if (result === 1) return 'cell-red';
|
|
if (result === 2) return 'cell-blue';
|
|
if (result === 3) return 'cell-green';
|
|
return '';
|
|
},
|
|
getResultText(result) {
|
|
if (result === 1) return '庄';
|
|
if (result === 2) return '闲';
|
|
if (result === 3) return '和';
|
|
return '';
|
|
},
|
|
getStatusClass(status) {
|
|
// 1: betting, 2: dealing/disabled
|
|
if (status === 1) return 'status-betting';
|
|
return 'status-dealing';
|
|
},
|
|
getStatusText(status) {
|
|
if (status === 1) return this.$lang.betting;
|
|
if (status === 2) return this.$lang.dealing;
|
|
return this.$lang.preparing;
|
|
},
|
|
enterGame(item) {
|
|
// 跳转到同域名下的 /game 子目录
|
|
const portalUrl = window.location.origin + '/#/pages/index';
|
|
const url = `/game/?token=${this.userInfo.api_token}&language=${this.language}&from=portal&returnUrl=${encodeURIComponent(portalUrl)}#/play?id=${item.id}`;
|
|
window.location.href = url;
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
/* Global Layout */
|
|
.page-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100vh;
|
|
background-color: #0c0c0d;
|
|
color: #fff;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
}
|
|
|
|
/* Header */
|
|
.top-header {
|
|
flex-shrink: 0;
|
|
position: relative;
|
|
/* background-image removed, using image tag instead */
|
|
padding-bottom: 5px;
|
|
overflow: hidden;
|
|
|
|
.header-bg {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
z-index: 0;
|
|
}
|
|
|
|
.status-bar {
|
|
height: var(--status-bar-height);
|
|
width: 100%;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.user-bar {
|
|
position: relative;
|
|
z-index: 1;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 10px 15px;
|
|
|
|
.left-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
|
|
.username {
|
|
font-size: 14px;
|
|
font-weight: bold;
|
|
color: #fff;
|
|
}
|
|
|
|
.balance-pill {
|
|
background: rgba(0,0,0,0.5);
|
|
border: 1px solid #f4c46f;
|
|
border-radius: 12px;
|
|
padding: 2px 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
|
|
.balance-text {
|
|
color: #f4c46f;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.banner-area {
|
|
position: relative;
|
|
z-index: 1;
|
|
padding: 5px 10px;
|
|
}
|
|
}
|
|
|
|
/* Body Layout */
|
|
.main-body {
|
|
flex: 1;
|
|
display: flex;
|
|
overflow: hidden;
|
|
|
|
/* Sidebar styles */
|
|
.sidebar {
|
|
width: 80px;
|
|
background-color: #161618;
|
|
border-right: 1px solid #1f1f1f;
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
.sidebar-item {
|
|
padding: 15px 5px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-bottom: 1px solid #222;
|
|
transition: all 0.3s;
|
|
|
|
&.active {
|
|
background-color: #f4c46f;
|
|
|
|
.cat-name, .cat-count {
|
|
color: #000;
|
|
font-weight: bold;
|
|
}
|
|
|
|
// Could change icon color via filter if using svg
|
|
}
|
|
|
|
.icon-box {
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.cat-name {
|
|
font-size: 11px;
|
|
color: #999;
|
|
margin-bottom: 2px;
|
|
text-align: center;
|
|
word-break: break-word;
|
|
line-height: 1.2;
|
|
max-width: 70px;
|
|
}
|
|
.cat-count {
|
|
font-size: 10px;
|
|
color: #666;
|
|
}
|
|
}
|
|
|
|
.online-count-box {
|
|
margin-top: auto;
|
|
padding: 20px 5px;
|
|
text-align: center;
|
|
|
|
.label { font-size: 10px; color: #666; display: block; }
|
|
.count { font-size: 14px; color: #f4c46f; font-weight: bold; }
|
|
}
|
|
}
|
|
|
|
/* Content styles */
|
|
.content-area {
|
|
flex: 1;
|
|
background-color: #0c0c0d;
|
|
padding: 10px;
|
|
|
|
.game-card {
|
|
background-color: #1e1e1e;
|
|
border-radius: 6px;
|
|
margin-bottom: 12px;
|
|
border: 1px solid #333;
|
|
overflow: hidden;
|
|
|
|
.card-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
background: #252627;
|
|
padding: 5px 10px;
|
|
border-bottom: 1px solid #333;
|
|
height: 30px;
|
|
|
|
.table-name {
|
|
font-size: 13px;
|
|
color: #fff;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.header-right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
|
|
.limit-info {
|
|
font-size: 10px;
|
|
color: #999;
|
|
}
|
|
|
|
.status-dot {
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: 50%;
|
|
background-color: #666;
|
|
&.is-live { background-color: #0f0; }
|
|
}
|
|
}
|
|
}
|
|
|
|
.card-body {
|
|
position: relative;
|
|
height: 110px; // Fixed height for roadmap
|
|
background: #fff; // White background for roadmap grid
|
|
|
|
.roadmap-grid {
|
|
width: 100%;
|
|
height: 100%;
|
|
position: relative;
|
|
// Grid background lines
|
|
background-size: 16.5px 17px;
|
|
background-image:
|
|
linear-gradient(to right, #ccc 1px, transparent 1px),
|
|
linear-gradient(to bottom, #ccc 1px, transparent 1px);
|
|
|
|
.road-cell {
|
|
position: absolute;
|
|
width: 14px;
|
|
height: 14px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
border: 1px solid rgba(0,0,0,0.1);
|
|
|
|
&.cell-red {
|
|
background-color: #fff;
|
|
border: 2px solid #d0021b;
|
|
color: #d0021b;
|
|
}
|
|
&.cell-blue {
|
|
background-color: #fff;
|
|
border: 2px solid #4a90e2;
|
|
color: #4a90e2;
|
|
}
|
|
&.cell-green {
|
|
background-color: #fff;
|
|
border: 2px solid #7ed321;
|
|
color: #7ed321;
|
|
}
|
|
|
|
.cell-text {
|
|
font-size: 9px;
|
|
font-weight: bold;
|
|
}
|
|
}
|
|
}
|
|
|
|
.center-overlay {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
|
|
.status-badge-lg {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 10px 20px;
|
|
background: rgba(0,0,0,0.7);
|
|
border-radius: 8px;
|
|
// Backdrop gradient
|
|
background: radial-gradient(circle, rgba(169,134,60,0.95) 0%, rgba(0,0,0,0.8) 100%);
|
|
color: #fff;
|
|
box-shadow: 0 4px 10px rgba(0,0,0,0.5);
|
|
width: 100px;
|
|
|
|
&.status-dealing {
|
|
// background: radial-gradient(circle, rgba(200,50,50,0.9) 0%, rgba(0,0,0,0.8) 100%);
|
|
}
|
|
|
|
.status-text-lg {
|
|
font-size: 14px;
|
|
font-weight: bold;
|
|
margin-top: 5px;
|
|
text-shadow: 1px 1px 2px #000;
|
|
}
|
|
.countdown-text {
|
|
font-size: 16px;
|
|
color: #fff;
|
|
margin-top: 2px;
|
|
font-family: monospace;
|
|
font-weight: bold;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style> |