236 lines
5.3 KiB
Vue
236 lines
5.3 KiB
Vue
<template>
|
|
<div class="offset-tool">
|
|
<div class="input" v-show="showInput">
|
|
<my-input
|
|
v-model="left"
|
|
@input="onInput"
|
|
@change="onChange"
|
|
type="number"
|
|
before="X"
|
|
after="%"
|
|
:min="-100"
|
|
:max="100"
|
|
/>
|
|
<my-input
|
|
v-model="top"
|
|
@input="onInput"
|
|
@change="onChange"
|
|
type="number"
|
|
before="Y"
|
|
after="%"
|
|
:min="-100"
|
|
:max="100"
|
|
/>
|
|
</div>
|
|
<div
|
|
class="dish"
|
|
@mousedown="mousedown"
|
|
@touchstart="mousedown"
|
|
ref="dishRef"
|
|
v-show="showDish"
|
|
>
|
|
<img src="/src/assets/images/icon/xyz.png" />
|
|
<span class="ball" :style="ballStyle"></span>
|
|
<span class="tip x">X: {{ left }}%</span>
|
|
<span class="tip y">Y: {{ top }}%</span>
|
|
<span class="line x"></span>
|
|
<span class="line y"></span>
|
|
<span class="line z" :style="lineZStyle"></span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, defineProps, defineEmits, watch, computed } from "vue";
|
|
import MyInput from "./MyInput.vue";
|
|
const props = defineProps({
|
|
left: {
|
|
type: Number,
|
|
default: 0,
|
|
},
|
|
top: {
|
|
type: Number,
|
|
default: 0,
|
|
},
|
|
showInput: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
showDish: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
});
|
|
const emit = defineEmits(["change", "input"]);
|
|
// 工具的实际坐标 -100 ~ 100
|
|
const top = ref(Math.round(props.top));
|
|
const left = ref(Math.round(props.left));
|
|
|
|
// 原点的坐标 0 ~ 100
|
|
const ballStyle = computed(() => ({
|
|
top: 50 + Number(top.value) / 2 + "%",
|
|
left: 50 + Number(left.value) / 2 + "%",
|
|
}));
|
|
watch(
|
|
() => props.left,
|
|
(v) => (left.value = Math.round(v))
|
|
);
|
|
watch(
|
|
() => props.top,
|
|
(v) => (top.value = Math.round(v))
|
|
);
|
|
const dishRef = ref<HTMLDivElement>();
|
|
const mousedown = (e: MouseEvent | TouchEvent) => {
|
|
if (!dishRef.value) return;
|
|
const mousemove = (e: MouseEvent | TouchEvent) => {
|
|
if (!dishRef.value) return;
|
|
const rect = dishRef.value.getBoundingClientRect();
|
|
const X = e.clientX || (e as TouchEvent).touches[0].clientX;
|
|
const Y = e.clientY || (e as TouchEvent).touches[0].clientY;
|
|
var x = ((X - rect.left) / rect.width) * 100;
|
|
var y = ((Y - rect.top) / rect.height) * 100;
|
|
if (x < 0) x = 0;
|
|
if (x > 100) x = 100;
|
|
if (y < 0) y = 0;
|
|
if (y > 100) y = 100;
|
|
left.value = Math.round((x - 50) * 2);
|
|
top.value = Math.round((y - 50) * 2);
|
|
onInput();
|
|
};
|
|
mousemove(e);
|
|
const mouseup = () => {
|
|
onChange();
|
|
document.removeEventListener("mousemove", mousemove);
|
|
document.removeEventListener("touchmove", mousemove);
|
|
document.removeEventListener("mouseup", mouseup);
|
|
document.removeEventListener("touchend", mouseup);
|
|
};
|
|
document.addEventListener("mousemove", mousemove);
|
|
document.addEventListener("touchmove", mousemove);
|
|
document.addEventListener("mouseup", mouseup);
|
|
document.addEventListener("touchend", mouseup);
|
|
};
|
|
const onInput = () => {
|
|
emit("input", { left: left.value, top: top.value });
|
|
};
|
|
var changeTime: any = null;
|
|
const onChange = () => {
|
|
clearTimeout(changeTime);
|
|
changeTime = setTimeout(() => {
|
|
emit("change", {
|
|
left: left.value,
|
|
top: top.value,
|
|
});
|
|
}, 500);
|
|
};
|
|
const lineZStyle = computed(() => ({
|
|
"--rotateZ": calculateAngle(0, 0, left.value, top.value) + "deg",
|
|
width: calculateDistance(0, 0, left.value, top.value) / 2 + "%",
|
|
}));
|
|
// 计算角度
|
|
function calculateAngle(x1: number, y1: number, x2: number, y2: number) {
|
|
const deltaX = x2 - x1;
|
|
const deltaY = y1 - y2;
|
|
let angle = Math.atan2(deltaX, deltaY) * (180 / Math.PI) - 90;
|
|
return angle;
|
|
}
|
|
// 计算距离
|
|
function calculateDistance(x1: number, y1: number, x2: number, y2: number) {
|
|
const deltaX = x2 - x1;
|
|
const deltaY = y2 - y1;
|
|
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
return distance;
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="less">
|
|
.offset-tool {
|
|
position: relative;
|
|
> .input {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
> * {
|
|
flex: 1;
|
|
margin-right: 12px;
|
|
&:last-child {
|
|
margin-right: 0;
|
|
}
|
|
}
|
|
}
|
|
> .dish {
|
|
width: 135px;
|
|
height: 135px;
|
|
border: 1px solid #eaeaea;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
position: relative;
|
|
background-color: #f6f6f6;
|
|
margin-top: 24px;
|
|
> * {
|
|
position: absolute;
|
|
pointer-events: none;
|
|
user-select: none;
|
|
}
|
|
> img {
|
|
width: 15px;
|
|
height: 15px;
|
|
bottom: 4px;
|
|
right: 4px;
|
|
}
|
|
> .ball {
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
width: 10px;
|
|
height: 10px;
|
|
border: 1px solid #fff;
|
|
background-color: #333;
|
|
border-radius: 50%;
|
|
box-shadow: 0px 0.68px 1.7px 0px rgba(0, 0, 0, 0.26);
|
|
}
|
|
> .tip {
|
|
font-size: 10px;
|
|
color: #000;
|
|
line-height: 24px;
|
|
&.x {
|
|
top: 50%;
|
|
right: 0%;
|
|
transform: translate(100%, -50%);
|
|
padding-left: 6px;
|
|
}
|
|
&.y {
|
|
top: 0%;
|
|
left: 50%;
|
|
transform: translate(-50%, -100%);
|
|
}
|
|
}
|
|
> .line {
|
|
border-color: #d9d9d9;
|
|
border-style: dashed;
|
|
border-width: 0;
|
|
width: 0;
|
|
height: 0;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
&.x {
|
|
width: 100%;
|
|
border-top-width: 1px;
|
|
}
|
|
&.y {
|
|
height: 100%;
|
|
border-left-width: 1px;
|
|
}
|
|
&.z {
|
|
width: 50%;
|
|
border-top-width: 1px;
|
|
border-color: #454754;
|
|
transform: translate(0%, -50%) rotateZ(var(--rotateZ));
|
|
transform-origin: left center;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|