1
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
<template>
|
||||
<hr-page page-bg="#F3F4F6" status-bg="#FFFFFF" nav-title="错题本" :xloading="true" @right-click="onSort">
|
||||
<template v-slot:right>
|
||||
<x-icon name="arrow-up-down-line" color="#6B7280" font-size="17"></x-icon>
|
||||
</template>
|
||||
|
||||
<view class="hr-body">
|
||||
|
||||
<!-- 课题筛选(组件库 xRadioButton 分段切换) -->
|
||||
<view style="margin-bottom:8rpx">
|
||||
<x-radio-button v-model="chipId" :list="chipTabs" height="64rpx" space="4rpx" round="22rpx"
|
||||
text-style="font-weight:bold;" bg-color="#E5E7EB" active-color="#2563EB"
|
||||
active-font-color="#FFFFFF" font-color="#6B7280" font-size="22rpx"></x-radio-button>
|
||||
</view>
|
||||
|
||||
<!-- 错题卡片 -->
|
||||
<view style="margin-top:10px">
|
||||
<view v-for="(q, i) in list" :key="q.id" class="qa-card">
|
||||
<view class="qa-top">
|
||||
<text class="qa-stem">{{ q.stem }}</text>
|
||||
<hr-chip :text="wrongTimes[i]" theme="red"></hr-chip>
|
||||
</view>
|
||||
<view class="qa-row">
|
||||
<text class="qa-k">你的答案</text>
|
||||
<text class="qa-wrong">{{ wrongKeys[i] }}</text>
|
||||
<text class="qa-k qa-k2">正确答案</text>
|
||||
<text class="qa-right">{{ rightAnswer(q) }}</text>
|
||||
</view>
|
||||
<view class="qa-meta">
|
||||
<text class="qa-meta-t">来自 <text class="qa-meta-hl">{{ q.from }}</text></text>
|
||||
<text class="qa-meta-t qa-meta-date">{{ dates[i] }} 答错</text>
|
||||
<view class="hr-flex1"></view>
|
||||
<view class="qa-fold" @click="toggle(i)">
|
||||
<x-icon :name="opened[i] ? 'arrow-up-s-line' : 'arrow-down-s-line'" color="#2563EB"
|
||||
font-size="26rpx"></x-icon>
|
||||
<text class="qa-fold-t">{{ opened[i] ? '收起解析' : '查看解析' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 手风琴展开:展开进度由 accStyle() 逐帧下发 -->
|
||||
<view v-if="opened[i]" :style="accStyle(i)">
|
||||
<view class="qa-analysis">
|
||||
<text class="qa-analysis-t">{{ q.analysis }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<hr-tip theme="info" icon="ℹ" text="已复习的错题不会自动移除;连续答对 2 次后可手动移出错题本。"></hr-tip>
|
||||
|
||||
<view style="height:14px"></view>
|
||||
</view>
|
||||
</hr-page>
|
||||
</template>
|
||||
|
||||
<script lang="uts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { wrongQuestions } from '@/mock/index.uts'
|
||||
import { HrQuestion } from '@/mock/types.uts'
|
||||
import { RADIO_BUTTON } from '@/uni_modules/tmx-ui/interface.uts'
|
||||
|
||||
/** s16 错题本 */
|
||||
/**
|
||||
* 课题筛选(组件库 xRadioButton 分段切换,替代原 hr-chip 横向滚动)
|
||||
* ⚠ RADIO_BUTTON 的 id 是 string:统一用下标字符串,选中值即筛选序号。
|
||||
*/
|
||||
const chipTabs : RADIO_BUTTON[] = [
|
||||
{ id: '0', title: '全部 12' } as RADIO_BUTTON,
|
||||
{ id: '1', title: '心肺复苏 5' } as RADIO_BUTTON,
|
||||
{ id: '2', title: '气道管理 4' } as RADIO_BUTTON,
|
||||
{ id: '3', title: 'AED 3' } as RADIO_BUTTON
|
||||
]
|
||||
const chipId = ref('0')
|
||||
|
||||
const wrongTimes : string[] = ['答错 2 次', '答错 1 次', '答错 1 次']
|
||||
const dates : string[] = ['09-15', '09-14', '09-12']
|
||||
const wrongKeys : string[] = ['A. 3~4 cm', 'C. 60~80 次', 'D. 每 6 秒 1 次']
|
||||
|
||||
const opened = ref<boolean[]>([false, false, false])
|
||||
const all = ref<HrQuestion[]>(wrongQuestions)
|
||||
|
||||
const list = computed(() : HrQuestion[] => {
|
||||
return all.value
|
||||
})
|
||||
|
||||
/** 选项文案里若已含「A. 」前缀则去掉,避免出现「A. A. xxx」 */
|
||||
function stripKey(text : string) : string {
|
||||
const i = text.indexOf('. ')
|
||||
if (i == 1 || i == 2) { return text.substring(i + 2) }
|
||||
return text
|
||||
}
|
||||
|
||||
// ⚠ UTS 不提升函数声明:stripKey 必须写在 rightAnswer 之前
|
||||
function rightAnswer(q : HrQuestion) : string {
|
||||
if (q.answer < q.options.length) {
|
||||
const o = q.options[q.answer]
|
||||
return `${o.key}. ${stripKey(o.text)}`
|
||||
}
|
||||
return '—'
|
||||
}
|
||||
|
||||
/*
|
||||
* 手风琴展开入场动画
|
||||
* ⚠ App 端 uvue 不支持 @keyframes / animation(编译期直接报 ERROR),
|
||||
* 改为定时器逐帧推进 accT(0 → 1),再由 accStyle() 下发 opacity + translateY。
|
||||
* 240ms(30ms × 8 帧),与设计稿 qa-acc-in 一致。
|
||||
*/
|
||||
const accIdx = ref(-1)
|
||||
const accT = ref(1)
|
||||
let accTimer : number = 0
|
||||
|
||||
function stopAcc() : void {
|
||||
if (accTimer != 0) {
|
||||
clearInterval(accTimer)
|
||||
accTimer = 0
|
||||
}
|
||||
}
|
||||
|
||||
function runAcc(i : number) : void {
|
||||
stopAcc()
|
||||
accIdx.value = i
|
||||
accT.value = 0
|
||||
accTimer = setInterval(() => {
|
||||
const t = accT.value + 0.16
|
||||
if (t >= 1) {
|
||||
accT.value = 1
|
||||
stopAcc()
|
||||
return
|
||||
}
|
||||
accT.value = t
|
||||
}, 30)
|
||||
}
|
||||
|
||||
/** 仅正在展开的那一项需要过渡样式,其余返回空串(即最终态) */
|
||||
function accStyle(i : number) : string {
|
||||
if (i != accIdx.value || accT.value >= 1) { return '' }
|
||||
const t = accT.value
|
||||
const y = Math.round(-16 * (1 - t))
|
||||
return 'opacity:' + t.toString() + ';transform:translateY(' + y.toString() + 'rpx);'
|
||||
}
|
||||
|
||||
/** 手风琴:先全部收起,再切换当前项(同时只展开一条) */
|
||||
function toggle(i : number) : void {
|
||||
const wasOpen = opened.value[i]
|
||||
const arr : boolean[] = []
|
||||
for (let k = 0; k < list.value.length; k++) { arr.push(false) }
|
||||
arr[i] = !wasOpen
|
||||
opened.value = arr
|
||||
if (wasOpen) {
|
||||
stopAcc()
|
||||
accIdx.value = -1
|
||||
accT.value = 1
|
||||
} else {
|
||||
runAcc(i)
|
||||
}
|
||||
}
|
||||
|
||||
onUnload(() => {
|
||||
stopAcc()
|
||||
})
|
||||
|
||||
function onSort() : void {
|
||||
uni.showToast({ title: '按错误次数 / 时间排序', icon: 'none' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.qa-card {
|
||||
background-color: #FFFFFF;
|
||||
border: 2rpx solid #E5E7EB;
|
||||
border-left: 8rpx solid #2563EB;
|
||||
border-radius: 20rpx;
|
||||
padding: 22rpx 24rpx 22rpx 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.qa-top {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.qa-stem {
|
||||
flex: 1;
|
||||
font-size: 29.2rpx;
|
||||
font-weight: bold;
|
||||
color: #1F2937;
|
||||
margin-right: 18rpx;
|
||||
}
|
||||
|
||||
.qa-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
|
||||
.qa-k {
|
||||
font-size: 26.8rpx;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.qa-k2 {
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
.qa-wrong {
|
||||
font-size: 26.8rpx;
|
||||
color: #DC2626;
|
||||
font-weight: bold;
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
|
||||
.qa-right {
|
||||
font-size: 26.8rpx;
|
||||
color: #059669;
|
||||
font-weight: bold;
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
|
||||
.qa-meta {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.qa-meta-t {
|
||||
font-size: 24.6rpx;
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
.qa-meta-hl {
|
||||
font-size: 24.6rpx;
|
||||
color: #1F2937;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.qa-meta-date {
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.qa-fold {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.qa-fold-t {
|
||||
font-size: 25.8rpx;
|
||||
color: #2563EB;
|
||||
font-weight: bold;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
|
||||
/* ---------- 手风琴展开 ----------
|
||||
⚠ App 端 uvue 不支持 @keyframes/animation,
|
||||
入场过渡已改为 script 内定时器推进 accT + accStyle() 下发样式。 */
|
||||
.qa-analysis {
|
||||
margin-top: 20rpx;
|
||||
padding: 18rpx 20rpx 18rpx 20rpx;
|
||||
border-radius: 18rpx;
|
||||
background-color: #F9FAFB;
|
||||
}
|
||||
|
||||
.qa-analysis-t {
|
||||
font-size: 25.8rpx;
|
||||
color: #6B7280;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user