Commit e2e5221c9f8b6256a09ff5b14d93aff79e0a0bf5
1 parent
83673de2
验证码登录
Showing
6 changed files
with
344 additions
and
146 deletions
api/user.js
| ... | ... | @@ -11,6 +11,14 @@ export const login = (params) => { |
| 11 | 11 | }; |
| 12 | 12 | |
| 13 | 13 | /** |
| 14 | + * 手机号验证码登录 | |
| 15 | + * @returns {Promise} | |
| 16 | + */ | |
| 17 | +export const smsLogin = (data) => { | |
| 18 | + return post('/admin-api/system/auth/sms-login',data) | |
| 19 | +} | |
| 20 | + | |
| 21 | +/** | |
| 14 | 22 | * 获取用户信息 |
| 15 | 23 | * @returns {Promise} |
| 16 | 24 | */ |
| ... | ... | @@ -68,6 +76,15 @@ export const getUnreadCount = () => { |
| 68 | 76 | } |
| 69 | 77 | |
| 70 | 78 | |
| 79 | +/** | |
| 80 | + * 获取验证码 | |
| 81 | + * @returns {Promise} | |
| 82 | + */ | |
| 83 | +export const getSmsCode = (data) => { | |
| 84 | + return post('/admin-api/system/auth/send-sms-code',data) | |
| 85 | +} | |
| 86 | + | |
| 87 | + | |
| 71 | 88 | |
| 72 | 89 | |
| 73 | 90 | ... | ... |
manifest.json
pages/index/index.vue
| ... | ... | @@ -4,8 +4,8 @@ |
| 4 | 4 | <view class="user-info-bar"> |
| 5 | 5 | <view class="user-info"> |
| 6 | 6 | <view class="user-text"> |
| 7 | - <view class="username">你好{{ userName }},欢迎登录</view> | |
| 8 | - <view class="login-desc">蓟城山水智慧园林养护平台</view> | |
| 7 | + <view class="username">你好{{ userName }}</view> | |
| 8 | + <view class="login-desc">蓟城山水全域智能运营管理平台</view> | |
| 9 | 9 | </view> |
| 10 | 10 | </view> |
| 11 | 11 | <view class="msg-icon" @click="handleMsgClick" hover-class="msg-icon--hover"> | ... | ... |
pages/login/index.vue
| ... | ... | @@ -2,29 +2,20 @@ |
| 2 | 2 | <view class="page-container"> |
| 3 | 3 | <!-- 顶部标题区 --> |
| 4 | 4 | <view class="top-title"> |
| 5 | - <text class="welcome-text">你好,欢迎光临</text> | |
| 6 | -<!-- <text class="platform-name">全域智能运营管理平台</text>--> | |
| 7 | - <text class="platform-name">蓟城山水智慧园林养护平台</text> | |
| 8 | - | |
| 5 | + <text class="welcome-text">你好,欢迎登录</text> | |
| 6 | + <text class="platform-name">蓟城山水全域智能运营管理平台</text> | |
| 9 | 7 | </view> |
| 10 | 8 | |
| 11 | 9 | <!-- 登录表单区域 --> |
| 12 | 10 | <view class="login-form"> |
| 13 | - <!-- uview-plus的Tabs组件 --> | |
| 11 | + <!-- 登录方式切换 --> | |
| 14 | 12 | <up-tabs |
| 15 | 13 | :list="tabList" |
| 16 | 14 | line-width="55" |
| 17 | 15 | line-height="2" |
| 18 | 16 | @change="handleTabChange" |
| 19 | - :activeStyle="{ | |
| 20 | - color: '#3c9cff', | |
| 21 | - fontWeight:'500', | |
| 22 | - fontSize: '14px' // 补充单位,避免样式异常 | |
| 23 | - }" | |
| 24 | - :inactiveStyle="{ | |
| 25 | - color: '#606060', | |
| 26 | - fontSize: '14px' // 补充单位,避免样式异常 | |
| 27 | - }" | |
| 17 | + :activeStyle="{ color: '#3c9cff', fontWeight:'500', fontSize: '14px' }" | |
| 18 | + :inactiveStyle="{ color: '#606060', fontSize: '14px' }" | |
| 28 | 19 | ></up-tabs> |
| 29 | 20 | |
| 30 | 21 | <!-- 表单校验容器 --> |
| ... | ... | @@ -34,71 +25,100 @@ |
| 34 | 25 | ref="loginFormRef" |
| 35 | 26 | labelWidth="0" |
| 36 | 27 | > |
| 37 | - <!-- 手机号输入框 --> | |
| 38 | - <up-form-item v-if="loginType === '手机号登录'" prop="mobile"> | |
| 39 | - <up-input | |
| 40 | - v-model="form.mobile" | |
| 41 | - border="surround" | |
| 42 | - clearable | |
| 43 | - maxlength="11" | |
| 44 | - input-align="left" | |
| 45 | - fontSize="14px" | |
| 46 | - :disabled="isLoading" | |
| 47 | - shape="circle" | |
| 48 | - placeholder="请输入手机号" | |
| 49 | - @blur="() => loginFormRef.validateField('mobile')" | |
| 50 | - /> | |
| 51 | - </up-form-item> | |
| 52 | - | |
| 53 | - <!-- 账号输入框 --> | |
| 54 | - <up-form-item v-else prop="account"> | |
| 55 | - <up-input | |
| 56 | - v-model="form.account" | |
| 57 | - border="surround" | |
| 58 | - clearable | |
| 59 | - maxlength="30" | |
| 60 | - input-align="left" | |
| 61 | - fontSize="14px" | |
| 62 | - :disabled="isLoading" | |
| 63 | - shape="circle" | |
| 64 | - placeholder="请输入账户" | |
| 65 | - @blur="() => loginFormRef.validateField('account')" | |
| 66 | - /> | |
| 67 | - </up-form-item> | |
| 68 | - | |
| 69 | - <!-- 密码输入框 --> | |
| 70 | - <up-form-item | |
| 71 | - prop="password" | |
| 72 | - class="password-item" | |
| 73 | - > | |
| 74 | - <up-input | |
| 75 | - v-model="form.password" | |
| 76 | - placeholder="请输入密码" | |
| 77 | - maxlength="20" | |
| 78 | - border="surround" | |
| 79 | - clearable | |
| 80 | - input-align="left" | |
| 81 | - type="password" | |
| 82 | - :disabled="isLoading" | |
| 83 | - shape="circle" | |
| 84 | - @blur="() => loginFormRef.validateField('password')" | |
| 85 | - /> | |
| 86 | - </up-form-item> | |
| 87 | - | |
| 88 | - <!-- 记住密码单独一行,居右显示 --> | |
| 89 | - <view class="remember-wrap"> | |
| 90 | - <up-checkbox | |
| 91 | - :customStyle="{marginBottom: '8px'}" | |
| 92 | - label="记住密码" | |
| 93 | - name="agree" | |
| 94 | - size="14" | |
| 95 | - iconSize="14" | |
| 96 | - labelSize="14" | |
| 97 | - labelColor="'#3F3F3F'" | |
| 98 | - usedAlone | |
| 99 | - v-model:checked="rememberPwd" | |
| 100 | - > | |
| 101 | - </up-checkbox> | |
| 28 | + <!-- ========== 手机号登录区域 ========== --> | |
| 29 | + <view v-if="loginType === '手机号登录'"> | |
| 30 | + <!-- 手机号输入框 --> | |
| 31 | + <up-form-item prop="mobile"> | |
| 32 | + <up-input | |
| 33 | + v-model="form.mobile" | |
| 34 | + border="surround" | |
| 35 | + maxlength="11" | |
| 36 | + input-align="left" | |
| 37 | + fontSize="14px" | |
| 38 | + :disabled="isLoading" | |
| 39 | + shape="circle" | |
| 40 | + placeholder="请输入手机号" | |
| 41 | + @blur="() => loginFormRef.validateField('mobile')" | |
| 42 | + /> | |
| 43 | + </up-form-item> | |
| 44 | + | |
| 45 | + <!-- 验证码输入框 + 自定义倒计时按钮 --> | |
| 46 | + <up-form-item prop="code"> | |
| 47 | + <up-input | |
| 48 | + v-model="form.code" | |
| 49 | + border="surround" | |
| 50 | + maxlength="4" | |
| 51 | + type="number" | |
| 52 | + input-align="left" | |
| 53 | + fontSize="14px" | |
| 54 | + :disabled="isLoading" | |
| 55 | + shape="circle" | |
| 56 | + placeholder="请输入验证码" | |
| 57 | + @blur="() => loginFormRef.validateField('code')" | |
| 58 | + > | |
| 59 | + <template #suffix> | |
| 60 | + <up-button | |
| 61 | + size="mini" | |
| 62 | + type="primary" | |
| 63 | + :disabled="isCodeBtnDisabled" | |
| 64 | + :loading="isCodeLoading" | |
| 65 | + @tap="getVerificationCode" | |
| 66 | + shape="circle" | |
| 67 | + > | |
| 68 | + {{ countdown > 0 ? `${countdown}秒后重新获取` : '获取验证码' }} | |
| 69 | + </up-button> | |
| 70 | + </template> | |
| 71 | + </up-input> | |
| 72 | + </up-form-item> | |
| 73 | + </view> | |
| 74 | + | |
| 75 | + <!-- ========== 账号登录区域 ========== --> | |
| 76 | + <view v-else> | |
| 77 | + <!-- 账号输入框 --> | |
| 78 | + <up-form-item prop="account"> | |
| 79 | + <up-input | |
| 80 | + v-model="form.account" | |
| 81 | + border="surround" | |
| 82 | + maxlength="30" | |
| 83 | + input-align="left" | |
| 84 | + fontSize="14px" | |
| 85 | + :disabled="isLoading" | |
| 86 | + shape="circle" | |
| 87 | + placeholder="请输入账户" | |
| 88 | + @blur="() => loginFormRef.validateField('account')" | |
| 89 | + /> | |
| 90 | + </up-form-item> | |
| 91 | + | |
| 92 | + <!-- 密码输入框 --> | |
| 93 | + <up-form-item prop="password" class="password-item"> | |
| 94 | + <up-input | |
| 95 | + v-model="form.password" | |
| 96 | + placeholder="请输入密码" | |
| 97 | + maxlength="20" | |
| 98 | + border="surround" | |
| 99 | + input-align="left" | |
| 100 | + type="password" | |
| 101 | + :disabled="isLoading" | |
| 102 | + shape="circle" | |
| 103 | + @blur="() => loginFormRef.validateField('password')" | |
| 104 | + /> | |
| 105 | + </up-form-item> | |
| 106 | + | |
| 107 | + <!-- 记住密码 --> | |
| 108 | + <view class="remember-wrap"> | |
| 109 | + <up-checkbox | |
| 110 | + :customStyle="{marginBottom: '8px'}" | |
| 111 | + label="记住密码" | |
| 112 | + name="agree" | |
| 113 | + size="14" | |
| 114 | + iconSize="14" | |
| 115 | + labelSize="14" | |
| 116 | + labelColor="#3F3F3F" | |
| 117 | + usedAlone | |
| 118 | + v-model:checked="rememberPwd" | |
| 119 | + > | |
| 120 | + </up-checkbox> | |
| 121 | + </view> | |
| 102 | 122 | </view> |
| 103 | 123 | </up-form> |
| 104 | 124 | |
| ... | ... | @@ -121,10 +141,12 @@ |
| 121 | 141 | </template> |
| 122 | 142 | |
| 123 | 143 | <script setup> |
| 124 | -import { ref, reactive, onMounted, nextTick } from 'vue'; | |
| 144 | +import { ref, reactive, onMounted, nextTick, computed, onUnmounted} from 'vue'; | |
| 145 | +import {onShow} from '@dcloudio/uni-app' | |
| 125 | 146 | import { useUserStore } from '@/pinia/user'; |
| 126 | 147 | import globalConfig from '@/common/config/global'; |
| 127 | 148 | import CryptoJS from 'crypto-js'; |
| 149 | +import { getSmsCode } from "@/api/user"; | |
| 128 | 150 | |
| 129 | 151 | // ========== 加密工具函数 ========== |
| 130 | 152 | const CRYPTO_CONFIG = { |
| ... | ... | @@ -163,26 +185,101 @@ const aesDecrypt = (encryptedText) => { |
| 163 | 185 | } |
| 164 | 186 | }; |
| 165 | 187 | |
| 166 | -// ========== 业务逻辑 ========== | |
| 188 | +// ========== 核心业务逻辑 ========== | |
| 167 | 189 | const userStore = useUserStore(); |
| 168 | 190 | const loginFormRef = ref(null); |
| 169 | 191 | const isLoading = ref(false); |
| 192 | +const isCodeLoading = ref(false); // 验证码按钮独立loading状态 | |
| 170 | 193 | |
| 171 | -// Tabs配置 | |
| 172 | -const tabList = ref([ | |
| 173 | - { name: '手机号登录' }, | |
| 174 | - { name: '账号登录' } | |
| 175 | -]); | |
| 194 | +// 登录方式切换 | |
| 195 | +const tabList = ref([{ name: '手机号登录' }, { name: '账号登录' }]); | |
| 176 | 196 | const loginType = ref('手机号登录'); |
| 177 | 197 | |
| 178 | -// 记住密码 | |
| 198 | +// 记住密码(仅账号登录生效) | |
| 179 | 199 | const rememberPwd = ref(true); |
| 180 | 200 | |
| 181 | 201 | // 表单数据 |
| 182 | 202 | const form = reactive({ |
| 183 | 203 | account: '', // 账号 |
| 184 | 204 | mobile: '', // 手机号 |
| 185 | - password: '' // 密码 | |
| 205 | + password: '', // 密码 | |
| 206 | + code: '' // 验证码 | |
| 207 | +}); | |
| 208 | + | |
| 209 | +// 验证码倒计时相关 | |
| 210 | +const countdown = ref(0); | |
| 211 | +let countdownTimer = null; // 倒计时定时器 | |
| 212 | + | |
| 213 | +// 临时缓存KEY | |
| 214 | +const TEMP_FORM_KEY = 'login_temp_form_data'; | |
| 215 | + | |
| 216 | +// ========== 方案二核心:保存/恢复表单数据 ========== | |
| 217 | +// 保存表单数据到本地缓存 | |
| 218 | +const saveFormData = () => { | |
| 219 | + try { | |
| 220 | + const tempData = { | |
| 221 | + form: { ...form }, | |
| 222 | + loginType: loginType.value, | |
| 223 | + countdown: countdown.value, | |
| 224 | + rememberPwd: rememberPwd.value | |
| 225 | + }; | |
| 226 | + // 加密存储,提升安全性 | |
| 227 | + const encryptData = aesEncrypt(JSON.stringify(tempData)); | |
| 228 | + uni.setStorageSync(TEMP_FORM_KEY, encryptData); | |
| 229 | + } catch (err) { | |
| 230 | + console.warn('保存表单数据失败:', err); | |
| 231 | + } | |
| 232 | +}; | |
| 233 | + | |
| 234 | +// 从本地缓存恢复表单数据 | |
| 235 | +const restoreFormData = () => { | |
| 236 | + try { | |
| 237 | + const encryptData = uni.getStorageSync(TEMP_FORM_KEY); | |
| 238 | + if (!encryptData) return; | |
| 239 | + | |
| 240 | + const tempData = JSON.parse(aesDecrypt(encryptData)); | |
| 241 | + // 恢复表单数据 | |
| 242 | + if (tempData.form) { | |
| 243 | + Object.assign(form, tempData.form); | |
| 244 | + } | |
| 245 | + // 恢复登录类型 | |
| 246 | + // if (tempData.loginType) { | |
| 247 | + // loginType.value = tempData.loginType; | |
| 248 | + // } | |
| 249 | + // 恢复记住密码状态 | |
| 250 | + if (tempData.rememberPwd !== undefined) { | |
| 251 | + rememberPwd.value = tempData.rememberPwd; | |
| 252 | + } | |
| 253 | + // 恢复倒计时 | |
| 254 | + if (tempData.countdown && tempData.countdown > 0) { | |
| 255 | + countdown.value = tempData.countdown; | |
| 256 | + // 重启倒计时定时器 | |
| 257 | + startCountdown(countdown.value); | |
| 258 | + } | |
| 259 | + | |
| 260 | + // 恢复记住密码的缓存数据(原有逻辑) | |
| 261 | + if (rememberPwd.value && loginType.value === '账号登录') { | |
| 262 | + const savedAccount = aesDecrypt(uni.getStorageSync('login_account') || ''); | |
| 263 | + const savedPwd = aesDecrypt(uni.getStorageSync('login_password') || ''); | |
| 264 | + if (savedAccount) { | |
| 265 | + form.account = savedAccount; | |
| 266 | + form.password = savedPwd; | |
| 267 | + } | |
| 268 | + } | |
| 269 | + } catch (err) { | |
| 270 | + console.warn('恢复表单数据失败:', err); | |
| 271 | + } | |
| 272 | +}; | |
| 273 | + | |
| 274 | +// 校验手机号格式 | |
| 275 | +const isMobileValid = computed(() => { | |
| 276 | + const mobileReg = /^1[3-9]\d{9}$/; | |
| 277 | + return mobileReg.test(form.mobile); | |
| 278 | +}); | |
| 279 | + | |
| 280 | +// 统一计算验证码按钮禁用状态 | |
| 281 | +const isCodeBtnDisabled = computed(() => { | |
| 282 | + return countdown.value > 0 || isLoading.value || isCodeLoading.value || !isMobileValid.value; | |
| 186 | 283 | }); |
| 187 | 284 | |
| 188 | 285 | // 表单校验规则 |
| ... | ... | @@ -198,20 +295,81 @@ const loginFormRules = reactive({ |
| 198 | 295 | password: [ |
| 199 | 296 | { type: 'string', required: true, message: '请输入登录密码', trigger: ['change', 'blur'] }, |
| 200 | 297 | { type: 'string', min: 3, max: 20, message: '密码长度为3-20个字符', trigger: ['change', 'blur'] } |
| 298 | + ], | |
| 299 | + code: [ | |
| 300 | + { type: 'string', required: true, message: '请输入验证码', trigger: ['change', 'blur'] }, | |
| 301 | + { type: 'string', len: 4, message: '验证码长度4位', trigger: ['change', 'blur'] } | |
| 201 | 302 | ] |
| 202 | 303 | }); |
| 203 | 304 | |
| 204 | -// Tabs切换事件:移除清空输入框的逻辑 | |
| 305 | +// 切换登录方式 | |
| 205 | 306 | const handleTabChange = ({ name }) => { |
| 206 | 307 | if (isLoading.value) return; |
| 207 | - console.log('切换到:', name); | |
| 308 | + | |
| 208 | 309 | loginType.value = name; |
| 209 | - // 只清空校验状态,不清除输入框内容 | |
| 310 | + // 清空校验状态和无关输入框 | |
| 210 | 311 | nextTick(() => { |
| 211 | 312 | loginFormRef.value?.clearValidate(); |
| 212 | 313 | }); |
| 213 | 314 | }; |
| 214 | 315 | |
| 316 | +// 开始验证码倒计时 | |
| 317 | +const startCountdown = (seconds = 60) => { | |
| 318 | + countdown.value = seconds; | |
| 319 | + | |
| 320 | + // 清除已有定时器,避免重复 | |
| 321 | + if (countdownTimer) clearInterval(countdownTimer); | |
| 322 | + | |
| 323 | + countdownTimer = setInterval(() => { | |
| 324 | + if (countdown.value <= 0) { | |
| 325 | + clearCountdown(); | |
| 326 | + return; | |
| 327 | + } | |
| 328 | + countdown.value--; | |
| 329 | + }, 1000); | |
| 330 | +}; | |
| 331 | + | |
| 332 | +// 清除倒计时 | |
| 333 | +const clearCountdown = () => { | |
| 334 | + countdown.value = 0; | |
| 335 | + if (countdownTimer) { | |
| 336 | + clearInterval(countdownTimer); | |
| 337 | + countdownTimer = null; | |
| 338 | + } | |
| 339 | +}; | |
| 340 | + | |
| 341 | +// 获取验证码 | |
| 342 | +const getVerificationCode = async () => { | |
| 343 | + // 双重校验:按钮禁用状态 | |
| 344 | + if (isCodeBtnDisabled.value) { | |
| 345 | + console.warn('验证码按钮已禁用,不执行请求'); | |
| 346 | + return; | |
| 347 | + } | |
| 348 | + | |
| 349 | + try { | |
| 350 | + isCodeLoading.value = true; // 按钮进入loading | |
| 351 | + uni.showLoading({ title: '发送验证码中...', mask: true }); // 遮罩防止重复点击 | |
| 352 | + | |
| 353 | + const postData = { | |
| 354 | + mobile: form.mobile, | |
| 355 | + scene: 21 | |
| 356 | + }; | |
| 357 | + // 调用验证码接口 | |
| 358 | + const res = await getSmsCode(postData); | |
| 359 | + | |
| 360 | + uni.hideLoading(); | |
| 361 | + uni.$u.toast('验证码已发送,请注意查收'); | |
| 362 | + startCountdown(); // 仅接口成功时开始倒计时 | |
| 363 | + } catch (err) { | |
| 364 | + // 捕获接口异常 | |
| 365 | + uni.hideLoading(); | |
| 366 | + console.error('获取验证码失败:', err); | |
| 367 | + uni.$u.toast(err.msg || '发送验证码失败,请重试'); | |
| 368 | + } finally { | |
| 369 | + isCodeLoading.value = false; // 结束loading状态 | |
| 370 | + } | |
| 371 | +}; | |
| 372 | + | |
| 215 | 373 | // 检查登录状态 |
| 216 | 374 | const checkLoginStatus = () => { |
| 217 | 375 | try { |
| ... | ... | @@ -226,45 +384,56 @@ const checkLoginStatus = () => { |
| 226 | 384 | } |
| 227 | 385 | }; |
| 228 | 386 | |
| 229 | -// 登录方法 | |
| 387 | +// 登录处理 | |
| 230 | 388 | const handleLogin = async () => { |
| 231 | 389 | try { |
| 232 | - await loginFormRef.value.validate(); | |
| 390 | + // 根据登录类型校验对应字段 | |
| 391 | + const validateFields = loginType.value === '手机号登录' | |
| 392 | + ? ['mobile', 'code'] | |
| 393 | + : ['account', 'password']; | |
| 394 | + | |
| 395 | + // 执行表单校验 | |
| 396 | + const validateRes = await loginFormRef.value.validate(validateFields); | |
| 397 | + if (!validateRes) { | |
| 398 | + return; | |
| 399 | + } | |
| 400 | + | |
| 233 | 401 | isLoading.value = true; |
| 234 | 402 | |
| 235 | 403 | // 组装登录参数 |
| 236 | - const loginParams = { password: form.password }; | |
| 404 | + const loginParams = {}; | |
| 237 | 405 | if (loginType.value === '手机号登录') { |
| 238 | - loginParams.username = form.mobile; | |
| 406 | + loginParams.mobile = form.mobile; | |
| 407 | + loginParams.code = form.code; | |
| 408 | + loginParams.type = 'sms'; | |
| 239 | 409 | } else { |
| 240 | 410 | loginParams.username = form.account; |
| 411 | + loginParams.password = form.password; | |
| 412 | + loginParams.type = 'password'; | |
| 241 | 413 | } |
| 242 | 414 | |
| 243 | 415 | // 执行登录 |
| 244 | 416 | await userStore.login(loginParams); |
| 245 | 417 | |
| 246 | - // 保存数据:账号/密码 均用AES加密 | |
| 247 | - if (rememberPwd.value) { | |
| 418 | + // 保存登录信息(仅账号登录且勾选记住密码) | |
| 419 | + if (loginType.value === '账号登录' && rememberPwd.value) { | |
| 248 | 420 | try { |
| 249 | - console.log('123+'+loginType.value) | |
| 250 | - if (loginType.value === '手机号登录') { | |
| 251 | - uni.setStorageSync('login_mobile', aesEncrypt(form.mobile)); | |
| 252 | - uni.removeStorageSync('login_account'); // 确保只存一种类型 | |
| 253 | - } else { | |
| 254 | - uni.setStorageSync('login_account', aesEncrypt(form.account)); | |
| 255 | - uni.removeStorageSync('login_mobile'); // 确保只存一种类型 | |
| 256 | - } | |
| 421 | + uni.setStorageSync('login_account', aesEncrypt(form.account)); | |
| 257 | 422 | uni.setStorageSync('login_password', aesEncrypt(form.password)); |
| 423 | + uni.removeStorageSync('login_mobile'); | |
| 258 | 424 | } catch (err) { |
| 259 | 425 | console.warn('保存登录信息失败:', err); |
| 260 | 426 | } |
| 261 | 427 | } else { |
| 262 | - // 清除所有缓存 | |
| 428 | + // 清除缓存 | |
| 263 | 429 | uni.removeStorageSync('login_account'); |
| 264 | 430 | uni.removeStorageSync('login_mobile'); |
| 265 | 431 | uni.removeStorageSync('login_password'); |
| 266 | 432 | } |
| 267 | 433 | |
| 434 | + // 登录成功后清除临时表单缓存 | |
| 435 | + uni.removeStorageSync(TEMP_FORM_KEY); | |
| 436 | + | |
| 268 | 437 | // 登录成功跳转 |
| 269 | 438 | uni.showToast({ title: '登录成功', icon: 'success', duration: 1000 }); |
| 270 | 439 | setTimeout(() => { |
| ... | ... | @@ -274,58 +443,50 @@ const handleLogin = async () => { |
| 274 | 443 | }); |
| 275 | 444 | }, 1000); |
| 276 | 445 | } catch (err) { |
| 277 | - if (!Array.isArray(err)) { | |
| 278 | - console.error('登录失败:', err); | |
| 279 | - uni.showToast({ | |
| 280 | - title: err.msg || '账号或密码错误,请重试', | |
| 281 | - icon: 'none', | |
| 282 | - duration: 1000 | |
| 283 | - }); | |
| 284 | - } | |
| 446 | + console.error('登录失败:', err); | |
| 447 | + uni.showToast({ | |
| 448 | + title: err.msg || (loginType.value === '手机号登录' ? '验证码错误' : '账号或密码错误'), | |
| 449 | + icon: 'none', | |
| 450 | + duration: 1000 | |
| 451 | + }); | |
| 285 | 452 | } finally { |
| 286 | 453 | isLoading.value = false; |
| 287 | 454 | } |
| 288 | 455 | }; |
| 289 | 456 | |
| 290 | -// 生命周期 | |
| 457 | +// ========== 小程序生命周期适配(方案二核心) ========== | |
| 458 | +// 页面显示时(切回页面)恢复数据 | |
| 459 | +onShow(() => { | |
| 460 | + restoreFormData(); | |
| 461 | +}); | |
| 462 | + | |
| 463 | +// 页面卸载时保存数据 | |
| 464 | +onUnmounted(() => { | |
| 465 | + clearCountdown(); | |
| 466 | + saveFormData(); | |
| 467 | +}); | |
| 468 | + | |
| 469 | +// 页面挂载时初始化 | |
| 291 | 470 | onMounted(() => { |
| 292 | 471 | checkLoginStatus(); |
| 472 | + | |
| 293 | 473 | // 初始化表单规则 |
| 294 | 474 | nextTick(() => { |
| 295 | 475 | loginFormRef.value?.setRules(loginFormRules); |
| 476 | + // 首次挂载也恢复一次数据 | |
| 477 | + restoreFormData(); | |
| 296 | 478 | }); |
| 297 | - // 读取缓存并解密填充:修复逻辑漏洞,确保只填充当前登录类型的输入框 | |
| 298 | - if (rememberPwd.value) { | |
| 299 | - try { | |
| 300 | - const savedAccount = aesDecrypt(uni.getStorageSync('login_account') || ''); | |
| 301 | - const savedMobile = aesDecrypt(uni.getStorageSync('login_mobile') || ''); | |
| 302 | - const savedPwd = aesDecrypt(uni.getStorageSync('login_password') || ''); | |
| 303 | - | |
| 304 | - // 核心修正:只填充对应类型的输入框,另一类置空 | |
| 305 | - if (savedAccount) { | |
| 306 | - form.account = savedAccount; | |
| 307 | - form.mobile = ''; // 清空手机号输入框 | |
| 308 | - // loginType.value = '账号登录'; | |
| 309 | - } else if (savedMobile) { | |
| 310 | - form.mobile = savedMobile; | |
| 311 | - form.account = ''; // 清空账号输入框 | |
| 312 | - // loginType.value = '手机号登录'; | |
| 313 | - } | |
| 314 | - form.password = savedPwd; | |
| 315 | - } catch (err) { | |
| 316 | - console.warn('读取缓存失败:', err); | |
| 317 | - } | |
| 318 | - } | |
| 319 | 479 | }); |
| 320 | 480 | </script> |
| 321 | 481 | |
| 322 | 482 | <style scoped lang="scss"> |
| 323 | -.page-container{ | |
| 324 | - height:100vh; | |
| 483 | +.page-container { | |
| 484 | + height: 100vh; | |
| 325 | 485 | background: url("https://img.jichengshanshui.com.cn:28207/appimg/loginbg.jpg") no-repeat; |
| 326 | 486 | background-position: bottom center; |
| 327 | 487 | background-size: 100% 240px; |
| 328 | 488 | } |
| 489 | + | |
| 329 | 490 | .top-title { |
| 330 | 491 | background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat; |
| 331 | 492 | background-size: 100% 100%; |
| ... | ... | @@ -361,9 +522,11 @@ onMounted(() => { |
| 361 | 522 | |
| 362 | 523 | :deep(.u-tabs) { |
| 363 | 524 | margin-bottom: 15px; |
| 525 | + | |
| 364 | 526 | .u-tabs__content { |
| 365 | 527 | height: auto !important; |
| 366 | 528 | } |
| 529 | + | |
| 367 | 530 | .u-tab-item { |
| 368 | 531 | padding: 0 10px; |
| 369 | 532 | } |
| ... | ... | @@ -415,4 +578,15 @@ onMounted(() => { |
| 415 | 578 | position: fixed; |
| 416 | 579 | bottom: 60px; |
| 417 | 580 | } |
| 581 | + | |
| 582 | +// 验证码按钮样式优化 | |
| 583 | +:deep(.u-input__suffix) { | |
| 584 | + padding-right: 10rpx; | |
| 585 | +} | |
| 586 | + | |
| 587 | +:deep(.u-button--mini) { | |
| 588 | + height: 60rpx; | |
| 589 | + line-height: 60rpx; | |
| 590 | + padding: 0 15rpx; | |
| 591 | +} | |
| 418 | 592 | </style> |
| 419 | 593 | \ No newline at end of file | ... | ... |
pages/workbench/index.vue
| ... | ... | @@ -10,8 +10,8 @@ |
| 10 | 10 | |
| 11 | 11 | <!-- 蓝色装饰块 --> |
| 12 | 12 | <view class="blue-decor-block" v-show="!loading"> |
| 13 | - <text class="welcome-text u-line-1">你好{{ userInfo?.user?.nickname || '' }},欢迎登录</text> | |
| 14 | - <text class="platform-name">蓟城山水智慧园林养护平台</text> | |
| 13 | + <text class="welcome-text u-line-1">你好{{ userInfo?.user?.nickname || '' }}</text> | |
| 14 | + <text class="platform-name">蓟城山水全域智能运营管理平台</text> | |
| 15 | 15 | </view> |
| 16 | 16 | |
| 17 | 17 | <!-- 内容容器 --> | ... | ... |
pinia/user.js
| ... | ... | @@ -2,7 +2,7 @@ import { defineStore } from 'pinia'; |
| 2 | 2 | import cache from '@/common/utils/cache'; |
| 3 | 3 | import globalConfig from '@/common/config/global'; |
| 4 | 4 | // 新增:导入 refreshToken 接口 |
| 5 | -import { login, getUserInfo, logout, moduleList, getSimpleDictDataList, refreshToken } from '@/api/user'; | |
| 5 | +import { login,smsLogin, getUserInfo, logout, moduleList, getSimpleDictDataList, refreshToken } from '@/api/user'; | |
| 6 | 6 | |
| 7 | 7 | // 新增:定义刷新Token的定时器标识(避免重复创建定时器) |
| 8 | 8 | let refreshTokenTimer = null; |
| ... | ... | @@ -41,7 +41,14 @@ export const useUserStore = defineStore('user', { |
| 41 | 41 | actions: { |
| 42 | 42 | async login(params) { |
| 43 | 43 | try { |
| 44 | - const res = await login(params); | |
| 44 | + // smsLogin | |
| 45 | + let res | |
| 46 | + if(params.type=='sms'){ | |
| 47 | + res = await smsLogin(params); | |
| 48 | + }else{ | |
| 49 | + res = await login(params); | |
| 50 | + } | |
| 51 | + | |
| 45 | 52 | // 新增:从登录接口返回值中获取 refreshToken(若接口返回字段名不一致,可调整,如 res.refresh_token) |
| 46 | 53 | const { accessToken, expiresTime, userId, refreshToken } = res; |
| 47 | 54 | ... | ... |