From 185d04bc327cc981b0f48a004ffbd54b5a6d0111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9F=A9=E5=BA=86=E4=BC=9F?= <1208669287@qq.com> Date: Wed, 21 May 2025 19:18:22 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=AB=98=E5=BA=A6?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/index/index.vue | 209 ++++++++++++++++++++++-------------- src/pages/preview/index.vue | 48 +++++++-- src/pages/webview/index.vue | 41 ++++++- 3 files changed, 205 insertions(+), 93 deletions(-) diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue index 6c5cdda..9fe16ac 100644 --- a/src/pages/index/index.vue +++ b/src/pages/index/index.vue @@ -97,33 +97,27 @@ > <view class="flex pr-1" @click="previewMoreImg(msg.content)"> <view - v-for="(file, fileIdx) in msg.content" + v-for="(file, fileIdx) in msg.content.slice(0, 4)" :key="fileIdx" class="relative rounded-md overflow-hidden mr-1" :class="{ 'w-60 h-60': msg.content.length == 1, 'w-15 h-15': msg.content.length == 2, 'w-30 h-15 ': msg.content.length == 3, - ' h-15 flex-grow-0 flex-shrink-0 basis-10': msg.content.length >= 4, + 'w-80 h-15 ': msg.content.length >= 4, }" > <view - v-if="showImageMask && fileIdx === 4" - class="absolute top-0 left-0 right-0 bottom-0 flex items-center justify-center" - :class="{ 'bg-black bg-opacity-70': showImageMask && fileIdx === 4 }" + v-if="fileIdx >= 3" + class="absolute inset-0 flex items-center justify-center bg-black bg-opacity-70 z-80" > - +{{ msg.content.length - 4 }} + + {{ msg.content.length - 4 }} </view> <image v-if="file.uploadFileType === uploadFileTypeEm.image" :src="file.url || file.tempFilePath" - :class="{ - 'w-100% h-100%': msg.content.length == 1, - 'w-15 h-15': msg.content.length == 2, - 'w-40 h-15 ': msg.content.length == 3, - 'w-40 h-15 object-cover': msg.content.length >= 4, - }" + class="w-full h-full object-cover ml-2" /> </view> </view> @@ -219,55 +213,56 @@ transition: 'transform 0.3s ease', }" > - <div - v-for="item in uploadList" - :key="item.id" - style="flex: 0 0 4rem" - class="relative w-16 h-16 rounded overflow-hidden" - > - <!-- 预览图,成功后用后端返回的 URL;上传中可以先用本地预览 --> - <img - v-if="item.uploadFileType !== uploadFileTypeEm.file" - :src="item.url || item.tempFilePath" - class="w-full h-full object-cover" - @click="previewImage(item.url)" - /> - - <view v-else class="text-xs text-gray-400 mt-1" @click="previewFile(item.url)"> - {{ item.name }} - </view> - - <!-- 关闭按钮 --> + <div class="flex overflow-x-auto space-x-3 py-2 w-88 flex-nowrap"> <div - class="absolute top-1 right-1 w-4 h-4 rounded-full bg-black bg-opacity-50 flex items-center justify-center cursor-pointer text-white text-xs z-5" - @click="removeImage(item.id)" + v-for="item in uploadList" + :key="item.id" + class="relative w-16 h-16 rounded overflow-hidden flex-shrink-0" > - × - </div> + <!-- 预览图,成功后用后端返回的 URL;上传中可以先用本地预览 --> + <img + v-if="item.uploadFileType !== uploadFileTypeEm.file" + :src="item.url || item.tempFilePath" + class="w-full h-full object-cover" + @click="previewImage(item.url)" + /> - <!-- 重试 --> - <span - v-if="item.status === 'error'" - class="absolute w-full h-full bg-black bg-opacity-40 pt-1 left-0 top-0 text-center color-white text-3xl" - @click.stop="retry(item)" - > - ↻ - </span> + <view v-else class="text-xs text-gray-400 mt-1" @click="previewFile(item.url)"> + {{ item.name }} + </view> - <!-- 进度 / 成功 / 失败 --> - <div - class="absolute bottom-0 left-0 w-full text-xs text-center py-1 bg-black bg-opacity-50" - :class="{ - 'text-black': item.status === 'uploading', - 'text-green': item.status === 'success', - 'text-red': item.status === 'error', - }" - > - <template v-if="item.status === 'uploading'"> - <view class="text-white">{{ item.progress }}%</view> - </template> - <template v-else-if="item.status === 'success'">✔ 成功</template> - <template v-else>✖ 失败</template> + <!-- 关闭按钮 --> + <div + class="absolute top-1 right-1 w-4 h-4 rounded-full bg-black bg-opacity-50 flex items-center justify-center cursor-pointer text-white text-xs z-5" + @click="removeImage(item.id)" + > + × + </div> + + <!-- 重试 --> + <span + v-if="item.status === 'error'" + class="absolute w-full h-full bg-black bg-opacity-40 pt-1 left-0 top-0 text-center color-white text-3xl" + @click.stop="retry(item)" + > + ↻ + </span> + + <!-- 进度 / 成功 / 失败 --> + <div + class="absolute bottom-0 left-0 w-full text-xs text-center py-1 bg-black bg-opacity-50" + :class="{ + 'text-black': item.status === 'uploading', + 'text-green': item.status === 'success', + 'text-red': item.status === 'error', + }" + > + <template v-if="item.status === 'uploading'"> + <view class="text-white">{{ item.progress }}%</view> + </template> + <template v-else-if="item.status === 'success'">✔ 成功</template> + <template v-else>✖ 失败</template> + </div> </div> </div> </div> @@ -423,7 +418,7 @@ interface IMessage { timestamp: Date } const userAvatar = ref('') -const chatMode = ref('tongyi-app') +const chatMode = ref('qwen-vl-plus') const baseUrl = getEnvBaseUrl() const messages = reactive<IMessage[]>([]) @@ -529,7 +524,7 @@ async function fetchHistoryList() { method: 'POST', data: { page: 1, - pageSize: 30, + pageSize: 9990, }, header: { Authorization: token.value, @@ -539,9 +534,7 @@ async function fetchHistoryList() { rawList.value = resp.data.data.data console.log('fetchHistoryList →', rawList.value) } - } catch (err) { - console.error('fetchHistoryList error:', err) - } + } catch (err) {} } /** 3. 拉取历史记录详情 */ @@ -558,16 +551,16 @@ async function fetchHistoryDiets(value) { Authorization: token.value, }, }) - console.log(resp, '/**************resp*********************/') + if (resp && resp.data) { const rawList = resp.data.data // 假设后端直接返回消息数组 + listUuid.value = resp.data.data[0].listUuid + const newMessages = parseBackendMessages(rawList) // 用解析后的消息替换当前消息列表 messages.splice(0, messages.length, ...newMessages) } - } catch (err) { - console.error('fetchHistoryList error:', err) - } + } catch (err) {} } function parseBackendMessages(rawList: any[]): IMessage[] { @@ -584,15 +577,20 @@ function parseBackendMessages(rawList: any[]): IMessage[] { const arr = JSON.parse(rawContent) as Array<{ text: string; type: string }> if (Array.isArray(arr)) { parts = arr.map((el) => { - if (el.type === 'text') { - return { type: 'text', content: el.text } - } - if (el.type === 'image_url' || el.type === 'image') { + if (el.type === 'file_url') { + return { + type: 'video', + content: [{ url: el.text, uploadFileType: 'video' }], + } + } else if (el.type === 'image_url' || el.type === 'image') { return { type: 'image', content: [{ url: el.text, uploadFileType: 'image' }], } + } else if (el.type === 'text') { + return { type: 'text', content: el.text } } + // 其他类型按需扩展 return { type: el.type, content: el.text } }) @@ -699,7 +697,6 @@ const mask = ref('') onMounted(() => { // 1. 定义一个 init 函数,拿 Extras 并依次调用接口 - const init = async () => { const wv = plus.webview.currentWebview() // token.value = wv.token || uni.getStorageSync('token') || import.meta.env.VITE_DEV_TOKEN @@ -718,7 +715,6 @@ function scrollToBottom() { const el = scrollEl.value! nextTick(() => { // el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' }) - console.log(el.scrollHeight, 'el.scrollHeight') el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' }) }) } @@ -778,9 +774,13 @@ async function newChat() { } const rotation = ref(0) function toggleActions() { - closeKeyboard() - showActions.value = !showActions.value + uni.hideKeyboard() + rotation.value += 45 + + console.log(rotation.value, '2') + showActions.value = !showActions.value + scrollToBottom() } @@ -791,10 +791,10 @@ const uploadFileTypeEm = { file: 'file', text: 'text', } - +// const url=baseUrl // 上传配置 const uploadConfig = reactive({ - url: 'http://114.218.158.24:9020/upload/img', + url: `${baseUrl}/upload/img`, formData: { source: 'chat', mask: mask.value, @@ -1032,11 +1032,51 @@ function previewImage(url: string) { } // 预览文件 +// 统一替换掉原来的 previewFile const previewFile = (url: string) => { - //跳转到webview打开 - uni.navigateTo({ - url: '/pages/webview/index?link=' + encodeURIComponent(url), + if (typeof plus !== 'undefined') { + // 在 App 里直接用原生下载 + 打开 + downloadAndOpenFile(url) + } else { + // H5 或者调试环境回退到 WebView + uni.navigateTo({ + url: '/pages/webview/index?link=' + encodeURIComponent(url), + }) + } +} + +const downloadAndOpenFile = (downloadUrl: string) => { + uni.showLoading({ title: '加载中...', mask: true }) + + if (!downloadUrl) { + uni.hideLoading() + return uni.showToast({ title: '文件路径无效', icon: 'none' }) + } + + // 将文件存放到应用私有下载目录,保证权限可读可写 + const options = { + // “_downloads/” 会自动映射到应用的 Documents/download 目录 + filename: '_downloads/', + } + + const dtask = plus.downloader.createDownload(downloadUrl, options, (d, status) => { + uni.hideLoading() + if (status === 200) { + const savedPath = d.filename + if (savedPath) { + // 用系统默认的方式打开任意类型文件(PDF/Word/Excel/图片/视频 都通用) + plus.runtime.openFile(savedPath, {}, () => { + // 打开失败的回调可选 + }) + } else { + uni.showToast({ title: '文件保存失败', icon: 'none' }) + } + } else { + uni.showToast({ title: '下载失败', icon: 'error' }) + } }) + + dtask.start() } const msgLoading = ref(true) // 发送消息 @@ -1226,10 +1266,10 @@ function refreshText() { const knowledgeOpen = ref(false) function toggleKnowledge() { console.error('44444', chatMode.value) - if (chatMode.value == 'tongyi-app') { - chatMode.value = 'qwen-vl-plus' - } else { + if (chatMode.value == 'qwen-vl-plus') { chatMode.value = 'tongyi-app' + } else { + chatMode.value = 'qwen-vl-plus' } knowledgeOpen.value = !knowledgeOpen.value showActions.value = false @@ -1310,7 +1350,7 @@ const onFocus = () => { border-radius: 12rpx 12rpx 0 0; } .popup.fullscreen { - height: 100%; + height: 90%; border-radius: 0; } /* Header */ @@ -1355,4 +1395,7 @@ const onFocus = () => { .tops { padding-top: var(--status-bar-height); } +.flex-i { + display: flex !important; +} </style> diff --git a/src/pages/preview/index.vue b/src/pages/preview/index.vue index 21d3b8b..b3fbe70 100644 --- a/src/pages/preview/index.vue +++ b/src/pages/preview/index.vue @@ -1,5 +1,5 @@ <template> - <view class="flex flex-col h-screen bg-#ffffff tops"> + <view class="flex flex-col h-90% bg-#ffffff"> <view class="flex-none flex items-center justify-between px-5 py-3 bg-white shadow-md h-20 pt-10 z-999 fixed top-0 w-full box-border" > @@ -11,14 +11,36 @@ :key="index" class="aspect-square overflow-hidden group" > - <!-- <image + <image :src="image.url" mode="aspectFill" class="w-full h-full" @click="handleImageClick(index)" - /> --> + /> - <wd-img :width="100" :height="100" :src="image.url" :enable-preview="true" /> + <!-- <wd-img :width="100" :height="100" :src="image.url" :enable-preview="true" /> --> + </view> + </view> + + <!-- 自定义预览弹窗 --> + <view + v-if="showPreview" + class="fixed inset-0 bg-black bg-opacity-90 z-999 flex justify-center items-start pt-10" + @click="closePreview" + > + <view class="relative w-full max-w-full mt-8 p-5 box-border"> + <image + class="w-full max-h-[calc(100vh-90px)] object-contain" + :src="previewUrl" + mode="widthFix" + @click.stop + /> + <view + class="absolute top--8 right-5 text-white text-3xl z-1000 w-10 h-10 text-center leading-10" + @click.stop="closePreview" + > + × + </view> </view> </view> @@ -62,10 +84,18 @@ onMounted(() => { videoList.value = previewVideos } }) + +const showPreview = ref(false) +const previewUrl = ref('') +const previewTop = ref(30) // 距离顶部30px +const handleImageClick = (index) => { + previewUrl.value = imageList.value[index].url + showPreview.value = true +} + +const closePreview = () => { + showPreview.value = false +} </script> -<style scoped> -.tops { - padding-top: var(--status-bar-height); -} -</style> +<style scoped></style> diff --git a/src/pages/webview/index.vue b/src/pages/webview/index.vue index 585c03b..8a9c4b8 100644 --- a/src/pages/webview/index.vue +++ b/src/pages/webview/index.vue @@ -1,5 +1,5 @@ <template> - <web-view :src="link"></web-view> + <web-view :src="link" :webview-styles="webviewStyles"></web-view> </template> <script> @@ -7,6 +7,12 @@ export default { data() { return { link: '', + webviewStyles: { + width: '100%', + progress: { + color: '#999', + }, + }, } }, onLoad(options) { @@ -14,6 +20,39 @@ export default { this.link = options.link } }, + mounted() { + // 只有 Webview 环境才有 plus + // if (window.plus) { + // const wv = plus.webview.currentWebview() + // // 页面加载完后再注入 padding + // wv.addEventListener('loaded', () => { + // wv.evalJS(` + // document.documentElement.style.padding = '30px'; + // document.body.style.margin = '0'; + // `) + // }) + // } + + uni.getSystemInfo({ + success: (res) => { + // 获取状态栏高度和窗口高度 + const statusBarHeight = res.statusBarHeight + const windowHeight = res.windowHeight + + // 计算WebView高度(窗口高度减去状态栏高度) + this.webviewStyles.height = windowHeight - statusBarHeight + }, + fail: (err) => { + console.error('获取系统信息失败:', err) + // 默认使用窗口高度 + uni.getSystemInfo({ + success: (res) => { + this.webviewStyles.height = res.windowHeight + }, + }) + }, + }) + }, } </script>