Explorar el Código

feat(signature): 内嵌手写签名改为全屏横屏独立签名页 + eventChannel 回传(WI-1~WI-5 + checkout 第三处)

- 新增 pages/signature/signature.vue:全屏横屏签名页,onLoad 切横屏/onUnload 恢复,底部重写/确认/返回,经 eventChannel emit('signatureConfirm') 回传 base64,含空白签名拦截
- 新增 utils/signature-bus.js:初始签名回显(module 级 prefill,onLoad consume 后清空);requireAgree/agreeText 走 URL 参数
- pages.json 注册 pages/signature 分包(navigationStyle custom + navigationBarTitleText 签名)
- health-plan-summary.vue / consignee-edit.vue / checkout.vue:移除内嵌 hand-signature,改预览图 + 去签名/重新签名按钮 → navigateTo 签名页,事件写回 signatureDataUrl / form.handSignature / purchaseForm.hand_signature
- 达成 AC5.3:全局再无业务页内嵌 hand-signature(hand-signature.vue 只读复用,未改动)
Agnes Agent hace 5 días
padre
commit
e35ca71864

+ 12 - 0
cfc-frontend/pages.json

@@ -2033,6 +2033,18 @@
           }
         }
       ]
+    },
+    {
+      "root": "pages/signature",
+      "pages": [
+        {
+          "path": "signature",
+          "style": {
+            "navigationBarTitleText": "签名",
+            "navigationStyle": "custom"
+          }
+        }
+      ]
     }
   ],
   "globalStyle": {

+ 46 - 21
cfc-frontend/pages/health/health-plan-summary.vue

@@ -98,18 +98,13 @@
         </view>
       </view>
 
-      <!-- 手写签名 -->
+      <!-- 手写签名:独立横屏签名页签署(去签名 → navigateTo → eventChannel 回传) -->
       <view class="signature-title">✍️ 手写签名确认</view>
       <view class="hint-text">签名即表示您确认上述问卷信息填写准确、真实有效</view>
-      <hand-signature
-        ref="signaturePad"
-        :value="signatureDataUrl"
-        :require-agree="true"
-        agree-text="我已阅读并确认上述问卷信息真实、准确,且由本人填写"
-        @confirm="onSignatureConfirm"
-        @clear="onSignatureClear"
-        @agree-change="onSignatureAgreeChange"
-      />
+      <view v-if="signatureDataUrl" class="signature-preview-box">
+        <image class="signature-preview-img" :src="signatureDataUrl" mode="widthFix" />
+      </view>
+      <button class="btn-sign-entry" @click="goSignature">{{ signatureDataUrl ? '重新签名' : '去签名' }}</button>
 
       <view class="btn-pair">
         <button class="btn-confirm full-btn" :disabled="!signatureDataUrl || loading" @click="confirmAndGenerate">{{ loading ? '生成中...' : '确认签名并生成方案' }}</button>
@@ -235,13 +230,13 @@ import { generateHealthPlan, regenerateSection, confirmPlan, saveHealthPlan, get
 import { getHealthStatus, saveHealthStatus, getDietPreferences, saveDietPreferences, getMyMembership, saveHealthSignature } from '../../utils/api'
 import MemberChipPicker from '@/pages/health/components/MemberChipPicker.vue'
 import ComprehensiveSurvey from '@/pages/health/components/ComprehensiveSurvey.vue'
-import HandSignature from '@/components/hand-signature/hand-signature.vue'
+import { setSignaturePrefill } from '@/utils/signature-bus.js'
 import { normalizeBowelForm } from '@/utils/bowel-form.js'
 import fontScaleMixin from '@/mixins/font-scale.js'
 
 export default {
   mixins: [fontScaleMixin],
-  components: { MemberChipPicker, ComprehensiveSurvey, HandSignature },
+  components: { MemberChipPicker, ComprehensiveSurvey },
   data() {
     return {
       // 步骤控制
@@ -884,10 +879,6 @@ selectedMembers: function() {
       var v = s && s.signedAt
       return v ? this.formatTime(v) : ''
     },
-    // 取消勾选同意 → 作废已确认签名
-    onSignatureAgreeChange: function(agreed) {
-      if (!agreed) this.signatureDataUrl = ''
-    },
     // 该成员是否有问卷/饮食数据可供确认
     hasMemberData: function(memberId) {
       var s = this.memberFormMap[String(memberId)]
@@ -896,11 +887,18 @@ selectedMembers: function() {
       var hasDiet = d && (this.hasDietContent(d) || d.noPreference)
       return !!(hasForm || hasDiet)
     },
-    onSignatureConfirm: function(dataUrl) {
-      this.signatureDataUrl = dataUrl || ''
-    },
-    onSignatureClear: function() {
-      this.signatureDataUrl = ''
+    // 跳转独立横屏签名页;base64 过长经 signature-bus 传初始值,结果经 eventChannel 回传
+    goSignature: function() {
+      var self = this
+      setSignaturePrefill(this.signatureDataUrl || '')
+      uni.navigateTo({
+        url: '/pages/signature/signature?requireAgree=1&agreeText=' + encodeURIComponent('我已阅读并确认上述问卷信息真实、准确,且由本人填写'),
+        events: {
+          signatureConfirm: function(dataUrl) {
+            self.signatureDataUrl = dataUrl || ''
+          }
+        }
+      })
     },
     // 确认签名并生成方案:先把签名逐成员落库,再生成
     confirmAndGenerate: function() {
@@ -1938,6 +1936,33 @@ selectedMembers: function() {
   margin-top: $uni-space-lg;
   margin-bottom: 10rpx;
 }
+/* Step 3 独立签名页入口:已签预览 + 去签名按钮 */
+.signature-preview-box {
+  margin-bottom: 16rpx;
+  border: 2rpx dashed $facade-text-ghost;
+  border-radius: 16rpx;
+  background: $app-surface;
+  overflow: hidden;
+}
+.signature-preview-img {
+  width: 100%;
+  display: block;
+}
+.btn-sign-entry {
+  width: 100%;
+  height: 88rpx;
+  line-height: 88rpx;
+  padding: 0;
+  background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
+  color: $app-surface;
+  border: none;
+  border-radius: 24rpx;
+  font-size: calc(30rpx * var(--font-scale, 1));
+  text-align: center;
+}
+.btn-sign-entry::after {
+  border: none;
+}
 .member-load-hint {
   font-size: calc(22rpx * var(--font-scale, 1));
   color: $brand-primary;

+ 45 - 25
cfc-frontend/pages/shop/checkout/checkout.vue

@@ -127,17 +127,13 @@
           <text v-if="field.prefillValue" class="purchase-field-prefill">已填写</text>
         </view>
 
-        <!-- 手签名购买信息:手写签名组件,签名数据 base64 直接存 purchaseForm -->
+        <!-- 手签名购买信息:独立横屏签名页签署,base64 经 eventChannel 回传存 purchaseForm -->
         <view v-if="hasHandSignatureField" class="purchase-signature-block">
           <view class="purchase-signature-title">✍️ 手写签名</view>
-          <hand-signature
-            :value="purchaseForm.hand_signature"
-            :require-agree="true"
-            agree-text="我已阅读并同意,确认以上填写信息真实、准确,且由本人签署"
-            @confirm="onHandSignatureConfirm"
-            @clear="onHandSignatureClear"
-            @agree-change="onHandSignatureAgreeChange"
-          />
+          <view v-if="purchaseForm.hand_signature" class="sig-preview-box">
+            <image class="signature-preview-img" :src="purchaseForm.hand_signature" mode="aspectFit" />
+          </view>
+          <button class="btn-sign-entry" @click="goSignature">{{ purchaseForm.hand_signature ? '重新签名' : '去签名' }}</button>
         </view>
       </view>
 
@@ -486,15 +482,14 @@ import { BrandColors } from '@/utils/brand-colors.js'
 import config from '@/config.js'
 import { getCouponList, getCouponCheckoutList, addressList, getProductRequiredFields, getMyMembership, getGiftMembershipProgress, productDetail, getFamilyPlatformBalance, exchangeCouponByCf, shopDiscountCalculate, getSpecialPriceInfo, applyCoupon } from '../../../utils/api.js'
 import { parseDate } from '@/utils/format.js'
+import { setSignaturePrefill } from '@/utils/signature-bus.js'
 import CountdownTag from '@/pages/shop/components/countdown-tag.vue'
-import HandSignature from '@/components/hand-signature/hand-signature.vue'
 import fontScaleMixin from '@/mixins/font-scale.js'
 
 export default {
   mixins: [fontScaleMixin],
   components: {
-    CountdownTag,
-    HandSignature
+    CountdownTag
   },
   data() {
     return {
@@ -950,19 +945,19 @@ export default {
       }
       this.purchaseForm = form
     },
-    onHandSignatureConfirm(dataUrl) {
-      var form = this.purchaseForm
-      form.hand_signature = dataUrl || ''
-      this.purchaseForm = form
-    },
-    onHandSignatureClear() {
-      var form = this.purchaseForm
-      form.hand_signature = ''
-      this.purchaseForm = form
-    },
-    onHandSignatureAgreeChange(agreed) {
-      // 取消勾选同意 → 作废已确认签名,需重新签署
-      if (!agreed) this.onHandSignatureClear()
+    // 跳转独立横屏签名页;base64 过长经 signature-bus 传初始值,结果经 eventChannel 回传
+    goSignature() {
+      setSignaturePrefill(this.purchaseForm.hand_signature || '')
+      uni.navigateTo({
+        url: '/pages/signature/signature?requireAgree=1&agreeText=' + encodeURIComponent('我已阅读并同意,确认以上填写信息真实、准确,且由本人签署'),
+        events: {
+          signatureConfirm: (dataUrl) => {
+            var form = this.purchaseForm
+            form.hand_signature = dataUrl || ''
+            this.purchaseForm = form
+          }
+        }
+      })
     },
     openAddressPicker() {
       if (this.addressList.length === 0) {
@@ -1820,6 +1815,31 @@ showOnlineSlotPicker: function() {
   font-weight: bold;
   margin-bottom: 12rpx;
 }
+.sig-preview-box {
+  margin-bottom: 16rpx;
+}
+.signature-preview-img {
+  width: 100%;
+  height: 160rpx;
+  background: $app-surface;
+  border: 2rpx dashed $facade-text-ghost;
+  border-radius: 16rpx;
+}
+.btn-sign-entry {
+  width: 100%;
+  height: 80rpx;
+  line-height: 80rpx;
+  padding: 0;
+  background: linear-gradient(135deg, $accent-indigo, $accent-indigo-deep);
+  color: $app-surface;
+  border: none;
+  border-radius: 24rpx;
+  font-size: calc(28rpx * var(--font-scale, 1));
+  text-align: center;
+}
+.btn-sign-entry::after {
+  border: none;
+}
 
 /* 配送方式选择 */
 .delivery-sub-label {

+ 44 - 22
cfc-frontend/pages/shop/consignee-edit/consignee-edit.vue

@@ -57,23 +57,19 @@
         </picker>
       </view>
 
-      <!-- 手签名 -->
+      <!-- 手签名:独立横屏签名页(去签名 → navigateTo → eventChannel 回传;选填语义不变) -->
       <view class="form-row form-row-signature">
         <text class="form-label">手签名</text>
         <view class="signature-cell">
           <text v-if="form.handSignature" class="signature-set">已设置手签名</text>
-          <text v-else class="placeholder">请在下方手写签名(选填)</text>
+          <text v-else class="placeholder">选填,点击「去签名」手写</text>
         </view>
       </view>
+      <view v-if="form.handSignature" class="signature-block sig-preview-block">
+        <image class="signature-preview-img" :src="form.handSignature" mode="aspectFit" />
+      </view>
       <view class="signature-block">
-        <hand-signature
-          :value="form.handSignature"
-          :require-agree="true"
-          agree-text="我已阅读并同意,本人确认以上信息真实、准确"
-          @confirm="onHandSignatureConfirm"
-          @clear="onHandSignatureClear"
-          @agree-change="onHandSignatureAgreeChange"
-        />
+        <button class="btn-sign-entry" @click="goSignature">{{ form.handSignature ? '重新签名' : '去签名' }}</button>
       </view>
 
       <!-- 设为默认 -->
@@ -97,12 +93,11 @@
 <script>
 import { BrandColors } from '@/utils/brand-colors.js'
 import { consigneeDetail, consigneeSave } from '../../../utils/api.js'
-import HandSignature from '@/components/hand-signature/hand-signature.vue'
+import { setSignaturePrefill } from '@/utils/signature-bus.js'
 import fontScaleMixin from '@/mixins/font-scale.js'
 
 export default {
   mixins: [fontScaleMixin],
-  components: { HandSignature },
   data() {
     return {
       switchColor: BrandColors.indigoPeriwinkle,
@@ -150,19 +145,21 @@ export default {
     onBloodTypeChange(e) {
       this.form.bloodType = this.bloodTypes[e.detail.value]
     },
-    onHandSignatureConfirm(dataUrl) {
-      this.form.handSignature = dataUrl || ''
-    },
-    onHandSignatureClear() {
-      this.form.handSignature = ''
-    },
-    onHandSignatureAgreeChange(agreed) {
-      // 取消勾选同意 → 作废已确认签名
-      if (!agreed) this.form.handSignature = ''
-    },
     onDefaultChange(e) {
       this.form.isDefault = e.detail.value ? 1 : 0
     },
+    // 跳转独立横屏签名页;base64 过长经 signature-bus 传初始值,结果经 eventChannel 回传
+    goSignature() {
+      setSignaturePrefill(this.form.handSignature || '')
+      uni.navigateTo({
+        url: '/pages/signature/signature?requireAgree=1&agreeText=' + encodeURIComponent('我已阅读并同意,本人确认以上信息真实、准确'),
+        events: {
+          signatureConfirm: (dataUrl) => {
+            this.form.handSignature = dataUrl || ''
+          }
+        }
+      })
+    },
     async handleSave() {
       if (!this.form.name || !this.form.name.trim()) {
         uni.showToast({ title: '请输入收货人姓名', icon: 'none' })
@@ -256,6 +253,31 @@ export default {
 .signature-block {
   padding: 0 0 24rpx;
 }
+.sig-preview-block {
+  margin-bottom: 8rpx;
+}
+.signature-preview-img {
+  width: 100%;
+  height: 160rpx;
+  background: $app-surface;
+  border: 2rpx dashed $facade-text-ghost;
+  border-radius: 16rpx;
+}
+.btn-sign-entry {
+  width: 100%;
+  height: 80rpx;
+  line-height: 80rpx;
+  padding: 0;
+  background: linear-gradient(135deg, $accent-indigo, $accent-indigo-deep);
+  color: $app-surface;
+  border: none;
+  border-radius: 24rpx;
+  font-size: calc(28rpx * var(--font-scale, 1));
+  text-align: center;
+}
+.btn-sign-entry::after {
+  border: none;
+}
 .save-btn {
   width: 100%;
   margin-top: 40rpx;

+ 312 - 0
cfc-frontend/pages/signature/signature.vue

@@ -0,0 +1,312 @@
+<template>
+  <view class="signature-page">
+    <!-- H5 降级提示:不支持 screen.orientation.lock 时提示横置设备(不报错) -->
+    <view v-if="h5DegradeHint" class="rotate-hint">
+      <text class="rotate-hint-text">🔄 请横置设备以获得最佳签名体验</text>
+    </view>
+
+    <!-- 顶部标题 -->
+    <view class="sig-header">
+      <text class="sig-title">✍️ 手写签名</text>
+      <text class="sig-subtitle">请在下方空白区域书写签名,完成后点击「确认」</text>
+    </view>
+
+    <!-- 画布区(横屏铺满,高度按视口短边计算) -->
+    <view class="sig-canvas-area">
+      <hand-signature
+        ref="signaturePad"
+        :value="prefill"
+        :require-agree="requireAgree"
+        :agree-text="agreeText"
+        :height="canvasHeight"
+        @confirm="onConfirm"
+        @clear="onClear"
+      />
+    </view>
+
+    <!-- 底部固定操作栏:重写 / 确认 / 返回 -->
+    <view class="sig-footer">
+      <button class="sig-btn sig-btn-back" @click="onBack">返回</button>
+      <button class="sig-btn sig-btn-clear" @click="onRewrite">重写</button>
+      <button class="sig-btn sig-btn-confirm" @click="onPageConfirm">确认</button>
+    </view>
+  </view>
+</template>
+
+<script>
+/*
+ * 独立横屏全屏签名页(WI-1/WI-2)
+ * - 复用通用 HandSignature 组件(只读复用,不改组件本身)
+ * - 入参:prefill 经 utils/signature-bus.js 模块级写入(base64 过长不进 URL);
+ *         requireAgree / agreeText 走 URL 参数
+ * - 横屏:微信 wx.setPageOrientation({orientation:'landscape'});
+ *         H5 screen.orientation.lock('landscape') 包裹 try/catch,不支持时显示「请横置设备」提示
+ * - 回传:onConfirm 经 getOpenerEventChannel().emit('signatureConfirm', dataUrl) 后 navigateBack;
+ *         返回按钮直接 navigateBack(不回传)
+ */
+import HandSignature from '@/components/hand-signature/hand-signature.vue'
+import { consumeSignaturePrefill } from '@/utils/signature-bus.js'
+
+export default {
+  components: { HandSignature },
+  data() {
+    return {
+      // 预填签名(base64 data URL,编辑/重新签名场景回显)
+      prefill: '',
+      // 是否要求先勾选同意声明
+      requireAgree: false,
+      // 同意声明文案
+      agreeText: '我已阅读并确认以上内容真实、准确',
+      // 画布高度(px,按横屏视口高度计算)
+      canvasHeight: 200,
+      // H5 无法锁横屏时降级提示
+      h5DegradeHint: false,
+      // 防止重复 navigateBack
+      _backed: false
+    }
+  },
+  onLoad: function(options) {
+    options = options || {}
+    // 读取签名页预填值(读后即清空,避免残留串扰)
+    this.prefill = consumeSignaturePrefill()
+    // URL 参数:requireAgree=1 / agreeText(中文已编码,运行时会自动解码,此处兜底解码一次)
+    this.requireAgree = options.requireAgree === '1' || options.requireAgree === 1
+    if (options.agreeText) {
+      var text = this.decodeURIComponentSafe(String(options.agreeText))
+      if (text) this.agreeText = text
+    }
+    this.canvasHeight = this.computeCanvasHeight()
+    this.enterLandscape()
+  },
+  onUnload: function() {
+    // 离开签名页恢复竖屏,不污染其它页面
+    this.restorePortrait()
+  },
+  methods: {
+    // 兼容运行时未解码/已解码两种情况(含特殊字符时兜底不抛错)
+    decodeURIComponentSafe: function(text) {
+      var out = text
+      try {
+        out = decodeURIComponent(text)
+      } catch (e) {
+        out = text
+      }
+      return out
+    },
+    // 横屏视口高度(短边)减去顶部标题/底部操作栏/同意声明占位,得到画布可书写高度
+    computeCanvasHeight: function() {
+      var win = null
+      try {
+        if (typeof uni.getWindowInfo === 'function') {
+          win = uni.getWindowInfo()
+        }
+      } catch (e) {
+        win = null
+      }
+      var vh = (win && win.windowHeight) ? win.windowHeight : 400
+      // 顶部标题 ~66px + 底部操作栏 92px + 同意声明 ~40px + 留白 16px
+      var reserved = this.requireAgree ? 214 : 174
+      var h = parseInt(vh, 10) - reserved
+      return h > 160 ? h : 160
+    },
+    // 进入签名页:切横屏(平台分流,条件编译)
+    enterLandscape: function() {
+      // #ifdef MP-WEIXIN
+      try {
+        if (typeof wx !== 'undefined' && typeof wx.setPageOrientation === 'function') {
+          wx.setPageOrientation({
+            orientation: 'landscape'
+          })
+        }
+      } catch (e) {}
+      // #endif
+      // #ifdef H5
+      try {
+        var so = (typeof window !== 'undefined' && window.screen) ? window.screen.orientation : null
+        if (so && typeof so.lock === 'function') {
+          var p = so.lock('landscape')
+          if (p && typeof p.catch === 'function') {
+            var self = this
+            p.catch(function() {
+              // 如非全屏/浏览器不支持 → 降级提示横置设备,不报错
+              self.h5DegradeHint = true
+            })
+          }
+        } else {
+          this.h5DegradeHint = true
+        }
+      } catch (e) {
+        this.h5DegradeHint = true
+      }
+      // #endif
+    },
+    // 离开签名页:恢复竖屏
+    restorePortrait: function() {
+      // #ifdef MP-WEIXIN
+      try {
+        if (typeof wx !== 'undefined' && typeof wx.setPageOrientation === 'function') {
+          wx.setPageOrientation({
+            orientation: 'portrait'
+          })
+        }
+      } catch (e) {}
+      // #endif
+      // #ifdef H5
+      try {
+        var so = (typeof window !== 'undefined' && window.screen) ? window.screen.orientation : null
+        if (so && typeof so.unlock === 'function') {
+          so.unlock()
+        }
+      } catch (e) {}
+      // #endif
+    },
+    // HandSignature 确认导出成功(内部「确认签名」按钮已被隐藏,由页面「确认」驱动 handleConfirm 走到这里)
+    onConfirm: function(dataUrl) {
+      var channel = this.getOpenerEventChannel ? this.getOpenerEventChannel() : null
+      if (channel && typeof channel.emit === 'function') {
+        channel.emit('signatureConfirm', dataUrl || '')
+      }
+      this.doBack()
+    },
+    onClear: function() {
+      // 画布被清空(重写触发):无额外处理
+    },
+    // 页面「重写」:调用组件清空画布
+    onRewrite: function() {
+      var pad = this.$refs.signaturePad
+      if (pad && typeof pad.handleClear === 'function') {
+        pad.handleClear()
+      }
+    },
+    // 页面「确认」:校验后再触发组件导出
+    onPageConfirm: function() {
+      var pad = this.$refs.signaturePad
+      if (!pad) return
+      // 明确判定组件内既无绘制也无预填签名时拦截,避免导出一张空白图;
+      // 读取不到内部状态时放行(组件自身校验不同意/无绘制场景)
+      if (pad.hasContent === false && !pad.currentDataUrl) {
+        uni.showToast({ title: '请先完成手写签名', icon: 'none' })
+        return
+      }
+      if (typeof pad.handleConfirm === 'function') {
+        pad.handleConfirm()
+      }
+    },
+    // 页面「返回」:不回传直接退出
+    onBack: function() {
+      this.doBack()
+    },
+    doBack: function() {
+      if (this._backed) return
+      this._backed = true
+      uni.navigateBack()
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.signature-page {
+  width: 100%;
+  height: 100vh;
+  background: $app-bg;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+/* H5 降级提示(绝对定位,不占布局高度) */
+.rotate-hint {
+  position: absolute;
+  top: 12px;
+  left: 12px;
+  right: 12px;
+  z-index: 20;
+  padding: 10px 16px;
+  background: $brand-primary-soft;
+  border: 2rpx solid $brand-primary;
+  border-radius: 16rpx;
+  text-align: center;
+}
+.rotate-hint-text {
+  font-size: calc(26rpx * var(--font-scale, 1));
+  color: $brand-primary;
+}
+
+/* 顶部标题 */
+.sig-header {
+  flex-shrink: 0;
+  padding: 16px 16px 6px;
+}
+.sig-title {
+  display: block;
+  font-size: calc(32rpx * var(--font-scale, 1));
+  font-weight: bold;
+  color: $facade-text-strong;
+}
+.sig-subtitle {
+  display: block;
+  margin-top: 4px;
+  font-size: calc(24rpx * var(--font-scale, 1));
+  color: $facade-text-muted;
+}
+
+/* 画布区:占满剩余空间 */
+.sig-canvas-area {
+  flex: 1;
+  min-height: 0;
+  padding: 0 16px;
+  overflow: hidden;
+}
+
+/* 底部固定操作栏 */
+.sig-footer {
+  flex-shrink: 0;
+  height: 92px;
+  display: flex;
+  align-items: center;
+  padding: 0 12px;
+  background: $app-surface;
+  box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.06);
+}
+.sig-btn {
+  flex: 1;
+  height: 72px;
+  line-height: 72px;
+  margin: 0 6px;
+  padding: 0;
+  border: none;
+  border-radius: 24rpx;
+  font-size: calc(30rpx * var(--font-scale, 1));
+  text-align: center;
+}
+.sig-btn::after {
+  border: none;
+}
+.sig-btn-back {
+  background: $app-surface;
+  color: $facade-text-body;
+  border: 2rpx solid $facade-fill-2;
+}
+.sig-btn-clear {
+  background: $facade-fill-2;
+  color: $facade-text-strong;
+}
+.sig-btn-confirm {
+  background: linear-gradient(135deg, $accent-indigo 0%, $accent-indigo-deep 100%);
+  color: $app-surface;
+}
+</style>
+
+<!--
+  非 scoped 样式:穿透 HandSignature 内部,隐藏其自带的「重写 / 确认签名」按钮行,
+  改由签名页底部固定操作栏统一承载(满足「底部操作栏:重写/确认/返回」需求)。
+  可行性依据:uni-app 编译的自定义组件默认 addGlobalClass=true(等价 styleIsolation:
+  apply-shared),页面样式可作用到组件内部;H5 无样式隔离,故以 .signature-page
+  祖先限定作用域避免影响其它页面。
+-->
+<style lang="scss">
+.signature-page .hand-signature .signature-actions {
+  display: none;
+}
+</style>

+ 36 - 0
cfc-frontend/utils/signature-bus.js

@@ -0,0 +1,36 @@
+// utils/signature-bus.js
+// 签名页预填值(base64 PNG data URL)的模块级临时存取。
+//
+// 背景:签名图片 base64 data URL 过长,不适合通过 navigateTo URL 传参;
+//      调用方跳转签名页前用 setSignaturePrefill() 写入,
+//      签名页 onLoad 用 consumeSignaturePrefill() 读取(读取后立即清空),
+//      避免残留值串扰下一次签名(编辑场景点「重新签名」时回显原签名)。
+// 本模块仅承载「跳转前写入 → 签名页读取」的一次性传递,跨页面数据仍走 eventChannel 回传。
+
+var signaturePrefill = ''
+
+/**
+ * 写入待签名页消费的预填值(base64 data URL,可空字符串)
+ * @param {string} value
+ */
+export function setSignaturePrefill(value) {
+  signaturePrefill = value || ''
+}
+
+/**
+ * 读取当前预填值(不消费)
+ * @returns {string}
+ */
+export function getSignaturePrefill() {
+  return signaturePrefill
+}
+
+/**
+ * 读取并清空预填值(签名页 onLoad 专用:保证一次性消费,避免下次进入回显旧签名)
+ * @returns {string}
+ */
+export function consumeSignaturePrefill() {
+  var v = signaturePrefill
+  signaturePrefill = ''
+  return v
+}