增加待看名单功能
This commit is contained in:
@@ -145,6 +145,15 @@ backend/
|
|||||||
- `POST /api/repository/migrate-old-to-new` - 从旧目录迁移到新目录
|
- `POST /api/repository/migrate-old-to-new` - 从旧目录迁移到新目录
|
||||||
- 参数: `oldDir` (旧目录路径), `newDir` (新目录路径)
|
- 参数: `oldDir` (旧目录路径), `newDir` (新目录路径)
|
||||||
|
|
||||||
|
### 待看名单相关
|
||||||
|
|
||||||
|
- `GET /api/watchlist` - 获取所有待看项目
|
||||||
|
- `POST /api/watchlist` - 添加待看项目
|
||||||
|
- 参数: `url` (必填), `title` (可选,不提供则自动生成)
|
||||||
|
- `PUT /api/watchlist/:id` - 更新待看项目
|
||||||
|
- 参数: `title` (项目标题)
|
||||||
|
- `DELETE /api/watchlist/:id` - 删除待看项目
|
||||||
|
|
||||||
## 🔧 配置说明
|
## 🔧 配置说明
|
||||||
|
|
||||||
### 代理配置
|
### 代理配置
|
||||||
@@ -178,6 +187,7 @@ backend/
|
|||||||
- **download.js**: 下载相关路由
|
- **download.js**: 下载相关路由
|
||||||
- **repository.js**: 仓库管理路由
|
- **repository.js**: 仓库管理路由
|
||||||
- **proxy.js**: 代理服务路由
|
- **proxy.js**: 代理服务路由
|
||||||
|
- **watchlist.js**: 待看名单路由
|
||||||
|
|
||||||
### 服务层
|
### 服务层
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,275 @@
|
|||||||
|
const fs = require('fs').promises
|
||||||
|
const path = require('path')
|
||||||
|
const { defaultLogger } = require('../utils/logger');
|
||||||
|
|
||||||
|
// 创建logger实例
|
||||||
|
const logger = defaultLogger.child('WatchlistManager');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 待看名单管理器
|
||||||
|
* 负责管理用户的待看名单数据
|
||||||
|
*/
|
||||||
|
class WatchlistManager {
|
||||||
|
constructor() {
|
||||||
|
// 检测是否在pkg打包环境中运行
|
||||||
|
const isPkg = process.pkg !== undefined;
|
||||||
|
|
||||||
|
if (isPkg) {
|
||||||
|
// 在打包环境中,使用可执行文件所在目录
|
||||||
|
this.configDir = path.join(process.cwd(), 'data', 'watchlist.json')
|
||||||
|
} else {
|
||||||
|
// 在开发环境中,使用相对路径
|
||||||
|
this.configDir = path.join(__dirname, 'watchlist.json')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保配置目录存在
|
||||||
|
this.ensureConfigDir()
|
||||||
|
|
||||||
|
this.defaultData = {
|
||||||
|
items: [],
|
||||||
|
lastUpdated: new Date().toISOString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确保配置目录存在
|
||||||
|
*/
|
||||||
|
ensureConfigDir() {
|
||||||
|
try {
|
||||||
|
const configDirPath = path.dirname(this.configDir)
|
||||||
|
if (!require('fs').existsSync(configDirPath)) {
|
||||||
|
require('fs').mkdirSync(configDirPath, { recursive: true })
|
||||||
|
logger.info('待看名单目录创建成功:', configDirPath)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('创建待看名单目录失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化待看名单文件
|
||||||
|
* 如果文件不存在,则创建默认数据
|
||||||
|
*/
|
||||||
|
async initialize() {
|
||||||
|
try {
|
||||||
|
// 检查文件是否存在
|
||||||
|
await fs.access(this.configDir)
|
||||||
|
logger.info('待看名单文件已存在')
|
||||||
|
} catch (error) {
|
||||||
|
// 文件不存在,创建默认文件
|
||||||
|
logger.info('创建默认待看名单文件...')
|
||||||
|
await this.createDefaultData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建默认数据文件
|
||||||
|
*/
|
||||||
|
async createDefaultData() {
|
||||||
|
try {
|
||||||
|
// 确保配置目录存在
|
||||||
|
const configDirPath = path.dirname(this.configDir)
|
||||||
|
await fs.mkdir(configDirPath, { recursive: true })
|
||||||
|
|
||||||
|
// 检查目录是否创建成功
|
||||||
|
try {
|
||||||
|
await fs.access(configDirPath)
|
||||||
|
logger.info('待看名单目录确认存在:', configDirPath)
|
||||||
|
} catch (accessError) {
|
||||||
|
logger.error('待看名单目录访问失败:', accessError)
|
||||||
|
throw new Error(`无法访问配置目录: ${configDirPath}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入默认数据
|
||||||
|
const dataContent = JSON.stringify(this.defaultData, null, 2)
|
||||||
|
await fs.writeFile(this.configDir, dataContent, 'utf8')
|
||||||
|
|
||||||
|
// 验证文件是否写入成功
|
||||||
|
try {
|
||||||
|
await fs.access(this.configDir)
|
||||||
|
logger.info('默认待看名单文件创建成功:', this.configDir)
|
||||||
|
} catch (verifyError) {
|
||||||
|
logger.error('待看名单文件验证失败:', verifyError)
|
||||||
|
throw new Error('待看名单文件创建后无法访问')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('创建默认待看名单文件失败:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取待看名单数据
|
||||||
|
*/
|
||||||
|
async readData() {
|
||||||
|
try {
|
||||||
|
// 首先检查文件是否存在
|
||||||
|
const exists = await this.dataExists()
|
||||||
|
if (!exists) {
|
||||||
|
logger.info('待看名单文件不存在,创建默认数据...')
|
||||||
|
await this.createDefaultData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataContent = await fs.readFile(this.configDir, 'utf8')
|
||||||
|
const data = JSON.parse(dataContent)
|
||||||
|
|
||||||
|
// 合并默认数据,确保所有必要的字段都存在
|
||||||
|
return { ...this.defaultData, ...data }
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('读取待看名单文件失败:', error)
|
||||||
|
logger.info('使用默认数据...')
|
||||||
|
// 如果读取失败,尝试创建默认数据
|
||||||
|
try {
|
||||||
|
await this.createDefaultData()
|
||||||
|
return { ...this.defaultData }
|
||||||
|
} catch (createError) {
|
||||||
|
logger.error('创建默认数据也失败:', createError)
|
||||||
|
// 最后返回内存中的默认数据
|
||||||
|
return { ...this.defaultData }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存待看名单数据
|
||||||
|
*/
|
||||||
|
async saveData(data) {
|
||||||
|
try {
|
||||||
|
// 添加更新时间
|
||||||
|
const dataToSave = {
|
||||||
|
...data,
|
||||||
|
lastUpdated: new Date().toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.writeFile(
|
||||||
|
this.configDir,
|
||||||
|
JSON.stringify(dataToSave, null, 2),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info('待看名单文件保存成功')
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('保存待看名单文件失败:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加待看项目
|
||||||
|
*/
|
||||||
|
async addItem(item) {
|
||||||
|
try {
|
||||||
|
const data = await this.readData()
|
||||||
|
|
||||||
|
// 检查是否已存在相同的URL
|
||||||
|
const existingIndex = data.items.findIndex(existingItem => existingItem.url === item.url)
|
||||||
|
|
||||||
|
if (existingIndex !== -1) {
|
||||||
|
// 如果存在,更新现有项目
|
||||||
|
data.items[existingIndex] = {
|
||||||
|
...data.items[existingIndex],
|
||||||
|
...item,
|
||||||
|
updatedAt: new Date().toISOString()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 添加新项目
|
||||||
|
const newItem = {
|
||||||
|
id: Date.now().toString(),
|
||||||
|
...item,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString()
|
||||||
|
}
|
||||||
|
data.items.push(newItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.saveData(data)
|
||||||
|
return data.items
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('添加待看项目失败:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除待看项目
|
||||||
|
*/
|
||||||
|
async removeItem(id) {
|
||||||
|
try {
|
||||||
|
const data = await this.readData()
|
||||||
|
const initialLength = data.items.length
|
||||||
|
|
||||||
|
data.items = data.items.filter(item => item.id !== id)
|
||||||
|
|
||||||
|
if (data.items.length === initialLength) {
|
||||||
|
throw new Error('待看项目不存在')
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.saveData(data)
|
||||||
|
return data.items
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('删除待看项目失败:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新待看项目
|
||||||
|
*/
|
||||||
|
async updateItem(id, updates) {
|
||||||
|
try {
|
||||||
|
const data = await this.readData()
|
||||||
|
const itemIndex = data.items.findIndex(item => item.id === id)
|
||||||
|
|
||||||
|
if (itemIndex === -1) {
|
||||||
|
throw new Error('待看项目不存在')
|
||||||
|
}
|
||||||
|
|
||||||
|
data.items[itemIndex] = {
|
||||||
|
...data.items[itemIndex],
|
||||||
|
...updates,
|
||||||
|
updatedAt: new Date().toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.saveData(data)
|
||||||
|
return data.items[itemIndex]
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('更新待看项目失败:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有待看项目
|
||||||
|
*/
|
||||||
|
async getAllItems() {
|
||||||
|
try {
|
||||||
|
const data = await this.readData()
|
||||||
|
return data.items
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('获取待看项目失败:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取配置文件路径
|
||||||
|
*/
|
||||||
|
getConfigPath() {
|
||||||
|
return this.configDir
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查配置文件是否存在
|
||||||
|
*/
|
||||||
|
async dataExists() {
|
||||||
|
try {
|
||||||
|
await fs.access(this.configDir)
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = WatchlistManager
|
||||||
@@ -0,0 +1,348 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "1757123368005",
|
||||||
|
"url": "http://localhost:3001/artist/72143697",
|
||||||
|
"title": "作者 72143697",
|
||||||
|
"createdAt": "2025-09-06T01:49:28.005Z",
|
||||||
|
"updatedAt": "2025-09-06T01:49:28.005Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757123801822",
|
||||||
|
"url": "http://localhost:3001/artist/103047332",
|
||||||
|
"title": "作者管理",
|
||||||
|
"createdAt": "2025-09-06T01:56:41.822Z",
|
||||||
|
"updatedAt": "2025-09-06T01:56:41.822Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092574",
|
||||||
|
"url": "http://localhost:3001/artist/113088709",
|
||||||
|
"title": "作者 113088709",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.574Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.574Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092579",
|
||||||
|
"url": "http://localhost:3001/artist/116491647",
|
||||||
|
"title": "作者 116491647",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.579Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.579Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092583",
|
||||||
|
"url": "http://localhost:3001/artist/20002274",
|
||||||
|
"title": "作者 20002274",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.583Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.583Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092586",
|
||||||
|
"url": "http://localhost:3001/artist/102068964",
|
||||||
|
"title": "作者 102068964",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.586Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.586Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092589",
|
||||||
|
"url": "http://localhost:3001/artist/57881733",
|
||||||
|
"title": "作者 57881733",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.589Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.589Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092595",
|
||||||
|
"url": "http://localhost:3001/artist/106498479",
|
||||||
|
"title": "作者 106498479",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.595Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.595Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092598",
|
||||||
|
"url": "http://localhost:3001/artist/87347255?page=21",
|
||||||
|
"title": "作者 87347255 - 第21页",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.598Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.598Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092601",
|
||||||
|
"url": "http://localhost:3001/artist/112391670",
|
||||||
|
"title": "作者 112391670",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.601Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.601Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092605",
|
||||||
|
"url": "http://localhost:3001/artist/97349140",
|
||||||
|
"title": "作者 97349140",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.605Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.605Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092608",
|
||||||
|
"url": "http://localhost:3001/artist/32222272",
|
||||||
|
"title": "作者 32222272",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.608Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.608Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092612",
|
||||||
|
"url": "http://localhost:3001/artist/16315304",
|
||||||
|
"title": "作者 16315304",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.612Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.612Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092615",
|
||||||
|
"url": "http://localhost:3001/artist/20420220",
|
||||||
|
"title": "作者 20420220",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.615Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.615Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092619",
|
||||||
|
"url": "http://localhost:3001/artist/95485582",
|
||||||
|
"title": "作者 95485582",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.619Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.619Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092622",
|
||||||
|
"url": "http://localhost:3001/artwork/99046180",
|
||||||
|
"title": "作品 99046180",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.622Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.622Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092625",
|
||||||
|
"url": "http://localhost:3001/artist/92969522",
|
||||||
|
"title": "作者 92969522",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.625Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.625Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092629",
|
||||||
|
"url": "http://localhost:3001/artist/35790899",
|
||||||
|
"title": "作者 35790899",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.629Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.629Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092632",
|
||||||
|
"url": "http://localhost:3001/artist/115689478?page=11",
|
||||||
|
"title": "作者 115689478 - 第11页",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.632Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.632Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092635",
|
||||||
|
"url": "http://localhost:3001/artist/54082094",
|
||||||
|
"title": "作者 54082094",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.635Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.635Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092639",
|
||||||
|
"url": "http://localhost:3001/artist/113605557",
|
||||||
|
"title": "作者 113605557",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.639Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.639Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092642",
|
||||||
|
"url": "http://localhost:3001/artist/12288015",
|
||||||
|
"title": "作者 12288015",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.642Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.642Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092646",
|
||||||
|
"url": "http://localhost:3001/artist/107444384",
|
||||||
|
"title": "作者 107444384",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.646Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.646Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092649",
|
||||||
|
"url": "http://localhost:3001/artist/98929998",
|
||||||
|
"title": "作者 98929998",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.649Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.649Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092653",
|
||||||
|
"url": "http://localhost:3001/artist/35227306",
|
||||||
|
"title": "作者 35227306",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.653Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.653Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092656",
|
||||||
|
"url": "http://localhost:3001/artist/55914620",
|
||||||
|
"title": "作者 55914620",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.656Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.656Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092662",
|
||||||
|
"url": "http://localhost:3001/artist/112826987",
|
||||||
|
"title": "作者 112826987",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.662Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.662Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092665",
|
||||||
|
"url": "http://localhost:3001/artist/117505514",
|
||||||
|
"title": "作者 117505514",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.665Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.665Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092669",
|
||||||
|
"url": "http://localhost:3001/artist/116034471?page=11",
|
||||||
|
"title": "作者 116034471 - 第11页",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.669Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.669Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092672",
|
||||||
|
"url": "http://localhost:3001/artist/116826562",
|
||||||
|
"title": "作者 116826562",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.672Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.672Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092675",
|
||||||
|
"url": "http://localhost:3001/artist/119419938",
|
||||||
|
"title": "作者 119419938",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.675Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.675Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092679",
|
||||||
|
"url": "http://localhost:3001/artist/105818043",
|
||||||
|
"title": "作者 105818043",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.679Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.679Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092683",
|
||||||
|
"url": "http://localhost:3001/artist/3328617",
|
||||||
|
"title": "作者 3328617",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.683Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.683Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092686",
|
||||||
|
"url": "http://localhost:3001/artist/114508563",
|
||||||
|
"title": "作者 114508563",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.686Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.686Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092689",
|
||||||
|
"url": "http://localhost:3001/artist/48447420",
|
||||||
|
"title": "作者 48447420",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.689Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.689Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092696",
|
||||||
|
"url": "http://localhost:3001/artist/18898196",
|
||||||
|
"title": "作者 18898196",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.696Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.696Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092699",
|
||||||
|
"url": "http://localhost:3001/artist/24503943",
|
||||||
|
"title": "作者 24503943",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.699Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.699Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092703",
|
||||||
|
"url": "http://localhost:3001/artist/83735825",
|
||||||
|
"title": "作者 83735825",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.703Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.703Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092706",
|
||||||
|
"url": "http://localhost:3001/artist/109989392",
|
||||||
|
"title": "作者 109989392",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.706Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.706Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092712",
|
||||||
|
"url": "http://localhost:3001/artist/119068816",
|
||||||
|
"title": "作者 119068816",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.712Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.712Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092718",
|
||||||
|
"url": "http://localhost:3001/artist/8934406",
|
||||||
|
"title": "作者 8934406",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.718Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.718Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092725",
|
||||||
|
"url": "http://localhost:3001/artist/118710458",
|
||||||
|
"title": "作者 118710458",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.725Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.725Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092733",
|
||||||
|
"url": "http://localhost:3001/artist/111705416",
|
||||||
|
"title": "作者 111705416",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.733Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.733Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092737",
|
||||||
|
"url": "http://localhost:3001/artist/114537113",
|
||||||
|
"title": "作者 114537113",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.737Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.737Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092741",
|
||||||
|
"url": "http://localhost:3001/artist/117665623",
|
||||||
|
"title": "作者 117665623",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.741Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.741Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092745",
|
||||||
|
"url": "http://localhost:3001/artist/17745716",
|
||||||
|
"title": "作者 17745716",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.745Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.745Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092749",
|
||||||
|
"url": "http://localhost:3001/artist/113801960",
|
||||||
|
"title": "作者 113801960",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.749Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.749Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092752",
|
||||||
|
"url": "http://localhost:3001/artist/116034471",
|
||||||
|
"title": "作者 116034471",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.752Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.752Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1757124092756",
|
||||||
|
"url": "http://localhost:3001/artist/117814316",
|
||||||
|
"title": "作者 117814316",
|
||||||
|
"createdAt": "2025-09-06T02:01:32.756Z",
|
||||||
|
"updatedAt": "2025-09-06T02:01:32.756Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastUpdated": "2025-09-06T02:01:32.756Z"
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const WatchlistManager = require('../config/watchlist-manager');
|
||||||
|
const ResponseUtil = require('../utils/response');
|
||||||
|
const { defaultLogger } = require('../utils/logger');
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
const logger = defaultLogger.child('WatchlistRouter');
|
||||||
|
const watchlistManager = new WatchlistManager();
|
||||||
|
|
||||||
|
// 初始化待看名单
|
||||||
|
watchlistManager.initialize().catch(error => {
|
||||||
|
logger.error('待看名单初始化失败:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有待看项目
|
||||||
|
* GET /api/watchlist
|
||||||
|
*/
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const items = await watchlistManager.getAllItems();
|
||||||
|
res.json(ResponseUtil.success(items));
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('获取待看名单失败:', error);
|
||||||
|
res.status(500).json(ResponseUtil.error(error.message));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加待看项目
|
||||||
|
* POST /api/watchlist
|
||||||
|
*/
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { url, title } = req.body;
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
return res.status(400).json(ResponseUtil.error('URL是必填项'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有提供标题,尝试从URL生成默认标题
|
||||||
|
let defaultTitle = title;
|
||||||
|
if (!defaultTitle) {
|
||||||
|
try {
|
||||||
|
// 从URL路径生成默认标题
|
||||||
|
const urlObj = new URL(url);
|
||||||
|
const pathSegments = urlObj.pathname.split('/').filter(segment => segment);
|
||||||
|
|
||||||
|
if (pathSegments.length >= 1) {
|
||||||
|
const type = pathSegments[0]; // 例如 "artist" 或 "artwork"
|
||||||
|
|
||||||
|
if (pathSegments.length >= 2) {
|
||||||
|
const id = pathSegments[1];
|
||||||
|
|
||||||
|
if (type === 'artist') {
|
||||||
|
defaultTitle = `作者 ${id}`;
|
||||||
|
} else if (type === 'artwork') {
|
||||||
|
defaultTitle = `作品 ${id}`;
|
||||||
|
} else if (type === 'search') {
|
||||||
|
// 处理搜索页面
|
||||||
|
const keywordMatch = urlObj.search.match(/keyword=([^&]+)/);
|
||||||
|
if (keywordMatch) {
|
||||||
|
defaultTitle = `搜索: ${decodeURIComponent(keywordMatch[1])}`;
|
||||||
|
} else {
|
||||||
|
defaultTitle = '搜索页面';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
defaultTitle = `${type} ${id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有页面参数,加上页面信息
|
||||||
|
if (urlObj.search.includes('page=')) {
|
||||||
|
const pageMatch = urlObj.search.match(/page=(\d+)/);
|
||||||
|
if (pageMatch) {
|
||||||
|
defaultTitle += ` - 第${pageMatch[1]}页`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 只有一级路径的情况
|
||||||
|
switch (type) {
|
||||||
|
case 'search':
|
||||||
|
const keywordMatch = urlObj.search.match(/keyword=([^&]+)/);
|
||||||
|
if (keywordMatch) {
|
||||||
|
defaultTitle = `搜索: ${decodeURIComponent(keywordMatch[1])}`;
|
||||||
|
} else {
|
||||||
|
defaultTitle = '搜索页面';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'ranking':
|
||||||
|
const modeMatch = urlObj.search.match(/mode=([^&]+)/);
|
||||||
|
if (modeMatch) {
|
||||||
|
const modeMap = { day: '日榜', week: '周榜', month: '月榜' };
|
||||||
|
defaultTitle = `排行榜 - ${modeMap[modeMatch[1]] || modeMatch[1]}`;
|
||||||
|
} else {
|
||||||
|
defaultTitle = '排行榜';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'bookmarks':
|
||||||
|
defaultTitle = '我的收藏';
|
||||||
|
break;
|
||||||
|
case 'artists':
|
||||||
|
defaultTitle = '作者管理';
|
||||||
|
break;
|
||||||
|
case 'downloads':
|
||||||
|
defaultTitle = '下载管理';
|
||||||
|
break;
|
||||||
|
case 'repository':
|
||||||
|
defaultTitle = '仓库管理';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
defaultTitle = `${type}页面`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
defaultTitle = '首页';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 如果URL解析失败,使用简单的默认标题
|
||||||
|
defaultTitle = '待看页面';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = {
|
||||||
|
url,
|
||||||
|
title: defaultTitle
|
||||||
|
};
|
||||||
|
|
||||||
|
const items = await watchlistManager.addItem(item);
|
||||||
|
res.json(ResponseUtil.success(items));
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('添加待看项目失败:', error);
|
||||||
|
res.status(500).json(ResponseUtil.error(error.message));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新待看项目
|
||||||
|
* PUT /api/watchlist/:id
|
||||||
|
*/
|
||||||
|
router.put('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const updates = req.body;
|
||||||
|
|
||||||
|
// 移除不应该被更新的字段
|
||||||
|
delete updates.id;
|
||||||
|
delete updates.createdAt;
|
||||||
|
|
||||||
|
const updatedItem = await watchlistManager.updateItem(id, updates);
|
||||||
|
res.json(ResponseUtil.success(updatedItem));
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('更新待看项目失败:', error);
|
||||||
|
if (error.message === '待看项目不存在') {
|
||||||
|
res.status(404).json(ResponseUtil.error(error.message));
|
||||||
|
} else {
|
||||||
|
res.status(500).json(ResponseUtil.error(error.message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除待看项目
|
||||||
|
* DELETE /api/watchlist/:id
|
||||||
|
*/
|
||||||
|
router.delete('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const items = await watchlistManager.removeItem(id);
|
||||||
|
res.json(ResponseUtil.success(items));
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('删除待看项目失败:', error);
|
||||||
|
if (error.message === '待看项目不存在') {
|
||||||
|
res.status(404).json(ResponseUtil.error(error.message));
|
||||||
|
} else {
|
||||||
|
res.status(500).json(ResponseUtil.error(error.message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
+2
-3
@@ -14,6 +14,7 @@ const downloadRoutes = require('./routes/download');
|
|||||||
const proxyRoutes = require('./routes/proxy');
|
const proxyRoutes = require('./routes/proxy');
|
||||||
const repositoryRoutes = require('./routes/repository');
|
const repositoryRoutes = require('./routes/repository');
|
||||||
const rankingRoutes = require('./routes/ranking');
|
const rankingRoutes = require('./routes/ranking');
|
||||||
|
const watchlistRoutes = require('./routes/watchlist');
|
||||||
|
|
||||||
// 导入中间件 - 临时注释掉来定位问题
|
// 导入中间件 - 临时注释掉来定位问题
|
||||||
const { errorHandler } = require('./middleware/errorHandler');
|
const { errorHandler } = require('./middleware/errorHandler');
|
||||||
@@ -123,9 +124,6 @@ function customLogger(req, res, next) {
|
|||||||
second: '2-digit',
|
second: '2-digit',
|
||||||
});
|
});
|
||||||
|
|
||||||
// 构建日志消息
|
|
||||||
const logMessage = `${statusColor}${statusIcon} ${methodIcon} ${method} ${url} ${statusCode} ${duration}ms\x1b[0m`;
|
|
||||||
|
|
||||||
// 输出日志
|
// 输出日志
|
||||||
logger.info(`${statusIcon} ${methodIcon} ${method} ${url} ${statusCode} ${duration}ms`);
|
logger.info(`${statusIcon} ${methodIcon} ${method} ${url} ${statusCode} ${duration}ms`);
|
||||||
|
|
||||||
@@ -228,6 +226,7 @@ class PixivServer {
|
|||||||
this.app.use('/api/ranking', authMiddleware, rankingRoutes);
|
this.app.use('/api/ranking', authMiddleware, rankingRoutes);
|
||||||
this.app.use('/api/repository', repositoryRoutes); // 仓库管理,不需要认证
|
this.app.use('/api/repository', repositoryRoutes); // 仓库管理,不需要认证
|
||||||
this.app.use('/api/proxy', proxyRoutes); // 图片代理,不需要认证
|
this.app.use('/api/proxy', proxyRoutes); // 图片代理,不需要认证
|
||||||
|
this.app.use('/api/watchlist', authMiddleware, watchlistRoutes); // 待看名单,需要认证
|
||||||
|
|
||||||
// 404 处理
|
// 404 处理
|
||||||
this.app.use((req, res) => {
|
this.app.use((req, res) => {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useAuthStore } from '@/stores/auth'
|
|||||||
import { useDownloadStore } from '@/stores/download'
|
import { useDownloadStore } from '@/stores/download'
|
||||||
import SettingsWidget from '@/components/common/SettingsWidget.vue'
|
import SettingsWidget from '@/components/common/SettingsWidget.vue'
|
||||||
import DownloadProgressWidget from '@/components/common/DownloadProgressWidget.vue'
|
import DownloadProgressWidget from '@/components/common/DownloadProgressWidget.vue'
|
||||||
|
import WatchlistWidget from '@/components/common/WatchlistWidget.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
@@ -92,6 +93,9 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<!-- 下载进度小组件 - 只在登录时显示,在下载管理页面隐藏 -->
|
<!-- 下载进度小组件 - 只在登录时显示,在下载管理页面隐藏 -->
|
||||||
<DownloadProgressWidget v-if="showDownloadWidget" />
|
<DownloadProgressWidget v-if="showDownloadWidget" />
|
||||||
|
|
||||||
|
<!-- 待看名单小组件 - 只在登录时显示 -->
|
||||||
|
<WatchlistWidget v-if="isLoggedIn" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,55 @@
|
|||||||
|
import { apiService } from './api';
|
||||||
|
import type { ApiResponse } from '@/types';
|
||||||
|
|
||||||
|
// 待看名单项目接口
|
||||||
|
export interface WatchlistItem {
|
||||||
|
id: string;
|
||||||
|
url: string;
|
||||||
|
title: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加项目的参数接口
|
||||||
|
export interface AddWatchlistItemParams {
|
||||||
|
url: string;
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新项目的参数接口
|
||||||
|
export interface UpdateWatchlistItemParams {
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WatchlistService {
|
||||||
|
/**
|
||||||
|
* 获取所有待看项目
|
||||||
|
*/
|
||||||
|
async getItems(): Promise<ApiResponse<WatchlistItem[]>> {
|
||||||
|
return apiService.get<WatchlistItem[]>('/api/watchlist');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加待看项目
|
||||||
|
*/
|
||||||
|
async addItem(params: AddWatchlistItemParams): Promise<ApiResponse<WatchlistItem[]>> {
|
||||||
|
return apiService.post<WatchlistItem[]>('/api/watchlist', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新待看项目
|
||||||
|
*/
|
||||||
|
async updateItem(id: string, params: UpdateWatchlistItemParams): Promise<ApiResponse<WatchlistItem>> {
|
||||||
|
return apiService.put<WatchlistItem>(`/api/watchlist/${id}`, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除待看项目
|
||||||
|
*/
|
||||||
|
async deleteItem(id: string): Promise<ApiResponse<WatchlistItem[]>> {
|
||||||
|
return apiService.delete<WatchlistItem[]>(`/api/watchlist/${id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const watchlistService = new WatchlistService();
|
||||||
|
export default watchlistService;
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { watchlistService, type WatchlistItem, type AddWatchlistItemParams, type UpdateWatchlistItemParams } from '@/services/watchlist';
|
||||||
|
|
||||||
|
export const useWatchlistStore = defineStore('watchlist', () => {
|
||||||
|
// 状态
|
||||||
|
const items = ref<WatchlistItem[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const error = ref<string | null>(null);
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const itemCount = computed(() => items.value.length);
|
||||||
|
const hasItems = computed(() => items.value.length > 0);
|
||||||
|
|
||||||
|
// 通用错误处理
|
||||||
|
const handleError = (err: any, defaultMessage: string) => {
|
||||||
|
console.error(defaultMessage, err);
|
||||||
|
error.value = err.response?.data?.error || err.message || defaultMessage;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取所有待看项目
|
||||||
|
const fetchItems = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
|
const response = await watchlistService.getItems();
|
||||||
|
if (response.success && response.data) {
|
||||||
|
items.value = response.data;
|
||||||
|
} else {
|
||||||
|
throw new Error(response.error || '获取待看名单失败');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
handleError(err, '获取待看名单失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加待看项目
|
||||||
|
const addItem = async (params: AddWatchlistItemParams) => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
|
const response = await watchlistService.addItem(params);
|
||||||
|
if (response.success && response.data) {
|
||||||
|
items.value = response.data;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(response.error || '添加待看项目失败');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
handleError(err, '添加待看项目失败');
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新待看项目
|
||||||
|
const updateItem = async (id: string, params: UpdateWatchlistItemParams) => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
|
const response = await watchlistService.updateItem(id, params);
|
||||||
|
if (response.success && response.data) {
|
||||||
|
// 更新本地数据
|
||||||
|
const index = items.value.findIndex(item => item.id === id);
|
||||||
|
if (index !== -1) {
|
||||||
|
items.value[index] = response.data;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(response.error || '更新待看项目失败');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
handleError(err, '更新待看项目失败');
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除待看项目
|
||||||
|
const deleteItem = async (id: string) => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
|
const response = await watchlistService.deleteItem(id);
|
||||||
|
if (response.success && response.data) {
|
||||||
|
items.value = response.data;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(response.error || '删除待看项目失败');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
handleError(err, '删除待看项目失败');
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查URL是否已存在
|
||||||
|
const hasUrl = (url: string) => {
|
||||||
|
return items.value.some(item => item.url === url);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据URL查找项目
|
||||||
|
const findByUrl = (url: string) => {
|
||||||
|
return items.value.find(item => item.url === url);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 清除错误
|
||||||
|
const clearError = () => {
|
||||||
|
error.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 状态
|
||||||
|
items,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
// 计算属性
|
||||||
|
itemCount,
|
||||||
|
hasItems,
|
||||||
|
// 方法
|
||||||
|
fetchItems,
|
||||||
|
addItem,
|
||||||
|
updateItem,
|
||||||
|
deleteItem,
|
||||||
|
hasUrl,
|
||||||
|
findByUrl,
|
||||||
|
clearError
|
||||||
|
};
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user