Files
shoot-miniprograms/src/components/BowTargetEdit.vue
2026-01-12 15:03:20 +08:00

437 lines
9.9 KiB
Vue
Raw 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.

<script setup>
import { ref, onMounted, onBeforeUnmount } from "vue";
import { getElementRect, calcRing, capsuleHeight } from "@/util";
const props = defineProps({
id: {
type: Number,
default: 0,
},
src: {
type: String,
default: "",
},
arrows: {
type: Array,
default: () => [],
},
onChange: {
type: Function,
default: null,
},
});
const rect = ref({});
const arrow = ref(null);
const isDragging = ref(false);
const dragStartPos = ref({ x: 0, y: 0 });
const scale = ref(1);
const scrollTop = ref(0);
const selected = ref(null);
let lastMoveTime = 0;
// 点击靶纸创建新的点
const onClick = async (e) => {
if (
arrow.value !== null ||
!props.onChange ||
Date.now() - lastMoveTime < 300
) {
return;
}
if (props.id === 7 || props.id === 9) {
if (
e.detail.x < rect.value.width * 0.2 ||
e.detail.x > rect.value.width * 0.8
)
return;
// 放大并通过滚动将点击位置置于视窗中心
scale.value = 1.4;
const viewportH = rect.value.width; // 容器高度等于宽度100vw
const contentH = scale.value * rect.value.width; // 内容高度
const clickYInContainer = e.detail.y - rect.value.top;
let target = clickYInContainer * scale.value - viewportH / 2;
target = Math.max(0, Math.min(contentH - viewportH, target));
setTimeout(() => {
scrollTop.value = target > 180 ? target + 10 : target;
}, 200);
}
const newArrow = {
x: (e.detail.x - 6) * scale.value,
y: (e.detail.y - rect.value.top - capsuleHeight - 6) * scale.value,
};
const side = rect.value.width;
newArrow.ring = calcRing(
props.id,
newArrow.x / scale.value - side * 0.05,
newArrow.y / scale.value - side * 0.05,
side * 0.9
);
arrow.value = {
...newArrow,
x: newArrow.x / side,
y: newArrow.y / side,
};
};
// 确认添加箭矢
const confirmAdd = () => {
if (props.onChange) {
props.onChange({
x: arrow.value.x / scale.value,
y: arrow.value.y / scale.value,
ring: arrow.value.ring || "M",
});
}
arrow.value = null;
scale.value = 1;
scrollTop.value = 0;
};
// 删除箭矢
const deleteArrow = () => {
arrow.value = null;
scale.value = 1;
scrollTop.value = 0;
};
// 开始拖拽 - 同样修复坐标获取
const startDrag = async (e) => {
if (!e.touches[0]) return;
isDragging.value = true;
dragStartPos.value = {
x: e.touches[0].clientX,
y: e.touches[0].clientY,
};
};
// 拖拽移动 - 同样修复坐标获取
const onDrag = async (e) => {
lastMoveTime = Date.now();
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;
const side = rect.value.width;
// 更新坐标
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)
);
arrow.value.ring = calcRing(
props.id,
arrow.value.x / scale.value - side * 0.05,
arrow.value.y / scale.value - side * 0.05,
side * 0.9
);
arrow.value.x = arrow.value.x / side;
arrow.value.y = arrow.value.y / side;
// 更新拖拽起始位置
dragStartPos.value = { x: clientX, y: clientY };
};
// 结束拖拽
const endDrag = (e) => {
isDragging.value = false;
};
const getNewPos = () => {
if (props.id === 7 || props.id === 9) {
if (arrow.value.y >= 1.33)
return { left: "-12px", bottom: "calc(50% - 12px)" };
} else {
if (arrow.value.y > 0.88) {
if (arrow.value.x < 0.05) {
return { left: "calc(100% - 12px)", bottom: "calc(100% - 12px)" };
}
return { left: "-12px", bottom: "calc(50% - 12px)" };
}
}
return { left: "calc(50% - 12px)", bottom: "-12px" };
};
const setEditArrow = (data) => {
selected.value = data;
// if (data === null) {
// arrow.value = null;
// scale.value = 1;
// scrollTop.value = 0;
// return;
// }
// if (props.id === 7 || props.id === 9) {
// scale.value = 1.4;
// const viewportH = rect.value.width; // 容器高度等于宽度100vw
// const contentH = scale.value * rect.value.width; // 内容高度
// const clickYInContainer = contentH * data.y - rect.value.top;
// let target = clickYInContainer * scale.value - viewportH / 2;
// target = Math.max(0, Math.min(contentH - viewportH, target));
// setTimeout(() => {
// scrollTop.value = target > 180 ? target + 10 : target;
// }, 200);
// }
// arrow.value = {
// ...data,
// x: data.x * scale.value,
// y: data.y * scale.value,
// };
};
onMounted(async () => {
const result = await getElementRect(".container");
rect.value = result;
uni.$on("set-edit-arrow", setEditArrow);
});
onBeforeUnmount(() => {
uni.$off("set-edit-arrow", setEditArrow);
});
</script>
<template>
<scroll-view
:scroll-y="scale > 1"
scroll-with-animation
:scroll-top="scrollTop"
:show-scrollbar="false"
:enhanced="true"
class="container"
@tap="onClick"
@touchmove="onDrag"
@touchend="endDrag"
>
<movable-area
class="move-area"
:style="{
width: scale * 100 + 'vw',
height: scale * 100 + 'vw',
transform: `translateX(${(100 - scale * 100) / 2}vw)`,
}"
>
<image :src="src" mode="widthFix" />
<view
v-for="(arrow, index) in arrows"
:key="index"
:class="`arrow-point ${
selected !== null && index === selected ? 'selected-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"
>
<text>{{ index + 1 }}</text>
</view>
</view>
<movable-view
v-if="arrow"
class="arrow-point"
direction="all"
:animation="false"
:out-of-bounds="true"
:x="arrow ? rect.width * arrow.x : 0"
:y="arrow ? rect.width * arrow.y : 0"
>
<view
class="point"
:style="{ minWidth: 10 * scale + 'px', minHeight: 10 * scale + 'px' }"
>
<view v-if="arrow" class="edit-buttons" @touchstart.stop>
<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>
<view
class="edit-btn confirm-btn"
@touchstart.stop="confirmAdd"
:style="{ ...getNewPos() }"
>
<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" />
</view>
<view
class="edit-btn drag-btn"
@touchstart.stop="startDrag($event)"
>
<image src="../static/arrow-edit-move.png" mode="widthFix" />
</view>
</view>
</view>
</movable-view>
<!-- <view class="test-view"></view> -->
</movable-area>
</scroll-view>
</template>
<style scoped>
.container {
width: 100vw;
height: 100vw;
overflow-x: hidden;
}
.container::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
}
.move-area {
width: 100%;
height: 100%;
transition: all 0.3s ease;
}
.move-area > image {
width: 90%;
height: 90%;
margin: 5%;
}
.move-view {
width: 90vw;
height: 90vw;
padding: 5vw;
position: relative;
}
.move-view > image {
width: 100%;
}
.arrow-point {
position: absolute;
}
.point {
min-width: 10px;
min-height: 10px;
border-radius: 50%;
border: 1px solid #fff;
color: #fff;
text-align: center;
line-height: 10px;
box-sizing: border-box;
background-color: #00bf04;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
transition: all 0.1s linear;
position: relative;
transform: translate(-50%, -50%);
}
.point > text {
display: block;
font-size: 16rpx;
line-height: 10px;
position: absolute;
top: 50%;
left: 50%;
font-family: "DINCondensed", "PingFang SC", "Helvetica Neue", Arial,
sans-serif;
transform: translate(-50%, -50%);
margin-top: 1px;
}
.edit-buttons {
position: absolute;
top: calc(50% - 44px);
left: calc(50% - 44px);
background: #18ff6899;
width: 88px;
height: 88px;
display: flex;
align-items: flex-end;
transition: all 0.1s linear;
}
.edit-btn-text {
width: 100%;
display: flex;
justify-content: center;
}
.edit-btn-text > text {
line-height: 50px;
font-size: 24px;
font-weight: bold;
color: #fff;
text-align: center;
}
.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 {
transition: all 0.3s ease;
}
.delete-btn {
left: calc(50% - 12px);
top: -12px;
}
.drag-btn {
right: -12px;
bottom: -12px;
}
.test-view {
position: absolute;
top: 29px;
left: 138px;
width: 115px;
height: 115px;
background-color: #ff000055;
}
.selected-arrow-point .point {
background: linear-gradient(180deg, #ffdaa6 0%, #e9a333 100%) !important;
box-shadow: 0rpx 2rpx 4rpx 0rpx rgba(0, 0, 0, 0.18);
animation: duang 0.35s ease-out;
}
@keyframes duang {
0% {
transform: translate(-50%, -50%) scale(0.7);
}
45% {
transform: translate(-50%, -50%) scale(1.4);
}
70% {
transform: translate(-50%, -50%) scale(0.9);
}
100% {
transform: translate(-50%, -50%) scale(1);
}
}
</style>