| | |
| | | <template> |
| | | <view class="login-container"> |
| | | <view v-if="isAutoLogining" class="sso-mask"> |
| | | <uni-load-more |
| | | status="loading" |
| | | :content-text="{ contentdown: '自动登录中...' }" |
| | | ></uni-load-more> |
| | | </view> |
| | | <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 |
| | |
| | | @click="showPassword = !showPassword" |
| | | /> |
| | | </view> |
| | | |
| | | <button |
| | | class="login-btn" |
| | | :class="{ active: username && password }" |
| | |
| | | > |
| | | 登录 |
| | | </button> |
| | | |
| | | <!-- <view class="footer-links"> |
| | | <view @click="gotoRegister">注册账号</view> |
| | | <view @click="gotoForgetPassword">忘记密码</view> |
| | | </view> --> |
| | | </view> |
| | | </view> |
| | | </template> |
| | |
| | | const showPassword = ref(false); |
| | | const redirect = ref(""); |
| | | |
| | | onLoad((options) => { |
| | | console.log("登录页onLoad,完整参数:", options); |
| | | console.log("页面栈:", getCurrentPages()); |
| | | // tabBar 页面列表(与 pages.json 保持一致) |
| | | const tabBarPages = [ |
| | | "/pages/index/index", |
| | | "/pages/appointment/index", |
| | | "/pages/consultation/index", |
| | | "/pages/my/index", |
| | | ]; |
| | | |
| | | // ✅ 获取完整URL参数 |
| | | const pages = getCurrentPages(); |
| | | if (pages.length > 0) { |
| | | const currentPage = pages[pages.length - 1]; |
| | | console.log("当前页面对象:", currentPage); |
| | | onLoad(async (options) => { |
| | | console.log("登录页onLoad,原始参数:", options); |
| | | const launchQuery = uni.getLaunchOptionsSync().query || {}; |
| | | const allParams = { ...launchQuery, ...options }; |
| | | console.log("合并后参数:", allParams); |
| | | |
| | | // 提取 redirect |
| | | if (allParams.redirect) { |
| | | redirect.value = decodeURIComponent(allParams.redirect); |
| | | } |
| | | |
| | | // 处理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); |
| | | // ✅ 优先处理 code 免登 |
| | | const code = allParams.code; |
| | | if (code) { |
| | | console.log("检测到免登码,开始解析:", code); |
| | | // 显示 loading |
| | | uni.showLoading({ title: "解析免登信息...", mask: true }); |
| | | try { |
| | | // 调用解析接口 |
| | | const res = await uni.$uapi.get("/GiLink/getCode", { code }); |
| | | uni.hideLoading(); |
| | | console.log(res.data); |
| | | res.data.passWord = res.data.userPhone; |
| | | if (!res.data || !res.data.userName || !res.data.passWord) { |
| | | throw new Error("解析免登码失败"); |
| | | } |
| | | } |
| | | let obj = JSON.parse(res.data.extContent); |
| | | // const { userName, passWord, ...otherParams } = res; |
| | | console.log(res.data.userName); |
| | | |
| | | // 检查是否已有token |
| | | if (getToken()) { |
| | | console.log("已存在token,直接跳转"); |
| | | setTimeout(() => { |
| | | navigateToTarget(); |
| | | }, 100); |
| | | const userName = res.data.userName; |
| | | const passWord = res.data.passWord; |
| | | const otherParams = { |
| | | id: obj.infoId, |
| | | type: obj.type, |
| | | status: obj.status, |
| | | fcid: obj.id, |
| | | }; |
| | | console.log("解析结果:", { userName, otherParams }); |
| | | // 清除可能存在的旧 token |
| | | if (getToken()) uni.removeStorageSync("token"); |
| | | // 执行 SSO 登录,并将其他业务参数传递下去 |
| | | await handleSSOLogin(userName, passWord, otherParams); |
| | | } catch (err) { |
| | | uni.hideLoading(); |
| | | console.error("免登码解析失败:", err); |
| | | uni.showToast({ title: "免登录失败,请手动登录", icon: "none" }); |
| | | } |
| | | 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; |
| | | } |
| | | // 原有逻辑:无 code 且已有 token 跳转目标页 |
| | | if (getToken()) { |
| | | setTimeout(() => navigateToTarget(), 100); |
| | | return; |
| | | } |
| | | |
| | | 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 = ""; |
| | | } |
| | | password.value = ""; |
| | | }); |
| | | |
| | | onShow(() => { |
| | | console.log("登录页onShow"); |
| | | if (hasAutoLogin) { |
| | | hasAutoLogin = false; |
| | | } |
| | | if (hasAutoLogin) hasAutoLogin = false; |
| | | }); |
| | | |
| | | // ✅ 改进的SSO登录函数 |
| | | /** |
| | | * SSO 免登 |
| | | */ |
| | | const handleSSOLogin = async (userName, passWord, pageParams = {}) => { |
| | | if (isAutoLogining) { |
| | | console.log("正在自动登录中,跳过"); |
| | | return; |
| | | } |
| | | |
| | | if (isAutoLogining) 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); |
| | | |
| | | const tokenRes = await uni.$uapi.post("/getToken", { userName, passWord }); |
| | | uni.hideLoading(); |
| | | |
| | | if (!tokenRes.data.token) { |
| | | throw new Error("获取token失败"); |
| | | } |
| | | 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); |
| | | if (userInfo && userInfo.user) { |
| | | userStore.setUserInfo(userInfo.user); |
| | | if (userInfo.roles) userStore.setroleKey(userInfo.roles); |
| | | } |
| | | |
| | | // 4. 构建目标页面URL |
| | | // 确定目标页面 |
| | | let targetPage = redirect.value; |
| | | if (!targetPage && Object.keys(pageParams).length > 0) { |
| | | // 尝试从原始访问路径构建 |
| | | if (!targetPage) { |
| | | const launchOptions = uni.getLaunchOptionsSync(); |
| | | if (launchOptions.path && !launchOptions.path.includes("login/Login")) { |
| | | targetPage = "/" + launchOptions.path; |
| | | const originalPath = launchOptions.path; |
| | | if (originalPath && !originalPath.includes("login/Login")) { |
| | | targetPage = "/" + originalPath; |
| | | } |
| | | } |
| | | if (!targetPage) targetPage = "/pages/index/index"; |
| | | |
| | | // 5. 跳转 |
| | | hasAutoLogin = true; |
| | | // 跳转时携带 pageParams(业务参数) |
| | | await navigateToTargetPage(targetPage, pageParams); |
| | | } catch (err) { |
| | | uni.hideLoading(); |
| | | console.error("SSO免登失败:", err); |
| | | uni.showToast({ |
| | | title: "自动登录失败,请手动登录", |
| | | title: err.message || "自动登录失败,请手动登录", |
| | | 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(); |
| | |
| | | password: encryptedPassword, |
| | | }); |
| | | |
| | | if (!loginRes || !loginRes.token) { |
| | | throw new Error("登录失败"); |
| | | } |
| | | |
| | | if (!loginRes || !loginRes.token) throw new Error("登录失败"); |
| | | setToken(loginRes.token); |
| | | |
| | | const userInfo = await uni.$uapi.get("/getInfo"); |
| | | if (userInfo) { |
| | | userStore.setUserInfo(userInfo); |
| | | if (userInfo && userInfo.user) { |
| | | userStore.setUserInfo(userInfo.user); |
| | | |
| | | if (userInfo.roles) userStore.setroleKey(userInfo.roles); |
| | | } |
| | | |
| | | uni.hideLoading(); |
| | |
| | | } |
| | | }; |
| | | |
| | | // ✅ 改进的跳转函数 |
| | | /** |
| | | * 根据 redirect 跳转 |
| | | */ |
| | | const navigateToTarget = () => { |
| | | if (redirect.value) { |
| | | return navigateToTargetPage(redirect.value, {}); |
| | | } else { |
| | | console.log("没有目标页面,跳转首页"); |
| | | uni.switchTab({ url: "/pages/index/index" }); |
| | | return Promise.resolve(true); |
| | | } |
| | | }; |
| | | |
| | | /** |
| | | * 通用跳转函数 |
| | | * @param {string} targetPage 目标页面路径 |
| | | * @param {object} pageParams 附加参数 |
| | | */ |
| | | const navigateToTargetPage = (targetPage, pageParams = {}) => { |
| | | return new Promise((resolve) => { |
| | | setTimeout(() => { |
| | | let targetUrl = targetPage || "/pages/index/index"; |
| | | if (!targetPage) { |
| | | console.log("目标页为空,跳转首页"); |
| | | uni.switchTab({ url: "/pages/index/index" }); |
| | | resolve(true); |
| | | return; |
| | | } |
| | | |
| | | // 处理页面参数 |
| | | let finalUrl = targetPage; |
| | | const paramKeys = Object.keys(pageParams).filter( |
| | | (key) => |
| | | pageParams[key] !== undefined && |
| | | pageParams[key] !== null && |
| | | key !== "userName" && |
| | | key !== "passWord" && |
| | | key !== "redirect", |
| | | (key) => pageParams[key] !== undefined && pageParams[key] !== null, |
| | | ); |
| | | |
| | | if (paramKeys.length > 0) { |
| | | const queryStr = paramKeys |
| | | .map((key) => `${key}=${encodeURIComponent(pageParams[key])}`) |
| | | .join("&"); |
| | | |
| | | targetUrl = targetUrl.includes("?") |
| | | ? `${targetUrl}&${queryStr}` |
| | | : `${targetUrl}?${queryStr}`; |
| | | finalUrl += (finalUrl.includes("?") ? "&" : "?") + queryStr + "&sso=1"; |
| | | } |
| | | |
| | | console.log("最终跳转目标:", targetUrl); |
| | | console.log("最终跳转目标URL:", finalUrl); |
| | | |
| | | const tabBarPages = [ |
| | | "/pages/index/index", |
| | | "/pages/appointment/index", |
| | | "/pages/consultation/index", |
| | | "/pages/my/index", |
| | | ]; |
| | | |
| | | const baseUrl = targetUrl.split("?")[0]; |
| | | const baseUrl = finalUrl.split("?")[0]; |
| | | const isTabBar = tabBarPages.includes(baseUrl); |
| | | |
| | | setTimeout(() => { |
| | | if (isTabBar) { |
| | | uni.switchTab({ |
| | | url: baseUrl, |
| | | success: () => resolve(true), |
| | | fail: () => { |
| | | uni.redirectTo({ url: "/pages/index/index" }); |
| | | fail: (err) => { |
| | | console.error("switchTab失败:", err); |
| | | uni.switchTab({ url: "/pages/index/index" }); |
| | | resolve(false); |
| | | }, |
| | | }); |
| | | } else { |
| | | uni.redirectTo({ |
| | | url: targetUrl, |
| | | url: finalUrl, |
| | | success: () => resolve(true), |
| | | fail: (err) => { |
| | | console.error("跳转失败:", err); |
| | | // 如果目标页跳转失败,跳首页 |
| | | uni.redirectTo({ url: "/pages/index/index" }); |
| | | console.error("redirectTo失败:", err); |
| | | uni.switchTab({ url: "/pages/index/index" }); |
| | | resolve(false); |
| | | }, |
| | | }); |
| | |
| | | background: linear-gradient(to bottom, #e6f7ff, #ffffff); |
| | | height: 100vh; |
| | | box-sizing: border-box; |
| | | position: relative; |
| | | } |
| | | |
| | | .sso-mask { |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: rgba(255, 255, 255, 0.9); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | z-index: 9999; |
| | | } |
| | | |
| | | .header { |
| | |
| | | |
| | | .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> |