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>