新增空闲锁屏、修复401等问题,新增文件版本管理、共享库等占位符
This commit is contained in:
@@ -54,6 +54,10 @@
|
||||
</template>
|
||||
<el-menu-item index="/drug/shipments">{{ TEXT.menu.drugShipments }}</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-menu-item index="/file-versions">
|
||||
<el-icon><Files /></el-icon>
|
||||
<span>{{ TEXT.menu.fileVersionManagement }}</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/startup/feasibility-ethics">
|
||||
<el-icon><Calendar /></el-icon>
|
||||
<span>{{ TEXT.menu.startupFeasibilityEthics }}</span>
|
||||
@@ -77,10 +81,12 @@
|
||||
<el-sub-menu index="knowledge">
|
||||
<template #title>
|
||||
<el-icon><Notebook /></el-icon>
|
||||
<span>{{ TEXT.menu.knowledge }}</span>
|
||||
<span>{{ TEXT.menu.sharedLibrary }}</span>
|
||||
</template>
|
||||
<el-menu-item index="/knowledge/medical-consult">{{ TEXT.menu.knowledgeMedicalConsult }}</el-menu-item>
|
||||
<el-menu-item index="/knowledge/notes">{{ TEXT.menu.knowledgeNotes }}</el-menu-item>
|
||||
<el-menu-item index="/knowledge/support-files">{{ TEXT.menu.knowledgeSupportFiles }}</el-menu-item>
|
||||
<el-menu-item index="/knowledge/instruction-files">{{ TEXT.menu.knowledgeInstructionFiles }}</el-menu-item>
|
||||
</el-sub-menu>
|
||||
</template>
|
||||
</el-menu>
|
||||
@@ -151,7 +157,7 @@ import StudySelector from "./StudySelector.vue";
|
||||
import { TEXT } from "../locales";
|
||||
import {
|
||||
Monitor, User, Suitcase, House, Calendar, Flag, ChatDotRound,
|
||||
CircleCheck, Box, Coin, Notebook, Document, ArrowDown, SwitchButton
|
||||
CircleCheck, Box, Coin, Notebook, Document, ArrowDown, SwitchButton, Files
|
||||
} from "@element-plus/icons-vue";
|
||||
|
||||
const auth = useAuthStore();
|
||||
@@ -167,6 +173,7 @@ const activeMenu = computed(() => {
|
||||
if (path.startsWith("/finance/contracts")) return "/finance/contracts";
|
||||
if (path.startsWith("/finance/special")) return "/finance/special";
|
||||
if (path.startsWith("/drug/shipments")) return "/drug/shipments";
|
||||
if (path.startsWith("/file-versions")) return "/file-versions";
|
||||
if (path.startsWith("/startup/feasibility") || path.startsWith("/startup/ethics")) return "/startup/feasibility-ethics";
|
||||
if (path.startsWith("/startup/meeting-auth") || path.startsWith("/startup/kickoff") || path.startsWith("/startup/training")) {
|
||||
return "/startup/meeting-auth";
|
||||
@@ -176,6 +183,8 @@ const activeMenu = computed(() => {
|
||||
if (path.startsWith("/audit")) return "/audit";
|
||||
if (path.startsWith("/knowledge/medical-consult")) return "/knowledge/medical-consult";
|
||||
if (path.startsWith("/knowledge/notes")) return "/knowledge/notes";
|
||||
if (path.startsWith("/knowledge/support-files")) return "/knowledge/support-files";
|
||||
if (path.startsWith("/knowledge/instruction-files")) return "/knowledge/instruction-files";
|
||||
if (path.startsWith("/projects/")) return "/admin/projects";
|
||||
if (path.startsWith("/admin/projects/")) return "/admin/projects";
|
||||
return path;
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<teleport to="body">
|
||||
<div class="lock-screen" role="dialog" aria-modal="true">
|
||||
<div class="lock-backdrop" />
|
||||
<div class="lock-panel">
|
||||
<h2 class="lock-title">已锁定</h2>
|
||||
<p class="lock-desc">30 分钟无操作,请输入密码解锁</p>
|
||||
<el-input
|
||||
ref="passwordInput"
|
||||
v-model="password"
|
||||
type="password"
|
||||
placeholder="请输入登录密码"
|
||||
show-password
|
||||
@keyup.enter="onUnlock"
|
||||
/>
|
||||
<div class="lock-actions">
|
||||
<el-button type="primary" :loading="loading" @click="onUnlock">解锁</el-button>
|
||||
<el-button @click="onLogout">退出登录</el-button>
|
||||
</div>
|
||||
<p v-if="errorMessage" class="lock-error">{{ errorMessage }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onMounted, ref, watch } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { useAuthStore } from "../store/auth";
|
||||
import { useSessionStore } from "../store/session";
|
||||
import { UNLOCK_MAX_ATTEMPTS, unlockWithPassword, forceLogout } from "../session/sessionManager";
|
||||
|
||||
const auth = useAuthStore();
|
||||
const session = useSessionStore();
|
||||
const password = ref("");
|
||||
const loading = ref(false);
|
||||
const errorMessage = ref("");
|
||||
const passwordInput = ref<{ focus?: () => void } | null>(null);
|
||||
|
||||
const focusPassword = () => {
|
||||
nextTick(() => {
|
||||
passwordInput.value?.focus?.();
|
||||
});
|
||||
};
|
||||
|
||||
const onUnlock = async () => {
|
||||
if (loading.value) return;
|
||||
errorMessage.value = "";
|
||||
const email = auth.user?.email;
|
||||
if (!email) {
|
||||
ElMessage.error("无法获取用户信息,请重新登录");
|
||||
forceLogout();
|
||||
return;
|
||||
}
|
||||
if (!password.value) {
|
||||
errorMessage.value = "请输入密码";
|
||||
focusPassword();
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
await unlockWithPassword(email, password.value);
|
||||
password.value = "";
|
||||
} catch (err: any) {
|
||||
session.incrementUnlockAttempt();
|
||||
const message = err?.response?.data?.detail || err?.response?.data?.message || "密码错误";
|
||||
errorMessage.value = message;
|
||||
if (session.unlockAttempts >= UNLOCK_MAX_ATTEMPTS) {
|
||||
ElMessage.error("错误次数过多,请重新登录");
|
||||
forceLogout();
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
focusPassword();
|
||||
}
|
||||
};
|
||||
|
||||
const onLogout = () => {
|
||||
forceLogout();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
focusPassword();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => session.locked,
|
||||
(value) => {
|
||||
if (value) {
|
||||
focusPassword();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.lock-screen {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 2000;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.lock-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(7, 10, 20, 0.35);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.lock-panel {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: min(420px, 92vw);
|
||||
background: #ffffff;
|
||||
border-radius: 16px;
|
||||
padding: 28px;
|
||||
box-shadow: 0 18px 48px rgba(15, 20, 35, 0.2);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.lock-title {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.lock-desc {
|
||||
margin: 0;
|
||||
color: #64748b;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.lock-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.lock-error {
|
||||
margin: 0;
|
||||
color: #d8342c;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user