refactor: 确立独立子工程架构 (backend, admin, mpuniapp) 并同步更新 AGENTS.md 规范
这个提交包含在:
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<!-- #ifdef APP -->
|
||||
<scroll-view style="flex: 1;padding: 6px;">
|
||||
<!-- #endif -->
|
||||
<text class="uni-h2">加速度计</text>
|
||||
<text>开始监听后可实时查看设备在 X、Y、Z 轴上的加速度数据。</text>
|
||||
<text class="margin-v">当前状态:{{ statusText }}</text>
|
||||
|
||||
<text class="uni-h3">采样频率</text>
|
||||
<radio-group class="option-group" @change="handleIntervalChange">
|
||||
<view class="option-item" v-for="item in intervalOptions" :key="item.value">
|
||||
<radio :value="item.value" :checked="activeInterval == item.value">
|
||||
<text class="option-text">{{ item.label }}</text>
|
||||
</radio>
|
||||
</view>
|
||||
</radio-group>
|
||||
<button class="margin-v" type="primary" @tap="startListen">开始监听</button>
|
||||
<button class="margin-v" @tap="stopListen">停止监听</button>
|
||||
<button class="margin-v" @tap="navigateToShake">跳转摇一摇</button>
|
||||
|
||||
<text class="uni-h3">实时数据</text>
|
||||
<view class="log-list">
|
||||
<text class="log-item" v-for="item in axisItems" :key="item.key">{{ item.key }}:{{ item.value }}</text>
|
||||
</view>
|
||||
|
||||
<!-- #ifdef APP -->
|
||||
</scroll-view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
type PageAccelerometerInterval = 'game' | 'ui' | 'normal'
|
||||
|
||||
type IntervalOption = {
|
||||
value : PageAccelerometerInterval
|
||||
label : string
|
||||
}
|
||||
|
||||
type AxisItem = {
|
||||
key : string
|
||||
value : string
|
||||
}
|
||||
|
||||
const intervalOptions : IntervalOption[] = [
|
||||
{ value: 'normal', label: 'normal' },
|
||||
{ value: 'ui', label: 'ui' },
|
||||
{ value: 'game', label: 'game' }
|
||||
]
|
||||
const activeInterval = ref<PageAccelerometerInterval>('normal')
|
||||
const statusText = ref('未启动')
|
||||
const axisX = ref('0.000')
|
||||
const axisY = ref('0.000')
|
||||
const axisZ = ref('0.000')
|
||||
const axisItems = computed((): AxisItem[] => {
|
||||
return [
|
||||
{ key: 'X', value: axisX.value },
|
||||
{ key: 'Y', value: axisY.value },
|
||||
{ key: 'Z', value: axisZ.value }
|
||||
]
|
||||
})
|
||||
|
||||
function formatAxisValue(value : number) : string {
|
||||
return value.toFixed(3)
|
||||
}
|
||||
|
||||
const accelerometerListener : OnAccelerometerChangeCallback = (result : OnAccelerometerChangeCallbackResult) => {
|
||||
axisX.value = formatAxisValue(result.x)
|
||||
axisY.value = formatAxisValue(result.y)
|
||||
axisZ.value = formatAxisValue(result.z)
|
||||
}
|
||||
|
||||
function handleIntervalChange(event : UniRadioGroupChangeEvent) : void {
|
||||
activeInterval.value = event.detail.value as PageAccelerometerInterval
|
||||
}
|
||||
|
||||
function startListen() : void {
|
||||
uni.onAccelerometerChange(accelerometerListener)
|
||||
uni.startAccelerometer({
|
||||
interval: activeInterval.value,
|
||||
success: () => {
|
||||
statusText.value = `监听中 (${activeInterval.value})`
|
||||
},
|
||||
fail: (error) => {
|
||||
statusText.value = error.errMsg != null ? error.errMsg : '启动失败'
|
||||
console.error('startAccelerometer:fail', error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function stopListen() : void {
|
||||
uni.stopAccelerometer({
|
||||
success: () => {
|
||||
statusText.value = '已停止'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function navigateToShake() : void {
|
||||
uni.navigateTo({
|
||||
url: '/pages/API/accelerometer/shake'
|
||||
})
|
||||
}
|
||||
|
||||
onUnload(() => {
|
||||
uni.offAccelerometerChange(accelerometerListener)
|
||||
uni.stopAccelerometer()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.margin-v {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.option-group {
|
||||
margin: 5px 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.option-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-right: 18px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.option-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.log-list {
|
||||
margin: 5px 0;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.log-item {
|
||||
font-size: 14px;
|
||||
margin: 4px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<!-- #ifdef APP -->
|
||||
<scroll-view style="flex:1;padding: 6px;">
|
||||
<!-- #endif -->
|
||||
<text class="uni-h3">摇一摇</text>
|
||||
<text>握住手机并快速摇晃,会调用加速计传感器,依据加速度变化检测动作,并弹出提示。</text>
|
||||
|
||||
<text class="log-text margin-v">{{ statusText }}</text>
|
||||
<view class="uni-row margin-v">
|
||||
<view class="metric-box">
|
||||
<text>瞬时变化</text>
|
||||
<text class="metric-value">{{ deltaText }}</text>
|
||||
</view>
|
||||
<view class="metric-box metric-box-space">
|
||||
<text>触发次数</text>
|
||||
<text class="metric-value">{{ shakeCountText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="sensitivity-header">
|
||||
<text>灵敏度</text>
|
||||
<text class="sensitivity-value">{{ sensitivityLabel }}</text>
|
||||
</view>
|
||||
<slider class="sensitivity-slider" :value="sensitivityValue" :min="1" :max="5" :step="1"
|
||||
@changing="onSensitivityChanging" @change="onSensitivityChange" />
|
||||
<text class="sensitivity-tip">当前触发阈值:{{ thresholdText }},数值越低灵敏度越高,越容易触发。</text>
|
||||
|
||||
<!-- #ifdef APP -->
|
||||
</scroll-view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
const statusText = ref('正在初始化传感器...')
|
||||
const deltaText = ref('0.00')
|
||||
const shakeCountText = ref('0')
|
||||
const thresholdText = ref('1.35')
|
||||
const sensitivityLabel = ref('中')
|
||||
const sensitivityValue = ref(3)
|
||||
|
||||
const shakeCooldown = 1200
|
||||
let lastMagnitude : number | null = null
|
||||
let lastShakeTime = 0
|
||||
let shakeCount = 0
|
||||
let shakeThreshold = 1.35
|
||||
|
||||
function formatNumber(value : number) : string {
|
||||
return value.toFixed(2)
|
||||
}
|
||||
|
||||
function createMagnitude(x : number, y : number, z : number) : number {
|
||||
return Math.sqrt(x * x + y * y + z * z)
|
||||
}
|
||||
|
||||
function getSensitivityLabel(value : number) : string {
|
||||
if (value <= 1) {
|
||||
return '很低'
|
||||
}
|
||||
if (value == 2) {
|
||||
return '较低'
|
||||
}
|
||||
if (value == 3) {
|
||||
return '中'
|
||||
}
|
||||
if (value == 4) {
|
||||
return '较高'
|
||||
}
|
||||
return '很高'
|
||||
}
|
||||
|
||||
function syncSensitivity(value : number) : void {
|
||||
sensitivityValue.value = value
|
||||
shakeThreshold = 2.1 - value * 0.25
|
||||
thresholdText.value = formatNumber(shakeThreshold)
|
||||
sensitivityLabel.value = getSensitivityLabel(value)
|
||||
}
|
||||
|
||||
function triggerShake(now : number) : void {
|
||||
lastShakeTime = now
|
||||
shakeCount = shakeCount + 1
|
||||
shakeCountText.value = `${shakeCount}`
|
||||
statusText.value = '检测成功,继续摇晃可再次触发'
|
||||
uni.showToast({
|
||||
title: '检测到摇一摇',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
const accelerometerListener : OnAccelerometerChangeCallback = (result : OnAccelerometerChangeCallbackResult) => {
|
||||
const magnitude = createMagnitude(result.x, result.y, result.z)
|
||||
if (lastMagnitude == null) {
|
||||
lastMagnitude = magnitude
|
||||
statusText.value = '请开始摇晃手机'
|
||||
return
|
||||
}
|
||||
|
||||
const delta = Math.abs(magnitude - (lastMagnitude as number))
|
||||
deltaText.value = formatNumber(delta)
|
||||
lastMagnitude = magnitude
|
||||
|
||||
const now = Date.now()
|
||||
const passed = now - lastShakeTime
|
||||
if (delta > shakeThreshold && passed > shakeCooldown) {
|
||||
triggerShake(now)
|
||||
}
|
||||
}
|
||||
|
||||
function onSensitivityChanging(event : UniSliderChangeEvent) : void {
|
||||
syncSensitivity(event.detail.value)
|
||||
}
|
||||
|
||||
function onSensitivityChange(event : UniSliderChangeEvent) : void {
|
||||
syncSensitivity(event.detail.value)
|
||||
statusText.value = `当前灵敏度:${sensitivityLabel.value},请继续摇晃手机`
|
||||
}
|
||||
|
||||
function startListen() : void {
|
||||
uni.startAccelerometer({
|
||||
interval: 'game',
|
||||
success: () => {
|
||||
statusText.value = '请开始摇晃手机'
|
||||
},
|
||||
fail: (error) => {
|
||||
statusText.value = error.errMsg != null ? error.errMsg : '传感器启动失败'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function stopListen() : void {
|
||||
uni.stopAccelerometer({
|
||||
success: () => {
|
||||
statusText.value = '已停止监听'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onReady(() => {
|
||||
syncSensitivity(sensitivityValue.value)
|
||||
uni.onAccelerometerChange(accelerometerListener)
|
||||
startListen()
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
startListen()
|
||||
})
|
||||
|
||||
onHide(() => {
|
||||
stopListen()
|
||||
})
|
||||
|
||||
onUnload(() => {
|
||||
uni.offAccelerometerChange(accelerometerListener)
|
||||
uni.stopAccelerometer()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.card-space {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.margin-v {
|
||||
margin: 5px 0;
|
||||
}
|
||||
.log-text {
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.metric-box {
|
||||
flex: 1;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.metric-box-space {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
margin-top: 4px;
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.sensitivity-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sensitivity-slider {
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
在新工单中引用
屏蔽一个用户