2025-07-31 14:32:14 +08:00
|
|
|
<script setup>
|
|
|
|
|
import { ref, onMounted } from "vue";
|
|
|
|
|
import { getElementRect, calcRing } from "@/util";
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
2025-08-04 16:28:34 +08:00
|
|
|
id: {
|
|
|
|
|
type: Number,
|
|
|
|
|
default: 0,
|
|
|
|
|
},
|
|
|
|
|
src: {
|
|
|
|
|
type: String,
|
|
|
|
|
default: "",
|
|
|
|
|
},
|
2025-07-31 14:32:14 +08:00
|
|
|
arrows: {
|
|
|
|
|
type: Array,
|
|
|
|
|
default: () => [],
|
|
|
|
|
},
|
|
|
|
|
onChange: {
|
|
|
|
|
type: Function,
|
2025-08-05 11:51:09 +08:00
|
|
|
default: null,
|
2025-07-31 14:32:14 +08:00
|
|
|
},
|
2025-10-11 09:06:56 +08:00
|
|
|
editMode: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: true,
|
|
|
|
|
},
|
2025-07-31 14:32:14 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const rect = ref({});
|
|
|
|
|
const arrow = ref(null);
|
|
|
|
|
const isDragging = ref(false);
|
|
|
|
|
const dragStartPos = ref({ x: 0, y: 0 });
|
2025-08-07 18:13:14 +08:00
|
|
|
const capsuleHeight = ref(0);
|
2025-08-09 12:19:39 +08:00
|
|
|
const scale = ref(1);
|
|
|
|
|
let lastMoveTime = 0;
|
2025-07-31 14:32:14 +08:00
|
|
|
|
|
|
|
|
// 点击靶纸创建新的点
|
|
|
|
|
const onClick = async (e) => {
|
2025-08-09 12:19:39 +08:00
|
|
|
if (
|
|
|
|
|
arrow.value !== null ||
|
|
|
|
|
!props.onChange ||
|
|
|
|
|
Date.now() - lastMoveTime < 300
|
|
|
|
|
) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-08-19 16:48:33 +08:00
|
|
|
if (props.id === 7 || props.id === 9) {
|
|
|
|
|
scale.value = 1.5;
|
|
|
|
|
}
|
2025-07-31 14:32:14 +08:00
|
|
|
const newArrow = {
|
2025-08-19 16:48:33 +08:00
|
|
|
x: (e.detail.x - 6) * scale.value,
|
|
|
|
|
y: (e.detail.y - rect.value.top - capsuleHeight.value - 6) * scale.value,
|
2025-08-09 12:19:39 +08:00
|
|
|
};
|
|
|
|
|
|
2025-08-07 18:13:14 +08:00
|
|
|
const side = rect.value.width;
|
|
|
|
|
newArrow.ring = calcRing(
|
|
|
|
|
props.id,
|
2025-08-09 12:19:39 +08:00
|
|
|
newArrow.x / scale.value - rect.value.width * 0.05,
|
|
|
|
|
newArrow.y / scale.value - rect.value.width * 0.05,
|
2025-08-07 18:13:14 +08:00
|
|
|
rect.value.width * 0.9
|
|
|
|
|
);
|
2025-08-06 10:56:57 +08:00
|
|
|
arrow.value = {
|
|
|
|
|
...newArrow,
|
2025-08-06 17:34:38 +08:00
|
|
|
x: newArrow.x / side,
|
|
|
|
|
y: newArrow.y / side,
|
2025-08-06 10:56:57 +08:00
|
|
|
};
|
2025-07-31 14:32:14 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 确认添加箭矢
|
|
|
|
|
const confirmAdd = () => {
|
2025-08-09 12:19:39 +08:00
|
|
|
if (props.onChange) {
|
|
|
|
|
props.onChange({
|
|
|
|
|
x: arrow.value.x / scale.value,
|
|
|
|
|
y: arrow.value.y / scale.value,
|
|
|
|
|
ring: arrow.value.ring || "M",
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-07-31 14:32:14 +08:00
|
|
|
arrow.value = null;
|
2025-08-19 16:48:33 +08:00
|
|
|
scale.value = 1;
|
2025-07-31 14:32:14 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 删除箭矢
|
|
|
|
|
const deleteArrow = () => {
|
|
|
|
|
arrow.value = null;
|
2025-08-19 16:48:33 +08:00
|
|
|
scale.value = 1;
|
2025-07-31 14:32:14 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 开始拖拽 - 同样修复坐标获取
|
2025-08-09 12:19:39 +08:00
|
|
|
const startDrag = async (e) => {
|
2025-07-31 14:32:14 +08:00
|
|
|
if (!e.touches[0]) return;
|
|
|
|
|
isDragging.value = true;
|
|
|
|
|
dragStartPos.value = {
|
|
|
|
|
x: e.touches[0].clientX,
|
|
|
|
|
y: e.touches[0].clientY,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 拖拽移动 - 同样修复坐标获取
|
|
|
|
|
const onDrag = async (e) => {
|
2025-08-09 12:19:39 +08:00
|
|
|
lastMoveTime = Date.now();
|
2025-07-31 14:32:14 +08:00
|
|
|
if (!isDragging.value || !e.touches[0] || !arrow.value) return;
|
|
|
|
|
let clientX = e.touches[0].clientX;
|
|
|
|
|
let clientY = e.touches[0].clientY;
|
|
|
|
|
|
|
|
|
|
// 计算移动距离
|
|
|
|
|
const deltaX = clientX - dragStartPos.value.x;
|
|
|
|
|
const deltaY = clientY - dragStartPos.value.y;
|
2025-08-07 18:13:14 +08:00
|
|
|
const side = rect.value.width;
|
2025-07-31 14:32:14 +08:00
|
|
|
// 更新坐标
|
2025-08-09 12:19:39 +08:00
|
|
|
arrow.value.x = Math.max(
|
|
|
|
|
0,
|
|
|
|
|
Math.min(side * scale.value, arrow.value.x * side + deltaX)
|
|
|
|
|
);
|
|
|
|
|
arrow.value.y = Math.max(
|
|
|
|
|
0,
|
|
|
|
|
Math.min(side * scale.value, arrow.value.y * side + deltaY)
|
|
|
|
|
);
|
2025-08-04 16:28:34 +08:00
|
|
|
arrow.value.ring = calcRing(
|
|
|
|
|
props.id,
|
2025-08-09 12:19:39 +08:00
|
|
|
arrow.value.x / scale.value - rect.value.width * 0.05,
|
|
|
|
|
arrow.value.y / scale.value - rect.value.width * 0.05,
|
2025-08-07 18:13:14 +08:00
|
|
|
rect.value.width * 0.9
|
2025-08-04 16:28:34 +08:00
|
|
|
);
|
2025-08-09 12:19:39 +08:00
|
|
|
|
2025-08-06 17:34:38 +08:00
|
|
|
arrow.value.x = arrow.value.x / side;
|
|
|
|
|
arrow.value.y = arrow.value.y / side;
|
2025-07-31 14:32:14 +08:00
|
|
|
|
|
|
|
|
// 更新拖拽起始位置
|
|
|
|
|
dragStartPos.value = { x: clientX, y: clientY };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 结束拖拽
|
2025-08-09 12:19:39 +08:00
|
|
|
const endDrag = (e) => {
|
2025-07-31 14:32:14 +08:00
|
|
|
isDragging.value = false;
|
|
|
|
|
};
|
2025-08-07 18:13:14 +08:00
|
|
|
|
2025-08-19 16:48:33 +08:00
|
|
|
const getNewPos = () => {
|
|
|
|
|
if (props.id === 7 || props.id === 9) {
|
|
|
|
|
if (arrow.value.y > 1.4)
|
|
|
|
|
return { left: "-12px", bottom: "calc(50% - 12px)" };
|
|
|
|
|
} else {
|
|
|
|
|
if (arrow.value.y > 0.88) {
|
|
|
|
|
return { left: "-12px", bottom: "calc(50% - 12px)" };
|
|
|
|
|
}
|
2025-08-09 12:19:39 +08:00
|
|
|
}
|
2025-08-19 16:48:33 +08:00
|
|
|
return { left: "calc(50% - 12px)", bottom: "-12px" };
|
2025-08-07 18:13:14 +08:00
|
|
|
};
|
|
|
|
|
|
2025-07-31 14:32:14 +08:00
|
|
|
onMounted(async () => {
|
2025-08-07 18:13:14 +08:00
|
|
|
const menuBtnInfo = uni.getMenuButtonBoundingClientRect();
|
|
|
|
|
capsuleHeight.value = menuBtnInfo.top - 9;
|
|
|
|
|
const result = await getElementRect(".container");
|
2025-07-31 14:32:14 +08:00
|
|
|
rect.value = result;
|
|
|
|
|
});
|
|
|
|
|
</script>
|
2025-07-30 17:38:48 +08:00
|
|
|
|
|
|
|
|
<template>
|
2025-08-07 18:13:14 +08:00
|
|
|
<view
|
2025-10-11 09:06:56 +08:00
|
|
|
:style="{ overflowY: editMode ? 'auto' : 'hidden' }"
|
2025-08-07 18:13:14 +08:00
|
|
|
class="container"
|
2025-08-09 12:19:39 +08:00
|
|
|
@tap="onClick"
|
2025-08-07 18:13:14 +08:00
|
|
|
@touchmove="onDrag"
|
|
|
|
|
@touchend="endDrag"
|
|
|
|
|
>
|
2025-08-19 16:48:33 +08:00
|
|
|
<movable-area
|
|
|
|
|
class="move-area"
|
|
|
|
|
:style="{
|
2025-08-20 13:52:29 +08:00
|
|
|
width: scale * 100 + 'vw',
|
|
|
|
|
height: scale * 100 + 'vw',
|
|
|
|
|
transform: `translate(${(100 - scale * 100) / 2}vw,${
|
|
|
|
|
(100 - scale * 100) / 2
|
|
|
|
|
}vw) translateY(${scale > 1 ? 16.7 : 0}%)`,
|
2025-08-19 16:48:33 +08:00
|
|
|
}"
|
|
|
|
|
>
|
|
|
|
|
<image :src="src" mode="widthFix" />
|
|
|
|
|
<view
|
|
|
|
|
v-for="(arrow, index) in arrows"
|
|
|
|
|
:key="index"
|
|
|
|
|
class="arrow-point"
|
|
|
|
|
:style="{
|
|
|
|
|
left: (arrow.x !== undefined ? arrow.x : 0) * 100 + '%',
|
|
|
|
|
top: (arrow.y !== undefined ? arrow.y : 0) * 100 + '%',
|
|
|
|
|
}"
|
|
|
|
|
>
|
|
|
|
|
<view
|
|
|
|
|
v-if="arrow.x !== undefined && arrow.y !== undefined"
|
|
|
|
|
class="point"
|
|
|
|
|
:style="{
|
|
|
|
|
transform: props.id === 7 || props.id === 9 ? 'scale(0.7)' : '',
|
|
|
|
|
}"
|
|
|
|
|
>
|
|
|
|
|
<text>{{ index + 1 }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
2025-08-07 18:13:14 +08:00
|
|
|
<movable-view
|
2025-08-19 16:48:33 +08:00
|
|
|
v-if="arrow"
|
|
|
|
|
class="arrow-point"
|
2025-08-07 18:13:14 +08:00
|
|
|
direction="all"
|
|
|
|
|
:animation="false"
|
|
|
|
|
:out-of-bounds="true"
|
2025-08-20 13:52:29 +08:00
|
|
|
:x="arrow ? rect.width * arrow.x : 0"
|
|
|
|
|
:y="arrow ? rect.width * arrow.y : 0"
|
2025-08-07 18:13:14 +08:00
|
|
|
>
|
2025-08-20 13:52:29 +08:00
|
|
|
<view class="point"> </view>
|
|
|
|
|
<view v-if="arrow" class="edit-buttons" @touchstart.stop>
|
2025-08-19 16:48:33 +08:00
|
|
|
<view class="edit-btn-text">
|
|
|
|
|
<text>{{ arrow.ring === 0 ? "M" : arrow.ring }}</text>
|
|
|
|
|
<text
|
|
|
|
|
v-if="arrow.ring > 0"
|
|
|
|
|
:style="{
|
|
|
|
|
fontSize: '16px',
|
|
|
|
|
marginLeft: '2px',
|
|
|
|
|
}"
|
|
|
|
|
>环</text
|
|
|
|
|
>
|
|
|
|
|
</view>
|
2025-08-06 17:34:38 +08:00
|
|
|
<view
|
2025-08-19 16:48:33 +08:00
|
|
|
class="edit-btn confirm-btn"
|
|
|
|
|
@touchstart.stop="confirmAdd"
|
|
|
|
|
:style="{ ...getNewPos() }"
|
2025-08-06 17:34:38 +08:00
|
|
|
>
|
2025-08-19 16:48:33 +08:00
|
|
|
<image src="../static/arrow-edit-save.png" mode="widthFix" />
|
|
|
|
|
</view>
|
|
|
|
|
<view class="edit-btn delete-btn" @touchstart.stop="deleteArrow">
|
|
|
|
|
<image src="../static/arrow-edit-delete.png" mode="widthFix" />
|
2025-08-06 17:34:38 +08:00
|
|
|
</view>
|
2025-08-19 16:48:33 +08:00
|
|
|
<view class="edit-btn drag-btn" @touchstart.stop="startDrag($event)">
|
|
|
|
|
<image src="../static/arrow-edit-move.png" mode="widthFix" />
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
2025-08-06 17:34:38 +08:00
|
|
|
</movable-view>
|
2025-08-07 18:13:14 +08:00
|
|
|
</movable-area>
|
|
|
|
|
</view>
|
2025-07-30 17:38:48 +08:00
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
2025-08-06 17:34:38 +08:00
|
|
|
.container {
|
2025-08-07 18:13:14 +08:00
|
|
|
width: 100vw;
|
|
|
|
|
height: 100vw;
|
2025-08-19 16:48:33 +08:00
|
|
|
overflow-x: hidden;
|
2025-08-07 18:13:14 +08:00
|
|
|
}
|
2025-08-20 13:52:29 +08:00
|
|
|
.container::-webkit-scrollbar {
|
|
|
|
|
width: 0;
|
|
|
|
|
height: 0;
|
|
|
|
|
color: transparent;
|
|
|
|
|
}
|
2025-08-07 18:13:14 +08:00
|
|
|
|
|
|
|
|
.move-area {
|
2025-08-06 17:34:38 +08:00
|
|
|
width: 100%;
|
2025-08-07 18:13:14 +08:00
|
|
|
height: 100%;
|
2025-08-19 16:48:33 +08:00
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.move-area > image {
|
|
|
|
|
width: 90%;
|
|
|
|
|
height: 90%;
|
|
|
|
|
margin: 5%;
|
2025-07-30 17:38:48 +08:00
|
|
|
}
|
2025-07-31 14:32:14 +08:00
|
|
|
|
2025-08-07 18:13:14 +08:00
|
|
|
.move-view {
|
|
|
|
|
width: 90vw;
|
|
|
|
|
height: 90vw;
|
2025-08-09 12:19:39 +08:00
|
|
|
padding: 5vw;
|
2025-08-07 18:13:14 +08:00
|
|
|
position: relative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.move-view > image {
|
2025-07-30 17:38:48 +08:00
|
|
|
width: 100%;
|
2025-07-31 14:32:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.arrow-point {
|
|
|
|
|
position: absolute;
|
2025-08-09 12:19:39 +08:00
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
2025-07-31 14:32:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.point {
|
2025-08-09 12:19:39 +08:00
|
|
|
min-width: 12px;
|
|
|
|
|
min-height: 12px;
|
2025-07-31 14:32:14 +08:00
|
|
|
border-radius: 50%;
|
|
|
|
|
border: 1px solid #fff;
|
|
|
|
|
color: #fff;
|
|
|
|
|
font-size: 8px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
line-height: 10px;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
background-color: #ff4444;
|
|
|
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
2025-08-09 12:19:39 +08:00
|
|
|
transition: all 0.1s linear;
|
2025-08-19 16:48:33 +08:00
|
|
|
position: relative;
|
2025-07-31 14:32:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.point > text {
|
|
|
|
|
transform: scaleX(0.7);
|
|
|
|
|
display: block;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.edit-buttons {
|
|
|
|
|
position: absolute;
|
2025-08-09 12:19:39 +08:00
|
|
|
top: calc(50% - 44px);
|
|
|
|
|
left: calc(50% - 44px);
|
2025-07-31 14:32:14 +08:00
|
|
|
background: #18ff6899;
|
|
|
|
|
width: 88px;
|
|
|
|
|
height: 88px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: flex-end;
|
2025-08-09 12:19:39 +08:00
|
|
|
transition: all 0.1s linear;
|
2025-07-31 14:32:14 +08:00
|
|
|
}
|
|
|
|
|
|
2025-08-06 14:58:53 +08:00
|
|
|
.edit-btn-text {
|
2025-07-31 14:32:14 +08:00
|
|
|
width: 100%;
|
2025-08-06 14:58:53 +08:00
|
|
|
display: flex;
|
2025-08-19 16:48:33 +08:00
|
|
|
justify-content: center;
|
|
|
|
|
/* margin-left: 10px; */
|
2025-08-06 14:58:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.edit-btn-text > text {
|
2025-07-31 14:32:14 +08:00
|
|
|
line-height: 50px;
|
2025-08-01 09:20:10 +08:00
|
|
|
font-size: 24px;
|
2025-07-31 14:32:14 +08:00
|
|
|
font-weight: bold;
|
|
|
|
|
color: #fff;
|
2025-08-07 09:21:30 +08:00
|
|
|
text-align: center;
|
2025-07-31 14:32:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.edit-btn {
|
|
|
|
|
width: 24px;
|
|
|
|
|
height: 24px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
position: absolute;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.edit-btn > image {
|
|
|
|
|
width: 24px;
|
|
|
|
|
height: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.confirm-btn {
|
2025-08-19 16:48:33 +08:00
|
|
|
transition: all 0.3s ease;
|
2025-07-31 14:32:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.delete-btn {
|
2025-08-06 14:58:53 +08:00
|
|
|
left: calc(50% - 12px);
|
|
|
|
|
top: -12px;
|
2025-07-31 14:32:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.drag-btn {
|
2025-08-06 14:58:53 +08:00
|
|
|
right: -12px;
|
|
|
|
|
bottom: -12px;
|
2025-07-30 17:38:48 +08:00
|
|
|
}
|
|
|
|
|
</style>
|