Browse Source

差一个个人信息页面(被删除了)

master
hejl 2 weeks ago
parent
commit
6ff202b8a5
  1. 212
      gofaster/FULL_STACK_ENHANCED.md
  2. 138
      gofaster/QUICK_START.md
  3. 0
      gofaster/UserProfile.vue
  4. 21
      gofaster/app/.npmrc
  5. 110
      gofaster/app/DEVELOPMENT_ENHANCED.md
  6. 63
      gofaster/app/dev-enhanced.bat
  7. 84
      gofaster/app/dev-enhanced.ps1
  8. 57
      gofaster/app/dev-utf8.ps1
  9. 1997
      gofaster/app/dist/renderer/js/index.js
  10. 78
      gofaster/app/package-lock.json
  11. 23
      gofaster/app/package.json
  12. 57
      gofaster/app/src/main/index.js
  13. 3
      gofaster/app/src/preload.js
  14. 252
      gofaster/app/src/renderer/components/LoginModal.vue
  15. 417
      gofaster/app/src/renderer/components/MainLayout.vue
  16. 185
      gofaster/app/src/renderer/components/StatusBar.vue
  17. 6
      gofaster/app/src/renderer/router/index.js
  18. 88
      gofaster/app/src/renderer/services/userService.js
  19. 99
      gofaster/app/src/renderer/utils/ipUtils.js
  20. 169
      gofaster/app/src/renderer/views/Home.vue
  21. 114
      gofaster/app/vue.config.js
  22. 18
      gofaster/backend/internal/auth/model/auth.go
  23. 8
      gofaster/backend/internal/auth/model/user.go
  24. 24
      gofaster/backend/internal/auth/repository/user_repo.go
  25. 111
      gofaster/backend/internal/auth/service/auth_service.go
  26. BIN
      gofaster/backend/main.exe
  27. BIN
      gofaster/backend/tmp/main.exe
  28. 165
      gofaster/dev-full.ps1
  29. 76
      gofaster/start-dev.bat
  30. 105
      gofaster/start-dev.ps1
  31. 47
      gofaster/start-simple.bat
  32. 45
      gofaster/start.ps1
  33. 122
      gofaster/test-login-complete.md
  34. 94
      gofaster/test-login-fix-verification.md
  35. 96
      gofaster/test-login-fixes.md

212
gofaster/FULL_STACK_ENHANCED.md

@ -0,0 +1,212 @@ @@ -0,0 +1,212 @@
# 🚀 GoFaster 增强版全栈开发环境
## 📋 功能特性
本增强版全栈开发环境集成了所有优化功能:
### **1. 编码优化**
- ✅ UTF-8 编码支持
- ✅ 中文显示无乱码
- ✅ PowerShell 编码配置
### **2. 日志增强**
- ✅ 详细构建日志
- ✅ 进度条显示
- ✅ 错误详情展示
- ✅ 实时状态监控
### **3. 智能依赖管理**
- ✅ 自动检查前端依赖
- ✅ 自动安装 cross-env
- ✅ 自动检查后端依赖
- ✅ 自动安装 air 工具
### **4. 灵活启动模式**
- ✅ 全栈启动(前后端)
- ✅ 仅启动后端
- ✅ 仅启动前端
- ✅ 调试模式
- ✅ 监听模式
## 🛠 使用方法
### **方法 1: 从根目录启动(推荐)**
```powershell
# 全栈启动(标准模式)
.\dev-full.ps1
# 全栈启动(调试模式)
.\dev-full.ps1 -Debug
# 全栈启动(监听模式)
.\dev-full.ps1 -Watch
# 仅启动后端
.\dev-full.ps1 -BackendOnly
# 仅启动前端
.\dev-full.ps1 -FrontendOnly
```
### **方法 2: 从前端目录启动**
```bash
cd app
# 全栈启动(标准模式)
npm run dev:full
# 全栈启动(调试模式)
npm run dev:full-debug
# 全栈启动(监听模式)
npm run dev:full-watch
# 仅启动后端
npm run dev:backend-only
# 仅启动前端
npm run dev:frontend-only
```
### **方法 3: 单独启动服务**
```bash
# 前端增强版
npm run dev:enhanced
# 前端调试版
npm run dev:debug
# 后端热重载
cd backend
air
```
## 🔧 新增功能详解
### **1. 智能依赖检查**
- 自动检查 `node_modules` 是否存在
- 自动安装缺失的依赖
- 自动安装 `cross-env` 工具
- 自动检查并安装 `air` 工具
### **2. 进程管理**
- 显示每个服务的进程 ID (PID)
- 提供进程管理命令
- 智能启动顺序(先后端,后前端)
- 启动状态检查
### **3. 环境配置**
- 自动设置 UTF-8 编码
- 配置 Vue CLI 环境变量
- 设置 Node.js 内存选项
- 配置日志级别
### **4. 错误处理**
- 友好的错误提示
- 详细的故障排除建议
- 状态码检查
- 启动失败处理
## 📁 文件结构
```
gofaster/
├── dev-full.ps1 # 增强版全栈启动脚本
├── app/
│ ├── dev-enhanced.ps1 # 前端增强启动脚本
│ ├── dev-enhanced.bat # 前端批处理脚本
│ ├── .npmrc # npm 配置文件
│ ├── vue.config.js # Vue CLI 增强配置
│ └── package.json # 包含新脚本的包配置
└── backend/
├── .air.toml # Air 热重载配置
└── main.go # 后端主程序
```
## 🚀 启动流程
### **全栈启动流程**
1. **环境检查**:设置编码、环境变量
2. **依赖检查**:检查前端和后端依赖
3. **后端启动**:启动 Go 服务 + Air 热重载
4. **状态检查**:验证后端启动状态
5. **前端启动**:启动 Electron + Vue 热重载
6. **完成提示**:显示服务信息和进程管理命令
### **启动参数说明**
| 参数 | 说明 | 示例 |
|------|------|------|
| `-Debug` | 启用调试模式,显示详细日志 | `.\dev-full.ps1 -Debug` |
| `-Watch` | 启用监听模式,自动重载 | `.\dev-full.ps1 -Watch` |
| `-BackendOnly` | 仅启动后端服务 | `.\dev-full.ps1 -BackendOnly` |
| `-FrontendOnly` | 仅启动前端服务 | `.\dev-full.ps1 -FrontendOnly` |
## 🔍 监控和管理
### **进程信息**
启动完成后会显示:
- 后端进程 ID (PID)
- 前端进程 ID (PID)
- 进程管理命令
### **进程管理命令**
```powershell
# 停止后端
Stop-Process -Id <后端PID>
# 停止前端
Stop-Process -Id <前端PID>
# 停止所有服务
Get-Process | Where-Object {$_.ProcessName -eq 'powershell'} | Stop-Process
```
### **服务状态检查**
- 后端健康检查:`http://localhost:8080/health`
- Swagger 文档:`http://localhost:8080/swagger/index.html`
- 前端应用:Electron 窗口自动打开
## 🚨 故障排除
### **常见问题**
#### **1. 编码问题**
- 确保使用 PowerShell 或 CMD
- 检查系统区域设置
- 使用 `-Debug` 参数查看详细日志
#### **2. 依赖问题**
- 脚本会自动检查和安装依赖
- 如果失败,手动运行 `npm install`
- 检查 Node.js 版本 (推荐 v16+)
#### **3. 端口冲突**
- 检查 8080 端口是否被占用
- 检查 3000 端口是否被占用
- 使用 `netstat -ano | findstr :8080` 查看
#### **4. 启动失败**
- 查看详细错误日志
- 检查 Go 和 Node.js 环境
- 确保在正确的目录下运行
### **调试技巧**
1. **使用调试模式**:`.\dev-full.ps1 -Debug`
2. **查看进程状态**:`Get-Process | Where-Object {$_.ProcessName -eq 'powershell'}`
3. **检查端口占用**:`netstat -ano | findstr :8080`
4. **查看日志文件**:检查各服务的日志输出
## 💡 最佳实践
1. **推荐使用全栈启动**:`.\dev-full.ps1`
2. **开发时使用监听模式**:`.\dev-full.ps1 -Watch`
3. **调试时使用调试模式**:`.\dev-full.ps1 -Debug`
4. **定期清理进程**:避免端口冲突
5. **保持依赖更新**:定期运行 `npm update`
## 🔄 更新日志
- **v2.0.0**: 集成所有增强功能
- **v2.1.0**: 添加智能依赖管理
- **v2.2.0**: 增强进程管理和监控
- **v2.3.0**: 优化启动流程和错误处理

138
gofaster/QUICK_START.md

@ -0,0 +1,138 @@ @@ -0,0 +1,138 @@
# 🚀 GoFaster 快速启动指南
## 📋 启动方式总览
### **🎯 多种启动方式**
#### **方式 1: PowerShell 脚本(推荐)**
```powershell
# 右键选择"使用 PowerShell 运行"
.\start.ps1
# 或者在 PowerShell 中运行
.\start.ps1
```
#### **方式 2: 批处理文件**
```bash
# 双击运行(如果仍有编码问题,使用方式1)
start-dev.bat
# 或者使用英文版本
start-simple.bat
```
#### **方式 3: 直接启动**
```powershell
# 全栈启动(标准模式)
.\dev-full.ps1
# 全栈启动(调试模式)
.\dev-full.ps1 -Debug
# 全栈启动(监听模式)
.\dev-full.ps1 -Watch
```
### **⚡ 直接启动**
```powershell
# 全栈启动(标准模式)
.\dev-full.ps1
# 全栈启动(调试模式)
.\dev-full.ps1 -Debug
# 全栈启动(监听模式)
.\dev-full.ps1 -Watch
```
### **🔧 灵活启动**
```powershell
# 仅启动后端
.\dev-full.ps1 -BackendOnly
# 仅启动前端
.\dev-full.ps1 -FrontendOnly
```
## 🚀 推荐启动流程
### **1. 首次使用**
```bash
# 双击运行
start-dev.bat
# 选择选项 1: 全栈启动
```
### **2. 日常开发**
```bash
# 选择选项 3: 监听模式(自动重载)
start-dev.bat
```
### **3. 调试问题**
```bash
# 选择选项 2: 调试模式(详细日志)
start-dev.bat
```
## 📁 文件说明
| 文件 | 说明 | 用途 |
|------|------|------|
| `start-dev.bat` | 快速启动菜单 | 一键选择启动模式 |
| `dev-full.ps1` | 增强版全栈脚本 | 智能启动前后端 |
| `app/dev-enhanced.ps1` | 前端增强脚本 | 仅启动前端 |
| `backend/dev.ps1` | 后端启动脚本 | 仅启动后端 |
## 🔧 功能特性
### **✅ 已解决的问题**
- 🎯 中文乱码问题
- 📊 日志信息少的问题
- 🔄 热重载不工作的问题
- 📦 依赖管理问题
### **🚀 新增功能**
- 🧠 智能依赖检查
- 📱 进程管理和监控
- 🎨 友好的用户界面
- ⚡ 多种启动模式
## 💡 使用技巧
### **开发阶段**
1. **开始开发**:使用监听模式,自动重载
2. **调试问题**:使用调试模式,查看详细日志
3. **测试功能**:使用标准模式,稳定运行
### **维护阶段**
1. **仅修改前端**:使用 `-FrontendOnly` 模式
2. **仅修改后端**:使用 `-BackendOnly` 模式
3. **全栈开发**:使用标准模式
## 🚨 常见问题
### **Q: 启动失败怎么办?**
A: 使用调试模式 `.\dev-full.ps1 -Debug` 查看详细错误信息
### **Q: 中文显示乱码?**
A: 脚本已自动设置 UTF-8 编码,确保使用 PowerShell 或 CMD
### **Q: 端口被占用?**
A: 脚本会自动检查端口状态,如有冲突会提示解决方案
### **Q: 依赖安装失败?**
A: 脚本会自动检查和安装依赖,如失败会提供手动安装指导
## 🎯 下一步
1. **立即体验**:双击 `start-dev.bat`
2. **查看文档**:阅读 `FULL_STACK_ENHANCED.md`
3. **自定义配置**:修改 `app/vue.config.js`
4. **反馈问题**:记录遇到的问题和解决方案
---
**🎉 现在你可以享受无乱码、详细日志、智能管理的开发环境了!**

0
gofaster/UserProfile.vue

21
gofaster/app/.npmrc

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
# npm 配置文件
# 设置编码
registry=https://registry.npmjs.org/
loglevel=info
color=true
unicode=true
# 设置环境变量
node-options=--max-old-space-size=4096
# 设置日志格式
progress=true
timing=true
# 设置缓存
cache-min=3600
prefer-offline=true
# 设置安全选项
audit=false
fund=false

110
gofaster/app/DEVELOPMENT_ENHANCED.md

@ -0,0 +1,110 @@ @@ -0,0 +1,110 @@
# 🚀 GoFaster 增强版开发环境
## 📋 问题解决
本增强版开发环境解决了以下问题:
### 1. **中文乱码问题**
- ✅ 设置 UTF-8 编码
- ✅ 配置 PowerShell 编码
- ✅ 设置环境变量 LANG 和 LC_ALL
### 2. **日志信息少的问题**
- ✅ 启用详细日志级别 (INFO)
- ✅ 配置 Webpack 统计信息
- ✅ 添加构建进度条
- ✅ 增强错误和警告显示
## 🛠 使用方法
### **方法 1: PowerShell 脚本(推荐)**
```powershell
# 标准模式
npm run dev:enhanced
# 调试模式
npm run dev:enhanced -Debug
# 监听模式
npm run dev:enhanced -Watch
```
### **方法 2: 批处理脚本**
```cmd
# 标准模式
npm run dev:enhanced-bat
```
### **方法 3: 直接命令**
```bash
# 标准模式
npm run dev
# 调试模式
npm run dev:debug
# 监听模式
npm run dev:watch
```
## 🔧 新增功能
### **1. 编码设置**
- 自动设置 UTF-8 编码
- 配置环境变量 LANG 和 LC_ALL
- 设置 Node.js 选项
### **2. 日志增强**
- 详细构建信息
- 进度条显示
- 错误详情展示
- 警告信息过滤
### **3. 依赖管理**
- 自动检查依赖
- 自动安装 cross-env
- 内存优化设置
### **4. 错误处理**
- 友好的错误提示
- 故障排除建议
- 状态码检查
## 📁 文件说明
| 文件 | 说明 |
|------|------|
| `dev-enhanced.ps1` | PowerShell 增强脚本 |
| `dev-enhanced.bat` | 批处理增强脚本 |
| `.npmrc` | npm 配置文件 |
| `vue.config.js` | Vue CLI 增强配置 |
## 🚨 故障排除
### **中文仍然乱码**
1. 确保使用 PowerShell 或 CMD
2. 检查系统区域设置
3. 使用 `npm run dev:enhanced` 脚本
### **日志信息仍然很少**
1. 使用 `npm run dev:debug` 命令
2. 检查 `vue.config.js` 配置
3. 确保 `cross-env` 已安装
### **构建失败**
1. 清除 `node_modules` 并重新安装
2. 检查 Node.js 版本 (推荐 v16+)
3. 查看详细错误日志
## 💡 最佳实践
1. **推荐使用 PowerShell 脚本**:更好的编码支持和错误处理
2. **开发时使用监听模式**:自动重新构建
3. **调试时使用调试模式**:获取详细信息
4. **定期清理缓存**:避免构建问题
## 🔄 更新日志
- **v1.0.0**: 基础增强功能
- **v1.1.0**: 添加调试和监听模式
- **v1.2.0**: 优化错误处理和用户提示

