feat: streamline mobile project switching

This commit is contained in:
kris
2026-03-30 12:51:37 +08:00
parent 794de0133e
commit ee39fbfaa0
2 changed files with 43 additions and 22 deletions

View File

@@ -3164,6 +3164,26 @@ function buildDashboardHomeModel() {
};
}
async function applySelectedProject(projectId = "") {
appState.selectedProjectId = projectId || "";
setBusy(true, "正在切换项目视图...");
try {
if (backendSupports("/v2/storage/status")) {
await loadStorageStatus(appState.selectedProjectId || "");
} else {
appState.storageStatus = null;
}
await loadAgentControlSurfaces(appState.selectedProjectId || "");
if (appState.selectedOnelinerSessionId) {
await loadOneLinerMessages(appState.selectedOnelinerSessionId);
}
} finally {
setBusy(false, "");
}
rememberAction("当前项目已切换", `已切换到「${getSelectedProject()?.name || "所选项目"}你现在看到的首页、Agent 和任务都会跟随更新。`, "green");
renderAll();
}
function openDashboardProjectSwitcher() {
const options = getProjectOptions();
if (!options.length) {
@@ -3225,36 +3245,29 @@ function openDashboardProjectSwitcher() {
},
{ name: "projectId", label: "当前项目", type: "select", value: getSelectedProject()?.id || "", options }
],
onOpen: ({ fields }) => {
onOpen: ({ fields, submit }) => {
const select = fields.querySelector('[data-action-field="projectId"]');
const isMobileViewport = typeof window !== "undefined" && window.matchMedia?.("(max-width: 760px)")?.matches;
if (submit && isMobileViewport) {
submit.hidden = true;
}
fields.querySelectorAll("[data-project-choice]").forEach((button) => {
button.addEventListener("click", () => {
button.addEventListener("click", async () => {
const nextProjectId = button.dataset.projectChoice || "";
if (select) {
select.value = button.dataset.projectChoice || "";
select.value = nextProjectId;
}
fields.querySelectorAll("[data-project-choice]").forEach((item) => item.classList.remove("active"));
button.classList.add("active");
if (isMobileViewport && nextProjectId) {
closeActionModal();
await applySelectedProject(nextProjectId);
}
});
});
},
onSubmit: async (payload) => {
appState.selectedProjectId = payload.projectId || "";
setBusy(true, "正在切换项目视图...");
try {
if (backendSupports("/v2/storage/status")) {
await loadStorageStatus(appState.selectedProjectId || "");
} else {
appState.storageStatus = null;
}
await loadAgentControlSurfaces(appState.selectedProjectId || "");
if (appState.selectedOnelinerSessionId) {
await loadOneLinerMessages(appState.selectedOnelinerSessionId);
}
} finally {
setBusy(false, "");
}
rememberAction("当前项目已切换", `已切换到「${getSelectedProject()?.name || "所选项目"}你现在看到的首页、Agent 和任务都会跟随更新。`, "green");
renderAll();
await applySelectedProject(payload.projectId || "");
}
});
}
@@ -7050,7 +7063,7 @@ function renderSettingsScreen() {
return screenShell(
"设置",
"这里不放系统治理内容,只处理当前用户需要理解的连接、界面和帮助信息。",
`${button("连接状态", "open-auth")} ${isSuperAdmin() ? button("管理员配置台", "goto-admin-workbench", "primary") : button("刷新", "refresh-data", "primary")}`,
`${isSuperAdmin() ? button("管理员配置台", "goto-admin-workbench", "primary") : button("刷新", "refresh-data", "primary")}`,
`
<div class="hero-card mobile-secondary-card">
<h3>设置与帮助</h3>

View File

@@ -116,10 +116,18 @@ test("project creation and switching use in-app sheets instead of browser prompt
test("mobile project sheets support direct project picking and zoom-safe form controls", () => {
const projectSwitcher = extractBetween(APP, "function openDashboardProjectSwitcher()", "function openDashboardActionReasonAction(");
const applySelectedProject = extractBetween(APP, "async function applySelectedProject(projectId = \"\")", "function openDashboardProjectSwitcher()");
const createProject = extractBetween(APP, "async function createProject()", "function openPreferredModelAction()");
assert.match(APP, /async function applySelectedProject\(projectId = ""\)/);
assert.match(projectSwitcher, /data-project-choice=/);
assert.match(projectSwitcher, /onOpen:\s*\(/);
assert.match(projectSwitcher, /select\.value = button\.dataset\.projectChoice/);
assert.match(projectSwitcher, /select\.value = nextProjectId/);
assert.match(projectSwitcher, /window\.matchMedia\?\.\("\(max-width: 760px\)"\)\?\.matches/);
assert.match(projectSwitcher, /submit\.hidden = true/);
assert.match(projectSwitcher, /closeActionModal\(\);/);
assert.match(projectSwitcher, /await applySelectedProject\(nextProjectId\);/);
assert.match(applySelectedProject, /loadStorageStatus\(appState\.selectedProjectId \|\| ""\)/);
assert.match(applySelectedProject, /loadAgentControlSurfaces\(appState\.selectedProjectId \|\| ""\)/);
assert.match(createProject, /onOpen:\s*\(/);
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.field-stack input,\s*[\s\S]*\.field-stack textarea,\s*[\s\S]*\.field-stack select\s*\{[\s\S]*min-height:\s*46px/);
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.field-stack input,\s*[\s\S]*\.field-stack textarea,\s*[\s\S]*\.field-stack select\s*\{[\s\S]*font-size:\s*16px/);