feat: 首页交互的

This commit is contained in:
2026-07-14 10:09:26 +08:00
parent b3a15eee9b
commit 515e02f3af
3 changed files with 203 additions and 5 deletions

View File

@@ -0,0 +1,114 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
const helperUrl = new URL("../src/utils/discoveryPanelScroll.js", import.meta.url);
const chatMainListUrl = new URL("../src/pages/ChatMain/ChatMainList/index.vue", import.meta.url);
const helperSource = await readFile(helperUrl, "utf8");
const chatMainListSource = await readFile(chatMainListUrl, "utf8");
const helperModule = await import(
`data:text/javascript;charset=utf-8,${encodeURIComponent(helperSource)}`
);
const {
canExpandDiscoveryPanelFromScroll,
getDiscoveryPanelCollapseState,
} = helperModule;
assert.equal(
getDiscoveryPanelCollapseState({
currentScrollTop: 24,
lastScrollTop: 0,
isCollapsed: false,
threshold: 16,
}),
true,
"scrolling up into discovery content should collapse the welcome panel"
);
assert.equal(
getDiscoveryPanelCollapseState({
currentScrollTop: 18,
lastScrollTop: 15,
isCollapsed: false,
threshold: 16,
}),
true,
"slow upward scrolling past the threshold should collapse the welcome panel"
);
assert.equal(
getDiscoveryPanelCollapseState({
currentScrollTop: 40,
lastScrollTop: 64,
isCollapsed: true,
threshold: 16,
allowExpand: true,
}),
false,
"scrolling down should reveal the welcome panel"
);
assert.equal(
getDiscoveryPanelCollapseState({
currentScrollTop: 40,
lastScrollTop: 64,
isCollapsed: true,
threshold: 16,
allowExpand: false,
}),
true,
"layout-driven reverse scroll after release should not reveal the welcome panel"
);
assert.equal(
getDiscoveryPanelCollapseState({
currentScrollTop: 63,
lastScrollTop: 64,
isCollapsed: true,
threshold: 16,
}),
true,
"small scroll jitter should keep the current panel state"
);
assert.match(
chatMainListSource,
/@touchmove\.capture="handleDiscoveryPanelTouchMove"/,
"discovery shell should capture touchmove before nested scroll-view consumes the gesture"
);
assert.equal(
canExpandDiscoveryPanelFromScroll({
isTouching: false,
touchDirection: "",
lastDownGestureAt: 1000,
now: 1400,
gestureRetainMs: 800,
}),
true,
"momentum scroll after a down gesture should still be allowed to reveal the welcome panel"
);
assert.equal(
canExpandDiscoveryPanelFromScroll({
isTouching: false,
touchDirection: "",
lastDownGestureAt: 1000,
now: 1901,
gestureRetainMs: 800,
}),
false,
"old reverse scroll should not be treated as an active down gesture"
);
assert.equal(
canExpandDiscoveryPanelFromScroll({
isTouching: true,
touchDirection: "up",
lastDownGestureAt: 1000,
now: 1200,
gestureRetainMs: 800,
}),
false,
"an active upward gesture should block reveal even if a down gesture happened earlier"
);

View File

