Files
xianyan/docs/preview/image_cache_preview.html
Developer adfa0af825 chore: 汇总2026-05-30全量更新
### 详细变更:
1.  **文档与配置**:更新AGENTS.md添加命令超时约束,升级Rive依赖至0.14.7并替换平台插件引用
2.  **UI优化**:重构AppInfo页面布局、移除图表冗余配置、锁定部分系统设置项
3.  **功能增强**:
    - 新增工具面板拖拽状态管理与介绍弹窗
    - 新增进度页面编辑/重排/清空用户进度功能
    - 新增摇一摇路由作用域拦截逻辑
4.  **体验优化**:
    - 统一外部链接跳转弹窗,添加文件打开确认逻辑
    - 修复设备卡片IP溢出、Android权限声明问题
    - 后台任务初始化增加协议校验
5.  **代码重构**:拆分工具面板配置、拖拽逻辑与动画参数,优化状态管理代码
6.  **工具脚本**:新增协议文件上传脚本
2026-05-30 05:29:50 +08:00

1937 lines
48 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<!--
Created: 2026-05-30
Updated: 2026-05-30
Name: 图片缓存管理预览页面
Purpose: 模拟iOS 26风格图片缓存管理页面设计
Last Update: 初始创建
-->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>🖼️ 图片缓存管理 - iOS 26 Preview</title>
<style>
:root {
--primary: #6C5CE7;
--primary-light: #A29BFE;
--primary-dark: #5A4BD1;
--background: #F2F2F7;
--surface: #FFFFFF;
--text: #1C1C1E;
--text-secondary: #8E8E93;
--text-tertiary: #AEAEB2;
--danger: #FF3B30;
--danger-light: #FF6961;
--success: #34C759;
--warning: #FF9500;
--info: #5AC8FA;
--separator: rgba(60, 60, 67, 0.12);
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--radius-xl: 20px;
--shadow-sm: 0 1px 3px rgba(0,0,0,0.06);
--shadow-md: 0 4px 12px rgba(0,0,0,0.08);
--shadow-lg: 0 10px 30px rgba(0,0,0,0.12);
--font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', 'Helvetica Neue', sans-serif;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 24px;
--space-6: 32px;
--space-7: 48px;
--blur-amount: 20px;
--transition-fast: 0.2s cubic-bezier(0.25, 0.1, 0.25, 1);
--transition-normal: 0.35s cubic-bezier(0.25, 0.1, 0.25, 1);
--transition-spring: 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
html {
font-size: 16px;
scroll-behavior: smooth;
}
body {
font-family: var(--font-family);
background: var(--background);
color: var(--text);
min-height: 100vh;
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.page-container {
max-width: 960px;
margin: 0 auto;
padding-bottom: 100px;
}
/* ========== Header ========== */
.header {
position: sticky;
top: 0;
z-index: 100;
background: linear-gradient(135deg, #6C5CE7 0%, #A29BFE 50%, #7C6FF7 100%);
padding: 52px 20px 28px;
color: #fff;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: -50%;
right: -20%;
width: 300px;
height: 300px;
background: radial-gradient(circle, rgba(255,255,255,0.15) 0%, transparent 70%);
border-radius: 50%;
pointer-events: none;
}
.header::after {
content: '';
position: absolute;
bottom: -30%;
left: -10%;
width: 200px;
height: 200px;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
border-radius: 50%;
pointer-events: none;
}
.header-content {
position: relative;
z-index: 1;
}
.header-top {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.header-back {
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba(255,255,255,0.2);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: none;
color: #fff;
font-size: 18px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition-fast);
}
.header-back:active {
transform: scale(0.9);
background: rgba(255,255,255,0.3);
}
.header-actions {
display: flex;
gap: 8px;
}
.header-btn {
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba(255,255,255,0.2);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: none;
color: #fff;
font-size: 16px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition-fast);
}
.header-btn:active {
transform: scale(0.9);
background: rgba(255,255,255,0.3);
}
.header-title {
font-size: 28px;
font-weight: 700;
letter-spacing: -0.5px;
margin-bottom: 4px;
}
.header-subtitle {
font-size: 15px;
opacity: 0.85;
font-weight: 400;
}
/* ========== Language Toggle ========== */
.lang-toggle {
position: fixed;
top: 12px;
right: 12px;
z-index: 200;
display: flex;
background: rgba(255,255,255,0.25);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-radius: 20px;
padding: 3px;
border: 1px solid rgba(255,255,255,0.3);
}
.lang-toggle button {
border: none;
background: transparent;
color: rgba(255,255,255,0.8);
font-size: 12px;
font-weight: 600;
padding: 5px 12px;
border-radius: 17px;
cursor: pointer;
transition: var(--transition-fast);
font-family: var(--font-family);
}
.lang-toggle button.active {
background: #fff;
color: var(--primary);
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
/* ========== Section ========== */
.section {
padding: 0 16px;
margin-top: 20px;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
padding: 0 4px;
}
.section-title {
font-size: 20px;
font-weight: 700;
color: var(--text);
letter-spacing: -0.3px;
}
.section-action {
font-size: 14px;
color: var(--primary);
font-weight: 500;
cursor: pointer;
border: none;
background: none;
padding: 4px 8px;
border-radius: var(--radius-sm);
transition: var(--transition-fast);
font-family: var(--font-family);
}
.section-action:active {
background: rgba(108, 92, 231, 0.1);
}
/* ========== Storage Overview ========== */
.storage-card {
background: var(--surface);
border-radius: var(--radius-xl);
padding: 24px;
box-shadow: var(--shadow-sm);
overflow: hidden;
position: relative;
}
.storage-main {
display: flex;
align-items: center;
gap: 24px;
margin-bottom: 20px;
}
.ring-chart-container {
position: relative;
width: 140px;
height: 140px;
flex-shrink: 0;
}
.ring-chart-container canvas {
width: 140px;
height: 140px;
}
.ring-center-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
}
.ring-center-text .size-value {
font-size: 22px;
font-weight: 700;
color: var(--text);
line-height: 1.1;
}
.ring-center-text .size-unit {
font-size: 12px;
color: var(--text-secondary);
font-weight: 500;
}
.storage-info {
flex: 1;
}
.storage-total-label {
font-size: 13px;
color: var(--text-secondary);
margin-bottom: 4px;
}
.storage-total-value {
font-size: 36px;
font-weight: 700;
color: var(--text);
letter-spacing: -1px;
line-height: 1.1;
}
.storage-total-value .unit {
font-size: 18px;
font-weight: 500;
color: var(--text-secondary);
margin-left: 2px;
}
.storage-bar {
height: 6px;
background: var(--background);
border-radius: 3px;
margin-top: 12px;
overflow: hidden;
display: flex;
}
.storage-bar-segment {
height: 100%;
transition: width var(--transition-normal);
}
/* ========== Category Stats ========== */
.category-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 10px;
margin-top: 16px;
}
.category-item {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 14px;
background: var(--background);
border-radius: var(--radius-md);
cursor: pointer;
transition: var(--transition-fast);
border: 2px solid transparent;
}
.category-item:hover {
background: #EDEDFF;
}
.category-item.active {
border-color: var(--primary);
background: #F0EEFF;
}
.category-item:active {
transform: scale(0.97);
}
.category-icon {
width: 36px;
height: 36px;
border-radius: var(--radius-sm);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.category-icon.card { background: #E8F5E9; }
.category-icon.wallpaper { background: #E3F2FD; }
.category-icon.avatar { background: #FFF3E0; }
.category-icon.feed { background: #FCE4EC; }
.category-icon.other { background: #F3E5F5; }
.category-detail {
flex: 1;
min-width: 0;
}
.category-name {
font-size: 13px;
font-weight: 600;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.category-size {
font-size: 12px;
color: var(--text-secondary);
margin-top: 1px;
}
/* ========== Toolbar ========== */
.toolbar {
display: flex;
align-items: center;
gap: 8px;
padding: 0 16px;
margin-top: 20px;
flex-wrap: wrap;
}
.view-toggle {
display: flex;
background: var(--surface);
border-radius: var(--radius-sm);
padding: 2px;
box-shadow: var(--shadow-sm);
}
.view-toggle button {
border: none;
background: transparent;
padding: 7px 14px;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
transition: var(--transition-fast);
color: var(--text-secondary);
font-family: var(--font-family);
}
.view-toggle button.active {
background: var(--primary);
color: #fff;
box-shadow: var(--shadow-sm);
}
.sort-btn {
display: flex;
align-items: center;
gap: 4px;
padding: 7px 14px;
background: var(--surface);
border: none;
border-radius: var(--radius-sm);
font-size: 13px;
color: var(--text);
cursor: pointer;
box-shadow: var(--shadow-sm);
transition: var(--transition-fast);
font-family: var(--font-family);
font-weight: 500;
}
.sort-btn:active {
transform: scale(0.96);
}
.filter-chips {
display: flex;
gap: 6px;
flex: 1;
justify-content: flex-end;
flex-wrap: wrap;
}
.filter-chip {
padding: 6px 14px;
border-radius: 16px;
border: 1.5px solid var(--separator);
background: var(--surface);
font-size: 12px;
font-weight: 500;
color: var(--text-secondary);
cursor: pointer;
transition: var(--transition-fast);
font-family: var(--font-family);
}
.filter-chip.active {
border-color: var(--primary);
background: rgba(108, 92, 231, 0.08);
color: var(--primary);
}
.filter-chip:active {
transform: scale(0.95);
}
/* ========== Image Grid ========== */
.image-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 12px;
padding: 0 16px;
margin-top: 12px;
}
.image-card {
background: var(--surface);
border-radius: var(--radius-lg);
overflow: hidden;
box-shadow: var(--shadow-sm);
cursor: pointer;
transition: var(--transition-normal);
position: relative;
border: 2px solid transparent;
}
.image-card:hover {
box-shadow: var(--shadow-md);
transform: translateY(-2px);
}
.image-card:active {
transform: scale(0.97);
}
.image-card.selected {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(108, 92, 231, 0.2);
}
.image-card .check-badge {
position: absolute;
top: 8px;
right: 8px;
width: 24px;
height: 24px;
border-radius: 50%;
background: rgba(0,0,0,0.3);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 12px;
z-index: 2;
transition: var(--transition-fast);
border: 2px solid rgba(255,255,255,0.5);
}
.image-card.selected .check-badge {
background: var(--primary);
border-color: #fff;
}
.image-thumb {
width: 100%;
aspect-ratio: 1;
object-fit: cover;
display: block;
background: linear-gradient(135deg, #E8E8ED 0%, #D1D1D6 100%);
}
.image-thumb-placeholder {
width: 100%;
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
background: linear-gradient(135deg, #E8E8ED 0%, #D1D1D6 100%);
}
.image-meta {
padding: 10px 12px;
}
.image-name {
font-size: 12px;
font-weight: 600;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.image-info {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 3px;
}
.image-size {
font-size: 11px;
color: var(--text-secondary);
}
.image-time {
font-size: 11px;
color: var(--text-tertiary);
}
/* ========== Image List View ========== */
.image-list {
display: none;
flex-direction: column;
gap: 8px;
padding: 0 16px;
margin-top: 12px;
}
.image-list.active {
display: flex;
}
.image-grid.active {
display: grid;
}
.image-list-item {
display: flex;
align-items: center;
gap: 14px;
padding: 12px 14px;
background: var(--surface);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
cursor: pointer;
transition: var(--transition-fast);
border: 2px solid transparent;
}
.image-list-item:active {
transform: scale(0.98);
}
.image-list-item.selected {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(108, 92, 231, 0.2);
}
.image-list-thumb {
width: 56px;
height: 56px;
border-radius: var(--radius-md);
object-fit: cover;
flex-shrink: 0;
background: linear-gradient(135deg, #E8E8ED 0%, #D1D1D6 100%);
}
.image-list-thumb-placeholder {
width: 56px;
height: 56px;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
flex-shrink: 0;
background: linear-gradient(135deg, #E8E8ED 0%, #D1D1D6 100%);
}
.image-list-info {
flex: 1;
min-width: 0;
}
.image-list-name {
font-size: 15px;
font-weight: 600;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.image-list-detail {
display: flex;
align-items: center;
gap: 8px;
margin-top: 3px;
}
.image-list-size {
font-size: 13px;
color: var(--text-secondary);
}
.image-list-time {
font-size: 13px;
color: var(--text-tertiary);
}
.image-list-type {
padding: 2px 8px;
border-radius: 10px;
font-size: 11px;
font-weight: 500;
}
.type-card { background: #E8F5E9; color: #2E7D32; }
.type-wallpaper { background: #E3F2FD; color: #1565C0; }
.type-avatar { background: #FFF3E0; color: #E65100; }
.type-feed { background: #FCE4EC; color: #C62828; }
.type-other { background: #F3E5F5; color: #6A1B9A; }
.image-list-check {
width: 24px;
height: 24px;
border-radius: 50%;
border: 2px solid var(--separator);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: var(--transition-fast);
}
.image-list-item.selected .image-list-check {
background: var(--primary);
border-color: var(--primary);
color: #fff;
font-size: 12px;
}
/* ========== Action Area ========== */
.action-area {
padding: 0 16px;
margin-top: 24px;
}
.action-group {
background: var(--surface);
border-radius: var(--radius-xl);
overflow: hidden;
box-shadow: var(--shadow-sm);
}
.action-item {
display: flex;
align-items: center;
gap: 14px;
padding: 16px 20px;
cursor: pointer;
transition: var(--transition-fast);
border: none;
background: none;
width: 100%;
text-align: left;
font-family: var(--font-family);
}
.action-item:active {
background: var(--background);
}
.action-item + .action-item {
border-top: 0.5px solid var(--separator);
}
.action-icon {
width: 36px;
height: 36px;
border-radius: var(--radius-sm);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.action-icon.danger { background: #FFE5E5; }
.action-icon.warning { background: #FFF3E0; }
.action-icon.primary { background: #EDEDFF; }
.action-text {
flex: 1;
}
.action-title {
font-size: 15px;
font-weight: 500;
color: var(--text);
}
.action-desc {
font-size: 12px;
color: var(--text-secondary);
margin-top: 1px;
}
.action-arrow {
color: var(--text-tertiary);
font-size: 14px;
}
.action-item.danger-action .action-title {
color: var(--danger);
}
/* ========== Multi-select Bar ========== */
.multi-select-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: rgba(255,255,255,0.85);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-top: 0.5px solid var(--separator);
padding: 12px 20px;
padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
display: flex;
align-items: center;
justify-content: space-between;
z-index: 150;
transform: translateY(100%);
transition: transform var(--transition-normal);
}
.multi-select-bar.visible {
transform: translateY(0);
}
.select-count {
font-size: 15px;
font-weight: 600;
color: var(--text);
}
.select-actions {
display: flex;
gap: 10px;
}
.select-btn {
padding: 8px 18px;
border-radius: 20px;
border: none;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: var(--transition-fast);
font-family: var(--font-family);
}
.select-btn:active {
transform: scale(0.95);
}
.select-btn.cancel {
background: var(--background);
color: var(--text-secondary);
}
.select-btn.delete {
background: var(--danger);
color: #fff;
}
/* ========== Image Preview Modal ========== */
.preview-modal {
position: fixed;
inset: 0;
z-index: 300;
background: rgba(0,0,0,0.85);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
pointer-events: none;
transition: opacity var(--transition-normal);
}
.preview-modal.visible {
opacity: 1;
pointer-events: auto;
}
.preview-close {
position: absolute;
top: 16px;
right: 16px;
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba(255,255,255,0.2);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: none;
color: #fff;
font-size: 18px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
transition: var(--transition-fast);
}
.preview-close:active {
transform: scale(0.9);
}
.preview-image {
max-width: 90vw;
max-height: 80vh;
border-radius: var(--radius-lg);
object-fit: contain;
transition: transform var(--transition-normal);
}
.preview-info {
position: absolute;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
background: rgba(255,255,255,0.15);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-radius: var(--radius-lg);
padding: 14px 24px;
color: #fff;
text-align: center;
min-width: 240px;
}
.preview-filename {
font-size: 15px;
font-weight: 600;
margin-bottom: 4px;
}
.preview-detail {
font-size: 13px;
opacity: 0.8;
}
/* ========== Sort Dropdown ========== */
.sort-dropdown {
position: absolute;
top: 100%;
left: 16px;
margin-top: 4px;
background: rgba(255,255,255,0.95);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-radius: var(--radius-md);
box-shadow: var(--shadow-lg);
padding: 6px;
z-index: 50;
opacity: 0;
transform: translateY(-8px) scale(0.96);
pointer-events: none;
transition: var(--transition-fast);
min-width: 160px;
}
.sort-dropdown.visible {
opacity: 1;
transform: translateY(0) scale(1);
pointer-events: auto;
}
.sort-option {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 14px;
border-radius: var(--radius-sm);
font-size: 14px;
color: var(--text);
cursor: pointer;
transition: var(--transition-fast);
border: none;
background: none;
width: 100%;
text-align: left;
font-family: var(--font-family);
}
.sort-option:hover {
background: var(--background);
}
.sort-option.active {
color: var(--primary);
font-weight: 600;
}
.sort-option .check {
width: 18px;
font-size: 14px;
}
/* ========== Pull Refresh ========== */
.pull-indicator {
text-align: center;
padding: 12px;
color: var(--text-secondary);
font-size: 13px;
display: none;
}
.pull-indicator.visible {
display: block;
}
.pull-spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid var(--separator);
border-top-color: var(--primary);
border-radius: 50%;
animation: spin 0.8s linear infinite;
vertical-align: middle;
margin-right: 6px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* ========== Toast ========== */
.toast {
position: fixed;
top: 80px;
left: 50%;
transform: translateX(-50%) translateY(-20px);
background: rgba(28, 28, 30, 0.9);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
color: #fff;
padding: 12px 24px;
border-radius: var(--radius-lg);
font-size: 14px;
font-weight: 500;
z-index: 500;
opacity: 0;
pointer-events: none;
transition: var(--transition-normal);
white-space: nowrap;
}
.toast.visible {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
/* ========== Confirm Modal ========== */
.confirm-overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.4);
z-index: 400;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
pointer-events: none;
transition: opacity var(--transition-fast);
}
.confirm-overlay.visible {
opacity: 1;
pointer-events: auto;
}
.confirm-dialog {
background: rgba(255,255,255,0.95);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-radius: var(--radius-xl);
width: 280px;
overflow: hidden;
transform: scale(1.1);
transition: transform var(--transition-spring);
}
.confirm-overlay.visible .confirm-dialog {
transform: scale(1);
}
.confirm-body {
padding: 24px 20px 16px;
text-align: center;
}
.confirm-title {
font-size: 17px;
font-weight: 700;
margin-bottom: 6px;
}
.confirm-message {
font-size: 13px;
color: var(--text-secondary);
line-height: 1.5;
}
.confirm-actions {
display: flex;
border-top: 0.5px solid var(--separator);
}
.confirm-actions button {
flex: 1;
padding: 14px;
border: none;
background: none;
font-size: 17px;
font-weight: 500;
cursor: pointer;
font-family: var(--font-family);
transition: var(--transition-fast);
}
.confirm-actions button:active {
background: var(--background);
}
.confirm-actions .cancel-btn {
color: var(--text-secondary);
border-right: 0.5px solid var(--separator);
}
.confirm-actions .danger-btn {
color: var(--danger);
font-weight: 600;
}
/* ========== Empty State ========== */
.empty-state {
text-align: center;
padding: 60px 20px;
display: none;
}
.empty-state.visible {
display: block;
}
.empty-icon {
font-size: 56px;
margin-bottom: 12px;
}
.empty-title {
font-size: 18px;
font-weight: 600;
color: var(--text);
margin-bottom: 4px;
}
.empty-desc {
font-size: 14px;
color: var(--text-secondary);
}
/* ========== Responsive ========== */
@media (max-width: 640px) {
.header {
padding: 48px 16px 24px;
}
.header-title {
font-size: 24px;
}
.storage-main {
flex-direction: column;
text-align: center;
}
.ring-chart-container {
margin: 0 auto;
}
.category-grid {
grid-template-columns: repeat(2, 1fr);
}
.image-grid {
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.toolbar {
gap: 6px;
}
.filter-chips {
justify-content: flex-start;
width: 100%;
padding-top: 4px;
}
}
@media (min-width: 641px) and (max-width: 768px) {
.image-grid {
grid-template-columns: repeat(3, 1fr);
}
.category-grid {
grid-template-columns: repeat(3, 1fr);
}
}
@media (min-width: 769px) {
.image-grid {
grid-template-columns: repeat(4, 1fr);
}
.category-grid {
grid-template-columns: repeat(5, 1fr);
}
}
@media (min-width: 1024px) {
.image-grid {
grid-template-columns: repeat(5, 1fr);
}
}
/* ========== Animations ========== */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-in {
animation: fadeInUp 0.4s ease-out both;
}
.animate-in:nth-child(1) { animation-delay: 0.05s; }
.animate-in:nth-child(2) { animation-delay: 0.1s; }
.animate-in:nth-child(3) { animation-delay: 0.15s; }
.animate-in:nth-child(4) { animation-delay: 0.2s; }
.animate-in:nth-child(5) { animation-delay: 0.25s; }
.animate-in:nth-child(6) { animation-delay: 0.3s; }
.animate-in:nth-child(7) { animation-delay: 0.35s; }
.animate-in:nth-child(8) { animation-delay: 0.4s; }
</style>
</head>
<body>
<div class="page-container" id="app">
<!-- Pull Refresh Indicator -->
<div class="pull-indicator" id="pullIndicator">
<span class="pull-spinner"></span>
<span data-i18n="refreshing">刷新中...</span>
</div>
<!-- Header -->
<header class="header">
<div class="header-content">
<div class="header-top">
<button class="header-back" onclick="showToast('← 返回')"></button>
<div class="header-actions">
<button class="header-btn" onclick="toggleMultiSelect()" id="selectBtn" title="多选">☑️</button>
<button class="header-btn" onclick="refreshData()" title="刷新">🔄</button>
</div>
</div>
<h1 class="header-title" data-i18n="title">🖼️ 图片缓存管理</h1>
<p class="header-subtitle" data-i18n="subtitle">查看和管理本地缓存的图片资源</p>
</div>
</header>
<!-- Language Toggle -->
<div class="lang-toggle">
<button class="active" onclick="setLang('zh')" id="langZh">中文</button>
<button onclick="setLang('en')" id="langEn">EN</button>
</div>
<!-- Storage Overview -->
<section class="section">
<div class="section-header">
<h2 class="section-title" data-i18n="storageOverview">📊 存储概览</h2>
</div>
<div class="storage-card">
<div class="storage-main">
<div class="ring-chart-container">
<canvas id="ringChart" width="280" height="280"></canvas>
<div class="ring-center-text">
<div class="size-value" id="ringCenterValue">1.2</div>
<div class="size-unit" data-i18n="gb">GB</div>
</div>
</div>
<div class="storage-info">
<div class="storage-total-label" data-i18n="totalCache">总缓存大小</div>
<div class="storage-total-value">1.2<span class="unit" data-i18n="gb">GB</span></div>
<div class="storage-bar" id="storageBar"></div>
</div>
</div>
<div class="category-grid" id="categoryGrid"></div>
</div>
</section>
<!-- Toolbar -->
<div class="toolbar" style="position: relative;">
<div class="view-toggle">
<button class="active" onclick="setView('grid')" id="gridBtn" data-i18n="grid">网格</button>
<button onclick="setView('list')" id="listBtn" data-i18n="list">列表</button>
</div>
<button class="sort-btn" onclick="toggleSortDropdown()">
<span>↕️</span>
<span data-i18n="sort">排序</span>
</button>
<div class="filter-chips" id="filterChips"></div>
<div class="sort-dropdown" id="sortDropdown">
<button class="sort-option active" onclick="setSort('size')">
<span class="check"></span>
<span data-i18n="sortBySize">按大小</span>
</button>
<button class="sort-option" onclick="setSort('time')">
<span class="check"></span>
<span data-i18n="sortByTime">按时间</span>
</button>
<button class="sort-option" onclick="setSort('type')">
<span class="check"></span>
<span data-i18n="sortByType">按类型</span>
</button>
</div>
</div>
<!-- Image Grid -->
<div class="image-grid active" id="imageGrid"></div>
<!-- Image List -->
<div class="image-list" id="imageList"></div>
<!-- Empty State -->
<div class="empty-state" id="emptyState">
<div class="empty-icon">📭</div>
<div class="empty-title" data-i18n="emptyTitle">暂无缓存图片</div>
<div class="empty-desc" data-i18n="emptyDesc">当前筛选条件下没有找到缓存图片</div>
</div>
<!-- Action Area -->
<section class="action-area">
<div class="section-header">
<h2 class="section-title" data-i18n="cacheOps">⚙️ 缓存操作</h2>
</div>
<div class="action-group">
<button class="action-item" onclick="showConfirm('clearExpired')">
<div class="action-icon warning"></div>
<div class="action-text">
<div class="action-title" data-i18n="clearExpired">清除过期缓存</div>
<div class="action-desc" data-i18n="clearExpiredDesc">删除7天前的缓存图片约 340 MB</div>
</div>
<div class="action-arrow"></div>
</button>
<button class="action-item" onclick="showConfirm('clearByType')">
<div class="action-icon primary">📂</div>
<div class="action-text">
<div class="action-title" data-i18n="clearByType">按类型清除</div>
<div class="action-desc" data-i18n="clearByTypeDesc">选择特定类型的缓存进行清除</div>
</div>
<div class="action-arrow"></div>
</button>
<button class="action-item danger-action" onclick="showConfirm('clearAll')">
<div class="action-icon danger">🗑️</div>
<div class="action-text">
<div class="action-title" data-i18n="clearAll">清除全部缓存</div>
<div class="action-desc" data-i18n="clearAllDesc">删除所有本地缓存图片1.2 GB</div>
</div>
<div class="action-arrow"></div>
</button>
</div>
</section>
</div>
<!-- Multi-select Bar -->
<div class="multi-select-bar" id="multiSelectBar">
<div class="select-count" id="selectCount">已选择 0 项</div>
<div class="select-actions">
<button class="select-btn cancel" onclick="toggleMultiSelect()" data-i18n="cancel">取消</button>
<button class="select-btn delete" onclick="deleteSelected()" data-i18n="deleteSelected">删除选中</button>
</div>
</div>
<!-- Image Preview Modal -->
<div class="preview-modal" id="previewModal" onclick="closePreview(event)">
<button class="preview-close" onclick="closePreview()"></button>
<div class="preview-image" id="previewImage" style="width:320px;height:320px;border-radius:16px;background:linear-gradient(135deg,#E8E8ED,#D1D1D6);display:flex;align-items:center;justify-content:center;font-size:64px;">🖼️</div>
<div class="preview-info">
<div class="preview-filename" id="previewFilename">image.jpg</div>
<div class="preview-detail" id="previewDetail">256 KB · 2026-05-28</div>
</div>
</div>
<!-- Confirm Modal -->
<div class="confirm-overlay" id="confirmOverlay" onclick="closeConfirm()">
<div class="confirm-dialog" onclick="event.stopPropagation()">
<div class="confirm-body">
<div class="confirm-title" id="confirmTitle">确认操作</div>
<div class="confirm-message" id="confirmMessage">此操作不可撤销</div>
</div>
<div class="confirm-actions">
<button class="cancel-btn" onclick="closeConfirm()" data-i18n="cancel">取消</button>
<button class="danger-btn" onclick="executeConfirm()" data-i18n="confirm">确认</button>
</div>
</div>
</div>
<!-- Toast -->
<div class="toast" id="toast"></div>
<script>
/* ========== i18n ========== */
const i18n = {
zh: {
title: '🖼️ 图片缓存管理',
subtitle: '查看和管理本地缓存的图片资源',
storageOverview: '📊 存储概览',
totalCache: '总缓存大小',
gb: 'GB',
mb: 'MB',
kb: 'KB',
grid: '网格',
list: '列表',
sort: '排序',
sortBySize: '按大小',
sortByTime: '按时间',
sortByType: '按类型',
all: '全部',
cardImg: '句子卡片',
wallpaper: '壁纸',
avatar: '头像',
feed: 'Feed',
other: '其他',
cacheOps: '⚙️ 缓存操作',
clearExpired: '清除过期缓存',
clearExpiredDesc: '删除7天前的缓存图片约 340 MB',
clearByType: '按类型清除',
clearByTypeDesc: '选择特定类型的缓存进行清除',
clearAll: '清除全部缓存',
clearAllDesc: '删除所有本地缓存图片1.2 GB',
cancel: '取消',
deleteSelected: '删除选中',
selected: '已选择',
items: '项',
confirm: '确认',
refreshing: '刷新中...',
refreshDone: '刷新完成',
emptyTitle: '暂无缓存图片',
emptyDesc: '当前筛选条件下没有找到缓存图片',
clearExpiredTitle: '清除过期缓存?',
clearExpiredMsg: '将删除7天前的缓存图片此操作不可撤销。',
clearAllTitle: '清除全部缓存?',
clearAllMsg: '将删除所有本地缓存图片1.2 GB此操作不可撤销。',
clearByTypeTitle: '按类型清除缓存',
clearByTypeMsg: '将清除所选类型的全部缓存,此操作不可撤销。',
deleted: '已删除',
cleared: '已清除',
daysAgo: '天前',
today: '今天',
yesterday: '昨天',
},
en: {
title: '🖼️ Image Cache',
subtitle: 'View and manage locally cached image resources',
storageOverview: '📊 Storage Overview',
totalCache: 'Total Cache Size',
gb: 'GB',
mb: 'MB',
kb: 'KB',
grid: 'Grid',
list: 'List',
sort: 'Sort',
sortBySize: 'By Size',
sortByTime: 'By Time',
sortByType: 'By Type',
all: 'All',
cardImg: 'Card',
wallpaper: 'Wallpaper',
avatar: 'Avatar',
feed: 'Feed',
other: 'Other',
cacheOps: '⚙️ Cache Operations',
clearExpired: 'Clear Expired Cache',
clearExpiredDesc: 'Delete cached images older than 7 days (~340 MB)',
clearByType: 'Clear by Type',
clearByTypeDesc: 'Select a specific cache type to clear',
clearAll: 'Clear All Cache',
clearAllDesc: 'Delete all locally cached images (1.2 GB)',
cancel: 'Cancel',
deleteSelected: 'Delete',
selected: 'Selected',
items: 'items',
confirm: 'Confirm',
refreshing: 'Refreshing...',
refreshDone: 'Refresh complete',
emptyTitle: 'No Cached Images',
emptyDesc: 'No cached images found under current filter',
clearExpiredTitle: 'Clear Expired Cache?',
clearExpiredMsg: 'Cached images older than 7 days will be deleted. This cannot be undone.',
clearAllTitle: 'Clear All Cache?',
clearAllMsg: 'All locally cached images (1.2 GB) will be deleted. This cannot be undone.',
clearByTypeTitle: 'Clear Cache by Type',
clearByTypeMsg: 'All cache of the selected type will be cleared. This cannot be undone.',
deleted: 'Deleted',
cleared: 'Cleared',
daysAgo: 'd ago',
today: 'Today',
yesterday: 'Yesterday',
}
};
let currentLang = 'zh';
function setLang(lang) {
currentLang = lang;
document.getElementById('langZh').classList.toggle('active', lang === 'zh');
document.getElementById('langEn').classList.toggle('active', lang === 'en');
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
if (i18n[lang][key]) {
el.textContent = i18n[lang][key];
}
});
renderCategories();
renderFilterChips();
renderImages();
updateMultiSelectBar();
}
function t(key) {
return i18n[currentLang][key] || key;
}
/* ========== Mock Data ========== */
const categories = [
{ id: 'card', nameKey: 'cardImg', icon: '🃏', color: '#34C759', size: 412, count: 156 },
{ id: 'wallpaper', nameKey: 'wallpaper', icon: '🏞️', color: '#5AC8FA', size: 387, count: 42 },
{ id: 'avatar', nameKey: 'avatar', icon: '👤', color: '#FF9500', size: 128, count: 89 },
{ id: 'feed', nameKey: 'feed', icon: '📰', color: '#FF3B30', size: 204, count: 234 },
{ id: 'other', nameKey: 'other', icon: '📎', color: '#AF52DE', size: 89, count: 67 },
];
const mockImages = [
{ id: 1, name: 'sunset_card_01.jpg', type: 'card', size: 2048, time: Date.now() - 86400000 * 0.5, emoji: '🌅' },
{ id: 2, name: 'mountain_wp.png', type: 'wallpaper', size: 4096, time: Date.now() - 86400000 * 2, emoji: '🏔️' },
{ id: 3, name: 'avatar_user_42.jpg', type: 'avatar', size: 256, time: Date.now() - 86400000 * 0.2, emoji: '👤' },
{ id: 4, name: 'feed_img_89.png', type: 'feed', size: 1536, time: Date.now() - 86400000 * 5, emoji: '📸' },
{ id: 5, name: 'ocean_card_02.jpg', type: 'card', size: 3072, time: Date.now() - 86400000 * 1, emoji: '🌊' },
{ id: 6, name: 'forest_wp.jpg', type: 'wallpaper', size: 5120, time: Date.now() - 86400000 * 8, emoji: '🌲' },
{ id: 7, name: 'avatar_user_17.jpg', type: 'avatar', size: 180, time: Date.now() - 86400000 * 3, emoji: '🙂' },
{ id: 8, name: 'feed_post_44.png', type: 'feed', size: 896, time: Date.now() - 86400000 * 0.8, emoji: '🖼️' },
{ id: 9, name: 'quote_card_03.jpg', type: 'card', size: 1024, time: Date.now() - 86400000 * 10, emoji: '💬' },
{ id: 10, name: 'night_wp.png', type: 'wallpaper', size: 3584, time: Date.now() - 86400000 * 4, emoji: '🌙' },
{ id: 11, name: 'misc_icon.png', type: 'other', size: 64, time: Date.now() - 86400000 * 6, emoji: '📎' },
{ id: 12, name: 'avatar_user_99.jpg', type: 'avatar', size: 320, time: Date.now() - 86400000 * 1.5, emoji: '😎' },
{ id: 13, name: 'feed_story_12.jpg', type: 'feed', size: 2048, time: Date.now() - 86400000 * 0.1, emoji: '📖' },
{ id: 14, name: 'flower_card_04.png', type: 'card', size: 1792, time: Date.now() - 86400000 * 7, emoji: '🌸' },
{ id: 15, name: 'lake_wp.jpg', type: 'wallpaper', size: 4608, time: Date.now() - 86400000 * 9, emoji: '💧' },
{ id: 16, name: 'temp_cache.dat', type: 'other', size: 512, time: Date.now() - 86400000 * 12, emoji: '📄' },
{ id: 17, name: 'avatar_group_5.jpg', type: 'avatar', size: 448, time: Date.now() - 86400000 * 0.3, emoji: '👥' },
{ id: 18, name: 'feed_banner.png', type: 'feed', size: 2816, time: Date.now() - 86400000 * 2.5, emoji: '🎨' },
];
/* ========== State ========== */
let currentView = 'grid';
let currentSort = 'size';
let currentFilter = 'all';
let isMultiSelect = false;
let selectedIds = new Set();
let confirmAction = null;
/* ========== Ring Chart ========== */
function drawRingChart() {
const canvas = document.getElementById('ringChart');
const ctx = canvas.getContext('2d');
const dpr = window.devicePixelRatio || 1;
canvas.width = 280 * dpr;
canvas.height = 280 * dpr;
ctx.scale(dpr, dpr);
const cx = 140, cy = 140, radius = 56, lineWidth = 18;
const total = categories.reduce((s, c) => s + c.size, 0);
let startAngle = -Math.PI / 2;
ctx.clearRect(0, 0, 280, 280);
categories.forEach(cat => {
const sweep = (cat.size / total) * Math.PI * 2;
ctx.beginPath();
ctx.arc(cx, cy, radius, startAngle, startAngle + sweep);
ctx.strokeStyle = cat.color;
ctx.lineWidth = lineWidth;
ctx.lineCap = 'round';
ctx.stroke();
startAngle += sweep + 0.04;
});
const bar = document.getElementById('storageBar');
bar.innerHTML = '';
categories.forEach(cat => {
const seg = document.createElement('div');
seg.className = 'storage-bar-segment';
seg.style.width = (cat.size / total * 100) + '%';
seg.style.background = cat.color;
bar.appendChild(seg);
});
}
/* ========== Categories ========== */
function renderCategories() {
const grid = document.getElementById('categoryGrid');
grid.innerHTML = categories.map(cat => `
<div class="category-item ${currentFilter === cat.id ? 'active' : ''}" onclick="setFilter('${cat.id}')">
<div class="category-icon ${cat.id === 'card' ? 'card' : cat.id}">${cat.icon}</div>
<div class="category-detail">
<div class="category-name">${t(cat.nameKey)}</div>
<div class="category-size">${cat.size} MB · ${cat.count} ${currentLang === 'zh' ? '张' : 'imgs'}</div>
</div>
</div>
`).join('');
}
/* ========== Filter Chips ========== */
function renderFilterChips() {
const container = document.getElementById('filterChips');
const filters = [
{ id: 'all', label: t('all') },
...categories.map(c => ({ id: c.id, label: t(c.nameKey) }))
];
container.innerHTML = filters.map(f => `
<button class="filter-chip ${currentFilter === f.id ? 'active' : ''}" onclick="setFilter('${f.id}')">${f.label}</button>
`).join('');
}
/* ========== Format Helpers ========== */
function formatSize(bytes) {
if (bytes >= 1048576) return (bytes / 1048576).toFixed(1) + ' ' + t('mb');
if (bytes >= 1024) return (bytes / 1024).toFixed(0) + ' ' + t('kb');
return bytes + ' B';
}
function formatTime(ts) {
const diff = Date.now() - ts;
const days = Math.floor(diff / 86400000);
if (days === 0) return t('today');
if (days === 1) return t('yesterday');
return days + ' ' + t('daysAgo');
}
/* ========== Images ========== */
function getFilteredSortedImages() {
let imgs = [...mockImages];
if (currentFilter !== 'all') {
imgs = imgs.filter(img => img.type === currentFilter);
}
if (currentSort === 'size') {
imgs.sort((a, b) => b.size - a.size);
} else if (currentSort === 'time') {
imgs.sort((a, b) => b.time - a.time);
} else {
imgs.sort((a, b) => a.type.localeCompare(b.type));
}
return imgs;
}
function renderImages() {
const imgs = getFilteredSortedImages();
const gridEl = document.getElementById('imageGrid');
const listEl = document.getElementById('imageList');
const emptyEl = document.getElementById('emptyState');
if (imgs.length === 0) {
gridEl.style.display = 'none';
listEl.style.display = 'none';
emptyEl.classList.add('visible');
return;
}
emptyEl.classList.remove('visible');
const typeClassMap = { card: 'type-card', wallpaper: 'type-wallpaper', avatar: 'type-avatar', feed: 'type-feed', other: 'type-other' };
gridEl.innerHTML = imgs.map(img => `
<div class="image-card animate-in ${selectedIds.has(img.id) ? 'selected' : ''}"
onclick="handleImageClick(${img.id})"
oncontextmenu="enterMultiSelect(${img.id}); return false;">
${isMultiSelect ? `<div class="check-badge">${selectedIds.has(img.id) ? '✓' : ''}</div>` : ''}
<div class="image-thumb-placeholder">${img.emoji}</div>
<div class="image-meta">
<div class="image-name">${img.name}</div>
<div class="image-info">
<span class="image-size">${formatSize(img.size)}</span>
<span class="image-time">${formatTime(img.time)}</span>
</div>
</div>
</div>
`).join('');
listEl.innerHTML = imgs.map(img => `
<div class="image-list-item ${selectedIds.has(img.id) ? 'selected' : ''}"
onclick="handleImageClick(${img.id})"
oncontextmenu="enterMultiSelect(${img.id}); return false;">
<div class="image-list-thumb-placeholder">${img.emoji}</div>
<div class="image-list-info">
<div class="image-list-name">${img.name}</div>
<div class="image-list-detail">
<span class="image-list-size">${formatSize(img.size)}</span>
<span class="image-list-time">${formatTime(img.time)}</span>
<span class="image-list-type ${typeClassMap[img.type] || 'type-other'}">${t(img.type === 'card' ? 'cardImg' : img.type)}</span>
</div>
</div>
${isMultiSelect ? `<div class="image-list-check">${selectedIds.has(img.id) ? '✓' : ''}</div>` : ''}
</div>
`).join('');
gridEl.style.display = currentView === 'grid' ? 'grid' : 'none';
listEl.style.display = currentView === 'list' ? 'flex' : 'none';
}
/* ========== View Toggle ========== */
function setView(view) {
currentView = view;
document.getElementById('gridBtn').classList.toggle('active', view === 'grid');
document.getElementById('listBtn').classList.toggle('active', view === 'list');
renderImages();
}
/* ========== Sort ========== */
function toggleSortDropdown() {
document.getElementById('sortDropdown').classList.toggle('visible');
}
function setSort(sort) {
currentSort = sort;
document.querySelectorAll('.sort-option').forEach(el => {
const isActive = el.getAttribute('onclick').includes(`'${sort}'`);
el.classList.toggle('active', isActive);
el.querySelector('.check').textContent = isActive ? '✓' : '';
});
document.getElementById('sortDropdown').classList.remove('visible');
renderImages();
}
/* ========== Filter ========== */
function setFilter(filter) {
currentFilter = filter;
renderCategories();
renderFilterChips();
renderImages();
}
/* ========== Multi-Select ========== */
function toggleMultiSelect() {
isMultiSelect = !isMultiSelect;
if (!isMultiSelect) selectedIds.clear();
document.getElementById('selectBtn').textContent = isMultiSelect ? '✅' : '☑️';
document.getElementById('multiSelectBar').classList.toggle('visible', isMultiSelect);
renderImages();
updateMultiSelectBar();
}
function enterMultiSelect(id) {
if (!isMultiSelect) {
isMultiSelect = true;
document.getElementById('selectBtn').textContent = '✅';
document.getElementById('multiSelectBar').classList.add('visible');
}
toggleSelect(id);
}
function toggleSelect(id) {
if (selectedIds.has(id)) {
selectedIds.delete(id);
} else {
selectedIds.add(id);
}
renderImages();
updateMultiSelectBar();
}
function updateMultiSelectBar() {
document.getElementById('selectCount').textContent =
`${t('selected')} ${selectedIds.size} ${t('items')}`;
}
function deleteSelected() {
if (selectedIds.size === 0) {
showToast(currentLang === 'zh' ? '请先选择图片' : 'Select images first');
return;
}
showConfirm('deleteSelected');
}
/* ========== Image Click ========== */
function handleImageClick(id) {
if (isMultiSelect) {
toggleSelect(id);
return;
}
const img = mockImages.find(i => i.id === id);
if (!img) return;
openPreview(img);
}
/* ========== Preview ========== */
function openPreview(img) {
const modal = document.getElementById('previewModal');
document.getElementById('previewImage').innerHTML = `<span style="font-size:80px">${img.emoji}</span>`;
document.getElementById('previewFilename').textContent = img.name;
document.getElementById('previewDetail').textContent = `${formatSize(img.size)} · ${formatTime(img.time)}`;
modal.classList.add('visible');
}
function closePreview(e) {
if (e && e.target !== e.currentTarget && !e.target.closest('.preview-close')) return;
document.getElementById('previewModal').classList.remove('visible');
}
/* ========== Confirm ========== */
function showConfirm(action) {
confirmAction = action;
const overlay = document.getElementById('confirmOverlay');
const titleEl = document.getElementById('confirmTitle');
const msgEl = document.getElementById('confirmMessage');
if (action === 'clearExpired') {
titleEl.textContent = t('clearExpiredTitle');
msgEl.textContent = t('clearExpiredMsg');
} else if (action === 'clearAll') {
titleEl.textContent = t('clearAllTitle');
msgEl.textContent = t('clearAllMsg');
} else if (action === 'clearByType') {
titleEl.textContent = t('clearByTypeTitle');
msgEl.textContent = t('clearByTypeMsg');
} else if (action === 'deleteSelected') {
titleEl.textContent = currentLang === 'zh' ? '删除选中图片?' : 'Delete selected images?';
msgEl.textContent = currentLang === 'zh'
? `将删除 ${selectedIds.size} 张图片,此操作不可撤销。`
: `${selectedIds.size} images will be deleted. This cannot be undone.`;
}
overlay.classList.add('visible');
}
function closeConfirm() {
document.getElementById('confirmOverlay').classList.remove('visible');
confirmAction = null;
}
function executeConfirm() {
closeConfirm();
if (confirmAction === 'deleteSelected') {
selectedIds.forEach(id => {
const idx = mockImages.findIndex(i => i.id === id);
if (idx !== -1) mockImages.splice(idx, 1);
});
selectedIds.clear();
if (isMultiSelect) toggleMultiSelect();
showToast(`${t('deleted')}!`);
} else if (confirmAction === 'clearExpired') {
const cutoff = Date.now() - 7 * 86400000;
for (let i = mockImages.length - 1; i >= 0; i--) {
if (mockImages[i].time < cutoff) mockImages.splice(i, 1);
}
showToast(`${t('cleared')}!`);
} else if (confirmAction === 'clearAll') {
mockImages.length = 0;
showToast(`${t('cleared')}!`);
} else if (confirmAction === 'clearByType') {
if (currentFilter !== 'all') {
for (let i = mockImages.length - 1; i >= 0; i--) {
if (mockImages[i].type === currentFilter) mockImages.splice(i, 1);
}
}
showToast(`${t('cleared')}!`);
}
renderImages();
confirmAction = null;
}
/* ========== Refresh ========== */
function refreshData() {
const indicator = document.getElementById('pullIndicator');
indicator.classList.add('visible');
setTimeout(() => {
indicator.classList.remove('visible');
showToast(t('refreshDone'));
}, 1200);
}
/* ========== Toast ========== */
let toastTimer;
function showToast(msg) {
const toast = document.getElementById('toast');
toast.textContent = msg;
toast.classList.add('visible');
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toast.classList.remove('visible'), 2200);
}
/* ========== Close Dropdown on Outside Click ========== */
document.addEventListener('click', (e) => {
if (!e.target.closest('.sort-btn') && !e.target.closest('.sort-dropdown')) {
document.getElementById('sortDropdown').classList.remove('visible');
}
});
/* ========== Keyboard Shortcuts ========== */
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
closePreview();
closeConfirm();
document.getElementById('sortDropdown').classList.remove('visible');
}
});
/* ========== Init ========== */
function init() {
drawRingChart();
renderCategories();
renderFilterChips();
renderImages();
}
init();
</script>
</body>
</html>