优化个人中心与界面交互

This commit is contained in:
Cheng Zhou
2026-06-11 09:09:17 +08:00
parent 84e55af8fe
commit b7484c8e01
15 changed files with 572 additions and 141 deletions
+63 -1
View File
@@ -204,6 +204,25 @@
</el-main>
</el-container>
</el-container>
<el-dialog
v-model="profileDialogVisible"
class="profile-settings-dialog"
:show-close="false"
:close-on-click-modal="false"
:close-on-press-escape="false"
:before-close="beforeProfileDialogClose"
width="980px"
align-center
destroy-on-close
@closed="profileDialogDirty = false"
>
<ProfileSettings
@close-request="requestProfileDialogClose"
@dirty-change="profileDialogDirty = $event"
@saved="profileDialogVisible = false"
/>
</el-dialog>
</template>
<script setup lang="ts">
@@ -221,6 +240,7 @@ import {
import { ElMessage, ElMessageBox } from "element-plus";
import { getProjectRoutePermission, hasProjectPermission, projectRouteLandingPaths } from "../utils/projectRoutePermissions";
import { forceLogout, LOGOUT_REASON_MANUAL } from "../session/sessionManager";
import ProfileSettings from "../views/ProfileSettings.vue";
const auth = useAuthStore();
const study = useStudyStore();
@@ -235,6 +255,8 @@ const canAccessProjectPath = (path: string) =>
const canAccessAnyProjectPath = (paths: string[]) => paths.some((path) => canAccessProjectPath(path));
const hasAnyProjectModuleAccess = computed(() => canAccessAnyProjectPath(projectRouteLandingPaths));
const loggingOut = ref(false);
const profileDialogVisible = ref(false);
const profileDialogDirty = ref(false);
/* 动态设置 body 上的侧边栏宽度 CSS 变量,用于全局弹窗定位偏移 */
const hasSidebar = computed(() => isAdmin.value || !!study.currentStudy);
@@ -480,7 +502,33 @@ const onCommand = (cmd: string) => {
if (cmd === "logout") {
onLogout();
} else if (cmd === "profile") {
router.push("/profile");
profileDialogVisible.value = true;
}
};
const confirmDiscardProfileChanges = async () => {
if (!profileDialogDirty.value) return true;
const confirmed = await ElMessageBox.confirm(
"当前修改尚未保存,关闭后将丢失这些改动。",
"确认关闭个人中心",
{
type: "warning",
confirmButtonText: "关闭",
cancelButtonText: "继续编辑",
}
).catch(() => false);
return Boolean(confirmed);
};
const requestProfileDialogClose = async () => {
if (await confirmDiscardProfileChanges()) {
profileDialogVisible.value = false;
}
};
const beforeProfileDialogClose = async (done: () => void) => {
if (await confirmDiscardProfileChanges()) {
done();
}
};
</script>
@@ -931,4 +979,18 @@ const onCommand = (cmd: string) => {
background-color: var(--el-color-primary-light-9);
font-weight: 600;
}
:global(.profile-settings-dialog) {
border-radius: 16px;
overflow: hidden;
box-shadow: 0 24px 70px rgba(15, 23, 42, 0.26);
}
:global(.profile-settings-dialog .el-dialog__header) {
display: none;
}
:global(.profile-settings-dialog .el-dialog__body) {
padding: 0;
}
</style>
@@ -52,4 +52,19 @@ describe("Layout breadcrumbs", () => {
expect(source).toContain("forceLogout(LOGOUT_REASON_MANUAL)");
expect(source).toContain('loggingOut ? "正在退出..." : TEXT.menu.logout');
});
it("opens profile settings as a desktop-style dialog instead of navigating away", () => {
const source = readLayout();
expect(source).toContain('v-model="profileDialogVisible"');
expect(source).toContain('class="profile-settings-dialog"');
expect(source).toContain(':close-on-click-modal="false"');
expect(source).toContain(':close-on-press-escape="false"');
expect(source).toContain('@close-request="requestProfileDialogClose"');
expect(source).toContain('@dirty-change="profileDialogDirty = $event"');
expect(source).toContain('@saved="profileDialogVisible = false"');
expect(source).toContain("profileDialogVisible.value = true");
expect(source).toContain("confirmDiscardProfileChanges");
expect(source).not.toContain('router.push("/profile")');
});
});