## 主要改动 ### 1. 修复二维码地址硬编码问题 - 将 MainDisplay.vue 中硬编码的移动端 URL 改为环境变量配置 - 添加 VITE_MOBILE_URL 环境变量支持 - 支持通过 .env 文件动态配置移动端地址 ### 2. 修复音频文件路径问题 - 修正 display.ts 中音频文件路径,添加 /screen 前缀 - 修复 BGM、抽奖音效、胜利音效的加载路径 ### 3. 修复 Docker 构建问题 - 添加中国 npm 镜像配置,解决构建超时问题 - 修复缺失的 tsconfig.base.json 文件拷贝 - 修复 Redis 环境变量配置(REDIS_HOST/REDIS_PORT) - 添加 Lua 脚本文件拷贝到生产容器 ### 4. 修复前端路由和资源加载 - 添加 Vite base path 配置 (/screen/) - 修复 Vue Router base path 配置 - 修正 Caddyfile 路由顺序,确保 /screen 路径优先匹配 ### 5. 修复 TypeScript 编译错误 - LuckyDrawView.vue: 添加 round 属性类型定义 - ProgramCard.vue: 添加非空断言处理 ### 6. 修复 SCSS 变量问题 - 替换未定义的 SCSS 变量为硬编码颜色值 - 修复 VoteView、ConnectionStatus、HomeView、ScanLoginView 中的样式问题 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
296 lines
6.4 KiB
Vue
296 lines
6.4 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue';
|
|
import { useRouter } from 'vue-router';
|
|
import { useConnectionStore } from '../stores/connection';
|
|
import { showToast } from 'vant';
|
|
|
|
const router = useRouter();
|
|
const connectionStore = useConnectionStore();
|
|
|
|
const userName = ref('');
|
|
const userDept = ref('技术部');
|
|
const isLoading = ref(false);
|
|
|
|
// Check if already logged in
|
|
onMounted(() => {
|
|
if (connectionStore.userId && connectionStore.userName && connectionStore.userName !== '访客') {
|
|
// Already logged in, redirect to vote page
|
|
router.replace('/vote');
|
|
}
|
|
});
|
|
|
|
async function handleEnter() {
|
|
if (!userName.value.trim()) {
|
|
showToast('请输入您的姓名');
|
|
return;
|
|
}
|
|
|
|
isLoading.value = true;
|
|
|
|
// Generate a simple user ID (in production, this would come from auth)
|
|
const odrawId = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
|
|
connectionStore.setUser(odrawId, userName.value.trim(), userDept.value);
|
|
|
|
// Wait for connection
|
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
|
|
isLoading.value = false;
|
|
router.push('/vote');
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="home-view safe-area-top safe-area-bottom">
|
|
<!-- Header decoration -->
|
|
<div class="header-decoration">
|
|
<div class="lantern left"></div>
|
|
<div class="lantern right"></div>
|
|
</div>
|
|
|
|
<!-- Main content -->
|
|
<div class="content">
|
|
<div class="logo-section">
|
|
<div class="year-badge">
|
|
<span class="year">2026</span>
|
|
<span class="zodiac">马年</span>
|
|
</div>
|
|
<h1 class="title gold-text">年会互动系统</h1>
|
|
<p class="subtitle">投票 · 抽奖 · 互动</p>
|
|
</div>
|
|
|
|
<div class="form-section">
|
|
<div class="input-wrapper guochao-border">
|
|
<van-field
|
|
v-model="userName"
|
|
placeholder="请输入您的姓名"
|
|
:border="false"
|
|
clearable
|
|
maxlength="20"
|
|
@keyup.enter="handleEnter"
|
|
/>
|
|
</div>
|
|
|
|
<div class="input-wrapper guochao-border">
|
|
<van-field
|
|
v-model="userDept"
|
|
placeholder="请输入您的部门"
|
|
:border="false"
|
|
clearable
|
|
maxlength="20"
|
|
@keyup.enter="handleEnter"
|
|
/>
|
|
</div>
|
|
|
|
<van-button
|
|
class="enter-btn"
|
|
type="primary"
|
|
block
|
|
round
|
|
:loading="isLoading"
|
|
loading-text="进入中..."
|
|
@click="handleEnter"
|
|
>
|
|
进入年会
|
|
</van-button>
|
|
</div>
|
|
|
|
<!-- Features -->
|
|
<div class="features">
|
|
<div class="feature-item">
|
|
<van-icon name="like-o" size="24" color="#c41230" />
|
|
<span>投票评选</span>
|
|
</div>
|
|
<div class="feature-item">
|
|
<van-icon name="gift-o" size="24" color="#d4a84b" />
|
|
<span>幸运抽奖</span>
|
|
</div>
|
|
<div class="feature-item">
|
|
<van-icon name="chart-trending-o" size="24" color="#c41230" />
|
|
<span>实时结果</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="footer">
|
|
<p>© 2026 公司年会</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
@use '../assets/styles/variables.scss' as *;
|
|
|
|
.home-view {
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: linear-gradient(180deg, #fff5f5 0%, #fef8f0 50%, #ffffff 100%);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.header-decoration {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 120px;
|
|
pointer-events: none;
|
|
|
|
.lantern {
|
|
position: absolute;
|
|
top: -20px;
|
|
width: 40px;
|
|
height: 60px;
|
|
background: linear-gradient(180deg, $color-primary 0%, $color-primary-dark 100%);
|
|
border-radius: 50% 50% 45% 45%;
|
|
box-shadow: 0 4px 12px rgba($color-primary, 0.3);
|
|
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: -8px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
width: 16px;
|
|
height: 12px;
|
|
background: $color-gold;
|
|
border-radius: 2px 2px 0 0;
|
|
}
|
|
|
|
&::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: -15px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
width: 2px;
|
|
height: 15px;
|
|
background: $color-gold;
|
|
}
|
|
|
|
&.left {
|
|
left: 20px;
|
|
animation: swing 3s ease-in-out infinite;
|
|
}
|
|
|
|
&.right {
|
|
right: 20px;
|
|
animation: swing 3s ease-in-out infinite 0.5s;
|
|
}
|
|
}
|
|
}
|
|
|
|
@keyframes swing {
|
|
0%,
|
|
100% {
|
|
transform: rotate(-5deg);
|
|
}
|
|
50% {
|
|
transform: rotate(5deg);
|
|
}
|
|
}
|
|
|
|
.content {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
padding: $spacing-xl $spacing-lg;
|
|
padding-top: 100px;
|
|
}
|
|
|
|
.logo-section {
|
|
text-align: center;
|
|
margin-bottom: $spacing-xl;
|
|
|
|
.year-badge {
|
|
display: inline-flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
background: linear-gradient(135deg, $color-primary 0%, $color-primary-dark 100%);
|
|
color: $color-text-inverse;
|
|
padding: $spacing-sm $spacing-lg;
|
|
border-radius: $radius-lg;
|
|
margin-bottom: $spacing-md;
|
|
box-shadow: $shadow-md;
|
|
|
|
.year {
|
|
font-size: $font-size-2xl;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.zodiac {
|
|
font-size: $font-size-sm;
|
|
opacity: 0.9;
|
|
}
|
|
}
|
|
|
|
.title {
|
|
font-size: 32px;
|
|
font-weight: bold;
|
|
margin-bottom: $spacing-sm;
|
|
}
|
|
|
|
.subtitle {
|
|
color: $color-text-secondary;
|
|
font-size: $font-size-md;
|
|
}
|
|
}
|
|
|
|
.form-section {
|
|
margin-bottom: $spacing-xl;
|
|
|
|
.input-wrapper {
|
|
background: $color-bg-card;
|
|
margin-bottom: $spacing-md;
|
|
padding: $spacing-xs;
|
|
|
|
:deep(.van-field) {
|
|
background: transparent;
|
|
|
|
.van-field__control {
|
|
text-align: center;
|
|
font-size: $font-size-lg;
|
|
}
|
|
}
|
|
}
|
|
|
|
.enter-btn {
|
|
background: linear-gradient(135deg, $color-primary-light 0%, $color-primary 100%);
|
|
border: none;
|
|
height: 48px;
|
|
font-size: $font-size-lg;
|
|
font-weight: 500;
|
|
box-shadow: $shadow-md;
|
|
}
|
|
}
|
|
|
|
.features {
|
|
display: flex;
|
|
justify-content: space-around;
|
|
padding: $spacing-md 0;
|
|
|
|
.feature-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: $spacing-xs;
|
|
|
|
span {
|
|
font-size: $font-size-sm;
|
|
color: $color-text-secondary;
|
|
}
|
|
}
|
|
}
|
|
|
|
.footer {
|
|
text-align: center;
|
|
padding: $spacing-md;
|
|
color: #999;
|
|
font-size: $font-size-xs;
|
|
}
|
|
</style>
|