This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
+271
View File
@@ -0,0 +1,271 @@
<!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>