fix: 修复文件上传逻辑和UI问题

- 修复文件上传暂停/恢复逻辑错误,调整播放状态与上传动作的对应关系
- 为视频上传添加半透明蒙层提升用户体验
- 移除上传管理中的冗余字段和注释代码
- 调整确认框标题的padding样式
- 添加消息重发确认功能
This commit is contained in:
Phoenix 2025-05-26 16:43:11 +08:00
parent 5bda2be585
commit e2e0a3ea3a
7 changed files with 49 additions and 107 deletions

View File

@ -29,7 +29,7 @@ const props = defineProps({
<XNModal v-model:show="show" :closable="false" class="w-724px" content-style="padding:0px" @after-leave="emit('after-leave')"> <XNModal v-model:show="show" :closable="false" class="w-724px" content-style="padding:0px" @after-leave="emit('after-leave')">
<div class="flex flex-col w-full px-25px pb-49px"> <div class="flex flex-col w-full px-25px pb-49px">
<div class="text-20px text-#1F2225 w-full text-center border-b-1px border-b-solid border-b-#E9E9E9 py-25px">{{ title }}</div> <div class="text-20px text-#1F2225 w-full text-center border-b-1px border-b-solid border-b-#E9E9E9 py-20px">{{ title }}</div>
<div class="py-60px text-center text-20px text-#1F2225"> <div class="py-60px text-center text-20px text-#1F2225">
{{ content }} {{ content }}
</div> </div>

View File

@ -71,9 +71,8 @@ function getFileExtension(filename) {
// //
const togglePlay = () => { const togglePlay = () => {
isPlaying.value = !isPlaying.value isPlaying.value = !isPlaying.value
if (props.extra.is_uploading && props.extra.upload_id) { if (props.extra.is_uploading && props.extra.upload_id) {
const action = isPlaying.value ? 'resumeUpload' : 'pauseUpload' const action = isPlaying.value ? 'pauseUpload' : 'resumeUpload'
uploadsStore[action](props.extra.upload_id) uploadsStore[action](props.extra.upload_id)
} }
} }
@ -124,7 +123,7 @@ const handleDownload = () => {
<!-- 上传进度圆环 - 上传状态 --> <!-- 上传进度圆环 - 上传状态 -->
<div v-if="extra.is_uploading" class="progress-overlay"> <div v-if="extra.is_uploading" class="progress-overlay">
<div class="circle-progress-container" @click="togglePlay"> <div class="circle-progress-container" @click.stop="togglePlay">
<svg class="circle-progress" width="20" height="20" viewBox="0 0 20 20"> <svg class="circle-progress" width="20" height="20" viewBox="0 0 20 20">
<!-- 底色圆环 --> <!-- 底色圆环 -->
<circle <circle
@ -150,13 +149,14 @@ const handleDownload = () => {
/> />
<!-- 暂停/播放图标 --> <!-- 暂停/播放图标 -->
<g v-if="isPlaying" class="pause-icon">
<g v-if="isPlaying" class="play-icon">
<rect x="6" y="6" width="8" height="8" :fill="fileInfo.color" />
</g>
<g v-else class="pause-icon">
<rect x="7" y="5" width="2" height="10" :fill="fileInfo.color" /> <rect x="7" y="5" width="2" height="10" :fill="fileInfo.color" />
<rect x="11" y="5" width="2" height="10" :fill="fileInfo.color" /> <rect x="11" y="5" width="2" height="10" :fill="fileInfo.color" />
</g> </g>
<g v-else class="play-icon">
<rect x="6" y="6" width="8" height="8" :fill="fileInfo.color" />
</g>
</svg> </svg>
</div> </div>
</div> </div>

View File

@ -137,7 +137,8 @@ function resumeUpload(e) {
<!-- <n-image :src="extra.cover" preview-disabled /> --> <!-- <n-image :src="extra.cover" preview-disabled /> -->
<video :src="props.extra.url" :controls="false"></video> <video :src="props.extra.url" :controls="false"></video>
<!-- 上传进度时的黑色半透明蒙层 -->
<div v-if="extra.is_uploading && !uploadFailed" class="upload-mask"></div>
<!-- 上传进度显示 --> <!-- 上传进度显示 -->
<div v-if="extra.is_uploading && !uploadFailed" class="upload-progress"> <div v-if="extra.is_uploading && !uploadFailed" class="upload-progress">
<n-progress <n-progress
@ -245,6 +246,17 @@ function resumeUpload(e) {
justify-content: center; justify-content: center;
} }
.upload-mask {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.45);
z-index: 1;
border-radius: 5px;
}
.upload-progress { .upload-progress {
position: absolute; position: absolute;
left: 50%; left: 50%;
@ -255,6 +267,7 @@ function resumeUpload(e) {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
z-index: 2;
.upload-control { .upload-control {
position: absolute; position: absolute;

View File

@ -79,28 +79,6 @@ export const useUploadsStore = defineStore('uploads', {
findItemByClientId(clientUploadId: string): UploadItem | undefined { findItemByClientId(clientUploadId: string): UploadItem | undefined {
return this.items.find((item) => item.client_upload_id === clientUploadId) return this.items.find((item) => item.client_upload_id === clientUploadId)
}, },
// // 暂停文件上传
// pauseUpload(uploadId: string) {
// const item = this.findItem(uploadId)
// if (!item) return
// item.is_paused = true
// console.log(`暂停上传: ${uploadId}`)
// },
// 恢复文件上传
// resumeUpload(uploadId: string) {
// const item = this.findItem(uploadId)
// if (!item) return
// item.is_paused = false
// console.log(`恢复上传: ${uploadId}`)
// // 继续上传
// this.triggerUpload(uploadId)
// },
// 发送上传消息 // 发送上传消息
async sendUploadMessage(item: any) { async sendUploadMessage(item: any) {
try { try {
@ -119,8 +97,7 @@ export const useUploadsStore = defineStore('uploads', {
file: File, file: File,
talkType: number, talkType: number,
receiverId: number, receiverId: number,
username: string, clientUploadId: string,
uploadId: string,
onProgress: (percentage: number) => void, onProgress: (percentage: number) => void,
onComplete: (data: any) => void onComplete: (data: any) => void
) { ) {
@ -147,13 +124,11 @@ export const useUploadsStore = defineStore('uploads', {
talk_type: talkType, talk_type: talkType,
receiver_id: receiverId, receiver_id: receiverId,
upload_id: upload_id, upload_id: upload_id,
client_upload_id: uploadId, // 客户端生成的上传ID用于前端标识 client_upload_id: clientUploadId, // 客户端生成的上传ID用于前端标识
uploadIndex: 0, uploadIndex: 0,
percentage: 0, percentage: 0,
status: 0, // 文件上传状态 0:等待上传 1:上传中 2:上传完成 3:网络异常 status: 0, // 文件上传状态 0:等待上传 1:上传中 2:上传完成 3:网络异常
files: fileChunks, files: fileChunks,
avatar: '',
username: username,
is_paused: false, is_paused: false,
onProgress: onProgress, onProgress: onProgress,
onComplete: onComplete, onComplete: onComplete,
@ -162,7 +137,7 @@ export const useUploadsStore = defineStore('uploads', {
this.isShow = false // 不显示上传管理抽屉 this.isShow = false // 不显示上传管理抽屉
// 开始上传分片 // 开始上传分片
this.triggerUpload(upload_id, uploadId) this.triggerUpload(upload_id, clientUploadId)
} else { } else {
message.error(res.message) message.error(res.message)
onProgress(-1) // 通知上传失败 onProgress(-1) // 通知上传失败
@ -198,16 +173,16 @@ export const useUploadsStore = defineStore('uploads', {
// 上传当前分片 // 上传当前分片
try { try {
const res = await ServeFileSubareaUpload(form) const res = await ServeFileSubareaUpload(form)
// 获取最新的项目状态,确保仍然存在且没有被暂停 // 获取最新的项目状态,确保仍然存在且没有被暂停
const updatedItem = this.findItem(uploadId) const updatedItem:any = this.findItem(uploadId)
if (!updatedItem || updatedItem.is_paused) return
if (res.code == 200) { if (res.code == 200) {
// 当前分片上传成功,增加索引 // 当前分片上传成功,增加索引
updatedItem.uploadIndex++ updatedItem.uploadIndex++
// 计算上传进度 // 计算上传进度
const percentage = (updatedItem.uploadIndex / updatedItem.files.length) * 100 const percentage = (updatedItem.uploadIndex / updatedItem.files.length) * 100
updatedItem.percentage = parseFloat(percentage.toFixed(1)) updatedItem.percentage = parseFloat(percentage.toFixed(1))
@ -234,8 +209,7 @@ export const useUploadsStore = defineStore('uploads', {
console.error(`分片上传失败,错误码: ${res.code},错误信息: ${res.message || '未知错误'}`); console.error(`分片上传失败,错误码: ${res.code},错误信息: ${res.message || '未知错误'}`);
updatedItem.status = 3 updatedItem.status = 3
// 尝试重试当前分片
this.retryUpload(uploadId, clientUploadId, res.message || '上传失败,请重试')
} }
} catch (error) { } catch (error) {
console.error("分片上传错误:", error); console.error("分片上传错误:", error);
@ -248,37 +222,10 @@ export const useUploadsStore = defineStore('uploads', {
if (updatedItem.is_paused) return if (updatedItem.is_paused) return
updatedItem.status = 3 updatedItem.status = 3
// 尝试重试当前分片
this.retryUpload(uploadId, clientUploadId, '网络错误,正在重试')
} }
}, },
// 重试上传
retryUpload(uploadId: string, clientUploadId?: string, errorMessage?: string) {
const item = this.findItem(uploadId)
if (!item) return
// 如果有暂停/恢复按钮,先告知用户上传出错
if (item.onProgress) {
item.onProgress(-1)
}
// 显示错误提示
message.warning(errorMessage)
// 创建一个5秒后自动重试的机制
setTimeout(() => {
const currentItem = this.findItem(uploadId)
if (!currentItem) return
// 如果用户没有手动暂停,则自动重试
if (!currentItem.is_paused) {
console.log('正在重试上传分片...');
this.triggerUpload(uploadId, clientUploadId)
}
}, 5000)
},
// 完成上传 // 完成上传
async completeUpload(item: UploadItem, clientUploadId: string) { async completeUpload(item: UploadItem, clientUploadId: string) {

View File

@ -17,6 +17,7 @@ import { ExclamationCircleFilled } from '@ant-design/icons-vue'
import { useUserStore } from '@/store' import { useUserStore } from '@/store'
import RevokeMessage from '@/components/talk/message/RevokeMessage.vue' import RevokeMessage from '@/components/talk/message/RevokeMessage.vue'
import { voiceToText } from '@/api/chat.js' import { voiceToText } from '@/api/chat.js'
import {confirmBox} from '@/components/confirm-box/service.js'
const props = defineProps({ const props = defineProps({
uid: { uid: {
type: Number, type: Number,
@ -314,6 +315,13 @@ watch(
// onMounted(() => { // onMounted(() => {
// onLoad({ ...props, limit: 30 }) // onLoad({ ...props, limit: 30 })
// }) // })
const retry=()=>{
confirmBox({
content:'确定重发吗'
}).then(()=>{
})
}
</script> </script>
<template> <template>
@ -414,7 +422,7 @@ watch(
" "
class="mr-10px" class="mr-10px"
> >
<n-button text style="font-size: 20px;"> <n-button text style="font-size: 20px;" @click="retry">
<n-icon color="#CF3050"> <n-icon color="#CF3050">
<ExclamationCircleFilled /> <ExclamationCircleFilled />
</n-icon> </n-icon>

View File

@ -116,15 +116,13 @@ const onSendVideoEvent = async ({ data }) => {
msg_type: 5, // msg_type: 5, //
user_id: props.uid, user_id: props.uid,
receiver_id: props.receiver_id, receiver_id: props.receiver_id,
nickname: '我', //
avatar: userStore.avatar, //
is_revoke: 0, is_revoke: 0,
is_mark: 0, is_mark: 0,
is_read: 1, is_read: 1,
content: '', content: '',
created_at: parseTime(new Date(), '{y}-{m}-{d} {h}:{i}'), created_at: parseTime(new Date(), '{y}-{m}-{d} {h}:{i}'),
extra: { extra: {
url: '', // url: '',
size: data.size, size: data.size,
is_uploading: true, is_uploading: true,
upload_id: uploadId, upload_id: uploadId,
@ -141,35 +139,12 @@ const onSendVideoEvent = async ({ data }) => {
data, data,
props.talk_type, props.talk_type,
props.receiver_id, props.receiver_id,
dialogueStore.talk.username,
uploadId, uploadId,
async (percentage) => { async (percentage) => {
dialogueStore.updateUploadProgress(uploadId, percentage) dialogueStore.updateUploadProgress(uploadId, percentage)
}, },
async () => { async () => {
dialogueStore.batchDelDialogueRecord([uploadId]) dialogueStore.batchDelDialogueRecord([uploadId])
// console.log('videoData', videoData)
// //
// //
// dialogueStore.completeUpload(uploadId, {
// url: videoData.data.ori_url,
// cover: videoData.data.cover_url
// })
// //
// let finalMessage = {
// type: 'video',
// url: videoData.data.ori_url,
// size: data.size
// }
// //
// onSendMessage(finalMessage, () => {
// //
// dialogueStore.batchDelDialogueRecord([uploadId])
// })
} }
) )
} }
@ -185,10 +160,10 @@ const onSendFileEvent = ({ data }) => {
if (data.size > maxsize) { if (data.size > maxsize) {
return window['$message'].warning('上传文件不能超过100M!') return window['$message'].warning('上传文件不能超过100M!')
} }
const uploadId = `file-${Date.now()}-${Math.floor(Math.random() * 1000)}` const clientUploadId = `file-${Date.now()}-${Math.floor(Math.random() * 1000)}`
const tempMessage = { const tempMessage = {
msg_id: uploadId, msg_id: clientUploadId,
sequence: Date.now(), sequence: Date.now(),
talk_type: props.talk_type, talk_type: props.talk_type,
msg_type: 6, msg_type: 6,
@ -204,20 +179,19 @@ const onSendFileEvent = ({ data }) => {
url: '', url: '',
size: data.size, size: data.size,
is_uploading: true, is_uploading: true,
upload_id: uploadId, upload_id: clientUploadId,
percentage: 0 percentage: 0
}, },
erp_user_id: 4692,
float: 'right' float: 'right'
} }
dialogueStore.addDialogueRecord(tempMessage) dialogueStore.addDialogueRecord(tempMessage)
uploadsStore.initUploadFile(data, props.talk_type, props.receiver_id, dialogueStore.talk.username,uploadId, uploadsStore.initUploadFile(data, props.talk_type, props.receiver_id,clientUploadId,
async (percentage) => { async (percentage) => {
dialogueStore.updateUploadProgress(uploadId, percentage) dialogueStore.updateUploadProgress(clientUploadId, percentage)
}, },
async () => { async () => {
dialogueStore.batchDelDialogueRecord([uploadId]) dialogueStore.batchDelDialogueRecord([clientUploadId])
} }
) )
} }

View File

@ -47,7 +47,7 @@ export default defineConfig(({ mode }) => {
compressPlugin(), compressPlugin(),
UnoCSS(), UnoCSS(),
vueDevTools({ vueDevTools({
launchEditor: 'cursor', launchEditor: 'trae',
}) })
], ],
define: { define: {