Compare commits
14 Commits
f240176563
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| fbda01a624 | |||
| b7c3fc9ff6 | |||
| 2f1702c858 | |||
| c4fb90b755 | |||
| a3bab0784d | |||
| 9965f2772d | |||
| 849e473c7d | |||
| e057e33601 | |||
| 314a714248 | |||
| 09f4d3ba52 | |||
| c5958812cf | |||
| 85ecfc4c5b | |||
| c90600c66e | |||
| 9bafcaed5d |
@@ -24,7 +24,7 @@
|
||||
|
||||
</view>
|
||||
<view class="new-chat-btn" @click="goContactPages">
|
||||
<text>+ 聊天列表</text>
|
||||
<text>+ 更多聊天列表</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="chat-history">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name" : "test1",
|
||||
"name" : "宇恒一号",
|
||||
"appid" : "__UNI__923FD9E",
|
||||
"description" : "",
|
||||
"versionName" : "1.1.1",
|
||||
|
||||
@@ -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 {
|
||||
@@ -210,6 +212,194 @@
|
||||
background: linear-gradient(135deg, #8b5cf6, #7c3aed);
|
||||
}
|
||||
|
||||
/* ========== 智能体选择弹窗 ========== */
|
||||
.agent-modal-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.45);
|
||||
backdrop-filter: blur(4px);
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.agent-modal {
|
||||
width: 85%;
|
||||
max-height: 70%;
|
||||
background-color: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.agent-modal-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 30rpx 30rpx 20rpx;
|
||||
position: relative;
|
||||
border-bottom: 2rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.agent-modal-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #1a1a2e;
|
||||
}
|
||||
|
||||
.agent-modal-close {
|
||||
position: absolute;
|
||||
right: 24rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
.agent-list {
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
padding: 20rpx 24rpx;
|
||||
max-height: 600rpx;
|
||||
}
|
||||
|
||||
.agent-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60rpx 0;
|
||||
}
|
||||
|
||||
.agent-loading-text {
|
||||
margin-top: 16rpx;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.agent-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80rpx 0;
|
||||
}
|
||||
|
||||
.agent-empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.agent-card {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
background: linear-gradient(135deg, #f8f5ff, #efe8ff);
|
||||
border: 2rpx solid #c4b5fd;
|
||||
border-radius: 20rpx;
|
||||
padding: 28rpx 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
transition: all 0.2s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.agent-card:active {
|
||||
transform: scale(0.98);
|
||||
border-color: #a78bfa;
|
||||
background: linear-gradient(135deg, #f3efff, #e8dcff);
|
||||
}
|
||||
|
||||
.agent-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.agent-avatar {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
border-radius: 20rpx;
|
||||
background: linear-gradient(135deg, #8b5cf6, #7c3aed);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.agent-card-info {
|
||||
margin-left: 16rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.agent-card-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #1a1a2e;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.agent-card-category {
|
||||
font-size: 22rpx;
|
||||
color: #8b5cf6;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.agent-card-desc {
|
||||
font-size: 26rpx;
|
||||
color: #555;
|
||||
line-height: 1.5;
|
||||
word-break: break-all;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 5;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.agent-card-welcome {
|
||||
font-size: 24rpx;
|
||||
color: #7c3aed;
|
||||
margin-top: 12rpx;
|
||||
padding-top: 12rpx;
|
||||
border-top: 2rpx dashed #ddd6fe;
|
||||
word-break: break-all;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.agent-modal-footer {
|
||||
padding: 20rpx 30rpx 30rpx;
|
||||
border-top: 2rpx solid #f0f0f0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.agent-modal-btn {
|
||||
padding: 18rpx 48rpx;
|
||||
border-radius: 16rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.agent-modal-btn-cancel {
|
||||
background: #f0f0f0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.agent-modal-btn-cancel:active {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
|
||||
/* ==========新建聊天列表相关====== */
|
||||
.close-option-card {
|
||||
width: 100%;
|
||||
@@ -317,6 +507,10 @@
|
||||
font-size: 48rpx;
|
||||
}
|
||||
|
||||
.chat-btn-group .head-btn:nth-child(4) {
|
||||
/* 刷新按钮 */
|
||||
}
|
||||
|
||||
.log-out {
|
||||
/* background: #ffd4d4; */
|
||||
}
|
||||
@@ -325,6 +519,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;
|
||||
|
||||
@@ -20,15 +20,59 @@
|
||||
</view>
|
||||
<uni-icons type="arrow-right" class="arrow-right-style"></uni-icons>
|
||||
</view>
|
||||
<view class="ncd-option ncd-intelligence">
|
||||
<view class="ncd-opt-icon">
|
||||
<uni-icons type="star" color="#ffffff" size="30"></uni-icons>
|
||||
<view class="ncd-option ncd-intelligence" @click="selectAgentChat">
|
||||
<view class="ncd-opt-icon">
|
||||
<uni-icons type="star" color="#ffffff" size="30"></uni-icons>
|
||||
</view>
|
||||
<view class="ncd-opt-title">
|
||||
<view class="ncd-opt-title-1">智能体会话</view>
|
||||
<view class="ncd-opt-title-2">选择专属智能体,获得精准专业服务</view>
|
||||
</view>
|
||||
<uni-icons type="arrow-right" class="arrow-right-style"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 智能体选择弹窗 -->
|
||||
<view class="agent-modal-mask" v-if="showAgentModal" @click="closeAgentModal">
|
||||
<view class="agent-modal" @click.stop>
|
||||
<view class="agent-modal-header">
|
||||
<text class="agent-modal-title">选择智能体</text>
|
||||
<view class="agent-modal-close" @click="closeAgentModal">
|
||||
<uni-icons type="closeempty" size="20" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<scroll-view class="agent-list" scroll-y="true">
|
||||
<view v-if="agentLoading" class="agent-loading">
|
||||
<uni-icons type="spinner-cycle" size="24" color="#1677ff"></uni-icons>
|
||||
<text class="agent-loading-text">加载智能体列表...</text>
|
||||
</view>
|
||||
<view v-else-if="agentList.length === 0" class="agent-empty">
|
||||
<text class="agent-empty-text">暂无可用智能体</text>
|
||||
</view>
|
||||
<view v-else
|
||||
v-for="agent in agentList"
|
||||
:key="agent._id"
|
||||
class="agent-card"
|
||||
@click="handleAgentSelect(agent)"
|
||||
>
|
||||
<view class="agent-card-header">
|
||||
<view class="agent-avatar">
|
||||
<uni-icons type="vip" size="22" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<view class="agent-card-info">
|
||||
<text class="agent-card-title">{{ agent.title }}</text>
|
||||
<!-- <text class="agent-card-category">{{ agent.category_name }}</text> -->
|
||||
</view>
|
||||
</view>
|
||||
<view class="ncd-opt-title">
|
||||
<view class="ncd-opt-title-1">智能体会话</view>
|
||||
<view class="ncd-opt-title-2">选择专属智能体,获得精准专业服务</view>
|
||||
</view>
|
||||
<uni-icons type="arrow-right" class="arrow-right-style"></uni-icons>
|
||||
<text class="agent-card-desc">{{ agent.description }}</text>
|
||||
<!-- <text class="agent-card-welcome" v-if="agent.welcome_message">{{ agent.welcome_message }}</text> -->
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="agent-modal-footer">
|
||||
<view class="agent-modal-btn agent-modal-btn-cancel" @click="closeAgentModal">
|
||||
<text>取消</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -41,8 +85,7 @@
|
||||
<view class="chat-sidebar" :class="{'sidebar-show' : isChatSidebar}">
|
||||
<ChatSidebar :chatList="UserConversations" v-model:showNewChatModal="showNewChatModal"
|
||||
v-model:currentSessionId="currentSessionId" v-model:chatType="ChatType"
|
||||
@refresh-conversations="takeUserConversations"
|
||||
@select-chat="isChatSidebar = false">
|
||||
@refresh-conversations="takeUserConversations" @select-chat="isChatSidebar = false">
|
||||
</ChatSidebar>
|
||||
</view>
|
||||
|
||||
@@ -56,18 +99,30 @@
|
||||
<view class="head-btn" @click="goWorkSpace">
|
||||
<view class="iconfont icon-wenjianjia"></view>
|
||||
</view>
|
||||
<view class="head-btn" @click="goCloudDatabase">
|
||||
<view class="iconfont icon-cloud"></view>
|
||||
</view>
|
||||
<view class="head-btn log-out" @click="logOut">
|
||||
<view class="head-btn" @click="goCloudDatabase">
|
||||
<view class="iconfont icon-cloud"></view>
|
||||
</view>
|
||||
<view class="head-btn" @click="handleRefresh">
|
||||
<uni-icons type="refreshempty" size="24" color="#52c41a"></uni-icons>
|
||||
</view>
|
||||
<view class="head-btn log-out" @click="logOut">
|
||||
<view class="iconfont icon-tuichu"></view>
|
||||
</view>
|
||||
</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">›</view>
|
||||
<view class="wyxd-banner-close" @click.stop="hasWyxdFile = false">×</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="true" @scroll="onScroll">
|
||||
:upper-threshold="0" :scroll-with-animation="!isThinking" @scroll="onScroll">
|
||||
<!-- 消息加载中弹窗(仅会话变化时显示) -->
|
||||
<view v-if="isLoadingMessages" class="messages-loading-overlay">
|
||||
<view class="messages-loading-card">
|
||||
@@ -294,6 +349,7 @@
|
||||
watch
|
||||
} from 'vue';
|
||||
import {
|
||||
onLoad,
|
||||
onShow,
|
||||
onUnload
|
||||
} from '@dcloudio/uni-app';
|
||||
@@ -306,7 +362,8 @@
|
||||
createConversation,
|
||||
getUserInfo,
|
||||
getUserAvatar,
|
||||
conversationMessageDownload
|
||||
conversationMessageDownload,
|
||||
getAgentList
|
||||
} from '@/utils/cloud-api.js'
|
||||
import {
|
||||
getToken,
|
||||
@@ -337,6 +394,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
|
||||
@@ -357,6 +415,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> 换行
|
||||
@@ -450,6 +515,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 ''
|
||||
@@ -459,14 +553,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(/ /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)
|
||||
@@ -507,15 +647,7 @@
|
||||
}
|
||||
// 打开文件
|
||||
const openFile = (url) => {
|
||||
uni.downloadFile({
|
||||
url: url,
|
||||
success: (res) => {
|
||||
uni.openDocument({
|
||||
filePath: res.tempFilePath,
|
||||
showMenu: true
|
||||
})
|
||||
}
|
||||
})
|
||||
downloadAndOpen(url);
|
||||
}
|
||||
// 文件大小格式化
|
||||
const formatFileSize = (size) => {
|
||||
@@ -587,6 +719,7 @@
|
||||
})
|
||||
return
|
||||
}
|
||||
closeNewChatModal();
|
||||
takeUserConversations();
|
||||
|
||||
} catch (error) {
|
||||
@@ -604,6 +737,66 @@
|
||||
showNewChatModal.value = false;
|
||||
}
|
||||
|
||||
// 智能体选择相关
|
||||
const showAgentModal = ref(false)
|
||||
const agentList = ref([])
|
||||
const agentLoading = ref(false)
|
||||
|
||||
const selectAgentChat = async () => {
|
||||
try {
|
||||
showNewChatModal.value = false
|
||||
showAgentModal.value = true
|
||||
agentLoading.value = true
|
||||
agentList.value = []
|
||||
// getAgentList 直接返回智能体对象数组
|
||||
const agents = await getAgentList(userToken.value)
|
||||
if (agents && Array.isArray(agents)) {
|
||||
agentList.value = agents
|
||||
} else {
|
||||
uni.showToast({ title: '获取智能体列表失败', icon: 'none' })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取智能体列表失败:', error)
|
||||
uni.showToast({ title: '获取智能体列表失败,请重试', icon: 'none' })
|
||||
} finally {
|
||||
agentLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const closeAgentModal = () => {
|
||||
showAgentModal.value = false
|
||||
agentList.value = []
|
||||
}
|
||||
|
||||
const handleAgentSelect = async (agent) => {
|
||||
console.log("点击了",agent.title);
|
||||
// try {
|
||||
// closeAgentModal()
|
||||
// const chars = '0123456789abcdef'
|
||||
// let name = ''
|
||||
// for (let i = 0; i < 24; i++) {
|
||||
// name += chars[Math.floor(Math.random() * chars.length)]
|
||||
// }
|
||||
// const workspaceId = await createWorkspace(userToken.value, name)
|
||||
// if (!workspaceId) {
|
||||
// uni.showToast({ title: '工作区创建失败', icon: 'none' })
|
||||
// return
|
||||
// }
|
||||
// const loginInfo = uni.getStorageSync('yxd_login_info')
|
||||
// const userName = loginInfo ? (loginInfo.username || '') : ''
|
||||
// const conversationId = await createConversation(userToken.value, workspaceId, agent.title, userName)
|
||||
// if (!conversationId) {
|
||||
// uni.showToast({ title: '智能体会话创建失败', icon: 'none' })
|
||||
// return
|
||||
// }
|
||||
// uni.showToast({ title: `已创建「${agent.title}」会话`, icon: 'success' })
|
||||
// takeUserConversations()
|
||||
// } catch (error) {
|
||||
// console.error('创建智能体会话失败:', error)
|
||||
// uni.showToast({ title: '创建失败,请重试', icon: 'none' })
|
||||
// }
|
||||
}
|
||||
|
||||
// 侧边栏相关
|
||||
const isChatSidebar = ref(false)
|
||||
const handleChatSidebar = () => {
|
||||
@@ -621,6 +814,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,确保从工作区返回时能恢复
|
||||
@@ -638,6 +842,71 @@
|
||||
})
|
||||
}
|
||||
|
||||
// 刷新当前会话消息
|
||||
const handleRefresh = async () => {
|
||||
// 1. 重置输入框状态
|
||||
isThinking.value = false
|
||||
isSelfSent.value = false
|
||||
socketStore.isThinking = false
|
||||
textMessage.value = ''
|
||||
uploadedFiles.value = []
|
||||
previewFileArray.value = []
|
||||
// 移除流式消息占位
|
||||
if (streamingMessageId.value) {
|
||||
allmessages.value = allmessages.value.filter(m => m._id !== streamingMessageId.value)
|
||||
streamingMessageId.value = null
|
||||
}
|
||||
|
||||
const titleMap = { 0: 'AI消息', 1: '好友消息', 2: '群聊消息' }
|
||||
const title = titleMap[ChatType.value] || '消息'
|
||||
uni.showLoading({ title: `刷新${title}中...`, mask: true })
|
||||
|
||||
try {
|
||||
// 2. 检查当前 token 是否还有效
|
||||
const token = getToken()
|
||||
if (!token) {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '登录已失效,请重新登录', icon: 'none', duration: 1500 })
|
||||
setTimeout(() => uni.reLaunch({ url: '/pages/Login/Login' }), 1500)
|
||||
return
|
||||
}
|
||||
await getUserInfo(token)
|
||||
|
||||
// 3. 确保 AI 对话模式下 socket 连接正常(PC 离线由 watcher 实时监控)
|
||||
if (ChatType.value === 0 && !socketStore.isConnected) {
|
||||
await handleConnect()
|
||||
}
|
||||
|
||||
// 4. 刷新消息
|
||||
switch (ChatType.value) {
|
||||
case 0:
|
||||
await takeConversationMessages()
|
||||
break
|
||||
case 1:
|
||||
await takeFriendMessages()
|
||||
break
|
||||
case 2:
|
||||
await takeGroupMessages()
|
||||
break
|
||||
default:
|
||||
await takeConversationMessages()
|
||||
break
|
||||
}
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '已刷新', icon: 'success', duration: 1200 })
|
||||
} catch (e) {
|
||||
uni.hideLoading()
|
||||
// 区分 token 失效和其他错误
|
||||
const errMsg = typeof e === 'string' ? e : (e?.message || '')
|
||||
if (errMsg.includes('token') || errMsg.includes('Token') || errMsg.includes('登录') || errMsg.includes('过期')) {
|
||||
uni.showToast({ title: '登录已失效,请重新登录', icon: 'none', duration: 1500 })
|
||||
setTimeout(() => uni.reLaunch({ url: '/pages/Login/Login' }), 1500)
|
||||
} else {
|
||||
uni.showToast({ title: '刷新失败', icon: 'error', duration: 1500 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const previewFileArray = ref([])
|
||||
const uploadPhoto = () => {
|
||||
console.log('点击了拍照上传');
|
||||
@@ -818,6 +1087,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
|
||||
@@ -878,12 +1191,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)
|
||||
@@ -938,6 +1247,17 @@
|
||||
}
|
||||
// 发送好友消息
|
||||
const sendFriendMessage = async () => {
|
||||
const newMessageData = {
|
||||
"sender": UserId.value,
|
||||
"receiver": receiverId,
|
||||
"content": textMessage.value,
|
||||
"taskId": null,
|
||||
"avatar": null,
|
||||
"createTime": "",
|
||||
"messageType": "null",
|
||||
"contentJson": hasFiles ? JSON.stringify(uploadedFiles.value) : 'null'
|
||||
}
|
||||
allmessages.value = [...allmessages.value, newMessageData]
|
||||
if (!friendSocketStore.isConnecting) await handleFriendConnect();
|
||||
const receiverId = getReceiverId()
|
||||
const hasFiles = uploadedFiles.value.length > 0
|
||||
@@ -959,17 +1279,17 @@
|
||||
"body": body
|
||||
}
|
||||
friendSocketStore.send(data)
|
||||
const newMessageData = {
|
||||
"sender": UserId.value,
|
||||
"receiver": receiverId,
|
||||
"content": textMessage.value,
|
||||
"taskId": null,
|
||||
"avatar": null,
|
||||
"createTime": "",
|
||||
"messageType": "null",
|
||||
"contentJson": hasFiles ? JSON.stringify(uploadedFiles.value) : 'null'
|
||||
}
|
||||
allmessages.value = [...allmessages.value, newMessageData]
|
||||
// const newMessageData = {
|
||||
// "sender": UserId.value,
|
||||
// "receiver": receiverId,
|
||||
// "content": textMessage.value,
|
||||
// "taskId": null,
|
||||
// "avatar": null,
|
||||
// "createTime": "",
|
||||
// "messageType": "null",
|
||||
// "contentJson": hasFiles ? JSON.stringify(uploadedFiles.value) : 'null'
|
||||
// }
|
||||
// allmessages.value = [...allmessages.value, newMessageData]
|
||||
textMessage.value = ''
|
||||
uploadedFiles.value = []
|
||||
setTimeout(() => {
|
||||
@@ -979,6 +1299,16 @@
|
||||
|
||||
// 发送群聊消息
|
||||
const sendGroupMessage = async () => {
|
||||
const newMessageData = {
|
||||
"id": allmessages.value.length + 1,
|
||||
"message": textMessage.value,
|
||||
"messageType": hasFiles ? 1 : 0,
|
||||
"groupId": null,
|
||||
"createTime": '',
|
||||
"contentJson": hasFiles ? JSON.stringify(uploadedFiles.value) : '',
|
||||
"sender": UserId.value
|
||||
}
|
||||
allmessages.value = [...allmessages.value, newMessageData]
|
||||
if (!friendSocketStore.isConnecting) await handleFriendConnect();
|
||||
const hasFiles = uploadedFiles.value.length > 0
|
||||
const body = {
|
||||
@@ -997,57 +1327,37 @@
|
||||
"body": body
|
||||
}
|
||||
friendSocketStore.send(data)
|
||||
const newMessageData = {
|
||||
"id": allmessages.value.length + 1,
|
||||
"message": textMessage.value,
|
||||
"messageType": hasFiles ? 1 : 0,
|
||||
"groupId": null,
|
||||
"createTime": '',
|
||||
"contentJson": hasFiles ? JSON.stringify(uploadedFiles.value) : '',
|
||||
"sender": UserId.value
|
||||
}
|
||||
allmessages.value = [...allmessages.value, newMessageData]
|
||||
// const newMessageData = {
|
||||
// "id": allmessages.value.length + 1,
|
||||
// "message": textMessage.value,
|
||||
// "messageType": hasFiles ? 1 : 0,
|
||||
// "groupId": null,
|
||||
// "createTime": '',
|
||||
// "contentJson": hasFiles ? JSON.stringify(uploadedFiles.value) : '',
|
||||
// "sender": UserId.value
|
||||
// }
|
||||
// allmessages.value = [...allmessages.value, newMessageData]
|
||||
textMessage.value = ''
|
||||
uploadedFiles.value = []
|
||||
setTimeout(() => {
|
||||
takeGroupMessages()
|
||||
}, 1000)
|
||||
}
|
||||
// // 发送AI消息
|
||||
// const sendAIMessage = async () => {
|
||||
// try {
|
||||
// handleConnect();
|
||||
// let messageContent = textMessage.value
|
||||
// if (uploadedFiles.value.length) {
|
||||
// const fileJson = JSON.stringify(uploadedFiles.value)
|
||||
// messageContent = messageContent ?
|
||||
// `${messageContent}\n[文件信息:${fileJson}]` :
|
||||
// `[文件信息:${fileJson}]`
|
||||
// }
|
||||
// const message_id = await addMessageDict(userToken.value, 'user', currentSessionId.value,
|
||||
// messageContent)
|
||||
// if (message_id) {
|
||||
// textMessage.value = ''
|
||||
// uploadedFiles.value = []
|
||||
// isThinking.value = true
|
||||
// }
|
||||
// await waitForAIResponse();
|
||||
// } catch (error) {
|
||||
// isThinking.value = false
|
||||
// uni.showToast({
|
||||
// title: `用户发送消息失败${error}`,
|
||||
// icon: 'error'
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// AI 回复超时定时器
|
||||
// let aiResponseTimer = null
|
||||
|
||||
//修改优先把消息加入列表
|
||||
|
||||
|
||||
// 发送AI消息
|
||||
const sendAIMessage = async () => {
|
||||
try {
|
||||
await handleConnect();
|
||||
let messageContent = textMessage.value
|
||||
// 立即添加用户消息到列表(使用完整的 messageContent,包含文件信息)
|
||||
allmessages.value = [...allmessages.value, {
|
||||
role: 'user',
|
||||
content: messageContent
|
||||
}]
|
||||
await handleConnect();
|
||||
|
||||
if (uploadedFiles.value.length) {
|
||||
const fileJson = JSON.stringify(uploadedFiles.value)
|
||||
messageContent = messageContent ?
|
||||
@@ -1062,11 +1372,11 @@
|
||||
socketStore.send(data)
|
||||
isThinking.value = true
|
||||
isSelfSent.value = true
|
||||
// 立即添加用户消息到列表(使用完整的 messageContent,包含文件信息)
|
||||
allmessages.value = [...allmessages.value, {
|
||||
role: 'user',
|
||||
content: messageContent
|
||||
}]
|
||||
// // 立即添加用户消息到列表(使用完整的 messageContent,包含文件信息)
|
||||
// allmessages.value = [...allmessages.value, {
|
||||
// role: 'user',
|
||||
// content: messageContent
|
||||
// }]
|
||||
// 添加流式消息气泡(加载中状态)
|
||||
streamingMessageId.value = 'streaming-' + Date.now()
|
||||
allmessages.value = [...allmessages.value, {
|
||||
@@ -1120,13 +1430,16 @@
|
||||
}
|
||||
|
||||
// 全局监听 socketStore.isThinking:跨设备同步思考状态,锁/解锁输入框
|
||||
watch(() => socketStore.isThinking, (newVal, oldVal) => {
|
||||
watch(() => socketStore.isThinking, async (newVal, oldVal) => {
|
||||
console.log("isThinking 变化:", oldVal, "→", newVal, "messageString:", socketStore.messageString);
|
||||
// false → true:后端开始回复,锁定输入框
|
||||
if (!oldVal && newVal) {
|
||||
console.log('AI开始回复(跨设备同步),锁定输入框');
|
||||
isThinking.value = true
|
||||
// 非本设备发起时,也需要添加流式气泡
|
||||
// 跨设备场景:先加载最新消息(含 PC 发的用户消息),再追加流式气泡
|
||||
if (!isSelfSent.value) {
|
||||
await takeConversationMessages()
|
||||
}
|
||||
if (!streamingMessageId.value) {
|
||||
streamingMessageId.value = 'streaming-' + Date.now()
|
||||
allmessages.value = [...allmessages.value, {
|
||||
@@ -1141,12 +1454,14 @@
|
||||
// true → false:AI 回复结束或中断,解锁输入框并刷新消息列表
|
||||
if (oldVal && !newVal) {
|
||||
console.log('AI回复结束(正常/中断),解锁并刷新消息列表');
|
||||
// 移除流式消息气泡,从服务器加载完整消息
|
||||
|
||||
// 先从服务器加载完整消息列表
|
||||
await takeConversationMessages()
|
||||
// 加载完成后再移除流式消息气泡,避免闪白
|
||||
if (streamingMessageId.value) {
|
||||
allmessages.value = allmessages.value.filter(m => m._id !== streamingMessageId.value)
|
||||
streamingMessageId.value = null
|
||||
}
|
||||
takeConversationMessages()
|
||||
isThinking.value = false
|
||||
isSelfSent.value = false
|
||||
textMessage.value = ''
|
||||
@@ -1159,14 +1474,35 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
// 监听 pc_offline,PC 端下线时立即强制跳转登录页
|
||||
watch(() => socketStore.pcOffline, (offline) => {
|
||||
if (offline) {
|
||||
isThinking.value = false
|
||||
socketStore.isThinking = false
|
||||
socketStore.pcOffline = false
|
||||
uni.showToast({
|
||||
title: 'PC端不在线,请先登录',
|
||||
icon: 'none',
|
||||
duration: 1500
|
||||
})
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/Login/Login'
|
||||
})
|
||||
}, 1500)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1362,6 +1698,7 @@
|
||||
try {
|
||||
allmessages.value = await getConversationMessages(userToken.value, currentSessionId.value) || [];
|
||||
// console.log("获取到的所有消息:", JSON.stringify(allmessages.value) );
|
||||
// console.log("获取到的所有消息:", allmessages.value);
|
||||
// currentMessages.value = allmessages.value.slice(-pageInfoNumber);
|
||||
// 数据获取后执行滚动
|
||||
scrollToBottom();
|
||||
@@ -1479,6 +1816,7 @@
|
||||
if (oldId && oldId !== newId) {
|
||||
isLoadingMessages.value = true
|
||||
allmessages.value = []
|
||||
hasWyxdFile.value = false
|
||||
}
|
||||
switch (ChatType.value) {
|
||||
case 0:
|
||||
@@ -1502,6 +1840,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()
|
||||
})
|
||||
@@ -1550,6 +1899,8 @@
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./chat.css";
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<!-- v-html 渲染的 markdown 内容无法被 scoped 样式命中,需非 scoped 样式块 -->
|
||||
@@ -1622,6 +1973,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 流式气泡中的加载动画 */
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -244,7 +252,12 @@ onLoad((options) => {
|
||||
.wyxd-slide-area {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 20rpx;
|
||||
/* padding: 20rpx; */
|
||||
}
|
||||
|
||||
.wyxd-swiper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.wyxd-slide-scroll {
|
||||
@@ -254,10 +267,18 @@ onLoad((options) => {
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.wyxd-slide-card {
|
||||
/* .wyxd-slide-card {
|
||||
padding: 32rpx;
|
||||
min-height: 100%;
|
||||
box-sizing: border-box;
|
||||
} */
|
||||
.wyxd-slide-card {
|
||||
min-height: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column; /* 子元素垂直排列 */
|
||||
}
|
||||
|
||||
/* ===== 底部翻页 ===== */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
@@ -605,7 +755,10 @@
|
||||
|
||||
// 处理文件夹点击(非选择模式下进入文件夹,仅目录可点击进入)
|
||||
const handleFolderClick = (folder) => {
|
||||
if (folder.type !== 'directory') return;
|
||||
if (folder.type !== 'directory') {
|
||||
handleLongPress(folder)
|
||||
return
|
||||
}
|
||||
folderStack.value.push({
|
||||
path: folder.path,
|
||||
name: folder.name
|
||||
@@ -666,6 +819,7 @@
|
||||
|
||||
onMounted(() => {
|
||||
initCurrentFolderList();
|
||||
loadDownloads();
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ export const useSocketStore = defineStore('socket', () => {
|
||||
const maxLogCount = ref(20)
|
||||
|
||||
const authFailReason = ref('')
|
||||
const pcOffline = ref(false)
|
||||
|
||||
// ==================== 计算属性 ====================
|
||||
const isConnected = computed(() => connectionStatus.value === 'connected')
|
||||
@@ -66,6 +67,7 @@ export const useSocketStore = defineStore('socket', () => {
|
||||
...config.value,
|
||||
...options
|
||||
}
|
||||
pcOffline.value = false // 每次连接时重置 PC 离线标记
|
||||
addLog('info',
|
||||
`准备连接WebSocket。Token:${config.value.token || '未设置'}。ConversationId: ${config.value.conversationId || '未设置'}`
|
||||
)
|
||||
@@ -182,6 +184,7 @@ export const useSocketStore = defineStore('socket', () => {
|
||||
break
|
||||
|
||||
case 'pc_offline':
|
||||
pcOffline.value = true
|
||||
addLog('warn', 'PC 端已离线')
|
||||
break
|
||||
|
||||
@@ -287,6 +290,7 @@ export const useSocketStore = defineStore('socket', () => {
|
||||
logs,
|
||||
config,
|
||||
authFailReason,
|
||||
pcOffline,
|
||||
|
||||
// 计算属性
|
||||
isConnected,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
;(function(){
|
||||
let u=void 0,isReady=false,onReadyCallbacks=[],isServiceReady=false,onServiceReadyCallbacks=[];
|
||||
const __uniConfig = {"pages":[],"globalStyle":{"backgroundColor":"#F8F8F8","navigationBar":{"backgroundColor":"#F8F8F8","titleText":"uni-app","type":"default","titleColor":"#000000"},"isNVue":false},"nvue":{"compiler":"uni-app","styleCompiler":"uni-app","flex-direction":"column"},"renderer":"auto","appname":"test1","splashscreen":{"alwaysShowBeforeRender":true,"autoclose":true},"compilerVersion":"5.07","entryPagePath":"pages/Login/Login","entryPageQuery":"","realEntryPagePath":"","networkTimeout":{"request":60000,"connectSocket":60000,"uploadFile":60000,"downloadFile":60000},"locales":{},"darkmode":false,"themeConfig":{}};
|
||||
const __uniRoutes = [{"path":"pages/Login/Login","meta":{"isQuit":true,"isEntry":true,"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"登录页面","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/Chat/Chat","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"聊天页面(主页面)","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/WorkSpace/WorkSpace","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"工作区文件管理","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/UserProfileModal/UserProfileModal","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/ContactPages/ContactPages","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/WorkSpace/TemplateSpace/TemplateSpace","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/CloudDatabase/CloudDatabase","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"云端数据","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/CloudDbDetail/CloudDbDetail","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"数据库详情","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/text/text","meta":{"navigationBar":{"titleText":"","type":"default"},"isNVue":false}},{"path":"pages/text/IntuitiveAipptViewer","meta":{"navigationBar":{"titleText":"","type":"default"},"isNVue":false}}].map(uniRoute=>(uniRoute.meta.route=uniRoute.path,__uniConfig.pages.push(uniRoute.path),uniRoute.path='/'+uniRoute.path,uniRoute));
|
||||
const __uniConfig = {"pages":[],"globalStyle":{"backgroundColor":"#F8F8F8","navigationBar":{"backgroundColor":"#F8F8F8","titleText":"uni-app","type":"default","titleColor":"#000000"},"isNVue":false},"nvue":{"compiler":"uni-app","styleCompiler":"uni-app","flex-direction":"column"},"renderer":"auto","appname":"宇恒一号","splashscreen":{"alwaysShowBeforeRender":true,"autoclose":true},"compilerVersion":"5.07","entryPagePath":"pages/Login/Login","entryPageQuery":"","realEntryPagePath":"","networkTimeout":{"request":60000,"connectSocket":60000,"uploadFile":60000,"downloadFile":60000},"locales":{},"darkmode":false,"themeConfig":{}};
|
||||
const __uniRoutes = [{"path":"pages/Login/Login","meta":{"isQuit":true,"isEntry":true,"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"登录页面","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/Chat/Chat","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"聊天页面(主页面)","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/WorkSpace/WorkSpace","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"工作区文件管理","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/UserProfileModal/UserProfileModal","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/ContactPages/ContactPages","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/WorkSpace/TemplateSpace/TemplateSpace","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/CloudDatabase/CloudDatabase","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"云端数据","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/CloudDbDetail/CloudDbDetail","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"数据库详情","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/text/text","meta":{"navigationBar":{"titleText":"","type":"default"},"isNVue":false}},{"path":"pages/text/IntuitiveAipptViewer","meta":{"navigationBar":{"titleText":"","type":"default"},"isNVue":false}},{"path":"pages/WYXD/WYXD","meta":{"titleNView":false,"bounce":"none","navigationBar":{"titleText":"","style":"custom","type":"default"},"isNVue":false}}].map(uniRoute=>(uniRoute.meta.route=uniRoute.path,__uniConfig.pages.push(uniRoute.path),uniRoute.path='/'+uniRoute.path,uniRoute));
|
||||
__uniConfig.styles=[];//styles
|
||||
__uniConfig.onReady=function(callback){if(__uniConfig.ready){callback()}else{onReadyCallbacks.push(callback)}};Object.defineProperty(__uniConfig,"ready",{get:function(){return isReady},set:function(val){isReady=val;if(!isReady){return}const callbacks=onReadyCallbacks.slice(0);onReadyCallbacks.length=0;callbacks.forEach(function(callback){callback()})}});
|
||||
__uniConfig.onServiceReady=function(callback){if(__uniConfig.serviceReady){callback()}else{onServiceReadyCallbacks.push(callback)}};Object.defineProperty(__uniConfig,"serviceReady",{get:function(){return isServiceReady},set:function(val){isServiceReady=val;if(!isServiceReady){return}const callbacks=onServiceReadyCallbacks.slice(0);onServiceReadyCallbacks.length=0;callbacks.forEach(function(callback){callback()})}});
|
||||
|
||||
15
unpackage/dist/build/app-plus/app-service.js
vendored
15
unpackage/dist/build/app-plus/app-service.js
vendored
File diff suppressed because one or more lines are too long
2
unpackage/dist/build/app-plus/manifest.json
vendored
2
unpackage/dist/build/app-plus/manifest.json
vendored
@@ -5,7 +5,7 @@
|
||||
"iPad"
|
||||
],
|
||||
"id": "__UNI__923FD9E",
|
||||
"name": "test1",
|
||||
"name": "宇恒一号",
|
||||
"version": {
|
||||
"name": "1.1.1",
|
||||
"code": 2
|
||||
|
||||
File diff suppressed because one or more lines are too long
1
unpackage/dist/build/app-plus/pages/WYXD/WYXD.css
vendored
Normal file
1
unpackage/dist/build/app-plus/pages/WYXD/WYXD.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
|
||||
;(function(){
|
||||
let u=void 0,isReady=false,onReadyCallbacks=[],isServiceReady=false,onServiceReadyCallbacks=[];
|
||||
const __uniConfig = {"pages":[],"globalStyle":{"backgroundColor":"#F8F8F8","navigationBar":{"backgroundColor":"#F8F8F8","titleText":"uni-app","type":"default","titleColor":"#000000"},"isNVue":false},"nvue":{"compiler":"uni-app","styleCompiler":"uni-app","flex-direction":"column"},"renderer":"auto","appname":"test1","splashscreen":{"alwaysShowBeforeRender":true,"autoclose":true},"compilerVersion":"5.07","entryPagePath":"pages/Login/Login","entryPageQuery":"","realEntryPagePath":"","networkTimeout":{"request":60000,"connectSocket":60000,"uploadFile":60000,"downloadFile":60000},"locales":{},"darkmode":false,"themeConfig":{}};
|
||||
const __uniConfig = {"pages":[],"globalStyle":{"backgroundColor":"#F8F8F8","navigationBar":{"backgroundColor":"#F8F8F8","titleText":"uni-app","type":"default","titleColor":"#000000"},"isNVue":false},"nvue":{"compiler":"uni-app","styleCompiler":"uni-app","flex-direction":"column"},"renderer":"auto","appname":"宇恒一号","splashscreen":{"alwaysShowBeforeRender":true,"autoclose":true},"compilerVersion":"5.07","entryPagePath":"pages/Login/Login","entryPageQuery":"","realEntryPagePath":"","networkTimeout":{"request":60000,"connectSocket":60000,"uploadFile":60000,"downloadFile":60000},"locales":{},"darkmode":false,"themeConfig":{}};
|
||||
const __uniRoutes = [{"path":"pages/Login/Login","meta":{"isQuit":true,"isEntry":true,"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"登录页面","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/Chat/Chat","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"聊天页面(主页面)","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/WorkSpace/WorkSpace","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"工作区文件管理","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/UserProfileModal/UserProfileModal","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/ContactPages/ContactPages","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/WorkSpace/TemplateSpace/TemplateSpace","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/CloudDatabase/CloudDatabase","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"云端数据","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/CloudDbDetail/CloudDbDetail","meta":{"titleNView":false,"bounce":"none","softinputMode":"adjustResize","navigationBar":{"titleText":"数据库详情","style":"custom","type":"default"},"isNVue":false}},{"path":"pages/text/text","meta":{"navigationBar":{"titleText":"","type":"default"},"isNVue":false}},{"path":"pages/text/IntuitiveAipptViewer","meta":{"navigationBar":{"titleText":"","type":"default"},"isNVue":false}},{"path":"pages/WYXD/WYXD","meta":{"titleNView":false,"bounce":"none","navigationBar":{"titleText":"","style":"custom","type":"default"},"isNVue":false}}].map(uniRoute=>(uniRoute.meta.route=uniRoute.path,__uniConfig.pages.push(uniRoute.path),uniRoute.path='/'+uniRoute.path,uniRoute));
|
||||
__uniConfig.styles=[];//styles
|
||||
__uniConfig.onReady=function(callback){if(__uniConfig.ready){callback()}else{onReadyCallbacks.push(callback)}};Object.defineProperty(__uniConfig,"ready",{get:function(){return isReady},set:function(val){isReady=val;if(!isReady){return}const callbacks=onReadyCallbacks.slice(0);onReadyCallbacks.length=0;callbacks.forEach(function(callback){callback()})}});
|
||||
|
||||
1202
unpackage/dist/dev/app-plus/app-service.js
vendored
1202
unpackage/dist/dev/app-plus/app-service.js
vendored
File diff suppressed because it is too large
Load Diff
2
unpackage/dist/dev/app-plus/manifest.json
vendored
2
unpackage/dist/dev/app-plus/manifest.json
vendored
@@ -5,7 +5,7 @@
|
||||
"iPad"
|
||||
],
|
||||
"id": "__UNI__923FD9E",
|
||||
"name": "test1",
|
||||
"name": "宇恒一号",
|
||||
"version": {
|
||||
"name": "1.1.1",
|
||||
"code": 2
|
||||
|
||||
84
unpackage/dist/dev/app-plus/pages/Chat/Chat.css
vendored
84
unpackage/dist/dev/app-plus/pages/Chat/Chat.css
vendored
@@ -1006,10 +1006,12 @@ to { opacity: 1; transform: translateY(0) scale(1);
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
color: #1a1a2e;
|
||||
align-items: center;
|
||||
}
|
||||
.ncd-header uni-icons[data-v-5eb7b895] {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
}
|
||||
.ncd-options[data-v-5eb7b895] {
|
||||
display: flex;
|
||||
@@ -1192,6 +1194,55 @@ to { opacity: 1; transform: translateY(0) scale(1);
|
||||
.log-out .iconfont[data-v-5eb7b895] {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
/* ==========WYXD 文件入口横幅========== */
|
||||
.wyxd-banner[data-v-5eb7b895] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.4375rem 0.75rem;
|
||||
margin: 0.1875rem 0.625rem;
|
||||
background: linear-gradient(135deg, #e8f4fd, #f0e6ff);
|
||||
border: 0.04688rem solid rgba(120, 120, 220, 0.25);
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 0.0625rem 0.25rem rgba(100, 100, 200, 0.08);
|
||||
}
|
||||
.wyxd-banner[data-v-5eb7b895]:active {
|
||||
background: linear-gradient(135deg, #d8eafb, #e6d8ff);
|
||||
transform: scale(0.98);
|
||||
}
|
||||
.wyxd-banner-icon[data-v-5eb7b895] {
|
||||
font-size: 1rem;
|
||||
color: #6c6ce0;
|
||||
margin-right: 0.375rem;
|
||||
}
|
||||
.wyxd-banner-text[data-v-5eb7b895] {
|
||||
flex: 1;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
color: #4a4ab8;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.wyxd-banner-arrow[data-v-5eb7b895] {
|
||||
font-size: 0.75rem;
|
||||
color: #9999cc;
|
||||
}
|
||||
.wyxd-banner-close[data-v-5eb7b895] {
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
line-height: 1.125rem;
|
||||
text-align: center;
|
||||
font-size: 0.875rem;
|
||||
color: #9999cc;
|
||||
border-radius: 50%;
|
||||
background: rgba(100, 100, 200, 0.08);
|
||||
margin-left: 0.25rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.wyxd-banner-close[data-v-5eb7b895]:active {
|
||||
background: rgba(100, 100, 200, 0.18);
|
||||
color: #6c6ce0;
|
||||
}
|
||||
/* ==========聊天主体部分========== */
|
||||
.main-chat[data-v-5eb7b895] {
|
||||
display: flex;
|
||||
@@ -1665,6 +1716,7 @@ to { opacity: 1; transform: translateY(0);
|
||||
/* 普通正文 */
|
||||
/* 粗体和斜体样式 */
|
||||
/* 气泡内的内联链接(替代 <a> 标签,点击由 JS 拦截处理) */
|
||||
/* 可下载文件链接 → 按钮卡片样式 */
|
||||
}
|
||||
.chat-content h1 {
|
||||
font-size: 1.3125rem;
|
||||
@@ -1710,6 +1762,38 @@ to { opacity: 1; transform: translateY(0);
|
||||
text-decoration: underline;
|
||||
word-break: break-all;
|
||||
}
|
||||
.chat-content .chat-download-btn {
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.25rem 0.4375rem;
|
||||
margin: 0.125rem 0;
|
||||
background: #f5f7fb;
|
||||
border: 0.0625rem solid #e4e8f0;
|
||||
border-radius: 0.25rem;
|
||||
text-decoration: none !important;
|
||||
color: #333 !important;
|
||||
word-break: break-all;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.chat-content .chat-download-btn:active {
|
||||
background: #eef1f7;
|
||||
border-color: #c8cde0;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
.chat-content .chat-download-btn .chat-dl-icon {
|
||||
font-size: 0.8125rem !important;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.chat-content .chat-download-btn .chat-dl-name {
|
||||
font-size: 16px !important;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 流式气泡中的加载动画 */
|
||||
.bubble-loading-dots {
|
||||
|
||||
158
unpackage/dist/dev/app-plus/pages/WYXD/WYXD.css
vendored
158
unpackage/dist/dev/app-plus/pages/WYXD/WYXD.css
vendored
@@ -333,6 +333,146 @@
|
||||
.workspace-footer .iconfont[data-v-507487c4] {
|
||||
font-size: 1.25rem !important;
|
||||
}
|
||||
/* ===== 标签切换 ===== */
|
||||
.workspace-tabs[data-v-507487c4] {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 0 0.75rem;
|
||||
box-sizing: border-box;
|
||||
gap: 0.625rem;
|
||||
}
|
||||
.tab-item[data-v-507487c4] {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0.5625rem 0;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #999;
|
||||
border-bottom: 0.125rem solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
}
|
||||
.tab-item.active[data-v-507487c4] {
|
||||
color: #6c63ff;
|
||||
border-bottom-color: #6c63ff;
|
||||
}
|
||||
.tab-badge[data-v-507487c4] {
|
||||
margin-left: 0.25rem;
|
||||
background: #ff5e57;
|
||||
color: #fff;
|
||||
font-size: 0.625rem;
|
||||
padding: 0.0625rem 0.3125rem;
|
||||
border-radius: 0.625rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
/* ===== 下载列表 ===== */
|
||||
.download-content[data-v-507487c4] {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
.download-empty[data-v-507487c4] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-top: 6.25rem;
|
||||
color: #bbb;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.download-empty .iconfont[data-v-507487c4] {
|
||||
font-size: 4.375rem;
|
||||
color: #ddd;
|
||||
margin-bottom: 0.625rem;
|
||||
}
|
||||
.download-list[data-v-507487c4] {
|
||||
padding: 0.625rem 0.75rem;
|
||||
}
|
||||
.download-header[data-v-507487c4] {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.375rem 0.25rem 0.625rem;
|
||||
font-size: 0.75rem;
|
||||
color: #999;
|
||||
}
|
||||
.clear-btn[data-v-507487c4] {
|
||||
color: #ff5e57;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
padding: 0.125rem 0.375rem;
|
||||
}
|
||||
.download-item[data-v-507487c4] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 0.625rem;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 0.625rem;
|
||||
margin-bottom: 0.5rem;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.download-item[data-v-507487c4]:active {
|
||||
transform: scale(0.98);
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
.di-icon[data-v-507487c4] {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: rgba(108, 99, 255, 0.08);
|
||||
border-radius: 0.5625rem;
|
||||
margin-right: 0.625rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.di-icon .iconfont[data-v-507487c4] {
|
||||
font-size: 1.25rem;
|
||||
color: #6c63ff;
|
||||
}
|
||||
.di-info[data-v-507487c4] {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.1875rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.di-name[data-v-507487c4] {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.di-meta[data-v-507487c4] {
|
||||
font-size: 0.6875rem;
|
||||
color: #999;
|
||||
}
|
||||
.di-path[data-v-507487c4] {
|
||||
font-size: 0.625rem;
|
||||
color: #bbb;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.di-delete[data-v-507487c4] {
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.di-delete .iconfont[data-v-507487c4] {
|
||||
font-size: 1.125rem;
|
||||
color: #ccc;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
.di-delete:active .iconfont[data-v-507487c4] {
|
||||
color: #ff5e57;
|
||||
}
|
||||
.wyxd-container[data-v-507487c4] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -378,7 +518,11 @@
|
||||
.wyxd-slide-area[data-v-507487c4] {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 0.625rem;
|
||||
/* padding: 20rpx; */
|
||||
}
|
||||
.wyxd-swiper[data-v-507487c4] {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.wyxd-slide-scroll[data-v-507487c4] {
|
||||
height: 100%;
|
||||
@@ -386,10 +530,18 @@
|
||||
border-radius: 0.375rem;
|
||||
box-shadow: 0 0.0625rem 0.375rem rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
.wyxd-slide-card[data-v-507487c4] {
|
||||
padding: 1rem;
|
||||
/* .wyxd-slide-card {
|
||||
padding: 32rpx;
|
||||
min-height: 100%;
|
||||
box-sizing: border-box;
|
||||
} */
|
||||
.wyxd-slide-card[data-v-507487c4] {
|
||||
min-height: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column; /* 子元素垂直排列 */
|
||||
}
|
||||
/* ===== 底部翻页 ===== */
|
||||
.wyxd-pager[data-v-507487c4] {
|
||||
|
||||
@@ -333,6 +333,146 @@
|
||||
.workspace-footer .iconfont[data-v-769edd25] {
|
||||
font-size: 1.25rem !important;
|
||||
}
|
||||
/* ===== 标签切换 ===== */
|
||||
.workspace-tabs[data-v-769edd25] {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 0 0.75rem;
|
||||
box-sizing: border-box;
|
||||
gap: 0.625rem;
|
||||
}
|
||||
.tab-item[data-v-769edd25] {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0.5625rem 0;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #999;
|
||||
border-bottom: 0.125rem solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
}
|
||||
.tab-item.active[data-v-769edd25] {
|
||||
color: #6c63ff;
|
||||
border-bottom-color: #6c63ff;
|
||||
}
|
||||
.tab-badge[data-v-769edd25] {
|
||||
margin-left: 0.25rem;
|
||||
background: #ff5e57;
|
||||
color: #fff;
|
||||
font-size: 0.625rem;
|
||||
padding: 0.0625rem 0.3125rem;
|
||||
border-radius: 0.625rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
/* ===== 下载列表 ===== */
|
||||
.download-content[data-v-769edd25] {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
.download-empty[data-v-769edd25] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-top: 6.25rem;
|
||||
color: #bbb;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.download-empty .iconfont[data-v-769edd25] {
|
||||
font-size: 4.375rem;
|
||||
color: #ddd;
|
||||
margin-bottom: 0.625rem;
|
||||
}
|
||||
.download-list[data-v-769edd25] {
|
||||
padding: 0.625rem 0.75rem;
|
||||
}
|
||||
.download-header[data-v-769edd25] {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.375rem 0.25rem 0.625rem;
|
||||
font-size: 0.75rem;
|
||||
color: #999;
|
||||
}
|
||||
.clear-btn[data-v-769edd25] {
|
||||
color: #ff5e57;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
padding: 0.125rem 0.375rem;
|
||||
}
|
||||
.download-item[data-v-769edd25] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 0.625rem;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 0.625rem;
|
||||
margin-bottom: 0.5rem;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.download-item[data-v-769edd25]:active {
|
||||
transform: scale(0.98);
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
.di-icon[data-v-769edd25] {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: rgba(108, 99, 255, 0.08);
|
||||
border-radius: 0.5625rem;
|
||||
margin-right: 0.625rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.di-icon .iconfont[data-v-769edd25] {
|
||||
font-size: 1.25rem;
|
||||
color: #6c63ff;
|
||||
}
|
||||
.di-info[data-v-769edd25] {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.1875rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.di-name[data-v-769edd25] {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.di-meta[data-v-769edd25] {
|
||||
font-size: 0.6875rem;
|
||||
color: #999;
|
||||
}
|
||||
.di-path[data-v-769edd25] {
|
||||
font-size: 0.625rem;
|
||||
color: #bbb;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.di-delete[data-v-769edd25] {
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.di-delete .iconfont[data-v-769edd25] {
|
||||
font-size: 1.125rem;
|
||||
color: #ccc;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
.di-delete:active .iconfont[data-v-769edd25] {
|
||||
color: #ff5e57;
|
||||
}
|
||||
/* 编辑模板弹窗 */
|
||||
.edit-template-wrapper[data-v-769edd25] {
|
||||
}
|
||||
|
||||
@@ -865,3 +865,143 @@
|
||||
.workspace-footer .iconfont[data-v-c1a60359] {
|
||||
font-size: 1.25rem !important;
|
||||
}
|
||||
/* ===== 标签切换 ===== */
|
||||
.workspace-tabs[data-v-c1a60359] {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 0 0.75rem;
|
||||
box-sizing: border-box;
|
||||
gap: 0.625rem;
|
||||
}
|
||||
.tab-item[data-v-c1a60359] {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0.5625rem 0;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #999;
|
||||
border-bottom: 0.125rem solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
}
|
||||
.tab-item.active[data-v-c1a60359] {
|
||||
color: #6c63ff;
|
||||
border-bottom-color: #6c63ff;
|
||||
}
|
||||
.tab-badge[data-v-c1a60359] {
|
||||
margin-left: 0.25rem;
|
||||
background: #ff5e57;
|
||||
color: #fff;
|
||||
font-size: 0.625rem;
|
||||
padding: 0.0625rem 0.3125rem;
|
||||
border-radius: 0.625rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
/* ===== 下载列表 ===== */
|
||||
.download-content[data-v-c1a60359] {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
.download-empty[data-v-c1a60359] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-top: 6.25rem;
|
||||
color: #bbb;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.download-empty .iconfont[data-v-c1a60359] {
|
||||
font-size: 4.375rem;
|
||||
color: #ddd;
|
||||
margin-bottom: 0.625rem;
|
||||
}
|
||||
.download-list[data-v-c1a60359] {
|
||||
padding: 0.625rem 0.75rem;
|
||||
}
|
||||
.download-header[data-v-c1a60359] {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.375rem 0.25rem 0.625rem;
|
||||
font-size: 0.75rem;
|
||||
color: #999;
|
||||
}
|
||||
.clear-btn[data-v-c1a60359] {
|
||||
color: #ff5e57;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
padding: 0.125rem 0.375rem;
|
||||
}
|
||||
.download-item[data-v-c1a60359] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 0.625rem;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 0.625rem;
|
||||
margin-bottom: 0.5rem;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.download-item[data-v-c1a60359]:active {
|
||||
transform: scale(0.98);
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
.di-icon[data-v-c1a60359] {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: rgba(108, 99, 255, 0.08);
|
||||
border-radius: 0.5625rem;
|
||||
margin-right: 0.625rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.di-icon .iconfont[data-v-c1a60359] {
|
||||
font-size: 1.25rem;
|
||||
color: #6c63ff;
|
||||
}
|
||||
.di-info[data-v-c1a60359] {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.1875rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.di-name[data-v-c1a60359] {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.di-meta[data-v-c1a60359] {
|
||||
font-size: 0.6875rem;
|
||||
color: #999;
|
||||
}
|
||||
.di-path[data-v-c1a60359] {
|
||||
font-size: 0.625rem;
|
||||
color: #bbb;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.di-delete[data-v-c1a60359] {
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.di-delete .iconfont[data-v-c1a60359] {
|
||||
font-size: 1.125rem;
|
||||
color: #ccc;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
.di-delete:active .iconfont[data-v-c1a60359] {
|
||||
color: #ff5e57;
|
||||
}
|
||||
|
||||
51
unpackage/dist/dev/app-plus/pages/text/text.css
vendored
51
unpackage/dist/dev/app-plus/pages/text/text.css
vendored
@@ -1006,10 +1006,12 @@ to { opacity: 1; transform: translateY(0) scale(1);
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
color: #1a1a2e;
|
||||
align-items: center;
|
||||
}
|
||||
.ncd-header uni-icons[data-v-fdf84df1] {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
}
|
||||
.ncd-options[data-v-fdf84df1] {
|
||||
display: flex;
|
||||
@@ -1192,6 +1194,55 @@ to { opacity: 1; transform: translateY(0) scale(1);
|
||||
.log-out .iconfont[data-v-fdf84df1] {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
/* ==========WYXD 文件入口横幅========== */
|
||||
.wyxd-banner[data-v-fdf84df1] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.4375rem 0.75rem;
|
||||
margin: 0.1875rem 0.625rem;
|
||||
background: linear-gradient(135deg, #e8f4fd, #f0e6ff);
|
||||
border: 0.04688rem solid rgba(120, 120, 220, 0.25);
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 0.0625rem 0.25rem rgba(100, 100, 200, 0.08);
|
||||
}
|
||||
.wyxd-banner[data-v-fdf84df1]:active {
|
||||
background: linear-gradient(135deg, #d8eafb, #e6d8ff);
|
||||
transform: scale(0.98);
|
||||
}
|
||||
.wyxd-banner-icon[data-v-fdf84df1] {
|
||||
font-size: 1rem;
|
||||
color: #6c6ce0;
|
||||
margin-right: 0.375rem;
|
||||
}
|
||||
.wyxd-banner-text[data-v-fdf84df1] {
|
||||
flex: 1;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
color: #4a4ab8;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.wyxd-banner-arrow[data-v-fdf84df1] {
|
||||
font-size: 0.75rem;
|
||||
color: #9999cc;
|
||||
}
|
||||
.wyxd-banner-close[data-v-fdf84df1] {
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
line-height: 1.125rem;
|
||||
text-align: center;
|
||||
font-size: 0.875rem;
|
||||
color: #9999cc;
|
||||
border-radius: 50%;
|
||||
background: rgba(100, 100, 200, 0.08);
|
||||
margin-left: 0.25rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.wyxd-banner-close[data-v-fdf84df1]:active {
|
||||
background: rgba(100, 100, 200, 0.18);
|
||||
color: #6c6ce0;
|
||||
}
|
||||
/* ==========聊天主体部分========== */
|
||||
.main-chat[data-v-fdf84df1] {
|
||||
display: flex;
|
||||
|
||||
@@ -1718,3 +1718,29 @@ export const conversationMessageDownload = (token, workspacesId, localUrl) => {
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 智能体
|
||||
// 获取智能体列表
|
||||
export const getAgentList = (token) =>{
|
||||
return new Promise((resolve,reject)=>{
|
||||
uni.request({
|
||||
url:`${BASE_URL}/cloud_api/agent/list`,
|
||||
method:"POST",
|
||||
header:{'Content-Type':'application/json'},
|
||||
data:{"access_token":token},
|
||||
success: (res) => {
|
||||
if(res.statusCode === 200){
|
||||
const respond = res.data;
|
||||
if(respond.success){
|
||||
resolve(respond.data)
|
||||
} else {
|
||||
const msg = '获取智能体列表出错啦';
|
||||
reject(msg);
|
||||
}
|
||||
}else{
|
||||
reject(`获取智能体列表失败:${res.statusCode}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
284
utils/downloadManager.js
Normal file
284
utils/downloadManager.js
Normal file
@@ -0,0 +1,284 @@
|
||||
/**
|
||||
* 下载管理工具
|
||||
* 文件保存到公共下载目录,用户可通过文件管理器访问
|
||||
*/
|
||||
|
||||
const STORAGE_KEY = 'localDownloads'
|
||||
const SUB_DIR = 'AppDownloads' // 公共下载目录下的子文件夹
|
||||
|
||||
/**
|
||||
* 获取公共下载目录路径
|
||||
* @returns {Promise<string>} 下载目录绝对路径
|
||||
*/
|
||||
function getPublicDownloadDir() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// #ifdef APP-PLUS
|
||||
plus.io.requestFileSystem(
|
||||
plus.io.PUBLIC_DOWNLOADS,
|
||||
(fs) => {
|
||||
// PUBLIC_DOWNLOADS 对应 /storage/emulated/0/Download/
|
||||
fs.root.getDirectory(
|
||||
SUB_DIR,
|
||||
{ create: true },
|
||||
(dirEntry) => {
|
||||
resolve(dirEntry.fullPath)
|
||||
},
|
||||
(err) => {
|
||||
console.error('getDirectory failed:', err)
|
||||
reject(err)
|
||||
}
|
||||
)
|
||||
},
|
||||
(err) => {
|
||||
console.error('requestFileSystem failed:', err)
|
||||
reject(err)
|
||||
}
|
||||
)
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
reject(new Error('仅APP端支持'))
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 从完整路径中提取可读的显示路径
|
||||
* 如 /storage/emulated/0/Download/AppDownloads/xxx.pdf → 下载/AppDownloads/xxx.pdf
|
||||
*/
|
||||
function getDisplayPath(fullPath) {
|
||||
const idx = fullPath.indexOf('/Download/')
|
||||
if (idx !== -1) {
|
||||
return '下载/' + fullPath.slice(idx + 10)
|
||||
}
|
||||
return fullPath
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有下载记录
|
||||
* @returns {Array} 下载记录列表
|
||||
*/
|
||||
export function getDownloadList() {
|
||||
try {
|
||||
return uni.getStorageSync(STORAGE_KEY) || []
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存下载记录(复制到公共下载目录)
|
||||
* @param {string} tempFilePath 临时文件路径(uni.downloadFile 返回的)
|
||||
* @param {string} fileName 文件名
|
||||
* @param {number} fileSize 文件大小(字节,可选)
|
||||
*/
|
||||
export function saveDownload(tempFilePath, fileName, fileSize) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// #ifdef APP-PLUS
|
||||
getPublicDownloadDir().then((dirPath) => {
|
||||
const destPathForUrl = plus.io.convertLocalFileSystemURL(dirPath + '/' + fileName)
|
||||
|
||||
plus.io.resolveLocalFileSystemURL(
|
||||
tempFilePath,
|
||||
(fileEntry) => {
|
||||
plus.io.resolveLocalFileSystemURL(
|
||||
dirPath,
|
||||
(dirEntry) => {
|
||||
fileEntry.copyTo(
|
||||
dirEntry,
|
||||
fileName,
|
||||
() => {
|
||||
const record = {
|
||||
id: Date.now().toString(),
|
||||
name: fileName,
|
||||
path: destPathForUrl,
|
||||
displayPath: getDisplayPath(destPathForUrl),
|
||||
size: fileSize || 0,
|
||||
time: Date.now()
|
||||
}
|
||||
const list = getDownloadList()
|
||||
const exists = list.some(item => item.name === fileName)
|
||||
if (exists) {
|
||||
const idx = list.findIndex(item => item.name === fileName)
|
||||
if (idx !== -1) list.splice(idx, 1)
|
||||
}
|
||||
list.unshift(record)
|
||||
try {
|
||||
uni.setStorageSync(STORAGE_KEY, list)
|
||||
} catch (e) {
|
||||
list.pop()
|
||||
uni.setStorageSync(STORAGE_KEY, list)
|
||||
}
|
||||
resolve(record)
|
||||
},
|
||||
(err) => {
|
||||
console.error('copyTo failed:', err)
|
||||
reject(err)
|
||||
}
|
||||
)
|
||||
},
|
||||
reject
|
||||
)
|
||||
},
|
||||
reject
|
||||
)
|
||||
}).catch(reject)
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
// 非 APP 端降级使用 uni.saveFile
|
||||
uni.saveFile({
|
||||
tempFilePath: tempFilePath,
|
||||
success: (saveRes) => {
|
||||
const record = {
|
||||
id: Date.now().toString(),
|
||||
name: fileName,
|
||||
path: saveRes.savedFilePath,
|
||||
size: fileSize || 0,
|
||||
time: Date.now()
|
||||
}
|
||||
const list = getDownloadList()
|
||||
list.unshift(record)
|
||||
uni.setStorageSync(STORAGE_KEY, list)
|
||||
resolve(record)
|
||||
},
|
||||
fail: reject
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除单条下载记录及其文件
|
||||
* @param {string} id 记录 ID
|
||||
*/
|
||||
export function deleteDownload(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const list = getDownloadList()
|
||||
const record = list.find(item => item.id === id)
|
||||
if (!record) {
|
||||
reject(new Error('记录不存在'))
|
||||
return
|
||||
}
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
// 删除公共目录下的文件
|
||||
plus.io.resolveLocalFileSystemURL(
|
||||
record.path,
|
||||
(fileEntry) => {
|
||||
fileEntry.remove(
|
||||
() => {
|
||||
const newList = list.filter(item => item.id !== id)
|
||||
uni.setStorageSync(STORAGE_KEY, newList)
|
||||
resolve()
|
||||
},
|
||||
() => {
|
||||
// 文件不存在也清除记录
|
||||
const newList = list.filter(item => item.id !== id)
|
||||
uni.setStorageSync(STORAGE_KEY, newList)
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
},
|
||||
() => {
|
||||
// 无法解析路径,直接清除记录
|
||||
const newList = list.filter(item => item.id !== id)
|
||||
uni.setStorageSync(STORAGE_KEY, newList)
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
uni.removeSavedFile({
|
||||
filePath: record.path,
|
||||
success: () => {
|
||||
const newList = list.filter(item => item.id !== id)
|
||||
uni.setStorageSync(STORAGE_KEY, newList)
|
||||
resolve()
|
||||
},
|
||||
fail: () => {
|
||||
const newList = list.filter(item => item.id !== id)
|
||||
uni.setStorageSync(STORAGE_KEY, newList)
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有下载记录和文件
|
||||
*/
|
||||
export function clearAllDownloads() {
|
||||
return new Promise((resolve) => {
|
||||
const list = getDownloadList()
|
||||
if (list.length === 0) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
let count = 0
|
||||
const done = () => {
|
||||
count++
|
||||
if (count >= list.length) {
|
||||
uni.setStorageSync(STORAGE_KEY, [])
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
|
||||
list.forEach(record => {
|
||||
plus.io.resolveLocalFileSystemURL(
|
||||
record.path,
|
||||
(fileEntry) => {
|
||||
fileEntry.remove(done, done)
|
||||
},
|
||||
done
|
||||
)
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
uni.setStorageSync(STORAGE_KEY, [])
|
||||
resolve()
|
||||
}, 5000)
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
let count = 0
|
||||
list.forEach(record => {
|
||||
uni.removeSavedFile({
|
||||
filePath: record.path,
|
||||
success: () => { count++; if (count >= list.length) { uni.setStorageSync(STORAGE_KEY, []); resolve() } },
|
||||
fail: () => { count++; if (count >= list.length) { uni.setStorageSync(STORAGE_KEY, []); resolve() } }
|
||||
})
|
||||
})
|
||||
if (list.length === 0) resolve()
|
||||
setTimeout(() => { uni.setStorageSync(STORAGE_KEY, []); resolve() }, 3000)
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件大小为可读字符串
|
||||
* @param {number} bytes 字节数
|
||||
* @returns {string}
|
||||
*/
|
||||
export function formatFileSize(bytes) {
|
||||
if (!bytes || bytes <= 0) return '未知大小'
|
||||
const units = ['B', 'KB', 'MB', 'GB']
|
||||
let i = 0
|
||||
let size = bytes
|
||||
while (size >= 1024 && i < units.length - 1) {
|
||||
size /= 1024
|
||||
i++
|
||||
}
|
||||
return size.toFixed(i === 0 ? 0 : 1) + ' ' + units[i]
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间戳为可读日期
|
||||
* @param {number} timestamp
|
||||
* @returns {string}
|
||||
*/
|
||||
export function formatTime(timestamp) {
|
||||
const d = new Date(timestamp)
|
||||
const pad = n => String(n).padStart(2, '0')
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
|
||||
}
|
||||
124
utils/fileOpener.js
Normal file
124
utils/fileOpener.js
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 文件打开工具 - 强制弹出应用选择器
|
||||
*
|
||||
* uni.openDocument 的 showMenu 和 plus.runtime.openFile 在真机上
|
||||
* 都可能直接跳转默认应用。用 Android 原生 Intent.createChooser
|
||||
* 强制弹出"选择应用"对话框。
|
||||
*
|
||||
* Android 7.0+ 会阻止 file:// URI 暴露给其他应用,需要禁用此限制。
|
||||
*/
|
||||
|
||||
/**
|
||||
* 根据文件扩展名获取 MIME 类型
|
||||
*/
|
||||
function getMimeType(filePath) {
|
||||
const ext = filePath.substring(filePath.lastIndexOf('.') + 1).toLowerCase()
|
||||
const mimeMap = {
|
||||
'pdf': 'application/pdf',
|
||||
'doc': 'application/msword',
|
||||
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'xls': 'application/vnd.ms-excel',
|
||||
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'ppt': 'application/vnd.ms-powerpoint',
|
||||
'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'txt': 'text/plain',
|
||||
'html': 'text/html',
|
||||
'htm': 'text/html',
|
||||
'xml': 'text/xml',
|
||||
'json': 'application/json',
|
||||
'csv': 'text/csv',
|
||||
'jpg': 'image/jpeg',
|
||||
'jpeg': 'image/jpeg',
|
||||
'png': 'image/png',
|
||||
'gif': 'image/gif',
|
||||
'bmp': 'image/bmp',
|
||||
'webp': 'image/webp',
|
||||
'mp4': 'video/mp4',
|
||||
'mp3': 'audio/mpeg',
|
||||
'wav': 'audio/wav',
|
||||
'zip': 'application/zip',
|
||||
'rar': 'application/x-rar-compressed',
|
||||
'7z': 'application/x-7z-compressed',
|
||||
'apk': 'application/vnd.android.package-archive'
|
||||
}
|
||||
return mimeMap[ext] || '*/*'
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开文件,弹出应用选择器
|
||||
* @param {string} filePath 本地文件路径
|
||||
* @param {Function} onFail 打开失败时的回调
|
||||
*/
|
||||
export function openFile(filePath, onFail) {
|
||||
// #ifdef APP-PLUS
|
||||
try {
|
||||
const main = plus.android.runtimeMainActivity()
|
||||
const Intent = plus.android.importClass('android.content.Intent')
|
||||
const Uri = plus.android.importClass('android.net.Uri')
|
||||
const File = plus.android.importClass('java.io.File')
|
||||
|
||||
// Android 7.0+ 默认禁止 file:// URI,禁用此限制
|
||||
if (plus.os.sdkInt >= 24) {
|
||||
const StrictMode = plus.android.importClass('android.os.StrictMode')
|
||||
StrictMode.disableDeathOnFileUriExposure()
|
||||
}
|
||||
|
||||
const file = new File(filePath)
|
||||
if (!file.exists()) {
|
||||
throw new Error('文件不存在: ' + filePath)
|
||||
}
|
||||
|
||||
const uri = Uri.fromFile(file)
|
||||
const mimeType = getMimeType(filePath)
|
||||
|
||||
const intent = new Intent(Intent.ACTION_VIEW)
|
||||
intent.setDataAndType(uri, mimeType)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
// 核心:createChooser 每次必弹选择器,无视已设置的默认应用
|
||||
const chooser = Intent.createChooser(intent, '选择应用打开')
|
||||
main.startActivity(chooser)
|
||||
|
||||
console.log('openFile success - chooser shown')
|
||||
} catch (e) {
|
||||
console.error('openFile native failed, fallback:', e)
|
||||
uni.openDocument({
|
||||
filePath: filePath,
|
||||
showMenu: true,
|
||||
fail: typeof onFail === 'function' ? onFail : () => {}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef APP-PLUS
|
||||
uni.openDocument({
|
||||
filePath: filePath,
|
||||
showMenu: true,
|
||||
fail: typeof onFail === 'function' ? onFail : () => {}
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件并打开(弹出应用选择器)
|
||||
* @param {string} url 远程文件 URL
|
||||
* @param {Function} onFail 失败回调
|
||||
*/
|
||||
export function downloadAndOpen(url, onFail) {
|
||||
uni.downloadFile({
|
||||
url: url,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
openFile(res.tempFilePath, onFail)
|
||||
} else {
|
||||
console.error('下载失败, statusCode:', res.statusCode)
|
||||
if (typeof onFail === 'function') onFail()
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('downloadFile fail:', err)
|
||||
if (typeof onFail === 'function') onFail()
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user