Files
2026-09-24 16:25:22 +08:00

272 lines
17 KiB
HTML
Raw Permalink 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">
<title>检伤建档 · 2D 人体图鉴 & 伤情抽屉(静态预览)</title>
<!--
目的:无构建环境下预览 create.uvue 的伤情标记区与 xDrawer 表单。
尺寸换算:rpx → px 按 0.5(375 逻辑宽),即 1:1 还原真机视觉。
数据/公式与 utils/injury.uts、components/hr-bodymap 一一对应。
-->
<style>
* { box-sizing: border-box; }
body { margin: 0; background: #F3F4F6; font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; color: #1F2937; }
.stage { width: 375px; padding: 12px 11px 24px; }
.card { background: #fff; border-radius: 11px; padding: 12px; margin-bottom: 14px; }
.sec { display: flex; flex-direction: row; align-items: center; font-size: 14px; font-weight: bold; margin: 4px 0 8px; }
.sec-bar { width: 3px; height: 13px; border-radius: 2px; background: #2563EB; margin-right: 6px; }
.sec-more { margin-left: auto; font-size: 11.5px; color: #6B7280; font-weight: normal; }
/* ---------- 2D 人体图鉴(几何值 = hr-design.css / hr-bodymap 的一半) ---------- */
.bm-row { display: flex; flex-direction: row; }
.bm-col { flex: 1; background: #FAFBFC; border: 1px dashed #E5E7EB; border-radius: 12px; padding: 10px 6px 8px; align-items: center; position: relative; margin-right: 10px; display: flex; flex-direction: column; }
.bm-col-last { margin-right: 0; }
.bm-figure { width: 96px; height: 184px; position: relative; }
.bm-head { position: absolute; left: 33px; top: 0; width: 30px; height: 30px; border-radius: 15px; border: 2.5px solid #CBD5E1; }
.bm-torso { position: absolute; left: 26px; top: 33px; width: 44px; height: 62px; border-radius: 14px; border: 2.5px solid #CBD5E1; }
.bm-arm-l { position: absolute; left: 8px; top: 36px; width: 12px; height: 58px; border-radius: 6px; border: 2.5px solid #CBD5E1; }
.bm-arm-r { position: absolute; left: 76px; top: 36px; width: 12px; height: 58px; border-radius: 6px; border: 2.5px solid #CBD5E1; }
.bm-leg-l { position: absolute; left: 28px; top: 96px; width: 13px; height: 84px; border-radius: 7px; border: 2.5px solid #CBD5E1; }
.bm-leg-r { position: absolute; left: 55px; top: 96px; width: 13px; height: 84px; border-radius: 7px; border: 2.5px solid #CBD5E1; }
.bmp-spine { position: absolute; left: 47px; top: 39px; width: 2px; height: 54px; border-radius: 1px; background: #E2E8F0; }
.bmp-hit { position: absolute; border-radius: 5px; border: 1px dashed #EDF0F4; }
.bmp-hit-on { border: 1px solid #BFDBFE; background: #EFF6FF; }
.bmp-mark { position: absolute; width: 38px; height: 38px; margin-left: -19px; margin-top: -19px; display: flex; flex-direction: row; align-items: center; justify-content: center; }
.bmp-bar { position: absolute; top: 13px; height: 12px; border-radius: 6px; opacity: .8; }
.bmp-dot { width: 22px; height: 22px; border-radius: 11px; border: 1.5px solid #fff; display: flex; align-items: center; justify-content: center; }
.bmp-dot-t { font-size: 11.5px; font-weight: bold; color: #fff; }
.bm-tag { font-size: 11.8px; color: #9CA3AF; margin-top: 7px; }
.c-sum { display: flex; align-items: center; justify-content: space-between; margin-top: 11px; font-size: 11.8px; }
.c-sum-t { color: #6B7280; }
.c-sum-json { color: #2563EB; }
.c-mklist { margin-top: 8px; }
.c-mk { display: flex; align-items: center; background: #F9FAFB; border: 1px solid #E5E7EB; border-radius: 11px; padding: 10px 12px; margin-bottom: 8px; }
.c-mk-main { display: flex; align-items: center; flex: 1; }
.c-mk-no { width: 22px; height: 22px; border-radius: 11px; display: flex; align-items: center; justify-content: center; margin-right: 10px; flex: 0 0 22px; }
.c-mk-no-t { font-size: 12.3px; font-weight: bold; color: #fff; }
.c-mk-t { font-size: 14px; font-weight: bold; color: #1F2937; }
.c-mk-s { font-size: 11.8px; color: #9CA3AF; margin-top: 2px; }
.c-mk-side { border-radius: 5px; padding: 2px 7px; margin-left: 6px; }
.c-mk-side-t { font-size: 10.6px; color: #6B7280; }
.c-mk-del { width: 20px; height: 20px; border-radius: 10px; background: #F3F4F6; display: flex; align-items: center; justify-content: center; margin-left: 8px; color: #6B7280; font-size: 11px; }
.c-addmark { display: flex; align-items: center; justify-content: center; height: 40px; border: 1.5px dashed #C7CDD6; border-radius: 11px; background: #fff; font-size: 12.9px; color: #6B7280; }
/* ---------- 伤情录入抽屉 ---------- */
.drawer { background: #fff; border-radius: 12px 12px 0 0; padding: 0 16px; }
.d-head { display: flex; align-items: center; width: 100%; padding-top: 3px; margin-bottom: 4px; }
.d-head-dot { width: 22px; height: 22px; border-radius: 11px; display: flex; align-items: center; justify-content: center; margin-right: 8px; flex: 0 0 22px; }
.d-head-dot-t { font-size: 11.5px; font-weight: bold; color: #fff; }
.d-head-t { font-size: 15px; font-weight: bold; }
.d-head-s { font-size: 10.6px; color: #9CA3AF; margin-top: 2px; }
.d-blk { margin-bottom: 12px; }
.d-blk-h { display: flex; align-items: center; justify-content: space-between; }
.d-fl { font-size: 12.9px; font-weight: bold; }
.d-regen { font-size: 11.4px; color: #2563EB; }
.d-chips { display: flex; flex-wrap: wrap; margin-top: 7px; }
.d-chip { display: flex; align-items: center; border-radius: 7px; padding: 6px 10px; margin-right: 7px; margin-bottom: 7px; font-size: 12px; }
.d-chip-z { border: 1px solid #E5E7EB; background: #fff; color: #4B5563; }
.d-chip-on { border: 1px solid #2563EB; background: #2563EB; color: #fff; font-weight: bold; }
.d-chip-dot { width: 8px; height: 8px; border-radius: 4px; margin-right: 5px; }
.d-side { display: flex; border: 1px solid #E5E7EB; border-radius: 7px; overflow: hidden; }
.d-side-i { padding: 4px 11px; font-size: 12px; }
.d-rot { display: flex; align-items: center; }
.d-rot-b { width: 40px; height: 26px; border: 1px solid #E5E7EB; border-radius: 7px; background: #fff; display: flex; align-items: center; justify-content: center; font-size: 11.4px; color: #4B5563; }
.d-rot-v { width: 42px; font-size: 12.9px; font-weight: bold; color: #2563EB; text-align: center; }
.d-prev { width: 100%; height: 66px; background: #FAFBFC; border: 1px dashed #E5E7EB; border-radius: 9px; position: relative; margin-top: 8px; }
.d-prev-bar { position: absolute; top: 27px; height: 12px; border-radius: 6px; opacity: .85; }
.d-prev-dot { position: absolute; left: 50%; top: 50%; width: 22px; height: 22px; border-radius: 11px; margin-left: -11px; margin-top: -11px; border: 1.5px solid #fff; display: flex; align-items: center; justify-content: center; }
.d-input { width: 100%; height: 36px; background: #F9FAFB; border: 1px solid #E5E7EB; border-radius: 8px; padding: 0 10px; font-size: 13.2px; color: #1F2937; }
.d-ta { width: 100%; height: 88px; background: #F9FAFB; border: 1px solid #E5E7EB; border-radius: 9px; padding: 9px 10px; font-size: 13.2px; color: #1F2937; margin-top: 7px; }
.d-foot { display: flex; align-items: center; padding: 10px 0 14px; }
.d-foot-cancel { flex: 0 0 110px; margin-right: 9px; }
.d-btn { height: 44px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 16px; font-weight: bold; }
.d-b-ghost { background: #fff; border: 1px solid #E5E7EB; color: #1F2937; }
.d-b-pri { background: #2563EB; color: #fff; flex: 1; }
.lbl { font-size: 11px; color: #9CA3AF; margin: 10px 0 4px; }
</style>
</head>
<body>
<div class="stage">
<div class="sec"><view class="sec-bar"></view>伤情标记(2D 人体图鉴)<span class="sec-more"> 手动添加</span></div>
<div class="card">
<div class="bm-row">
<div class="bm-col">
<div class="bm-figure" id="figFront"></div>
<div class="bm-tag">正面 · 点部位即标记</div>
</div>
<div class="bm-col bm-col-last">
<div class="bm-figure" id="figBack"></div>
<div class="bm-tag">背面 · 点部位即标记</div>
</div>
</div>
<div class="c-sum">
<span class="c-sum-t" id="sum"></span>
<span class="c-sum-json">查看 JSON </span>
</div>
<div class="c-mklist" id="mklist"></div>
</div>
<div class="sec"><view class="sec-bar"></view>伤情录入抽屉(xDrawer · 底部弹出)</div>
<div class="drawer">
<div class="d-head">
<div class="d-head-dot" id="dDot"><span class="d-head-dot-t" id="dNo">4</span></div>
<div style="flex:1">
<div class="d-head-t">新增伤情</div>
<div class="d-head-s">部位已自动填入,其余按默认值预填,可直接保存</div>
</div>
</div>
<div id="dBody"></div>
<div class="d-foot">
<div class="d-btn d-b-ghost d-foot-cancel">取消</div>
<div class="d-btn d-b-pri">保存标记</div>
</div>
</div>
<div class="lbl">※ 预览为 create.uvue 的静态还原:绿色虚线框是热区(实际运行不可见,这里画出来核对是否覆盖人体轮廓)</div>
</div>
<script>
/* ============ 与 utils/injury.uts 保持一致的数据与公式 ============ */
function buildParts(side) {
const flip = side === 'front';
const armViewLeft = flip ? '右上肢' : '左上肢';
const armViewRight = flip ? '左上肢' : '右上肢';
const legViewLeft = flip ? '右下肢' : '左下肢';
const legViewRight = flip ? '左下肢' : '右下肢';
return [
{ id: 'head', name: '头部', x: 33, y: 0, w: 34, h: 15, mx: 50, my: 8 },
{ id: 'neck', name: '颈部', x: 38, y: 15, w: 24, h: 8, mx: 50, my: 19 },
{ id: 'chest', name: '胸部', x: 26, y: 23, w: 48, h: 15, mx: 50, my: 30 },
{ id: 'abdomen', name: '腹部', x: 26, y: 38, w: 48, h: 10, mx: 50, my: 43 },
{ id: 'pelvis', name: '骨盆', x: 26, y: 48, w: 48, h: 10, mx: 50, my: 53 },
{ id: 'armVL', name: armViewLeft, x: 5, y: 18, w: 19, h: 36, mx: 14, my: 33 },
{ id: 'armVR', name: armViewRight, x: 76, y: 18, w: 19, h: 36, mx: 86, my: 33 },
{ id: 'legVL', name: legViewLeft, x: 26, y: 58, w: 19, h: 42, mx: 35, my: 79 },
{ id: 'legVR', name: legViewRight, x: 55, y: 58, w: 19, h: 42, mx: 65, my: 79 }
];
}
const EDGE = { 活动性出血: ['#DC2626', '#FCA5A5'], 渗血: ['#F59E0B', '#FCD34D'], 已止血: ['#10B981', '#6EE7B7'], 无出血: ['#9CA3AF', '#E5E7EB'] };
function sizeBarWidth(size) { // 与 injury.uts 同算法
const m = /^(\d+)/.exec(size);
if (!m) return 56;
let w = 40 + parseInt(m[1], 10) * 9;
return Math.round(Math.max(44, Math.min(132, w)));
}
function defaultDesc(part, nature, size, bleeding) {
const tail = { 开放性外伤: '创缘不整,未见异物', 裂伤: '创缘整齐,深及皮下', 擦伤: '表皮剥脱,创面污染',
挫伤: '局部肿胀压痛,无开放伤口', 刺伤: '创口小、伤道深,需探查异物', 切割伤: '创缘整齐,注意肌腱神经损伤',
烧伤: '创面潮红,需记录面积与深度', 挤压伤: '肢体肿胀张力高,警惕骨筋膜室综合征', 闭合性骨折: '畸形、反常活动、骨擦感',
开放性骨折: '骨折端外露,已覆盖并固定', 关节脱位: '关节畸形、弹性固定、活动受限', 贯通伤: '可见入口与出口,警惕深部脏器损伤',
动物咬伤: '创面污染重,需清创并评估狂犬病暴露' }[nature] || '待补充伤情描述';
return part + ' ' + nature + (size ? '' + size + '' : '') + '' + bleeding + '' + tail + '。';
}
/* 与页面示例数据一致(demoMark 三处) */
let marks = [
{ id: 'd1', no: 1, side: 'front', partId: 'head', partName: '头部', rot: 0, nature: '开放性外伤', size: '4×2cm', bleeding: '活动性出血' },
{ id: 'd2', no: 2, side: 'front', partId: 'armVL', partName: '右上肢', rot: 90, nature: '闭合性骨折', size: '5×3cm', bleeding: '已止血' },
{ id: 'd3', no: 3, side: 'back', partId: 'legVL', partName: '左下肢', rot: 45, nature: '挤压伤', size: '8×4cm', bleeding: '渗血' }
];
const partsOf = (s) => buildParts(s);
marks.forEach(m => {
const p = partsOf(m.side).find(x => x.id === m.partId);
m.x = p.mx; m.y = p.my;
m.desc = defaultDesc(m.partName, m.nature, m.size, m.bleeding);
});
/* ============ 渲染 ============ */
const SKELETON = '<div class="bm-head"></div><div class="bm-torso"></div><div class="bm-arm-l"></div><div class="bm-arm-r"></div><div class="bm-leg-l"></div><div class="bm-leg-r"></div>';
function renderFigure(el, side, showHit) {
const parts = partsOf(side);
const ms = marks.filter(m => m.side === side);
let h = SKELETON;
if (side === 'back') h += '<div class="bmp-spine"></div>';
if (showHit) {
parts.forEach(p => {
const on = ms.some(m => m.partId === p.id);
h += `<div class="bmp-hit${on ? ' bmp-hit-on' : ''}" title="${p.name}" style="left:${p.x}%;top:${p.y}%;width:${p.w}%;height:${p.h}%;border-color:${on ? '' : '#22C55E'};border-style:${on ? 'solid' : 'dashed'};"></div>`;
});
}
ms.forEach(m => {
const w = sizeBarWidth(m.size), half = Math.round(w / 2);
const [dot, bar] = EDGE[m.bleeding];
h += `<div class="bmp-mark" style="left:${m.x}%;top:${m.y}%;">
<div class="bmp-bar" style="width:${w / 2}px;margin-left:-${half / 2}px;left:50%;background:${bar};transform:rotate(${m.rot}deg)"></div>
<div class="bmp-dot" style="background:${dot}"><span class="bmp-dot-t">${m.no}</span></div>
</div>`;
});
el.innerHTML = h;
}
renderFigure(document.getElementById('figFront'), 'front', true);
renderFigure(document.getElementById('figBack'), 'back', true);
document.getElementById('sum').textContent =
'共 ' + marks.length + ' 处标记,其中 ' + marks.filter(m => m.bleeding === '活动性出血').length + ' 处活动性出血';
document.getElementById('mklist').innerHTML = marks.map(m => `
<div class="c-mk">
<div class="c-mk-main">
<div class="c-mk-no" style="background:${EDGE[m.bleeding][0]}"><span class="c-mk-no-t">${m.no}</span></div>
<div style="flex:1">
<div class="c-mk-t">${m.partName} · ${m.nature}</div>
<div class="c-mk-s">${m.size} · ${m.bleeding} · 走向 ${m.rot}°</div>
</div>
<div class="c-mk-side" style="background:${m.side === 'back' ? '#F3F4F6' : '#EFF6FF'}"><span class="c-mk-side-t">${m.side === 'back' ? '背面' : '正面'}</span></div>
</div>
<div class="c-mk-del">✕</div>
</div>`).join('') + `
<div class="c-addmark"> 点人体图部位快速添加,或点此手动添加</div>`;
/* ---- 抽屉:模拟「点了正面图左臂(伤员左上肢)」后的自动填充态 ---- */
const dSide = 'front', dPartId = 'armVR', dNature = '开放性外伤', dBleeding = '渗血', dSize = '4×2cm', dRot = 135;
const partName = partsOf(dSide).find(p => p.id === dPartId).name;
const chip = (t, on, dot) => `<span class="d-chip ${on ? 'd-chip-on' : 'd-chip-z'}">${dot ? `<i class="d-chip-dot" style="background:${dot}"></i>` : ''}${t}</span>`;
const natures = ['开放性外伤', '裂伤', '擦伤', '挫伤', '刺伤', '切割伤', '烧伤', '挤压伤', '闭合性骨折', '开放性骨折', '关节脱位', '贯通伤', '动物咬伤'];
const bleedings = ['活动性出血', '渗血', '已止血', '无出血'];
const sizes = ['1×1cm', '2×2cm', '4×2cm', '5×3cm', '8×4cm', '>10cm'];
const bw = sizeBarWidth(dSize), bhalf = Math.round(bw / 2);
document.getElementById('dDot').style.background = EDGE[dBleeding][0];
document.getElementById('dBody').innerHTML = `
<div class="d-blk">
<div class="d-blk-h">
<span class="d-fl">部位</span>
<span class="d-side">
<span class="d-side-i" style="background:#2563EB;color:#fff">正面</span>
<span class="d-side-i" style="background:#fff;color:#6B7280">背面</span>
</span>
</div>
<div class="d-chips">${partsOf(dSide).map(p => chip(p.name, p.id === dPartId)).join('')}</div>
</div>
<div class="d-blk">
<span class="d-fl">伤口性质</span>
<div class="d-chips">${natures.map(n => chip(n, n === dNature)).join('')}</div>
</div>
<div class="d-blk">
<span class="d-fl">出血情况(决定标记颜色)</span>
<div class="d-chips">${bleedings.map(b => chip(b, b === dBleeding, EDGE[b][0])).join('')}</div>
</div>
<div class="d-blk">
<span class="d-fl">尺寸 / 面积</span>
<div class="d-chips">${sizes.map(s => chip(s, s === dSize)).join('')}</div>
<input class="d-input" value="${dSize}">
</div>
<div class="d-blk">
<div class="d-blk-h">
<span class="d-fl">伤口走向</span>
<span class="d-rot"><span class="d-rot-b">45°</span><span class="d-rot-v">${dRot}°</span><span class="d-rot-b">45°</span></span>
</div>
<div class="d-prev">
<div class="d-prev-bar" style="width:${bw / 2}px;margin-left:-${bhalf / 2}px;left:50%;background:${EDGE[dBleeding][1]};transform:rotate(${dRot}deg)"></div>
<div class="d-prev-dot" style="background:${EDGE[dBleeding][0]}"><span class="bmp-dot-t">4</span></div>
</div>
<div class="d-chips">${[0, 45, 90, 135].map(r => chip(r + '°', r === dRot)).join('')}</div>
</div>
<div class="d-blk">
<div class="d-blk-h"><span class="d-fl">详情描述</span><span class="d-regen">↻ 按当前选项重生成</span></div>
<textarea class="d-ta">${defaultDesc(partName, dNature, dSize, dBleeding)}</textarea>
</div>`;
</script>
</body>
</html>