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

style(signature): 阅读同意与底部按钮同一行,组件回归纯签名职责

- 将「阅读同意」从 hand-signature 组件上移至 signature.vue 底部栏,与返回/重写/确认按钮并排同行(左侧勾选框+文案,右侧按钮组 margin-left:auto 靠右)
- hand-signature 组件移除 requireAgree/agreeText props、agreed 状态、toggleAgree、agree-change emit 及确认拦截,回归纯签名职责(唯一引用方 signature.vue 无影响)
- signature.vue 接管 agree:新增 agreed 状态与 toggleAgree,onPageConfirm 增加 requireAgree&&!agreed 拦截
- computeCanvasHeight 因阅读同意移出画布区,预留高度统一为 134(不再按 requireAgree 分档)
- 同步更新 TC-SIG2D-02(预留统一 134)与 TC-SIG2D-08(组件已不承载 agree,页脚同行承载)
asus 2 дней назад
Родитель
Сommit
d0913ecb2b

+ 15 - 11
cfc-frontend/__tests__/pages/signature/signature.canvas2d.test.js

@@ -13,12 +13,12 @@
  *     TC-SIG2D-01b 模板不渲染 h5DegradeHint/.rotate-hint(本轮未重新引入)
  *     TC-SIG2D-01c 调 wx.setPageOrientation(landscape + portrait)
  *     TC-SIG2D-01d~01j 行为级:H5 降级不切横屏 / success+complete 重算高度 / 窗口监听成对注册注销 / doBack 恢复竖屏 / onUnload 清理
- *     TC-SIG2D-02  computeCanvasHeight 保留(134/170 两档 + 160 下限)
+ *     TC-SIG2D-02  computeCanvasHeight 预留统一 134(阅读同意已移出画布区)+ 160 下限
  *   S4-2 canvas 2d(hand-signature.vue,不回归)
  *     TC-SIG2D-03~07、01-E、02-E 保持不变
  *     TC-SIG2D-07b height watch 仅在值变化且非 savingLive/销毁时重建(书写中不丢笔迹)
  *   S4-3 不回归
- *     TC-SIG2D-08 组件对外契约 props/emit 不变(value/strokeColor/strokeWidth/height/requireAgree/agreeText + confirm/clear/agree-change)
+ *     TC-SIG2D-08 阅读同意上移至签名页(组件回归纯签名,页脚同行承载)
  *
  * 本文件只读源码并求值 script 段,不修改任何业务代码。
  */
@@ -264,9 +264,9 @@ describe('恢复签名页自动横屏 + canvas 2d 不回归 · signature.canvas2
     expect(seq).toEqual(['offResize', 'orient:portrait'])
   })
 
