This commit is contained in:
hy
2026-08-03 17:09:31 +08:00
26 changed files with 2546 additions and 370 deletions

View File

@@ -105,11 +105,13 @@
font-size: 36rpx;
font-weight: 700;
color: #1a1a2e;
align-items: center;
}
.ncd-header uni-icons {
display: flex;
flex-shrink: 0;
align-items: center;
}
.ncd-options {
@@ -325,6 +327,62 @@
color: #ff4d4f;
}
/* ==========WYXD 文件入口横幅========== */
.wyxd-banner {
display: flex;
align-items: center;
padding: 14rpx 24rpx;
margin: 6rpx 20rpx;
background: linear-gradient(135deg, #e8f4fd, #f0e6ff);
border: 1.5rpx solid rgba(120, 120, 220, 0.25);
border-radius: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(100, 100, 200, 0.08);
}
.wyxd-banner:active {
background: linear-gradient(135deg, #d8eafb, #e6d8ff);
transform: scale(0.98);
}
.wyxd-banner-icon {
font-size: 32rpx;
color: #6c6ce0;
margin-right: 12rpx;
}
.wyxd-banner-text {
flex: 1;
font-size: 26rpx;
font-weight: 600;
color: #4a4ab8;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.wyxd-banner-arrow {
font-size: 24rpx;
color: #9999cc;
}
.wyxd-banner-close {
width: 36rpx;
height: 36rpx;
line-height: 36rpx;
text-align: center;
font-size: 28rpx;
color: #9999cc;
border-radius: 50%;
background: rgba(100, 100, 200, 0.08);
margin-left: 8rpx;
flex-shrink: 0;
}
.wyxd-banner-close:active {
background: rgba(100, 100, 200, 0.18);
color: #6c6ce0;
}
/* ==========聊天主体部分========== */
.main-chat {
display: flex;

View File

@@ -64,6 +64,15 @@
</view>
</view>
<!-- WYXD 文件入口从工作区 AI 修改进入时显示 -->
<view v-if="hasWyxdFile" class="wyxd-banner">
<view class="wyxd-banner-icon iconfont icon-wenjian"></view>
<view class="wyxd-banner-text" @click="goWyxdViewer">查看: {{ wyxdFileName }}</view>
<view class="wyxd-banner-arrow" @click="goWyxdViewer">&rsaquo;</view>
<view class="wyxd-banner-close" @click.stop="hasWyxdFile = false">&times;</view>
</view>
<view class="main-chat">
<scroll-view class="chat-messages" direction="vertical" scroll-y :scroll-into-view="scrollToView"
:upper-threshold="0" :scroll-with-animation="!isThinking" @scroll="onScroll">
@@ -210,10 +219,6 @@
</view>
</view>
<!-- 底部锚点两个交替切换确保流式时每次都能触发滚动到底部 -->
<!-- <view>
<view id="chat-bottom-a" style="height: 0rpx;width: 1rpx;"></view>
<view id="chat-bottom-b" style="height: 0rpx;width: 1rpx;"></view>
</view> -->
<view id="chat-bottom-a" style="height: 1rpx;"></view>
<view id="chat-bottom-b" style="height: 1rpx;"></view>
</scroll-view>
@@ -297,6 +302,7 @@
watch
} from 'vue';
import {
onLoad,
onShow,
onUnload
} from '@dcloudio/uni-app';
@@ -340,6 +346,7 @@
chooseFile
} from '@/uni_modules/lime-choose-file'
import socketManager from '../../utils/socket';
import { openFile as openFileNative, downloadAndOpen } from '@/utils/fileOpener.js'
const friendSocketStore = useFriendSocketStore()
// 连接实时通讯eriendsocket
@@ -360,6 +367,13 @@
// 聊天类型ai好友群聊
const ChatType = ref(0)
// WYXD 关联数据(从 WorkSpace 的「AI修改」进入时携带
const wyxdWorkspaceId = ref('')
const wyxdFilePath = ref('')
const wyxdFileName = ref('')
const wyxdUserToken = ref('')
const hasWyxdFile = ref(false)
// marked.setOptions({
// breaks: true, // 自动把换行符转成 <br> 换行
@@ -453,6 +467,35 @@
return text
}
// 判断链接是否为可下载文件
const isDownloadLink = (url) => {
const supportedExtensions = ['html', 'htm', 'xlsx', 'xls', 'pdf', 'zip', 'rar', '7z', 'md', 'markdown',
'docx', 'doc', 'pptx', 'ppt', 'txt', 'csv', 'json', 'xml', 'yaml', 'yml',
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp', 'ico', 'tiff', 'tif', 'dxf'
]
const extPattern = supportedExtensions.join('|')
const extRegex = new RegExp(`\\.(${extPattern})(?:\\?|$)`, 'i')
return extRegex.test(url)
}
// 渲染下载按钮 HTML
const renderDownloadBtn = (href, displayName) => {
const ext = ((href.split('.').pop() || '').split('?')[0] || '').toLowerCase()
const fileInfo = getFileTypeInfo(ext)
const name = displayName || extractFileNameFromUrl(href)
return '<span class="chat-inline-link chat-download-btn" data-url="' + href + '">'
+ '<span class="chat-dl-icon">' + fileInfo.emoji + '</span>'
+ '<span class="chat-dl-name">' + name + '</span>'
+ '</span>'
}
// 支持的文件扩展名(用于原始 URL 检测)
const supportedExtensions = ['html', 'htm', 'xlsx', 'xls', 'pdf', 'zip', 'rar', '7z', 'md', 'markdown',
'docx', 'doc', 'pptx', 'ppt', 'txt', 'csv', 'json', 'xml', 'yaml', 'yml',
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp', 'ico', 'tiff', 'tif', 'dxf'
]
const extPattern = supportedExtensions.join('|')
// Markdown转HTML节点
const pareseMarkdown = (content) => {
if (!content) return ''
@@ -462,14 +505,60 @@
try {
// 转义单词中的单下划线,防止 snarkdown 误解析为斜体
content = escapeLoneUnderscores(content)
// 修复1: URL 中的中文括号编码,防止 snarkdown 截断URL
// [text](http://xxx/高二(1)班.xlsx) → [text](http://xxx/高二%281%29班.xlsx)
content = content.replace(
/\[([^\]]+)\]\((https?:\/\/[^\s]+)\)/g,
(m, name, url) => '[' + name + '](' + url.replace(/\(/g, '%28').replace(/\)/g, '%29') + ')'
)
// 修复2: 误用括号 [高二](1)班成绩表 → 高二(1)班成绩表
// 当 (数字/短文本) 后紧跟中文时说明不是真实URL还原为纯文本
content = content.replace(
/\[([^\]]+)\]\((\d{1,2}|[^\s)]{1,3})\)(?=[\u4e00-\u9fff])/g,
'$1($2)'
)
// 先转换表格,再用 snarkdown 处理其他 Markdown
content = convertMarkdownTable(content)
let html = snarkdown(content)
// 将 <a href="..."> 替换为自定义 span避免 WebView 触发默认下载
// 步骤1暂存 <a> 标签,避免 raw URL 替换误伤 href 内的链接
const linkPlaceholders = []
html = html.replace(
/<a\s+href="([^"]*)"[^>]*>(.*?)<\/a>/gi,
'<span class="chat-inline-link" data-url="$1">$2</span>'
(match, href, innerText) => {
const idx = linkPlaceholders.length
linkPlaceholders.push({ href, innerText })
return '\x00LINK_' + idx + '\x00'
}
)
// 步骤2将正文中的原始文件 URL 替换为下载按钮
const rawUrlRegex = new RegExp(
`https?://[^\\s<>"'']+\\.(${extPattern})(?:[?#][^\\s<>"'']*)?`,
'gi'
)
html = html.replace(rawUrlRegex, (url) => {
return renderDownloadBtn(url)
})
// 步骤3恢复 <a> 标签并分类处理
html = html.replace(/\x00LINK_(\d+)\x00/g, (_, idx) => {
const { href, innerText } = linkPlaceholders[idx]
if (isDownloadLink(href)) {
const plainText = innerText
.replace(/<[^>]*>/g, ' ')
.replace(/&nbsp;/gi, ' ')
.replace(/\s+/g, ' ')
.trim()
return renderDownloadBtn(href, plainText)
}
// 普通链接保持原有样式
return '<span class="chat-inline-link" data-url="' + href + '">' + innerText + '</span>'
})
return html
} catch (e) {
console.error('解析失败', e)
@@ -510,15 +599,7 @@
}
// 打开文件
const openFile = (url) => {
uni.downloadFile({
url: url,
success: (res) => {
uni.openDocument({
filePath: res.tempFilePath,
showMenu: true
})
}
})
downloadAndOpen(url);
}
// 文件大小格式化
const formatFileSize = (size) => {
@@ -625,6 +706,17 @@
})
}
// 跳转到 WYXD 查看器(强制重新下载,不走缓存)
const goWyxdViewer = () => {
uni.navigateTo({
url: '/pages/WYXD/WYXD?workspaceId=' + encodeURIComponent(wyxdWorkspaceId.value)
+ '&filePath=' + encodeURIComponent(wyxdFilePath.value)
+ '&fileName=' + encodeURIComponent(wyxdFileName.value)
+ '&userToken=' + encodeURIComponent(wyxdUserToken.value)
+ '&forceDownload=1'
})
}
// 跳转到云端数据库
const goCloudDatabase = () => {
// 保存当前会话id确保从工作区返回时能恢复
@@ -872,6 +964,50 @@
}
return urlStr
}
// 文件类型 → 图标和标签映射
const getFileTypeInfo = (ext) => {
const map = {
// 文档类
pdf: { emoji: '📕', label: 'PDF', color: '#e74c3c' },
doc: { emoji: '📘', label: 'Word', color: '#2b579a' },
docx: { emoji: '📘', label: 'Word', color: '#2b579a' },
xls: { emoji: '📗', label: 'Excel', color: '#217346' },
xlsx: { emoji: '📗', label: 'Excel', color: '#217346' },
ppt: { emoji: '📙', label: 'PPT', color: '#d24726' },
pptx: { emoji: '📙', label: 'PPT', color: '#d24726' },
txt: { emoji: '📄', label: '文本', color: '#666' },
md: { emoji: '📝', label: 'Markdown', color: '#333' },
markdown: { emoji: '📝', label: 'Markdown', color: '#333' },
csv: { emoji: '📊', label: 'CSV', color: '#217346' },
json: { emoji: '📋', label: 'JSON', color: '#f0a500' },
xml: { emoji: '📋', label: 'XML', color: '#e67e22' },
yaml: { emoji: '📋', label: 'YAML', color: '#e67e22' },
yml: { emoji: '📋', label: 'YAML', color: '#e67e22' },
// 压缩包
zip: { emoji: '📦', label: 'ZIP', color: '#f39c12' },
rar: { emoji: '📦', label: 'RAR', color: '#f39c12' },
'7z': { emoji: '📦', label: '7Z', color: '#f39c12' },
// 网页
html: { emoji: '🌐', label: '网页', color: '#e44d26' },
htm: { emoji: '🌐', label: '网页', color: '#e44d26' },
// 图片
jpg: { emoji: '🖼️', label: '图片', color: '#9b59b6' },
jpeg: { emoji: '🖼️', label: '图片', color: '#9b59b6' },
png: { emoji: '🖼️', label: '图片', color: '#9b59b6' },
gif: { emoji: '🖼️', label: 'GIF', color: '#9b59b6' },
bmp: { emoji: '🖼️', label: '图片', color: '#9b59b6' },
svg: { emoji: '🖼️', label: 'SVG', color: '#9b59b6' },
webp: { emoji: '🖼️', label: '图片', color: '#9b59b6' },
ico: { emoji: '🖼️', label: '图标', color: '#9b59b6' },
tiff: { emoji: '🖼️', label: '图片', color: '#9b59b6' },
tif: { emoji: '🖼️', label: '图片', color: '#9b59b6' },
// CAD/设计类
dxf: { emoji: '📐', label: 'CAD/DXF', color: '#2c3e50' },
}
return map[ext] || { emoji: '📎', label: ext.toUpperCase(), color: '#888' }
}
const showMessageDetail = (message) => {
detailLinks.value = extractLinks(message.content || '')
showMessageModal.value = true
@@ -932,12 +1068,8 @@
success: (downloadRes) => {
if (downloadRes.statusCode === 200) {
hideDownloadToast()
uni.openDocument({
filePath: downloadRes.tempFilePath,
showMenu: true,
fail: () => {
showDownloadToast('error', '无法打开此文件', 2500)
}
openFileNative(downloadRes.tempFilePath, () => {
showDownloadToast('error', '无法打开此文件', 2500)
})
} else {
showDownloadToast('error', '下载失败', 2500)
@@ -1185,14 +1317,16 @@
if (reason === 'pc offline') {
isThinking.value = false
socketStore.authFailReason = ''
nextTick(() => {
uni.showModal({
title: '提示',
content: 'PC端不在线不可聊天',
showCancel: false,
confirmText: '知道了'
})
uni.showToast({
title: 'PC端不在线请先登录',
icon: 'none',
duration: 500
})
setTimeout(() => {
uni.reLaunch({
url: '/pages/Login/Login'
})
}, 500)
}
})
@@ -1387,7 +1521,8 @@
const takeConversationMessages = async () => {
try {
allmessages.value = await getConversationMessages(userToken.value, currentSessionId.value) || [];
// console.log("获取到的所有消息:", JSON.stringify(allmessages.value) );
console.log("获取到的所有消息:", JSON.stringify(allmessages.value) );
console.log("获取到的所有消息:", allmessages.value);
// currentMessages.value = allmessages.value.slice(-pageInfoNumber);
// 数据获取后执行滚动
scrollToBottom();
@@ -1505,6 +1640,7 @@
if (oldId && oldId !== newId) {
isLoadingMessages.value = true
allmessages.value = []
hasWyxdFile.value = false
}
switch (ChatType.value) {
case 0:
@@ -1528,6 +1664,17 @@
immediate: true
})
// 接收 WYXD 参数从工作区「AI修改」进入时由 navigateTo 传入)
onLoad((options) => {
if (options.wyxdFilePath) {
wyxdWorkspaceId.value = options.wyxdWorkspaceId ? decodeURIComponent(options.wyxdWorkspaceId) : ''
wyxdFilePath.value = decodeURIComponent(options.wyxdFilePath)
wyxdFileName.value = options.wyxdFileName ? decodeURIComponent(options.wyxdFileName) : '未命名文档'
wyxdUserToken.value = options.wyxdUserToken ? decodeURIComponent(options.wyxdUserToken) : ''
hasWyxdFile.value = true
}
})
onMounted(() => {
// currentSessionId.value = getCurrentSessionId()
})
@@ -1648,6 +1795,43 @@
text-decoration: underline;
word-break: break-all;
}
/* 可下载文件链接 → 按钮卡片样式 */
.chat-download-btn {
display: inline-flex !important;
align-items: center;
gap: 8rpx;
padding: 8rpx 14rpx;
margin: 4rpx 0;
background: #f5f7fb;
border: 2rpx solid #e4e8f0;
border-radius: 8rpx;
text-decoration: none !important;
color: #333 !important;
word-break: break-all;
transition: all 0.15s ease;
&:active {
background: #eef1f7;
border-color: #c8cde0;
transform: scale(0.98);
}
.chat-dl-icon {
font-size: 26rpx !important;
line-height: 1;
flex-shrink: 0;
}
.chat-dl-name {
font-size: 16px !important;
font-weight: 500;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
/* 流式气泡中的加载动画 */

View File

@@ -475,6 +475,7 @@
uploadCloudDbFile,
deleteCloudDb
} from '@/utils/cloud-api.js';
import { openFile as openFileNative } from '@/utils/fileOpener.js'
const userToken = ref('');
const userId = ref('');
@@ -826,19 +827,10 @@
}
};
// 使用系统默认应用打开
// 打开文件(弹出应用选择菜单)
const openFile = (filePath) => {
uni.openDocument({
filePath: filePath,
success: () => uni.showToast({
title: '打开成功',
icon: 'success'
}),
fail: (err) => {
console.error('打开文件失败:', err);
// 如果默认应用打开失败,尝试弹出选择菜单
openFileWithMenu(filePath);
}
openFileNative(filePath, () => {
openFileWithMenu(filePath);
});
};

View File

@@ -32,9 +32,14 @@
<!-- 幻灯片 -->
<view v-else class="wyxd-slide-area">
<scroll-view class="wyxd-slide-scroll" scroll-y :show-scrollbar="false">
<view class="wyxd-slide-card" v-html="currentSlide.html"></view>
</scroll-view>
<swiper class="wyxd-swiper" :current="currentIndex"
@change="onSwiperChange" :duration="250" easing-function="easeOutCubic">
<swiper-item v-for="(slide, idx) in slides" :key="idx">
<scroll-view class="wyxd-slide-scroll" scroll-y :show-scrollbar="false">
<view class="wyxd-slide-card" v-html="slide.html"></view>
</scroll-view>
</swiper-item>
</swiper>
</view>
</view>
@@ -89,10 +94,12 @@ const statusText = computed(() => {
const displayName = computed(() => fileName.value || '未命名文档')
const currentSlide = computed(() => slides.value[currentIndex.value] || { html: '' })
const pad = (n) => String(n).padStart(2, '0')
const onSwiperChange = (e) => {
currentIndex.value = e.detail.current
}
const goBack = () => {
uni.navigateBack()
}
@@ -181,7 +188,8 @@ onLoad((options) => {
userToken: userToken.value
})
if (isWyxdCached(workspaceId.value, filePath.value)) {
// forceDownload 强制重新下载不走缓存
if (options.forceDownload !== '1' && isWyxdCached(workspaceId.value, filePath.value)) {
loadFromCache()
} else {
startDownload()
@@ -247,6 +255,11 @@ onLoad((options) => {
/* padding: 20rpx; */
}
.wyxd-swiper {
width: 100%;
height: 100%;
}
.wyxd-slide-scroll {
height: 100%;
background: #fff;

View File

@@ -380,3 +380,164 @@
.workspace-footer .iconfont {
font-size: 40rpx !important;
}
/* ===== 标签切换 ===== */
.workspace-tabs {
display: flex;
width: 100%;
padding: 0 24rpx;
box-sizing: border-box;
gap: 20rpx;
}
.tab-item {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
padding: 18rpx 0;
font-size: 28rpx;
font-weight: 500;
color: #999;
border-bottom: 4rpx solid transparent;
transition: all 0.2s ease;
position: relative;
}
.tab-item.active {
color: #6c63ff;
border-bottom-color: #6c63ff;
}
.tab-badge {
margin-left: 8rpx;
background: #ff5e57;
color: #fff;
font-size: 20rpx;
padding: 2rpx 10rpx;
border-radius: 20rpx;
line-height: 1.4;
}
/* ===== 下载列表 ===== */
.download-content {
flex: 1;
overflow: auto;
}
.download-empty {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding-top: 200rpx;
color: #bbb;
font-size: 28rpx;
}
.download-empty .iconfont {
font-size: 140rpx;
color: #ddd;
margin-bottom: 20rpx;
}
.download-list {
padding: 20rpx 24rpx;
}
.download-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12rpx 8rpx 20rpx;
font-size: 24rpx;
color: #999;
}
.clear-btn {
color: #ff5e57;
font-size: 24rpx;
font-weight: 500;
padding: 4rpx 12rpx;
}
.download-item {
display: flex;
align-items: center;
padding: 24rpx 20rpx;
background: rgba(255, 255, 255, 0.85);
border-radius: 20rpx;
margin-bottom: 16rpx;
transition: all 0.15s ease;
}
.download-item:active {
transform: scale(0.98);
background: rgba(255, 255, 255, 0.95);
}
.di-icon {
width: 80rpx;
height: 80rpx;
display: flex;
justify-content: center;
align-items: center;
background: rgba(108, 99, 255, 0.08);
border-radius: 18rpx;
margin-right: 20rpx;
flex-shrink: 0;
}
.di-icon .iconfont {
font-size: 40rpx;
color: #6c63ff;
}
.di-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 6rpx;
min-width: 0;
}
.di-name {
font-size: 28rpx;
font-weight: 500;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.di-meta {
font-size: 22rpx;
color: #999;
}
.di-path {
font-size: 20rpx;
color: #bbb;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.di-delete {
width: 56rpx;
height: 56rpx;
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
}
.di-delete .iconfont {
font-size: 36rpx;
color: #ccc;
transition: color 0.15s ease;
}
.di-delete:active .iconfont {
color: #ff5e57;
}

View File

@@ -66,8 +66,20 @@
<view v-if="isSearchFocus" class="cancel-search" @click="handleSearchFocus">取消</view>
</view>
<!-- 标签切换 -->
<view class="workspace-tabs">
<view class="tab-item" :class="{ active: activeTab === 'workspace' }" @click="switchTab('workspace')">
工作区
</view>
<view class="tab-item" :class="{ active: activeTab === 'downloads' }" @click="switchTab('downloads')">
下载列表
<text v-if="downloadList.length > 0" class="tab-badge">{{ downloadList.length }}</text>
</view>
</view>
</view>
<view class="workspace-content">
<!-- ===== 工作区文件 ===== -->
<view class="workspace-content" v-if="activeTab === 'workspace'">
<view class="folder-null" v-if="currentFolderList.length===0">
<view class="iconfont icon-wenjianjia"></view>
文件夹为空
@@ -89,6 +101,34 @@
</view>
</view>
</view>
<!-- ===== 下载列表 ===== -->
<view class="download-content" v-if="activeTab === 'downloads'">
<view v-if="downloadList.length === 0" class="download-empty">
<text class="iconfont icon-xiazai"></text>
<text>暂无下载文件</text>
</view>
<view v-else class="download-list">
<view class="download-header">
<text>已下载 {{ downloadList.length }} 个文件</text>
<text class="clear-btn" @click="handleClearDownloads">清空</text>
</view>
<view class="download-item" v-for="item in downloadList" :key="item.id" @click="openDownloadFile(item)">
<view class="di-icon">
<text class="iconfont" :class="getFileIcon(item.name)"></text>
</view>
<view class="di-info">
<text class="di-name">{{ item.name }}</text>
<text class="di-meta">{{ formatDownloadTime(item.time) }} · {{ formatDownloadSize(item.size) }}</text>
<text class="di-path">{{ item.displayPath || item.path }}</text>
</view>
<view class="di-delete" @click.stop="handleDeleteDownload(item.id)">
<text class="iconfont icon-del"></text>
</view>
</view>
</view>
</view>
<view v-if="isSelectFolder" class="workspace-footer" :class="{'folder-actions':selectFileList.length>0}">
<view class="iconfont icon-shangchuan" @click="handleUploadFile"></view>
<!-- <view class="iconfont icon-fuzhi"></view> -->
@@ -121,11 +161,22 @@
workspaceUploadFileByURL,
updateWorkspaceFileName
} from '@/utils/cloud-api.js'
import { openFile as openFileNative } from '@/utils/fileOpener.js'
import {
getDownloadList,
saveDownload,
deleteDownload,
clearAllDownloads,
formatFileSize,
formatTime
} from '@/utils/downloadManager.js'
const UserToken = ref('')
const workspaceId = ref('')
const navbarBeforeTitle = ref('');
const navbarTitle = ref('工作区');
const activeTab = ref('workspace')
const downloadList = ref([])
// 选择文件模式
const isSelectFolder = ref(false);
@@ -284,20 +335,35 @@
const handleLongPress = (folder) => {
if (isSelectFolder.value) return;
const isWyxd = folder.name && folder.name.toLowerCase().endsWith('.wyxd');
const itemList = isWyxd ? ['下载', '删除', 'WYXD查看器'] : ['下载', '删除'];
const itemList = isWyxd ? ['下载', '删除', 'WYXD查看器', 'AI修改'] : ['下载', '删除'];
uni.showActionSheet({
title: folder.name,
itemList: itemList,
success: (res) => {
switch (res.tapIndex) {
case 0:
handleDownload(folder);
break;
case 1:
handleDelete(folder);
break;
case 2:
if (isWyxd) openWyxdViewer(folder);
break;
if (isWyxd) {
switch (res.tapIndex) {
case 0:
handleDownload(folder);
break;
case 1:
handleDelete(folder);
break;
case 2:
openWyxdViewer(folder);
break;
case 3: // 'AI修改',
handleAiModify(folder);
break;
}
} else {
switch (res.tapIndex) {
case 0:
handleDownload(folder);
break;
case 1:
handleDelete(folder);
break;
}
}
}
})
@@ -379,6 +445,7 @@
const downloadUrl = fileInfo.url;
const fileName = folder.name;
const fileSize = fileInfo.size || 0;
uni.showLoading({
title: '下载中...'
@@ -391,8 +458,14 @@
uni.hideLoading();
if (downloadRes.statusCode === 200) {
const tempPath = downloadRes.tempFilePath;
// 下载成功,直接打开文件
console.log("// 下载成功,文件下载路径为:",tempPath);
// 持久化保存到本地
saveDownload(tempPath, fileName, fileSize).then((record) => {
downloadList.value.unshift(record);
uni.showToast({ title: '下载成功', icon: 'success' });
}).catch(() => {
// 保存失败不影响打开
});
// 下载成功后打开文件
openFile(tempPath);
} else {
uni.showToast({
@@ -420,42 +493,108 @@
}
};
// 使用系统默认应用打开
// 打开文件(弹出应用选择菜单)
const openFile = (filePath) => {
uni.openDocument({
filePath: filePath,
success: () => uni.showToast({
title: '打开成功',
icon: 'success'
}),
fail: (err) => {
console.error('打开文件失败:', err);
uni.showToast({
title: '无法打开此文件',
icon: 'error'
});
}
openFileNative(filePath, () => {
uni.showToast({ title: '无法打开此文件', icon: 'error' });
});
};
// 选择其他应用打开
const openFileWithMenu = (filePath) => {
uni.openDocument({
filePath: filePath,
showMenu: true,
success: () => uni.showToast({
title: '打开成功',
icon: 'success'
}),
fail: (err) => {
console.error('打开文件失败:', err);
uni.showToast({
title: '无法打开此文件',
icon: 'error'
});
// ===== 下载列表相关 =====
const switchTab = (tab) => {
activeTab.value = tab
if (tab === 'downloads') {
loadDownloads()
}
}
const loadDownloads = () => {
downloadList.value = getDownloadList()
}
const openDownloadFile = (item) => {
// #ifdef APP-PLUS
// 检查公共目录下文件是否存在
plus.io.resolveLocalFileSystemURL(
item.path,
() => {
// 文件存在,直接打开
openFile(item.path)
},
() => {
// 文件已被手动删除
uni.showToast({ title: '文件已失效,将移除记录', icon: 'none' })
handleDeleteDownload(item.id)
}
});
};
)
// #endif
// #ifndef APP-PLUS
uni.getSavedFileList({
success: (res) => {
const found = res.fileList.find(f => f.filePath === item.path)
if (found) {
openFile(item.path)
} else {
uni.showToast({ title: '文件已失效,将移除记录', icon: 'none' })
handleDeleteDownload(item.id)
}
},
fail: () => openFile(item.path)
})
// #endif
}
const handleDeleteDownload = (id) => {
uni.showModal({
title: '删除文件',
content: '确定要删除此下载文件吗?',
success: (res) => {
if (res.confirm) {
deleteDownload(id).then(() => {
downloadList.value = getDownloadList()
uni.showToast({ title: '已删除', icon: 'success' })
}).catch(() => {
uni.showToast({ title: '删除失败', icon: 'error' })
})
}
}
})
}
const handleClearDownloads = () => {
if (downloadList.value.length === 0) return
uni.showModal({
title: '清空下载',
content: '确定要清空所有下载文件吗?此操作不可撤销。',
success: (res) => {
if (res.confirm) {
clearAllDownloads().then(() => {
downloadList.value = []
uni.showToast({ title: '已清空', icon: 'success' })
})
}
}
})
}
const formatDownloadSize = formatFileSize
const formatDownloadTime = formatTime
// const getFileIcon = (fileName) => {
// const ext = fileName.split('.').pop().toLowerCase()
// const iconMap = {
// pdf: 'icon-PDF',
// doc: 'icon-word', docx: 'icon-word',
// xls: 'icon-excel', xlsx: 'icon-excel',
// ppt: 'icon-PPD', pptx: 'icon-PPD',
// zip: 'icon-yasuobao', rar: 'icon-yasuobao', '7z': 'icon-yasuobao',
// jpg: 'icon-tupian', jpeg: 'icon-tupian', png: 'icon-tupian', gif: 'icon-tupian',
// mp3: 'icon-yinle', wav: 'icon-yinle', flac: 'icon-yinle',
// mp4: 'icon-shipin', avi: 'icon-shipin', mov: 'icon-shipin',
// txt: 'icon-txt', md: 'icon-txt',
// }
// return iconMap[ext] || 'icon-weizhiwenjian'
// }
// WYXD 查看器入口
const openWyxdViewer = (folder) => {
@@ -472,6 +611,17 @@
});
};
// AI修改入口跳转对话页并传入 WYXD 信息
const handleAiModify = (folder) => {
const filePath = folder.path.startsWith('./') ? folder.path.slice(2) : folder.path;
uni.navigateTo({
url: '/pages/Chat/Chat?wyxdWorkspaceId=' + encodeURIComponent(workspaceId.value)
+ '&wyxdFilePath=' + encodeURIComponent(filePath)
+ '&wyxdFileName=' + encodeURIComponent(folder.name)
+ '&wyxdUserToken=' + encodeURIComponent(UserToken.value)
});
};
// 删除
const handleDelete = (folder) => {
uni.showModal({
@@ -669,6 +819,7 @@
onMounted(() => {
initCurrentFolderList();
loadDownloads();
})
</script>

View File

@@ -325,6 +325,7 @@
chooseFile
} from '@/uni_modules/lime-choose-file'
import socketManager from '../../utils/socket';
import { openFile as openFileNative } from '@/utils/fileOpener.js'
const friendSocketStore = useFriendSocketStore()
// 连接实时通讯eriendsocket
@@ -490,15 +491,7 @@
}
// 打开文件
const openFile = (url) => {
uni.downloadFile({
url: url,
success: (res) => {
uni.openDocument({
filePath: res.tempFilePath,
showMenu: true
})
}
})
downloadAndOpen(url);
}
// 文件大小格式化
const formatFileSize = (size) => {
@@ -1131,14 +1124,16 @@
if (reason === 'pc offline') {
isThinking.value = false
socketStore.authFailReason = ''
nextTick(() => {
uni.showModal({
title: '提示',
content: 'PC端不在线不可聊天',
showCancel: false,
confirmText: '知道了'
})
uni.showToast({
title: 'PC端不在线',
icon: 'none',
duration: 1500
})
setTimeout(() => {
uni.reLaunch({
url: '/pages/Login/Login'
})
}, 1500)
}
})