mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 03:15:04 +08:00
update: cks
This commit is contained in:
@@ -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. **扩展性好**:为未来功能扩展提供基础
|
||||
|
||||
虽然工厂实例本身开销不大,但在高频调用的场景下(如定时任务),单例模式能带来明显的性能提升和资源优化。
|
||||
@@ -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{}{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
52
common/quark_user_info_test.go
Normal file
52
common/quark_user_info_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 创建资源请求
|
||||
|
||||
@@ -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 待处理资源响应
|
||||
|
||||
@@ -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"`
|
||||
|
||||
29
db/migration_add_used_space.sql
Normal file
29
db/migration_add_used_space.sql
Normal 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);
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
|
||||
1
main.go
1
main.go
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>© 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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user