Files
client-qgdzs/pages/index/index.vue

659 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<scroll-view
class="container"
scroll-y
show-scrollbar="false"
@scrolltolower="loadMore"
>
<view class="header">
<view class="welcome">
<text class="welcome-title">答题记录</text>
<text class="welcome-subtitle">查看你的答题历史</text>
</view>
</view>
<view class="record-section">
<view v-if="isNotLoggedIn" class="empty-record">
<text class="empty-icon">🔐</text>
<text class="empty-text">需要登录</text>
<text class="empty-hint">登录后才会记录答题记录</text>
</view>
<view v-else-if="recordList.length > 0" class="record-list">
<view v-for="(record, index) in recordList" :key="index" class="record-item" @click="handleRecordClick(record)">
<view class="record-header">
<view class="record-category">{{ record.category }}</view>
<view class="record-difficulty" :class="getDifficultyClass(record.difficulty)">
<text class="difficulty-text">{{ getDifficultyText(record.difficulty) }}</text>
</view>
</view>
<view class="record-question">{{ record.question }}</view>
<view class="record-footer">
<view class="record-result" :class="record.is_correct ? 'correct' : 'wrong'">
<text class="result-icon">{{ record.is_correct ? '✓' : '✗' }}</text>
<text class="result-text">{{ record.is_correct ? '回答正确' : '回答错误' }}</text>
</view>
<text class="record-time">{{ formatTime(record.create_time) }}</text>
</view>
</view>
</view>
<view v-else class="empty-record">
<text class="empty-icon">📝</text>
<text class="empty-text">暂无答题记录</text>
<text class="empty-hint">快去答题吧</text>
</view>
<view v-if="isLoading" class="loading-more">
<text class="loading-text">加载中...</text>
</view>
<view v-if="!hasMore && recordList.length > 0" class="no-more">
<text class="no-more-text">没有更多了</text>
</view>
</view>
<view v-if="showDetail" class="detail-modal" @click="closeDetail">
<view class="detail-content" @click.stop>
<view class="detail-header">
<text class="detail-title">题目详情</text>
<text class="detail-close" @click="closeDetail"></text>
</view>
<view v-if="detailLoading" class="detail-loading">
<text class="loading-text">加载中...</text>
</view>
<view v-else-if="detailQuestion" class="detail-body">
<view class="detail-meta">
<view class="meta-tag category-tag">
<text class="meta-text">{{ detailQuestion.category }}</text>
</view>
<view class="meta-tag difficulty-tag" :class="getDifficultyClass(detailQuestion.difficulty)">
<text class="meta-text">{{ getDifficultyText(detailQuestion.difficulty) }}</text>
</view>
</view>
<view class="detail-question">
<text class="question-text">{{ detailQuestion.question }}</text>
</view>
<view v-if="detailQuestion.options" class="detail-options">
<view
v-for="(option, index) in detailQuestion.options"
:key="index"
class="detail-option"
:class="{
'correct': getOptionLabel(option) === getCorrectAnswer(),
'wrong': getOptionLabel(option) === detailQuestion.user_answer && getOptionLabel(option) !== getCorrectAnswer()
}"
>
<text class="option-text">{{ option }}</text>
</view>
</view>
<view v-if="detailQuestion.explanation" class="detail-analysis">
<text class="analysis-label">解析</text>
<text class="analysis-text">{{ detailQuestion.explanation }}</text>
</view>
</view>
</view>
</view>
</scroll-view>
</template>
<script>
import api from '../../utils/api.js'
export default {
data() {
return {
recordList: [],
currentPage: 1,
pageSize: 10,
totalCount: 0,
hasMore: true,
isLoading: false,
isFirstLoad: true,
showDetail: false,
detailQuestion: null,
detailLoading: false,
isNotLoggedIn: false
}
},
onLoad() {
this.loadRecord()
},
onShow() {
if (!this.isFirstLoad) {
this.loadRecord()
}
this.isFirstLoad = false
},
methods: {
async handleRecordClick(record) {
this.showDetail = true
this.detailLoading = true
this.detailQuestion = null
try {
const res = await api.getQuestionInfo(record.question_sn)
if (res.data) {
this.detailQuestion = {
...res.data,
is_correct: record.is_correct,
user_answer: record.answer,
correct_answer: record.question_answer
}
}
} catch (error) {
console.error('获取题目详情失败:', error)
uni.showToast({
title: '获取失败',
icon: 'none'
})
} finally {
this.detailLoading = false
}
},
closeDetail() {
this.showDetail = false
this.detailQuestion = null
},
getCorrectAnswer() {
if (!this.detailQuestion) return ''
return String(this.detailQuestion.correct_answer || '').toUpperCase()
},
getOptionLabel(option) {
if (!option) return ''
const match = option.match(/^([A-D])\./)
return match ? match[1] : ''
},
async loadRecord(isLoadMore = false) {
if (this.isLoading) return
if (!isLoadMore) {
this.currentPage = 1
this.hasMore = true
this.isNotLoggedIn = false
}
if (!this.hasMore) return
this.isLoading = true
try {
const res = await api.getRecord(this.currentPage, this.pageSize)
if (res.data) {
const newRecords = (res.data.records || []).map(record => ({
...record,
is_correct: String(record.answer || '').toUpperCase() === String(record.question_answer || '').toUpperCase()
}))
if (isLoadMore) {
this.recordList = [...this.recordList, ...newRecords]
} else {
this.recordList = newRecords
}
this.totalCount = res.data.count || 0
if (this.recordList.length >= this.totalCount) {
this.hasMore = false
} else {
this.currentPage++
}
}
} catch (error) {
console.error('获取答题记录失败:', error)
// 检查是否是401错误
if (error.message && error.message.includes('401')) {
this.isNotLoggedIn = true
}
} finally {
this.isLoading = false
}
},
loadMore() {
this.loadRecord(true)
},
getDifficultyText(difficulty) {
if (!difficulty && difficulty !== 0) return '简单'
if (difficulty <= 30) return '简单'
if (difficulty <= 60) return '中等'
if (difficulty <= 80) return '困难'
return '极难'
},
getDifficultyClass(difficulty) {
if (!difficulty && difficulty !== 0) return 'difficulty-easy'
if (difficulty <= 30) return 'difficulty-easy'
if (difficulty <= 60) return 'difficulty-medium'
if (difficulty <= 80) return 'difficulty-hard'
return 'difficulty-extreme'
},
formatTime(timestamp) {
if (!timestamp) return ''
const date = new Date(timestamp * 1000)
const now = new Date()
const diff = now - date
const minute = 60 * 1000
const hour = 60 * minute
const day = 24 * hour
if (diff < minute) {
return '刚刚'
} else if (diff < hour) {
return Math.floor(diff / minute) + '分钟前'
} else if (diff < day) {
return Math.floor(diff / hour) + '小时前'
} else if (diff < 7 * day) {
return Math.floor(diff / day) + '天前'
} else {
const month = date.getMonth() + 1
const dayOfMonth = date.getDate()
return `${month}${dayOfMonth}`
}
}
}
}
</script>
<style scoped>
.container {
height: 100vh;
width: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 40rpx;
box-sizing: border-box;
}
.container::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
background: transparent;
}
/deep/ ::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
background: transparent;
}
.header {
margin-bottom: 60rpx;
}
.welcome {
text-align: center;
}
.welcome-title {
display: block;
font-size: 48rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 20rpx;
}
.welcome-subtitle {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.9);
}
.record-section {
margin-bottom: 60rpx;
}
.record-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.record-item {
background: #ffffff;
border-radius: 20rpx;
padding: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
width: 100%;
box-sizing: border-box;
}
.record-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.record-category {
font-size: 26rpx;
color: #666;
background: #f0f0f0;
padding: 6rpx 16rpx;
border-radius: 12rpx;
}
.record-difficulty {
padding: 6rpx 16rpx;
border-radius: 12rpx;
font-size: 24rpx;
}
.difficulty-easy {
background: #d4edda;
color: #155724;
}
.difficulty-medium {
background: #fff3cd;
color: #856404;
}
.difficulty-hard {
background: #fd7e14;
color: #ffffff;
}
.difficulty-extreme {
background: #dc3545;
color: #ffffff;
}
.difficulty-tag.difficulty-easy {
background: #d4edda;
color: #155724;
}
.difficulty-tag.difficulty-medium {
background: #fff3cd;
color: #856404;
}
.difficulty-tag.difficulty-hard {
background: #fd7e14;
color: #ffffff;
}
.difficulty-tag.difficulty-extreme {
background: #dc3545;
color: #ffffff;
}
.difficulty-text {
font-weight: bold;
}
.record-question {
font-size: 30rpx;
color: #333;
line-height: 1.6;
margin-bottom: 20rpx;
}
.record-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 20rpx;
border-top: 2rpx solid #f0f0f0;
}
.record-result {
display: flex;
align-items: center;
gap: 10rpx;
font-size: 26rpx;
}
.record-result.correct {
color: #28a745;
}
.record-result.wrong {
color: #dc3545;
}
.result-icon {
font-size: 32rpx;
font-weight: bold;
}
.result-text {
font-weight: bold;
}
.record-time {
font-size: 24rpx;
color: #999;
}
.empty-record {
background: rgba(255, 255, 255, 0.15);
border-radius: 20rpx;
padding: 100rpx 40rpx;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 20rpx;
}
.empty-icon {
font-size: 80rpx;
}
.empty-text {
font-size: 32rpx;
color: rgba(255, 255, 255, 0.9);
font-weight: bold;
}
.empty-hint {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.7);
}
.loading-more {
text-align: center;
padding: 40rpx 0;
}
.loading-text {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.9);
}
.no-more {
text-align: center;
padding: 40rpx 0;
}
.no-more-text {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.6);
}
.detail-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.detail-content {
background: #ffffff;
border-radius: 20rpx;
width: 90%;
max-height: 80vh;
display: flex;
flex-direction: column;
}
.detail-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 2rpx solid #f0f0f0;
}
.detail-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.detail-close {
font-size: 48rpx;
color: #999;
line-height: 1;
}
.detail-loading {
padding: 80rpx 40rpx;
text-align: center;
}
.detail-body {
padding: 30rpx;
overflow-y: auto;
}
.detail-meta {
display: flex;
gap: 20rpx;
margin-bottom: 30rpx;
}
.meta-tag {
padding: 8rpx 20rpx;
border-radius: 16rpx;
font-size: 24rpx;
}
.category-tag {
background: #e3f2fd;
color: #0d47a1;
}
.difficulty-tag {
background: #f8f9fa;
}
.detail-question {
margin-bottom: 30rpx;
}
.question-text {
font-size: 32rpx;
color: #333;
line-height: 1.8;
}
.detail-analysis {
background: #f0f9ff;
padding: 24rpx;
border-radius: 12rpx;
margin-bottom: 30rpx;
}
.analysis-label {
font-size: 26rpx;
font-weight: bold;
color: #0d47a1;
margin-right: 10rpx;
}
.analysis-text {
font-size: 28rpx;
color: #666;
line-height: 1.6;
}
.detail-options {
display: flex;
flex-direction: column;
gap: 20rpx;
margin-bottom: 30rpx;
}
.detail-option {
display: flex;
align-items: center;
padding: 24rpx 30rpx;
background: #f8f9fa;
border-radius: 12rpx;
gap: 16rpx;
}
.detail-option.correct {
background: #d4edda;
border: 2rpx solid #28a745;
}
.detail-option.wrong {
background: #f8d7da;
border: 2rpx solid #dc3545;
}
.option-label {
font-size: 28rpx;
font-weight: bold;
color: #666;
min-width: 40rpx;
}
.option-text {
font-size: 28rpx;
color: #333;
flex: 1;
}
.detail-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 20rpx;
border-top: 2rpx solid #f0f0f0;
}
.detail-result {
display: flex;
align-items: center;
gap: 10rpx;
font-size: 28rpx;
}
.detail-result.correct {
color: #28a745;
}
.detail-result.wrong {
color: #dc3545;
}
.detail-answer {
font-size: 28rpx;
color: #666;
}
.answer-text {
font-weight: bold;
color: #333;
}
</style>