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