|
|
@@ -0,0 +1,113 @@
|
|
|
+# 手签名实现方式重写(竖屏直签 + canvas 2d)任务书
|
|
|
+
|
|
|
+- 需求编号:DOC-2026-11-09-signature-canvas2d-rewrite
|
|
|
+- 需求来源:主理人反馈(小程序端)
|
|
|
+- 状态:待开发
|
|
|
+- **取代**:`docs/superpowers/specs/2026-11-08-signature-landscape-fix.md`(commit `a7edf5698`,横屏适配方案,**整份作废**,勿再按旧任务书实施)
|
|
|
+- 关联需求变更:`docs/需求分析/2026-11-09-手签名实现方式重写需求变更.md`(取代 2026-11-08-手签名横屏适配需求变更)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 一、需求原话(主理人 2026-11-09 更新)
|
|
|
+
|
|
|
+> 换一种手签名的实现方式,目前的方式字迹显示不出来,部分手机还实现不了自动横屏
|
|
|
+
|
|
|
+(注:此前"横屏适配"方向已被主理人推翻——不再依赖横屏;核心诉求变为「**不依赖横屏、无需特殊机型能力,任何手机都能正常手写并看到笔迹**」。)
|
|
|
+
|
|
|
+## 二、现状核查(已确认根因)
|
|
|
+
|
|
|
+涉改文件(均基于 `origin/cfclub` 最新,共 2 个前端文件):
|
|
|
+
|
|
|
+1. `cfc-frontend/pages/signature/signature.vue`(311 行,签名页,`navigationStyle:custom`)
|
|
|
+2. `cfc-frontend/components/hand-signature/hand-signature.vue`(367 行,通用手签名组件,**当前实际仅 signature.vue 渲染**;checkout.vue 仅跳转不内嵌,改动零波及)
|
|
|
+
|
|
|
+### 根因 R1:部分手机无法自动横屏(机型能力差异)
|
|
|
+
|
|
|
+- 现实现依赖 `wx.setPageOrientation({orientation:'landscape'})` 强制横屏(H5 还依赖 `screen.orientation.lock`)。
|
|
|
+- **结论**:`setPageOrientation` 在部分安卓机型/基础库版本下不生效或需手动能力校验,属于**不可靠的平台能力**;「必须横屏才能签名」本身是错误的交互前提,应整体放弃横屏依赖。
|
|
|
+
|
|
|
+### 根因 R2:字迹显示不出来(旧式 canvas API 兼容性差)
|
|
|
+
|
|
|
+- 手签名组件是**全前端唯一仍用旧式 `uni.createCanvasContext`(canvas-id + `ctx.draw()`)的 canvas 组件**;项目内其它 canvas 组件(`RadarChart.vue`、`TrendChart.vue`、`FamilyRelationGraph.vue`、`wuxing-sandbox.vue`、`mbti-result.vue`、`AbilityDiagram.vue` 等 10+ 处)**已全部采用新版 `type="2d"` + `canvas.getContext('2d')` + `fields({node:true})` 接口**。
|
|
|
+- 旧式 `uni.createCanvasContext` 在新基础库上有两个已知不稳定点:
|
|
|
+ 1. 触摸坐标依赖手动缓存 `rect`(`loadRect` 只在 mounted 取一次,页面/布局变化后坐标偏移 → 笔迹画到画布外,**用户感知"字迹显示不出来"**);新版 2d 触摸事件直接提供相对画布的 `touches[0].x/y`,不依赖 rect 缓存。
|
|
|
+ 2. `ctx.draw(true/false)` 异步提交在部分机型存在丢帧/不渲染问题;新版 `ctx.stroke()` 为同步绘制,笔迹实时可见。
|
|
|
+- **对照标准实现(开发必读参考)**:`cfc-frontend/components/RadarChart.vue`——
|
|
|
+ - 初始化:`uni.createSelectorQuery().in(this).select('#'+this.canvasId).fields({node:true,size:true,rect:true}).exec(...)`(注意 `.in(this)` 限定组件作用域;`canvasId` 用 `'xxx_' + (this._uid||0)` 保证多实例唯一);
|
|
|
+ - `fields size` 返回 0(flex 塌陷)时用 `uni.getWindowInfo().windowWidth` 按 rpx 换算兜底;
|
|
|
+ - `canvas.width = width * dpr; canvas.height = height * dpr; ctx.scale(dpr, dpr)`(dpr 取 `uni.getWindowInfo().pixelRatio`);
|
|
|
+ - `getContext('2d')` 返回 null 时走失败回调并重试(不要静默)。
|
|
|
+
|
|
|
+### 波及面确认(改动安全):
|
|
|
+
|
|
|
+- `hand-signature` 组件引用方:grep 全前端仅 `signature.vue` 实际渲染(checkout.vue 只有 `hasHandSignatureField/purchase-signature-block` 字样,不 import 不渲染)→ **组件改动零波及**。
|
|
|
+- 调用方 health-plan-summary.vue / checkout.vue / consignee-edit.vue 均为 navigateTo 跳转 + eventChannel 回传,**不改**。
|
|
|
+- 组件对外契约(props:value/strokeColor/strokeWidth/height/requireAgree/agreeText;emit:confirm/clear)**保持不变**,三调用方无感。
|
|
|
+- 无后端改动。
|
|
|
+
|
|
|
+## 三、需求变更定义
|
|
|
+
|
|
|
+### 变更 S4-1:弃用强制横屏,改竖屏直签(signature.vue)
|
|
|
+
|
|
|
+- 删除 `enterLandscape()` / `restorePortrait()` 两个方法及其在 `onLoad`/`onUnload` 中的调用(连同 H5 降级提示 `h5DegradeHint`、`.rotate-hint` 模板块一并删除)。
|
|
|
+- 页面保持默认竖屏布局(`navigationStyle:custom` 保留),**不再调用 `wx.setPageOrientation` / `screen.orientation.lock`**。
|
|
|
+- `computeCanvasHeight()` 保留竖屏计算:`uni.getWindowInfo().windowHeight`(竖屏=长边,如 812/844)− reserved(requireAgree 时 214,否则 174)→ 画布高度充足(≈600px 级),签名区域大。
|
|
|
+- 页面标题/底部栏/同意声明布局与样式保持(竖屏下 rpx 尺寸正常,无需 px 化)。
|
|
|
+- **删除旧横屏任务书的 S1/S2/S3 全部内容**(窗口尺寸监听重算、rpx→px、refreshRect),不再实施。
|
|
|
+
|
|
|
+### 变更 S4-2:canvas 重写为新版 2d 接口(hand-signature.vue)
|
|
|
+
|
|
|
+模板:
|
|
|
+- `<canvas :id="canvasId" type="2d" class="signature-canvas" :style="'width:100%;height:'+height+'px;'" @touchstart.stop.prevent="onTouchStart" @touchmove.stop.prevent="onTouchMove" @touchend.stop.prevent="onTouchEnd" />`(canvas-id 换成 `:id` + `type="2d"`,参照 RadarChart)。
|
|
|
+
|
|
|
+data 新增:
|
|
|
+- `canvasId: 'hsCanvas_' + (this._uid || 0)`
|
|
|
+- `_canvasNode: null`(2d 节点,导出用)
|
|
|
+- `canvasWidth / canvasHeight / dpr`(绘制与清空用)
|
|
|
+- `_destroyed: false`、`_initTimer: null`(防销毁后继续查询刷错;参照 RadarChart)
|
|
|
+
|
|
|
+初始化(替换现 `loadRect` 逻辑):
|
|
|
+- `mounted`:按 RadarChart 标准执行 `createSelectorQuery().in(this).select('#'+canvasId).fields({node:true,size:true,rect:true}).exec()`;
|
|
|
+- `size` 为 0 且 `rect` 有效时用 `rect.width/rect.height`;两者皆无 → 用 `windowWidth/750` 按 height prop 换算兜底;
|
|
|
+- `canvas.width=width*dpr; canvas.height=height*dpr; ctx=canvas.getContext('2d'); ctx.scale(dpr,dpr)`;
|
|
|
+- 失败路径 console.log + 定时器重试一次(mounted 延迟初始化),`_destroyed` 时停止。
|
|
|
+
|
|
|
+触摸坐标(核心修复,替代旧 `getPos`):
|
|
|
+- **优先** `var t = e.touches && e.touches[0]; var x = (t && t.x) || 0; var y = (t && t.y) || 0`(type=2d 时微信提供相对画布坐标,**不再依赖缓存 rect**);
|
|
|
+- 兜底:`t.x === undefined` 时用 `t.clientX - rect.left / t.clientY - rect.top`(rect 用 fields 返回的 `info.left/top` 存为 `_rectLeft/_rectTop`,比旧实现实时、准确)。
|
|
|
+
|
|
|
+绘制:
|
|
|
+- `onTouchMove`:`ctx.beginPath(); ctx.moveTo(lastX,lastY); ctx.lineTo(x,y); ctx.strokeStyle=this.strokeColor; ctx.lineWidth=this.strokeWidth; ctx.lineCap='round'; ctx.lineJoin='round'; ctx.stroke()`(**同步绘制,无需 `ctx.draw()`**);更新 lastX/lastY、`hasContent=true; hasDrawn=true`。
|
|
|
+
|
|
|
+导出(handleConfirm 改造):
|
|
|
+- 用 `uni.canvasToTempFilePath({ canvas: this._canvasNode, x:0, y:0, width: this.canvasWidth, height: this.canvasHeight, destWidth: this.canvasWidth, destHeight: this.canvasHeight, fileType:'png', quality:1, success..., fail... }, this)`——**2d 接口传 `canvas` 节点**(不再传 canvasId);success 后 H5 直接 pass dataURL、小程序 `fs.readFile` 转 base64 的既有链路**保持不变**。
|
|
|
+- 既有分支:未绘制但有 `currentDataUrl` 预填 → 直接 emit 原签名(保留)。
|
|
|
+
|
|
|
+清空(handleClear 改造):
|
|
|
+- `ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)`(同步,无需 `ctx.draw`);保留 `hasContent/hasDrawn/currentDataUrl` 复位与 `$emit('clear')`。
|
|
|
+
|
|
|
+prefill 回显:
|
|
|
+- **保持现有 image 层叠加方案**(`currentDataUrl && !hasContent` 时 `<image mode="aspectFit">` 覆盖展示、pointer-events-none),不把预填画进 2d canvas;`hasDrawn` 语义沿用(已手绘则盖住预填)。
|
|
|
+
|
|
|
+### 不修改:
|
|
|
+
|
|
|
+- 不改 `signature-bus.js`、`pages.json`、后端;
|
|
|
+- 不改三个调用方跳转回传链路;
|
|
|
+- 不引入任何第三方签名库(保持自研组件 + 项目内标准 2d 写法);
|
|
|
+- 不做横屏适配(S4-1 已弃用)。
|
|
|
+
|
|
|
+## 四、TDD 入池建议
|
|
|
+
|
|
|
+- 能力键:`signature.canvas2d.rewrite`(前端 UI 纵切,无独立后端接口)
|
|
|
+- 验收(开发自验全部通过才可提交):
|
|
|
+ 1. 任意机型(含无法自动横屏的)进入签名页即**竖屏可签**,无任何横屏依赖;
|
|
|
+ 2. 画布竖屏全宽、高度 ≈ windowHeight − 固定栏(≥400px),签名区域充足;
|
|
|
+ 3. **书写笔迹实时可见**(2d 同步绘制),不再出现"画了看不到";
|
|
|
+ 4. 确认导出:小程序转 base64、H5 dataURL,提交后回显正常;
|
|
|
+ 5. 重写/确认/返回按钮行为、同意声明勾选、prefill 回显均不回归;
|
|
|
+ 6. 项目内既有 2d 组件(雷达图/沙盘/趋势图/mbti)不受影响(未触碰)。
|
|
|
+
|
|
|
+## 五、风险与关联记录
|
|
|
+
|
|
|
+- 风险:`type="2d"` 需要微信基础库 ≥2.9.0——项目内 10+ 组件已在用,**无新增兼容风险**;H5 端 `fields node` 与 RadarChart 同路径处理(主目标平台为微信小程序)。
|
|
|
+- 关联:取代 `2026-11-08-signature-landscape-fix.md`(`a7edf5698`);需求变更 `2026-11-09-手签名实现方式重写需求变更.md` 取代 11-08 横屏适配变更。无其它任务书依赖。
|