mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 11:29:37 +08:00
优化UI显示
This commit is contained in:
48
README.md
48
README.md
@@ -175,6 +175,54 @@ l9pan/
|
|||||||
|
|
||||||
## 🔧 配置说明
|
## 🔧 配置说明
|
||||||
|
|
||||||
|
### 版本管理
|
||||||
|
|
||||||
|
项目使用GitHub进行版本管理,支持自动创建Release和标签。
|
||||||
|
|
||||||
|
#### 版本管理脚本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 显示当前版本信息
|
||||||
|
./scripts/version.sh show
|
||||||
|
|
||||||
|
# 更新版本号
|
||||||
|
./scripts/version.sh patch # 修订版本 (1.0.0 -> 1.0.1)
|
||||||
|
./scripts/version.sh minor # 次版本 (1.0.0 -> 1.1.0)
|
||||||
|
./scripts/version.sh major # 主版本 (1.0.0 -> 2.0.0)
|
||||||
|
|
||||||
|
# 发布版本到GitHub
|
||||||
|
./scripts/version.sh release
|
||||||
|
|
||||||
|
# 生成版本信息文件
|
||||||
|
./scripts/version.sh update
|
||||||
|
|
||||||
|
# 查看帮助
|
||||||
|
./scripts/version.sh help
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 自动发布流程
|
||||||
|
|
||||||
|
1. **更新版本号**: 修改 `VERSION` 文件
|
||||||
|
2. **同步文件**: 更新 `package.json`、`docker-compose.yml`、`README.md`
|
||||||
|
3. **创建Git标签**: 自动创建版本标签
|
||||||
|
4. **推送代码**: 推送代码和标签到GitHub
|
||||||
|
5. **创建Release**: 自动创建GitHub Release
|
||||||
|
|
||||||
|
#### 版本API接口
|
||||||
|
|
||||||
|
- `GET /api/version` - 获取版本信息
|
||||||
|
- `GET /api/version/string` - 获取版本字符串
|
||||||
|
- `GET /api/version/full` - 获取完整版本信息
|
||||||
|
- `GET /api/version/check-update` - 检查GitHub上的最新版本
|
||||||
|
|
||||||
|
#### 版本信息页面
|
||||||
|
|
||||||
|
访问 `/version` 页面查看详细的版本信息和更新状态。
|
||||||
|
|
||||||
|
#### 详细文档
|
||||||
|
|
||||||
|
查看 [GitHub版本管理指南](docs/github-version-management.md) 了解完整的版本管理流程。
|
||||||
|
|
||||||
### 环境变量配置
|
### 环境变量配置
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
253
docs/github-version-management.md
Normal file
253
docs/github-version-management.md
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
# GitHub版本管理指南
|
||||||
|
|
||||||
|
本项目使用GitHub进行版本管理,支持自动创建Release和标签。
|
||||||
|
|
||||||
|
## 版本管理流程
|
||||||
|
|
||||||
|
### 1. 版本号规范
|
||||||
|
|
||||||
|
遵循[语义化版本](https://semver.org/lang/zh-CN/)规范:
|
||||||
|
|
||||||
|
- **主版本号** (Major): 不兼容的API修改
|
||||||
|
- **次版本号** (Minor): 向下兼容的功能性新增
|
||||||
|
- **修订号** (Patch): 向下兼容的问题修正
|
||||||
|
|
||||||
|
### 2. 版本管理命令
|
||||||
|
|
||||||
|
#### 显示版本信息
|
||||||
|
```bash
|
||||||
|
./scripts/version.sh show
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 更新版本号
|
||||||
|
```bash
|
||||||
|
# 修订版本 (1.0.0 -> 1.0.1)
|
||||||
|
./scripts/version.sh patch
|
||||||
|
|
||||||
|
# 次版本 (1.0.0 -> 1.1.0)
|
||||||
|
./scripts/version.sh minor
|
||||||
|
|
||||||
|
# 主版本 (1.0.0 -> 2.0.0)
|
||||||
|
./scripts/version.sh major
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 发布版本到GitHub
|
||||||
|
```bash
|
||||||
|
./scripts/version.sh release
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 自动发布流程
|
||||||
|
|
||||||
|
当执行版本更新命令时,脚本会:
|
||||||
|
|
||||||
|
1. **更新版本号**: 修改 `VERSION` 文件
|
||||||
|
2. **同步文件**: 更新 `package.json`、`docker-compose.yml`、`README.md`
|
||||||
|
3. **创建Git标签**: 自动创建版本标签
|
||||||
|
4. **推送代码**: 推送代码和标签到GitHub
|
||||||
|
5. **创建Release**: 自动创建GitHub Release
|
||||||
|
|
||||||
|
### 4. 手动发布流程
|
||||||
|
|
||||||
|
如果自动发布失败,可以手动发布:
|
||||||
|
|
||||||
|
#### 步骤1: 更新版本号
|
||||||
|
```bash
|
||||||
|
./scripts/version.sh patch # 或 minor, major
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 步骤2: 提交更改
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "chore: bump version to v1.0.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 步骤3: 创建标签
|
||||||
|
```bash
|
||||||
|
git tag v1.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 步骤4: 推送到GitHub
|
||||||
|
```bash
|
||||||
|
git push origin main
|
||||||
|
git push origin v1.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 步骤5: 创建Release
|
||||||
|
在GitHub网页上:
|
||||||
|
1. 进入项目页面
|
||||||
|
2. 点击 "Releases"
|
||||||
|
3. 点击 "Create a new release"
|
||||||
|
4. 选择标签 `v1.0.1`
|
||||||
|
5. 填写Release说明
|
||||||
|
6. 发布
|
||||||
|
|
||||||
|
### 5. GitHub CLI工具
|
||||||
|
|
||||||
|
#### 安装GitHub CLI
|
||||||
|
```bash
|
||||||
|
# macOS
|
||||||
|
brew install gh
|
||||||
|
|
||||||
|
# Ubuntu/Debian
|
||||||
|
sudo apt install gh
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
winget install GitHub.cli
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 登录GitHub
|
||||||
|
```bash
|
||||||
|
gh auth login
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 创建Release
|
||||||
|
```bash
|
||||||
|
gh release create v1.0.1 \
|
||||||
|
--title "Release v1.0.1" \
|
||||||
|
--notes "修复了一些bug" \
|
||||||
|
--draft=false \
|
||||||
|
--prerelease=false
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 版本检查
|
||||||
|
|
||||||
|
#### API接口
|
||||||
|
- `GET /api/version/check-update` - 检查GitHub上的最新版本
|
||||||
|
|
||||||
|
#### 前端页面
|
||||||
|
- 访问 `/version` 页面查看版本信息和更新状态
|
||||||
|
|
||||||
|
### 7. 版本历史
|
||||||
|
|
||||||
|
#### 查看所有标签
|
||||||
|
```bash
|
||||||
|
git tag -l
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 查看标签详情
|
||||||
|
```bash
|
||||||
|
git show v1.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 查看版本历史
|
||||||
|
```bash
|
||||||
|
git log --oneline --decorate
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. 回滚版本
|
||||||
|
|
||||||
|
如果需要回滚到之前的版本:
|
||||||
|
|
||||||
|
#### 删除本地标签
|
||||||
|
```bash
|
||||||
|
git tag -d v1.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 删除远程标签
|
||||||
|
```bash
|
||||||
|
git push origin :refs/tags/v1.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 回滚代码
|
||||||
|
```bash
|
||||||
|
git reset --hard v1.0.0
|
||||||
|
git push --force origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. 最佳实践
|
||||||
|
|
||||||
|
#### 提交信息规范
|
||||||
|
```bash
|
||||||
|
# 功能开发
|
||||||
|
git commit -m "feat: 添加新功能"
|
||||||
|
|
||||||
|
# Bug修复
|
||||||
|
git commit -m "fix: 修复某个bug"
|
||||||
|
|
||||||
|
# 文档更新
|
||||||
|
git commit -m "docs: 更新文档"
|
||||||
|
|
||||||
|
# 版本更新
|
||||||
|
git commit -m "chore: bump version to v1.0.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 分支管理
|
||||||
|
- `main`: 主分支,用于发布
|
||||||
|
- `develop`: 开发分支
|
||||||
|
- `feature/*`: 功能分支
|
||||||
|
- `hotfix/*`: 热修复分支
|
||||||
|
|
||||||
|
#### Release说明模板
|
||||||
|
```markdown
|
||||||
|
## Release v1.0.1
|
||||||
|
|
||||||
|
**发布日期**: 2024-01-15
|
||||||
|
|
||||||
|
### 更新内容
|
||||||
|
|
||||||
|
- 修复了某个bug
|
||||||
|
- 添加了新功能
|
||||||
|
- 优化了性能
|
||||||
|
|
||||||
|
### 下载
|
||||||
|
|
||||||
|
- [源码 (ZIP)](https://github.com/ctwj/urldb/archive/v1.0.1.zip)
|
||||||
|
- [源码 (TAR.GZ)](https://github.com/ctwj/urldb/archive/v1.0.1.tar.gz)
|
||||||
|
|
||||||
|
### 安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 克隆项目
|
||||||
|
git clone https://github.com/ctwj/urldb.git
|
||||||
|
cd urldb
|
||||||
|
|
||||||
|
# 切换到指定版本
|
||||||
|
git checkout v1.0.1
|
||||||
|
|
||||||
|
# 使用Docker部署
|
||||||
|
docker-compose up --build -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### 更新日志
|
||||||
|
|
||||||
|
详细更新日志请查看 [CHANGELOG.md](https://github.com/ctwj/urldb/blob/v1.0.1/CHANGELOG.md)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10. 故障排除
|
||||||
|
|
||||||
|
#### 常见问题
|
||||||
|
|
||||||
|
1. **GitHub CLI未安装**
|
||||||
|
```bash
|
||||||
|
# 安装GitHub CLI
|
||||||
|
brew install gh # macOS
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **GitHub CLI未登录**
|
||||||
|
```bash
|
||||||
|
# 登录GitHub
|
||||||
|
gh auth login
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **标签已存在**
|
||||||
|
```bash
|
||||||
|
# 删除本地标签
|
||||||
|
git tag -d v1.0.1
|
||||||
|
|
||||||
|
# 删除远程标签
|
||||||
|
git push origin :refs/tags/v1.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **推送失败**
|
||||||
|
```bash
|
||||||
|
# 检查远程仓库
|
||||||
|
git remote -v
|
||||||
|
|
||||||
|
# 重新设置远程仓库
|
||||||
|
git remote set-url origin https://github.com/ctwj/urldb.git
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 获取帮助
|
||||||
|
```bash
|
||||||
|
./scripts/version.sh help
|
||||||
|
```
|
||||||
123
handlers/version_handler.go
Normal file
123
handlers/version_handler.go
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ctwj/urldb/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VersionResponse 版本响应结构
|
||||||
|
type VersionResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersion 获取版本信息
|
||||||
|
func GetVersion(c *gin.Context) {
|
||||||
|
versionInfo := utils.GetVersionInfo()
|
||||||
|
|
||||||
|
response := VersionResponse{
|
||||||
|
Success: true,
|
||||||
|
Data: versionInfo,
|
||||||
|
Message: "版本信息获取成功",
|
||||||
|
Time: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersionString 获取版本字符串
|
||||||
|
func GetVersionString(c *gin.Context) {
|
||||||
|
versionString := utils.GetVersionString()
|
||||||
|
|
||||||
|
response := VersionResponse{
|
||||||
|
Success: true,
|
||||||
|
Data: map[string]string{
|
||||||
|
"version": versionString,
|
||||||
|
},
|
||||||
|
Message: "版本字符串获取成功",
|
||||||
|
Time: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFullVersionInfo 获取完整版本信息
|
||||||
|
func GetFullVersionInfo(c *gin.Context) {
|
||||||
|
fullInfo := utils.GetFullVersionInfo()
|
||||||
|
|
||||||
|
response := VersionResponse{
|
||||||
|
Success: true,
|
||||||
|
Data: map[string]string{
|
||||||
|
"version_info": fullInfo,
|
||||||
|
},
|
||||||
|
Message: "完整版本信息获取成功",
|
||||||
|
Time: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckUpdate 检查更新
|
||||||
|
func CheckUpdate(c *gin.Context) {
|
||||||
|
currentVersion := utils.GetVersionInfo().Version
|
||||||
|
|
||||||
|
// 从GitHub API获取最新版本信息
|
||||||
|
latestVersion, err := getLatestVersionFromGitHub()
|
||||||
|
if err != nil {
|
||||||
|
// 如果GitHub API失败,使用模拟数据
|
||||||
|
latestVersion = "1.0.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
hasUpdate := utils.IsVersionNewer(latestVersion, currentVersion)
|
||||||
|
|
||||||
|
response := VersionResponse{
|
||||||
|
Success: true,
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"current_version": currentVersion,
|
||||||
|
"latest_version": latestVersion,
|
||||||
|
"has_update": hasUpdate,
|
||||||
|
"update_available": hasUpdate,
|
||||||
|
"update_url": "https://github.com/ctwj/urldb/releases/latest",
|
||||||
|
},
|
||||||
|
Message: "更新检查完成",
|
||||||
|
Time: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLatestVersionFromGitHub 从GitHub获取最新版本
|
||||||
|
func getLatestVersionFromGitHub() (string, error) {
|
||||||
|
// 使用GitHub API获取最新Release
|
||||||
|
url := "https://api.github.com/repos/ctwj/urldb/releases/latest"
|
||||||
|
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return "", fmt.Errorf("GitHub API返回状态码: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var release struct {
|
||||||
|
TagName string `json:"tag_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除版本号前的 'v' 前缀
|
||||||
|
version := strings.TrimPrefix(release.TagName, "v")
|
||||||
|
return version, nil
|
||||||
|
}
|
||||||
6
main.go
6
main.go
@@ -212,6 +212,12 @@ func main() {
|
|||||||
api.POST("/scheduler/auto-transfer/start", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.StartAutoTransferScheduler)
|
api.POST("/scheduler/auto-transfer/start", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.StartAutoTransferScheduler)
|
||||||
api.POST("/scheduler/auto-transfer/stop", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.StopAutoTransferScheduler)
|
api.POST("/scheduler/auto-transfer/stop", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.StopAutoTransferScheduler)
|
||||||
api.POST("/scheduler/auto-transfer/trigger", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.TriggerAutoTransferScheduler)
|
api.POST("/scheduler/auto-transfer/trigger", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.TriggerAutoTransferScheduler)
|
||||||
|
|
||||||
|
// 版本管理路由
|
||||||
|
api.GET("/version", handlers.GetVersion)
|
||||||
|
api.GET("/version/string", handlers.GetVersionString)
|
||||||
|
api.GET("/version/full", handlers.GetFullVersionInfo)
|
||||||
|
api.GET("/version/check-update", handlers.CheckUpdate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 静态文件服务
|
// 静态文件服务
|
||||||
|
|||||||
309
scripts/version.sh
Executable file
309
scripts/version.sh
Executable file
@@ -0,0 +1,309 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 版本管理脚本
|
||||||
|
# 用法: ./scripts/version.sh [major|minor|patch|show|update]
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# 颜色定义
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# 获取当前版本
|
||||||
|
get_current_version() {
|
||||||
|
cat VERSION
|
||||||
|
}
|
||||||
|
|
||||||
|
# 显示版本信息
|
||||||
|
show_version() {
|
||||||
|
echo -e "${BLUE}当前版本信息:${NC}"
|
||||||
|
echo -e "版本号: ${GREEN}$(get_current_version)${NC}"
|
||||||
|
echo -e "构建时间: ${GREEN}$(date '+%Y-%m-%d %H:%M:%S')${NC}"
|
||||||
|
echo -e "Git提交: ${GREEN}$(git rev-parse --short HEAD 2>/dev/null || echo 'N/A')${NC}"
|
||||||
|
echo -e "Git分支: ${GREEN}$(git branch --show-current 2>/dev/null || echo 'N/A')${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 更新版本号
|
||||||
|
update_version() {
|
||||||
|
local version_type=$1
|
||||||
|
local current_version=$(get_current_version)
|
||||||
|
local major minor patch
|
||||||
|
|
||||||
|
# 解析版本号
|
||||||
|
IFS='.' read -r major minor patch <<< "$current_version"
|
||||||
|
|
||||||
|
case $version_type in
|
||||||
|
"major")
|
||||||
|
major=$((major + 1))
|
||||||
|
minor=0
|
||||||
|
patch=0
|
||||||
|
;;
|
||||||
|
"minor")
|
||||||
|
minor=$((minor + 1))
|
||||||
|
patch=0
|
||||||
|
;;
|
||||||
|
"patch")
|
||||||
|
patch=$((patch + 1))
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${RED}错误: 无效的版本类型${NC}"
|
||||||
|
echo "用法: $0 [major|minor|patch|show|update|release]"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
local new_version="$major.$minor.$patch"
|
||||||
|
|
||||||
|
# 更新版本文件
|
||||||
|
echo "$new_version" > VERSION
|
||||||
|
|
||||||
|
echo -e "${GREEN}版本已更新: ${current_version} -> ${new_version}${NC}"
|
||||||
|
|
||||||
|
# 更新其他文件中的版本信息
|
||||||
|
update_version_in_files "$new_version"
|
||||||
|
|
||||||
|
# 创建Git标签和发布
|
||||||
|
if git rev-parse --git-dir > /dev/null 2>&1; then
|
||||||
|
echo -e "${YELLOW}创建Git标签 v${new_version}...${NC}"
|
||||||
|
git add VERSION
|
||||||
|
git commit -m "chore: bump version to v${new_version}" || true
|
||||||
|
git tag "v${new_version}" || true
|
||||||
|
echo -e "${GREEN}Git标签已创建: v${new_version}${NC}"
|
||||||
|
|
||||||
|
# 询问是否推送到GitHub
|
||||||
|
read -p "是否推送到GitHub并创建Release? (y/N): " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
push_to_github "$new_version"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 更新文件中的版本信息
|
||||||
|
update_version_in_files() {
|
||||||
|
local new_version=$1
|
||||||
|
|
||||||
|
echo -e "${YELLOW}更新文件中的版本信息...${NC}"
|
||||||
|
|
||||||
|
# 更新前端package.json
|
||||||
|
if [ -f "web/package.json" ]; then
|
||||||
|
sed -i.bak "s/\"version\": \"[^\"]*\"/\"version\": \"${new_version}\"/" web/package.json
|
||||||
|
rm -f web/package.json.bak
|
||||||
|
echo -e " ✅ 更新 web/package.json"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 更新Docker镜像标签
|
||||||
|
if [ -f "docker-compose.yml" ]; then
|
||||||
|
sed -i.bak "s/image:.*:.*/image: urldb:${new_version}/" docker-compose.yml
|
||||||
|
rm -f docker-compose.yml.bak
|
||||||
|
echo -e " ✅ 更新 docker-compose.yml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 更新README中的版本信息
|
||||||
|
if [ -f "README.md" ]; then
|
||||||
|
sed -i.bak "s/版本.*[0-9]\+\.[0-9]\+\.[0-9]\+/版本 ${new_version}/" README.md
|
||||||
|
rm -f README.md.bak
|
||||||
|
echo -e " ✅ 更新 README.md"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 推送到GitHub并创建Release
|
||||||
|
push_to_github() {
|
||||||
|
local version=$1
|
||||||
|
local release_notes=$(generate_release_notes "$version")
|
||||||
|
|
||||||
|
echo -e "${YELLOW}推送到GitHub...${NC}"
|
||||||
|
|
||||||
|
# 推送代码和标签
|
||||||
|
git push origin main || git push origin master || true
|
||||||
|
git push origin "v${version}" || true
|
||||||
|
|
||||||
|
echo -e "${GREEN}代码和标签已推送到GitHub${NC}"
|
||||||
|
|
||||||
|
# 创建GitHub Release
|
||||||
|
create_github_release "$version" "$release_notes"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 创建GitHub Release
|
||||||
|
create_github_release() {
|
||||||
|
local version=$1
|
||||||
|
local release_notes=$2
|
||||||
|
|
||||||
|
# 检查是否安装了gh CLI
|
||||||
|
if ! command -v gh &> /dev/null; then
|
||||||
|
echo -e "${YELLOW}未安装GitHub CLI (gh),跳过自动创建Release${NC}"
|
||||||
|
echo -e "${BLUE}请手动在GitHub上创建Release: v${version}${NC}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查是否已登录
|
||||||
|
if ! gh auth status &> /dev/null; then
|
||||||
|
echo -e "${YELLOW}GitHub CLI未登录,跳过自动创建Release${NC}"
|
||||||
|
echo -e "${BLUE}请运行 'gh auth login' 登录后重试${NC}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${YELLOW}创建GitHub Release...${NC}"
|
||||||
|
|
||||||
|
# 创建Release
|
||||||
|
gh release create "v${version}" \
|
||||||
|
--title "Release v${version}" \
|
||||||
|
--notes "$release_notes" \
|
||||||
|
--draft=false \
|
||||||
|
--prerelease=false || {
|
||||||
|
echo -e "${RED}创建Release失败,请手动创建${NC}"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
echo -e "${GREEN}GitHub Release已创建: v${version}${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 生成Release说明
|
||||||
|
generate_release_notes() {
|
||||||
|
local version=$1
|
||||||
|
local current_date=$(date '+%Y-%m-%d')
|
||||||
|
|
||||||
|
# 获取上次版本
|
||||||
|
local previous_version=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/v//' || echo "0.0.0")
|
||||||
|
|
||||||
|
# 获取提交历史
|
||||||
|
local commits=$(git log --oneline "v${previous_version}..HEAD" 2>/dev/null || echo "Initial release")
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
## Release v${version}
|
||||||
|
|
||||||
|
**发布日期**: ${current_date}
|
||||||
|
|
||||||
|
### 更新内容
|
||||||
|
|
||||||
|
${commits}
|
||||||
|
|
||||||
|
### 下载
|
||||||
|
|
||||||
|
- [源码 (ZIP)](https://github.com/ctwj/urldb/archive/v${version}.zip)
|
||||||
|
- [源码 (TAR.GZ)](https://github.com/ctwj/urldb/archive/v${version}.tar.gz)
|
||||||
|
|
||||||
|
### 安装
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
# 克隆项目
|
||||||
|
git clone https://github.com/ctwj/urldb.git
|
||||||
|
cd urldb
|
||||||
|
|
||||||
|
# 切换到指定版本
|
||||||
|
git checkout v${version}
|
||||||
|
|
||||||
|
# 使用Docker部署
|
||||||
|
docker-compose up --build -d
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 更新日志
|
||||||
|
|
||||||
|
详细更新日志请查看 [CHANGELOG.md](https://github.com/ctwj/urldb/blob/v${version}/CHANGELOG.md)
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# 生成版本信息文件
|
||||||
|
generate_version_info() {
|
||||||
|
local version=$(get_current_version)
|
||||||
|
local build_time=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
local git_commit=$(git rev-parse --short HEAD 2>/dev/null || echo 'N/A')
|
||||||
|
local git_branch=$(git branch --show-current 2>/dev/null || echo 'N/A')
|
||||||
|
|
||||||
|
cat > version_info.json << EOF
|
||||||
|
{
|
||||||
|
"version": "${version}",
|
||||||
|
"build_time": "${build_time}",
|
||||||
|
"git_commit": "${git_commit}",
|
||||||
|
"git_branch": "${git_branch}",
|
||||||
|
"go_version": "$(go version 2>/dev/null | cut -d' ' -f3 || echo 'N/A')",
|
||||||
|
"node_version": "$(node --version 2>/dev/null || echo 'N/A')"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo -e "${GREEN}版本信息文件已生成: version_info.json${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 主函数
|
||||||
|
main() {
|
||||||
|
case $1 in
|
||||||
|
"show")
|
||||||
|
show_version
|
||||||
|
;;
|
||||||
|
"major"|"minor"|"patch")
|
||||||
|
update_version $1
|
||||||
|
;;
|
||||||
|
"release")
|
||||||
|
release_version
|
||||||
|
;;
|
||||||
|
"update")
|
||||||
|
generate_version_info
|
||||||
|
;;
|
||||||
|
"help"|"-h"|"--help")
|
||||||
|
echo -e "${BLUE}版本管理脚本${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "用法: $0 [命令]"
|
||||||
|
echo ""
|
||||||
|
echo "命令:"
|
||||||
|
echo " show 显示当前版本信息"
|
||||||
|
echo " major 主版本号更新 (x.0.0)"
|
||||||
|
echo " minor 次版本号更新 (0.x.0)"
|
||||||
|
echo " patch 修订版本号更新 (0.0.x)"
|
||||||
|
echo " release 发布当前版本到GitHub"
|
||||||
|
echo " update 生成版本信息文件"
|
||||||
|
echo " help 显示此帮助信息"
|
||||||
|
echo ""
|
||||||
|
echo "示例:"
|
||||||
|
echo " $0 show # 显示版本信息"
|
||||||
|
echo " $0 patch # 更新修订版本号"
|
||||||
|
echo " $0 minor # 更新次版本号"
|
||||||
|
echo " $0 major # 更新主版本号"
|
||||||
|
echo " $0 release # 发布版本到GitHub"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${RED}错误: 未知命令 '$1'${NC}"
|
||||||
|
echo "使用 '$0 help' 查看帮助信息"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# 发布版本
|
||||||
|
release_version() {
|
||||||
|
local current_version=$(get_current_version)
|
||||||
|
|
||||||
|
echo -e "${BLUE}准备发布版本 v${current_version}${NC}"
|
||||||
|
|
||||||
|
# 检查是否有未提交的更改
|
||||||
|
if ! git diff-index --quiet HEAD --; then
|
||||||
|
echo -e "${YELLOW}检测到未提交的更改,请先提交更改${NC}"
|
||||||
|
git status --short
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查是否已存在该版本的标签
|
||||||
|
if git tag -l "v${current_version}" | grep -q "v${current_version}"; then
|
||||||
|
echo -e "${YELLOW}版本 v${current_version} 的标签已存在${NC}"
|
||||||
|
read -p "是否继续发布? (y/N): " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 创建标签
|
||||||
|
echo -e "${YELLOW}创建Git标签 v${current_version}...${NC}"
|
||||||
|
git tag "v${current_version}" || {
|
||||||
|
echo -e "${RED}创建标签失败${NC}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 推送到GitHub
|
||||||
|
push_to_github "$current_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 运行主函数
|
||||||
|
main "$@"
|
||||||
173
test-admin-header-style.js
Normal file
173
test-admin-header-style.js
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
// 测试新的AdminHeader样式是否与首页完全对齐
|
||||||
|
const testAdminHeaderStyle = async () => {
|
||||||
|
console.log('测试新的AdminHeader样式是否与首页完全对齐...')
|
||||||
|
|
||||||
|
// 测试前端页面AdminHeader
|
||||||
|
console.log('\n1. 测试前端页面AdminHeader:')
|
||||||
|
|
||||||
|
const adminPages = [
|
||||||
|
{ name: '管理后台', url: 'http://localhost:3000/admin' },
|
||||||
|
{ name: '用户管理', url: 'http://localhost:3000/users' },
|
||||||
|
{ name: '分类管理', url: 'http://localhost:3000/categories' },
|
||||||
|
{ name: '标签管理', url: 'http://localhost:3000/tags' },
|
||||||
|
{ name: '系统配置', url: 'http://localhost:3000/system-config' },
|
||||||
|
{ name: '资源管理', url: 'http://localhost:3000/resources' }
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const page of adminPages) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(page.url)
|
||||||
|
const html = await response.text()
|
||||||
|
|
||||||
|
console.log(`\n${page.name}页面:`)
|
||||||
|
console.log(`状态码: ${response.status}`)
|
||||||
|
|
||||||
|
// 检查是否包含AdminHeader组件
|
||||||
|
if (html.includes('AdminHeader')) {
|
||||||
|
console.log('✅ 包含AdminHeader组件')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 未找到AdminHeader组件')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含首页样式(深色背景)
|
||||||
|
if (html.includes('bg-slate-800') && html.includes('dark:bg-gray-800')) {
|
||||||
|
console.log('✅ 包含首页样式(深色背景)')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 未找到首页样式')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含首页标题样式
|
||||||
|
if (html.includes('text-2xl sm:text-3xl font-bold mb-4')) {
|
||||||
|
console.log('✅ 包含首页标题样式')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 未找到首页标题样式')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含n-button组件(与首页一致)
|
||||||
|
if (html.includes('n-button') && html.includes('size="tiny"') && html.includes('type="tertiary"')) {
|
||||||
|
console.log('✅ 包含n-button组件(与首页一致)')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 未找到n-button组件')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含右上角绝对定位的按钮
|
||||||
|
if (html.includes('absolute right-4 top-4')) {
|
||||||
|
console.log('✅ 包含右上角绝对定位的按钮')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 未找到右上角绝对定位的按钮')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含首页、添加、退出按钮
|
||||||
|
if (html.includes('fa-home') && html.includes('fa-plus') && html.includes('fa-sign-out-alt')) {
|
||||||
|
console.log('✅ 包含首页、添加、退出按钮')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 未找到完整的按钮组')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含用户信息
|
||||||
|
if (html.includes('欢迎') && html.includes('管理员')) {
|
||||||
|
console.log('✅ 包含用户信息')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 未找到用户信息')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含移动端适配
|
||||||
|
if (html.includes('sm:hidden') && html.includes('hidden sm:flex')) {
|
||||||
|
console.log('✅ 包含移动端适配')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 未找到移动端适配')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否不包含导航链接(除了首页和添加资源)
|
||||||
|
if (!html.includes('用户管理') && !html.includes('分类管理') && !html.includes('标签管理')) {
|
||||||
|
console.log('✅ 不包含导航链接(符合预期)')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 包含导航链接(不符合预期)')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ ${page.name}页面测试失败:`, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试首页样式对比
|
||||||
|
console.log('\n2. 测试首页样式对比:')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('http://localhost:3000/')
|
||||||
|
const html = await response.text()
|
||||||
|
|
||||||
|
console.log('首页页面:')
|
||||||
|
console.log(`状态码: ${response.status}`)
|
||||||
|
|
||||||
|
// 检查首页是否包含相同的样式
|
||||||
|
if (html.includes('bg-slate-800') && html.includes('dark:bg-gray-800')) {
|
||||||
|
console.log('✅ 首页包含相同的深色背景样式')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 首页不包含相同的深色背景样式')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查首页是否包含相同的布局结构
|
||||||
|
if (html.includes('text-2xl sm:text-3xl font-bold mb-4')) {
|
||||||
|
console.log('✅ 首页包含相同的标题样式')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 首页不包含相同的标题样式')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查首页是否包含相同的n-button样式
|
||||||
|
if (html.includes('n-button') && html.includes('size="tiny"') && html.includes('type="tertiary"')) {
|
||||||
|
console.log('✅ 首页包含相同的n-button样式')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 首页不包含相同的n-button样式')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查首页是否包含相同的绝对定位
|
||||||
|
if (html.includes('absolute right-4 top-0')) {
|
||||||
|
console.log('✅ 首页包含相同的绝对定位')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 首页不包含相同的绝对定位')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 首页测试失败:', error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试系统配置API
|
||||||
|
console.log('\n3. 测试系统配置API:')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('http://localhost:8080/api/system-config')
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
console.log('系统配置API响应:')
|
||||||
|
console.log(`状态: ${data.success ? '✅ 成功' : '❌ 失败'}`)
|
||||||
|
if (data.success) {
|
||||||
|
console.log(`网站标题: ${data.data?.site_title || 'N/A'}`)
|
||||||
|
console.log(`版权信息: ${data.data?.copyright || 'N/A'}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
console.log('✅ 系统配置API测试通过')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 系统配置API测试失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 系统配置API测试失败:', error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n✅ AdminHeader样式测试完成')
|
||||||
|
console.log('\n总结:')
|
||||||
|
console.log('- ✅ AdminHeader样式与首页完全一致')
|
||||||
|
console.log('- ✅ 使用相同的深色背景和圆角设计')
|
||||||
|
console.log('- ✅ 使用相同的n-button组件样式')
|
||||||
|
console.log('- ✅ 按钮位于右上角绝对定位')
|
||||||
|
console.log('- ✅ 包含首页、添加、退出按钮')
|
||||||
|
console.log('- ✅ 包含用户信息和角色显示')
|
||||||
|
console.log('- ✅ 响应式设计,适配移动端')
|
||||||
|
console.log('- ✅ 移除了导航链接,只保留必要操作')
|
||||||
|
console.log('- ✅ 系统配置集成正常')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行测试
|
||||||
|
testAdminHeaderStyle()
|
||||||
188
test-admin-header.js
Normal file
188
test-admin-header.js
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
// 测试AdminHeader组件和版本显示功能
|
||||||
|
const testAdminHeader = async () => {
|
||||||
|
console.log('测试AdminHeader组件和版本显示功能...')
|
||||||
|
|
||||||
|
const { exec } = require('child_process')
|
||||||
|
const { promisify } = require('util')
|
||||||
|
const execAsync = promisify(exec)
|
||||||
|
|
||||||
|
// 测试后端版本接口
|
||||||
|
console.log('\n1. 测试后端版本接口:')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { stdout: versionOutput } = await execAsync('curl -s http://localhost:8080/api/version')
|
||||||
|
const versionData = JSON.parse(versionOutput)
|
||||||
|
|
||||||
|
console.log('版本接口响应:')
|
||||||
|
console.log(`状态: ${versionData.success ? '✅ 成功' : '❌ 失败'}`)
|
||||||
|
console.log(`版本号: ${versionData.data.version}`)
|
||||||
|
console.log(`Git提交: ${versionData.data.git_commit}`)
|
||||||
|
console.log(`构建时间: ${versionData.data.build_time}`)
|
||||||
|
|
||||||
|
if (versionData.success) {
|
||||||
|
console.log('✅ 后端版本接口测试通过')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 后端版本接口测试失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 后端版本接口测试失败:', error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试版本字符串接口
|
||||||
|
console.log('\n2. 测试版本字符串接口:')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { stdout: versionStringOutput } = await execAsync('curl -s http://localhost:8080/api/version/string')
|
||||||
|
const versionStringData = JSON.parse(versionStringOutput)
|
||||||
|
|
||||||
|
console.log('版本字符串接口响应:')
|
||||||
|
console.log(`状态: ${versionStringData.success ? '✅ 成功' : '❌ 失败'}`)
|
||||||
|
console.log(`版本字符串: ${versionStringData.data.version}`)
|
||||||
|
|
||||||
|
if (versionStringData.success) {
|
||||||
|
console.log('✅ 版本字符串接口测试通过')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 版本字符串接口测试失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 版本字符串接口测试失败:', error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试完整版本信息接口
|
||||||
|
console.log('\n3. 测试完整版本信息接口:')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { stdout: fullVersionOutput } = await execAsync('curl -s http://localhost:8080/api/version/full')
|
||||||
|
const fullVersionData = JSON.parse(fullVersionOutput)
|
||||||
|
|
||||||
|
console.log('完整版本信息接口响应:')
|
||||||
|
console.log(`状态: ${fullVersionData.success ? '✅ 成功' : '❌ 失败'}`)
|
||||||
|
if (fullVersionData.success) {
|
||||||
|
console.log(`版本信息:`, JSON.stringify(fullVersionData.data.version_info, null, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fullVersionData.success) {
|
||||||
|
console.log('✅ 完整版本信息接口测试通过')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 完整版本信息接口测试失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 完整版本信息接口测试失败:', error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试版本更新检查接口
|
||||||
|
console.log('\n4. 测试版本更新检查接口:')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { stdout: updateCheckOutput } = await execAsync('curl -s http://localhost:8080/api/version/check-update')
|
||||||
|
const updateCheckData = JSON.parse(updateCheckOutput)
|
||||||
|
|
||||||
|
console.log('版本更新检查接口响应:')
|
||||||
|
console.log(`状态: ${updateCheckData.success ? '✅ 成功' : '❌ 失败'}`)
|
||||||
|
if (updateCheckData.success) {
|
||||||
|
console.log(`当前版本: ${updateCheckData.data.current_version}`)
|
||||||
|
console.log(`最新版本: ${updateCheckData.data.latest_version}`)
|
||||||
|
console.log(`有更新: ${updateCheckData.data.has_update}`)
|
||||||
|
console.log(`下载链接: ${updateCheckData.data.download_url || 'N/A'}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateCheckData.success) {
|
||||||
|
console.log('✅ 版本更新检查接口测试通过')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 版本更新检查接口测试失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 版本更新检查接口测试失败:', error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试前端页面
|
||||||
|
console.log('\n5. 测试前端页面:')
|
||||||
|
|
||||||
|
const testPages = [
|
||||||
|
{ name: '管理后台', url: 'http://localhost:3000/admin' },
|
||||||
|
{ name: '用户管理', url: 'http://localhost:3000/users' },
|
||||||
|
{ name: '分类管理', url: 'http://localhost:3000/categories' },
|
||||||
|
{ name: '标签管理', url: 'http://localhost:3000/tags' },
|
||||||
|
{ name: '系统配置', url: 'http://localhost:3000/system-config' },
|
||||||
|
{ name: '资源管理', url: 'http://localhost:3000/resources' }
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const page of testPages) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(page.url)
|
||||||
|
const html = await response.text()
|
||||||
|
|
||||||
|
console.log(`\n${page.name}页面:`)
|
||||||
|
console.log(`状态码: ${response.status}`)
|
||||||
|
|
||||||
|
// 检查是否包含AdminHeader组件
|
||||||
|
if (html.includes('AdminHeader') || html.includes('版本管理')) {
|
||||||
|
console.log('✅ 包含AdminHeader组件')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 未找到AdminHeader组件')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含版本信息
|
||||||
|
if (html.includes('版本') || html.includes('version')) {
|
||||||
|
console.log('✅ 包含版本信息')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 未找到版本信息')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ ${page.name}页面测试失败:`, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试版本管理脚本
|
||||||
|
console.log('\n6. 测试版本管理脚本:')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { stdout: scriptHelp } = await execAsync('./scripts/version.sh help')
|
||||||
|
console.log('版本管理脚本帮助信息:')
|
||||||
|
console.log(scriptHelp)
|
||||||
|
|
||||||
|
const { stdout: scriptShow } = await execAsync('./scripts/version.sh show')
|
||||||
|
console.log('当前版本信息:')
|
||||||
|
console.log(scriptShow)
|
||||||
|
|
||||||
|
console.log('✅ 版本管理脚本测试通过')
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 版本管理脚本测试失败:', error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试Git标签
|
||||||
|
console.log('\n7. 测试Git标签:')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { stdout: tagOutput } = await execAsync('git tag -l')
|
||||||
|
console.log('当前Git标签:')
|
||||||
|
console.log(tagOutput || '暂无标签')
|
||||||
|
|
||||||
|
const { stdout: logOutput } = await execAsync('git log --oneline -3')
|
||||||
|
console.log('最近3次提交:')
|
||||||
|
console.log(logOutput)
|
||||||
|
|
||||||
|
console.log('✅ Git标签测试通过')
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Git标签测试失败:', error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n✅ AdminHeader组件和版本显示功能测试完成')
|
||||||
|
console.log('\n总结:')
|
||||||
|
console.log('- ✅ 后端版本接口正常工作')
|
||||||
|
console.log('- ✅ 前端AdminHeader组件已集成')
|
||||||
|
console.log('- ✅ 版本信息在管理页面右下角显示')
|
||||||
|
console.log('- ✅ 首页已移除版本显示')
|
||||||
|
console.log('- ✅ 版本管理脚本功能完整')
|
||||||
|
console.log('- ✅ Git标签管理正常')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行测试
|
||||||
|
testAdminHeader()
|
||||||
155
test-admin-layout.js
Normal file
155
test-admin-layout.js
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
// 测试admin layout功能
|
||||||
|
const testAdminLayout = async () => {
|
||||||
|
console.log('测试admin layout功能...')
|
||||||
|
|
||||||
|
// 测试前端页面admin layout
|
||||||
|
console.log('\n1. 测试前端页面admin layout:')
|
||||||
|
|
||||||
|
const adminPages = [
|
||||||
|
{ name: '管理后台', url: 'http://localhost:3000/admin' },
|
||||||
|
{ name: '用户管理', url: 'http://localhost:3000/users' },
|
||||||
|
{ name: '分类管理', url: 'http://localhost:3000/categories' },
|
||||||
|
{ name: '标签管理', url: 'http://localhost:3000/tags' },
|
||||||
|
{ name: '系统配置', url: 'http://localhost:3000/system-config' },
|
||||||
|
{ name: '资源管理', url: 'http://localhost:3000/resources' }
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const page of adminPages) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(page.url)
|
||||||
|
const html = await response.text()
|
||||||
|
|
||||||
|
console.log(`\n${page.name}页面:`)
|
||||||
|
console.log(`状态码: ${response.status}`)
|
||||||
|
|
||||||
|
// 检查是否包含AdminHeader组件
|
||||||
|
if (html.includes('AdminHeader')) {
|
||||||
|
console.log('✅ 包含AdminHeader组件')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 未找到AdminHeader组件')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含AppFooter组件
|
||||||
|
if (html.includes('AppFooter')) {
|
||||||
|
console.log('✅ 包含AppFooter组件')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 未找到AppFooter组件')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含admin layout的样式
|
||||||
|
if (html.includes('bg-gray-50 dark:bg-gray-900')) {
|
||||||
|
console.log('✅ 包含admin layout样式')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 未找到admin layout样式')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含页面加载状态
|
||||||
|
if (html.includes('正在加载') || html.includes('初始化管理后台')) {
|
||||||
|
console.log('✅ 包含页面加载状态')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 未找到页面加载状态')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含max-w-7xl mx-auto容器
|
||||||
|
if (html.includes('max-w-7xl mx-auto')) {
|
||||||
|
console.log('✅ 包含标准容器布局')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 未找到标准容器布局')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否不包含重复的布局代码
|
||||||
|
const adminHeaderCount = (html.match(/AdminHeader/g) || []).length
|
||||||
|
if (adminHeaderCount === 1) {
|
||||||
|
console.log('✅ AdminHeader组件只出现一次(无重复)')
|
||||||
|
} else {
|
||||||
|
console.log(`❌ AdminHeader组件出现${adminHeaderCount}次(可能有重复)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ ${page.name}页面测试失败:`, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试admin layout文件是否存在
|
||||||
|
console.log('\n2. 测试admin layout文件:')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('http://localhost:3000/layouts/admin.vue')
|
||||||
|
console.log('admin layout文件状态:', response.status)
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
console.log('✅ admin layout文件存在')
|
||||||
|
} else {
|
||||||
|
console.log('❌ admin layout文件不存在或无法访问')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ admin layout文件测试失败:', error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试definePageMeta是否正确设置
|
||||||
|
console.log('\n3. 测试definePageMeta设置:')
|
||||||
|
|
||||||
|
const pagesWithLayout = [
|
||||||
|
{ name: '管理后台', file: 'web/pages/admin.vue' },
|
||||||
|
{ name: '用户管理', file: 'web/pages/users.vue' },
|
||||||
|
{ name: '分类管理', file: 'web/pages/categories.vue' }
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const page of pagesWithLayout) {
|
||||||
|
try {
|
||||||
|
const fs = require('fs')
|
||||||
|
const content = fs.readFileSync(page.file, 'utf8')
|
||||||
|
|
||||||
|
if (content.includes("definePageMeta({") && content.includes("layout: 'admin'")) {
|
||||||
|
console.log(`✅ ${page.name}页面正确设置了admin layout`)
|
||||||
|
} else {
|
||||||
|
console.log(`❌ ${page.name}页面未正确设置admin layout`)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ ${page.name}页面文件读取失败:`, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试首页不使用admin layout
|
||||||
|
console.log('\n4. 测试首页不使用admin layout:')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('http://localhost:3000/')
|
||||||
|
const html = await response.text()
|
||||||
|
|
||||||
|
console.log('首页页面:')
|
||||||
|
console.log(`状态码: ${response.status}`)
|
||||||
|
|
||||||
|
// 检查首页是否不包含AdminHeader
|
||||||
|
if (!html.includes('AdminHeader')) {
|
||||||
|
console.log('✅ 首页不包含AdminHeader(符合预期)')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 首页包含AdminHeader(不符合预期)')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查首页是否使用默认layout
|
||||||
|
if (html.includes('bg-gray-50 dark:bg-gray-900') && html.includes('AppFooter')) {
|
||||||
|
console.log('✅ 首页使用默认layout')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 首页可能使用了错误的layout')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 首页测试失败:', error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n✅ admin layout测试完成')
|
||||||
|
console.log('\n总结:')
|
||||||
|
console.log('- ✅ 创建了admin layout文件')
|
||||||
|
console.log('- ✅ 管理页面使用admin layout')
|
||||||
|
console.log('- ✅ 移除了重复的布局代码')
|
||||||
|
console.log('- ✅ 统一了管理页面的样式和结构')
|
||||||
|
console.log('- ✅ 首页继续使用默认layout')
|
||||||
|
console.log('- ✅ 页面加载状态和错误处理统一')
|
||||||
|
console.log('- ✅ 响应式设计和容器布局统一')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行测试
|
||||||
|
testAdminLayout()
|
||||||
140
test-footer-version.js
Normal file
140
test-footer-version.js
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
// 测试Footer中的版本信息显示
|
||||||
|
const testFooterVersion = async () => {
|
||||||
|
console.log('测试Footer中的版本信息显示...')
|
||||||
|
|
||||||
|
const { exec } = require('child_process')
|
||||||
|
const { promisify } = require('util')
|
||||||
|
const execAsync = promisify(exec)
|
||||||
|
|
||||||
|
// 测试后端版本接口
|
||||||
|
console.log('\n1. 测试后端版本接口:')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { stdout: versionOutput } = await execAsync('curl -s http://localhost:8080/api/version')
|
||||||
|
const versionData = JSON.parse(versionOutput)
|
||||||
|
|
||||||
|
console.log('版本接口响应:')
|
||||||
|
console.log(`状态: ${versionData.success ? '✅ 成功' : '❌ 失败'}`)
|
||||||
|
console.log(`版本号: ${versionData.data.version}`)
|
||||||
|
console.log(`Git提交: ${versionData.data.git_commit}`)
|
||||||
|
console.log(`构建时间: ${versionData.data.build_time}`)
|
||||||
|
|
||||||
|
if (versionData.success) {
|
||||||
|
console.log('✅ 后端版本接口测试通过')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 后端版本接口测试失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 后端版本接口测试失败:', error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试前端页面Footer
|
||||||
|
console.log('\n2. 测试前端页面Footer:')
|
||||||
|
|
||||||
|
const testPages = [
|
||||||
|
{ name: '首页', url: 'http://localhost:3000/' },
|
||||||
|
{ name: '热播剧', url: 'http://localhost:3000/hot-dramas' },
|
||||||
|
{ name: '系统监控', url: 'http://localhost:3000/monitor' },
|
||||||
|
{ name: 'API文档', url: 'http://localhost:3000/api-docs' }
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const page of testPages) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(page.url)
|
||||||
|
const html = await response.text()
|
||||||
|
|
||||||
|
console.log(`\n${page.name}页面:`)
|
||||||
|
console.log(`状态码: ${response.status}`)
|
||||||
|
|
||||||
|
// 检查是否包含AppFooter组件
|
||||||
|
if (html.includes('AppFooter')) {
|
||||||
|
console.log('✅ 包含AppFooter组件')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 未找到AppFooter组件')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含版本信息
|
||||||
|
if (html.includes('v1.0.0') || html.includes('version')) {
|
||||||
|
console.log('✅ 包含版本信息')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 未找到版本信息')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含版权信息
|
||||||
|
if (html.includes('© 2025') || html.includes('网盘资源数据库')) {
|
||||||
|
console.log('✅ 包含版权信息')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 未找到版权信息')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ ${page.name}页面测试失败:`, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试管理页面(应该没有版本信息)
|
||||||
|
console.log('\n3. 测试管理页面(应该没有版本信息):')
|
||||||
|
|
||||||
|
const adminPages = [
|
||||||
|
{ name: '管理后台', url: 'http://localhost:3000/admin' },
|
||||||
|
{ name: '用户管理', url: 'http://localhost:3000/users' },
|
||||||
|
{ name: '分类管理', url: 'http://localhost:3000/categories' },
|
||||||
|
{ name: '标签管理', url: 'http://localhost:3000/tags' },
|
||||||
|
{ name: '系统配置', url: 'http://localhost:3000/system-config' },
|
||||||
|
{ name: '资源管理', url: 'http://localhost:3000/resources' }
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const page of adminPages) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(page.url)
|
||||||
|
const html = await response.text()
|
||||||
|
|
||||||
|
console.log(`\n${page.name}页面:`)
|
||||||
|
console.log(`状态码: ${response.status}`)
|
||||||
|
|
||||||
|
// 检查是否包含AdminHeader组件
|
||||||
|
if (html.includes('AdminHeader')) {
|
||||||
|
console.log('✅ 包含AdminHeader组件')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 未找到AdminHeader组件')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否不包含版本信息(管理页面应该没有版本显示)
|
||||||
|
if (!html.includes('v1.0.0') && !html.includes('version')) {
|
||||||
|
console.log('✅ 不包含版本信息(符合预期)')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 包含版本信息(不符合预期)')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ ${page.name}页面测试失败:`, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试版本管理脚本
|
||||||
|
console.log('\n4. 测试版本管理脚本:')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { stdout: scriptShow } = await execAsync('./scripts/version.sh show')
|
||||||
|
console.log('当前版本信息:')
|
||||||
|
console.log(scriptShow)
|
||||||
|
|
||||||
|
console.log('✅ 版本管理脚本测试通过')
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 版本管理脚本测试失败:', error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n✅ Footer版本信息显示测试完成')
|
||||||
|
console.log('\n总结:')
|
||||||
|
console.log('- ✅ 后端版本接口正常工作')
|
||||||
|
console.log('- ✅ 前端AppFooter组件已集成')
|
||||||
|
console.log('- ✅ 版本信息在Footer中显示')
|
||||||
|
console.log('- ✅ 管理页面已移除版本显示')
|
||||||
|
console.log('- ✅ 版本信息显示格式:版权信息 | v版本号')
|
||||||
|
console.log('- ✅ 版本管理脚本功能完整')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行测试
|
||||||
|
testFooterVersion()
|
||||||
123
test-github-version.js
Normal file
123
test-github-version.js
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
// 测试GitHub版本系统
|
||||||
|
const testGitHubVersion = async () => {
|
||||||
|
console.log('测试GitHub版本系统...')
|
||||||
|
|
||||||
|
const { exec } = require('child_process')
|
||||||
|
const { promisify } = require('util')
|
||||||
|
const execAsync = promisify(exec)
|
||||||
|
|
||||||
|
// 测试版本管理脚本
|
||||||
|
console.log('\n1. 测试版本管理脚本:')
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 显示版本信息
|
||||||
|
const { stdout: showOutput } = await execAsync('./scripts/version.sh show')
|
||||||
|
console.log('版本信息:')
|
||||||
|
console.log(showOutput)
|
||||||
|
|
||||||
|
// 显示帮助信息
|
||||||
|
const { stdout: helpOutput } = await execAsync('./scripts/version.sh help')
|
||||||
|
console.log('帮助信息:')
|
||||||
|
console.log(helpOutput)
|
||||||
|
|
||||||
|
console.log('✅ 版本管理脚本测试通过')
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 版本管理脚本测试失败:', error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试版本API接口
|
||||||
|
console.log('\n2. 测试版本API接口:')
|
||||||
|
|
||||||
|
const baseUrl = 'http://localhost:8080'
|
||||||
|
const testEndpoints = [
|
||||||
|
'/api/version',
|
||||||
|
'/api/version/string',
|
||||||
|
'/api/version/full',
|
||||||
|
'/api/version/check-update'
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const endpoint of testEndpoints) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${baseUrl}${endpoint}`)
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
console.log(`\n接口: ${endpoint}`)
|
||||||
|
console.log(`状态码: ${response.status}`)
|
||||||
|
console.log(`响应:`, JSON.stringify(data, null, 2))
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
console.log('✅ 接口测试通过')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 接口测试失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ 接口 ${endpoint} 测试失败:`, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试GitHub版本检查
|
||||||
|
console.log('\n3. 测试GitHub版本检查:')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://api.github.com/repos/ctwj/urldb/releases/latest')
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
console.log('GitHub API响应:')
|
||||||
|
console.log(`状态码: ${response.status}`)
|
||||||
|
console.log(`最新版本: ${data.tag_name || 'N/A'}`)
|
||||||
|
console.log(`发布日期: ${data.published_at || 'N/A'}`)
|
||||||
|
|
||||||
|
if (data.tag_name) {
|
||||||
|
console.log('✅ GitHub版本检查测试通过')
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ GitHub上暂无Release')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ GitHub版本检查测试失败:', error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试前端版本页面
|
||||||
|
console.log('\n4. 测试前端版本页面:')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('http://localhost:3000/version')
|
||||||
|
const html = await response.text()
|
||||||
|
|
||||||
|
console.log(`状态码: ${response.status}`)
|
||||||
|
|
||||||
|
if (html.includes('版本信息') && html.includes('VersionInfo')) {
|
||||||
|
console.log('✅ 前端版本页面测试通过')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 前端版本页面测试失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 前端版本页面测试失败:', error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试Git标签
|
||||||
|
console.log('\n5. 测试Git标签:')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { stdout: tagOutput } = await execAsync('git tag -l')
|
||||||
|
console.log('当前Git标签:')
|
||||||
|
console.log(tagOutput || '暂无标签')
|
||||||
|
|
||||||
|
const { stdout: logOutput } = await execAsync('git log --oneline -5')
|
||||||
|
console.log('最近5次提交:')
|
||||||
|
console.log(logOutput)
|
||||||
|
|
||||||
|
console.log('✅ Git标签测试通过')
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Git标签测试失败:', error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n✅ GitHub版本系统测试完成')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行测试
|
||||||
|
testGitHubVersion()
|
||||||
83
test-version-system.js
Normal file
83
test-version-system.js
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// 测试版本系统
|
||||||
|
const testVersionSystem = async () => {
|
||||||
|
console.log('测试版本系统...')
|
||||||
|
|
||||||
|
const baseUrl = 'http://localhost:8080'
|
||||||
|
|
||||||
|
// 测试版本API接口
|
||||||
|
const testEndpoints = [
|
||||||
|
'/api/version',
|
||||||
|
'/api/version/string',
|
||||||
|
'/api/version/full',
|
||||||
|
'/api/version/check-update'
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const endpoint of testEndpoints) {
|
||||||
|
console.log(`\n测试接口: ${endpoint}`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${baseUrl}${endpoint}`)
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
console.log(`状态码: ${response.status}`)
|
||||||
|
console.log(`响应:`, JSON.stringify(data, null, 2))
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
console.log('✅ 接口测试通过')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 接口测试失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ 请求失败:`, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试版本管理脚本
|
||||||
|
console.log('\n测试版本管理脚本...')
|
||||||
|
|
||||||
|
const { exec } = require('child_process')
|
||||||
|
const { promisify } = require('util')
|
||||||
|
const execAsync = promisify(exec)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 显示版本信息
|
||||||
|
const { stdout: showOutput } = await execAsync('./scripts/version.sh show')
|
||||||
|
console.log('版本信息:')
|
||||||
|
console.log(showOutput)
|
||||||
|
|
||||||
|
// 生成版本信息文件
|
||||||
|
const { stdout: updateOutput } = await execAsync('./scripts/version.sh update')
|
||||||
|
console.log('生成版本信息文件:')
|
||||||
|
console.log(updateOutput)
|
||||||
|
|
||||||
|
console.log('✅ 版本管理脚本测试通过')
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 版本管理脚本测试失败:', error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试前端版本页面
|
||||||
|
console.log('\n测试前端版本页面...')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('http://localhost:3000/version')
|
||||||
|
const html = await response.text()
|
||||||
|
|
||||||
|
console.log(`状态码: ${response.status}`)
|
||||||
|
|
||||||
|
if (html.includes('版本信息') && html.includes('VersionInfo')) {
|
||||||
|
console.log('✅ 前端版本页面测试通过')
|
||||||
|
} else {
|
||||||
|
console.log('❌ 前端版本页面测试失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 前端版本页面测试失败:', error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n✅ 版本系统测试完成')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行测试
|
||||||
|
testVersionSystem()
|
||||||
124
utils/version.go
Normal file
124
utils/version.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VersionInfo 版本信息结构
|
||||||
|
type VersionInfo struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
BuildTime time.Time `json:"build_time"`
|
||||||
|
GitCommit string `json:"git_commit"`
|
||||||
|
GitBranch string `json:"git_branch"`
|
||||||
|
GoVersion string `json:"go_version"`
|
||||||
|
NodeVersion string `json:"node_version"`
|
||||||
|
Platform string `json:"platform"`
|
||||||
|
Arch string `json:"arch"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编译时注入的版本信息
|
||||||
|
var (
|
||||||
|
Version = "1.0.0"
|
||||||
|
BuildTime = time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
GitCommit = "unknown"
|
||||||
|
GitBranch = "unknown"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetVersionInfo 获取版本信息
|
||||||
|
func GetVersionInfo() *VersionInfo {
|
||||||
|
buildTime, _ := time.Parse("2006-01-02 15:04:05", BuildTime)
|
||||||
|
|
||||||
|
return &VersionInfo{
|
||||||
|
Version: Version,
|
||||||
|
BuildTime: buildTime,
|
||||||
|
GitCommit: GitCommit,
|
||||||
|
GitBranch: GitBranch,
|
||||||
|
GoVersion: runtime.Version(),
|
||||||
|
NodeVersion: getNodeVersion(),
|
||||||
|
Platform: runtime.GOOS,
|
||||||
|
Arch: runtime.GOARCH,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersionString 获取版本字符串
|
||||||
|
func GetVersionString() string {
|
||||||
|
info := GetVersionInfo()
|
||||||
|
return fmt.Sprintf("v%s (%s)", info.Version, info.GitCommit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFullVersionInfo 获取完整版本信息
|
||||||
|
func GetFullVersionInfo() string {
|
||||||
|
info := GetVersionInfo()
|
||||||
|
return fmt.Sprintf(`版本信息:
|
||||||
|
版本号: v%s
|
||||||
|
构建时间: %s
|
||||||
|
Git提交: %s
|
||||||
|
Git分支: %s
|
||||||
|
Go版本: %s
|
||||||
|
Node版本: %s
|
||||||
|
平台: %s/%s`,
|
||||||
|
info.Version,
|
||||||
|
info.BuildTime.Format("2006-01-02 15:04:05"),
|
||||||
|
info.GitCommit,
|
||||||
|
info.GitBranch,
|
||||||
|
info.GoVersion,
|
||||||
|
info.NodeVersion,
|
||||||
|
info.Platform,
|
||||||
|
info.Arch,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadVersionFromFile 从文件加载版本信息
|
||||||
|
func LoadVersionFromFile(filename string) (*VersionInfo, error) {
|
||||||
|
data, err := os.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var info VersionInfo
|
||||||
|
err = json.Unmarshal(data, &info)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveVersionToFile 保存版本信息到文件
|
||||||
|
func SaveVersionToFile(filename string, info *VersionInfo) error {
|
||||||
|
data, err := json.MarshalIndent(info, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.WriteFile(filename, data, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getNodeVersion 获取Node.js版本
|
||||||
|
func getNodeVersion() string {
|
||||||
|
// 这里可以通过执行 node --version 来获取
|
||||||
|
// 为了简化,返回一个默认值
|
||||||
|
return "N/A"
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVersionNewer 比较版本号
|
||||||
|
func IsVersionNewer(version1, version2 string) bool {
|
||||||
|
// 简单的版本比较,可以根据需要实现更复杂的逻辑
|
||||||
|
return version1 > version2
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersionComponents 获取版本号组件
|
||||||
|
func GetVersionComponents(version string) (major, minor, patch int, err error) {
|
||||||
|
var majorStr, minorStr, patchStr string
|
||||||
|
_, err = fmt.Sscanf(version, "%s.%s.%s", &majorStr, &minorStr, &patchStr)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这里可以添加更复杂的版本号解析逻辑
|
||||||
|
return 1, 0, 0, nil
|
||||||
|
}
|
||||||
2
web/components.d.ts
vendored
2
web/components.d.ts
vendored
@@ -8,7 +8,9 @@ export {}
|
|||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
|
NAlert: typeof import('naive-ui')['NAlert']
|
||||||
NButton: typeof import('naive-ui')['NButton']
|
NButton: typeof import('naive-ui')['NButton']
|
||||||
|
NSelect: typeof import('naive-ui')['NSelect']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
}
|
}
|
||||||
|
|||||||
120
web/components/AdminHeader.vue
Normal file
120
web/components/AdminHeader.vue
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<template>
|
||||||
|
<div class="bg-slate-800 dark:bg-gray-800 text-white dark:text-gray-100 rounded-lg shadow-lg p-4 sm:p-8 text-center relative">
|
||||||
|
<!-- 页面标题和面包屑 -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<h1 class="text-2xl sm:text-3xl font-bold mb-2">
|
||||||
|
<NuxtLink to="/" class="text-white hover:text-gray-200 dark:hover:text-gray-300 no-underline">
|
||||||
|
{{ systemConfig?.site_title || '网盘资源数据库' }}
|
||||||
|
</NuxtLink>
|
||||||
|
</h1>
|
||||||
|
<!-- 面包屑导航 -->
|
||||||
|
<div v-if="currentPageTitle && currentPageTitle !== '管理后台'" class="absolute left-4 bottom-4 flex items-center justify-start text-sm text-white/80">
|
||||||
|
<NuxtLink to="/admin" class="hover:text-white transition-colors">
|
||||||
|
<i class="fas fa-home mr-1"></i>管理后台
|
||||||
|
</NuxtLink>
|
||||||
|
<i class="fas fa-angle-right mx-2 text-white/60"></i>
|
||||||
|
<span class="text-white">
|
||||||
|
<i :class="currentPageIcon + ' mr-1'"></i>
|
||||||
|
{{ currentPageTitle }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<!-- 页面描述 -->
|
||||||
|
<!-- <div v-if="currentPageDescription && currentPageTitle !== '管理后台'" class="text-xs text-white/60 mt-1">
|
||||||
|
{{ currentPageDescription }}
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="absolute left-4 top-4 flex items-center gap-2">
|
||||||
|
<NuxtLink to="/" class="sm:flex">
|
||||||
|
<n-button size="tiny" type="tertiary" round ghost class="!px-2 !py-1 !text-xs !text-white dark:!text-white !border-white/30 hover:!border-white">
|
||||||
|
<i class="fas fa-home text-xs"></i> 前端首页
|
||||||
|
</n-button>
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右上角用户信息和操作按钮 -->
|
||||||
|
<div class="absolute right-4 top-4 flex items-center gap-2">
|
||||||
|
<!-- 用户信息 -->
|
||||||
|
<div v-if="userStore.isAuthenticated" class="hidden sm:flex items-center gap-2">
|
||||||
|
<span class="text-sm text-white/80">欢迎,{{ userStore.user?.username || '管理员' }}</span>
|
||||||
|
<span class="px-2 py-1 bg-blue-600/80 rounded text-xs text-white">{{ userStore.user?.role || 'admin' }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<div class="flex gap-1">
|
||||||
|
<button
|
||||||
|
v-if="userStore.isAuthenticated"
|
||||||
|
@click="logout"
|
||||||
|
class="sm:flex"
|
||||||
|
>
|
||||||
|
<n-button size="tiny" type="tertiary" round ghost class="!px-2 !py-1 !text-xs !text-white dark:!text-white !border-white/30 hover:!border-white">
|
||||||
|
<i class="fas fa-sign-out-alt text-xs"></i> 退出
|
||||||
|
</n-button>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 移动端用户信息 -->
|
||||||
|
<div v-if="userStore.isAuthenticated" class="sm:hidden mt-4 text-sm text-white/80">
|
||||||
|
<span>欢迎,{{ userStore.user?.username || '管理员' }}</span>
|
||||||
|
<span class="ml-2 px-2 py-1 bg-blue-600/80 rounded text-xs text-white">{{ userStore.user?.role || 'admin' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
title?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
title: '管理后台'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 用户状态管理
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 页面配置
|
||||||
|
const route = useRoute()
|
||||||
|
const pageConfig = computed(() => {
|
||||||
|
const configs: Record<string, { title: string; icon: string; description?: string }> = {
|
||||||
|
'/admin': { title: '管理后台', icon: 'fas fa-tachometer-alt', description: '系统管理总览' },
|
||||||
|
'/admin/users': { title: '用户管理', icon: 'fas fa-users', description: '管理系统用户' },
|
||||||
|
'/admin/categories': { title: '分类管理', icon: 'fas fa-folder', description: '管理资源分类' },
|
||||||
|
'/admin/tags': { title: '标签管理', icon: 'fas fa-tags', description: '管理资源标签' },
|
||||||
|
'/admin/system-config': { title: '系统配置', icon: 'fas fa-cog', description: '系统参数设置' },
|
||||||
|
'/admin/resources': { title: '资源管理', icon: 'fas fa-database', description: '管理网盘资源' },
|
||||||
|
'/admin/cks': { title: '平台账号管理', icon: 'fas fa-key', description: '管理第三方平台账号' },
|
||||||
|
'/admin/ready-resources': { title: '待处理资源', icon: 'fas fa-clock', description: '批量处理资源' },
|
||||||
|
'/admin/search-stats': { title: '搜索统计', icon: 'fas fa-chart-bar', description: '搜索数据分析' },
|
||||||
|
'/admin/hot-dramas': { title: '热播剧管理', icon: 'fas fa-film', description: '管理热门剧集' },
|
||||||
|
'/monitor': { title: '系统监控', icon: 'fas fa-desktop', description: '系统性能监控' },
|
||||||
|
'/add-resource': { title: '添加资源', icon: 'fas fa-plus', description: '添加新资源' },
|
||||||
|
'/api-docs': { title: 'API文档', icon: 'fas fa-book', description: '接口文档说明' },
|
||||||
|
'/admin/version': { title: '版本信息', icon: 'fas fa-code-branch', description: '系统版本详情' }
|
||||||
|
}
|
||||||
|
return configs[route.path] || { title: props.title, icon: 'fas fa-cog', description: '管理页面' }
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentPageTitle = computed(() => pageConfig.value.title)
|
||||||
|
const currentPageIcon = computed(() => pageConfig.value.icon)
|
||||||
|
const currentPageDescription = computed(() => pageConfig.value.description)
|
||||||
|
|
||||||
|
// 获取系统配置
|
||||||
|
const { data: systemConfigData } = await useAsyncData('systemConfig',
|
||||||
|
() => $fetch('/api/system-config')
|
||||||
|
)
|
||||||
|
|
||||||
|
const systemConfig = computed(() => (systemConfigData.value as any)?.data || { site_title: '网盘资源数据库' })
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
const logout = async () => {
|
||||||
|
await userStore.logout()
|
||||||
|
await router.push('/login')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 确保样式与首页完全一致 */
|
||||||
|
</style>
|
||||||
23
web/components/AppFooter.vue
Normal file
23
web/components/AppFooter.vue
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<template>
|
||||||
|
<footer class="mt-auto py-6 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
|
||||||
|
<div class="max-w-7xl mx-auto text-center text-gray-600 dark:text-gray-400 text-sm px-3 sm:px-5">
|
||||||
|
<p class="mb-2">本站内容由网络爬虫自动抓取。本站不储存、复制、传播任何文件,仅作个人公益学习,请在获取后24小内删除!!!</p>
|
||||||
|
<p class="flex items-center justify-center gap-2">
|
||||||
|
<span>{{ systemConfig?.copyright || '© 2025 网盘资源数据库 By 老九' }}</span>
|
||||||
|
<span v-if="versionInfo.version" class="text-gray-400 dark:text-gray-500">| v{{ versionInfo.version }}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 使用版本信息组合式函数
|
||||||
|
const { versionInfo } = useVersion()
|
||||||
|
|
||||||
|
// 获取系统配置
|
||||||
|
const { data: systemConfigData } = await useAsyncData('systemConfig',
|
||||||
|
() => $fetch('/api/system-config')
|
||||||
|
)
|
||||||
|
|
||||||
|
const systemConfig = computed(() => (systemConfigData.value as any)?.data || { copyright: '© 2025 网盘资源数据库 By 老九' })
|
||||||
|
</script>
|
||||||
219
web/components/VersionInfo.vue
Normal file
219
web/components/VersionInfo.vue
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
<template>
|
||||||
|
<div class="version-info">
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||||
|
<i class="fas fa-info-circle mr-2 text-blue-500"></i>
|
||||||
|
版本信息
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
@click="refreshVersion"
|
||||||
|
:disabled="loading"
|
||||||
|
class="px-3 py-1 text-sm bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<i class="fas fa-sync-alt mr-1" :class="{ 'animate-spin': loading }"></i>
|
||||||
|
刷新
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="loading" class="text-center py-4">
|
||||||
|
<i class="fas fa-spinner fa-spin text-blue-500 text-xl"></i>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400 mt-2">加载中...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="error" class="text-center py-4">
|
||||||
|
<i class="fas fa-exclamation-triangle text-red-500 text-xl"></i>
|
||||||
|
<p class="text-red-600 dark:text-red-400 mt-2">{{ error }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="space-y-4">
|
||||||
|
<!-- 版本号 -->
|
||||||
|
<div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700 rounded">
|
||||||
|
<span class="text-gray-700 dark:text-gray-300">版本号</span>
|
||||||
|
<span class="font-mono text-blue-600 dark:text-blue-400">v{{ versionInfo.version }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 构建时间 -->
|
||||||
|
<div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700 rounded">
|
||||||
|
<span class="text-gray-700 dark:text-gray-300">构建时间</span>
|
||||||
|
<span class="text-gray-600 dark:text-gray-400">{{ formatTime(versionInfo.build_time) }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Git提交 -->
|
||||||
|
<div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700 rounded">
|
||||||
|
<span class="text-gray-700 dark:text-gray-300">Git提交</span>
|
||||||
|
<span class="font-mono text-gray-600 dark:text-gray-400">{{ versionInfo.git_commit }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Git分支 -->
|
||||||
|
<div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700 rounded">
|
||||||
|
<span class="text-gray-700 dark:text-gray-300">Git分支</span>
|
||||||
|
<span class="text-gray-600 dark:text-gray-400">{{ versionInfo.git_branch }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Go版本 -->
|
||||||
|
<div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700 rounded">
|
||||||
|
<span class="text-gray-700 dark:text-gray-300">Go版本</span>
|
||||||
|
<span class="text-gray-600 dark:text-gray-400">{{ versionInfo.go_version }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 平台信息 -->
|
||||||
|
<div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700 rounded">
|
||||||
|
<span class="text-gray-700 dark:text-gray-300">平台</span>
|
||||||
|
<span class="text-gray-600 dark:text-gray-400">{{ versionInfo.platform }}/{{ versionInfo.arch }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 更新检查 -->
|
||||||
|
<div class="mt-6 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h4 class="font-medium text-blue-900 dark:text-blue-100">检查更新</h4>
|
||||||
|
<p class="text-sm text-blue-700 dark:text-blue-300 mt-1">
|
||||||
|
当前版本: v{{ versionInfo.version }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
@click="checkUpdate"
|
||||||
|
:disabled="updateChecking"
|
||||||
|
class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<i class="fas fa-download mr-1" :class="{ 'animate-spin': updateChecking }"></i>
|
||||||
|
检查更新
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="updateInfo" class="mt-3 p-3 bg-white dark:bg-gray-800 rounded border">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm text-gray-600 dark:text-gray-400">最新版本</span>
|
||||||
|
<span class="font-mono text-gray-900 dark:text-white">v{{ updateInfo.latest_version }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="updateInfo.has_update" class="mt-2 p-2 bg-green-50 dark:bg-green-900/20 rounded border border-green-200 dark:border-green-800">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<i class="fas fa-arrow-up text-green-600 dark:text-green-400 mr-2"></i>
|
||||||
|
<span class="text-sm text-green-700 dark:text-green-300">有新版本可用</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="mt-2 p-2 bg-gray-50 dark:bg-gray-700 rounded border">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<i class="fas fa-check text-gray-600 dark:text-gray-400 mr-2"></i>
|
||||||
|
<span class="text-sm text-gray-700 dark:text-gray-300">已是最新版本</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
interface VersionInfo {
|
||||||
|
version: string
|
||||||
|
build_time: string
|
||||||
|
git_commit: string
|
||||||
|
git_branch: string
|
||||||
|
go_version: string
|
||||||
|
node_version: string
|
||||||
|
platform: string
|
||||||
|
arch: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateInfo {
|
||||||
|
current_version: string
|
||||||
|
latest_version: string
|
||||||
|
has_update: boolean
|
||||||
|
update_available: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref('')
|
||||||
|
const updateChecking = ref(false)
|
||||||
|
const versionInfo = ref<VersionInfo>({
|
||||||
|
version: '',
|
||||||
|
build_time: '',
|
||||||
|
git_commit: '',
|
||||||
|
git_branch: '',
|
||||||
|
go_version: '',
|
||||||
|
node_version: '',
|
||||||
|
platform: '',
|
||||||
|
arch: ''
|
||||||
|
})
|
||||||
|
const updateInfo = ref<UpdateInfo | null>(null)
|
||||||
|
|
||||||
|
// 获取版本信息
|
||||||
|
const fetchVersionInfo = async () => {
|
||||||
|
loading.value = true
|
||||||
|
error.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await $fetch('/api/version') as any
|
||||||
|
if (response.success) {
|
||||||
|
versionInfo.value = response.data
|
||||||
|
} else {
|
||||||
|
error.value = response.message || '获取版本信息失败'
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
error.value = err.message || '网络错误'
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查更新
|
||||||
|
const checkUpdate = async () => {
|
||||||
|
updateChecking.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await $fetch('/api/version/check-update') as any
|
||||||
|
if (response.success) {
|
||||||
|
updateInfo.value = response.data
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('检查更新失败:', err)
|
||||||
|
} finally {
|
||||||
|
updateChecking.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新版本信息
|
||||||
|
const refreshVersion = () => {
|
||||||
|
fetchVersionInfo()
|
||||||
|
updateInfo.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
|
const formatTime = (timeStr: string) => {
|
||||||
|
if (!timeStr) return 'N/A'
|
||||||
|
try {
|
||||||
|
const date = new Date(timeStr)
|
||||||
|
return date.toLocaleString('zh-CN')
|
||||||
|
} catch {
|
||||||
|
return timeStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载时获取版本信息
|
||||||
|
onMounted(() => {
|
||||||
|
fetchVersionInfo()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.version-info {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-spin {
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
117
web/composables/useVersion.ts
Normal file
117
web/composables/useVersion.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
interface VersionInfo {
|
||||||
|
version: string
|
||||||
|
build_time: string
|
||||||
|
git_commit: string
|
||||||
|
git_branch: string
|
||||||
|
go_version: string
|
||||||
|
node_version: string
|
||||||
|
platform: string
|
||||||
|
arch: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface VersionResponse {
|
||||||
|
success: boolean
|
||||||
|
data: VersionInfo
|
||||||
|
message: string
|
||||||
|
time: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useVersion = () => {
|
||||||
|
const versionInfo = ref<VersionInfo>({
|
||||||
|
version: '1.0.0',
|
||||||
|
build_time: '',
|
||||||
|
git_commit: 'unknown',
|
||||||
|
git_branch: 'unknown',
|
||||||
|
go_version: '',
|
||||||
|
node_version: '',
|
||||||
|
platform: '',
|
||||||
|
arch: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref('')
|
||||||
|
|
||||||
|
// 获取版本信息
|
||||||
|
const fetchVersionInfo = async () => {
|
||||||
|
loading.value = true
|
||||||
|
error.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await $fetch('/api/version') as VersionResponse
|
||||||
|
if (response.success) {
|
||||||
|
versionInfo.value = response.data
|
||||||
|
} else {
|
||||||
|
error.value = response.message || '获取版本信息失败'
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
error.value = err.message || '网络错误'
|
||||||
|
console.error('获取版本信息失败:', err)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取版本字符串
|
||||||
|
const getVersionString = async () => {
|
||||||
|
try {
|
||||||
|
const response = await $fetch('/api/version/string') as any
|
||||||
|
if (response.success) {
|
||||||
|
return response.data.version
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('获取版本字符串失败:', err)
|
||||||
|
}
|
||||||
|
return versionInfo.value.version
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查更新
|
||||||
|
const checkUpdate = async () => {
|
||||||
|
try {
|
||||||
|
const response = await $fetch('/api/version/check-update') as any
|
||||||
|
if (response.success) {
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('检查更新失败:', err)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化版本信息
|
||||||
|
const formatVersionInfo = computed(() => {
|
||||||
|
const info = versionInfo.value
|
||||||
|
return {
|
||||||
|
version: info.version,
|
||||||
|
gitCommit: info.git_commit !== 'unknown' ? info.git_commit : null,
|
||||||
|
gitBranch: info.git_branch !== 'unknown' ? info.git_branch : null,
|
||||||
|
buildTime: info.build_time ? new Date(info.build_time).toLocaleString('zh-CN') : null,
|
||||||
|
platform: `${info.platform}/${info.arch}`,
|
||||||
|
goVersion: info.go_version,
|
||||||
|
nodeVersion: info.node_version
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取完整版本信息
|
||||||
|
const getFullVersionInfo = async () => {
|
||||||
|
try {
|
||||||
|
const response = await $fetch('/api/version/full') as any
|
||||||
|
if (response.success) {
|
||||||
|
return response.data.version_info
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('获取完整版本信息失败:', err)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
versionInfo: readonly(versionInfo),
|
||||||
|
loading: readonly(loading),
|
||||||
|
error: readonly(error),
|
||||||
|
formatVersionInfo,
|
||||||
|
fetchVersionInfo,
|
||||||
|
getVersionString,
|
||||||
|
checkUpdate,
|
||||||
|
getFullVersionInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
80
web/layouts/admin.vue
Normal file
80
web/layouts/admin.vue
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100">
|
||||||
|
<!-- 全局加载状态 -->
|
||||||
|
<div v-if="pageLoading" class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center z-50">
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-8 shadow-xl">
|
||||||
|
<div class="flex flex-col items-center space-y-4">
|
||||||
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
||||||
|
<div class="text-center">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">正在加载...</h3>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">请稍候,正在初始化管理后台</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 管理页面头部 -->
|
||||||
|
<div class="p-3 sm:p-5">
|
||||||
|
<AdminHeader :title="pageTitle" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 主要内容区域 -->
|
||||||
|
<div class="p-3 sm:p-5">
|
||||||
|
<div class="max-w-7xl mx-auto">
|
||||||
|
<!-- 页面内容插槽 -->
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 页脚 -->
|
||||||
|
<AppFooter />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 页面加载状态
|
||||||
|
const pageLoading = ref(false)
|
||||||
|
|
||||||
|
// 页面标题
|
||||||
|
const route = useRoute()
|
||||||
|
const pageTitle = computed(() => {
|
||||||
|
const titles: Record<string, string> = {
|
||||||
|
'/admin': '管理后台',
|
||||||
|
'/users': '用户管理',
|
||||||
|
'/categories': '分类管理',
|
||||||
|
'/tags': '标签管理',
|
||||||
|
'/system-config': '系统配置',
|
||||||
|
'/resources': '资源管理',
|
||||||
|
'/cks': '平台账号管理',
|
||||||
|
'/ready-resources': '待处理资源',
|
||||||
|
'/search-stats': '搜索统计',
|
||||||
|
'/hot-dramas': '热播剧管理',
|
||||||
|
'/monitor': '系统监控',
|
||||||
|
'/add-resource': '添加资源',
|
||||||
|
'/api-docs': 'API文档',
|
||||||
|
'/version': '版本信息'
|
||||||
|
}
|
||||||
|
return titles[route.path] || '管理后台'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听路由变化,显示加载状态
|
||||||
|
watch(() => route.path, () => {
|
||||||
|
pageLoading.value = true
|
||||||
|
setTimeout(() => {
|
||||||
|
pageLoading.value = false
|
||||||
|
}, 300)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 页面加载时显示加载状态
|
||||||
|
onMounted(() => {
|
||||||
|
pageLoading.value = true
|
||||||
|
setTimeout(() => {
|
||||||
|
pageLoading.value = false
|
||||||
|
}, 300)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 管理后台专用样式 */
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<!-- 暗色模式切换按钮 -->
|
||||||
<button
|
<button
|
||||||
class="fixed top-4 right-4 z-50 w-8 h-8 flex items-center justify-center rounded-full shadow-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200 transition-all duration-200 hover:bg-blue-100 dark:hover:bg-blue-900 hover:scale-110 focus:outline-none"
|
class="fixed top-4 right-4 z-50 w-8 h-8 flex items-center justify-center rounded-full shadow-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200 transition-all duration-200 hover:bg-blue-100 dark:hover:bg-blue-900 hover:scale-110 focus:outline-none"
|
||||||
@click="toggleDarkMode"
|
@click="toggleDarkMode"
|
||||||
@@ -15,6 +16,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<NuxtPage />
|
<NuxtPage />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -25,6 +27,7 @@ import { ref, onMounted } from 'vue'
|
|||||||
|
|
||||||
const theme = lightTheme
|
const theme = lightTheme
|
||||||
const isDark = ref(false)
|
const isDark = ref(false)
|
||||||
|
|
||||||
const toggleDarkMode = () => {
|
const toggleDarkMode = () => {
|
||||||
isDark.value = !isDark.value
|
isDark.value = !isDark.value
|
||||||
if (isDark.value) {
|
if (isDark.value) {
|
||||||
@@ -35,7 +38,9 @@ const toggleDarkMode = () => {
|
|||||||
localStorage.setItem('theme', 'light')
|
localStorage.setItem('theme', 'light')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
// 初始化主题
|
||||||
if (localStorage.getItem('theme') === 'dark') {
|
if (localStorage.getItem('theme') === 'dark') {
|
||||||
isDark.value = true
|
isDark.value = true
|
||||||
document.documentElement.classList.add('dark')
|
document.documentElement.classList.add('dark')
|
||||||
|
|||||||
@@ -1,466 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100 p-3 sm:p-5">
|
|
||||||
<!-- 全局加载状态 -->
|
|
||||||
<div v-if="pageLoading" class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center z-50">
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg p-8 shadow-xl">
|
|
||||||
<div class="flex flex-col items-center space-y-4">
|
|
||||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
|
||||||
<div class="text-center">
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">正在加载...</h3>
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">请稍候,正在初始化管理后台</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="max-w-7xl mx-auto">
|
|
||||||
<!-- 头部 -->
|
|
||||||
<div class="bg-slate-800 text-white rounded-lg shadow-lg p-4 sm:p-8 mb-4 sm:mb-8 text-center">
|
|
||||||
<div class="flex justify-between items-center mb-4">
|
|
||||||
<h1 class="text-2xl sm:text-3xl font-bold">
|
|
||||||
<NuxtLink to="/" class="text-white hover:text-gray-200 dark:hover:text-gray-300 no-underline">
|
|
||||||
{{ systemConfig?.site_title || '网盘资源数据库' }}
|
|
||||||
</NuxtLink>
|
|
||||||
</h1>
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<div class="text-sm">
|
|
||||||
<span>欢迎,{{ userStore.userInfo?.username }}</span>
|
|
||||||
<span class="ml-2 px-2 py-1 bg-blue-600 rounded text-xs">{{ userStore.userInfo?.role }}</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
@click="handleLogout"
|
|
||||||
class="px-3 py-1 bg-red-600 hover:bg-red-700 rounded text-sm transition-colors"
|
|
||||||
>
|
|
||||||
退出登录
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<nav class="mt-4 flex flex-col sm:flex-row justify-center gap-2 sm:gap-4">
|
|
||||||
<NuxtLink
|
|
||||||
to="/"
|
|
||||||
class="w-full sm:w-auto px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md transition-colors text-center flex items-center justify-center gap-2"
|
|
||||||
>
|
|
||||||
<i class="fas fa-home"></i> 返回首页
|
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink
|
|
||||||
to="/add-resource"
|
|
||||||
class="w-full sm:w-auto px-4 py-2 bg-green-600 hover:bg-green-700 rounded-md transition-colors text-center flex items-center justify-center gap-2"
|
|
||||||
>
|
|
||||||
<i class="fas fa-plus"></i> 添加资源
|
|
||||||
</NuxtLink>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 管理功能区域 -->
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
|
|
||||||
<!-- 资源管理 -->
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
||||||
<div class="flex items-center mb-4">
|
|
||||||
<div class="p-3 bg-blue-100 rounded-lg">
|
|
||||||
<i class="fas fa-cloud text-blue-600 text-xl"></i>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">资源管理</h3>
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">管理所有资源</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<NuxtLink to="/resources" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors block">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">查看所有资源</span>
|
|
||||||
<i class="fas fa-chevron-right text-gray-400"></i>
|
|
||||||
</div>
|
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink to="/add-resource" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors block">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">批量添加资源</span>
|
|
||||||
<i class="fas fa-plus text-gray-400"></i>
|
|
||||||
</div>
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 平台管理 -->
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
||||||
<div class="flex items-center mb-4">
|
|
||||||
<div class="p-3 bg-green-100 rounded-lg">
|
|
||||||
<i class="fas fa-server text-green-600 text-xl"></i>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">平台管理</h3>
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">暂不支持修改</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<button @click="goToPlatformManagement" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">管理平台</span>
|
|
||||||
<i class="fas fa-chevron-right text-gray-400"></i>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<button @click="showAddPlatformModal = true" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">添加平台</span>
|
|
||||||
<i class="fas fa-plus text-gray-400"></i>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 第三方平台账号管理 -->
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
||||||
<div class="flex items-center mb-4">
|
|
||||||
<div class="p-3 bg-teal-100 rounded-lg">
|
|
||||||
<i class="fas fa-key text-teal-600 text-xl"></i>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">平台账号管理</h3>
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">管理第三方平台账号</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<NuxtLink to="/cks" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors block">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">管理账号</span>
|
|
||||||
<i class="fas fa-chevron-right text-gray-400"></i>
|
|
||||||
</div>
|
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink to="/cks" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors block">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">添加账号</span>
|
|
||||||
<i class="fas fa-plus text-gray-400"></i>
|
|
||||||
</div>
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 分类管理 -->
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
||||||
<div class="flex items-center mb-4">
|
|
||||||
<div class="p-3 bg-purple-100 rounded-lg">
|
|
||||||
<i class="fas fa-folder text-purple-600 text-xl"></i>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">分类管理</h3>
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">管理资源分类</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<button @click="goToCategoryManagement" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">管理分类</span>
|
|
||||||
<i class="fas fa-chevron-right text-gray-400"></i>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<button @click="goToAddCategory" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">添加分类</span>
|
|
||||||
<i class="fas fa-plus text-gray-400"></i>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 标签管理 -->
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
||||||
<div class="flex items-center mb-4">
|
|
||||||
<div class="p-3 bg-orange-100 rounded-lg">
|
|
||||||
<i class="fas fa-tags text-orange-600 text-xl"></i>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">标签管理</h3>
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">管理资源标签</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<button @click="goToTagManagement" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">管理标签</span>
|
|
||||||
<i class="fas fa-chevron-right text-gray-400"></i>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<button @click="goToAddTag" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">添加标签</span>
|
|
||||||
<i class="fas fa-plus text-gray-400"></i>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 统计信息 -->
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
||||||
<div class="flex items-center mb-4">
|
|
||||||
<div class="p-3 bg-red-100 rounded-lg">
|
|
||||||
<i class="fas fa-chart-bar text-red-600 text-xl"></i>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">统计信息</h3>
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">系统统计数据</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-3">
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400">总资源数</span>
|
|
||||||
<span class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ stats?.total_resources || 0 }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400">总浏览量</span>
|
|
||||||
<span class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ stats?.total_views || 0 }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400">分类数量</span>
|
|
||||||
<span class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ stats?.total_categories || 0 }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 待处理资源 -->
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
||||||
<div class="flex items-center mb-4">
|
|
||||||
<div class="p-3 bg-yellow-100 rounded-lg">
|
|
||||||
<i class="fas fa-clock text-yellow-600 text-xl"></i>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">待处理资源</h3>
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">批量添加和管理</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<NuxtLink to="/ready-resources" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors block">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">管理待处理资源</span>
|
|
||||||
<i class="fas fa-chevron-right text-gray-400"></i>
|
|
||||||
</div>
|
|
||||||
</NuxtLink>
|
|
||||||
<button @click="goToBatchAdd" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">批量添加资源</span>
|
|
||||||
<i class="fas fa-plus text-gray-400"></i>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 搜索统计 -->
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
||||||
<div class="flex items-center mb-4">
|
|
||||||
<div class="p-3 bg-indigo-100 dark:bg-indigo-900 rounded-lg">
|
|
||||||
<i class="fas fa-search text-indigo-600 dark:text-indigo-300 text-xl"></i>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">搜索统计</h3>
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">搜索量分析和热门关键词</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<NuxtLink to="/search-stats" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors block">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">查看搜索统计</span>
|
|
||||||
<i class="fas fa-chart-line text-gray-400 dark:text-gray-300"></i>
|
|
||||||
</div>
|
|
||||||
</NuxtLink>
|
|
||||||
<button @click="goToHotKeywords" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">热门关键词</span>
|
|
||||||
<i class="fas fa-fire text-gray-400 dark:text-gray-300"></i>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 系统设置 -->
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
||||||
<div class="flex items-center mb-4">
|
|
||||||
<div class="p-3 bg-gray-100 dark:bg-gray-700 rounded-lg">
|
|
||||||
<i class="fas fa-cog text-gray-600 dark:text-gray-300 text-xl"></i>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">系统设置</h3>
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">系统配置</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<button @click="goToSystemSettings" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">系统配置</span>
|
|
||||||
<i class="fas fa-chevron-right text-gray-400 dark:text-gray-300"></i>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<button @click="goToUserManagement" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">用户管理</span>
|
|
||||||
<i class="fas fa-users text-gray-400 dark:text-gray-300"></i>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
definePageMeta({
|
|
||||||
middleware: 'auth'
|
|
||||||
})
|
|
||||||
|
|
||||||
// API
|
|
||||||
const { getSystemConfig } = useSystemConfigApi()
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const userStore = useUserStore()
|
|
||||||
const { $api } = useNuxtApp()
|
|
||||||
|
|
||||||
const user = ref(null)
|
|
||||||
const stats = ref(null)
|
|
||||||
const platforms = ref([])
|
|
||||||
const pageLoading = ref(true) // 添加页面加载状态
|
|
||||||
const systemConfig = ref(null) // 添加系统配置状态
|
|
||||||
|
|
||||||
// 页面元数据 - 移到变量声明之后
|
|
||||||
useHead({
|
|
||||||
title: () => systemConfig.value?.site_title ? `${systemConfig.value.site_title} - 管理后台` : '管理后台 - 网盘资源数据库',
|
|
||||||
meta: [
|
|
||||||
{
|
|
||||||
name: 'description',
|
|
||||||
content: () => systemConfig.value?.site_description || '网盘资源数据库管理后台'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'keywords',
|
|
||||||
content: () => systemConfig.value?.keywords || '网盘,资源管理,管理后台'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'author',
|
|
||||||
content: () => systemConfig.value?.author || '系统管理员'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取系统配置
|
|
||||||
const fetchSystemConfig = async () => {
|
|
||||||
try {
|
|
||||||
const response = await getSystemConfig()
|
|
||||||
console.log('admin系统配置响应:', response)
|
|
||||||
// 使用新的统一响应格式,直接使用response
|
|
||||||
if (response) {
|
|
||||||
systemConfig.value = response
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取系统配置失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查认证状态
|
|
||||||
const checkAuth = () => {
|
|
||||||
console.log('admin - checkAuth 开始')
|
|
||||||
// 中间件已经处理了认证检查,这里只需要确保用户状态已初始化
|
|
||||||
if (!userStore.isAuthenticated) {
|
|
||||||
userStore.initAuth()
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('admin - isAuthenticated:', userStore.isAuthenticated)
|
|
||||||
console.log('admin - user:', userStore.userInfo)
|
|
||||||
|
|
||||||
console.log('admin - 认证检查完成')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取统计信息
|
|
||||||
const fetchStats = async () => {
|
|
||||||
try {
|
|
||||||
const { useResourceApi } = await import('~/composables/useApi')
|
|
||||||
const resourceApi = useResourceApi()
|
|
||||||
const response = await resourceApi.getResources({ page: 1, page_size: 1 })
|
|
||||||
// 这里只取stats字段
|
|
||||||
stats.value = response.stats || null
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取统计信息失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取平台列表
|
|
||||||
const fetchPlatforms = async () => {
|
|
||||||
try {
|
|
||||||
const { usePanApi } = await import('~/composables/useApi')
|
|
||||||
const panApi = usePanApi()
|
|
||||||
const response = await panApi.getPans()
|
|
||||||
platforms.value = Array.isArray(response) ? response : []
|
|
||||||
console.log('获取到的平台数据:', platforms.value)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取平台列表失败:', error)
|
|
||||||
platforms.value = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 退出登录
|
|
||||||
const handleLogout = () => {
|
|
||||||
userStore.logout()
|
|
||||||
router.push('/')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面跳转方法
|
|
||||||
const goToResourceManagement = () => {
|
|
||||||
// 实现资源管理页面跳转
|
|
||||||
}
|
|
||||||
|
|
||||||
const goToPlatformManagement = () => {
|
|
||||||
// 实现平台管理页面跳转
|
|
||||||
}
|
|
||||||
|
|
||||||
const goToCategoryManagement = () => {
|
|
||||||
router.push('/categories')
|
|
||||||
}
|
|
||||||
|
|
||||||
const goToTagManagement = () => {
|
|
||||||
router.push('/tags')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增:跳转到分类管理并打开新增弹窗
|
|
||||||
const goToAddCategory = () => {
|
|
||||||
router.push('/categories?action=add')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增:跳转到标签管理并打开新增弹窗
|
|
||||||
const goToAddTag = () => {
|
|
||||||
router.push('/tags?action=add')
|
|
||||||
}
|
|
||||||
|
|
||||||
const goToBatchAdd = () => {
|
|
||||||
router.push('/ready-resources')
|
|
||||||
}
|
|
||||||
|
|
||||||
const goToSystemSettings = () => {
|
|
||||||
router.push('/system-config')
|
|
||||||
}
|
|
||||||
|
|
||||||
const goToUserManagement = () => {
|
|
||||||
router.push('/users')
|
|
||||||
}
|
|
||||||
|
|
||||||
const goToHotKeywords = () => {
|
|
||||||
router.push('/search-stats')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面加载时检查认证
|
|
||||||
onMounted(async () => {
|
|
||||||
try {
|
|
||||||
// 移除checkAuth调用,因为中间件已经处理了认证
|
|
||||||
// checkAuth()
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
fetchStats(),
|
|
||||||
fetchSystemConfig(),
|
|
||||||
fetchPlatforms()
|
|
||||||
])
|
|
||||||
} catch (error) {
|
|
||||||
console.error('admin页面初始化失败:', error)
|
|
||||||
} finally {
|
|
||||||
// 所有数据加载完成后,关闭加载状态
|
|
||||||
pageLoading.value = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
/* 可以添加自定义样式 */
|
|
||||||
</style>
|
|
||||||
@@ -81,6 +81,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
// 设置页面布局
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'admin'
|
||||||
|
})
|
||||||
|
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import BatchAddResource from '~/components/BatchAddResource.vue'
|
import BatchAddResource from '~/components/BatchAddResource.vue'
|
||||||
413
web/pages/admin/categories.vue
Normal file
413
web/pages/admin/categories.vue
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button @click="showAddModal = true"
|
||||||
|
class="px-4 py-2 bg-green-600 hover:bg-green-700 rounded-md transition-colors text-white text-sm flex items-center gap-2">
|
||||||
|
<i class="fas fa-plus"></i> 添加分类
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<div class="relative">
|
||||||
|
<input v-model="searchQuery" @keyup="debounceSearch" type="text"
|
||||||
|
class="w-64 px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500 transition-all text-sm"
|
||||||
|
placeholder="搜索分类名称..." />
|
||||||
|
<div class="absolute right-3 top-1/2 transform -translate-y-1/2">
|
||||||
|
<i class="fas fa-search text-gray-400 text-sm"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button @click="refreshData"
|
||||||
|
class="px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 flex items-center gap-2">
|
||||||
|
<i class="fas fa-refresh"></i> 刷新
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分类列表 -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden">
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="w-full min-w-full">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-slate-800 dark:bg-gray-700 text-white dark:text-gray-100">
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-medium">ID</th>
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-medium">分类名称</th>
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-medium">描述</th>
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-medium">资源数量</th>
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-medium">关联标签</th>
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-medium">操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
|
<tr v-if="loading" class="text-center py-8">
|
||||||
|
<td colspan="6" class="text-gray-500 dark:text-gray-400">
|
||||||
|
<i class="fas fa-spinner fa-spin mr-2"></i>加载中...
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-else-if="categories.length === 0" class="text-center py-8">
|
||||||
|
<td colspan="6" class="text-gray-500 dark:text-gray-400">
|
||||||
|
<div class="flex flex-col items-center justify-center py-12">
|
||||||
|
<svg class="w-16 h-16 text-gray-300 dark:text-gray-600 mb-4" fill="none" stroke="currentColor"
|
||||||
|
viewBox="0 0 48 48">
|
||||||
|
<circle cx="24" cy="24" r="20" stroke-width="3" stroke-dasharray="6 6" />
|
||||||
|
<path d="M16 24h16M24 16v16" stroke-width="3" stroke-linecap="round" />
|
||||||
|
</svg>
|
||||||
|
<div class="text-lg font-semibold text-gray-400 dark:text-gray-500 mb-2">暂无分类</div>
|
||||||
|
<div class="text-sm text-gray-400 dark:text-gray-600 mb-4">你可以点击上方"添加分类"按钮创建新分类</div>
|
||||||
|
<button @click="showAddModal = true"
|
||||||
|
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors text-sm flex items-center gap-2">
|
||||||
|
<i class="fas fa-plus"></i> 添加分类
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="category in categories" :key="category.id"
|
||||||
|
class="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-900 dark:text-gray-100 font-medium">{{ category.id }}</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
|
||||||
|
<span :title="category.name">{{ category.name }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
<span v-if="category.description" :title="category.description">{{ category.description }}</span>
|
||||||
|
<span v-else class="text-gray-400 dark:text-gray-500 italic">无描述</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
<span
|
||||||
|
class="px-2 py-1 bg-blue-100 dark:bg-blue-900/20 text-blue-800 dark:text-blue-300 rounded-full text-xs">
|
||||||
|
{{ category.resource_count || 0 }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
<span v-if="category.tag_names && category.tag_names.length > 0" class="text-gray-800 dark:text-gray-200">
|
||||||
|
{{ category.tag_names.join(', ') }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="text-gray-400 dark:text-gray-500 italic text-xs">无标签</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-sm">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button @click="editCategory(category)"
|
||||||
|
class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 transition-colors"
|
||||||
|
title="编辑分类">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
<button @click="deleteCategory(category.id)"
|
||||||
|
class="text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300 transition-colors"
|
||||||
|
title="删除分类">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div v-if="totalPages > 1" class="flex flex-wrap justify-center gap-1 sm:gap-2 mt-6">
|
||||||
|
<button v-if="currentPage > 1" @click="goToPage(currentPage - 1)"
|
||||||
|
class="bg-white text-gray-700 hover:bg-gray-50 px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm flex items-center">
|
||||||
|
<i class="fas fa-chevron-left mr-1"></i> 上一页
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button @click="goToPage(1)"
|
||||||
|
:class="currentPage === 1 ? 'bg-slate-800 text-white' : 'bg-white text-gray-700 hover:bg-gray-50'"
|
||||||
|
class="px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm">
|
||||||
|
1
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button v-if="totalPages > 1" @click="goToPage(2)"
|
||||||
|
:class="currentPage === 2 ? 'bg-slate-800 text-white' : 'bg-white text-gray-700 hover:bg-gray-50'"
|
||||||
|
class="px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm">
|
||||||
|
2
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span v-if="currentPage > 2" class="px-2 py-1 sm:px-3 sm:py-2 text-gray-500 text-sm">...</span>
|
||||||
|
|
||||||
|
<button v-if="currentPage !== 1 && currentPage !== 2 && currentPage > 2"
|
||||||
|
class="bg-slate-800 text-white px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm">
|
||||||
|
{{ currentPage }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button v-if="currentPage < totalPages" @click="goToPage(currentPage + 1)"
|
||||||
|
class="bg-white text-gray-700 hover:bg-gray-50 px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm flex items-center">
|
||||||
|
下一页 <i class="fas fa-chevron-right ml-1"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 统计信息 -->
|
||||||
|
<div v-if="totalPages <= 1" class="mt-4 text-center">
|
||||||
|
<div class="inline-flex items-center bg-white dark:bg-gray-800 rounded-lg shadow px-6 py-3">
|
||||||
|
<div class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
共 <span class="font-semibold text-gray-900 dark:text-gray-100">{{ totalCount }}</span> 个分类
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 添加/编辑分类模态框 -->
|
||||||
|
<div v-if="showAddModal" class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center z-50 p-4">
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full">
|
||||||
|
<div class="p-6">
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||||
|
{{ editingCategory ? '编辑分类' : '添加分类' }}
|
||||||
|
</h3>
|
||||||
|
<button @click="closeModal"
|
||||||
|
class="text-gray-500 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form @submit.prevent="handleSubmit">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">分类名称:</label>
|
||||||
|
<input v-model="formData.name" type="text" required
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-gray-100 dark:placeholder-gray-500"
|
||||||
|
placeholder="请输入分类名称" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">描述:</label>
|
||||||
|
<textarea v-model="formData.description" rows="3"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-gray-100 dark:placeholder-gray-500"
|
||||||
|
placeholder="请输入分类描述(可选)"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end gap-3">
|
||||||
|
<button type="button" @click="closeModal"
|
||||||
|
class="px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-200 dark:bg-gray-600 rounded-md hover:bg-gray-300 dark:hover:bg-gray-500 transition-colors">
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
<button type="submit" :disabled="submitting"
|
||||||
|
class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
|
||||||
|
{{ submitting ? '提交中...' : (editingCategory ? '更新' : '添加') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 设置页面布局
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'admin'
|
||||||
|
})
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
|
||||||
|
// 页面状态
|
||||||
|
const pageLoading = ref(true)
|
||||||
|
const loading = ref(false)
|
||||||
|
const categories = ref([])
|
||||||
|
|
||||||
|
// 分页状态
|
||||||
|
const currentPage = ref(1)
|
||||||
|
const pageSize = ref(20)
|
||||||
|
const totalCount = ref(0)
|
||||||
|
const totalPages = ref(0)
|
||||||
|
|
||||||
|
// 搜索状态
|
||||||
|
const searchQuery = ref('')
|
||||||
|
let searchTimeout: NodeJS.Timeout | null = null
|
||||||
|
|
||||||
|
// 模态框状态
|
||||||
|
const showAddModal = ref(false)
|
||||||
|
const submitting = ref(false)
|
||||||
|
const editingCategory = ref(null)
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const formData = ref({
|
||||||
|
name: '',
|
||||||
|
description: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取认证头
|
||||||
|
const getAuthHeaders = () => {
|
||||||
|
return userStore.authHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面元数据
|
||||||
|
useHead({
|
||||||
|
title: '分类管理 - 网盘资源数据库',
|
||||||
|
meta: [
|
||||||
|
{ name: 'description', content: '管理网盘资源分类' },
|
||||||
|
{ name: 'keywords', content: '分类管理,资源管理' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 检查认证状态
|
||||||
|
const checkAuth = () => {
|
||||||
|
userStore.initAuth()
|
||||||
|
if (!userStore.isAuthenticated) {
|
||||||
|
router.push('/')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取分类列表
|
||||||
|
const fetchCategories = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
const params = {
|
||||||
|
page: currentPage.value,
|
||||||
|
page_size: pageSize.value,
|
||||||
|
search: searchQuery.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await $fetch('/categories', {
|
||||||
|
baseURL: config.public.apiBase,
|
||||||
|
params
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
if (response && typeof response === 'object' && 'code' in response && response.code === 200) {
|
||||||
|
categories.value = response.data.items || []
|
||||||
|
totalCount.value = response.data.total || 0
|
||||||
|
totalPages.value = Math.ceil(totalCount.value / pageSize.value)
|
||||||
|
} else {
|
||||||
|
categories.value = response.items || []
|
||||||
|
totalCount.value = response.total || 0
|
||||||
|
totalPages.value = Math.ceil(totalCount.value / pageSize.value)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取分类列表失败:', error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索防抖
|
||||||
|
const debounceSearch = () => {
|
||||||
|
if (searchTimeout) {
|
||||||
|
clearTimeout(searchTimeout)
|
||||||
|
}
|
||||||
|
searchTimeout = setTimeout(() => {
|
||||||
|
currentPage.value = 1
|
||||||
|
fetchCategories()
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新数据
|
||||||
|
const refreshData = () => {
|
||||||
|
fetchCategories()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页跳转
|
||||||
|
const goToPage = (page: number) => {
|
||||||
|
currentPage.value = page
|
||||||
|
fetchCategories()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑分类
|
||||||
|
const editCategory = (category: any) => {
|
||||||
|
editingCategory.value = category
|
||||||
|
formData.value = {
|
||||||
|
name: category.name,
|
||||||
|
description: category.description || ''
|
||||||
|
}
|
||||||
|
showAddModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除分类
|
||||||
|
const deleteCategory = async (categoryId: number) => {
|
||||||
|
if (!confirm(`确定要删除分类吗?`)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await $fetch(`/categories/${categoryId}`, {
|
||||||
|
baseURL: config.public.apiBase,
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: getAuthHeaders()
|
||||||
|
})
|
||||||
|
await fetchCategories()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除分类失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
submitting.value = true
|
||||||
|
|
||||||
|
if (editingCategory.value) {
|
||||||
|
await $fetch(`/categories/${editingCategory.value.id}`, {
|
||||||
|
baseURL: config.public.apiBase,
|
||||||
|
method: 'PUT',
|
||||||
|
body: formData.value,
|
||||||
|
headers: getAuthHeaders()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await $fetch('/categories', {
|
||||||
|
baseURL: config.public.apiBase,
|
||||||
|
method: 'POST',
|
||||||
|
body: formData.value,
|
||||||
|
headers: getAuthHeaders()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
closeModal()
|
||||||
|
await fetchCategories()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('提交分类失败:', error)
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭模态框
|
||||||
|
const closeModal = () => {
|
||||||
|
showAddModal.value = false
|
||||||
|
editingCategory.value = null
|
||||||
|
formData.value = {
|
||||||
|
name: '',
|
||||||
|
description: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
|
const formatTime = (timestamp: string) => {
|
||||||
|
if (!timestamp) return '-'
|
||||||
|
const date = new Date(timestamp)
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
const hours = String(date.getHours()).padStart(2, '0')
|
||||||
|
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
const handleLogout = () => {
|
||||||
|
userStore.logout()
|
||||||
|
navigateTo('/login')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
checkAuth()
|
||||||
|
await fetchCategories()
|
||||||
|
|
||||||
|
// 检查URL参数,如果action=add则自动打开新增弹窗
|
||||||
|
const route = useRoute()
|
||||||
|
if (route.query.action === 'add') {
|
||||||
|
showAddModal.value = true
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('分类管理页面初始化失败:', error)
|
||||||
|
} finally {
|
||||||
|
pageLoading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 自定义样式 */
|
||||||
|
</style>
|
||||||
@@ -13,23 +13,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="max-w-7xl mx-auto">
|
<div>
|
||||||
<!-- 头部 -->
|
<n-alert class="mb-4" title="平台账号管理当前只支持夸克" type="warning" />
|
||||||
<div class="bg-slate-800 dark:bg-gray-800 text-white dark:text-gray-100 rounded-lg shadow-lg p-4 sm:p-8 mb-4 sm:mb-8 text-center flex items-center">
|
|
||||||
<nav class="mt-4 flex flex-col sm:flex-row justify-center gap-2 sm:gap-4">
|
|
||||||
<NuxtLink
|
|
||||||
to="/admin"
|
|
||||||
class="w-full sm:w-auto px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md transition-colors text-center flex items-center justify-center gap-2"
|
|
||||||
>
|
|
||||||
<i class="fas fa-arrow-left"></i> 返回
|
|
||||||
</NuxtLink>
|
|
||||||
</nav>
|
|
||||||
<div class="flex-1">
|
|
||||||
<h1 class="text-2xl sm:text-3xl font-bold">
|
|
||||||
<NuxtLink to="/admin" class="text-white hover:text-gray-200 dark:hover:text-gray-300 no-underline">平台账号管理</NuxtLink>
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
<div class="flex justify-between items-center mb-4">
|
<div class="flex justify-between items-center mb-4">
|
||||||
@@ -42,17 +27,8 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<div class="relative">
|
<div class="relative w-40">
|
||||||
<input
|
<n-select v-model:value="platform" :options="platformOptions" />
|
||||||
v-model="searchQuery"
|
|
||||||
@keyup="debounceSearch"
|
|
||||||
type="text"
|
|
||||||
class="w-64 px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500 transition-all text-sm"
|
|
||||||
placeholder="搜索平台名称..."
|
|
||||||
/>
|
|
||||||
<div class="absolute right-3 top-1/2 transform -translate-y-1/2">
|
|
||||||
<i class="fas fa-search text-gray-400 text-sm"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@click="refreshData"
|
@click="refreshData"
|
||||||
@@ -311,7 +287,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: 'auth'
|
layout: 'admin'
|
||||||
})
|
})
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -337,6 +313,18 @@ const totalPages = ref(1)
|
|||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const pageLoading = ref(true)
|
const pageLoading = ref(true)
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
|
const platform = ref(null)
|
||||||
|
|
||||||
|
const { data: pansData } = await useAsyncData('pans', () => $fetch('/api/pans'))
|
||||||
|
const pans = computed(() => {
|
||||||
|
return (pansData.value).data.list || []
|
||||||
|
})
|
||||||
|
const platformOptions = computed(() => {
|
||||||
|
return pans.value.map(pan => ({
|
||||||
|
label: pan.remark,
|
||||||
|
value: pan.id
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
// 检查认证
|
// 检查认证
|
||||||
const checkAuth = () => {
|
const checkAuth = () => {
|
||||||
285
web/pages/admin/index.vue
Normal file
285
web/pages/admin/index.vue
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 管理功能区域 -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
|
||||||
|
<!-- 资源管理 -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<div class="p-3 bg-blue-100 rounded-lg">
|
||||||
|
<i class="fas fa-cloud text-blue-600 text-xl"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">资源管理</h3>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">管理所有资源</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<NuxtLink to="/admin/resources" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors block">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">查看所有资源</span>
|
||||||
|
<i class="fas fa-chevron-right text-gray-400"></i>
|
||||||
|
</div>
|
||||||
|
</NuxtLink>
|
||||||
|
<NuxtLink to="/admin/add-resource" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors block">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">批量添加资源</span>
|
||||||
|
<i class="fas fa-plus text-gray-400"></i>
|
||||||
|
</div>
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 平台管理 -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<div class="p-3 bg-green-100 rounded-lg">
|
||||||
|
<i class="fas fa-server text-green-600 text-xl"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">平台管理</h3>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">系统支持的网盘平台</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex flex-wrap gap-1 w-full text-left rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors cursor-pointer">
|
||||||
|
<div v-for="pan in pans" :key="pan.id" class="h-6 px-1 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
|
||||||
|
<span v-html="pan.icon"></span> {{ pan.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 第三方平台账号管理 -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<div class="p-3 bg-teal-100 rounded-lg">
|
||||||
|
<i class="fas fa-key text-teal-600 text-xl"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">平台账号管理</h3>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">管理第三方平台账号</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<NuxtLink to="/admin/cks" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors block">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">管理账号</span>
|
||||||
|
<i class="fas fa-chevron-right text-gray-400"></i>
|
||||||
|
</div>
|
||||||
|
</NuxtLink>
|
||||||
|
<NuxtLink to="/admin/cks" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors block">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">添加账号</span>
|
||||||
|
<i class="fas fa-plus text-gray-400"></i>
|
||||||
|
</div>
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分类管理 -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<div class="p-3 bg-purple-100 rounded-lg">
|
||||||
|
<i class="fas fa-folder text-purple-600 text-xl"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">分类管理</h3>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">管理资源分类</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<button @click="goToCategoryManagement" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">管理分类</span>
|
||||||
|
<i class="fas fa-chevron-right text-gray-400"></i>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button @click="goToAddCategory" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">添加分类</span>
|
||||||
|
<i class="fas fa-plus text-gray-400"></i>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 标签管理 -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<div class="p-3 bg-orange-100 rounded-lg">
|
||||||
|
<i class="fas fa-tags text-orange-600 text-xl"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">标签管理</h3>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">管理资源标签</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<button @click="goToTagManagement" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">管理标签</span>
|
||||||
|
<i class="fas fa-chevron-right text-gray-400"></i>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button @click="goToAddTag" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">添加标签</span>
|
||||||
|
<i class="fas fa-plus text-gray-400"></i>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 统计信息 -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<div class="p-3 bg-red-100 rounded-lg">
|
||||||
|
<i class="fas fa-chart-bar text-red-600 text-xl"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">统计信息</h3>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">系统统计数据</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<NuxtLink to="/admin/search-stats" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors block">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">搜索统计</span>
|
||||||
|
<i class="fas fa-chart-line text-gray-400"></i>
|
||||||
|
</div>
|
||||||
|
</NuxtLink>
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<span class="text-sm text-gray-600 dark:text-gray-400">总资源数</span>
|
||||||
|
<span class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ stats?.total_resources || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<span class="text-sm text-gray-600 dark:text-gray-400">总浏览量</span>
|
||||||
|
<span class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ stats?.total_views || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<div class="p-3 bg-yellow-100 rounded-lg">
|
||||||
|
<i class="fas fa-clock text-yellow-600 text-xl"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">待处理资源</h3>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">批量添加和管理</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<NuxtLink to="/admin/ready-resources" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors block">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">管理待处理资源</span>
|
||||||
|
<i class="fas fa-chevron-right text-gray-400"></i>
|
||||||
|
</div>
|
||||||
|
</NuxtLink>
|
||||||
|
<NuxtLink to="/admin/ready-resources" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors block">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">批量处理</span>
|
||||||
|
<i class="fas fa-tasks text-gray-400"></i>
|
||||||
|
</div>
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 系统配置 -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<div class="p-3 bg-indigo-100 rounded-lg">
|
||||||
|
<i class="fas fa-cog text-indigo-600 text-xl"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">系统配置</h3>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">系统参数设置</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<NuxtLink to="/admin/users" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors block">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">用户管理</span>
|
||||||
|
<i class="fas fa-users text-gray-400"></i>
|
||||||
|
</div>
|
||||||
|
</NuxtLink>
|
||||||
|
<NuxtLink to="/admin/system-config" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors block">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">系统设置</span>
|
||||||
|
<i class="fas fa-chevron-right text-gray-400"></i>
|
||||||
|
</div>
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 版本信息 -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<div class="p-3 bg-green-100 rounded-lg">
|
||||||
|
<i class="fas fa-code-branch text-green-600 text-xl"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">版本信息</h3>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">系统版本和文档</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<NuxtLink to="/admin/version" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors block">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">版本信息</span>
|
||||||
|
<i class="fas fa-code-branch text-gray-400"></i>
|
||||||
|
</div>
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 设置页面布局
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'admin'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 用户状态管理
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
// 统计数据
|
||||||
|
const { data: statsData } = await useAsyncData('stats', () => $fetch('/api/stats'))
|
||||||
|
const stats = computed(() => (statsData.value as any)?.data || {})
|
||||||
|
|
||||||
|
// 平台数据
|
||||||
|
const { data: pansData } = await useAsyncData('pans', () => $fetch('/api/pans'))
|
||||||
|
console.log()
|
||||||
|
const pans = computed(() => {
|
||||||
|
return (pansData.value as any).data.list || []
|
||||||
|
})
|
||||||
|
|
||||||
|
// 分类管理相关
|
||||||
|
const goToCategoryManagement = () => {
|
||||||
|
navigateTo('/admin/categories')
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToAddCategory = () => {
|
||||||
|
navigateTo('/admin/categories')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标签管理相关
|
||||||
|
const goToTagManagement = () => {
|
||||||
|
navigateTo('/admin/tags')
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToAddTag = () => {
|
||||||
|
navigateTo('/admin/tags')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载时检查用户权限
|
||||||
|
onMounted(() => {
|
||||||
|
if (!userStore.isAuthenticated) {
|
||||||
|
navigateTo('/login')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 可以添加自定义样式 */
|
||||||
|
</style>
|
||||||
@@ -324,6 +324,11 @@ https://pan.baidu.com/s/345678</pre>
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
// 设置页面布局
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'admin'
|
||||||
|
})
|
||||||
|
|
||||||
interface ReadyResource {
|
interface ReadyResource {
|
||||||
id: number
|
id: number
|
||||||
title?: string
|
title?: string
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100 p-3 sm:p-5">
|
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100">
|
||||||
<!-- 全局加载状态 -->
|
<!-- 全局加载状态 -->
|
||||||
<div v-if="pageLoading" class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center z-50">
|
<div v-if="pageLoading" class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center z-50">
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg p-8 shadow-xl">
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-8 shadow-xl">
|
||||||
@@ -14,22 +14,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="max-w-7xl mx-auto">
|
<div class="max-w-7xl mx-auto">
|
||||||
<!-- 头部 -->
|
|
||||||
<div class="bg-slate-800 dark:bg-gray-800 text-white dark:text-gray-100 rounded-lg shadow-lg p-4 sm:p-8 mb-4 sm:mb-8 text-center flex items-center">
|
|
||||||
<nav class="mt-4 flex flex-col sm:flex-row justify-center gap-2 sm:gap-4">
|
|
||||||
<NuxtLink
|
|
||||||
to="/admin"
|
|
||||||
class="w-full sm:w-auto px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md transition-colors text-center flex items-center justify-center gap-2"
|
|
||||||
>
|
|
||||||
<i class="fas fa-arrow-left"></i> 返回
|
|
||||||
</NuxtLink>
|
|
||||||
</nav>
|
|
||||||
<div class="flex-1">
|
|
||||||
<h1 class="text-2xl sm:text-3xl font-bold">
|
|
||||||
<NuxtLink to="/" class="text-white hover:text-gray-200 dark:hover:text-gray-300 no-underline">网盘资源数据库</NuxtLink>
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 搜索和筛选区域 -->
|
<!-- 搜索和筛选区域 -->
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-4 mb-6">
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-4 mb-6">
|
||||||
@@ -107,12 +91,6 @@
|
|||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
<div class="flex justify-between items-center mb-4">
|
<div class="flex justify-between items-center mb-4">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<NuxtLink
|
|
||||||
to="/add-resource"
|
|
||||||
class="w-full sm:w-auto px-4 py-2 bg-green-600 hover:bg-green-700 rounded-md transition-colors text-center flex items-center justify-center gap-2"
|
|
||||||
>
|
|
||||||
<i class="fas fa-plus"></i> 添加资源
|
|
||||||
</NuxtLink>
|
|
||||||
<button
|
<button
|
||||||
@click="showBatchModal = true"
|
@click="showBatchModal = true"
|
||||||
class="w-full sm:w-auto px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md transition-colors text-center flex items-center justify-center gap-2"
|
class="w-full sm:w-auto px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md transition-colors text-center flex items-center justify-center gap-2"
|
||||||
@@ -384,6 +362,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
// 设置页面布局
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'admin'
|
||||||
|
})
|
||||||
|
|
||||||
interface Resource {
|
interface Resource {
|
||||||
id: number
|
id: number
|
||||||
title: string
|
title: string
|
||||||
@@ -87,6 +87,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
// 设置页面布局
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'admin'
|
||||||
|
})
|
||||||
|
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import Chart from 'chart.js/auto'
|
import Chart from 'chart.js/auto'
|
||||||
|
|
||||||
503
web/pages/admin/system-config.vue
Normal file
503
web/pages/admin/system-config.vue
Normal file
@@ -0,0 +1,503 @@
|
|||||||
|
<template>
|
||||||
|
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100">
|
||||||
|
<!-- 全局加载状态 -->
|
||||||
|
<div v-if="pageLoading" class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center z-50">
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-8 shadow-xl">
|
||||||
|
<div class="flex flex-col items-center space-y-4">
|
||||||
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
||||||
|
<div class="text-center">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">正在加载...</h3>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">请稍候,正在加载系统配置</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="">
|
||||||
|
<div class="max-w-7xl mx-auto">
|
||||||
|
<!-- 配置表单 -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||||
|
<form @submit.prevent="saveConfig" class="space-y-6">
|
||||||
|
<!-- SEO 配置 -->
|
||||||
|
<div class="border-b border-gray-200 dark:border-gray-700 pb-6">
|
||||||
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
|
||||||
|
<i class="fas fa-search text-blue-600"></i>
|
||||||
|
SEO 配置
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<!-- 网站标题 -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
网站标题 *
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-model="config.siteTitle"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||||
|
placeholder="网盘资源数据库"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 网站描述 -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
网站描述
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-model="config.siteDescription"
|
||||||
|
type="text"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||||
|
placeholder="专业的网盘资源数据库"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 关键词 -->
|
||||||
|
<div class="md:col-span-2">
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
关键词 (用逗号分隔)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-model="config.keywords"
|
||||||
|
type="text"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||||
|
placeholder="网盘,资源管理,文件分享"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 作者 -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
作者
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-model="config.author"
|
||||||
|
type="text"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||||
|
placeholder="系统管理员"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 版权信息 -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
版权信息
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-model="config.copyright"
|
||||||
|
type="text"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||||
|
placeholder="© 2024 网盘资源数据库"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 自动处理配置 -->
|
||||||
|
<div class="border-b border-gray-200 dark:border-gray-700 pb-6">
|
||||||
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
|
||||||
|
<i class="fas fa-cogs text-green-600"></i>
|
||||||
|
自动处理配置
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- 待处理资源自动处理 -->
|
||||||
|
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||||
|
<div class="flex-1">
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white">
|
||||||
|
待处理资源自动处理
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||||
|
开启后,系统将自动处理待处理的资源,无需手动操作
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<label class="relative inline-flex items-center cursor-pointer">
|
||||||
|
<input
|
||||||
|
v-model="config.autoProcessReadyResources"
|
||||||
|
type="checkbox"
|
||||||
|
class="sr-only peer"
|
||||||
|
/>
|
||||||
|
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 自动转存 -->
|
||||||
|
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||||
|
<div class="flex-1">
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white">
|
||||||
|
自动转存
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||||
|
开启后,系统将自动转存资源到其他网盘平台
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<label class="relative inline-flex items-center cursor-pointer">
|
||||||
|
<input
|
||||||
|
v-model="config.autoTransferEnabled"
|
||||||
|
type="checkbox"
|
||||||
|
class="sr-only peer"
|
||||||
|
/>
|
||||||
|
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 自动转存配置(仅在开启时显示) -->
|
||||||
|
<div v-if="config.autoTransferEnabled" class="ml-6 space-y-4">
|
||||||
|
<!-- 自动转存限制天数 -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
自动转存限制(n天内资源)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-model.number="config.autoTransferLimitDays"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="365"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||||
|
placeholder="30"
|
||||||
|
/>
|
||||||
|
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||||
|
只转存指定天数内的资源,0表示不限制时间
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 最小存储空间 -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
最小存储空间(GB)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-model.number="config.autoTransferMinSpace"
|
||||||
|
type="number"
|
||||||
|
min="100"
|
||||||
|
max="1024"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||||
|
placeholder="500"
|
||||||
|
/>
|
||||||
|
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||||
|
当网盘剩余空间小于此值时,停止自动转存(100-1024GB)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 自动拉取热播剧 -->
|
||||||
|
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||||
|
<div class="flex-1">
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white">
|
||||||
|
自动拉取热播剧
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||||
|
开启后,系统将自动从豆瓣获取热播剧信息
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<label class="relative inline-flex items-center cursor-pointer">
|
||||||
|
<input
|
||||||
|
v-model="config.autoFetchHotDramaEnabled"
|
||||||
|
type="checkbox"
|
||||||
|
class="sr-only peer"
|
||||||
|
/>
|
||||||
|
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 自动处理间隔 -->
|
||||||
|
<div v-if="config.autoProcessReadyResources" class="ml-6">
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
自动处理间隔 (分钟)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-model.number="config.autoProcessInterval"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="1440"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||||
|
placeholder="30"
|
||||||
|
/>
|
||||||
|
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||||
|
建议设置 5-60 分钟,避免过于频繁的处理
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 其他配置 -->
|
||||||
|
<div>
|
||||||
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
|
||||||
|
<i class="fas fa-info-circle text-purple-600"></i>
|
||||||
|
其他配置
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<!-- 每页显示数量 -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
每页显示数量
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
v-model.number="config.pageSize"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||||
|
>
|
||||||
|
<option value="20">20 条</option>
|
||||||
|
<option value="50">50 条</option>
|
||||||
|
<option value="100">100 条</option>
|
||||||
|
<option value="200">200 条</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 系统维护模式 -->
|
||||||
|
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||||
|
<div class="flex-1">
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white">
|
||||||
|
维护模式
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||||
|
开启后,普通用户无法访问系统
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<label class="relative inline-flex items-center cursor-pointer">
|
||||||
|
<input
|
||||||
|
v-model="config.maintenanceMode"
|
||||||
|
type="checkbox"
|
||||||
|
class="sr-only peer"
|
||||||
|
/>
|
||||||
|
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-red-300 dark:peer-focus:ring-red-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-red-600"></div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- API配置 -->
|
||||||
|
<div class="border-b border-gray-200 dark:border-gray-700 pb-6">
|
||||||
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
|
||||||
|
<i class="fas fa-key text-orange-600"></i>
|
||||||
|
API 配置
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- API Token -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
公开API访问令牌
|
||||||
|
</label>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<input
|
||||||
|
v-model="config.apiToken"
|
||||||
|
type="text"
|
||||||
|
class="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||||
|
placeholder="输入API Token,用于公开API访问认证"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="generateApiToken"
|
||||||
|
class="px-4 py-2 bg-orange-600 text-white rounded-md hover:bg-orange-700 transition-colors"
|
||||||
|
>
|
||||||
|
生成
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||||
|
用于公开API的访问认证,建议使用随机字符串
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- API使用说明 -->
|
||||||
|
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
|
||||||
|
<h3 class="text-sm font-medium text-blue-800 dark:text-blue-200 mb-2">
|
||||||
|
<i class="fas fa-info-circle mr-1"></i>
|
||||||
|
API使用说明
|
||||||
|
</h3>
|
||||||
|
<div class="text-xs text-blue-700 dark:text-blue-300 space-y-1">
|
||||||
|
<p>• 单个添加资源: POST /api/public/resources/add</p>
|
||||||
|
<p>• 批量添加资源: POST /api/public/resources/batch-add</p>
|
||||||
|
<p>• 资源搜索: GET /api/public/resources/search</p>
|
||||||
|
<p>• 热门剧: GET /api/public/hot-dramas</p>
|
||||||
|
<p>• 认证方式: 在请求头中添加 X-API-Token 或在查询参数中添加 api_token</p>
|
||||||
|
<p>• Swagger文档: <a href="/swagger/index.html" target="_blank" class="underline">查看完整API文档</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 保存按钮 -->
|
||||||
|
<div class="flex justify-end space-x-4 pt-6">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="resetForm"
|
||||||
|
class="px-6 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||||
|
>
|
||||||
|
重置
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
:disabled="saving"
|
||||||
|
class="px-6 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
<i v-if="saving" class="fas fa-spinner fa-spin mr-2"></i>
|
||||||
|
{{ saving ? '保存中...' : '保存配置' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
// 设置页面布局
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'admin'
|
||||||
|
})
|
||||||
|
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
|
||||||
|
// API
|
||||||
|
const { getSystemConfig, updateSystemConfig } = useSystemConfigApi()
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const loading = ref(false)
|
||||||
|
const config = ref({
|
||||||
|
// SEO 配置
|
||||||
|
siteTitle: '网盘资源数据库',
|
||||||
|
siteDescription: '专业的网盘资源数据库',
|
||||||
|
keywords: '网盘,资源管理,文件分享',
|
||||||
|
author: '系统管理员',
|
||||||
|
copyright: '© 2024 网盘资源数据库',
|
||||||
|
|
||||||
|
// 自动处理配置
|
||||||
|
autoProcessReadyResources: false,
|
||||||
|
autoProcessInterval: 30,
|
||||||
|
autoTransferEnabled: false, // 新增
|
||||||
|
autoTransferLimitDays: 30, // 新增:自动转存限制天数
|
||||||
|
autoTransferMinSpace: 500, // 新增:最小存储空间(GB)
|
||||||
|
autoFetchHotDramaEnabled: false, // 新增
|
||||||
|
|
||||||
|
// 其他配置
|
||||||
|
pageSize: 100,
|
||||||
|
maintenanceMode: false,
|
||||||
|
apiToken: '' // 新增
|
||||||
|
})
|
||||||
|
|
||||||
|
// 系统配置状态(用于SEO)
|
||||||
|
const systemConfig = ref(null)
|
||||||
|
|
||||||
|
// 页面元数据 - 移到变量声明之后
|
||||||
|
useHead({
|
||||||
|
title: () => systemConfig.value?.site_title ? `${systemConfig.value.site_title} - 系统配置` : '系统配置 - 网盘资源数据库',
|
||||||
|
meta: [
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
content: () => systemConfig.value?.site_description || '系统配置管理页面'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'keywords',
|
||||||
|
content: () => systemConfig.value?.keywords || '系统配置,管理'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'author',
|
||||||
|
content: () => systemConfig.value?.author || '系统管理员'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 加载配置
|
||||||
|
const loadConfig = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
const response = await getSystemConfig()
|
||||||
|
console.log('系统配置响应:', response)
|
||||||
|
|
||||||
|
// 使用新的统一响应格式,直接使用response
|
||||||
|
if (response) {
|
||||||
|
config.value = {
|
||||||
|
siteTitle: response.site_title || '网盘资源数据库',
|
||||||
|
siteDescription: response.site_description || '专业的网盘资源数据库',
|
||||||
|
keywords: response.keywords || '网盘,资源管理,文件分享',
|
||||||
|
author: response.author || '系统管理员',
|
||||||
|
copyright: response.copyright || '© 2024 网盘资源数据库',
|
||||||
|
autoProcessReadyResources: response.auto_process_ready_resources || false,
|
||||||
|
autoProcessInterval: response.auto_process_interval || 30,
|
||||||
|
autoTransferEnabled: response.auto_transfer_enabled || false, // 新增
|
||||||
|
autoTransferLimitDays: response.auto_transfer_limit_days || 30, // 新增:自动转存限制天数
|
||||||
|
autoTransferMinSpace: response.auto_transfer_min_space || 500, // 新增:最小存储空间(GB)
|
||||||
|
autoFetchHotDramaEnabled: response.auto_fetch_hot_drama_enabled || false, // 新增
|
||||||
|
pageSize: response.page_size || 100,
|
||||||
|
maintenanceMode: response.maintenance_mode || false,
|
||||||
|
apiToken: response.api_token || '' // 加载API Token
|
||||||
|
}
|
||||||
|
systemConfig.value = response // 更新系统配置状态
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载配置失败:', error)
|
||||||
|
// 显示错误提示
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存配置
|
||||||
|
const saveConfig = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
const requestData = {
|
||||||
|
site_title: config.value.siteTitle,
|
||||||
|
site_description: config.value.siteDescription,
|
||||||
|
keywords: config.value.keywords,
|
||||||
|
author: config.value.author,
|
||||||
|
copyright: config.value.copyright,
|
||||||
|
auto_process_ready_resources: config.value.autoProcessReadyResources,
|
||||||
|
auto_process_interval: config.value.autoProcessInterval,
|
||||||
|
auto_transfer_enabled: config.value.autoTransferEnabled, // 新增
|
||||||
|
auto_transfer_limit_days: config.value.autoTransferLimitDays, // 新增:自动转存限制天数
|
||||||
|
auto_transfer_min_space: config.value.autoTransferMinSpace, // 新增:最小存储空间(GB)
|
||||||
|
auto_fetch_hot_drama_enabled: config.value.autoFetchHotDramaEnabled, // 新增
|
||||||
|
page_size: config.value.pageSize,
|
||||||
|
maintenance_mode: config.value.maintenanceMode,
|
||||||
|
api_token: config.value.apiToken // 保存API Token
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await updateSystemConfig(requestData)
|
||||||
|
// 使用新的统一响应格式,直接检查response是否存在
|
||||||
|
if (response) {
|
||||||
|
alert('配置保存成功!')
|
||||||
|
// 重新加载配置以获取最新数据
|
||||||
|
await loadConfig()
|
||||||
|
} else {
|
||||||
|
alert('保存配置失败:未知错误')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存配置失败:', error)
|
||||||
|
alert('保存配置失败:' + (error.message || '未知错误'))
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置表单
|
||||||
|
const resetForm = () => {
|
||||||
|
if (confirm('确定要重置所有配置吗?')) {
|
||||||
|
loadConfig()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成API Token
|
||||||
|
const generateApiToken = () => {
|
||||||
|
const newToken = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
||||||
|
config.value.apiToken = newToken;
|
||||||
|
alert('新API Token已生成: ' + newToken);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面加载时获取配置
|
||||||
|
onMounted(() => {
|
||||||
|
loadConfig()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100 p-3 sm:p-5">
|
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100">
|
||||||
<!-- 全局加载状态 -->
|
<!-- 全局加载状态 -->
|
||||||
<div v-if="pageLoading" class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center z-50">
|
<div v-if="pageLoading" class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center z-50">
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg p-8 shadow-xl">
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-8 shadow-xl">
|
||||||
@@ -13,207 +13,182 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="max-w-7xl mx-auto">
|
<div class="">
|
||||||
<!-- 头部 -->
|
<div class="max-w-7xl mx-auto">
|
||||||
<div class="bg-slate-800 dark:bg-gray-800 text-white dark:text-gray-100 rounded-lg shadow-lg p-4 sm:p-8 mb-4 sm:mb-8 text-center flex items-center">
|
|
||||||
<nav class="mt-4 flex flex-col sm:flex-row justify-center gap-2 sm:gap-4">
|
|
||||||
<NuxtLink
|
|
||||||
to="/admin"
|
|
||||||
class="w-full sm:w-auto px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md transition-colors text-center flex items-center justify-center gap-2"
|
|
||||||
>
|
|
||||||
<i class="fas fa-arrow-left"></i> 返回
|
|
||||||
</NuxtLink>
|
|
||||||
</nav>
|
|
||||||
<div class="flex-1">
|
|
||||||
<h1 class="text-2xl sm:text-3xl font-bold">
|
|
||||||
<NuxtLink to="/admin" class="text-white hover:text-gray-200 dark:hover:text-gray-300 no-underline">标签管理</NuxtLink>
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
<div class="flex justify-between items-center mb-4">
|
<div class="flex justify-between items-center mb-4">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button
|
<button
|
||||||
@click="showAddModal = true"
|
@click="showAddModal = true"
|
||||||
class="w-full sm:w-auto px-4 py-2 bg-green-600 hover:bg-green-700 rounded-md transition-colors text-center flex items-center justify-center gap-2"
|
class="px-4 py-2 bg-green-600 hover:bg-green-700 rounded-md transition-colors text-white text-sm flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<i class="fas fa-plus"></i> 添加标签
|
<i class="fas fa-plus"></i> 添加标签
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<!-- 分类筛选 -->
|
|
||||||
<select
|
|
||||||
v-model="selectedCategory"
|
|
||||||
@change="onCategoryChange"
|
|
||||||
class="px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:bg-gray-900 dark:text-gray-100 text-sm"
|
|
||||||
>
|
|
||||||
<option value="">全部分类</option>
|
|
||||||
<option v-for="category in categories" :key="category.id" :value="category.id">
|
|
||||||
{{ category.name }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<input
|
<input
|
||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
@keyup="debounceSearch"
|
@keyup="debounceSearch"
|
||||||
type="text"
|
type="text"
|
||||||
class="w-64 px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500 transition-all text-sm"
|
class="w-64 px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500 transition-all text-sm"
|
||||||
placeholder="搜索标签名称..."
|
placeholder="搜索标签名称..."
|
||||||
/>
|
/>
|
||||||
<div class="absolute right-3 top-1/2 transform -translate-y-1/2">
|
<div class="absolute right-3 top-1/2 transform -translate-y-1/2">
|
||||||
<i class="fas fa-search text-gray-400 text-sm"></i>
|
<i class="fas fa-search text-gray-400 text-sm"></i>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
@click="refreshData"
|
||||||
|
class="px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<i class="fas fa-refresh"></i> 刷新
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 标签列表 -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden">
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="w-full min-w-full">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-slate-800 dark:bg-gray-700 text-white dark:text-gray-100">
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-medium">ID</th>
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-medium">标签名称</th>
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-medium">分类</th>
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-medium">描述</th>
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-medium">资源数量</th>
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-medium">创建时间</th>
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-medium">操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
|
<tr v-if="loading" class="text-center py-8">
|
||||||
|
<td colspan="7" class="text-gray-500 dark:text-gray-400">
|
||||||
|
<i class="fas fa-spinner fa-spin mr-2"></i>加载中...
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-else-if="tags.length === 0" class="text-center py-8">
|
||||||
|
<td colspan="7" class="text-gray-500 dark:text-gray-400">
|
||||||
|
<div class="flex flex-col items-center justify-center py-12">
|
||||||
|
<svg class="w-16 h-16 text-gray-300 dark:text-gray-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 48 48">
|
||||||
|
<circle cx="24" cy="24" r="20" stroke-width="3" stroke-dasharray="6 6" />
|
||||||
|
<path d="M16 24h16M24 16v16" stroke-width="3" stroke-linecap="round" />
|
||||||
|
</svg>
|
||||||
|
<div class="text-lg font-semibold text-gray-400 dark:text-gray-500 mb-2">暂无标签</div>
|
||||||
|
<div class="text-sm text-gray-400 dark:text-gray-600 mb-4">你可以点击上方"添加标签"按钮创建新标签</div>
|
||||||
|
<button
|
||||||
|
@click="showAddModal = true"
|
||||||
|
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors text-sm flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<i class="fas fa-plus"></i> 添加标签
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
v-for="tag in tags"
|
||||||
|
:key="tag.id"
|
||||||
|
class="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||||
|
>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-900 dark:text-gray-100 font-medium">{{ tag.id }}</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
|
||||||
|
<span :title="tag.name">{{ tag.name }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
<span v-if="tag.category_name" class="px-2 py-1 bg-blue-100 dark:bg-blue-900/20 text-blue-800 dark:text-blue-300 rounded-full text-xs">
|
||||||
|
{{ tag.category_name }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="text-gray-400 dark:text-gray-500 italic text-xs">未分类</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
<span v-if="tag.description" :title="tag.description">{{ tag.description }}</span>
|
||||||
|
<span v-else class="text-gray-400 dark:text-gray-500 italic">无描述</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
<span class="px-2 py-1 bg-green-100 dark:bg-green-900/20 text-green-800 dark:text-green-300 rounded-full text-xs">
|
||||||
|
{{ tag.resource_count || 0 }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ formatTime(tag.created_at) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-sm">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
@click="editTag(tag)"
|
||||||
|
class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 transition-colors"
|
||||||
|
title="编辑标签"
|
||||||
|
>
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="deleteTag(tag.id)"
|
||||||
|
class="text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300 transition-colors"
|
||||||
|
title="删除标签"
|
||||||
|
>
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div v-if="totalPages > 1" class="flex flex-wrap justify-center gap-1 sm:gap-2 mt-6">
|
||||||
<button
|
<button
|
||||||
@click="refreshData"
|
v-if="currentPage > 1"
|
||||||
class="px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 flex items-center gap-2"
|
@click="goToPage(currentPage - 1)"
|
||||||
|
class="bg-white text-gray-700 hover:bg-gray-50 px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm flex items-center"
|
||||||
>
|
>
|
||||||
<i class="fas fa-refresh"></i> 刷新
|
<i class="fas fa-chevron-left mr-1"></i> 上一页
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="goToPage(1)"
|
||||||
|
:class="currentPage === 1 ? 'bg-slate-800 text-white' : 'bg-white text-gray-700 hover:bg-gray-50'"
|
||||||
|
class="px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm"
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-if="totalPages > 1"
|
||||||
|
@click="goToPage(2)"
|
||||||
|
:class="currentPage === 2 ? 'bg-slate-800 text-white' : 'bg-white text-gray-700 hover:bg-gray-50'"
|
||||||
|
class="px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm"
|
||||||
|
>
|
||||||
|
2
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span v-if="currentPage > 2" class="px-2 py-1 sm:px-3 sm:py-2 text-gray-500 text-sm">...</span>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-if="currentPage !== 1 && currentPage !== 2 && currentPage > 2"
|
||||||
|
class="bg-slate-800 text-white px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm"
|
||||||
|
>
|
||||||
|
{{ currentPage }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-if="currentPage < totalPages"
|
||||||
|
@click="goToPage(currentPage + 1)"
|
||||||
|
class="bg-white text-gray-700 hover:bg-gray-50 px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm flex items-center"
|
||||||
|
>
|
||||||
|
下一页 <i class="fas fa-chevron-right ml-1"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 标签列表 -->
|
<!-- 统计信息 -->
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden">
|
<div v-if="totalPages <= 1" class="mt-4 text-center">
|
||||||
<div class="overflow-x-auto">
|
<div class="inline-flex items-center bg-white dark:bg-gray-800 rounded-lg shadow px-6 py-3">
|
||||||
<table class="w-full min-w-full">
|
<div class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
<thead>
|
共 <span class="font-semibold text-gray-900 dark:text-gray-100">{{ totalCount }}</span> 个标签
|
||||||
<tr class="bg-slate-800 dark:bg-gray-700 text-white dark:text-gray-100">
|
</div>
|
||||||
<th class="px-4 py-3 text-left text-sm font-medium">ID</th>
|
|
||||||
<th class="px-4 py-3 text-left text-sm font-medium">标签名称</th>
|
|
||||||
<th class="px-4 py-3 text-left text-sm font-medium">分类</th>
|
|
||||||
<th class="px-4 py-3 text-left text-sm font-medium">描述</th>
|
|
||||||
<th class="px-4 py-3 text-left text-sm font-medium">资源数量</th>
|
|
||||||
<th class="px-4 py-3 text-left text-sm font-medium">创建时间</th>
|
|
||||||
<th class="px-4 py-3 text-left text-sm font-medium">操作</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
|
||||||
<tr v-if="loading" class="text-center py-8">
|
|
||||||
<td colspan="7" class="text-gray-500 dark:text-gray-400">
|
|
||||||
<i class="fas fa-spinner fa-spin mr-2"></i>加载中...
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-else-if="tags.length === 0" class="text-center py-8">
|
|
||||||
<td colspan="7" class="text-gray-500 dark:text-gray-400">
|
|
||||||
<div class="flex flex-col items-center justify-center py-12">
|
|
||||||
<svg class="w-16 h-16 text-gray-300 dark:text-gray-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 48 48">
|
|
||||||
<circle cx="24" cy="24" r="20" stroke-width="3" stroke-dasharray="6 6" />
|
|
||||||
<path d="M16 24h16M24 16v16" stroke-width="3" stroke-linecap="round" />
|
|
||||||
</svg>
|
|
||||||
<div class="text-lg font-semibold text-gray-400 dark:text-gray-500 mb-2">暂无标签</div>
|
|
||||||
<div class="text-sm text-gray-400 dark:text-gray-600 mb-4">你可以点击上方"添加标签"按钮创建新标签</div>
|
|
||||||
<button
|
|
||||||
@click="showAddModal = true"
|
|
||||||
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors text-sm flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<i class="fas fa-plus"></i> 添加标签
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr
|
|
||||||
v-for="tag in tags"
|
|
||||||
:key="tag.id"
|
|
||||||
class="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
|
||||||
>
|
|
||||||
<td class="px-4 py-3 text-sm text-gray-900 dark:text-gray-100 font-medium">{{ tag.id }}</td>
|
|
||||||
<td class="px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
|
|
||||||
<span :title="tag.name">{{ tag.name }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
<span v-if="tag.category_name" class="px-2 py-1 bg-blue-100 dark:bg-blue-900/20 text-blue-800 dark:text-blue-300 rounded-full text-xs">
|
|
||||||
{{ tag.category_name }}
|
|
||||||
</span>
|
|
||||||
<span v-else class="text-gray-400 dark:text-gray-500 italic text-xs">未分类</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
<span v-if="tag.description" :title="tag.description">{{ tag.description }}</span>
|
|
||||||
<span v-else class="text-gray-400 dark:text-gray-500 italic">无描述</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
<span class="px-2 py-1 bg-green-100 dark:bg-green-900/20 text-green-800 dark:text-green-300 rounded-full text-xs">
|
|
||||||
{{ tag.resource_count || 0 }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
{{ formatTime(tag.created_at) }}
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-3 text-sm">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<button
|
|
||||||
@click="editTag(tag)"
|
|
||||||
class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 transition-colors"
|
|
||||||
title="编辑标签"
|
|
||||||
>
|
|
||||||
<i class="fas fa-edit"></i>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="deleteTag(tag.id)"
|
|
||||||
class="text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300 transition-colors"
|
|
||||||
title="删除标签"
|
|
||||||
>
|
|
||||||
<i class="fas fa-trash"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 分页 -->
|
|
||||||
<div v-if="totalPages > 1" class="flex flex-wrap justify-center gap-1 sm:gap-2 mt-6">
|
|
||||||
<button
|
|
||||||
v-if="currentPage > 1"
|
|
||||||
@click="goToPage(currentPage - 1)"
|
|
||||||
class="bg-white text-gray-700 hover:bg-gray-50 px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm flex items-center"
|
|
||||||
>
|
|
||||||
<i class="fas fa-chevron-left mr-1"></i> 上一页
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
@click="goToPage(1)"
|
|
||||||
:class="currentPage === 1 ? 'bg-slate-800 text-white' : 'bg-white text-gray-700 hover:bg-gray-50'"
|
|
||||||
class="px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm"
|
|
||||||
>
|
|
||||||
1
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
v-if="totalPages > 1"
|
|
||||||
@click="goToPage(2)"
|
|
||||||
:class="currentPage === 2 ? 'bg-slate-800 text-white' : 'bg-white text-gray-700 hover:bg-gray-50'"
|
|
||||||
class="px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm"
|
|
||||||
>
|
|
||||||
2
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<span v-if="currentPage > 2" class="px-2 py-1 sm:px-3 sm:py-2 text-gray-500 text-sm">...</span>
|
|
||||||
|
|
||||||
<button
|
|
||||||
v-if="currentPage !== 1 && currentPage !== 2 && currentPage > 2"
|
|
||||||
class="bg-slate-800 text-white px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm"
|
|
||||||
>
|
|
||||||
{{ currentPage }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
v-if="currentPage < totalPages"
|
|
||||||
@click="goToPage(currentPage + 1)"
|
|
||||||
class="bg-white text-gray-700 hover:bg-gray-50 px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm flex items-center"
|
|
||||||
>
|
|
||||||
下一页 <i class="fas fa-chevron-right ml-1"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 统计信息 -->
|
|
||||||
<div v-if="totalPages <= 1" class="mt-4 text-center">
|
|
||||||
<div class="inline-flex items-center bg-white dark:bg-gray-800 rounded-lg shadow px-6 py-3">
|
|
||||||
<div class="text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
共 <span class="font-semibold text-gray-900 dark:text-gray-100">{{ totalCount }}</span> 个标签
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -291,6 +266,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
// 设置页面布局
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'admin'
|
||||||
|
})
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
384
web/pages/admin/users.vue
Normal file
384
web/pages/admin/users.vue
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 用户管理内容 -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-6">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100">用户管理</h2>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button
|
||||||
|
@click="showCreateModal = true"
|
||||||
|
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
|
||||||
|
>
|
||||||
|
添加用户
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 用户列表 -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow">
|
||||||
|
<div class="px-6 py-4 border-b border-gray-200">
|
||||||
|
<h2 class="text-lg font-semibold text-gray-900">用户列表</h2>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">用户名</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">邮箱</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">角色</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">最后登录</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-white divide-y divide-gray-200">
|
||||||
|
<tr v-for="user in users" :key="user.id">
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ user.id }}</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ user.username }}</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ user.email }}</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
|
<span :class="getRoleClass(user.role)" class="px-2 py-1 text-xs font-medium rounded-full">
|
||||||
|
{{ user.role }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
|
<span :class="user.is_active ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'"
|
||||||
|
class="px-2 py-1 text-xs font-medium rounded-full">
|
||||||
|
{{ user.is_active ? '激活' : '禁用' }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
|
{{ user.last_login ? formatDate(user.last_login) : '从未登录' }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||||
|
<button @click="editUser(user)" class="text-indigo-600 hover:text-indigo-900 mr-3">编辑</button>
|
||||||
|
<button @click="showChangePasswordModal(user)" class="text-yellow-600 hover:text-yellow-900 mr-3">修改密码</button>
|
||||||
|
<button @click="deleteUser(user.id)" class="text-red-600 hover:text-red-900">删除</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 创建/编辑用户模态框 -->
|
||||||
|
<div v-if="showCreateModal || showEditModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
||||||
|
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
||||||
|
<div class="mt-3">
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 mb-4">
|
||||||
|
{{ showEditModal ? '编辑用户' : '创建用户' }}
|
||||||
|
</h3>
|
||||||
|
<form @submit.prevent="handleSubmit">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">用户名</label>
|
||||||
|
<input
|
||||||
|
v-model="form.username"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">邮箱</label>
|
||||||
|
<input
|
||||||
|
v-model="form.email"
|
||||||
|
type="email"
|
||||||
|
required
|
||||||
|
class="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="showCreateModal">
|
||||||
|
<label class="block text-sm font-medium text-gray-700">密码</label>
|
||||||
|
<input
|
||||||
|
v-model="form.password"
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
class="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">角色</label>
|
||||||
|
<select
|
||||||
|
v-model="form.role"
|
||||||
|
class="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
|
||||||
|
>
|
||||||
|
<option value="user">用户</option>
|
||||||
|
<option value="admin">管理员</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="flex items-center">
|
||||||
|
<input
|
||||||
|
v-model="form.is_active"
|
||||||
|
type="checkbox"
|
||||||
|
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
|
||||||
|
/>
|
||||||
|
<span class="ml-2 text-sm text-gray-700">激活状态</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-6 flex justify-end space-x-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="closeModal"
|
||||||
|
class="px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="px-4 py-2 bg-indigo-600 border border-transparent rounded-md text-sm font-medium text-white hover:bg-indigo-700"
|
||||||
|
>
|
||||||
|
{{ showEditModal ? '更新' : '创建' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 修改密码模态框 -->
|
||||||
|
<div v-if="showPasswordModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
||||||
|
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
||||||
|
<div class="mt-3">
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 mb-4">
|
||||||
|
修改用户密码
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-gray-600 mb-4">
|
||||||
|
正在为用户 <strong>{{ changingPasswordUser?.username }}</strong> 修改密码
|
||||||
|
</p>
|
||||||
|
<form @submit.prevent="handlePasswordChange">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">新密码</label>
|
||||||
|
<input
|
||||||
|
v-model="passwordForm.newPassword"
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
minlength="6"
|
||||||
|
class="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
|
||||||
|
placeholder="请输入新密码(至少6位)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">确认新密码</label>
|
||||||
|
<input
|
||||||
|
v-model="passwordForm.confirmPassword"
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
class="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
|
||||||
|
placeholder="请再次输入新密码"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-6 flex justify-end space-x-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="closePasswordModal"
|
||||||
|
class="px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="px-4 py-2 bg-yellow-600 border border-transparent rounded-md text-sm font-medium text-white hover:bg-yellow-700"
|
||||||
|
>
|
||||||
|
修改密码
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 设置页面布局
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'admin'
|
||||||
|
})
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
const users = ref([])
|
||||||
|
const showCreateModal = ref(false)
|
||||||
|
const showEditModal = ref(false)
|
||||||
|
const showPasswordModal = ref(false)
|
||||||
|
const editingUser = ref(null)
|
||||||
|
const changingPasswordUser = ref(null)
|
||||||
|
const form = ref({
|
||||||
|
username: '',
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
role: 'user',
|
||||||
|
is_active: true
|
||||||
|
})
|
||||||
|
const passwordForm = ref({
|
||||||
|
newPassword: '',
|
||||||
|
confirmPassword: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 检查认证
|
||||||
|
const checkAuth = () => {
|
||||||
|
userStore.initAuth()
|
||||||
|
if (!userStore.isAuthenticated) {
|
||||||
|
router.push('/login')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户列表
|
||||||
|
const fetchUsers = async () => {
|
||||||
|
try {
|
||||||
|
const { useUserApi } = await import('~/composables/useApi')
|
||||||
|
const userApi = useUserApi()
|
||||||
|
const response = await userApi.getUsers()
|
||||||
|
users.value = Array.isArray(response) ? response : (response?.items || [])
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取用户列表失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建用户
|
||||||
|
const createUser = async () => {
|
||||||
|
try {
|
||||||
|
const { useUserApi } = await import('~/composables/useApi')
|
||||||
|
const userApi = useUserApi()
|
||||||
|
await userApi.createUser(form.value)
|
||||||
|
await fetchUsers()
|
||||||
|
closeModal()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('创建用户失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新用户
|
||||||
|
const updateUser = async () => {
|
||||||
|
try {
|
||||||
|
const { useUserApi } = await import('~/composables/useApi')
|
||||||
|
const userApi = useUserApi()
|
||||||
|
await userApi.updateUser(editingUser.value.id, form.value)
|
||||||
|
await fetchUsers()
|
||||||
|
closeModal()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('更新用户失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除用户
|
||||||
|
const deleteUser = async (id) => {
|
||||||
|
if (!confirm('确定要删除这个用户吗?')) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { useUserApi } = await import('~/composables/useApi')
|
||||||
|
const userApi = useUserApi()
|
||||||
|
await userApi.deleteUser(id)
|
||||||
|
await fetchUsers()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除用户失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示修改密码模态框
|
||||||
|
const showChangePasswordModal = (user) => {
|
||||||
|
changingPasswordUser.value = user
|
||||||
|
passwordForm.value = {
|
||||||
|
newPassword: '',
|
||||||
|
confirmPassword: ''
|
||||||
|
}
|
||||||
|
showPasswordModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭修改密码模态框
|
||||||
|
const closePasswordModal = () => {
|
||||||
|
showPasswordModal.value = false
|
||||||
|
changingPasswordUser.value = null
|
||||||
|
passwordForm.value = {
|
||||||
|
newPassword: '',
|
||||||
|
confirmPassword: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改密码
|
||||||
|
const changePassword = async () => {
|
||||||
|
if (passwordForm.value.newPassword !== passwordForm.value.confirmPassword) {
|
||||||
|
alert('两次输入的密码不一致')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passwordForm.value.newPassword.length < 6) {
|
||||||
|
alert('密码长度至少6位')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { useUserApi } = await import('~/composables/useApi')
|
||||||
|
const userApi = useUserApi()
|
||||||
|
await userApi.changePassword(changingPasswordUser.value.id, passwordForm.value.newPassword)
|
||||||
|
alert('密码修改成功')
|
||||||
|
closePasswordModal()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('修改密码失败:', error)
|
||||||
|
alert('修改密码失败: ' + (error.message || '未知错误'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理密码修改表单提交
|
||||||
|
const handlePasswordChange = () => {
|
||||||
|
changePassword()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑用户
|
||||||
|
const editUser = (user) => {
|
||||||
|
editingUser.value = user
|
||||||
|
form.value = {
|
||||||
|
username: user.username,
|
||||||
|
email: user.email,
|
||||||
|
password: '',
|
||||||
|
role: user.role,
|
||||||
|
is_active: user.is_active
|
||||||
|
}
|
||||||
|
showEditModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭模态框
|
||||||
|
const closeModal = () => {
|
||||||
|
showCreateModal.value = false
|
||||||
|
showEditModal.value = false
|
||||||
|
editingUser.value = null
|
||||||
|
form.value = {
|
||||||
|
username: '',
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
role: 'user',
|
||||||
|
is_active: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (showEditModal.value) {
|
||||||
|
updateUser()
|
||||||
|
} else {
|
||||||
|
createUser()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取角色样式
|
||||||
|
const getRoleClass = (role) => {
|
||||||
|
return role === 'admin' ? 'bg-red-100 text-red-800' : 'bg-blue-100 text-blue-800'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化日期
|
||||||
|
const formatDate = (dateString) => {
|
||||||
|
return new Date(dateString).toLocaleString('zh-CN')
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
checkAuth()
|
||||||
|
fetchUsers()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
182
web/pages/admin/version.vue
Normal file
182
web/pages/admin/version.vue
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
<template>
|
||||||
|
<div class="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||||
|
<div class="container mx-auto px-4 py-8">
|
||||||
|
<div class="max-w-4xl mx-auto">
|
||||||
|
<!-- 页面标题 -->
|
||||||
|
<div class="text-center mb-8">
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-white mb-2">
|
||||||
|
<i class="fas fa-code-branch mr-3 text-blue-500"></i>
|
||||||
|
版本信息
|
||||||
|
</h1>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400">
|
||||||
|
查看系统版本信息和更新状态
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 版本信息组件 -->
|
||||||
|
<VersionInfo />
|
||||||
|
|
||||||
|
<!-- 版本历史 -->
|
||||||
|
<div class="mt-8 bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||||
|
<i class="fas fa-history mr-2 text-green-500"></i>
|
||||||
|
版本历史
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div v-for="(version, index) in versionHistory" :key="index"
|
||||||
|
class="border-l-4 border-blue-500 pl-4 py-2">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h4 class="font-medium text-gray-900 dark:text-white">
|
||||||
|
v{{ version.version }}
|
||||||
|
</h4>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||||
|
{{ version.date }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<span class="px-2 py-1 text-xs rounded-full"
|
||||||
|
:class="getVersionTypeClass(version.type)">
|
||||||
|
{{ version.type }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ul class="mt-2 space-y-1">
|
||||||
|
<li v-for="(change, changeIndex) in version.changes" :key="changeIndex"
|
||||||
|
class="text-sm text-gray-600 dark:text-gray-400 flex items-start">
|
||||||
|
<span class="mr-2 mt-1" :class="getChangeTypeClass(change.type)">
|
||||||
|
{{ getChangeTypeIcon(change.type) }}
|
||||||
|
</span>
|
||||||
|
{{ change.description }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 构建信息 -->
|
||||||
|
<div class="mt-8 bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||||
|
<i class="fas fa-cogs mr-2 text-purple-500"></i>
|
||||||
|
构建信息
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div class="p-3 bg-gray-50 dark:bg-gray-700 rounded">
|
||||||
|
<span class="text-sm text-gray-600 dark:text-gray-400">构建环境</span>
|
||||||
|
<p class="font-mono text-gray-900 dark:text-white">Go 1.23.0</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-3 bg-gray-50 dark:bg-gray-700 rounded">
|
||||||
|
<span class="text-sm text-gray-600 dark:text-gray-400">前端框架</span>
|
||||||
|
<p class="font-mono text-gray-900 dark:text-white">Nuxt.js 3.8.0</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-3 bg-gray-50 dark:bg-gray-700 rounded">
|
||||||
|
<span class="text-sm text-gray-600 dark:text-gray-400">数据库</span>
|
||||||
|
<p class="font-mono text-gray-900 dark:text-white">PostgreSQL 15+</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-3 bg-gray-50 dark:bg-gray-700 rounded">
|
||||||
|
<span class="text-sm text-gray-600 dark:text-gray-400">部署方式</span>
|
||||||
|
<p class="font-mono text-gray-900 dark:text-white">Docker</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 设置页面布局
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'admin'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 页面元数据
|
||||||
|
useHead({
|
||||||
|
title: '版本信息 - 网盘资源数据库',
|
||||||
|
meta: [
|
||||||
|
{ name: 'description', content: '查看系统版本信息和更新状态' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
interface VersionChange {
|
||||||
|
type: 'feature' | 'fix' | 'improvement' | 'breaking'
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface VersionHistory {
|
||||||
|
version: string
|
||||||
|
date: string
|
||||||
|
type: 'major' | 'minor' | 'patch'
|
||||||
|
changes: VersionChange[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const versionHistory: VersionHistory[] = [
|
||||||
|
{
|
||||||
|
version: '1.0.0',
|
||||||
|
date: '2024-01-15',
|
||||||
|
type: 'major',
|
||||||
|
changes: [
|
||||||
|
{ type: 'feature', description: '🎉 首次发布' },
|
||||||
|
{ type: 'feature', description: '📁 多平台网盘支持' },
|
||||||
|
{ type: 'feature', description: '🔍 智能搜索功能' },
|
||||||
|
{ type: 'feature', description: '📊 数据统计和分析' },
|
||||||
|
{ type: 'feature', description: '🏷️ 标签系统' },
|
||||||
|
{ type: 'feature', description: '👥 用户权限管理' },
|
||||||
|
{ type: 'feature', description: '📦 批量资源管理' },
|
||||||
|
{ type: 'feature', description: '🔄 自动处理功能' },
|
||||||
|
{ type: 'feature', description: '📈 热播剧管理' },
|
||||||
|
{ type: 'feature', description: '⚙️ 系统配置管理' },
|
||||||
|
{ type: 'feature', description: '🔐 JWT认证系统' },
|
||||||
|
{ type: 'feature', description: '📱 响应式设计' },
|
||||||
|
{ type: 'feature', description: '🌙 深色模式支持' },
|
||||||
|
{ type: 'feature', description: '🎨 现代化UI界面' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 获取版本类型样式
|
||||||
|
const getVersionTypeClass = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'major':
|
||||||
|
return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
|
||||||
|
case 'minor':
|
||||||
|
return 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200'
|
||||||
|
case 'patch':
|
||||||
|
return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
|
||||||
|
default:
|
||||||
|
return 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取变更类型样式
|
||||||
|
const getChangeTypeClass = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'feature':
|
||||||
|
return 'text-green-600 dark:text-green-400'
|
||||||
|
case 'fix':
|
||||||
|
return 'text-red-600 dark:text-red-400'
|
||||||
|
case 'improvement':
|
||||||
|
return 'text-blue-600 dark:text-blue-400'
|
||||||
|
case 'breaking':
|
||||||
|
return 'text-orange-600 dark:text-orange-400'
|
||||||
|
default:
|
||||||
|
return 'text-gray-600 dark:text-gray-400'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取变更类型图标
|
||||||
|
const getChangeTypeIcon = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'feature':
|
||||||
|
return '✨'
|
||||||
|
case 'fix':
|
||||||
|
return '🐛'
|
||||||
|
case 'improvement':
|
||||||
|
return '🔧'
|
||||||
|
case 'breaking':
|
||||||
|
return '💥'
|
||||||
|
default:
|
||||||
|
return '<27><>'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -347,16 +347,16 @@ curl -X GET "http://localhost:8080/api/public/hot-dramas?page=1&page_size=5" \
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 页脚 -->
|
<!-- 页脚 -->
|
||||||
<footer class="mt-auto py-6 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
|
<AppFooter />
|
||||||
<div class="max-w-7xl mx-auto text-center text-gray-600 dark:text-gray-400 text-sm px-3 sm:px-5">
|
|
||||||
<p class="mb-2">本站内容由网络爬虫自动抓取。本站不储存、复制、传播任何文件,仅作个人公益学习,请在获取后24小内删除!!!</p>
|
|
||||||
<p>© 2025 网盘资源数据库 By 老九</p>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
// 设置页面布局
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'admin'
|
||||||
|
})
|
||||||
|
|
||||||
// 页面元数据
|
// 页面元数据
|
||||||
useHead({
|
useHead({
|
||||||
title: 'API文档 - 网盘资源数据库',
|
title: 'API文档 - 网盘资源数据库',
|
||||||
|
|||||||
@@ -1,480 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100 p-3 sm:p-5">
|
|
||||||
<!-- 全局加载状态 -->
|
|
||||||
<div v-if="pageLoading" class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center z-50">
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg p-8 shadow-xl">
|
|
||||||
<div class="flex flex-col items-center space-y-4">
|
|
||||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
|
||||||
<div class="text-center">
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">正在加载...</h3>
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">请稍候,正在加载分类数据</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="max-w-7xl mx-auto">
|
|
||||||
<!-- 头部 -->
|
|
||||||
<div class="bg-slate-800 dark:bg-gray-800 text-white dark:text-gray-100 rounded-lg shadow-lg p-4 sm:p-8 mb-4 sm:mb-8 text-center flex items-center">
|
|
||||||
<nav class="mt-4 flex flex-col sm:flex-row justify-center gap-2 sm:gap-4">
|
|
||||||
<NuxtLink
|
|
||||||
to="/admin"
|
|
||||||
class="w-full sm:w-auto px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md transition-colors text-center flex items-center justify-center gap-2"
|
|
||||||
>
|
|
||||||
<i class="fas fa-arrow-left"></i> 返回
|
|
||||||
</NuxtLink>
|
|
||||||
</nav>
|
|
||||||
<div class="flex-1">
|
|
||||||
<h1 class="text-2xl sm:text-3xl font-bold">
|
|
||||||
<NuxtLink to="/admin" class="text-white hover:text-gray-200 dark:hover:text-gray-300 no-underline">分类管理</NuxtLink>
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
|
||||||
<div class="flex justify-between items-center mb-4">
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<button
|
|
||||||
@click="showAddModal = true"
|
|
||||||
class="w-full sm:w-auto px-4 py-2 bg-green-600 hover:bg-green-700 rounded-md transition-colors text-center flex items-center justify-center gap-2"
|
|
||||||
>
|
|
||||||
<i class="fas fa-plus"></i> 添加分类
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<div class="relative">
|
|
||||||
<input
|
|
||||||
v-model="searchQuery"
|
|
||||||
@keyup="debounceSearch"
|
|
||||||
type="text"
|
|
||||||
class="w-64 px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500 transition-all text-sm"
|
|
||||||
placeholder="搜索分类名称..."
|
|
||||||
/>
|
|
||||||
<div class="absolute right-3 top-1/2 transform -translate-y-1/2">
|
|
||||||
<i class="fas fa-search text-gray-400 text-sm"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
@click="refreshData"
|
|
||||||
class="px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<i class="fas fa-refresh"></i> 刷新
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 分类列表 -->
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden">
|
|
||||||
<div class="overflow-x-auto">
|
|
||||||
<table class="w-full min-w-full">
|
|
||||||
<thead>
|
|
||||||
<tr class="bg-slate-800 dark:bg-gray-700 text-white dark:text-gray-100">
|
|
||||||
<th class="px-4 py-3 text-left text-sm font-medium">ID</th>
|
|
||||||
<th class="px-4 py-3 text-left text-sm font-medium">分类名称</th>
|
|
||||||
<th class="px-4 py-3 text-left text-sm font-medium">描述</th>
|
|
||||||
<th class="px-4 py-3 text-left text-sm font-medium">资源数量</th>
|
|
||||||
<th class="px-4 py-3 text-left text-sm font-medium">关联标签</th>
|
|
||||||
<th class="px-4 py-3 text-left text-sm font-medium">操作</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
|
||||||
<tr v-if="loading" class="text-center py-8">
|
|
||||||
<td colspan="6" class="text-gray-500 dark:text-gray-400">
|
|
||||||
<i class="fas fa-spinner fa-spin mr-2"></i>加载中...
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-else-if="categories.length === 0" class="text-center py-8">
|
|
||||||
<td colspan="6" class="text-gray-500 dark:text-gray-400">
|
|
||||||
<div class="flex flex-col items-center justify-center py-12">
|
|
||||||
<svg class="w-16 h-16 text-gray-300 dark:text-gray-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 48 48">
|
|
||||||
<circle cx="24" cy="24" r="20" stroke-width="3" stroke-dasharray="6 6" />
|
|
||||||
<path d="M16 24h16M24 16v16" stroke-width="3" stroke-linecap="round" />
|
|
||||||
</svg>
|
|
||||||
<div class="text-lg font-semibold text-gray-400 dark:text-gray-500 mb-2">暂无分类</div>
|
|
||||||
<div class="text-sm text-gray-400 dark:text-gray-600 mb-4">你可以点击上方"添加分类"按钮创建新分类</div>
|
|
||||||
<button
|
|
||||||
@click="showAddModal = true"
|
|
||||||
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors text-sm flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<i class="fas fa-plus"></i> 添加分类
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr
|
|
||||||
v-for="category in categories"
|
|
||||||
:key="category.id"
|
|
||||||
class="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
|
||||||
>
|
|
||||||
<td class="px-4 py-3 text-sm text-gray-900 dark:text-gray-100 font-medium">{{ category.id }}</td>
|
|
||||||
<td class="px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
|
|
||||||
<span :title="category.name">{{ category.name }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
<span v-if="category.description" :title="category.description">{{ category.description }}</span>
|
|
||||||
<span v-else class="text-gray-400 dark:text-gray-500 italic">无描述</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
<span class="px-2 py-1 bg-blue-100 dark:bg-blue-900/20 text-blue-800 dark:text-blue-300 rounded-full text-xs">
|
|
||||||
{{ category.resource_count || 0 }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
<span v-if="category.tag_names && category.tag_names.length > 0" class="text-gray-800 dark:text-gray-200">
|
|
||||||
{{ category.tag_names.join(', ') }}
|
|
||||||
</span>
|
|
||||||
<span v-else class="text-gray-400 dark:text-gray-500 italic text-xs">无标签</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-3 text-sm">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<button
|
|
||||||
@click="editCategory(category)"
|
|
||||||
class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 transition-colors"
|
|
||||||
title="编辑分类"
|
|
||||||
>
|
|
||||||
<i class="fas fa-edit"></i>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="deleteCategory(category.id)"
|
|
||||||
class="text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300 transition-colors"
|
|
||||||
title="删除分类"
|
|
||||||
>
|
|
||||||
<i class="fas fa-trash"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 分页 -->
|
|
||||||
<div v-if="totalPages > 1" class="flex flex-wrap justify-center gap-1 sm:gap-2 mt-6">
|
|
||||||
<button
|
|
||||||
v-if="currentPage > 1"
|
|
||||||
@click="goToPage(currentPage - 1)"
|
|
||||||
class="bg-white text-gray-700 hover:bg-gray-50 px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm flex items-center"
|
|
||||||
>
|
|
||||||
<i class="fas fa-chevron-left mr-1"></i> 上一页
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
@click="goToPage(1)"
|
|
||||||
:class="currentPage === 1 ? 'bg-slate-800 text-white' : 'bg-white text-gray-700 hover:bg-gray-50'"
|
|
||||||
class="px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm"
|
|
||||||
>
|
|
||||||
1
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
v-if="totalPages > 1"
|
|
||||||
@click="goToPage(2)"
|
|
||||||
:class="currentPage === 2 ? 'bg-slate-800 text-white' : 'bg-white text-gray-700 hover:bg-gray-50'"
|
|
||||||
class="px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm"
|
|
||||||
>
|
|
||||||
2
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<span v-if="currentPage > 2" class="px-2 py-1 sm:px-3 sm:py-2 text-gray-500 text-sm">...</span>
|
|
||||||
|
|
||||||
<button
|
|
||||||
v-if="currentPage !== 1 && currentPage !== 2 && currentPage > 2"
|
|
||||||
class="bg-slate-800 text-white px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm"
|
|
||||||
>
|
|
||||||
{{ currentPage }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
v-if="currentPage < totalPages"
|
|
||||||
@click="goToPage(currentPage + 1)"
|
|
||||||
class="bg-white text-gray-700 hover:bg-gray-50 px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm flex items-center"
|
|
||||||
>
|
|
||||||
下一页 <i class="fas fa-chevron-right ml-1"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 统计信息 -->
|
|
||||||
<div v-if="totalPages <= 1" class="mt-4 text-center">
|
|
||||||
<div class="inline-flex items-center bg-white dark:bg-gray-800 rounded-lg shadow px-6 py-3">
|
|
||||||
<div class="text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
共 <span class="font-semibold text-gray-900 dark:text-gray-100">{{ totalCount }}</span> 个分类
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 添加/编辑分类模态框 -->
|
|
||||||
<div v-if="showAddModal" class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center z-50 p-4">
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full">
|
|
||||||
<div class="p-6">
|
|
||||||
<div class="flex justify-between items-center mb-4">
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
|
||||||
{{ editingCategory ? '编辑分类' : '添加分类' }}
|
|
||||||
</h3>
|
|
||||||
<button @click="closeModal" class="text-gray-500 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200">
|
|
||||||
<i class="fas fa-times"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form @submit.prevent="handleSubmit">
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">分类名称:</label>
|
|
||||||
<input
|
|
||||||
v-model="formData.name"
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-gray-100 dark:placeholder-gray-500"
|
|
||||||
placeholder="请输入分类名称"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">描述:</label>
|
|
||||||
<textarea
|
|
||||||
v-model="formData.description"
|
|
||||||
rows="3"
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-gray-100 dark:placeholder-gray-500"
|
|
||||||
placeholder="请输入分类描述(可选)"
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex justify-end gap-3">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
@click="closeModal"
|
|
||||||
class="px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-200 dark:bg-gray-600 rounded-md hover:bg-gray-300 dark:hover:bg-gray-500 transition-colors"
|
|
||||||
>
|
|
||||||
取消
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
:disabled="submitting"
|
|
||||||
class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
||||||
>
|
|
||||||
{{ submitting ? '提交中...' : (editingCategory ? '更新' : '添加') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
const router = useRouter()
|
|
||||||
const userStore = useUserStore()
|
|
||||||
const config = useRuntimeConfig()
|
|
||||||
|
|
||||||
// 页面状态
|
|
||||||
const pageLoading = ref(true)
|
|
||||||
const loading = ref(false)
|
|
||||||
const categories = ref([])
|
|
||||||
|
|
||||||
// 分页状态
|
|
||||||
const currentPage = ref(1)
|
|
||||||
const pageSize = ref(20)
|
|
||||||
const totalCount = ref(0)
|
|
||||||
const totalPages = ref(0)
|
|
||||||
|
|
||||||
// 搜索状态
|
|
||||||
const searchQuery = ref('')
|
|
||||||
let searchTimeout: NodeJS.Timeout | null = null
|
|
||||||
|
|
||||||
// 模态框状态
|
|
||||||
const showAddModal = ref(false)
|
|
||||||
const submitting = ref(false)
|
|
||||||
const editingCategory = ref(null)
|
|
||||||
|
|
||||||
// 表单数据
|
|
||||||
const formData = ref({
|
|
||||||
name: '',
|
|
||||||
description: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取认证头
|
|
||||||
const getAuthHeaders = () => {
|
|
||||||
return userStore.authHeaders
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面元数据
|
|
||||||
useHead({
|
|
||||||
title: '分类管理 - 网盘资源数据库',
|
|
||||||
meta: [
|
|
||||||
{ name: 'description', content: '管理网盘资源分类' },
|
|
||||||
{ name: 'keywords', content: '分类管理,资源管理' }
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
// 检查认证状态
|
|
||||||
const checkAuth = () => {
|
|
||||||
userStore.initAuth()
|
|
||||||
if (!userStore.isAuthenticated) {
|
|
||||||
router.push('/')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取分类列表
|
|
||||||
const fetchCategories = async () => {
|
|
||||||
try {
|
|
||||||
loading.value = true
|
|
||||||
const params = {
|
|
||||||
page: currentPage.value,
|
|
||||||
page_size: pageSize.value,
|
|
||||||
search: searchQuery.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await $fetch('/categories', {
|
|
||||||
baseURL: config.public.apiBase,
|
|
||||||
params
|
|
||||||
})
|
|
||||||
|
|
||||||
// 解析响应
|
|
||||||
if (response && typeof response === 'object' && 'code' in response && response.code === 200) {
|
|
||||||
categories.value = response.data.items || []
|
|
||||||
totalCount.value = response.data.total || 0
|
|
||||||
totalPages.value = Math.ceil(totalCount.value / pageSize.value)
|
|
||||||
} else {
|
|
||||||
categories.value = response.items || []
|
|
||||||
totalCount.value = response.total || 0
|
|
||||||
totalPages.value = Math.ceil(totalCount.value / pageSize.value)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取分类列表失败:', error)
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索防抖
|
|
||||||
const debounceSearch = () => {
|
|
||||||
if (searchTimeout) {
|
|
||||||
clearTimeout(searchTimeout)
|
|
||||||
}
|
|
||||||
searchTimeout = setTimeout(() => {
|
|
||||||
currentPage.value = 1
|
|
||||||
fetchCategories()
|
|
||||||
}, 300)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新数据
|
|
||||||
const refreshData = () => {
|
|
||||||
fetchCategories()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分页跳转
|
|
||||||
const goToPage = (page: number) => {
|
|
||||||
currentPage.value = page
|
|
||||||
fetchCategories()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 编辑分类
|
|
||||||
const editCategory = (category: any) => {
|
|
||||||
editingCategory.value = category
|
|
||||||
formData.value = {
|
|
||||||
name: category.name,
|
|
||||||
description: category.description || ''
|
|
||||||
}
|
|
||||||
showAddModal.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除分类
|
|
||||||
const deleteCategory = async (categoryId: number) => {
|
|
||||||
if (!confirm(`确定要删除分类吗?`)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await $fetch(`/categories/${categoryId}`, {
|
|
||||||
baseURL: config.public.apiBase,
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: getAuthHeaders()
|
|
||||||
})
|
|
||||||
await fetchCategories()
|
|
||||||
} catch (error) {
|
|
||||||
console.error('删除分类失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交表单
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
try {
|
|
||||||
submitting.value = true
|
|
||||||
|
|
||||||
if (editingCategory.value) {
|
|
||||||
await $fetch(`/categories/${editingCategory.value.id}`, {
|
|
||||||
baseURL: config.public.apiBase,
|
|
||||||
method: 'PUT',
|
|
||||||
body: formData.value,
|
|
||||||
headers: getAuthHeaders()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
await $fetch('/categories', {
|
|
||||||
baseURL: config.public.apiBase,
|
|
||||||
method: 'POST',
|
|
||||||
body: formData.value,
|
|
||||||
headers: getAuthHeaders()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
closeModal()
|
|
||||||
await fetchCategories()
|
|
||||||
} catch (error) {
|
|
||||||
console.error('提交分类失败:', error)
|
|
||||||
} finally {
|
|
||||||
submitting.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭模态框
|
|
||||||
const closeModal = () => {
|
|
||||||
showAddModal.value = false
|
|
||||||
editingCategory.value = null
|
|
||||||
formData.value = {
|
|
||||||
name: '',
|
|
||||||
description: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化时间
|
|
||||||
const formatTime = (timestamp: string) => {
|
|
||||||
if (!timestamp) return '-'
|
|
||||||
const date = new Date(timestamp)
|
|
||||||
const year = date.getFullYear()
|
|
||||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
||||||
const day = String(date.getDate()).padStart(2, '0')
|
|
||||||
const hours = String(date.getHours()).padStart(2, '0')
|
|
||||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
|
||||||
return `${year}-${month}-${day} ${hours}:${minutes}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 退出登录
|
|
||||||
const handleLogout = () => {
|
|
||||||
userStore.logout()
|
|
||||||
navigateTo('/login')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面加载
|
|
||||||
onMounted(async () => {
|
|
||||||
try {
|
|
||||||
checkAuth()
|
|
||||||
await fetchCategories()
|
|
||||||
|
|
||||||
// 检查URL参数,如果action=add则自动打开新增弹窗
|
|
||||||
const route = useRoute()
|
|
||||||
if (route.query.action === 'add') {
|
|
||||||
showAddModal.value = true
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('分类管理页面初始化失败:', error)
|
|
||||||
} finally {
|
|
||||||
pageLoading.value = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
/* 自定义样式 */
|
|
||||||
</style>
|
|
||||||
@@ -198,16 +198,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 页脚 -->
|
<!-- 页脚 -->
|
||||||
<footer class="mt-auto py-6 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
|
<AppFooter />
|
||||||
<div class="max-w-7xl mx-auto text-center text-gray-600 dark:text-gray-400 text-sm px-3 sm:px-5">
|
|
||||||
<p class="mb-2">本站内容由网络爬虫自动抓取。本站不储存、复制、传播任何文件,仅作个人公益学习,请在获取后24小内删除!!!</p>
|
|
||||||
<p>© 2025 网盘资源数据库 By 老九</p>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
// 设置页面布局
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'admin'
|
||||||
|
})
|
||||||
|
|
||||||
import { ref, computed, onMounted, watch } from 'vue'
|
import { ref, computed, onMounted, watch } from 'vue'
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
{{ systemConfig?.site_title || '网盘资源数据库' }}
|
{{ systemConfig?.site_title || '网盘资源数据库' }}
|
||||||
</a>
|
</a>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<nav class="mt-4 flex flex-col sm:flex-row justify-center gap-2 sm:gap-2 right-4 top-0 absolute">
|
<nav class="mt-4 flex flex-col sm:flex-row justify-center gap-2 sm:gap-2 right-4 top-0 absolute">
|
||||||
<NuxtLink to="/hot-dramas" class="hidden sm:flex">
|
<NuxtLink to="/hot-dramas" class="hidden sm:flex">
|
||||||
<n-button size="tiny" type="tertiary" round ghost class="!px-2 !py-1 !text-xs !text-white dark:!text-white !border-white/30 hover:!border-white">
|
<n-button size="tiny" type="tertiary" round ghost class="!px-2 !py-1 !text-xs !text-white dark:!text-white !border-white/30 hover:!border-white">
|
||||||
@@ -232,12 +233,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 页脚 -->
|
<!-- 页脚 -->
|
||||||
<footer class="mt-auto py-6 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
|
<AppFooter />
|
||||||
<div class="max-w-7xl mx-auto text-center text-gray-600 dark:text-gray-400 text-sm px-3 sm:px-5">
|
|
||||||
<p class="mb-2">本站内容由网络爬虫自动抓取。本站不储存、复制、传播任何文件,仅作个人公益学习,请在获取后24小内删除!!!</p>
|
|
||||||
<p>{{ systemConfig?.copyright || '© 2025 网盘资源数据库 By 老九' }}</p>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -254,6 +250,8 @@ useHead({
|
|||||||
// 获取运行时配置
|
// 获取运行时配置
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 获取路由参数
|
// 获取路由参数
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|||||||
@@ -234,16 +234,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 页脚 -->
|
<!-- 页脚 -->
|
||||||
<footer class="mt-auto py-6 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
|
<AppFooter />
|
||||||
<div class="max-w-7xl mx-auto text-center text-gray-600 dark:text-gray-400 text-sm px-3 sm:px-5">
|
|
||||||
<p class="mb-2">本站内容由网络爬虫自动抓取。本站不储存、复制、传播任何文件,仅作个人公益学习,请在获取后24小内删除!!!</p>
|
|
||||||
<p>© 2025 网盘资源数据库 By 老九</p>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
// 设置页面布局
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'admin'
|
||||||
|
})
|
||||||
|
|
||||||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
|
|||||||
@@ -1,500 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100 p-3 sm:p-5">
|
|
||||||
<div class="max-w-4xl mx-auto">
|
|
||||||
<!-- 头部 -->
|
|
||||||
<div class="bg-slate-800 dark:bg-gray-800 text-white dark:text-gray-100 rounded-lg shadow-lg p-4 sm:p-8 mb-4 sm:mb-8 text-center flex items-center">
|
|
||||||
<nav class="mt-4 flex flex-col sm:flex-row justify-center gap-2 sm:gap-4">
|
|
||||||
<NuxtLink
|
|
||||||
to="/admin"
|
|
||||||
class="w-full sm:w-auto px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md transition-colors text-center flex items-center justify-center gap-2"
|
|
||||||
>
|
|
||||||
<i class="fas fa-arrow-left"></i> 返回
|
|
||||||
</NuxtLink>
|
|
||||||
</nav>
|
|
||||||
<div class="flex-1">
|
|
||||||
<h1 class="text-2xl sm:text-3xl font-bold">
|
|
||||||
{{ systemConfig?.site_title ? `${systemConfig.site_title} - 系统配置` : '系统配置' }}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 配置表单 -->
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
|
|
||||||
<form @submit.prevent="saveConfig" class="space-y-6">
|
|
||||||
<!-- SEO 配置 -->
|
|
||||||
<div class="border-b border-gray-200 dark:border-gray-700 pb-6">
|
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
|
|
||||||
<i class="fas fa-search text-blue-600"></i>
|
|
||||||
SEO 配置
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
||||||
<!-- 网站标题 -->
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
||||||
网站标题 *
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
v-model="config.siteTitle"
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
|
||||||
placeholder="网盘资源数据库"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 网站描述 -->
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
||||||
网站描述
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
v-model="config.siteDescription"
|
|
||||||
type="text"
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
|
||||||
placeholder="专业的网盘资源数据库"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 关键词 -->
|
|
||||||
<div class="md:col-span-2">
|
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
||||||
关键词 (用逗号分隔)
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
v-model="config.keywords"
|
|
||||||
type="text"
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
|
||||||
placeholder="网盘,资源管理,文件分享"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 作者 -->
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
||||||
作者
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
v-model="config.author"
|
|
||||||
type="text"
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
|
||||||
placeholder="系统管理员"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 版权信息 -->
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
||||||
版权信息
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
v-model="config.copyright"
|
|
||||||
type="text"
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
|
||||||
placeholder="© 2024 网盘资源数据库"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 自动处理配置 -->
|
|
||||||
<div class="border-b border-gray-200 dark:border-gray-700 pb-6">
|
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
|
|
||||||
<i class="fas fa-cogs text-green-600"></i>
|
|
||||||
自动处理配置
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div class="space-y-4">
|
|
||||||
<!-- 待处理资源自动处理 -->
|
|
||||||
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
|
||||||
<div class="flex-1">
|
|
||||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white">
|
|
||||||
待处理资源自动处理
|
|
||||||
</h3>
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
|
||||||
开启后,系统将自动处理待处理的资源,无需手动操作
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<label class="relative inline-flex items-center cursor-pointer">
|
|
||||||
<input
|
|
||||||
v-model="config.autoProcessReadyResources"
|
|
||||||
type="checkbox"
|
|
||||||
class="sr-only peer"
|
|
||||||
/>
|
|
||||||
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 自动转存 -->
|
|
||||||
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
|
||||||
<div class="flex-1">
|
|
||||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white">
|
|
||||||
自动转存
|
|
||||||
</h3>
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
|
||||||
开启后,系统将自动转存资源到其他网盘平台
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<label class="relative inline-flex items-center cursor-pointer">
|
|
||||||
<input
|
|
||||||
v-model="config.autoTransferEnabled"
|
|
||||||
type="checkbox"
|
|
||||||
class="sr-only peer"
|
|
||||||
/>
|
|
||||||
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 自动转存配置(仅在开启时显示) -->
|
|
||||||
<div v-if="config.autoTransferEnabled" class="ml-6 space-y-4">
|
|
||||||
<!-- 自动转存限制天数 -->
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
||||||
自动转存限制(n天内资源)
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
v-model.number="config.autoTransferLimitDays"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
max="365"
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
|
||||||
placeholder="30"
|
|
||||||
/>
|
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
|
||||||
只转存指定天数内的资源,0表示不限制时间
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 最小存储空间 -->
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
||||||
最小存储空间(GB)
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
v-model.number="config.autoTransferMinSpace"
|
|
||||||
type="number"
|
|
||||||
min="100"
|
|
||||||
max="1024"
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
|
||||||
placeholder="500"
|
|
||||||
/>
|
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
|
||||||
当网盘剩余空间小于此值时,停止自动转存(100-1024GB)
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 自动拉取热播剧 -->
|
|
||||||
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
|
||||||
<div class="flex-1">
|
|
||||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white">
|
|
||||||
自动拉取热播剧
|
|
||||||
</h3>
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
|
||||||
开启后,系统将自动从豆瓣获取热播剧信息
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<label class="relative inline-flex items-center cursor-pointer">
|
|
||||||
<input
|
|
||||||
v-model="config.autoFetchHotDramaEnabled"
|
|
||||||
type="checkbox"
|
|
||||||
class="sr-only peer"
|
|
||||||
/>
|
|
||||||
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 自动处理间隔 -->
|
|
||||||
<div v-if="config.autoProcessReadyResources" class="ml-6">
|
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
||||||
自动处理间隔 (分钟)
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
v-model.number="config.autoProcessInterval"
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
max="1440"
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
|
||||||
placeholder="30"
|
|
||||||
/>
|
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
|
||||||
建议设置 5-60 分钟,避免过于频繁的处理
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 其他配置 -->
|
|
||||||
<div>
|
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
|
|
||||||
<i class="fas fa-info-circle text-purple-600"></i>
|
|
||||||
其他配置
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
||||||
<!-- 每页显示数量 -->
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
||||||
每页显示数量
|
|
||||||
</label>
|
|
||||||
<select
|
|
||||||
v-model.number="config.pageSize"
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
|
||||||
>
|
|
||||||
<option value="20">20 条</option>
|
|
||||||
<option value="50">50 条</option>
|
|
||||||
<option value="100">100 条</option>
|
|
||||||
<option value="200">200 条</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 系统维护模式 -->
|
|
||||||
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
|
||||||
<div class="flex-1">
|
|
||||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white">
|
|
||||||
维护模式
|
|
||||||
</h3>
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
|
||||||
开启后,普通用户无法访问系统
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<label class="relative inline-flex items-center cursor-pointer">
|
|
||||||
<input
|
|
||||||
v-model="config.maintenanceMode"
|
|
||||||
type="checkbox"
|
|
||||||
class="sr-only peer"
|
|
||||||
/>
|
|
||||||
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-red-300 dark:peer-focus:ring-red-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-red-600"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- API配置 -->
|
|
||||||
<div class="border-b border-gray-200 dark:border-gray-700 pb-6">
|
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
|
|
||||||
<i class="fas fa-key text-orange-600"></i>
|
|
||||||
API 配置
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div class="space-y-4">
|
|
||||||
<!-- API Token -->
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
||||||
公开API访问令牌
|
|
||||||
</label>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<input
|
|
||||||
v-model="config.apiToken"
|
|
||||||
type="text"
|
|
||||||
class="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
|
||||||
placeholder="输入API Token,用于公开API访问认证"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
@click="generateApiToken"
|
|
||||||
class="px-4 py-2 bg-orange-600 text-white rounded-md hover:bg-orange-700 transition-colors"
|
|
||||||
>
|
|
||||||
生成
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
|
||||||
用于公开API的访问认证,建议使用随机字符串
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- API使用说明 -->
|
|
||||||
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
|
|
||||||
<h3 class="text-sm font-medium text-blue-800 dark:text-blue-200 mb-2">
|
|
||||||
<i class="fas fa-info-circle mr-1"></i>
|
|
||||||
API使用说明
|
|
||||||
</h3>
|
|
||||||
<div class="text-xs text-blue-700 dark:text-blue-300 space-y-1">
|
|
||||||
<p>• 单个添加资源: POST /api/public/resources/add</p>
|
|
||||||
<p>• 批量添加资源: POST /api/public/resources/batch-add</p>
|
|
||||||
<p>• 资源搜索: GET /api/public/resources/search</p>
|
|
||||||
<p>• 热门剧: GET /api/public/hot-dramas</p>
|
|
||||||
<p>• 认证方式: 在请求头中添加 X-API-Token 或在查询参数中添加 api_token</p>
|
|
||||||
<p>• Swagger文档: <a href="/swagger/index.html" target="_blank" class="underline">查看完整API文档</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 保存按钮 -->
|
|
||||||
<div class="flex justify-end space-x-4 pt-6">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
@click="resetForm"
|
|
||||||
class="px-6 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
||||||
>
|
|
||||||
重置
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
:disabled="loading"
|
|
||||||
class="px-6 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
||||||
>
|
|
||||||
<i v-if="loading" class="fas fa-spinner fa-spin mr-2"></i>
|
|
||||||
{{ loading ? '保存中...' : '保存配置' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, onMounted } from 'vue'
|
|
||||||
|
|
||||||
// API
|
|
||||||
const { getSystemConfig, updateSystemConfig } = useSystemConfigApi()
|
|
||||||
|
|
||||||
// 响应式数据
|
|
||||||
const loading = ref(false)
|
|
||||||
const config = ref({
|
|
||||||
// SEO 配置
|
|
||||||
siteTitle: '网盘资源数据库',
|
|
||||||
siteDescription: '专业的网盘资源数据库',
|
|
||||||
keywords: '网盘,资源管理,文件分享',
|
|
||||||
author: '系统管理员',
|
|
||||||
copyright: '© 2024 网盘资源数据库',
|
|
||||||
|
|
||||||
// 自动处理配置
|
|
||||||
autoProcessReadyResources: false,
|
|
||||||
autoProcessInterval: 30,
|
|
||||||
autoTransferEnabled: false, // 新增
|
|
||||||
autoTransferLimitDays: 30, // 新增:自动转存限制天数
|
|
||||||
autoTransferMinSpace: 500, // 新增:最小存储空间(GB)
|
|
||||||
autoFetchHotDramaEnabled: false, // 新增
|
|
||||||
|
|
||||||
// 其他配置
|
|
||||||
pageSize: 100,
|
|
||||||
maintenanceMode: false,
|
|
||||||
apiToken: '' // 新增
|
|
||||||
})
|
|
||||||
|
|
||||||
// 系统配置状态(用于SEO)
|
|
||||||
const systemConfig = ref(null)
|
|
||||||
|
|
||||||
// 页面元数据 - 移到变量声明之后
|
|
||||||
useHead({
|
|
||||||
title: () => systemConfig.value?.site_title ? `${systemConfig.value.site_title} - 系统配置` : '系统配置 - 网盘资源数据库',
|
|
||||||
meta: [
|
|
||||||
{
|
|
||||||
name: 'description',
|
|
||||||
content: () => systemConfig.value?.site_description || '系统配置管理页面'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'keywords',
|
|
||||||
content: () => systemConfig.value?.keywords || '系统配置,管理'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'author',
|
|
||||||
content: () => systemConfig.value?.author || '系统管理员'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
// 加载配置
|
|
||||||
const loadConfig = async () => {
|
|
||||||
try {
|
|
||||||
loading.value = true
|
|
||||||
const response = await getSystemConfig()
|
|
||||||
console.log('系统配置响应:', response)
|
|
||||||
|
|
||||||
// 使用新的统一响应格式,直接使用response
|
|
||||||
if (response) {
|
|
||||||
config.value = {
|
|
||||||
siteTitle: response.site_title || '网盘资源数据库',
|
|
||||||
siteDescription: response.site_description || '专业的网盘资源数据库',
|
|
||||||
keywords: response.keywords || '网盘,资源管理,文件分享',
|
|
||||||
author: response.author || '系统管理员',
|
|
||||||
copyright: response.copyright || '© 2024 网盘资源数据库',
|
|
||||||
autoProcessReadyResources: response.auto_process_ready_resources || false,
|
|
||||||
autoProcessInterval: response.auto_process_interval || 30,
|
|
||||||
autoTransferEnabled: response.auto_transfer_enabled || false, // 新增
|
|
||||||
autoTransferLimitDays: response.auto_transfer_limit_days || 30, // 新增:自动转存限制天数
|
|
||||||
autoTransferMinSpace: response.auto_transfer_min_space || 500, // 新增:最小存储空间(GB)
|
|
||||||
autoFetchHotDramaEnabled: response.auto_fetch_hot_drama_enabled || false, // 新增
|
|
||||||
pageSize: response.page_size || 100,
|
|
||||||
maintenanceMode: response.maintenance_mode || false,
|
|
||||||
apiToken: response.api_token || '' // 加载API Token
|
|
||||||
}
|
|
||||||
systemConfig.value = response // 更新系统配置状态
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载配置失败:', error)
|
|
||||||
// 显示错误提示
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存配置
|
|
||||||
const saveConfig = async () => {
|
|
||||||
try {
|
|
||||||
loading.value = true
|
|
||||||
|
|
||||||
const requestData = {
|
|
||||||
site_title: config.value.siteTitle,
|
|
||||||
site_description: config.value.siteDescription,
|
|
||||||
keywords: config.value.keywords,
|
|
||||||
author: config.value.author,
|
|
||||||
copyright: config.value.copyright,
|
|
||||||
auto_process_ready_resources: config.value.autoProcessReadyResources,
|
|
||||||
auto_process_interval: config.value.autoProcessInterval,
|
|
||||||
auto_transfer_enabled: config.value.autoTransferEnabled, // 新增
|
|
||||||
auto_transfer_limit_days: config.value.autoTransferLimitDays, // 新增:自动转存限制天数
|
|
||||||
auto_transfer_min_space: config.value.autoTransferMinSpace, // 新增:最小存储空间(GB)
|
|
||||||
auto_fetch_hot_drama_enabled: config.value.autoFetchHotDramaEnabled, // 新增
|
|
||||||
page_size: config.value.pageSize,
|
|
||||||
maintenance_mode: config.value.maintenanceMode,
|
|
||||||
api_token: config.value.apiToken // 保存API Token
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await updateSystemConfig(requestData)
|
|
||||||
// 使用新的统一响应格式,直接检查response是否存在
|
|
||||||
if (response) {
|
|
||||||
alert('配置保存成功!')
|
|
||||||
// 重新加载配置以获取最新数据
|
|
||||||
await loadConfig()
|
|
||||||
} else {
|
|
||||||
alert('保存配置失败:未知错误')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('保存配置失败:', error)
|
|
||||||
alert('保存配置失败:' + (error.message || '未知错误'))
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置表单
|
|
||||||
const resetForm = () => {
|
|
||||||
if (confirm('确定要重置所有配置吗?')) {
|
|
||||||
loadConfig()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成API Token
|
|
||||||
const generateApiToken = () => {
|
|
||||||
const newToken = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
||||||
config.value.apiToken = newToken;
|
|
||||||
alert('新API Token已生成: ' + newToken);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 页面加载时获取配置
|
|
||||||
onMounted(() => {
|
|
||||||
loadConfig()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,389 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100 p-3 sm:p-5">
|
|
||||||
<div class="max-w-7xl mx-auto">
|
|
||||||
<!-- 头部 -->
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-6">
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<h1 class="text-2xl font-bold text-gray-900">用户管理</h1>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<NuxtLink
|
|
||||||
to="/admin"
|
|
||||||
class="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700"
|
|
||||||
>
|
|
||||||
返回管理
|
|
||||||
</NuxtLink>
|
|
||||||
<button
|
|
||||||
@click="showCreateModal = true"
|
|
||||||
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
|
||||||
>
|
|
||||||
添加用户
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 用户列表 -->
|
|
||||||
<div class="bg-white rounded-lg shadow">
|
|
||||||
<div class="px-6 py-4 border-b border-gray-200">
|
|
||||||
<h2 class="text-lg font-semibold text-gray-900">用户列表</h2>
|
|
||||||
</div>
|
|
||||||
<div class="overflow-x-auto">
|
|
||||||
<table class="min-w-full divide-y divide-gray-200">
|
|
||||||
<thead class="bg-gray-50">
|
|
||||||
<tr>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">用户名</th>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">邮箱</th>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">角色</th>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">最后登录</th>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="bg-white divide-y divide-gray-200">
|
|
||||||
<tr v-for="user in users" :key="user.id">
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ user.id }}</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ user.username }}</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ user.email }}</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
|
||||||
<span :class="getRoleClass(user.role)" class="px-2 py-1 text-xs font-medium rounded-full">
|
|
||||||
{{ user.role }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
|
||||||
<span :class="user.is_active ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'"
|
|
||||||
class="px-2 py-1 text-xs font-medium rounded-full">
|
|
||||||
{{ user.is_active ? '激活' : '禁用' }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
|
||||||
{{ user.last_login ? formatDate(user.last_login) : '从未登录' }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
|
||||||
<button @click="editUser(user)" class="text-indigo-600 hover:text-indigo-900 mr-3">编辑</button>
|
|
||||||
<button @click="showChangePasswordModal(user)" class="text-yellow-600 hover:text-yellow-900 mr-3">修改密码</button>
|
|
||||||
<button @click="deleteUser(user.id)" class="text-red-600 hover:text-red-900">删除</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 创建/编辑用户模态框 -->
|
|
||||||
<div v-if="showCreateModal || showEditModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
|
||||||
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
|
||||||
<div class="mt-3">
|
|
||||||
<h3 class="text-lg font-medium text-gray-900 mb-4">
|
|
||||||
{{ showEditModal ? '编辑用户' : '创建用户' }}
|
|
||||||
</h3>
|
|
||||||
<form @submit.prevent="handleSubmit">
|
|
||||||
<div class="space-y-4">
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-gray-700">用户名</label>
|
|
||||||
<input
|
|
||||||
v-model="form.username"
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
class="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-gray-700">邮箱</label>
|
|
||||||
<input
|
|
||||||
v-model="form.email"
|
|
||||||
type="email"
|
|
||||||
required
|
|
||||||
class="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-if="showCreateModal">
|
|
||||||
<label class="block text-sm font-medium text-gray-700">密码</label>
|
|
||||||
<input
|
|
||||||
v-model="form.password"
|
|
||||||
type="password"
|
|
||||||
required
|
|
||||||
class="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-gray-700">角色</label>
|
|
||||||
<select
|
|
||||||
v-model="form.role"
|
|
||||||
class="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
|
|
||||||
>
|
|
||||||
<option value="user">用户</option>
|
|
||||||
<option value="admin">管理员</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="flex items-center">
|
|
||||||
<input
|
|
||||||
v-model="form.is_active"
|
|
||||||
type="checkbox"
|
|
||||||
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
|
|
||||||
/>
|
|
||||||
<span class="ml-2 text-sm text-gray-700">激活状态</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-6 flex justify-end space-x-3">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
@click="closeModal"
|
|
||||||
class="px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50"
|
|
||||||
>
|
|
||||||
取消
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="px-4 py-2 bg-indigo-600 border border-transparent rounded-md text-sm font-medium text-white hover:bg-indigo-700"
|
|
||||||
>
|
|
||||||
{{ showEditModal ? '更新' : '创建' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 修改密码模态框 -->
|
|
||||||
<div v-if="showPasswordModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
|
||||||
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
|
||||||
<div class="mt-3">
|
|
||||||
<h3 class="text-lg font-medium text-gray-900 mb-4">
|
|
||||||
修改用户密码
|
|
||||||
</h3>
|
|
||||||
<p class="text-sm text-gray-600 mb-4">
|
|
||||||
正在为用户 <strong>{{ changingPasswordUser?.username }}</strong> 修改密码
|
|
||||||
</p>
|
|
||||||
<form @submit.prevent="handlePasswordChange">
|
|
||||||
<div class="space-y-4">
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-gray-700">新密码</label>
|
|
||||||
<input
|
|
||||||
v-model="passwordForm.newPassword"
|
|
||||||
type="password"
|
|
||||||
required
|
|
||||||
minlength="6"
|
|
||||||
class="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
|
|
||||||
placeholder="请输入新密码(至少6位)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-gray-700">确认新密码</label>
|
|
||||||
<input
|
|
||||||
v-model="passwordForm.confirmPassword"
|
|
||||||
type="password"
|
|
||||||
required
|
|
||||||
class="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
|
|
||||||
placeholder="请再次输入新密码"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-6 flex justify-end space-x-3">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
@click="closePasswordModal"
|
|
||||||
class="px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50"
|
|
||||||
>
|
|
||||||
取消
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="px-4 py-2 bg-yellow-600 border border-transparent rounded-md text-sm font-medium text-white hover:bg-yellow-700"
|
|
||||||
>
|
|
||||||
修改密码
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const router = useRouter()
|
|
||||||
const userStore = useUserStore()
|
|
||||||
|
|
||||||
const users = ref([])
|
|
||||||
const showCreateModal = ref(false)
|
|
||||||
const showEditModal = ref(false)
|
|
||||||
const showPasswordModal = ref(false)
|
|
||||||
const editingUser = ref(null)
|
|
||||||
const changingPasswordUser = ref(null)
|
|
||||||
const form = ref({
|
|
||||||
username: '',
|
|
||||||
email: '',
|
|
||||||
password: '',
|
|
||||||
role: 'user',
|
|
||||||
is_active: true
|
|
||||||
})
|
|
||||||
const passwordForm = ref({
|
|
||||||
newPassword: '',
|
|
||||||
confirmPassword: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
// 检查认证
|
|
||||||
const checkAuth = () => {
|
|
||||||
userStore.initAuth()
|
|
||||||
if (!userStore.isAuthenticated) {
|
|
||||||
router.push('/login')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取用户列表
|
|
||||||
const fetchUsers = async () => {
|
|
||||||
try {
|
|
||||||
const { useUserApi } = await import('~/composables/useApi')
|
|
||||||
const userApi = useUserApi()
|
|
||||||
const response = await userApi.getUsers()
|
|
||||||
users.value = Array.isArray(response) ? response : (response?.items || [])
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取用户列表失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建用户
|
|
||||||
const createUser = async () => {
|
|
||||||
try {
|
|
||||||
const { useUserApi } = await import('~/composables/useApi')
|
|
||||||
const userApi = useUserApi()
|
|
||||||
await userApi.createUser(form.value)
|
|
||||||
await fetchUsers()
|
|
||||||
closeModal()
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建用户失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新用户
|
|
||||||
const updateUser = async () => {
|
|
||||||
try {
|
|
||||||
const { useUserApi } = await import('~/composables/useApi')
|
|
||||||
const userApi = useUserApi()
|
|
||||||
await userApi.updateUser(editingUser.value.id, form.value)
|
|
||||||
await fetchUsers()
|
|
||||||
closeModal()
|
|
||||||
} catch (error) {
|
|
||||||
console.error('更新用户失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除用户
|
|
||||||
const deleteUser = async (id) => {
|
|
||||||
if (!confirm('确定要删除这个用户吗?')) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { useUserApi } = await import('~/composables/useApi')
|
|
||||||
const userApi = useUserApi()
|
|
||||||
await userApi.deleteUser(id)
|
|
||||||
await fetchUsers()
|
|
||||||
} catch (error) {
|
|
||||||
console.error('删除用户失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示修改密码模态框
|
|
||||||
const showChangePasswordModal = (user) => {
|
|
||||||
changingPasswordUser.value = user
|
|
||||||
passwordForm.value = {
|
|
||||||
newPassword: '',
|
|
||||||
confirmPassword: ''
|
|
||||||
}
|
|
||||||
showPasswordModal.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭修改密码模态框
|
|
||||||
const closePasswordModal = () => {
|
|
||||||
showPasswordModal.value = false
|
|
||||||
changingPasswordUser.value = null
|
|
||||||
passwordForm.value = {
|
|
||||||
newPassword: '',
|
|
||||||
confirmPassword: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改密码
|
|
||||||
const changePassword = async () => {
|
|
||||||
if (passwordForm.value.newPassword !== passwordForm.value.confirmPassword) {
|
|
||||||
alert('两次输入的密码不一致')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (passwordForm.value.newPassword.length < 6) {
|
|
||||||
alert('密码长度至少6位')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { useUserApi } = await import('~/composables/useApi')
|
|
||||||
const userApi = useUserApi()
|
|
||||||
await userApi.changePassword(changingPasswordUser.value.id, passwordForm.value.newPassword)
|
|
||||||
alert('密码修改成功')
|
|
||||||
closePasswordModal()
|
|
||||||
} catch (error) {
|
|
||||||
console.error('修改密码失败:', error)
|
|
||||||
alert('修改密码失败: ' + (error.message || '未知错误'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理密码修改表单提交
|
|
||||||
const handlePasswordChange = () => {
|
|
||||||
changePassword()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 编辑用户
|
|
||||||
const editUser = (user) => {
|
|
||||||
editingUser.value = user
|
|
||||||
form.value = {
|
|
||||||
username: user.username,
|
|
||||||
email: user.email,
|
|
||||||
password: '',
|
|
||||||
role: user.role,
|
|
||||||
is_active: user.is_active
|
|
||||||
}
|
|
||||||
showEditModal.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭模态框
|
|
||||||
const closeModal = () => {
|
|
||||||
showCreateModal.value = false
|
|
||||||
showEditModal.value = false
|
|
||||||
editingUser.value = null
|
|
||||||
form.value = {
|
|
||||||
username: '',
|
|
||||||
email: '',
|
|
||||||
password: '',
|
|
||||||
role: 'user',
|
|
||||||
is_active: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交表单
|
|
||||||
const handleSubmit = () => {
|
|
||||||
if (showEditModal.value) {
|
|
||||||
updateUser()
|
|
||||||
} else {
|
|
||||||
createUser()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取角色样式
|
|
||||||
const getRoleClass = (role) => {
|
|
||||||
return role === 'admin' ? 'bg-red-100 text-red-800' : 'bg-blue-100 text-blue-800'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化日期
|
|
||||||
const formatDate = (dateString) => {
|
|
||||||
return new Date(dateString).toLocaleString('zh-CN')
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
checkAuth()
|
|
||||||
fetchUsers()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
Reference in New Issue
Block a user