<template>
<div>
<h3 text-center m-0 mb-20px>{{ t("login.login") }}</h3>
<el-form ref="loginFormRef" :model="loginFormData" :rules="loginRules" size="large"
:validate-on-rule-change="false">
<!-- 用户名 -->
<el-form-item prop="username">
<el-input v-model.trim="loginFormData.username" :placeholder="t('login.username')">
<template #prefix>
<el-icon>
<User />
</el-icon>
</template>
</el-input>
</el-form-item>
<!-- 密码 -->
<el-tooltip :visible="isCapsLock" :content="t('login.capsLock')" placement="right">
<el-form-item prop="password">
<el-input v-model.trim="loginFormData.password" :placeholder="t('login.password')" type="password"
show-password @keyup="checkCapsLock" @keyup.enter="handleLoginSubmit">
<template #prefix>
<el-icon>
<Lock />
</el-icon>
</template>
</el-input>
</el-form-item>
</el-tooltip>
<div class="flex-x-between w-full">
<el-checkbox v-model="loginFormData.rememberMe">{{ t("login.rememberMe") }}</el-checkbox>
<el-link type="primary" underline="never" @click="toOtherForm('resetPwd')">
{{ t("login.forgetPassword") }}
</el-link>
</div>
<!-- 登录按钮 -->
<el-form-item>
<el-button :loading="loading" type="primary" class="w-full" @click="handleLoginSubmit">
{{ t("login.login") }}
</el-button>
</el-form-item>
</el-form>
<div flex-center gap-10px>
<el-text size="default">{{ t("login.noAccount") }}</el-text>
<el-link type="primary" underline="never" @click="toOtherForm('register')">
{{ t("login.reg") }}
</el-link>
</div>
<!-- 第三方登录 -->
<div class="third-party-login">
<div class="divider-container">
<div class="divider-line"></div>
<span class="divider-text">{{ t("login.otherLoginMethods") }}</span>
<div class="divider-line"></div>
</div>
<div class="flex-center gap-x-5 w-full text-[var(--el-text-color-secondary)]">
<CommonWrapper>
<div text-20px class="i-svg:wechat" />
</CommonWrapper>
<CommonWrapper>
<div text-20px cursor-pointer class="i-svg:qq" />
</CommonWrapper>
<CommonWrapper>
<div text-20px cursor-pointer class="i-svg:github" />
</CommonWrapper>
<CommonWrapper>
<div text-20px cursor-pointer class="i-svg:gitee" />
</CommonWrapper>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type { FormInstance } from "element-plus";
import { User, Lock } from "@element-plus/icons-vue";
import { useI18n } from "vue-i18n";
import { useRoute, useRouter } from "vue-router";
import { ElMessage } from "element-plus";
// 引入核心依赖(移除 permissionStore 相关)
import AuthAPI, { type LoginFormData } from "@/api/auth-api";
import { useUserStore } from "@/store"; // 仅保留用户Store
import CommonWrapper from "@/components/CommonWrapper/index.vue";
import { AuthStorage } from "@/utils/auth";
import axios from "axios";
// 初始化工具实例(移除 permissionStore)
const { t } = useI18n();
const userStore = useUserStore();
const route = useRoute();
const router = useRouter(); // 确保是全局正确的路由实例
// 表单核心状态(不变)
const loginFormRef = ref<FormInstance>();
const loading = ref(false);
const isCapsLock = ref(false);
// 登录表单数据(不变)
const loginFormData = ref<Omit<LoginFormData, "captchaKey" | "captchaCode">>({
username: "李四",
password: "123",
rememberMe: false
});
// 表单验证规则(不变)
const loginRules = computed(() => ({
username: [
{ required: true, trigger: "blur", message: t("login.message.username.required") }
],
password: [
{ required: true, trigger: "blur", message: t("login.message.password.required") }
]
}));
/**
* 登录提交(核心简化:移除动态路由逻辑,直接跳转)
*/
async function handleLoginSubmit() {
try {
const valid = await loginFormRef.value?.validate();
if (!valid) return;
loading.value = true;
// 1. 执行登录(存储 Token)
await userStore.login(loginFormData.value);
console.log("登录成功,表单数据:", loginFormData.value);
// 2. 获取 Token 并校验(确保登录状态有效)
const token = AuthStorage.getAccessToken();
if (!token) {
throw new Error("Token 存储失败,请重试");
}
console.log("当前 Token:", token);
// 3. (可选)获取用户信息(仅存信息,不依赖其菜单)
const infoRes = await axios({
url: "http://10.170.1.104:8978/admin/admin/info",
method: "get",
headers: { "Authorization": token }
});
if (infoRes.data.code !== 200) {
throw new Error(`获取用户信息失败:${infoRes.data.msg || "未知错误"}`);
}
console.log("用户信息:", infoRes.data.data); // 仅打印,不用于路由
// 4. 确定跳转路径(简化:redirect参数优先,无则跳首页)
let redirectPath = "/"; // 兜底跳首页(静态路由,确保存在)
// 如果有 redirect 参数(如之前访问 /dashboard 被拦截),则跳该路径
if (route.query.redirect && typeof route.query.redirect === "string") {
const decodedRedirect = decodeURIComponent(route.query.redirect);
// 仅允许跳静态路由中的路径(避免无效路径)
const validStaticPaths = ["/", "/dashboard", "/profile"]; // 替换为你的静态路由路径
if (validStaticPaths.includes(decodedRedirect)) {
redirectPath = decodedRedirect;
}
console.log("使用 redirect 参数跳转:", redirectPath);
}
console.log("最终跳转路径:", redirectPath);
// // 5. 直接跳转(移除所有动态路由相关干扰)
// await router.replace(redirectPath);
// ElMessage.success("登录成功,已进入系统");
// 5. 直接跳转(添加详细日志)
console.log("准备执行跳转,路由实例:", router);
console.log("router.replace 方法是否存在:", typeof router.replace === "function");
console.log("跳转路径是否为字符串:", typeof redirectPath === "string");
try {
console.log("开始执行 router.replace,路径:", redirectPath);
const result = await router.replace(redirectPath);
console.log("router.replace 执行成功,返回结果:", result);
ElMessage.success("登录成功,已进入系统");
} catch (error) {
console.error("router.replace 执行失败,错误详情:", error);
// console.error("错误名称:", error.name);
// console.error("错误消息:", error.message);
// console.error("错误堆栈:", jumpError.stack);
// ElMessage.error("跳转失败:" + jumpError.message);
}
} catch (error: any) {
// 错误详情打印(便于排查)
console.error("登录跳转失败详情:", {
message: error.message,
statusCode: error.response?.status,
responseData: error.response?.data,
requestUrl: error.response?.config?.url
});
// 用户友好提示
const errorMsg = error.response?.data?.msg || error.message || "登录失败,请联系管理员";
ElMessage.error(errorMsg);
} finally {
loading.value = false;
}
}
// 大写锁定检查(不变)
function checkCapsLock(event: KeyboardEvent) {
isCapsLock.value = event.getModifierState("CapsLock");
}
// 切换表单(不变)
const emit = defineEmits(["update:modelValue"]);
function toOtherForm(type: "register" | "resetPwd") {
emit("update:modelValue", type);
}
</script>
<style lang="scss" scoped>
.third-party-login {
.divider-container {
display: flex;
align-items: center;
margin: 40px 0;
.divider-line {
flex: 1;
height: 1px;
background: linear-gradient(to right, transparent, var(--el-border-color-light), transparent);
}
.divider-text {
padding: 0 16px;
font-size: 12px;
color: var(--el-text-color-regular);
white-space: nowrap;
}
}
}
</style>
import { createApp } from "vue";
import App from "./App.vue";
import setupPlugins from "@/plugins";
// 导入router
import router from "@/router";
// 暗黑主题样式
import "element-plus/theme-chalk/dark/css-vars.css";
import "vxe-table/lib/style.css";
// 暗黑模式自定义变量
import "@/styles/dark/css-vars.css";
import "@/styles/index.scss";
import "uno.css";
// 过渡动画
import "animate.css";
// 自动为某些默认事件(如 touchstart、wheel 等)添加 { passive: true },提升滚动性能并消除控制台的非被动事件监听警告
import "default-passive-events";
const app = createApp(App);
// 注册插件
app.use(setupPlugins);
app.use(router);
app.mount("#app");
// src/router/index.ts
import type { App } from "vue"; // ✅ 从 vue 导入 App 类型
import type { RouteRecordRaw } from "vue-router"; // ✅ RouteRecordRaw 仍从 vue-router 导入
import { createRouter, createWebHashHistory } from "vue-router";
// 1. 导入静态路由(根据你的项目实际路由调整,若没有单独文件可直接定义)
const constantRoutes: RouteRecordRaw[] = [
{
path: "/login",
component: () => import("@/views/login/index.vue"),
meta: { hidden: true },
},
{
path: "/404",
component: () => import("@/views/error/404.vue"),
meta: { hidden: true },
},
{
path: "/test-jump",
name: "TestJump",
component: () => import("../views/test/TestJump.vue"), // 新建空白组件
meta: { hidden: true },
},
{
path: "/",
component: () => import("@/layouts/index.vue"), // 你的布局组件路径
redirect: "/dashboard",
children: [
{
path: "dashboard",
component: () => import("@/views/dashboard/index.vue"), // 你的首页路径
meta: { title: "首页", icon: "home" },
},
],
},
];
// 2. 创建路由实例(这里用 hash 模式,避免后端配置,适合开发)
const router = createRouter({
history: createWebHashHistory(), // 若用 history 模式,需改 createWebHistory()
routes: constantRoutes,
scrollBehavior: () => ({ left: 0, top: 0 }), // 跳转后滚动到顶部
});
// 3. 全局注册路由的方法(供 main.ts 使用)
export function setupRouter(app: App<Element>) {
app.use(router);
}
// 4. 关键:添加默认导出!!!(解决“不提供 default 导出”的核心)
export default router;
// 可选:保留命名导出(方便需要显式导入的场景,非必需)
export { router, constantRoutes };
为什么不能执行const result = await router.replace(redirectPath);,进行安全跳转