1
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user