提交 02441cdb authored 作者: yanpeng's avatar yanpeng

Merge branch 'pre' of http://8.140.26.4:10003/caijian/risk-monitor into yp-dev

流水线 #376 已通过 于阶段
in 1 分 45 秒
import { createRouter, createWebHistory } from "vue-router";
import { getIsLoggedIn } from "@/utils/auth";
const Home = () => import('@/views/home/index.vue')
const DataLibrary = () => import('@/views/dataLibrary/index.vue')
......@@ -21,6 +22,14 @@ const dataRoutes = Object.keys(datas).reduce((acc, path) => {
}, [])
const routes = [
{
path: "/login",
name: "Login",
component: () => import("@/views/login/index.vue"),
meta: {
title: "登录"
}
},
{
path: "/",
name: "Home",
......@@ -66,6 +75,19 @@ const router = createRouter({
// 路由守卫 - 设置页面标题
router.beforeEach((to, from, next) => {
// 登录态:同一次前端服务 BOOT_ID 内跨刷新/跨新标签有效;服务重启后自动失效
const isAuthed = getIsLoggedIn();
const isLoginRoute = to.name === "Login" || /^\/login\/?$/.test(String(to.path || ""));
if (!isLoginRoute && !isAuthed) {
next({
path: "/login",
query: { redirect: to.fullPath }
});
return;
}
if (to.meta.title) {
if (to.meta.dynamicTitle) {
console.log('to', to);
......
......@@ -4,7 +4,7 @@ const ZMOverview = () => import('@/views/ZMOverView/index.vue')
const ZMOverviewRoutes = [
{
path: "/",
redirect: "/ZMOverView"
redirect: "/login"
},
{
path: "/ZMOverView",
......
const STORAGE_LOGIN_FLAG = "isLoggedIn";
const STORAGE_LOGIN_BOOT_ID = "loginBootId";
const getBootId = () => {
// 由 vite.config.js 的 define 注入:重启 dev server 会变化
try {
// eslint-disable-next-line no-undef
return String(__APP_BOOT_ID__ || "");
} catch (_) {
return "";
}
};
export const getIsLoggedIn = () => {
try {
const flag = window.localStorage.getItem(STORAGE_LOGIN_FLAG) === "1";
const savedBootId = window.localStorage.getItem(STORAGE_LOGIN_BOOT_ID) || "";
return flag && savedBootId && savedBootId === getBootId();
} catch (_) {
return false;
}
};
export const setIsLoggedIn = (val) => {
try {
if (val) {
window.localStorage.setItem(STORAGE_LOGIN_FLAG, "1");
window.localStorage.setItem(STORAGE_LOGIN_BOOT_ID, getBootId());
} else {
window.localStorage.removeItem(STORAGE_LOGIN_FLAG);
window.localStorage.removeItem(STORAGE_LOGIN_BOOT_ID);
}
} catch (_) {
// ignore
}
};
<template>
<div class="login-page">
<div class="login-container">
<div class="left">
<img src="./image/login-left.png" alt="">
<div class="img-shield">
<img src="./image/img-shield.png" alt="">
</div>
<div class="img-lock">
<img src="./image/img-lock.png" alt="">
</div>
<div class="img-network">
<img src="./image/img-network.png" alt="">
</div>
<div class="img-science">
<img src="./image/img-science.png" alt="">
</div>
<div class="left-text">{{ "某方向风险监测预警系统" }}</div>
</div>
<div class="right">
<div class="right-text-box">
<div class="right-text-left-img"><img src="./image/blue-right.png" alt=""></div>
<div class="right-text">{{ "用户登录" }}</div>
<div class="right-text-right-img"><img src="./image/blue-left.png" alt=""></div>
</div>
<div class="right-login-box">
<div class="user-input">
<el-input placeholder="请输入用户名" v-model="username" class="input">
<template #prefix>
<img src="./image/user-icon.png" alt="" class="user-icon">
</template>
</el-input>
</div>
<div class="keyword-input">
<el-input placeholder="请输入密码" v-model="password" class="input" type="password" show-password>
<template #prefix>
<img src="./image/keyword-icon.png" alt="" class="keyword-icon">
</template>
</el-input>
</div>
<div class="login-footer">
<div class="check">
<el-checkbox v-model="checked" label="记住我" style="height: 24px;" />
</div>
<div class="login-footer-text" @click="handleDevelopingTip">{{ "忘记密码?" }}</div>
</div>
</div>
<div class="right-btn-box">
<div class="right-login-btn" @click="handleLogin">
<div class="login-btn-text">{{ "登&nbsp;&nbsp;录" }}</div>
</div>
<div class="right-register-btn" @click="handleDevelopingTip">
<div class="register-btn-text">{{ "没有帐户?去注册&nbsp;&nbsp;&nbsp;&nbsp; " }}</div>
<div class="right-arrow"><img src="./image/right-arrow.png" alt=""></div>
</div>
</div>
</div>
<div class="footer">
{{ "中华人民共和国某某某某部&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; © 2015 京ICP备05022684号 " }}
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch, onMounted } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { setIsLoggedIn } from "@/utils/auth";
const router = useRouter();
const username = ref("");
const password = ref("");
const checked = ref(false);
const STORAGE_REMEMBER = "loginRememberMe";
const STORAGE_USERNAME = "loginSavedUsername";
const STORAGE_PASSWORD = "loginSavedPassword";
const clearLoginRememberStorage = () => {
try {
window.localStorage.removeItem(STORAGE_REMEMBER);
window.localStorage.removeItem(STORAGE_USERNAME);
window.localStorage.removeItem(STORAGE_PASSWORD);
} catch (_) {
// ignore
}
};
const saveLoginRemember = (u, p) => {
try {
window.localStorage.setItem(STORAGE_REMEMBER, "1");
window.localStorage.setItem(STORAGE_USERNAME, u);
window.localStorage.setItem(STORAGE_PASSWORD, p);
} catch (_) {
// ignore
}
};
const loadLoginRemember = () => {
try {
if (window.localStorage.getItem(STORAGE_REMEMBER) !== "1") {
checked.value = false;
username.value = "";
password.value = "";
return;
}
checked.value = true;
username.value = window.localStorage.getItem(STORAGE_USERNAME) || "";
password.value = window.localStorage.getItem(STORAGE_PASSWORD) || "";
} catch (_) {
checked.value = false;
username.value = "";
password.value = "";
}
};
onMounted(() => {
loadLoginRemember();
});
/** 取消记住我:清空本地存储与输入框,刷新/再次进入也为空 */
watch(checked, (val) => {
if (!val) {
clearLoginRememberStorage();
username.value = "";
password.value = "";
}
});
const DEVELOPING_TIP = "当前功能正在开发中,敬请期待!";
const handleDevelopingTip = () => {
ElMessage.warning(DEVELOPING_TIP);
};
/** 当前页跳转到概览页(需先校验用户名、密码) */
const handleLogin = () => {
const u = String(username.value ?? "").trim();
const p = String(password.value ?? "").trim();
if (!u && !p) {
ElMessage.warning("请填写用户名与密码");
return;
}
if (!u) {
ElMessage.warning("请填写用户名");
return;
}
if (!p) {
ElMessage.warning("请填写密码");
return;
}
// 临时账号校验(后续可替换为真实登录接口)
const VALID_USERNAME = "admin";
const VALID_PASSWORD = "qa5okm,.09!$~";
if (u !== VALID_USERNAME || p !== VALID_PASSWORD) {
ElMessage.error("用户名或密码错误");
return;
}
setIsLoggedIn(true);
if (checked.value) {
saveLoginRemember(u, p);
} else {
clearLoginRememberStorage();
}
const redirect = (() => {
try {
const url = new URL(window.location.href);
return url.searchParams.get("redirect") || "";
} catch (_) {
return "";
}
})();
router.push(redirect || { name: "ZMOverView" });
};
</script>
<style scoped lang="scss">
.login-page {
/* 允许纵向滚动,禁止横向滚动(放大时避免横向滚动条) */
width: 100vw;
min-height: 100vh;
overflow-x: hidden;
overflow-y: auto;
/* 背景色(图片加载不出来时兜底) */
background: rgb(5, 95, 194);
/* 仅保留原有背景配置,背景实际由 fixed 层承载,避免下拉露白 */
background-image: url("./image/login-backgroud.png");
margin: 0;
padding: 0;
box-sizing: border-box;
display: flex;
/* 固定背景层:页面纵向滚动时不出现空白,100% 时视觉保持一致 */
&::before {
content: "";
position: fixed;
inset: 0;
z-index: -1;
background: rgb(5, 95, 194);
background-image: url("./image/login-backgroud.png");
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
.login-container {
display: flex;
position: relative;
/* 放大后如超出仅纵向滚动,不产生横向滚动条 */
max-width: 100%;
.left {
width: 1008px;
height: 1008px;
left: 52px;
top: 40px;
position: relative;
display: flex;
img {
width: 100%;
height: 100%;
display: block;
}
.img-shield {
position: absolute;
top: 301px;
left: 322px;
width: 363px;
height: 405px;
img {
width: 100%;
height: 100%;
display: block;
}
}
.img-lock {
position: absolute;
top: 589px;
left: 119px;
width: 64px;
height: 64px;
img {
width: 100%;
height: 100%;
display: block;
}
}
.img-network {
position: absolute;
top: 178px;
left: 275px;
width: 45px;
height: 45px;
img {
width: 100%;
height: 100%;
display: block;
}
}
.img-science {
position: absolute;
top: 752px;
left: 685px;
width: 94px;
height: 94px;
img {
width: 100%;
height: 100%;
display: block;
}
}
.left-text {
position: absolute;
top: 458px;
left: 199px;
height: 81px;
color: rgb(246, 250, 255);
font-family: "YouSheBiaoTiHei";
font-weight: 400;
font-size: 60px;
line-height: 78px;
letter-spacing: 0px;
text-align: left;
}
}
.right {
position: absolute;
width: 520px;
height: 580px;
left: 1148px;
top: 250px;
background-color: rgb(255, 255, 255);
border: 1px solid rgb(255, 255, 255);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
.right-text-box {
position: absolute;
top: 66px;
left: 139px;
display: flex;
width: 205px;
height: 47px;
.right-text-left-img {
margin-right: auto;
width: 16px !important;
height: 16px !important;
display: block;
margin-top: 15.5px;
img {
width: 16px !important;
height: 16px !important;
object-fit: cover;
/* 不变形 */
display: block;
}
}
.right-text {
font-family: "YouSheBiaoTiHei";
font-weight: 400;
font-size: 36px;
line-height: 47px;
letter-spacing: 0px;
text-align: center;
}
.right-text-right-img {
margin-left: auto;
width: 16px !important;
height: 16px !important;
display: block;
margin-top: 15.5px;
img {
width: 16px !important;
height: 16px !important;
object-fit: cover;
/* 不变形 */
display: block;
}
}
}
.right-login-box {
position: absolute;
top: 177px;
left: 50%;
transform: translateX(-50%);
width: 367px;
height: 140px;
display: flex;
flex-direction: column;
.user-input {
width: 360px;
height: 42px;
.input {
width: 100%;
height: 100%;
border-radius: 4px;
border: 1px solid rgb(230, 231, 232);
background-color: rgb(255, 255, 255);
box-sizing: border-box;
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0;
color: rgb(132, 136, 142);
}
.user-icon {
width: 16px;
height: 16px;
object-fit: cover;
/* 不变形 */
display: block;
}
}
.keyword-input {
width: 360px;
height: 42px;
margin-top: 16px;
.input {
width: 100%;
height: 100%;
border-radius: 4px;
border: 1px solid rgb(230, 231, 232);
background-color: rgb(255, 255, 255);
box-sizing: border-box;
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0;
color: rgb(132, 136, 142);
}
.keyword-icon {
width: 16px;
height: 16px;
object-fit: cover;
/* 不变形 */
display: block;
}
}
.login-footer {
width: 367px;
height: 24px;
margin-top: 16px;
justify-content: space-between;
display: flex;
.check {
:deep(.el-checkbox__label) {
font-family: "Source Han Sans CN", sans-serif;
font-weight: 400;
/* Regular */
font-size: 16px;
/* 你要的字号 */
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
color: rgb(132, 136, 142);
/* 两端对齐 */
}
:deep(.el-checkbox__inner) {
align-items: center;
margin-top: 3px;
width: 14px;
height: 14px;
display: block;
border-radius: 4px;
}
}
.login-footer-text {
color: rgb(5, 95, 194);
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
cursor: pointer;
}
}
}
.right-btn-box {
position: absolute;
width: 360px;
height: 100px;
left: 50%;
transform: translateX(-50%);
top: 363px;
display: flex;
flex-direction: column;
gap: 16px;
.right-login-btn {
width: 360px;
height: 42px;
background-color: rgb(5, 95, 194);
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
.login-btn-text {
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0px;
text-align: center;
color: rgb(255, 255, 255);
}
}
.right-register-btn {
width: 360px;
height: 42px;
background-color: rgb(255, 255, 255);
border-radius: 4px;
cursor: pointer;
border: 1px solid rgb(230, 231, 232);
display: flex;
align-items: center;
position: relative;
.register-btn-text {
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0px;
text-align: center;
color: rgb(95, 101, 108);
margin: 0 auto;
}
.right-arrow {
width: 16px;
height: 19px;
position: absolute;
left: 243px;
img {
width: 100%;
height: 100%;
display: block;
}
}
}
}
}
.footer {
position: absolute;
bottom: 21px;
left: 100%;
/* 关键:水平居中起点 */
transform: translateX(-50%);
/* 关键:完美居中 */
width: 375px;
color: rgba(255, 255, 255, 0.8);
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 14px;
line-height: 22px;
letter-spacing: 0;
text-align: center;
}
}
}
</style>
\ No newline at end of file
......@@ -207,7 +207,7 @@
<div class="thinkTank-view">
<div class="thinkTank-image">
<img src="../assets/images/rand-image.png" alt="" />
</div>
</div>
{{ `${domainName} ${consensusList.length + differenceList.length}条` }}
</div>
</div> -->
......@@ -1479,7 +1479,7 @@ onMounted(async () => {
.page-box {
margin-top: 21px;
align-items: center;
align-items: center;
justify-content: center;
display: flex;
}
......
......@@ -6,6 +6,10 @@ import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import { resolve } from 'path'
export default defineConfig({
// 每次重启前端服务(Vite)都会变化:用于登录态失效判断
define: {
__APP_BOOT_ID__: JSON.stringify(`${Date.now()}`),
},
plugins: [
vue(),
AutoImport({
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论