1
This commit is contained in:
@@ -6,7 +6,7 @@ import http from '@/plugins/http'
|
|||||||
|
|
||||||
// 获取链接状态
|
// 获取链接状态
|
||||||
export function getLinkStatus(code) {
|
export function getLinkStatus(code) {
|
||||||
return http.get('/api/link/status', {
|
return http.get(`/api/link/status?t=${Date.now()}`, {
|
||||||
params: { code }
|
params: { code }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -34,12 +34,12 @@ export function pollLoginStatus(code) {
|
|||||||
|
|
||||||
// 获取游戏界面数据
|
// 获取游戏界面数据
|
||||||
export function getGameInterface(code) {
|
export function getGameInterface(code) {
|
||||||
return http.get(`/api/link/${code}/game-interface`)
|
return http.get(`/api/link/${code}/game-interface?t=${Date.now()}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取二维码图片(可选,用于代理二维码避免混合内容问题)
|
// 获取二维码图片(可选,用于代理二维码避免混合内容问题)
|
||||||
export function getQRCode(code) {
|
export function getQRCode(code) {
|
||||||
return http.get('/api/link/qr.png', {
|
return http.get(`/api/link/qr.png?t=${Date.now()}`, {
|
||||||
params: { code },
|
params: { code },
|
||||||
responseType: 'blob'
|
responseType: 'blob'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -110,23 +110,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
isWaitingQr(newVal) {
|
// 移除二维码探测逻辑,确保必须等待指定时间
|
||||||
if (newVal && this.mecmachineId) {
|
|
||||||
this.startQrProbe()
|
|
||||||
} else {
|
|
||||||
this.stopQrProbe()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mecmachineId(newVal) {
|
|
||||||
if (this.isWaitingQr && newVal) {
|
|
||||||
this.startQrProbe()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.isWaitingQr && this.mecmachineId) {
|
// 移除二维码探测逻辑,确保必须等待指定时间
|
||||||
this.startQrProbe()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
this.stopQrProbe()
|
this.stopQrProbe()
|
||||||
@@ -137,21 +124,7 @@ export default {
|
|||||||
const secs = seconds % 60
|
const secs = seconds % 60
|
||||||
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
||||||
},
|
},
|
||||||
startQrProbe() {
|
// 移除二维码探测方法,确保必须等待指定时间后才显示二维码
|
||||||
this.stopQrProbe()
|
|
||||||
const attemptLoad = () => {
|
|
||||||
if (!this.mecmachineId) return
|
|
||||||
const testImg = new Image()
|
|
||||||
testImg.onload = () => {
|
|
||||||
this.$emit('qrImageLoad')
|
|
||||||
this.stopQrProbe()
|
|
||||||
}
|
|
||||||
testImg.onerror = () => {}
|
|
||||||
testImg.src = `https://uzi1.cn/image/${this.mecmachineId}/二维码.png?t=${Date.now()}`
|
|
||||||
}
|
|
||||||
attemptLoad()
|
|
||||||
this.probeTimer = setInterval(attemptLoad, 1000)
|
|
||||||
},
|
|
||||||
stopQrProbe() {
|
stopQrProbe() {
|
||||||
if (this.probeTimer) {
|
if (this.probeTimer) {
|
||||||
clearInterval(this.probeTimer)
|
clearInterval(this.probeTimer)
|
||||||
|
|||||||
@@ -197,38 +197,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleQrReadyEarly = async () => {
|
const handleQrReadyEarly = async () => {
|
||||||
try {
|
// 移除提前显示二维码的逻辑,确保必须等待指定时间后才显示
|
||||||
if (!state.isWaitingQr) return
|
console.log('二维码提前就绪事件被忽略,必须等待指定时间后才显示')
|
||||||
if (!state.mecmachineId) return
|
|
||||||
if (state.qrInfo && state.qrInfo.url) return
|
|
||||||
// 结束等待并清理相关定时器
|
|
||||||
state.isWaitingQr = false
|
|
||||||
clearQrDelayCountdown()
|
|
||||||
if (typeof clearQrDelayTimeout === 'function') {
|
|
||||||
clearQrDelayTimeout(state)
|
|
||||||
}
|
|
||||||
// 立即拉取二维码并开始倒计时与登录轮询
|
|
||||||
await fetchQrCodeAfterDelay(
|
|
||||||
state,
|
|
||||||
countdown,
|
|
||||||
state.mecmachineId,
|
|
||||||
state.qrCreatedAt,
|
|
||||||
state.qrExpireAt
|
|
||||||
)
|
|
||||||
if (state.status === 'USING') {
|
|
||||||
startCountdown()
|
|
||||||
startLoginPolling(state.code, handleLoggedInStatus, handleCompletedStatus)
|
|
||||||
startProgressPolling(state.code, (progressData) => {
|
|
||||||
state.currentPoints = progressData.currentPoints || state.currentPoints
|
|
||||||
state.totalPoints = progressData.totalPoints || state.totalPoints
|
|
||||||
if (progressData.completedPoints !== undefined) {
|
|
||||||
state.completedPoints = progressData.completedPoints
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('提前获取二维码失败:', error)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
282
部署指南.md
Normal file
282
部署指南.md
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
# 游戏平台前端项目部署指南
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
|
||||||
|
本项目是一个基于 Vue 3 + Vite 的单页应用(SPA),包含管理后台和游戏功能。项目使用 Element Plus 作为 UI 组件库,Axios 进行 HTTP 请求。
|
||||||
|
|
||||||
|
## 📋 环境要求
|
||||||
|
|
||||||
|
### 开发环境
|
||||||
|
- Node.js 版本:>= 16.0.0
|
||||||
|
- npm 版本:>= 8.0.0 或 yarn >= 1.22.0
|
||||||
|
|
||||||
|
### 生产环境
|
||||||
|
- Web 服务器:Nginx(推荐)或 Apache
|
||||||
|
- 后端 API 服务:需要运行在 `http://192.140.164.137:18080`
|
||||||
|
|
||||||
|
## 🚀 快速部署
|
||||||
|
|
||||||
|
### 1. 准备工作
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 克隆项目(如果需要)
|
||||||
|
git clone <repository-url>
|
||||||
|
cd login_task_web
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 验证开发环境
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 构建生产版本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 构建项目
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 构建完成后,dist 目录包含所有静态文件
|
||||||
|
```
|
||||||
|
|
||||||
|
构建产物说明:
|
||||||
|
- `dist/index.html` - 主页面文件
|
||||||
|
- `dist/assets/` - 静态资源(JS、CSS、图片等)
|
||||||
|
|
||||||
|
### 3. 部署到服务器
|
||||||
|
|
||||||
|
将 `dist` 目录下的所有文件上传到 Web 服务器的网站根目录。
|
||||||
|
|
||||||
|
## 🔧 服务器配置
|
||||||
|
|
||||||
|
由于本项目是单页应用(SPA),需要正确配置服务器以支持:
|
||||||
|
1. **路由回退**:所有前端路由都应返回 `index.html`
|
||||||
|
2. **API 代理**:代理 `/api/*` 请求到后端服务器
|
||||||
|
3. **CORS 处理**:解决跨域问题
|
||||||
|
|
||||||
|
### 🌟 方式一:Nginx 配置(推荐)
|
||||||
|
|
||||||
|
#### 使用项目提供的配置文件
|
||||||
|
|
||||||
|
1. **复制 Nginx 配置**
|
||||||
|
```bash
|
||||||
|
cp nginx.conf /etc/nginx/sites-available/your-site.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **修改配置文件中的路径**
|
||||||
|
```nginx
|
||||||
|
# 修改为您的实际部署路径
|
||||||
|
root /var/www/your-site/dist;
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **启用站点并重载**
|
||||||
|
```bash
|
||||||
|
sudo ln -s /etc/nginx/sites-available/your-site.conf /etc/nginx/sites-enabled/
|
||||||
|
sudo nginx -t
|
||||||
|
sudo nginx -s reload
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 完整 Nginx 配置示例
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name your-domain.com;
|
||||||
|
root /var/www/your-site/dist;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# 静态资源缓存
|
||||||
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# API 代理
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://192.140.164.137:18080/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# CORS 设置
|
||||||
|
add_header Access-Control-Allow-Origin *;
|
||||||
|
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';
|
||||||
|
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
|
||||||
|
|
||||||
|
# 处理预检请求
|
||||||
|
if ($request_method = 'OPTIONS') {
|
||||||
|
add_header Access-Control-Allow-Origin *;
|
||||||
|
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';
|
||||||
|
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
|
||||||
|
add_header Access-Control-Max-Age 1728000;
|
||||||
|
add_header Content-Type 'text/plain; charset=utf-8';
|
||||||
|
add_header Content-Length 0;
|
||||||
|
return 204;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# SPA 路由配置(关键!)
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ @fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
location @fallback {
|
||||||
|
rewrite ^.*$ /index.html last;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 错误页面
|
||||||
|
error_page 404 /index.html;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🛠️ 方式二:宝塔面板部署
|
||||||
|
|
||||||
|
参考项目中的 `宝塔面板操作步骤.md` 文件,关键步骤:
|
||||||
|
|
||||||
|
1. **上传文件**
|
||||||
|
- 将 `dist` 目录下所有文件上传到网站根目录
|
||||||
|
|
||||||
|
2. **修改 Nginx 配置**
|
||||||
|
- 登录宝塔面板 → 网站 → 设置 → 配置文件
|
||||||
|
- 在 `#REWRITE-END` 后添加 API 代理配置
|
||||||
|
- 添加 SPA 路由回退配置
|
||||||
|
|
||||||
|
3. **保存并重载配置**
|
||||||
|
|
||||||
|
### 🔨 方式三:Apache 配置
|
||||||
|
|
||||||
|
1. **复制 .htaccess 文件**
|
||||||
|
```bash
|
||||||
|
cp apache.htaccess dist/.htaccess
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **确保 Apache 模块已启用**
|
||||||
|
```bash
|
||||||
|
sudo a2enmod rewrite
|
||||||
|
sudo a2enmod headers
|
||||||
|
sudo a2enmod proxy
|
||||||
|
sudo a2enmod proxy_http
|
||||||
|
sudo systemctl restart apache2
|
||||||
|
```
|
||||||
|
|
||||||
|
### ☁️ 方式四:Netlify 部署
|
||||||
|
|
||||||
|
1. **复制重定向文件**
|
||||||
|
```bash
|
||||||
|
cp _redirects dist/
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **部署到 Netlify**
|
||||||
|
- 上传 `dist` 目录到 Netlify
|
||||||
|
- 或连接 Git 仓库自动部署
|
||||||
|
|
||||||
|
## 🧪 部署验证
|
||||||
|
|
||||||
|
### 功能测试清单
|
||||||
|
|
||||||
|
- [ ] 主页访问:`http://your-domain.com/`
|
||||||
|
- [ ] 直接路由访问:`http://your-domain.com/play?code=xxx`
|
||||||
|
- [ ] 页面刷新:在任意页面刷新不应出现 404
|
||||||
|
- [ ] API 请求:检查网络面板,确认 API 请求正常
|
||||||
|
- [ ] 登录功能:测试用户登录流程
|
||||||
|
- [ ] 游戏功能:测试游戏相关功能
|
||||||
|
|
||||||
|
### 验证命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 测试 Nginx 配置
|
||||||
|
sudo nginx -t
|
||||||
|
|
||||||
|
# 查看错误日志
|
||||||
|
sudo tail -f /var/log/nginx/error.log
|
||||||
|
|
||||||
|
# 测试 API 连通性
|
||||||
|
curl -I http://your-domain.com/api/health
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚨 常见问题及解决方案
|
||||||
|
|
||||||
|
### 问题 1:直接访问路由出现 404
|
||||||
|
**原因**:Web 服务器未配置 SPA 路由回退
|
||||||
|
**解决**:按照上述配置添加路由回退规则
|
||||||
|
|
||||||
|
### 问题 2:API 请求失败 (CORS 错误)
|
||||||
|
**原因**:跨域配置不正确
|
||||||
|
**解决**:
|
||||||
|
1. 检查 API 代理配置
|
||||||
|
2. 确认后端服务 `http://192.140.164.137:18080` 可访问
|
||||||
|
3. 检查 CORS 头部设置
|
||||||
|
|
||||||
|
### 问题 3:静态资源加载失败
|
||||||
|
**原因**:资源路径配置问题
|
||||||
|
**解决**:
|
||||||
|
1. 检查 `vite.config.js` 中的 `base` 配置
|
||||||
|
2. 确认所有文件都已正确上传
|
||||||
|
|
||||||
|
### 问题 4:页面空白
|
||||||
|
**原因**:通常是 JavaScript 错误
|
||||||
|
**解决**:
|
||||||
|
1. 打开浏览器开发者工具查看控制台错误
|
||||||
|
2. 检查网络请求是否正常
|
||||||
|
3. 确认构建过程无错误
|
||||||
|
|
||||||
|
## 📊 性能优化建议
|
||||||
|
|
||||||
|
### 1. 启用 Gzip 压缩
|
||||||
|
```nginx
|
||||||
|
# 在 Nginx 配置中添加
|
||||||
|
gzip on;
|
||||||
|
gzip_types text/plain text/css application/javascript application/json;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 配置缓存策略
|
||||||
|
- 静态资源(JS、CSS、图片):长期缓存
|
||||||
|
- HTML 文件:短期缓存或无缓存
|
||||||
|
|
||||||
|
### 3. CDN 加速
|
||||||
|
考虑使用 CDN 服务加速静态资源访问
|
||||||
|
|
||||||
|
## 🔒 安全建议
|
||||||
|
|
||||||
|
1. **HTTPS 配置**:生产环境应启用 HTTPS
|
||||||
|
2. **API 安全**:确保后端 API 有适当的安全验证
|
||||||
|
3. **访问控制**:配置防火墙规则
|
||||||
|
4. **定期更新**:保持依赖库和服务器软件更新
|
||||||
|
|
||||||
|
## 📱 移动端适配
|
||||||
|
|
||||||
|
项目已包含移动端检测,确保在移动设备上访问体验良好。
|
||||||
|
|
||||||
|
## 🔄 更新部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 拉取最新代码
|
||||||
|
git pull origin main
|
||||||
|
|
||||||
|
# 2. 安装/更新依赖
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 3. 重新构建
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 4. 上传新的构建文件
|
||||||
|
# 替换服务器上的 dist 目录内容
|
||||||
|
|
||||||
|
# 5. 重载服务器配置(如有必要)
|
||||||
|
sudo nginx -s reload
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📞 技术支持
|
||||||
|
|
||||||
|
如果在部署过程中遇到问题:
|
||||||
|
|
||||||
|
1. 首先查看上述常见问题
|
||||||
|
2. 检查服务器错误日志
|
||||||
|
3. 确认网络和防火墙配置
|
||||||
|
4. 验证后端服务可用性
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**注意**:本文档基于当前项目配置编写,如果项目配置有变更,请相应更新此文档。
|
||||||
Reference in New Issue
Block a user