Files
hospital-rescue-app-v2/.workbuddy/uvue-anim-preview.html
T
2026-09-24 16:25:22 +08:00

392 lines
9.2 KiB
HTML
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.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>uvue 动效改写验证 · 红点呼吸 / 扫描线 / 手风琴</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 32px 24px 64px;
background: #F3F4F6;
font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
color: #1F2937;
}
.wrap {
max-width: 1040px;
margin: 0 auto;
}
h1 {
font-size: 22px;
margin: 0 0 6px;
}
.sub {
font-size: 13px;
color: #6B7280;
margin: 0 0 8px;
line-height: 1.7;
}
.warn {
background: #FFFBEB;
border: 1px solid #FDE68A;
color: #92400E;
border-radius: 12px;
padding: 12px 16px;
font-size: 13px;
line-height: 1.7;
margin-bottom: 26px;
}
.grid {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.card {
flex: 1 1 300px;
min-width: 300px;
background: #FFF;
border: 1px solid #E5E7EB;
border-radius: 16px;
padding: 18px 20px 20px;
}
.card h2 {
font-size: 15px;
margin: 0 0 4px;
}
.src {
font-size: 11px;
color: #9CA3AF;
font-family: ui-monospace, Menlo, Consolas, monospace;
margin: 0 0 16px;
}
.stage {
height: 210px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
/* ---- 1. 红点 ---- */
.evt-card {
width: 210px;
background: #FFFBFB;
border: 1px solid #FECACA;
border-radius: 13px;
padding: 12px 13px;
}
.evt-top {
display: flex;
align-items: center;
}
.evt-dot {
width: 8px;
height: 8px;
border-radius: 4px;
margin: 0 8px;
background-color: #DC2626;
}
.evt-name {
flex: 1;
font-size: 15px;
font-weight: 700;
}
.pri {
background: #DC2626;
color: #FFF;
font-size: 11px;
font-weight: 700;
border-radius: 6px;
padding: 2px 8px;
}
/* ---- 2. 扫描线 ---- */
.sc-box {
width: 100%;
height: 178px;
background: #0F172A;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.sc-frame {
width: 93px;
height: 93px;
position: relative;
}
.cf {
position: absolute;
width: 13px;
height: 13px;
border: 3px solid #10B981;
border-radius: 4px;
}
.cf-tl {
top: 0;
left: 0;
border-right-width: 0;
border-bottom-width: 0;
}
.cf-tr {
top: 0;
right: 0;
border-left-width: 0;
border-bottom-width: 0;
}
.cf-bl {
bottom: 0;
left: 0;
border-right-width: 0;
border-top-width: 0;
}
.cf-br {
bottom: 0;
right: 0;
border-left-width: 0;
border-top-width: 0;
}
.sc-line {
position: absolute;
left: 8.4px;
right: 8.4px;
height: 2px;
background: #10B981;
box-shadow: 0 0 8px rgba(16, 185, 129, .9);
}
.sc-tip {
position: absolute;
bottom: 12px;
left: 0;
right: 0;
text-align: center;
font-size: 11px;
color: rgba(255, 255, 255, .78);
}
/* ---- 3. 手风琴 ---- */
.qa-card {
width: 250px;
background: #FFF;
border: 1px solid #E5E7EB;
border-left: 4px solid #2563EB;
border-radius: 10px;
padding: 11px 12px;
box-shadow: 0 2px 10px rgba(15, 23, 42, .06);
}
.qa-stem {
font-size: 13px;
font-weight: 700;
}
.qa-fold {
margin-top: 8px;
display: flex;
justify-content: flex-end;
}
.qa-btn {
font-size: 12px;
color: #2563EB;
font-weight: 700;
cursor: pointer;
user-select: none;
}
.qa-analysis {
margin-top: 10px;
padding: 9px 10px;
border-radius: 9px;
background: #F9FAFB;
font-size: 12px;
color: #6B7280;
line-height: 1.6;
}
.readout {
margin-top: 12px;
font-size: 11px;
color: #9CA3AF;
font-family: ui-monospace, Menlo, Consolas, monospace;
}
</style>
</head>
<body>
<div class="wrap">
<h1>uvue App 端动效改写 · 效果验证</h1>
<p class="sub">下方三个动效与 App 里 <b>完全相同</b> 的算法在跑(定时器逐帧推进 → 下发内联 style),
只是把 rpx 折算成 px1rpx = 0.5px @375)。</p>
<div class="warn">
<b>背景</b>App 端 uvue 的 CSS 编译器不接受 <code>@keyframes</code>0%/75%/from/to 选择器直接报
ERROR)与 <code>animation</code> 属性,
因此这 3 处动效已全部改为 <code>setInterval</code> + <code>ref</code> 驱动内联样式的等价实现。
</div>
<div class="grid">
<!-- 1 -->
<div class="card">
<h2>① 待接收红点「呼吸」</h2>
<p class="src">pages/event/index.uvue · evtPulse</p>
<div class="stage" style="background:#F9FAFB">
<div class="evt-card">
<div class="evt-top">
<div class="evt-dot" id="dot"></div>
<span class="evt-name">车祸伤 · 多人受伤</span>
<span class="pri">P0</span>
</div>
</div>
</div>
<div class="readout" id="dotOut">半径 0rpx · 透明度 0.55</div>
</div>
<!-- 2 -->
<div class="card">
<h2>② 取景框扫描线</h2>
<p class="src">pages/warehouse/scan.uvue · hrScanMove</p>
<div class="stage">
<div class="sc-box">
<div class="sc-frame">
<div class="cf cf-tl"></div>
<div class="cf cf-tr"></div>
<div class="cf cf-bl"></div>
<div class="cf cf-br"></div>
<div class="sc-line" id="line"></div>
</div>
<div class="sc-tip">将条码对准框内 · 自动识别</div>
</div>
</div>
<div class="readout" id="lineOut">top 60rpx → 313rpx · 32 帧 × 68ms = 2.18s</div>
</div>
<!-- 3 -->
<div class="card">
<h2>③ 手风琴展开入场</h2>
<p class="src">pages/study/center.uvue · qa-acc-in</p>
<div class="stage" style="background:#F9FAFB">
<div class="qa-card">
<div class="qa-stem">成人胸外按压的深度应为?</div>
<div class="qa-fold"><span class="qa-btn" id="accBtn">点击展开解析</span></div>
<div id="accHost"></div>
</div>
</div>
<div class="readout" id="accOut">opacity 0 → 1 · translateY -16rpx → 0 · 240ms</div>
</div>
</div>
</div>
<script>
/* ============ ① 红点呼吸:与 pages/event/index.uvue 完全同算法 ============ */
const PULSE_FRAMES = 25
const PULSE_RAMP = 19
let pulseF = 0
const dotEl = document.getElementById('dot')
const dotOut = document.getElementById('dotOut')
/* 改写前的 CSS 版本,用作「应该长什么样」的参照 */
const RING_MAX_PX = 16 * 0.5 // 16rpx
function renderPulse() {
const i = pulseF
let r = 0, a = 0
if (i > PULSE_RAMP) { r = 0; a = 0 } else {
const k = i / PULSE_RAMP
r = Math.round(k * 16)
a = Math.round(55 * (1 - k))
}
dotEl.style.boxShadow = '0 0 0 ' + (r * 0.5).toFixed(1) + 'px rgba(220,38,38,0.' + a + ')'
dotOut.textContent = '半径 ' + r + 'rpx · 透明度 0.' + a + ' · 第 ' + i + '/25 帧'
const nf = pulseF + 1
pulseF = nf >= PULSE_FRAMES ? 0 : nf
}
setInterval(renderPulse, 68)
renderPulse()
/* ============ ② 扫描线:与 pages/warehouse/scan.uvue 完全同算法 ============ */
const LASER_MIN = 60, LASER_SPAN = 253
let laserT = 0.0
const lineEl = document.getElementById('line')
const lineOut = document.getElementById('lineOut')
function renderLaser() {
laserT = laserT + 0.03125
if (laserT > 1) { laserT = laserT - 1 }
const k = laserT < 0.5 ? laserT * 2 : (1 - laserT) * 2
const top = Math.round(LASER_MIN + k * LASER_SPAN)
lineEl.style.top = (top * 0.5).toFixed(1) + 'px'
lineOut.textContent = 'top ' + top + 'rpx (区间 60 → 313) · 32 帧 × 68ms = 2.18s'
}
setInterval(renderLaser, 68)
renderLaser()
/* ============ ③ 手风琴:与 pages/study/center.uvue 完全同算法 ============ */
let accIdx = -1, accT = 1, accTimer = 0, accOpen = false
const accHost = document.getElementById('accHost')
const accBtn = document.getElementById('accBtn')
const accOut = document.getElementById('accOut')
function stopAcc() {
if (accTimer != 0) { clearInterval(accTimer); accTimer = 0 }
}
function runAcc() {
stopAcc()
accIdx = 0
accT = 0
accTimer = setInterval(() => {
const t = accT + 0.16
if (t >= 1) { accT = 1; stopAcc(); renderAcc(); return }
accT = t
renderAcc()
}, 30)
}
function renderAcc() {
if (!accOpen) { accHost.innerHTML = ''; accOut.textContent = '已收起'; return }
if (accT >= 1) {
accHost.innerHTML = '<div class="qa-analysis">按压深度 5~6 cm,频率 100~120 次/分;每次按压后让胸廓完全回弹。</div>'
accOut.textContent = 'opacity 1 · translateY 0 · 动画完成'
return
}
const t = accT
const y = Math.round(-16 * (1 - t))
accHost.innerHTML = '<div class="qa-analysis" style="opacity:' + t +
';transform:translateY(' + (y * 0.5).toFixed(1) + 'px)">按压深度 5~6 cm,频率 100~120 次/分;每次按压后让胸廓完全回弹。</div>'
accOut.textContent = 'opacity ' + t.toFixed(2) + ' · translateY ' + y + 'rpx'
}
accBtn.addEventListener('click', () => {
accOpen = !accOpen
accBtn.textContent = accOpen ? '收起解析' : '点击展开解析'
if (accOpen) { runAcc() } else { stopAcc(); accIdx = -1; accT = 1; renderAcc() }
})
</script>
</body>
</html>