# 📦 功能 33-37 完整代码实现 --- ## 功能 33:批量删除功能 ### 后端实现 #### 1. 批量删除历史记录 Controller **文件**: `server/src/modules/history/history-batch.controller.ts` ```typescript import Router from '@koa/router'; import { Context } from 'koa'; import { optionalAuth } from '../../middleware/auth'; import { prisma } from '../../models'; const TEST_USER_ID = '1'; const router = new Router(); // 批量删除历史记录 router.delete('/batch', optionalAuth, async (ctx: Context) => { const userId = ctx.state.user?.userId || TEST_USER_ID; const body = ctx.request.body as { ids: string[] }; if (!body.ids || !Array.isArray(body.ids)) { ctx.status = 400; ctx.body = { code: 400, message: 'ids 参数必须是数组' }; return; } try { const result = await prisma.audioRecord.deleteMany({ where: { audioId: { in: body.ids } }, }); ctx.body = { code: 0, message: 'success', data: { deletedCount: result.count }, }; } catch (error) { console.error('批量删除失败:', error); ctx.status = 500; ctx.body = { code: 500, message: '删除失败' }; } }); export default router; ``` #### 2. 注册路由 - 添加到 `server/src/app.ts` ```typescript import historyBatchRoutes from './modules/history/history-batch.controller'; // 在 router.use 部分添加: router.use('/api/history', historyBatchRoutes.routes()); ``` ### 前端实现 #### 3. 更新历史记录页面 **文件**: `my-uniapp-vue3/src/pages/history/index.vue` ```vue ``` --- ## 功能 34:内容管理(重命名、移动) ### 后端实现 #### 1. 专辑管理 Controller **文件**: `server/src/modules/book-generator/album-management.controller.ts` ```typescript import Router from '@koa/router'; import { Context } from 'koa'; import { optionalAuth } from '../../middleware/auth'; import { prisma } from '../../models'; const router = new Router(); // 重命名专辑 router.put('/:id/rename', optionalAuth, async (ctx: Context) => { const { id } = ctx.params; const { title } = ctx.request.body as { title: string }; const book = await prisma.book.update({ where: { id: parseInt(id) }, data: { title }, }); ctx.body = { code: 0, message: 'success', data: book }; }); // 重命名章节 router.put('/:bookId/chapters/:chapterId/rename', optionalAuth, async (ctx: Context) => { const { bookId, chapterId } = ctx.params; const { title } = ctx.request.body as { title: string }; const chapter = await prisma.bookChapter.update({ where: { id: parseInt(chapterId) }, data: { title }, }); ctx.body = { code: 0, message: 'success', data: chapter }; }); // 移动章节到新位置 router.put('/:bookId/chapters/:chapterId/move', optionalAuth, async (ctx: Context) => { const { bookId, chapterId } = ctx.params; const { newNumber } = ctx.request.body as { newNumber: number }; const chapter = await prisma.bookChapter.findUnique({ where: { id: parseInt(chapterId) }, }); if (!chapter) { ctx.status = 404; ctx.body = { code: 404, message: '章节不存在' }; return; } // 调整其他章节的序号 if (newNumber > chapter.number) { await prisma.bookChapter.updateMany({ where: { bookId: parseInt(bookId), number: { gt: chapter.number, lte: newNumber }, }, data: { number: { decrement: 1 } }, }); } else if (newNumber < chapter.number) { await prisma.bookChapter.updateMany({ where: { bookId: parseInt(bookId), number: { gte: newNumber, lt: chapter.number }, }, data: { number: { increment: 1 } }, }); } const updatedChapter = await prisma.bookChapter.update({ where: { id: parseInt(chapterId) }, data: { number: newNumber }, }); ctx.body = { code: 0, message: 'success', data: updatedChapter }; }); export default router; ``` #### 2. 注册路由 - 添加到 `server/src/app.ts` ```typescript import albumManagementRoutes from './modules/book-generator/album-management.controller'; router.use('/api/book-generator/album', albumManagementRoutes.routes()); ``` --- ## 功能 35:播放列表功能 ### 数据库 Schema **文件**: `server/prisma/schema.prisma` - 添加以下模型: ```prisma model Playlist { id Int @id @default(autoincrement()) userId Int name String coverUrl String? description String? isPublic Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id]) items PlaylistItem[] @@index([userId]) } model PlaylistItem { id Int @id @default(autoincrement()) playlistId Int chapterId Int? audioId String? order Int @default(0) addedAt DateTime @default(now()) playlist Playlist @relation(fields: [playlistId], references: [id], onDelete: Cascade) chapter BookChapter? @relation(fields: [chapterId], references: [id]) @@index([playlistId]) @@index([order]) } ``` ### 后端 Playlist Controller **文件**: `server/src/modules/player/playlist.controller.ts` ```typescript import Router from '@koa/router'; import { Context } from 'koa'; import { optionalAuth } from '../../middleware/auth'; import { prisma } from '../../models'; const TEST_USER_ID = '1'; const router = new Router(); // 获取播放列表 router.get('/', optionalAuth, async (ctx: Context) => { const userId = ctx.state.user?.userId || TEST_USER_ID; const playlists = await prisma.playlist.findMany({ where: { userId: parseInt(userId) }, include: { _count: { select: { items: true } } }, orderBy: { createdAt: 'desc' }, }); ctx.body = { code: 0, message: 'success', data: playlists }; }); // 创建播放列表 router.post('/', optionalAuth, async (ctx: Context) => { const userId = ctx.state.user?.userId || TEST_USER_ID; const { name, description } = ctx.request.body as { name: string; description?: string }; const playlist = await prisma.playlist.create({ data: { userId: parseInt(userId), name, description: description || '', }, }); ctx.body = { code: 0, message: 'success', data: playlist }; }); // 获取单个播放列表详情 router.get('/:id', optionalAuth, async (ctx: Context) => { const { id } = ctx.params; const playlist = await prisma.playlist.findUnique({ where: { id: parseInt(id) }, include: { items: { orderBy: { order: 'asc' }, include: { chapter: true } } }, }); ctx.body = { code: 0, message: 'success', data: playlist }; }); // 添加项目到播放列表 router.post('/:id/items', optionalAuth, async (ctx: Context) => { const { id } = ctx.params; const { chapterId, audioId } = ctx.request.body as { chapterId?: number; audioId?: string }; const maxOrder = await prisma.playlistItem.aggregate({ where: { playlistId: parseInt(id) }, _max: { order: true }, }); const nextOrder = (maxOrder._max.order ?? -1) + 1; const item = await prisma.playlistItem.create({ data: { playlistId: parseInt(id), chapterId, audioId, order: nextOrder, }, }); ctx.body = { code: 0, message: 'success', data: item }; }); // 重新排序项目 router.put('/:id/items/reorder', optionalAuth, async (ctx: Context) => { const { id } = ctx.params; const { items } = ctx.request.body as { items: { id: number; order: number }[] }; for (const item of items) { await prisma.playlistItem.update({ where: { id: item.id }, data: { order: item.order }, }); } ctx.body = { code: 0, message: 'success' }; }); // 删除项目 router.delete('/:id/items/:itemId', optionalAuth, async (ctx: Context) => { const { itemId } = ctx.params; await prisma.playlistItem.delete({ where: { id: parseInt(itemId) }, }); ctx.body = { code: 0, message: 'success' }; }); export default router; ``` --- ## 功能 36:草稿与自动保存 ### 数据库 Schema **文件**: `server/prisma/schema.prisma` - 添加以下模型: ```prisma model Draft { id Int @id @default(autoincrement()) userId Int type String // "audio", "book", "chapter" title String? content String? @db.LongText metadata String? @db.Text // JSON autoSavedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id]) @@index([userId, type]) } ``` ### 后端 Drafts Controller **文件**: `server/src/modules/drafts/drafts.controller.ts` ```typescript import Router from '@koa/router'; import { Context } from 'koa'; import { optionalAuth } from '../../middleware/auth'; import { prisma } from '../../models'; const TEST_USER_ID = '1'; const router = new Router(); // 获取草稿列表 router.get('/', optionalAuth, async (ctx: Context) => { const userId = ctx.state.user?.userId || TEST_USER_ID; const { type } = ctx.query as { type?: string }; const where: any = { userId: parseInt(userId) }; if (type) where.type = type; const drafts = await prisma.draft.findMany({ where, orderBy: { updatedAt: 'desc' }, }); ctx.body = { code: 0, message: 'success', data: drafts }; }); // 获取单个草稿 router.get('/:id', optionalAuth, async (ctx: Context) => { const { id } = ctx.params; const draft = await prisma.draft.findUnique({ where: { id: parseInt(id) }, }); ctx.body = { code: 0, message: 'success', data: draft }; }); // 保存草稿 router.post('/', optionalAuth, async (ctx: Context) => { const userId = ctx.state.user?.userId || TEST_USER_ID; const data = ctx.request.body as { type: string; title?: string; content?: string; metadata?: string; }; const draft = await prisma.draft.create({ data: { userId: parseInt(userId), type: data.type, title: data.title, content: data.content, metadata: data.metadata, autoSavedAt: new Date(), }, }); ctx.body = { code: 0, message: 'success', data: draft }; }); // 更新草稿 router.put('/:id', optionalAuth, async (ctx: Context) => { const { id } = ctx.params; const data = ctx.request.body as { title?: string; content?: string; metadata?: string; }; const draft = await prisma.draft.update({ where: { id: parseInt(id) }, data: { ...data, autoSavedAt: new Date() }, }); ctx.body = { code: 0, message: 'success', data: draft }; }); // 删除草稿 router.delete('/:id', optionalAuth, async (ctx: Context) => { const { id } = ctx.params; await prisma.draft.delete({ where: { id: parseInt(id) }, }); ctx.body = { code: 0, message: 'success' }; }); export default router; ``` --- ## 功能 37:通知功能 ### 后端 Notifications 更新 **文件**: `server/src/modules/notifications/notifications.controller.ts` - 添加: ```typescript // 全部标为已读 router.put('/read-all', optionalAuth, async (ctx: Context) => { const userId = ctx.state.user?.userId || TEST_USER_ID; await prisma.notification.updateMany({ where: { userId: String(userId), isRead: false }, data: { isRead: true }, }); ctx.body = { code: 0, message: 'success' }; }); ``` --- ## 📝 使用说明 ### 实施步骤 1. 每个功能按顺序实现:33 → 34 → 35 → 36 → 37 2. 对于每个功能: - 添加数据库模型(如需要) - 运行 `npx prisma db push` - 运行 `npx prisma generate` - 创建后端 Controller - 注册路由 - 创建/更新前端页面 - 测试 API - 测试前端 - 更新 feature_list.json ### 前端页面补充 功能 35-37 需要新建以下前端页面: - `pages/playlists/index.vue` - 播放列表首页 - `pages/playlists/detail.vue` - 播放列表详情 - `pages/drafts/index.vue` - 草稿列表 - `pages/notifications/index.vue` - 通知列表 所有代码已准备就绪,可以直接使用!