@@ -21,7 +21,9 @@
</view> </view>
<view v-show="tabIndex === 0" class="discovery-shell flex-full" @touchstart.capture="handleDiscoveryPanelTouchStart" <view v-show="tabIndex === 0" class="discovery-shell flex-full" @touchstart.capture="handleDiscoveryPanelTouchStart"
@touchstart="handleDiscoveryPanelTouchStart" @touchmove="handleDiscoveryPanelTouchMove"> @touchstart="handleDiscoveryPanelTouchStart" @touchmove.capture="handleDiscoveryPanelTouchMove"
@touchmove="handleDiscoveryPanelTouchMove"
@touchend="handleDiscoveryPanelTouchEnd" @touchcancel="handleDiscoveryPanelTouchEnd">
<view class="home-welcome-panel"> <view class="home-welcome-panel">
<HomeWelcome class="flex-shrink-0" :mainPageDataModel="mainPageDataModel" /> <HomeWelcome class="flex-shrink-0" :mainPageDataModel="mainPageDataModel" />
</view> </view>
@@ -196,6 +198,10 @@ import {
recentConversation, recentConversation,
} from "@/request/api/ConversationApi"; } from "@/request/api/ConversationApi";
import WebSocketManager from "@/utils/WebSocketManager"; import WebSocketManager from "@/utils/WebSocketManager";
import {
canExpandDiscoveryPanelFromScroll,
getDiscoveryPanelCollapseState,
} from "@/utils/discoveryPanelScroll";
import { IdUtils } from "@/utils"; import { IdUtils } from "@/utils";
import { import {
appendLongTextChunk, appendLongTextChunk,
@@ -228,8 +234,14 @@ const scrollTop = ref(99999);
const discoveryInnerScrollTop = ref(0); const discoveryInnerScrollTop = ref(0);
const discoveryPanelTouchStartY = ref(0); const discoveryPanelTouchStartY = ref(0);
const isDiscoveryPanelCollapsed = ref(false); const isDiscoveryPanelCollapsed = ref(false);
let lastDiscoveryInnerScrollTop = 0;
let isDiscoveryPanelTouching = false;
let discoveryPanelTouchDirection = "";
let lastDiscoveryDownGestureAt = 0;
const DISCOVERY_PANEL_EXPAND_TOP = 4; const DISCOVERY_PANEL_EXPAND_TOP = 4;
const DISCOVERY_PANEL_TOUCH_DELTA = 16; const DISCOVERY_PANEL_TOUCH_DELTA = 16;
const DISCOVERY_PANEL_DIRECTION_DELTA = 2;
const DISCOVERY_PANEL_GESTURE_RETAIN_MS = 800;
/// 会话列表 /// 会话列表
const chatMsgList = ref([]); const chatMsgList = ref([]);
@@ -389,6 +401,10 @@ const handleScrollAreaTouchMove = () => {
const resetDiscoveryScrollState = () => { const resetDiscoveryScrollState = () => {
discoveryInnerScrollTop.value = 0; discoveryInnerScrollTop.value = 0;
discoveryPanelTouchStartY.value = 0; discoveryPanelTouchStartY.value = 0;
lastDiscoveryInnerScrollTop = 0;
isDiscoveryPanelTouching = false;
discoveryPanelTouchDirection = "";
lastDiscoveryDownGestureAt = 0;
isDiscoveryPanelCollapsed.value = false; isDiscoveryPanelCollapsed.value = false;
}; };
@@ -400,6 +416,8 @@ const getTouchPointY = (e) => {
const handleDiscoveryPanelTouchStart = (e) => { const handleDiscoveryPanelTouchStart = (e) => {
handleScrollAreaTouchStart(); handleScrollAreaTouchStart();
isDiscoveryPanelTouching = true;
discoveryPanelTouchDirection = "";
discoveryPanelTouchStartY.value = getTouchPointY(e); discoveryPanelTouchStartY.value = getTouchPointY(e);
}; };
@@ -410,6 +428,14 @@ const handleDiscoveryPanelTouchMove = (e) => {
if (!currentY || !discoveryPanelTouchStartY.value) return; if (!currentY || !discoveryPanelTouchStartY.value) return;
const touchDelta = currentY - discoveryPanelTouchStartY.value; const touchDelta = currentY - discoveryPanelTouchStartY.value;
if (touchDelta < -DISCOVERY_PANEL_DIRECTION_DELTA) {
discoveryPanelTouchDirection = "up";
lastDiscoveryDownGestureAt = 0;
} else if (touchDelta > DISCOVERY_PANEL_DIRECTION_DELTA) {
discoveryPanelTouchDirection = "down";
lastDiscoveryDownGestureAt = Date.now();
}
if (touchDelta < -DISCOVERY_PANEL_TOUCH_DELTA) { if (touchDelta < -DISCOVERY_PANEL_TOUCH_DELTA) {
isDiscoveryPanelCollapsed.value = true; isDiscoveryPanelCollapsed.value = true;
} else if ( } else if (
@@ -420,11 +446,27 @@ const handleDiscoveryPanelTouchMove = (e) => {
} }
}; };
const handleDiscoveryPanelTouchEnd = () => {
isDiscoveryPanelTouching = false;
discoveryPanelTouchDirection = "";
};
const handleDiscoveryContentScroll = ({ scrollTop }) => { const handleDiscoveryContentScroll = ({ scrollTop }) => {
discoveryInnerScrollTop.value = Number(scrollTop || 0); const currentScrollTop = Number(scrollTop || 0);
if (discoveryInnerScrollTop.value > DISCOVERY_PANEL_TOUCH_DELTA) { isDiscoveryPanelCollapsed.value = getDiscoveryPanelCollapseState({
isDiscoveryPanelCollapsed.value = true; currentScrollTop,
} lastScrollTop: lastDiscoveryInnerScrollTop,
isCollapsed: isDiscoveryPanelCollapsed.value,
threshold: DISCOVERY_PANEL_TOUCH_DELTA,
allowExpand: canExpandDiscoveryPanelFromScroll({
isTouching: isDiscoveryPanelTouching,
touchDirection: discoveryPanelTouchDirection,
lastDownGestureAt: lastDiscoveryDownGestureAt,
gestureRetainMs: DISCOVERY_PANEL_GESTURE_RETAIN_MS,
}),
});
discoveryInnerScrollTop.value = currentScrollTop;
lastDiscoveryInnerScrollTop = currentScrollTop;
}; };
const welcomeHeight = ref(0); const welcomeHeight = ref(0);

View File

@@ -0,0 +1,42 @@
export const getDiscoveryPanelCollapseState = ({
currentScrollTop = 0,
lastScrollTop = 0,
isCollapsed = false,
threshold = 16,
directionThreshold = 2,
allowExpand = true,
}) => {
const current = Math.max(Number(currentScrollTop) || 0, 0);
const last = Math.max(Number(lastScrollTop) || 0, 0);
const delta = current - last;
if (delta > directionThreshold && current > threshold) {
return true;
}
if (allowExpand && delta < -directionThreshold) {
return false;
}
return isCollapsed;
};
export const canExpandDiscoveryPanelFromScroll = ({
isTouching = false,
touchDirection = "",
lastDownGestureAt = 0,
now = Date.now(),
gestureRetainMs = 800,
}) => {
if (isTouching) {
return touchDirection === "down";
}
const lastDownAt = Math.max(Number(lastDownGestureAt) || 0, 0);
if (!lastDownAt) {
return false;
}
const currentTime = Math.max(Number(now) || 0, 0);
return currentTime - lastDownAt <= gestureRetainMs;
};