fix: production deployment improvements and configuration fixes
## 主要改动 ### 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>
This commit is contained in:
@@ -1,17 +1,10 @@
|
|||||||
{
|
{
|
||||||
email your-email@example.com
|
email let5sne@gmail.com
|
||||||
}
|
}
|
||||||
|
|
||||||
your-domain.com {
|
2026.cptp.let5see.xyz {
|
||||||
# Mobile client (default)
|
# Screen client (must be before mobile to match first)
|
||||||
handle {
|
handle /screen* {
|
||||||
root * /srv/mobile
|
|
||||||
try_files {path} /index.html
|
|
||||||
file_server
|
|
||||||
}
|
|
||||||
|
|
||||||
# Screen client
|
|
||||||
handle /screen/* {
|
|
||||||
root * /srv/screen
|
root * /srv/screen
|
||||||
uri strip_prefix /screen
|
uri strip_prefix /screen
|
||||||
try_files {path} /index.html
|
try_files {path} /index.html
|
||||||
@@ -28,6 +21,13 @@ your-domain.com {
|
|||||||
reverse_proxy server:3000
|
reverse_proxy server:3000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Mobile client (default, matches everything else)
|
||||||
|
handle {
|
||||||
|
root * /srv/mobile
|
||||||
|
try_files {path} /index.html
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
|
||||||
# Gzip compression
|
# Gzip compression
|
||||||
encode gzip
|
encode gzip
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,17 @@ FROM node:20-alpine AS builder
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN npm install -g pnpm
|
# Build arguments for environment variables
|
||||||
|
ARG VITE_SOCKET_URL
|
||||||
|
ARG VITE_API_URL
|
||||||
|
ARG VITE_MOBILE_URL
|
||||||
|
|
||||||
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
|
# Install pnpm and configure registry
|
||||||
|
RUN npm config set registry https://registry.npmmirror.com && \
|
||||||
|
npm install -g pnpm && \
|
||||||
|
pnpm config set registry https://registry.npmmirror.com
|
||||||
|
|
||||||
|
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml tsconfig.base.json ./
|
||||||
COPY packages/shared ./packages/shared
|
COPY packages/shared ./packages/shared
|
||||||
COPY packages/client-screen ./packages/client-screen
|
COPY packages/client-screen ./packages/client-screen
|
||||||
COPY packages/client-mobile ./packages/client-mobile
|
COPY packages/client-mobile ./packages/client-mobile
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ FROM node:20-alpine AS builder
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install pnpm
|
# Install pnpm and configure registry
|
||||||
RUN npm install -g pnpm
|
RUN npm config set registry https://registry.npmmirror.com && \
|
||||||
|
npm install -g pnpm && \
|
||||||
|
pnpm config set registry https://registry.npmmirror.com
|
||||||
|
|
||||||
# Copy workspace files
|
# Copy workspace files
|
||||||
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
|
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml tsconfig.base.json ./
|
||||||
COPY packages/shared ./packages/shared
|
COPY packages/shared ./packages/shared
|
||||||
COPY packages/client-screen ./packages/client-screen
|
COPY packages/client-screen ./packages/client-screen
|
||||||
COPY packages/client-mobile ./packages/client-mobile
|
COPY packages/client-mobile ./packages/client-mobile
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- PORT=3000
|
- PORT=3000
|
||||||
- REDIS_URL=redis://redis:6379
|
- REDIS_HOST=redis
|
||||||
|
- REDIS_PORT=6379
|
||||||
- CORS_ORIGINS=${CORS_ORIGINS:-*}
|
- CORS_ORIGINS=${CORS_ORIGINS:-*}
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
@@ -30,6 +31,10 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: deploy/Dockerfile.caddy
|
dockerfile: deploy/Dockerfile.caddy
|
||||||
|
args:
|
||||||
|
- VITE_SOCKET_URL=${VITE_SOCKET_URL:-}
|
||||||
|
- VITE_API_URL=${VITE_API_URL:-}
|
||||||
|
- VITE_MOBILE_URL=${VITE_MOBILE_URL:-}
|
||||||
container_name: gala-caddy
|
container_name: gala-caddy
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ const statusText = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.latency {
|
.latency {
|
||||||
color: $color-text-muted;
|
color: #999;
|
||||||
font-size: $font-size-xs;
|
font-size: $font-size-xs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ function delay(ms: number): Promise<void> {
|
|||||||
<Postmark
|
<Postmark
|
||||||
v-if="hasStamp && stampInfo"
|
v-if="hasStamp && stampInfo"
|
||||||
:award-name="stampInfo.name"
|
:award-name="stampInfo.name"
|
||||||
:award-icon-key="stampedWith"
|
:award-icon-key="stampedWith!"
|
||||||
:user-name="connectionStore.userName || ''"
|
:user-name="connectionStore.userName || ''"
|
||||||
color="gold"
|
color="gold"
|
||||||
class="applied-stamp"
|
class="applied-stamp"
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ async function handleEnter() {
|
|||||||
.footer {
|
.footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: $spacing-md;
|
padding: $spacing-md;
|
||||||
color: $color-text-muted;
|
color: #999;
|
||||||
font-size: $font-size-xs;
|
font-size: $font-size-xs;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ async function handleSubmit() {
|
|||||||
.footer {
|
.footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
color: $color-text-muted;
|
color: #999;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.logout-btn {
|
.logout-btn {
|
||||||
color: $color-text-muted;
|
color: #999;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -181,7 +181,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background: rgba(255, 255, 255, 0.1);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
color: $color-text-light;
|
color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
VITE_SOCKET_URL=
|
VITE_SOCKET_URL=
|
||||||
VITE_API_URL=
|
VITE_API_URL=
|
||||||
|
VITE_MOBILE_URL=
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function generateToken(code: string): string {
|
|||||||
export { ADMIN_TOKEN_KEY, ADMIN_ACCESS_CODE, generateToken };
|
export { ADMIN_TOKEN_KEY, ADMIN_ACCESS_CODE, generateToken };
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory('/screen/'),
|
||||||
routes: [
|
routes: [
|
||||||
// ============================================
|
// ============================================
|
||||||
// Big Screen Display Routes (LED PC)
|
// Big Screen Display Routes (LED PC)
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ let audioPlayer: HTMLAudioElement | null = null;
|
|||||||
|
|
||||||
// Audio file paths - place audio files in public/audio/
|
// Audio file paths - place audio files in public/audio/
|
||||||
const AUDIO_TRACKS: Record<string, string> = {
|
const AUDIO_TRACKS: Record<string, string> = {
|
||||||
bgm: '/audio/bgm.mp3',
|
bgm: '/screen/audio/bgm.mp3',
|
||||||
lottery: '/audio/lottery.mp3',
|
lottery: '/screen/audio/lottery.mp3',
|
||||||
fanfare: '/audio/fanfare.mp3',
|
fanfare: '/screen/audio/fanfare.mp3',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useDisplayStore = defineStore('display', () => {
|
export const useDisplayStore = defineStore('display', () => {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const participantCount = ref(0);
|
|||||||
let previousSubPhase: string | null = null;
|
let previousSubPhase: string | null = null;
|
||||||
|
|
||||||
// Prize configuration - 从服务器加载
|
// Prize configuration - 从服务器加载
|
||||||
const prizes = ref<Array<{ level: string; name: string; winnerCount: number; poolTag?: string }>>([]);
|
const prizes = ref<Array<{ level: string; name: string; winnerCount: number; poolTag?: string; round?: number }>>([]);
|
||||||
|
|
||||||
// 从 API 获取奖项配置
|
// 从 API 获取奖项配置
|
||||||
async function fetchPrizes() {
|
async function fetchPrizes() {
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ const displayStore = useDisplayStore();
|
|||||||
// QR Code Login (legacy - kept for direct access)
|
// QR Code Login (legacy - kept for direct access)
|
||||||
const showQRLogin = ref(false);
|
const showQRLogin = ref(false);
|
||||||
|
|
||||||
// Mobile URL for entry QR code
|
// Mobile URL for entry QR code - use environment variable or fallback to current origin
|
||||||
const mobileUrl = 'http://192.168.1.5:5174';
|
const mobileUrl = import.meta.env.VITE_MOBILE_URL || window.location.origin;
|
||||||
|
|
||||||
function handleLoginSuccess(data: { userId: string; userName: string; department: string; sessionToken: string }) {
|
function handleLoginSuccess(data: { userId: string; userName: string; department: string; sessionToken: string }) {
|
||||||
console.log('Login success:', data);
|
console.log('Login success:', data);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import vue from '@vitejs/plugin-vue';
|
|||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
base: '/screen/',
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ FROM node:20-alpine AS builder
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install pnpm
|
# Install pnpm and configure registry
|
||||||
RUN npm install -g pnpm
|
RUN npm config set registry https://registry.npmmirror.com && \
|
||||||
|
npm install -g pnpm && \
|
||||||
|
pnpm config set registry https://registry.npmmirror.com
|
||||||
|
|
||||||
# Copy workspace files
|
# Copy workspace files
|
||||||
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
|
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml tsconfig.base.json ./
|
||||||
COPY packages/shared ./packages/shared
|
COPY packages/shared ./packages/shared
|
||||||
COPY packages/server ./packages/server
|
COPY packages/server ./packages/server
|
||||||
|
|
||||||
@@ -26,18 +28,28 @@ FROM node:20-alpine AS production
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN npm install -g pnpm
|
RUN npm config set registry https://registry.npmmirror.com && \
|
||||||
|
npm install -g pnpm && \
|
||||||
|
pnpm config set registry https://registry.npmmirror.com
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY --from=builder /app/pnpm-workspace.yaml ./
|
||||||
|
COPY --from=builder /app/package.json ./
|
||||||
|
COPY --from=builder /app/pnpm-lock.yaml ./
|
||||||
|
COPY --from=builder /app/packages/server/package.json ./packages/server/
|
||||||
|
COPY --from=builder /app/packages/shared ./packages/shared
|
||||||
|
|
||||||
|
# Install production dependencies only
|
||||||
|
RUN pnpm install --prod --frozen-lockfile
|
||||||
|
|
||||||
# Copy built files
|
# Copy built files
|
||||||
COPY --from=builder /app/packages/server/dist ./dist
|
COPY --from=builder /app/packages/server/dist ./packages/server/dist
|
||||||
COPY --from=builder /app/packages/server/package.json ./
|
COPY --from=builder /app/packages/server/src/lua ./packages/server/lua
|
||||||
COPY --from=builder /app/packages/shared /app/packages/shared
|
|
||||||
COPY --from=builder /app/node_modules ./node_modules
|
|
||||||
COPY --from=builder /app/packages/server/node_modules ./packages/server/node_modules
|
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV PORT=3000
|
ENV PORT=3000
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
|
WORKDIR /app/packages/server
|
||||||
CMD ["node", "dist/index.js"]
|
CMD ["node", "dist/index.js"]
|
||||||
|
|||||||
Reference in New Issue
Block a user