添加本地连接下载,取消仅保存

This commit is contained in:
2026-07-21 14:57:19 +08:00
parent bf9d5f3f22
commit d5d9423599
7 changed files with 601 additions and 535 deletions

View File

@@ -52,7 +52,7 @@
align-items: center;
justify-content: center;
background-color: rgba(30, 30, 50, 0.35);
z-index: 1000;
z-index: 9999;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
animation: fadeIn 0.25s ease;
@@ -801,13 +801,15 @@
display: flex;
flex-direction: column;
border-radius: 40rpx;
padding: 40rpx 36rpx;
}
.message-detail-content {
flex: 1;
min-height: 200rpx;
max-height: 60vh;
overflow-y: auto;
padding: 20rpx 0;
padding: 16rpx 0;
font-size: 28rpx;
line-height: 1.7;
color: #333333;

View File

@@ -34,26 +34,6 @@
</view>
</view>
<!-- ===== 消息详情弹窗 ===== -->
<view v-if="showMessageModal" class="ncd-overlay" @click="closeMessageDetail">
<view class="ncd-card message-detail-card" @click.stop>
<view class="ncd-header">
<text class="ncd-title-zh">消息详情</text>
<uni-icons type="closeempty" color="#ff0000" size="24" @click="closeMessageDetail"></uni-icons>
</view>
<scroll-view class="message-detail-content" scroll-y>
<text>{{ detailMessageContent }}</text>
<view v-if="detailLinks.length" class="detail-links-section">
<view class="detail-links-title">链接 ({{ detailLinks.length }})</view>
<view v-for="(link, i) in detailLinks" :key="i" class="detail-link-item" @click="openLink(link)">
<text class="link-text">{{ link }}</text>
</view>
</view>
</scroll-view>
</view>
</view>
<view class="chat-page page-container">
<!-- 弹窗出来时的笼罩层 -->
<view v-if="isChatSidebar" class="mask" @click="handleChatSidebar"></view>
@@ -251,6 +231,40 @@
</view>
</view>
<!-- ===== 消息详情弹窗 ===== -->
<view v-if="showMessageModal" class="ncd-overlay" @click="closeMessageDetail">
<view class="ncd-card message-detail-card" @click.stop>
<view class="ncd-header">
<text class="ncd-title-zh">消息详情</text>
<uni-icons type="closeempty" color="#ff0000" size="24" @click="closeMessageDetail"></uni-icons>
</view>
<scroll-view class="message-detail-content" scroll-y>
<text>{{ detailMessageContent }}</text>
<view v-if="detailLinks.length" class="detail-links-section">
<view class="detail-links-title">链接 ({{ detailLinks.length }})</view>
<view v-for="(link, i) in detailLinks" :key="i" class="detail-link-item" @click="openLink(link)">
<text class="link-text">{{ link }}</text>
</view>
</view>
</scroll-view>
</view>
</view>
<!-- ===== 自定义下载提示层级高于弹窗 ===== -->
<view v-if="downloadToast.show" class="download-toast-overlay" :class="downloadToast.show ? '' : ''">
<view class="download-toast-card" :class="'download-toast-' + downloadToast.type">
<view v-if="downloadToast.type === 'loading'" class="download-toast-icon">
<view class="toast-dot"></view>
<view class="toast-dot"></view>
<view class="toast-dot"></view>
</view>
<text v-else class="download-toast-icon">{{ downloadToast.type === 'success' ? '✓' : '✕' }}</text>
<text class="download-toast-text">{{ downloadToast.message }}</text>
</view>
</view>
</template>
<script setup>
@@ -274,7 +288,8 @@
createWorkspace,
createConversation,
getUserInfo,
getUserAvatar
getUserAvatar,
conversationMessageDownload
} from '@/utils/cloud-api.js'
import {
getToken,
@@ -696,7 +711,7 @@
const detailMessageContent = ref('')
const detailLinks = ref([])
const extractLinks = (text) => {
const regex = /https?:\/\/[^\s<>"{}|\\^`[\]]+/gi
const regex = /https?:\/\/[^\s<>"{}|\\^`\[\]()]+/gi
const matches = text.match(regex)
return matches ? [...new Set(matches)] : []
}
@@ -710,14 +725,62 @@
detailMessageContent.value = ''
detailLinks.value = []
}
const openLink = (url) => {
// #ifdef APP-PLUS
plus.runtime.openURL(url)
// #endif
// #ifdef H5
window.open(url, '_blank')
// #endif
const downloadToast = ref({ show: false, type: 'loading', message: '' })
let downloadToastTimer = null
const showDownloadToast = (type, message, duration) => {
if (downloadToastTimer) clearTimeout(downloadToastTimer)
downloadToast.value = { show: true, type, message }
if (duration > 0) {
downloadToastTimer = setTimeout(() => {
downloadToast.value = { show: false, type: 'loading', message: '' }
}, duration)
}
}
const hideDownloadToast = () => {
if (downloadToastTimer) clearTimeout(downloadToastTimer)
downloadToast.value = { show: false, type: 'loading', message: '' }
}
const openLink = (url) => {
downloadAndHandle(url)
}
const downloadAndHandle = async (url) => {
try {
showDownloadToast('loading', '下载中...', 0)
const workspaceId = uni.getStorageSync('workspace_id') || ''
const result = await conversationMessageDownload(userToken.value, workspaceId, url)
const downloadUrl = typeof result === 'string' ? result : (result?.url || result?.download_url || '')
if (!downloadUrl) {
showDownloadToast('error', '获取下载地址失败', 2500)
return
}
uni.downloadFile({
url: downloadUrl,
success: (downloadRes) => {
if (downloadRes.statusCode === 200) {
hideDownloadToast()
uni.openDocument({
filePath: downloadRes.tempFilePath,
showMenu: true,
fail: () => {
showDownloadToast('error', '无法打开此文件', 2500)
}
})
} else {
showDownloadToast('error', '下载失败', 2500)
}
},
fail: () => {
showDownloadToast('error', '下载失败', 2500)
}
})
} catch (err) {
showDownloadToast('error', '下载失败: ' + (err.message || err), 2500)
}
}
const sendMessage = async () => {
const message = textMessage.value.trim()
@@ -1181,7 +1244,7 @@
const takeConversationMessages = async () => {
try {
allmessages.value = await getConversationMessages(userToken.value, currentSessionId.value) || [];
console.log("获取到的所有消息:", JSON.stringify(allmessages.value) );
// console.log("获取到的所有消息:", JSON.stringify(allmessages.value) );
// currentMessages.value = allmessages.value.slice(-pageInfoNumber);
// 数据获取后执行滚动
scrollToBottom();
@@ -1462,4 +1525,84 @@
color: #007aff;
word-break: break-all;
}
/* ========== 自定义下载 Toast层级高于 ncd-overlay 的 9999========== */
.download-toast-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
z-index: 10001;
pointer-events: none;
}
.download-toast-card {
display: flex;
align-items: center;
justify-content: center;
padding: 24rpx 40rpx;
border-radius: 24rpx;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.15);
animation: toastFadeIn 0.25s ease;
pointer-events: auto;
}
@keyframes toastFadeIn {
from { opacity: 0; transform: scale(0.85); }
to { opacity: 1; transform: scale(1); }
}
.download-toast-loading {
background: rgba(40, 40, 60, 0.92);
}
.download-toast-success {
background: rgba(40, 60, 40, 0.92);
}
.download-toast-error {
background: rgba(60, 30, 30, 0.92);
}
.download-toast-icon {
font-size: 36rpx;
color: #fff;
margin-right: 16rpx;
display: flex;
align-items: center;
}
.download-toast-card.download-toast-loading .download-toast-icon {
gap: 8rpx;
}
.toast-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: #fff;
animation: toastDotBlink 1.2s infinite ease-in-out;
}
.toast-dot:nth-child(2) { animation-delay: 0.3s; }
.toast-dot:nth-child(3) { animation-delay: 0.6s; }
@keyframes toastDotBlink {
0%, 100% { opacity: 0.3; transform: scale(0.75); }
50% { opacity: 1; transform: scale(1.2); }
}
.download-toast-text {
font-size: 28rpx;
color: #fff;
font-weight: 500;
}
</style>