update: cks

This commit is contained in:
ctwj
2025-07-17 01:43:22 +08:00
parent af09fc4537
commit 613831a1a9
22 changed files with 1656 additions and 1467 deletions

View File

@@ -1,153 +0,0 @@
# PanFactory 单例模式分析
## 为什么需要单例模式?
### 1. 当前使用场景分析
`utils/scheduler.go` 中的使用:
```go
func (s *Scheduler) processReadyResources() {
// 每次处理待处理资源时都创建新实例
factory := panutils.GetInstance() // 使用单例模式
for _, readyResource := range readyResources {
// 处理每个资源
s.convertReadyResourceToResource(readyResource, factory)
}
}
```
### 2. 性能开销分析
#### 工厂实例本身的开销
```go
type PanFactory struct{} // 空结构体,几乎无开销
```
- 内存占用几乎为0
- 创建时间:纳秒级别
#### 实际开销分析
真正的开销在于每次调用 `CreatePanService` 时创建的具体服务实例:
1. **HTTP客户端创建**
```go
httpClient: &http.Client{
Timeout: 30 * time.Second,
}
```
2. **请求头设置**
```go
headers: make(map[string]string)
```
3. **配置对象创建**
```go
config := &PanConfig{...}
```
### 3. 单例模式的优势
#### 内存优化
- **避免重复创建**:工厂实例只创建一次
- **减少GC压力**:减少对象创建和销毁
- **内存占用稳定**:不会因为频繁创建导致内存波动
#### 性能优化
- **减少初始化开销**:避免重复的初始化操作
- **提高响应速度**:后续调用直接使用已创建的实例
- **减少锁竞争**:单例模式使用 `sync.Once`,性能更好
#### 资源管理
- **统一管理**:所有地方使用同一个工厂实例
- **状态一致性**:避免多个实例状态不一致的问题
- **配置共享**:可以在工厂中维护全局配置
### 4. 实现细节
#### 线程安全的单例实现
```go
var (
instance *PanFactory
once sync.Once
)
func NewPanFactory() *PanFactory {
once.Do(func() {
instance = &PanFactory{}
})
return instance
}
func GetInstance() *PanFactory {
return NewPanFactory()
}
```
#### 优势
1. **线程安全**:使用 `sync.Once` 保证线程安全
2. **延迟初始化**:只有在第一次调用时才创建实例
3. **性能优秀**:后续调用直接返回已创建的实例
### 5. 性能测试结果
#### 单例模式 vs 普通创建
```bash
# 单例模式性能测试
BenchmarkSingletonCreation-8 100000000 11.2 ns/op
# 普通创建性能测试(如果每次都创建新实例)
BenchmarkNewPanFactory-8 100000000 12.1 ns/op
```
#### 内存使用对比
- **单例模式**:固定内存占用,无波动
- **普通创建**:每次创建新实例,增加内存占用
### 6. 使用建议
#### 推荐使用方式
```go
// ✅ 推荐:使用单例模式
factory := panutils.GetInstance()
// ❌ 不推荐:每次都创建新实例
factory := panutils.NewPanFactory()
```
#### 在定时任务中的使用
```go
func (s *Scheduler) processReadyResources() {
// 获取单例实例
factory := panutils.GetInstance()
for _, readyResource := range readyResources {
// 复用同一个工厂实例
s.convertReadyResourceToResource(readyResource, factory)
}
}
```
### 7. 扩展性考虑
#### 未来可能的扩展
1. **配置缓存**:在工厂中缓存常用配置
2. **连接池管理**管理HTTP连接池
3. **服务实例缓存**:缓存已创建的服务实例
4. **监控和统计**:在工厂中添加使用统计
#### 单例模式的适用性
- ✅ **适合**:工厂类、配置管理、连接池
- ❌ **不适合**:有状态的对象、需要隔离的实例
### 8. 总结
对于 `PanFactory` 使用单例模式是**推荐的做法**,原因:
1. **性能优化**:减少不必要的对象创建
2. **内存优化**降低内存占用和GC压力
3. **资源管理**:统一管理工厂实例
4. **线程安全**:保证并发环境下的正确性
5. **扩展性好**:为未来功能扩展提供基础
虽然工厂实例本身开销不大,但在高频调用的场景下(如定时任务),单例模式能带来明显的性能提升和资源优化。

View File

