Skip to main content
Glama
admin_publish.py17.8 kB
# -*- coding: utf-8 -*- """发布管理页面(FastMCP-UI 风格,折叠卡片排队 + 分页)""" from starlette.responses import HTMLResponse from .ui_base import ( build_page_with_nav, create_page_header, create_button_group, ) # ---------------------------------------------------------------------------- # 表单片段(统一使用 mc-* 样式) # ---------------------------------------------------------------------------- def create_strategy_form() -> str: return """ <div class="mc-form-group"> <label>最小发布间隔(秒)</label> <input type="number" id="min-interval" min="60" value="300"> <small>建议不小于 60 秒</small> </div> <div class="mc-form-group"> <label>每日发布限制(条)</label> <input type="number" id="daily-limit" min="1" value="10"> </div> <div class="mc-form-group"> <label>每小时发布限制(条)</label> <input type="number" id="hourly-limit" min="1" value="5"> </div> <div class="mc-form-group"> <label>失败重试次数</label> <input type="number" id="retry-count" min="0" value="3"> </div> <div class="mc-form-group"> <div class="btn-group"> <button type="button" class="btn btn-primary" id="update-strategy-btn">保存配置</button> <button type="button" class="btn btn-secondary" id="refresh-strategy-btn">刷新</button> </div> </div> """ def create_strategy_card() -> str: return f""" <div class="mc-status-card"> <h3>发布策略配置</h3> {create_strategy_form()} </div> """ def create_task_list_card() -> str: return """ <div class="mc-status-card"> <div style="display:flex;justify-content:space-between;align-items:center;gap:1rem; margin-bottom: 1rem;"> <h3 style="margin:0;">发布任务队列</h3> <div class="btn-group"> <button type="button" class="btn btn-primary" id="btn-publish-xhs"> <i class="fas fa-plus-circle" style="margin-right: 0.5rem;"></i>发布小红书 </button> <button type="button" class="btn btn-primary" id="btn-publish-dy" disabled> <i class="fas fa-plus-circle" style="margin-right: 0.5rem;"></i>发布抖音 </button> <button type="button" class="btn btn-secondary" id="refresh-tasks-btn"> <i class="fas fa-sync-alt" style="margin-right: 0.5rem;"></i>刷新 </button> </div> </div> <!-- 小红书发布折叠框 --> <div id="xhs-publish-collapse" class="publish-collapse" style="display: none;"> <div class="publish-form-card"> <div class="publish-form-header"> <h5><i class="fas fa-feather-alt" style="margin-right: 0.5rem;"></i>小红书发布参数</h5> </div> <div class="mc-form-group"> <label>内容类型</label> <div class="btn-group" role="group"> <button type="button" class="btn btn-secondary is-active" data-type="image" id="xhs-btn-type-image"> <i class="fas fa-images" style="margin-right: 0.25rem;"></i>图文 </button> <button type="button" class="btn btn-secondary" data-type="video" id="xhs-btn-type-video"> <i class="fas fa-video" style="margin-right: 0.25rem;"></i>视频 </button> </div> <input type="hidden" id="xhs-content-type" value="image" /> </div> <div class="mc-form-group"> <label for="xhs-title">标题</label> <input type="text" id="xhs-title" placeholder="请输入标题"> </div> <div class="mc-form-group"> <label for="xhs-content">正文内容</label> <textarea id="xhs-content" rows="5" placeholder="请输入正文内容"></textarea> </div> <div class="mc-form-group"> <label for="xhs-tags">标签</label> <input type="text" id="xhs-tags" placeholder="多个标签用逗号分隔,如:美食推荐,好吃"> </div> <div class="mc-form-group"> <label for="xhs-topics">话题</label> <input type="text" id="xhs-topics" placeholder="多个话题用逗号分隔,如:#美食探店,#周末好去处"> </div> <div class="mc-form-group" id="xhs-image-upload-section"> <label>图片上传</label> <div class="mc-dropzone" id="xhs-image-dropzone"> <p>点击选择或直接拖拽图片到此处</p> <input type="file" id="xhs-image-files" multiple accept="image/*" style="display:none"> <button type="button" class="btn btn-secondary" id="xhs-choose-images">选择图片</button> <small style="display:block;margin-top:0.5rem;">最多支持9张图片,支持JPG、PNG等格式</small> </div> <div id="xhs-image-preview" style="margin-top:0.75rem;"></div> </div> <div class="mc-form-group is-hidden" id="xhs-video-upload-section"> <label>视频上传</label> <div class="mc-dropzone" id="xhs-video-dropzone"> <p>点击选择或直接拖拽视频到此处</p> <input type="file" id="xhs-video-file" accept="video/*" style="display:none"> <button type="button" class="btn btn-secondary" id="xhs-choose-video">选择视频</button> <small style="display:block;margin-top:0.5rem;">支持MP4、MOV等格式</small> </div> <div id="xhs-video-preview" style="margin-top:0.75rem;"></div> </div> <div class="mc-form-group"> <label for="xhs-location">定位(可选)</label> <input type="text" id="xhs-location" placeholder="请输入地点"> </div> <div class="mc-form-group"> <label style="display:flex;align-items:center;gap:.5rem;"> <input type="checkbox" id="xhs-is-private"> 私密发布(仅自己可见) </label> </div> <div class="mc-form-group"> <div class="btn-group"> <button type="button" class="btn btn-primary" id="xhs-publish-btn"> <i class="fas fa-paper-plane" style="margin-right: 0.5rem;"></i>立即发布 </button> <button type="button" class="btn btn-secondary" id="xhs-clear-btn"> <i class="fas fa-eraser" style="margin-right: 0.5rem;"></i>清空 </button> <button type="button" class="btn btn-secondary" id="xhs-cancel-btn"> <i class="fas fa-times" style="margin-right: 0.5rem;"></i>取消 </button> </div> </div> </div> </div> <!-- 状态筛选标签 --> <div class="task-filter-tabs"> <button class="filter-tab is-active" data-status="all"> <i class="fas fa-list"></i> <span>全部</span> <span class="tab-count" id="count-all">0</span> </button> <button class="filter-tab" data-status="queued"> <i class="fas fa-clock"></i> <span>排队中</span> <span class="tab-count" id="count-queued">0</span> </button> <button class="filter-tab" data-status="processing"> <i class="fas fa-spinner"></i> <span>处理中</span> <span class="tab-count" id="count-processing">0</span> </button> <button class="filter-tab" data-status="success"> <i class="fas fa-check-circle"></i> <span>成功</span> <span class="tab-count" id="count-success">0</span> </button> <button class="filter-tab" data-status="failed"> <i class="fas fa-times-circle"></i> <span>失败</span> <span class="tab-count" id="count-failed">0</span> </button> </div> <!-- 队列统计 --> <div class="queue-stats-bar"> <div class="stat-item"> <i class="fas fa-layer-group"></i> <span id="queue-stats">加载中...</span> </div> </div> <!-- 任务列表 --> <div id="task-list" class="task-list-container"></div> </div> """ def create_publish_styles() -> str: return """ <style> .mc-dropzone { border: 2px dashed var(--border-color, #e2e8f0); padding: 2rem; text-align: center; border-radius: .5rem; color: var(--text-muted, #6b7280); background: var(--bg-color, #fafafa); transition: all 0.3s ease; cursor: pointer; } .mc-dropzone:hover { background: var(--item-bg, #f0f0f0); border-color: var(--primary-color, #007bff); } .mc-dropzone.dragover { background: var(--primary-bg, #e3f2fd); border-color: var(--primary-color, #007bff); color: var(--primary-color, #007bff); } .is-hidden { display: none !important; } .publish-collapse { transition: all 0.3s ease-in-out; overflow: hidden; margin-bottom: 1.5rem; } .btn.is-active { background-color: var(--primary-color, #007bff) !important; color: white !important; border-color: var(--primary-color, #007bff) !important; } .image-preview-item { display: inline-block; position: relative; margin: 0.25rem; border-radius: 0.375rem; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.12); } .image-preview-item img { width: 80px; height: 80px; object-fit: cover; display: block; } .image-preview-item .remove-btn { position: absolute; top: 4px; right: 4px; width: 20px; height: 20px; background: rgba(220, 53, 69, 0.9); color: white; border: none; border-radius: 50%; font-size: 12px; cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0; } .image-preview-item .remove-btn:hover { background: rgba(220, 53, 69, 1); } /* 任务展开箭头样式 */ .task-toggle-icon { font-size: 1em; margin-right: 0.5rem; color: #0066cc; transition: transform 0.2s ease; display: inline-block; } .task-toggle-icon.expanded { transform: rotate(180deg); } /* 发布表单卡片 */ .publish-form-card { background: var(--card-bg, white); border: 1px solid var(--border-color, #e2e8f0); border-radius: 0.5rem; padding: 1.5rem; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); } .publish-form-header { margin-bottom: 1.5rem; padding-bottom: 1rem; border-bottom: 2px solid var(--border-color, #e2e8f0); } .publish-form-header h5 { margin: 0; color: var(--primary-color, #0066cc); font-size: 1.125rem; font-weight: 600; display: flex; align-items: center; } /* 详情值样式 */ .detail-value { padding: 0.75rem; background: var(--bg-color, #f9fafb); border: 1px solid var(--border-color, #e5e7eb); border-radius: 0.375rem; color: var(--text-color, #374151); } /* Alert样式 */ .alert { padding: 0.75rem 1rem; border-radius: 0.375rem; border: 1px solid; } .alert-danger { background: #fee2e2; color: #991b1b; border-color: #fecaca; } /* 状态筛选标签 */ .task-filter-tabs { display: flex; gap: 0.5rem; margin-bottom: 1rem; padding: 0.75rem; background: var(--bg-color, #f8f9fa); border-radius: 0.5rem; flex-wrap: wrap; } .filter-tab { flex: 1; min-width: 120px; display: flex; align-items: center; justify-content: center; gap: 0.5rem; padding: 0.75rem 1rem; background: white; border: 2px solid var(--border-color, #e2e8f0); border-radius: 0.375rem; color: var(--text-color, #374151); font-size: 0.875rem; font-weight: 500; cursor: pointer; transition: all 0.2s ease; position: relative; } .filter-tab:hover { background: var(--item-bg, #f3f4f6); border-color: var(--primary-color, #0066cc); transform: translateY(-1px); } .filter-tab.is-active { background: var(--primary-color, #0066cc); color: white; border-color: var(--primary-color, #0066cc); box-shadow: 0 2px 4px rgba(0, 102, 204, 0.2); } .filter-tab i { font-size: 1rem; } .tab-count { background: rgba(0, 0, 0, 0.1); padding: 0.125rem 0.5rem; border-radius: 1rem; font-size: 0.75rem; font-weight: 600; min-width: 24px; text-align: center; } .filter-tab.is-active .tab-count { background: rgba(255, 255, 255, 0.3); } /* 队列统计栏 */ .queue-stats-bar { display: flex; align-items: center; gap: 1.5rem; padding: 0.875rem 1rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 0.5rem; margin-bottom: 1rem; box-shadow: 0 2px 4px rgba(102, 126, 234, 0.2); } .queue-stats-bar .stat-item { display: flex; align-items: center; gap: 0.5rem; font-size: 0.9rem; } .queue-stats-bar i { font-size: 1.25rem; opacity: 0.9; } /* 任务列表容器 */ .task-list-container { display: flex; flex-direction: column; gap: 0.75rem; } /* 任务卡片样式优化 */ .task-item { background: var(--card-bg, white); border: 1px solid var(--border-color, #e2e8f0); border-radius: 0.5rem; padding: 1rem; transition: all 0.2s ease; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); cursor: pointer; } .task-item:hover { box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); border-color: var(--primary-color, #0066cc); } .task-item-header { display: flex; align-items: center; justify-content: space-between; user-select: none; } .task-item-header:hover .task-toggle-icon { color: var(--primary-color, #0066cc); } .task-item-title { display: flex; align-items: center; gap: 0.5rem; flex: 1; font-weight: 500; color: var(--text-color, #1f2937); } .task-status-badge { padding: 0.25rem 0.75rem; border-radius: 1rem; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.025em; } .task-status-badge.queued { background: #fef3c7; color: #92400e; } .task-status-badge.processing { background: #dbeafe; color: #1e40af; } .task-status-badge.success { background: #d1fae5; color: #065f46; } .task-status-badge.failed { background: #fee2e2; color: #991b1b; } .task-status-badge.pending { background: #f3f4f6; color: #4b5563; } /* 任务详情 */ .task-details { margin-top: 1rem; padding-top: 1rem; border-top: 2px solid var(--border-color, #e2e8f0); } /* 删除按钮样式 - 与登录页面退出按钮保持一致 */ .btn-delete { background: #fee2e2 !important; color: #991b1b !important; border-color: #fecaca !important; } .btn-delete:hover { background: #fecaca !important; color: #7f1d1d !important; border-color: #fca5a5 !important; } </style> """ def create_publish_scripts() -> str: return '<script src="/static/js/publish.js"></script>' def render_publish_management_page() -> HTMLResponse: header = create_page_header( title="发布管理", breadcrumb="首页 / 发布管理", actions=create_button_group([ ("刷新队列", "#", "secondary"), ]).replace("href='#'", "id=\"header-refresh-btn\" style='cursor:pointer'") ) main = f""" {header} <div class="mc-dashboard-grid"> {create_task_list_card()} </div> <div class="mc-dashboard-grid" style="margin-top: 2rem;"> {create_strategy_card()} </div> {create_publish_styles()} {create_publish_scripts()} """ return build_page_with_nav( main_content=main, title="发布管理 · MediaCrawler MCP", current_path="/publish" )

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mcp-service/media-crawler-mcp-service'

If you have feedback or need assistance with the MCP directory API, please join our Discord server