221 lines
4.7 KiB
Vue
221 lines
4.7 KiB
Vue
<template>
|
|
<div class="transfer-container">
|
|
<div class="transfer-header">
|
|
传输列表 ({{ tasks.length }})
|
|
<div class="transfer-header-btns">
|
|
<Button type="ghost" class="clear-btn" @click="clearTasks">清理</Button>
|
|
<Button
|
|
type="ghost"
|
|
class="toggle-btn"
|
|
@click="isExpanded = !isExpanded"
|
|
>{{ isExpanded ? "▼" : "▲" }}</Button
|
|
>
|
|
</div>
|
|
</div>
|
|
<Transition name="file-transfer-view">
|
|
<div v-show="isExpanded" class="transfer-content">
|
|
<div
|
|
v-for="task in tasks"
|
|
:key="task.fileData.path"
|
|
class="transfer-item"
|
|
>
|
|
<div class="task-header">
|
|
<div class="file-name">{{ task.fileData.name }}</div>
|
|
<div class="task-status" :class="task.progress.status">
|
|
{{ getStatusText(task.progress.status) }}
|
|
</div>
|
|
</div>
|
|
<div class="file-info">
|
|
<span>{{ formatSize(task.fileData.size) }}</span>
|
|
<span v-if="task.progress.speed"
|
|
>· {{ formatSize(task.progress.speed) }}/s</span
|
|
>
|
|
</div>
|
|
<Progress
|
|
:percent="task.progress.percent"
|
|
:status="getProgressStatus(task.progress.status)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</div>
|
|
</template>
|
|
<script setup lang="ts">
|
|
import { Button } from "ant-design-vue";
|
|
import { Progress } from "ant-design-vue";
|
|
import {
|
|
fileTransferMgrInstance,
|
|
type TransferTask,
|
|
TransferStatus,
|
|
} from "../utils/fileTransfer";
|
|
import { type Ref, ref } from "vue";
|
|
import { formatSize } from "../utils/common";
|
|
|
|
const isExpanded = ref(true);
|
|
const tasks: Ref<TransferTask[]> = ref([]);
|
|
|
|
const clearTasks = () => {
|
|
fileTransferMgrInstance.getAllFileTransfers().forEach((transfer) => {
|
|
transfer.clearCompletedTasks();
|
|
});
|
|
updateTasks();
|
|
};
|
|
|
|
const getStatusText = (status: TransferStatus) => {
|
|
const statusMap: Record<TransferStatus, string> = {
|
|
[TransferStatus.WAITING]: "等待中",
|
|
[TransferStatus.SENDING]: "发送中",
|
|
[TransferStatus.RECEIVING]: "接收中",
|
|
[TransferStatus.COMPLETED]: "已完成",
|
|
[TransferStatus.ERROR]: "失败",
|
|
[TransferStatus.PAUSED]: "已暂停",
|
|
};
|
|
return statusMap[status] || status;
|
|
};
|
|
|
|
const getProgressStatus = (status: TransferStatus) => {
|
|
if (status === TransferStatus.ERROR) return "exception";
|
|
if (status === TransferStatus.COMPLETED) return "success";
|
|
return "active";
|
|
};
|
|
|
|
const updateTasks = () => {
|
|
tasks.value = Array.from(
|
|
fileTransferMgrInstance
|
|
.getAllFileTransfers()
|
|
.flatMap((transfer) => transfer.getTasks()),
|
|
);
|
|
// newTasks.forEach((task) => {
|
|
// const index = tasks.value.findIndex(
|
|
// (t) => t.fileData.path === task.fileData.path
|
|
// );
|
|
// if (index === -1) {
|
|
// tasks.value.push(task);
|
|
// }
|
|
// });
|
|
};
|
|
|
|
fileTransferMgrInstance.onTransferChanged((transfer) => {
|
|
updateTasks();
|
|
});
|
|
|
|
updateTasks();
|
|
</script>
|
|
<style scoped>
|
|
.transfer-container {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
background: white;
|
|
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
|
|
z-index: 10;
|
|
}
|
|
|
|
.transfer-header {
|
|
padding: 10px 20px;
|
|
background: #f5f5f5;
|
|
cursor: pointer;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
border-top: 1px solid #ddd;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.transfer-content {
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
max-height: 600px;
|
|
}
|
|
|
|
.transfer-item {
|
|
padding: 12px;
|
|
padding-left: 20px;
|
|
padding-right: 20px;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
|
|
.task-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.file-name {
|
|
font-weight: 500;
|
|
flex: 1;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.task-status {
|
|
font-size: 12px;
|
|
padding: 2px 8px;
|
|
border-radius: 10px;
|
|
background: #f0f0f0;
|
|
}
|
|
|
|
.task-status.completed {
|
|
background: #e6f7e6;
|
|
color: #52c41a;
|
|
}
|
|
|
|
.task-status.error {
|
|
background: #fff1f0;
|
|
color: #ff4d4f;
|
|
}
|
|
|
|
.task-status.sending,
|
|
.task-status.receiving {
|
|
background: #e6f7ff;
|
|
color: #1890ff;
|
|
}
|
|
|
|
.task-status.waiting {
|
|
background: #f5f5f5;
|
|
color: #8c8c8c;
|
|
}
|
|
|
|
.task-status.paused {
|
|
background: #fff7e6;
|
|
color: #faad14;
|
|
}
|
|
|
|
.file-info {
|
|
margin-bottom: 8px;
|
|
font-size: 12px;
|
|
color: #666;
|
|
}
|
|
|
|
.toggle-icon {
|
|
font-size: 12px;
|
|
}
|
|
.transfer-header-btns {
|
|
display: flex;
|
|
align-items: flex-end;
|
|
}
|
|
.clear-btn {
|
|
margin-left: 10px;
|
|
}
|
|
|
|
/* 过渡动画 */
|
|
.file-transfer-view-enter-active,
|
|
.file-transfer-view-leave-active {
|
|
transition: max-height 0.5s ease-in-out;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.file-transfer-view-enter-from,
|
|
.file-transfer-view-leave-to {
|
|
max-height: 0;
|
|
}
|
|
|
|
.file-transfer-view-enter-to,
|
|
.file-transfer-view-leave-from {
|
|
max-height: 600px;
|
|
}
|
|
</style>
|