@@ -253,6 +253,71 @@ func (a *AlipanService) DeleteFiles(fileList []string) (*TransferResult, error)
return SuccessResult("删除成功", nil), nil
}
// GetUserInfo 获取用户信息
func (a *AlipanService) GetUserInfo(cookie string) (*UserInfo, error) {
// 设置Cookie
a.SetHeader("Cookie", cookie)
// 获取access token
accessToken, err := a.manageAccessToken()
if err != nil {
return nil, fmt.Errorf("获取access_token失败: %v", err)
}
// 设置Authorization头
a.SetHeader("Authorization", "Bearer "+accessToken)
// 调用阿里云盘用户信息API
userInfoURL := "https://api.alipan.com/v2/user/get"
resp, err := a.HTTPGet(userInfoURL, nil)
if err != nil {
return nil, fmt.Errorf("获取用户信息失败: %v", err)
}
// 解析响应
var result struct {
Code string `json:"code"`
Data struct {
NickName string `json:"nick_name"`
Avatar string `json:"avatar"`
DriveInfo struct {
TotalSize string `json:"total_size"`
UsedSize string `json:"used_size"`
} `json:"drive_info"`
VipInfo struct {
VipStatus string `json:"vip_status"`
} `json:"vip_info"`
} `json:"data"`
}
if err := a.ParseJSONResponse(resp, &result); err != nil {
return nil, fmt.Errorf("解析用户信息失败: %v", err)
}
if result.Code != "" {
return nil, fmt.Errorf("API返回错误: %s", result.Code)
}
// 转换VIP状态
vipStatus := result.Data.VipInfo.VipStatus == "vip"
// 转换容量字符串为字节数
totalSizeStr := result.Data.DriveInfo.TotalSize
usedSizeStr := result.Data.DriveInfo.UsedSize
// 解析容量字符串
totalSizeBytes := ParseCapacityString(totalSizeStr)
usedSizeBytes := ParseCapacityString(usedSizeStr)
return &UserInfo{
Username: result.Data.NickName,
VIPStatus: vipStatus,
UsedSpace: usedSizeBytes,
TotalSpace: totalSizeBytes,
ServiceType: "alipan",
}, nil
}
// getAlipan1 通过分享id获取file_id
func (a *AlipanService) getAlipan1(shareID string) (*AlipanShareInfo, error) {
data := map[string]interface{}{

View File

@@ -1,5 +1,9 @@
package pan
import (
"fmt"
)
// BaiduPanService 百度网盘服务
type BaiduPanService struct {
*BasePanService
@@ -44,3 +48,56 @@ func (b *BaiduPanService) DeleteFiles(fileList []string) (*TransferResult, error
// TODO: 实现百度网盘文件删除
return ErrorResult("百度网盘文件删除功能暂未实现"), nil
}
// GetUserInfo 获取用户信息
func (b *BaiduPanService) GetUserInfo(cookie string) (*UserInfo, error) {
// 设置Cookie
b.SetHeader("Cookie", cookie)
// 调用百度网盘用户信息API
userInfoURL := "https://pan.baidu.com/api/gettemplatevariable"
data := map[string]interface{}{
"fields": "['username','uk','vip_type','vip_endtime','total_capacity','used_capacity']",
}
resp, err := b.HTTPPost(userInfoURL, data, nil)
if err != nil {
return nil, fmt.Errorf("获取用户信息失败: %v", err)
}
// 解析响应
var result struct {
Errno int `json:"errno"`
Data struct {
Username string `json:"username"`
Uk string `json:"uk"`
VipType int `json:"vip_type"`
VipEndtime string `json:"vip_endtime"`
TotalCapacity string `json:"total_capacity"`
UsedCapacity string `json:"used_capacity"`
} `json:"data"`
}
if err := b.ParseJSONResponse(resp, &result); err != nil {
return nil, fmt.Errorf("解析用户信息失败: %v", err)
}
if result.Errno != 0 {
return nil, fmt.Errorf("API返回错误: %d", result.Errno)
}
// 转换VIP状态
vipStatus := result.Data.VipType > 0
// 解析容量字符串
totalCapacityBytes := ParseCapacityString(result.Data.TotalCapacity)
usedCapacityBytes := ParseCapacityString(result.Data.UsedCapacity)
return &UserInfo{
Username: result.Data.Username,
VIPStatus: vipStatus,
UsedSpace: usedCapacityBytes,
TotalSpace: totalCapacityBytes,
ServiceType: "baidu",
}, nil
}

View File

@@ -2,6 +2,7 @@ package pan
import (
"fmt"
"strconv"
"strings"
"sync"
)
@@ -53,6 +54,15 @@ type TransferResult struct {
Fid string `json:"fid,omitempty"`
}
// UserInfo 用户信息结构体
type UserInfo struct {
Username string `json:"username"` // 用户名
VIPStatus bool `json:"vipStatus"` // VIP状态
UsedSpace int64 `json:"usedSpace"` // 已使用空间
TotalSpace int64 `json:"totalSpace"` // 总空间
ServiceType string `json:"serviceType"` // 服务类型
}
// PanService 网盘服务接口
type PanService interface {
// Transfer 转存分享链接
@@ -64,6 +74,9 @@ type PanService interface {
// DeleteFiles 删除文件
DeleteFiles(fileList []string) (*TransferResult, error)
// GetUserInfo 获取用户信息
GetUserInfo(cookie string) (*UserInfo, error)
// GetServiceType 获取服务类型
GetServiceType() ServiceType
}
@@ -210,3 +223,46 @@ func ErrorResult(message string) *TransferResult {
Message: message,
}
}
// ParseCapacityString 解析容量字符串为字节数
func ParseCapacityString(capacityStr string) int64 {
if capacityStr == "" {
return 0
}
// 移除空格并转换为小写
capacityStr = strings.TrimSpace(strings.ToLower(capacityStr))
var multiplier int64 = 1
if strings.Contains(capacityStr, "gb") {
multiplier = 1024 * 1024 * 1024
capacityStr = strings.Replace(capacityStr, "gb", "", -1)
} else if strings.Contains(capacityStr, "mb") {
multiplier = 1024 * 1024
capacityStr = strings.Replace(capacityStr, "mb", "", -1)
} else if strings.Contains(capacityStr, "kb") {
multiplier = 1024
capacityStr = strings.Replace(capacityStr, "kb", "", -1)
} else if strings.Contains(capacityStr, "b") {
capacityStr = strings.Replace(capacityStr, "b", "", -1)
}
// 解析数字
capacityStr = strings.TrimSpace(capacityStr)
if capacityStr == "" {
return 0
}
// 尝试解析浮点数
if strings.Contains(capacityStr, ".") {
if val, err := strconv.ParseFloat(capacityStr, 64); err == nil {
return int64(val * float64(multiplier))
}
} else {
if val, err := strconv.ParseInt(capacityStr, 10, 64); err == nil {
return val * multiplier
}
}
return 0
}

View File

@@ -614,3 +614,99 @@ type PasswordResult struct {
Fid string `json:"fid"`
} `json:"first_file"`
}
// GetUserInfo 获取用户信息
func (q *QuarkPanService) GetUserInfo(cookie string) (*UserInfo, error) {
// 临时设置cookie
originalCookie := q.GetHeader("Cookie")
q.SetHeader("Cookie", cookie)
defer q.SetHeader("Cookie", originalCookie) // 恢复原始cookie
// 获取用户基本信息
queryParams := map[string]string{
"platform": "pc",
"fr": "pc",
}
data, err := q.HTTPGet("https://pan.quark.cn/account/info", queryParams)
if err != nil {
return nil, fmt.Errorf("获取用户信息失败: %v", err)
}
var response struct {
Success bool `json:"success"`
Code string `json:"code"`
Data struct {
Nickname string `json:"nickname"`
AvatarUri string `json:"avatarUri"`
Mobilekps string `json:"mobilekps"`
Config struct{} `json:"config"`
} `json:"data"`
}
if err := json.Unmarshal(data, &response); err != nil {
return nil, fmt.Errorf("解析用户信息失败: %v", err)
}
if !response.Success || response.Code != "OK" {
return nil, fmt.Errorf("获取用户信息失败: API返回错误")
}
// 获取用户详细信息(容量和会员信息)
queryParams1 := map[string]string{
"pr": "ucpro",
"fr": "pc",
"uc_param_str": "",
"fetch_subscribe": "true",
"_ch": "home",
"fetch_identity": "true",
}
data1, err := q.HTTPGet("https://drive-pc.quark.cn/1/clouddrive/member", queryParams1)
if err != nil {
return nil, fmt.Errorf("获取用户详细信息失败: %v", err)
}
var memberResponse struct {
Status int `json:"status"`
Code int `json:"code"`
Message string `json:"message"`
Data struct {
TotalCapacity int64 `json:"secret_total_capacity"`
SecretUseCapacity int64 `json:"secret_use_capacity"`
MemberType string `json:"member_type"`
} `json:"data"`
}
if err := json.Unmarshal(data1, &memberResponse); err != nil {
return nil, fmt.Errorf("解析用户详细信息失败: %v", err)
}
if memberResponse.Status != 200 || memberResponse.Code != 0 {
return nil, fmt.Errorf("获取用户详细信息失败: %s", memberResponse.Message)
}
// 判断VIP状态
vipStatus := memberResponse.Data.MemberType != "NORMAL"
return &UserInfo{
Username: response.Data.Nickname,
VIPStatus: vipStatus,
UsedSpace: memberResponse.Data.SecretUseCapacity,
TotalSpace: memberResponse.Data.TotalCapacity,
ServiceType: "quark",
}, nil
}
// formatBytes 格式化字节数为可读格式
func formatBytes(bytes int64) string {
const unit = 1024
if bytes < unit {
return fmt.Sprintf("%d B", bytes)
}
div, exp := int64(unit), 0
for n := bytes / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}

View File

@@ -0,0 +1,52 @@
package pan
import (
"testing"
)
// TestQuarkGetUserInfo 测试夸克网盘GetUserInfo功能
func TestQuarkGetUserInfo(t *testing.T) {
// 创建夸克网盘服务实例
service := NewQuarkPanService(&PanConfig{})
// 测试无效cookie
_, err := service.GetUserInfo("invalid_cookie")
if err == nil {
t.Error("期望返回错误,但没有返回")
} else {
t.Logf("正确返回错误: %v", err)
}
}
// TestQuarkGetUserInfoWithValidCookie 测试有效cookie的情况
func TestQuarkGetUserInfoWithValidCookie(t *testing.T) {
// 创建夸克网盘服务实例
service := NewQuarkPanService(&PanConfig{})
// 使用测试cookie需要替换为有效的cookie
testCookie := "your_test_cookie_here"
if testCookie == "your_test_cookie_here" {
t.Skip("跳过测试需要提供有效的cookie")
}
userInfo, err := service.GetUserInfo(testCookie)
if err != nil {
t.Logf("获取用户信息失败: %v", err)
return
}
// 验证返回的用户信息
if userInfo == nil {
t.Fatal("用户信息为空")
}
t.Logf("用户名: %s", userInfo.Username)
t.Logf("VIP状态: %t", userInfo.VIPStatus)
t.Logf("容量信息: %s", userInfo.Capacity)
t.Logf("服务类型: %s", userInfo.ServiceType)
// 基本验证
if userInfo.ServiceType != "quark" {
t.Errorf("服务类型不匹配,期望: quark, 实际: %s", userInfo.ServiceType)
}
}

View File

@@ -1,5 +1,7 @@
package pan
import "fmt"
// UCService UC网盘服务
type UCService struct {
*BasePanService
@@ -44,3 +46,54 @@ func (u *UCService) DeleteFiles(fileList []string) (*TransferResult, error) {
// TODO: 实现UC网盘文件删除
return ErrorResult("UC网盘文件删除功能暂未实现"), nil
}
// GetUserInfo 获取用户信息
func (u *UCService) GetUserInfo(cookie string) (*UserInfo, error) {
// 设置Cookie
u.SetHeader("Cookie", cookie)
// 调用UC网盘用户信息API
userInfoURL := "https://drive.uc.cn/api/user/info"
resp, err := u.HTTPGet(userInfoURL, nil)
if err != nil {
return nil, fmt.Errorf("获取用户信息失败: %v", err)
}
// 解析响应
var result struct {
Code int `json:"code"`
Data struct {
Username string `json:"username"`
Nickname string `json:"nickname"`
VipStatus int `json:"vip_status"`
TotalSpace int64 `json:"total_space"`
UsedSpace int64 `json:"used_space"`
} `json:"data"`
}
if err := u.ParseJSONResponse(resp, &result); err != nil {
return nil, fmt.Errorf("解析用户信息失败: %v", err)
}
if result.Code != 0 {
return nil, fmt.Errorf("API返回错误: %d", result.Code)
}
// 转换VIP状态
vipStatus := result.Data.VipStatus > 0
// 使用nickname或username
username := result.Data.Nickname
if username == "" {
username = result.Data.Username
}
return &UserInfo{
Username: username,
VIPStatus: vipStatus,
UsedSpace: result.Data.UsedSpace,
TotalSpace: result.Data.TotalSpace,
ServiceType: "uc",
}, nil
}

View File

@@ -127,14 +127,18 @@ func ToPanResponseList(pans []entity.Pan) []dto.PanResponse {
// ToCksResponse 将Cks实体转换为CksResponse
func ToCksResponse(cks *entity.Cks) dto.CksResponse {
return dto.CksResponse{
ID: cks.ID,
PanID: cks.PanID,
Idx: cks.Idx,
Ck: cks.Ck,
IsValid: cks.IsValid,
Space: cks.Space,
LeftSpace: cks.LeftSpace,
Remark: cks.Remark,
ID: cks.ID,
PanID: cks.PanID,
Idx: cks.Idx,
Ck: cks.Ck,
IsValid: cks.IsValid,
Space: cks.Space,
LeftSpace: cks.LeftSpace,
UsedSpace: cks.UsedSpace,
Username: cks.Username,
VipStatus: cks.VipStatus,
ServiceType: cks.ServiceType,
Remark: cks.Remark,
}
}

View File

@@ -18,24 +18,32 @@ type UpdatePanRequest struct {
// CreateCksRequest 创建cookie请求
type CreateCksRequest struct {
PanID uint `json:"pan_id" binding:"required"`
Idx int `json:"idx"`
Ck string `json:"ck"`
IsValid bool `json:"is_valid"`
Space int64 `json:"space"`
LeftSpace int64 `json:"left_space"`
Remark string `json:"remark"`
PanID uint `json:"pan_id" binding:"required"`
Idx int `json:"idx"`
Ck string `json:"ck"`
IsValid bool `json:"is_valid"`
Space int64 `json:"space"`
LeftSpace int64 `json:"left_space"`
UsedSpace int64 `json:"used_space"`
Username string `json:"username"`
VipStatus bool `json:"vip_status"`
ServiceType string `json:"service_type"`
Remark string `json:"remark"`
}
// UpdateCksRequest 更新cookie请求
type UpdateCksRequest struct {
PanID uint `json:"pan_id"`
Idx int `json:"idx"`
Ck string `json:"ck"`
IsValid bool `json:"is_valid"`
Space int64 `json:"space"`
LeftSpace int64 `json:"left_space"`
Remark string `json:"remark"`
PanID uint `json:"pan_id"`
Idx int `json:"idx"`
Ck string `json:"ck"`
IsValid bool `json:"is_valid"`
Space int64 `json:"space"`
LeftSpace int64 `json:"left_space"`
UsedSpace int64 `json:"used_space"`
Username string `json:"username"`
VipStatus bool `json:"vip_status"`
ServiceType string `json:"service_type"`
Remark string `json:"remark"`
}
// CreateResourceRequest 创建资源请求

View File

@@ -59,14 +59,18 @@ type PanResponse struct {
// CksResponse Cookie响应
type CksResponse struct {
ID uint `json:"id"`
PanID uint `json:"pan_id"`
Idx int `json:"idx"`
Ck string `json:"ck"`
IsValid bool `json:"is_valid"`
Space int64 `json:"space"`
LeftSpace int64 `json:"left_space"`
Remark string `json:"remark"`
ID uint `json:"id"`
PanID uint `json:"pan_id"`
Idx int `json:"idx"`
Ck string `json:"ck"`
IsValid bool `json:"is_valid"`
Space int64 `json:"space"`
LeftSpace int64 `json:"left_space"`
UsedSpace int64 `json:"used_space"`
Username string `json:"username"`
VipStatus bool `json:"vip_status"`
ServiceType string `json:"service_type"`
Remark string `json:"remark"`
}
// ReadyResourceResponse 待处理资源响应

View File

@@ -8,17 +8,21 @@ import (
// Cks 第三方平台账号cookie表
type Cks struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
PanID uint `json:"pan_id" gorm:"not null;comment:平台ID"`
Idx int `json:"idx" gorm:"comment:索引"`
Ck string `json:"ck" gorm:"type:text;comment:cookie"`
IsValid bool `json:"is_valid" gorm:"default:true;comment:是否有效"`
Space int64 `json:"space" gorm:"default:0;comment:总空间"`
LeftSpace int64 `json:"left_space" gorm:"default:0;comment:剩余空间"`
Remark string `json:"remark" gorm:"size:64;not null;comment:备注"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
PanID uint `json:"pan_id" gorm:"not null;comment:平台ID"`
Idx int `json:"idx" gorm:"comment:索引"`
Ck string `json:"ck" gorm:"type:text;comment:cookie"`
IsValid bool `json:"is_valid" gorm:"default:true;comment:是否有效"`
Space int64 `json:"space" gorm:"default:0;comment:总空间(字节)"`
LeftSpace int64 `json:"left_space" gorm:"default:0;comment:剩余空间(字节)"`
UsedSpace int64 `json:"used_space" gorm:"default:0;comment:已使用空间(字节)"`
Username string `json:"username" gorm:"size:100;comment:用户名"`
VipStatus bool `json:"vip_status" gorm:"default:false;comment:VIP状态"`
ServiceType string `json:"service_type" gorm:"size:20;comment:服务类型"`
Remark string `json:"remark" gorm:"size:64;not null;comment:备注"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
// 关联关系
Pan Pan `json:"pan" gorm:"foreignKey:PanID"`

View File

@@ -0,0 +1,29 @@
-- 添加used_space字段
ALTER TABLE cks ADD COLUMN IF NOT EXISTS used_space BIGINT DEFAULT 0;
-- 更新现有数据将字符串类型的容量字段转换为bigint
-- 注意:这里需要根据实际情况调整转换逻辑
UPDATE cks SET
used_space = CASE
WHEN used_space_text IS NOT NULL AND used_space_text != '' THEN
CASE
WHEN used_space_text LIKE '%GB%' THEN
CAST(REPLACE(REPLACE(used_space_text, 'GB', ''), ' ', '') AS BIGINT) * 1024 * 1024 * 1024
WHEN used_space_text LIKE '%MB%' THEN
CAST(REPLACE(REPLACE(used_space_text, 'MB', ''), ' ', '') AS BIGINT) * 1024 * 1024
WHEN used_space_text LIKE '%KB%' THEN
CAST(REPLACE(REPLACE(used_space_text, 'KB', ''), ' ', '') AS BIGINT) * 1024
ELSE 0
END
ELSE 0
END
WHERE used_space_text IS NOT NULL;
-- 删除旧的字符串类型字段(可选,建议先备份数据)
-- ALTER TABLE cks DROP COLUMN IF EXISTS capacity;
-- ALTER TABLE cks DROP COLUMN IF EXISTS used_space_text;
-- ALTER TABLE cks DROP COLUMN IF EXISTS total_space_text;
-- 添加索引以提高查询性能
CREATE INDEX IF NOT EXISTS idx_cks_used_space ON cks(used_space);
CREATE INDEX IF NOT EXISTS idx_cks_space_left_space ON cks(space, left_space);

View File

@@ -3,7 +3,9 @@ package handlers
import (
"net/http"
"strconv"
"strings"
panutils "res_db/common"
"res_db/db/converter"
"res_db/db/dto"
"res_db/db/entity"
@@ -31,28 +33,116 @@ func CreateCks(c *gin.Context) {
return
}
cks := &entity.Cks{
PanID: req.PanID,
Idx: req.Idx,
Ck: req.Ck,
IsValid: req.IsValid,
Space: req.Space,
LeftSpace: req.LeftSpace,
Remark: req.Remark,
// 获取平台信息以确定服务类型
pan, err := repoManager.PanRepository.FindByID(req.PanID)
if err != nil {
ErrorResponse(c, "平台不存在", http.StatusBadRequest)
return
}
err := repoManager.CksRepository.Create(cks)
// 根据平台名称确定服务类型
var serviceType panutils.ServiceType
switch pan.Name {
case "quark":
serviceType = panutils.Quark
case "alipan":
serviceType = panutils.Alipan
case "baidu":
serviceType = panutils.BaiduPan
case "uc":
serviceType = panutils.UC
default:
ErrorResponse(c, "不支持的平台类型", http.StatusBadRequest)
return
}
// 创建网盘服务实例
factory := panutils.GetInstance()
service, err := factory.CreatePanServiceByType(serviceType, &panutils.PanConfig{})
if err != nil {
ErrorResponse(c, "创建网盘服务失败: "+err.Error(), http.StatusInternalServerError)
return
}
// 获取用户信息
userInfo, err := service.GetUserInfo(req.Ck)
if err != nil {
ErrorResponse(c, "无法获取用户信息,账号创建失败: "+err.Error(), http.StatusBadRequest)
return
}
leftSpaceBytes := userInfo.TotalSpace - userInfo.UsedSpace
// 创建Cks实体
cks := &entity.Cks{
PanID: req.PanID,
Idx: req.Idx,
Ck: req.Ck,
IsValid: userInfo.VIPStatus, // 根据VIP状态设置有效性
Space: userInfo.TotalSpace,
LeftSpace: leftSpaceBytes,
UsedSpace: userInfo.UsedSpace,
Username: userInfo.Username,
VipStatus: userInfo.VIPStatus,
ServiceType: userInfo.ServiceType,
Remark: req.Remark,
}
err = repoManager.CksRepository.Create(cks)
if err != nil {
ErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
SuccessResponse(c, gin.H{
"message": "Cookie创建成功",
"message": "账号创建成功",
"cks": converter.ToCksResponse(cks),
})
}
// parseCapacityToBytes 将容量字符串转换为字节数
func parseCapacityToBytes(capacity string) int64 {
if capacity == "未知" || capacity == "" {
return 0
}
// 移除空格并转换为小写
capacity = strings.TrimSpace(strings.ToLower(capacity))
var multiplier int64 = 1
if strings.Contains(capacity, "gb") {
multiplier = 1024 * 1024 * 1024
capacity = strings.Replace(capacity, "gb", "", -1)
} else if strings.Contains(capacity, "mb") {
multiplier = 1024 * 1024
capacity = strings.Replace(capacity, "mb", "", -1)
} else if strings.Contains(capacity, "kb") {
multiplier = 1024
capacity = strings.Replace(capacity, "kb", "", -1)
} else if strings.Contains(capacity, "b") {
capacity = strings.Replace(capacity, "b", "", -1)
}
// 解析数字
capacity = strings.TrimSpace(capacity)
if capacity == "" {
return 0
}
// 尝试解析浮点数
if strings.Contains(capacity, ".") {
if val, err := strconv.ParseFloat(capacity, 64); err == nil {
return int64(val * float64(multiplier))
}
} else {
if val, err := strconv.ParseInt(capacity, 10, 64); err == nil {
return val * multiplier
}
}
return 0
}
// GetCksByID 根据ID获取Cookie详情
func GetCksByID(c *gin.Context) {
idStr := c.Param("id")
@@ -103,12 +193,19 @@ func UpdateCks(c *gin.Context) {
cks.Ck = req.Ck
}
cks.IsValid = req.IsValid
if req.Space != 0 {
cks.Space = req.Space
}
if req.LeftSpace != 0 {
cks.LeftSpace = req.LeftSpace
}
if req.UsedSpace != 0 {
cks.UsedSpace = req.UsedSpace
}
if req.Username != "" {
cks.Username = req.Username
}
cks.VipStatus = req.VipStatus
if req.ServiceType != "" {
cks.ServiceType = req.ServiceType
}
if req.Remark != "" {
cks.Remark = req.Remark
}
@@ -158,3 +255,80 @@ func GetCksByIDGlobal(c *gin.Context) {
response := converter.ToCksResponse(cks)
SuccessResponse(c, response)
}
// RefreshCapacity 刷新账号容量信息
func RefreshCapacity(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
ErrorResponse(c, "无效的ID", http.StatusBadRequest)
return
}
// 获取账号信息
cks, err := repoManager.CksRepository.FindByID(uint(id))
if err != nil {
ErrorResponse(c, "账号不存在", http.StatusNotFound)
return
}
// 获取平台信息以确定服务类型
pan, err := repoManager.PanRepository.FindByID(cks.PanID)
if err != nil {
ErrorResponse(c, "平台不存在", http.StatusBadRequest)
return
}
// 根据平台名称确定服务类型
var serviceType panutils.ServiceType
switch pan.Name {
case "quark":
serviceType = panutils.Quark
case "alipan":
serviceType = panutils.Alipan
case "baidu":
serviceType = panutils.BaiduPan
case "uc":
serviceType = panutils.UC
default:
ErrorResponse(c, "不支持的平台类型", http.StatusBadRequest)
return
}
// 创建网盘服务实例
factory := panutils.GetInstance()
service, err := factory.CreatePanServiceByType(serviceType, &panutils.PanConfig{})
if err != nil {
ErrorResponse(c, "创建网盘服务失败: "+err.Error(), http.StatusInternalServerError)
return
}
// 获取最新的用户信息
userInfo, err := service.GetUserInfo(cks.Ck)
if err != nil {
ErrorResponse(c, "无法获取用户信息,刷新失败: "+err.Error(), http.StatusBadRequest)
return
}
leftSpaceBytes := userInfo.TotalSpace - userInfo.UsedSpace
// 更新账号信息
cks.Username = userInfo.Username
cks.VipStatus = userInfo.VIPStatus
cks.ServiceType = userInfo.ServiceType
cks.Space = userInfo.TotalSpace
cks.LeftSpace = leftSpaceBytes
cks.UsedSpace = userInfo.UsedSpace
cks.IsValid = userInfo.VIPStatus // 根据VIP状态更新有效性
err = repoManager.CksRepository.Update(cks)
if err != nil {
ErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
SuccessResponse(c, gin.H{
"message": "容量信息刷新成功",
"cks": converter.ToCksResponse(cks),
})
}

View File

@@ -160,6 +160,7 @@ func main() {
api.PUT("/cks/:id", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.UpdateCks)
api.DELETE("/cks/:id", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.DeleteCks)
api.GET("/cks/:id", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.GetCksByID)
api.POST("/cks/:id/refresh-capacity", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.RefreshCapacity)
// 标签管理
api.GET("/tags", handlers.GetTags)

View File

@@ -1,290 +0,0 @@
package models
import (
"database/sql"
"fmt"
"log"
"os"
_ "github.com/lib/pq"
)
var DB *sql.DB
// InitDB 初始化数据库连接
func InitDB() error {
host := os.Getenv("DB_HOST")
if host == "" {
host = "localhost"
}
port := os.Getenv("DB_PORT")
if port == "" {
port = "5432"
}
user := os.Getenv("DB_USER")
if user == "" {
user = "postgres"
}
password := os.Getenv("DB_PASSWORD")
if password == "" {
password = "password"
}
dbname := os.Getenv("DB_NAME")
if dbname == "" {
dbname = "res_db"
}
psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
host, port, user, password, dbname)
var err error
DB, err = sql.Open("postgres", psqlInfo)
if err != nil {
return err
}
if err = DB.Ping(); err != nil {
return err
}
// 创建表
if err := createTables(); err != nil {
return err
}
log.Println("数据库连接成功")
return nil
}
// createTables 创建数据库表
func createTables() error {
// 创建pan表
panTable := `
CREATE TABLE IF NOT EXISTS pan (
id SERIAL PRIMARY KEY,
name VARCHAR(64) DEFAULT NULL,
key INTEGER DEFAULT NULL,
icon VARCHAR(128) DEFAULT NULL,
remark VARCHAR(64) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);`
// 创建分类表
categoryTable := `
CREATE TABLE IF NOT EXISTS categories (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);`
// 创建标签表
tagTable := `
CREATE TABLE IF NOT EXISTS tags (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);`
// 创建资源表 - 更新后的结构
resourceTable := `
CREATE TABLE IF NOT EXISTS resources (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
url VARCHAR(128),
pan_id INTEGER REFERENCES pan(id) ON DELETE SET NULL,
quark_url VARCHAR(500),
file_size VARCHAR(100),
category_id INTEGER REFERENCES categories(id) ON DELETE SET NULL,
view_count INTEGER DEFAULT 0,
is_valid BOOLEAN DEFAULT true,
is_public BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);`
// 创建资源标签关联表
resourceTagTable := `
CREATE TABLE IF NOT EXISTS resource_tags (
id SERIAL PRIMARY KEY,
resource_id INTEGER NOT NULL REFERENCES resources(id) ON DELETE CASCADE,
tag_id INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(resource_id, tag_id)
);`
if _, err := DB.Exec(panTable); err != nil {
return err
}
if _, err := DB.Exec(categoryTable); err != nil {
return err
}
if _, err := DB.Exec(tagTable); err != nil {
return err
}
if _, err := DB.Exec(resourceTable); err != nil {
return err
}
if _, err := DB.Exec(resourceTagTable); err != nil {
return err
}
// 创建cks表
cksTable := `
CREATE TABLE IF NOT EXISTS cks (
id SERIAL PRIMARY KEY,
pan_id INTEGER NOT NULL REFERENCES pan(id) ON DELETE CASCADE,
idx INTEGER DEFAULT NULL,
ck TEXT,
is_valid BOOLEAN DEFAULT true,
space BIGINT DEFAULT 0,
left_space BIGINT DEFAULT 0,
remark VARCHAR(64) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);`
// 创建待处理资源表
readyResourceTable := `
CREATE TABLE IF NOT EXISTS ready_resource (
id SERIAL PRIMARY KEY,
title VARCHAR(255),
url VARCHAR(500) NOT NULL,
category VARCHAR(100) DEFAULT NULL,
tags VARCHAR(500) DEFAULT NULL,
img VARCHAR(500) DEFAULT NULL,
source VARCHAR(100) DEFAULT NULL,
extra TEXT DEFAULT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ip VARCHAR(45) DEFAULT NULL
);`
// 创建搜索统计表
searchStatTable := `
CREATE TABLE IF NOT EXISTS search_stats (
id SERIAL PRIMARY KEY,
keyword VARCHAR(255) NOT NULL,
count INTEGER DEFAULT 1,
date DATE NOT NULL,
ip VARCHAR(45),
user_agent VARCHAR(500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);`
// 创建系统配置表
systemConfigTable := `
CREATE TABLE IF NOT EXISTS system_configs (
id SERIAL PRIMARY KEY,
site_title VARCHAR(200) NOT NULL DEFAULT '网盘资源管理系统',
site_description VARCHAR(500),
keywords VARCHAR(500),
author VARCHAR(100),
copyright VARCHAR(200),
auto_process_ready_resources BOOLEAN DEFAULT false,
auto_process_interval INTEGER DEFAULT 30,
auto_transfer_enabled BOOLEAN DEFAULT false,
auto_fetch_hot_drama_enabled BOOLEAN DEFAULT false,
page_size INTEGER DEFAULT 100,
maintenance_mode BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);`
// 创建热播剧表
hotDramaTable := `
CREATE TABLE IF NOT EXISTS hot_dramas (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
rating DECIMAL(3,1) DEFAULT 0.0,
year VARCHAR(10),
directors VARCHAR(500),
actors VARCHAR(1000),
category VARCHAR(50),
sub_type VARCHAR(50),
source VARCHAR(50) DEFAULT 'douban',
douban_id VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);`
if _, err := DB.Exec(panTable); err != nil {
return err
}
if _, err := DB.Exec(cksTable); err != nil {
return err
}
if _, err := DB.Exec(readyResourceTable); err != nil {
return err
}
if _, err := DB.Exec(searchStatTable); err != nil {
return err
}
if _, err := DB.Exec(systemConfigTable); err != nil {
return err
}
if _, err := DB.Exec(hotDramaTable); err != nil {
return err
}
// 插入默认分类
insertDefaultCategories := `
INSERT INTO categories (name, description) VALUES
('电影', '电影资源'),
('电视剧', '电视剧资源'),
('动漫', '动漫资源'),
('音乐', '音乐资源'),
('软件', '软件资源'),
('PC游戏', 'PC游戏资源'),
('手机游戏', '手机游戏'),
('文档', '文档资源'),
('短剧', '短剧'),
('学习资源', '学习资源'),
('视频教程', '学习资源'),
('其他', '其他资源')
ON CONFLICT (name) DO NOTHING;`
// 插入默认网盘平台
insertDefaultPans := `
INSERT INTO pan (name, key, icon, remark) VALUES
('baidu', 1, '<i class="fas fa-cloud text-blue-500"></i>', '百度网盘'),
('aliyun', 2, '<i class="fas fa-cloud text-orange-500"></i>', '阿里云盘'),
('quark', 3, '<i class="fas fa-atom text-purple-500"></i>', '夸克网盘'),
('xunlei', 5, '<i class="fas fa-bolt text-yellow-500"></i>', '迅雷云盘'),
('lanzou', 7, '<i class="fas fa-cloud text-blue-400"></i>', '蓝奏云'),
('123', 8, '<i class="fas fa-cloud text-red-500"></i>', '123云盘'),
('ctfile', 9, '<i class="fas fa-folder text-yellow-600"></i>', '城通网盘'),
('115', 10, '<i class="fas fa-cloud-upload-alt text-green-600"></i>', '115网盘'),
('magnet', 11, '<i class="fas fa-magnet text-red-600"></i>', '磁力链接'),
('uc', 12, '<i class="fas fa-cloud-download-alt text-purple-600"></i>', 'UC网盘'),
('other', 13, '<i class="fas fa-cloud text-gray-500"></i>', '其他')
ON CONFLICT (name) DO NOTHING;`
if _, err := DB.Exec(insertDefaultCategories); err != nil {
return err
}
if _, err := DB.Exec(insertDefaultPans); err != nil {
return err
}
return nil
}

View File

@@ -1,66 +0,0 @@
package models
import (
"time"
)
// Pan 第三方平台表
type Pan struct {
ID int `json:"id"`
Name string `json:"name"` // Qurak
Key int `json:"key"` // quark
Icon string `json:"icon"` // 图标文字
Remark string `json:"remark"` // 备注
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// Cks 第三方平台账号cookie表
type Cks struct {
ID int `json:"id"`
PanID int `json:"pan_id"` // pan ID
Idx int `json:"idx"` // index
Ck string `json:"ck"` // cookie
IsValid bool `json:"is_valid"` // 是否有效
Space int64 `json:"space"` // 空间
LeftSpace int64 `json:"left_space"` // 剩余空间
Remark string `json:"remark"` // 备注
}
// CreatePanRequest 创建平台请求
type CreatePanRequest struct {
Name string `json:"name" binding:"required"`
Key int `json:"key"`
Icon string `json:"icon"`
Remark string `json:"remark"`
}
// UpdatePanRequest 更新平台请求
type UpdatePanRequest struct {
Name string `json:"name"`
Key int `json:"key"`
Icon string `json:"icon"`
Remark string `json:"remark"`
}
// CreateCksRequest 创建cookie请求
type CreateCksRequest struct {
PanID int `json:"pan_id" binding:"required"`
Idx int `json:"idx"`
Ck string `json:"ck"`
IsValid bool `json:"is_valid"`
Space int64 `json:"space"`
LeftSpace int64 `json:"left_space"`
Remark string `json:"remark"`
}
// UpdateCksRequest 更新cookie请求
type UpdateCksRequest struct {
PanID int `json:"pan_id"`
Idx int `json:"idx"`
Ck string `json:"ck"`
IsValid bool `json:"is_valid"`
Space int64 `json:"space"`
LeftSpace int64 `json:"left_space"`
Remark string `json:"remark"`
}

View File

@@ -1,209 +0,0 @@
package models
import (
"database/sql/driver"
"fmt"
"strings"
"time"
"github.com/lib/pq"
)
// Tags 自定义类型,用于处理 PostgreSQL 数组
type Tags []string
// Value 实现 driver.Valuer 接口
func (t Tags) Value() (driver.Value, error) {
if t == nil {
return nil, nil
}
return pq.Array(t), nil
}
// Scan 实现 sql.Scanner 接口
func (t *Tags) Scan(value interface{}) error {
if value == nil {
*t = nil
return nil
}
switch v := value.(type) {
case []byte:
// 处理 PostgreSQL 数组格式: {tag1,tag2,tag3}
if len(v) == 0 {
*t = Tags{}
return nil
}
// 移除 { } 并分割
s := string(v)
s = strings.Trim(s, "{}")
if s == "" {
*t = Tags{}
return nil
}
tags := strings.Split(s, ",")
*t = Tags(tags)
return nil
case string:
// 处理字符串格式
if v == "" {
*t = Tags{}
return nil
}
s := strings.Trim(v, "{}")
if s == "" {
*t = Tags{}
return nil
}
tags := strings.Split(s, ",")
*t = Tags(tags)
return nil
default:
return fmt.Errorf("cannot scan %T into Tags", value)
}
}
// Resource 资源模型 - 更新后的结构
type Resource struct {
ID int `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
URL string `json:"url"` // 单个URL字符串
PanID *int `json:"pan_id"` // 平台ID标识链接类型
QuarkURL string `json:"quark_url"` // 新增字段
FileSize string `json:"file_size"` // 改为 string 类型
CategoryID *int `json:"category_id"`
CategoryName string `json:"category_name"`
ViewCount int `json:"view_count"`
IsValid bool `json:"is_valid"` // 新增字段
IsPublic bool `json:"is_public"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// Tag 标签模型
type Tag struct {
ID int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// ResourceTag 资源标签关联表
type ResourceTag struct {
ID int `json:"id"`
ResourceID int `json:"resource_id"`
TagID int `json:"tag_id"`
CreatedAt time.Time `json:"created_at"`
}
// Category 分类模型
type Category struct {
ID int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// CreateResourceRequest 创建资源请求
type CreateResourceRequest struct {
Title string `json:"title" binding:"required"`
Description string `json:"description"`
URL string `json:"url"`
PanID *int `json:"pan_id"` // 平台ID
QuarkURL string `json:"quark_url"`
FileSize string `json:"file_size"`
CategoryID *int `json:"category_id"`
IsValid bool `json:"is_valid"`
IsPublic bool `json:"is_public"`
TagIDs []int `json:"tag_ids"` // 标签ID列表
}
// UpdateResourceRequest 更新资源请求
type UpdateResourceRequest struct {
Title string `json:"title"`
Description string `json:"description"`
URL string `json:"url"`
PanID *int `json:"pan_id"` // 平台ID
QuarkURL string `json:"quark_url"`
FileSize string `json:"file_size"`
CategoryID *int `json:"category_id"`
IsValid bool `json:"is_valid"`
IsPublic bool `json:"is_public"`
TagIDs []int `json:"tag_ids"` // 标签ID列表
}
// CreateTagRequest 创建标签请求
type CreateTagRequest struct {
Name string `json:"name" binding:"required"`
Description string `json:"description"`
}
// UpdateTagRequest 更新标签请求
type UpdateTagRequest struct {
Name string `json:"name"`
Description string `json:"description"`
}
// CreateCategoryRequest 创建分类请求
type CreateCategoryRequest struct {
Name string `json:"name" binding:"required"`
Description string `json:"description"`
}
// UpdateCategoryRequest 更新分类请求
type UpdateCategoryRequest struct {
Name string `json:"name"`
Description string `json:"description"`
}
// SearchRequest 搜索请求
type SearchRequest struct {
Query string `json:"query"`
CategoryID *int `json:"category_id"`
Page int `json:"page"`
Limit int `json:"limit"`
}
// SearchResponse 搜索响应
type SearchResponse struct {
Resources []Resource `json:"resources"`
Total int `json:"total"`
Page int `json:"page"`
Limit int `json:"limit"`
}
// ReadyResource 待处理资源模型
type ReadyResource struct {
ID int `json:"id"`
Title *string `json:"title"`
URL string `json:"url"`
CreateTime time.Time `json:"create_time"`
IP *string `json:"ip"`
}
// CreateReadyResourceRequest 创建待处理资源请求
type CreateReadyResourceRequest struct {
Title *string `json:"title"`
URL string `json:"url" binding:"required"`
IP *string `json:"ip"`
}
// BatchCreateReadyResourceRequest 批量创建待处理资源请求
type BatchCreateReadyResourceRequest struct {
Resources []CreateReadyResourceRequest `json:"resources" binding:"required"`
}
// Stats 统计信息
type Stats struct {
TotalResources int `json:"total_resources"`
TotalCategories int `json:"total_categories"`
TotalDownloads int `json:"total_downloads"`
TotalViews int `json:"total_views"`
}

View File

@@ -331,12 +331,22 @@ export const useCksApi = () => {
return parseApiResponse(response)
}
const refreshCapacity = async (id: number) => {
const response = await $fetch(`/cks/${id}/refresh-capacity`, {
baseURL: config.public.apiBase,
method: 'POST',
headers: getAuthHeaders() as Record<string, string>
})
return parseApiResponse(response)
}
return {
getCks,
getCksByID,
createCks,
updateCks,
deleteCks,
refreshCapacity,
}
}

View File

@@ -1,68 +1,74 @@
<template>
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100">
<!-- 头部 -->
<div class="bg-slate-800 dark:bg-gray-800 text-white dark:text-gray-100 shadow-lg">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="text-center">
<h1 class="text-3xl sm:text-4xl font-bold mb-4">
网盘资源管理系统 - API文档
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100 flex flex-col">
<!-- 主要内容区域 -->
<div class="flex-1 p-3 sm:p-5">
<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 relative">
<h1 class="text-2xl sm:text-3xl font-bold mb-4">
<a href="/" class="text-white hover:text-gray-200 dark:hover:text-gray-300 no-underline">
网盘资源管理系统 - API文档
</a>
</h1>
<p class="text-lg text-gray-300 max-w-2xl mx-auto">
公开API接口文档支持资源添加搜索和热门剧获取等功能
</p>
<div class="mt-6 flex flex-col sm:flex-row gap-4 justify-center">
<NuxtLink
to="/"
class="px-6 py-3 bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors text-center"
>
<i class="fas fa-home mr-2"></i>返回首页
<p class="text-gray-300 max-w-2xl mx-auto">公开API接口文档支持资源添加搜索和热门剧获取等功能</p>
<nav class="mt-4 flex flex-col sm:flex-row justify-center gap-2 sm:gap-2 right-4 top-0 absolute">
<NuxtLink to="/" 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">
<i class="fas fa-home text-xs"></i> 首页
</n-button>
</NuxtLink>
<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">
<i class="fas fa-film text-xs"></i> 热播剧
</n-button>
</NuxtLink>
<NuxtLink to="/monitor" 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">
<i class="fas fa-chart-line text-xs"></i> 系统监控
</n-button>
</NuxtLink>
</nav>
</div>
<!-- 认证说明 -->
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-6 mb-8">
<h2 class="text-xl font-semibold text-blue-800 dark:text-blue-200 mb-4 flex items-center">
<i class="fas fa-key mr-2"></i>
API认证说明
</h2>
<div class="space-y-3 text-blue-700 dark:text-blue-300">
<p><strong>认证方式</strong>所有API都需要提供API Token进行认证</p>
<p><strong>请求头方式</strong><code class="bg-blue-100 dark:bg-blue-800 px-2 py-1 rounded">X-API-Token: your_token</code></p>
<p><strong>查询参数方式</strong><code class="bg-blue-100 dark:bg-blue-800 px-2 py-1 rounded">?api_token=your_token</code></p>
<p><strong>获取Token</strong>请联系管理员在系统配置中设置API Token</p>
</div>
</div>
</div>
</div>
<!-- 主要内容 -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- 认证说明 -->
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-6 mb-8">
<h2 class="text-xl font-semibold text-blue-800 dark:text-blue-200 mb-4 flex items-center">
<i class="fas fa-key mr-2"></i>
API认证说明
</h2>
<div class="space-y-3 text-blue-700 dark:text-blue-300">
<p><strong>认证方式</strong>所有API都需要提供API Token进行认证</p>
<p><strong>请求头方式</strong><code class="bg-blue-100 dark:bg-blue-800 px-2 py-1 rounded">X-API-Token: your_token</code></p>
<p><strong>查询参数方式</strong><code class="bg-blue-100 dark:bg-blue-800 px-2 py-1 rounded">?api_token=your_token</code></p>
<p><strong>获取Token</strong>请联系管理员在系统配置中设置API Token</p>
</div>
</div>
<!-- API接口列表 -->
<div class="space-y-8">
<!-- 单个添加资源 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
<div class="bg-green-600 text-white px-6 py-4">
<h3 class="text-xl font-semibold flex items-center">
<i class="fas fa-plus-circle mr-2"></i>
单个添加资源
</h3>
<p class="text-green-100 mt-1">添加单个资源到待处理列表</p>
</div>
<div class="p-6">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">请求信息</h4>
<div class="space-y-2 text-sm">
<p><strong>方法</strong><span class="bg-green-100 dark:bg-green-800 text-green-800 dark:text-green-200 px-2 py-1 rounded">POST</span></p>
<p><strong>路径</strong><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">/api/public/resources/add</code></p>
<p><strong>认证</strong><span class="text-red-600 dark:text-red-400">必需</span></p>
<!-- API接口列表 -->
<div class="space-y-8">
<!-- 单个添加资源 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
<div class="bg-green-600 text-white px-6 py-4">
<h3 class="text-xl font-semibold flex items-center">
<i class="fas fa-plus-circle mr-2"></i>
单个添加资源
</h3>
<p class="text-green-100 mt-1">添加单个资源到待处理列表</p>
</div>
<div class="p-6">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">请求信息</h4>
<div class="space-y-2 text-sm">
<p><strong>方法</strong><span class="bg-green-100 dark:bg-green-800 text-green-800 dark:text-green-200 px-2 py-1 rounded">POST</span></p>
<p><strong>路径</strong><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">/api/public/resources/add</code></p>
<p><strong>认证</strong><span class="text-red-600 dark:text-red-400">必需</span></p>
</div>
</div>
</div>
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">请求参数</h4>
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
<pre class="text-sm overflow-x-auto"><code>{
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">请求参数</h4>
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
<pre class="text-sm overflow-x-auto"><code>{
"title": "资源标题",
"description": "资源描述",
"url": "资源链接",
@@ -72,13 +78,13 @@
"source": "数据来源",
"extra": "额外信息"
}</code></pre>
</div>
</div>
</div>
</div>
<div class="mt-6">
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">响应示例</h4>
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
<pre class="text-sm overflow-x-auto"><code>{
<div class="mt-6">
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">响应示例</h4>
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
<pre class="text-sm overflow-x-auto"><code>{
"success": true,
"message": "资源添加成功,已进入待处理列表",
"data": {
@@ -86,34 +92,34 @@
},
"code": 200
}</code></pre>
</div>
</div>
</div>
</div>
</div>
<!-- 批量添加资源 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
<div class="bg-purple-600 text-white px-6 py-4">
<h3 class="text-xl font-semibold flex items-center">
<i class="fas fa-layer-group mr-2"></i>
批量添加资源
</h3>
<p class="text-purple-100 mt-1">批量添加多个资源到待处理列表</p>
</div>
<div class="p-6">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">请求信息</h4>
<div class="space-y-2 text-sm">
<p><strong>方法</strong><span class="bg-purple-100 dark:bg-purple-800 text-purple-800 dark:text-purple-200 px-2 py-1 rounded">POST</span></p>
<p><strong>路径</strong><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">/api/public/resources/batch-add</code></p>
<p><strong>认证</strong><span class="text-red-600 dark:text-red-400">必需</span></p>
<!-- 批量添加资源 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
<div class="bg-purple-600 text-white px-6 py-4">
<h3 class="text-xl font-semibold flex items-center">
<i class="fas fa-layer-group mr-2"></i>
批量添加资源
</h3>
<p class="text-purple-100 mt-1">批量添加多个资源到待处理列表</p>
</div>
<div class="p-6">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">请求信息</h4>
<div class="space-y-2 text-sm">
<p><strong>方法</strong><span class="bg-purple-100 dark:bg-purple-800 text-purple-800 dark:text-purple-200 px-2 py-1 rounded">POST</span></p>
<p><strong>路径</strong><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">/api/public/resources/batch-add</code></p>
<p><strong>认证</strong><span class="text-red-600 dark:text-red-400">必需</span></p>
</div>
</div>
</div>
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">请求参数</h4>
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
<pre class="text-sm overflow-x-auto"><code>{
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">请求参数</h4>
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
<pre class="text-sm overflow-x-auto"><code>{
"resources": [
{
"title": "资源1",
@@ -127,13 +133,13 @@
}
]
}</code></pre>
</div>
</div>
</div>
</div>
<div class="mt-6">
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">响应示例</h4>
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
<pre class="text-sm overflow-x-auto"><code>{
<div class="mt-6">
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">响应示例</h4>
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
<pre class="text-sm overflow-x-auto"><code>{
"success": true,
"message": "批量添加成功,共添加 2 个资源",
"data": {
@@ -142,45 +148,45 @@
},
"code": 200
}</code></pre>
</div>
</div>
</div>
</div>
</div>
<!-- 资源搜索 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
<div class="bg-blue-600 text-white px-6 py-4">
<h3 class="text-xl font-semibold flex items-center">
<i class="fas fa-search mr-2"></i>
资源搜索
</h3>
<p class="text-blue-100 mt-1">搜索资源支持关键词标签分类过滤</p>
</div>
<div class="p-6">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">请求信息</h4>
<div class="space-y-2 text-sm">
<p><strong>方法</strong><span class="bg-blue-100 dark:bg-blue-800 text-blue-800 dark:text-blue-200 px-2 py-1 rounded">GET</span></p>
<p><strong>路径</strong><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">/api/public/resources/search</code></p>
<p><strong>认证</strong><span class="text-red-600 dark:text-red-400">必需</span></p>
</div>
</div>
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">查询参数</h4>
<div class="space-y-2 text-sm">
<p><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">keyword</code> - 搜索关键词</p>
<p><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">tag</code> - 标签过滤</p>
<p><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">category</code> - 分类过滤</p>
<p><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">page</code> - 页码默认1</p>
<p><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">page_size</code> - 每页数量默认20最大100</p>
</div>
</div>
<!-- 资源搜索 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
<div class="bg-blue-600 text-white px-6 py-4">
<h3 class="text-xl font-semibold flex items-center">
<i class="fas fa-search mr-2"></i>
资源搜索
</h3>
<p class="text-blue-100 mt-1">搜索资源支持关键词标签分类过滤</p>
</div>
<div class="mt-6">
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">响应示例</h4>
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
<pre class="text-sm overflow-x-auto"><code>{
<div class="p-6">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">请求信息</h4>
<div class="space-y-2 text-sm">
<p><strong>方法</strong><span class="bg-blue-100 dark:bg-blue-800 text-blue-800 dark:text-blue-200 px-2 py-1 rounded">GET</span></p>
<p><strong>路径</strong><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">/api/public/resources/search</code></p>
<p><strong>认证</strong><span class="text-red-600 dark:text-red-400">必需</span></p>
</div>
</div>
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">查询参数</h4>
<div class="space-y-2 text-sm">
<p><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">keyword</code> - 搜索关键词</p>
<p><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">tag</code> - 标签过滤</p>
<p><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">category</code> - 分类过滤</p>
<p><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">page</code> - 页码默认1</p>
<p><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">page_size</code> - 每页数量默认20最大100</p>
</div>
</div>
</div>
<div class="mt-6">
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">响应示例</h4>
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
<pre class="text-sm overflow-x-auto"><code>{
"success": true,
"message": "搜索成功",
"data": {
@@ -201,42 +207,42 @@
},
"code": 200
}</code></pre>
</div>
</div>
</div>
</div>
</div>
<!-- 热门剧 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
<div class="bg-orange-600 text-white px-6 py-4">
<h3 class="text-xl font-semibold flex items-center">
<i class="fas fa-film mr-2"></i>
热门剧列表
</h3>
<p class="text-orange-100 mt-1">获取热门剧列表支持分页</p>
</div>
<div class="p-6">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">请求信息</h4>
<div class="space-y-2 text-sm">
<p><strong>方法</strong><span class="bg-orange-100 dark:bg-orange-800 text-orange-800 dark:text-orange-200 px-2 py-1 rounded">GET</span></p>
<p><strong>路径</strong><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">/api/public/hot-dramas</code></p>
<p><strong>认证</strong><span class="text-red-600 dark:text-red-400">必需</span></p>
</div>
</div>
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">查询参数</h4>
<div class="space-y-2 text-sm">
<p><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">page</code> - 页码默认1</p>
<p><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">page_size</code> - 每页数量默认20最大100</p>
</div>
</div>
<!-- 热门剧 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
<div class="bg-orange-600 text-white px-6 py-4">
<h3 class="text-xl font-semibold flex items-center">
<i class="fas fa-film mr-2"></i>
热门剧列表
</h3>
<p class="text-orange-100 mt-1">获取热门剧列表支持分页</p>
</div>
<div class="mt-6">
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">响应示例</h4>
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
<pre class="text-sm overflow-x-auto"><code>{
<div class="p-6">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">请求信息</h4>
<div class="space-y-2 text-sm">
<p><strong>方法</strong><span class="bg-orange-100 dark:bg-orange-800 text-orange-800 dark:text-orange-200 px-2 py-1 rounded">GET</span></p>
<p><strong>路径</strong><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">/api/public/hot-dramas</code></p>
<p><strong>认证</strong><span class="text-red-600 dark:text-red-400">必需</span></p>
</div>
</div>
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">查询参数</h4>
<div class="space-y-2 text-sm">
<p><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">page</code> - 页码默认1</p>
<p><code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">page_size</code> - 每页数量默认20最大100</p>
</div>
</div>
</div>
<div class="mt-6">
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">响应示例</h4>
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
<pre class="text-sm overflow-x-auto"><code>{
"success": true,
"message": "获取热门剧成功",
"data": {
@@ -262,59 +268,59 @@
},
"code": 200
}</code></pre>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 错误码说明 -->
<div class="mt-12 bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
<div class="bg-red-600 text-white px-6 py-4">
<h3 class="text-xl font-semibold flex items-center">
<i class="fas fa-exclamation-triangle mr-2"></i>
错误码说明
</h3>
</div>
<div class="p-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">HTTP状态码</h4>
<div class="space-y-2 text-sm">
<p><span class="bg-green-100 dark:bg-green-800 text-green-800 dark:text-green-200 px-2 py-1 rounded">200</span> - 请求成功</p>
<p><span class="bg-red-100 dark:bg-red-800 text-red-800 dark:text-red-200 px-2 py-1 rounded">400</span> - 请求参数错误</p>
<p><span class="bg-red-100 dark:bg-red-800 text-red-800 dark:text-red-200 px-2 py-1 rounded">401</span> - 认证失败Token无效或缺失</p>
<p><span class="bg-red-100 dark:bg-red-800 text-red-800 dark:text-red-200 px-2 py-1 rounded">500</span> - 服务器内部错误</p>
<p><span class="bg-yellow-100 dark:bg-yellow-800 text-yellow-800 dark:text-yellow-200 px-2 py-1 rounded">503</span> - 系统维护中或API Token未配置</p>
<!-- 错误码说明 -->
<div class="mt-12 bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
<div class="bg-red-600 text-white px-6 py-4">
<h3 class="text-xl font-semibold flex items-center">
<i class="fas fa-exclamation-triangle mr-2"></i>
错误码说明
</h3>
</div>
<div class="p-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">HTTP状态码</h4>
<div class="space-y-2 text-sm">
<p><span class="bg-green-100 dark:bg-green-800 text-green-800 dark:text-green-200 px-2 py-1 rounded">200</span> - 请求成功</p>
<p><span class="bg-red-100 dark:bg-red-800 text-red-800 dark:text-red-200 px-2 py-1 rounded">400</span> - 请求参数错误</p>
<p><span class="bg-red-100 dark:bg-red-800 text-red-800 dark:text-red-200 px-2 py-1 rounded">401</span> - 认证失败Token无效或缺失</p>
<p><span class="bg-red-100 dark:bg-red-800 text-red-800 dark:text-red-200 px-2 py-1 rounded">500</span> - 服务器内部错误</p>
<p><span class="bg-yellow-100 dark:bg-yellow-800 text-yellow-800 dark:text-yellow-200 px-2 py-1 rounded">503</span> - 系统维护中或API Token未配置</p>
</div>
</div>
</div>
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">响应格式</h4>
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
<pre class="text-sm overflow-x-auto"><code>{
<div>
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">响应格式</h4>
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
<pre class="text-sm overflow-x-auto"><code>{
"success": true/false,
"message": "响应消息",
"data": {}, // 响应数据
"code": 200 // 状态码
}</code></pre>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 使用示例 -->
<div class="mt-8 bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
<div class="bg-indigo-600 text-white px-6 py-4">
<h3 class="text-xl font-semibold flex items-center">
<i class="fas fa-code mr-2"></i>
使用示例
</h3>
</div>
<div class="p-6">
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">cURL示例</h4>
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
<pre class="text-sm overflow-x-auto"><code># 设置API Token
<!-- 使用示例 -->
<div class="mt-8 bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
<div class="bg-indigo-600 text-white px-6 py-4">
<h3 class="text-xl font-semibold flex items-center">
<i class="fas fa-code mr-2"></i>
使用示例
</h3>
</div>
<div class="p-6">
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">cURL示例</h4>
<div class="bg-gray-50 dark:bg-gray-700 rounded p-4">
<pre class="text-sm overflow-x-auto"><code># 设置API Token
API_TOKEN="your_api_token_here"
# 单个添加资源
@@ -334,15 +340,17 @@ curl -X GET "http://localhost:8080/api/public/resources/search?keyword=测试" \
# 获取热门剧
curl -X GET "http://localhost:8080/api/public/hot-dramas?page=1&page_size=5" \
-H "X-API-Token: $API_TOKEN"</code></pre>
</div>
</div>
</div>
</div>
</div>
<!-- 页脚 -->
<footer class="bg-gray-800 dark:bg-gray-900 text-gray-300 py-8 mt-12">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<p>&copy; 2024 网盘资源管理系统. 保留所有权利.</p>
<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>© 2025 网盘资源管理系统 By 老九</p>
</div>
</footer>
</div>

View File

@@ -1,72 +1,180 @@
<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-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 dark:text-gray-100">第三方平台账号管理</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 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="showCreateModal = 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">
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
<h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100">账号列表</h2>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">ID</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">平台</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">索引</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">状态</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">总空间</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">剩余空间</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">备注</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">操作</th>
<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>
<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="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
<tr v-for="cks in cksList" :key="cks.id">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">{{ cks.id }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
<tr v-if="loading" class="text-center py-8">
<td colspan="10" class="text-gray-500 dark:text-gray-400">
<i class="fas fa-spinner fa-spin mr-2"></i>加载中...
</td>
</tr>
<tr v-else-if="filteredCksList.length === 0" class="text-center py-8">
<td colspan="10" 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="showCreateModal = 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="cks in filteredCksList"
:key="cks.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">{{ cks.id }}</td>
<td class="px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
<div class="flex items-center">
<span v-html="getPlatformIcon(cks.pan?.name || '')" class="mr-2"></span>
{{ cks.pan?.name || '未知平台' }}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">{{ cks.idx || '-' }}</td>
<td class="px-6 py-4 whitespace-nowrap">
<td class="px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
<span v-if="cks.username" :title="cks.username">{{ cks.username }}</span>
<span v-else class="text-gray-400 dark:text-gray-500 italic">未知用户</span>
</td>
<td class="px-4 py-3 text-sm">
<span :class="cks.is_valid ? 'bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-200' : 'bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-200'"
class="px-2 py-1 text-xs font-medium rounded-full">
{{ cks.is_valid ? '有效' : '无效' }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<td class="px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
{{ formatFileSize(cks.space) }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<td class="px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
{{ formatFileSize(cks.space - cks.left_space) }}
</td>
<td class="px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
{{ formatFileSize(cks.left_space) }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">{{ cks.remark || '-' }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<button @click="editCks(cks)" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300 mr-3">编辑</button>
<button @click="deleteCks(cks.id)" class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300">删除</button>
<td class="px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
<div class="flex items-center">
<div class="w-16 bg-gray-200 rounded-full h-2 mr-2">
<div
class="bg-blue-600 h-2 rounded-full transition-all duration-300"
:style="{ width: getUsagePercentage(cks) + '%' }"
></div>
</div>
<span class="text-xs text-gray-500">{{ getUsagePercentage(cks).toFixed(1) }}%</span>
</div>
</td>
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
<span v-if="cks.remark" :title="cks.remark">{{ cks.remark }}</span>
<span v-else class="text-gray-400 dark:text-gray-500 italic">无备注</span>
</td>
<td class="px-4 py-3 text-sm">
<div class="flex items-center gap-2">
<button
@click="refreshCapacity(cks.id)"
class="text-green-600 hover:text-green-800 dark:text-green-400 dark:hover:text-green-300 transition-colors"
title="刷新容量"
>
<i class="fas fa-sync-alt"></i>
</button>
<button
@click="editCks(cks)"
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="deleteCks(cks.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>
@@ -74,107 +182,142 @@
</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 dark:bg-gray-800">
<div class="mt-3">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
{{ showEditModal ? '编辑账号' : '添加账号' }}
</h3>
<form @submit.prevent="handleSubmit">
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">平台 <span class="text-red-500">*</span></label>
<select
v-model="form.pan_id"
required
class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:text-gray-100"
>
<option value="">请选择平台</option>
<option v-for="pan in platforms" :key="pan.id" :value="pan.id">
{{ pan.name }}
</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Cookie <span class="text-red-500">*</span></label>
<textarea
v-model="form.ck"
required
rows="4"
class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:text-gray-100"
placeholder="请输入Cookie内容"
></textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">索引</label>
<input
v-model.number="form.idx"
type="number"
class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:text-gray-100"
placeholder="可选,账号索引"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">总空间 (GB)</label>
<input
v-model.number="form.space"
type="number"
step="0.01"
class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:text-gray-100"
placeholder="可选,总空间大小"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">剩余空间 (GB)</label>
<input
v-model.number="form.left_space"
type="number"
step="0.01"
class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:text-gray-100"
placeholder="可选,剩余空间大小"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">备注</label>
<input
v-model="form.remark"
type="text"
class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:text-gray-100"
placeholder="可选,备注信息"
/>
</div>
<div>
<label class="flex items-center">
<input
v-model="form.is_valid"
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 dark:bg-gray-700 dark:border-gray-600"
/>
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">账号有效</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 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700"
>
取消
</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 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">{{ filteredCksList.length }}</span> 个账号
</div>
</div>
</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 dark:bg-gray-800">
<div class="mt-3">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
{{ showEditModal ? '编辑账号' : '添加账号' }}
</h3>
<form @submit.prevent="handleSubmit">
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">平台类型 <span class="text-red-500">*</span></label>
<select
v-model="form.pan_id"
required
:disabled="showEditModal"
:class="showEditModal ? 'bg-gray-100 dark:bg-gray-600 cursor-not-allowed' : ''"
class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:text-gray-100"
>
<option value="">请选择平台</option>
<option v-for="pan in platforms" :key="pan.id" :value="pan.id">
{{ pan.name }}
</option>
</select>
<p v-if="showEditModal" class="mt-1 text-xs text-gray-500 dark:text-gray-400">编辑时不允许修改平台类型</p>
</div>
<div v-if="showEditModal && editingCks?.username">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">用户名</label>
<div class="mt-1 px-3 py-2 bg-gray-100 dark:bg-gray-600 rounded-md text-sm text-gray-900 dark:text-gray-100">
{{ editingCks.username }}
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Cookie <span class="text-red-500">*</span></label>
<textarea
v-model="form.ck"
required
rows="4"
class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:text-gray-100"
placeholder="请输入Cookie内容系统将自动识别容量"
></textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">备注</label>
<input
v-model="form.remark"
type="text"
class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:text-gray-100"
placeholder="可选,备注信息"
/>
</div>
<div v-if="showEditModal">
<label class="flex items-center">
<input
v-model="form.is_valid"
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 dark:bg-gray-700 dark:border-gray-600"
/>
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">账号有效</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 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700"
>
取消
</button>
<button
type="submit"
:disabled="submitting"
class="px-4 py-2 bg-indigo-600 border border-transparent rounded-md text-sm font-medium text-white hover:bg-indigo-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
{{ submitting ? '处理中...' : (showEditModal ? '更新' : '创建') }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
@@ -193,14 +336,20 @@ const showEditModal = ref(false)
const editingCks = ref(null)
const form = ref({
pan_id: '',
idx: null,
ck: '',
is_valid: true,
space: null,
left_space: null,
remark: ''
})
// 搜索和分页逻辑
const searchQuery = ref('')
const currentPage = ref(1)
const itemsPerPage = ref(10)
const totalPages = ref(1)
const loading = ref(true)
const pageLoading = ref(true)
const submitting = ref(false)
// 检查认证
const checkAuth = () => {
userStore.initAuth()
@@ -212,6 +361,7 @@ const checkAuth = () => {
// 获取账号列表
const fetchCks = async () => {
loading.value = true
try {
const { useCksApi } = await import('~/composables/useApi')
const cksApi = useCksApi()
@@ -219,6 +369,9 @@ const fetchCks = async () => {
cksList.value = Array.isArray(response) ? response : []
} catch (error) {
console.error('获取账号列表失败:', error)
} finally {
loading.value = false
pageLoading.value = false
}
}
@@ -236,6 +389,7 @@ const fetchPlatforms = async () => {
// 创建账号
const createCks = async () => {
submitting.value = true
try {
const { useCksApi } = await import('~/composables/useApi')
const cksApi = useCksApi()
@@ -245,11 +399,14 @@ const createCks = async () => {
} catch (error) {
console.error('创建账号失败:', error)
alert('创建账号失败: ' + (error.message || '未知错误'))
} finally {
submitting.value = false
}
}
// 更新账号
const updateCks = async () => {
submitting.value = true
try {
const { useCksApi } = await import('~/composables/useApi')
const cksApi = useCksApi()
@@ -259,6 +416,8 @@ const updateCks = async () => {
} catch (error) {
console.error('更新账号失败:', error)
alert('更新账号失败: ' + (error.message || '未知错误'))
} finally {
submitting.value = false
}
}
@@ -277,17 +436,30 @@ const deleteCks = async (id) => {
}
}
// 刷新容量
const refreshCapacity = async (id) => {
if (!confirm('确定要刷新此账号的容量信息吗?')) return
try {
const { useCksApi } = await import('~/composables/useApi')
const cksApi = useCksApi()
await cksApi.refreshCapacity(id)
await fetchCks()
alert('容量信息已刷新!')
} catch (error) {
console.error('刷新容量失败:', error)
alert('刷新容量失败: ' + (error.message || '未知错误'))
}
}
// 编辑账号
const editCks = (cks) => {
editingCks.value = cks
form.value = {
pan_id: cks.pan_id,
idx: cks.idx,
ck: cks.ck,
is_valid: cks.is_valid,
space: cks.space,
left_space: cks.left_space,
remark: cks.remark
remark: cks.remark || ''
}
showEditModal.value = true
}
@@ -299,21 +471,18 @@ const closeModal = () => {
editingCks.value = null
form.value = {
pan_id: '',
idx: null,
ck: '',
is_valid: true,
space: null,
left_space: null,
remark: ''
}
}
// 提交表单
const handleSubmit = () => {
const handleSubmit = async () => {
if (showEditModal.value) {
updateCks()
await updateCks()
} else {
createCks()
await createCks()
}
}
@@ -357,9 +526,62 @@ const formatFileSize = (bytes) => {
return bytes + ' B'
}
onMounted(() => {
checkAuth()
// 获取使用率百分比
const getUsagePercentage = (cks) => {
if (!cks.space || cks.space === 0) return 0
const used = cks.used_space || (cks.space - cks.left_space)
return (used / cks.space) * 100
}
// 过滤和分页计算
const filteredCksList = computed(() => {
let filtered = cksList.value
if (searchQuery.value) {
const query = searchQuery.value.toLowerCase()
filtered = filtered.filter(cks =>
cks.pan?.name?.toLowerCase().includes(query) ||
cks.remark?.toLowerCase().includes(query)
)
}
totalPages.value = Math.ceil(filtered.length / itemsPerPage.value)
const start = (currentPage.value - 1) * itemsPerPage.value
const end = start + itemsPerPage.value
return filtered.slice(start, end)
})
// 防抖搜索
let searchTimeout = null
const debounceSearch = () => {
if (searchTimeout) {
clearTimeout(searchTimeout)
}
searchTimeout = setTimeout(() => {
currentPage.value = 1
}, 500)
}
// 刷新数据
const refreshData = () => {
currentPage.value = 1
fetchCks()
fetchPlatforms()
}
// 分页跳转
const goToPage = (page) => {
currentPage.value = page
}
// 页面加载
onMounted(async () => {
try {
checkAuth()
await Promise.all([
fetchCks(),
fetchPlatforms()
])
} catch (error) {
console.error('页面初始化失败:', error)
}
})
</script>

View File

@@ -1,177 +1,209 @@
<template>
<div class="min-h-screen bg-gray-50 dark:bg-gray-900">
<div class="container mx-auto px-4 py-8">
<!-- 页面标题 -->
<div class="mb-8">
<h1 class="text-3xl font-bold text-gray-900 dark:text-white mb-2">热播剧榜单</h1>
<p class="text-gray-600 dark:text-gray-400">实时获取豆瓣热门电影和电视剧榜单</p>
</div>
<!-- 统计信息 -->
<div class="mb-6 grid grid-cols-1 md:grid-cols-4 gap-4">
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 shadow-sm">
<div class="flex items-center">
<div class="p-2 bg-blue-100 dark:bg-blue-900 rounded-lg">
<i class="fas fa-film text-blue-600 dark:text-blue-400"></i>
</div>
<div class="ml-3">
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">总数量</p>
<p class="text-lg font-semibold text-gray-900 dark:text-white">{{ total }}</p>
</div>
</div>
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100 flex flex-col">
<!-- 主要内容区域 -->
<div class="flex-1 p-3 sm:p-5">
<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 relative">
<h1 class="text-2xl sm:text-3xl font-bold mb-4">
<a href="/" class="text-white hover:text-gray-200 dark:hover:text-gray-300 no-underline">
热播剧榜单
</a>
</h1>
<p class="text-gray-300 max-w-2xl mx-auto">实时获取豆瓣热门电影和电视剧榜单</p>
<nav class="mt-4 flex flex-col sm:flex-row justify-center gap-2 sm:gap-2 right-4 top-0 absolute">
<NuxtLink to="/" 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">
<i class="fas fa-home text-xs"></i> 首页
</n-button>
</NuxtLink>
<NuxtLink to="/monitor" 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">
<i class="fas fa-chart-line text-xs"></i> 系统监控
</n-button>
</NuxtLink>
<NuxtLink to="/api-docs" 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">
<i class="fas fa-book text-xs"></i> API文档
</n-button>
</NuxtLink>
</nav>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 shadow-sm">
<div class="flex items-center">
<div class="p-2 bg-green-100 dark:bg-green-900 rounded-lg">
<i class="fas fa-video text-green-600 dark:text-green-400"></i>
</div>
<div class="ml-3">
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">电影</p>
<p class="text-lg font-semibold text-gray-900 dark:text-white">{{ movieCount }}</p>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 shadow-sm">
<div class="flex items-center">
<div class="p-2 bg-purple-100 dark:bg-purple-900 rounded-lg">
<i class="fas fa-tv text-purple-600 dark:text-purple-400"></i>
</div>
<div class="ml-3">
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">电视剧</p>
<p class="text-lg font-semibold text-gray-900 dark:text-white">{{ tvCount }}</p>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 shadow-sm">
<div class="flex items-center">
<div class="p-2 bg-yellow-100 dark:bg-yellow-900 rounded-lg">
<i class="fas fa-star text-yellow-600 dark:text-yellow-400"></i>
</div>
<div class="ml-3">
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">平均评分</p>
<p class="text-lg font-semibold text-gray-900 dark:text-white">{{ averageRating }}</p>
</div>
</div>
</div>
</div>
<!-- 筛选器 -->
<div class="mb-6 flex flex-wrap gap-4">
<button
v-for="category in categories"
:key="category.value"
@click="selectedCategory = category.value"
:class="[
'px-4 py-2 rounded-lg font-medium transition-colors',
selectedCategory === category.value
? 'bg-blue-600 text-white'
: 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 border border-gray-300 dark:border-gray-600'
]"
>
{{ category.label }}
</button>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="flex justify-center items-center py-12">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
<!-- 热播剧列表 -->
<div v-else-if="filteredDramas.length > 0" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
<div
v-for="drama in filteredDramas"
:key="drama.id"
class="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow border border-gray-200 dark:border-gray-700"
>
<!-- 剧集信息 -->
<div class="p-6">
<div class="flex items-start justify-between mb-3">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white line-clamp-2 flex-1">
{{ drama.title }}
</h3>
<div class="flex items-center ml-2 flex-shrink-0">
<span class="text-yellow-500 text-sm font-medium">{{ drama.rating }}</span>
<span class="text-gray-400 dark:text-gray-500 text-xs ml-1"></span>
<!-- 统计信息 -->
<div class="mb-6 grid grid-cols-1 md:grid-cols-4 gap-4">
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 shadow-sm">
<div class="flex items-center">
<div class="p-2 bg-blue-100 dark:bg-blue-900 rounded-lg">
<i class="fas fa-film text-blue-600 dark:text-blue-400"></i>
</div>
<div class="ml-3">
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">总数量</p>
<p class="text-lg font-semibold text-gray-900 dark:text-white">{{ total }}</p>
</div>
</div>
<!-- 副标题 -->
<div v-if="drama.card_subtitle" class="mb-3">
<p class="text-sm text-gray-600 dark:text-gray-400 line-clamp-1">{{ drama.card_subtitle }}</p>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 shadow-sm">
<div class="flex items-center">
<div class="p-2 bg-green-100 dark:bg-green-900 rounded-lg">
<i class="fas fa-video text-green-600 dark:text-green-400"></i>
</div>
<div class="ml-3">
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">电影</p>
<p class="text-lg font-semibold text-gray-900 dark:text-white">{{ movieCount }}</p>
</div>
</div>
<!-- 年份地区类型 -->
<div class="flex items-center gap-2 mb-3 flex-wrap">
<span v-if="drama.year" class="text-sm text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">
{{ drama.year }}
</span>
<span v-if="drama.region" class="text-sm text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">
{{ drama.region }}
</span>
<span class="text-sm text-blue-600 dark:text-blue-400 bg-blue-100 dark:bg-blue-900 px-2 py-1 rounded">
{{ drama.category }}
</span>
<span v-if="drama.sub_type" class="text-sm text-purple-600 dark:text-purple-400 bg-purple-100 dark:bg-purple-900 px-2 py-1 rounded">
{{ drama.sub_type }}
</span>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 shadow-sm">
<div class="flex items-center">
<div class="p-2 bg-purple-100 dark:bg-purple-900 rounded-lg">
<i class="fas fa-tv text-purple-600 dark:text-purple-400"></i>
</div>
<div class="ml-3">
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">电视剧</p>
<p class="text-lg font-semibold text-gray-900 dark:text-white">{{ tvCount }}</p>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 shadow-sm">
<div class="flex items-center">
<div class="p-2 bg-yellow-100 dark:bg-yellow-900 rounded-lg">
<i class="fas fa-star text-yellow-600 dark:text-yellow-400"></i>
</div>
<div class="ml-3">
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">平均评分</p>
<p class="text-lg font-semibold text-gray-900 dark:text-white">{{ averageRating }}</p>
</div>
</div>
</div>
</div>
<!-- 类型标签 -->
<div v-if="drama.genres" class="mb-3">
<div class="flex flex-wrap gap-1">
<span
v-for="genre in drama.genres.split(',')"
:key="genre"
class="text-xs text-gray-600 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded"
>
{{ genre.trim() }}
<!-- 筛选器 -->
<div class="mb-6 flex flex-wrap gap-4">
<button
v-for="category in categories"
:key="category.value"
@click="selectedCategory = category.value"
:class="[
'px-4 py-2 rounded-lg font-medium transition-colors',
selectedCategory === category.value
? 'bg-blue-600 text-white'
: 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 border border-gray-300 dark:border-gray-600'
]"
>
{{ category.label }}
</button>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="flex justify-center items-center py-12">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
<!-- 热播剧列表 -->
<div v-else-if="filteredDramas.length > 0" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
<div
v-for="drama in filteredDramas"
:key="drama.id"
class="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow border border-gray-200 dark:border-gray-700"
>
<!-- 剧集信息 -->
<div class="p-6">
<div class="flex items-start justify-between mb-3">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white line-clamp-2 flex-1">
{{ drama.title }}
</h3>
<div class="flex items-center ml-2 flex-shrink-0">
<span class="text-yellow-500 text-sm font-medium">{{ drama.rating }}</span>
<span class="text-gray-400 dark:text-gray-500 text-xs ml-1"></span>
</div>
</div>
<!-- 副标题 -->
<div v-if="drama.card_subtitle" class="mb-3">
<p class="text-sm text-gray-600 dark:text-gray-400 line-clamp-1">{{ drama.card_subtitle }}</p>
</div>
<!-- 年份地区类型 -->
<div class="flex items-center gap-2 mb-3 flex-wrap">
<span v-if="drama.year" class="text-sm text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">
{{ drama.year }}
</span>
<span v-if="drama.region" class="text-sm text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">
{{ drama.region }}
</span>
<span class="text-sm text-blue-600 dark:text-blue-400 bg-blue-100 dark:bg-blue-900 px-2 py-1 rounded">
{{ drama.category }}
</span>
<span v-if="drama.sub_type" class="text-sm text-purple-600 dark:text-purple-400 bg-purple-100 dark:bg-purple-900 px-2 py-1 rounded">
{{ drama.sub_type }}
</span>
</div>
</div>
<!-- 导演 -->
<div v-if="drama.directors" class="mb-2">
<span class="text-xs text-gray-500 dark:text-gray-400">导演</span>
<span class="text-sm text-gray-700 dark:text-gray-300 line-clamp-1">{{ drama.directors }}</span>
</div>
<!-- 类型标签 -->
<div v-if="drama.genres" class="mb-3">
<div class="flex flex-wrap gap-1">
<span
v-for="genre in drama.genres.split(',')"
:key="genre"
class="text-xs text-gray-600 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded"
>
{{ genre.trim() }}
</span>
</div>
</div>
<!-- -->
<div v-if="drama.actors" class="mb-3">
<span class="text-xs text-gray-500 dark:text-gray-400"></span>
<span class="text-sm text-gray-700 dark:text-gray-300 line-clamp-2">{{ drama.actors }}</span>
</div>
<!-- -->
<div v-if="drama.directors" class="mb-2">
<span class="text-xs text-gray-500 dark:text-gray-400"></span>
<span class="text-sm text-gray-700 dark:text-gray-300 line-clamp-1">{{ drama.directors }}</span>
</div>
<!-- 集数信息 -->
<div v-if="drama.episodes_info" class="mb-3">
<span class="text-xs text-gray-500 dark:text-gray-400">集数</span>
<span class="text-sm text-gray-700 dark:text-gray-300">{{ drama.episodes_info }}</span>
</div>
<!-- 演员 -->
<div v-if="drama.actors" class="mb-3">
<span class="text-xs text-gray-500 dark:text-gray-400">主演</span>
<span class="text-sm text-gray-700 dark:text-gray-300 line-clamp-2">{{ drama.actors }}</span>
</div>
<!-- 评分人数 -->
<div v-if="drama.rating_count" class="mb-3">
<span class="text-xs text-gray-500 dark:text-gray-400">评分人</span>
<span class="text-sm text-gray-700 dark:text-gray-300">{{ formatNumber(drama.rating_count) }}</span>
</div>
<!-- 集数信息 -->
<div v-if="drama.episodes_info" class="mb-3">
<span class="text-xs text-gray-500 dark:text-gray-400"></span>
<span class="text-sm text-gray-700 dark:text-gray-300">{{ drama.episodes_info }}</span>
</div>
<!-- 据来源和时间 -->
<div class="flex items-center justify-between text-xs text-gray-400 dark:text-gray-500 pt-3 border-t border-gray-200 dark:border-gray-600">
<span>来源{{ drama.source }}</span>
<span>{{ formatDate(drama.created_at) }}</span>
<!-- 评分人 -->
<div v-if="drama.rating_count" class="mb-3">
<span class="text-xs text-gray-500 dark:text-gray-400">评分人数</span>
<span class="text-sm text-gray-700 dark:text-gray-300">{{ formatNumber(drama.rating_count) }}</span>
</div>
<!-- 数据来源和时间 -->
<div class="flex items-center justify-between text-xs text-gray-400 dark:text-gray-500 pt-3 border-t border-gray-200 dark:border-gray-600">
<span>来源{{ drama.source }}</span>
<span>{{ formatDate(drama.created_at) }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- 空状态 -->
<div v-else class="text-center py-12">
<div class="text-gray-400 dark:text-gray-500 mb-4">
<i class="fas fa-film text-6xl"></i>
<!-- 空状态 -->
<div v-else class="text-center py-12">
<div class="text-gray-400 dark:text-gray-500 mb-4">
<i class="fas fa-film text-6xl"></i>
</div>
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">暂无热播剧数据</h3>
<p class="text-gray-500 dark:text-gray-400">请稍后再试或联系管理员</p>
</div>
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">暂无热播剧数据</h3>
<p class="text-gray-500 dark:text-gray-400">请稍后再试或联系管理员</p>
</div>
</div>
<!-- 页脚 -->
<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>© 2025 网盘资源管理系统 By 老九</p>
</div>
</footer>
</div>
</template>

View File

@@ -1,213 +1,245 @@
<template>
<div class="min-h-screen bg-gray-50 dark:bg-gray-900">
<div class="container mx-auto px-4 py-8">
<!-- 页面标题 -->
<div class="mb-8">
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-2">系统性能监控</h1>
<p class="text-gray-600 dark:text-gray-400">实时监控系统运行状态和性能指标</p>
</div>
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100 flex flex-col">
<!-- 主要内容区域 -->
<div class="flex-1 p-3 sm:p-5">
<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 relative">
<h1 class="text-2xl sm:text-3xl font-bold mb-4">
<a href="/" class="text-white hover:text-gray-200 dark:hover:text-gray-300 no-underline">
系统性能监控
</a>
</h1>
<p class="text-gray-300 max-w-2xl mx-auto">实时监控系统运行状态和性能指标</p>
<nav class="mt-4 flex flex-col sm:flex-row justify-center gap-2 sm:gap-2 right-4 top-0 absolute">
<NuxtLink to="/" 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">
<i class="fas fa-home text-xs"></i> 首页
</n-button>
</NuxtLink>
<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">
<i class="fas fa-film text-xs"></i> 热播剧
</n-button>
</NuxtLink>
<NuxtLink to="/api-docs" 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">
<i class="fas fa-book text-xs"></i> API文档
</n-button>
</NuxtLink>
</nav>
</div>
<!-- 刷新按钮 -->
<div class="mb-6 flex justify-between items-center">
<div class="flex items-center space-x-4">
<button
@click="refreshData"
:disabled="loading"
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center space-x-2"
>
<i class="fas fa-sync-alt" :class="{ 'fa-spin': loading }"></i>
<span>{{ loading ? '刷新中...' : '刷新数据' }}</span>
</button>
<div class="text-sm text-gray-500 dark:text-gray-400">
最后更新: {{ lastUpdateTime }}
<!-- 刷新按钮 -->
<div class="mb-6 flex justify-between items-center">
<div class="flex items-center space-x-4">
<button
@click="refreshData"
:disabled="loading"
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center space-x-2"
>
<i class="fas fa-sync-alt" :class="{ 'fa-spin': loading }"></i>
<span>{{ loading ? '刷新中...' : '刷新数据' }}</span>
</button>
<div class="text-sm text-gray-500 dark:text-gray-400">
最后更新: {{ lastUpdateTime }}
</div>
</div>
</div>
<div class="flex items-center space-x-2">
<label class="text-sm text-gray-600 dark:text-gray-400">自动刷新:</label>
<input
v-model="autoRefresh"
type="checkbox"
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<span class="text-sm text-gray-500 dark:text-gray-400">{{ autoRefreshInterval }}</span>
</div>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="flex justify-center items-center py-12">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
<!-- 监控数据 -->
<div v-else class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
<!-- 系统信息卡片 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center">
<i class="fas fa-server mr-2 text-blue-600"></i>
系统信息
</h3>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">运行时间:</span>
<span class="font-medium">{{ systemInfo.uptime || 'N/A' }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">启动时间:</span>
<span class="font-medium">{{ systemInfo.start_time || 'N/A' }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">版本:</span>
<span class="font-medium">{{ systemInfo.version || 'N/A' }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">运行模式:</span>
<span class="font-medium">{{ systemInfo.environment?.gin_mode || 'N/A' }}</span>
</div>
<div class="flex items-center space-x-2">
<label class="text-sm text-gray-600 dark:text-gray-400">自动刷新:</label>
<input
v-model="autoRefresh"
type="checkbox"
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<span class="text-sm text-gray-500 dark:text-gray-400">{{ autoRefreshInterval }}</span>
</div>
</div>
<!-- 内存使用卡片 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center">
<i class="fas fa-memory mr-2 text-green-600"></i>
内存使用
</h3>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">当前分配:</span>
<span class="font-medium">{{ formatBytes(performanceStats.memory?.alloc) }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">总分配:</span>
<span class="font-medium">{{ formatBytes(performanceStats.memory?.total_alloc) }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">系统内存:</span>
<span class="font-medium">{{ formatBytes(performanceStats.memory?.sys) }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">堆内存:</span>
<span class="font-medium">{{ formatBytes(performanceStats.memory?.heap_alloc) }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">GC次数:</span>
<span class="font-medium">{{ performanceStats.memory?.num_gc || 0 }}</span>
</div>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="flex justify-center items-center py-12">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
<!-- 数据库连接卡片 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center">
<i class="fas fa-database mr-2 text-purple-600"></i>
数据库连接
</h3>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">最大连接数:</span>
<span class="font-medium">{{ performanceStats.database?.max_open_connections || 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">当前连接数:</span>
<span class="font-medium">{{ performanceStats.database?.open_connections || 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">使用中:</span>
<span class="font-medium">{{ performanceStats.database?.in_use || 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">空闲:</span>
<span class="font-medium">{{ performanceStats.database?.idle || 0 }}</span>
</div>
</div>
</div>
<!-- 系统资源卡片 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center">
<i class="fas fa-microchip mr-2 text-orange-600"></i>
系统资源
</h3>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">CPU核心数:</span>
<span class="font-medium">{{ performanceStats.system?.cpu_count || 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">Go版本:</span>
<span class="font-medium">{{ performanceStats.system?.go_version || 'N/A' }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">协程数:</span>
<span class="font-medium">{{ performanceStats.goroutines || 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">时间戳:</span>
<span class="font-medium">{{ formatTimestamp(performanceStats.timestamp) }}</span>
</div>
</div>
</div>
<!-- 基础统计卡片 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center">
<i class="fas fa-chart-bar mr-2 text-red-600"></i>
基础统计
</h3>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">资源总数:</span>
<span class="font-medium">{{ basicStats.total_resources || 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">分类总数:</span>
<span class="font-medium">{{ basicStats.total_categories || 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">标签总数:</span>
<span class="font-medium">{{ basicStats.total_tags || 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">总浏览量:</span>
<span class="font-medium">{{ basicStats.total_views || 0 }}</span>
</div>
</div>
</div>
<!-- 性能图表卡片 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center">
<i class="fas fa-chart-line mr-2 text-indigo-600"></i>
性能趋势
</h3>
<div class="space-y-3">
<div class="text-center py-4">
<div class="text-2xl font-bold text-indigo-600">
{{ formatBytes(performanceStats.memory?.alloc) }}
<!-- 监控数据 -->
<div v-else class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
<!-- 系统信息卡片 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center">
<i class="fas fa-server mr-2 text-blue-600"></i>
系统信息
</h3>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">运行时间:</span>
<span class="font-medium">{{ systemInfo.uptime || 'N/A' }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">启动时间:</span>
<span class="font-medium">{{ systemInfo.start_time || 'N/A' }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">版本:</span>
<span class="font-medium">{{ systemInfo.version || 'N/A' }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">运行模式:</span>
<span class="font-medium">{{ systemInfo.environment?.gin_mode || 'N/A' }}</span>
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">当前内存使用</div>
</div>
<div class="w-full bg-gray-200 rounded-full h-2">
<div
class="bg-indigo-600 h-2 rounded-full transition-all duration-300"
:style="{ width: memoryUsagePercentage + '%' }"
></div>
</div>
<!-- 内存使用卡片 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center">
<i class="fas fa-memory mr-2 text-green-600"></i>
内存使用
</h3>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">当前分配:</span>
<span class="font-medium">{{ formatBytes(performanceStats.memory?.alloc) }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">总分配:</span>
<span class="font-medium">{{ formatBytes(performanceStats.memory?.total_alloc) }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">系统内存:</span>
<span class="font-medium">{{ formatBytes(performanceStats.memory?.sys) }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">堆内存:</span>
<span class="font-medium">{{ formatBytes(performanceStats.memory?.heap_alloc) }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">GC次数:</span>
<span class="font-medium">{{ performanceStats.memory?.num_gc || 0 }}</span>
</div>
</div>
<div class="text-center text-sm text-gray-500 dark:text-gray-400">
内存使用率: {{ memoryUsagePercentage.toFixed(1) }}%
</div>
<!-- 数据库连接卡片 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center">
<i class="fas fa-database mr-2 text-purple-600"></i>
数据库连接
</h3>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">最大连接数:</span>
<span class="font-medium">{{ performanceStats.database?.max_open_connections || 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">当前连接数:</span>
<span class="font-medium">{{ performanceStats.database?.open_connections || 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">使用中:</span>
<span class="font-medium">{{ performanceStats.database?.in_use || 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">空闲:</span>
<span class="font-medium">{{ performanceStats.database?.idle || 0 }}</span>
</div>
</div>
</div>
<!-- 系统资源卡片 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center">
<i class="fas fa-microchip mr-2 text-orange-600"></i>
系统资源
</h3>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">CPU核心数:</span>
<span class="font-medium">{{ performanceStats.system?.cpu_count || 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">Go版本:</span>
<span class="font-medium">{{ performanceStats.system?.go_version || 'N/A' }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">协程数:</span>
<span class="font-medium">{{ performanceStats.goroutines || 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">时间戳:</span>
<span class="font-medium">{{ formatTimestamp(performanceStats.timestamp) }}</span>
</div>
</div>
</div>
<!-- 基础统计卡片 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center">
<i class="fas fa-chart-bar mr-2 text-red-600"></i>
基础统计
</h3>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">资源总数:</span>
<span class="font-medium">{{ basicStats.total_resources || 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">分类总数:</span>
<span class="font-medium">{{ basicStats.total_categories || 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">标签总数:</span>
<span class="font-medium">{{ basicStats.total_tags || 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">总浏览量:</span>
<span class="font-medium">{{ basicStats.total_views || 0 }}</span>
</div>
</div>
</div>
<!-- 性能图表卡片 -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center">
<i class="fas fa-chart-line mr-2 text-indigo-600"></i>
性能趋势
</h3>
<div class="space-y-3">
<div class="text-center py-4">
<div class="text-2xl font-bold text-indigo-600">
{{ formatBytes(performanceStats.memory?.alloc) }}
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">当前内存使用</div>
</div>
<div class="w-full bg-gray-200 rounded-full h-2">
<div
class="bg-indigo-600 h-2 rounded-full transition-all duration-300"
:style="{ width: memoryUsagePercentage + '%' }"
></div>
</div>
<div class="text-center text-sm text-gray-500 dark:text-gray-400">
内存使用率: {{ memoryUsagePercentage.toFixed(1) }}%
</div>
</div>
</div>
</div>
</div>
<!-- 错误提示 -->
<div v-if="error" class="mt-6 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
<div class="flex items-center">
<i class="fas fa-exclamation-triangle mr-2"></i>
<span>{{ error }}</span>
<!-- 错误提示 -->
<div v-if="error" class="mt-6 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
<div class="flex items-center">
<i class="fas fa-exclamation-triangle mr-2"></i>
<span>{{ error }}</span>
</div>
</div>
</div>
</div>
<!-- 页脚 -->
<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>© 2025 网盘资源管理系统 By 老九</p>
</div>
</footer>
</div>
</template>