1轮修复

This commit is contained in:
2026-01-20 16:17:39 +08:00
parent 0bf7361672
commit 8ab6107f25
23 changed files with 2587 additions and 612 deletions

View File

@@ -7,7 +7,8 @@
position: relative;
// 多种安全区域适配方式
padding-top: env(safe-area-inset-top);
padding-top: constant(safe-area-inset-top); /* 兼容iOS < 11.2 */
padding-top: constant(safe-area-inset-top);
/* 兼容iOS < 11.2 */
box-sizing: border-box;
}
@@ -18,12 +19,12 @@
left: 0;
right: 0;
display: flex;
flex-direction: row;
flex-direction: row;
align-items: center;
padding: 0 16px;
z-index: 100;
box-sizing: border-box;
// 小程序需要为右侧胶囊按钮留出空间
/* #ifdef MP-WEIXIN */
padding-right: 100px;
@@ -83,7 +84,7 @@
// 退出按钮特殊样式
.logout-btn {
background: rgba(255, 59, 48, 0.1);
.btn-text {
color: #ff3b30;
}
@@ -117,10 +118,25 @@
border: none !important;
}
.r1 { width: 260px; height: 260px; }
.r2 { width: 200px; height: 200px; }
.r3 { width: 150px; height: 150px; }
.r4 { width: 110px; height: 110px; }
.r1 {
width: 260px;
height: 260px;
}
.r2 {
width: 200px;
height: 200px;
}
.r3 {
width: 150px;
height: 150px;
}
.r4 {
width: 110px;
height: 110px;
}
.robot {
position: relative;
@@ -128,10 +144,10 @@
width: 140px;
height: 140px;
/* 父容器加一层径向渐变背景,模拟外层模糊光晕 */
background: radial-gradient(circle at center,
rgba(180, 220, 255, 0.5) 0%,
rgba(180, 220, 255, 0.25) 50%,
transparent 75%);
background: radial-gradient(circle at center,
rgba(180, 220, 255, 0.5) 0%,
rgba(180, 220, 255, 0.25) 50%,
transparent 75%);
border-radius: 50%;
display: flex;
align-items: center;
@@ -164,16 +180,30 @@
.float-tag {
position: absolute;
padding: 6px 12px;
background: transparent; /* 去掉背景色,和左侧一致 */
border: none; /* 去掉边框 */
border-radius: 0; /* 保持直角如果需要圆角也可以改回16px */
background: transparent;
/* 去掉背景色,和左侧一致 */
border: none;
/* 去掉边框 */
border-radius: 0;
/* 保持直角如果需要圆角也可以改回16px */
font-size: 12px;
color: #666;
}
.t1 { right: 20px; top: 40px; }
.t2 { left: 20px; top: 80px; }
.t3 { right: 30px; bottom: 50px; }
.t1 {
right: 20px;
top: 40px;
}
.t2 {
left: 20px;
top: 80px;
}
.t3 {
right: 30px;
bottom: 50px;
}
.greeting {
text-align: left;
@@ -199,7 +229,7 @@
background: #fff;
padding: 12px 16px;
border-radius: 12px;
margin: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
@@ -373,63 +403,86 @@
padding-left: 48px;
}
// 底部操作区域
// 底部操作区域(如果没有定义,需要添加)
.bottom-area {
position: fixed;
bottom: 0;
left: 0;
right: 0;
// background: rgba(240, 241, 246, 0.95);
padding: 12px 16px;
padding-bottom: calc(12px + env(safe-area-inset-bottom));
z-index: 50;
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 12px 16px;
padding-bottom: calc(12px + env(safe-area-inset-bottom));
z-index: 50;
background-color: #fff; // 增加背景色,避免穿透
}
// 快捷按钮横向滚动
// 快捷按钮横向滚动 - 修复横向滚动问题
.quick-scroll {
white-space: nowrap;
margin-bottom: 12px;
width: 100%;
margin-bottom: 12px;
/* 核心必须设置固定高度否则scroll-view可能无法正常工作 */
height: 50px;
/* 确保scroll-view能正确处理滚动 */
overflow-x: scroll;
display: flex;
flex-direction: row;
-webkit-overflow-scrolling: touch;
/* 隐藏滚动条但保留滚动功能 */
scrollbar-width: none;
-ms-overflow-style: none;
}
.quick-inner {
display: flex;
flex-direction: row;
align-items: center; /* ✅ 垂直居中 */
padding: 0 16px;
height: 100%;
}
/* 隐藏滚动条 */
.quick-scroll::-webkit-scrollbar {
display: none;
}
.quick-list {
display: inline-flex;
flex-direction: row;
align-items: center;
gap: 8px;
display: inline-flex; /* 使用inline-flex确保宽度随内容增长触发横向滚动 */
flex-direction: row;
align-items: center;
gap: 8px;
padding: 0 16px;
/* 关键:让容器宽度自动适应内容,超出父容器时触发滚动 */
width: auto;
/* 确保垂直居中 */
height: 100%;
}
.quick-btn {
margin: 0;
position: relative;
box-sizing: border-box;
flex-shrink: 0;
flex-grow: 0;
flex-basis: auto;
align-content: stretch;
min-height: 0px;
min-width: 0px;
overflow: hidden;
align-items: center;
justify-content: center;
background: #fff;
border-radius: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
border: none;
display: flex;
flex-direction: row;
padding: 10px 24px;
color: black;
/* 保持原有样式 */
margin: 0;
position: relative;
box-sizing: border-box;
background: #fff;
border-radius: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
border: none;
padding: 10px 24px;
color: black;
/* 确保按钮不换行 */
white-space: nowrap;
/* 关键防止flex子项被压缩确保产生横向溢出以便滚动 */
flex-shrink: 0;
/* 按钮内部使用flex布局 */
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
/* 确保按钮高度合适 */
height: 36px;
}
.quick-btn.has-icon {
// background: linear-gradient(90deg, #173294 0%, #4a6fd9 100%);
border: none;
padding: 10px 24px;
/* 保持原有样式 */
}
.quick-btn.has-icon .quick-text {
// color: #fff;
font-weight: 500;
/* 保持原有样式 */
}
.quick-icon {
@@ -443,6 +496,7 @@
height: 20px;
background: #d0d5dd;
margin: 0 4px;
flex-shrink: 0;
}
.quick-text {
@@ -696,10 +750,14 @@
}
@keyframes typing-bounce {
0%, 80%, 100% {
0%,
80%,
100% {
transform: scale(0.6);
opacity: 0.5;
}
40% {
transform: scale(1);
opacity: 1;
@@ -953,3 +1011,77 @@
font-size: 14px;
color: #666;
}
// 操作选择弹窗样式
.operation-select-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
.modal-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
}
.modal-content {
position: relative;
width: 80%;
max-width: 400px;
background: white;
border-radius: 16px;
padding: 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
}
.modal-header {
text-align: center;
margin-bottom: 20px;
}
.modal-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
.operation-select-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.operation-select-item {
display: flex;
align-items: center;
padding: 16px;
background: #f5f7fa;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
}
.operation-select-item:active {
background: #e4e7ed;
transform: scale(0.98);
}
.select-icon {
font-size: 24px;
margin-right: 12px;
}
.select-text {
font-size: 16px;
color: #333;
}

View File

@@ -7,13 +7,13 @@
<button class="workcase-btn" @tap="showUserSelector">
<text class="btn-text">{{userInfo.username || '切换'}}</text>
</button>
<button class="workcase-btn" @tap="goToChatRoomList">
<!-- <button class="workcase-btn" @tap="showOperationSelect('chat')">
<text class="btn-text">聊天室</text>
</button>
<button class="workcase-btn" @tap="goToWorkList">
<button class="workcase-btn" @tap="showOperationSelect('workcase')">
<image class="btn-icon" src="/static/imgs/case.svg" />
<text class="btn-text">工单</text>
</button>
</button> -->
</view>
</view>
<!-- 欢迎区域(机器人+浮动标签) -->
@@ -41,7 +41,8 @@
</view>
<!-- 聊天消息区域 -->
<scroll-view class="chat-messages" scroll-y="true" :scroll-top="scrollTop" scroll-with-animation="true" :class="{ started: messages.length > 0 }">
<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>
@@ -59,7 +60,8 @@
</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-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>
@@ -86,69 +88,74 @@
<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>
<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>
<!-- 快捷按钮横向滚动 -->
<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>
</scroll-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 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>
<!-- 输入框 -->
<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 v-else class="file-preview-doc">
<text class="doc-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>
<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>
@@ -157,13 +164,8 @@
<text class="modal-title">请输入设备代码</text>
</view>
<view class="modal-body">
<input
class="device-code-input"
v-model="deviceCodeInput"
placeholder="请输入设备代码"
focus
@confirm="confirmDeviceCodeInput"
/>
<input class="device-code-input" v-model="deviceCodeInput" placeholder="请输入设备代码" focus
@confirm="confirmDeviceCodeInput" />
</view>
<view class="modal-footer">
<button class="modal-btn cancel" @tap="cancelDeviceCodeInput">
@@ -176,6 +178,28 @@
</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>
@@ -237,11 +261,11 @@
import { AGENT_ID } from '@/config'
// 前端消息展示类型
interface ChatMessageItem {
type: 'user' | 'bot'
content: string
time: string
actions?: string[] | null
files?: string[] // 文件ID数组
type : 'user' | 'bot'
content : string
time : string
actions ?: string[] | null
files ?: string[] // 文件ID数组
}
const agentId = AGENT_ID
// 响应式数据
@@ -268,7 +292,7 @@
userId: ''
})
const userType = ref(false)
// 是否显示手机号授权弹窗
const showPhoneAuthModal = ref(false)
// 临时保存的微信登录code
@@ -283,6 +307,12 @@
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 {
@@ -290,7 +320,7 @@
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
@@ -300,14 +330,14 @@
phone: parsedUserInfo.phone || '',
userId: parsedUserInfo.userId || ''
}
// 判断用户类型
if (parsedUserInfo.status === 'guest') {
userType.value = false
} else {
userType.value = true
}
console.log('使用缓存的用户信息:', userInfo.value)
return
}
@@ -324,7 +354,7 @@
// 自动登录
async function autoLogin() {
uni.showLoading({ title: '初始化中...' })
try {
// 使用 wx.login 获取 code
uni.login({
@@ -332,10 +362,10 @@
success: async (loginRes) => {
console.log('微信登录成功code:', loginRes.code)
uni.hideLoading()
// 保存code等待手机号授权后使用
tempWechatCode.value = loginRes.code
// 显示手机号授权弹窗
showPhoneAuthModal.value = true
},
@@ -345,7 +375,7 @@
uni.showToast({ title: '登录失败,请重试', icon: 'none' })
}
})
} catch (error: any) {
} catch (error : any) {
console.error('自动登录失败:', error)
uni.hideLoading()
uni.showToast({
@@ -362,7 +392,7 @@
uni.removeStorageSync('userInfo')
uni.removeStorageSync('loginDomain')
uni.removeStorageSync('wechatId')
uni.showLoading({ title: '登录中...' })
try {
const res = await guestAPI.identify({
@@ -379,7 +409,7 @@
userInfo.value.userId = loginDomain.user?.userId || ''
console.log('identify成功:', loginDomain)
uni.showToast({ title: '登录成功', icon: 'success' })
if(loginDomain.user.status == 'guest') {
if (loginDomain.user.status == 'guest') {
userType.value = false
} else {
userType.value = true
@@ -394,12 +424,12 @@
}
// 获取手机号回调(保留用于正式环境)
async function onGetPhoneNumber(e: any) {
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({
@@ -418,20 +448,20 @@
// 关闭弹窗
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({
@@ -442,18 +472,18 @@
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,
@@ -461,14 +491,14 @@
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 {
@@ -479,7 +509,7 @@
// 登录失败,重新显示授权弹窗
showPhoneAuthModal.value = true
}
} catch (error: any) {
} catch (error : any) {
console.error('手机号授权登录失败:', error)
uni.hideLoading()
uni.showToast({
@@ -492,7 +522,7 @@
}
// 选择模拟用户(测试用)
async function selectMockUser(phone: string, name: string, wechatId: string) {
async function selectMockUser(phone : string, name : string, wechatId : string) {
showPhoneAuthModal.value = false
uni.showLoading({ title: '登录中...' })
@@ -505,18 +535,18 @@
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,
@@ -524,14 +554,14 @@
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 {
@@ -541,7 +571,7 @@
})
showPhoneAuthModal.value = true
}
} catch (error: any) {
} catch (error : any) {
console.error('模拟登录失败:', error)
uni.hideLoading()
uni.showToast({
@@ -570,7 +600,7 @@
// 获取窗口信息
const windowInfo = uni.getWindowInfo()
statusBarHeight.value = windowInfo.statusBarHeight || 0
// #ifdef MP-WEIXIN
// 获取胶囊按钮信息仅小程序计算header位置
try {
@@ -602,7 +632,7 @@
})
// 添加用户消息(包含文件)
const userMessage: ChatMessageItem = {
const userMessage : ChatMessageItem = {
type: 'user',
content: text,
time: new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }),
@@ -640,7 +670,7 @@
}
// 准备流式对话(包含文件)
const prepareData: ChatPrepareData = {
const prepareData : ChatPrepareData = {
chatId: chatId.value,
query: query,
agentId: agentId,
@@ -776,7 +806,7 @@
}
// 检查并获取设备代码
function checkDeviceCode(action: 'workcase' | 'human') {
function checkDeviceCode(action : 'workcase' | 'human') {
if (!deviceCode.value) {
// 如果没有设备代码,显示输入弹窗
pendingAction.value = action
@@ -881,7 +911,7 @@
icon: 'none'
})
}
} catch (error: any) {
} catch (error : any) {
uni.hideLoading()
console.error('创建聊天室失败:', error)
uni.showToast({
@@ -891,6 +921,47 @@
}
}
// 显示操作选择弹窗
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() {
// 检查设备代码
@@ -1023,7 +1094,7 @@
sourceType: ['album'],
success: (res) => {
if (res.tempFilePaths && res.tempFilePaths.length > 0) {
res.tempFilePaths.forEach((filePath: string) => {
res.tempFilePaths.forEach((filePath : string) => {
uploadSingleFile(filePath)
})
}
@@ -1039,15 +1110,15 @@
count: 5,
type: 'file',
extension: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'txt'],
success: (res: any) => {
success: (res : any) => {
console.log('选择文件成功:', res)
if (res.tempFiles && res.tempFiles.length > 0) {
res.tempFiles.forEach((file: any) => {
res.tempFiles.forEach((file : any) => {
uploadSingleFile(file.path)
})
}
},
fail: (err: any) => {
fail: (err : any) => {
console.error('选择文件失败:', err)
uni.showToast({
title: '选择文件失败',
@@ -1065,15 +1136,15 @@
uni.chooseFile({
count: 5,
extension: ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.txt'],
success: (res: any) => {
success: (res : any) => {
console.log('选择文件成功:', res)
if (res.tempFilePaths && res.tempFilePaths.length > 0) {
res.tempFilePaths.forEach((filePath: string) => {
res.tempFilePaths.forEach((filePath : string) => {
uploadSingleFile(filePath)
})
}
},
fail: (err: any) => {
fail: (err : any) => {
console.error('选择文件失败:', err)
uni.showToast({
title: '选择文件失败',
@@ -1091,7 +1162,7 @@
}
// 上传单个文件
async function uploadSingleFile(filePath: string) {
async function uploadSingleFile(filePath : string) {
console.log('开始上传文件:', filePath)
if (!agentId) {
@@ -1112,7 +1183,7 @@
} else {
uni.showToast({ title: result.message || '上传失败', icon: 'none' })
}
} catch (error: any) {
} catch (error : any) {
console.error('文件上传失败:', error)
uni.showToast({ title: '上传失败: ' + (error.message || '未知错误'), icon: 'none' })
} finally {
@@ -1122,27 +1193,27 @@
}
// 移除已上传的文件
function removeUploadedFile(index: number) {
function removeUploadedFile(index : number) {
uploadedFiles.value.splice(index, 1)
}
// 判断是否为图片文件
function isImageFile(file: DifyFileInfo): boolean {
function isImageFile(file : DifyFileInfo) : boolean {
return file.type === 'image' || file.mime_type?.startsWith('image/') || false
}
// 获取文件预览URL
function getFilePreviewUrl(file: DifyFileInfo): string {
function getFilePreviewUrl(file : DifyFileInfo) : string {
return file.preview_url || file.source_url || file.url || ''
}
// 获取文件下载URL通过文件ID
function getFileDownloadUrl(fileId: string): string {
function getFileDownloadUrl(fileId : string) : string {
return fileAPI.getDownloadUrl(fileId)
}
// 判断文件ID对应的文件是否为图片
function isImageFileById(fileId: string): boolean {
function isImageFileById(fileId : string) : boolean {
// 从缓存中查找文件信息
const file = fileInfoCache.value.get(fileId)
if (file) {
@@ -1157,13 +1228,13 @@
}
// 获取文件名(从缓存)
function getFileName(fileId: string): string {
function getFileName(fileId : string) : string {
const file = fileInfoCache.value.get(fileId)
return file?.name || fileId.substring(0, 8) + '...'
}
// 文件预览
function previewFile(fileId: string) {
function previewFile(fileId : string) {
const url = getFileDownloadUrl(fileId)
// 如果是图片,使用图片预览
if (isImageFileById(fileId)) {
@@ -1191,7 +1262,6 @@
})
}
}
</script>
<style lang="scss" scoped>