Jelajahi Sumber

fix(uniapp): H5 端图片走 oss proxy + 显式 px + Sass 修复

- 新增 utils/oss.ts: ossUrl(url) 工具
  * http://xxx → /dev-api/resource/oss/proxy?url=encoded(...)
  * https/相对路径/data:/blob: 不动
  * BASE_API 硬编码 '/dev-api',避免循环依赖 + SyntaxError

- pages/inspection/check + qualityInspection/check:
  * <image :src> 改 ossUrl(p) 走代理,解决 H5 端 mixed content
  * <image @error=onPhotoError> 加载失败容错
  * .photo-thumb 显式 90px×90px(H5 rpx 可能 0×0)
  * .photo-img 加 display:block + object-fit:cover 兜底
  * 加 .photo-view-count 角标显示已选张数
  * Sass 变量 $primary-color 在 scoped style 不继承,改成硬编码 #409EFF

- onSubmit 位置获取独立 try-catch:
  * 二维码校验独立 catch(具体错误信息)
  * 位置获取独立 catch(失败仅 warn,不阻塞)
  * 跳 check 页时经纬度传 0,0
yangjingjing 18 jam lalu
induk
melakukan
05935517e0

+ 57 - 11
src/pages/inspection/check/index.vue

@@ -77,11 +77,20 @@
                 :key="i"
                 class="photo-thumb"
               >
-                <image :src="p" mode="aspectFill" class="photo-img" />
+                <!-- H5 端 image 必须显式 width/height,否则可能 0×0 不显示 -->
+                <image
+                  :src="ossUrl(p)"
+                  mode="aspectFill"
+                  class="photo-img"
+                  @error="onPhotoError(p, i)"
+                />
                 <view v-if="!readonly" class="photo-del" @tap="delPhoto(item, i)">×</view>
               </view>
               <view v-if="!readonly" class="photo-add" @tap="choosePhoto(item)">
                 <text class="photo-add-icon">📷</text>
+                <text class="photo-view-count" v-if="item.photos && item.photos.length">
+                  {{ item.photos.length }}
+                </text>
                 <text class="photo-add-text">添加照片</text>
               </view>
             </view>
@@ -115,6 +124,7 @@ import { getInspectionTaskDetail, submitPunch, uploadPhoto } from '@/api/inspect
 import { request } from '@/utils/request'
 import { getToken } from '@/utils/auth'
 import { getCurrentLocation, ensureLocationAuth, pickLocationFallback } from '@/utils/location'
+import { ossUrl } from '@/utils/oss'
 
 const taskId = ref<string>('')
 const readonly = ref<boolean>(false)
@@ -249,10 +259,23 @@ async function choosePhoto(item: InspectionItem) {
   // 4. 全部成功:写入 item.photos
   if (!item.photos) item.photos = []
   item.photos.push(...uploaded.map((u) => u.url))
-  items.value = [...items.value]
+  // 强制刷新(items 本身是 ref,数组内对象也应该是 reactive,这里保险起见重新赋值)
+  // 注意:不要用 items.value = [...items.value],会丢失其他项的 photos 引用变更追踪
+  // 改用整个数组的浅拷贝 + 重新赋值
+  items.value = items.value.map((it) =>
+    it === item ? { ...it, photos: [...(it.photos || [])] } : it
+  )
   uni.showToast({ title: `已上传 ${uploaded.length} 张`, icon: 'success' })
 }
 
+/**
+ * 图片加载失败回调(H5 端常见:跨域、URL 失效、网络问题)
+ * 仅 console.warn,不影响主流程
+ */
+function onPhotoError(url: string, idx: number) {
+  console.warn(`[check] 照片加载失败 idx=${idx} url=${url}`)
+}
+
 /**
  * 回滚已上传但最终未使用的对象(调后端删除接口)
  * - 失败不重试:MinIO 端有定时清理,这里只做尽力而为
@@ -351,14 +374,21 @@ async function onSubmit() {
     let lng = 0
     let lat = 0
     if (pickedCoord.value) {
+      // 用户已在地图上选过点,优先用
       lng = pickedCoord.value.lng
       lat = pickedCoord.value.lat
     } else {
-      const auth = await ensureLocationAuth()
-      if (auth) {
-        const loc = await getCurrentLocation()
-        lng = loc.longitude
-        lat = loc.latitude
+      // H5 端 try-catch:uni.authorize/getLocation 在浏览器可能不存在/被拒/超时
+      // 失败也不阻塞提交,经纬度传 0,0
+      try {
+        const auth = await ensureLocationAuth()
+        if (auth) {
+          const loc = await getCurrentLocation()
+          lng = loc.longitude
+          lat = loc.latitude
+        }
+      } catch (locErr: any) {
+        console.warn('[check] 获取位置失败(非阻塞):', locErr?.message || locErr)
       }
     }
 
@@ -581,14 +611,17 @@ const hasAbnormal = computed(() => items.value.some((it) => it.result === 'ABNOR
 }
 .photo-thumb {
   position: relative;
-  width: 160rpx;
-  height: 160rpx;
+  width: 90px;  /* 显式 px,避免 H5 端 rpx 计算问题 */
+  height: 90px;
   border-radius: $radius-md;
   overflow: hidden;
