Compare commits
No commits in common. "a212a0076cc9e1560abb7fca57b8e4b2dc51a5c7" and "2766a8ab571733c0e53a499bf00c5403f6bc99c7" have entirely different histories.
a212a0076c
...
2766a8ab57
@ -338,43 +338,8 @@ def delete_host_workspace(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def set_default_host_workspace(
|
|
||||||
workspace_id: str,
|
|
||||||
config_path: Optional[Union[str, Path]] = None,
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""设置指定工作区为默认工作区。"""
|
|
||||||
ws_id = str(workspace_id or "").strip()
|
|
||||||
if not ws_id:
|
|
||||||
raise ValueError("缺少 workspace_id")
|
|
||||||
|
|
||||||
cfg_path = _resolve_config_path(config_path)
|
|
||||||
with _HOST_WORKSPACE_LOCK:
|
|
||||||
payload = _ensure_config_file(cfg_path, strict=True)
|
|
||||||
raw_workspaces = payload.get("workspaces")
|
|
||||||
if not isinstance(raw_workspaces, list):
|
|
||||||
raw_workspaces = []
|
|
||||||
|
|
||||||
seen_ids = set()
|
|
||||||
for idx, item in enumerate(raw_workspaces):
|
|
||||||
normalized = _normalize_entry(item, idx)
|
|
||||||
if normalized:
|
|
||||||
seen_ids.add(normalized.get("workspace_id"))
|
|
||||||
|
|
||||||
if ws_id not in seen_ids:
|
|
||||||
raise ValueError("工作区不存在")
|
|
||||||
|
|
||||||
payload["default_workspace_id"] = ws_id
|
|
||||||
_atomic_write_json(cfg_path, payload)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"default_workspace_id": ws_id,
|
|
||||||
"catalog": load_host_workspace_catalog(config_path=cfg_path),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"load_host_workspace_catalog",
|
"load_host_workspace_catalog",
|
||||||
"resolve_host_workspace",
|
"resolve_host_workspace",
|
||||||
"create_host_workspace",
|
"create_host_workspace",
|
||||||
"set_default_host_workspace",
|
|
||||||
]
|
]
|
||||||
|
|||||||
@ -36,7 +36,6 @@ class UserRecord:
|
|||||||
invite_code: Optional[str] = None
|
invite_code: Optional[str] = None
|
||||||
role: str = "user"
|
role: str = "user"
|
||||||
tutorial_completed: bool = False
|
tutorial_completed: bool = False
|
||||||
default_workspace_id: str = "default"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -391,14 +390,6 @@ class UserManager:
|
|||||||
workspace.workspace_id = workspace_id
|
workspace.workspace_id = workspace_id
|
||||||
return workspace
|
return workspace
|
||||||
|
|
||||||
def _get_user_default_workspace_id(self, username: str) -> str:
|
|
||||||
"""返回用户设置的默认项目 ID,如未设置或无效则返回 default。"""
|
|
||||||
record = self._users.get(self._normalize_username(username))
|
|
||||||
default_id = str(getattr(record, "default_workspace_id", None) or "default").strip()
|
|
||||||
if not default_id:
|
|
||||||
return "default"
|
|
||||||
return default_id
|
|
||||||
|
|
||||||
def list_user_workspaces(self, username: str) -> Dict[str, Dict]:
|
def list_user_workspaces(self, username: str) -> Dict[str, Dict]:
|
||||||
username = self._normalize_username(username)
|
username = self._normalize_username(username)
|
||||||
self._migrate_legacy_docker_workspace_layout(username)
|
self._migrate_legacy_docker_workspace_layout(username)
|
||||||
@ -413,21 +404,15 @@ class UserManager:
|
|||||||
if web_root.exists():
|
if web_root.exists():
|
||||||
self._collect_workspaces(result, web_root)
|
self._collect_workspaces(result, web_root)
|
||||||
|
|
||||||
default_workspace_id = self._get_user_default_workspace_id(username)
|
|
||||||
|
|
||||||
# 确保 default 始终存在
|
# 确保 default 始终存在
|
||||||
if "default" not in result:
|
if "default" not in result:
|
||||||
result["default"] = {
|
result["default"] = {
|
||||||
"workspace_id": "default",
|
"workspace_id": "default",
|
||||||
"label": "默认项目",
|
"label": "默认项目",
|
||||||
"path": "",
|
"path": "",
|
||||||
"is_default": default_workspace_id == "default",
|
"is_default": True,
|
||||||
"has_conversations": False,
|
"has_conversations": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
# 同步 is_default 标记
|
|
||||||
for ws_id in result:
|
|
||||||
result[ws_id]["is_default"] = ws_id == default_workspace_id
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _collect_workspaces(self, result: Dict[str, Dict], projects_root: Path) -> None:
|
def _collect_workspaces(self, result: Dict[str, Dict], projects_root: Path) -> None:
|
||||||
@ -534,20 +519,6 @@ class UserManager:
|
|||||||
raise ValueError("项目不存在")
|
raise ValueError("项目不存在")
|
||||||
shutil.rmtree(target)
|
shutil.rmtree(target)
|
||||||
|
|
||||||
def set_default_user_workspace(self, username: str, workspace_id: str) -> str:
|
|
||||||
"""设置指定项目为用户默认项目,返回生效的默认项目 ID。"""
|
|
||||||
username = self._normalize_username(username)
|
|
||||||
ws_id = self.normalize_workspace_id(workspace_id)
|
|
||||||
known = self.list_user_workspaces(username)
|
|
||||||
if ws_id not in known:
|
|
||||||
raise ValueError("项目不存在")
|
|
||||||
record = self._users.get(username)
|
|
||||||
if not record:
|
|
||||||
raise ValueError("用户不存在")
|
|
||||||
record.default_workspace_id = ws_id
|
|
||||||
self._save_users()
|
|
||||||
return ws_id
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _load_workspace_label(workspace_root: Path, fallback: str) -> str:
|
def _load_workspace_label(workspace_root: Path, fallback: str) -> str:
|
||||||
meta_path = workspace_root / "project.json"
|
meta_path = workspace_root / "project.json"
|
||||||
@ -681,12 +652,9 @@ class UserManager:
|
|||||||
invite_code=payload.get("invite_code"),
|
invite_code=payload.get("invite_code"),
|
||||||
role=payload.get("role", "user"),
|
role=payload.get("role", "user"),
|
||||||
tutorial_completed=bool(payload.get("tutorial_completed", False)),
|
tutorial_completed=bool(payload.get("tutorial_completed", False)),
|
||||||
default_workspace_id=str(payload.get("default_workspace_id") or "default").strip() or "default",
|
|
||||||
)
|
)
|
||||||
if "tutorial_completed" not in payload:
|
if "tutorial_completed" not in payload:
|
||||||
migrated = True
|
migrated = True
|
||||||
if "default_workspace_id" not in payload:
|
|
||||||
migrated = True
|
|
||||||
self._users[username] = record
|
self._users[username] = record
|
||||||
self._index_user(record)
|
self._index_user(record)
|
||||||
if migrated:
|
if migrated:
|
||||||
@ -704,7 +672,6 @@ class UserManager:
|
|||||||
"invite_code": record.invite_code,
|
"invite_code": record.invite_code,
|
||||||
"role": record.role,
|
"role": record.role,
|
||||||
"tutorial_completed": bool(record.tutorial_completed),
|
"tutorial_completed": bool(record.tutorial_completed),
|
||||||
"default_workspace_id": str(record.default_workspace_id or "default").strip() or "default",
|
|
||||||
}
|
}
|
||||||
for username, record in self._users.items()
|
for username, record in self._users.items()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,6 @@ from modules.host_workspace_manager import (
|
|||||||
load_host_workspace_catalog,
|
load_host_workspace_catalog,
|
||||||
rename_host_workspace,
|
rename_host_workspace,
|
||||||
resolve_host_workspace,
|
resolve_host_workspace,
|
||||||
set_default_host_workspace,
|
|
||||||
)
|
)
|
||||||
from utils.host_workspace_debug import write_host_workspace_debug
|
from utils.host_workspace_debug import write_host_workspace_debug
|
||||||
from .utils_common import log_conn_diag
|
from .utils_common import log_conn_diag
|
||||||
@ -50,15 +49,14 @@ def _close_terminal_for_key(term_key: str):
|
|||||||
|
|
||||||
def _build_docker_projects_payload(username: str, current_id: str) -> list[dict]:
|
def _build_docker_projects_payload(username: str, current_id: str) -> list[dict]:
|
||||||
running_by_workspace = _active_task_counts(username)
|
running_by_workspace = _active_task_counts(username)
|
||||||
workspaces = user_manager.list_user_workspaces(username)
|
|
||||||
projects = []
|
projects = []
|
||||||
for ws_id, item in workspaces.items():
|
for ws_id, item in user_manager.list_user_workspaces(username).items():
|
||||||
projects.append({
|
projects.append({
|
||||||
"workspace_id": ws_id,
|
"workspace_id": ws_id,
|
||||||
"label": item.get("label") or ("默认项目" if ws_id == "default" else ws_id),
|
"label": item.get("label") or ("默认项目" if ws_id == "default" else ws_id),
|
||||||
"path": "",
|
"path": "",
|
||||||
"is_current": ws_id == current_id,
|
"is_current": ws_id == current_id,
|
||||||
"is_default": bool(item.get("is_default", ws_id == "default")),
|
"is_default": ws_id == "default",
|
||||||
"running_task_count": int(running_by_workspace.get(ws_id, 0)),
|
"running_task_count": int(running_by_workspace.get(ws_id, 0)),
|
||||||
})
|
})
|
||||||
return projects
|
return projects
|
||||||
@ -1053,11 +1051,10 @@ def list_docker_projects():
|
|||||||
# 默认项目用于向后兼容旧 Docker Web 文件/对话。
|
# 默认项目用于向后兼容旧 Docker Web 文件/对话。
|
||||||
user_manager.ensure_user_workspace(username, current_id)
|
user_manager.ensure_user_workspace(username, current_id)
|
||||||
projects = _build_docker_projects_payload(username, current_id)
|
projects = _build_docker_projects_payload(username, current_id)
|
||||||
default_workspace_id = user_manager._get_user_default_workspace_id(username)
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"success": True,
|
"success": True,
|
||||||
"data": {
|
"data": {
|
||||||
"default_workspace_id": default_workspace_id,
|
"default_workspace_id": "default",
|
||||||
"current_workspace_id": current_id,
|
"current_workspace_id": current_id,
|
||||||
"workspaces": projects,
|
"workspaces": projects,
|
||||||
}
|
}
|
||||||
@ -1103,13 +1100,12 @@ def select_docker_project():
|
|||||||
attach_user_broadcast(terminal, username)
|
attach_user_broadcast(terminal, username)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
default_workspace_id = user_manager._get_user_default_workspace_id(username)
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"success": True,
|
"success": True,
|
||||||
"data": {
|
"data": {
|
||||||
"current_workspace_id": session["workspace_id"],
|
"current_workspace_id": session["workspace_id"],
|
||||||
"project_path": "",
|
"project_path": "",
|
||||||
"default_workspace_id": default_workspace_id,
|
"default_workspace_id": "default",
|
||||||
"reloaded": previous_workspace_id != session["workspace_id"],
|
"reloaded": previous_workspace_id != session["workspace_id"],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -1131,14 +1127,13 @@ def create_docker_project():
|
|||||||
return jsonify({"success": False, "error": str(exc)}), 400
|
return jsonify({"success": False, "error": str(exc)}), 400
|
||||||
projects = []
|
projects = []
|
||||||
current_id = session.get("workspace_id") or "default"
|
current_id = session.get("workspace_id") or "default"
|
||||||
default_workspace_id = user_manager._get_user_default_workspace_id(username)
|
|
||||||
for ws_id, item in user_manager.list_user_workspaces(username).items():
|
for ws_id, item in user_manager.list_user_workspaces(username).items():
|
||||||
projects.append({
|
projects.append({
|
||||||
"workspace_id": ws_id,
|
"workspace_id": ws_id,
|
||||||
"label": item.get("label") or ws_id,
|
"label": item.get("label") or ws_id,
|
||||||
"path": "",
|
"path": "",
|
||||||
"is_current": ws_id == current_id,
|
"is_current": ws_id == current_id,
|
||||||
"is_default": ws_id == default_workspace_id,
|
"is_default": ws_id == "default",
|
||||||
})
|
})
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"success": True,
|
"success": True,
|
||||||
@ -1149,7 +1144,7 @@ def create_docker_project():
|
|||||||
"label": label,
|
"label": label,
|
||||||
"path": "",
|
"path": "",
|
||||||
},
|
},
|
||||||
"default_workspace_id": default_workspace_id,
|
"default_workspace_id": "default",
|
||||||
"current_workspace_id": current_id,
|
"current_workspace_id": current_id,
|
||||||
"workspaces": projects,
|
"workspaces": projects,
|
||||||
}
|
}
|
||||||
@ -1174,12 +1169,11 @@ def rename_docker_project():
|
|||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
return jsonify({"success": False, "error": str(exc)}), 400
|
return jsonify({"success": False, "error": str(exc)}), 400
|
||||||
current_id = session.get("workspace_id") or "default"
|
current_id = session.get("workspace_id") or "default"
|
||||||
default_workspace_id = user_manager._get_user_default_workspace_id(username)
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"success": True,
|
"success": True,
|
||||||
"data": {
|
"data": {
|
||||||
"workspace": workspace,
|
"workspace": workspace,
|
||||||
"default_workspace_id": default_workspace_id,
|
"default_workspace_id": "default",
|
||||||
"current_workspace_id": current_id,
|
"current_workspace_id": current_id,
|
||||||
"workspaces": _build_docker_projects_payload(username, current_id),
|
"workspaces": _build_docker_projects_payload(username, current_id),
|
||||||
}
|
}
|
||||||
@ -1224,20 +1218,11 @@ def delete_docker_project():
|
|||||||
)
|
)
|
||||||
except RuntimeError as exc:
|
except RuntimeError as exc:
|
||||||
return jsonify({"success": False, "error": str(exc)}), 503
|
return jsonify({"success": False, "error": str(exc)}), 503
|
||||||
|
|
||||||
# 如果删除的是当前默认项目,重置为 default
|
|
||||||
if user_manager._get_user_default_workspace_id(username) == workspace_id:
|
|
||||||
try:
|
|
||||||
user_manager.set_default_user_workspace(username, "default")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
default_workspace_id = user_manager._get_user_default_workspace_id(username)
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"success": True,
|
"success": True,
|
||||||
"data": {
|
"data": {
|
||||||
"deleted_workspace_id": workspace_id,
|
"deleted_workspace_id": workspace_id,
|
||||||
"default_workspace_id": default_workspace_id,
|
"default_workspace_id": "default",
|
||||||
"current_workspace_id": current_id,
|
"current_workspace_id": current_id,
|
||||||
"workspaces": _build_docker_projects_payload(username, current_id),
|
"workspaces": _build_docker_projects_payload(username, current_id),
|
||||||
}
|
}
|
||||||
@ -1489,64 +1474,6 @@ def delete_host_workspace_api():
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@status_bp.route('/api/host/workspaces/set-default', methods=['POST'])
|
|
||||||
@api_login_required
|
|
||||||
def set_default_host_workspace_api():
|
|
||||||
if not _is_host_mode_request():
|
|
||||||
return jsonify({"success": False, "error": "仅宿主机模式可用"}), 403
|
|
||||||
|
|
||||||
payload = request.get_json(silent=True) or {}
|
|
||||||
workspace_id = (payload.get("workspace_id") or "").strip()
|
|
||||||
if not workspace_id:
|
|
||||||
return jsonify({"success": False, "error": "缺少 workspace_id"}), 400
|
|
||||||
try:
|
|
||||||
result = set_default_host_workspace(workspace_id)
|
|
||||||
except ValueError as exc:
|
|
||||||
return jsonify({"success": False, "error": str(exc)}), 400
|
|
||||||
except Exception as exc:
|
|
||||||
return jsonify({"success": False, "error": str(exc)}), 500
|
|
||||||
|
|
||||||
catalog = result.get("catalog") or load_host_workspace_catalog()
|
|
||||||
current_id = (session.get("host_workspace_id") or "").strip()
|
|
||||||
return jsonify({
|
|
||||||
"success": True,
|
|
||||||
"data": {
|
|
||||||
"default_workspace_id": catalog.get("default_workspace_id"),
|
|
||||||
"current_workspace_id": current_id,
|
|
||||||
"workspaces": _build_host_workspaces_payload(catalog, current_id),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@status_bp.route('/api/projects/set-default', methods=['POST'])
|
|
||||||
@api_login_required
|
|
||||||
def set_default_docker_project_api():
|
|
||||||
if not _is_docker_project_request():
|
|
||||||
return jsonify({"success": False, "error": "仅 Docker Web 模式可用"}), 403
|
|
||||||
|
|
||||||
payload = request.get_json(silent=True) or {}
|
|
||||||
workspace_id = (payload.get("workspace_id") or "").strip()
|
|
||||||
if not workspace_id:
|
|
||||||
return jsonify({"success": False, "error": "缺少项目 ID"}), 400
|
|
||||||
username = session.get("username")
|
|
||||||
try:
|
|
||||||
user_manager.set_default_user_workspace(username, workspace_id)
|
|
||||||
except ValueError as exc:
|
|
||||||
return jsonify({"success": False, "error": str(exc)}), 400
|
|
||||||
except Exception as exc:
|
|
||||||
return jsonify({"success": False, "error": str(exc)}), 500
|
|
||||||
|
|
||||||
current_id = session.get("workspace_id") or "default"
|
|
||||||
return jsonify({
|
|
||||||
"success": True,
|
|
||||||
"data": {
|
|
||||||
"default_workspace_id": user_manager._get_user_default_workspace_id(username),
|
|
||||||
"current_workspace_id": current_id,
|
|
||||||
"workspaces": _build_docker_projects_payload(username, current_id),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@status_bp.route('/api/container-status')
|
@status_bp.route('/api/container-status')
|
||||||
@api_login_required
|
@api_login_required
|
||||||
@with_terminal
|
@with_terminal
|
||||||
|
|||||||
@ -551,7 +551,6 @@
|
|||||||
@create="submitHostWorkspaceCreateFromManage"
|
@create="submitHostWorkspaceCreateFromManage"
|
||||||
@rename="renameHostWorkspace"
|
@rename="renameHostWorkspace"
|
||||||
@delete="deleteHostWorkspace"
|
@delete="deleteHostWorkspace"
|
||||||
@set-default="setDefaultHostWorkspace"
|
|
||||||
/>
|
/>
|
||||||
</transition>
|
</transition>
|
||||||
<TutorialOverlay v-if="tutorialStore.running" />
|
<TutorialOverlay v-if="tutorialStore.running" />
|
||||||
|
|||||||
@ -1474,52 +1474,6 @@ export const uiMethods = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async setDefaultHostWorkspace(payload = {}) {
|
|
||||||
if (!(this.versioningHostMode || this.dockerProjectMode)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const workspaceId = String(payload?.workspace_id || '').trim();
|
|
||||||
if (!workspaceId) return;
|
|
||||||
if (workspaceId === this.defaultHostWorkspaceId) return;
|
|
||||||
|
|
||||||
this.hostWorkspaceManageSubmitting = true;
|
|
||||||
this.hostWorkspaceCreateError = '';
|
|
||||||
const kindLabel = this.versioningHostMode ? '工作区' : '项目';
|
|
||||||
try {
|
|
||||||
const endpoint = this.versioningHostMode
|
|
||||||
? '/api/host/workspaces/set-default'
|
|
||||||
: '/api/projects/set-default';
|
|
||||||
const resp = await fetch(endpoint, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
credentials: 'same-origin',
|
|
||||||
body: JSON.stringify({ workspace_id: workspaceId })
|
|
||||||
});
|
|
||||||
const result = await resp.json().catch(() => ({}));
|
|
||||||
if (!resp.ok || !result?.success) {
|
|
||||||
throw new Error(result?.error || `设置默认${kindLabel}失败`);
|
|
||||||
}
|
|
||||||
const data = result.data || {};
|
|
||||||
this.defaultHostWorkspaceId = String(data.default_workspace_id || workspaceId);
|
|
||||||
await this.fetchHostWorkspaces();
|
|
||||||
this.uiPushToast({
|
|
||||||
title: `已设为默认${kindLabel}`,
|
|
||||||
message: (this.hostWorkspaces || []).find((w: any) => w.workspace_id === workspaceId)?.label || workspaceId,
|
|
||||||
type: 'success'
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
const message = error instanceof Error ? error.message : String(error || `设置默认${kindLabel}失败`);
|
|
||||||
this.hostWorkspaceCreateError = message;
|
|
||||||
this.uiPushToast({
|
|
||||||
title: `设置默认${kindLabel}失败`,
|
|
||||||
message,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
this.hostWorkspaceManageSubmitting = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchTodoList() {
|
fetchTodoList() {
|
||||||
return this.fileFetchTodoList();
|
return this.fileFetchTodoList();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -597,11 +597,6 @@ const slashHighlightTop = ref(4);
|
|||||||
/** 手动滚动后切到鼠标 hover 模式,隐藏 JS 高亮条 */
|
/** 手动滚动后切到鼠标 hover 模式,隐藏 JS 高亮条 */
|
||||||
const slashManualScrolled = ref(false);
|
const slashManualScrolled = ref(false);
|
||||||
let slashAnimId: number | null = null;
|
let slashAnimId: number | null = null;
|
||||||
/** 动画真正进行中(比 animId 更可靠,可覆盖 rAF 结束后的延迟 scroll 事件) */
|
|
||||||
let slashAnimating = false;
|
|
||||||
/** 记录最近一次动画的目标 scrollTop 和结束时间,用于过滤动画后的延迟 scroll 事件 */
|
|
||||||
let lastSlashAnimatedScroll: number | null = null;
|
|
||||||
let lastSlashAnimationEndTime = 0;
|
|
||||||
|
|
||||||
const slashHighlightStyle = computed(() => {
|
const slashHighlightStyle = computed(() => {
|
||||||
if (slashManualScrolled.value) return { display: 'none' };
|
if (slashManualScrolled.value) return { display: 'none' };
|
||||||
@ -620,8 +615,6 @@ const animateSlash = (element: HTMLElement, targetScroll: number) => {
|
|||||||
if (interrupted) {
|
if (interrupted) {
|
||||||
cancelAnimationFrame(slashAnimId);
|
cancelAnimationFrame(slashAnimId);
|
||||||
}
|
}
|
||||||
slashAnimating = true;
|
|
||||||
lastSlashAnimatedScroll = targetScroll;
|
|
||||||
const startScroll = element.scrollTop;
|
const startScroll = element.scrollTop;
|
||||||
const startHighlight = slashHighlightTop.value;
|
const startHighlight = slashHighlightTop.value;
|
||||||
const distScroll = targetScroll - startScroll;
|
const distScroll = targetScroll - startScroll;
|
||||||
@ -631,8 +624,7 @@ const animateSlash = (element: HTMLElement, targetScroll: number) => {
|
|||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
|
|
||||||
const step = (now: number) => {
|
const step = (now: number) => {
|
||||||
// rAF 第一帧的 now 偶尔会早于 startTime,导致 progress 为负、scrollTop 反向抖动
|
const elapsed = now - startTime;
|
||||||
const elapsed = Math.max(0, now - startTime);
|
|
||||||
const progress = Math.min(elapsed / duration, 1);
|
const progress = Math.min(elapsed / duration, 1);
|
||||||
const eased = 1 - Math.pow(1 - progress, 3);
|
const eased = 1 - Math.pow(1 - progress, 3);
|
||||||
element.scrollTop = startScroll + distScroll * eased;
|
element.scrollTop = startScroll + distScroll * eased;
|
||||||
@ -641,8 +633,6 @@ const animateSlash = (element: HTMLElement, targetScroll: number) => {
|
|||||||
slashAnimId = requestAnimationFrame(step);
|
slashAnimId = requestAnimationFrame(step);
|
||||||
} else {
|
} else {
|
||||||
slashAnimId = null;
|
slashAnimId = null;
|
||||||
slashAnimating = false;
|
|
||||||
lastSlashAnimationEndTime = performance.now();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
slashAnimId = requestAnimationFrame(step);
|
slashAnimId = requestAnimationFrame(step);
|
||||||
@ -650,18 +640,7 @@ const animateSlash = (element: HTMLElement, targetScroll: number) => {
|
|||||||
|
|
||||||
/** 手动滚动时切到鼠标 hover 模式,不再跟踪键盘选中 */
|
/** 手动滚动时切到鼠标 hover 模式,不再跟踪键盘选中 */
|
||||||
const onSlashScroll = () => {
|
const onSlashScroll = () => {
|
||||||
if (slashAnimating || slashAnimId !== null) return;
|
if (slashAnimId !== null) return;
|
||||||
const list = skillSlashList.value;
|
|
||||||
if (!list) return;
|
|
||||||
const now = performance.now();
|
|
||||||
// 动画结束后的延迟 scroll 事件:scrollTop 与动画目标一致,说明是动画尾声,忽略
|
|
||||||
if (
|
|
||||||
lastSlashAnimatedScroll !== null &&
|
|
||||||
now - lastSlashAnimationEndTime < 150 &&
|
|
||||||
Math.abs(list.scrollTop - lastSlashAnimatedScroll) < 1
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
slashManualScrolled.value = true;
|
slashManualScrolled.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -787,9 +766,6 @@ const closeSlashMenu = () => {
|
|||||||
cancelAnimationFrame(slashAnimId);
|
cancelAnimationFrame(slashAnimId);
|
||||||
slashAnimId = null;
|
slashAnimId = null;
|
||||||
}
|
}
|
||||||
slashAnimating = false;
|
|
||||||
lastSlashAnimatedScroll = null;
|
|
||||||
lastSlashAnimationEndTime = 0;
|
|
||||||
skillSlashOpen.value = false;
|
skillSlashOpen.value = false;
|
||||||
skillSlashQuery.value = '';
|
skillSlashQuery.value = '';
|
||||||
skillSlashActiveIndex.value = 0;
|
skillSlashActiveIndex.value = 0;
|
||||||
|
|||||||
@ -77,24 +77,12 @@
|
|||||||
{{ item.path || '(未配置路径)' }}
|
{{ item.path || '(未配置路径)' }}
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-manage-badges">
|
<div class="workspace-manage-badges">
|
||||||
<span v-if="item.workspace_id === defaultWorkspaceId" class="workspace-manage-badge default">
|
|
||||||
默认
|
|
||||||
</span>
|
|
||||||
<span v-if="Number(item.running_task_count || 0) > 0" class="workspace-manage-badge running">
|
<span v-if="Number(item.running_task_count || 0) > 0" class="workspace-manage-badge running">
|
||||||
运行中
|
运行中
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-manage-actions">
|
<div class="workspace-manage-actions">
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="host-workspace-dialog__btn ghost"
|
|
||||||
:class="{ active: item.workspace_id === defaultWorkspaceId }"
|
|
||||||
:disabled="busy || item.workspace_id === defaultWorkspaceId"
|
|
||||||
@click="submitSetDefault(item)"
|
|
||||||
>
|
|
||||||
{{ item.workspace_id === defaultWorkspaceId ? '已默认' : '设为默认' }}
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="host-workspace-dialog__btn ghost"
|
class="host-workspace-dialog__btn ghost"
|
||||||
@ -152,7 +140,6 @@ const emits = defineEmits<{
|
|||||||
(event: 'create', payload: { path: string; label: string }): void;
|
(event: 'create', payload: { path: string; label: string }): void;
|
||||||
(event: 'rename', payload: { workspace_id: string; label: string }): void;
|
(event: 'rename', payload: { workspace_id: string; label: string }): void;
|
||||||
(event: 'delete', item: WorkspaceItem): void;
|
(event: 'delete', item: WorkspaceItem): void;
|
||||||
(event: 'setDefault', payload: { workspace_id: string }): void;
|
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const workspaceKind = props.workspaceKind || 'workspace';
|
const workspaceKind = props.workspaceKind || 'workspace';
|
||||||
@ -203,11 +190,6 @@ const submitRename = (item: WorkspaceItem) => {
|
|||||||
label: String(renameDrafts[item.workspace_id] ?? '').trim()
|
label: String(renameDrafts[item.workspace_id] ?? '').trim()
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitSetDefault = (item: WorkspaceItem) => {
|
|
||||||
if (item.workspace_id === props.defaultWorkspaceId) return;
|
|
||||||
emits('setDefault', { workspace_id: item.workspace_id });
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -386,11 +368,6 @@ const submitSetDefault = (item: WorkspaceItem) => {
|
|||||||
color: var(--claude-accent);
|
color: var(--claude-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-manage-badge.default {
|
|
||||||
color: var(--state-success);
|
|
||||||
border-color: color-mix(in srgb, var(--state-success) 45%, var(--theme-control-border-strong));
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-manage-empty {
|
.workspace-manage-empty {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--claude-text-secondary);
|
color: var(--claude-text-secondary);
|
||||||
|
|||||||
@ -222,12 +222,6 @@
|
|||||||
<div class="host-workspace-card-head">
|
<div class="host-workspace-card-head">
|
||||||
<span class="host-workspace-label">{{ item.label }}</span>
|
<span class="host-workspace-label">{{ item.label }}</span>
|
||||||
<span class="host-workspace-badges">
|
<span class="host-workspace-badges">
|
||||||
<span
|
|
||||||
v-if="item.workspace_id === hostWorkspaceDefaultId"
|
|
||||||
class="host-workspace-badge default"
|
|
||||||
>
|
|
||||||
默认
|
|
||||||
</span>
|
|
||||||
<span
|
<span
|
||||||
v-if="Number(item.running_task_count || 0) > 0"
|
v-if="Number(item.running_task_count || 0) > 0"
|
||||||
class="host-workspace-badge running"
|
class="host-workspace-badge running"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user