<!--
|
* @Author: ShawnPhang
|
* @Date: 2023-11-29 10:34:54
|
* @Description: 角度手柄
|
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
* @LastEditTime: 2023-11-29 19:24:14
|
-->
|
<template>
|
<div class="angle-input-box">
|
<input
|
ref="numInput"
|
v-model="num"
|
class="angle-input"
|
@focus="visiable = true"
|
@blur="visiable = false"
|
@input="inputChange"
|
/>
|
<div
|
v-show="visiable"
|
class="AngleHandle"
|
@mousedown="touch($event, true)"
|
@mouseup="touch($event, false)"
|
>
|
<div class="angle" @mouseup="turn" @mousemove="turn">
|
<div :style="`transform: rotate(${angleInDegrees}deg)`" class="line"></div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script lang="ts">
|
import { defineComponent, ref, watch, computed } from 'vue';
|
|
export default defineComponent({
|
props: ['modelValue'],
|
emits: ['change', 'update:modelValue'],
|
setup(props, { emit }) {
|
const num = ref(90);
|
const numInput = ref(null);
|
const angleInDegrees = computed(() => {
|
return num.value - 90;
|
});
|
let inProcess = false;
|
const visiable = ref(false);
|
|
const inputChange = (e: any) => {
|
emit('change', e);
|
};
|
watch(
|
() => num.value,
|
(v) => {
|
props.modelValue !== num.value && emit('update:modelValue', v);
|
emit('change');
|
}
|
);
|
watch(
|
() => props.modelValue,
|
(v) => {
|
num.value = v;
|
}
|
);
|
|
const turn = (e: any) => {
|
if (!inProcess) {
|
return;
|
}
|
const origin = { x: 27, y: 27 };
|
// 计算相对于原点的坐标差值
|
const deltaX = e.offsetX - origin.x;
|
const deltaY = e.offsetY - origin.y;
|
// 计算夹角(弧度)
|
const angleInRadians = Math.atan2(deltaY, deltaX);
|
// 将弧度转换为角度
|
const angleInDegrees = (angleInRadians * 180) / Math.PI;
|
num.value = Math.round(angleInDegrees + 90);
|
};
|
|
const touch = (e: any, isHandle: boolean) => {
|
e.preventDefault();
|
inProcess = isHandle;
|
};
|
return { inputChange, num, turn, touch, angleInDegrees, numInput, visiable };
|
},
|
});
|
</script>
|
|
<style lang="less">
|
.angle-input {
|
width: 38px;
|
margin-left: 5px;
|
padding: 0 0 0 4px;
|
border: 1px solid #e8eaec;
|
border-radius: 4px;
|
position: relative;
|
}
|
.angle-input-box {
|
position: relative;
|
}
|
.angle-input-box::after {
|
content: '°';
|
width: 5px;
|
height: 2px;
|
position: absolute;
|
right: 2px;
|
top: 0;
|
}
|
|
.AngleHandle {
|
position: absolute;
|
z-index: 2;
|
right: 2px;
|
margin-top: 3px;
|
background: #ffffff;
|
width: 60px;
|
height: 60px;
|
border-radius: 7px;
|
box-shadow: 0 0 2px rgb(0 0 0 / 60%);
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
.angle {
|
width: 54px;
|
height: 54px;
|
position: relative;
|
overflow: hidden;
|
background: #f1f2f4;
|
border-radius: 50%;
|
user-select: none;
|
cursor: pointer;
|
}
|
.line {
|
position: absolute;
|
top: 50%;
|
left: 50%;
|
width: 50%;
|
height: 1px;
|
background: #999999;
|
pointer-events: none;
|
transform-origin: left top;
|
}
|
.line::before {
|
position: absolute;
|
content: '';
|
left: -1px;
|
top: -1px;
|
width: 3px;
|
height: 3px;
|
border-radius: 50%;
|
background: #999999;
|
}
|
.line::after {
|
position: absolute;
|
content: '';
|
right: 0;
|
top: -2px;
|
width: 5px;
|
height: 5px;
|
border-radius: 50%;
|
background: #999999;
|
}
|
}
|
</style>
|