feat: add Admin Control Panel, voting status check, and router security
Admin Control Panel: - Add full AdminControl.vue with 3 sections (Voting, Lottery, Global) - Add AdminLogin.vue with access code gate (20268888) - Add admin.ts store with state persistence - Add admin.types.ts with state machine types - Add router guards for /admin/director-console Voting System Fixes: - Add voting status check before accepting votes (VOTING_CLOSED error) - Fix client to display server error messages - Fix button disabled logic to prevent ambiguity in paused state - Auto-generate userId on connect to fix UNAUTHORIZED error Big Screen Enhancements: - Add LiveVotingView.vue with particle system - Add LotteryMachine.ts with 3-stage animation (Galaxy/Storm/Reveal) - Add useSocketClient.ts composable - Fix MainDisplay.vue SCSS syntax error - Add admin state sync listener in display store Server Updates: - Add admin.service.ts for state management - Add isVotingOpen() and getVotingStatus() methods - Add admin socket event handlers Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
100
packages/client-mobile/src/views/VotingPage.vue
Normal file
100
packages/client-mobile/src/views/VotingPage.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<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.usedTickets.length }}/7 枚印章
|
||||
</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>
|
||||
Reference in New Issue
Block a user