嘿,最近我搞了個Java刷題的小程序,用Vue寫的,界面和功能都還挺完整的。今天就來跟大家聊聊這個小程序是怎么實現的,代碼里都藏著哪些小細節。
先看整體結構,我把整個頁面分成了幾個大塊:頂部導航欄、題目內容區、底部按鈕欄,還有一個完成后的彈窗。這種布局應該挺符合大家做題庫類應用的習慣吧?
頂部導航欄
<view?class="navbar"><text?class="nav-title">Java刷題</text><text?class="nav-progress">{{ currentIndex + 1 }}/{{ totalCount }}</text>
</view>
這塊很簡單,左邊是標題,右邊顯示當前進度,比如"3/10"這種。用currentIndex + 1
是因為數組索引是從0開始的,加1才符合我們平時的計數習慣。
題目內容區
這部分是核心,我分了題干、選項列表和答案解析三個部分。
首先是題干:
<view?class="question-content"><text?class="question-title">題目 {{ currentIndex + 1 }}:</text><text?class="question-text">{{ currentQuestion.questionContent }}</text>
</view>
這里用了v-if="currentQuestion"
來確保數據加載完成后才顯示,避免頁面閃爍。
然后是選項列表,這塊有點意思:
<view?class="options-list"><view?class="option-item"?v-for="(option, idx) in parsedOptions"?:key="idx":class="{?'selected': selectedIndex === idx,'correct': showExplanation && isCorrectOption(idx),'incorrect': showExplanation && selectedIndex === idx && !isCorrectOption(idx)}"@click="handleSelectOption(idx)"><text?class="option-letter">{{ String.fromCharCode(65 + idx) }}</text><text?class="option-text">{{ option }}</text></view>
</view>
我用了v-for
來循環渲染選項,parsedOptions
是處理過的選項數組。這里有個小技巧,選項字母A、B、C、D是通過String.fromCharCode(65 + idx)
生成的,65對應的就是字母A的ASCII碼,這樣就不用手動寫每個選項的字母了。
樣式方面,我用了動態class:
-
選中狀態:
selected
類 -
正確答案:
correct
類(只有在顯示解析時才生效) -
錯誤答案:
incorrect
類(選中的答案不對時才顯示)
這樣用戶選完答案提交后,就能清楚地看到自己選的對不對,正確答案是哪個。
接下來是答案解析,只有提交答案后才會顯示:
<view?class="explanation"?v-if="showExplanation"><text?class="explanation-title">解析:</text><text?class="explanation-content">{{ currentQuestion.answerExplanation }}</text>
</view>
底部導航按鈕
<view?class="bottom-bar"><button?class="btn-prev"?@click="prevQuestion"?:disabled="currentIndex === 0">上一題</button><button?class="btn-next"?@click="nextQuestion"?:disabled="!hasSelected && currentIndex === totalCount - 1">{{ currentIndex === totalCount - 1 ? '完成練習' : (hasSelected ? '下一題' : '請選擇答案') }}</button>
</view>
這里的按鈕文本會根據當前狀態動態變化:
-
如果是最后一題,顯示"完成練習"
-
否則,如果已經選了答案,顯示"下一題"
-
還沒選答案的話,顯示"請選擇答案"
disabled屬性也做了處理,第一題時上一題按鈕禁用,最后一題沒選答案時,完成按鈕也會禁用,防止用戶跳過題目。
完成彈窗
<view?class="result-popup"?v-if="showResult"><view?class="popup-content"><text?class="popup-title">練習完成!</text><text?class="popup-score">得分: {{ correctCount }}/{{ totalCount }}</text><button?class="popup-button"?@click="restart">重新開始</button><button?class="popup-button"?style="margin-top: 10px;"?@click="restart2">再戰錯題</button></view>
</view>
全部做完后會彈出這個彈窗,顯示得分,還有兩個按鈕:重新開始和再戰錯題。這個再戰錯題功能我覺得還挺實用的,能針對性地鞏固薄弱點。
腳本部分
接下來看看邏輯實現,我用的是Vue 3的setup語法。
首先定義了一些狀態變量:
const?currentIndex = ref(0)?// 當前題目索引
const?currentQuestion = ref(null)?// 當前題目數據
const?selectedIndex = ref(-1)?// 選中的選項索引,-1表示未選擇
const?showExplanation = ref(false)?// 是否顯示解析
const?totalCount = ref(0)?// 總題數
const?correctCount = ref(0)?// 做對的題數
const?showResult = ref(false)?// 是否顯示結果彈窗
然后是一些計算屬性:
// 解析選項,把字符串分割成數組
const?parsedOptions = computed(()?=>?{if?(!currentQuestion.value?.options)?return?[]return?currentQuestion.value.options.split('\n').filter(option?=>?option.trim()).map(option?=>?option.replace(/^[A-Z]\./,?'').trim())
})// 是否已選擇答案
const?hasSelected = computed(()?=>?selectedIndex.value !==?-1)
parsedOptions
會把后端返回的選項字符串(可能是用換行分隔的)轉換成數組,還會去掉每個選項前面的A.、B.這種前綴,讓顯示更干凈。
處理選擇選項的方法:
const?handleSelectOption =?(idx) =>?{if?(showExplanation.value)?return?// 已顯示解析,禁止修改selectedIndex.value = idx
}
這里加了個判斷,如果已經顯示解析了,就不能再改答案了,避免用戶反復修改。
上一題和下一題的邏輯:
const?prevQuestion =?()?=>?{
if?(currentIndex.value >?0) {currentIndex.value--loadCurrentQuestion()}
}const?nextQuestion =?()?=>?{
if?(!hasSelected.value)?return// 未選擇答案// 檢查答案是否正確
if?(!showExplanation.value) {const?userAnswer =?String.fromCharCode(65?+ selectedIndex.value)if?(userAnswer === currentQuestion.value.correctAnswer) {correctCount.value++reportCorrect(currentQuestion.value.id);?// 上報正確答案}?else?{reportIncorrect(currentQuestion.value.id);?// 上報錯誤答案}showExplanation.value =?truereturn}// 跳轉到下一題
if?(currentIndex.value < totalCount.value -?1) {currentIndex.value++loadCurrentQuestion()}?else?{// 完成所有題目showResult.value =?true}
}
下一題的邏輯稍微復雜點:
-
首先檢查是否已經選擇答案,如果沒有就直接返回
-
如果還沒顯示解析,就先判斷答案是否正確,更新正確題數,然后顯示解析
-
如果已經顯示解析了,就跳到下一題,或者如果是最后一題,就顯示結果彈窗
加載當前題目的方法:
const?loadCurrentQuestion =?()?=>?{currentQuestion.value = questionData.value[currentIndex.value]?selectedIndex.value =?-1?// 重置選擇狀態showExplanation.value =?false?// 隱藏解析
}
每次切換題目時,都會重置選擇狀態和解析顯示狀態。
重新開始和再戰錯題的功能:
const?restart =?()?=>?{currentIndex.value =?0correctCount.value =?0showResult.value =?falseuni.redirectTo({url:"/pages/aaa/aaa"})
}const?restart2 =?async?() => {
let?resData =?await?expendPoint('java刷題',20)
if?(resData.code !==?200) {uni.showModal({title:?'提示',content: resData.message,});return}showResult.value =?falsecurrentIndex.value =?0correctCount.value =?0uni.showLoading({title:"加載中",mask:?true});
const?tms =?await?selectError();?// 獲取錯題uni.hideLoading();questionData.value = tms;totalCount.value = tms.length;loadCurrentQuestion()
}
這里的expendPoint
是個能量消耗的接口,可能是我這個小程序里的一個積分系統,做錯題練習需要消耗20點能量。
最后是頁面加載時的初始化:
onMounted(async?() => {
let?resData =?await?expendPoint('java刷題',20)
if?(resData.code !==?200) {uni.showModal({title:?'提示',content: resData.message,});return}uni.showLoading({title:"加載中",mask:?true});
const?tms =?await?randomTm();?// 獲取隨機題目uni.hideLoading();questionData.value = tms;totalCount.value = tms.length;loadCurrentQuestion()
})
頁面一加載就會調用接口獲取題目數據,同時顯示加載中提示,讓用戶知道正在加載。
樣式部分
樣式我用了scoped屬性,確保不會污染其他組件。主要處理了各種狀態的顯示效果,比如選中、正確、錯誤的樣式區分,還有彈窗的遮罩效果等。
總的來說,這個小程序雖然簡單,但功能還挺完整的,用戶體驗上也做了不少細節處理。比如:
-
清晰的進度顯示
-
直觀的答案反饋(正確/錯誤)
-
詳細的解析說明
-
錯題重練功能
-
合理的按鈕狀態控制
代碼結構也比較清晰,把UI和邏輯分離,方便后續維護和擴展。如果想加新功能,比如收藏題目、難度篩選什么的,也很容易在這個基礎上擴展。
大家覺得這個小程序怎么樣?有什么可以改進的地方歡迎一起討論~
點擊下方鏈接,即可體驗~
速用百寶箱https://mp.weixin.qq.com/s?__biz=MzI1ODI1ODkzMA==&mid=2247497389&idx=1&sn=dd67cedd437a01b3f06507d38650ffde&chksm=ebe1b5699d11eaf0db4a06891d66a85e81642d24ac5f209576907e695aa84080485fa0af6136&scene=126&sessionid=1753317024&subscene=91&clicktime=1753317025&enterid=1753317025&key=daf9bdc5abc4e8d0431ce024e74393f3d4d7363a48a9149b474f3b8cf23986d4b35229bab8d5ea130bb8d6cac53a81552b00c36509a1417c0cd156bcc51f1eab9f4256a30fa6f38a00fce6ebe4c6687ac0a6951f6cece7c99ac7a3ad0802fec5444a13ea0b7ec6943b769291a6397feb6cb7ce96bb4dcd0ea4c3a20b94274060&ascene=0&uin=NDAwODI4MTc2&devicetype=Windows+11+x64&version=63090c37&lang=zh_CN&countrycode=CN&exportkey=n_ChQIAhIQC%2Byv%2BJhP3dcGtQjVPBMVaRLmAQIE97dBBAEAAAAAAONbCcPymDMAAAAOpnltbLcz9gKNyK89dVj0mId9lGY7cgnRR9uMjFqtA85HhyhxR%2F8MkxC519%2BAAh5oQXtwcxPqtbYuMxU9JG0jLZYRcQ6v6JjRHJNp8ys0DuFDth01%2Fp3jjmgfq9HvxRpISFTGcQeWwa5V638C1okgLeuZSo%2BIQ7HhM0cM1JDjoeoCj4RS4DVLDLeupELVNXBvlf7OkIUXSv6UqorKMIEzGKrpeQA%2BBPGRVfR3u%2BhoINPcohS22vIVXyOvaMNODUGi04FFuwOSZzsrHtd3Mkxp&acctmode=0&pass_ticket=blZdPvi3Lplm9v5ei2FR0KEPOc%2FjdsNpEJy8DRmJcCqu5pYRRJMo5GEgEjyl5FKH&wx_header=1