From fff1ba9c2e2e3aaa208f7962342b09ac93d36d7b Mon Sep 17 00:00:00 2001 From: openclaw Date: Thu, 23 Jul 2026 23:47:35 +0800 Subject: [PATCH] Refine category wheel and taxonomy --- .../components/CategoryCoordinateSelector.vue | 29 ++++---- apps/web/src/data/categories.ts | 27 +++---- docs/分类体系.md | 71 +++++++++++++++++++ 3 files changed, 94 insertions(+), 33 deletions(-) create mode 100644 docs/分类体系.md diff --git a/apps/web/src/components/CategoryCoordinateSelector.vue b/apps/web/src/components/CategoryCoordinateSelector.vue index 2b549bf..f20fd4f 100644 --- a/apps/web/src/components/CategoryCoordinateSelector.vue +++ b/apps/web/src/components/CategoryCoordinateSelector.vue @@ -25,6 +25,7 @@ const emit = defineEmits<{ }>(); const FOCUS_ANGLE = 270; +const wheelEntryTypes = [...entryTypes, ...entryTypes]; const selectorLevel = ref("type"); const wheelStage = ref(null); const wheelRotation = ref(0); @@ -37,7 +38,7 @@ let inertiaFrame: number | null = null; const selectedParent = computed(() => props.categoryPath[0]); const selectorOptions = computed(() => { - if (selectorLevel.value === "type") return entryTypes; + if (selectorLevel.value === "type") return wheelEntryTypes; if (selectorLevel.value === "category") return categories[props.entryType]; return selectedParent.value?.children ?? []; }); @@ -172,12 +173,16 @@ function moveWheelDrag(event: PointerEvent) { const delta = normalizeAngle(angle - pointerLastAngle); const now = performance.now(); const elapsed = Math.max(1, now - pointerLastTime); + const instantaneousVelocity = delta / elapsed; wheelRotation.value += delta; pointerTravel += Math.abs(delta); if (pointerTravel > 5 && wheelStage.value && !wheelStage.value.hasPointerCapture(event.pointerId)) { wheelStage.value.setPointerCapture(event.pointerId); } - pointerVelocity = pointerVelocity * 0.62 + (delta / elapsed) * 0.38; + pointerVelocity = Math.max( + -1.2, + Math.min(1.2, pointerVelocity * 0.62 + instantaneousVelocity * 0.38), + ); pointerLastAngle = angle; pointerLastTime = now; } @@ -188,8 +193,10 @@ function endWheelDrag(event: PointerEvent) { if (wheelStage.value?.hasPointerCapture(event.pointerId)) { wheelStage.value.releasePointerCapture(event.pointerId); } - if (pointerTravel > 5 && Math.abs(pointerVelocity) > 0.015) startInertia(); - else snapWheel(); + const releaseDelay = Math.max(0, performance.now() - pointerLastTime - 16); + pointerVelocity *= Math.exp(-releaseDelay / 90); + if (pointerTravel > 5 && Math.abs(pointerVelocity) > 0.008) startInertia(); + else pointerVelocity = 0; } function cancelOptionClick(event: MouseEvent) { @@ -205,10 +212,10 @@ function startInertia() { const elapsed = Math.min(32, now - previousTime); previousTime = now; wheelRotation.value += pointerVelocity * elapsed; - pointerVelocity *= Math.pow(0.92, elapsed / 16); - if (Math.abs(pointerVelocity) < 0.012) { + pointerVelocity *= Math.exp(-elapsed / 280); + if (Math.abs(pointerVelocity) < 0.002) { inertiaFrame = null; - snapWheel(); + pointerVelocity = 0; return; } inertiaFrame = requestAnimationFrame(step); @@ -216,12 +223,6 @@ function startInertia() { inertiaFrame = requestAnimationFrame(step); } -function snapWheel() { - const step = segmentAngle.value; - const target = Math.round((wheelRotation.value - FOCUS_ANGLE) / step) * step + FOCUS_ANGLE; - wheelRotation.value = target; -} - function stopInertia() { if (inertiaFrame !== null) cancelAnimationFrame(inertiaFrame); inertiaFrame = null; @@ -257,7 +258,7 @@ function resetShellScroll() {