diff --git a/app/static/public/css/nsfw.css b/app/static/public/css/nsfw.css index aac3c62..c1c6759 100644 --- a/app/static/public/css/nsfw.css +++ b/app/static/public/css/nsfw.css @@ -824,3 +824,230 @@ html[data-theme='dark'] .candidate-meta { grid-column: auto; } } + +/* ============================ + NSFW 工作台 移动端双行 Sticky Bar(<=900px) + ============================ */ +@media (max-width: 900px) { + body { + padding-bottom: 108px !important; + } + + .nsfw-sticky-bar { + position: fixed; + bottom: 0; + left: 0; + right: 0; + z-index: 200; + background: #fff; + border-top: 1px solid var(--border); + padding: 8px 14px; + padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px)); + box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.07); + display: flex; + flex-direction: column; + gap: 0; + } + + .nsfw-sticky-row { + display: flex; + align-items: center; + gap: 7px; + padding: 5px 0; + } + + .nsfw-sticky-row-sep { + height: 1px; + background: var(--border); + margin: 0 -2px; + opacity: 0.6; + } + + .nsfw-icon-divider { + width: 1px; + height: 22px; + background: var(--border); + flex-shrink: 0; + } + + .nsfw-icon-group { + display: flex; + align-items: center; + gap: 4px; + flex: 1; + justify-content: flex-start; + } + + .nsfw-icon-btn { + width: 36px; + height: 36px; + border: 1px solid var(--border); + border-radius: 9px; + background: #fff; + color: var(--fg); + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + flex-shrink: 0; + transition: background .15s ease; + padding: 0; + position: relative; + } + + .nsfw-icon-btn:active { + background: var(--accents-1); + } + + .nsfw-icon-btn--danger { + color: #ef4444; + border-color: #fecaca; + } + + .nsfw-icon-btn--danger:active { + background: #fef2f2; + } + + .nsfw-main-btn { + height: 36px; + padding: 0 14px; + font-size: 13px; + font-weight: 600; + display: inline-flex; + align-items: center; + gap: 5px; + flex-shrink: 0; + border-radius: 9px; + min-width: 84px; + justify-content: center; + margin-left: auto; + } + + .nsfw-main-btn.is-stop { + background: #fee2e2 !important; + color: #ef4444 !important; + border: 1px solid #fecaca !important; + } + + /* 视频行 */ + .nsfw-sticky-row--video { + opacity: 0.46; + pointer-events: none; + transition: opacity .2s ease; + } + + .nsfw-sticky-row--video.has-selected { + opacity: 1; + pointer-events: auto; + } + + .nsfw-selected-label { + flex: 1; + font-size: 11px; + color: var(--accents-5); + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .nsfw-sticky-row--video.has-selected .nsfw-selected-label { + color: #2563eb; + font-weight: 500; + } + + .nsfw-video-actions { + display: flex; + align-items: center; + gap: 5px; + flex-shrink: 0; + } + + /* Settings Overlay */ + .nsfw-settings-overlay { + display: none; + position: fixed; + inset: 0; + z-index: 300; + background: rgba(0, 0, 0, 0.38); + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); + } + + .nsfw-settings-overlay.active { + display: block; + } + + /* Settings Bottom Sheet */ + .nsfw-settings-sheet { + position: fixed; + bottom: 0; + left: 0; + right: 0; + z-index: 301; + background: #fff; + border-radius: 20px 20px 0 0; + padding: 0 16px 24px; + padding-bottom: calc(24px + env(safe-area-inset-bottom, 0px)); + max-height: 80vh; + overflow-y: auto; + box-shadow: 0 -8px 40px rgba(0, 0, 0, 0.18); + transition: transform .28s cubic-bezier(.2, 0, 0, 1); + transform: translateY(100%); + } + + .nsfw-settings-sheet.open { + transform: translateY(0); + } + + .nsfw-settings-sheet-handle { + display: flex; + justify-content: center; + padding: 12px 0 6px; + cursor: pointer; + } + + .nsfw-settings-sheet-handle::after { + content: ''; + width: 36px; + height: 4px; + border-radius: 999px; + background: var(--border); + } + + .nsfw-settings-sheet-title { + font-size: 14px; + font-weight: 600; + color: var(--accents-7); + margin-bottom: 14px; + text-align: center; + } +} + +/* 暗色主题 */ +html[data-theme='dark'] .nsfw-sticky-bar { + background: #111821; + border-top-color: #2b3440; + box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.3); +} + +html[data-theme='dark'] .nsfw-icon-btn { + background: #1a2330; + border-color: #3b4654; + color: #c9d6e5; +} + +html[data-theme='dark'] .nsfw-icon-btn:active { + background: #243244; +} + +html[data-theme='dark'] .nsfw-main-btn.is-stop { + background: #2d1414 !important; + color: #f87171 !important; + border-color: #7f1d1d !important; +} + +html[data-theme='dark'] .nsfw-settings-sheet { + background: #111821; + color: #e2e8f0; +} diff --git a/app/static/public/js/nsfw.js b/app/static/public/js/nsfw.js index 8571f03..382a449 100644 --- a/app/static/public/js/nsfw.js +++ b/app/static/public/js/nsfw.js @@ -1,4 +1,4 @@ -(() => { +(() => { const imagePromptInput = document.getElementById('imagePromptInput'); const videoPromptInput = document.getElementById('videoPromptInput'); const ratioSelect = document.getElementById('ratioSelect'); @@ -1818,4 +1818,153 @@ updateCounters(); updateImageButtons(); updateVideoButtons(); + + /** + * NSFW 工作台 双行 Sticky Bar 初始化 + */ + function initNsfwStickyBar() { + /* --- 代理映射 --- */ + const proxyMap = { + imClearImagesBtn: 'clearImagesBtn', + imNextBatchBtn: 'nextBatchBtn', + imClearVideosBtn: 'clearVideosBtn', + }; + Object.entries(proxyMap).forEach(([sId, oId]) => { + const sEl = document.getElementById(sId); + const oEl = document.getElementById(oId); + if (sEl && oEl) sEl.addEventListener('click', () => oEl.click()); + }); + + /* --- 候选图主按钮:生成6张/停止候选 --- */ + const imGenBtn = document.getElementById('imGenerateBtn'); + const genPlayIcon = imGenBtn && imGenBtn.querySelector('.nsfw-btn-play'); + const genStopIcon = imGenBtn && imGenBtn.querySelector('.nsfw-btn-stop'); + const genLabel = imGenBtn && imGenBtn.querySelector('.nsfw-btn-label'); + + function syncGenBtn() { + if (!imGenBtn) return; + const isRunning = generateBatchBtn && generateBatchBtn.dataset.running === '1'; + imGenBtn.classList.toggle('is-stop', isRunning); + if (genPlayIcon) genPlayIcon.style.display = isRunning ? 'none' : ''; + if (genStopIcon) genStopIcon.style.display = isRunning ? '' : 'none'; + if (genLabel) genLabel.textContent = isRunning ? '停止候选' : '生成 6 张'; + } + + if (imGenBtn) { + imGenBtn.addEventListener('click', () => { + const isRunning = generateBatchBtn && generateBatchBtn.dataset.running === '1'; + (isRunning ? stopBatchBtn : generateBatchBtn) && (isRunning ? stopBatchBtn : generateBatchBtn).click(); + }); + } + /* 观察 generateBatchBtn 的 data-running 属性和 disabled 状态 */ + if (generateBatchBtn) { + new MutationObserver(syncGenBtn).observe(generateBatchBtn, { attributes: true, attributeFilter: ['data-running', 'disabled'] }); + } + syncGenBtn(); + + /* --- 视频主按钮:生成视频/中断视频 --- */ + const imVideoBtn = document.getElementById('imStartVideoBtn'); + const vidPlayIcon = imVideoBtn && imVideoBtn.querySelector('.nsfw-btn-play'); + const vidStopIcon = imVideoBtn && imVideoBtn.querySelector('.nsfw-btn-stop'); + const vidLabel = imVideoBtn && imVideoBtn.querySelector('.nsfw-btn-label'); + + function syncVideoBtn() { + if (!imVideoBtn) return; + const isRunning = startVideoBtn && startVideoBtn.dataset.running === '1'; + const hasSelected = startVideoBtn && !startVideoBtn.disabled; + imVideoBtn.classList.toggle('is-stop', isRunning); + imVideoBtn.disabled = !isRunning && !hasSelected; + if (vidPlayIcon) vidPlayIcon.style.display = isRunning ? 'none' : ''; + if (vidStopIcon) vidStopIcon.style.display = isRunning ? '' : 'none'; + if (vidLabel) vidLabel.textContent = isRunning ? '中断视频' : '生成视频'; + } + + if (imVideoBtn) { + imVideoBtn.addEventListener('click', () => { + const isRunning = startVideoBtn && startVideoBtn.dataset.running === '1'; + (isRunning ? stopVideoBtn : startVideoBtn) && (isRunning ? stopVideoBtn : startVideoBtn).click(); + }); + } + if (startVideoBtn) { + new MutationObserver(syncVideoBtn).observe(startVideoBtn, { attributes: true, attributeFilter: ['data-running', 'disabled'] }); + } + syncVideoBtn(); + + /* --- 候选图已选状态同步 → 第二行高亮 --- */ + const videoRow = document.getElementById('nsfwVideoRow'); + const selectedLabel = document.getElementById('nsfwSelectedLabel'); + + function syncSelected() { + if (!selectedMeta || !videoRow || !selectedLabel) return; + const text = String(selectedMeta.textContent || '').trim(); + const hasSelected = !text.includes('未选择'); + videoRow.classList.toggle('has-selected', hasSelected); + // 截短显示 + if (hasSelected) { + selectedLabel.textContent = text.length > 22 ? text.slice(0, 22) + '…' : text; + } else { + selectedLabel.textContent = '未选择候选图'; + } + syncVideoBtn(); + } + + if (selectedMeta) { + new MutationObserver(syncSelected).observe(selectedMeta, { characterData: true, childList: true, subtree: true }); + syncSelected(); + } + + /* --- 设置 Bottom Sheet(画面比例、并发、分辨率、时长、NSFW) --- */ + const settingsBtn = document.getElementById('nsfwSettingsBtn'); + const settingsOverlay = document.getElementById('nsfwSettingsOverlay'); + + let settingsSheet = document.getElementById('nsfwSettingsSheet'); + if (!settingsSheet) { + settingsSheet = document.createElement('div'); + settingsSheet.id = 'nsfwSettingsSheet'; + settingsSheet.className = 'nsfw-settings-sheet'; + + const handle = document.createElement('div'); + handle.className = 'nsfw-settings-sheet-handle'; + settingsSheet.appendChild(handle); + + const titleEl = document.createElement('div'); + titleEl.className = 'nsfw-settings-sheet-title'; + titleEl.textContent = '生成参数'; + settingsSheet.appendChild(titleEl); + + // 将参数表单(画面比例、并发、分辨率、时长、NSFW 这些 field,不含 textarea)移入 sheet + const formGrid = document.querySelector('.form-grid'); + if (formGrid) { + const nonWideFields = formGrid.querySelectorAll('.field:not(.field-wide)'); + const sheetGrid = document.createElement('div'); + sheetGrid.className = 'form-grid'; + sheetGrid.style.cssText = 'grid-template-columns: 1fr 1fr; gap: 10px 14px;'; + nonWideFields.forEach(f => sheetGrid.appendChild(f)); + settingsSheet.appendChild(sheetGrid); + } + + document.body.appendChild(settingsSheet); + } + + const openSettings = () => { + if (!settingsSheet || !settingsOverlay) return; + settingsOverlay.classList.add('active'); + requestAnimationFrame(() => settingsSheet.classList.add('open')); + }; + const closeSettings = () => { + if (!settingsSheet || !settingsOverlay) return; + settingsSheet.classList.remove('open'); + settingsOverlay.classList.remove('active'); + }; + + if (settingsBtn) settingsBtn.addEventListener('click', openSettings); + if (settingsOverlay) settingsOverlay.addEventListener('click', closeSettings); + const sheetHandle = settingsSheet && settingsSheet.querySelector('.nsfw-settings-sheet-handle'); + if (sheetHandle) sheetHandle.addEventListener('click', closeSettings); + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && settingsSheet && settingsSheet.classList.contains('open')) closeSettings(); + }); + } + + initNsfwStickyBar(); })(); diff --git a/app/static/public/pages/nsfw.html b/app/static/public/pages/nsfw.html index d080035..c94cdb3 100644 --- a/app/static/public/pages/nsfw.html +++ b/app/static/public/pages/nsfw.html @@ -19,7 +19,7 @@ - + @@ -132,23 +132,88 @@ -
-
-
- - - - - - - + +
+ + +
+ +
+ + +
+ +
+ + + + +
+ + +
+ + +
+ + +
+ +
未选择候选图
+ +
+ + + +
-