Files
workspace/code/fms/fms-vue/src/views/login/index.vue
T
2026-07-24 17:34:26 +08:00

319 lines
7.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { onMounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Building2, LockKeyhole, LogIn, MapPin, Ship, Truck, UserRound } from "@lucide/vue";
import { App } from "antdv-next";
import { loginApi } from "@/services/api";
import { useAuthStore } from "@/stores/auth";
const LOGIN_IDENTITY_KEY = "fms-login-identity";
function readRememberedIdentity() {
try {
const identity = JSON.parse(localStorage.getItem(LOGIN_IDENTITY_KEY) || "{}");
return {
orgId: typeof identity.orgId === "string" ? identity.orgId : "",
userId: typeof identity.userId === "string" ? identity.userId : "",
};
} catch {
return { orgId: "", userId: "" };
}
}
function rememberIdentity(orgId, userId) {
try {
localStorage.setItem(LOGIN_IDENTITY_KEY, JSON.stringify({ orgId, userId }));
} catch {
// Login remains available when browser storage is restricted.
}
}
const route = useRoute();
const router = useRouter();
const authStore = useAuthStore();
const { message } = App.useApp();
const loading = ref(false);
const rememberedIdentity = readRememberedIdentity();
const form = reactive({
orgId: rememberedIdentity.orgId,
userId: rememberedIdentity.userId,
password: "",
});
onMounted(() => {
if (route.query.reason !== "session-replaced") return;
message.warning("账号已在其他设备登录,请重新登录");
const query = { ...route.query };
delete query.reason;
router.replace({ name: "login", query });
});
function getRedirectPath() {
const redirect = route.query.redirect;
return typeof redirect === "string" && redirect.startsWith("/") && !redirect.startsWith("//")
? redirect
: "/dashboard";
}
async function submit() {
if (loading.value) return;
loading.value = true;
try {
const response = await loginApi(form.orgId, form.userId, form.password);
const session = response.data;
authStore.setSession(session, form.orgId);
rememberIdentity(form.orgId, form.userId);
message.success("登录成功,正在进入工作台");
await router.replace(getRedirectPath());
} catch (error) {
message.error(error.message || "登录失败");
} finally {
loading.value = false;
}
}
</script>
<template>
<main class="login-page">
<section class="login-shell" aria-label="FMS 货代物流管理系统登录">
<aside class="login-overview" aria-label="货代物流业务概览">
<div class="overview-brand">
<div class="overview-brand__mark"><Ship /></div>
<div class="overview-brand__copy">
<strong>FMS</strong>
<span>Freight &amp; Logistics</span>
</div>
</div>
<div class="overview-copy">
<span class="overview-copy__eyebrow">货代 · 物流 · 费用一体化</span>
<h1>货代物流管理系统</h1>
<p>贯通订舱委托、运输跟踪与费用结算,让每一票业务清晰可控。</p>
</div>
<div class="overview-scope">
<span><Ship /> 海运订舱</span>
<span><Truck /> 运输跟踪</span>
<span><MapPin /> 业务协同</span>
</div>
</aside>
<div class="login-panel">
<header class="login-heading">
<span>业务工作台</span>
<h2>账号登录</h2>
<p>欢迎回来,请使用您的业务账号登录</p>
</header>
<a-form layout="vertical" :model="form" @finish="submit">
<a-form-item
label="机构码"
name="orgId"
:rules="[{ required: true, message: '请输入机构码' }]"
>
<a-input
v-model:value="form.orgId"
size="large"
autocomplete="organization"
placeholder="请输入机构码"
autofocus
>
<template #prefix><Building2 /></template>
</a-input>
</a-form-item>
<a-form-item
label="账号"
name="userId"
:rules="[{ required: true, message: '请输入账号' }]"
>
<a-input
v-model:value="form.userId"
size="large"
autocomplete="username"
placeholder="请输入账号"
>
<template #prefix><UserRound /></template>
</a-input>
</a-form-item>
<a-form-item label="密码" name="password">
<a-input-password
v-model:value="form.password"
size="large"
autocomplete="current-password"
placeholder="请输入密码"
>
<template #prefix><LockKeyhole /></template>
</a-input-password>
</a-form-item>
<a-button type="primary" html-type="submit" size="large" block :loading="loading">
<template #icon><LogIn /></template>
登录
</a-button>
</a-form>
<footer class="login-footer">FMS · 货代 / 物流管理系统</footer>
</div>
</section>
</main>
</template>
<style lang="scss" scoped>
.login-page {
display: grid;
width: 100%;
height: 100%;
overflow: auto;
place-items: center;
padding: 32px;
background: #edf1f3;
}
.login-shell {
display: grid;
width: 880px;
min-height: 520px;
grid-template-columns: 380px 500px;
overflow: hidden;
border: 1px solid #d5dee2;
border-radius: 8px;
background: #fff;
box-shadow: 0 18px 48px rgb(25 47 55 / 12%);
}
.login-overview {
display: flex;
flex-direction: column;
padding: 42px;
color: #f5faf9;
background: #173a42;
}
.overview-brand {
display: flex;
align-items: center;
gap: 11px;
&__mark {
display: grid;
width: 38px;
height: 38px;
place-items: center;
border: 1px solid rgb(255 255 255 / 20%);
border-radius: 6px;
background: #15998e;
color: #fff;
:deep(.lucide) {
width: 18px;
height: 18px;
}
}
&__copy {
display: flex;
min-width: 0;
flex-direction: column;
strong {
font-size: 15px;
line-height: 1.2;
}
span {
margin-top: 3px;
color: #a9c0c2;
font-size: 10px;
letter-spacing: 0;
}
}
}
.overview-copy {
margin-top: 72px;
&__eyebrow {
display: block;
margin-bottom: 10px;
color: #5dd2c5;
font-size: 12px;
font-weight: 600;
}
h1 {
margin: 0;
color: #fff;
font-size: 28px;
line-height: 1.35;
}
p {
margin: 14px 0 0;
color: #c4d4d5;
font-size: 13px;
line-height: 1.75;
}
}
.overview-scope {
display: flex;
margin-top: auto;
flex-direction: column;
gap: 13px;
color: #b8cbcc;
font-size: 12px;
span {
display: flex;
align-items: center;
gap: 9px;
}
:deep(.lucide) {
width: 14px;
height: 14px;
color: #62c8be;
}
}
.login-panel {
display: flex;
flex-direction: column;
padding: 58px 60px 30px;
background: #fff;
}
.login-heading {
margin-bottom: 30px;
span {
color: #168f86;
font-size: 11px;
font-weight: 700;
}
h2 {
margin: 8px 0 0;
color: #19323a;
font-size: 25px;
line-height: 1.3;
}
p {
margin: 8px 0 0;
color: #738287;
font-size: 13px;
}
}
.login-footer {
margin-top: auto;
padding-top: 26px;
color: #8c999d;
font-size: 11px;
text-align: center;
}
</style>