31 KiB
移除光栅卡硬件陀螺仪 · 实施计划
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 转换;新增独立组件 <ManualTiltSlider> 通过卡片内置 showSlider prop 透出;v-model:sliderValue 双向绑定。所有硬件相关代码、依赖、权限、原生插件全部删除。
Tech Stack: uni-app 3.x + Vue 3 组合式 API + uni-app 原生 <slider> 组件;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 <noreply@anthropic.com> - 分支:当前在
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 之前)插入:
// 手动模拟倾斜:归一化 → 度数 转换系数(spec §3.5)
const NORMALIZED_TO_DEG = 45
- Step 2: 在
useLenticularCraftTiltPreview函数体内(return语句之前)新增 3 个方法
定位到文件末尾 return { ... } 之前(大约 line 210),在 stopTiltPreview 函数定义之后插入:
// ====================================================================
// 手动模拟倾斜入口(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 个键:
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 执行:
// 从 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: 提交
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 <noreply@anthropic.com>"
Task 2: 新增 ManualTiltSlider.vue 独立组件
Files:
- Create:
frontend/components/lenticular/ManualTiltSlider.vue
Interfaces:
-
Consumes: 无(独立组件)
-
Produces:
<ManualTiltSlider value disabled showReset min max step @change @reset> -
Step 1: 创建组件文件,写入完整模板和 script
新建 frontend/components/lenticular/ManualTiltSlider.vue:
<template>
<view v-if="showReset" class="tilt-slider-row">
<view v-if="showReset" class="tilt-slider-reset" @tap="handleReset">
<text class="tilt-slider-reset-icon">↺</text>
</view>
<slider
class="tilt-slider"
:min="min"
:max="max"
:step="step"
:value="value"
:disabled="disabled"
:activeColor="activeColor"
:backgroundColor="backgroundColor"
:show-value="false"
@change="handleChange"
/>
</view>
</template>
<script setup>
const props = defineProps({
value: { type: Number, default: 0.5, validator: v => v >= 0 && v <= 1 },
disabled: { type: Boolean, default: false },
showReset: { type: Boolean, default: true },
min: { type: Number, default: 0 },
max: { type: Number, default: 1 },
step: { type: Number, default: 0.01 },
activeColor: { type: String, default: '#6E7AFF' },
backgroundColor: { type: String, default: '#E5E7EB' },
})
const emit = defineEmits<{
change: [v: number]
reset: []
}>()
function handleChange(e) {
const v = Number(e.detail.value)
if (!Number.isFinite(v)) {
console.warn('[ManualTiltSlider] non-finite value', e.detail.value)
return
}
if (v < 0 || v > 1) {
console.warn('[ManualTiltSlider] value out of [0,1]', v)
return
}
emit('change', v)
}
function handleReset() {
emit('reset')
}
</script>
<style scoped>
.tilt-slider-row {
display: flex;
align-items: center;
gap: 12rpx;
padding: 8rpx 16rpx;
height: 48rpx;
}
.tilt-slider-reset {
width: 32rpx;
height: 32rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.tilt-slider-reset-icon {
font-size: 28rpx;
color: #6B7280;
}
.tilt-slider {
flex: 1;
min-width: 0;
}
</style>
- Step 2: 临时挂载到 H5 测试页面验证
找一个测试用页面(如 pages/index/index.vue)临时挂载:
<ManualTiltSlider :value="0.5" @change="v => console.log('change', v)" @reset="console.log('reset')" />
H5 Chrome 打开该页面,验证:
-
拖动滑块 → console 打印
change 0.42之类的数值 -
点击 ↺ 按钮 → console 打印
reset -
滑块无法拖到 0..1 范围外(uniapp
<slider>组件自带 clamp) -
Step 3: 还原测试挂载
如果步骤 2 加了临时代码,删除或还原。
- Step 4: 提交
git add frontend/components/lenticular/ManualTiltSlider.vue
git commit -m "feat(lenticular): 新增 ManualTiltSlider 独立组件
- uniapp <slider> 包装,0..1 双向绑定
- 内置 ↺ 复位按钮
- value validator 拦截 0..1 范围外输入
Co-Authored-By: Claude <noreply@anthropic.com>"
Task 3: LenticularCard.vue 加 slider 接入 + 改 touchend 行为
Files:
- Modify:
frontend/components/lenticular/LenticularCard.vue:54-63, 209-212
Interfaces:
-
Consumes: Task 2 产出的
<ManualTiltSlider> -
Produces: 新增 props
showSlider/sliderValue;新增 emitupdate:sliderValue/sliderReset;touchend 不再 emit (0,0) -
Step 1: 添加 showSlider / sliderValue props
在 frontend/components/lenticular/LenticularCard.vue 的 defineProps({...}) 块(line 54-63)末尾追加:
// 新增:手动滑块接入(spec §3.3)
showSlider: { type: Boolean, default: false },
sliderValue: { type: Number, default: 0.5 },
- Step 2: 在 defineEmits 中添加 update:sliderValue / sliderReset
找到 defineEmits(如果有)或 <script setup> 顶部,添加:
const emit = defineEmits(['simulate', 'update:sliderValue', 'sliderReset'])
注:如果 LenticularCard.vue 原本没有
defineEmits,需要新建一行。
- Step 3: 导入并使用 ManualTiltSlider
在 <script setup> 顶部 import 区添加:
import ManualTiltSlider from './ManualTiltSlider.vue'
在模板中(</view> 结束卡片框架之后),追加:
<ManualTiltSlider
v-if="showSlider"
:value="sliderValue"
@change="v => emit('update:sliderValue', v)"
@reset="emit('sliderReset')"
/>
- Step 4: 修复 touchend emit (0,0) bug
修改 frontend/components/lenticular/LenticularCard.vue 的 onFrameTouchEnd 函数(line 209-212):
function onFrameTouchEnd() {
if (props.skipBuiltInTouch) return
// 修改前:emit('simulate', 0, 0) ← 与滑块数据驱动冲突
// 修改后:触摸结束不再强制发 (0,0),由调用方决定
// (spec §4.1 修复方案)
}
- Step 5: H5 Chrome 验证
打开任意挂载 <LenticularCard> 的页面(H5 dev server),DevTools 验证:
// 1. touchend 后卡片不强制回中
// 操作:拖动卡片到右边 → 松开 → 卡片应保持右边位置
// 2. v-model:sliderValue 双向绑定
// 临时挂载(dev):<LenticularCard :show-slider="true" v-model:sliderValue="..." />
// 操作:拖动滑块 → 卡片视觉跟随
- Step 6: 提交
git add frontend/components/lenticular/LenticularCard.vue
git commit -m "feat(lenticular-card): 接入 ManualTiltSlider + 修 touchend 行为
- 新增 showSlider / sliderValue props
- 新增 update:sliderValue / sliderReset emits(v-model 配套)
- 触摸结束不再 emit (0,0),与滑块数据驱动相容
Co-Authored-By: Claude <noreply@anthropic.com>"
Task 4: HolographicCard.vue 同步 Task 3 改动
Files:
- Modify:
frontend/components/lenticular/HolographicCard.vue:43-67, 129-132
Interfaces: 与 Task 3 相同的 prop / emit 接口
- Step 1: 添加 props
在 HolographicCard.vue 的 defineProps({...}) 块(line 43-67)末尾追加:
// 新增:手动滑块接入(与 LenticularCard 对齐)
showSlider: { type: Boolean, default: false },
sliderValue: { type: Number, default: 0.5 },
- Step 2: 在 defineEmits 中添加 update:sliderValue / sliderReset
修改 const emit = defineEmits([...])(line 67):
const emit = defineEmits(['simulate', 'update:sliderValue', 'sliderReset', 'ready', 'error', 'fpsUpdate'])
- Step 3: 导入 ManualTiltSlider 并在模板中渲染
<script setup> 顶部添加:
import ManualTiltSlider from './ManualTiltSlider.vue'
模板中(</view> 结束 holo-frame 之后)添加:
<ManualTiltSlider
v-if="showSlider"
:value="sliderValue"
@change="v => emit('update:sliderValue', v)"
@reset="emit('sliderReset')"
/>
- Step 4: 修复 touchend emit (0,0) bug
修改 onTouchEnd 函数(line 129-132):
function onTouchEnd() {
if (props.skipBuiltInTouch) return
// 修改前:emit('simulate', 0, 0)
// 修改后:触摸结束不再发 (0,0)
}
- Step 5: 提交
git add frontend/components/lenticular/HolographicCard.vue
git commit -m "feat(holographic-card): 接入 ManualTiltSlider + 修 touchend 行为
- 与 LenticularCard 对齐的 showSlider / sliderValue / v-model 接口
- 触摸结束不再 emit (0,0)
Co-Authored-By: Claude <noreply@anthropic.com>"
Task 5: CastloveLenticularPreview.vue 加 show-slider prop 透传
Files:
- Modify:
frontend/components/lenticular/CastloveLenticularPreview.vue:4-14
Interfaces:
-
Consumes: Task 3 产出的
<LenticularCard :show-slider :slider-value> -
Produces: 透传 prop 让调用方控制
-
Step 1: 添加 showSlider prop
修改 CastloveLenticularPreview.vue 的 defineProps({...}) 块(line 22-28),在 imageSrc 后追加:
showSlider: { type: Boolean, default: false },
- Step 2: 透传到 LenticularCard
修改模板(line 4-12):
<LenticularCard
:layers="lenticularLayers"
:transforms="layerTransforms"
gyro-source="simulation"
:approximate-preview="true"
tilt-hint-text="滑动卡片预览光栅效果"
:show-slider="showSlider"
@simulate="simulate"
/>
- Step 3: 提交
git add frontend/components/lenticular/CastloveLenticularPreview.vue
git commit -m "feat(castlove-preview): 透传 showSlider prop
Co-Authored-By: Claude <noreply@anthropic.com>"
Task 6: lenticular-result.vue 切换到手动模式
Files:
- Modify:
frontend/pages/castlove/lenticular/lenticular-result.vue:31-34, 66, 102-109
Interfaces:
- Consumes: Task 1 产出的
simulateTilt / simulateTiltFromNormalized / simulateTiltReset;Task 3 产出的卡片 v-model 接口 - Produces: 移除
scheduleTiltStart/stopTiltPreview调用;加 show-slider;触摸 ↔ 滑块 双向同步(用户最新澄清要求)
关键设计:触摸 ↔ 滑块 双向同步
-
触摸 x ∈ [-1, +1] → 滑块 value ∈ [0, 1]:
slider = (x + 1) / 2(clamp 到 [0,1]) -
滑块 value ∈ [0, 1] → 触摸角度:滑块拖动时
simulateTilt((v - 0.5) * 90, 0),让卡片视觉跟到那个角度 -
双向都通过
tiltValue这个 ref 作为 single source of truth -
Step 1: 从 useLenticularCraftTiltPreview 解构 simulateTilt / simulateTiltFromNormalized / simulateTiltReset
修改 useLenticularCraftTiltPreview 解构(line 102-109):
const {
layerTransforms,
simulateTilt,
simulateTiltFromNormalized,
simulateTiltReset,
} = useLenticularCraftTiltPreview(lenticularLayers)
- Step 2: 添加 tiltValue 状态(用于 v-model:sliderValue,触摸/滑块 single source of truth)
在 lenticularLayers ref 声明(line 102)之后,添加:
const tiltValue = ref(0.5)
- Step 3: 添加 3 个事件处理函数(触摸/滑块双向同步)
在 tiltValue ref 声明后,添加 3 个函数:
/**
* 触摸 → 滑块 + 引擎
* 把触摸 x 投影到滑块 0..1(用户最新要求:手滑时滑块也动)
*/
function onTouchSimulate(x, y) {
const sliderPos = Math.max(0, Math.min(1, (x + 1) / 2))
tiltValue.value = sliderPos
simulateTiltFromNormalized(x, y)
}
/**
* 滑块 → 引擎(tiltValue 已被 v-model 自动更新,这里只驱动引擎)
* 用户最新要求:滑块滑动,相关的手滑/卡片也到那个角度
*/
function onSliderChange(v) {
const dx = (v - 0.5) * 90
simulateTilt(dx, 0)
}
/**
* 复位按钮:滑块回 0.5 + 引擎回中
*/
function onSliderReset() {
tiltValue.value = 0.5
simulateTiltReset()
}
- Step 4: 改 LenticularCard 标签(接 3 个新事件处理)
修改 <LenticularCard> 标签(line 31-34):
<LenticularCard v-if="lenticularLayers.length > 0" class="craft-lenticular-card"
:layers="lenticularLayers" :transforms="layerTransforms"
:show-slider="true" v-model:slider-value="tiltValue"
tilt-hint-text="晃动或滑动查看" :shimmer-mid-opacity="0.16"
@simulate="onTouchSimulate"
@update:slider-value="onSliderChange"
@slider-reset="onSliderReset" />
注意改动点:
-
删
:skip-built-in-touch="true"(让触摸事件生效) -
加
:show-slider="true" -
加
v-model:slider-value="tiltValue"(双向) -
@simulate改为onTouchSimulate(同步触摸 → 滑块 + 引擎) -
加
@update:slider-value="onSliderChange"(同步滑块 → 引擎) -
加
@slider-reset="onSliderReset"(复位) -
改
tilt-hint-text文案 -
Step 5: 删除 scheduleTiltStart / stopTiltPreview / lockPreviewStill 引用
搜索整个 lenticular-result.vue:
grep -n "scheduleTiltStart\|stopTiltPreview\|gyroSourceLabel" frontend/pages/castlove/lenticular/lenticular-result.vue
预期命中 3-4 处(line 107、713、769、774、778 等)。逐处删除:
-
解构中的
gyroSourceLabel / scheduleTiltStart / stopTiltPreview已通过 Step 1 覆盖 -
函数调用处的
scheduleTiltStart();和stopTiltPreview();直接删除 -
模板中如有
{{ gyroSourceLabel }}引用也删除 -
Step 6: H5 Chrome 端到端验证(重点验证双向同步)
H5 dev server 打开 lenticular-result 页面:
- 卡片正常显示
- 手滑 → 滑块跟动:拖动卡片到最右 → 滑块应自动滑到右边(接近 1.0)
- 手滑 → 滑块跟动(中间):拖动卡片到中心 → 滑块应回到 0.5
- 手滑 → 卡片:拖动卡片到最左 → 滑块到 0、卡片到最左
- 滑块 → 卡片:拖动滑块到 0.7 → 卡片光栅应到对应位置
- 松开不强制回中:手指松开后滑块保持在拖到的位置(不回 0.5)
- 复位:点击 ↺ → 滑块回 0.5,卡片回中间
- DevTools console 无 error
- Step 7: 提交
git add frontend/pages/castlove/lenticular/lenticular-result.vue
git commit -m "feat(lenticular-result): 切换为手动倾斜模式 + 触摸滑块双向同步
- @simulate → onTouchSimulate:触摸 x 投影到滑块 + 驱动引擎
- @update:slider-value → onSliderChange:滑块拖动驱动引擎
- @slider-reset → onSliderReset:复位滑块+引擎
- v-model:slider-value 双向
- 删 3 处 scheduleTiltStart / stopTiltPreview / gyroSourceLabel 引用
- 触摸结束不强制回中
Co-Authored-By: Claude <noreply@anthropic.com>"
Task 7: 删原生插件目录 imengyu-UniAndroidGyro
Files:
-
Delete:
frontend/nativePlugins/imengyu-UniAndroidGyro/(含 android/ ios/ package.json .DS_Store) -
Step 1: 先确认目录无外部引用
grep -rn "imengyu" frontend/ 2>/dev/null
预期:仅 manifest.json(line 31、33、35)和 useLenticularStudioTilt.js(line 183、194)有引用。前者将在 Task 8 清理,后者将在 Task 9 清理。
- Step 2: 备份确认(防止误删)
ls -la frontend/nativePlugins/imengyu-UniAndroidGyro/
预期:
.DS_Store
android/
ios/
package.json
确认是 imengyu 的陀螺仪插件目录。
- Step 3: 删除目录
rm -rf frontend/nativePlugins/imengyu-UniAndroidGyro/
- Step 4: 验证删除
ls frontend/nativePlugins/ 2>/dev/null
预期:为空目录,或目录已被 uniapp 工具链清理。
[ -d frontend/nativePlugins/imengyu-UniAndroidGyro ] && echo "STILL EXISTS" || echo "DELETED OK"
预期:DELETED OK
- Step 5: 提交
git add -A frontend/nativePlugins/
git commit -m "chore(native-plugin): 移除 imengyu-UniAndroidGyro 陀螺仪插件
- 28KB AAR + iOS Framework + package.json 全部删除
- 与 useLenticularStudioTilt.js 同步(Task 9 跟进)
Co-Authored-By: Claude <noreply@anthropic.com>"
Task 8: 改 manifest.json(删 3 段)
Files:
-
Modify:
frontend/manifest.json:30-44, 69, 82 -
Step 1: 删除 nativePlugins.imengyu-UniAndroidGyro 段(line 30-44)
打开 frontend/manifest.json,删除以下整段(注意包含外层逗号):
"nativePlugins" : {
"imengyu-UniAndroidGyro" : {
"__plugin_info__" : {
"name" : "imengyu-UniAndroidGyro",
"description" : "APP端陀螺仪数据采集",
"platforms" : "Android,iOS",
"url" : "",
"android_package_name" : "",
"ios_bundle_id" : "",
"isCloud" : false,
"bought" : -1,
"pid" : "",
"parameters" : {}
}
}
},
/* 应用发布信息 */
改为只剩:
/* 应用发布信息 */
- Step 2: 删除 BODY_SENSORS 权限(line 69)
删除这一行:
"<uses-permission android:name=\"android.permission.BODY_SENSORS\"/>"
注意末尾的逗号:删除后需确认上一行末尾没有多余的逗号(如果原文件 BODY_SENSORS 已经是最后一行,删除后前一行末尾的逗号也要去掉)。
- Step 3: 删除 NSMotionUsageDescription(line 82)
删除整行:
"NSMotionUsageDescription" : "我们需要访问运动传感器以实现光栅卡倾斜预览功能"
注意逗号:如果前一行没有逗号则不需处理;如果删除后 privacyDescription 块为空,需保留 {}。
- Step 4: 验证 JSON 合法
node -e "JSON.parse(require('fs').readFileSync('frontend/manifest.json', 'utf8'))" && echo "JSON OK"
预期:JSON OK
- Step 5: grep 验证
grep -n "imengyu\|BODY_SENSORS\|NSMotionUsageDescription" frontend/manifest.json
预期:0 命中
- Step 6: 提交
git add frontend/manifest.json
git commit -m "chore(manifest): 移除 imengyu 插件注册与传感器权限
- 删 nativePlugins.imengyu-UniAndroidGyro 段(30-44 行)
- 删 BODY_SENSORS 权限(69 行)
- 删 NSMotionUsageDescription 隐私描述(82 行)
Co-Authored-By: Claude <noreply@anthropic.com>"
Task 9: 删 useLenticularStudioTilt.js + useOneEuroFilter.js + 清理 CraftTiltPreview 引用
Files:
- Delete:
frontend/composables/useLenticularStudioTilt.js - Delete:
frontend/composables/useOneEuroFilter.js - Modify:
frontend/composables/useLenticularCraftTiltPreview.js:12-13, 176-185, 215-225
Interfaces:
-
Consumes: Task 1 留下的 simulateTilt* 入口
-
Produces: 不再依赖硬件传感器路径
-
Step 1: 确认 useLenticularStudioTilt 仅 1 处引用
grep -rn "useLenticularStudioTilt" frontend/ 2>/dev/null
预期:仅 useLenticularCraftTiltPreview.js 命中 1 处。
- Step 2: 确认 useOneEuroFilter 仅 1 处引用
grep -rn "useOneEuroFilter" frontend/ 2>/dev/null
预期:仅 useLenticularStudioTilt.js 命中 1 处(即将删除,所以最终 0 命中)。
- Step 3: 从 useLenticularCraftTiltPreview 移除 useLenticularStudioTilt 引用
打开 frontend/composables/useLenticularCraftTiltPreview.js,删除以下 2 处:
- 顶部 import(约 line 12-13):
import { useLenticularStudioTilt } from '@/composables/useLenticularStudioTilt.js'
- 函数体内(约 line 176-185):
const { start: startTilt, stop: stopTilt } = useLenticularStudioTilt({
simulate,
simulateFromSignedDegrees,
gyroSourceLabel,
onTiltDriverFallback: () => {
lastLayerIdx = -1
dirSign = 0
dirOppositeStreak = 0
},
})
以及相关状态变量(let entryTiltTimer = null 和 cancelScheduledTiltStart / scheduleTiltStart / stopTiltPreview 三个函数定义)。
最终 return 对象同步清理(去掉 scheduleTiltStart / stopTiltPreview / gyroSourceLabel):
return {
physics,
layerTransforms,
simulate,
relax,
lockPreviewStill,
// 新增:手动模拟入口
simulateTilt,
simulateTiltFromNormalized,
simulateTiltReset,
}
- Step 4: 删除 useLenticularStudioTilt.js 和 useOneEuroFilter.js
rm frontend/composables/useLenticularStudioTilt.js frontend/composables/useOneEuroFilter.js
- Step 5: 验证无残留
grep -rn "useLenticularStudioTilt\|useOneEuroFilter" frontend/ 2>/dev/null
预期:0 命中
- Step 6: H5 Chrome 验证 lenticular-result 仍正常
打开 lenticular-result 页面,验证:
-
卡片正常显示
-
触摸 + 滑块都工作
-
DevTools console 无
useLenticularStudioTilt is not defined错误 -
Step 7: 提交
git add frontend/composables/
git commit -m "refactor(craft-tilt): 删除硬件传感器模块
- 删 useLenticularStudioTilt.js (28KB, 894 行)
- 删 useOneEuroFilter.js (75 行)
- useLenticularCraftTiltPreview 移除对二者的引用
- 保留手动模拟入口 simulateTilt / simulateTiltFromNormalized / simulateTiltReset
Co-Authored-By: Claude <noreply@anthropic.com>"
Task 10: 清理 useHolographicPreview.js DeviceOrientationEvent 路径
Files:
- Modify:
frontend/composables/useHolographicPreview.js:7, 26-31, 44-96, 102-105
Interfaces:
-
Consumes: 现有
simulate(x, y) -
Produces: 删除所有传感器路径,
simulate(x, y)成为唯一驱动入口 -
Step 1: 删除 import 中的 onUnmounted(如果不再使用)
打开 frontend/composables/useHolographicPreview.js:
第 7 行:
import { ref, reactive, onMounted, onUnmounted } from 'vue'
改为:
import { ref, reactive } from 'vue'
- Step 2: 删除传感器相关局部状态
删除 line 26-31:
let accelHandler = null
let accelSmoothed = 0
let accelBaselineReady = false
let accelBaseX = 0
let accelBaseY = 0
- Step 3: 删除 startGyro / startDeviceOrientation / stopGyro 函数(line 44-96)
整段删除:
function startGyro() { ... }
function startDeviceOrientation() { ... }
function stopGyro() { ... }
- Step 4: 简化 onMounted 钩子(如果保留)
如果 onMounted 不再需要任何操作,删除整个 onMounted(() => { ... }) 块(line 102-105)。否则保留空函数体。
- Step 5: 清理 onUnmounted
删除 onUnmounted(() => { stopGyro() })(line 105)。
- Step 6: 从 return 对象中删除 startGyro / stopGyro / gyroSource
修改末尾 return { ... }(line 107-113),删除 gyroSource, startGyro, stopGyro 三项。保留 physics, viewAngle, isWebGLReady, hasWebGLError, fps, simulate, relax, onWebGLReady, onWebGLError, onFPSUpdate, detectPerformanceTier, HOLO_PERFORMANCE_PRESETS。
- Step 7: 验证无残留
grep -n "DeviceOrientation\|requestPermission\|accelHandler\|startGyro\|stopGyro\|gyroSource" frontend/composables/useHolographicPreview.js
预期:0 命中
- Step 8: H5 Chrome 验证全息卡
如有任何页面挂载 <HolographicCard>(grep 验证),H5 dev server 打开该页面:
-
卡片正常显示
-
触摸拖拽有效
-
DevTools console 无
DeviceOrientationEvent is not defined之类的错误 -
Step 9: 提交
git add frontend/composables/useHolographicPreview.js
git commit -m "refactor(holographic): 移除 DeviceOrientationEvent 路径
- 删 startGyro / startDeviceOrientation / stopGyro
- 删 accelHandler / accelBaselineReady 等基线追踪状态
- simulate(x, y) 成为唯一驱动入口
- 保留 onMounted / onUnmounted 钩子(如果外部有需要可保留,否则删)
Co-Authored-By: Claude <noreply@anthropic.com>"
Task 11: 终验 — grep 校验清单 + 验收清单
Files: 无(验证性任务)
- Step 1: 全量 grep 校验(spec 附录)
grep -rn "useLenticularStudioTilt" frontend/ 2>/dev/null
grep -rn "useOneEuroFilter" frontend/ 2>/dev/null
grep -rn "imengyu-UniAndroidGyro" frontend/ 2>/dev/null
grep -rn "imengyu" frontend/ 2>/dev/null
grep -rn "BODY_SENSORS" frontend/ 2>/dev/null
grep -rn "NSMotionUsageDescription" frontend/ 2>/dev/null
预期:全部 0 命中
- Step 2: H5 Chrome 完整验收清单
启动 H5 dev server,打开 lenticular-result 页面,按 spec §4.3 验收清单逐项过:
- 打开页面,卡片显示正常
- 触摸拖拽:手指在卡片上左右拖动,卡片光栅效果跟随
- 松开不回中:触摸结束卡片不强制回中(验证 4.1 修复)
- 滑块拖动:拖动滑块 0 → 1,卡片光栅从最左切到最右
- 复位按钮:点击 ↺,滑块回 0.5,卡片回中间
- 组合:拖动滑块到 0.3,然后触摸卡片到最右 → 触摸胜出
lenticular-create/lenticular-thinking页面无回归(无报错)
- Step 3: 移动端构建检查(如有真机)
# 触发 HBuilderX 重新打包 App
# 真机扫描:
adb logcat | grep -iE "sensors|gyroscope|accelerometer" | head -20
# 预期:无 SENSOR_SERVICE / BODY_SENSORS 申请日志
iOS 真机:扫描 console.app 或 Xcode device log,不应出现 NSMotionUsageDescription 弹窗。
- Step 4: 提交验收报告(如需要)
验收不通过则 revert 对应 Task;通过则无需新 commit,结束于前一个 Task。
执行建议
按 Task 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 → 10 → 11 顺序执行。前 6 个 Task 是新增/修改(每步可独立 revert),Task 7-10 是清理(不可逆但可整体 revert),Task 11 是终验。
预计开发 2-3 小时,QA 1 天。