<template>
|
<view class="login-container">
|
<view class="header">
|
<image src="/static/avatar/logo.png" class="logo" />
|
<text class="hospital-name">青大附院OPO管理平台</text>
|
</view>
|
|
<view class="form-container">
|
<view class="input-group">
|
<uni-icons type="contact" size="24" color="#409EFF" />
|
<input v-model="username" placeholder="请输入账号" class="input" />
|
</view>
|
|
<view class="input-group">
|
<uni-icons type="locked" size="24" color="#409EFF" />
|
<input
|
v-model="password"
|
:password="!showPassword"
|
placeholder="请输入密码"
|
class="input"
|
/>
|
<uni-icons
|
:type="showPassword ? 'eye' : 'eye-slash'"
|
size="22"
|
color="#999"
|
@click="showPassword = !showPassword"
|
/>
|
</view>
|
|
<button
|
class="login-btn"
|
:class="{ active: username && password }"
|
@click="handleLogin"
|
hover-class="button-hover"
|
>
|
登录
|
</button>
|
|
<!-- <view class="footer-links">
|
<view @click="gotoRegister">注册账号</view>
|
<view @click="gotoForgetPassword">忘记密码</view>
|
</view> -->
|
</view>
|
</view>
|
</template>
|
|
<script setup>
|
import { ref } from "vue";
|
import { onLoad, onShow } from "@dcloudio/uni-app";
|
import { useUserStore } from "@/stores/user";
|
import { getToken, setToken } from "@/utils/auth";
|
import { encrypt } from "@/utils/crypto";
|
|
let isAutoLogining = false;
|
let hasAutoLogin = false;
|
|
const username = ref("");
|
const password = ref("");
|
const showPassword = ref(false);
|
const redirect = ref("");
|
|
onLoad((options) => {
|
console.log("登录页onLoad,完整参数:", options);
|
console.log("页面栈:", getCurrentPages());
|
|
// ✅ 获取完整URL参数
|
const pages = getCurrentPages();
|
if (pages.length > 0) {
|
const currentPage = pages[pages.length - 1];
|
console.log("当前页面对象:", currentPage);
|
}
|
|
// 处理redirect参数
|
if (options.redirect) {
|
redirect.value = decodeURIComponent(options.redirect);
|
} else if (options.userName && options.passWord) {
|
// 如果没有redirect但有SSO参数,尝试从referrer获取
|
const launchOptions = uni.getLaunchOptionsSync();
|
if (launchOptions.referrerInfo && launchOptions.referrerInfo.extraData) {
|
const referrer = launchOptions.referrerInfo.extraData;
|
console.log("referrer信息:", referrer);
|
}
|
}
|
|
// 检查是否已有token
|
if (getToken()) {
|
console.log("已存在token,直接跳转");
|
setTimeout(() => {
|
navigateToTarget();
|
}, 100);
|
return;
|
}
|
|
// ✅ SSO登录:检查所有可能的参数位置
|
let ssoUserName = options.userName;
|
let ssoPassWord = options.passWord;
|
|
// 如果没有从options获取到,尝试从启动参数获取
|
if (!ssoUserName || !ssoPassWord) {
|
const launchOptions = uni.getLaunchOptionsSync();
|
if (launchOptions.query) {
|
ssoUserName = ssoUserName || launchOptions.query.userName;
|
ssoPassWord = ssoPassWord || launchOptions.query.passWord;
|
}
|
}
|
|
if (ssoUserName && ssoPassWord) {
|
console.log("检测到SSO参数,自动登录", { ssoUserName });
|
|
// 合并所有页面参数
|
const pageParams = { ...options };
|
delete pageParams.userName;
|
delete pageParams.passWord;
|
delete pageParams.redirect;
|
|
handleSSOLogin(ssoUserName, ssoPassWord, pageParams);
|
} else {
|
// 普通登录
|
password.value = "";
|
username.value = "";
|
}
|
});
|
|
onShow(() => {
|
console.log("登录页onShow");
|
if (hasAutoLogin) {
|
hasAutoLogin = false;
|
}
|
});
|
|
// ✅ 改进的SSO登录函数
|
const handleSSOLogin = async (userName, passWord, pageParams = {}) => {
|
if (isAutoLogining) {
|
console.log("正在自动登录中,跳过");
|
return;
|
}
|
|
isAutoLogining = true;
|
console.log("开始SSO免登:", { userName, pageParams });
|
|
uni.showLoading({ title: "自动登录中...", mask: true });
|
|
try {
|
console.log(11);
|
|
// 1. 获取token
|
const tokenRes = await uni.$uapi.post("/getToken", {
|
userName,
|
passWord,
|
});
|
console.log(tokenRes.data);
|
|
uni.hideLoading();
|
|
if (!tokenRes.data.token) {
|
throw new Error("获取token失败");
|
}
|
|
console.log("获取到token成功");
|
|
// 2. 保存token
|
setToken(tokenRes.data.token);
|
|
// 3. 获取用户信息
|
const userStore = useUserStore();
|
const userInfo = await uni.$uapi.get("/getInfo");
|
if (userInfo) {
|
userStore.setUserInfo(userInfo);
|
}
|
|
// 4. 构建目标页面URL
|
let targetPage = redirect.value;
|
if (!targetPage && Object.keys(pageParams).length > 0) {
|
// 尝试从原始访问路径构建
|
const launchOptions = uni.getLaunchOptionsSync();
|
if (launchOptions.path && !launchOptions.path.includes("login/Login")) {
|
targetPage = "/" + launchOptions.path;
|
}
|
}
|
|
// 5. 跳转
|
hasAutoLogin = true;
|
await navigateToTargetPage(targetPage, pageParams);
|
} catch (err) {
|
uni.hideLoading();
|
console.error("SSO免登失败:", err);
|
uni.showToast({
|
title: "自动登录失败,请手动登录",
|
icon: "none",
|
duration: 3000,
|
});
|
|
uni.removeStorageSync("token");
|
} finally {
|
isAutoLogining = false;
|
}
|
};
|
|
// 普通登录
|
const handleLogin = async () => {
|
try {
|
if (!username.value || !password.value) {
|
uni.showToast({ title: "请输入账号密码", icon: "none" });
|
return;
|
}
|
|
uni.showLoading({ title: "登录中...", mask: true });
|
|
const userStore = useUserStore();
|
const encryptedPassword = encrypt(password.value);
|
const encryptedUsername = encrypt(username.value);
|
|
const loginRes = await uni.$uapi.post("/login", {
|
username: encryptedUsername,
|
password: encryptedPassword,
|
});
|
|
if (!loginRes || !loginRes.token) {
|
throw new Error("登录失败");
|
}
|
|
setToken(loginRes.token);
|
|
const userInfo = await uni.$uapi.get("/getInfo");
|
if (userInfo) {
|
userStore.setUserInfo(userInfo);
|
}
|
|
uni.hideLoading();
|
await navigateToTarget();
|
} catch (err) {
|
uni.hideLoading();
|
console.error("登录失败:", err);
|
uni.showToast({
|
title: err.message || "登录失败",
|
icon: "none",
|
duration: 3000,
|
});
|
}
|
};
|
|
// ✅ 改进的跳转函数
|
const navigateToTarget = () => {
|
return navigateToTargetPage(redirect.value, {});
|
};
|
|
const navigateToTargetPage = (targetPage, pageParams = {}) => {
|
return new Promise((resolve) => {
|
setTimeout(() => {
|
let targetUrl = targetPage || "/pages/index/index";
|
|
// 处理页面参数
|
const paramKeys = Object.keys(pageParams).filter(
|
(key) =>
|
pageParams[key] !== undefined &&
|
pageParams[key] !== null &&
|
key !== "userName" &&
|
key !== "passWord" &&
|
key !== "redirect",
|
);
|
|
if (paramKeys.length > 0) {
|
const queryStr = paramKeys
|
.map((key) => `${key}=${encodeURIComponent(pageParams[key])}`)
|
.join("&");
|
|
targetUrl = targetUrl.includes("?")
|
? `${targetUrl}&${queryStr}`
|
: `${targetUrl}?${queryStr}`;
|
}
|
|
console.log("最终跳转目标:", targetUrl);
|
|
const tabBarPages = [
|
"/pages/index/index",
|
"/pages/appointment/index",
|
"/pages/consultation/index",
|
"/pages/my/index",
|
];
|
|
const baseUrl = targetUrl.split("?")[0];
|
const isTabBar = tabBarPages.includes(baseUrl);
|
|
if (isTabBar) {
|
uni.switchTab({
|
url: baseUrl,
|
success: () => resolve(true),
|
fail: () => {
|
uni.redirectTo({ url: "/pages/index/index" });
|
resolve(false);
|
},
|
});
|
} else {
|
uni.redirectTo({
|
url: targetUrl,
|
success: () => resolve(true),
|
fail: (err) => {
|
console.error("跳转失败:", err);
|
// 如果目标页跳转失败,跳首页
|
uni.redirectTo({ url: "/pages/index/index" });
|
resolve(false);
|
},
|
});
|
}
|
}, 300);
|
});
|
};
|
</script>
|
|
<style scoped>
|
.login-container {
|
padding: 40rpx;
|
background: linear-gradient(to bottom, #e6f7ff, #ffffff);
|
height: 100vh;
|
box-sizing: border-box;
|
}
|
|
.header {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
margin: 100rpx 0 60rpx;
|
}
|
|
.logo {
|
width: 160rpx;
|
height: 160rpx;
|
border-radius: 50%;
|
border: 3rpx solid #d6ecff;
|
box-shadow: 0 6rpx 18rpx rgba(24, 144, 255, 0.3);
|
}
|
|
.hospital-name {
|
font-size: 38rpx;
|
font-weight: bold;
|
color: #1890ff;
|
margin-top: 24rpx;
|
letter-spacing: 3rpx;
|
}
|
|
.form-container {
|
background: #ffffff;
|
border-radius: 24rpx;
|
padding: 50rpx;
|
box-shadow: 0 8rpx 24rpx rgba(24, 144, 255, 0.1);
|
}
|
|
.input-group {
|
display: flex;
|
align-items: center;
|
flex-direction: row;
|
padding: 28rpx 0;
|
border-bottom: 1rpx solid #f0f0f0;
|
flex-wrap: nowrap;
|
}
|
|
.input {
|
flex: 1;
|
margin: 0 20rpx;
|
font-size: 30rpx;
|
color: #333;
|
background: transparent;
|
}
|
|
.login-btn {
|
margin-top: 60rpx;
|
height: 90rpx;
|
line-height: 90rpx;
|
border-radius: 50rpx;
|
font-size: 34rpx;
|
border: none;
|
color: white;
|
background: #c0dfff;
|
transition: all 0.3s ease;
|
}
|
|
.login-btn.active {
|
background: linear-gradient(to right, #40a9ff, #1890ff);
|
box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.4);
|
}
|
|
.button-hover {
|
opacity: 0.8;
|
}
|
|
.footer-links {
|
display: flex;
|
flex-direction: row;
|
justify-content: space-between;
|
text-align: center;
|
margin-top: 30rpx;
|
color: #1890ff;
|
font-size: 28rpx;
|
width: 100%;
|
}
|
</style>
|