+  background: #f5f5f5;
 }
 .photo-img {
   width: 100%;
   height: 100%;
+  display: block;  /* 避免 inline-image 底部 3px 空隙 */
+  object-fit: cover;  /* 兜底 H5 端 image 组件 aspectFill 失效 */
 }
 .photo-del {
   position: absolute;
@@ -604,8 +637,9 @@ const hasAbnormal = computed(() => items.value.some((it) => it.result === 'ABNOR
   font-size: 24rpx;
 }
 .photo-add {
-  width: 160rpx;
-  height: 160rpx;
+  position: relative;  /* 让 .photo-view-count 角标能绝对定位 */
+  width: 90px;
+  height: 90px;
   border: 2rpx dashed $border-color;
   border-radius: $radius-md;
   display: flex;
@@ -618,6 +652,18 @@ const hasAbnormal = computed(() => items.value.some((it) => it.result === 'ABNOR
   font-size: 48rpx;
   margin-bottom: 8rpx;
 }
+.photo-view-count {
+  position: absolute;
+  top: 4rpx;
+  right: 8rpx;
+  background: #409EFF;  /* 用主色硬编码,scoped 不会继承全局 Sass 变量 */
+  color: #fff;
+  font-size: 20rpx;
+  padding: 2rpx 8rpx;
+  border-radius: 20rpx;
+  min-width: 28rpx;
+  text-align: center;
+}
 .photo-add-text {
   font-size: $font-xs;
 }

+ 2 - 1
src/pages/qualityInspection/check/index.vue

@@ -78,7 +78,7 @@
                 :key="i"
                 class="photo-thumb"
               >
-                <image :src="p" mode="aspectFill" class="photo-img" />
+                <image :src="ossUrl(p)" mode="aspectFill" class="photo-img" />
                 <view v-if="!readonly" class="photo-del" @tap="delPhoto(item, i)">×</view>
               </view>
               <view v-if="!readonly" class="photo-add" @tap="choosePhoto(item)">
@@ -120,6 +120,7 @@ import {
 import { request } from '@/utils/request'
 import { getToken } from '@/utils/auth'
 import { getCurrentLocation, ensureLocationAuth, pickLocationFallback } from '@/utils/location'
+import { ossUrl } from '@/utils/oss'
 
 // 后端 Java Long > JS Number 安全整数,这里必须保留字符串
 const taskId = ref<string>('')

+ 43 - 0
src/utils/oss.ts

@@ -0,0 +1,43 @@
+/**
+ * OSS URL 工具(解决 H5 端 mixed content 阻止 HTTPS 页面加载 HTTP OSS 资源)
+ * <p>
+ * 问题:
+ * - H5 端是 https://localhost:9000
+ * - OSS url 是 http://106.55.100.207:9000/... (HTTP)
+ * - 浏览器阻止 HTTPS 页面加载 HTTP 子资源(mixed content)
+ * <p>
+ * 解决方案:
+ * - 后端加了 /resource/oss/proxy?url=... 接口(流式转发 OSS)
+ * - 前端把外部 http url 通过走代理
+ * - 相对路径(/开头)或已经是 https 的不动
+ *
+ * 使用:
+ *   import { ossUrl } from '@/utils/oss'
+ *   <image :src="ossUrl(p)" />
+ *
+ * 注意: BASE_API 不 import request.ts 的常量(避免循环依赖 + 运行时 500),
+ * 改为硬编码 '/dev-api' —— 与 request.ts 默认行为一致
+ */
+
+// H5 端走 vite proxy,请求到本机 5173,被 proxy 到 9225
+// 真机/小程序端走同域反代,也是 '/dev-api'
+// 部署到生产时,如 nginx 配置调整,这里也要同步改
+const BASE_API = '/dev-api'
+
+export function ossUrl(url: string | null | undefined): string {
+  if (!url) return ''
+  // 已经是 https / 相对路径 / data: / blob: → 不动
+  if (
+    url.startsWith('https://') ||
+    url.startsWith('/') ||
+    url.startsWith('data:') ||
+    url.startsWith('blob:')
+  ) {
+    return url
+  }
+  // http://... → 走后端代理(走 vite proxy,避免 mixed content)
+  if (url.startsWith('http://') || url.startsWith('https://')) {
+    return `${BASE_API}/resource/oss/proxy?url=${encodeURIComponent(url)}`
+  }
+  return url
+}