diff --git a/.gitignore b/.gitignore index a286fbd..c02daea 100644 Binary files a/.gitignore and b/.gitignore differ diff --git a/client/js/data/UserManager.js b/client/js/data/UserManager.js index e3f3cd3..c63d465 100644 --- a/client/js/data/UserManager.js +++ b/client/js/data/UserManager.js @@ -1,7 +1,7 @@ /** * 用户数据管理器 */ -import { get, post, del } from '../utils/http'; +import { get, post, put, del } from '../utils/http'; export default class UserManager { constructor() { @@ -271,6 +271,53 @@ export default class UserManager { } } + /** + * 获取已发布到创作中心的草稿 + * @param {string} draftType - 草稿类型: rewrite/continue + */ + async getPublishedDrafts(draftType) { + if (!this.isLoggedIn) return []; + try { + console.log('[UserManager] 获取已发布草稿, userId:', this.userId, 'draftType:', draftType); + const res = await get('/drafts/published', { userId: this.userId, draftType }); + console.log('[UserManager] 已发布草稿响应:', res); + return res || []; + } catch (e) { + console.error('获取已发布草稿失败:', e); + return []; + } + } + + /** + * 发布草稿到创作中心 + * @param {number} draftId - 草稿ID + */ + async publishDraft(draftId) { + if (!this.isLoggedIn) return false; + try { + await put(`/drafts/${draftId}/publish`, null, { params: { userId: this.userId } }); + return true; + } catch (e) { + console.error('发布草稿失败:', e); + return false; + } + } + + /** + * 从创作中心取消发布 + * @param {number} draftId - 草稿ID + */ + async unpublishDraft(draftId) { + if (!this.isLoggedIn) return false; + try { + await put(`/drafts/${draftId}/unpublish`, null, { params: { userId: this.userId } }); + return true; + } catch (e) { + console.error('取消发布失败:', e); + return false; + } + } + // ========== 游玩记录相关 ========== /** diff --git a/client/js/scenes/AICreateScene.js b/client/js/scenes/AICreateScene.js index f3e492d..b72a2f2 100644 --- a/client/js/scenes/AICreateScene.js +++ b/client/js/scenes/AICreateScene.js @@ -6,8 +6,8 @@ import BaseScene from './BaseScene'; export default class AICreateScene extends BaseScene { constructor(main, params) { super(main, params); - this.currentTab = 0; // 0:改写 1:续写 2:创作 - this.tabs = ['AI改写', 'AI续写', 'AI创作']; + this.currentTab = 0; // 0:我的改写 1:我的续写 2:AI创作 + this.tabs = ['我的改写', '我的续写', 'AI创作']; // 滚动 this.scrollY = 0; @@ -17,8 +17,8 @@ export default class AICreateScene extends BaseScene { this.hasMoved = false; // 用户数据 - this.recentStories = []; - this.aiHistory = []; + this.publishedRewrites = []; // 已发布的改写作品 + this.publishedContinues = []; // 已发布的续写作品 this.quota = { daily: 3, used: 0, purchased: 0 }; // 创作表单 @@ -29,12 +29,7 @@ export default class AICreateScene extends BaseScene { conflict: '' }; - // 选中的故事(用于改写/续写) - this.selectedStory = null; - // 快捷标签 - this.rewriteTags = ['主角逆袭', '甜蜜HE', '虐心BE', '反转剧情', '意外重逢', '身份揭秘']; - this.continueTags = ['增加悬念', '感情升温', '冲突加剧', '真相大白', '误会解除']; this.genreTags = ['都市言情', '古风宫廷', '悬疑推理', '校园青春', '修仙玄幻', '职场商战']; } @@ -44,10 +39,17 @@ export default class AICreateScene extends BaseScene { async loadData() { try { - // 加载最近游玩的故事 - this.recentStories = await this.main.userManager.getRecentPlayed() || []; - // 加载AI创作历史 - this.aiHistory = await this.main.userManager.getAIHistory() || []; + const userId = this.main.userManager.userId; + if (!userId) return; + + // 加载已发布的改写作品 + const rewriteRes = await this.main.userManager.getPublishedDrafts('rewrite'); + this.publishedRewrites = rewriteRes || []; + + // 加载已发布的续写作品 + const continueRes = await this.main.userManager.getPublishedDrafts('continue'); + this.publishedContinues = continueRes || []; + // 加载配额 const quotaData = await this.main.userManager.getAIQuota(); if (quotaData) this.quota = quotaData; @@ -59,8 +61,10 @@ export default class AICreateScene extends BaseScene { calculateMaxScroll() { let contentHeight = 400; - if (this.currentTab === 0 || this.currentTab === 1) { - contentHeight = 300 + this.recentStories.length * 80; + if (this.currentTab === 0) { + contentHeight = 300 + this.publishedRewrites.length * 90; + } else if (this.currentTab === 1) { + contentHeight = 300 + this.publishedContinues.length * 90; } else { contentHeight = 600; } @@ -210,23 +214,10 @@ export default class AICreateScene extends BaseScene { ctx.fillStyle = 'rgba(255,255,255,0.6)'; ctx.font = '13px sans-serif'; ctx.textAlign = 'center'; - ctx.fillText('选择一个已玩过的故事,AI帮你改写结局', this.screenWidth / 2, y + 25); + ctx.fillText('展示你从草稿箱发布的改写作品', this.screenWidth / 2, y + 25); - // 快捷标签 - ctx.fillStyle = 'rgba(255,255,255,0.8)'; - ctx.font = '12px sans-serif'; - ctx.textAlign = 'left'; - ctx.fillText('热门改写方向:', padding, y + 55); - - const tagEndY = this.renderTags(ctx, this.rewriteTags, padding, y + 70, 'rewrite'); - - // 选择故事 - 位置根据标签高度动态调整 - ctx.fillStyle = 'rgba(255,255,255,0.8)'; - ctx.font = '13px sans-serif'; - ctx.textAlign = 'left'; - ctx.fillText('选择要改写的故事:', padding, tagEndY + 25); - - this.renderStoryList(ctx, tagEndY + 40, 'rewrite'); + // 作品列表 + this.renderPublishedList(ctx, y + 50, this.publishedRewrites, 'rewrite'); } renderContinueTab(ctx, startY) { @@ -236,21 +227,10 @@ export default class AICreateScene extends BaseScene { ctx.fillStyle = 'rgba(255,255,255,0.6)'; ctx.font = '13px sans-serif'; ctx.textAlign = 'center'; - ctx.fillText('选择一个进行中的故事,AI帮你续写剧情', this.screenWidth / 2, y + 25); + ctx.fillText('展示你从草稿箱发布的续写作品', this.screenWidth / 2, y + 25); - ctx.fillStyle = 'rgba(255,255,255,0.8)'; - ctx.font = '12px sans-serif'; - ctx.textAlign = 'left'; - ctx.fillText('续写方向:', padding, y + 55); - - const tagEndY = this.renderTags(ctx, this.continueTags, padding, y + 70, 'continue'); - - ctx.fillStyle = 'rgba(255,255,255,0.8)'; - ctx.font = '13px sans-serif'; - ctx.textAlign = 'left'; - ctx.fillText('选择要续写的故事:', padding, tagEndY + 25); - - this.renderStoryList(ctx, tagEndY + 40, 'continue'); + // 作品列表 + this.renderPublishedList(ctx, y + 50, this.publishedContinues, 'continue'); } renderCreateTab(ctx, startY) { @@ -388,6 +368,84 @@ export default class AICreateScene extends BaseScene { this.inputRects[field] = { x, y: y + this.scrollY, width, height, field }; } + renderPublishedList(ctx, startY, items, type) { + const padding = 15; + const cardHeight = 80; + const cardGap = 12; + + if (!this.publishedRects) this.publishedRects = {}; + this.publishedRects[type] = []; + + if (!items || items.length === 0) { + ctx.fillStyle = 'rgba(255,255,255,0.4)'; + ctx.font = '13px sans-serif'; + ctx.textAlign = 'center'; + const tipText = type === 'rewrite' + ? '暂无改写作品,去草稿箱发布吧' + : '暂无续写作品,去草稿箱发布吧'; + ctx.fillText(tipText, this.screenWidth / 2, startY + 40); + + // 跳转草稿箱按钮 + const btnY = startY + 70; + const btnWidth = 120; + const btnX = (this.screenWidth - btnWidth) / 2; + ctx.fillStyle = 'rgba(168, 85, 247, 0.3)'; + this.roundRect(ctx, btnX, btnY, btnWidth, 36, 18); + ctx.fill(); + ctx.fillStyle = '#a855f7'; + ctx.font = '13px sans-serif'; + ctx.fillText('前往草稿箱', this.screenWidth / 2, btnY + 24); + this.gotoDraftsBtnRect = { x: btnX, y: btnY + this.scrollY, width: btnWidth, height: 36 }; + return; + } + + items.forEach((item, index) => { + const y = startY + index * (cardHeight + cardGap); + + // 卡片背景 + ctx.fillStyle = 'rgba(255,255,255,0.06)'; + this.roundRect(ctx, padding, y, this.screenWidth - padding * 2, cardHeight, 12); + ctx.fill(); + + // 标题 + ctx.fillStyle = '#ffffff'; + ctx.font = 'bold 14px sans-serif'; + ctx.textAlign = 'left'; + const title = item.title?.length > 15 ? item.title.substring(0, 15) + '...' : (item.title || '未命名作品'); + ctx.fillText(title, padding + 15, y + 25); + + // 原故事 + ctx.fillStyle = 'rgba(255,255,255,0.5)'; + ctx.font = '11px sans-serif'; + ctx.fillText(`原故事:${item.storyTitle || '未知'}`, padding + 15, y + 45); + + // 创作时间 + ctx.fillText(item.createdAt || '', padding + 15, y + 65); + + // 阅读按钮 + const btnX = this.screenWidth - padding - 70; + const btnGradient = ctx.createLinearGradient(btnX, y + 25, btnX + 60, y + 25); + btnGradient.addColorStop(0, '#a855f7'); + btnGradient.addColorStop(1, '#ec4899'); + ctx.fillStyle = btnGradient; + this.roundRect(ctx, btnX, y + 25, 60, 30, 15); + ctx.fill(); + ctx.fillStyle = '#ffffff'; + ctx.font = '12px sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText('阅读', btnX + 30, y + 45); + + this.publishedRects[type].push({ + x: padding, + y: y + this.scrollY, + width: this.screenWidth - padding * 2, + height: cardHeight, + item, + btnRect: { x: btnX, y: y + 25 + this.scrollY, width: 60, height: 30 } + }); + }); + } + renderStoryList(ctx, startY, type) { const padding = 15; const cardHeight = 70; @@ -550,7 +608,6 @@ export default class AICreateScene extends BaseScene { if (this.currentTab !== tab.index) { this.currentTab = tab.index; this.scrollY = 0; - this.selectedStory = null; this.calculateMaxScroll(); } return; @@ -561,14 +618,34 @@ export default class AICreateScene extends BaseScene { // 调整y坐标(考虑滚动) const scrolledY = y + this.scrollY; - // 标签点击 - if (this.tagRects) { - const tagType = this.currentTab === 0 ? 'rewrite' : this.currentTab === 1 ? 'continue' : 'genre'; - const tags = this.tagRects[tagType]; + // 前往草稿箱按钮 + if (this.gotoDraftsBtnRect && this.isInRect(x, scrolledY, this.gotoDraftsBtnRect)) { + this.main.sceneManager.switchScene('drafts'); + return; + } + + // 已发布作品点击(改写/续写Tab) + if (this.currentTab < 2 && this.publishedRects) { + const type = this.currentTab === 0 ? 'rewrite' : 'continue'; + const items = this.publishedRects[type]; + if (items) { + for (const rect of items) { + // 阅读按钮点击 + if (this.isInRect(x, scrolledY, rect.btnRect)) { + this.handleReadPublished(rect.item); + return; + } + } + } + } + + // 标签点击(只有创作Tab有标签) + if (this.currentTab === 2 && this.tagRects) { + const tags = this.tagRects['genre']; if (tags) { for (const tag of tags) { if (this.isInRect(x, scrolledY, tag)) { - this.handleTagSelect(tagType, tag); + this.handleTagSelect('genre', tag); return; } } @@ -586,26 +663,6 @@ export default class AICreateScene extends BaseScene { } } - // 故事列表点击 - if (this.currentTab < 2 && this.storyRects) { - const type = this.currentTab === 0 ? 'rewrite' : 'continue'; - const stories = this.storyRects[type]; - if (stories) { - for (const rect of stories) { - if (this.isInRect(x, scrolledY, rect)) { - this.selectedStory = rect.story; - return; - } - } - } - } - - // 操作按钮 - if (this.actionBtnRect && this.isInRect(x, scrolledY, this.actionBtnRect)) { - this.handleAction(this.actionBtnRect.type); - return; - } - // 创作按钮 if (this.currentTab === 2 && this.createBtnRect && this.isInRect(x, scrolledY, this.createBtnRect)) { this.handleCreate(); @@ -613,6 +670,15 @@ export default class AICreateScene extends BaseScene { } } + handleReadPublished(item) { + // 跳转到故事场景,播放AI改写/续写的内容 + this.main.sceneManager.switchScene('story', { + storyId: item.storyId, + draftId: item.id, + fromDrafts: true + }); + } + isInRect(x, y, rect) { return x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height; } @@ -620,10 +686,6 @@ export default class AICreateScene extends BaseScene { handleTagSelect(type, tag) { if (type === 'genre') { this.createForm.genre = tag.value; - } else if (type === 'rewrite') { - this.selectedRewriteTag = tag.index; - } else if (type === 'continue') { - this.selectedContinueTag = tag.index; } } diff --git a/client/js/scenes/ProfileScene.js b/client/js/scenes/ProfileScene.js index bfe21b1..6795820 100644 --- a/client/js/scenes/ProfileScene.js +++ b/client/js/scenes/ProfileScene.js @@ -636,10 +636,10 @@ export default class ProfileScene extends BaseScene { const promptText = item.userPrompt ? `"「${item.userPrompt}」"` : ''; ctx.fillText(this.truncateText(ctx, promptText, w - 100), textX, y + 48); - // 时间 + // 时间(放在左下角) ctx.fillStyle = 'rgba(255,255,255,0.35)'; ctx.font = '10px sans-serif'; - ctx.fillText(item.createdAt || '', textX, y + 68); + ctx.fillText(item.createdAt || '', textX, y + 72); // 未读标记 if (!item.isRead && item.status === 'completed') { @@ -649,31 +649,50 @@ export default class ProfileScene extends BaseScene { ctx.fill(); } - // 按钮 - const btnY = y + 62; - - // 删除按钮(所有状态都显示) - ctx.fillStyle = 'rgba(239, 68, 68, 0.2)'; - this.roundRect(ctx, x + w - 55, btnY, 45, 24, 12); - ctx.fill(); - ctx.fillStyle = '#ef4444'; - ctx.font = '11px sans-serif'; - ctx.textAlign = 'center'; - ctx.fillText('删除', x + w - 32, btnY + 16); + // 按钮行(放在右下角) + const btnY = y + 60; + const btnStartX = x + w - 170; // 从右边开始排列按钮 // 播放按钮(仅已完成状态) if (item.status === 'completed') { - const btnGradient = ctx.createLinearGradient(textX, btnY, textX + 65, btnY); + const btnGradient = ctx.createLinearGradient(btnStartX, btnY, btnStartX + 50, btnY); btnGradient.addColorStop(0, '#a855f7'); btnGradient.addColorStop(1, '#ec4899'); ctx.fillStyle = btnGradient; - this.roundRect(ctx, textX + 120, btnY, 60, 24, 12); + this.roundRect(ctx, btnStartX, btnY, 50, 26, 13); ctx.fill(); ctx.fillStyle = '#ffffff'; ctx.font = 'bold 11px sans-serif'; ctx.textAlign = 'center'; - ctx.fillText('播放', textX + 150, btnY + 16); + ctx.fillText('播放', btnStartX + 25, btnY + 17); + + // 发布按钮(仅已完成且未发布) + if (!item.publishedToCenter) { + ctx.fillStyle = 'rgba(34, 197, 94, 0.2)'; + this.roundRect(ctx, btnStartX + 58, btnY, 50, 26, 13); + ctx.fill(); + ctx.fillStyle = '#22c55e'; + ctx.font = '11px sans-serif'; + ctx.fillText('发布', btnStartX + 83, btnY + 17); + } else { + // 已发布标识 + ctx.fillStyle = 'rgba(34, 197, 94, 0.15)'; + this.roundRect(ctx, btnStartX + 58, btnY, 55, 26, 13); + ctx.fill(); + ctx.fillStyle = '#22c55e'; + ctx.font = '10px sans-serif'; + ctx.fillText('已发布', btnStartX + 85, btnY + 17); + } } + + // 删除按钮(所有状态都显示,最右边) + ctx.fillStyle = 'rgba(239, 68, 68, 0.2)'; + this.roundRect(ctx, x + w - 55, btnY, 45, 26, 13); + ctx.fill(); + ctx.fillStyle = '#ef4444'; + ctx.font = '11px sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText('删除', x + w - 32, btnY + 17); } renderSimpleCard(ctx, item, x, y, w, h, index) { @@ -896,23 +915,32 @@ export default class ProfileScene extends BaseScene { // AI草稿 Tab 的按钮检测 if (this.currentTab === 1) { - const btnY = 62; - const btnH = 24; + const btnY = 60; + const btnH = 26; + const btnStartX = padding + cardW - 170; - // 检测删除按钮点击(右侧) + // 检测删除按钮点击(最右侧) const deleteBtnX = padding + cardW - 55; if (x >= deleteBtnX && x <= deleteBtnX + 45 && relativeY >= btnY && relativeY <= btnY + btnH) { this.confirmDeleteDraft(item, index); return; } - // 检测播放按钮点击(左侧,仅已完成状态) + // 检测播放按钮点击(仅已完成状态) if (item.status === 'completed') { - const playBtnX = padding + 88 + 120; - if (x >= playBtnX && x <= playBtnX + 60 && relativeY >= btnY && relativeY <= btnY + btnH) { + if (x >= btnStartX && x <= btnStartX + 50 && relativeY >= btnY && relativeY <= btnY + btnH) { this.main.sceneManager.switchScene('story', { storyId: item.storyId, draftId: item.id }); return; } + + // 检测发布按钮点击(仅未发布状态) + if (!item.publishedToCenter) { + const publishBtnX = btnStartX + 58; + if (x >= publishBtnX && x <= publishBtnX + 50 && relativeY >= btnY && relativeY <= btnY + btnH) { + this.confirmPublishDraft(item, index); + return; + } + } } // 点击卡片其他区域 @@ -1027,6 +1055,31 @@ export default class ProfileScene extends BaseScene { }); } + // 确认发布草稿到创作中心 + confirmPublishDraft(item, index) { + wx.showModal({ + title: '发布到创作中心', + content: `确定要将「${item.title || 'AI改写'}」发布到创作中心吗?`, + confirmText: '发布', + confirmColor: '#22c55e', + cancelText: '取消', + success: async (res) => { + if (res.confirm) { + wx.showLoading({ title: '发布中...' }); + const success = await this.main.userManager.publishDraft(item.id); + wx.hideLoading(); + if (success) { + // 更新本地状态 + this.drafts[index].publishedToCenter = true; + wx.showToast({ title: '发布成功', icon: 'success' }); + } else { + wx.showToast({ title: '发布失败', icon: 'none' }); + } + } + } + }); + } + // 确认删除游玩记录 confirmDeleteRecord(item, index) { wx.showModal({ diff --git a/client/js/utils/http.js b/client/js/utils/http.js index d5ca00f..d10d1df 100644 --- a/client/js/utils/http.js +++ b/client/js/utils/http.js @@ -152,8 +152,8 @@ function requestCloud(options) { /** * GET请求 */ -export function get(url, data) { - return request({ url, method: 'GET', data }); +export function get(url, params) { + return request({ url, method: 'GET', params }); } /** @@ -170,4 +170,11 @@ export function del(url, data) { return request({ url, method: 'DELETE', data }); } -export default { request, get, post, del }; +/** + * PUT请求 + */ +export function put(url, data, options = {}) { + return request({ url, method: 'PUT', data, ...options }); +} + +export default { request, get, post, put, del }; diff --git a/server/app/models/story.py b/server/app/models/story.py index 5d69824..e072337 100644 --- a/server/app/models/story.py +++ b/server/app/models/story.py @@ -121,6 +121,8 @@ class StoryDraft(Base): status = Column(Enum(DraftStatus), default=DraftStatus.pending) error_message = Column(String(500), default="") is_read = Column(Boolean, default=False) # 用户是否已查看 + published_to_center = Column(Boolean, default=False) # 是否发布到创作中心 + draft_type = Column(String(20), default="rewrite") # 草稿类型: rewrite/continue/create created_at = Column(TIMESTAMP, server_default=func.now()) completed_at = Column(TIMESTAMP, default=None) diff --git a/server/app/routers/drafts.py b/server/app/routers/drafts.py index ed2cf2f..8258c92 100644 --- a/server/app/routers/drafts.py +++ b/server/app/routers/drafts.py @@ -374,7 +374,8 @@ async def create_draft( current_node_key=request.currentNodeKey, current_content=request.currentContent, user_prompt=request.prompt, - status=DraftStatus.pending + status=DraftStatus.pending, + draft_type='rewrite' ) db.add(draft) @@ -419,7 +420,8 @@ async def create_ending_draft( current_node_key=request.endingName, # 保存结局名称 current_content=request.endingContent, # 保存结局内容 user_prompt=request.prompt, - status=DraftStatus.pending + status=DraftStatus.pending, + draft_type='rewrite' ) db.add(draft) @@ -464,7 +466,8 @@ async def create_continue_ending_draft( current_node_key=request.endingName, # 保存结局名称 current_content=request.endingContent, # 保存结局内容 user_prompt=request.prompt, - status=DraftStatus.pending + status=DraftStatus.pending, + draft_type='continue' ) db.add(draft) @@ -508,6 +511,8 @@ async def get_drafts( "userPrompt": draft.user_prompt, "status": draft.status.value if draft.status else "pending", "isRead": draft.is_read, + "publishedToCenter": draft.published_to_center, + "draftType": draft.draft_type or "rewrite", "createdAt": draft.created_at.strftime("%Y-%m-%d %H:%M") if draft.created_at else "", "completedAt": draft.completed_at.strftime("%Y-%m-%d %H:%M") if draft.completed_at else None }) @@ -549,6 +554,48 @@ async def check_new_drafts( } +@router.get("/published") +async def get_published_drafts( + userId: int, + draftType: Optional[str] = None, + db: AsyncSession = Depends(get_db) +): + """获取已发布到创作中心的草稿列表""" + query = select(StoryDraft, Story.title.label('story_title')).join( + Story, StoryDraft.story_id == Story.id + ).where( + StoryDraft.user_id == userId, + StoryDraft.published_to_center == True, + StoryDraft.status == DraftStatus.completed + ) + + # 按类型筛选 + if draftType: + query = query.where(StoryDraft.draft_type == draftType) + + query = query.order_by(StoryDraft.created_at.desc()) + + result = await db.execute(query) + rows = result.all() + + drafts = [] + for draft, story_title in rows: + drafts.append({ + "id": draft.id, + "storyId": draft.story_id, + "storyTitle": story_title or "未知故事", + "title": draft.title or "", + "userPrompt": draft.user_prompt, + "draftType": draft.draft_type or "rewrite", + "createdAt": draft.created_at.strftime("%Y-%m-%d %H:%M") if draft.created_at else "" + }) + + return { + "code": 0, + "data": drafts + } + + @router.get("/{draft_id}") async def get_draft_detail( draft_id: int, @@ -652,3 +699,51 @@ async def mark_all_drafts_read( await db.commit() return {"code": 0, "message": "已全部标记为已读"} + + +@router.put("/{draft_id}/publish") +async def publish_draft_to_center( + draft_id: int, + userId: int, + db: AsyncSession = Depends(get_db) +): + """发布草稿到创作中心""" + # 验证草稿存在且属于该用户 + result = await db.execute( + select(StoryDraft).where( + StoryDraft.id == draft_id, + StoryDraft.user_id == userId, + StoryDraft.status == DraftStatus.completed + ) + ) + draft = result.scalar_one_or_none() + + if not draft: + raise HTTPException(status_code=404, detail="草稿不存在或未完成") + + # 更新发布状态 + draft.published_to_center = True + await db.commit() + + return {"code": 0, "message": "已发布到创作中心"} + + +@router.put("/{draft_id}/unpublish") +async def unpublish_draft_from_center( + draft_id: int, + userId: int, + db: AsyncSession = Depends(get_db) +): + """从创作中心取消发布""" + await db.execute( + update(StoryDraft) + .where( + StoryDraft.id == draft_id, + StoryDraft.user_id == userId + ) + .values(published_to_center=False) + ) + await db.commit() + + return {"code": 0, "message": "已从创作中心移除"} + diff --git a/server/sql/schema.sql b/server/sql/schema.sql index 923133b..05cc637 100644 --- a/server/sql/schema.sql +++ b/server/sql/schema.sql @@ -146,6 +146,8 @@ CREATE TABLE IF NOT EXISTS `story_drafts` ( `status` ENUM('pending', 'processing', 'completed', 'failed') DEFAULT 'pending' COMMENT '状态', `error_message` VARCHAR(500) DEFAULT '' COMMENT '失败原因', `is_read` TINYINT(1) DEFAULT 0 COMMENT '用户是否已查看', + `published_to_center` TINYINT(1) DEFAULT 0 COMMENT '是否发布到创作中心', + `draft_type` VARCHAR(20) DEFAULT 'rewrite' COMMENT '草稿类型: rewrite/continue/create', `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `completed_at` TIMESTAMP NULL DEFAULT NULL COMMENT '完成时间', PRIMARY KEY (`id`),