login.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. <template>
  2. <view class="login-container">
  3. <view class="login-form">
  4. <view class="logo-section">
  5. <view class="logo-text">自助洗车</view>
  6. <view class="logo-subtitle">运营管理小程序</view>
  7. </view>
  8. <view class="input-group">
  9. <view class="input-label">
  10. <AppIcon name="smartphone" size="20" color="#C6171E" />
  11. <text class="label">手机号</text>
  12. </view>
  13. <input
  14. v-model="loginForm.mobilePhone"
  15. type="number"
  16. placeholder="请输入手机号"
  17. maxlength="11"
  18. class="input-field"
  19. />
  20. </view>
  21. <view class="input-group">
  22. <view class="input-label">
  23. <AppIcon name="lock" size="20" color="#C6171E" />
  24. <text class="label">密码</text>
  25. </view>
  26. <input
  27. v-model="loginForm.password"
  28. type="password"
  29. placeholder="请输入密码"
  30. @confirm="handleLogin"
  31. class="input-field"
  32. />
  33. </view>
  34. <button class="login-btn" @click="handleLogin" :disabled="loading">
  35. {{ loading ? '登录中...' : '登录' }}
  36. </button>
  37. </view>
  38. </view>
  39. </template>
  40. <script setup>
  41. import { ref, onMounted } from 'vue'
  42. import { login } from '../../api/auth.js'
  43. import { loadDicts } from '../../utils/dict.js'
  44. import { storage, showToast } from '../../utils/index.js'
  45. import JSEncrypt from 'jsencrypt'
  46. const loading = ref(false)
  47. const loginForm = ref({
  48. mobilePhone: '',
  49. password: ''
  50. })
  51. // RSA加密函数(与admin-web保持一致)
  52. const encryptData = (str) => {
  53. let encryptor = new JSEncrypt()
  54. // 设置公钥(从后端配置文件获取,与admin-web相同)
  55. let publicKey = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNc4Zrvk3E0mUkO8NOeNYOOaPI4uLoBAuDt9Rp0urX7y0wq7vvQzytvwzXXeM9Xp89j7g4ZLR7qBLBCj3QNPH0SUjE1yy9KVBKdjkPre7WT+plS74s2rJz/hygKiJ3Vxa+Z15v6JEHy/3/+i9gW3p/bCLaMQtvGemNvDXwCTwINQIDAQAB'
  56. encryptor.setPublicKey(publicKey)
  57. // 加密数据
  58. return encryptor.encrypt(str)
  59. }
  60. // 加载字典数据
  61. const loadDictionaries = async () => {
  62. await loadDicts()
  63. }
  64. const handleLogin = async () => {
  65. // 表单验证
  66. const phone = loginForm.value.mobilePhone.trim()
  67. if (!phone) {
  68. showToast('请输入手机号')
  69. return
  70. }
  71. // 手机号格式验证
  72. if (!/^1[3-9]\d{9}$/.test(phone)) {
  73. showToast('请输入正确的手机号')
  74. return
  75. }
  76. if (!loginForm.value.password) {
  77. showToast('请输入密码')
  78. return
  79. }
  80. loading.value = true
  81. try {
  82. // RSA加密密码
  83. const encryptedPassword = encryptData(loginForm.value.password)
  84. const res = await login(phone, encryptedPassword)
  85. if (res && res.code === 200) {
  86. // 保存token和用户信息
  87. storage.set('token', res.data.accessToken)
  88. storage.set('userInfo', {
  89. id: res.data.id,
  90. name: res.data.username || phone,
  91. mobilePhone: phone
  92. })
  93. // 登录成功后加载字典数据(与admin-web保持一致)
  94. await loadDictionaries()
  95. showToast('登录成功', 'success')
  96. // 跳转到首页
  97. setTimeout(() => {
  98. uni.switchTab({
  99. url: '/pages/index/index'
  100. })
  101. }, 800)
  102. } else {
  103. showToast(res.msg || '登录失败')
  104. }
  105. } catch (error) {
  106. console.error('登录失败:', error)
  107. showToast(error.msg || '网络错误,请稍后重试')
  108. } finally {
  109. loading.value = false
  110. }
  111. }
  112. </script>
  113. <style scoped>
  114. /* 登录容器 */
  115. .login-container {
  116. background: #C6171E;
  117. display: flex;
  118. align-items: center;
  119. justify-content: center;
  120. min-height: 100vh;
  121. padding: 60rpx 30rpx;
  122. box-sizing: border-box;
  123. }
  124. /* 登录表单卡片 */
  125. .login-form {
  126. background-color: #FFFFFF;
  127. border-radius: 32rpx;
  128. padding: 80rpx 60rpx;
  129. width: 100%;
  130. max-width: 650rpx;
  131. box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.15);
  132. }
  133. /* Logo区域 */
  134. .logo-section {
  135. text-align: center;
  136. margin-bottom: 80rpx;
  137. }
  138. .logo-text {
  139. font-size: 56rpx;
  140. font-weight: 700;
  141. color: #C6171E;
  142. margin-bottom: 16rpx;
  143. display: block;
  144. letter-spacing: 4rpx;
  145. }
  146. .logo-subtitle {
  147. font-size: 28rpx;
  148. color: #999999;
  149. display: block;
  150. font-weight: 400;
  151. }
  152. /* 输入组 */
  153. .input-group {
  154. margin-bottom: 40rpx;
  155. }
  156. .input-label {
  157. display: flex;
  158. align-items: center;
  159. margin-bottom: 20rpx;
  160. }
  161. .input-label .app-icon {
  162. margin-right: 12rpx;
  163. }
  164. .label {
  165. color: #1A1A1A;
  166. font-size: 28rpx;
  167. font-weight: 600;
  168. }
  169. /* 输入框 */
  170. .input-field {
  171. width: 100%;
  172. height: 90rpx;
  173. padding: 0 30rpx;
  174. border: 2rpx solid #E8E8E8;
  175. border-radius: 16rpx;
  176. font-size: 28rpx;
  177. color: #1A1A1A;
  178. box-sizing: border-box;
  179. background-color: #F5F7FA;
  180. transition: all 0.3s;
  181. }
  182. .input-field:focus {
  183. border-color: #C6171E;
  184. background-color: #FFFFFF;
  185. outline: none;
  186. box-shadow: 0 0 0 4rpx rgba(198, 23, 30, 0.1);
  187. }
  188. /* 登录按钮 */
  189. .login-btn {
  190. width: 100%;
  191. height: 90rpx;
  192. background: #C6171E;
  193. color: #FFFFFF;
  194. border: none;
  195. border-radius: 16rpx;
  196. font-size: 32rpx;
  197. font-weight: 600;
  198. margin-top: 60rpx;
  199. box-shadow: 0 8rpx 24rpx rgba(198, 23, 30, 0.4);
  200. transition: all 0.3s;
  201. }
  202. .login-btn:active {
  203. transform: translateY(2rpx);
  204. box-shadow: 0 6rpx 20rpx rgba(198, 23, 30, 0.25);
  205. }
  206. .login-btn:disabled {
  207. background: #CCCCCC;
  208. box-shadow: none;
  209. opacity: 0.6;
  210. }
  211. </style>