# 移除光栅卡硬件陀螺仪 · 实施计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 彻底移除光栅卡/全息卡对硬件陀螺仪/加速度计的依赖(含 Android 原生插件),改用触摸拖拽 + 手动滑块双输入,简化 ~28KB 硬件传感器代码、消除 iOS/Android 权限摩擦。 **Architecture:** 在 `useLenticularCraftTiltPreview` 暴露 `simulateTilt(dx, dy)`(度)作为唯一倾斜入口;触摸拖拽通过 `simulateTiltFromNormalized` 转换;新增独立组件 `` 通过卡片内置 `showSlider` prop 透出;v-model:sliderValue 双向绑定。所有硬件相关代码、依赖、权限、原生插件全部删除。 **Tech Stack:** uni-app 3.x + Vue 3 组合式 API + uni-app 原生 `` 组件;H5 Chrome 作为开发期验证平台。 ## Global Constraints - **测试原则**:前端不写 Vitest(memory `share-impl-test-policy.md`),所有验证通过 H5 Chrome 手动 + DevTools console + `console.log` 完成 - **平台目标**:以 app-plus(Android + iOS)为主,H5 同样支持;H5 验证为开发期主线 - **包大小目标**:删除 ≥28KB 硬件代码 + 1 个原生插件目录 - **提交规范**:每个 Task 一个 commit,commit 末尾追加 `Co-Authored-By: Claude ` - **分支**:当前在 `feat/actvity`(或显示为 `feat/len`),**不要** checkout 到 main - **数值约定**(spec §3.5): - 触摸 emit 实际 [-1.1, +1.1],有效 [-1, +1] - 滑块 value: 0..1,中位 0.5 = 0° - `simulateTilt(dx, dy)`: -45..+45 度 - 滑块→度: `dx = (v - 0.5) * 90` - 触摸→度: `dx = x * 45` - **错误处理**(spec §4.2):`simulateTilt` 入口 `Number.isFinite()` 校验;`layersRef.length < 2` 兜底保留 - **Phase 5 grep 校验**(必须 0 命中):`useLenticularStudioTilt` / `useOneEuroFilter` / `imengyu-UniAndroidGyro` / `BODY_SENSORS` / `NSMotionUsageDescription` --- ## File Structure | 文件 | 操作 | 职责 | |------|------|------| | `frontend/composables/useLenticularCraftTiltPreview.js` | 改 | 新增 `simulateTilt*` 入口;最终删除 `useLenticularStudioTilt` 引用 | | `frontend/composables/useLenticularStudioTilt.js` | 删 | 28KB 硬件传感器模块 | | `frontend/composables/useOneEuroFilter.js` | 删 | 仅被 StudioTilt 使用 | | `frontend/composables/useHolographicPreview.js` | 改 | 删 DeviceOrientationEvent 路径;保留 `simulate(x, y)` | | `frontend/components/lenticular/ManualTiltSlider.vue` | 新增 | 独立滑块组件 | | `frontend/components/lenticular/LenticularCard.vue` | 改 | 加 slider 接入 + touchend 不发 (0,0) | | `frontend/components/lenticular/HolographicCard.vue` | 改 | 同上 | | `frontend/components/lenticular/CastloveLenticularPreview.vue` | 改 | 透传 show-slider prop | | `frontend/pages/castlove/lenticular/lenticular-result.vue` | 改 | 加 show-slider + 改 simulate 接收器 + 删 scheduleTiltStart | | `frontend/nativePlugins/imengyu-UniAndroidGyro/` | 删 | 原生插件目录(AAR + iOS Framework) | | `frontend/manifest.json` | 改 | 删 nativePlugins 段 + BODY_SENSORS + NSMotionUsageDescription | --- ## Task 1: 在 useLenticularCraftTiltPreview 新增手动倾斜入口 **Files:** - Modify: `frontend/composables/useLenticularCraftTiltPreview.js:176-213` **Interfaces:** - Consumes: 现有的 `useLenticularStudioTilt`(仍保留运行,本 Task 不动) - Produces: 新增 3 个公共方法 `simulateTilt(dx, dy)` / `simulateTiltFromNormalized(x, y)` / `simulateTiltReset()` - [ ] **Step 1: 在 useLenticularCraftTiltPreview 顶部添加数值常量** 打开 `frontend/composables/useLenticularCraftTiltPreview.js`,在第 12 行附近(`FLOW_PHYSICS` 之前)插入: ```js // 手动模拟倾斜:归一化 → 度数 转换系数(spec §3.5) const NORMALIZED_TO_DEG = 45 ``` - [ ] **Step 2: 在 `useLenticularCraftTiltPreview` 函数体内(`return` 语句之前)新增 3 个方法** 定位到文件末尾 `return { ... }` 之前(大约 line 210),在 `stopTiltPreview` 函数定义之后插入: ```js // ==================================================================== // 手动模拟倾斜入口(spec §3.1) // ==================================================================== /** * 滑块/外部入口:直接传度数(-45..+45) * 内部转给 simulateFromSignedDegrees 走现有离散档位逻辑 */ function simulateTilt(dx, dy) { if (!Number.isFinite(dx) || !Number.isFinite(dy)) { console.warn('[CraftTilt] simulateTilt: non-finite input', dx, dy) return } simulateFromSignedDegrees(dx, dy) } /** * 触摸入口:-1..+1 归一化转度数(系数 45) * 由 LenticularCard.vue / HolographicCard.vue 的 @simulate 事件触发 */ function simulateTiltFromNormalized(x, y) { const dx = x * NORMALIZED_TO_DEG const dy = (y != null ? y : 0) * NORMALIZED_TO_DEG simulateTilt(dx, dy) } /** * 滑块点"复位"时调用:归零 + 清空离散档位状态 */ function simulateTiltReset() { lockPreviewStill() } ``` - [ ] **Step 3: 在 return 对象中暴露这 3 个方法** 修改文件末尾的 `return { ... }`(约 line 215-225),添加这 3 个键: ```js return { physics, layerTransforms, simulate, relax, gyroSourceLabel, scheduleTiltStart, stopTiltPreview, lockPreviewStill, // 新增:手动模拟入口(spec §3.1) simulateTilt, simulateTiltFromNormalized, simulateTiltReset, } ``` - [ ] **Step 4: H5 Chrome 手动验证(在 DevTools console)** 启动 H5 dev server(具体命令见项目 README),打开 `lenticular-result` 页面,浏览器 DevTools console 执行: ```js // 从 Vue 组件实例拿 useLenticularCraftTiltPreview 返回的 simulateTilt //(具体取法:组件实例 proxy 上找,或者在 setup 顶层临时挂到 window 调试) window.__test_simulateTilt?.(45, 0) // 应触发右边硬切 window.__test_simulateTilt?.(0, 0) // 应触发归中 window.__test_simulateTilt?.(NaN, 0) // 应 console.warn 不崩 ``` > 注:如果没有 `window.__test_*` 暴露,可以临时在组件 setup 末尾加 `window.__test_simulateTilt = simulateTilt` 调试,验证完删除。 **预期**: - 前两次调用:卡片视觉有响应(γ → ±1) - 第三次:DevTools console 出现 `[CraftTilt] simulateTilt: non-finite input` 警告,卡片无变化 - [ ] **Step 5: 提交** ```bash git add frontend/composables/useLenticularCraftTiltPreview.js git commit -m "feat(craft-tilt): 新增 simulateTilt 手动模拟入口 - simulateTilt(dx, dy) 单位度,校验 NaN/Infinity - simulateTiltFromNormalized(x, y) 触摸归一化转度(系数 45) - simulateTiltReset() 滑块复位用 Co-Authored-By: Claude " ``` --- ## Task 2: 新增 ManualTiltSlider.vue 独立组件 **Files:** - Create: `frontend/components/lenticular/ManualTiltSlider.vue` **Interfaces:** - Consumes: 无(独立组件) - Produces: `` - [ ] **Step 1: 创建组件文件,写入完整模板和 script** 新建 `frontend/components/lenticular/ManualTiltSlider.vue`: ```vue ``` - [ ] **Step 2: 临时挂载到 H5 测试页面验证** 找一个测试用页面(如 `pages/index/index.vue`)临时挂载: ```vue ``` H5 Chrome 打开该页面,验证: - 拖动滑块 → console 打印 `change 0.42` 之类的数值 - 点击 ↺ 按钮 → console 打印 `reset` - 滑块无法拖到 0..1 范围外(uniapp `` 组件自带 clamp) - [ ] **Step 3: 还原测试挂载** 如果步骤 2 加了临时代码,删除或还原。 - [ ] **Step 4: 提交** ```bash git add frontend/components/lenticular/ManualTiltSlider.vue git commit -m "feat(lenticular): 新增 ManualTiltSlider 独立组件 - uniapp 包装,0..1 双向绑定 - 内置 ↺ 复位按钮 - value validator 拦截 0..1 范围外输入 Co-Authored-By: Claude " ``` --- ## Task 3: LenticularCard.vue 加 slider 接入 + 改 touchend 行为 **Files:** - Modify: `frontend/components/lenticular/LenticularCard.vue:54-63, 209-212` **Interfaces:** - Consumes: Task 2 产出的 `` - Produces: 新增 props `showSlider` / `sliderValue`;新增 emit `update:sliderValue` / `sliderReset`;touchend 不再 emit (0,0) - [ ] **Step 1: 添加 showSlider / sliderValue props** 在 `frontend/components/lenticular/LenticularCard.vue` 的 `defineProps({...})` 块(line 54-63)末尾追加: ```js // 新增:手动滑块接入(spec §3.3) showSlider: { type: Boolean, default: false }, sliderValue: { type: Number, default: 0.5 }, ``` - [ ] **Step 2: 在 defineEmits 中添加 update:sliderValue / sliderReset** 找到 `defineEmits`(如果有)或 `