u
This commit is contained in:
1 parent
04491e14a3
commit
12fdf320c2
12 files changed
+2487
-818
No files matched your search
@@ -2,158 +2,375 @@
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>多空间 App 原型</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<title>附件组件(最终完整版)</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial;
|
||||
background: #f5f6f8;
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* 左 */
|
||||
.left {
|
||||
width: 200px;
|
||||
background: #fff;
|
||||
border-right: 1px solid #eee;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* 中 */
|
||||
.center {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.grid {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 12px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 卡片 */
|
||||
.card {
|
||||
position: relative;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
border: 1px solid transparent;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
border-color: #1677ff;
|
||||
}
|
||||
|
||||
.card.selected {
|
||||
background: #eaf2ff;
|
||||
border-color: #1677ff;
|
||||
}
|
||||
|
||||
/* checkbox */
|
||||
.check {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
/* 右上角 */
|
||||
.top-actions {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* 状态 */
|
||||
.status {
|
||||
background: #ff4d4f;
|
||||
color: #fff;
|
||||
font-size: 10px;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* 删除按钮 */
|
||||
.delete-btn {
|
||||
border: none;
|
||||
background: #fff;
|
||||
color: #ff4d4f;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
padding: 2px 4px;
|
||||
opacity: 0;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.card:hover .delete-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.delete-btn:hover {
|
||||
background: #fff1f0;
|
||||
}
|
||||
|
||||
/* 缩略图 */
|
||||
.thumb {
|
||||
height: 100px;
|
||||
background: #f0f2f5;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
}
|
||||
|
||||
.thumb img {
|
||||
width:100%;
|
||||
height:100%;
|
||||
object-fit:cover;
|
||||
}
|
||||
|
||||
/* info */
|
||||
.info {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.meta {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
/* 操作 */
|
||||
.actions {
|
||||
display: flex;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.actions button {
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: #fff;
|
||||
padding: 6px 0;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.actions button:hover {
|
||||
background: #f5f6f8;
|
||||
}
|
||||
|
||||
/* 更多菜单 */
|
||||
.more-menu {
|
||||
position: absolute;
|
||||
bottom: 38px;
|
||||
right: 6px;
|
||||
background: #fff;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
display: none;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.more-menu div {
|
||||
padding: 6px 10px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.more-menu div:hover {
|
||||
background: #f5f6f8;
|
||||
}
|
||||
|
||||
/* 右侧 */
|
||||
.right {
|
||||
width: 300px;
|
||||
background: #fff;
|
||||
border-left: 1px solid #eee;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.preview {
|
||||
height: 200px;
|
||||
background: #f0f2f5;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
}
|
||||
|
||||
.preview img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-100">
|
||||
<body>
|
||||
|
||||
<div class="max-w-md mx-auto h-screen flex flex-col bg-gray-100">
|
||||
<div class="layout">
|
||||
|
||||
<!-- 顶部 -->
|
||||
<div class="bg-white shadow px-4 py-3 flex justify-between items-center">
|
||||
<div id="pageTitle" class="font-semibold text-lg">首页</div>
|
||||
<div class="left">分类区域(可扩展)</div>
|
||||
|
||||
<button onclick="openSpaceModal()"
|
||||
class="text-sm bg-gray-100 px-3 py-1 rounded-lg">
|
||||
<span id="currentSpace">空间A</span>
|
||||
</button>
|
||||
<div class="center">
|
||||
<div class="grid" id="grid"></div>
|
||||
</div>
|
||||
|
||||
<!-- 内容 -->
|
||||
<div class="flex-1 overflow-auto p-4" id="content">
|
||||
|
||||
<!-- 首页 -->
|
||||
<div id="homePage">
|
||||
<div class="h-32 bg-gradient-to-r from-blue-500 to-purple-500 rounded-2xl mb-4"></div>
|
||||
|
||||
<!-- 功能 -->
|
||||
<div class="grid grid-cols-4 gap-4 mb-4">
|
||||
<div class="bg-white p-3 rounded-xl shadow text-center">任务</div>
|
||||
<div class="bg-white p-3 rounded-xl shadow text-center">聊天</div>
|
||||
<div class="bg-white p-3 rounded-xl shadow text-center">文件</div>
|
||||
<div class="bg-white p-3 rounded-xl shadow text-center">报表</div>
|
||||
<div class="right">
|
||||
<div class="preview" id="previewBox">暂无预览</div>
|
||||
<div id="detail">请选择文件</div>
|
||||
</div>
|
||||
|
||||
<!-- 最近 -->
|
||||
<div class="bg-white p-4 rounded-xl shadow">
|
||||
<div class="font-semibold mb-2">最近内容</div>
|
||||
<div class="text-sm text-gray-500">任务:优化UI设计</div>
|
||||
<div class="text-sm text-gray-500">消息:项目讨论</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 消息 -->
|
||||
<div id="messagePage" class="hidden space-y-3">
|
||||
<div class="bg-white p-4 rounded-xl shadow">用户1:你好</div>
|
||||
<div class="bg-white p-4 rounded-xl shadow">用户2:项目OK</div>
|
||||
<div class="bg-white p-4 rounded-xl shadow">用户3:收到</div>
|
||||
</div>
|
||||
|
||||
<!-- 我的 -->
|
||||
<div id="minePage" class="hidden space-y-4">
|
||||
|
||||
<div class="bg-white p-4 rounded-xl shadow flex items-center gap-3">
|
||||
<div class="w-12 h-12 bg-gray-300 rounded-full"></div>
|
||||
<div>
|
||||
<div class="font-semibold">张三</div>
|
||||
<div class="text-xs text-gray-400">ID: 10001</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-4 rounded-xl shadow">
|
||||
<div class="font-semibold mb-2">当前空间</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span id="currentSpace2">空间A</span>
|
||||
<button onclick="openSpaceModal()" class="text-blue-500 text-sm">切换</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-4 rounded-xl shadow space-y-2">
|
||||
<div class="font-semibold">空间操作</div>
|
||||
<button class="w-full bg-blue-500 text-white py-2 rounded-lg">创建空间</button>
|
||||
<button class="w-full bg-green-500 text-white py-2 rounded-lg">加入空间</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 底部导航 -->
|
||||
<div class="bg-white border-t flex justify-around py-3">
|
||||
<button onclick="switchPage('home')" id="navHome" class="text-blue-500">首页</button>
|
||||
<button onclick="switchPage('message')" id="navMessage" class="text-gray-500">消息</button>
|
||||
<button onclick="switchPage('mine')" id="navMine" class="text-gray-500">我的</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 空间弹层 -->
|
||||
<div id="spaceModal"
|
||||
class="fixed inset-0 bg-black/40 hidden flex items-end">
|
||||
|
||||
<div class="bg-white w-full rounded-t-2xl p-4">
|
||||
|
||||
<div class="text-center font-semibold mb-4">选择空间</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div onclick="switchSpace('空间A')" class="p-3 bg-gray-100 rounded-lg cursor-pointer">空间A</div>
|
||||
<div onclick="switchSpace('空间B')" class="p-3 bg-gray-100 rounded-lg cursor-pointer">空间B</div>
|
||||
<div onclick="switchSpace('空间C')" class="p-3 bg-gray-100 rounded-lg cursor-pointer">空间C</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 space-y-2">
|
||||
<button class="w-full bg-blue-500 text-white py-2 rounded-lg">创建空间</button>
|
||||
<button class="w-full bg-green-500 text-white py-2 rounded-lg">加入空间</button>
|
||||
</div>
|
||||
|
||||
<button onclick="closeSpaceModal()" class="w-full mt-3 text-gray-500">关闭</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentPage = "home";
|
||||
let currentSpace = "空间A";
|
||||
const files = [
|
||||
{ id: 1, name: "合同.pdf", type: "doc", size: "800KB", date: "2026-04-25" },
|
||||
{ id: 2, name: "设计图.png", type: "img", url: "https://picsum.photos/400/300?1", size: "1.2MB", date: "2026-04-24", status: "未上传" }
|
||||
];
|
||||
|
||||
function switchPage(page) {
|
||||
currentPage = page;
|
||||
let selected = new Set();
|
||||
|
||||
document.getElementById("homePage").classList.add("hidden");
|
||||
document.getElementById("messagePage").classList.add("hidden");
|
||||
document.getElementById("minePage").classList.add("hidden");
|
||||
/* 渲染 */
|
||||
function render() {
|
||||
const grid = document.getElementById("grid");
|
||||
grid.innerHTML = "";
|
||||
|
||||
document.getElementById(page + "Page").classList.remove("hidden");
|
||||
files.forEach(f => {
|
||||
const card = document.createElement("div");
|
||||
card.className = "card" + (selected.has(f.id) ? " selected" : "");
|
||||
|
||||
document.getElementById("pageTitle").innerText =
|
||||
page === "home" ? "首页" :
|
||||
page === "message" ? "消息" : "我的";
|
||||
card.onclick = () => toggleSelect(f.id);
|
||||
|
||||
// 导航高亮
|
||||
["navHome","navMessage","navMine"].forEach(id=>{
|
||||
document.getElementById(id).classList.remove("text-blue-500");
|
||||
document.getElementById(id).classList.add("text-gray-500");
|
||||
/* checkbox */
|
||||
const check = document.createElement("input");
|
||||
check.type = "checkbox";
|
||||
check.className = "check";
|
||||
check.checked = selected.has(f.id);
|
||||
check.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
toggleSelect(f.id);
|
||||
};
|
||||
|
||||
/* 右上角 */
|
||||
const top = document.createElement("div");
|
||||
top.className = "top-actions";
|
||||
|
||||
if (f.status) {
|
||||
const st = document.createElement("div");
|
||||
st.className = "status";
|
||||
st.innerText = f.status;
|
||||
top.appendChild(st);
|
||||
}
|
||||
|
||||
const del = document.createElement("button");
|
||||
del.className = "delete-btn";
|
||||
del.innerText = "🗑";
|
||||
del.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
if (confirm("确定删除?")) deleteFile(f.id);
|
||||
};
|
||||
|
||||
top.appendChild(del);
|
||||
|
||||
/* thumb */
|
||||
const thumb = document.createElement("div");
|
||||
thumb.className = "thumb";
|
||||
thumb.innerHTML = f.type === "img"
|
||||
? `<img src="${f.url}">`
|
||||
: "📄";
|
||||
|
||||
/* info */
|
||||
const info = document.createElement("div");
|
||||
info.className = "info";
|
||||
info.innerHTML = `
|
||||
<div class="name">${f.name}</div>
|
||||
<div class="meta">${f.date} · ${f.size}</div>
|
||||
`;
|
||||
|
||||
/* actions */
|
||||
const actions = document.createElement("div");
|
||||
actions.className = "actions";
|
||||
|
||||
const btnView = createBtn("预览", () => preview(f));
|
||||
const btnDown = createBtn("下载", () => alert("下载 " + f.name));
|
||||
|
||||
/* 更多 */
|
||||
const btnMore = document.createElement("button");
|
||||
btnMore.innerText = "⋯";
|
||||
|
||||
const menu = document.createElement("div");
|
||||
menu.className = "more-menu";
|
||||
|
||||
const rename = document.createElement("div");
|
||||
rename.innerText = "重命名";
|
||||
rename.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
const n = prompt("新文件名", f.name);
|
||||
if (n) f.name = n;
|
||||
render();
|
||||
};
|
||||
|
||||
const remark = document.createElement("div");
|
||||
remark.innerText = "备注";
|
||||
remark.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
const r = prompt("备注", f.remark || "");
|
||||
f.remark = r;
|
||||
};
|
||||
|
||||
menu.append(rename, remark);
|
||||
|
||||
btnMore.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
closeMenus();
|
||||
menu.style.display = "block";
|
||||
};
|
||||
|
||||
actions.append(btnView, btnDown, btnMore);
|
||||
|
||||
card.append(check, top, thumb, info, actions, menu);
|
||||
grid.appendChild(card);
|
||||
});
|
||||
|
||||
document.getElementById("nav" + page.charAt(0).toUpperCase() + page.slice(1))
|
||||
.classList.add("text-blue-500");
|
||||
}
|
||||
|
||||
function openSpaceModal() {
|
||||
document.getElementById("spaceModal").classList.remove("hidden");
|
||||
/* 工具函数 */
|
||||
function createBtn(text, fn) {
|
||||
const b = document.createElement("button");
|
||||
b.innerText = text;
|
||||
b.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
fn();
|
||||
};
|
||||
return b;
|
||||
}
|
||||
|
||||
function closeSpaceModal() {
|
||||
document.getElementById("spaceModal").classList.add("hidden");
|
||||
function toggleSelect(id) {
|
||||
selected.has(id) ? selected.delete(id) : selected.add(id);
|
||||
render();
|
||||
}
|
||||
|
||||
function switchSpace(space) {
|
||||
currentSpace = space;
|
||||
document.getElementById("currentSpace").innerText = space;
|
||||
document.getElementById("currentSpace2").innerText = space;
|
||||
closeSpaceModal();
|
||||
function preview(f) {
|
||||
const box = document.getElementById("previewBox");
|
||||
const detail = document.getElementById("detail");
|
||||
|
||||
box.innerHTML = f.type === "img"
|
||||
? `<img src="${f.url}">`
|
||||
: "📄 " + f.name;
|
||||
|
||||
detail.innerHTML = `${f.name}<br>${f.size}<br>${f.date}`;
|
||||
}
|
||||
|
||||
function deleteFile(id) {
|
||||
const i = files.findIndex(f => f.id === id);
|
||||
if (i > -1) files.splice(i, 1);
|
||||
selected.delete(id);
|
||||
render();
|
||||
}
|
||||
|
||||
function closeMenus() {
|
||||
document.querySelectorAll(".more-menu").forEach(m => m.style.display = "none");
|
||||
}
|
||||
|
||||
document.addEventListener("click", closeMenus);
|
||||
|
||||
/* init */
|
||||
render();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
# Task Dock 重构计划
|
||||
|
||||
## 1. 项目分析
|
||||
|
||||
### 1.1 当前代码结构
|
||||
|
||||
* **index.js**: 包含 G3File 构造函数和所有核心逻辑
|
||||
|
||||
* **index.css**: 包含所有样式定义
|
||||
|
||||
* **libs/**: 包含 FileSaver 和 JSZip 库
|
||||
|
||||
### 1.2 当前下载进度条实现
|
||||
|
||||
* 位于 `g3-file-container` 内部
|
||||
|
||||
* 固定在顶部的 `download-progress-bar` 元素
|
||||
|
||||
* 仅支持下载任务
|
||||
|
||||
* 显示简单的进度信息
|
||||
|
||||
## 2. 重构目标
|
||||
|
||||
### 2.1 核心目标
|
||||
|
||||
1. **移除 download-progress-bar**
|
||||
2. **新增全局 task dock**(右下角浮层)
|
||||
3. **支持所有异步任务类型**(不限定 download/upload)
|
||||
|
||||
### 2.2 功能要求
|
||||
|
||||
1. **全局任务中心**:fixed 右下角浮层
|
||||
2. **通用任务模型**:id, name, type, progress, status
|
||||
3. **批量任务支持**:多个任务同时运行,批量更新进度
|
||||
4. **结果汇总**:总任务数、成功数、失败数
|
||||
5. **UI Lock**:任务运行时禁止页面其他操作
|
||||
6. **交互要求**:close,实时进度更新
|
||||
|
||||
## 3. 实现计划
|
||||
|
||||
### 3.1 文件修改
|
||||
|
||||
#### 3.1.1 index.js 修改
|
||||
|
||||
1. **移除 download-progress-bar 相关代码**:
|
||||
|
||||
* 删除 `showDownloadProgress` 方法
|
||||
|
||||
* 删除 `updateDownloadProgress` 方法
|
||||
|
||||
* 删除 `completeDownloadProgress` 方法
|
||||
|
||||
* 删除 `$downloadProgressBar` 变量
|
||||
|
||||
2. **新增 Task 管理功能**:
|
||||
|
||||
* 新增 `tasks` 数组存储任务列表
|
||||
|
||||
* 新增 `taskStats` 对象存储统计信息
|
||||
|
||||
* 新增 `addTask` 方法
|
||||
|
||||
* 新增 `updateTask` 方法
|
||||
|
||||
* 新增 `finishTask` 方法
|
||||
|
||||
* 新增 `updateTaskStats` 方法
|
||||
|
||||
* 新增 `clearTasks` 方法
|
||||
|
||||
3. **新增 Task Dock 管理**:
|
||||
|
||||
* 新增 `renderTaskDock` 方法
|
||||
|
||||
* 新增 `updateTaskDock` 方法
|
||||
|
||||
* 新增 `showTaskDock` 方法
|
||||
|
||||
* 新增 `hideTaskDock` 方法
|
||||
|
||||
* 新增 `minimizeTaskDock` 方法
|
||||
|
||||
4. **新增 UI Lock 功能**:
|
||||
|
||||
* 新增 `lockUI` 方法
|
||||
|
||||
* 新增 `unlockUI` 方法
|
||||
|
||||
* 新增 `isUILocked` 方法
|
||||
|
||||
5. **修改现有方法**:
|
||||
|
||||
* 修改 `internalDownloadBatch` 方法使用新的任务管理
|
||||
|
||||
* 修改 `handleDownload` 方法支持任务管理
|
||||
|
||||
#### 3.1.2 index.css 修改
|
||||
|
||||
1. **移除 download-progress-bar 样式**
|
||||
2. **新增 task-dock 相关样式**:
|
||||
|
||||
* `.task-dock`:基础容器样式
|
||||
|
||||
* `.dock-header`:头部样式
|
||||
|
||||
* `.dock-body`:身体样式
|
||||
|
||||
* `.progress-line`:进度行样式
|
||||
|
||||
* `.task-name`:任务名称样式
|
||||
|
||||
* `.task-type`:任务类型样式
|
||||
|
||||
* `.bar`:进度条样式
|
||||
|
||||
* `.bar-inner`:进度条内部样式
|
||||
|
||||
* `.task-result`:结果汇总样式
|
||||
|
||||
* `.ui-lock`:UI 锁定遮罩样式
|
||||
|
||||
### 3.2 实现步骤
|
||||
|
||||
1. **步骤 1:修改 CSS 样式**
|
||||
|
||||
* 移除 `download-progress-bar` 相关样式
|
||||
|
||||
* 添加 `task-dock` 相关样式
|
||||
|
||||
* 添加 `ui-lock` 遮罩样式
|
||||
|
||||
2. **步骤 2:修改 HTML 结构**
|
||||
|
||||
* 从 `render` 方法中移除 `download-progress-bar` 元素
|
||||
|
||||
* 添加 `task-dock` 元素到页面
|
||||
|
||||
3. **步骤 3:实现 Task 管理功能**
|
||||
|
||||
* 添加任务管理相关变量
|
||||
|
||||
* 实现任务管理方法
|
||||
|
||||
4. **步骤 4:实现 Task Dock UI 管理**
|
||||
|
||||
* 实现 dock 渲染和更新方法
|
||||
|
||||
* 实现 dock 显示/隐藏/最小化方法
|
||||
|
||||
5. **步骤 5:实现 UI Lock 功能**
|
||||
|
||||
* 实现 UI 锁定和解锁方法
|
||||
|
||||
6. **步骤 6:修改现有方法**
|
||||
|
||||
* 修改 `internalDownloadBatch` 方法使用新的任务管理
|
||||
|
||||
* 确保所有异步任务都通过任务中心管理
|
||||
|
||||
7. **步骤 7:测试验证**
|
||||
|
||||
* 测试批量下载功能
|
||||
|
||||
* 测试任务状态更新
|
||||
|
||||
* 测试 UI Lock 功能
|
||||
|
||||
* 测试交互功能(close、minimize)
|
||||
|
||||
## 4. 技术实现细节
|
||||
|
||||
### 4.1 任务模型
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: "task_123",
|
||||
name: "report.pdf",
|
||||
type: "download", // download / upload / custom
|
||||
progress: 0, // 0-100
|
||||
status: "running" // running / success / fail / paused
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 统计模型
|
||||
|
||||
```javascript
|
||||
{
|
||||
total: 10,
|
||||
success: 7,
|
||||
fail: 3
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 DOM 结构
|
||||
|
||||
```html
|
||||
<div class="task-dock">
|
||||
<div class="dock-header">
|
||||
<span class="title">Task Running</span>
|
||||
<div class="dock-actions">
|
||||
<button title="minimize">−</button>
|
||||
<button title="close">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dock-body">
|
||||
<div class="progress-line">
|
||||
<span class="count">3 / 10</span>
|
||||
<span class="percent">30%</span>
|
||||
</div>
|
||||
<div class="task-name">report.pdf</div>
|
||||
<div class="task-type">download / upload / custom</div>
|
||||
<div class="bar">
|
||||
<div class="bar-inner"></div>
|
||||
</div>
|
||||
<div class="task-result">
|
||||
Total: xxx | Success: xxx | Fail: xxx
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 4.4 样式设计
|
||||
|
||||
* **位置**:fixed 定位在右下角
|
||||
|
||||
* **大小**:固定宽度,高度自适应
|
||||
|
||||
* **颜色**:使用与现有设计一致的配色
|
||||
|
||||
* **动画**:平滑的显示/隐藏动画
|
||||
|
||||
* **响应式**:适配不同屏幕尺寸
|
||||
|
||||
## 5. 风险评估
|
||||
|
||||
### 5.1 潜在风险
|
||||
|
||||
1. **兼容性问题**:确保新功能在所有浏览器中正常工作
|
||||
2. **性能问题**:确保任务管理不会影响页面性能
|
||||
3. **用户体验**:确保新的任务中心比原有的进度条更直观易用
|
||||
|
||||
### 5.2 风险缓解措施
|
||||
|
||||
1. **兼容性测试**:在主流浏览器中测试功能
|
||||
2. **性能优化**:使用防抖和节流技术减少 DOM 更新
|
||||
3. **用户测试**:确保新的任务中心操作流畅直观
|
||||
|
||||
## 6. 预期效果
|
||||
|
||||
1. **功能增强**:支持多种任务类型,提供更详细的任务信息
|
||||
2. **用户体验**:右下角浮层不干扰主界面操作
|
||||
3. **可扩展性**:易于添加新的任务类型和功能
|
||||
4. **代码质量**:UI 与逻辑分离,代码结构清晰
|
||||
|
||||
## 7. 时间估计
|
||||
|
||||
* **CSS 样式修改**:30 分钟
|
||||
|
||||
* **HTML 结构修改**:15 分钟
|
||||
|
||||
* **Task 管理功能实现**:60 分钟
|
||||
|
||||
* **Task Dock UI 实现**:45 分钟
|
||||
|
||||
* **UI Lock 功能实现**:30 分钟
|
||||
|
||||
* **现有方法修改**:30 分钟
|
||||
|
||||
* **测试验证**:45 分钟
|
||||
|
||||
**总时间**:约 255 分钟(4.25 小时)
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Lucide by Lucide Contributors - https://github.com/lucide-icons/lucide/blob/main/LICENSE --><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12.586 12.586L19 19M3.688 3.037a.497.497 0 0 0-.651.651l6.5 15.999a.501.501 0 0 0 .947-.062l1.569-6.083a2 2 0 0 1 1.448-1.479l6.124-1.579a.5.5 0 0 0 .063-.947z"/></svg>
|
||||
|
Before Width: | Height: | Size: 466 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Lucide by Lucide Contributors - https://github.com/lucide-icons/lucide/blob/main/LICENSE --><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 11v6m4-6v6m5-11v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
||||
|
Before Width: | Height: | Size: 405 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Lucide by Lucide Contributors - https://github.com/lucide-icons/lucide/blob/main/LICENSE --><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 15V3m9 12v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10l5 5l5-5"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 386 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Lucide by Lucide Contributors - https://github.com/lucide-icons/lucide/blob/main/LICENSE --><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v12m5-7l-5-5l-5 5m14 7v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/></svg>
|
||||
|
Before Width: | Height: | Size: 368 B |
@@ -9,6 +9,7 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.g3-file-container .header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -19,23 +20,30 @@
|
||||
border-bottom: 1px solid #eef0f2;
|
||||
}
|
||||
|
||||
/* 主要内容区域 */
|
||||
.g3-file-container .main {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 4px 0px;
|
||||
|
||||
overflow: auto;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
align-items: flex-start;
|
||||
align-content: flex-start;
|
||||
|
||||
border-left: 1px solid #eef0f2;
|
||||
border-right: 1px solid #eef0f2;
|
||||
|
||||
/* justify-content: center; */
|
||||
}
|
||||
|
||||
.g3-file-container .main > * {
|
||||
margin: 7px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
/* 空状态提示 */
|
||||
.g3-file-container .empty-tip {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
@@ -47,6 +55,7 @@
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 底部样式 */
|
||||
.g3-file-container .footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -55,7 +64,7 @@
|
||||
padding: 10px 14px;
|
||||
|
||||
background: #fafbfc;
|
||||
border-top: 1px solid #eef0f2;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.g3-file-container .header,
|
||||
@@ -93,6 +102,7 @@
|
||||
|
||||
.g3-file-container .check-all span {
|
||||
margin-left: 4px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 按钮 */
|
||||
@@ -140,50 +150,81 @@
|
||||
background: #ff4d4f;
|
||||
}
|
||||
|
||||
.g3-file-container .file-item {
|
||||
width: calc(50% - 20px);
|
||||
/* 文件项样式 */
|
||||
.g3-file-container .g3-file-item {
|
||||
width: calc(50% - 26px);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: all 0.18s ease;
|
||||
|
||||
border: 1px solid #eef0f2;
|
||||
border-radius: 6px;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.g3-file-container .file-item:hover {
|
||||
border-color: #d6e4ff;
|
||||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
|
||||
transform: translateY(-2px);
|
||||
.g3-file-container .g3-file-item:hover {
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.file-item .file-preview {
|
||||
height: 120px;
|
||||
/* 文件预览 */
|
||||
.g3-file-item .g3-file-preview {
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #fafbfc;
|
||||
}
|
||||
|
||||
.g3-file-item .g3-file-preview .g3-file-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.g3-file-item .g3-file-preview svg {
|
||||
height: 86px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 图片容器样式 */
|
||||
.g3-file-item .g3-file-preview .g3-img-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.file-item .file-preview svg,
|
||||
.file-item .file-preview img {
|
||||
/* 图片错误状态 */
|
||||
.g3-file-item .g3-file-preview .g3-img-container.is-error .g3-img-error {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
|
||||
.g3-file-item .g3-file-preview .g3-img-container .g3-img-error {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.file-item .file-checkbox {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
.g3-file-item .g3-file-preview .g3-img-container .g3-img-error svg {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.file-item .file-info {
|
||||
padding: 10px 10px 12px;
|
||||
/* 文件信息 */
|
||||
.g3-file-item .g3-file-info {
|
||||
padding: 8px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
line-height: 1.4;
|
||||
@@ -196,125 +237,129 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.file-item .file-info span {
|
||||
.g3-file-item .g3-file-info span {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.file-item .file-info .file-name {
|
||||
.g3-file-item .g3-file-info .g3-file-name {
|
||||
font-size: 13px;
|
||||
color: #1f2937;
|
||||
font-weight: 600;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.file-item .file-actions {
|
||||
/* 文件操作按钮 */
|
||||
.g3-file-item .g3-file-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 8px;
|
||||
margin-right: 8px;
|
||||
|
||||
overflow: hidden;
|
||||
border-top: 1px solid #f0f2f5;
|
||||
}
|
||||
|
||||
.file-item .file-action {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
||||
.g3-file-item .g3-file-action {
|
||||
padding: 8px 2px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
border-radius: 6px;
|
||||
|
||||
/* 👇 稍微更明显 */
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
|
||||
margin-left: 6px;
|
||||
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
gap: 4px;
|
||||
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 12px;
|
||||
border-right: 1px solid #f0f2f5;
|
||||
}
|
||||
|
||||
/* hover:明显增强 */
|
||||
.file-item .file-action:hover {
|
||||
.g3-file-item .g3-file-action:hover {
|
||||
background-color: #f5f6f8;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.g3-file-item .g3-file-action:active {
|
||||
background: rgba(22, 93, 255, 0.12);
|
||||
color: #165dff;
|
||||
}
|
||||
|
||||
/* active:点击反馈 */
|
||||
.file-item .file-action:active {
|
||||
background: rgba(22, 93, 255, 0.18);
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
.file-item .file-action.is-disabled {
|
||||
.g3-file-item .g3-file-action.is-disabled {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.g3-download-modal {
|
||||
.g3-file-item .g3-file-action:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.g3-file-item .g3-file-action svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 复选框样式 */
|
||||
.g3-file-item .g3-file-checkbox {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 9999;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.g3-download-mask {
|
||||
/* 自定义复选框样式 */
|
||||
.g3-file-item .g3-file-checkbox + .g3-checkbox-label {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 2px;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.g3-download-box {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
width: 260px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.g3-download-bar {
|
||||
height: 6px;
|
||||
background: #eee;
|
||||
border-radius: 4px;
|
||||
margin-top: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.g3-download-bar-inner {
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
background: #4caf50;
|
||||
transition: width 0.2s;
|
||||
}
|
||||
|
||||
/* 文件项选中样式 */
|
||||
.g3-file-container .file-item.file-item-selected {
|
||||
.g3-file-item .g3-file-checkbox:checked + .g3-checkbox-label {
|
||||
background: #165dff;
|
||||
border-color: #165dff;
|
||||
box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.2);
|
||||
background: rgba(22, 93, 255, 0.05);
|
||||
}
|
||||
|
||||
.g3-file-container .file-item.file-item-selected:hover {
|
||||
.g3-file-item .g3-file-checkbox:checked + .g3-checkbox-label::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 5px;
|
||||
width: 4px;
|
||||
height: 8px;
|
||||
border: solid white;
|
||||
border-width: 0 2px 2px 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.g3-file-item .g3-file-checkbox:hover + .g3-checkbox-label {
|
||||
border-color: #165dff;
|
||||
box-shadow: 0 6px 18px rgba(22, 93, 255, 0.2);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.1);
|
||||
}
|
||||
|
||||
.g3-file-item .g3-file-checkbox:disabled + .g3-checkbox-label {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 未上传标记 */
|
||||
.file-item .file-pending-badge {
|
||||
.g3-file-item .g3-file-pending-badge {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
background: #ff9800;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
@@ -322,3 +367,203 @@
|
||||
border-radius: 10px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 浮动删除按钮 */
|
||||
.g3-file-container .g3-file-delete-btn {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -8px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: #ebebeb;
|
||||
color: #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
z-index: 100;
|
||||
font-size: 12px;
|
||||
border: 1px solid #e8e8e8;
|
||||
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.g3-file-container .g3-file-delete-btn:hover {
|
||||
background: #ff4d4f;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.g3-file-container .g3-file-item .g3-file-delete-btn.is-disabled {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 全局菜单 */
|
||||
.g3-global-menu {
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
|
||||
width: 160px;
|
||||
background: #fff;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
|
||||
position: fixed; /* ⭐必须,不然一定会错位 */
|
||||
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.g3-global-menu.is-open {
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* 更多菜单项样式 */
|
||||
.g3-global-menu .g3-action-menu-item {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.g3-global-menu .g3-action-menu-item:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.g3-global-menu .g3-action-menu-item:active {
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
|
||||
/* Task Dock 样式 */
|
||||
.task-dock {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
width: 320px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #eef0f2;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
z-index: 9999;
|
||||
display: none;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(100%) translateY(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0) translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.task-dock .dock-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
background: #fafbfc;
|
||||
border-bottom: 1px solid #eef0f2;
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
|
||||
.task-dock .dock-header .title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.task-dock .dock-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.task-dock .dock-actions button {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.task-dock .dock-actions button:hover {
|
||||
background: #f0f2f5;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.task-dock .dock-body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.task-dock .progress-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.task-dock .task-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.task-dock .task-type {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.task-dock .bar {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
background: #f0f2f5;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.task-dock .bar .bar-inner {
|
||||
height: 100%;
|
||||
background: #165dff;
|
||||
border-radius: 3px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.task-dock .task-result {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* UI Lock 样式 */
|
||||
.ui-lock {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
z-index: 9998;
|
||||
display: none;
|
||||
}
|
||||
+1178
-572
File diff suppressed because it is too large.
Load diff
+138
-12
@@ -23,16 +23,41 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.button-group button {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: #5D87FF;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.button-group button:hover {
|
||||
background: #4a6fcf;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="app">
|
||||
<button id="reloadBtn">重新加载文件</button>
|
||||
<button id="reloadBtn2">重新加载文件2</button>
|
||||
<div class="button-group">
|
||||
<button id="reloadBtn">加载文件组1</button>
|
||||
<button id="reloadBtn2">加载文件组2</button>
|
||||
<button id="addFileBtn">添加文件</button>
|
||||
<button id="clearBtn">清空选择</button>
|
||||
<button id="destroyBtn">销毁组件</button>
|
||||
</div>
|
||||
|
||||
<div style="height: 600px;width: 400px;">
|
||||
<div style="height: 600px;width: 500px;">
|
||||
<div id="fileContainer"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,32 +69,133 @@
|
||||
data: [
|
||||
{ name: 'test.jar', size: 1024000, uploadDate: '2024-01-15T10:30:00', url: 'http://118.89.70.199:9000/test/XI26033384.rar' },
|
||||
],
|
||||
permissions: {
|
||||
globalPermission: {
|
||||
select: true,
|
||||
upload: true,
|
||||
download: true,
|
||||
delete: true,
|
||||
view: true
|
||||
},
|
||||
onUpload: function () {
|
||||
console.log('Upload clicked');
|
||||
alert('上传功能');
|
||||
onUpload: function (pendingFiles) {
|
||||
console.log('上传文件:', pendingFiles);
|
||||
alert('上传功能,共 ' + pendingFiles.length + ' 个文件待上传');
|
||||
},
|
||||
onDelete: function (files, callback) {
|
||||
console.log('删除文件:', files);
|
||||
if (confirm('确定要删除 ' + files.length + ' 个文件吗?')) {
|
||||
callback(true);
|
||||
} else {
|
||||
callback(false);
|
||||
}
|
||||
},
|
||||
onPreview: function (files) {
|
||||
console.log('预览文件:', files);
|
||||
alert('预览文件: ' + files[0].name);
|
||||
},
|
||||
onSelect: function (files) {
|
||||
console.log('选择文件:', files);
|
||||
},
|
||||
getActionMenu: function (file) {
|
||||
const menuItems = [];
|
||||
|
||||
menuItems.push({
|
||||
title: '审批',
|
||||
onClick: function (f) {
|
||||
console.log('审批文件:', f.name);
|
||||
alert('审批功能:' + f.name);
|
||||
}
|
||||
});
|
||||
|
||||
menuItems.push({
|
||||
title: '修改',
|
||||
onClick: function (f) {
|
||||
console.log('修改文件:', f.name);
|
||||
alert('修改功能:' + f.name);
|
||||
}
|
||||
});
|
||||
|
||||
menuItems.push({
|
||||
title: '重命名',
|
||||
onClick: function (f) {
|
||||
console.log('重命名文件:', f.name);
|
||||
const newName = prompt('请输入新文件名:', f.name);
|
||||
if (newName && newName !== f.name) {
|
||||
console.log('文件已重命名为:', newName);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
menuItems.push({
|
||||
title: '复制',
|
||||
disabled: false,
|
||||
onClick: function (f) {
|
||||
console.log('复制文件:', f.name);
|
||||
alert('复制功能:' + f.name);
|
||||
}
|
||||
});
|
||||
|
||||
menuItems.push({
|
||||
title: '移动到文件夹',
|
||||
disabled: false,
|
||||
onClick: function (f) {
|
||||
console.log('移动文件:', f.name);
|
||||
alert('移动功能:' + f.name);
|
||||
}
|
||||
});
|
||||
|
||||
menuItems.push({
|
||||
title: '分享',
|
||||
disabled: false,
|
||||
onClick: function (f) {
|
||||
console.log('分享文件:', f.name);
|
||||
alert('分享功能:' + f.name);
|
||||
}
|
||||
});
|
||||
|
||||
menuItems.push({
|
||||
title: '历史版本',
|
||||
disabled: false,
|
||||
onClick: function (f) {
|
||||
console.log('查看历史版本:', f.name);
|
||||
alert('历史版本功能:' + f.name);
|
||||
}
|
||||
});
|
||||
|
||||
return menuItems;
|
||||
}
|
||||
});
|
||||
|
||||
$('#reloadBtn').click(function () {
|
||||
file.loadFiles([
|
||||
{ name: '测试文档1.pdf', uploadDate: '2024-01-15T10:30:00', url: '' },
|
||||
{ name: 'test.png', size: 1024000, uploadDate: '2024-01-15T10:30:00', url: 'http://118.89.70.199:9000/test/DC2A4692147D10C9C7B48A7EA29BCBA8.jpg' },
|
||||
file.setFiles([
|
||||
{ name: '测试文档1.pdf', size: 2048000, uploadDate: '2024-01-15T10:30:00', url: '' },
|
||||
{ name: 'test.png', size: 1024000, uploadDate: '2024-01-15T10:30:00', url: 'http://118.89.70.:9000/test/DC2A4692147D10C9C7B48A7EA29BCBA8.jpg' },
|
||||
{ name: '项目报告.docx', size: 524288, uploadDate: '2024-02-20T14:45:00', url: 'http://118.89.70.199:9000/test/report.docx' },
|
||||
]);
|
||||
});
|
||||
|
||||
$('#reloadBtn2').click(function () {
|
||||
file.loadFiles([
|
||||
{ name: '测试文档2.pdf', size: 1024000, uploadDate: '2024-01-15T10:30:00', url: '' },
|
||||
file.setFiles([
|
||||
{ name: '测试文档2.pdf', size: 4096000, uploadDate: '2024-01-15T10:30:00', url: '' },
|
||||
{ name: '项目报告2.docx', size: 524288, uploadDate: '2024-02-20T14:45:00', url: '' },
|
||||
{ name: '数据表.xlsx', size: 1572864, uploadDate: '2024-03-01T09:00:00', url: 'http://118.89.70.199:9000/test/data.xlsx' },
|
||||
{ name: '演示文稿.pptx', size: 3145728, uploadDate: '2024-03-05T16:30:00', url: '' },
|
||||
]);
|
||||
});
|
||||
|
||||
$('#addFileBtn').click(function () {
|
||||
file.$btnSelect.click();
|
||||
});
|
||||
|
||||
$('#clearBtn').click(function () {
|
||||
file.updateState({ selectedFiles: [] });
|
||||
});
|
||||
|
||||
$('#destroyBtn').click(function () {
|
||||
if (confirm('确定要销毁组件吗?')) {
|
||||
file.destroy();
|
||||
$('#fileContainer').html('<p style="text-align:center;padding:50px;color:#999;">组件已销毁</p>');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
(function(a,b){if("function"==typeof define&&define.amd)define([],b);else if("undefined"!=typeof exports)b();else{b(),a.FileSaver={exports:{}}.exports}})(this,function(){"use strict";function b(a,b){return"undefined"==typeof b?b={autoBom:!1}:"object"!=typeof b&&(console.warn("Deprecated: Expected third argument to be a object"),b={autoBom:!b}),b.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob(["\uFEFF",a],{type:a.type}):a}function c(a,b,c){var d=new XMLHttpRequest;d.open("GET",a),d.responseType="blob",d.onload=function(){g(d.response,b,c)},d.onerror=function(){console.error("could not download file")},d.send()}function d(a){var b=new XMLHttpRequest;b.open("HEAD",a,!1);try{b.send()}catch(a){}return 200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent("click"))}catch(c){var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b)}}var f="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof global&&global.global===global?global:void 0,a=/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),g=f.saveAs||("object"!=typeof window||window!==f?function(){}:"download"in HTMLAnchorElement.prototype&&!a?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement("a");g=g||b.name||"download",j.download=g,j.rel="noopener","string"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target="_blank")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href)},4E4),setTimeout(function(){e(j)},0))}:"msSaveOrOpenBlob"in navigator?function(f,g,h){if(g=g||f.name||"download","string"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else{var i=document.createElement("a");i.href=f,i.target="_blank",setTimeout(function(){e(i)})}}:function(b,d,e,g){if(g=g||open("","_blank"),g&&(g.document.title=g.document.body.innerText="downloading..."),"string"==typeof b)return c(b,d,e);var h="application/octet-stream"===b.type,i=/constructor/i.test(f.HTMLElement)||f.safari,j=/CriOS\/[\d]+/.test(navigator.userAgent);if((j||h&&i||a)&&"undefined"!=typeof FileReader){var k=new FileReader;k.onloadend=function(){var a=k.result;a=j?a:a.replace(/^data:[^;]*;/,"data:attachment/file;"),g?g.location.href=a:location=a,g=null},k.readAsDataURL(b)}else{var l=f.URL||f.webkitURL,m=l.createObjectURL(b);g?g.location=m:location.href=m,g=null,setTimeout(function(){l.revokeObjectURL(m)},4E4)}});f.saveAs=g.saveAs=g,"undefined"!=typeof module&&(module.exports=g)});
|
||||
|
||||
//# sourceMappingURL=FileSaver.min.js.map
|
||||
@@ -0,0 +1,205 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Download Progress UI Comparison</title>
|
||||
|
||||
<style>
|
||||
/* =========================
|
||||
Base Layout
|
||||
========================= */
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header,
|
||||
.footer {
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.footer {
|
||||
border-top: 1px solid #eee;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
🔴 方案 A:嵌入式进度条
|
||||
========================= */
|
||||
.embedded-bar {
|
||||
background: #fff7e6;
|
||||
border: 1px solid #ffe58f;
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.embedded-bar .row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.embedded-bar .actions {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.embedded-bar button {
|
||||
border: 1px solid #ddd;
|
||||
background: #fff;
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
🟢 方案 B:浮层 Dock(推荐)
|
||||
========================= */
|
||||
.download-dock {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
width: 320px;
|
||||
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.15);
|
||||
|
||||
overflow: hidden;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.dock-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px 12px;
|
||||
background: #f6f6f6;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dock-actions button {
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.dock-body {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.progress-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 13px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.current-file {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.bar {
|
||||
height: 6px;
|
||||
background: #eee;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bar-inner {
|
||||
width: 45%;
|
||||
height: 100%;
|
||||
background: #faad14;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="page">
|
||||
|
||||
<div class="header">File Manager Header</div>
|
||||
|
||||
<div class="content">
|
||||
|
||||
<!-- =========================
|
||||
🔴 方案 A:嵌入式
|
||||
========================== -->
|
||||
<div class="embedded-bar">
|
||||
<div class="row">
|
||||
<span>Downloading</span>
|
||||
<span>3 / 10</span>
|
||||
<span>report.pdf</span>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button>Hide</button>
|
||||
<button>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>File list area...</p>
|
||||
<p>Scroll content...</p>
|
||||
<p>Scroll content...</p>
|
||||
<p>Scroll content...</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="footer">Footer</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- =========================
|
||||
🟢 方案 B:浮层 Dock
|
||||
========================= -->
|
||||
<div class="download-dock">
|
||||
<div class="dock-header">
|
||||
<span>Downloading</span>
|
||||
|
||||
<div class="dock-actions">
|
||||
<button title="minimize">−</button>
|
||||
<button title="close">×</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dock-body">
|
||||
<div class="progress-line">
|
||||
<span>3 / 10</span>
|
||||
<span>30%</span>
|
||||
</div>
|
||||
|
||||
<div class="current-file">
|
||||
report.pdf
|
||||
</div>
|
||||
|
||||
<div class="bar">
|
||||
<div class="bar-inner"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -50,7 +50,8 @@ axiosInstance.interceptors.request.use(
|
||||
if (needAuth) {
|
||||
if (!userStore.isLogined) {
|
||||
userStore.clear()
|
||||
router.push('/login')
|
||||
// router.push('/login')
|
||||
router.push('/')
|
||||
return Promise.reject(new Error('尚未登录,请先登录!'))
|
||||
}
|
||||
}
|
||||
@@ -82,7 +83,11 @@ axiosInstance.interceptors.response.use(
|
||||
const resData = response.data as ResponseData<any>
|
||||
|
||||
if (resData.code === 401) {
|
||||
router.push('/auth/login')
|
||||
const userStore = useUserStore()
|
||||
|
||||
userStore.clear()
|
||||
router.push('/')
|
||||
// router.push('/auth/login')
|
||||
return Promise.resolve(response.data)
|
||||
}
|
||||
else if (resData.code === 404) {
|
||||
|
||||
Reference in new issue
Block a user