💄 style(verify-code): 调整 APP 端验证码输入框的隐藏方案

- 将原生输入框移出可视区域,避免 Android 设备上原生光标与自绘数字冲突
- 为 xt__verify-code 增加 overflow 隐藏,输入内容与光标不再显示在验证码格上

♻️ refactor(login): 移除 u-code 组件并自实现验证码倒计时

- 用 displayCodeTip 计算属性配合计时器替换 u-code,统一验证码按钮文案展示
- 新增 onBeforeUnmount 清理倒计时定时器,避免内存泄漏
- 重构 fetchCode 发送逻辑,简化校验与错误处理分支
- 更新获取验证码按钮样式为橙色渐变并调整文字颜色

♻️ refactor(live): 为 APP 端补充直播服务占位实现

- 通过条件编译在 APP-PLUS 下提供 LiveChatService 与 LiveMqttService 空实现
- 避免 APP 端引入直播聊天与 MQTT 依赖,仅保留其他平台原有逻辑
This commit is contained in:
Yer11214
2026-09-20 16:41:01 +08:00
parent 42e9b7c15f
commit 27034902fe
3 changed files with 102 additions and 63 deletions

View File

@@ -281,8 +281,18 @@ function codeFormat(val: string | undefined, isPassword: boolean): string {
} }
/* #ifdef APP-PLUS */ /* #ifdef APP-PLUS */
/* APP 中让自绘验证码位于透明输入框上方;点击仍由 input-ground 触发聚焦。 */ /*
* App 的原生 input 如果覆盖在验证码格上,部分 Android 设备会出现
* 原生光标/输入绘制层与自绘数字冲突。沿用 u-code-input 的隐藏输入方式,
* 将输入内容和原生光标移出可视区域,验证码格只负责展示响应式状态。
*/
.xt__verify-code {
overflow: hidden;
}
.xt__input { .xt__input {
left: -750rpx;
width: 1500rpx;
z-index: 1; z-index: 1;
} }

View File

