Просмотр исходного кода

fix(signature/product): 横屏签名回显占满 + 商品页隐藏营养成分

签名回显问题:
- hand-signature 导出时按笔迹包围盒裁剪留白(getImageData 扫描 alpha),使签名占满导出图,不再淹没在横屏画布上下留白中
- 回显 image 由 aspectFit 改 widthFix 并去掉固定高度,签名占满预览宽度(checkout/consignee-edit/组件内预填预览三处)
- 同步更新 TC-SIG2D-07 断言(传 canvas 节点 + 裁剪逻辑)

商品页需求:
- product-detail hasNutritionInfo 返回 false,整块隐藏营养成分(热量/蛋白质/脂肪/碳水/标签)
asus 2 дней назад
Родитель
Сommit
b42e90e7d7

+ 8 - 2
cfc-frontend/__tests__/pages/signature/signature.canvas2d.test.js

@@ -324,8 +324,14 @@ describe('恢复签名页自动横屏 + canvas 2d 不回归 · signature.canvas2
     expect(jit).not.toMatch(/\.draw\(/)
   })
 
-  test('TC-SIG2D-07 导出传 canvas 节点;清空用 ctx.clearRect(同步)', function () {
-    expect(padScript).toMatch(/canvasToTempFilePath\(\s*\{[^}]*canvas\s*:\s*this\._canvasNode/m)
+  test('TC-SIG2D-07 导出传 canvas 节点(2d);按笔迹包围盒裁剪留白;清空用 ctx.clearRect(同步)', function () {
+    // 重构后:canvas 节点赋给局部变量 canvas,再作为 exportOpts.canvas 传入 canvasToTempFilePath
+    expect(padScript).toMatch(/var canvas = this\._canvasNode/)
+    expect(padScript).toMatch(/canvasToTempFilePath\(\s*exportOpts\s*,\s*this\)/)
+    expect(padScript).toMatch(/canvas\s*:\s*canvas/)
+    // 横屏画布宽而矮:导出前按笔迹包围盒裁剪留白,使签名占满图(getImageData 扫描 alpha)
+    expect(padScript).toMatch(/getImageData\(/)
+    expect(padScript).toMatch(/canvasToTempFilePath/)
     // 清空:ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
     expect(padScript).toMatch(/clearRect\(/)
     expect(padScript).not.toMatch(/clearRect[\s\S]{0,80}ctx\.draw\(\)/)

+ 2 - 1
cfc-frontend/pages/discover-detail/product-detail/product-detail.vue

@@ -414,8 +414,9 @@ export default {
       return list
     },
     // 营养信息区是否展示(无任何营养数据与标签时隐藏)
+    // 产品决策:商品详情页暂不展示营养成分(2026-11-11),整块隐藏。
     hasNutritionInfo: function() {
-      return this.nutritionList.length > 0 || this.nutritionTagList.length > 0
+      return false
     }
   },
   onLoad(options) {

+ 2 - 2
cfc-frontend/pages/shop/checkout/checkout.vue

@@ -131,7 +131,7 @@
         <view v-if="hasHandSignatureField" class="purchase-signature-block">
           <view class="purchase-signature-title">✍️ 手写签名</view>
           <view v-if="purchaseForm.hand_signature" class="sig-preview-box">
-            <image class="signature-preview-img" :src="purchaseForm.hand_signature" mode="aspectFit" />
+            <image class="signature-preview-img" :src="purchaseForm.hand_signature" mode="widthFix" />
           </view>
           <button class="btn-sign-entry" @click="goSignature">{{ purchaseForm.hand_signature ? '重新签名' : '去签名' }}</button>
         </view>
@@ -1820,7 +1820,7 @@ showOnlineSlotPicker: function() {
 }
 .signature-preview-img {
   width: 100%;
-  height: 160rpx;
+  height: auto;
   background: $app-surface;
   border: 2rpx dashed $facade-text-ghost;
   border-radius: 16rpx;

+ 2 - 2
cfc-frontend/pages/shop/consignee-edit/consignee-edit.vue

@@ -66,7 +66,7 @@
         </view>
       </view>
       <view v-if="form.handSignature" class="signature-block sig-preview-block">
-        <image class="signature-preview-img" :src="form.handSignature" mode="aspectFit" />
+        <image class="signature-preview-img" :src="form.handSignature" mode="widthFix" />
       </view>
       <view class="signature-block">
         <button class="btn-sign-entry" @click="goSignature">{{ form.handSignature ? '重新签名' : '去签名' }}</button>
@@ -258,7 +258,7 @@ export default {
 }
 .signature-preview-img {
   width: 100%;
-  height: 160rpx;
+  height: auto;
   background: $app-surface;
   border: 2rpx dashed $facade-text-ghost;
   border-radius: 16rpx;

+ 52 - 4
cfc-frontend/pages/signature/components/hand-signature/hand-signature.vue

@@ -28,7 +28,7 @@
       <image
         v-if="currentDataUrl && !hasContent"
         :src="currentDataUrl"
-        mode="aspectFit"
+        mode="widthFix"
         class="signature-preview"
       />
     </view>
@@ -296,9 +296,48 @@ export default {
         return
       }
       this.savingLive = true
+      var canvas = this._canvasNode
+      var dpr = this.dpr || 1
+      // 计算笔迹包围盒并裁剪留白,使签名占满导出图(横屏画布宽而矮,直接导出会让签名
+      // 淹没在上下留白中、回显时仅占图片一小条)。失败时回退为整画布导出,保证可用性。
+      var crop = null
+      try {
+        var ictx = this.ctx
+        var cw = canvas.width
+        var ch = canvas.height
+        if (ictx && cw > 0 && ch > 0) {
+          var img = ictx.getImageData(0, 0, cw, ch)
+          var px = img.data
+          var minX = cw, minY = ch, maxX = -1, maxY = -1
+          var found = false
+          for (var y = 0; y < ch; y++) {
+            var row = y * cw
+            for (var x = 0; x < cw; x++) {
+              if (px[(row + x) * 4 + 3] > 16) {
+                found = true
+                if (x < minX) minX = x
+                if (x > maxX) maxX = x
+                if (y < minY) minY = y
+                if (y > maxY) maxY = y
+              }
+            }
+          }
+          if (found) {
+            // 设备像素 → CSS 像素(canvasToTempFilePath 的 x/y/width/height 与绘制坐标一致,均为 CSS px)
+            var pad = Math.max(2, Math.round(2 * dpr))
+            var bx = Math.max(0, minX - pad)
+            var by = Math.max(0, minY - pad)
+            var bw = Math.min(cw, maxX + pad) - bx
+            var bh = Math.min(ch, maxY + pad) - by
+            crop = { x: bx / dpr, y: by / dpr, width: bw / dpr, height: bh / dpr }
+          }
+        }
+      } catch (e) {
+        crop = null
+      }
       // 2d 接口传 canvas 节点(不再传 canvasId);第二参数 this 为组件实例
-      uni.canvasToTempFilePath({
-        canvas: this._canvasNode,
+      var exportOpts = {
+        canvas: canvas,
         x: 0,
         y: 0,
         width: this.canvasWidth,
@@ -342,7 +381,16 @@ export default {
           uni.showToast({ title: '签名导出失败,请重试', icon: 'none' })
           self.savingLive = false
         }
-      }, this)
+      }
+      if (crop) {
+        exportOpts.x = crop.x
+        exportOpts.y = crop.y
+        exportOpts.width = crop.width
+        exportOpts.height = crop.height
+        exportOpts.destWidth = crop.width
+        exportOpts.destHeight = crop.height
+      }
+      uni.canvasToTempFilePath(exportOpts, this)
     },
     // 统一回写签名并通知父组件(小程序已转 base64,H5 直接透传 data URL)
     _emitSignature: function(dataUrl) {