2022-12-28 20:34:23 +08:00
|
|
|
<template>
|
|
|
|
<div :id="`box-${id}`" class="box">
|
|
|
|
<div :id="`box-main-${id}`" class="box-main" @click="click">
|
|
|
|
<slot></slot>
|
|
|
|
</div>
|
2023-01-06 16:21:17 +08:00
|
|
|
<div
|
|
|
|
v-if="dragable"
|
|
|
|
:id="`box-move-${id}`"
|
|
|
|
class="box-move"
|
|
|
|
:selected="moveSelected"
|
|
|
|
>
|
2024-11-06 15:29:33 +08:00
|
|
|
<drag-outlined :id="`box-drag-${id}`" class="box-drag" />
|
|
|
|
</div>
|
|
|
|
<div
|
|
|
|
v-if="resizable"
|
|
|
|
:id="`box-size-${id}`"
|
|
|
|
class="box-size"
|
|
|
|
:selected="moveSelected"
|
|
|
|
>
|
|
|
|
<ArrowsAltOutlined :id="`box-resize-${id}`" class="box-resize" />
|
2022-12-28 20:34:23 +08:00
|
|
|
</div>
|
|
|
|
<div
|
|
|
|
class="border border-vertical border-left"
|
|
|
|
:id="`border-left-${id}`"
|
|
|
|
:selected="moveSelected && resizable"
|
2023-01-06 16:21:17 +08:00
|
|
|
:selectable="resizable"
|
2022-12-28 20:34:23 +08:00
|
|
|
></div>
|
|
|
|
<div
|
|
|
|
class="border border-horizontal border-top"
|
|
|
|
:id="`border-top-${id}`"
|
|
|
|
:selected="moveSelected && resizable"
|
2023-01-06 16:21:17 +08:00
|
|
|
:selectable="resizable"
|
2022-12-28 20:34:23 +08:00
|
|
|
></div>
|
|
|
|
<div
|
|
|
|
class="border border-vertical border-right"
|
|
|
|
:id="`border-right-${id}`"
|
|
|
|
:selected="moveSelected && resizable"
|
2023-01-06 16:21:17 +08:00
|
|
|
:selectable="resizable"
|
2022-12-28 20:34:23 +08:00
|
|
|
></div>
|
|
|
|
<div
|
|
|
|
class="border border-horizontal border-bottom"
|
|
|
|
:id="`border-bottom-${id}`"
|
|
|
|
:selected="moveSelected && resizable"
|
2023-01-06 16:21:17 +08:00
|
|
|
:selectable="resizable"
|
2022-12-28 20:34:23 +08:00
|
|
|
></div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
2024-04-20 12:27:38 +08:00
|
|
|
import { onMounted, onUnmounted, onUpdated, ref, watch } from 'vue';
|
2024-11-06 15:29:33 +08:00
|
|
|
import { ArrowsAltOutlined, DragOutlined } from '@ant-design/icons-vue';
|
2022-12-28 20:34:23 +08:00
|
|
|
import { isMobile, useDrag, cancelGlobalDrag } from '../plugin/use';
|
2024-04-21 15:45:06 +08:00
|
|
|
import { has, requireUniqueSymbol } from '../plugin/utils';
|
2022-12-29 00:26:12 +08:00
|
|
|
import { sleep } from 'mutate-animate';
|
2022-12-28 20:34:23 +08:00
|
|
|
|
|
|
|
const props = defineProps<{
|
2023-01-06 16:21:17 +08:00
|
|
|
dragable?: boolean;
|
2022-12-28 20:34:23 +08:00
|
|
|
resizable?: boolean;
|
|
|
|
left?: number;
|
|
|
|
top?: number;
|
|
|
|
width?: number;
|
|
|
|
height?: number;
|
|
|
|
}>();
|
|
|
|
|
|
|
|
const emits = defineEmits<{
|
|
|
|
(e: 'update:left', data: number): void;
|
|
|
|
(e: 'update:top', data: number): void;
|
|
|
|
(e: 'update:width', data: number): void;
|
|
|
|
(e: 'update:height', data: number): void;
|
|
|
|
}>();
|
|
|
|
|
2024-04-21 15:45:06 +08:00
|
|
|
const id = requireUniqueSymbol().toFixed(0);
|
2022-12-28 20:34:23 +08:00
|
|
|
|
|
|
|
const moveSelected = ref(false);
|
|
|
|
let moveTimeout = 0;
|
|
|
|
let main: HTMLDivElement;
|
|
|
|
let leftB: HTMLDivElement;
|
|
|
|
let rightB: HTMLDivElement;
|
|
|
|
let topB: HTMLDivElement;
|
|
|
|
let bottomB: HTMLDivElement;
|
|
|
|
let drag: HTMLElement;
|
2024-11-06 15:29:33 +08:00
|
|
|
let size: HTMLElement;
|
2022-12-28 20:34:23 +08:00
|
|
|
|
2024-11-06 15:45:11 +08:00
|
|
|
const maxWidth = window.innerWidth;
|
|
|
|
const maxHeight = window.innerHeight;
|
|
|
|
|
|
|
|
const width = ref(isMobile ? maxWidth - 100 : maxHeight * 0.175);
|
|
|
|
const height = ref(isMobile ? 250 : maxHeight - 100);
|
2023-04-22 10:28:19 +08:00
|
|
|
const left = ref(isMobile ? 30 : 50);
|
|
|
|
const top = ref(isMobile ? 30 : 50);
|
2022-12-28 20:34:23 +08:00
|
|
|
|
|
|
|
watch(left, n => emits('update:left', n));
|
|
|
|
watch(top, n => emits('update:top', n));
|
|
|
|
watch(width, n => emits('update:width', n));
|
|
|
|
watch(height, n => emits('update:height', n));
|
|
|
|
|
|
|
|
async function click() {
|
|
|
|
moveSelected.value = true;
|
|
|
|
moveTimeout = window.setTimeout(() => {
|
|
|
|
moveSelected.value = false;
|
|
|
|
}, 4000);
|
|
|
|
}
|
|
|
|
|
|
|
|
let lastX = 0;
|
|
|
|
let lastY = 0;
|
|
|
|
|
2024-11-06 15:45:11 +08:00
|
|
|
function clampX(x: number) {
|
|
|
|
if (x < 16) x = 16;
|
|
|
|
const mx = maxWidth - 16 - main.offsetWidth;
|
|
|
|
if (x > mx) x = mx;
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
|
|
|
|
function clampY(y: number) {
|
|
|
|
if (y < 16) y = 16;
|
|
|
|
const my = maxHeight - 16 - main.offsetHeight;
|
|
|
|
if (y > my) y = my;
|
|
|
|
return y;
|
|
|
|
}
|
|
|
|
|
|
|
|
function clampPos(x: number, y: number) {
|
|
|
|
return { x: clampX(x), y: clampY(y) };
|
|
|
|
}
|
|
|
|
|
|
|
|
function clampWidth(w: number) {
|
|
|
|
if (w < 16) w = 16;
|
|
|
|
const mw = maxWidth - 16 - main.offsetLeft;
|
|
|
|
if (w > mw) w = mw;
|
|
|
|
return w;
|
|
|
|
}
|
|
|
|
|
|
|
|
function clampHeight(h: number) {
|
|
|
|
if (h < 16) h = 16;
|
|
|
|
const mh = maxHeight - 16 - main.offsetTop;
|
|
|
|
if (h > mh) h = mh;
|
|
|
|
return h;
|
|
|
|
}
|
|
|
|
|
|
|
|
function clampSize(w: number, h: number) {
|
|
|
|
return { w: clampWidth(w), h: clampHeight(h) };
|
|
|
|
}
|
|
|
|
|
2022-12-28 20:34:23 +08:00
|
|
|
function dragFn(x: number, y: number) {
|
2024-11-06 15:45:11 +08:00
|
|
|
const { x: tx, y: ty } = clampPos(x, y);
|
|
|
|
left.value = tx;
|
|
|
|
top.value = ty;
|
|
|
|
main.style.left = `${tx}px`;
|
|
|
|
main.style.top = `${ty}px`;
|
2022-12-28 20:34:23 +08:00
|
|
|
|
|
|
|
moveSelected.value = true;
|
|
|
|
clearTimeout(moveTimeout);
|
|
|
|
lastX = x;
|
|
|
|
lastY = y;
|
|
|
|
}
|
|
|
|
|
|
|
|
let right = left.value + width.value;
|
|
|
|
function leftDrag(x: number, y: number) {
|
2024-11-06 15:45:11 +08:00
|
|
|
const tx = clampX(x);
|
|
|
|
main.style.left = `${tx}px`;
|
|
|
|
width.value = right - tx;
|
|
|
|
left.value = tx;
|
2022-12-28 20:34:23 +08:00
|
|
|
main.style.width = `${width.value}px`;
|
|
|
|
}
|
|
|
|
|
|
|
|
let bottom = top.value + height.value;
|
|
|
|
function topDrag(x: number, y: number) {
|
2024-11-06 15:45:11 +08:00
|
|
|
const ty = clampY(y);
|
|
|
|
main.style.top = `${ty}px`;
|
|
|
|
height.value = bottom - ty;
|
|
|
|
top.value = ty;
|
2022-12-28 20:34:23 +08:00
|
|
|
main.style.height = `${height.value}px`;
|
|
|
|
}
|
|
|
|
|
|
|
|
function rightDrag(x: number, y: number) {
|
2024-11-06 15:45:11 +08:00
|
|
|
const w = clampWidth(x - main.offsetLeft);
|
|
|
|
width.value = w;
|
|
|
|
main.style.width = `${w}px`;
|
2022-12-28 20:34:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function bottomDrag(x: number, y: number) {
|
2024-11-06 15:45:11 +08:00
|
|
|
const h = clampHeight(y - main.offsetTop);
|
|
|
|
height.value = h;
|
|
|
|
main.style.height = `${h}px`;
|
2024-11-06 15:29:33 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function resizeBox(x: number, y: number) {
|
2024-11-06 15:45:11 +08:00
|
|
|
const { w, h } = clampSize(x - main.offsetLeft, y - main.offsetTop);
|
|
|
|
width.value = w;
|
|
|
|
height.value = h;
|
|
|
|
main.style.width = `${w}px`;
|
|
|
|
main.style.height = `${h}px`;
|
2022-12-28 20:34:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function resize() {
|
|
|
|
main = document.getElementById(`box-${id}`) as HTMLDivElement;
|
|
|
|
leftB = document.getElementById(`border-left-${id}`) as HTMLDivElement;
|
|
|
|
topB = document.getElementById(`border-top-${id}`) as HTMLDivElement;
|
|
|
|
rightB = document.getElementById(`border-right-${id}`) as HTMLDivElement;
|
|
|
|
bottomB = document.getElementById(`border-bottom-${id}`) as HTMLDivElement;
|
|
|
|
drag = document.getElementById(`box-drag-${id}`) as HTMLElement;
|
2024-11-06 15:29:33 +08:00
|
|
|
size = document.getElementById(`box-resize-${id}`) as HTMLElement;
|
2022-12-28 20:34:23 +08:00
|
|
|
|
2022-12-30 14:14:28 +08:00
|
|
|
if (!main) return;
|
|
|
|
|
2022-12-28 20:34:23 +08:00
|
|
|
if (has(props.left)) left.value = props.left;
|
|
|
|
if (has(props.top)) top.value = props.top;
|
|
|
|
if (has(props.width)) width.value = props.width;
|
|
|
|
if (has(props.height)) height.value = props.height;
|
|
|
|
|
2022-12-29 00:26:12 +08:00
|
|
|
main.style.left = `${left.value}px`;
|
|
|
|
main.style.top = `${top.value}px`;
|
|
|
|
main.style.width = `${width.value}px`;
|
|
|
|
main.style.height = `${height.value}px`;
|
2022-12-28 20:34:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
onUpdated(resize);
|
|
|
|
|
2022-12-29 00:26:12 +08:00
|
|
|
onMounted(async () => {
|
2022-12-28 20:34:23 +08:00
|
|
|
resize();
|
|
|
|
|
2022-12-30 14:14:28 +08:00
|
|
|
if (!main) return;
|
|
|
|
|
2023-01-06 16:21:17 +08:00
|
|
|
if (props.dragable) {
|
|
|
|
useDrag(
|
|
|
|
drag,
|
|
|
|
dragFn,
|
|
|
|
(x, y) => {
|
|
|
|
lastX = x;
|
|
|
|
lastY = y;
|
|
|
|
},
|
|
|
|
() => {
|
|
|
|
moveSelected.value = false;
|
|
|
|
},
|
|
|
|
true
|
|
|
|
);
|
|
|
|
}
|
2022-12-28 20:34:23 +08:00
|
|
|
|
|
|
|
if (props.resizable) {
|
|
|
|
useDrag(
|
|
|
|
leftB,
|
|
|
|
leftDrag,
|
|
|
|
(x, y) => {
|
|
|
|
right = left.value + width.value;
|
|
|
|
},
|
|
|
|
void 0,
|
|
|
|
true
|
|
|
|
);
|
|
|
|
|
|
|
|
useDrag(
|
|
|
|
topB,
|
|
|
|
topDrag,
|
|
|
|
(x, y) => {
|
|
|
|
bottom = top.value + height.value;
|
|
|
|
},
|
|
|
|
void 0,
|
|
|
|
true
|
|
|
|
);
|
|
|
|
|
|
|
|
useDrag(rightB, rightDrag, void 0, void 0, true);
|
|
|
|
useDrag(bottomB, bottomDrag, void 0, void 0, true);
|
2024-11-06 15:29:33 +08:00
|
|
|
useDrag(size, resizeBox, void 0, void 0, true);
|
2022-12-28 20:34:23 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
2023-01-06 16:21:17 +08:00
|
|
|
if (props.dragable) cancelGlobalDrag(dragFn);
|
2022-12-28 20:34:23 +08:00
|
|
|
if (props.resizable) {
|
|
|
|
cancelGlobalDrag(leftDrag);
|
|
|
|
cancelGlobalDrag(topDrag);
|
|
|
|
cancelGlobalDrag(rightDrag);
|
|
|
|
cancelGlobalDrag(bottomDrag);
|
2024-11-06 15:29:33 +08:00
|
|
|
cancelGlobalDrag(resizeBox);
|
2022-12-28 20:34:23 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="less" scoped>
|
|
|
|
.box {
|
|
|
|
width: 300px;
|
|
|
|
height: calc(100vh - 100px);
|
|
|
|
position: fixed;
|
|
|
|
left: 50px;
|
|
|
|
top: 50px;
|
|
|
|
display: flex;
|
|
|
|
overflow: visible;
|
|
|
|
}
|
|
|
|
|
|
|
|
.box-main {
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
overflow: hidden;
|
|
|
|
}
|
|
|
|
|
|
|
|
.box-move {
|
|
|
|
transition: font-size 0.3s ease-out;
|
|
|
|
position: absolute;
|
|
|
|
left: -32px;
|
|
|
|
top: -32px;
|
|
|
|
width: 32px;
|
|
|
|
height: 32px;
|
|
|
|
}
|
|
|
|
|
2024-11-06 15:29:33 +08:00
|
|
|
.box-size {
|
|
|
|
transition: font-size 0.3s ease-out;
|
|
|
|
position: absolute;
|
|
|
|
left: 100%;
|
|
|
|
top: 100%;
|
|
|
|
}
|
|
|
|
|
2022-12-28 20:34:23 +08:00
|
|
|
.box-drag {
|
|
|
|
cursor: all-scroll;
|
|
|
|
user-select: none;
|
2024-11-06 15:29:33 +08:00
|
|
|
right: 0;
|
|
|
|
bottom: 0;
|
|
|
|
position: absolute;
|
|
|
|
}
|
|
|
|
|
|
|
|
.box-resize {
|
|
|
|
left: 0;
|
|
|
|
top: 0;
|
|
|
|
position: absolute;
|
|
|
|
transform: rotateX(180deg);
|
|
|
|
user-select: none;
|
|
|
|
cursor: nwse-resize;
|
2022-12-28 20:34:23 +08:00
|
|
|
}
|
|
|
|
|
2024-11-06 15:29:33 +08:00
|
|
|
.box-move[selected='false'],
|
|
|
|
.box-size[selected='false'] {
|
|
|
|
font-size: 16px;
|
2022-12-28 20:34:23 +08:00
|
|
|
}
|
|
|
|
|
2024-11-06 15:29:33 +08:00
|
|
|
.box-move[selected='true'],
|
|
|
|
.box-size[selected='true'] {
|
2022-12-28 20:34:23 +08:00
|
|
|
font-size: 32px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.border {
|
|
|
|
margin: 0;
|
|
|
|
position: absolute;
|
|
|
|
transition: transform 0.3s ease-out;
|
|
|
|
}
|
|
|
|
|
|
|
|
.border-horizontal {
|
|
|
|
width: 100%;
|
|
|
|
height: 0px;
|
|
|
|
left: 0px;
|
|
|
|
}
|
|
|
|
|
2023-01-06 16:21:17 +08:00
|
|
|
.border-horizontal[selected='true'][selectable='true'] {
|
2022-12-28 20:34:23 +08:00
|
|
|
transform: scaleY(300%);
|
|
|
|
cursor: ns-resize;
|
|
|
|
}
|
|
|
|
|
2023-01-06 16:21:17 +08:00
|
|
|
.border-horizontal:hover[selectable='true'],
|
|
|
|
.border-horizontal:active[selectable='true'] {
|
2022-12-28 20:34:23 +08:00
|
|
|
transform: scaleY(500%);
|
|
|
|
cursor: ns-resize;
|
|
|
|
}
|
|
|
|
|
|
|
|
.border-vertical {
|
|
|
|
width: 0px;
|
|
|
|
height: 100%;
|
|
|
|
top: 0px;
|
|
|
|
}
|
|
|
|
|
2023-01-06 16:21:17 +08:00
|
|
|
.border-vertical[selected='true'][selectable='true'] {
|
2022-12-28 20:34:23 +08:00
|
|
|
transform: scaleX(300%);
|
|
|
|
cursor: ew-resize;
|
|
|
|
}
|
|
|
|
|
2023-01-06 16:21:17 +08:00
|
|
|
.border-vertical:hover[selectable='true'],
|
|
|
|
.border-vertical:active[selectable='true'] {
|
2022-12-28 20:34:23 +08:00
|
|
|
transform: scaleX(500%);
|
|
|
|
cursor: ew-resize;
|
|
|
|
}
|
|
|
|
|
|
|
|
.border-left {
|
|
|
|
left: 0;
|
|
|
|
border-left: 2px solid #ddd9;
|
|
|
|
}
|
|
|
|
|
|
|
|
.border-right {
|
|
|
|
right: 0;
|
|
|
|
border-right: 2px solid #ddd9;
|
|
|
|
}
|
|
|
|
|
|
|
|
.border-top {
|
|
|
|
top: 0;
|
|
|
|
border-top: 2px solid #ddd9;
|
|
|
|
}
|
|
|
|
|
|
|
|
.border-bottom {
|
|
|
|
bottom: 0;
|
|
|
|
border-bottom: 2px solid #ddd9;
|
|
|
|
}
|
|
|
|
|
|
|
|
@media screen and (max-width: 600px) {
|
|
|
|
.box {
|
|
|
|
width: calc(100vw - 100px);
|
|
|
|
height: 250px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|