feat(imagine): 使用粘性导航条和底部设置表单替换悬浮按钮
This commit is contained in:
1 parent
b6eefe5cff
commit
5bf8a03ef6
3 files changed
+367
-32
No files matched your search
@@ -1252,3 +1252,205 @@ html[data-theme='dark'] .waterfall-item.selected .image-checkbox::after {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================
|
||||
移动端 Sticky Bar im-sticky-bar(<=900px)
|
||||
============================ */
|
||||
@media (max-width: 900px) {
|
||||
body {
|
||||
padding-bottom: 72px !important;
|
||||
}
|
||||
|
||||
.im-sticky-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 200;
|
||||
background: #fff;
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 10px 16px;
|
||||
padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
|
||||
box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.07);
|
||||
}
|
||||
|
||||
.im-sticky-bar-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.im-icon-divider {
|
||||
width: 1px;
|
||||
height: 24px;
|
||||
background: var(--border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.im-icon-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.im-icon-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
color: var(--fg);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: background .15s ease, border-color .15s ease;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.im-icon-btn:active {
|
||||
background: var(--accents-1);
|
||||
}
|
||||
|
||||
.im-icon-btn--danger {
|
||||
color: #ef4444;
|
||||
border-color: #fecaca;
|
||||
}
|
||||
|
||||
.im-icon-btn--danger:active {
|
||||
background: #fef2f2;
|
||||
}
|
||||
|
||||
.im-selected-badge {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
padding: 0 4px;
|
||||
border-radius: 999px;
|
||||
background: #ef4444;
|
||||
color: #fff;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
display: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.im-selected-badge.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.im-main-btn {
|
||||
height: 40px;
|
||||
padding: 0 18px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 10px;
|
||||
min-width: 88px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.im-main-btn.is-stop {
|
||||
background: #fee2e2 !important;
|
||||
color: #ef4444 !important;
|
||||
border: 1px solid #fecaca !important;
|
||||
}
|
||||
|
||||
.im-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);
|
||||
}
|
||||
|
||||
.im-settings-overlay.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.im-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: 88vh;
|
||||
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%);
|
||||
}
|
||||
|
||||
.im-settings-sheet.open {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.im-settings-sheet-handle {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 12px 0 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.im-settings-sheet-handle::after {
|
||||
content: '';
|
||||
width: 36px;
|
||||
height: 4px;
|
||||
border-radius: 999px;
|
||||
background: var(--border);
|
||||
}
|
||||
|
||||
.im-settings-sheet-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--accents-7);
|
||||
margin-bottom: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .im-sticky-bar {
|
||||
background: #111821;
|
||||
border-top-color: #2b3440;
|
||||
box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .im-icon-btn {
|
||||
background: #1a2330;
|
||||
border-color: #3b4654;
|
||||
color: #c9d6e5;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .im-icon-btn:active {
|
||||
background: #243244;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .im-main-btn.is-stop {
|
||||
background: #2d1414 !important;
|
||||
color: #f87171 !important;
|
||||
border-color: #7f1d1d !important;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .im-settings-sheet {
|
||||
background: #111821;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
@@ -2303,4 +2303,106 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动端 Sticky Bar 初始化
|
||||
*/
|
||||
function initStickyBar() {
|
||||
/* 图标按钮代理 */
|
||||
const proxyMap = {
|
||||
imClearBtn: 'clearBtn',
|
||||
imBatchBtn: 'batchDownloadBtn',
|
||||
imSelectAllBtn: 'toggleSelectAllBtn',
|
||||
imDownloadSelectedBtn: 'downloadSelectedBtn',
|
||||
};
|
||||
Object.entries(proxyMap).forEach(([stickyId, origId]) => {
|
||||
const stickyEl = document.getElementById(stickyId);
|
||||
const origEl = document.getElementById(origId);
|
||||
if (stickyEl && origEl) stickyEl.addEventListener('click', () => origEl.click());
|
||||
});
|
||||
|
||||
/* 主按钮(开始/停止)同步 */
|
||||
const imBtn = document.getElementById('imStartStopBtn');
|
||||
const playIcon = imBtn && imBtn.querySelector('.im-btn-icon-play');
|
||||
const stopIcon = imBtn && imBtn.querySelector('.im-btn-icon-stop');
|
||||
const btnLabel = imBtn && imBtn.querySelector('.im-btn-label');
|
||||
|
||||
function syncMainBtn() {
|
||||
if (!imBtn) return;
|
||||
const isStopping = startBtn && startBtn.classList.contains('hidden');
|
||||
imBtn.classList.toggle('is-stop', !!isStopping);
|
||||
if (playIcon) playIcon.style.display = isStopping ? 'none' : '';
|
||||
if (stopIcon) stopIcon.style.display = isStopping ? '' : 'none';
|
||||
if (btnLabel) btnLabel.textContent = isStopping ? '停止' : '开始';
|
||||
}
|
||||
|
||||
if (imBtn) {
|
||||
imBtn.addEventListener('click', () => {
|
||||
const isStopping = startBtn && startBtn.classList.contains('hidden');
|
||||
(isStopping ? stopBtn : startBtn) && (isStopping ? stopBtn : startBtn).click();
|
||||
});
|
||||
}
|
||||
if (startBtn) new MutationObserver(syncMainBtn).observe(startBtn, { attributes: true, attributeFilter: ['class'] });
|
||||
syncMainBtn();
|
||||
|
||||
/* 选中数字角标同步 */
|
||||
const origBadge = document.getElementById('selectedCount');
|
||||
const imBadge = document.getElementById('imSelectedBadge');
|
||||
function syncBadge() {
|
||||
if (!origBadge || !imBadge) return;
|
||||
const count = parseInt(origBadge.textContent || '0', 10);
|
||||
imBadge.textContent = String(count);
|
||||
imBadge.classList.toggle('visible', count > 0);
|
||||
}
|
||||
if (origBadge) {
|
||||
new MutationObserver(syncBadge).observe(origBadge, { characterData: true, childList: true, subtree: true });
|
||||
syncBadge();
|
||||
}
|
||||
|
||||
/* 生成设置 Bottom Sheet */
|
||||
const settingsBtn = document.getElementById('imSettingsBtn');
|
||||
const settingsOverlay = document.getElementById('imSettingsOverlay');
|
||||
const settingsCard = document.querySelector('.settings-card');
|
||||
|
||||
let settingsSheet = document.getElementById('imSettingsSheet');
|
||||
if (!settingsSheet && settingsCard) {
|
||||
settingsSheet = document.createElement('div');
|
||||
settingsSheet.id = 'imSettingsSheet';
|
||||
settingsSheet.className = 'im-settings-sheet';
|
||||
|
||||
const handle = document.createElement('div');
|
||||
handle.className = 'im-settings-sheet-handle';
|
||||
settingsSheet.appendChild(handle);
|
||||
|
||||
const titleEl = document.createElement('div');
|
||||
titleEl.className = 'im-settings-sheet-title';
|
||||
titleEl.textContent = '生成设置';
|
||||
settingsSheet.appendChild(titleEl);
|
||||
|
||||
const cardContent = settingsCard.querySelector('.card-content');
|
||||
if (cardContent) settingsSheet.appendChild(cardContent);
|
||||
document.body.appendChild(settingsSheet);
|
||||
}
|
||||
|
||||
function openSettings() {
|
||||
if (!settingsSheet || !settingsOverlay) return;
|
||||
settingsOverlay.classList.add('active');
|
||||
requestAnimationFrame(() => settingsSheet.classList.add('open'));
|
||||
}
|
||||
function 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('.im-settings-sheet-handle');
|
||||
if (sheetHandle) sheetHandle.addEventListener('click', closeSettings);
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && settingsSheet && settingsSheet.classList.contains('open')) closeSettings();
|
||||
});
|
||||
}
|
||||
|
||||
initStickyBar();
|
||||
})();
|
||||
@@ -257,39 +257,71 @@
|
||||
清空
|
||||
</button>
|
||||
<span class="toolbar-sep"></span>
|
||||
<button id="batchDownloadBtn"
|
||||
class="batch-download-btn geist-button-outline text-xs px-3 gap-1 border-0 hover:bg-gray-100" title="批量下载">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="7 10 12 15 17 10"></polyline>
|
||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||
</svg>
|
||||
<span>批量</span>
|
||||
</button>
|
||||
<div id="selectionToolbar" class="selection-toolbar hidden">
|
||||
<button id="toggleSelectAllBtn"
|
||||
class="geist-button-outline text-xs px-3 gap-1 border-0 hover:bg-gray-100">全选</button>
|
||||
<button id="downloadSelectedBtn" class="geist-button-outline text-xs px-3 gap-1 border-0 hover:bg-gray-100">下载
|
||||
<span id="selectedCount" class="selected-count">0</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 移动端生成设置遮罩(Bottom Sheet) -->
|
||||
<div id="imSettingsOverlay" class="im-settings-overlay"></div>
|
||||
|
||||
<div class="mobile-fab" data-mobile-fab>
|
||||
<div class="mobile-fab-panel">
|
||||
<div class="mobile-fab-actions">
|
||||
<button class="geist-button" data-proxy-click="#startBtn" type="button">开始</button>
|
||||
<button class="geist-button-outline" data-proxy-click="#stopBtn" type="button">停止</button>
|
||||
<button class="geist-button-outline" data-proxy-click="#clearBtn" type="button">清空</button>
|
||||
<button class="geist-button-outline" data-proxy-click="#batchDownloadBtn" type="button">批量</button>
|
||||
<button class="geist-button-outline" data-proxy-click="#toggleSelectAllBtn" type="button">全选</button>
|
||||
<button class="geist-button-outline" data-proxy-click="#downloadSelectedBtn" type="button">下载</button>
|
||||
<!-- 移动端底部 Sticky 操作栏 -->
|
||||
<div id="imStickyBar" class="im-sticky-bar">
|
||||
<div class="im-sticky-bar-inner">
|
||||
<!-- 左侧:齿轮(生成设置) -->
|
||||
<button class="im-icon-btn" id="imSettingsBtn" type="button" title="生成设置" aria-label="生成设置">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="im-icon-divider"></div>
|
||||
<!-- 图标区 -->
|
||||
<div class="im-icon-group">
|
||||
<!-- 清空 -->
|
||||
<button class="im-icon-btn im-icon-btn--danger" id="imClearBtn" type="button" title="清空图片" aria-label="清空">
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>
|
||||
<path d="M10 11v6M14 11v6"/><path d="M9 6V4h6v2"/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- 批量下载 -->
|
||||
<button class="im-icon-btn" id="imBatchBtn" type="button" title="批量下载" aria-label="批量下载">
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
||||
<polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- 全选 -->
|
||||
<button class="im-icon-btn" id="imSelectAllBtn" type="button" title="全选" aria-label="全选">
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="9 11 12 14 22 4"/>
|
||||
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- 下载选中(带数字角标) -->
|
||||
<button class="im-icon-btn im-download-selected-btn" id="imDownloadSelectedBtn" type="button"
|
||||
title="下载选中" aria-label="下载选中">
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
||||
<polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>
|
||||
</svg>
|
||||
<span id="imSelectedBadge" class="im-selected-badge">0</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- 右侧:主按钮(开始/停止) -->
|
||||
<button id="imStartStopBtn" class="geist-button im-main-btn" type="button">
|
||||
<svg class="im-btn-icon-play" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polygon points="5 3 19 12 5 21 5 3"/>
|
||||
</svg>
|
||||
<svg class="im-btn-icon-stop" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none">
|
||||
<rect x="5" y="5" width="14" height="14"/>
|
||||
</svg>
|
||||
<span class="im-btn-label">开始</span>
|
||||
</button>
|
||||
</div>
|
||||
<button class="mobile-fab-toggle" data-fab-toggle type="button" aria-label="打开操作菜单">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 5v14M5 12h14" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="app-footer"></div>
|
||||
@@ -298,12 +330,11 @@
|
||||
<script src="/static/common/js/parentpost-memory.js?v=0.1.0"></script>
|
||||
<script src="/static/common/js/public-header.js?v=0.3.3"></script>
|
||||
<script src="/static/common/js/footer.js?v=0.3.0"></script>
|
||||
<script src="/static/common/js/mobile-fab.js?v=0.1.1"></script>
|
||||
<script src="/static/common/js/toast.js"></script>
|
||||
<script src="/static/common/js/prompt-enhancer.js?v=0.1.15"></script>
|
||||
<script src="/static/common/js/pwa.js?v=0.1.1"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
|
||||
<script src="/static/public/js/imagine.js?v=0.3.8"></script>
|
||||
<script src="/static/public/js/imagine.js?v=0.3.9"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in new issue
Block a user