Files
cutThenThink/design/app.html
congsh c4a77f8aa4 feat: 实现CutThenThink P0阶段核心功能
项目初始化
- 创建完整项目结构(src/, data/, docs/, examples/, tests/)
- 配置requirements.txt依赖
- 创建.gitignore

P0基础框架
- 数据库模型:Record模型,6种分类类型
- 配置管理:YAML配置,支持AI/OCR/云存储/UI配置
- OCR模块:PaddleOCR本地识别,支持云端扩展
- AI模块:支持OpenAI/Claude/通义/Ollama,6种分类
- 存储模块:完整CRUD,搜索,统计,导入导出
- 主窗口框架:侧边栏导航,米白配色方案
- 图片处理:截图/剪贴板/文件选择/图片预览
- 处理流程整合:OCR→AI→存储串联,Markdown展示,剪贴板复制
- 分类浏览:卡片网格展示,分类筛选,搜索,详情查看

技术栈
- PyQt6 + SQLAlchemy + PaddleOCR + OpenAI/Claude SDK
- 共47个Python文件,4000+行代码

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 18:21:31 +08:00

1091 lines
36 KiB
HTML
Raw Permalink 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>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CutThenThink</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap');
:root {
/* 明亮配色方案 */
--bg-primary: #faf8f5;
--bg-secondary: #ffffff;
--bg-card: #ffffff;
--accent: #ff6b6b;
--accent-light: #ff8787;
--accent-soft: #ffe0e0;
--success: #51cf66;
--warning: #ffd43b;
--text-primary: #2d3436;
--text-secondary: #868e96;
--border: #e9ecef;
--border-light: #f1f3f5;
--shadow: 0 2px 12px rgba(0,0,0,0.06);
--shadow-hover: 0 8px 24px rgba(0,0,0,0.1);
/* 分类颜色 */
--color-todo: #ff6b6b;
--color-note: #4dabf7;
--color-idea: #ffd43b;
--color-ref: #51cf66;
--color-funny: #ff922b;
--color-text: #adb5bd;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Noto Sans SC', sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
height: 100vh;
overflow: hidden;
}
.app {
display: flex;
height: 100vh;
}
/* 侧边栏 */
.sidebar {
width: 240px;
background: var(--bg-secondary);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
padding: 20px 16px;
}
.logo {
display: flex;
align-items: center;
gap: 10px;
padding: 0 8px 24px;
border-bottom: 1px solid var(--border);
margin-bottom: 20px;
}
.logo-icon {
width: 36px;
height: 36px;
background: var(--accent);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
}
.logo-text {
font-family: 'Space Grotesk', sans-serif;
font-weight: 700;
font-size: 18px;
color: var(--text-primary);
}
.nav-section {
margin-bottom: 24px;
}
.nav-title {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-secondary);
padding: 0 8px;
margin-bottom: 8px;
}
.nav-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
font-size: 14px;
color: var(--text-secondary);
}
.nav-item:hover {
background: var(--bg-primary);
color: var(--text-primary);
}
.nav-item.active {
background: var(--accent-soft);
color: var(--accent);
font-weight: 500;
}
.nav-icon {
font-size: 18px;
width: 20px;
text-align: center;
}
.nav-count {
margin-left: auto;
font-size: 12px;
padding: 2px 6px;
background: var(--border);
border-radius: 10px;
}
.sidebar-footer {
margin-top: auto;
padding-top: 16px;
border-top: 1px solid var(--border);
}
/* 主内容区 */
.main {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.header {
height: 60px;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
}
.header-title {
font-size: 18px;
font-weight: 600;
}
.header-actions {
display: flex;
gap: 12px;
}
.btn {
padding: 8px 16px;
border-radius: 8px;
border: none;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
font-family: inherit;
display: flex;
align-items: center;
gap: 6px;
}
.btn-secondary {
background: var(--bg-primary);
color: var(--text-secondary);
}
.btn-secondary:hover {
background: var(--border);
}
.btn-primary {
background: var(--accent);
color: white;
}
.btn-primary:hover {
background: var(--accent-light);
transform: translateY(-1px);
box-shadow: var(--shadow-hover);
}
/* 内容页面 */
.content {
flex: 1;
overflow-y: auto;
padding: 24px;
}
/* 卡片网格 */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 16px;
}
.card {
background: var(--bg-card);
border-radius: 12px;
border: 1px solid var(--border);
padding: 16px;
transition: all 0.2s;
cursor: pointer;
}
.card:hover {
border-color: var(--accent-light);
box-shadow: var(--shadow);
transform: translateY(-2px);
}
.card-image {
width: 100%;
height: 160px;
border-radius: 8px;
overflow: hidden;
background: var(--bg-primary);
margin-bottom: 12px;
}
.card-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.card-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 8px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
margin-bottom: 8px;
}
.badge-todo { background: #ffe0e0; color: var(--color-todo); }
.badge-note { background: #e7f5ff; color: var(--color-note); }
.badge-idea { background: #fff9db; color: #f59f00; }
.badge-ref { background: #d3f9d8; color: var(--color-ref); }
.badge-funny { background: #ffe8cc; color: var(--color-funny); }
.badge-text { background: #f8f9fa; color: var(--color-text); }
.card-title {
font-size: 14px;
color: var(--text-secondary);
margin-bottom: 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card-preview {
font-size: 13px;
color: var(--text-primary);
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--border-light);
}
.card-date {
font-size: 12px;
color: var(--text-secondary);
}
.card-actions {
display: flex;
gap: 8px;
}
.card-btn {
padding: 4px 10px;
font-size: 12px;
border-radius: 6px;
border: 1px solid var(--border);
background: transparent;
color: var(--text-secondary);
cursor: pointer;
transition: all 0.2s;
}
.card-btn:hover {
border-color: var(--accent);
color: var(--accent);
}
/* 批量上传页面 */
.batch-upload {
display: flex;
gap: 20px;
height: 100%;
}
.upload-zone {
flex: 0 0 300px;
background: var(--bg-card);
border-radius: 16px;
border: 2px dashed var(--border);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
transition: all 0.3s;
cursor: pointer;
}
.upload-zone:hover {
border-color: var(--accent);
background: var(--accent-soft);
}
.upload-icon {
width: 64px;
height: 64px;
background: var(--accent-soft);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
margin-bottom: 16px;
}
.upload-text {
font-size: 14px;
color: var(--text-secondary);
text-align: center;
}
.upload-text strong {
color: var(--accent);
}
.upload-hint {
font-size: 12px;
color: var(--text-secondary);
margin-top: 8px;
}
.image-list {
flex: 1;
display: flex;
flex-direction: column;
gap: 12px;
overflow-y: auto;
}
.image-item {
display: flex;
gap: 12px;
background: var(--bg-card);
border-radius: 12px;
padding: 12px;
border: 1px solid var(--border);
}
.image-item input[type="checkbox"] {
width: 18px;
height: 18px;
margin-top: 4px;
accent-color: var(--accent);
}
.image-item-preview {
width: 60px;
height: 60px;
border-radius: 8px;
overflow: hidden;
background: var(--bg-primary);
flex-shrink: 0;
}
.image-item-preview img {
width: 100%;
height: 100%;
object-fit: cover;
}
.image-item-info {
flex: 1;
}
.image-item-name {
font-size: 14px;
font-weight: 500;
margin-bottom: 4px;
}
.image-item-meta {
font-size: 12px;
color: var(--text-secondary);
}
.image-item-remove {
padding: 4px 10px;
font-size: 12px;
border-radius: 6px;
border: 1px solid var(--border);
background: transparent;
color: var(--text-secondary);
cursor: pointer;
}
.image-item-remove:hover {
border-color: var(--accent);
color: var(--accent);
}
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
color: var(--text-secondary);
}
.empty-icon {
font-size: 64px;
margin-bottom: 16px;
opacity: 0.6;
}
.empty-text {
font-size: 16px;
}
/* 设置页面 */
.settings-page {
max-width: 600px;
}
.settings-section {
background: var(--bg-card);
border-radius: 12px;
padding: 20px;
margin-bottom: 16px;
border: 1px solid var(--border);
}
.settings-section h3 {
font-size: 16px;
font-weight: 600;
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.settings-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid var(--border-light);
}
.settings-row:last-child {
border-bottom: none;
}
.settings-label {
font-size: 14px;
color: var(--text-primary);
}
.settings-desc {
font-size: 12px;
color: var(--text-secondary);
margin-top: 2px;
}
.settings-input {
padding: 8px 12px;
border: 1px solid var(--border);
border-radius: 6px;
font-size: 14px;
width: 200px;
font-family: inherit;
}
.settings-select {
padding: 8px 12px;
border: 1px solid var(--border);
border-radius: 6px;
font-size: 14px;
background: white;
font-family: inherit;
}
/* 页面切换 */
.page {
display: none;
}
.page.active {
display: block;
}
/* 详情弹窗 */
.modal {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.5);
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal.active {
display: flex;
}
.modal-content {
background: white;
border-radius: 16px;
width: 90%;
max-width: 600px;
max-height: 80vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
.modal-header {
padding: 20px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
}
.modal-header h2 {
font-size: 18px;
font-weight: 600;
}
.modal-close {
width: 32px;
height: 32px;
border-radius: 8px;
border: none;
background: var(--bg-primary);
cursor: pointer;
font-size: 18px;
display: flex;
align-items: center;
justify-content: center;
}
.modal-body {
padding: 20px;
overflow-y: auto;
}
.modal-image {
width: 100%;
border-radius: 12px;
margin-bottom: 16px;
}
.modal-markdown {
background: var(--bg-primary);
border-radius: 12px;
padding: 16px;
font-size: 14px;
line-height: 1.7;
}
.modal-footer {
padding: 16px 20px;
border-top: 1px solid var(--border);
display: flex;
gap: 12px;
justify-content: flex-end;
}
/* 进度条 */
.progress-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 3px;
background: var(--accent);
transform-origin: left;
transform: scaleX(0);
transition: transform 0.3s;
z-index: 1000;
}
.progress-bar.active {
animation: progress 2s ease-in-out infinite;
}
@keyframes progress {
0% { transform: scaleX(0); }
50% { transform: scaleX(0.5); }
100% { transform: scaleX(1); }
}
/* 状态标签 */
.status-badge {
padding: 4px 8px;
border-radius: 6px;
font-size: 11px;
font-weight: 500;
}
.status-pending { background: var(--bg-primary); color: var(--text-secondary); }
.status-processing { background: var(--accent-soft); color: var(--accent); }
.status-done { background: #d3f9d8; color: var(--success); }
.status-error { background: #ffe3e3; color: var(--accent); }
#file-input { display: none; }
</style>
</head>
<body>
<div class="progress-bar" id="progress-bar"></div>
<div class="app">
<!-- 侧边栏 -->
<aside class="sidebar">
<div class="logo">
<div class="logo-icon">✂️</div>
<div class="logo-text">CutThenThink</div>
</div>
<nav class="nav-section">
<div class="nav-title">浏览</div>
<div class="nav-item active" data-page="all" onclick="switchPage('all')">
<span class="nav-icon">📋</span>
<span>全部</span>
<span class="nav-count">12</span>
</div>
<div class="nav-item" data-page="todo" onclick="switchPage('todo')">
<span class="nav-icon"></span>
<span>待办事项</span>
<span class="nav-count">4</span>
</div>
<div class="nav-item" data-page="note" onclick="switchPage('note')">
<span class="nav-icon">📝</span>
<span>笔记</span>
<span class="nav-count">3</span>
</div>
<div class="nav-item" data-page="idea" onclick="switchPage('idea')">
<span class="nav-icon">💡</span>
<span>灵感</span>
<span class="nav-count">2</span>
</div>
<div class="nav-item" data-page="ref" onclick="switchPage('ref')">
<span class="nav-icon">📚</span>
<span>参考资料</span>
<span class="nav-count">2</span>
</div>
<div class="nav-item" data-page="funny" onclick="switchPage('funny')">
<span class="nav-icon">😄</span>
<span>搞笑文案</span>
<span class="nav-count">1</span>
</div>
<div class="nav-item" data-page="text" onclick="switchPage('text')">
<span class="nav-icon">📄</span>
<span>纯文本</span>
<span class="nav-count">0</span>
</div>
</nav>
<nav class="nav-section">
<div class="nav-title">操作</div>
<div class="nav-item" data-page="upload" onclick="switchPage('upload')">
<span class="nav-icon">📤</span>
<span>批量上传</span>
</div>
<div class="nav-item" data-page="settings" onclick="switchPage('settings')">
<span class="nav-icon">⚙️</span>
<span>设置</span>
</div>
</nav>
<div class="sidebar-footer">
<div class="nav-item">
<span class="nav-icon">🔄</span>
<span>同步状态</span>
<span class="status-badge status-done">已同步</span>
</div>
</div>
</aside>
<!-- 主内容 -->
<main class="main">
<header class="header">
<h1 class="header-title" id="page-title">全部</h1>
<div class="header-actions" id="header-actions">
<button class="btn btn-secondary" onclick="alert('快速截图')">📷 截图</button>
<button class="btn btn-primary"> 新建</button>
</div>
</header>
<div class="content">
<!-- 全部/分类页面 -->
<div class="page active" id="page-all">
<div class="card-grid" id="card-grid">
<!-- 示例卡片 -->
<div class="card" onclick="openDetail(1)">
<div class="card-image">
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='320' height='160'%3E%3Crect fill='%23f1f3f5' width='320' height='160'/%3E%3Ctext x='50%25' y='50%25' text-anchor='middle' dy='.3em' fill='%23adb5bd' font-size='24'%3E📸%3C/text%3E%3C/svg%3E" alt="">
</div>
<span class="card-badge badge-todo">✅ 待办事项</span>
<div class="card-title">项目任务清单截图</div>
<div class="card-preview">## 待办事项\n- [ ] 完成界面设计\n- [ ] 编写 API 文档\n- [ ] 测试 OCR 功能...</div>
<div class="card-footer">
<span class="card-date">今天 14:30</span>
<div class="card-actions">
<button class="card-btn">复制</button>
</div>
</div>
</div>
<div class="card" onclick="openDetail(2)">
<div class="card-image">
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='320' height='160'%3E%3Crect fill='%23e7f5ff' width='320' height='160'/%3E%3Ctext x='50%25' y='50%25' text-anchor='middle' dy='.3em' fill='%234dabf7' font-size='24'%3E💭%3C/text%3E%3C/svg%3E" alt="">
</div>
<span class="card-badge badge-note">📝 笔记</span>
<div class="card-title">技术文章要点</div>
<div class="card-preview">Python 异步编程的关键点:\n1. async/await 语法\n2. 事件循环机制\n3. 协程的概念...</div>
<div class="card-footer">
<span class="card-date">昨天 09:15</span>
<div class="card-actions">
<button class="card-btn">复制</button>
</div>
</div>
</div>
<div class="card" onclick="openDetail(3)">
<div class="card-image">
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='320' height='160'%3E%3Crect fill='%23fff9db' width='320' height='160'/%3E%3Ctext x='50%25' y='50%25' text-anchor='middle' dy='.3em' fill='%23ffd43b' font-size='24'%3E💡%3C/text%3E%3C/svg%3E" alt="">
</div>
<span class="card-badge badge-idea">💡 灵感</span>
<div class="card-title">产品创意</div>
<div class="card-preview">做一个能自动整理桌面的工具,通过 AI 识别文件类型并分类...</div>
<div class="card-footer">
<span class="card-date">2天前</span>
<div class="card-actions">
<button class="card-btn">复制</button>
</div>
</div>
</div>
</div>
</div>
<!-- 批量上传页面 -->
<div class="page" id="page-upload">
<div class="batch-upload">
<div class="upload-zone" id="upload-zone">
<div class="upload-icon">📁</div>
<div class="upload-text"><strong>点击选择</strong> 或拖放图片</div>
<div class="upload-hint">支持 PNG, JPG, WEBP</div>
</div>
<div class="image-list" id="upload-list">
<div class="empty-state">
<div class="empty-icon">📷</div>
<div class="empty-text">暂无图片,请从左侧添加</div>
</div>
</div>
</div>
</div>
<!-- 设置页面 -->
<div class="page settings-page" id="page-settings">
<div class="settings-section">
<h3>🤖 AI 配置</h3>
<div class="settings-row">
<div>
<div class="settings-label">AI 提供商</div>
<div class="settings-desc">选择用于分类和生成计划的 AI 服务</div>
</div>
<select class="settings-select">
<option>OpenAI (GPT-4)</option>
<option>Anthropic (Claude)</option>
<option>通义千问</option>
<option>本地模型 (Ollama)</option>
</select>
</div>
<div class="settings-row">
<div>
<div class="settings-label">API Key</div>
<div class="settings-desc">您的 API 密钥</div>
</div>
<input type="password" class="settings-input" placeholder="sk-***">
</div>
<div class="settings-row">
<div>
<div class="settings-label">模型名称</div>
<div class="settings-desc">要使用的模型</div>
</div>
<input type="text" class="settings-input" value="gpt-4o">
</div>
</div>
<div class="settings-section">
<h3>🔍 OCR 配置</h3>
<div class="settings-row">
<div>
<div class="settings-label">OCR 提供商</div>
<div class="settings-desc">优先使用的 OCR 服务</div>
</div>
<select class="settings-select">
<option>本地 PaddleOCR</option>
<option>百度 OCR</option>
<option>腾讯 OCR</option>
<option>自定义 API</option>
</select>
</div>
<div class="settings-row">
<div>
<div class="settings-label">失败时降级到本地</div>
<div class="settings-desc">云端 OCR 失败时自动使用本地 OCR</div>
</div>
<input type="checkbox" checked>
</div>
</div>
<div class="settings-section">
<h3>☁️ 云存储配置(可选)</h3>
<div class="settings-row">
<div>
<div class="settings-label">存储类型</div>
<div class="settings-desc">选择云存储服务</div>
</div>
<select class="settings-select">
<option>不使用云存储</option>
<option>WebDAV</option>
<option>阿里云 OSS</option>
<option>AWS S3</option>
</select>
</div>
</div>
<div class="settings-section">
<h3>📝 提示词模板</h3>
<div class="settings-row">
<div>
<div class="settings-label">分类提示词</div>
<div class="settings-desc">自定义 AI 分类规则</div>
</div>
<button class="btn btn-secondary">编辑</button>
</div>
</div>
</div>
</div>
</main>
</div>
<!-- 详情弹窗 -->
<div class="modal" id="detail-modal">
<div class="modal-content">
<div class="modal-header">
<h2>详情</h2>
<button class="modal-close" onclick="closeDetail()"></button>
</div>
<div class="modal-body">
<img src="" class="modal-image" id="modal-image" alt="">
<div class="modal-markdown" id="modal-content">
## 待办事项
- [ ] 完成界面设计
- [ ] 编写 API 文档
- [ ] 测试 OCR 功能
---
*AI 分析于 2024-01-15 14:30*
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeDetail()">关闭</button>
<button class="btn btn-primary" onclick="copyMarkdown()">📋 复制 Markdown</button>
</div>
</div>
</div>
<input type="file" id="file-input" multiple accept="image/png,image/jpeg,image/webp">
<script>
let uploadImages = [];
// 页面切换
function switchPage(page) {
// 更新导航
document.querySelectorAll('.nav-item').forEach(item => {
item.classList.remove('active');
});
document.querySelector(`[data-page="${page}"]`).classList.add('active');
// 更新页面
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
document.getElementById(`page-${page}`).classList.add('active');
// 更新标题
const titles = {
all: '全部',
todo: '待办事项',
note: '笔记',
idea: '灵感',
ref: '参考资料',
funny: '搞笑文案',
text: '纯文本',
upload: '批量上传',
settings: '设置'
};
document.getElementById('page-title').textContent = titles[page] || '全部';
// 更新头部按钮
const headerActions = document.getElementById('header-actions');
if (page === 'upload') {
headerActions.innerHTML = `
<button class="btn btn-secondary" onclick="clearUpload()">清空</button>
<button class="btn btn-primary" onclick="processUpload()">开始处理</button>
`;
} else if (page === 'settings') {
headerActions.innerHTML = '';
} else {
headerActions.innerHTML = `
<button class="btn btn-secondary" onclick="alert('快速截图')">📷 截图</button>
<button class="btn btn-primary"> 新建</button>
`;
}
}
// 详情弹窗
function openDetail(id) {
document.getElementById('detail-modal').classList.add('active');
}
function closeDetail() {
document.getElementById('detail-modal').classList.remove('active');
}
function copyMarkdown() {
const content = document.getElementById('modal-content').textContent;
navigator.clipboard.writeText(content);
alert('已复制到剪贴板');
}
// 批量上传
const uploadZone = document.getElementById('upload-zone');
const fileInput = document.getElementById('file-input');
const uploadList = document.getElementById('upload-list');
uploadZone.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (e) => handleFiles(e.target.files));
uploadZone.addEventListener('dragover', (e) => {
e.preventDefault();
uploadZone.style.borderColor = 'var(--accent)';
});
uploadZone.addEventListener('dragleave', () => {
uploadZone.style.borderColor = 'var(--border)';
});
uploadZone.addEventListener('drop', (e) => {
e.preventDefault();
uploadZone.style.borderColor = 'var(--border)';
handleFiles(e.dataTransfer.files);
});
function handleFiles(files) {
for (const file of files) {
if (file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = (e) => {
uploadImages.push({
name: file.name,
size: formatSize(file.size),
preview: e.target.result
});
renderUploadList();
};
reader.readAsDataURL(file);
}
}
}
function formatSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
function renderUploadList() {
if (uploadImages.length === 0) {
uploadList.innerHTML = `
<div class="empty-state">
<div class="empty-icon">📷</div>
<div class="empty-text">暂无图片,请从左侧添加</div>
</div>
`;
return;
}
uploadList.innerHTML = uploadImages.map((img, i) => `
<div class="image-item">
<input type="checkbox" checked>
<div class="image-item-preview">
<img src="${img.preview}" alt="${img.name}">
</div>
<div class="image-item-info">
<div class="image-item-name">${img.name}</div>
<div class="image-item-meta">${img.size}</div>
</div>
<button class="image-item-remove" onclick="removeUpload(${i})">移除</button>
</div>
`).join('');
}
function removeUpload(index) {
uploadImages.splice(index, 1);
renderUploadList();
}
function clearUpload() {
uploadImages = [];
renderUploadList();
}
function processUpload() {
if (uploadImages.length === 0) {
alert('请先添加图片');
return;
}
document.getElementById('progress-bar').classList.add('active');
setTimeout(() => {
document.getElementById('progress-bar').classList.remove('active');
alert('处理完成!');
uploadImages = [];
renderUploadList();
}, 2000);
}
// 点击模态框背景关闭
document.getElementById('detail-modal').addEventListener('click', (e) => {
if (e.target.id === 'detail-modal') {
closeDetail();
}
});
</script>
</body>
</html>