This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
+187
View File
@@ -0,0 +1,187 @@
<template>
<!--
2D 人体图鉴(单面)
- 人体轮廓沿用 hr-design.css 里的 .bm-* 几何(192rpx × 368rpx),不要再改用 SVG
- 热区:9 个不重叠的可点部位,点一下就是「在该部位新增标记」
- 标记:编号圆点(不旋转,保证数字可读)+ 伤口长轴条(长度=尺寸、角度=走向、颜色=出血)
-->
<view class="bm-figure">
<!-- ===== 人体轮廓(装饰,不响应点击) ===== -->
<view class="bm-head"></view>
<view class="bm-torso"></view>
<view class="bm-arm-l"></view>
<view class="bm-arm-r"></view>
<view class="bm-leg-l"></view>
<view class="bm-leg-r"></view>
<!-- 背面加一条脊柱线,让正/背面一眼能分辨 -->
<view v-if="side == 'back'" class="bmp-spine"></view>
<!-- ===== 可点部位热区 ===== -->
<view v-for="(p, i) in parts" :key="p.id" :class="hitClass(p)" :style="hitStyle(p)"
@click="onHit(p.id)">
</view>
<!-- ===== 伤情标记 ===== -->
<view v-for="(m, i) in marks" :key="m.id" class="bmp-mark" :style="markStyle(m)" @click="onMark(m.id)">
<!-- 伤口长轴:先画在下方,编号圆点盖在上面 -->
<view class="bmp-bar" :style="barStyle(m)"></view>
<view class="bmp-dot" :style="dotStyle(m)">
<text class="bmp-dot-t">{{ m.no }}</text>
</view>
</view>
</view>
</template>
<script lang="uts" setup>
import { computed } from 'vue'
import { HrInjury, HrBodyPart, sizeBarWidth, bleedingColor, bleedingBarColor } from '@/utils/injury.uts'
/**
* 2D 人体图鉴
* 只负责「画」和「命中」,业务(弹窗、表单、JSON)全部交给页面,
* 这样同一套图能复用到批量检伤、伤情详情等页面。
*/
type HrBodyMapProps = {
/** 当前视野:front 正面 / back 背面 */
side : string
/** 该视野的部位热区表(来自 utils/injury.uts 的 partsOf */
parts : HrBodyPart[]
/** 该视野要显示的标记(页面先按 side 过滤好再传进来) */
marks : HrInjury[]
}
const props = withDefaults(defineProps<HrBodyMapProps>(), {
side : 'front',
parts : () : HrBodyPart[] => [] as HrBodyPart[],
marks : () : HrInjury[] => [] as HrInjury[]
})
const emits = defineEmits<{
/** 点击部位热区 */
(e : 'part-tap', partId : string) : void
/** 点击已有标记 */
(e : 'mark-tap', id : string) : void
}>()
/* ---------- 热区 ---------- */
function hitStyle(p : HrBodyPart) : string {
return 'left:' + p.x + '%;top:' + p.y + '%;width:' + p.w + '%;height:' + p.h + '%;'
}
/** 该部位是否已有标记(用于把热区点亮,一眼看出伤在哪) */
function hasMark(partId : string) : boolean {
for (let i = 0; i < props.marks.length; i++) {
if (props.marks[i].partId == partId) { return true }
}
return false
}
function hitClass(p : HrBodyPart) : string {
return hasMark(p.id) ? 'bmp-hit bmp-hit-on' : 'bmp-hit'
}
function onHit(partId : string) : void {
emits('part-tap', partId)
}
/* ---------- 标记 ---------- */
/** 标记整体位置:靠负 margin 居中(不用 transform,把 transform 留给旋转) */
function markStyle(m : HrInjury) : string {
return 'left:' + m.x + '%;top:' + m.y + '%;'
}
function dotStyle(m : HrInjury) : string {
return 'background-color:' + bleedingColor(m.bleeding) + ';'
}
/**
* 伤口长轴条:长度按尺寸、角度按 rot。
* left:50% + 负 margin 做水平居中,避免和 rotate 抢 transform。
*/
function barStyle(m : HrInjury) : string {
const w = sizeBarWidth(m.size)
const half = Math.round(w / 2)
return 'width:' + w + 'rpx;margin-left:-' + half + 'rpx;left:50%;' +
'background-color:' + bleedingBarColor(m.bleeding) + ';' +
'transform:rotate(' + m.rot + 'deg);'
}
function onMark(id : string) : void {
emits('mark-tap', id)
}
/* ---------- 导出给外部复用的换算(页面里的旋转预览用同一套) ---------- */
const sideLabel = computed(() : string => {
return props.side == 'back' ? '背面' : '正面'
})
</script>
<style>
/* 背面识别线:躯干中轴 */
.bmp-spine {
position: absolute;
left: 94rpx;
top: 78rpx;
width: 4rpx;
height: 108rpx;
border-radius: 2rpx;
background-color: #E2E8F0;
}
/* 部位热区:平时只用极浅虚线提示「这里可点」,避免糊住人体图 */
.bmp-hit {
position: absolute;
border-radius: 10rpx;
border: 2rpx dashed #EDF0F4;
}
/* 已有标记的部位点亮,形成「伤情分布」 */
.bmp-hit-on {
border: 2rpx solid #BFDBFE;
background-color: #EFF6FF;
}
/* 标记容器:绝对定位 + 负 margin 居中 */
.bmp-mark {
position: absolute;
width: 76rpx;
height: 76rpx;
margin-left: -38rpx;
margin-top: -38rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
/* 伤口长轴条:竖直居中于容器((76-24)/2 = 26rpx */
.bmp-bar {
position: absolute;
top: 26rpx;
height: 24rpx;
border-radius: 12rpx;
opacity: 0.8;
}
/* 编号圆点:不参与旋转 */
.bmp-dot {
width: 44rpx;
height: 44rpx;
border-radius: 22rpx;
border: 3rpx solid #FFFFFF;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.bmp-dot-t {
font-size: 23rpx;
font-weight: bold;
color: #FFFFFF;
}
</style>