## Changes ### Database Integration - Add MySQL 8.0 service to docker-compose.yml - Configure DATABASE_URL environment variable for server - Add health check for MySQL service - Create mysql_data volume for data persistence ### Dockerfile Improvements - Generate Prisma Client in builder stage - Copy Prisma Client from correct pnpm workspace location - Ensure Prisma Client is available in production container ### Client-Mobile Fixes - Remove deprecated StampDock.vue component - Fix voting store API usage in VotingPage.vue - Add type assertion for userTickets in connection.ts - Add remark property to AwardConfig interface in voting.ts ## Testing - All containers start successfully - Database connection established - Redis connection working - 94 participants restored from Redis - Vote data synced (20 votes) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
101 lines
2.6 KiB
Vue
101 lines
2.6 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue';
|
|
import { useVotingStore } from '../stores/voting';
|
|
import { useConnectionStore } from '../stores/connection';
|
|
import VotingDock from '../components/VotingDock.vue';
|
|
import ProgramCard from '../components/ProgramCard.vue';
|
|
import ConnectionStatus from '../components/ConnectionStatus.vue';
|
|
|
|
const votingStore = useVotingStore();
|
|
const connectionStore = useConnectionStore();
|
|
|
|
// Mock programs data (replace with API call)
|
|
const programs = ref([
|
|
{ id: 'p1', name: '龙腾四海', team: '市场部' },
|
|
{ id: 'p2', name: '金马奔腾', team: '技术部' },
|
|
{ id: 'p3', name: '春风得意', team: '人力资源部' },
|
|
{ id: 'p4', name: '鸿运当头', team: '财务部' },
|
|
{ id: 'p5', name: '马到成功', team: '运营部' },
|
|
{ id: 'p6', name: '一马当先', team: '产品部' },
|
|
{ id: 'p7', name: '万马奔腾', team: '设计部' },
|
|
{ id: 'p8', name: '龙马精神', team: '销售部' },
|
|
]);
|
|
|
|
const isLoading = ref(false);
|
|
|
|
onMounted(async () => {
|
|
// Connect if not connected
|
|
if (!connectionStore.isConnected) {
|
|
connectionStore.connect();
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="voting-page safe-area-top">
|
|
<!-- Header -->
|
|
<header class="page-header">
|
|
<h1 class="page-title">节目投票</h1>
|
|
<p class="page-subtitle">
|
|
已使用 {{ votingStore.usedTicketCount }}/{{ votingStore.totalTicketCount }} 枚印章
|
|
</p>
|
|
<ConnectionStatus />
|
|
</header>
|
|
|
|
<!-- Program List (Postcards fade in with stagger) -->
|
|
<main class="program-list">
|
|
<ProgramCard
|
|
v-for="(program, index) in programs"
|
|
:key="program.id"
|
|
:program-id="program.id"
|
|
:program-name="program.name"
|
|
:team-name="program.team"
|
|
:index="index"
|
|
/>
|
|
</main>
|
|
|
|
<!-- Stamp Dock -->
|
|
<VotingDock />
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
@use '../assets/styles/variables.scss' as *;
|
|
|
|
.voting-page {
|
|
min-height: 100vh;
|
|
background: $color-bg-primary;
|
|
padding-bottom: 120px; // Space for dock
|
|
}
|
|
|
|
.page-header {
|
|
background: $color-surface-glass;
|
|
backdrop-filter: $backdrop-blur;
|
|
-webkit-backdrop-filter: $backdrop-blur;
|
|
padding: $spacing-lg;
|
|
padding-top: calc(env(safe-area-inset-top) + #{$spacing-lg});
|
|
color: $color-text-inverse;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: $z-index-sticky;
|
|
}
|
|
|
|
.page-title {
|
|
font-size: $font-size-2xl;
|
|
font-weight: bold;
|
|
margin-bottom: $spacing-xs;
|
|
}
|
|
|
|
.page-subtitle {
|
|
font-size: $font-size-sm;
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.program-list {
|
|
padding: $spacing-md;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: $spacing-sm;
|
|
}
|
|
</style>
|