优化游戏界面

main
wang 2 months ago
parent 1bedce8fb8
commit 86afebdf0a

@ -17,49 +17,88 @@
- **数据库**MySQL
- **部署**Docker、Nginx
## 快速开始 (Docker)
## Docker部署说明
本项目已完全支持Docker部署只需几个简单的步骤即可运行
### 环境要求
- Docker
- Docker Compose
- Docker 20.10+
- Docker Compose 2.0+
### 部署步骤
1. 克隆仓库
1. 克隆项目代码:
```bash
git clone https://github.com/yourusername/goldminer.git
git clone <仓库地址>
cd goldminer
```
2. 启动服务 (Linux/Mac)
2. 创建环境配置文件(可选):
```bash
chmod +x start.sh
./start.sh
```
# 复制示例配置
cp .env.example .env
Windows系统:
# 根据需要编辑配置
nano .env # 或使用其他编辑器
```
3. 启动服务:
```bash
# Windows用户
start.bat
# Linux/Mac用户
bash start.sh
```
3. 访问游戏
- 打开浏览器访问: http://localhost:8080
- 管理员初始账号: admin
- 管理员初始密码: admin
或者手动启动:
### 环境变量配置
```bash
# 构建并启动容器
docker-compose up -d --build
```
4. 访问服务:
- 前端网页http://localhost:8080
- 管理员初始账号admin
- 管理员初始密码admin
### 环境变量说明
系统使用`.env`文件管理环境变量,首次运行会自动创建。主要配置项:
可以通过创建`.env`文件或设置环境变量来自定义配置:
| 变量名 | 描述 | 默认值 |
| 变量名 | 说明 | 默认值 |
|--------|------|--------|
| PORT | 前端服务端口 | 8080 |
| DB_HOST | 数据库主机名 | mysql2.sqlpub.com |
| PORT | 网站访问端口 | 8080 |
| FLASK_ENV | Flask环境 | production |
| DB_HOST | 数据库主机 | mysql2.sqlpub.com |
| DB_PORT | 数据库端口 | 3307 |
| DB_USER | 数据库用户名 | goldminer |
| DB_PASSWORD | 数据库密码 | nBAWq9DDwJ14Fugq |
| SECRET_KEY | Flask会话密钥 | dev_key_for_goldminer |
| ADMIN_SETUP_KEY | 管理员初始化密钥 | goldminer_admin_setup_key |
| DB_NAME | 数据库名称 | goldminer |
| SECRET_KEY | 应用密钥 | dev_key_for_goldminer |
| ADMIN_SETUP_KEY | 管理员设置密钥 | goldminer_admin_setup_key |
### 常见问题排查
1. 如果无法访问前端页面,请检查:
- 确认容器是否正常运行:`docker-compose ps`
- 查看前端日志:`docker-compose logs frontend`
- 检查端口是否被占用:`netstat -ano | findstr 8080`
2. 如果图片资源无法显示:
- 确认项目根目录下是否存在`images`文件夹
- 确保`images`文件夹中包含必要的图片资源
- 查看nginx日志`docker-compose logs frontend`
3. 如果后端API无法连接
- 检查后端服务是否正常:`docker-compose logs backend`
- 确认数据库连接信息是否正确
## 手动开发环境搭建

@ -7,6 +7,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
python3-dev \
default-libmysqlclient-dev \
curl \
&& rm -rf /var/lib/apt/lists/*
# 复制依赖文件
@ -15,6 +16,9 @@ COPY requirements.txt .
# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt
# 创建日志目录
RUN mkdir -p /app/logs && chmod 777 /app/logs
# 复制所有后端源码
COPY . .

@ -37,6 +37,8 @@ services:
- backend
ports:
- "${PORT:-8080}:80"
volumes:
- ./images:/usr/share/nginx/html/images
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/"]
interval: 30s

@ -14,6 +14,12 @@ RUN npm ci --quiet
# 复制所有前端源码
COPY . .
# 确保images目录存在于public目录中
RUN if [ ! -d "public/images" ] && [ -d "../images" ]; then \
mkdir -p public/images && \
cp -r ../images/* public/images/; \
fi
# 设置生产环境构建
ENV NODE_ENV=production
@ -26,9 +32,16 @@ RUN ls -la dist || exit 1
# 第二阶段使用nginx提供静态文件
FROM nginx:stable-alpine AS production-stage
# 安装curl用于健康检查
RUN apk add --no-cache curl
# 从构建阶段复制构建结果到nginx默认目录
COPY --from=build-stage /app/dist /usr/share/nginx/html
# 创建images目录并确保权限正确
RUN mkdir -p /usr/share/nginx/html/images && \
chmod -R 755 /usr/share/nginx/html
# 复制自定义nginx配置
COPY nginx.conf /etc/nginx/conf.d/default.conf

@ -8,6 +8,11 @@ server {
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# 允许跨域访问
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
root /usr/share/nginx/html;
index index.html;
@ -18,6 +23,15 @@ server {
add_header Cache-Control "public, max-age=31536000";
access_log off;
}
# 特别处理images目录
location /images/ {
alias /usr/share/nginx/html/images/;
autoindex off;
expires max;
add_header Cache-Control "public, max-age=31536000";
try_files $uri $uri/ =404;
}
# 静态资源请求直接访问
location / {

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

@ -1,115 +1,3 @@
<template>
<div class="admin-container">
<h1>管理控制台</h1>
@ -179,9 +67,6 @@
:disabled="currentUser && currentUser.id === user.id">
删除
</button>
<button @click="resetUserScore(user.id)" class="reset-btn">
重置分数
</button>
</td>
</tr>
</tbody>
@ -222,44 +107,6 @@
</div>
</div>
<!-- 排行榜管理 -->
<div v-if="activeTab === 'leaderboard'" class="tab-content">
<h2>排行榜管理</h2>
<div class="leaderboard-actions">
<button @click="confirmResetAllScores" class="danger-btn">
重置所有分数
</button>
</div>
<div class="table-wrapper">
<table class="leaderboard-table">
<thead>
<tr>
<th>排名</th>
<th>用户名</th>
<th>最高分</th>
<th>最后游戏</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(user, index) in leaderboard" :key="user.id">
<td>{{ index + 1 }}</td>
<td>{{ user.username }}</td>
<td>{{ user.high_score }}</td>
<td>{{ formatDate(user.last_login) }}</td>
<td class="actions">
<button @click="editUser(user)" class="edit-btn">编辑</button>
<button @click="resetUserScore(user.id)" class="reset-btn">
重置分数
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 游戏历史管理 -->
<div v-if="activeTab === 'history'" class="tab-content">
<h2>游戏历史管理</h2>
@ -334,7 +181,6 @@ export default {
activeTab: 'users',
tabs: [
{ id: 'users', name: '用户管理' },
{ id: 'leaderboard', name: '排行榜管理' },
{ id: 'history', name: '游戏历史' }
],
@ -343,9 +189,6 @@ export default {
filteredUsers: [],
userSearchTerm: '',
//
leaderboard: [],
//
gameHistory: [],
filteredGameHistory: [],
@ -441,7 +284,6 @@ export default {
await Promise.all([
this.loadUsers(),
this.loadLeaderboard(),
this.loadGameHistory()
])
},
@ -472,59 +314,6 @@ export default {
this.loadUsers()
},
//
async loadLeaderboard() {
try {
const response = await axios.get('/api/leaderboard')
this.leaderboard = response.data.leaderboard
} catch (error) {
console.error('加载排行榜失败:', error)
}
},
async confirmResetAllScores() {
this.showConfirmDialog = true
this.confirmDialogTitle = '重置所有分数'
this.confirmDialogMessage = '确定要重置所有用户的分数吗?此操作不可撤销!'
this.confirmDialogAction = this.resetAllScores
},
async resetAllScores() {
try {
await axios.post('/api/admin/leaderboard/reset')
this.showConfirmDialog = false
//
await this.loadUsers()
await this.loadLeaderboard()
await this.loadGameHistory()
alert('所有分数已重置')
} catch (error) {
console.error('重置分数失败:', error)
alert('重置分数失败: ' + (error.response?.data?.error || '未知错误'))
}
},
async resetUserScore(userId) {
this.showConfirmDialog = true
this.confirmDialogTitle = '重置用户分数'
this.confirmDialogMessage = '确定要重置此用户的分数吗?此操作不可撤销!'
this.confirmDialogAction = async () => {
try {
await axios.post('/api/admin/leaderboard/reset', { user_id: userId })
this.showConfirmDialog = false
//
await this.loadUsers()
await this.loadLeaderboard()
await this.loadGameHistory()
alert('用户分数已重置')
} catch (error) {
console.error('重置分数失败:', error)
alert('重置分数失败: ' + (error.response?.data?.error || '未知错误'))
}
}
},
//
async loadGameHistory() {
try {
@ -595,7 +384,6 @@ export default {
//
await this.loadUsers()
await this.loadLeaderboard()
// localStorage
const userData = JSON.parse(localStorage.getItem('user'))
@ -656,7 +444,6 @@ export default {
//
await this.loadUsers()
await this.loadLeaderboard()
await this.loadGameHistory()
alert('用户已删除')
@ -784,7 +571,7 @@ tr:hover {
gap: 5px;
}
.edit-btn, .delete-btn, .reset-btn, .refresh-btn {
.edit-btn, .delete-btn, .refresh-btn {
padding: 5px 8px;
border: none;
border-radius: 3px;
@ -802,11 +589,6 @@ tr:hover {
color: white;
}
.reset-btn {
background-color: #ff9800;
color: white;
}
.refresh-btn {
background-color: #2196F3;
color: white;

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Loading…
Cancel
Save