Files
hospital-rescue-app-v2/pages/study/wrong-book.uvue
T
2026-09-24 16:25:22 +08:00

271 lines
6.9 KiB
Plaintext
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>
<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),
* 改为定时器逐帧推进 accT0 → 1),再由 accStyle() 下发 opacity + translateY。
* 240ms30ms × 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>