暫無描述

login.vue 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. <script lang="ts" setup>
  2. import type { ILoginForm } from '@/api/login'
  3. import type { ICaptcha } from '@/api/types/login'
  4. import { computed, onMounted, ref } from 'vue'
  5. import { getCaptcha } from '@/api/login'
  6. import { useAuthStore } from '@/store/auth'
  7. definePage({
  8. style: {
  9. navigationBarTitleText: '登录',
  10. },
  11. })
  12. const authStore = useAuthStore()
  13. // 验证码响应实体类
  14. const captchaInfo = ref<ICaptcha>({
  15. captchaEnabled: false,
  16. img: '',
  17. uuid: '',
  18. })
  19. // 登录表单数据
  20. const loginForm = ref<ILoginForm>({
  21. username: '',
  22. password: '',
  23. code: '',
  24. uuid: '',
  25. })
  26. // 验证码相关数据
  27. const captchaData = ref({
  28. code: '',
  29. uuid: '',
  30. })
  31. // 重定向地址
  32. const redirectUrl = ref('')
  33. // 表单验证规则
  34. const formRules = computed(() => ({
  35. username: {
  36. required: true,
  37. message: '请输入用户名',
  38. trigger: 'blur',
  39. },
  40. password: {
  41. required: true,
  42. message: '请输入密码',
  43. trigger: 'blur',
  44. },
  45. code: {
  46. required: captchaInfo.value.captchaEnabled,
  47. message: '请输入验证码',
  48. trigger: 'blur',
  49. },
  50. }))
  51. // 获取验证码接口
  52. async function loadCaptcha() {
  53. try {
  54. const resp = await getCaptcha()
  55. console.log('获取验证码响应:', resp)
  56. if (resp.captchaEnabled && resp.img) {
  57. resp.img = `data:image/png;base64,${resp.img}`
  58. }
  59. captchaInfo.value = resp
  60. loginForm.value.uuid = resp.uuid || ''
  61. }
  62. catch (error) {
  63. console.error('获取验证码失败:', error)
  64. uni.showToast({
  65. title: '获取验证码失败',
  66. icon: 'error',
  67. })
  68. }
  69. }
  70. // uni-app页面生命周期钩子
  71. onLoad((options) => {
  72. // 获取重定向地址
  73. if (options?.redirect) {
  74. redirectUrl.value = decodeURIComponent(options.redirect)
  75. }
  76. })
  77. onMounted(async () => {
  78. await loadCaptcha()
  79. })
  80. // 处理登录
  81. async function handleLogin() {
  82. try {
  83. // 构建登录参数
  84. const requestParam: ILoginForm = {
  85. username: loginForm.value.username,
  86. password: loginForm.value.password,
  87. code: '',
  88. uuid: '',
  89. }
  90. // 如果需要验证码,添加验证码参数
  91. if (captchaInfo.value.captchaEnabled) {
  92. requestParam.code = loginForm.value.code
  93. requestParam.uuid = loginForm.value.uuid
  94. }
  95. // 调用登录接口
  96. await authStore.authLogin(requestParam)
  97. uni.showToast({
  98. title: '登录成功',
  99. icon: 'success',
  100. })
  101. // 登录成功后跳转
  102. setTimeout(() => {
  103. if (redirectUrl.value) {
  104. // 如果有重定向地址,跳转到指定页面
  105. if (redirectUrl.value.startsWith('/pages/')) {
  106. // 检查是否为tabbar页面
  107. if (redirectUrl.value === '/pages/index/index' || redirectUrl.value === '/pages/me/me') {
  108. uni.switchTab({
  109. url: redirectUrl.value,
  110. })
  111. }
  112. else {
  113. uni.redirectTo({
  114. url: redirectUrl.value,
  115. })
  116. }
  117. }
  118. else {
  119. uni.redirectTo({
  120. url: redirectUrl.value,
  121. })
  122. }
  123. }
  124. else {
  125. // 没有重定向地址,跳转到首页
  126. uni.switchTab({
  127. url: '/pages/index/index',
  128. })
  129. }
  130. }, 1500)
  131. }
  132. catch (error) {
  133. console.error('登录失败:', error)
  134. uni.showToast({
  135. title: '登录失败,请检查用户名密码',
  136. icon: 'error',
  137. })
  138. // 处理验证码错误,刷新验证码
  139. if (captchaInfo.value.captchaEnabled) {
  140. await loadCaptcha()
  141. loginForm.value.code = ''
  142. }
  143. }
  144. }
  145. // 表单提交
  146. function onSubmit() {
  147. handleLogin()
  148. }
  149. // 刷新验证码
  150. function refreshCaptcha() {
  151. loadCaptcha()
  152. }
  153. // 表单验证
  154. const formRef = ref()
  155. function validateForm() {
  156. formRef.value?.validate().then(() => {
  157. onSubmit()
  158. }).catch(() => {
  159. uni.showToast({
  160. title: '请检查表单输入',
  161. icon: 'error',
  162. })
  163. })
  164. }
  165. </script>
  166. <template>
  167. <view class="login-container">
  168. <!-- 登录头部 -->
  169. <view class="login-header">
  170. <view class="logo-container">
  171. <image src="/static/logo.svg" class="logo" mode="aspectFit" />
  172. </view>
  173. <text class="login-title">用户登录</text>
  174. </view>
  175. <!-- 登录表单 -->
  176. <view class="login-form">
  177. <wd-form ref="formRef" :model="loginForm" :rules="formRules">
  178. <!-- 用户名输入 -->
  179. <wd-form-item prop="username" label="用户名" required>
  180. <wd-input v-model="loginForm.username" placeholder="请输入用户名" clearable :show-clear="true" />
  181. </wd-form-item>
  182. <!-- 密码输入 -->
  183. <wd-form-item prop="password" label="密码" required>
  184. <wd-input v-model="loginForm.password" type="password" placeholder="请输入密码" clearable :show-clear="true" :show-password="true" />
  185. </wd-form-item>
  186. <!-- 验证码输入 -->
  187. <wd-form-item v-if="captchaInfo && captchaInfo.captchaEnabled" prop="code" label="验证码" required>
  188. <view class="captcha-container">
  189. <wd-input v-model="loginForm.code" placeholder="请输入验证码" clearable :show-clear="true" class="captcha-input" />
  190. <view class="captcha-image-wrapper" @click="refreshCaptcha">
  191. <image v-if="captchaInfo && captchaInfo.img" :src="captchaInfo.img" class="captcha-image" mode="aspectFit" />
  192. <text v-else class="captcha-placeholder">点击获取</text>
  193. </view>
  194. </view>
  195. </wd-form-item>
  196. </wd-form>
  197. <!-- 登录按钮 -->
  198. <view class="login-actions">
  199. <wd-button type="primary" round block :loading="authStore.loginLoading" @click="validateForm">
  200. {{ authStore.loginLoading ? '登录中...' : '登录' }}
  201. </wd-button>
  202. </view>
  203. </view>
  204. </view>
  205. </template>
  206. <style lang="scss" scoped>
  207. .login-container {
  208. min-height: 100vh;
  209. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  210. padding: 0 32rpx;
  211. display: flex;
  212. flex-direction: column;
  213. justify-content: center;
  214. }
  215. .login-header {
  216. text-align: center;
  217. margin-bottom: 80rpx;
  218. .logo-container {
  219. margin-bottom: 40rpx;
  220. .logo {
  221. width: 120rpx;
  222. height: 120rpx;
  223. }
  224. }
  225. .login-title {
  226. display: block;
  227. font-size: 48rpx;
  228. font-weight: bold;
  229. color: white;
  230. margin-bottom: 16rpx;
  231. }
  232. .login-subtitle {
  233. display: block;
  234. font-size: 28rpx;
  235. color: rgba(255, 255, 255, 0.8);
  236. }
  237. }
  238. .login-form {
  239. background: white;
  240. border-radius: 24rpx;
  241. padding: 48rpx 32rpx;
  242. box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
  243. .test-section {
  244. margin-bottom: 32rpx;
  245. padding: 16rpx;
  246. background: #f0f8ff;
  247. border-radius: 8rpx;
  248. text {
  249. display: block;
  250. margin-bottom: 16rpx;
  251. font-size: 28rpx;
  252. color: #333;
  253. }
  254. }
  255. :deep(.wd-form-item) {
  256. margin-bottom: 32rpx;
  257. }
  258. :deep(.wd-form-item__label) {
  259. font-weight: 500;
  260. color: #333;
  261. }
  262. :deep(.wd-input) {
  263. height: 88rpx;
  264. border-radius: 12rpx;
  265. background: #f8f9fa;
  266. }
  267. .captcha-container {
  268. display: flex;
  269. align-items: center;
  270. gap: 16rpx;
  271. .captcha-input {
  272. flex: 1;
  273. }
  274. .captcha-image-wrapper {
  275. width: 160rpx;
  276. height: 88rpx;
  277. border-radius: 12rpx;
  278. background: #f8f9fa;
  279. display: flex;
  280. align-items: center;
  281. justify-content: center;
  282. border: 2rpx solid #e9ecef;
  283. cursor: pointer;
  284. transition: all 0.3s ease;
  285. &:active {
  286. transform: scale(0.95);
  287. background: #e9ecef;
  288. }
  289. .captcha-image {
  290. width: 100%;
  291. height: 100%;
  292. border-radius: 8rpx;
  293. }
  294. .captcha-placeholder {
  295. font-size: 24rpx;
  296. color: #999;
  297. }
  298. }
  299. }
  300. .login-actions {
  301. margin: 48rpx 0 32rpx;
  302. :deep(.wd-button) {
  303. height: 96rpx;
  304. font-size: 32rpx;
  305. font-weight: 600;
  306. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  307. border: none;
  308. }
  309. }
  310. .login-footer {
  311. text-align: center;
  312. .footer-text {
  313. font-size: 28rpx;
  314. color: #666;
  315. }
  316. .register-link {
  317. font-size: 28rpx;
  318. color: #667eea;
  319. font-weight: 500;
  320. margin-left: 8rpx;
  321. }
  322. }
  323. }
  324. </style>