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,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 | * @returns {Promise} | 23 | * @returns {Promise} |
| 16 | */ | 24 | */ |
| @@ -68,6 +76,15 @@ export const getUnreadCount = () => { | @@ -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,8 +4,8 @@ | ||
| 4 | <view class="user-info-bar"> | 4 | <view class="user-info-bar"> |
| 5 | <view class="user-info"> | 5 | <view class="user-info"> |
| 6 | <view class="user-text"> | 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 | </view> | 9 | </view> |
| 10 | </view> | 10 | </view> |
| 11 | <view class="msg-icon" @click="handleMsgClick" hover-class="msg-icon--hover"> | 11 | <view class="msg-icon" @click="handleMsgClick" hover-class="msg-icon--hover"> |
pages/login/index.vue
| @@ -2,29 +2,20 @@ | @@ -2,29 +2,20 @@ | ||
| 2 | <view class="page-container"> | 2 | <view class="page-container"> |
| 3 | <!-- 顶部标题区 --> | 3 | <!-- 顶部标题区 --> |
| 4 | <view class="top-title"> | 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 | </view> | 7 | </view> |
| 10 | 8 | ||
| 11 | <!-- 登录表单区域 --> | 9 | <!-- 登录表单区域 --> |
| 12 | <view class="login-form"> | 10 | <view class="login-form"> |
| 13 | - <!-- uview-plus的Tabs组件 --> | 11 | + <!-- 登录方式切换 --> |
| 14 | <up-tabs | 12 | <up-tabs |
| 15 | :list="tabList" | 13 | :list="tabList" |
| 16 | line-width="55" | 14 | line-width="55" |
| 17 | line-height="2" | 15 | line-height="2" |
| 18 | @change="handleTabChange" | 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 | ></up-tabs> | 19 | ></up-tabs> |
| 29 | 20 | ||
| 30 | <!-- 表单校验容器 --> | 21 | <!-- 表单校验容器 --> |
| @@ -34,71 +25,100 @@ | @@ -34,71 +25,100 @@ | ||
| 34 | ref="loginFormRef" | 25 | ref="loginFormRef" |
| 35 | labelWidth="0" | 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 | </view> | 122 | </view> |
| 103 | </up-form> | 123 | </up-form> |
| 104 | 124 | ||
| @@ -121,10 +141,12 @@ | @@ -121,10 +141,12 @@ | ||
| 121 | </template> | 141 | </template> |
| 122 | 142 | ||
| 123 | <script setup> | 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 | import { useUserStore } from '@/pinia/user'; | 146 | import { useUserStore } from '@/pinia/user'; |
| 126 | import globalConfig from '@/common/config/global'; | 147 | import globalConfig from '@/common/config/global'; |
| 127 | import CryptoJS from 'crypto-js'; | 148 | import CryptoJS from 'crypto-js'; |
| 149 | +import { getSmsCode } from "@/api/user"; | ||
| 128 | 150 | ||
| 129 | // ========== 加密工具函数 ========== | 151 | // ========== 加密工具函数 ========== |
| 130 | const CRYPTO_CONFIG = { | 152 | const CRYPTO_CONFIG = { |
| @@ -163,26 +185,101 @@ const aesDecrypt = (encryptedText) => { | @@ -163,26 +185,101 @@ const aesDecrypt = (encryptedText) => { | ||
| 163 | } | 185 | } |
| 164 | }; | 186 | }; |
| 165 | 187 | ||
| 166 | -// ========== 业务逻辑 ========== | 188 | +// ========== 核心业务逻辑 ========== |
| 167 | const userStore = useUserStore(); | 189 | const userStore = useUserStore(); |
| 168 | const loginFormRef = ref(null); | 190 | const loginFormRef = ref(null); |
| 169 | const isLoading = ref(false); | 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 | const loginType = ref('手机号登录'); | 196 | const loginType = ref('手机号登录'); |
| 177 | 197 | ||
| 178 | -// 记住密码 | 198 | +// 记住密码(仅账号登录生效) |
| 179 | const rememberPwd = ref(true); | 199 | const rememberPwd = ref(true); |
| 180 | 200 | ||
| 181 | // 表单数据 | 201 | // 表单数据 |
| 182 | const form = reactive({ | 202 | const form = reactive({ |
| 183 | account: '', // 账号 | 203 | account: '', // 账号 |
| 184 | mobile: '', // 手机号 | 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,20 +295,81 @@ const loginFormRules = reactive({ | ||
| 198 | password: [ | 295 | password: [ |
| 199 | { type: 'string', required: true, message: '请输入登录密码', trigger: ['change', 'blur'] }, | 296 | { type: 'string', required: true, message: '请输入登录密码', trigger: ['change', 'blur'] }, |
| 200 | { type: 'string', min: 3, max: 20, message: '密码长度为3-20个字符', trigger: ['change', 'blur'] } | 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 | const handleTabChange = ({ name }) => { | 306 | const handleTabChange = ({ name }) => { |
| 206 | if (isLoading.value) return; | 307 | if (isLoading.value) return; |
| 207 | - console.log('切换到:', name); | 308 | + |
| 208 | loginType.value = name; | 309 | loginType.value = name; |
| 209 | - // 只清空校验状态,不清除输入框内容 | 310 | + // 清空校验状态和无关输入框 |
| 210 | nextTick(() => { | 311 | nextTick(() => { |
| 211 | loginFormRef.value?.clearValidate(); | 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 | const checkLoginStatus = () => { | 374 | const checkLoginStatus = () => { |
| 217 | try { | 375 | try { |
| @@ -226,45 +384,56 @@ const checkLoginStatus = () => { | @@ -226,45 +384,56 @@ const checkLoginStatus = () => { | ||
| 226 | } | 384 | } |
| 227 | }; | 385 | }; |
| 228 | 386 | ||
| 229 | -// 登录方法 | 387 | +// 登录处理 |
| 230 | const handleLogin = async () => { | 388 | const handleLogin = async () => { |
| 231 | try { | 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 | isLoading.value = true; | 401 | isLoading.value = true; |
| 234 | 402 | ||
| 235 | // 组装登录参数 | 403 | // 组装登录参数 |
| 236 | - const loginParams = { password: form.password }; | 404 | + const loginParams = {}; |
| 237 | if (loginType.value === '手机号登录') { | 405 | if (loginType.value === '手机号登录') { |
| 238 | - loginParams.username = form.mobile; | 406 | + loginParams.mobile = form.mobile; |
| 407 | + loginParams.code = form.code; | ||
| 408 | + loginParams.type = 'sms'; | ||
| 239 | } else { | 409 | } else { |
| 240 | loginParams.username = form.account; | 410 | loginParams.username = form.account; |
| 411 | + loginParams.password = form.password; | ||
| 412 | + loginParams.type = 'password'; | ||
| 241 | } | 413 | } |
| 242 | 414 | ||
| 243 | // 执行登录 | 415 | // 执行登录 |
| 244 | await userStore.login(loginParams); | 416 | await userStore.login(loginParams); |
| 245 | 417 | ||
| 246 | - // 保存数据:账号/密码 均用AES加密 | ||
| 247 | - if (rememberPwd.value) { | 418 | + // 保存登录信息(仅账号登录且勾选记住密码) |
| 419 | + if (loginType.value === '账号登录' && rememberPwd.value) { | ||
| 248 | try { | 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 | uni.setStorageSync('login_password', aesEncrypt(form.password)); | 422 | uni.setStorageSync('login_password', aesEncrypt(form.password)); |
| 423 | + uni.removeStorageSync('login_mobile'); | ||
| 258 | } catch (err) { | 424 | } catch (err) { |
| 259 | console.warn('保存登录信息失败:', err); | 425 | console.warn('保存登录信息失败:', err); |
| 260 | } | 426 | } |
| 261 | } else { | 427 | } else { |
| 262 | - // 清除所有缓存 | 428 | + // 清除缓存 |
| 263 | uni.removeStorageSync('login_account'); | 429 | uni.removeStorageSync('login_account'); |
| 264 | uni.removeStorageSync('login_mobile'); | 430 | uni.removeStorageSync('login_mobile'); |
| 265 | uni.removeStorageSync('login_password'); | 431 | uni.removeStorageSync('login_password'); |
| 266 | } | 432 | } |
| 267 | 433 | ||
| 434 | + // 登录成功后清除临时表单缓存 | ||
| 435 | + uni.removeStorageSync(TEMP_FORM_KEY); | ||
| 436 | + | ||
| 268 | // 登录成功跳转 | 437 | // 登录成功跳转 |
| 269 | uni.showToast({ title: '登录成功', icon: 'success', duration: 1000 }); | 438 | uni.showToast({ title: '登录成功', icon: 'success', duration: 1000 }); |
| 270 | setTimeout(() => { | 439 | setTimeout(() => { |
| @@ -274,58 +443,50 @@ const handleLogin = async () => { | @@ -274,58 +443,50 @@ const handleLogin = async () => { | ||
| 274 | }); | 443 | }); |
| 275 | }, 1000); | 444 | }, 1000); |
| 276 | } catch (err) { | 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 | } finally { | 452 | } finally { |
| 286 | isLoading.value = false; | 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 | onMounted(() => { | 470 | onMounted(() => { |
| 292 | checkLoginStatus(); | 471 | checkLoginStatus(); |
| 472 | + | ||
| 293 | // 初始化表单规则 | 473 | // 初始化表单规则 |
| 294 | nextTick(() => { | 474 | nextTick(() => { |
| 295 | loginFormRef.value?.setRules(loginFormRules); | 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 | </script> | 480 | </script> |
| 321 | 481 | ||
| 322 | <style scoped lang="scss"> | 482 | <style scoped lang="scss"> |
| 323 | -.page-container{ | ||
| 324 | - height:100vh; | 483 | +.page-container { |
| 484 | + height: 100vh; | ||
| 325 | background: url("https://img.jichengshanshui.com.cn:28207/appimg/loginbg.jpg") no-repeat; | 485 | background: url("https://img.jichengshanshui.com.cn:28207/appimg/loginbg.jpg") no-repeat; |
| 326 | background-position: bottom center; | 486 | background-position: bottom center; |
| 327 | background-size: 100% 240px; | 487 | background-size: 100% 240px; |
| 328 | } | 488 | } |
| 489 | + | ||
| 329 | .top-title { | 490 | .top-title { |
| 330 | background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat; | 491 | background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat; |
| 331 | background-size: 100% 100%; | 492 | background-size: 100% 100%; |
| @@ -361,9 +522,11 @@ onMounted(() => { | @@ -361,9 +522,11 @@ onMounted(() => { | ||
| 361 | 522 | ||
| 362 | :deep(.u-tabs) { | 523 | :deep(.u-tabs) { |
| 363 | margin-bottom: 15px; | 524 | margin-bottom: 15px; |
| 525 | + | ||
| 364 | .u-tabs__content { | 526 | .u-tabs__content { |
| 365 | height: auto !important; | 527 | height: auto !important; |
| 366 | } | 528 | } |
| 529 | + | ||
| 367 | .u-tab-item { | 530 | .u-tab-item { |
| 368 | padding: 0 10px; | 531 | padding: 0 10px; |
| 369 | } | 532 | } |
| @@ -415,4 +578,15 @@ onMounted(() => { | @@ -415,4 +578,15 @@ onMounted(() => { | ||
| 415 | position: fixed; | 578 | position: fixed; |
| 416 | bottom: 60px; | 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 | </style> | 592 | </style> |
| 419 | \ No newline at end of file | 593 | \ No newline at end of file |
pages/workbench/index.vue
| @@ -10,8 +10,8 @@ | @@ -10,8 +10,8 @@ | ||
| 10 | 10 | ||
| 11 | <!-- 蓝色装饰块 --> | 11 | <!-- 蓝色装饰块 --> |
| 12 | <view class="blue-decor-block" v-show="!loading"> | 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 | </view> | 15 | </view> |
| 16 | 16 | ||
| 17 | <!-- 内容容器 --> | 17 | <!-- 内容容器 --> |
pinia/user.js
| @@ -2,7 +2,7 @@ import { defineStore } from 'pinia'; | @@ -2,7 +2,7 @@ import { defineStore } from 'pinia'; | ||
| 2 | import cache from '@/common/utils/cache'; | 2 | import cache from '@/common/utils/cache'; |
| 3 | import globalConfig from '@/common/config/global'; | 3 | import globalConfig from '@/common/config/global'; |
| 4 | // 新增:导入 refreshToken 接口 | 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 | // 新增:定义刷新Token的定时器标识(避免重复创建定时器) | 7 | // 新增:定义刷新Token的定时器标识(避免重复创建定时器) |
| 8 | let refreshTokenTimer = null; | 8 | let refreshTokenTimer = null; |
| @@ -41,7 +41,14 @@ export const useUserStore = defineStore('user', { | @@ -41,7 +41,14 @@ export const useUserStore = defineStore('user', { | ||
| 41 | actions: { | 41 | actions: { |
| 42 | async login(params) { | 42 | async login(params) { |
| 43 | try { | 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 | // 新增:从登录接口返回值中获取 refreshToken(若接口返回字段名不一致,可调整,如 res.refresh_token) | 52 | // 新增:从登录接口返回值中获取 refreshToken(若接口返回字段名不一致,可调整,如 res.refresh_token) |
| 46 | const { accessToken, expiresTime, userId, refreshToken } = res; | 53 | const { accessToken, expiresTime, userId, refreshToken } = res; |
| 47 | 54 |