```vue
<!-- 轮盘（一级圆盘 + 二级左半弧滚动盘）-->
<template>
	<view class="draw-all">
		<!-- 摄像头 -->
		<view class="carmera">
			<image class="all-size" src="/static/index/watch/camera.png" />
		</view>

		<!-- 轮子背景 -->
		<view class="roll">
			<image class="all-size" src="/static/index/watch/panzi.png" />
		</view>

		<!-- 一级转盘：完整圆盘，可旋转、自动校正、选中最左侧 -->
		<view ref="compass" class="compass-container" @touchstart.stop.prevent="onTouchStart"
			@touchmove.prevent.stop="onTouchMove" @touchend.stop.prevent="onTouchEnd"
			@touchcancel.stop.prevent="onTouchEnd" :style="wrapperStyle">
			<view v-for="(item, i) in items" :key="i" class="compass-item" :style="itemStyle(item.baseAngle)">
				<text :class="i===target?`item-label-target`:  `item-label`" :style="labelStyle">
					<view
						style="z-index: 2;display: flex;flex-direction: column;justify-content: center;align-items: center;">
						<image style="width: 50rpx;height: 50rpx;margin-bottom: 0rpx;"
							:src="`/static/index/watch/Wheel/${i}${i===target?1:0}.png`" />
						<view :style="i===target?{color:'#fff'}:{}">
							{{ item.label }}
						</view>
					</view>
					<image class="targetimge" src="/static/index/watch/bluetarget.png"
						:style="{opacity:i===target?1:0}" />
				</text>
			</view>
		</view>

		<!-- 二级转盘：只占左半球（90°~270°），竖向滚动、有边界、吸附、最多 5 个 -->
		<view v-if="items2.length" ref="compass2" class="compass-container second"
			@touchstart.stop.prevent="onTouchStart2" @touchmove.prevent.stop="onTouchMove2"
			@touchend.stop.prevent="onTouchEnd2" @touchcancel.stop.prevent="onTouchEnd2" :style="wrapperStyle2"
			v-show="target">
			<view v-for="(item, i) in items2" :key="i" class="compass-item" :style="itemStyle2(item.baseAngle)">
				<text :class="i===target2?`item-label-second-target`:  `item-label-second`" :style="labelStyle2">
					<view
						style="z-index: 2;display: flex;flex-direction: column;justify-content: center;align-items: center;">
						<image style="width: 70rpx;height: 70rpx;margin-bottom: 0rpx;"
							:src="`/static/index/watch/Wheel/${target}${i}${i===target2?1:0}.png`" />
						<view v-show="target!==-1" :style="i===target2?{color:'#0E86EA'}:{}">
							{{ item.label }}
						</view>
					</view>
				</text>
			</view>
		</view>
		<!-- <view class="" v-show="target===5">
			<joystick @movecard="" :movebottom="44" :moveleft="-5" :pao="false" :notext="true" />
		</view> -->

		<!-- 一级手势蒙层 -->
		<view v-if="dragging==='first'" class="gesture-mask" @touchmove.stop.prevent="onTouchMove"
			@touchend.stop.prevent="onTouchEnd" @touchcancel.stop.prevent="onTouchEnd" />
		<!-- 二级手势蒙层 -->
		<view v-if="dragging==='second'" class="gesture-mask" @touchmove.stop.prevent="onTouchMove2"
			@touchend.stop.prevent="onTouchEnd2" @touchcancel.stop.prevent="onTouchEnd2" />
	</view>
</template>

<script setup lang="ts">
	import { ref, reactive, onMounted, computed, nextTick, watch, onBeforeUnmount } from 'vue'
	// import joystick from '@/component/public/newgame/joysticknew.vue';

	const emit = defineEmits([])
	const props = defineProps({})

	/* ===================== 一级：完整圆盘 ===================== */
	// 10 个预设方向文字
	const labels = ['开机', '静音', '对讲', '截屏', '录制', '方位', '清晰度', '分屏', '翻转', '告警']
	const count = labels.length

	// 一级：每个文字对应的初始角度（360 等分）
	const items = reactive(
		labels.map((label, idx) => ({
			label,
			baseAngle: (360 / count) * idx
		}))
	)
	// 恢复定时器（最简单的实现）
	let restoreTimer : number | null = null
	let restoreTimer2 : number | null = null
	const RESTORE_MS = 300 // 0.3s
	// 当前累积旋转角度（一级）
	const currentAngle = ref(0)
	let startAngle = 0
	const dragging = ref<null | 'first' | 'second'>(null)

	let idleTimer : number | null = null
	function armIdleFinish(handler : () => void, ms = 160) {
		if (idleTimer) clearTimeout(idleTimer)
		idleTimer = setTimeout(() => {
			if (dragging.value) handler()
		}, ms) as unknown as number
	}
	function clearIdle() {
		if (idleTimer) {
			clearTimeout(idleTimer)
			idleTimer = null
		}
	}
	// 圆心坐标（用于一级 atan2）
	const center = reactive({ x: 0, y: 0 })

	// 获取圆心（以一级容器为基准）
	onMounted(async () => {
		await nextTick()
		uni
			.createSelectorQuery()
			.select('.compass-container')
			.boundingClientRect(rect => {
				if (rect) {
					center.x = rect.left + rect.width / 2
					center.y = rect.top + rect.height / 2
				}
			})
			.exec()
	})

	// 容器样式（一级）
	const transitioning = ref(false)
	const wrapperStyle = computed(() => ({
		transform: `rotate(${currentAngle.value}deg)`,
		transition: transitioning.value ? 'transform 0.3s ease-out' : 'none'
	}))

	// 单项样式（一级）
	function itemStyle(baseAngle : number) {
		const radius = 190
		const rad = (baseAngle * Math.PI) / 180
		const x = radius * Math.cos(rad)
		const y = radius * Math.sin(rad)
		return { transform: `translate(${x}px, ${y}px)` }
	}

	// 标签正立（一级）
	const labelStyle = computed(() => ({
		transform: `rotate(${-currentAngle.value}deg)`
	}))

	// atan2 角度（一级）
	function getTouchAngle(e : TouchEvent) {
		const t = e.touches[0]
		const dx = t.clientX - center.x
		const dy = t.clientY - center.y
		return ((Math.atan2(dy, dx) * 180) / Math.PI) * 2 // 灵敏度×2
	}
	function clearRestoreTimer() {
		if (restoreTimer !== null) {
			clearTimeout(restoreTimer)
			restoreTimer = null
		}
	}
	// 选中最左侧索引（一级）
	const target = ref(5)
	const saveindex = ref(-1)
	function startRestoreTimer() {
	  clearTimeout(restoreTimer)
	  restoreTimer = setTimeout(() => {
	    if (target.value === -1) {
	      target.value = saveindex.value
	    }
	  }, 300)
	}
	function startRestoreTimer2() {
	  clearTimeout(restoreTimer2)
	  restoreTimer2 = setTimeout(() => {
	    if (target2.value === -1) {
	      target2.value = saveindex2.value
	    }
	  }, 300)
	}
	function onTouchStart(e : TouchEvent) {
		saveindex.value = target.value
		target.value = -1
		transitioning.value = false
		startAngle = getTouchAngle(e) - currentAngle.value
		dragging.value = 'first'
		startRestoreTimer()
	}
	function onTouchMove(e : TouchEvent) {
		const angle = getTouchAngle(e)
		currentAngle.value = angle - startAngle
		armIdleFinish(onTouchEnd)
		// 重启恢复定时器：0.3s 内没有新的 move 就恢复 saveindex
		startRestoreTimer()
	}
	function getLeftmostIndex() {
		let minDiff = Infinity
		let idx = 0
		items.forEach((item, i) => {
			let real = (item.baseAngle + currentAngle.value) % 360
			if (real < 0) real += 360
			const diff = Math.abs(real - 180)
			if (diff < minDiff) {
				minDiff = diff
				idx = i
			}
		})

		return idx
	}
	function onTouchEnd() {
		// 结束时先清定时器，避免同时恢复和吸附冲突
		clearRestoreTimer()

		const step = 360 / count
		const raw = currentAngle.value
		const nearest = Math.round(raw / step) * step
		transitioning.value = true
		currentAngle.value = nearest
		const leftIndex = getLeftmostIndex()
		target.value = leftIndex
		// console.log("???", target.value)
		setTimeout(() => (transitioning.value = false), 300)
		dragging.value = null
	}
	onBeforeUnmount(() => {
		if (restoreTimer !== null) clearTimeout(restoreTimer)
		if (restoreTimer2 !== null) clearTimeout(restoreTimer2)
		clearIdle()
	})
	/* ===================== 二级：左半弧竖向滚动盘 ===================== */
	// 二级映射（每组最多 5 个）
	const secondMap : Record<number, string[]> = {
		0: [],
		1: [],
		2: [],
		3: [],
		4: [],
		5: [],
		6: ['超清', '流畅', '自动'],
		7: ['180°全景', '四分屏', '360°全景', '全景拉伸', '原图'],
		8: ['上下翻转', '关闭', '左右翻转'],
		9: []
	}

	// 二级数据与状态
	const items2 = reactive<{ label : string; baseAngle : number }[]>([])
	const target2 = ref(0)

	// 二级：半弧滚动偏移角（只允许在 [-Δ, +Δ]）
	const currentOffset2 = ref(0)
	const step2 = ref(0) // Δ = 180 / (n + 1)
	const transitioning2 = ref(false)

	// 竖向滚动灵敏度（度/像素），可按设备调节
	const DEG_PER_PX = 0.5

	// 容器样式（二级：整体绕圆心微旋转 currentOffset2）
	const wrapperStyle2 = computed(() => ({
		transform: `rotate(${currentOffset2.value}deg)`,
		transition: transitioning2.value ? 'transform 0.25s ease-out' : 'none'
	}))

	// 标签正立（二级）
	const labelStyle2 = computed(() => ({
		transform: `rotate(${-currentOffset2.value}deg)`
	}))

	// 单项样式（二级：布局在左半弧 90°~270°）
	function itemStyle2(baseAngle : number) {
		const radius = 240
		const rad = (baseAngle * Math.PI) / 180
		const x = radius * Math.cos(rad)
		const y = radius * Math.sin(rad)
		return { transform: `translate(${x}px, ${y}px)` }
	}

	// 固定角度位置（左半球从上到下）
	const presetAngles = [135, 157.5, 180, 202.5, 225]

	// 优先顺序：中间 → 上 → 下 → 上上 → 下下
	function getBalancedAngles(n : number) : number[] {
		const order = [2, 1, 3, 0, 4]
		return order.slice(0, n).map(i => presetAngles[i])
	}
	const minOffset2 = ref(0);
	const maxOffset2 = ref(0);
	// 替换原来的 rebuildSecondByFirstIndex：
	function rebuildSecondByFirstIndex(firstIdx : number) {
		const list = (secondMap[firstIdx] || []).slice(0, 5)
		const angles = getBalancedAngles(list.length)
		minOffset2.value = 180 - Math.max(...angles)
		maxOffset2.value = 180 - Math.min(...angles)
		items2.splice(0, items2.length)
		if (!list.length) {
			currentOffset2.value = 0
			step2.value = 0
			target2.value = -1
			return
		}

		for (let i = 0; i < list.length; i++) {
			items2.push({
				label: list[i],
				baseAngle: angles[i]
			})
		}

		currentOffset2.value = 0
		step2.value = 22.5 // 每个角度偏移
		nextTick(() => {
			target2.value = getLeftmostIndex2()
		})
	}
	// 获取二级“最左侧”（最靠近 180°）的索引
	function getLeftmostIndex2() {
		if (!items2.length) return -1
		let minDiff = Infinity
		let idx = 0
		items2.forEach((item, i) => {
			let real = (item.baseAngle + currentOffset2.value) % 360
			if (real < 0) real += 360
			const diff = Math.abs(real - 180)
			if (diff < minDiff) {
				minDiff = diff
				idx = i
			}
		})
		return idx
	}

	// 监听一级 target，联动二级
	watch(
		() => target.value,
		(idx) => {
			if (idx >= 0) rebuildSecondByFirstIndex(idx)
		},
		{ immediate: true }
	)

	/* ---- 二级手势：竖向滚动到极限距离（[-Δ, +Δ]），松手吸附到 Δ 的整数倍 ---- */
	let startY2 = 0
	let startOffset2 = 0

	function clamp2(val : number) {
		return Math.max(minOffset2.value, Math.min(maxOffset2.value, val))
	}
	const saveindex2 =ref(-1)
	function onTouchStart2(e : TouchEvent) {
		saveindex2.value = target2.value
		const t = e.touches[0]
		startY2 = t.clientY
		startOffset2 = currentOffset2.value
		transitioning2.value = false
		target2.value = -1
		dragging.value = 'second'
		startRestoreTimer2()
	}

	function onTouchMove2(e : TouchEvent) {
		if (!items2.length) return
		const t = e.touches[0]
		const dy = t.clientY - startY2
		const Δ = step2.value || 1
		// 上滑为正角度：使用 -dy
		const raw = startOffset2 + (-dy) * DEG_PER_PX
		currentOffset2.value = clamp2(raw, -Δ, +Δ)
		armIdleFinish(onTouchEnd2)
		startRestoreTimer2()
	}

	function onTouchEnd2() {
		if (!items2.length) return
		const Δ = step2.value || 1
		// 吸附到 Δ 的整数倍，并限制在 [-Δ, +Δ]
		const snapped = Math.round(currentOffset2.value / Δ) * Δ
		transitioning2.value = true
		currentOffset2.value = clamp2(snapped, -Δ, +Δ)

		// 选中靠近 180° 的项
		target2.value = getLeftmostIndex2()
		setTimeout(() => (transitioning2.value = false), 250)
		dragging.value = null
	}
</script>

<style lang="less" scoped>
	.draw-all {
		width: 100%;
		height: 100%;
		background-color: #eff0f4;
		overflow: hidden;
		position: relative;

		.carmera {
			position: absolute;
			right: 0;
			bottom: 250rpx;
			height: 600rpx;
			width: 200rpx;
			z-index: 1;
		}

		.roll {
			position: absolute;
			right: 0;
			bottom: 0rpx;
			height: 1300rpx;
			width: 650rpx;
		}
	}

	.compass-container {
		width: 380px;
		height: 380px;
		border-radius: 50%;
		position: absolute;
		right: -65%;
		transform: translateY(-60%);
		bottom: 270rpx;
		margin: auto;
		touch-action: none;
		z-index: 9999;
	}

	.compass-item {
		position: absolute;
		top: 50%;
		left: 50%;
		width: 100px;
		height: 100px;
		margin: -50px 0 0 -50px;
		display: flex;
		align-items: center;
		justify-content: center;
	}

	.item-label {
		font-size: 25rpx;
		width: 130rpx;
		height: 130rpx;
		border-radius: 50%;
		display: flex;
		justify-content: center;
		align-items: center;
		background: linear-gradient(to bottom, #e6e7ed, #f4f5f7);
		border: 2rpx solid #fff;
		position: relative;
	}

	.item-label-target {
		font-size: 25rpx;
		width: 130rpx;
		height: 130rpx;
		border-radius: 50%;
		display: flex;
		justify-content: center;
		align-items: center;
		background: linear-gradient(to bottom, #e6e7ed, #f4f5f7);
		position: relative;
	}

	.item-label-second {
		font-size: 25rpx;
		width: 130rpx;
		height: 130rpx;
		border-radius: 50%;
		display: flex;
		justify-content: center;
		align-items: center;
		position: relative;
	}

	.item-label-second-target {
		font-size: 25rpx;
		width: 130rpx;
		height: 130rpx;
		border-radius: 50%;
		display: flex;
		justify-content: center;
		align-items: center;
		position: relative;
	}

	.all-size {
		width: 100%;
		height: 100%;
	}

	.targetimge {
		width: 150rpx;
		height: 130rpx;
		position: absolute;
		right: 0;
		top: 0;
		z-index: 1;
		transition: opacity 0.8s ease;
	}

	/* 二级容器放在一级之下，尺寸更大一圈（视觉在后面） */
	.compass-container.second {
		width: 480px;
		height: 480px;
		border-radius: 50%;
		position: absolute;
		right: -75%;
		transform: translateY(-70%);
		bottom: 172rpx;
		margin: auto;
		touch-action: none;
		z-index: 1; // 在一级之下
	}

	.gesture-mask {
		position: fixed;
		left: 0;
		top: 0;
		right: 0;
		bottom: 0;
		z-index: 9999;
		background: transparent;
		/* 或 rgba(0,0,0,0.001) */
	}
</style>
```