Files
urbanLifeline/urbanLifelineWeb/packages/workcase_wechat/pages/index/index.uvue
2025-12-29 12:49:23 +08:00

852 lines
26 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="chat-container">
<!-- 顶部标题栏 -->
<view class="header" :style="{ paddingTop: headerPaddingTop + 'px', height: headerTotalHeight + 'px' }">
<text class="title">泰豪小电</text>
<view class="header-right">
<button class="workcase-btn" @tap="switchMockUser" v-if="isMockMode">
<text class="btn-text">切换</text>
</button>
<button class="workcase-btn" @tap="goToChatRoomList">
<text class="btn-text">聊天室</text>
</button>
<button class="workcase-btn" @tap="goToWorkList">
<image class="btn-icon" src="/static/imgs/case.svg" />
<text class="btn-text">工单</text>
</button>
</view>
</view>
<!-- 欢迎区域(机器人+浮动标签) -->
<view class="hero" v-if="messages.length === 0">
<view class="rings">
<view class="ring r1"></view>
<view class="ring r2"></view>
<view class="ring r3"></view>
<view class="ring r4"></view>
</view>
<view class="robot">
<view class="robot-face">
<view class="eye left"></view>
<view class="eye right"></view>
</view>
</view>
<view class="float-tag t1">查询质保状态</view>
<view class="float-tag t2">发动机无法启动</view>
<view class="float-tag t3">申请上门维修</view>
</view>
<view class="greeting" v-if="messages.length === 0">
<text class="greeting-title">Hi~ 有什么可以帮您!</text>
<text class="greeting-sub">泰豪小电为您服务:)</text>
</view>
<!-- 聊天消息区域 -->
<scroll-view class="chat-messages" scroll-y="true" :scroll-top="scrollTop" scroll-with-animation="true" :class="{ started: messages.length > 0 }">
<!-- AI初始消息 -->
<view class="ai-initial-msg" v-if="messages.length === 0">
<text class="ai-msg-text">您好,我是泰豪小电智能客服。请描述您的问题,我会尽力协助。</text>
</view>
<!-- 聊天消息列表 -->
<view class="messages-list" v-else>
<view class="message-item" v-for="(item, index) in messages" :key="index"
:class="item.type === 'user' ? 'user-message' : 'bot-message'">
<!-- 用户消息(右侧) -->
<view class="user-message-content" v-if="item.type === 'user'">
<view class="message-content-wrapper">
<!-- 文字气泡 -->
<view class="message-bubble user-bubble" v-if="item.content">
<text class="message-text">{{item.content}}</text>
</view>
<!-- 用户消息的文件列表(在气泡外面) -->
<view v-if="item.files && item.files.length > 0" class="message-files">
<view v-for="fileId in item.files" :key="fileId" class="message-file-item" @tap="previewFile(fileId)">
<view v-if="isImageFileById(fileId)" class="file-thumb image">
<image :src="getFileDownloadUrl(fileId)" mode="aspectFill" class="file-img" />
</view>
<view v-else class="file-thumb doc">
<text class="file-icon">📄</text>
<text class="file-name-small">{{getFileName(fileId)}}</text>
</view>
</view>
</view>
</view>
<view class="avatar user-avatar">
<text class="avatar-text">我</text>
</view>
</view>
<!-- Bot/员工消息(左侧) -->
<view class="bot-message-content" v-else>
<view class="avatar bot-avatar">
<text class="avatar-text">AI</text>
</view>
<view class="message-bubble bot-bubble">
<!-- 加载动画:内容为空时显示 -->
<view class="typing-indicator" v-if="!item.content && isTyping">
<view class="typing-dot"></view>
<view class="typing-dot"></view>
<view class="typing-dot"></view>
</view>
<rich-text v-else :nodes="renderMarkdown(item.content)" class="message-rich-text"></rich-text>
</view>
</view>
<text class="message-time">{{item.time}}</text>
</view>
</view>
</scroll-view>
<!-- 底部操作区域 -->
<view class="bottom-area">
<!-- 快捷按钮横向滚动 -->
<scroll-view class="quick-scroll" scroll-x="true">
<view class="quick-list">
<view class="quick-btn has-icon" @tap="contactHuman">
<text class="quick-icon">☎</text>
<text class="quick-text">联系人工</text>
</view>
<view class="quick-btn has-icon" @tap="showCreator">
<text class="quick-icon">⊕</text>
<text class="quick-text">创建工单</text>
</view>
<view class="quick-divider"></view>
<view class="quick-btn" @tap="handleQuickQuestion('查询质保状态')">
<text class="quick-text">查询质保状态</text>
</view>
<view class="quick-btn" @tap="handleQuickQuestion('发动机无法启动')">
<text class="quick-text">发动机无法启动</text>
</view>
</view>
</scroll-view>
<!-- 输入区域 -->
<view class="chat-input-wrap">
<!-- 已上传文件预览 -->
<view v-if="uploadedFiles.length > 0" class="uploaded-files">
<view v-for="(file, index) in uploadedFiles" :key="file.id || index" class="uploaded-file-item">
<view v-if="isImageFile(file)" class="file-preview-image">
<image :src="getFilePreviewUrl(file)" mode="aspectFill" class="preview-img" />
</view>
<view v-else class="file-preview-doc">
<text class="doc-icon">📄</text>
</view>
<text class="file-name">{{file.name || '文件'}}</text>
<view class="remove-file-btn" @tap="removeUploadedFile(index)">
<text class="remove-icon">✕</text>
</view>
</view>
</view>
<!-- 输入框 -->
<view class="input-row">
<view class="upload-btn" :class="{ uploading: isUploading }" @tap="showUploadOptions">
<text v-if="isUploading" class="upload-icon">⏳</text>
<text v-else class="upload-icon">📎</text>
</view>
<input class="chat-input" v-model="inputText" placeholder="输入问题 来问问我~" @confirm="sendMessage" />
<view class="send-btn" :class="{ active: inputText.trim() || uploadedFiles.length > 0 }" @tap="sendMessage">
<text class="send-icon">➤</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, nextTick, onMounted } from 'vue'
import { guestAPI, aiChatAPI, workcaseChatAPI } from '@/api'
import type { TbWorkcaseDTO } from '@/types'
import type { DifyFileInfo } from '@/types/ai/aiChat'
import { AGENT_ID, FILE_DOWNLOAD_URL } from '@/config'
// 前端消息展示类型
interface ChatMessageItem {
type: 'user' | 'bot'
content: string
time: string
actions?: string[] | null
files?: string[] // 文件ID数组
}
const agentId = AGENT_ID
// 响应式数据
const messages = ref<ChatMessageItem[]>([])
const inputText = ref<string>('')
const isTyping = ref<boolean>(false)
const scrollTop = ref<number>(0)
const showWorkcaseCreator = ref<boolean>(false)
const statusBarHeight = ref<number>(0)
const headerPaddingTop = ref<number>(44) // header顶部padding默认44px
const headerTotalHeight = ref<number>(76) // header总高度默认76px
// 文件上传相关
const uploadedFiles = ref<DifyFileInfo[]>([])
const isUploading = ref<boolean>(false)
// 文件信息缓存 (fileId -> DifyFileInfo)
const fileInfoCache = ref<Map<string, DifyFileInfo>>(new Map())
// 用户信息
const userInfo = ref({
wechatId: '',
username: '',
phone: '',
userId: ''
})
const isMockMode = ref(true) // 开发环境mock模式
const userType = ref(false)
// AI 对话相关
const chatId = ref<string>('') // 当前会话ID
const currentTaskId = ref<string>('') // 当前任务ID用于停止
// 初始化用户信息
async function initUserInfo() {
// #ifdef MP-WEIXIN
// 正式环境:从微信获取用户信息
// wx.login({
// success: (loginRes) => {
// // 使用code换取openid等信息
// console.log('微信登录code:', loginRes.code)
// }
// })
// #endif
// 开发环境使用mock数据
if (isMockMode.value) {
userInfo.value = {
wechatId: '17857100377',
username: '访客用户',
phone: '17857100377',
userId: ''
}
await doIdentify()
}
}
// 切换mock用户开发调试用
function switchMockUser() {
uni.showActionSheet({
itemList: ['员工 (17857100375)', '访客 (17857100377)'],
success: (res) => {
if (res.tapIndex === 0) {
userInfo.value = { wechatId: '17857100375', username: '员工用户', phone: '17857100375', userId: '' }
} else {
userInfo.value = { wechatId: '17857100377', username: '访客用户', phone: '17857100377', userId: '' }
}
doIdentify()
}
})
}
// 调用identify接口
async function doIdentify() {
// 先清空本地存储的登录信息,确保重新识别身份
uni.removeStorageSync('token')
uni.removeStorageSync('userInfo')
uni.removeStorageSync('loginDomain')
uni.removeStorageSync('wechatId')
uni.showLoading({ title: '登录中...' })
try {
const res = await guestAPI.identify({
wechatId: userInfo.value.wechatId,
phone: userInfo.value.phone
})
uni.hideLoading()
if (res.success && res.data) {
const loginDomain = res.data
uni.setStorageSync('token', loginDomain.token || '')
uni.setStorageSync('userInfo', JSON.stringify(loginDomain.user))
uni.setStorageSync('loginDomain', JSON.stringify(loginDomain))
uni.setStorageSync('wechatId', userInfo.value.wechatId)
userInfo.value.userId = loginDomain.user?.userId || ''
console.log('identify成功:', loginDomain)
uni.showToast({ title: '登录成功', icon: 'success' })
if(loginDomain.user.status == 'guest') {
userType.value = false
} else {
userType.value = true
}
} else {
console.error('identify失败:', res.message)
}
} catch (err) {
uni.hideLoading()
console.error('identify请求失败:', err)
}
}
// 生命周期
onMounted(() => {
// 初始化用户信息
initUserInfo()
// 设置页面标题
uni.setNavigationBarTitle({
title: '智能助手'
})
// 获取窗口信息
const windowInfo = uni.getWindowInfo()
statusBarHeight.value = windowInfo.statusBarHeight || 0
// #ifdef MP-WEIXIN
// 获取胶囊按钮信息仅小程序计算header位置
try {
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
headerPaddingTop.value = menuButtonInfo.top
headerTotalHeight.value = menuButtonInfo.bottom
} catch (e) {
// 使用默认值
headerPaddingTop.value = 44
headerTotalHeight.value = 76
}
// #endif
})
// 发送消息
async function sendMessage() {
const text = inputText.value.trim()
// 允许只有文件或只有文本
if ((!text && uploadedFiles.value.length === 0) || isTyping.value) return
const query = text || '[文件]'
const currentFiles = [...uploadedFiles.value] // 保存当前文件列表副本
// 将文件信息缓存起来,用于立即渲染
currentFiles.forEach(f => {
if (f.sys_file_id) {
fileInfoCache.value.set(f.sys_file_id, f)
}
})
// 添加用户消息(包含文件)
const userMessage: ChatMessageItem = {
type: 'user',
content: text,
time: new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }),
files: currentFiles.length > 0 ? currentFiles.map(item => item.sys_file_id || '') : undefined
}
messages.value.push(userMessage)
inputText.value = ''
// 清空已上传的文件
uploadedFiles.value = []
// 调用AI聊天接口携带文件
await callAIChat(query, currentFiles)
}
// 调用AI聊天接口
async function callAIChat(query : string, files : DifyFileInfo[] = []) {
isTyping.value = true
try {
// 如果没有会话ID先创建会话
if (!chatId.value) {
const createRes = await aiChatAPI.createChat({
title: '智能助手对话',
userId: userInfo.value.userId || userInfo.value.wechatId,
agentId: agentId,
userType: userType.value
})
if (createRes.success && createRes.data) {
chatId.value = createRes.data.chatId || ''
console.log('创建会话成功:', chatId.value)
} else {
throw new Error(createRes.message || '创建会话失败')
}
}
// 准备流式对话(包含文件)
const prepareData = {
chatId: chatId.value,
query: query,
agentId: agentId,
userType: userType.value,
userId: userInfo.value.userId,
files: files.length > 0 ? files : undefined
}
console.log('准备流式对话参数:', JSON.stringify(prepareData))
const prepareRes = await aiChatAPI.prepareChatMessageSession(prepareData)
if (!prepareRes.success || !prepareRes.data) {
throw new Error(prepareRes.message || '准备对话失败')
}
const sessionId = prepareRes.data
console.log('准备流式对话成功:', sessionId)
// 添加空的AI消息占位
const messageIndex = messages.value.length
addMessage('bot', '')
// 建立SSE连接
startStreamChat(sessionId, messageIndex)
} catch (error : any) {
console.error('AI聊天失败:', error)
isTyping.value = false
addMessage('bot', '抱歉AI服务暂时不可用请稍后重试。')
}
}
// SSE 流式对话
function startStreamChat(sessionId : string, messageIndex : number) {
console.log('建立SSE连接, sessionId:', sessionId)
aiChatAPI.streamChat(sessionId, {
onMessage: (data) => {
if (data.answer) {
messages.value[messageIndex].content += data.answer
nextTick(() => scrollToBottom())
}
},
onEnd: (taskId) => {
isTyping.value = false
if (taskId) {
currentTaskId.value = taskId
}
},
onError: (error) => {
console.error('SSE错误:', error)
isTyping.value = false
messages.value[messageIndex].content = error
},
onComplete: () => {
isTyping.value = false
}
})
}
// 添加消息
function addMessage(type : 'user' | 'bot', content : string, actions : string[] | null = null) {
const now = new Date()
const time = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`
messages.value.push({
type,
content,
time,
actions
})
// 滚动到底部
nextTick(() => {
scrollToBottom()
})
}
// 模拟AI回复
function simulateAIResponse(userMessage : string) {
isTyping.value = true
setTimeout(() => {
isTyping.value = false
let response = ''
let actions : string[] | null = null
// 根据用户输入生成回复
if (userMessage.includes('工单') || userMessage.includes('报修') || userMessage.includes('问题')) {
response = '我理解您需要处理工单相关的事务。我可以帮您:'
actions = ['创建新工单', '查看工单状态', '联系客服']
} else if (userMessage.includes('你好') || userMessage.includes('您好')) {
response = '您好!很高兴为您服务。请问有什么可以帮助您的吗?'
actions = ['创建工单', '查看工单', '常见问题']
} else if (userMessage.includes('帮助') || userMessage.includes('功能')) {
response = '我可以为您提供以下服务:\n1. 创建工单 - 报告问题或提交服务请求\n2. 查看工单 - 跟踪您的工单处理进度\n3. 智能问答 - 解答常见问题'
actions = ['创建工单', '查看工单']
} else {
response = '感谢您的咨询。如果您遇到具体问题,建议创建工单,我们的专业团队会尽快为您处理。'
actions = ['创建工单', '联系人工客服']
}
addMessage('bot', response, actions)
}, 1000 + Math.random() * 1000)
}
// 快捷操作
function quickAction(action : string) {
if (action === '创建工单') {
showCreator()
} else if (action === '查看工单') {
goToWorkList()
} else {
addMessage('user', action)
simulateAIResponse(action)
}
}
// 处理建议操作
function handleSuggestedAction(action : string) {
if (action === '创建工单' || action === '创建新工单') {
showCreator()
} else if (action === '查看工单' || action === '查看工单状态') {
goToWorkList()
} else if (action === '联系客服' || action === '联系人工客服') {
uni.showModal({
title: '联系客服',
content: '客服电话400-123-4567\n工作时间9:00-18:00',
showCancel: false
})
} else {
addMessage('user', action)
simulateAIResponse(action)
}
}
// 直接跳转到工单详情页的 create 模式(复用 workcaseDetail 页面)
async function showCreator() {
// 首页直接创建工单为了让工单和聊天室绑定这里先创建一个聊天室workcase类型再带 roomId 跳转
// 如果你希望“无聊天室也能创建工单”,后端 WorkcaseServiceImpl 也支持 roomId 为空时自动创建聊天室
uni.showLoading({ title: '正在创建工单...' })
try {
const res = await workcaseChatAPI.createChatRoom({
guestId: userInfo.value.userId || userInfo.value.wechatId,
guestName: userInfo.value.username || '访客',
roomName: `${userInfo.value.username || '访客'}的工单`,
roomType: 'workcase',
status: 'active',
aiSessionId: chatId.value || ''
})
uni.hideLoading()
if (res.success && res.data?.roomId) {
const roomId = res.data.roomId
uni.navigateTo({
url: `/pages/workcase/workcaseDetail/workcaseDetail?mode=create&roomId=${roomId}`
})
return
}
uni.showToast({ title: res.message || '创建工单失败', icon: 'none' })
} catch (e) {
uni.hideLoading()
console.error('首页创建工单失败:', e)
uni.showToast({ title: '创建工单失败,请稍后重试', icon: 'none' })
}
}
// 兼容旧逻辑:不再使用页面内工单创建器
function hideCreator() {
showWorkcaseCreator.value = false
}
// 兼容旧逻辑:不再使用页面内工单创建器
function onWorkcaseCreated(workcaseData : TbWorkcaseDTO) {
hideCreator()
addMessage('bot', `工单创建成功!\n类型${workcaseData.type || ''}\n设备${workcaseData.device || ''}\n我们会尽快处理您的问题。`, ['查看工单', '创建新工单'])
}
// 跳转到工单列表
function goToWorkList() {
uni.navigateTo({
url: '/pages/workcase/workcaseList/workcaseList'
})
}
// 跳转到聊天室列表
function goToChatRoomList() {
uni.navigateTo({
url: '/pages/chatRoom/chatRoomList/chatRoomList'
})
}
// 滚动到底部
function scrollToBottom() {
// 先重置再设置大值,确保值变化触发滚动
scrollTop.value = scrollTop.value + 1
nextTick(() => {
scrollTop.value = 999999
})
}
// 联系人工客服 - 创建聊天室并进入
async function contactHuman() {
uni.showLoading({ title: '正在连接客服...' })
try {
// 创建聊天室
const res = await workcaseChatAPI.createChatRoom({
guestId: userInfo.value.userId || userInfo.value.wechatId,
guestName: userInfo.value.username || '访客',
roomName: `${userInfo.value.username || '访客'}的咨询`,
roomType: 'guest',
status: 'active',
aiSessionId: chatId.value || ''
})
uni.hideLoading()
if (res.success && res.data) {
const roomId = res.data.roomId
console.log('创建聊天室成功:', roomId)
// 跳转到聊天室页面
uni.navigateTo({
url: `/pages/chatRoom/chatRoom/chatRoom?roomId=${roomId}&roomName=${encodeURIComponent(res.data.roomName || '人工客服')}`
})
} else {
uni.showToast({
title: res.message || '连接客服失败',
icon: 'none'
})
}
} catch (error: any) {
uni.hideLoading()
console.error('创建聊天室失败:', error)
uni.showToast({
title: '连接客服失败,请稍后重试',
icon: 'none'
})
}
}
// 处理快速问题
async function handleQuickQuestion(question : string) {
addMessage('user', question)
await callAIChat(question)
}
// Markdown渲染函数返回富文本节点
function renderMarkdown(text : string) : string {
if (!text) return ''
// 转义HTML特殊字符
let html = text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
// 处理粗体(**语法)
html = html.replace(/\*\*([^\*]+)\*\*/g, '<strong>$1</strong>')
// 处理斜体(*语法,但要避免和粗体冲突)
html = html.replace(/(?<!\*)\*([^\*]+)\*(?!\*)/g, '<em>$1</em>')
// 处理行内代码(`语法)
html = html.replace(/`([^`]+)`/g, '<code style="background-color:#f5f5f5;padding:2px 6px;border-radius:3px;font-family:monospace;color:#e53e3e;">$1</code>')
// 处理链接([text](url)语法)
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" style="color:#0055AA;text-decoration:underline;">$1</a>')
// 处理标题(# ## ###等)
html = html.replace(/^### (.+)$/gm, '<div style="font-size:16px;font-weight:600;margin:8px 0 4px;">$1</div>')
html = html.replace(/^## (.+)$/gm, '<div style="font-size:18px;font-weight:600;margin:10px 0 6px;">$1</div>')
html = html.replace(/^# (.+)$/gm, '<div style="font-size:20px;font-weight:700;margin:12px 0 8px;">$1</div>')
// 处理无序列表(- 或 * 开头)
html = html.replace(/^[*-] (.+)$/gm, '<div style="margin-left:16px;">• $1</div>')
// 处理换行
html = html.replace(/\n/g, '<br/>')
return html
}
// 显示上传选项
function showUploadOptions() {
uni.showActionSheet({
itemList: ['拍照', '从相册选择', '选择文件'],
success: (res) => {
switch (res.tapIndex) {
case 0:
// 拍照
chooseImageFromCamera()
break
case 1:
// 从相册选择
chooseImageFromAlbum()
break
case 2:
// 选择文件
chooseFile()
break
}
}
})
}
// 拍照
function chooseImageFromCamera() {
uni.chooseImage({
count: 1,
sourceType: ['camera'],
success: (res) => {
if (res.tempFilePaths && res.tempFilePaths.length > 0) {
uploadSingleFile(res.tempFilePaths[0])
}
}
})
}
// 从相册选择
function chooseImageFromAlbum() {
uni.chooseImage({
count: 9,
sourceType: ['album'],
success: (res) => {
if (res.tempFilePaths && res.tempFilePaths.length > 0) {
res.tempFilePaths.forEach((filePath: string) => {
uploadSingleFile(filePath)
})
}
}
})
}
// 选择文件
function chooseFile() {
// #ifdef MP-WEIXIN
// 微信小程序使用 chooseMessageFile
uni.chooseMessageFile({
count: 5,
type: 'file',
extension: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'txt'],
success: (res: any) => {
console.log('选择文件成功:', res)
if (res.tempFiles && res.tempFiles.length > 0) {
res.tempFiles.forEach((file: any) => {
uploadSingleFile(file.path)
})
}
},
fail: (err: any) => {
console.error('选择文件失败:', err)
uni.showToast({
title: '选择文件失败',
icon: 'none'
})
}
})
// #endif
// #ifndef MP-WEIXIN
// 非微信小程序环境
// @ts-ignore
if (typeof uni.chooseFile === 'function') {
// @ts-ignore
uni.chooseFile({
count: 5,
extension: ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.txt'],
success: (res: any) => {
console.log('选择文件成功:', res)
if (res.tempFilePaths && res.tempFilePaths.length > 0) {
res.tempFilePaths.forEach((filePath: string) => {
uploadSingleFile(filePath)
})
}
},
fail: (err: any) => {
console.error('选择文件失败:', err)
uni.showToast({
title: '选择文件失败',
icon: 'none'
})
}
})
} else {
uni.showToast({
title: '当前环境不支持文件选择',
icon: 'none'
})
}
// #endif
}
// 上传单个文件
async function uploadSingleFile(filePath: string) {
console.log('开始上传文件:', filePath)
if (!agentId) {
uni.showToast({ title: '智能体未配置', icon: 'none' })
return
}
isUploading.value = true
uni.showLoading({ title: '上传中...' })
try {
const result = await aiChatAPI.uploadFileForChat(filePath, agentId)
console.log('上传结果:', result)
if (result.success && result.data) {
uploadedFiles.value.push(result.data)
uni.showToast({ title: '上传成功', icon: 'success', duration: 1000 })
} else {
uni.showToast({ title: result.message || '上传失败', icon: 'none' })
}
} catch (error: any) {
console.error('文件上传失败:', error)
uni.showToast({ title: '上传失败: ' + (error.message || '未知错误'), icon: 'none' })
} finally {
isUploading.value = false
uni.hideLoading()
}
}
// 移除已上传的文件
function removeUploadedFile(index: number) {
uploadedFiles.value.splice(index, 1)
}
// 判断是否为图片文件
function isImageFile(file: DifyFileInfo): boolean {
return file.type === 'image' || file.mime_type?.startsWith('image/') || false
}
// 获取文件预览URL
function getFilePreviewUrl(file: DifyFileInfo): string {
return file.preview_url || file.source_url || file.url || ''
}
// 获取文件下载URL通过文件ID
function getFileDownloadUrl(fileId: string): string {
return `${FILE_DOWNLOAD_URL}${fileId}`
}
// 判断文件ID对应的文件是否为图片
function isImageFileById(fileId: string): boolean {
// 从缓存中查找文件信息
const file = fileInfoCache.value.get(fileId)
if (file) {
return isImageFile(file)
}
// 如果缓存中没有尝试从uploadedFiles中查找
const uploadedFile = uploadedFiles.value.find(f => f.sys_file_id === fileId)
if (uploadedFile) {
return isImageFile(uploadedFile)
}
return false
}
// 获取文件名(从缓存)
function getFileName(fileId: string): string {
const file = fileInfoCache.value.get(fileId)
return file?.name || fileId.substring(0, 8) + '...'
}
// 文件预览
function previewFile(fileId: string) {
const url = getFileDownloadUrl(fileId)
// 如果是图片,使用图片预览
if (isImageFileById(fileId)) {
uni.previewImage({
urls: [url],
current: url
})
} else {
// 其他文件,提示下载或打开
uni.showModal({
title: '提示',
content: '是否下载该文件?',
success: (res) => {
if (res.confirm) {
uni.downloadFile({
url: url,
success: (downloadRes) => {
if (downloadRes.statusCode === 200) {
uni.showToast({ title: '下载成功', icon: 'success' })
}
}
})
}
}
})
}
}
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>