|
|
@@ -0,0 +1,258 @@
|
|
|
+<template>
|
|
|
+ <div class="overflowWrapper" :style="{ width: props.domWidth + 'px', height: domHeight + 'px' }">
|
|
|
+ <div
|
|
|
+ class="scaleWrapper"
|
|
|
+ :style="{
|
|
|
+ scale: scale,
|
|
|
+ width: props.canvasSize.width + 'px',
|
|
|
+ height: props.canvasSize.height + 'px',
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <v-stage
|
|
|
+ :config="configKonva"
|
|
|
+ @mouse-down="handleStageMouseDown"
|
|
|
+ @mouse-move="handleStageMouseMove"
|
|
|
+ ref="stageRef"
|
|
|
+ >
|
|
|
+ <v-layer>
|
|
|
+ <FenceItem
|
|
|
+ :fenceGroups="fenceGroups"
|
|
|
+ :draggable="!drawingGroupId"
|
|
|
+ @select-group="handleSelectGroup"
|
|
|
+ :is-edit="isEdit"
|
|
|
+ />
|
|
|
+ </v-layer>
|
|
|
+ </v-stage>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+<script lang="ts" setup>
|
|
|
+ import Konva from 'konva';
|
|
|
+ import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
|
|
+ import FenceItem from './FenceItem.vue';
|
|
|
+ import { createCircleConfigItem, createGroupConfig } from './utils';
|
|
|
+ import { FenceGroup } from './types';
|
|
|
+ import { ElMessage } from 'element-plus';
|
|
|
+ import { GROUP_NAME } from './constants';
|
|
|
+
|
|
|
+ const props = defineProps<{
|
|
|
+ /** 电子围栏的坐标 */
|
|
|
+ linePoints: [number, number][][];
|
|
|
+ /** 画布的大小 */
|
|
|
+ canvasSize: { width: number; height: number };
|
|
|
+ /** dom的真实尺寸 */
|
|
|
+ domWidth: number;
|
|
|
+ }>();
|
|
|
+
|
|
|
+ const scale = computed(() => {
|
|
|
+ return props.domWidth / props.canvasSize.width;
|
|
|
+ });
|
|
|
+
|
|
|
+ const stageRef = ref();
|
|
|
+ const isEdit = ref(false);
|
|
|
+
|
|
|
+ const fenceGroups = ref<FenceGroup[]>([]);
|
|
|
+
|
|
|
+ /** 当前正在画的多边形的groupId */
|
|
|
+ const drawingGroupId = ref('');
|
|
|
+ /** 当前选中的多边形groupId,点击、拖拽、画线都会给它赋值 */
|
|
|
+ const currentGroupId = ref('');
|
|
|
+
|
|
|
+ watch(
|
|
|
+ () => props.linePoints,
|
|
|
+ (newLinePoints) => {
|
|
|
+ const configs: FenceGroup[] =
|
|
|
+ newLinePoints.map((points) => {
|
|
|
+ const flattenedPoints = points.reduce((total, next) => {
|
|
|
+ return [...total, ...next];
|
|
|
+ }, [] as number[]);
|
|
|
+ return createGroupConfig(flattenedPoints, scale.value);
|
|
|
+ }) || [];
|
|
|
+ fenceGroups.value = configs;
|
|
|
+ },
|
|
|
+ {
|
|
|
+ immediate: true,
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ onMounted(() => {
|
|
|
+ /** 取消默认的右键 */
|
|
|
+ document.oncontextmenu = function () {
|
|
|
+ return false;
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
+ onUnmounted(() => {
|
|
|
+ /** 取消默认的右键 */
|
|
|
+ document.oncontextmenu = function () {
|
|
|
+ return true;
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
+ const configKonva = computed(() => {
|
|
|
+ return props.canvasSize;
|
|
|
+ });
|
|
|
+
|
|
|
+ const canvasRatio = computed(() => {
|
|
|
+ const size = props.canvasSize;
|
|
|
+ if (!size) return 1;
|
|
|
+ return size.height / size.width;
|
|
|
+ });
|
|
|
+
|
|
|
+ const domHeight = computed(() => {
|
|
|
+ return props.domWidth * canvasRatio.value;
|
|
|
+ });
|
|
|
+
|
|
|
+ const handleStageMouseDown = (e) => {
|
|
|
+ if (!isEdit.value) return;
|
|
|
+ /**
|
|
|
+ * parent存在,说明点击的不是stage
|
|
|
+ * !drawingGroupId,说明当前不处于绘制多边形中
|
|
|
+ * 这两种情况,都不能执行stage的点击事件,要执行点击对象的默认事件
|
|
|
+ */
|
|
|
+ if (e.target.parent && !drawingGroupId.value) return;
|
|
|
+ const stage = e.currentTarget as Konva.Stage;
|
|
|
+ // 获取当前鼠标相对舞台的位置
|
|
|
+ const mousePosition = stage.getPointerPosition();
|
|
|
+ if (!mousePosition?.x || !mousePosition?.y) return;
|
|
|
+ const point = [mousePosition.x, mousePosition.y] as [number, number];
|
|
|
+
|
|
|
+ /** 如果还没开始画线,那么增加第一个点 */
|
|
|
+ if (!drawingGroupId.value) {
|
|
|
+ const groupConfig = createGroupConfig(point, scale.value);
|
|
|
+ drawingGroupId.value = groupConfig.uid;
|
|
|
+ groupConfig._temp.points = point;
|
|
|
+ fenceGroups.value.push(groupConfig);
|
|
|
+ } else {
|
|
|
+ /** 右键点击,取消最后一个点 */
|
|
|
+ if (e.evt.button === 2) {
|
|
|
+ /** 否则就追加点 */
|
|
|
+ const groupConfig = fenceGroups.value.find((x) => x.uid === drawingGroupId.value);
|
|
|
+ if (!groupConfig) {
|
|
|
+ console.error('drawingGroupId无效', drawingGroupId.value);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if ((groupConfig._temp.points.length || 0) <= 4) {
|
|
|
+ ElMessage({
|
|
|
+ message: '顶点数必须大于2个!',
|
|
|
+ type: 'warning',
|
|
|
+ center: true,
|
|
|
+ duration: 1000,
|
|
|
+ });
|
|
|
+ groupConfig.lineConfig.points = [];
|
|
|
+ groupConfig.circleConfigs = [];
|
|
|
+ } else {
|
|
|
+ groupConfig.lineConfig.points = groupConfig._temp.points;
|
|
|
+ }
|
|
|
+ drawingGroupId.value = '';
|
|
|
+ groupConfig._temp.points = [];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ /** 否则就追加点 */
|
|
|
+ const groupConfig = fenceGroups.value.find((x) => x.uid === drawingGroupId.value);
|
|
|
+ if (!groupConfig) {
|
|
|
+ console.error('drawingGroupId无效', drawingGroupId.value);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const tempPoints = groupConfig._temp?.points || [];
|
|
|
+ const finalPoints = [...tempPoints, ...point];
|
|
|
+ groupConfig.lineConfig.points = finalPoints;
|
|
|
+ groupConfig._temp.points = finalPoints;
|
|
|
+
|
|
|
+ const circleConfig = createCircleConfigItem(
|
|
|
+ point,
|
|
|
+ groupConfig.circleConfigs.length,
|
|
|
+ scale.value,
|
|
|
+ );
|
|
|
+ groupConfig.circleConfigs.push(circleConfig);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleStageMouseMove = (e) => {
|
|
|
+ if (!isEdit.value) return;
|
|
|
+ const stage = e.currentTarget as Konva.Stage;
|
|
|
+ /** 获取当前鼠标的坐标 */
|
|
|
+ const mousePosition = stage.getPointerPosition();
|
|
|
+ if (!mousePosition?.x || !mousePosition?.y) return;
|
|
|
+ const newPoint = [mousePosition.x, mousePosition.y];
|
|
|
+ if (drawingGroupId.value) {
|
|
|
+ const groupConfig: FenceGroup | undefined = fenceGroups.value.find(
|
|
|
+ (x) => x.uid === drawingGroupId.value,
|
|
|
+ );
|
|
|
+ if (!groupConfig) {
|
|
|
+ console.error('drawingGroupId无效', drawingGroupId.value);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 如果正在画线,那么替换最后一个点 */
|
|
|
+ const initialPoints = groupConfig.lineConfig.points as number[];
|
|
|
+ if (groupConfig._temp.points.length > 0) {
|
|
|
+ groupConfig.lineConfig.points = [...groupConfig._temp.points, ...newPoint];
|
|
|
+ } else {
|
|
|
+ groupConfig._temp.points = initialPoints;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleSelectGroup = (groupId: string) => {
|
|
|
+ currentGroupId.value = groupId;
|
|
|
+ };
|
|
|
+
|
|
|
+ /** 清空所有元素 */
|
|
|
+ const clear = () => {
|
|
|
+ fenceGroups.value = [];
|
|
|
+ };
|
|
|
+
|
|
|
+ /** 删除当前选中的group项 */
|
|
|
+ const remove = () => {
|
|
|
+ fenceGroups.value = fenceGroups.value.filter((x) => x.uid !== currentGroupId.value);
|
|
|
+ };
|
|
|
+
|
|
|
+ /** 导出为json格式 */
|
|
|
+ const toObject = () => {
|
|
|
+ const stage = stageRef.value.getStage();
|
|
|
+ const fenceGroups = stage?.find('.' + GROUP_NAME);
|
|
|
+ const gropuPoints = fenceGroups?.map((item) => {
|
|
|
+ const groupX = item.x();
|
|
|
+ const groupY = item.y();
|
|
|
+
|
|
|
+ const line = (item as Konva.Group).findOne((x: any) => x.className === 'Line') as Konva.Line;
|
|
|
+ const points = line?.points();
|
|
|
+ const newPoints: number[][] = [];
|
|
|
+ /** 存到后端的时候,只给点的坐标信息,不会给group的位置信息,所以要将点的坐标加上group的位移,才是之后点的最终坐标 */
|
|
|
+ for (let i = 0; i < points.length; i += 2) {
|
|
|
+ newPoints.push([Math.floor(points[i] + groupX), Math.floor(points[i + 1] + groupY)]);
|
|
|
+ }
|
|
|
+ return newPoints;
|
|
|
+ });
|
|
|
+ return gropuPoints;
|
|
|
+ };
|
|
|
+
|
|
|
+ /** 退出编辑模式 */
|
|
|
+ const exitEditMode = () => {
|
|
|
+ currentGroupId.value = '';
|
|
|
+ isEdit.value = false;
|
|
|
+ };
|
|
|
+ /** 进入编辑模式 */
|
|
|
+ const setEditMode = () => {
|
|
|
+ isEdit.value = true;
|
|
|
+ };
|
|
|
+
|
|
|
+ defineExpose({
|
|
|
+ clear,
|
|
|
+ remove,
|
|
|
+ toObject,
|
|
|
+ exitEditMode,
|
|
|
+ setEditMode,
|
|
|
+ });
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+ .scaleWrapper {
|
|
|
+ transform-origin: left top;
|
|
|
+ }
|
|
|
+ .overflowWrapper {
|
|
|
+ overflow: hidden;
|
|
|
+ border: 1px solid #ccc;
|
|
|
+ }
|
|
|
+</style>
|