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:
@@ -1,29 +1,96 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import { createRouter, createWebHistory, type RouteLocationNormalized } from 'vue-router';
|
||||
|
||||
// Admin auth constants
|
||||
const ADMIN_TOKEN_KEY = 'gala_admin_token';
|
||||
const ADMIN_ACCESS_CODE = '20268888';
|
||||
|
||||
// Auth guard for admin routes
|
||||
function requireAdminAuth(to: RouteLocationNormalized) {
|
||||
const token = localStorage.getItem(ADMIN_TOKEN_KEY);
|
||||
if (!token || token !== generateToken(ADMIN_ACCESS_CODE)) {
|
||||
return { path: '/admin/login', query: { redirect: to.fullPath } };
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Simple token generator (not cryptographically secure, but sufficient for internal event)
|
||||
function generateToken(code: string): string {
|
||||
return btoa(`gala2026:${code}:${code.split('').reverse().join('')}`);
|
||||
}
|
||||
|
||||
// Export for use in login component
|
||||
export { ADMIN_TOKEN_KEY, ADMIN_ACCESS_CODE, generateToken };
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
// ============================================
|
||||
// Big Screen Display Routes (LED PC)
|
||||
// ============================================
|
||||
{
|
||||
path: '/',
|
||||
name: 'main',
|
||||
name: 'screen-main',
|
||||
component: () => import('../views/MainDisplay.vue'),
|
||||
meta: { title: '年会大屏 - 主页' },
|
||||
},
|
||||
{
|
||||
path: '/draw',
|
||||
name: 'draw',
|
||||
path: '/screen/voting',
|
||||
name: 'screen-voting',
|
||||
component: () => import('../views/LiveVotingView.vue'),
|
||||
meta: { title: '年会大屏 - 实时投票' },
|
||||
},
|
||||
{
|
||||
path: '/screen/draw',
|
||||
name: 'screen-draw',
|
||||
component: () => import('../views/LuckyDrawView.vue'),
|
||||
meta: { title: '年会大屏 - 幸运抽奖' },
|
||||
},
|
||||
{
|
||||
path: '/results',
|
||||
name: 'results',
|
||||
path: '/screen/results',
|
||||
name: 'screen-results',
|
||||
component: () => import('../views/VoteResultsView.vue'),
|
||||
meta: { title: '年会大屏 - 投票结果' },
|
||||
},
|
||||
|
||||
// Legacy routes (redirect to new paths)
|
||||
{ path: '/voting', redirect: '/screen/voting' },
|
||||
{ path: '/draw', redirect: '/screen/draw' },
|
||||
{ path: '/results', redirect: '/screen/results' },
|
||||
|
||||
// ============================================
|
||||
// Admin Routes (Director Console)
|
||||
// ============================================
|
||||
{
|
||||
path: '/admin/login',
|
||||
name: 'admin-login',
|
||||
component: () => import('../views/AdminLogin.vue'),
|
||||
meta: { title: '管理员登录' },
|
||||
},
|
||||
{
|
||||
path: '/admin',
|
||||
name: 'admin',
|
||||
path: '/admin/director-console',
|
||||
name: 'admin-console',
|
||||
component: () => import('../views/AdminControl.vue'),
|
||||
meta: { title: '导演控制台' },
|
||||
beforeEnter: requireAdminAuth,
|
||||
},
|
||||
|
||||
// Legacy admin route (redirect)
|
||||
{ path: '/admin', redirect: '/admin/director-console' },
|
||||
|
||||
// ============================================
|
||||
// 404 Catch-all
|
||||
// ============================================
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'not-found',
|
||||
redirect: '/',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Update document title on navigation
|
||||
router.afterEach((to) => {
|
||||
document.title = (to.meta.title as string) || '年会互动系统';
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
Reference in New Issue
Block a user