feat: initialize Annual Gala Interactive System monorepo
- Set up pnpm workspace with 4 packages: shared, server, client-mobile, client-screen - Implement Redis atomic voting with Lua scripts (HINCRBY + distributed lock) - Add optimistic UI with IndexedDB queue for offline resilience - Configure Socket.io with auto-reconnection (infinite retries) - Separate mobile (Vant) and big screen (Pixi.js) dependencies Tech stack: - Frontend Mobile: Vue 3 + Vant + Socket.io-client - Frontend Screen: Vue 3 + Pixi.js + GSAP - Backend: Express + Socket.io + Redis + Prisma/MySQL Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
201
packages/client-screen/src/views/VoteResultsView.vue
Normal file
201
packages/client-screen/src/views/VoteResultsView.vue
Normal file
@@ -0,0 +1,201 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
function goBack() {
|
||||
router.push('/');
|
||||
}
|
||||
|
||||
// Mock vote results
|
||||
const categories = [
|
||||
{
|
||||
name: '最佳员工',
|
||||
results: [
|
||||
{ name: '张三', votes: 45, percentage: 30 },
|
||||
{ name: '李四', votes: 38, percentage: 25 },
|
||||
{ name: '王五', votes: 32, percentage: 21 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '最佳团队',
|
||||
results: [
|
||||
{ name: '技术一组', votes: 52, percentage: 35 },
|
||||
{ name: '产品组', votes: 41, percentage: 27 },
|
||||
{ name: '设计组', votes: 35, percentage: 23 },
|
||||
],
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="vote-results-view">
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<button class="back-btn" @click="goBack">← 返回</button>
|
||||
<h1 class="title gold-text">投票结果</h1>
|
||||
<div class="placeholder"></div>
|
||||
</header>
|
||||
|
||||
<!-- Results grid -->
|
||||
<main class="results-grid">
|
||||
<div v-for="category in categories" :key="category.name" class="category-card">
|
||||
<h2 class="category-name">{{ category.name }}</h2>
|
||||
<div class="results-list">
|
||||
<div
|
||||
v-for="(result, index) in category.results"
|
||||
:key="result.name"
|
||||
class="result-item"
|
||||
:class="{ winner: index === 0 }"
|
||||
>
|
||||
<span class="rank">{{ index + 1 }}</span>
|
||||
<span class="name">{{ result.name }}</span>
|
||||
<div class="bar-container">
|
||||
<div class="bar" :style="{ width: result.percentage + '%' }"></div>
|
||||
</div>
|
||||
<span class="votes">{{ result.votes }}票</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../assets/styles/variables.scss';
|
||||
|
||||
.vote-results-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: $color-bg-gradient;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 30px 50px;
|
||||
|
||||
.back-btn {
|
||||
background: none;
|
||||
border: 1px solid $color-gold;
|
||||
color: $color-gold;
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
transition: all $transition-fast;
|
||||
|
||||
&:hover {
|
||||
background: rgba($color-gold, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 48px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.results-grid {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 40px;
|
||||
padding: 40px 50px;
|
||||
}
|
||||
|
||||
.category-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba($color-gold, 0.3);
|
||||
border-radius: 16px;
|
||||
padding: 30px;
|
||||
|
||||
.category-name {
|
||||
font-size: 28px;
|
||||
color: $color-gold;
|
||||
margin-bottom: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.results-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.result-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-radius: 8px;
|
||||
|
||||
&.winner {
|
||||
background: rgba($color-gold, 0.1);
|
||||
border: 1px solid $color-gold;
|
||||
|
||||
.rank {
|
||||
background: $color-gold;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.name {
|
||||
color: $color-gold;
|
||||
}
|
||||
|
||||
.bar {
|
||||
background: linear-gradient(90deg, $color-gold-dark, $color-gold);
|
||||
}
|
||||
}
|
||||
|
||||
.rank {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 50%;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.name {
|
||||
width: 120px;
|
||||
font-size: 20px;
|
||||
color: $color-text-light;
|
||||
}
|
||||
|
||||
.bar-container {
|
||||
flex: 1;
|
||||
height: 24px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
|
||||
.bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, $color-primary-dark, $color-primary);
|
||||
border-radius: 12px;
|
||||
transition: width 1s ease;
|
||||
}
|
||||
}
|
||||
|
||||
.votes {
|
||||
width: 60px;
|
||||
text-align: right;
|
||||
font-size: 18px;
|
||||
color: $color-text-muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user