63
gofaster/app/dev-enhanced.bat

@ -0,0 +1,63 @@ @@ -0,0 +1,63 @@
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
REM 增强版开发脚本 - 解决中文编码和日志问题
echo 🚀 启动 GoFaster 前端开发环境...
echo 📝 编码设置: UTF-8
echo 🔧 日志级别: INFO
echo ⚡ 热重载: 已启用
echo.
REM 设置环境变量
set VUE_CLI_BABEL_TRANSPILE_MODULES=false
set VUE_CLI_MODERN_BUILD=false
set VUE_CLI_LOG_LEVEL=info
set VUE_CLI_DEBUG=true
set LANG=zh_CN.UTF-8
set LC_ALL=zh_CN.UTF-8
set NODE_OPTIONS=--max-old-space-size=4096
REM 检查依赖
echo 📦 检查依赖...
if not exist "node_modules" (
echo 依赖未安装,正在安装...
call npm install
if errorlevel 1 (
echo ❌ 依赖安装失败
pause
exit /b 1
)
)
REM 安装 cross-env(如果不存在)
call npm list cross-env >nul 2>&1
if errorlevel 1 (
echo 📦 安装 cross-env...
call npm install --save-dev cross-env
)
echo ✅ 依赖检查完成
echo.
REM 启动服务
echo 🚀 标准模式启动...
echo 执行命令: npm run dev
echo.
call npm run dev
if errorlevel 1 (
echo.
echo ❌ 启动失败
echo.
echo 🔧 故障排除:
echo 1. 检查 Node.js 版本 (推荐 v16+)
echo 2. 清除 node_modules 并重新安装
echo 3. 检查端口占用情况
echo 4. 查看详细错误日志
pause
exit /b 1
)
pause

84
gofaster/app/dev-enhanced.ps1

@ -0,0 +1,84 @@ @@ -0,0 +1,84 @@
# 增强版开发脚本 - 解决中文编码和日志问题
param(
[switch]$Debug,
[switch]$Watch
)
# 设置控制台编码为 UTF-8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
# 设置环境变量
$env:VUE_CLI_BABEL_TRANSPILE_MODULES = "false"
$env:VUE_CLI_MODERN_BUILD = "false"
$env:VUE_CLI_LOG_LEVEL = "info"
$env:VUE_CLI_DEBUG = "true"
$env:LANG = "zh_CN.UTF-8"
$env:LC_ALL = "zh_CN.UTF-8"
$env:NODE_OPTIONS = "--max-old-space-size=4096"
# 显示启动信息
Write-Host "🚀 启动 GoFaster 前端开发环境..." -ForegroundColor Cyan
Write-Host "📝 编码设置: UTF-8" -ForegroundColor Green
Write-Host "🔧 日志级别: INFO" -ForegroundColor Green
Write-Host "⚡ 热重载: 已启用" -ForegroundColor Green
Write-Host ""
# 检查依赖
Write-Host "📦 检查依赖..." -ForegroundColor Yellow
if (-not (Test-Path "node_modules")) {
Write-Host " 依赖未安装,正在安装..." -ForegroundColor Yellow
npm install
if ($LASTEXITCODE -ne 0) {
Write-Host "❌ 依赖安装失败" -ForegroundColor Red
exit 1
}
}
# 安装 cross-env(如果不存在)
$crossEnvInstalled = npm list cross-env 2>$null
if (-not $crossEnvInstalled) {
Write-Host "📦 安装 cross-env..." -ForegroundColor Yellow
npm install --save-dev cross-env
}
Write-Host "✅ 依赖检查完成" -ForegroundColor Green
Write-Host ""
# 选择运行模式
if ($Debug) {
Write-Host "🐛 调试模式启动..." -ForegroundColor Magenta
$env:DEBUG = "*"
$script = "npm run dev:debug"
} elseif ($Watch) {
Write-Host "👀 监听模式启动..." -ForegroundColor Blue
$script = "npm run dev:watch"
} else {
Write-Host "🚀 标准模式启动..." -ForegroundColor Green
$script = "npm run dev"
}
Write-Host ""
Write-Host "💡 提示:" -ForegroundColor Cyan
Write-Host " - 使用 -Debug 参数启用详细调试信息" -ForegroundColor White
Write-Host " - 使用 -Watch 参数启用文件监听" -ForegroundColor White
Write-Host " - 按 Ctrl+C 停止服务" -ForegroundColor White
Write-Host ""
# 启动服务
Write-Host " 执行命令: $script" -ForegroundColor Yellow
Write-Host ""
try {
Invoke-Expression $script
} catch {
Write-Host "❌ 启动失败: $($_.Exception.Message)" -ForegroundColor Red
Write-Host ""
Write-Host "🔧 故障排除:" -ForegroundColor Yellow
Write-Host " 1. 检查 Node.js 版本 (推荐 v16+)" -ForegroundColor White
Write-Host " 2. 清除 node_modules 并重新安装" -ForegroundColor White
Write-Host " 3. 检查端口占用情况" -ForegroundColor White
Write-Host " 4. 查看详细错误日志" -ForegroundColor White
exit 1
}

57
gofaster/app/dev-utf8.ps1

@ -0,0 +1,57 @@ @@ -0,0 +1,57 @@
# 前端启动脚本 - 解决中文编码问题
param(
[switch]$Debug,
[switch]$Watch
)
# 强制设置控制台编码
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
# 设置环境变量
$env:VUE_CLI_BABEL_TRANSPILE_MODULES = "false"
$env:VUE_CLI_MODERN_BUILD = "false"
$env:VUE_CLI_LOG_LEVEL = "info"
$env:VUE_CLI_DEBUG = "true"
$env:LANG = "zh_CN.UTF-8"
$env:LC_ALL = "zh_CN.UTF-8"
$env:NODE_OPTIONS = "--max-old-space-size=4096"
$env:CHROME_BIN = "C:\Program Files\Google\Chrome\Application\chrome.exe"
# Windows 特定编码设置
$env:PYTHONIOENCODING = "utf-8"
$env:PYTHONLEGACYWINDOWSSTDIO = "utf-8"
Write-Host "Starting Frontend Development Environment (UTF-8 Optimized)..." -ForegroundColor Cyan
Write-Host "Encoding: UTF-8" -ForegroundColor Green
Write-Host "Log Level: INFO" -ForegroundColor Green
Write-Host "Hot Reload: Enabled" -ForegroundColor Green
Write-Host ""
# Select startup mode
$script = if ($Debug) { "npm run dev:debug" } elseif ($Watch) { "npm run dev:watch" } else { "npm run dev" }
Write-Host "Startup Command: $script" -ForegroundColor Yellow
Write-Host ""
# Start frontend
try {
Invoke-Expression $script
} catch {
Write-Host "Startup failed: $($_.Exception.Message)" -ForegroundColor Red
Write-Host "Trying fallback startup method..." -ForegroundColor Yellow
# Fallback startup method
if ($Debug) {
npm run dev:debug
} elseif ($Watch) {
npm run dev:watch
} else {
npm run dev
}
}
Write-Host ""
Write-Host "Press any key to exit..."
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

1997
gofaster/app/dist/renderer/js/index.js vendored

File diff suppressed because it is too large Load Diff

78
gofaster/app/package-lock.json generated

@ -19,6 +19,7 @@ @@ -19,6 +19,7 @@
"@types/node": "^24.2.1",
"@vue/cli-service": "^5.0.8",
"concurrently": "^9.2.0",
"cross-env": "^7.0.3",
"electron": "^37.2.6",
"electron-builder": "^26.0.12",
"electron-reload": "^2.0.0-alpha.1",
@ -3775,6 +3776,83 @@ @@ -3775,6 +3776,83 @@
"optional": true,
"peer": true
},
"node_modules/cross-env": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
"dev": true,
"dependencies": {
"cross-spawn": "^7.0.1"
},
"bin": {
"cross-env": "src/bin/cross-env.js",
"cross-env-shell": "src/bin/cross-env-shell.js"
},
"engines": {
"node": ">=10.14",
"npm": ">=6",
"yarn": ">=1"
}
},
"node_modules/cross-env/node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/cross-env/node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/cross-env/node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/cross-env/node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/cross-env/node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/cross-spawn": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz",

23
gofaster/app/package.json

@ -3,14 +3,24 @@ @@ -3,14 +3,24 @@
"version": "1.0.0",
"main": "src/main/index.js",
"scripts": {
"dev": "set VUE_CLI_BABEL_TRANSPILE_MODULES=false && set VUE_CLI_MODERN_BUILD=false && vue-cli-service build --mode development && electron .",
"dev:watch": "set VUE_CLI_BABEL_TRANSPILE_MODULES=false && set VUE_CLI_MODERN_BUILD=false && concurrently \"vue-cli-service build --mode development --watch\" \"wait-on dist/renderer/index.html && electron .\"",
"build": "set VUE_CLI_BABEL_TRANSPILE_MODULES=false && set VUE_CLI_MODERN_BUILD=false && vue-cli-service build && electron-builder",
"electron:serve": "set VUE_CLI_BABEL_TRANSPILE_MODULES=false && set VUE_CLI_MODERN_BUILD=false && vue-cli-service build --mode development && electron .",
"build:vue": "vue-cli-service build --mode development",
"dev": "cross-env VUE_CLI_BABEL_TRANSPILE_MODULES=false VUE_CLI_MODERN_BUILD=false VUE_CLI_SERVICE_CONFIG_PATH=vue.config.js vue-cli-service build --mode development --verbose && electron .",
"dev:watch": "cross-env VUE_CLI_BABEL_TRANSPILE_MODULES=false VUE_CLI_MODERN_BUILD=false VUE_CLI_SERVICE_CONFIG_PATH=vue.config.js concurrently \"vue-cli-service build --mode development --watch --verbose\" \"wait-on dist/renderer/index.html && electron .\"",
"build": "cross-env VUE_CLI_BABEL_TRANSPILE_MODULES=false VUE_CLI_MODERN_BUILD=false VUE_CLI_SERVICE_CONFIG_PATH=vue.config.js vue-cli-service build --verbose && electron-builder",
"electron:serve": "cross-env VUE_CLI_BABEL_TRANSPILE_MODULES=false VUE_CLI_MODERN_BUILD=false VUE_CLI_SERVICE_CONFIG_PATH=vue.config.js vue-cli-service build --mode development --verbose && electron .",
"build:vue": "cross-env VUE_CLI_BABEL_TRANSPILE_MODULES=false VUE_CLI_MODERN_BUILD=false VUE_CLI_SERVICE_CONFIG_PATH=vue.config.js vue-cli-service build --mode development --verbose",
"dev:ps": "powershell -ExecutionPolicy Bypass -File dev.ps1",
"dev:bat": "dev.bat",
"dev:stable": "dev-stable.bat"
"dev:stable": "dev-stable.bat",
"dev:enhanced": "powershell -ExecutionPolicy Bypass -File dev-enhanced.ps1",
"dev:enhanced-bat": "dev-enhanced.bat",
"dev:utf8": "powershell -ExecutionPolicy Bypass -File dev-utf8.ps1",
"dev:utf8-debug": "powershell -ExecutionPolicy Bypass -File dev-utf8.ps1 -Debug",
"dev:debug": "cross-env VUE_CLI_BABEL_TRANSPILE_MODULES=false VUE_CLI_MODERN_BUILD=false VUE_CLI_SERVICE_CONFIG_PATH=vue.config.js DEBUG=* vue-cli-service build --mode development --verbose && electron .",
"dev:full": "powershell -ExecutionPolicy Bypass -File ../dev-full.ps1",
"dev:full-debug": "powershell -ExecutionPolicy Bypass -File ../dev-full.ps1 -Debug",
"dev:full-watch": "powershell -ExecutionPolicy Bypass -File ../dev-full.ps1 -Watch",
"dev:backend-only": "powershell -ExecutionPolicy Bypass -File ../dev-full.ps1 -BackendOnly",
"dev:frontend-only": "powershell -ExecutionPolicy Bypass -File ../dev-full.ps1 -FrontendOnly"
},
"keywords": [],
"author": "",
@ -21,6 +31,7 @@ @@ -21,6 +31,7 @@
"@types/node": "^24.2.1",
"@vue/cli-service": "^5.0.8",
"concurrently": "^9.2.0",
"cross-env": "^7.0.3",
"electron": "^37.2.6",
"electron-builder": "^26.0.12",
"electron-reload": "^2.0.0-alpha.1",

57
gofaster/app/src/main/index.js

