百度做的網(wǎng)站seo實(shí)戰(zhàn)
一、需求描述
最近經(jīng)常使用Trae生成一些小組件和功能代碼(對Trae趕興趣的可以看之前的文章《TraeAi上手體驗(yàn)》),剛好在用uniapp開發(fā)微信小程序時(shí)需要開發(fā)一個(gè)輸入密碼的彈框組件,于是想用Trae來實(shí)現(xiàn)。原型設(shè)計(jì)稿如下:
二、Trae生成的雛形組件
經(jīng)過一次描述附加原型設(shè)計(jì)圖后,Trae給我生成了初始代碼,預(yù)覽如下:
在微信開發(fā)者工具中使用似乎是可以的,它還細(xì)心的額外給我增加了對確認(rèn)按鈕的激活條件(多選框需要至少選一個(gè)),這個(gè)激活條件我是沒有體現(xiàn)在需求描述上的,但實(shí)際也是需要的。
然而,在實(shí)際使用的時(shí)候,會(huì)發(fā)現(xiàn)以下問題:
①它在界面上使用了4個(gè)input輸入框,當(dāng)輸入框?yàn)榭諘r(shí)再按下backspace鍵,無法觸發(fā)@input或者@keyup,導(dǎo)致無法將光標(biāo)回退到上一個(gè)輸入框。同時(shí),因?yàn)槭褂昧?個(gè)不同的input輸入框,導(dǎo)致在輸入后光標(biāo)自動(dòng)移動(dòng)到下一個(gè)輸入框時(shí)會(huì)引起輸入法鍵盤的閃動(dòng)。
②input上雖然設(shè)置了type=“number”,但在微信開發(fā)者工具中,還是能輸入其它字符。
③雖然它在彈窗關(guān)閉時(shí)對所有輸入變量進(jìn)行了重置,但再次打開彈框時(shí),多選的checkbox組件依然顯示之前的勾選狀態(tài),而沒有被重新初始化為未勾選狀態(tài)。
④需求描述和原型設(shè)計(jì)稿中都有右上角關(guān)閉的描述,但實(shí)際沒有顯示彈窗的右上角的關(guān)閉按鈕(其實(shí)代碼是有生成,但它給的關(guān)閉圖片并不存在,圖片路徑卻不是隨機(jī)的,而似乎有根據(jù)項(xiàng)目使用的cdn來生成,所以差點(diǎn)讓我信以為真)
三、問題修復(fù)
上面4個(gè)問題中,第一個(gè)問題是最主要的,也是比較麻煩的。
在保留4個(gè)input輸入框的方式下,最初為了解決當(dāng)輸入框?yàn)榭諘r(shí)再按下backspace鍵,無法觸發(fā)@input的問題時(shí),Trae想到了插入零寬字符\u200B
的方案:
但在實(shí)際測試過程中,會(huì)導(dǎo)致輸入的數(shù)字無法正常顯示。
經(jīng)過幾次嘗試,如果在不改變實(shí)現(xiàn)方式,也就是保留4個(gè)input輸入框的方式下,無法解決事件觸發(fā)及輸入法鍵盤閃動(dòng)的問題。于是Trae給出來用“4個(gè)普通view元素+隱藏的input”的方式來實(shí)現(xiàn)密碼輸入:
主要改動(dòng)說明:1. 移除了多個(gè)輸入框,改用一個(gè)隱藏的真實(shí)輸入框
2. 添加了顯示用的密碼框
3. 簡化了輸入處理邏輯
4. 在關(guān)閉彈窗時(shí)增加了狀態(tài)重置
5. 優(yōu)化了樣式結(jié)構(gòu),確保隱藏輸入框覆蓋整個(gè)輸入?yún)^(qū)域
經(jīng)過上述修改,輸入法閃動(dòng)的問題解決了,backspace回退的問題也解決了。但是引入了一個(gè)新的問題:
由于真實(shí)的輸入框是覆蓋在上層,雖然它也設(shè)置了opacity: 0;
,在微信開發(fā)者工具中沒有發(fā)現(xiàn)什么問題,但是在Android真機(jī)上卻顯示了輸入框的文字和閃爍的光標(biāo)。
輸入框文字可以用css樣式進(jìn)行隱藏,但光標(biāo)卻始終無法隱藏:
Trae給出的幾次方案:
方案一:
.real-password-input {position: absolute;top: 0;left: 0;width: 100%;height: 100rpx;opacity: 0;z-index: 1;background: transparent;color: transparent;caret-color: transparent;}
方案二:
.real-password-input {pointer-events: none; /* 添加這行 */
}
方案三:
.real-password-input {position: absolute;top: -9999rpx; // 將輸入框移出可視區(qū)域left: -9999rpx;width: 100%;height: 100rpx;opacity: 0;z-index: 1;background: transparent;color: transparent;}
當(dāng)然還有deepseek給出的unselectable=“on” ,readonly(微信小程序input沒有這個(gè)屬性),disabled,這些方案都不行。
最終為了不讓光標(biāo)顯示出來,那么只能采用方案三,但方案三有個(gè)問題,就是如果輸入完成,輸入法的鍵盤消失后,如果想再次輸入該怎么辦?能否再次點(diǎn)擊那4個(gè)輸入框來喚起輸入法的鍵盤呢?
一般我們使用input來獲取焦點(diǎn)是這樣的input.focus()
,但在uniapp微信小程序的開發(fā)中,實(shí)際上發(fā)現(xiàn)這樣是無效的。當(dāng)然同時(shí)也發(fā)現(xiàn)了,在彈框出現(xiàn)后如果想讓input自動(dòng)獲得焦點(diǎn),也同樣不能使用這樣的方式,哪怕你設(shè)置了setTimeout延時(shí):
好在Trae最終給出了另一個(gè)方案:
通過focus
的設(shè)置,解決了在彈窗顯示時(shí)自動(dòng)獲取焦點(diǎn)的問題。那么對于點(diǎn)擊密碼輸入框自動(dòng)獲取焦點(diǎn)的解決是否也可以通過改變focus
屬性的方式實(shí)現(xiàn)呢?答案是可以的,只不過這時(shí)候我們加上了setTimeout的包裹:
const handlePasswordInputClick = () => {isFocus.value = falsesetTimeout(() => {isFocus.value = true}, 100)
}
第一個(gè)問題終于解決,剩下的三個(gè)問題中的第三個(gè)問題checkbox組件勾選狀態(tài)重置問題,Trae最終也給出了解決辦法:
最終得到的完整代碼如下:
<template><uni-popup ref="popup" type="center" :mask-click="false" @change="handlePopupChange"><view class="share-popup"><view class="share-popup-header"><text class="share-popup-title">分享xx記錄</text><uni-icons type="closeempty" size="20" color="#118170" class="popup-detail-header__close"@click="handleClose" /></view><view class="share-content"><view class="share-tip">選擇分享內(nèi)容,并設(shè)置查看密碼后,對方通過輸入密碼就能查看到患者分享的記錄內(nèi)容了~</view><view class="share-options"><checkbox-group @change="handleShareOptionChange"><label v-for="(item, index) in shareOptions" :key="item.value" class="share-option-item"><checkbox :value="item.value" color="#00D997" :checked="checkedStatus[index]"/><text>{{item.label}}</text></label></checkbox-group></view><view class="password-section"><view class="password-title">輸入密碼</view><view class="password-input-group" @click="handlePasswordGroupClick"><view v-for="(item, index) in 4" :key="index"class="password-input"@click="handlePasswordInputClick">{{ password[index] }}</view><input type="number"maxlength="4"v-model="realPassword"class="real-password-input"@input="handlePasswordInput"ref="passwordInput":focus="isFocus" /></view></view><button class="confirm-btn" :class="{'confirm-btn-active': isValid}":disabled="!isValid"@click="handleConfirm">確認(rèn)</button></view></view></uni-popup>
</template><script setup>
import { ref, computed, nextTick } from 'vue'const popup = ref(null)
const password = ref(['','','',''])
const realPassword = ref('')
const selectedOptions = ref([])
const passwordInput = ref(null)
const isFocus = ref(false)
const checkedStatus = ref(new Array(3).fill(false))const shareOptions = [{ label: '選項(xiàng)1, value: '1' },{ label: '選項(xiàng)2', value: '2' },{ label: '選項(xiàng)3', value: '3' }
]const isValid = computed(() => {return password.value.every(v => v !== '') && selectedOptions.value.length > 0
})const handlePasswordInput = (e) => {const value = e.detail.valueif (!/^\d*$/.test(value)) {nextTick(() => {realPassword.value = realPassword.value.replace(/\D/g, '')})return}const valueArray = value.split('')password.value = new Array(4).fill('').map((_, index) => valueArray[index] || '')// 當(dāng)輸入滿4位數(shù)時(shí),自動(dòng)失去焦點(diǎn)if (value.length === 4) {isFocus.value = false}
}const handlePasswordGroupClick = () => {const input = passwordInput.valueif (input) {input.focus()}
}const handleShareOptionChange = (e) => {selectedOptions.value = e.detail.valuecheckedStatus.value = shareOptions.map(option => selectedOptions.value.includes(option.value))
}const handleConfirm = () => {popup.value.close()
}const handleClose = () => {popup.value.close()
}const resetState = () => {password.value = ['','','','']realPassword.value = ''selectedOptions.value = []checkedStatus.value = new Array(3).fill(false)
}const handlePopupChange = (e) => {isFocus.value = e.showif (!e.show) {resetState()}
}const show = () => {resetState()nextTick(() => {popup.value.open()})
}const handlePasswordInputClick = () => {isFocus.value = falsesetTimeout(() => {isFocus.value = true}, 100)
}defineExpose({show
})
</script><style lang="scss" scoped>
.share-popup {width: 622rpx;background: #FFFFFF;border-radius: 24rpx;padding: 40rpx;&-header {display: flex;justify-content: space-between;align-items: center;margin-bottom: 30rpx;}&-title {font-size: 36rpx;font-weight: 500;color: #1A1A1A;}&-close {width: 48rpx;height: 48rpx;}
}.share-content {.share-tip {font-size: 26rpx;color: #999999;line-height: 37rpx;margin-bottom: 30rpx;}
}.share-options {margin-bottom: 40rpx;.share-option-item {display: flex;align-items: center;margin-bottom: 20rpx;font-size: 30rpx;color: #1A1A1A;}
}.password-section {.password-title {font-size: 30rpx;color: #1A1A1A;margin-bottom: 20rpx;}.password-input-group {display: flex;justify-content: space-between;margin-bottom: 40rpx;position: relative;}.password-input {width: 100rpx;height: 100rpx;background: #F5F5F5;border-radius: 12rpx;text-align: center;font-size: 36rpx;line-height: 100rpx;cursor: pointer;}.real-password-input {position: absolute;top: -9999px;left: -9999px;width: 1rpx;height: 1rpx;opacity: 0;z-index: 1;background: transparent;color: transparent;caret-color: transparent;}
}.confirm-btn {width: 100%;height: 88rpx;background: #CCCCCC;border-radius: 44rpx;color: #FFFFFF;font-size: 32rpx;display: flex;align-items: center;justify-content: center;&-active {background: linear-gradient(132deg, #00D997 0%, #00D57D 100%);}
}
</style>
四、后續(xù)
上面我們通過改變focus屬性的方式解決了在彈窗顯示時(shí)自動(dòng)獲取焦點(diǎn)的問題,但后面我們發(fā)現(xiàn)這種方式能否生效與彈窗顯示的方式有關(guān):
①當(dāng)用戶通過點(diǎn)擊的交互方式觸發(fā)顯示彈框時(shí),通過改變focus屬性的方式可以讓input自動(dòng)獲得焦點(diǎn),在移動(dòng)端表現(xiàn)為輸入法的軟鍵盤被喚起,在微信開發(fā)者工具中表現(xiàn)為直接可以輸入數(shù)字顯示到輸入框中。
②當(dāng)彈框是通過腳本喚起,而非用戶交互的結(jié)果時(shí),通過改變focus屬性的方式,在開發(fā)者工具中無法自動(dòng)獲得焦點(diǎn),但在移動(dòng)端可以自動(dòng)獲得焦點(diǎn),不過對focus屬性的改變最好是在彈框顯示的回調(diào)里通過setTimeout進(jìn)行設(shè)置,否則可能會(huì)無法生效(比如在iphone上會(huì)出現(xiàn)):
const handlePopupChange = (e) => {setTimeout(() => {isFocus.value = e.show}, 100)
}