重构 Play.vue 组件,拆分为多个子组件,提升代码可维护性和可读性。
主要变更: - 将 Play.vue 拆分为 LoadingOverlay、SelectRegion、ScanPage、GamePage、RefreshWaitPage、ErrorPage 等组件 - 新增 composables 目录,分离业务逻辑(usePlayState、useTimers、useQrCode) - 优化代码结构,提升开发效率和维护性 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
83
src/components/play/ErrorPage.vue
Normal file
83
src/components/play/ErrorPage.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div class="error-page">
|
||||
<div class="error-container">
|
||||
<div class="error-icon">❌</div>
|
||||
<h2 class="error-title">{{ errorTitle }}</h2>
|
||||
<p class="error-message">{{ errorMessage }}</p>
|
||||
<button @click="$emit('retry')" class="retry-btn">重新尝试</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ErrorPage',
|
||||
props: {
|
||||
errorTitle: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
errorMessage: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['retry']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.error-page {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.error-container {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
text-align: center;
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 12px 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
margin: 0 0 24px 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 32px;
|
||||
border-radius: 25px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.retry-btn:hover {
|
||||
background: #5a6fd8;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
</style>
|
||||
238
src/components/play/GamePage.vue
Normal file
238
src/components/play/GamePage.vue
Normal file
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<div class="game-page">
|
||||
<!-- 顶部标签页 -->
|
||||
<div class="tab-header">
|
||||
<div class="tab-item active">代练大区</div>
|
||||
<div class="tab-item status-tab">状态</div>
|
||||
<div class="tab-item target-tab">目标点数</div>
|
||||
</div>
|
||||
<div class="tab-header">{{ region }}
|
||||
<div class="tab-item active" v-if="region === 'Q'">QQ</div>
|
||||
<div class="tab-item active" v-if="region === 'V'">微信</div>
|
||||
<div class="tab-item status-tab">{{ displayStatus }}</div>
|
||||
<div class="tab-item target-tab">{{ completedPoints || 0 }}/{{ totalPoints || 0 }}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="tab-content">
|
||||
<!-- 状态提示 -->
|
||||
<div class="status-message" :class="statusMessageClass">
|
||||
{{ statusMessage }}
|
||||
</div>
|
||||
|
||||
<!-- 游戏截图展示区域 -->
|
||||
<div class="image-gallery">
|
||||
<div class="image-item" v-if="assets?.homepageUrl">
|
||||
<img :src="assets.homepageUrl" alt="首次主页" class="game-image" />
|
||||
<div class="image-label">首次主页</div>
|
||||
</div>
|
||||
<div class="image-item" v-if="assets?.firstRewardUrl">
|
||||
<img :src="assets.firstRewardUrl" alt="首次赏金" class="game-image" style="transform: rotate(-90deg);" />
|
||||
<div class="image-label">首次赏金</div>
|
||||
</div>
|
||||
<div class="image-item" v-if="assets?.midRewardUrl">
|
||||
<img :src="assets.midRewardUrl" alt="中途赏金" class="game-image" />
|
||||
<div class="image-label">中途赏金</div>
|
||||
</div>
|
||||
<div class="image-item" v-if="assets?.endRewardUrl">
|
||||
<img :src="assets.endRewardUrl" alt="结束赏金" class="game-image" />
|
||||
<div class="image-label">结束赏金</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部状态显示 -->
|
||||
<div class="bottom-status">
|
||||
ss{{ currentPoints || 0 }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'GamePage',
|
||||
props: {
|
||||
region: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
displayStatus: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
completedPoints: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
totalPoints: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
statusMessage: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
statusMessageClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
assets: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
currentPoints: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.game-page {
|
||||
flex: 1;
|
||||
background: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.tab-header {
|
||||
display: flex;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
padding: 12px 16px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
border-bottom: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
border-bottom-color: #4CAF50;
|
||||
}
|
||||
|
||||
.tab-item.status-tab {
|
||||
background: #2196F3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tab-item.target-tab {
|
||||
color: #f44336;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.status-message {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
padding: 12px 16px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
border-left: 4px solid #f44336;
|
||||
}
|
||||
|
||||
.status-message-completed {
|
||||
background: #d4edda !important;
|
||||
color: #155724 !important;
|
||||
border-left: 4px solid #28a745 !important;
|
||||
}
|
||||
|
||||
.image-gallery {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.image-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: white;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.image-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.game-image {
|
||||
width: 100%;
|
||||
max-width: 150px;
|
||||
height: auto;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.image-label {
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bottom-status {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
background: #f8f9fa;
|
||||
border-top: 1px solid #e9ecef;
|
||||
margin: 0 -16px -16px -16px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tab-item {
|
||||
padding: 10px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.status-message {
|
||||
font-size: 13px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.image-gallery {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.image-item {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.game-image {
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
.image-label {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
43
src/components/play/LoadingOverlay.vue
Normal file
43
src/components/play/LoadingOverlay.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div class="loading-overlay">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'LoadingOverlay'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #667eea;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
137
src/components/play/RefreshWaitPage.vue
Normal file
137
src/components/play/RefreshWaitPage.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div class="refresh-wait-page">
|
||||
<div class="page-header">
|
||||
<h1 class="title">请选择您的账号类型</h1>
|
||||
</div>
|
||||
|
||||
<div class="refresh-container">
|
||||
<div class="warning-icon">⚠️</div>
|
||||
<p class="refresh-text">页面需要刷新</p>
|
||||
<p class="refresh-desc">请等待后重新选择区域</p>
|
||||
<button
|
||||
@click="$emit('refresh')"
|
||||
class="refresh-btn"
|
||||
:disabled="refreshCooldown > 0"
|
||||
>
|
||||
{{ refreshCooldown > 0 ? `请等待 ${refreshCooldown}s` : '确定' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="notice-text">
|
||||
<p>代理技术代练平台操作中</p>
|
||||
<p>绝对安全保障!请耐心等待</p>
|
||||
<p>温馨提示: 请选择正确区域</p>
|
||||
<p v-if="mecmachineId" class="machine-id-info">机器ID: {{ mecmachineId }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'RefreshWaitPage',
|
||||
props: {
|
||||
refreshCooldown: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
mecmachineId: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
emits: ['refresh']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.refresh-wait-page {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
text-align: center;
|
||||
padding: 40px 20px 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 16px 0;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.refresh-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.warning-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.refresh-text {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.refresh-desc {
|
||||
font-size: 16px;
|
||||
margin: 0 0 24px 0;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
background: white;
|
||||
color: #667eea;
|
||||
border: none;
|
||||
padding: 12px 32px;
|
||||
border-radius: 25px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.refresh-btn:hover:not(:disabled) {
|
||||
background: #f0f0f0;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.refresh-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.notice-text {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.notice-text p {
|
||||
margin: 4px 0;
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.machine-id-info {
|
||||
color: #4CAF50 !important;
|
||||
font-weight: 600 !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
</style>
|
||||
295
src/components/play/ScanPage.vue
Normal file
295
src/components/play/ScanPage.vue
Normal file
@@ -0,0 +1,295 @@
|
||||
<template>
|
||||
<div class="scan-page">
|
||||
<div class="page-header">
|
||||
<h1 class="title">请选择您的账号类型</h1>
|
||||
<div class="selected-region">{{ regionName }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 等待二维码 -->
|
||||
<div v-if="isWaitingQr" class="qr-waiting">
|
||||
<div class="loading-spinner"></div>
|
||||
<p class="waiting-text">正在准备二维码...</p>
|
||||
<p class="waiting-desc">预计等待 {{ qrDelaySeconds }} 秒</p>
|
||||
<p v-if="qrRetryCount > 0" class="retry-info">重试中... ({{ qrRetryCount }}/{{ maxQrRetries }})</p>
|
||||
</div>
|
||||
|
||||
<!-- 二维码区域 -->
|
||||
<div v-else-if="qrInfo && countdown > 0" class="qr-container">
|
||||
<div class="qr-wrapper">
|
||||
<img :src="qrInfo.url" class="qr-code" alt="扫码登录" @error="$emit('qrImageError', $event)" />
|
||||
</div>
|
||||
<div class="countdown-timer">{{ formatTime(countdown) }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 二维码获取失败 -->
|
||||
<div v-else-if="qrError" class="qr-error">
|
||||
<div class="error-icon">❌</div>
|
||||
<p class="error-text">二维码获取失败</p>
|
||||
<p class="error-desc">{{ qrError }}</p>
|
||||
<button
|
||||
@click="$emit('retryQrCode')"
|
||||
class="retry-btn"
|
||||
:disabled="submitting"
|
||||
>
|
||||
重新获取
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 二维码过期 -->
|
||||
<div v-else class="qr-expired">
|
||||
<div class="warning-icon">⚠️</div>
|
||||
<p class="expired-text">扫码超时{{ qrInfo?.url }}</p>
|
||||
<p class="expired-desc">请手动刷新页面重新获取二维码</p>
|
||||
<img :src="qrInfo?.url" class="qr-code" alt="扫码登录" />
|
||||
<button
|
||||
@click="$emit('pageRefresh')"
|
||||
class="refresh-btn"
|
||||
>
|
||||
刷新页面
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="notice-text">
|
||||
<p>代理技术代练平台操作中</p>
|
||||
<p>绝对安全保障!请耐心等待</p>
|
||||
<p>温馨提示: 请选择正确区域</p>
|
||||
<p v-if="mecmachineId" class="machine-id-info">机器ID: {{ mecmachineId }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ScanPage',
|
||||
props: {
|
||||
regionName: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
isWaitingQr: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
qrInfo: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
countdown: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
qrDelaySeconds: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
qrRetryCount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
maxQrRetries: {
|
||||
type: Number,
|
||||
default: 3
|
||||
},
|
||||
qrError: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
submitting: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
mecmachineId: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
emits: ['qrImageError', 'retryQrCode', 'pageRefresh'],
|
||||
methods: {
|
||||
formatTime(seconds) {
|
||||
const mins = Math.floor(seconds / 60)
|
||||
const secs = seconds % 60
|
||||
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scan-page {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
text-align: center;
|
||||
padding: 40px 20px 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 16px 0;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.selected-region {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.qr-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.qr-waiting {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.waiting-text {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 16px 0 8px 0;
|
||||
}
|
||||
|
||||
.waiting-desc {
|
||||
font-size: 16px;
|
||||
margin: 0;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.retry-info {
|
||||
font-size: 14px;
|
||||
margin: 8px 0 0 0;
|
||||
opacity: 0.9;
|
||||
color: #ffd700;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.qr-wrapper {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.qr-code {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.countdown-timer {
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.qr-expired, .qr-error {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.warning-icon, .error-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.expired-text, .error-text {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.expired-desc, .error-desc {
|
||||
font-size: 16px;
|
||||
margin: 0 0 24px 0;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.refresh-btn, .retry-btn {
|
||||
background: white;
|
||||
color: #667eea;
|
||||
border: none;
|
||||
padding: 12px 32px;
|
||||
border-radius: 25px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.refresh-btn:hover:not(:disabled) {
|
||||
background: #f0f0f0;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.refresh-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #667eea;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.notice-text {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.notice-text p {
|
||||
margin: 4px 0;
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.machine-id-info {
|
||||
color: #4CAF50 !important;
|
||||
font-weight: 600 !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.qr-code {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
215
src/components/play/SelectRegion.vue
Normal file
215
src/components/play/SelectRegion.vue
Normal file
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<div class="select-region-page">
|
||||
<div class="page-header">
|
||||
<h1 class="title">请选择您的账号类型</h1>
|
||||
</div>
|
||||
|
||||
<div class="region-buttons">
|
||||
<button
|
||||
@click="$emit('selectRegion', 'Q')"
|
||||
class="region-btn qq-btn"
|
||||
:disabled="submitting"
|
||||
:class="{ 'loading': submitting }"
|
||||
>
|
||||
<div v-if="submitting" class="loading-spinner small"></div>
|
||||
<div v-else class="btn-icon">Q</div>
|
||||
<span class="btn-text">{{ submitting ? '正在连接...' : 'QQ区' }}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="$emit('selectRegion', 'V')"
|
||||
class="region-btn wx-btn"
|
||||
:disabled="submitting"
|
||||
:class="{ 'loading': submitting }"
|
||||
>
|
||||
<div v-if="submitting" class="loading-spinner small"></div>
|
||||
<div v-else class="btn-icon">V</div>
|
||||
<span class="btn-text">{{ submitting ? '正在连接...' : '微信区' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="notice-text">
|
||||
<p>代理技术代练平台操作中</p>
|
||||
<p>绝对安全保障!请耐心等待</p>
|
||||
<p>温馨提示: 请选择正确区域</p>
|
||||
<p v-if="mecmachineId" class="machine-id-info">机器ID: {{ mecmachineId }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SelectRegion',
|
||||
props: {
|
||||
submitting: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
mecmachineId: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
emits: ['selectRegion']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.select-region-page {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
text-align: center;
|
||||
padding: 40px 20px 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 16px 0;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.region-buttons {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 40px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.region-btn {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 20px;
|
||||
border: none;
|
||||
background: white;
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.region-btn:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 12px 35px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.region-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.region-btn.loading {
|
||||
opacity: 0.8;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.region-btn.loading:hover {
|
||||
transform: none;
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.qq-btn .btn-icon {
|
||||
background: #12B7F5;
|
||||
}
|
||||
|
||||
.wx-btn .btn-icon {
|
||||
background: #07C160;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #667eea;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.loading-spinner.small {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid #f3f3f3;
|
||||
border-top: 2px solid #667eea;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.notice-text {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.notice-text p {
|
||||
margin: 4px 0;
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.machine-id-info {
|
||||
color: #4CAF50 !important;
|
||||
font-weight: 600 !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.region-buttons {
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.region-btn {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user