fix(asset-ai): refine mobile viewport and operating query scope
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
shishengliang
2026-09-23 18:49:26 +08:00
parent 5be6240bbf
commit 6de77adb47
11 changed files with 145 additions and 23 deletions
+39 -10
View File
@@ -92,18 +92,47 @@ export default function AssetAssistant({
const page = useRef<HTMLElement>(null);
useLayoutEffect(() => {
const viewport = window.visualViewport;
const mobile = window.matchMedia('(max-width: 767px)');
let frame = 0;
let unlock: (() => void) | undefined;
const updateViewport = () => {
frame = 0;
if (!mobile.matches || (viewport && viewport.scale !== 1)) return;
page.current?.style.setProperty('--aq-viewport-height', `${viewport?.height ?? window.innerHeight}px`);
page.current?.style.setProperty('--aq-viewport-top', `${viewport?.offsetTop ?? 0}px`);
};
updateViewport();
viewport?.addEventListener('resize', updateViewport);
viewport?.addEventListener('scroll', updateViewport);
window.addEventListener('resize', updateViewport);
const scheduleViewport = () => {
if (!frame) frame = requestAnimationFrame(updateViewport);
};
const syncMobile = () => {
unlock?.();
unlock = undefined;
if (mobile.matches) {
const scrollY = window.scrollY;
const body = document.body;
const previous = body.style.top;
body.style.top = `${-scrollY}px`;
body.classList.add('aq-mobile-open');
unlock = () => {
body.classList.remove('aq-mobile-open');
body.style.top = previous;
window.scrollTo({ top: scrollY, behavior: 'instant' });
};
}
updateViewport();
};
syncMobile();
mobile.addEventListener('change', syncMobile);
viewport?.addEventListener('resize', scheduleViewport);
viewport?.addEventListener('scroll', scheduleViewport);
window.addEventListener('resize', scheduleViewport);
return () => {
viewport?.removeEventListener('resize', updateViewport);
viewport?.removeEventListener('scroll', updateViewport);
window.removeEventListener('resize', updateViewport);
cancelAnimationFrame(frame);
mobile.removeEventListener('change', syncMobile);
viewport?.removeEventListener('resize', scheduleViewport);
viewport?.removeEventListener('scroll', scheduleViewport);
window.removeEventListener('resize', scheduleViewport);
unlock?.();
};
}, []);
const [theme, setTheme] = useState(appearance);
@@ -214,7 +243,7 @@ export default function AssetAssistant({
setActiveResult(undefined);
sessionRef.current = undefined;
setIndependent(true);
composer.current?.focus();
composer.current?.focus({ preventScroll: true });
};
useEffect(
() => () => {
@@ -360,7 +389,7 @@ export default function AssetAssistant({
setTurns([]);
setExpandedTurns(new Set());
setDraft("");
composer.current?.focus();
composer.current?.focus({ preventScroll: true });
};
useEffect(() => {
reset();
@@ -390,7 +419,7 @@ export default function AssetAssistant({
className="aq-nav-active"
onClick={() => {
scroller.current?.scrollTo({ top: 0 });
composer.current?.focus();
composer.current?.focus({ preventScroll: true });
}}
>
<MessageSquare size={16} />
+6 -6
View File
@@ -799,9 +799,7 @@ function ResultViewContent({
"分组",
"车辆数",
"占比",
"运营率",
"异动",
"待交车",
...(result.plan.category === "operating" ? [] : ["运营率", "异动", "待交车"]),
"下钻",
].map((h) => (
<th key={h}>{h}</th>
@@ -814,9 +812,11 @@ function ResultViewContent({
<td>{g.name}</td>
<td>{g.count}</td>
<td>{g.share}%</td>
<td>{g.rate}%</td>
<td>{g.abnormal}</td>
<td>{g.pending}</td>
{result.plan.category !== "operating" && <>
<td>{g.rate}%</td>
<td>{g.abnormal}</td>
<td>{g.pending}</td>
</>}
<td>
<button
disabled={
@@ -2098,3 +2098,23 @@
.aq-page .aq-shimmer { background: none; color: var(--aq-muted); }
.aq-page .aq-trace-collapse { transition: none; }
}
/* Keep the document stationary while the keyboard resizes the conversation. */
@media (max-width: 767px) {
body.aq-mobile-open { position: fixed; width: 100%; overflow: hidden; overscroll-behavior: none; }
.aq-page .aq-workspace { min-height: 0; }
.aq-page .aq-header { min-height: 48px; height: 48px; padding: 2px 10px; }
.aq-page .aq-scope { padding: 6px 12px; }
.aq-page .aq-conversation { min-height: 0; overscroll-behavior-y: contain; }
.aq-page .aq-composer-area { padding: 6px 12px max(6px, env(safe-area-inset-bottom)); }
.aq-page .aq-composer { position: relative; border-radius: 14px; }
.aq-page .aq-composer textarea { min-height: 44px; padding: 10px 56px 8px 12px; line-height: 1.5; }
.aq-page .aq-composer-bottom { padding: 0 10px 6px; min-height: 22px; }
.aq-page .aq-composer-bottom > div:last-child { position: absolute; right: 4px; bottom: 4px; }
.aq-page .aq-composer .aq-send { width: 44px; height: 44px; }
.aq-page .aq-composer-foot { padding-top: 4px; }
.aq-page .aq-context { flex-wrap: nowrap; overflow-x: auto; margin-bottom: 5px; padding-bottom: 2px; }
.aq-page .aq-context > * { flex-shrink: 0; }
.aq-page .aq-latest-answer { min-height: 32px; margin-bottom: 2px; }
.aq-page:has(.aq-composer textarea:focus) .aq-composer-foot { display: none; }
}