1270 lines
39 KiB
Plaintext
1270 lines
39 KiB
Plaintext
<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="showUserSelector">
|
||
<text class="btn-text">{{userInfo.username || '切换'}}</text>
|
||
</button>
|
||
<!-- <button class="workcase-btn" @tap="showOperationSelect('chat')">
|
||
<text class="btn-text">聊天室</text>
|
||
</button>
|
||
<button class="workcase-btn" @tap="showOperationSelect('workcase')">
|
||
<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>
|
||
<!-- 快捷按钮横向滚动 -->
|
||
<scroll-view class="quick-scroll" scroll-x="true" show-scrollbar="false">
|
||
<view class="quick-inner">
|
||
<view class="quick-btn has-icon" @tap="showOperationSelect('chat')">
|
||
<text class="quick-icon">☎</text>
|
||
<text class="quick-text">联系人工</text>
|
||
</view>
|
||
<view class="quick-btn has-icon" @tap="showOperationSelect('workcase')">
|
||
<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 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 class="device-code-modal" v-if="showDeviceCodeDialog">
|
||
<view class="modal-mask" @tap="cancelDeviceCodeInput"></view>
|
||
<view class="modal-content">
|
||
<view class="modal-header">
|
||
<text class="modal-title">请输入设备代码</text>
|
||
</view>
|
||
<view class="modal-body">
|
||
<input class="device-code-input" v-model="deviceCodeInput" placeholder="请输入设备代码" focus
|
||
@confirm="confirmDeviceCodeInput" />
|
||
</view>
|
||
<view class="modal-footer">
|
||
<button class="modal-btn cancel" @tap="cancelDeviceCodeInput">
|
||
<text class="btn-text">取消</text>
|
||
</button>
|
||
<button class="modal-btn confirm" @tap="confirmDeviceCodeInput">
|
||
<text class="btn-text">确定</text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 操作选择弹窗 -->
|
||
<view class="operation-select-modal" v-if="showOperationSelectDialog">
|
||
<view class="modal-mask" @tap="cancelOperationSelect"></view>
|
||
<view class="modal-content">
|
||
<view class="modal-header">
|
||
<text class="modal-title">{{ operationSelectTitle }}</text>
|
||
</view>
|
||
<view class="modal-body">
|
||
<view class="operation-select-list">
|
||
<view class="operation-select-item" @tap="handleOperationSelect('existing')">
|
||
<text class="select-icon">📋</text>
|
||
<text class="select-text">进入已有{{ operationSelectType === 'chat' ? '聊天室' : '工单' }}</text>
|
||
</view>
|
||
<view class="operation-select-item" @tap="handleOperationSelect('new')">
|
||
<text class="select-icon">➕</text>
|
||
<text class="select-text">创建新{{ operationSelectType === 'chat' ? '聊天室' : '工单' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 手机号授权弹窗 -->
|
||
<view class="phone-auth-modal" v-if="showPhoneAuthModal">
|
||
<view class="modal-mask"></view>
|
||
<view class="modal-content phone-auth-content">
|
||
<view class="modal-header">
|
||
<text class="modal-title">选择测试用户</text>
|
||
</view>
|
||
<view class="modal-body phone-auth-body">
|
||
<view class="mock-user-list">
|
||
<view class="mock-user-group">
|
||
<text class="group-title">管理员</text>
|
||
<view class="mock-user-item" @tap="selectMockUser('17857100375', '超级管理员', '17857100375')">
|
||
<text class="user-name">超级管理员</text>
|
||
<text class="user-phone">17857100375</text>
|
||
</view>
|
||
</view>
|
||
<view class="mock-user-group">
|
||
<text class="group-title">工程师</text>
|
||
<view class="mock-user-item" @tap="selectMockUser('13870055185', '魏瑶', '75719954')">
|
||
<text class="user-name">魏瑶</text>
|
||
<text class="user-phone">13870055185</text>
|
||
</view>
|
||
<view class="mock-user-item" @tap="selectMockUser('15170466624', '刘杰', 'liujie1370984851')">
|
||
<text class="user-name">刘杰</text>
|
||
<text class="user-phone">15170466624</text>
|
||
</view>
|
||
<view class="mock-user-item" @tap="selectMockUser('15170037929', '万家明', 'WJM15170037929')">
|
||
<text class="user-name">万家明</text>
|
||
<text class="user-phone">15170037929</text>
|
||
</view>
|
||
</view>
|
||
<view class="mock-user-group">
|
||
<text class="group-title">客户</text>
|
||
<view class="mock-user-item" @tap="selectMockUser('19100185270', '戴斌', 'BaiBin0714')">
|
||
<text class="user-name">戴斌</text>
|
||
<text class="user-phone">19100185270</text>
|
||
</view>
|
||
<view class="mock-user-item" @tap="selectMockUser('15797790517', '余其跃', 'a540378218')">
|
||
<text class="user-name">余其跃</text>
|
||
<text class="user-phone">15797790517</text>
|
||
</view>
|
||
<view class="mock-user-item" @tap="selectMockUser('15879126468', '李小华', 'wxid_vgmmzfdcwx9021')">
|
||
<text class="user-name">李小华</text>
|
||
<text class="user-phone">15879126468</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, nextTick, onMounted } from 'vue'
|
||
import { guestAPI, aiChatAPI, workcaseChatAPI, fileAPI } from '@/api'
|
||
import { clearLoginInfo } from '@/api/base'
|
||
import type { TbWorkcaseDTO } from '@/types'
|
||
import type { DifyFileInfo } from '@/types/ai/aiChat'
|
||
import { AGENT_ID } 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 userType = ref(false)
|
||
|
||
// 是否显示手机号授权弹窗
|
||
const showPhoneAuthModal = ref(false)
|
||
// 临时保存的微信登录code
|
||
const tempWechatCode = ref('')
|
||
// AI 对话相关
|
||
const chatId = ref<string>('') // 当前会话ID
|
||
const currentTaskId = ref<string>('') // 当前任务ID(用于停止)
|
||
|
||
// 设备代码相关
|
||
const deviceCode = ref<string>('') // 设备代码
|
||
const showDeviceCodeDialog = ref<boolean>(false) // 是否显示设备代码输入弹窗
|
||
const deviceCodeInput = ref<string>('') // 弹窗中的设备代码输入
|
||
const pendingAction = ref<'workcase' | 'human' | ''>('') // 待执行的操作类型
|
||
|
||
// 操作选择弹窗相关
|
||
const showOperationSelectDialog = ref<boolean>(false) // 是否显示操作选择弹窗
|
||
const operationSelectType = ref<'chat' | 'workcase'>('chat') // 选择类型:chat=聊天室, workcase=工单
|
||
const operationSelectTitle = ref<string>('') // 弹窗标题
|
||
const pendingOperation = ref<'existing' | 'new' | ''>('') // 待执行的操作:existing=进入已有, new=创建新的
|
||
|
||
// 初始化用户信息
|
||
async function initUserInfo() {
|
||
try {
|
||
// 1. 先尝试从缓存获取
|
||
const cachedWechatId = uni.getStorageSync('wechatId')
|
||
const cachedUserInfo = uni.getStorageSync('userInfo')
|
||
const cachedToken = uni.getStorageSync('token')
|
||
|
||
if (cachedWechatId && cachedUserInfo && cachedToken) {
|
||
// 有完整缓存,直接使用
|
||
const parsedUserInfo = typeof cachedUserInfo === 'string' ? JSON.parse(cachedUserInfo) : cachedUserInfo
|
||
userInfo.value = {
|
||
wechatId: cachedWechatId,
|
||
username: parsedUserInfo.username || parsedUserInfo.realName || '微信用户',
|
||
phone: parsedUserInfo.phone || '',
|
||
userId: parsedUserInfo.userId || ''
|
||
}
|
||
|
||
// 判断用户类型
|
||
if (parsedUserInfo.status === 'guest') {
|
||
userType.value = false
|
||
} else {
|
||
userType.value = true
|
||
}
|
||
|
||
console.log('使用缓存的用户信息:', userInfo.value)
|
||
return
|
||
}
|
||
|
||
// 2. 没有缓存,自动登录
|
||
await autoLogin()
|
||
} catch (error) {
|
||
console.error('初始化用户信息失败:', error)
|
||
// 登录失败,尝试自动登录
|
||
await autoLogin()
|
||
}
|
||
}
|
||
|
||
// 自动登录
|
||
async function autoLogin() {
|
||
uni.showLoading({ title: '初始化中...' })
|
||
|
||
try {
|
||
// 使用 wx.login 获取 code
|
||
uni.login({
|
||
provider: 'weixin',
|
||
success: async (loginRes) => {
|
||
console.log('微信登录成功,code:', loginRes.code)
|
||
uni.hideLoading()
|
||
|
||
// 保存code,等待手机号授权后使用
|
||
tempWechatCode.value = loginRes.code
|
||
|
||
// 显示手机号授权弹窗
|
||
showPhoneAuthModal.value = true
|
||
},
|
||
fail: (err) => {
|
||
console.error('微信登录失败:', err)
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||
}
|
||
})
|
||
} catch (error : any) {
|
||
console.error('自动登录失败:', error)
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: error.message || '登录失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
|
||
// 调用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)
|
||
}
|
||
}
|
||
|
||
// 获取手机号回调(保留用于正式环境)
|
||
async function onGetPhoneNumber(e : any) {
|
||
console.log('获取手机号回调:', e)
|
||
|
||
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
|
||
console.error('获取手机号失败:', e.detail)
|
||
|
||
// 如果是权限问题,提示用户
|
||
if (e.detail.errno === 102) {
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '该小程序暂未开通手机号快速验证功能,请联系管理员开通后再试。',
|
||
showCancel: false
|
||
})
|
||
} else {
|
||
uni.showToast({
|
||
title: '需要授权手机号才能使用',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
return
|
||
}
|
||
|
||
// 关闭弹窗
|
||
showPhoneAuthModal.value = false
|
||
|
||
uni.showLoading({ title: '登录中...' })
|
||
|
||
try {
|
||
const code = tempWechatCode.value
|
||
const wechatId = code.substring(0, 20) // 使用部分code作为临时ID
|
||
|
||
// 获取手机号授权返回的数据
|
||
const phoneCode = e.detail.code // 手机号授权code(推荐使用)
|
||
const encryptedData = e.detail.encryptedData // 加密数据
|
||
const iv = e.detail.iv // 解密向量
|
||
|
||
console.log('手机号授权数据:', { code, phoneCode, encryptedData: encryptedData?.substring(0, 50) + '...', iv })
|
||
|
||
// 调用 identify 接口
|
||
// 后端可以选择使用 phoneCode 或 encryptedData+iv 来解密手机号
|
||
const identifyRes = await guestAPI.identify({
|
||
wechatId: wechatId,
|
||
code: code, // 微信登录code
|
||
phoneCode: phoneCode, // 手机号授权code(新版API推荐)
|
||
encryptedData: encryptedData, // 加密数据(旧版API)
|
||
iv: iv, // 解密向量(旧版API)
|
||
loginType: 'wechat_miniprogram'
|
||
})
|
||
|
||
uni.hideLoading()
|
||
|
||
if (identifyRes.success && identifyRes.data) {
|
||
const loginDomain = identifyRes.data
|
||
|
||
// 保存登录信息
|
||
uni.setStorageSync('token', loginDomain.token || '')
|
||
uni.setStorageSync('userInfo', JSON.stringify(loginDomain.user))
|
||
uni.setStorageSync('loginDomain', JSON.stringify(loginDomain))
|
||
uni.setStorageSync('wechatId', wechatId)
|
||
|
||
// 更新用户信息
|
||
userInfo.value = {
|
||
wechatId: wechatId,
|
||
username: loginDomain.user?.username || loginDomain.user?.realName || '微信用户',
|
||
phone: loginDomain.user?.phone || '',
|
||
userId: loginDomain.user?.userId || ''
|
||
}
|
||
|
||
// 判断用户类型
|
||
if (loginDomain.user?.status === 'guest') {
|
||
userType.value = false
|
||
} else {
|
||
userType.value = true
|
||
}
|
||
|
||
console.log('手机号授权登录成功:', userInfo.value)
|
||
uni.showToast({ title: '登录成功', icon: 'success' })
|
||
} else {
|
||
uni.showToast({
|
||
title: identifyRes.message || '登录失败',
|
||
icon: 'none'
|
||
})
|
||
// 登录失败,重新显示授权弹窗
|
||
showPhoneAuthModal.value = true
|
||
}
|
||
} catch (error : any) {
|
||
console.error('手机号授权登录失败:', error)
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: error.message || '登录失败',
|
||
icon: 'none'
|
||
})
|
||
// 登录失败,重新显示授权弹窗
|
||
showPhoneAuthModal.value = true
|
||
}
|
||
}
|
||
|
||
// 选择模拟用户(测试用)
|
||
async function selectMockUser(phone : string, name : string, wechatId : string) {
|
||
showPhoneAuthModal.value = false
|
||
uni.showLoading({ title: '登录中...' })
|
||
|
||
try {
|
||
// 调用 identify 接口,使用模拟模式
|
||
const identifyRes = await guestAPI.identify({
|
||
phone: phone,
|
||
wechatId: wechatId,
|
||
username: name,
|
||
mockMode: true,
|
||
loginType: 'wechat_miniprogram'
|
||
})
|
||
|
||
uni.hideLoading()
|
||
|
||
if (identifyRes.success && identifyRes.data) {
|
||
const loginDomain = identifyRes.data
|
||
|
||
// 保存登录信息
|
||
uni.setStorageSync('token', loginDomain.token || '')
|
||
uni.setStorageSync('userInfo', JSON.stringify(loginDomain.user))
|
||
uni.setStorageSync('loginDomain', JSON.stringify(loginDomain))
|
||
uni.setStorageSync('wechatId', wechatId)
|
||
|
||
// 更新用户信息
|
||
userInfo.value = {
|
||
wechatId: wechatId,
|
||
username: name,
|
||
phone: phone,
|
||
userId: loginDomain.user?.userId || ''
|
||
}
|
||
|
||
// 判断用户类型
|
||
if (loginDomain.user?.status === 'guest') {
|
||
userType.value = false
|
||
} else {
|
||
userType.value = true
|
||
}
|
||
|
||
console.log('模拟登录成功:', userInfo.value)
|
||
uni.showToast({ title: `${name} 登录成功`, icon: 'success' })
|
||
} else {
|
||
uni.showToast({
|
||
title: identifyRes.message || '登录失败',
|
||
icon: 'none'
|
||
})
|
||
showPhoneAuthModal.value = true
|
||
}
|
||
} catch (error : any) {
|
||
console.error('模拟登录失败:', error)
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: error.message || '登录失败',
|
||
icon: 'none'
|
||
})
|
||
showPhoneAuthModal.value = true
|
||
}
|
||
}
|
||
|
||
// 显示用户选择弹窗(切换人员)
|
||
function showUserSelector() {
|
||
showPhoneAuthModal.value = true
|
||
}
|
||
|
||
// 生命周期
|
||
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 : ChatPrepareData = {
|
||
chatId: chatId.value,
|
||
query: query,
|
||
agentId: agentId,
|
||
userType: userType.value,
|
||
userId: userInfo.value.userId,
|
||
files: files.length > 0 ? files : undefined,
|
||
service: "workcase"
|
||
}
|
||
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 = '抱歉,AI服务暂时不可用,请稍后重试。'
|
||
},
|
||
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)
|
||
}
|
||
}
|
||
|
||
// 检查并获取设备代码
|
||
function checkDeviceCode(action : 'workcase' | 'human') {
|
||
if (!deviceCode.value) {
|
||
// 如果没有设备代码,显示输入弹窗
|
||
pendingAction.value = action
|
||
deviceCodeInput.value = ''
|
||
showDeviceCodeDialog.value = true
|
||
} else {
|
||
// 如果已有设备代码,直接执行对应操作
|
||
if (action === 'workcase') {
|
||
doCreateWorkcase()
|
||
} else {
|
||
doContactHuman()
|
||
}
|
||
}
|
||
}
|
||
|
||
// 确认输入设备代码
|
||
function confirmDeviceCodeInput() {
|
||
if (!deviceCodeInput.value.trim()) {
|
||
uni.showToast({
|
||
title: '请输入设备代码',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
deviceCode.value = deviceCodeInput.value.trim()
|
||
showDeviceCodeDialog.value = false
|
||
|
||
// 执行待处理的操作
|
||
if (pendingAction.value === 'workcase') {
|
||
doCreateWorkcase()
|
||
} else if (pendingAction.value === 'human') {
|
||
doContactHuman()
|
||
}
|
||
pendingAction.value = ''
|
||
}
|
||
|
||
// 取消输入设备代码
|
||
function cancelDeviceCodeInput() {
|
||
showDeviceCodeDialog.value = false
|
||
deviceCodeInput.value = ''
|
||
pendingAction.value = ''
|
||
}
|
||
|
||
// 实际创建工单
|
||
async function doCreateWorkcase() {
|
||
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',
|
||
deviceCode: deviceCode.value,
|
||
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' })
|
||
}
|
||
}
|
||
|
||
// 实际联系人工
|
||
async function doContactHuman() {
|
||
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',
|
||
deviceCode: deviceCode.value,
|
||
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'
|
||
})
|
||
}
|
||
}
|
||
|
||
// 显示操作选择弹窗
|
||
function showOperationSelect(type : 'chat' | 'workcase') {
|
||
operationSelectType.value = type
|
||
operationSelectTitle.value = type === 'chat' ? '选择聊天室操作' : '选择工单操作'
|
||
showOperationSelectDialog.value = true
|
||
}
|
||
|
||
// 处理操作选择
|
||
function handleOperationSelect(operation : 'existing' | 'new') {
|
||
showOperationSelectDialog.value = false
|
||
pendingOperation.value = operation
|
||
|
||
if (operation === 'existing') {
|
||
// 进入已有
|
||
if (operationSelectType.value === 'chat') {
|
||
// 进入已有聊天室
|
||
goToChatRoomList()
|
||
} else {
|
||
// 进入已有工单
|
||
goToWorkList()
|
||
}
|
||
} else {
|
||
// 创建新的
|
||
if (operationSelectType.value === 'chat') {
|
||
// 创建新聊天室(联系人工)
|
||
contactHuman()
|
||
} else {
|
||
// 创建新工单
|
||
showCreator()
|
||
}
|
||
}
|
||
}
|
||
|
||
// 取消操作选择
|
||
function cancelOperationSelect() {
|
||
showOperationSelectDialog.value = false
|
||
operationSelectType.value = 'chat'
|
||
operationSelectTitle.value = ''
|
||
pendingOperation.value = ''
|
||
}
|
||
|
||
// 直接跳转到工单详情页的 create 模式(复用 workcaseDetail 页面)
|
||
async function showCreator() {
|
||
// 检查设备代码
|
||
checkDeviceCode('workcase')
|
||
}
|
||
|
||
// 兼容旧逻辑:不再使用页面内工单创建器
|
||
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() {
|
||
// 检查设备代码
|
||
checkDeviceCode('human')
|
||
}
|
||
|
||
// 处理快速问题
|
||
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, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
|
||
// 处理粗体(**语法)
|
||
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;word-break:break-all;">$1</code>')
|
||
|
||
// 处理链接([text](url)语法)
|
||
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" style="color:#0055AA;text-decoration:underline;word-break:break-all;overflow-wrap:break-word;">$1</a>')
|
||
|
||
// 处理标题(# ## ###等)
|
||
html = html.replace(/^### (.+)$/gm, '<div style="font-size:16px;font-weight:600;margin:8px 0 4px;word-break:break-word;">$1</div>')
|
||
html = html.replace(/^## (.+)$/gm, '<div style="font-size:18px;font-weight:600;margin:10px 0 6px;word-break:break-word;">$1</div>')
|
||
html = html.replace(/^# (.+)$/gm, '<div style="font-size:20px;font-weight:700;margin:12px 0 8px;word-break:break-word;">$1</div>')
|
||
|
||
// 处理无序列表(- 或 * 开头)
|
||
html = html.replace(/^[*-] (.+)$/gm, '<div style="margin-left:16px;word-break:break-word;">• $1</div>')
|
||
|
||
// 处理换行
|
||
html = html.replace(/\n/g, '<br/>')
|
||
|
||
// 包裹在一个具有换行样式的容器中
|
||
return `<div style="word-break:break-word;overflow-wrap:break-word;white-space:normal;max-width:100%;">${html}</div>`
|
||
}
|
||
|
||
// 显示上传选项
|
||
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 fileAPI.getDownloadUrl(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> |