- ChatArea: 新增触摸手势脱锁(手指下拉>8px 视为脱锁意图),与桌面端 wheel-up 同路径;嵌套滚动容器内触摸不解除外层锁定 - ChatArea: 展开锚定改为按条件跳过——仅贴底锁定中的运行态块交给逐帧追底,手动展开(含脱锁后历史块)走锚定,修复外框与内容移动不同步 - scroll: 脱锁标志重置改为严格贴底判定(<=4px),nearBottom 70px 容差与意图冷却期内不再误清脱锁标志,修复移动端第一次上滑必被拽回底部 - lifecycle: 移动端视口 watcher 提前到路由/对话加载前,避免加载期按桌面布局渲染 - MarkdownRenderer: 文本段按顶层块分片渲染+分段级缓存,流式期间已完成前缀块 DOM 不再重设,保护 show_file 卡片/表格/KaTeX 不被反复重建 - view_image: 工具结果携带 size,前端按 path 走 /api/file/content 加载原图,图片限高 480px 防长截图占满对话区
139 lines
5.0 KiB
TypeScript
139 lines
5.0 KiB
TypeScript
// @ts-nocheck
|
||
import { useChatActionStore } from '../stores/chatActions';
|
||
import { normalizeScrollLock } from '../composables/useScrollControl';
|
||
import { setupShowImageObserver, teardownShowImageObserver } from './bootstrap';
|
||
import { debugLog } from './methods/common';
|
||
|
||
export function created() {
|
||
const actionStore = useChatActionStore();
|
||
actionStore.registerDependencies({
|
||
pushToast: (payload) => this.uiPushToast(payload),
|
||
autoResizeInput: () => this.autoResizeInput(),
|
||
focusComposer: () => {
|
||
const inputEl = this.getComposerElement('stadiumInput');
|
||
if (inputEl && typeof inputEl.focus === 'function') {
|
||
inputEl.focus();
|
||
}
|
||
},
|
||
isConnected: () => this.isConnected,
|
||
executeCommand: async (command) => this.executeSystemCommand(command, { showToast: false }),
|
||
downloadResource: (url, filename) => this.downloadResource(url, filename)
|
||
});
|
||
}
|
||
|
||
export async function mounted() {
|
||
debugLog('Vue应用已挂载');
|
||
if (window.ensureCsrfToken) {
|
||
window.ensureCsrfToken().catch((err) => {
|
||
console.warn('CSRF token 初始化失败:', err);
|
||
});
|
||
}
|
||
// 移动端视口判断必须在路由/对话加载前生效,
|
||
// 否则加载期间移动端会按桌面布局渲染(QuickDock 挤压页面)。
|
||
this.setupMobileViewportWatcher();
|
||
// 并行启动路由解析与初始化数据,网页端不再依赖 WebSocket 初始化
|
||
const routePromise = this.bootstrapRoute();
|
||
await routePromise;
|
||
this.$nextTick(() => {
|
||
this.ensureScrollListener();
|
||
// 刷新后若无输出,自动解锁滚动锁定
|
||
normalizeScrollLock(this);
|
||
});
|
||
setupShowImageObserver();
|
||
|
||
// 立即加载初始数据(并行获取状态,优先同步运行模式)
|
||
const initialDataPromise = this.loadInitialData();
|
||
this.startProjectGitSummaryIdleRefresh?.();
|
||
this.startConnectionHeartbeat();
|
||
this.fetchTerminalCount();
|
||
this.startTerminalCountIdleRefresh();
|
||
this.checkTutorialPrompt();
|
||
if (initialDataPromise && typeof initialDataPromise.then === 'function') {
|
||
initialDataPromise
|
||
.then(() => {
|
||
// 初始数据加载完成后再刷新 git 摘要,确保 workspace/project_path 已就绪
|
||
this.refreshProjectGitSummary?.();
|
||
// 避免在初始加载阶段被覆盖,加载完成后再兜底检查一次
|
||
this.checkTutorialPrompt();
|
||
})
|
||
.catch(() => {});
|
||
}
|
||
|
||
// 注册全局事件处理器(用于任务轮询)
|
||
(window as any).__taskEventHandler = (event: any) => {
|
||
if (typeof this.handleTaskEvent === 'function') {
|
||
this.handleTaskEvent(event);
|
||
}
|
||
};
|
||
|
||
// 立即尝试恢复运行中的任务(不延迟)
|
||
if (typeof this.restoreTaskState === 'function') {
|
||
this.restoreTaskState();
|
||
}
|
||
|
||
document.addEventListener('click', this.handleClickOutsideQuickMenu);
|
||
document.addEventListener('click', this.handleClickOutsideHeaderMenu);
|
||
document.addEventListener('click', this.handleClickOutsideMobileMenu);
|
||
document.addEventListener('click', this.handleCopyCodeClick);
|
||
window.addEventListener('popstate', this.handlePopState);
|
||
window.addEventListener('keydown', this.handleMobileOverlayEscape);
|
||
window.addEventListener('beforeunload', this.handleBeforeUnloadDraftPersist);
|
||
|
||
this.subAgentFetch();
|
||
this.backgroundCommandFetch();
|
||
|
||
this.$nextTick(() => {
|
||
this.autoResizeInput();
|
||
});
|
||
this.resourceBindContainerVisibilityWatcher();
|
||
this.resourceStartContainerStatsPolling();
|
||
this.resourceStartProjectStoragePolling();
|
||
this.resourceStartUsageQuotaPolling();
|
||
|
||
// 设置拖拽上传
|
||
this.setupDragAndDrop();
|
||
}
|
||
|
||
export function beforeUnmount() {
|
||
if (this._todoRefreshTimer) {
|
||
clearTimeout(this._todoRefreshTimer);
|
||
this._todoRefreshTimer = null;
|
||
}
|
||
|
||
// 停止任务轮询
|
||
try {
|
||
const { useTaskStore } = require('../stores/task');
|
||
const taskStore = useTaskStore();
|
||
taskStore.stopPolling();
|
||
} catch (error) {
|
||
// ignore
|
||
}
|
||
|
||
document.removeEventListener('click', this.handleClickOutsideQuickMenu);
|
||
document.removeEventListener('click', this.handleClickOutsideHeaderMenu);
|
||
document.removeEventListener('click', this.handleClickOutsideMobileMenu);
|
||
document.removeEventListener('click', this.handleCopyCodeClick);
|
||
window.removeEventListener('popstate', this.handlePopState);
|
||
window.removeEventListener('keydown', this.handleMobileOverlayEscape);
|
||
window.removeEventListener('beforeunload', this.handleBeforeUnloadDraftPersist);
|
||
this.teardownMobileViewportWatcher();
|
||
this.stopProjectGitSummaryIdleRefresh?.();
|
||
this.stopTerminalCountIdleRefresh?.();
|
||
this.resourceStopContainerStatsPolling();
|
||
this.resourceStopProjectStoragePolling();
|
||
this.resourceStopUsageQuotaPolling();
|
||
this.stopConnectionHeartbeat();
|
||
teardownShowImageObserver();
|
||
|
||
// 移除拖拽上传监听
|
||
this.teardownDragAndDrop();
|
||
if (this.titleTypingTimer) {
|
||
clearInterval(this.titleTypingTimer);
|
||
this.titleTypingTimer = null;
|
||
}
|
||
const cleanup = this.destroyEasterEggEffect(true);
|
||
if (cleanup && typeof cleanup.catch === 'function') {
|
||
cleanup.catch(() => {});
|
||
}
|
||
}
|