| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308 |
- <template>
- <view class="change-page">
- <text class="hint">通过短信验证码修改密码,无需旧密码。修改成功后请使用新密码登录。</text>
- <view class="form">
- <input class="input" v-model="mobile" placeholder="手机号" type="number" />
- <view class="captcha-row">
- <input
- class="input captcha-input"
- v-model="captchaCode"
- placeholder="图形验证码"
- maxlength="8"
- />
- <view class="captcha-img-wrap" @click="refreshCaptcha">
- <image v-if="captchaImageSrc" class="captcha-img" :src="captchaImageSrc" mode="aspectFit" />
- <text v-else class="captcha-placeholder">{{ captchaLoading ? '加载中' : '点击刷新' }}</text>
- </view>
- </view>
- <view class="sms-row">
- <input class="input sms-input" v-model="smsCode" placeholder="短信验证码" type="number" maxlength="8" />
- <view class="sms-btn" :class="{ disabled: sending || countdown > 0 }" @click="onSendSms">
- <text>{{ countdown > 0 ? countdown + 's' : (sending ? '发送中' : '获取验证码') }}</text>
- </view>
- </view>
- <input class="input" v-model="newPassword" placeholder="新密码(须含字母与数字)" password />
- <input class="input" v-model="confirmPassword" placeholder="确认新密码" password />
- <view class="submit" :class="{ disabled: loading }" @click="onSubmit">
- <text>{{ loading ? '提交中...' : '确认修改' }}</text>
- </view>
- </view>
- </view>
- </template>
- <script>
- import {
- getToken,
- setToken,
- getCaptcha,
- sendOpenSms,
- resetPasswordOpen,
- getCurrentUserInfo
- } from '../../utils/api'
- function passwordMeetsRule(pwd) {
- const s = String(pwd || '')
- return /[A-Za-z]/.test(s) && /\d/.test(s)
- }
- function normalizeCaptchaImageSrc(image) {
- if (!image) return ''
- const s = String(image)
- if (s.startsWith('data:')) return s
- return `data:image/png;base64,${s}`
- }
- function extractMobileFromMe(raw) {
- if (!raw || typeof raw !== 'object') return ''
- const o =
- raw.user != null && typeof raw.user === 'object'
- ? raw.user
- : raw.data != null && typeof raw.data === 'object'
- ? raw.data
- : raw
- const m = o.mobile ?? o.phone ?? o.phone_number
- return m != null ? String(m).trim() : ''
- }
- export default {
- data() {
- return {
- mobile: '',
- captchaId: '',
- captchaCode: '',
- captchaImageSrc: '',
- captchaLoading: false,
- smsCode: '',
- newPassword: '',
- confirmPassword: '',
- sending: false,
- countdown: 0,
- timer: null,
- loading: false
- }
- },
- async onLoad() {
- this.refreshCaptcha()
- try {
- const raw = uni.getStorageSync('current_user')
- const fromStorage = extractMobileFromMe(raw)
- if (fromStorage) this.mobile = fromStorage
- } catch (e) {}
- const token = getToken()
- if (!token) return
- try {
- const me = await getCurrentUserInfo(token)
- const m = extractMobileFromMe(me)
- if (m) this.mobile = m
- } catch (e) {}
- },
- onShow() {
- if (!getToken()) {
- uni.showToast({ title: '请先登录', icon: 'none' })
- setTimeout(() => {
- uni.reLaunch({ url: '/pages/login/index' })
- }, 80)
- }
- },
- onUnload() {
- if (this.timer) clearInterval(this.timer)
- this.timer = null
- },
- methods: {
- async refreshCaptcha() {
- this.captchaLoading = true
- this.captchaCode = ''
- try {
- const data = await getCaptcha()
- this.captchaId = data.captcha_id != null ? String(data.captcha_id) : ''
- this.captchaImageSrc = normalizeCaptchaImageSrc(data.image)
- } catch (e) {
- this.captchaImageSrc = ''
- uni.showToast({ title: e.message || '图形码加载失败', icon: 'none' })
- } finally {
- this.captchaLoading = false
- }
- },
- async onSendSms() {
- if (this.sending || this.countdown > 0) return
- const m = String(this.mobile || '').trim()
- if (!m) {
- uni.showToast({ title: '请输入手机号', icon: 'none' })
- return
- }
- if (!this.captchaId) {
- uni.showToast({ title: '请等待图形验证码加载', icon: 'none' })
- return
- }
- const cc = String(this.captchaCode || '').trim()
- if (!cc) {
- uni.showToast({ title: '请输入图形验证码', icon: 'none' })
- return
- }
- this.sending = true
- try {
- await sendOpenSms(m, this.captchaId, cc)
- uni.showToast({ title: '短信已发送', icon: 'none' })
- this.countdown = 60
- this.timer = setInterval(() => {
- this.countdown -= 1
- if (this.countdown <= 0) {
- clearInterval(this.timer)
- this.timer = null
- this.countdown = 0
- }
- }, 1000)
- } catch (e) {
- uni.showToast({ title: e.message || '发送失败', icon: 'none' })
- this.refreshCaptcha()
- } finally {
- this.sending = false
- }
- },
- async onSubmit() {
- if (this.loading) return
- const m = String(this.mobile || '').trim()
- if (!m) {
- uni.showToast({ title: '请输入手机号', icon: 'none' })
- return
- }
- const code = String(this.smsCode || '').trim()
- if (!code) {
- uni.showToast({ title: '请输入短信验证码', icon: 'none' })
- return
- }
- const pwd = String(this.newPassword || '')
- const confirm = String(this.confirmPassword || '')
- if (!pwd) {
- uni.showToast({ title: '请输入新密码', icon: 'none' })
- return
- }
- if (!passwordMeetsRule(pwd)) {
- uni.showToast({ title: '新密码须同时包含字母与数字', icon: 'none' })
- return
- }
- if (pwd !== confirm) {
- uni.showToast({ title: '两次新密码不一致', icon: 'none' })
- return
- }
- this.loading = true
- try {
- await resetPasswordOpen(m, code, pwd)
- uni.showToast({ title: '修改成功,请重新登录', icon: 'success' })
- setToken('')
- try {
- uni.removeStorageSync('current_user')
- } catch (e) {}
- setTimeout(() => {
- uni.reLaunch({ url: '/pages/login/index' })
- }, 500)
- } catch (e) {
- uni.showToast({ title: e.message || '修改失败', icon: 'none' })
- } finally {
- this.loading = false
- }
- }
- }
- }
- </script>
- <style scoped>
- .change-page {
- min-height: 100vh;
- background: #fff;
- padding: 32rpx 48rpx 64rpx;
- box-sizing: border-box;
- }
- .hint {
- display: block;
- font-size: 26rpx;
- color: #888;
- line-height: 1.5;
- margin-bottom: 32rpx;
- }
- .form {
- margin-top: 8rpx;
- }
- .input {
- height: 88rpx;
- padding: 0 24rpx;
- background: #f7f7f7;
- border-radius: 16rpx;
- font-size: 28rpx;
- margin-bottom: 20rpx;
- }
- .captcha-row {
- display: flex;
- align-items: center;
- gap: 16rpx;
- margin-bottom: 20rpx;
- }
- .captcha-input {
- flex: 1;
- margin-bottom: 0;
- }
- .captcha-img-wrap {
- width: 220rpx;
- height: 88rpx;
- border-radius: 16rpx;
- background: #f0f0f0;
- overflow: hidden;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- }
- .captcha-img {
- width: 100%;
- height: 100%;
- }
- .captcha-placeholder {
- font-size: 22rpx;
- color: #999;
- padding: 0 8rpx;
- text-align: center;
- }
- .sms-row {
- display: flex;
- align-items: center;
- gap: 16rpx;
- margin-bottom: 20rpx;
- }
- .sms-input {
- flex: 1;
- margin-bottom: 0;
- }
- .sms-btn {
- height: 88rpx;
- padding: 0 24rpx;
- border-radius: 16rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- background: #259653;
- color: #fff;
- font-size: 26rpx;
- }
- .sms-btn.disabled {
- opacity: 0.6;
- }
- .submit {
- margin-top: 32rpx;
- height: 92rpx;
- border-radius: 18rpx;
- background: #259653;
- display: flex;
- align-items: center;
- justify-content: center;
- color: #fff;
- font-size: 30rpx;
- }
- .submit.disabled {
- opacity: 0.7;
- }
- </style>
|