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
|
||||
}
|
||||
|
||||
@@ -134,6 +134,10 @@ func ToCksResponse(cks *entity.Cks) dto.CksResponse {
|
||||
IsValid: cks.IsValid,
|
||||
Space: cks.Space,
|
||||
LeftSpace: cks.LeftSpace,
|
||||
UsedSpace: cks.UsedSpace,
|
||||
Username: cks.Username,
|
||||
VipStatus: cks.VipStatus,
|
||||
ServiceType: cks.ServiceType,
|
||||
Remark: cks.Remark,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@ type CreateCksRequest struct {
|
||||
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"`
|
||||
}
|
||||
|
||||
@@ -35,6 +39,10 @@ type UpdateCksRequest struct {
|
||||
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"`
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,10 @@ type CksResponse struct {
|
||||
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"`
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,12 @@ type Cks struct {
|
||||
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:剩余空间"`
|
||||
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"`
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 获取平台信息以确定服务类型
|
||||
pan, err := repoManager.PanRepository.FindByID(req.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(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: req.IsValid,
|
||||
Space: req.Space,
|
||||
LeftSpace: req.LeftSpace,
|
||||
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)
|
||||
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,29 +1,35 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100">
|
||||
<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 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">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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="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">
|
||||
@@ -338,11 +344,13 @@ curl -X GET "http://localhost:8080/api/public/hot-dramas?page=1&page_size=5" \
|
||||
</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 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 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="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 class="max-w-7xl mx-auto">
|
||||
<!-- 头部 -->
|
||||
<div class="bg-slate-800 dark:bg-gray-800 text-white dark:text-gray-100 rounded-lg shadow-lg p-4 sm:p-8 mb-4 sm:mb-8 text-center flex items-center">
|
||||
<nav class="mt-4 flex flex-col sm:flex-row justify-center gap-2 sm:gap-4">
|
||||
<NuxtLink
|
||||
to="/admin"
|
||||
class="w-full sm:w-auto px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md transition-colors text-center flex items-center justify-center gap-2"
|
||||
>
|
||||
<i class="fas fa-arrow-left"></i> 返回
|
||||
</NuxtLink>
|
||||
</nav>
|
||||
<div class="flex-1">
|
||||
<h1 class="text-2xl sm:text-3xl font-bold">
|
||||
<NuxtLink to="/admin" class="text-white hover:text-gray-200 dark:hover:text-gray-300 no-underline">平台账号管理</NuxtLink>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
@click="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 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,6 +182,61 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div v-if="totalPages > 1" class="flex flex-wrap justify-center gap-1 sm:gap-2 mt-6">
|
||||
<button
|
||||
v-if="currentPage > 1"
|
||||
@click="goToPage(currentPage - 1)"
|
||||
class="bg-white text-gray-700 hover:bg-gray-50 px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm flex items-center"
|
||||
>
|
||||
<i class="fas fa-chevron-left mr-1"></i> 上一页
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="goToPage(1)"
|
||||
:class="currentPage === 1 ? 'bg-slate-800 text-white' : 'bg-white text-gray-700 hover:bg-gray-50'"
|
||||
class="px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm"
|
||||
>
|
||||
1
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="totalPages > 1"
|
||||
@click="goToPage(2)"
|
||||
:class="currentPage === 2 ? 'bg-slate-800 text-white' : 'bg-white text-gray-700 hover:bg-gray-50'"
|
||||
class="px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm"
|
||||
>
|
||||
2
|
||||
</button>
|
||||
|
||||
<span v-if="currentPage > 2" class="px-2 py-1 sm:px-3 sm:py-2 text-gray-500 text-sm">...</span>
|
||||
|
||||
<button
|
||||
v-if="currentPage !== 1 && currentPage !== 2 && currentPage > 2"
|
||||
class="bg-slate-800 text-white px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm"
|
||||
>
|
||||
{{ currentPage }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="currentPage < totalPages"
|
||||
@click="goToPage(currentPage + 1)"
|
||||
class="bg-white text-gray-700 hover:bg-gray-50 px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm flex items-center"
|
||||
>
|
||||
下一页 <i class="fas fa-chevron-right ml-1"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 统计信息 -->
|
||||
<div v-if="totalPages <= 1" class="mt-4 text-center">
|
||||
<div class="inline-flex items-center bg-white dark:bg-gray-800 rounded-lg shadow px-6 py-3">
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400">
|
||||
共 <span class="font-semibold text-gray-900 dark:text-gray-100">{{ 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">
|
||||
@@ -84,10 +247,12 @@
|
||||
<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>
|
||||
<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>
|
||||
@@ -95,6 +260,13 @@
|
||||
{{ 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>
|
||||
@@ -103,38 +275,9 @@
|
||||
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内容"
|
||||
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
|
||||
@@ -144,7 +287,7 @@
|
||||
placeholder="可选,备注信息"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="showEditModal">
|
||||
<label class="flex items-center">
|
||||
<input
|
||||
v-model="form.is_valid"
|
||||
@@ -165,9 +308,10 @@
|
||||
</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"
|
||||
: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"
|
||||
>
|
||||
{{ showEditModal ? '更新' : '创建' }}
|
||||
{{ submitting ? '处理中...' : (showEditModal ? '更新' : '创建') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -175,7 +319,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -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,10 +1,33 @@
|
||||
<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 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>
|
||||
|
||||
<!-- 统计信息 -->
|
||||
@@ -173,6 +196,15 @@
|
||||
</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>
|
||||
|
||||
<script setup>
|
||||
|
||||
@@ -1,10 +1,33 @@
|
||||
<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 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>
|
||||
|
||||
<!-- 刷新按钮 -->
|
||||
@@ -209,6 +232,15 @@
|
||||
</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>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
Reference in New Issue
Block a user