login.vue 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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. <text class="iconfont">📱</text>
  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. <text class="iconfont">🔒</text>
  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 { getDataDictList } from '../../api/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. onMounted(() => {
  62. const token = storage.get('token')
  63. if (token) {
  64. // 如果已有token,直接跳转到首页
  65. uni.switchTab({
  66. url: '/pages/index/index'
  67. })
  68. }
  69. })
  70. // 加载字典数据(与admin-web保持一致)
  71. const loadDictionaries = async () => {
  72. try {
  73. const res = await getDataDictList()
  74. if (res && res.code === 200) {
  75. const list = res.data
  76. // 按code分组,与admin-web保持一致
  77. const dictGroup = {}
  78. list.forEach(item => {
  79. if (!dictGroup[item.code]) {
  80. dictGroup[item.code] = []
  81. }
  82. dictGroup[item.code].push(item)
  83. })
  84. // 保存到缓存
  85. storage.set('dicts', dictGroup)
  86. console.log('字典数据加载成功:', dictGroup)
  87. }
  88. } catch (error) {
  89. console.error('加载字典数据失败:', error)
  90. }
  91. }
  92. const handleLogin = async () => {
  93. // 表单验证
  94. const phone = loginForm.value.mobilePhone.trim()
  95. if (!phone) {
  96. showToast('请输入手机号')
  97. return
  98. }
  99. // 手机号格式验证
  100. if (!/^1[3-9]\d{9}$/.test(phone)) {
  101. showToast('请输入正确的手机号')
  102. return
  103. }
  104. if (!loginForm.value.password) {
  105. showToast('请输入密码')
  106. return
  107. }
  108. loading.value = true
  109. try {
  110. // RSA加密密码
  111. const encryptedPassword = encryptData(loginForm.value.password)
  112. const res = await login(phone, encryptedPassword)
  113. if (res && res.code === 200) {
  114. // 保存token和用户信息
  115. storage.set('token', res.data.accessToken)
  116. storage.set('userInfo', {
  117. id: res.data.id,
  118. name: res.data.username || phone,
  119. mobilePhone: phone
  120. })
  121. // 登录成功后加载字典数据(与admin-web保持一致)
  122. await loadDictionaries()
  123. showToast('登录成功', 'success')
  124. // 跳转到首页
  125. setTimeout(() => {
  126. uni.switchTab({
  127. url: '/pages/index/index'
  128. })
  129. }, 800)
  130. } else {
  131. showToast(res.msg || '登录失败')
  132. }
  133. } catch (error) {
  134. console.error('登录失败:', error)
  135. showToast(error.msg || '网络错误,请稍后重试')
  136. } finally {
  137. loading.value = false
  138. }
  139. }
  140. </script>
  141. <style scoped>
  142. /* 登录容器 */
  143. .login-container {
  144. background: linear-gradient(135deg, #667EEA 0%, #764BA2 100%);
  145. display: flex;
  146. align-items: center;
  147. justify-content: center;
  148. min-height: 100vh;
  149. padding: 60rpx 30rpx;
  150. box-sizing: border-box;
  151. }
  152. /* 登录表单卡片 */
  153. .login-form {
  154. background-color: #FFFFFF;
  155. border-radius: 32rpx;
  156. padding: 80rpx 60rpx;
  157. width: 100%;
  158. max-width: 650rpx;
  159. box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.15);
  160. }
  161. /* Logo区域 */
  162. .logo-section {
  163. text-align: center;
  164. margin-bottom: 80rpx;
  165. }
  166. .logo-text {
  167. font-size: 56rpx;
  168. font-weight: 700;
  169. color: #667EEA;
  170. margin-bottom: 16rpx;
  171. display: block;
  172. letter-spacing: 4rpx;
  173. }
  174. .logo-subtitle {
  175. font-size: 28rpx;
  176. color: #999999;
  177. display: block;
  178. font-weight: 400;
  179. }
  180. /* 输入组 */
  181. .input-group {
  182. margin-bottom: 40rpx;
  183. }
  184. .input-label {
  185. display: flex;
  186. align-items: center;
  187. margin-bottom: 20rpx;
  188. }
  189. .input-label .iconfont {
  190. font-size: 32rpx;
  191. margin-right: 12rpx;
  192. color: #667EEA;
  193. }
  194. .label {
  195. color: #1A1A1A;
  196. font-size: 28rpx;
  197. font-weight: 600;
  198. }
  199. /* 输入框 */
  200. .input-field {
  201. width: 100%;
  202. height: 90rpx;
  203. padding: 0 30rpx;
  204. border: 2rpx solid #E8E8E8;
  205. border-radius: 16rpx;
  206. font-size: 28rpx;
  207. color: #1A1A1A;
  208. box-sizing: border-box;
  209. background-color: #F8F9FA;
  210. transition: all 0.3s;
  211. }
  212. .input-field:focus {
  213. border-color: #667EEA;
  214. background-color: #FFFFFF;
  215. outline: none;
  216. box-shadow: 0 0 0 4rpx rgba(102, 126, 234, 0.1);
  217. }
  218. /* 登录按钮 */
  219. .login-btn {
  220. width: 100%;
  221. height: 90rpx;
  222. background: linear-gradient(90deg, #667EEA 0%, #764BA2 100%);
  223. color: #FFFFFF;
  224. border: none;
  225. border-radius: 16rpx;
  226. font-size: 32rpx;
  227. font-weight: 600;
  228. margin-top: 60rpx;
  229. box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.4);
  230. transition: all 0.3s;
  231. }
  232. .login-btn:active {
  233. transform: translateY(2rpx);
  234. box-shadow: 0 6rpx 20rpx rgba(102, 126, 234, 0.5);
  235. }
  236. .login-btn:disabled {
  237. background: linear-gradient(90deg, #CCCCCC 0%, #BBBBBB 100%);
  238. box-shadow: none;
  239. opacity: 0.6;
  240. }
  241. </style>