-  test('TC-SIG2D-02 computeCanvasHeight 保留横屏计算(134/170 两档 + 160 下限)', function () {
+  test('TC-SIG2D-02 computeCanvasHeight 预留统一 134(阅读同意已移出画布区)+ 160 下限', function () {
     expect(makePage(390, false).computeCanvasHeight()).toBe(390 - 134)
-    expect(makePage(390, true).computeCanvasHeight()).toBe(390 - 170)
+    expect(makePage(390, true).computeCanvasHeight()).toBe(390 - 134)
     expect(makePage(200, true).computeCanvasHeight()).toBe(160)
   })
 
@@ -397,18 +397,22 @@ describe('恢复签名页自动横屏 + canvas 2d 不回归 · signature.canvas2
 
   // ---------- S4-3 不回归 ----------
 
-  test('TC-SIG2D-08 组件对外契约不变(props/emit 保持,三调用方无感)', function () {
+  test('TC-SIG2D-08 阅读同意上移至签名页(组件回归纯签名,页脚同行承载)', function () {
     const opts = loadOptions(PAD, { uni: {}, wx: undefined, window: undefined })
     const props = opts.props
     expect(props).toHaveProperty('value')
     expect(props).toHaveProperty('strokeColor')
     expect(props).toHaveProperty('strokeWidth')
     expect(props).toHaveProperty('height')
-    expect(props).toHaveProperty('requireAgree')
-    expect(props).toHaveProperty('agreeText')
-    const src = padScript
-    expect(src).toMatch(/\$emit\(['"]confirm['"]/)
-    expect(src).toMatch(/\$emit\(['"]clear['"]/)
-    expect(src).toMatch(/\$emit\(['"]agree-change['"]/)
+    // 组件不再承载同意声明(已移至签名页底部栏同行)
+    expect(props).not.toHaveProperty('requireAgree')
+    expect(props).not.toHaveProperty('agreeText')
+    expect(padScript).not.toMatch(/agree-change/)
+    expect(padScript).not.toMatch(/toggleAgree/)
+    expect(padScript).not.toMatch(/requireAgree/)
+    // 签名页接管:模板含阅读同意,逻辑含 toggleAgree 与未勾选拦截
+    expect(pageTpl).toMatch(/sig-agree/)
+    expect(pageScript).toMatch(/toggleAgree\s*:/)
+    expect(pageScript).toMatch(/requireAgree\s*&&\s*!this\.agreed/)
   })
 })

+ 1 - 68
cfc-frontend/pages/signature/components/hand-signature/hand-signature.vue

@@ -5,12 +5,9 @@
     strokeColor    string   笔触颜色,默认 '#1a1a1a'
     strokeWidth    number   笔触宽度(逻辑像素),默认 2
     height         number   画布高度(px),默认 200
-    requireAgree   boolean  是否要求先勾选同意声明才能确认签名,默认 false
-    agreeText      string   同意声明文案,默认 '我已阅读并确认以上内容真实、准确'
   Emits:
     confirm      dataUrl: string  画布转 base64 PNG data URL(点击「确认签名」时触发)
     clear                          画布被清空
-    agree-change agreed: boolean   同意勾选状态变化(取消勾选时调用方应作废已确认签名)
   用法:
     <hand-signature @confirm="onSign" @clear="onClear" />
     onSign(url) { this.signatureDataUrl = url }
@@ -35,18 +32,11 @@
         class="signature-preview"
       />
     </view>
-    <!-- 同意声明(requireAgree=true 时显示,未勾选不可确认签名):置于签字框下方,与页面底部操作按钮成组 -->
-    <view v-if="requireAgree" class="signature-agree" @click="toggleAgree">
-      <view class="agree-box" :class="{ 'agree-box-on': agreed }">
-        <text v-if="agreed" class="agree-tick">✓</text>
-      </view>
-      <text class="agree-text">{{ agreeText }}</text>
-    </view>
     <view class="signature-actions">
       <button class="btn-sign-clear" :disabled="savingLive || !hasContent && !currentDataUrl" @click="handleClear">
         {{ savingLive ? '处理中...' : '重写' }}
       </button>
-      <button class="btn-sign-confirm" :disabled="savingLive || !hasContent || (requireAgree && !agreed)" @click="handleConfirm">
+      <button class="btn-sign-confirm" :disabled="savingLive || !hasContent" @click="handleConfirm">
         {{ savingLive ? '处理中...' : '确认签名' }}
       </button>
     </view>
@@ -64,12 +54,9 @@
  *   strokeColor    string   笔触颜色,默认 '#1a1a1a'
  *   strokeWidth    number   笔触宽度(逻辑像素),默认 2
  *   height         number   画布高度(px),默认 200
- *   requireAgree   boolean  是否要求先勾选同意声明才能确认签名,默认 false
- *   agreeText      string   同意声明文案,默认 '我已阅读并确认以上内容真实、准确'
  * Emits:
  *   confirm      dataUrl: string  画布转 base64 PNG data URL(点击「确认签名」时触发)
  *   clear                          画布被清空
- *   agree-change agreed: boolean   同意勾选状态变化(取消勾选时调用方应作废已确认签名)
  * 用法:
  *   <hand-signature @confirm="onSign" @clear="onClear" />
  *   onSign(url) { this.signatureDataUrl = url }
@@ -93,16 +80,6 @@ export default {
       type: Number,
       default: 200
     },
-    /** 是否要求先勾选同意声明才能确认签名 */
-    requireAgree: {
-      type: Boolean,
-      default: false
-    },
-    /** 同意声明文案(requireAgree=true 时生效) */
-    agreeText: {
-      type: String,
-      default: '我已阅读并确认以上内容真实、准确'
-    }
   },
   data: function() {
     return {
@@ -112,7 +89,6 @@ export default {
       lastY: 0,
       hasContent: false,
       hasDrawn: false,
-      agreed: false,
       currentDataUrl: this.value || '',
       savingLive: false,
       /** 当前实例唯一 canvas id(多实例时避免 select('#hsCanvas') 命中错误节点,参照 RadarChart) */
@@ -307,18 +283,9 @@ export default {
     onTouchEnd: function() {
       this.drawing = false
     },
-    // 切换同意勾选;取消勾选时通知父组件作废已确认签名
-    toggleAgree: function() {
-      this.agreed = !this.agreed
-      this.$emit('agree-change', this.agreed)
-    },
     handleConfirm: function() {
       var self = this
       if (this.savingLive) return
-      if (this.requireAgree && !this.agreed) {
-        uni.showToast({ title: '请先勾选同意声明', icon: 'none' })
-        return
-      }
       // 未在画布上绘制、但有预填签名时:直接透传原签名,避免导出空白画布覆盖
       if (!this.hasDrawn && this.currentDataUrl) {
         this.$emit('confirm', this.currentDataUrl)
@@ -403,40 +370,6 @@ export default {
 .hand-signature {
   width: 100%;
 }
-/* 同意声明 */
-.signature-agree {
-  display: flex;
-  align-items: flex-start;
-  margin-top: 12rpx;
-}
-.agree-box {
-  flex: 0 0 16px;
-  width: 16px;
-  height: 16px;
-  margin-right: 12rpx;
-  margin-top: 2rpx;
-  border: 2rpx solid #c8c9cc;
-  border-radius: 8rpx;
-  background: $app-surface;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-.agree-box-on {
-  background: $accent-indigo;
-  border-color: $accent-indigo;
-}
-.agree-tick {
-  font-size: calc(11px * var(--font-scale, 1));
-  color: $app-surface;
-  line-height: 1;
-}
-.agree-text {
-  flex: 1;
-  font-size: calc(12px * var(--font-scale, 1));
-  color: $facade-text-body;
-  line-height: 1.5;
-}
 .signature-canvas-wrap {
   position: relative;
   width: 100%;

+ 75 - 11
cfc-frontend/pages/signature/signature.vue

@@ -11,19 +11,25 @@
       <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 v-if="requireAgree" class="sig-agree" @click="toggleAgree">
+        <view class="agree-box" :class="{ 'agree-box-on': agreed }">
+          <text v-if="agreed" class="agree-tick">✓</text>
+        </view>
+        <text class="agree-text">{{ agreeText }}</text>
+      </view>
+      <view class="sig-btns">
+        <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>
   </view>
 </template>
@@ -52,6 +58,8 @@ export default {
       requireAgree: false,
       // 同意声明文案
       agreeText: '我已阅读并确认以上内容真实、准确',
+      // 是否已勾选同意声明(requireAgree=true 时,未勾选不可确认签名)
+      agreed: false,
       // 画布高度(px,横屏下按短边视口高度计算,旋转后由 refreshCanvasHeight 重算)
       canvasHeight: 200,
       // 窗口尺寸变化监听句柄(onUnload 注销,防泄漏)
@@ -105,8 +113,8 @@ export default {
         win = null
       }
       var vh = (win && win.windowHeight) ? win.windowHeight : 400
-        // 顶部标题 ~66px + 底部操作栏(已缩小) 56px + 同意声明 ~36px(位于签字框下方、计入组件高度) + 留白 12px
-      var reserved = this.requireAgree ? 170 : 134
+      // 同意声明已移至底部操作栏同行(不再占用画布区高度);预留:顶部标题 ~66px + 底部栏 56px + 留白 12px
+      var reserved = 134
       var h = parseInt(vh, 10) - reserved
       return h > 160 ? h : 160
     },
@@ -175,12 +183,21 @@ export default {
         pad.handleClear()
       }
     },
+    // 切换阅读同意勾选
+    toggleAgree: function() {
+      this.agreed = !this.agreed
+    },
     // 页面「确认」:校验后再触发组件导出
     onPageConfirm: function() {
       var pad = this.$refs.signaturePad
       if (!pad) return
+      // 要求先阅读同意时,未勾选拦截
+      if (this.requireAgree && !this.agreed) {
+        uni.showToast({ title: '请先勾选同意声明', icon: 'none' })
+        return
+      }
       // 明确判定组件内既无绘制也无预填签名时拦截,避免导出一张空白图;
-      // 读取不到内部状态时放行(组件自身校验不同意/无绘制场景)
+      // 读取不到内部状态时放行(组件自身校验无绘制场景)
       if (pad.hasContent === false && !pad.currentDataUrl) {
         uni.showToast({ title: '请先完成手写签名', icon: 'none' })
         return
@@ -240,18 +257,65 @@ export default {
   overflow: hidden;
 }
 
-/* 底部固定操作栏(横屏下紧凑:按钮内容宽度、不再占满整行) */
+/* 底部固定操作栏(横屏下紧凑:阅读同意 + 按钮同一行) */
 .sig-footer {
   flex-shrink: 0;
   height: 56px;
   display: flex;
   align-items: center;
-  justify-content: center;
+  justify-content: flex-start;
   gap: 8px;
   padding: 0 12px;
   background: $app-surface;
   box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.06);
 }
+/* 阅读同意(与底部按钮同行,位于左侧) */
+.sig-agree {
+  flex: 1;
+  min-width: 0;
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+}
+.agree-box {
+  flex: 0 0 16px;
+  width: 16px;
+  height: 16px;
+  margin-right: 6px;
+  border: 2rpx solid #c8c9cc;
+  border-radius: 4rpx;
+  background: $app-surface;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.agree-box-on {
+  background: $accent-indigo;
+  border-color: $accent-indigo;
+}
+.agree-tick {
+  font-size: calc(11px * var(--font-scale, 1));
+  color: $app-surface;
+  line-height: 1;
+}
+.agree-text {
+  flex: 1;
+  min-width: 0;
+  font-size: calc(12px * var(--font-scale, 1));
+  color: $facade-text-body;
+  line-height: 1.4;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+/* 按钮组:始终靠右 */
+.sig-btns {
+  flex-shrink: 0;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-left: auto;
+}
 .sig-btn {
   flex: 0 0 auto;
   height: 40px;