@ -11,6 +11,35 @@ process.env.LC_ALL = 'zh_CN.UTF-8' @@ -11,6 +11,35 @@ process.env.LC_ALL = 'zh_CN.UTF-8'
const appRoot = path.resolve(__dirname, '../..')
console.log('Application root:', appRoot)
// 获取本地IP地址
function getLocalIPAddresses() {
try {
const interfaces = os.networkInterfaces()
const addresses = []
for (const name of Object.keys(interfaces)) {
for (const interface of interfaces[name]) {
// 跳过内部地址和非IPv4地址
if (interface.family === 'IPv4' && !interface.internal) {
addresses.push({
name: name,
address: interface.address,
netmask: interface.netmask,
family: interface.family
})
}
}
}
// 优先返回非回环地址
const nonLoopback = addresses.filter(addr => addr.address !== '127.0.0.1')
return nonLoopback.length > 0 ? nonLoopback[0] : addresses[0] || null
} catch (error) {
console.error('获取本地IP地址失败:', error)
return null
}
}
let mainWindow
let errorLogCount = 0
let logFilePath
@ -399,6 +428,34 @@ ipcMain.handle('open-log-folder', async () => { @@ -399,6 +428,34 @@ ipcMain.handle('open-log-folder', async () => {
}
});
// 获取本地IP地址
ipcMain.handle('get-local-ip', async () => {
try {
const ipInfo = getLocalIPAddresses();
if (ipInfo) {
return {
success: true,
ip: ipInfo.address,
interface: ipInfo.name,
netmask: ipInfo.netmask
};
} else {
return {
success: false,
message: '无法获取本地IP地址',
fallback: '127.0.0.1'
};
}
} catch (error) {
console.error('获取本地IP地址失败:', error);
return {
success: false,
message: '获取本地IP地址失败: ' + error.message,
fallback: '127.0.0.1'
};
}
});
}); // 闭合 app.whenReady().then() 的回调函数
app.on('window-all-closed', () => {

3
gofaster/app/src/preload.js

@ -9,5 +9,6 @@ contextBridge.exposeInMainWorld('electronAPI', { @@ -9,5 +9,6 @@ contextBridge.exposeInMainWorld('electronAPI', {
getErrorLogCount: () => ipcRenderer.invoke('get-error-log-count'),
getLogFilePath: () => ipcRenderer.invoke('get-log-file-path'),
onErrorLogUpdated: (callback) => ipcRenderer.on('error-log-updated', callback),
openLogFolder: () => ipcRenderer.invoke('open-log-folder')
openLogFolder: () => ipcRenderer.invoke('open-log-folder'),
getLocalIP: () => ipcRenderer.invoke('get-local-ip')
})

252
gofaster/app/src/renderer/components/LoginModal.vue

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
<template>
<div v-if="visible" class="login-modal-overlay" @click="handleOverlayClick">
<div v-if="visible" class="login-modal-overlay">
<div class="login-modal" @click.stop>
<div class="login-modal-header">
<h2>用户登录</h2>
@ -24,14 +24,25 @@ @@ -24,14 +24,25 @@
<!-- 密码 -->
<div class="form-group">
<label for="password">密码</label>
<input
id="password"
v-model="loginForm.password"
type="password"
placeholder="请输入密码"
required
:disabled="loading"
/>
<div class="password-input-container">
<input
id="password"
v-model="loginForm.password"
:type="showPassword ? 'text' : 'password'"
placeholder="请输入密码"
required
:disabled="loading"
/>
<button
type="button"
class="password-toggle-btn"
@click="togglePassword"
:disabled="loading"
:title="showPassword ? '隐藏密码' : '显示密码'"
>
<span class="password-icon">{{ showPassword ? '👁' : '👁🗨' }}</span>
</button>
</div>
</div>
<!-- 验证码 -->
@ -75,27 +86,24 @@ @@ -75,27 +86,24 @@
{{ loading ? '登录中...' : '登录' }}
</button>
<!-- 表单验证提示 -->
<div v-if="!isFormValid && (loginForm.username || loginForm.password || loginForm.captcha)" class="form-validation-hint">
<span v-if="!loginForm.username.trim()">请填写用户名</span>
<span v-else-if="!loginForm.password.trim()">请填写密码</span>
<span v-else-if="!loginForm.captcha.trim()">请填写验证码</span>
<span v-else-if="!captchaImage">请先获取验证码</span>
</div>
</div>
<!-- 错误提示 -->
<div v-if="errorMessage" class="error-message">
<div v-if="errorMessage" class="error-message" role="alert">
<span class="error-icon"></span>
{{ errorMessage }}
</div>
</form>
</div>
<div class="login-modal-footer">
<p class="login-tips">
<span>提示</span>
<span>默认管理员账号sysadmin</span>
<span>默认密码sysadmin@123</span>
<span>默认管理员账号admin</span>
<span>默认密码password</span>
</p>
</div>
</div>
@ -104,6 +112,7 @@ @@ -104,6 +112,7 @@
<script>
import { userService } from '../services/userService'
import { getClientIP } from '../utils/ipUtils'
export default {
name: 'LoginModal',
@ -117,9 +126,11 @@ export default { @@ -117,9 +126,11 @@ export default {
return {
loading: false,
captchaLoading: false,
showPassword: false,
errorMessage: '',
captchaImage: '',
captchaId: '',
clientIP: '',
loginForm: {
username: '',
password: '',
@ -133,12 +144,15 @@ export default { @@ -133,12 +144,15 @@ export default {
this.loginForm.password.trim() &&
this.loginForm.captcha.trim() &&
this.captchaImage //
}
},
},
watch: {
visible(newVal) {
if (newVal) {
this.resetForm()
this.getClientIP() // IP
this.refreshCaptcha()
}
}
@ -159,7 +173,8 @@ export default { @@ -159,7 +173,8 @@ export default {
username: this.loginForm.username,
password: this.loginForm.password,
captcha: this.loginForm.captcha,
captcha_id: this.captchaId // snake_case
captcha_id: this.captchaId, // snake_case
client_ip: this.clientIP // IP
})
//
@ -170,13 +185,80 @@ export default { @@ -170,13 +185,80 @@ export default {
this.$emit('show-message', {
type: 'success',
title: '登录成功',
content: `欢迎回来,${response.user?.username || '用户'}`
content: `欢迎回来,${response.data?.user?.username || response.user?.username || this.loginForm.username || '用户'}`
})
} catch (error) {
this.errorMessage = error.message || '登录失败,请重试'
//
this.refreshCaptcha()
//
console.log('登录错误详情:', error)
console.log('错误响应状态:', error.response?.status)
console.log('错误响应数据:', error.response?.data)
let errorMsg = '登录失败,请重试'
if (error.response) {
//
const status = error.response.status
const data = error.response.data
//
if (data.message && data.message.includes('验证码')) {
errorMsg = '验证码错误,请重新输入'
} else if (data.error && data.error.includes('验证码')) {
errorMsg = '验证码错误,请重新输入'
} else if (status === 400) {
// 400
if (data.message) {
errorMsg = data.message
} else if (data.error) {
errorMsg = data.error
} else {
errorMsg = '请求参数错误,请检查输入信息'
}
} else if (status === 401) {
// 401
errorMsg = '用户名或密码错误'
} else if (status === 422) {
// 422
if (data.message) {
errorMsg = data.message
} else {
errorMsg = '验证失败,请检查输入信息'
}
} else if (status === 423) {
//
if (data.error) {
errorMsg = data.error
} else if (data.message) {
errorMsg = data.message
} else {
errorMsg = '账户被锁定,请稍后重试'
}
} else if (status === 500) {
errorMsg = '服务器内部错误,请稍后重试'
}
} else if (error.message) {
//
if (error.message.includes('验证码')) {
errorMsg = '验证码错误,请重新输入'
} else if (error.message.includes('用户名') || error.message.includes('密码')) {
//
errorMsg = '用户名或密码错误'
} else {
errorMsg = error.message
}
}
console.log('最终显示的错误信息:', errorMsg)
this.errorMessage = errorMsg
//
this.$nextTick(() => {
console.log('错误信息已设置到界面:', this.errorMessage)
})
//
this.refreshCaptchaWithoutClearError()
} finally {
this.loading = false
}
@ -203,24 +285,57 @@ export default { @@ -203,24 +285,57 @@ export default {
}
},
async refreshCaptchaWithoutClearError() {
try {
this.captchaLoading = true
//
//
const response = await userService.getCaptcha()
// snake_case使 camelCase
this.captchaImage = response.data.captcha_image
this.captchaId = response.data.captcha_id
this.loginForm.captcha = ''
console.log('验证码获取成功(不清空错误):', response)
} catch (error) {
console.error('获取验证码失败:', error)
//
this.captchaImage = ''
this.captchaId = ''
} finally {
this.captchaLoading = false
}
},
// IP
async getClientIP() {
try {
this.clientIP = await getClientIP()
console.log('获取到客户端IP:', this.clientIP)
} catch (error) {
console.error('获取客户端IP失败:', error)
this.clientIP = '127.0.0.1'
}
},
resetForm() {
this.loginForm = {
username: '',
password: '',
captcha: ''
}
this.showPassword = false
this.errorMessage = ''
this.captchaImage = ''
this.captchaId = ''
},
togglePassword() {
this.showPassword = !this.showPassword
},
closeModal() {
this.$emit('update:visible', false)
this.resetForm()
},
handleOverlayClick() {
this.closeModal()
}
}
}
@ -318,6 +433,7 @@ export default { @@ -318,6 +433,7 @@ export default {
}
.form-group input {
width: 100%;
padding: 12px 16px;
border: 2px solid var(--border-color);
border-radius: 8px;
@ -325,6 +441,7 @@ export default { @@ -325,6 +441,7 @@ export default {
background: var(--bg-secondary);
color: var(--text-primary);
transition: all 0.2s;
box-sizing: border-box;
}
.form-group input:focus {
@ -338,6 +455,47 @@ export default { @@ -338,6 +455,47 @@ export default {
cursor: not-allowed;
}
/* 密码输入框容器 */
.password-input-container {
position: relative;
display: flex;
align-items: center;
}
.password-input-container input {
padding-right: 50px; /* 为密码切换按钮留出空间 */
}
.password-toggle-btn {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
}
.password-toggle-btn:hover {
background: var(--bg-secondary);
}
.password-toggle-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.password-icon {
font-size: 16px;
line-height: 1;
}
.captcha-container {
display: flex;
gap: 12px;
@ -451,26 +609,7 @@ export default { @@ -451,26 +609,7 @@ export default {
box-shadow: none;
}
/* 表单验证提示样式 */
.form-validation-hint {
margin-top: 8px;
padding: 8px 12px;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 12px;
color: var(--text-secondary);
text-align: center;
}
.form-validation-hint span {
display: block;
margin-bottom: 2px;
}
.form-validation-hint span:last-child {
margin-bottom: 0;
}
.loading-spinner {
width: 16px;
@ -495,6 +634,23 @@ export default { @@ -495,6 +634,23 @@ export default {
font-size: 14px;
text-align: center;
border: 1px solid var(--error-border);
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-top: 16px;
animation: errorShake 0.5s ease-in-out;
}
.error-icon {
font-size: 16px;
flex-shrink: 0;
}
@keyframes errorShake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
/* 深色主题下的错误提示样式优化 */

417
gofaster/app/src/renderer/components/MainLayout.vue

@ -47,7 +47,7 @@ @@ -47,7 +47,7 @@
<div class="user-info">
<div v-if="isLoggedIn" class="user-avatar" @click="toggleUserMenu">
<img v-if="currentUser.avatar" :src="currentUser.avatar" :alt="currentUser.name" />
<span v-else class="avatar-placeholder">{{ currentUser.name?.charAt(0) || 'U' }}</span>
<span v-else class="avatar-placeholder">{{ (currentUser.name || '').charAt(0) || 'U' }}</span>
</div>
<!-- 未登录状态显示登录按钮 -->
@ -63,6 +63,8 @@ @@ -63,6 +63,8 @@
<div class="user-details">
<div class="user-name">{{ currentUser.name || '用户' }}</div>
<div class="user-email">{{ currentUser.email || 'user@example.com' }}</div>
<div v-if="currentUser.role" class="user-role">{{ currentUser.role }}</div>
<div v-if="currentUser.lastLogin" class="user-last-login">上次登录: {{ formatLastLogin(currentUser.lastLogin) }}</div>
</div>
</div>
<div class="user-menu-items">
@ -192,7 +194,7 @@ @@ -192,7 +194,7 @@
</template>
<script>
import { ref, reactive, computed, onMounted, watch } from 'vue'
import { ref, reactive, computed, onMounted, watch, nextTick, provide } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { userService } from '@/services/userService'
import themeManager from '../utils/themeManager.js'
@ -205,13 +207,6 @@ export default { @@ -205,13 +207,6 @@ export default {
LoginModal,
Toast
},
provide() {
return {
isLoggedIn: this.isLoggedIn,
currentUser: this.currentUser,
showLoginModal: this.showLoginModal
}
},
setup() {
const router = useRouter()
const route = useRoute()
@ -226,11 +221,32 @@ export default { @@ -226,11 +221,32 @@ export default {
{ id: 'home', title: '欢迎', path: '/', closable: false }
])
const currentUser = reactive({
name: '',
email: '',
avatar: null
})
const currentUser = reactive({
id: null,
name: '',
email: '',
avatar: null,
role: '',
lastLogin: null,
lastLoginIP: '',
status: 1,
roles: []
})
//
const safeCurrentUser = computed(() => {
return {
id: currentUser.id || null,
name: currentUser.name || '',
email: currentUser.email || '',
avatar: currentUser.avatar || null,
role: currentUser.role || '',
lastLogin: currentUser.lastLogin || null,
lastLoginIP: currentUser.lastLoginIP || '',
status: currentUser.status || 1,
roles: Array.isArray(currentUser.roles) ? currentUser.roles : []
}
})
//
const isLoggedIn = ref(false)
@ -265,28 +281,39 @@ export default { @@ -265,28 +281,39 @@ export default {
}
])
const mainMenuItems = ref([
{ id: 'home', name: '欢迎', path: '/', icon: '🏠', favorite: false },
{ id: 'speed-test', name: '速度测试', path: '/speed-test', icon: '⚡', favorite: false },
{ id: 'user-management', name: '用户管理', path: '/user-management', icon: '👥', favorite: false },
{ id: 'history', name: '历史记录', path: '/history', icon: '📊', favorite: false },
{ id: 'settings', name: '用户设置', path: '/settings', icon: '⚙', favorite: false }
])
const allMenuItems = ref([
{ id: 'home', name: '欢迎', path: '/', icon: '🏠', favorite: false, requireAuth: false },
{ id: 'speed-test', name: '速度测试', path: '/speed-test', icon: '⚡', favorite: false, requireAuth: true },
{ id: 'user-management', name: '用户管理', path: '/user-management', icon: '👥', favorite: false, requireAuth: true },
{ id: 'history', name: '历史记录', path: '/history', icon: '📊', favorite: false, requireAuth: true },
{ id: 'user-profile', name: '个人资料', path: '/user-profile', icon: '👤', favorite: false, requireAuth: true },
{ id: 'settings', name: '用户设置', path: '/settings', icon: '⚙', favorite: false, requireAuth: false }
])
//
const mainMenuItems = computed(() => {
if (isLoggedIn.value) {
return allMenuItems.value
} else {
return allMenuItems.value.filter(item => !item.requireAuth)
}
})
const favoriteMenuItems = ref([])
//
const unreadCount = computed(() => messages.value.filter(m => !m.read).length)
const currentRoute = computed(() => route.path)
const breadcrumbs = computed(() => {
const path = route.path
if (path === '/') return ['欢迎']
if (path === '/user-management') return ['欢迎', '用户管理']
if (path === '/speed-test') return ['欢迎', '速度测试']
if (path === '/history') return ['欢迎', '历史记录']
if (path === '/settings') return ['欢迎', '用户设置']
return ['欢迎']
})
const breadcrumbs = computed(() => {
const path = route.path
if (path === '/') return ['欢迎']
if (path === '/user-management') return ['欢迎', '用户管理']
if (path === '/speed-test') return ['欢迎', '速度测试']
if (path === '/history') return ['欢迎', '历史记录']
if (path === '/settings') return ['欢迎', '用户设置']
if (path === '/user-profile') return ['欢迎', '个人资料']
return ['欢迎']
})
//
const toggleMessagePanel = () => {
@ -339,7 +366,7 @@ export default { @@ -339,7 +366,7 @@ export default {
}
const toggleFavorite = (itemId) => {
const item = mainMenuItems.value.find(i => i.id === itemId)
const item = allMenuItems.value.find(i => i.id === itemId)
if (item) {
item.favorite = !item.favorite
updateFavoriteMenu()
@ -347,7 +374,7 @@ export default { @@ -347,7 +374,7 @@ export default {
}
const updateFavoriteMenu = () => {
favoriteMenuItems.value = mainMenuItems.value.filter(item => item.favorite)
favoriteMenuItems.value = allMenuItems.value.filter(item => item.favorite)
}
const markAsRead = (messageId) => {
@ -357,22 +384,58 @@ export default { @@ -357,22 +384,58 @@ export default {
}
}
const formatTime = (time) => {
const now = new Date()
const diff = now - time
const minutes = Math.floor(diff / (1000 * 60))
const hours = Math.floor(diff / (1000 * 60 * 60))
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
if (minutes < 60) return `${minutes}分钟前`
if (hours < 24) return `${hours}小时前`
return `${days}天前`
}
const formatTime = (time) => {
const now = new Date()
const diff = now - time
const minutes = Math.floor(diff / (1000 * 60))
const hours = Math.floor(diff / (1000 * 60 * 60))
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
if (minutes < 60) return `${minutes}分钟前`
if (hours < 24) return `${hours}小时前`
return `${days}天前`
}
const formatLastLogin = (lastLogin) => {
if (!lastLogin) return '未知'
try {
const loginTime = new Date(lastLogin)
const now = new Date()
const diff = now - loginTime
const minutes = Math.floor(diff / (1000 * 60))
const hours = Math.floor(diff / (1000 * 60 * 60))
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
if (minutes < 60) return `${minutes}分钟前`
if (hours < 24) return `${hours}小时前`
if (days < 7) return `${days}天前`
// 7
return loginTime.toLocaleDateString('zh-CN', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})
} catch (error) {
return '未知'
}
}
const openProfile = () => {
showUserMenu.value = false
//
}
const openProfile = () => {
showUserMenu.value = false
//
addTabIfNotExists({
id: 'user-profile',
title: '个人资料',
path: '/user-profile',
closable: true
})
router.push('/user-profile')
}
const openSettings = () => {
showUserMenu.value = false
@ -407,27 +470,111 @@ export default { @@ -407,27 +470,111 @@ export default {
showUserMenu.value = false
}
const handleLoginSuccess = (response) => {
//
if (response.user) {
currentUser.name = response.user.username || response.user.name
currentUser.email = response.user.email
currentUser.avatar = response.user.avatar
}
//
isLoggedIn.value = true
//
localStorage.setItem('user', JSON.stringify(currentUser))
localStorage.setItem('isLoggedIn', 'true')
//
showLoginModalFlag.value = false
//
console.log(`欢迎回来,${currentUser.name}`)
}
const handleLoginSuccess = async (response) => {
console.log('登录成功响应:', response)
// - { code: 200, message: "", data: { token: "...", user: {...} } }
const responseData = response.data || response
const token = responseData.token
const user = responseData.user
if (!token) {
console.error('登录响应中没有token:', response)
return
}
//
isLoggedIn.value = true
// token
localStorage.setItem('isLoggedIn', 'true')
localStorage.setItem('token', token)
//
showLoginModalFlag.value = false
try {
//
const userResponse = await userService.getCurrentUser(token)
console.log('获取到的完整用户信息:', userResponse)
if (userResponse.data) {
const userData = userResponse.data
// - 使
currentUser.id = userData.id || null
currentUser.name = userData.username || '用户'
currentUser.email = userData.email || ''
currentUser.phone = userData.phone || '' //
currentUser.avatar = null
// roles
currentUser.role = userData.roles && userData.roles.length > 0 ? userData.roles[0].name : ''
currentUser.lastLogin = userData.last_login_at || null
currentUser.lastLoginAt = userData.last_login_at || null //
currentUser.lastLoginIP = userData.last_login_ip || ''
currentUser.status = userData.status || 1
currentUser.roles = userData.roles || []
// 使
const userForStorage = {
id: currentUser.id,
username: currentUser.name, //
name: currentUser.name,
email: currentUser.email,
phone: currentUser.phone,
status: currentUser.status,
roles: currentUser.roles,
last_login_at: userData.last_login_at || null,
last_login_ip: userData.last_login_ip || ''
}
localStorage.setItem('user', JSON.stringify(userForStorage))
console.log('更新后的用户信息:', currentUser)
console.log(`欢迎回来,${currentUser.name}`)
//
window.dispatchEvent(new CustomEvent('user-login-success', {
detail: { user: currentUser, token: token }
}))
}
} catch (error) {
console.error('获取用户信息失败:', error)
// 使
if (user) {
currentUser.id = user.id || null
currentUser.name = user.username || '用户'
currentUser.email = user.email || ''
currentUser.phone = user.phone || ''
currentUser.avatar = null
currentUser.role = ''
currentUser.lastLogin = null
currentUser.lastLoginIP = ''
currentUser.status = 1
currentUser.roles = []
//
const userForStorage = {
id: currentUser.id,
username: currentUser.name,
name: currentUser.name,
email: currentUser.email,
phone: currentUser.phone,
status: currentUser.status,
roles: currentUser.roles,
last_login_at: null,
last_login_ip: ''
}
localStorage.setItem('user', JSON.stringify(userForStorage))
}
}
//
nextTick(() => {
console.log('强制更新后的登录状态:', isLoggedIn.value)
console.log('强制更新后的用户信息:', currentUser)
})
}
const handleShowMessage = (message) => {
// Toast
@ -442,11 +589,26 @@ export default { @@ -442,11 +589,26 @@ export default {
const logout = async () => {
try {
await userService.logout()
//
// token
const token = localStorage.getItem('token')
if (token) {
//
await userService.logout(token)
}
} catch (error) {
console.error('调用登出接口失败:', error)
// 使
} finally {
//
currentUser.id = null
currentUser.name = ''
currentUser.email = ''
currentUser.avatar = null
currentUser.role = ''
currentUser.lastLogin = null
currentUser.lastLoginIP = ''
currentUser.status = 1
currentUser.roles = []
isLoggedIn.value = false
//
@ -457,10 +619,20 @@ export default { @@ -457,10 +619,20 @@ export default {
//
showUserMenu.value = false
//
window.dispatchEvent(new CustomEvent('user-logout', {
detail: { user: currentUser }
}))
//
router.push('/')
} catch (error) {
console.error('退出登录失败:', error)
//
handleShowMessage({
type: 'info',
title: '已退出登录',
content: '您已成功退出登录'
})
}
}
@ -517,22 +689,52 @@ export default { @@ -517,22 +689,52 @@ export default {
//
updateFavoriteMenu()
//
const savedIsLoggedIn = localStorage.getItem('isLoggedIn')
const savedUser = localStorage.getItem('user')
if (savedIsLoggedIn === 'true' && savedUser) {
try {
const userData = JSON.parse(savedUser)
Object.assign(currentUser, userData)
isLoggedIn.value = true
} catch (error) {
console.warn('解析用户信息失败:', error)
//
localStorage.removeItem('user')
localStorage.removeItem('isLoggedIn')
}
}
//
const savedIsLoggedIn = localStorage.getItem('isLoggedIn')
const savedUser = localStorage.getItem('user')
if (savedIsLoggedIn === 'true' && savedUser) {
try {
const userData = JSON.parse(savedUser)
console.log('从localStorage恢复的用户数据:', userData)
// localStorage
const defaultUser = {
id: null,
name: '',
email: '',
avatar: null,
role: '',
lastLogin: null,
lastLoginIP: '',
status: 1,
roles: []
}
//
const mappedUserData = {
id: userData.id || null,
name: userData.username || userData.name || '',
email: userData.email || '',
avatar: userData.avatar || null,
role: userData.role || '',
lastLogin: userData.last_login_at || userData.lastLogin || null,
lastLoginIP: userData.last_login_ip || userData.lastLoginIP || '',
status: userData.status || 1,
roles: userData.roles || []
}
Object.assign(currentUser, defaultUser, mappedUserData)
isLoggedIn.value = true
console.log('恢复后的用户信息:', currentUser)
} catch (error) {
console.warn('解析用户信息失败:', error)
//
localStorage.removeItem('user')
localStorage.removeItem('isLoggedIn')
}
}
//
loadAppSettings()
@ -556,6 +758,17 @@ export default { @@ -556,6 +758,17 @@ export default {
})
})
//
provide('isLoggedIn', isLoggedIn)
provide('currentUser', safeCurrentUser)
provide('showLoginModal', showLoginModal)
//
console.log('MainLayout setup completed')
console.log('isLoggedIn:', isLoggedIn)
console.log('safeCurrentUser:', safeCurrentUser)
console.log('showLoginModal:', showLoginModal)
return {
showMessagePanel,
showUserMenu,
@ -563,7 +776,7 @@ export default { @@ -563,7 +776,7 @@ export default {
showToast,
currentTab,
openTabs,
currentUser,
currentUser: safeCurrentUser,
isLoggedIn,
appSettings,
toastConfig,
@ -583,6 +796,7 @@ export default { @@ -583,6 +796,7 @@ export default {
toggleFavorite,
markAsRead,
formatTime,
formatLastLogin,
openProfile,
openSettings,
logout,
@ -837,10 +1051,27 @@ export default { @@ -837,10 +1051,27 @@ export default {
color: var(--text-primary);
}
.user-email {
font-size: 12px;
color: var(--text-secondary);
}
.user-email {
font-size: 12px;
color: var(--text-secondary);
}
.user-role {
font-size: 11px;
color: var(--accent-color);
background: rgba(25, 118, 210, 0.1);
padding: 2px 6px;
border-radius: 10px;
display: inline-block;
margin-top: 4px;
}
.user-last-login {
font-size: 10px;
color: var(--text-muted);
margin-top: 4px;
opacity: 0.8;
}
.user-menu-items {
padding: 8px 0;

185
gofaster/app/src/renderer/components/StatusBar.vue

@ -22,45 +22,68 @@ @@ -22,45 +22,68 @@
</template>
<script>
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { getFinalConfig } from '../../config/app.config.js';
export default {
name: 'StatusBar',
setup() {
const serverUrl = ref(getFinalConfig().serverUrl);
const userInfo = ref('未登录');
const errorCount = ref(0);
const appVersion = ref(process.env.VUE_APP_VERSION || '1.0.0');
const memoryUsage = ref(0);
const systemMemoryUsage = ref('N/A');
// 使 - 使
let intervalId;
onMounted(() => {
//
getInitialErrorCount();
intervalId = setInterval(() => {
updateMemoryUsage();
}, 3000); // 3
});
onUnmounted(() => {
if (intervalId) {
clearInterval(intervalId);
}
});
data() {
return {
serverUrl: getFinalConfig().serverUrl,
userInfo: '未登录',
errorCount: 0,
appVersion: process.env.VUE_APP_VERSION || '1.0.0',
memoryUsage: 0,
systemMemoryUsage: 'N/A',
intervalId: null
};
},
mounted() {
//
this.getInitialErrorCount();
this.intervalId = setInterval(() => {
this.updateMemoryUsage();
}, 3000); // 3
//
this.listenToUserChanges();
//
this.setupMainProcessListeners();
// localStorage
window.addEventListener('storage', this.updateServerUrl);
//
window.addEventListener('gofaster-settings-changed', this.updateServerUrl);
//
window.addEventListener('user-login-success', this.handleUserLogin);
//
window.addEventListener('user-logout', this.handleUserLogout);
},
beforeDestroy() {
if (this.intervalId) {
clearInterval(this.intervalId);
}
window.removeEventListener('storage', this.updateServerUrl);
window.removeEventListener('gofaster-settings-changed', this.updateServerUrl);
window.removeEventListener('user-login-success', this.handleUserLogin);
window.removeEventListener('user-logout', this.handleUserLogout);
},
methods: {
// 使
const updateMemoryUsage = async () => {
async updateMemoryUsage() {
try {
// 使 Electron IPC
if (window.electronAPI && window.electronAPI.getProcessMemoryInfo) {
const memoryInfo = await window.electronAPI.getProcessMemoryInfo();
if (memoryInfo && memoryInfo.privateBytes) {
// privateBytes 使
memoryUsage.value = (memoryInfo.privateBytes / (1024 * 1024)).toFixed(1);
this.memoryUsage = (memoryInfo.privateBytes / (1024 * 1024)).toFixed(1);
}
// 使
@ -68,70 +91,100 @@ export default { @@ -68,70 +91,100 @@ export default {
const totalGB = (memoryInfo.systemTotal / (1024 * 1024 * 1024)).toFixed(1);
const usedGB = (memoryInfo.systemUsed / (1024 * 1024 * 1024)).toFixed(1);
const usedPercent = ((memoryInfo.systemUsed / memoryInfo.systemTotal) * 100).toFixed(1);
systemMemoryUsage.value = `${usedGB}/${totalGB} GB (${usedPercent}%)`;
this.systemMemoryUsage = `${usedGB}/${totalGB} GB (${usedPercent}%)`;
}
} else {
// 退API
fallbackMemoryUsage();
this.fallbackMemoryUsage();
}
} catch (error) {
console.warn('无法获取内存使用情况:', error);
fallbackMemoryUsage();
this.fallbackMemoryUsage();
}
};
},
// 退
const fallbackMemoryUsage = () => {
fallbackMemoryUsage() {
if (window.performance && window.performance.memory) {
const memory = window.performance.memory;
// 使 usedJSHeapSize JavaScript使
memoryUsage.value = (memory.usedJSHeapSize / (1024 * 1024)).toFixed(1);
systemMemoryUsage.value = 'N/A (浏览器模式)';
this.memoryUsage = (memory.usedJSHeapSize / (1024 * 1024)).toFixed(1);
this.systemMemoryUsage = 'N/A (浏览器模式)';
} else {
memoryUsage.value = 'N/A';
systemMemoryUsage.value = 'N/A';
this.memoryUsage = 'N/A';
this.systemMemoryUsage = 'N/A';
}
};
},
//
const getInitialErrorCount = async () => {
async getInitialErrorCount() {
try {
if (window.electronAPI && window.electronAPI.getErrorLogCount) {
const result = await window.electronAPI.getErrorLogCount();
errorCount.value = result.count || 0;
this.errorCount = result.count || 0;
}
} catch (error) {
console.warn('无法获取初始错误日志计数:', error);
}
};
},
//
listenToUserChanges() {
// localStorage
const savedUser = localStorage.getItem('user');
const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true';
if (isLoggedIn && savedUser) {
try {
const user = JSON.parse(savedUser);
// 使username
this.userInfo = user.username || user.name || '已登录';
} catch (error) {
console.warn('解析用户信息失败:', error);
this.userInfo = '已登录';
}
} else {
this.userInfo = '未登录';
}
},
//
handleUserLogin(event) {
if (event.detail && event.detail.user) {
const user = event.detail.user;
this.userInfo = user.username || user.name || '已登录';
}
},
//
handleUserLogout() {
this.userInfo = '未登录';
},
//
window.electronAPI?.onStatusUpdate((event, data) => {
if (data.userInfo) userInfo.value = data.userInfo;
if (data.errorCount !== undefined) errorCount.value = data.errorCount;
});
//
window.electronAPI?.onErrorLogUpdated((event, data) => {
if (data.count !== undefined) {
errorCount.value = data.count;
setupMainProcessListeners() {
if (window.electronAPI) {
window.electronAPI.onStatusUpdate((event, data) => {
if (data.userInfo) this.userInfo = data.userInfo;
if (data.errorCount !== undefined) this.errorCount = data.errorCount;
});
window.electronAPI.onErrorLogUpdated((event, data) => {
if (data.count !== undefined) {
this.errorCount = data.count;
}
});
}
});
},
//
const updateServerUrl = () => {
updateServerUrl() {
const config = getFinalConfig();
serverUrl.value = config.serverUrl;
};
// localStorage
window.addEventListener('storage', updateServerUrl);
//
window.addEventListener('gofaster-settings-changed', updateServerUrl);
this.serverUrl = config.serverUrl;
},
//
const openLogFolder = async () => {
async openLogFolder() {
try {
if (window.electronAPI && window.electronAPI.openLogFolder) {
const result = await window.electronAPI.openLogFolder();
@ -142,17 +195,7 @@ export default { @@ -142,17 +195,7 @@ export default {
} catch (error) {
console.warn('打开日志文件夹失败:', error);
}
};
return {
serverUrl,
userInfo,
errorCount,
appVersion,
memoryUsage,
systemMemoryUsage,
openLogFolder
};
}
}
};
</script>

6
gofaster/app/src/renderer/router/index.js

@ -3,6 +3,7 @@ import MainLayout from '@/components/MainLayout.vue' @@ -3,6 +3,7 @@ import MainLayout from '@/components/MainLayout.vue'
import Home from '@/views/Home.vue'
import History from '@/views/History.vue'
import UserManagement from '@/views/UserManagement.vue'
// import UserProfile from '@/views/UserProfile.vue'
const routes = [
{
@ -33,6 +34,11 @@ const routes = [ @@ -33,6 +34,11 @@ const routes = [
path: '/settings',
name: 'Settings',
component: () => import('@/views/Settings.vue')
},
{
path: '/user-profile',
name: 'UserProfile',
component: () => import('@/views/UserProfile.vue')
}
]
}

88
gofaster/app/src/renderer/services/userService.js

@ -36,30 +36,8 @@ api.interceptors.response.use( @@ -36,30 +36,8 @@ api.interceptors.response.use(
},
error => {
console.error('API请求错误:', error)
if (error.response) {
// 服务器返回错误状态码
const { status, data } = error.response
switch (status) {
case 400:
throw new Error(data.error || '请求参数错误')
case 401:
throw new Error('未授权,请重新登录')
case 403:
throw new Error('权限不足')
case 404:
throw new Error('请求的资源不存在')
case 500:
throw new Error(data.error || '服务器内部错误')
default:
throw new Error(data.error || `请求失败 (${status})`)
}
} else if (error.request) {
// 请求已发出但没有收到响应
throw new Error('网络连接失败,请检查网络设置')
} else {
// 其他错误
throw new Error(error.message || '请求失败')
}
// 直接返回错误对象,让组件处理具体的错误信息
return Promise.reject(error)
}
)
@ -149,12 +127,23 @@ export const userService = { @@ -149,12 +127,23 @@ export const userService = {
// 用户登录
async login(credentials) {
try {
const response = await api.post('/auth/login', credentials)
// 保存token到localStorage
// 确保credentials包含client_ip
const loginData = {
username: credentials.username,
password: credentials.password,
captcha: credentials.captcha,
captcha_id: credentials.captcha_id,
client_ip: credentials.client_ip || '127.0.0.1' // 默认IP
}
const response = await api.post('/auth/login', loginData)
// 只保存token,用户信息由MainLayout处理
if (response.token) {
localStorage.setItem('token', response.token)
localStorage.setItem('user', JSON.stringify(response.user))
localStorage.setItem('isLoggedIn', 'true')
}
return response
} catch (error) {
throw error
@ -162,22 +151,55 @@ export const userService = { @@ -162,22 +151,55 @@ export const userService = {
},
// 用户登出
async logout() {
async logout(token) {
try {
await api.post('/auth/logout')
// 清除本地存储的认证信息
if (token) {
// 使用传入的token调用登出接口,发送token到请求体
await api.post('/auth/logout', { token: token }, {
headers: {
'Authorization': `Bearer ${token}`
}
})
} else {
// 如果没有传入token,从localStorage获取并使用
const storedToken = localStorage.getItem('token')
if (storedToken) {
await api.post('/auth/logout', { token: storedToken }, {
headers: {
'Authorization': `Bearer ${storedToken}`
}
})
} else {
// 如果没有token,直接调用登出接口(后端会处理)
await api.post('/auth/logout', { token: '' })
}
}
// 完全清理本地存储的认证信息
localStorage.removeItem('token')
localStorage.removeItem('user')
localStorage.removeItem('isLoggedIn')
return true
} catch (error) {
throw error
}
},
// 获取当前用户信息
async getCurrentUser() {
// 获取当前用户信息
async getCurrentUser(token = null) {
try {
const response = await api.get('/auth/me')
// 如果传入了token,使用传入的token;否则使用localStorage中的token
const authToken = token || localStorage.getItem('token')
if (!authToken) {
throw new Error('未找到认证token')
}
const response = await api.get('/auth/userinfo', {
headers: {
'Authorization': `Bearer ${authToken}`
}
})
return response
} catch (error) {
throw error

99
gofaster/app/src/renderer/utils/ipUtils.js

@ -0,0 +1,99 @@ @@ -0,0 +1,99 @@
/**
* IP地址获取工具
* 使用Electron API获取本地IP地址
*/
/**
* 获取客户端IP地址
* @returns {Promise<string>} IP地址字符串
*/
export async function getClientIP() {
try {
// 使用 Electron API 获取本地IP
if (window.electronAPI && window.electronAPI.getLocalIP) {
try {
const result = await window.electronAPI.getLocalIP()
if (result.success) {
console.log('通过Electron API获取到本地IP:', result.ip, '接口:', result.interface)
return result.ip
} else {
console.warn('Electron API获取本地IP失败:', result.message)
// 如果API调用失败,使用备用IP
if (result.fallback) {
console.log('使用备用IP:', result.fallback)
return result.fallback
}
}
} catch (error) {
console.warn('调用Electron API获取本地IP失败:', error.message)
}
} else {
console.warn('Electron API不可用,无法获取本地IP')
}
// 如果所有方法都失败,使用默认值
console.log('使用默认IP: 127.0.0.1')
return '127.0.0.1'
} catch (error) {
console.error('获取客户端IP失败:', error)
return '127.0.0.1'
}
}
/**
* 获取本地网络接口信息
* @returns {Promise<Object|null>} 网络接口信息或null
*/
export async function getLocalNetworkInfo() {
try {
if (window.electronAPI && window.electronAPI.getLocalIP) {
const result = await window.electronAPI.getLocalIP()
if (result.success) {
return {
ip: result.ip,
interface: result.interface,
netmask: result.netmask,
type: 'local'
}
}
}
return null
} catch (error) {
console.error('获取本地网络信息失败:', error)
return null
}
}
/**
* 检查是否为本地IP地址
* @param {string} ip IP地址字符串
* @returns {boolean} 是否为本地IP
*/
export function isLocalIP(ip) {
if (!ip) return false
// 检查是否为本地回环地址
if (ip === '127.0.0.1' || ip === 'localhost') return true
// 检查是否为私有IP地址范围
const privateRanges = [
/^10\./, // 10.0.0.0 - 10.255.255.255
/^172\.(1[6-9]|2[0-9]|3[0-1])\./, // 172.16.0.0 - 172.31.255.255
/^192\.168\./ // 192.168.0.0 - 192.168.255.255
]
return privateRanges.some(range => range.test(ip))
}
/**
* 获取IP地址类型描述
* @param {string} ip IP地址字符串
* @returns {string} IP地址类型描述
*/
export function getIPTypeDescription(ip) {
if (!ip) return '未知'
if (ip === '127.0.0.1') return '本地回环'
if (isLocalIP(ip)) return '本地网络'
return '公网地址'
}

169
gofaster/app/src/renderer/views/Home.vue

@ -12,12 +12,7 @@ @@ -12,12 +12,7 @@
<h2 v-else>欢迎光临请登录</h2>
<p v-if="isLoggedIn" class="subtitle">今天是 {{ currentDate }}您有 {{ todoList.length }} 个待办事项</p>
<p v-else class="subtitle">登录后享受更多功能</p>
</div>
<div v-if="!isLoggedIn" class="login-section">
<button class="login-btn" @click="showLoginModalHandler">
<i class="icon">🔐</i> 登录
</button>
</div>
</div>
</div>
</div>
</section>
@ -130,6 +125,15 @@ export default { @@ -130,6 +125,15 @@ export default {
db: null //
}
},
computed: {
//
computedIsLoggedIn() {
return this.isLoggedIn
},
computedCurrentUser() {
return this.currentUser
}
},
methods: {
updateCurrentDate() {
const now = new Date();
@ -199,64 +203,64 @@ export default { @@ -199,64 +203,64 @@ export default {
});
this.$router.push('/settings');
},
exportData() {
console.log('导出数据');
},
showLoginModalHandler() {
//
if (this.showLoginModal) {
this.showLoginModal();
}
},
async addTodo() {
const newTodo = {
id: Date.now(),
text: '新的待办事项',
completed: false,
date: '今天'
};
this.todoList.unshift(newTodo);
//
try {
if (this.db && this.db.data) {
this.db.data.todoList = this.todoList;
await this.db.write();
}
} catch (error) {
console.error('保存待办事项失败:', error);
}
},
exportData() {
console.log('导出数据');
async toggleTodo(id) {
const todo = this.todoList.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
//
try {
if (this.db && this.db.data) {
this.db.data.todoList = this.todoList;
await this.db.write();
}
} catch (error) {
console.error('更新待办事项失败:', error);
}
}
},
showLoginModalHandler() {
//
if (this.showLoginModal) {
this.showLoginModal();
}
},
async addTodo() {
const newTodo = {
id: Date.now(),
text: '新的待办事项',
completed: false,
date: '今天'
};
this.todoList.unshift(newTodo);
//
try {
if (this.db && this.db.data) {
this.db.data.todoList = this.todoList;
await this.db.write();
}
} catch (error) {
console.error('保存待办事项失败:', error);
}
},
async toggleTodo(id) {
const todo = this.todoList.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
//
try {
if (this.db && this.db.data) {
this.db.data.todoList = this.todoList;
await this.db.write();
}
} catch (error) {
console.error('更新待办事项失败:', error);
}
}
},
async deleteTodo(id) {
this.todoList = this.todoList.filter(t => t.id !== id);
//
try {
if (this.db && this.db.data) {
this.db.data.todoList = this.todoList;
await this.db.write();
}
} catch (error) {
console.error('删除待办事项失败:', error);
}
}
async deleteTodo(id) {
this.todoList = this.todoList.filter(t => t.id !== id);
//
try {
if (this.db && this.db.data) {
this.db.data.todoList = this.todoList;
await this.db.write();
}
} catch (error) {
console.error('删除待办事项失败:', error);
}
}
},
async mounted() {
this.updateCurrentDate();
@ -275,13 +279,44 @@ export default { @@ -275,13 +279,44 @@ export default {
console.error('数据库初始化失败:', error);
}
//
this.$watch('isLoggedIn', (newVal) => {
if (newVal && this.currentUser) {
this.userInfo.name = this.currentUser.name || this.userInfo.name;
this.userInfo.email = this.currentUser.email || this.userInfo.email;
// - 使
this.$watch('computedIsLoggedIn', (newVal) => {
console.log('Home.vue - 登录状态变化:', newVal);
if (newVal && this.computedCurrentUser) {
console.log('Home.vue - 当前用户信息:', this.computedCurrentUser);
this.userInfo.name = this.computedCurrentUser.name || this.userInfo.name;
this.userInfo.email = this.computedCurrentUser.email || this.userInfo.email;
console.log('Home.vue - 更新后的用户信息:', this.userInfo);
}
}, { immediate: true });
// - 使
this.$watch('computedCurrentUser', (newUser) => {
console.log('Home.vue - 用户信息变化:', newUser);
if (newUser && newUser.name) {
this.userInfo.name = newUser.name;
this.userInfo.email = newUser.email || this.userInfo.email;
console.log('Home.vue - 用户信息更新后:', this.userInfo);
}
}, { immediate: true, deep: true });
//
window.addEventListener('user-login-success', (event) => {
console.log('Home.vue - 收到登录成功事件:', event.detail);
const { user } = event.detail;
if (user) {
this.userInfo.name = user.name || this.userInfo.name;
this.userInfo.email = user.email || this.userInfo.email;
console.log('Home.vue - 事件更新后的用户信息:', this.userInfo);
}
});
//
window.addEventListener('user-logout', () => {
console.log('Home.vue - 收到登出事件');
this.userInfo.name = '用户';
this.userInfo.email = 'user@example.com';
});
}
}
</script>

114
gofaster/app/vue.config.js

@ -1,10 +1,26 @@ @@ -1,10 +1,26 @@
const path = require('path')
const { defineConfig } = require('@vue/cli-service')
// 强制禁用所有eval相关功能
// 设置环境变量
process.env.VUE_CLI_BABEL_TRANSPILE_MODULES = 'false'
process.env.VUE_CLI_MODERN_BUILD = 'false'
// 设置日志级别
process.env.VUE_CLI_LOG_LEVEL = 'info'
process.env.VUE_CLI_DEBUG = 'true'
// 设置编码 - 增强版
process.env.LANG = 'zh_CN.UTF-8'
process.env.LC_ALL = 'zh_CN.UTF-8'
process.env.NODE_OPTIONS = '--max-old-space-size=4096'
process.env.CHROME_BIN = process.env.CHROME_BIN || 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
// 强制设置控制台编码
if (process.platform === 'win32') {
process.env.PYTHONIOENCODING = 'utf-8'
process.env.PYTHONLEGACYWINDOWSSTDIO = 'utf-8'
}
const appRoot = path.resolve(__dirname)
module.exports = defineConfig({
@ -87,18 +103,58 @@ module.exports = defineConfig({ @@ -87,18 +103,58 @@ module.exports = defineConfig({
},
devServer: {
hot: false,
liveReload: false,
hot: true, // 启用热重载
liveReload: true, // 启用实时重载
allowedHosts: "all",
static: {
directory: path.join(__dirname, 'dist/renderer'),
watch: false
watch: true // 启用文件监听
},
compress: false,
proxy: null,
// 安全配置
headers: {
"Content-Security-Policy": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' ws: wss:; object-src 'none'; frame-src 'none';"
},
// 增强日志配置 - 解决中文乱码
client: {
logging: 'info',
overlay: {
errors: true,
warnings: false
},
progress: true
},
// 详细日志 - 增强编码支持
logLevel: 'info',
stats: {
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false,
entrypoints: false,
warnings: true,
errors: true,
errorDetails: true,
reasons: true,
timings: true,
builtAt: true,
version: true,
hash: true,
assets: true,
assetsSort: 'field',
chunksSort: 'field',
modulesSort: 'field'
},
// 编码设置
setupMiddlewares: (middlewares, devServer) => {
// 强制设置响应头编码
devServer.app.use((req, res, next) => {
res.setHeader('Content-Type', 'text/html; charset=utf-8')
next()
})
return middlewares
}
},
@ -139,10 +195,58 @@ module.exports = defineConfig({ @@ -139,10 +195,58 @@ module.exports = defineConfig({
config.output.chunkFilename('[name].js')
config.output.globalObject('globalThis')
// 禁用动态导入的eval
// 配置热重载插件
config.plugin('hot').use(require('webpack').HotModuleReplacementPlugin, [{
multiStep: false,
fullBuildTimeout: 200
}])
// 确保热重载正常工作
config.plugin('hmr').use(require('webpack').HotModuleReplacementPlugin)
// 增强日志配置
config.stats({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false,
entrypoints: false,
warnings: true,
warningsFilter: /export.*was not found in/,
errors: true,
errorDetails: true,
reasons: true,
usedExports: false,
providedExports: false,
optimizationBailout: false,
source: false,
publicPath: false,
timings: true,
builtAt: true,
version: true,
hash: true
})
// 添加进度条
config.plugin('progress').use(require('webpack/lib/ProgressPlugin'), [{
activeModules: false,
entries: true,
handler(percentage, message, ...args) {
if (percentage === 0) {
console.log('🚀 开始构建...')
} else if (percentage === 1) {
console.log('✅ 构建完成!')
} else if (percentage % 0.1 === 0) {
console.log(`📊 构建进度: ${Math.round(percentage * 100)}%`)
}
},
modules: false,
modulesCount: 5000,
profile: false,
dependencies: false,
dependenciesCount: 10000,
percentBy: null
}])
}
})

18
gofaster/backend/internal/auth/model/auth.go

@ -8,6 +8,7 @@ type LoginRequest struct { @@ -8,6 +8,7 @@ type LoginRequest struct {
Password string `json:"password" binding:"required" validate:"required,min=6,max=100"`
Captcha string `json:"captcha" binding:"required" validate:"required,len=4"`
CaptchaID string `json:"captcha_id" binding:"required" validate:"required"`
ClientIP string `json:"client_ip"` // 客户端IP地址
}
// LoginResponse 登录响应
@ -33,9 +34,20 @@ type UserInfo struct { @@ -33,9 +34,20 @@ type UserInfo struct {
// RoleInfo 角色信息
type RoleInfo struct {
ID uint `json:"id"`
Name string `json:"name"`
Code string `json:"code"`
ID uint `json:"id"`
Name string `json:"name"`
Code string `json:"code"`
Description string `json:"description"`
Permissions []PermissionInfo `json:"permissions"`
}
// PermissionInfo 权限信息
type PermissionInfo struct {
ID uint `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Resource string `json:"resource"`
Action string `json:"action"`
}
// RefreshTokenRequest 刷新令牌请求

8
gofaster/backend/internal/auth/model/user.go

@ -21,7 +21,13 @@ type User struct { @@ -21,7 +21,13 @@ type User struct {
// IsLocked 检查用户是否被锁定
func (u *User) IsLocked() bool {
if u.Status == 3 || u.LockedAt == nil {
// 如果状态是锁定(3),直接返回true
if u.Status == 3 {
return true
}
// 如果没有锁定时间,说明没有被锁定
if u.LockedAt == nil {
return false
}

24
gofaster/backend/internal/auth/repository/user_repo.go

@ -91,11 +91,24 @@ func (r *userRepository) List(ctx context.Context, offset, limit int) ([]*model. @@ -91,11 +91,24 @@ func (r *userRepository) List(ctx context.Context, offset, limit int) ([]*model.
// IncrementPasswordError 增加密码错误次数
func (r *userRepository) IncrementPasswordError(ctx context.Context, userID uint) error {
return r.db.WithContext(ctx).Model(&model.User{}).
Where("id = ?", userID).
Updates(map[string]interface{}{
"password_error_count": gorm.Expr("password_error_count + 1"),
}).Error
// 先获取当前用户信息
var user model.User
if err := r.db.WithContext(ctx).First(&user, userID).Error; err != nil {
return err
}
// 增加错误次数
user.PasswordErrorCount++
// 如果达到5次错误,锁定账户
if user.PasswordErrorCount >= 5 {
user.Status = 3 // 设置为锁定状态
now := r.db.NowFunc()
user.LockedAt = &now
}
// 保存更新
return r.db.WithContext(ctx).Save(&user).Error
}
// ResetPasswordError 重置密码错误次数
@ -124,6 +137,7 @@ func (r *userRepository) GetUserWithRoles(ctx context.Context, userID uint) (*mo @@ -124,6 +137,7 @@ func (r *userRepository) GetUserWithRoles(ctx context.Context, userID uint) (*mo
var user model.User
err := r.db.WithContext(ctx).
Preload("Roles").
Preload("Roles.Permissions").
First(&user, userID).Error
if err != nil {
return nil, err

111
gofaster/backend/internal/auth/service/auth_service.go

@ -2,7 +2,6 @@ package service @@ -2,7 +2,6 @@ package service
import (
"context"
"crypto/rand"
"encoding/base64"
"fmt"
mathrand "math/rand"
@ -82,7 +81,8 @@ func (s *authService) Login(ctx context.Context, req *model.LoginRequest, client @@ -82,7 +81,8 @@ func (s *authService) Login(ctx context.Context, req *model.LoginRequest, client
return nil, fmt.Errorf("系统错误,请稍后重试")
}
if err := s.userRepo.UpdateLastLogin(ctx, user.ID, clientIP); err != nil {
// 更新最后登录时间和IP
if err := s.userRepo.UpdateLastLogin(ctx, user.ID, req.ClientIP); err != nil {
// 登录信息更新失败不影响登录流程
fmt.Printf("更新登录信息失败: %v\n", err)
}
@ -179,15 +179,12 @@ func (s *authService) RefreshToken(ctx context.Context, refreshToken string) (*m @@ -179,15 +179,12 @@ func (s *authService) RefreshToken(ctx context.Context, refreshToken string) (*m
// GenerateCaptcha 生成验证码
func (s *authService) GenerateCaptcha(ctx context.Context) (*model.CaptchaResponse, error) {
// 生成随机验证码ID
captchaID, err := s.generateRandomID()
if err != nil {
return nil, fmt.Errorf("生成验证码ID失败: %w", err)
}
// 生成4位随机验证码
captchaText := s.generateRandomCaptcha(4)
// 使用验证码文本的base64编码作为ID
captchaID := base64.StdEncoding.EncodeToString([]byte(captchaText))
// 设置5分钟过期时间
expiresAt := time.Now().Add(5 * time.Minute)
@ -196,7 +193,7 @@ func (s *authService) GenerateCaptcha(ctx context.Context) (*model.CaptchaRespon @@ -196,7 +193,7 @@ func (s *authService) GenerateCaptcha(ctx context.Context) (*model.CaptchaRespon
return nil, fmt.Errorf("保存验证码失败: %w", err)
}
// 生成验证码图片(这里简化处理,实际应该生成图片)
// 生成验证码图片
captchaImage := s.generateCaptchaImage(captchaText)
return &model.CaptchaResponse{
@ -236,10 +233,24 @@ func (s *authService) GetUserInfo(ctx context.Context, userID uint) (*model.User @@ -236,10 +233,24 @@ func (s *authService) GetUserInfo(ctx context.Context, userID uint) (*model.User
func (s *authService) buildUserInfo(user *model.User) *model.UserInfo {
roles := make([]model.RoleInfo, 0, len(user.Roles))
for _, role := range user.Roles {
// 构建权限信息
permissions := make([]model.PermissionInfo, 0, len(role.Permissions))
for _, perm := range role.Permissions {
permissions = append(permissions, model.PermissionInfo{
ID: perm.ID,
Name: perm.Name,
Description: perm.Description,
Resource: perm.Resource,
Action: perm.Action,
})
}
roles = append(roles, model.RoleInfo{
ID: role.ID,
Name: role.Name,
Code: role.Code,
ID: role.ID,
Name: role.Name,
Code: role.Code,
Description: role.Description,
Permissions: permissions,
})
}
@ -255,15 +266,6 @@ func (s *authService) buildUserInfo(user *model.User) *model.UserInfo { @@ -255,15 +266,6 @@ func (s *authService) buildUserInfo(user *model.User) *model.UserInfo {
}
}
// generateRandomID 生成随机ID
func (s *authService) generateRandomID() (string, error) {
b := make([]byte, 16)
if _, err := rand.Read(b); err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil
}
// generateRandomCaptcha 生成随机验证码
func (s *authService) generateRandomCaptcha(length int) string {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
@ -274,19 +276,70 @@ func (s *authService) generateRandomCaptcha(length int) string { @@ -274,19 +276,70 @@ func (s *authService) generateRandomCaptcha(length int) string {
return string(result)
}
// generateCaptchaImage 生成验证码图片(简化版本,返回Base64编码
// generateCaptchaImage 生成验证码图片(增强版,包含干扰元素
func (s *authService) generateCaptchaImage(text string) string {
// 创建一个简单的文本图片作为验证码
// 使用HTML5 Canvas风格的文本渲染
width := 120
height := 40
// 创建一个简单的SVG图片,包含验证码文本
// 生成随机干扰线
interferenceLines := ""
for i := 0; i < 3; i++ {
x1 := mathrand.Intn(width)
y1 := mathrand.Intn(height)
x2 := mathrand.Intn(width)
y2 := mathrand.Intn(height)
color := fmt.Sprintf("#%06x", mathrand.Intn(0xFFFFFF))
interferenceLines += fmt.Sprintf(`<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="%s" stroke-width="1" opacity="0.3"/>`, x1, y1, x2, y2, color)
}
// 生成随机干扰点
interferenceDots := ""
for i := 0; i < 20; i++ {
x := mathrand.Intn(width)
y := mathrand.Intn(height)
color := fmt.Sprintf("#%06x", mathrand.Intn(0xFFFFFF))
interferenceDots += fmt.Sprintf(`<circle cx="%d" cy="%d" r="1" fill="%s" opacity="0.4"/>`, x, y, color)
}
// 生成随机干扰圆
interferenceCircles := ""
for i := 0; i < 5; i++ {
cx := mathrand.Intn(width)
cy := mathrand.Intn(height)
r := 2 + mathrand.Intn(3)
color := fmt.Sprintf("#%06x", mathrand.Intn(0xFFFFFF))
interferenceCircles += fmt.Sprintf(`<circle cx="%d" cy="%d" r="%d" fill="%s" opacity="0.2"/>`, cx, cy, r, color)
}
// 为每个字符添加随机旋转和颜色
textElements := ""
charWidth := width / (len(text) + 1)
for i, char := range text {
x := charWidth * (i + 1)
y := height/2 + mathrand.Intn(6) - 3
rotation := mathrand.Intn(20) - 10
color := fmt.Sprintf("#%06x", mathrand.Intn(0x666666)+0x333333)
fontSize := 18 + mathrand.Intn(8)
textElements += fmt.Sprintf(`<text x="%d" y="%d" font-family="Arial, sans-serif" font-size="%d" font-weight="bold"
fill="%s" text-anchor="middle" dominant-baseline="middle" transform="rotate(%d %d %d)">%c</text>`,
x, y, fontSize, color, rotation, x, y, char)
}
// 创建增强版SVG图片
svg := fmt.Sprintf(`<svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg">
<rect width="%d" height="%d" fill="#f0f0f0"/>
<text x="%d" y="%d" font-family="Arial, sans-serif" font-size="24" font-weight="bold"
fill="#333" text-anchor="middle" dominant-baseline="middle">%s</text>
</svg>`, width, height, width, height, width/2, height/2, text)
<defs>
<filter id="noise" x="0%%" y="0%%" width="100%%" height="100%%">
<feTurbulence type="fractalNoise" baseFrequency="0.8" numOctaves="4" stitchTiles="stitch"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
</filter>
</defs>
<rect width="%d" height="%d" fill="#f8f9fa"/>
<rect width="%d" height="%d" fill="url(#noise)" opacity="0.1"/>
%s
%s
%s
%s
</svg>`, width, height, width, height, width, height, interferenceLines, interferenceDots, interferenceCircles, textElements)
// 将SVG转换为Base64
return fmt.Sprintf("data:image/svg+xml;base64,%s", base64.StdEncoding.EncodeToString([]byte(svg)))

BIN
gofaster/backend/gofaster.exe → gofaster/backend/main.exe

Binary file not shown.

BIN
gofaster/backend/tmp/main.exe

Binary file not shown.

165
gofaster/dev-full.ps1

@ -1,28 +1,157 @@ @@ -1,28 +1,157 @@
Write-Host "🚀 Starting GoFaster Full Stack Development Environment..." -ForegroundColor Cyan
# GoFaster Full Stack Development Environment
param(
[switch]$Debug,
[switch]$Watch,
[switch]$BackendOnly,
[switch]$FrontendOnly
)
# Set console encoding
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
# Set environment variables
$env:VUE_CLI_BABEL_TRANSPILE_MODULES = "false"
$env:VUE_CLI_MODERN_BUILD = "false"
$env:VUE_CLI_LOG_LEVEL = "info"
$env:VUE_CLI_DEBUG = "true"
$env:LANG = "zh_CN.UTF-8"
$env:LC_ALL = "zh_CN.UTF-8"
$env:NODE_OPTIONS = "--max-old-space-size=4096"
# Display startup information
Write-Host "Starting GoFaster Full Stack Development Environment..." -ForegroundColor Cyan
Write-Host "Encoding: UTF-8" -ForegroundColor Green
Write-Host "Log Level: INFO" -ForegroundColor Green
Write-Host "Hot Reload: Enabled" -ForegroundColor Green
Write-Host ""
Write-Host "Frontend: Electron + Vue.js with Hot Reload" -ForegroundColor Green
Write-Host "Backend: Go + Gin with Hot Reload" -ForegroundColor Green
Write-Host "Frontend: Electron + Vue.js with Enhanced Hot Reload" -ForegroundColor Green
Write-Host "Backend: Go + Gin with Air Hot Reload" -ForegroundColor Green
Write-Host ""
Write-Host "Press Ctrl+C to stop all services" -ForegroundColor Yellow
# Check run mode
if ($Debug) {
Write-Host "Debug mode starting..." -ForegroundColor Magenta
$env:DEBUG = "*"
} elseif ($Watch) {
Write-Host "Watch mode starting..." -ForegroundColor Blue
} else {
Write-Host "Standard mode starting..." -ForegroundColor Green
}
Write-Host ""
# 启动后端热加载(后台运行)
Write-Host "Starting Backend with Hot Reload..." -ForegroundColor Green
Start-Process powershell -ArgumentList "-NoExit", "-Command", "cd backend; air" -WindowStyle Normal
# Check dependencies
Write-Host "Checking frontend dependencies..." -ForegroundColor Yellow
if (-not (Test-Path "app/node_modules")) {
Write-Host "Frontend dependencies not installed, installing..." -ForegroundColor Yellow
Set-Location "app"
npm install
if ($LASTEXITCODE -ne 0) {
Write-Host "Frontend dependency installation failed" -ForegroundColor Red
exit 1
}
Set-Location ".."
}
# 等待2秒让后端启动
Start-Sleep -Seconds 2
# Install cross-env if not exists
$crossEnvInstalled = npm list cross-env 2>$null -Path "app"
if (-not $crossEnvInstalled) {
Write-Host "Installing cross-env..." -ForegroundColor Yellow
Set-Location "app"
npm install --save-dev cross-env
Set-Location ".."
}
# 启动前端热加载(后台运行)
Write-Host "Starting Frontend with Hot Reload..." -ForegroundColor Green
Start-Process powershell -ArgumentList "-NoExit", "-Command", "cd app; npm run dev" -WindowStyle Normal
Write-Host "Frontend dependency check completed" -ForegroundColor Green
Write-Host ""
# Check backend dependencies
Write-Host "Checking backend dependencies..." -ForegroundColor Yellow
if (-not (Test-Path "backend/go.mod")) {
Write-Host "Backend Go module not found" -ForegroundColor Red
exit 1
}
# Check if air is installed
try {
$airVersion = air -v 2>$null
if (-not $airVersion) {
Write-Host "Air not installed, installing..." -ForegroundColor Yellow
go install github.com/air-verse/air@latest
}
} catch {
Write-Host "Air not installed, installing..." -ForegroundColor Yellow
go install github.com/air-verse/air@latest
}
Write-Host "Backend dependency check completed" -ForegroundColor Green
Write-Host ""
Write-Host "✅ Both services started successfully!" -ForegroundColor Green
Write-Host "Frontend: http://localhost:3000 (Electron app)" -ForegroundColor Cyan
Write-Host "Backend: http://localhost:8080" -ForegroundColor Cyan
Write-Host "Swagger: http://localhost:8080/swagger/index.html" -ForegroundColor Cyan
# Select startup mode
$frontendScript = if ($Debug) { "npm run dev:debug" } elseif ($Watch) { "npm run dev:watch" } else { "npm run dev" }
$backendScript = "air"
# Start services
if (-not $FrontendOnly) {
Write-Host "Starting backend hot reload..." -ForegroundColor Green
$backendProcess = Start-Process powershell -ArgumentList "-NoExit", "-Command", "cd backend; $backendScript" -WindowStyle Normal -PassThru
Write-Host "Backend started (PID: $($backendProcess.Id))" -ForegroundColor Green
# Wait for backend to start
Write-Host "Waiting for backend to start..." -ForegroundColor Yellow
Start-Sleep -Seconds 3
# Check if backend started successfully
try {
$response = Invoke-WebRequest -Uri "http://localhost:8080/health" -Method GET -TimeoutSec 5 -ErrorAction Stop
if ($response.StatusCode -eq 200) {
Write-Host "Backend started successfully" -ForegroundColor Green
}
} catch {
Write-Host "Backend might still be starting, continuing with frontend..." -ForegroundColor Yellow
}
}
if (-not $BackendOnly) {
Write-Host "Starting frontend hot reload..." -ForegroundColor Green
$frontendProcess = Start-Process powershell -ArgumentList "-NoExit", "-Command", "cd app; $frontendScript" -WindowStyle Normal -PassThru
Write-Host "Frontend started (PID: $($frontendProcess.Id))" -ForegroundColor Green
}
Write-Host ""
Write-Host "💡 Code changes will automatically trigger rebuilds and reloads!" -ForegroundColor Yellow
Write-Host "Press any key to exit this launcher..."
Write-Host "Service startup completed!" -ForegroundColor Green
if (-not $BackendOnly) {
Write-Host "Backend: http://localhost:8080" -ForegroundColor Cyan
Write-Host "Swagger: http://localhost:8080/swagger/index.html" -ForegroundColor Cyan
}
if (-not $FrontendOnly) {
Write-Host "Frontend: Electron app (auto-reload)" -ForegroundColor Cyan
}
Write-Host ""
Write-Host "Usage:" -ForegroundColor Yellow
Write-Host " - Use -Debug parameter to enable detailed debug info" -ForegroundColor White
Write-Host " - Use -Watch parameter to enable file watching" -ForegroundColor White
Write-Host " - Use -BackendOnly to start only backend" -ForegroundColor White
Write-Host " - Use -FrontendOnly to start only frontend" -ForegroundColor White
Write-Host " - Press Ctrl+C to stop services" -ForegroundColor White
Write-Host ""
# Display process information
if (-not $BackendOnly -and -not $FrontendOnly) {
Write-Host "Process Information:" -ForegroundColor Yellow
Write-Host " Backend PID: $($backendProcess.Id)" -ForegroundColor White
Write-Host " Frontend PID: $($frontendProcess.Id)" -ForegroundColor White
Write-Host ""
Write-Host "Management Commands:" -ForegroundColor Yellow
Write-Host " Stop Backend: Stop-Process -Id $($backendProcess.Id)" -ForegroundColor White
Write-Host " Stop Frontend: Stop-Process -Id $($frontendProcess.Id)" -ForegroundColor White
Write-Host " Stop All: Get-Process | Where-Object {$_.ProcessName -eq 'powershell'} | Stop-Process" -ForegroundColor White
}
Write-Host "Code changes will automatically trigger rebuilds and reloads!" -ForegroundColor Yellow
Write-Host "Press any key to exit..."
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

76
gofaster/start-dev.bat

@ -0,0 +1,76 @@ @@ -0,0 +1,76 @@
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
echo ========================================
echo GoFaster 开发环境快速启动
echo ========================================
echo.
REM 检查 PowerShell 执行策略
powershell -Command "Get-ExecutionPolicy" >nul 2>&1
if errorlevel 1 (
echo [INFO] 正在设置 PowerShell 执行策略...
powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser -Force"
)
echo.
echo 启动选项:
echo 1. 全栈启动 (前后端)
echo 2. 全栈启动 (调试模式)
echo 3. 全栈启动 (监听模式)
echo 4. 仅启动后端
echo 5. 仅启动前端
echo 6. 退出
echo.
set /p choice="请选择 (1-6): "
if "%choice%"=="1" (
echo [INFO] 启动全栈开发环境...
powershell -ExecutionPolicy Bypass -File dev-full.ps1
if errorlevel 1 (
echo [ERROR] 启动失败,请检查错误信息
pause
)
) else if "%choice%"=="2" (
echo [INFO] 启动全栈开发环境 (调试模式)...
powershell -ExecutionPolicy Bypass -File dev-full.ps1 -Debug
if errorlevel 1 (
echo [ERROR] 启动失败,请检查错误信息
pause
)
) else if "%choice%"=="3" (
echo [INFO] 启动全栈开发环境 (监听模式)...
powershell -ExecutionPolicy Bypass -File dev-full.ps1 -Watch
if errorlevel 1 (
echo [ERROR] 启动失败,请检查错误信息
pause
)
) else if "%choice%"=="4" (
echo [INFO] 仅启动后端...
powershell -ExecutionPolicy Bypass -File dev-full.ps1 -BackendOnly
if errorlevel 1 (
echo [ERROR] 启动失败,请检查错误信息
pause
)
) else if "%choice%"=="5" (
echo [INFO] 仅启动前端...
powershell -ExecutionPolicy Bypass -File dev-full.ps1 -FrontendOnly
if errorlevel 1 (
echo [ERROR] 启动失败,请检查错误信息
pause
)
) else if "%choice%"=="6" (
echo [INFO] 再见!
pause
exit /b 0
) else (
echo [ERROR] 无效选择,请重新运行脚本
pause
exit /b 1
)
echo.
echo 按任意键退出...
pause >nul

105
gofaster/start-dev.ps1

@ -0,0 +1,105 @@ @@ -0,0 +1,105 @@
# GoFaster 开发环境启动脚本
param(
[switch]$Help
)
# 设置控制台编码
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
# 显示帮助信息
if ($Help) {
Write-Host "使用方法:" -ForegroundColor Cyan
Write-Host " .\start-dev.ps1 # 显示菜单" -ForegroundColor White
Write-Host " .\start-dev.ps1 -Help # 显示此帮助" -ForegroundColor White
Write-Host " .\dev-full.ps1 # 直接启动全栈" -ForegroundColor White
Write-Host " .\dev-full.ps1 -Debug # 调试模式启动" -ForegroundColor White
Write-Host " .\dev-full.ps1 -Watch # 监听模式启动" -ForegroundColor White
exit 0
}
# 显示启动菜单
function Show-Menu {
Clear-Host
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " GoFaster 开发环境启动菜单" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "启动选项:" -ForegroundColor Yellow
Write-Host " 1. 全栈启动 (前后端)" -ForegroundColor White
Write-Host " 2. 全栈启动 (调试模式)" -ForegroundColor White
Write-Host " 3. 全栈启动 (监听模式)" -ForegroundColor White
Write-Host " 4. 仅启动后端" -ForegroundColor White
Write-Host " 5. 仅启动前端" -ForegroundColor White
Write-Host " 6. 退出" -ForegroundColor White
Write-Host ""
}
# 主菜单循环
do {
Show-Menu
$choice = Read-Host "请选择 (1-6)"
switch ($choice) {
"1" {
Write-Host "[INFO] 启动全栈开发环境..." -ForegroundColor Green
try {
& ".\dev-full.ps1"
} catch {
Write-Host "[ERROR] 启动失败: $($_.Exception.Message)" -ForegroundColor Red
Read-Host "按回车键继续..."
}
}
"2" {
Write-Host "[INFO] 启动全栈开发环境 (调试模式)..." -ForegroundColor Green
try {
& ".\dev-full.ps1" -Debug
} catch {
Write-Host "[ERROR] 启动失败: $($_.Exception.Message)" -ForegroundColor Red
Read-Host "按回车键继续..."
}
}
"3" {
Write-Host "[INFO] 启动全栈开发环境 (监听模式)..." -ForegroundColor Green
try {
& ".\dev-full.ps1" -Watch
} catch {
Write-Host "[ERROR] 启动失败: $($_.Exception.Message)" -ForegroundColor Red
Read-Host "按回车键继续..."
}
}
"4" {
Write-Host "[INFO] 仅启动后端..." -ForegroundColor Green
try {
& ".\dev-full.ps1" -BackendOnly
} catch {
Write-Host "[ERROR] 启动失败: $($_.Exception.Message)" -ForegroundColor Red
Read-Host "按回车键继续..."
}
}
"5" {
Write-Host "[INFO] 仅启动前端..." -ForegroundColor Green
try {
& ".\dev-full.ps1" -FrontendOnly
} catch {
Write-Host "[ERROR] 启动失败: $($_.Exception.Message)" -ForegroundColor Red
Read-Host "按回车键继续..."
}
}
"6" {
Write-Host "[INFO] 再见!" -ForegroundColor Green
exit 0
}
default {
Write-Host "[ERROR] 无效选择,请重新选择" -ForegroundColor Red
Start-Sleep -Seconds 2
}
}
if ($choice -match "^[1-5]$") {
$continue = Read-Host "是否返回主菜单? (y/n)"
if ($continue -eq "n" -or $continue -eq "N") {
break
}
}
} while ($true)

47
gofaster/start-simple.bat

@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
@echo off
title GoFaster Development Environment
echo ========================================
echo GoFaster Development Environment
echo ========================================
echo.
echo Starting options:
echo 1. Full Stack (Frontend + Backend)
echo 2. Full Stack (Debug Mode)
echo 3. Full Stack (Watch Mode)
echo 4. Backend Only
echo 5. Frontend Only
echo 6. Exit
echo.
set /p choice="Please select (1-6): "
if "%choice%"=="1" (
echo [INFO] Starting full stack development environment...
powershell -ExecutionPolicy Bypass -File dev-full.ps1
) else if "%choice%"=="2" (
echo [INFO] Starting full stack development environment (Debug Mode)...
powershell -ExecutionPolicy Bypass -File dev-full.ps1 -Debug
) else if "%choice%"=="3" (
echo [INFO] Starting full stack development environment (Watch Mode)...
powershell -ExecutionPolicy Bypass -File dev-full.ps1 -Watch
) else if "%choice%"=="4" (
echo [INFO] Starting backend only...
powershell -ExecutionPolicy Bypass -File dev-full.ps1 -BackendOnly
) else if "%choice%"=="5" (
echo [INFO] Starting frontend only...
powershell -ExecutionPolicy Bypass -File dev-full.ps1 -FrontendOnly
) else if "%choice%"=="6" (
echo [INFO] Goodbye!
pause
exit /b 0
) else (
echo [ERROR] Invalid choice, please run the script again
pause
exit /b 1
)
echo.
echo Press any key to exit...
pause >nul

45
gofaster/start.ps1

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
# GoFaster 开发环境启动脚本
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " GoFaster Development Environment" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "选择启动模式:" -ForegroundColor Yellow
Write-Host "1. 全栈启动 (前后端)" -ForegroundColor White
Write-Host "2. 全栈启动 (调试模式)" -ForegroundColor White
Write-Host "3. 全栈启动 (监听模式)" -ForegroundColor White
Write-Host "4. 仅启动后端" -ForegroundColor White
Write-Host "5. 仅启动前端" -ForegroundColor White
Write-Host ""
$choice = Read-Host "请输入选择 (1-5)"
switch ($choice) {
"1" {
Write-Host "启动全栈开发环境..." -ForegroundColor Green
& ".\dev-full.ps1"
}
"2" {
Write-Host "启动全栈开发环境 (调试模式)..." -ForegroundColor Green
& ".\dev-full.ps1" -Debug
}
"3" {
Write-Host "启动全栈开发环境 (监听模式)..." -ForegroundColor Green
& ".\dev-full.ps1" -Watch
}
"4" {
Write-Host "仅启动后端..." -ForegroundColor Green
& ".\dev-full.ps1" -BackendOnly
}
"5" {
Write-Host "仅启动前端..." -ForegroundColor Green
& ".\dev-full.ps1" -FrontendOnly
}
default {
Write-Host "无效选择!" -ForegroundColor Red
}
}
Write-Host ""
Write-Host "按任意键退出..." -ForegroundColor Gray
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

122
gofaster/test-login-complete.md

@ -0,0 +1,122 @@ @@ -0,0 +1,122 @@
# 登录功能完整测试脚本
## 🚨 重要提醒
**在测试之前,请确保:**
1. 后端已经重新编译并启动
2. 前端已经重新编译并启动
3. 所有修改都已保存
## 🔧 问题诊断
### 检查 1: 后端验证码是否增强
1. 访问 `http://localhost:8080/api/auth/captcha`
2. **预期结果**: 返回的 `captcha_image` 应该包含复杂的 SVG 内容
3. **如果还是简单文本**: 说明后端没有重新编译
### 检查 2: 前端模态窗是否修复
1. 打开前端应用
2. 点击"登录"按钮
3. 尝试点击登录弹窗外部区域
4. **预期结果**: 弹窗不应该关闭
### 检查 3: 登录状态切换是否正常
1. 完成登录流程
2. 检查欢迎页面是否显示用户信息
3. **预期结果**: 应该显示"欢迎回来,sysadmin!"
## 🧪 完整测试流程
### 步骤 1: 重启服务
```bash
# 停止当前服务
# 重新启动后端
cd backend
air
# 重新启动前端
cd app
npm run dev
```
### 步骤 2: 测试验证码增强
1. 打开浏览器开发者工具
2. 访问 `http://localhost:8080/api/auth/captcha`
3. 检查响应中的 `captcha_image` 字段
4. **应该看到**: 复杂的 SVG 内容,包含干扰元素
### 步骤 3: 测试前端模态窗
1. 打开前端应用
2. 点击右上角"登录"按钮
3. 尝试点击弹窗外部区域
4. **预期**: 弹窗保持打开状态
5. 点击弹窗右上角 × 按钮
6. **预期**: 弹窗正常关闭
### 步骤 4: 测试登录流程
1. 在登录弹窗中输入:
- 用户名: `sysadmin`
- 密码: `sysadmin@123`
- 验证码: 从图片中识别
2. 点击"登录"按钮
3. **预期**: 登录成功,弹窗关闭
### 步骤 5: 测试状态切换
1. 登录成功后,检查欢迎页面
2. **预期看到**:
- "欢迎回来,sysadmin!"
- 功能卡片(速度测试、历史记录等)
- 待办事项列表
- 最近活动
3. **不应该看到**: "登录"按钮
### 步骤 6: 测试登出功能
1. 点击右上角用户头像
2. 选择"退出登录"
3. **预期**: 页面回到未登录状态
## 🐛 常见问题排查
### 问题 1: 验证码还是简单文本
**原因**: 后端没有重新编译
**解决**:
```bash
cd backend
go build -o main.exe .
./main.exe
```
### 问题 2: 模态窗还是可以点击外部关闭
**原因**: 前端没有重新编译
**解决**:
```bash
cd app
npm run dev
```
### 问题 3: 登录后状态没有切换
**原因**: Vue 响应式问题
**解决**: 检查浏览器控制台是否有错误
## 📊 测试结果记录
| 测试项目 | 预期结果 | 实际结果 | 状态 |
|---------|---------|---------|------|
| 验证码增强 | 复杂SVG | | |
| 模态窗修复 | 外部点击不关闭 | | |
| 登录状态切换 | 显示用户信息 | | |
| 登出功能 | 回到未登录状态 | | |
## 🎯 下一步行动
如果测试失败,请:
1. 检查控制台错误信息
2. 确认服务是否重新启动
3. 检查网络请求是否正常
4. 提供具体的错误信息
## 📞 技术支持
如果问题仍然存在,请提供:
1. 浏览器控制台错误信息
2. 网络请求的响应内容
3. 具体的操作步骤和结果

94
gofaster/test-login-fix-verification.md

@ -0,0 +1,94 @@ @@ -0,0 +1,94 @@
# 登录状态切换修复验证
## 🔧 已修复的问题
### 1. MainLayout.vue 架构问题 ✅
- **问题**: provide 在 export default 中引用了 setup 中的响应式数据
- **修复**: 将 provide 移到 setup 函数中,使用正确的 Vue 3 Composition API 语法
- **效果**: 子组件现在可以正确接收到响应式的登录状态
### 2. 响应式数据传递 ✅
- **问题**: isLoggedIn 和 currentUser 没有正确传递给子组件
- **修复**: 使用 provide() 函数在 setup 中提供响应式数据
- **效果**: 子组件可以实时响应登录状态变化
## 🧪 测试步骤
### 步骤 1: 重启前端服务
```bash
# 停止当前前端服务 (Ctrl+C)
cd app
npm run dev
```
### 步骤 2: 测试登录流程
1. 打开前端应用
2. 点击右上角"登录"按钮
3. 输入凭据:
- 用户名: `sysadmin`
- 密码: `sysadmin@123`
- 验证码: 从图片中识别
4. 点击"登录"按钮
### 步骤 3: 验证状态切换
**预期结果**:
- 登录成功后弹窗关闭
- 欢迎页面显示"欢迎回来,sysadmin!"
- 不显示"登录"按钮
- 显示功能卡片(速度测试、历史记录等)
- 显示待办事项列表
- 显示最近活动
### 步骤 4: 验证用户信息
**预期结果**:
- 用户信息应该显示为 `admin` 而不是 `用户`
- 邮箱应该显示为 `admin@gofaster.com` 而不是 `user@example.com`
## 🐛 如果仍有问题
### 检查控制台日志
应该看到以下日志:
```
Home.vue - 登录状态变化: true
Home.vue - 当前用户信息: {name: "admin", email: "admin@gofaster.com", ...}
Home.vue - 更新后的用户信息: {name: "admin", email: "admin@gofaster.com", ...}
```
### 检查网络请求
1. 验证码获取是否成功
2. 登录请求是否成功
3. 响应数据格式是否正确
### 检查本地存储
```javascript
// 在浏览器控制台中执行
localStorage.getItem('isLoggedIn') // 应该是 "true"
localStorage.getItem('user') // 应该包含用户信息
localStorage.getItem('token') // 应该包含 JWT token
```
## 📝 技术细节
### 修复的核心问题
1. **Vue 3 Composition API 架构**: 将 provide 移到 setup 函数中
2. **响应式数据传递**: 使用 provide() 函数提供响应式引用
3. **数据绑定**: 确保子组件能正确接收和响应数据变化
### 数据流
```
MainLayout (setup) → provide() → Home.vue (inject) → 模板渲染
响应式数据变化 → 自动更新 → UI 状态切换
```
## 🎯 下一步
如果测试成功:
1. 验证登出功能是否正常工作
2. 检查其他页面的登录状态是否正确
3. 测试页面刷新后的状态保持
如果测试失败:
1. 提供具体的错误信息
2. 检查控制台日志
3. 确认前端服务是否重新启动

96
gofaster/test-login-fixes.md

@ -0,0 +1,96 @@ @@ -0,0 +1,96 @@
# 登录功能修复测试指南
## 🔧 已修复的问题
### 1. 登录弹窗模态窗问题 ✅
- **问题**:点击登录窗外部区域,登录窗会消失
- **修复**:将 `@click="handleOverlayClick"` 改为 `@click.self="closeModal"`
- **效果**:现在只有点击遮罩层(非弹窗区域)才会关闭弹窗
### 2. 验证码安全性增强 ✅
- **问题**:验证码只是简单字符,安全性不足
- **修复**:添加了干扰线、干扰点、干扰圆、字符旋转、随机颜色等
- **效果**:验证码现在包含多种干扰元素,提高安全性
### 3. 登录后欢迎页面状态切换 ✅
- **问题**:登录后欢迎页面没有切换到登录状态
- **修复**:修复了 `$watch` 语法,确保正确监听登录状态变化
- **效果**:登录后应该正确显示用户信息和功能卡片
## 🧪 测试步骤
### 测试 1: 模态窗功能
1. 点击右上角"登录"按钮
2. 尝试点击登录弹窗外部区域
3. **预期结果**:弹窗不会关闭
4. 点击弹窗右上角的 × 按钮
5. **预期结果**:弹窗正常关闭
### 测试 2: 验证码安全性
1. 打开登录弹窗
2. 点击"点击获取验证码"
3. **预期结果**:显示包含干扰元素的验证码图片
4. 刷新验证码几次
5. **预期结果**:每次生成的验证码都有不同的干扰元素
### 测试 3: 登录状态切换
1. 使用正确凭据登录(sysadmin / sysadmin@123)
2. **预期结果**:登录成功后弹窗关闭
3. 检查欢迎页面
4. **预期结果**
- 显示"欢迎回来,sysadmin!"
- 不显示"登录"按钮
- 显示功能卡片(速度测试、历史记录等)
- 显示待办事项列表
- 显示最近活动
### 测试 4: 登出功能
1. 点击右上角用户头像
2. 选择"退出登录"
3. **预期结果**
- 欢迎页面切换回未登录状态
- 显示"欢迎光临,请登录!"
- 显示"登录"按钮
- 隐藏功能卡片和待办事项
## 🐛 如果仍有问题
### 检查控制台日志
- 查看是否有 JavaScript 错误
- 检查登录状态变化日志
- 检查用户信息更新日志
### 检查网络请求
- 验证码获取是否成功
- 登录请求是否成功
- 响应数据格式是否正确
### 检查本地存储
- `localStorage.getItem('isLoggedIn')`
- `localStorage.getItem('user')`
- `localStorage.getItem('token')`
## 📝 技术细节
### 验证码增强特性
- 随机干扰线(3条)
- 随机干扰点(20个)
- 随机干扰圆(5个)
- 字符随机旋转(±10度)
- 字符随机颜色
- 字符随机字体大小
- 噪声滤镜效果
### 状态管理
- 使用 Vue 3 Composition API
- 响应式数据绑定
- 全局事件通信
- 本地存储持久化
## 🎯 下一步优化建议
1. **添加验证码刷新按钮**:在验证码图片旁边添加刷新图标
2. **增强错误处理**:显示更友好的错误提示
3. **添加记住密码功能**:可选的密码记忆功能
4. **添加自动登录**:检查本地存储的登录状态
5. **优化移动端体验**:响应式设计优化
Loading…
Cancel
Save