loginUtils.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. import {computed, ref} from 'vue'
  2. import {
  3. getTenantIdByName,
  4. login,
  5. logout,
  6. smsCode,
  7. smsLogin
  8. } from '@/api/login/login.js'
  9. import {ElLoading, ElMessage} from 'element-plus'
  10. import {refreshAllDictData} from './dictUtils.js';
  11. // CryptoJS 库(用于密码加密)
  12. import CryptoJS from 'crypto-js';
  13. // 消息工具
  14. import {Message} from './message/Message.js';
  15. import {CONFIG, refreshRoleRoute} from "@/utils/roleUtils.js";
  16. import {homeRoutes, managementRoutes} from "@/router/index.js";
  17. // 加密密钥 (从环境变量获取)
  18. const SECRET_KEY = import.meta.env.VITE_SECRET_KEY;
  19. // 是否开启租户模式
  20. const tenantOpen = import.meta.env.VITE_APP_COMPULSORY_TENANT === 'true';
  21. //需要删除的缓存key列表
  22. const CACHE_KEYS_TO_DELETE = [
  23. 'userId',
  24. 'token',
  25. 'isLoggedIn',
  26. CONFIG.USER_ROLE_ROUTE_KEY,
  27. CONFIG.USER_ROLE_ROUTE_KEY + CONFIG.EXPIRY_SUFFIX,
  28. CONFIG.USER_ROLE_ROUTE_MENU_KEY,
  29. CONFIG.USER_ROLE_ROUTE_MENU_KEY + CONFIG.EXPIRY_SUFFIX,
  30. ];
  31. // 密码加密函数(使用AES加密)
  32. const encryptPassword = (password) => {
  33. // 将加密结果转换为字符串并返回 (encrypt加密)
  34. return CryptoJS.AES.encrypt(password, SECRET_KEY).toString();
  35. };
  36. // 密码解密函数 (接收加密后的密码字符串作为参数)
  37. const decryptPassword = (encryptedPassword) => {
  38. // (decrypt解密)
  39. const bytes = CryptoJS.AES.decrypt(encryptedPassword, SECRET_KEY);
  40. // 将解密结果转换为 UTF-8 编码的字符串并返回
  41. return bytes.toString(CryptoJS.enc.Utf8);
  42. };
  43. // 登录数据结构
  44. const createLoginData = () => {
  45. return ref({
  46. loginForm: {
  47. tenantName: tenantOpen ? (import.meta.env.VITE_APP_DEFAULT_LOGIN_TENANT || '') : null,
  48. username: import.meta.env.VITE_APP_DEFAULT_LOGIN_USERNAME || '',
  49. password: import.meta.env.VITE_APP_DEFAULT_LOGIN_PASSWORD || '',
  50. smsCode: '', // 短信验证码字段
  51. rememberMe: false, // 记住
  52. phoneNumber: '' // 手机号字段
  53. }
  54. })
  55. }
  56. // 验证码相关逻辑
  57. const createVerificationCodeLogic = () => {
  58. const countingDown = ref(false)
  59. const countDown = ref(60)
  60. const sendingCode = ref(false)
  61. let countDownTimer = null
  62. // 开始倒计时
  63. const startCountDown = () => {
  64. countingDown.value = true
  65. countDown.value = 60
  66. if (countDownTimer) {
  67. clearInterval(countDownTimer)
  68. }
  69. countDownTimer = setInterval(() => {
  70. countDown.value--
  71. if (countDown.value <= 0) {
  72. clearInterval(countDownTimer)
  73. countingDown.value = false
  74. }
  75. }, 1000)
  76. }
  77. // 清除计时器
  78. const clearCountDownTimer = () => {
  79. if (countDownTimer) {
  80. clearInterval(countDownTimer)
  81. countDownTimer = null
  82. }
  83. }
  84. // 获取短信验证码
  85. const getSmsCode = async (tenantId, tenantName, mobile) => {
  86. sendingCode.value = true
  87. try {
  88. // 构建请求头,只有当 tenantId 存在时才添加 Tenant-Id
  89. const headers = tenantId ? { 'Tenant-Id': tenantId } : {}
  90. const res = await smsCode(
  91. headers, {
  92. tenantName,
  93. mobile,
  94. scene: import.meta.env.VITE_APP_LOGIN_SMS_TEMPLATE_ID,
  95. }
  96. )
  97. if (res.code === 0) {
  98. ElMessage.success('验证码发送成功')
  99. startCountDown()
  100. } else {
  101. ElMessage.error(res.message || '验证码发送失败')
  102. }
  103. } catch (error) {
  104. ElMessage.error('验证码发送失败,请重试')
  105. console.error('发送验证码错误:', error)
  106. } finally {
  107. sendingCode.value = false
  108. }
  109. }
  110. return {
  111. countingDown,
  112. countDown,
  113. sendingCode,
  114. getSmsCode,
  115. clearCountDownTimer
  116. }
  117. }
  118. // 获取租户ID
  119. const getTenantId = async (tenantName) => {
  120. try {
  121. const res = await getTenantIdByName(tenantName)
  122. if (res && res.data) {
  123. return res.data
  124. } else {
  125. ElMessage.error('租户填写错误!')
  126. return null
  127. }
  128. } catch (error) {
  129. ElMessage.error('租户填写错误!')
  130. console.error('获取租户 ID 错误:', error)
  131. return null
  132. }
  133. }
  134. // 登录逻辑
  135. const loginLogic = async (loginForm, tenantId, isAuthorized, router) => {
  136. const loginLoading = ref(false)
  137. const loading = ref()
  138. const isLoggedIn = ref(false)
  139. loginLoading.value = true
  140. try {
  141. let res
  142. // 构建请求头,只有当 tenantId 存在时才添加 Tenant-Id
  143. const headers = tenantId ? { 'Tenant-Id': tenantId } : {}
  144. if (!isAuthorized.value) {
  145. // 未授权状态,使用短信验证码登录
  146. res = await smsLogin(
  147. headers, {
  148. mobile: loginForm.phoneNumber,
  149. code: loginForm.smsCode,
  150. }
  151. )
  152. } else {
  153. // 授权状态,使用账号密码登录
  154. // 传输时使用明文密码,确保后端能正确处理
  155. res = await login(
  156. headers,
  157. loginForm
  158. )
  159. }
  160. if (!res) {
  161. return false
  162. }
  163. // 校验登录状态
  164. if (res.code === 0) {
  165. // ElMessage.success('登录成功')
  166. isLoggedIn.value = true
  167. // 存储登录状态
  168. localStorage.setItem('isLoggedIn', 'true')
  169. localStorage.setItem('token', res.data.accessToken)
  170. // 登录成功后存储用户ID
  171. if (res.data.userId) {
  172. localStorage.setItem('userId', res.data.userId)
  173. }
  174. // 总是存储用户名和租户名称
  175. localStorage.setItem('tenantName', res.data.tenantName)
  176. // 存储记住我状态
  177. localStorage.setItem('rememberMe', loginForm.rememberMe)
  178. if (loginForm.rememberMe) {
  179. localStorage.setItem('userName', loginForm.username)
  180. // 保存加密后的密码
  181. localStorage.setItem('password', encryptPassword(loginForm.password))
  182. } else {
  183. // 如果没有勾选记住我,清除密码
  184. localStorage.removeItem('password')
  185. }
  186. loading.value = ElLoading.service({
  187. lock: true,
  188. text: '正在加载系统中...',
  189. background: 'rgba(0, 0, 0, 0.7)'
  190. })
  191. // 获取字典数据
  192. await refreshDictData();
  193. // 获取角色路由数据
  194. await refreshRoleRouteData();
  195. // 登录成功后,跳转到指定的页面
  196. router.push(managementRoutes.home)
  197. return true
  198. } else if (res.code === 1002000009) {
  199. // 未授权状态,切换到短信验证码登录
  200. ElMessage.warning(res.msg || '登录IP未被授权,请使用手机号短信验证码登录!')
  201. loginForm.phoneNumber = res.data?.mobile
  202. isAuthorized.value = false
  203. return false
  204. } else {
  205. ElMessage.error(res.msg || '登录失败,请检查账号密码!')
  206. return false
  207. }
  208. } catch (error) {
  209. ElMessage.error('登录出错,请重试!')
  210. console.error('登录错误:', error)
  211. return false
  212. } finally {
  213. loginLoading.value = false
  214. if (loading.value) {
  215. loading.value.close()
  216. }
  217. }
  218. }
  219. // 本地存储管理
  220. const loadLoginData = (loginData) => {
  221. const storedTenantName = localStorage.getItem('tenantName')
  222. const storedUserName = localStorage.getItem('userName')
  223. const storedPassword = localStorage.getItem('password')
  224. // 恢复登录信息到输入框
  225. if (tenantOpen && storedTenantName) {
  226. loginData.value.loginForm.tenantName = storedTenantName
  227. }
  228. if (storedUserName) {
  229. loginData.value.loginForm.username = storedUserName
  230. }
  231. if (storedPassword) {
  232. // 解密密码并显示明文
  233. loginData.value.loginForm.password = decryptPassword(storedPassword)
  234. loginData.value.loginForm.rememberMe = true
  235. }
  236. }
  237. // 检查登录状态
  238. const checkLoginStatus = (router) => {
  239. const storedStatus = localStorage.getItem('isLoggedIn')
  240. if (storedStatus === 'true') {
  241. // 获取字典数据
  242. refreshDictData();
  243. // 获取角色路由数据
  244. refreshRoleRouteData();
  245. router.push(managementRoutes.home)
  246. return true
  247. }
  248. return false
  249. }
  250. // 表单验证规则生成
  251. const generateRules = (isAuthorized) => {
  252. return computed(() => {
  253. if (isAuthorized.value) {
  254. // 授权状态:需要账号和密码
  255. const rules = {
  256. username: [{ required: true, message: '请输入账号', trigger: 'blur' }],
  257. password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
  258. }
  259. if (tenantOpen) {
  260. rules.tenantName = [{ required: true, message: '请输入学校名称', trigger: 'blur' }]
  261. }
  262. return rules
  263. } else {
  264. // 未授权状态:需要手机号和短信验证码
  265. const rules = {
  266. phoneNumber: [
  267. { required: true, message: '请输入手机号', trigger: 'blur' },
  268. { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' }
  269. ],
  270. smsCode: [{ required: true, message: '请输入短信验证码', trigger: 'blur' }]
  271. }
  272. if (tenantOpen) {
  273. rules.tenantName = [{ required: true, message: '请输入学校名称', trigger: 'blur' }]
  274. }
  275. return rules
  276. }
  277. })
  278. }
  279. // 自动登录逻辑(用于QuickLogin和PromotionLogin)
  280. const autoLogin = async (tenantName, username, password, router, redirectPath) => {
  281. let loading = null
  282. try {
  283. // 显示全局加载状态
  284. loading = ElLoading.service({
  285. lock: true,
  286. text: '登录中...',
  287. background: 'rgba(0, 0, 0, 0.7)'
  288. })
  289. // 获取租户ID,只有当 tenantOpen 为 true 时才获取
  290. let tenantId = await getTenantId(tenantName)
  291. if (!tenantId) {
  292. // 租户验证失败
  293. return false
  294. }
  295. // 构建请求头,只有当 tenantId 存在时才添加 Tenant-Id
  296. const headers = tenantId ? { 'Tenant-Id': tenantId } : {}
  297. // 执行登录
  298. const res = await login(
  299. headers,
  300. { tenantName, username, password }
  301. )
  302. if (res && res.code === 0) {
  303. // 登录成功,保存登录状态
  304. localStorage.setItem('isLoggedIn', 'true')
  305. localStorage.setItem('token', res.data.accessToken)
  306. localStorage.setItem('userName', username)
  307. localStorage.setItem('tenantName', tenantName)
  308. localStorage.setItem('password', encryptPassword(password))
  309. localStorage.setItem('rememberMe', 'true')
  310. // 获取字典数据
  311. await refreshDictData();
  312. // 获取角色路由数据
  313. await refreshRoleRouteData();
  314. ElMessage.success('信息校验成功')
  315. // 跳转到课程界面
  316. router.push(redirectPath)
  317. return true
  318. } else {
  319. ElMessage.error(res?.message || '信息校验失败')
  320. // 如果登录失败,跳转到正常登录页面
  321. router.push(homeRoutes.login)
  322. return false
  323. }
  324. } catch (error) {
  325. ElMessage.error('信息校验过程中发生错误')
  326. console.error('信息校验错误:', error)
  327. // 错误时跳转到登录页面
  328. router.push(homeRoutes.login)
  329. return false
  330. } finally {
  331. // 关闭加载状态
  332. if (loading) {
  333. loading.close()
  334. }
  335. }
  336. }
  337. // 退出登录逻辑
  338. const logoutLogic = async (router, redirectPath = homeRoutes.login) => {
  339. try {
  340. // 调用logout API 退出登录
  341. const logoutRes = await logout()
  342. console.log('退出登录:', logoutRes);
  343. //清除本地存储
  344. removeLocalStorage();
  345. // 检查是否勾选了记住我
  346. const rememberMe = localStorage.getItem('rememberMe') === 'true'
  347. if (!rememberMe) {
  348. // 未勾选记住我,清空所有信息
  349. localStorage.removeItem('userName')
  350. localStorage.removeItem('tenantName')
  351. localStorage.removeItem('rememberMe')
  352. }
  353. // 跳转到登录页面
  354. router.push({ path: redirectPath })
  355. } catch (error) {
  356. console.error('退出登录失败:', error)
  357. // API调用失败,也清空本地存储
  358. //清除本地存储
  359. removeLocalStorage();
  360. // 检查是否勾选了记住我
  361. const rememberMe = localStorage.getItem('rememberMe') === 'true'
  362. if (!rememberMe) {
  363. // 未勾选记住我,清空所有信息
  364. localStorage.removeItem('userName')
  365. localStorage.removeItem('tenantName')
  366. localStorage.removeItem('rememberMe')
  367. }
  368. Message().notifyError('退出登录失败,请重试', true)
  369. router.push({ path: homeRoutes.login })
  370. }
  371. }
  372. //清除本地存储
  373. const removeLocalStorage = () => {
  374. // 清空登录状态相关信息
  375. CACHE_KEYS_TO_DELETE.forEach(key => localStorage.removeItem(key));
  376. removeLocalStorageKey(localStorage.getItem('token'))
  377. }
  378. const removeLocalStorageKey = (remKey) => {
  379. if (remKey) {
  380. for (let i = 0; i < localStorage.length; i++) {
  381. const key = localStorage.key(i);
  382. if (key && key.startsWith(remKey)) {
  383. localStorage.removeItem(key);
  384. }
  385. }
  386. }
  387. }
  388. // 添加获取字典数据的函数
  389. const refreshDictData = async () => {
  390. try {
  391. // 使用典管理工具更新所有字典
  392. await refreshAllDictData();
  393. } catch (error) {
  394. console.error('获取字典数据失败:', error);
  395. }
  396. };
  397. // 添加获取角色路由数据的函数
  398. const refreshRoleRouteData = async () => {
  399. try {
  400. // 使用角色路由工具更新角色路由
  401. await refreshRoleRoute();
  402. } catch (error) {
  403. console.error('获取角色路由数据失败:', error);
  404. }
  405. };
  406. export {
  407. createLoginData,
  408. createVerificationCodeLogic,
  409. getTenantId,
  410. loginLogic,
  411. loadLoginData,
  412. checkLoginStatus,
  413. generateRules,
  414. autoLogin,
  415. logoutLogic,
  416. removeLocalStorageKey
  417. }