You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

243 lines
5.3 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!-- components/ProgressTracker.vue -->
<template>
<teleport to="body">
<div v-if="visible" class="progress-container" :class="{ minimized: isMinimized }">
<!-- 全尺寸模式 -->
<div v-if="!isMinimized" class="progress-expanded">
<div class="progress-header">
<span>{{ title }}</span>
<button class="btn-close" @click="close">&times;</button>
</div>
<!-- 进度条 -->
<div class="progress-bar-bg">
<div class="progress-bar" :style="{ width: progress + '%' }"></div>
</div>
<!-- 进度消息 + 百分比 -->
<div class="progress-footer">
<div class="progress-message">
<span v-if="progress < 100">🔄{{ progress }} %</span>
<span v-else>✅</span>
{{ message }}
</div>
<button class="btn-minimize" @click="toggleMinimize">最小化</button>
</div>
</div>
<!-- 最小化模式:右下角小窗口 -->
<div v-else class="progress-minimized" @click="toggleMinimize">
{{ progress }}%
<button class="btn-close-sm" @click.stop="close">&times;</button>
</div>
</div>
</teleport>
</template>
<script setup>
import { ref, watch } from 'vue'
// Props
const props = defineProps({
modelValue: {
type: Boolean,
default: true
},
initialProgress: {
type: Number,
default: 0
},
title: {
type: String,
default: '上传进度'
},
// ✅ 新增:进度消息
message: {
type: String,
default: ''
}
})
// Emits
const emit = defineEmits(['update:modelValue', 'complete',
'manual-close'])
// 状态
const visible = ref(props.modelValue)
const isMinimized = ref(false)
const progress = computed(() => props.initialProgress)
// 监听 v-model
watch(() => props.modelValue, (val) => {
visible.value = val
if (val && progress.value >= 100) {
close()
}
})
// 监听进度变化:自动关闭
watch(progress, (val) => {
if (val >= 100) {
setTimeout(() => close(), 500)
emit('complete')
}
})
// 控制显隐
function close() {
visible.value = false
emit('update:modelValue', false)
emit('manual-close')
}
function toggleMinimize() {
isMinimized.value = !isMinimized.value
}
// 暴露方法给父组件
defineExpose({
close,
setProgress(newVal) {
progress.value = Math.min(100, Math.max(0, newVal))
},
getProgress() {
return progress.value
},
increase(step = 1) {
progress.value = Math.min(100, progress.value + step)
},
// ✅ 新增:动态设置消息
setMessage(newMsg) {
// 注意prop 是只读的,我们通过 expose 提供方法更新内部状态
// 实际使用中,建议通过 prop 绑定响应式变量,这里 expose 仅为方便
}
})
</script>
<style scoped>
.progress-container {
position: fixed;
bottom: 50px;
right: 20px;
z-index: 3000;
width: 350px;
font-family: Arial, sans-serif;
}
.progress-expanded {
background: #fff;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
overflow: hidden;
}
.progress-header {
padding: 10px 15px;
background: #f5f5f5;
display: flex;
justify-content: space-between;
align-items: center;
font-weight: bold;
color: #333;
}
.btn-close,
.btn-close-sm {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: #999;
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
}
.btn-close:hover,
.btn-close-sm:hover {
color: #ff4d4f;
}
.progress-bar-bg {
height: 6px;
background: #e0e0e0;
border-radius: 3px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background: #1890ff;
width: 0;
transition: width 0.3s ease;
}
.progress-footer {
padding: 12px 15px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
color: #555;
}
.progress-message {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #333;
font-size: 13px;
}
.btn-minimize {
background: #f0f0f0;
border: 1px solid #ddd;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.btn-minimize:hover {
background: #e0e0e0;
}
.progress-minimized {
width: 60px;
height: 60px;
background: #1890ff;
color: white;
border-radius: 8px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 14px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
cursor: pointer;
position: relative;
}
.progress-minimized .btn-close-sm {
position: absolute;
top: -8px;
right: -8px;
background: white;
border-radius: 50%;
width: 18px;
height: 18px;
line-height: 18px;
font-size: 14px;
}
@media (max-width: 480px) {
.progress-container {
right: 10px;
left: auto;
width: 280px;
}
}
</style>