@@ -41,13 +41,11 @@
cursorColor="#D8D8D8" cursorColor="#D8D8D8"
/> />
<div class="fetch-btn"> <view class="fetch-btn" style="display: flex; align-items: center; justify-content: center; width: 370rpx; height: 80rpx; border-radius: 100rpx; background-color: #ff6a00">
<u-code change-text="验证码已发送x" end-text="重新获取验证码" unique-key="page-login" <text @tap="fetchCode" style="color: #fff; font-size: 28rpx; line-height: 80rpx">
:seconds="seconds" @end="end" @start="start" ref="uCode" @change="codeChange"> {{ displayCodeTip }}
</u-code> </text>
<span @tap="fetchCode" :style="{ color: codeColor }"> </view>
{{ tips }}</span>
</div>
</div> </div>
</div> </div>
@@ -131,7 +129,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, computed, watch, nextTick, getCurrentInstance, onMounted } from 'vue' import { ref, reactive, computed, watch, getCurrentInstance, onMounted, onBeforeUnmount } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app' import { onLoad, onShow } from '@dcloudio/uni-app'
import { useStore } from '@/store' import { useStore } from '@/store'
import { openIdLogin, loginCallback, webConnect } from '@/api/connect.js' import { openIdLogin, loginCallback, webConnect } from '@/api/connect.js'
@@ -166,8 +164,14 @@ const codeFlag = ref(true)
const tips = ref('') const tips = ref('')
const enableUserPwdBox = ref(false) const enableUserPwdBox = ref(false)
const current = ref(0) const current = ref(0)
const codeColor = ref('#999') const isCodeCountingDown = ref(false)
const seconds = ref(60) const seconds = 60
const remainingCodeSeconds = ref(60)
let codeCountdownTimer: ReturnType<typeof setInterval> | null = null
const displayCodeTip = computed(() => {
if (isCodeCountingDown.value) return `验证码已发送(${remainingCodeSeconds.value}`
return tips.value || '获取验证码'
})
const loginTitleWay = [ const loginTitleWay = [
{ title: '欢迎登录', desc: '登录后更精彩,美好生活即将开始' }, { title: '欢迎登录', desc: '登录后更精彩,美好生活即将开始' },
{ title: '请输入验证码', desc: '已经发送验证码至' }, { title: '请输入验证码', desc: '已经发送验证码至' },
@@ -192,7 +196,13 @@ const loginList = ref<LoginListItem[]>([
const clientType = ref('') const clientType = ref('')
const verification = ref<any>(null) const verification = ref<any>(null)
const uCode = ref<any>(null)
onBeforeUnmount(() => {
if (codeCountdownTimer) {
clearInterval(codeCountdownTimer)
codeCountdownTimer = null
}
})
onShow(() => { onShow(() => {
// 只要是app登录的全部清除内容 // 只要是app登录的全部清除内容
@@ -304,24 +314,19 @@ watch(flage, async (val) => {
return return
} }
// 首次发送时 uCode 尚未挂载;重新获取时才检查倒计时 const canSendCode = current.value === 0 || !isCodeCountingDown.value
const canSendCode = current.value === 0 || uCode.value?.canGetCode
if (!canSendCode) { if (!canSendCode) {
proxy.$u.toast('请倒计时结束后再发送') proxy.$u.toast('请倒计时结束后再发送')
return return
} }
uni.showLoading({}) uni.showLoading({})
try {
const res = await sendMobile(mobile.value) const res = await sendMobile(mobile.value)
if (store.state.isShowToast) {
uni.hideLoading()
}
if (res.data.success) { if (res.data.success) {
code.value = '' code.value = ''
current.value = 1 current.value = 1
nextTick(() => { startCodeCountdown()
uCode.value?.start()
})
} else { } else {
uni.showToast({ uni.showToast({
title: res.data.message, title: res.data.message,
@@ -331,6 +336,9 @@ watch(flage, async (val) => {
flage.value = false flage.value = false
verification.value?.getCode() verification.value?.getCode()
} }
} finally {
uni.hideLoading()
}
} else { } else {
verification.value?.hide() verification.value?.hide()
} }
@@ -594,24 +602,29 @@ function navigateToPrivacy(val: string) {
}) })
} }
function start() { function startCodeCountdown() {
codeColor.value = '#999' if (codeCountdownTimer) clearInterval(codeCountdownTimer)
isCodeCountingDown.value = true
remainingCodeSeconds.value = seconds
tips.value = `验证码已发送(${seconds}`
codeCountdownTimer = setInterval(() => {
if (remainingCodeSeconds.value > 1) {
remainingCodeSeconds.value -= 1
return
}
clearInterval(codeCountdownTimer!)
codeCountdownTimer = null
isCodeCountingDown.value = false
remainingCodeSeconds.value = 0
tips.value = '重新获取验证码'
codeFlag.value = true
}, 1000)
proxy.$u.toast('验证码已发送') proxy.$u.toast('验证码已发送')
flage.value = false flage.value = false
codeFlag.value = false codeFlag.value = false
verification.value?.hide() verification.value?.hide()
} }
function codeChange(text: string) {
tips.value = text
}
function end() {
codeColor.value = lightColor.value
codeFlag.value = true
console.log(codeColor.value)
}
function passwordLogin() { function passwordLogin() {
if (!enablePrivacy.value) { if (!enablePrivacy.value) {
uni.showToast({ uni.showToast({
@@ -676,20 +689,6 @@ function fetchCode() {
}) })
return false return false
} }
if (tips.value == '重新获取验证码') {
uni.showLoading({ title: '加载中' })
if (!codeFlag.value) {
const timer = setInterval(() => {
if (verification.value) {
verification.value.error()
}
clearInterval(timer)
}, 100)
}
if (store.state.isShowToast) {
uni.hideLoading()
}
}
if (!flage.value) { if (!flage.value) {
verification.value?.error() verification.value?.error()
return false return false
@@ -817,14 +816,18 @@ declare function miniProgramLogin(code: string | undefined): Promise<{ data: any
} }
.fetch-btn { .fetch-btn {
display: flex;
align-items: center;
justify-content: center;
width: 370rpx; width: 370rpx;
height: 80rpx; height: 80rpx;
line-height: 80rpx; line-height: 80rpx;
text-align: center; text-align: center;
background: #f2f2f2; background-color: #ff6a00;
background: linear-gradient(57.72deg, #ff8a19 18.14%, #ff5e00 98.44%);
border-radius: 100rpx; border-radius: 100rpx;
font-size: 28rpx; font-size: 28rpx;
color: #999; color: #fff;
margin: 71rpx auto 0 auto; margin: 71rpx auto 0 auto;
} }

View File

@@ -488,8 +488,34 @@ import {
import config from '@/config/config' import config from '@/config/config'
import { useStore } from '@/store' import { useStore } from '@/store'
import { isLogin, navigateToLogin, unitPrice } from '@/utils/filters.js' import { isLogin, navigateToLogin, unitPrice } from '@/utils/filters.js'
// #ifndef APP-PLUS
import { LiveChatService } from './utils/liveChat.js' import { LiveChatService } from './utils/liveChat.js'
import { LiveMqttService } from './utils/liveMqtt.js' import { LiveMqttService } from './utils/liveMqtt.js'
// #endif
// #ifdef APP-PLUS
class LiveChatService {
userInfo: any = {}
constructor(_options: any = {}) {}
setGroupID(_id: string) {}
async initTencentIm(_viewLiveResult?: any) {}
cleanup() {}
}
class LiveMqttService {
userId: any = null
mqttClient: any = null
constructor(_liveId: any, userId: any) {
this.userId = userId
}
setMessageHandlers(_handlers: any) {}
isConnected() {
return false
}
initMqtt() {}
cleanup() {
this.mqttClient = null
}
}
// #endif
import { findRecommendPayload, parseRecommendMessageArray } from './utils/liveRecommend.js' import { findRecommendPayload, parseRecommendMessageArray } from './utils/liveRecommend.js'
import { seedLiveSetting } from './utils/liveSetting.js' import { seedLiveSetting } from './utils/liveSetting.js'
// #ifdef H5 // #ifdef H5