重构虚拟窗口移动,调整大小逻辑

This commit is contained in:
fangxiang 2022-02-12 10:16:08 +08:00
parent bfc271db2d
commit e1b1ec4ee7
18 changed files with 1426 additions and 736 deletions

View File

@ -14,20 +14,20 @@
"@types/element-resize-detector": "^1.1.3",
"autoprefixer": "^10.4.2",
"axios": "^0.21.1",
"core-js": "^3.6.5",
"element-resize-detector": "^1.2.3",
"quasar": "^2.5.4",
"core-js": "^3.21.0",
"element-resize-detector": "^1.2.4",
"quasar": "^2.5.5",
"reconnecting-websocket": "^4.4.0",
"v-viewer": "^3.0.9",
"vue": "^3.0.0",
"vue-i18n": "^9.0.0-beta.0",
"vue-i18n": "^9.1.9",
"vue-router": "^4.0.0",
"vuex": "^4.0.1"
},
"devDependencies": {
"@quasar/app": "^3.3.2",
"@quasar/app": "^3.3.3",
"@types/node": "^10.17.15",
"workbox-webpack-plugin": "^6.0.0"
"workbox-webpack-plugin": "^6.4.2"
},
"browserslist": [
"last 10 Chrome versions",

View File

@ -493,6 +493,26 @@ export default class ClientConnection {
);
}
public setWindowGeometry(
window_id: number,
x: number,
y: number,
width: number,
height: number
) {
this.ws?.send(
JSON.stringify(
new Protocol.SetWindowGeometryRequestEntity(
window_id,
x,
y,
width,
height
)
)
);
}
public closeWindow(window_id: number) {
this.ws?.send(
JSON.stringify(new Protocol.CloseWindowRequestEntity(window_id))

View File

@ -18,7 +18,6 @@ export default class EventBus extends EventEmitter {
export namespace EventNamesDefine {
export const UnKnow = "onUnKnow";
export const UnSelectAllWindows = "onUnSelectAllWindows";
export const WindowResize = "onWindowResize";
export const WindowMouseDown = "onWindowMouseDown";
export const WindowMouseMove = "onWindowMouseMove";

View File

@ -1,12 +1,12 @@
<template>
<div
class="window_class window_flag"
:class="selected ? 'window_selected' : 'window_normal'"
:class="
$props.window.uuid == $store.state.selected_window
? 'window_selected'
: 'window_normal'
"
@click="onClick"
@mousedown="onMouseDown"
@mousemove="onMouseMove"
@mouseleave="onMouseLeave"
@mouseup="onMouseUp"
:style="{
background: $props.window.client_color,
}"
@ -154,87 +154,6 @@
}}</q-tooltip>
</div>
</div>
<div
v-if="!$props.disable && selected && can_resize"
class="resize_div absolute_up"
ref="resize_up"
@mousedown="onMouseDown"
@mousemove="(evt) => onResizeMouseMove(evt, flags.up_flag)"
@mouseleave="onMouseLeave"
@mouseup="onMouseLeave"
></div>
<div
v-if="!$props.disable && selected && can_resize"
class="resize_div absolute_down"
ref="resize_down"
@mousedown="onMouseDown"
@mousemove="(evt) => onResizeMouseMove(evt, flags.down_flag)"
@mouseleave="onMouseLeave"
@mouseup="onMouseLeave"
></div>
<div
v-if="!$props.disable && selected && can_resize"
class="resize_div absolute_left"
ref="resize_left"
@mousedown="onMouseDown"
@mousemove="(evt) => onResizeMouseMove(evt, flags.left_flag)"
@mouseleave="onMouseLeave"
@mouseup="onMouseLeave"
></div>
<div
v-if="!$props.disable && selected && can_resize"
class="resize_div absolute_right"
ref="resize_right"
@mousedown="onMouseDown"
@mousemove="(evt) => onResizeMouseMove(evt, flags.right_flag)"
@mouseleave="onMouseLeave"
@mouseup="onMouseLeave"
></div>
<div
v-if="!$props.disable && selected && can_resize"
class="resize_div absolute_left_up"
ref="resize_left_up"
@mousedown="onMouseDown"
@mousemove="
(evt) => onResizeMouseMove(evt, flags.up_flag | flags.left_flag)
"
@mouseleave="onMouseLeave"
@mouseup="onMouseLeave"
></div>
<div
v-if="!$props.disable && selected && can_resize"
class="resize_div absolute_right_up"
ref="resize_right_up"
@mousedown="onMouseDown"
@mousemove="
(evt) => onResizeMouseMove(evt, flags.up_flag | flags.right_flag)
"
@mouseleave="onMouseLeave"
@mouseup="onMouseLeave"
></div>
<div
v-if="!$props.disable && selected && can_resize"
class="resize_div absolute_left_down"
ref="resize_left_down"
@mousedown="onMouseDown"
@mousemove="
(evt) => onResizeMouseMove(evt, flags.down_flag | flags.left_flag)
"
@mouseleave="onMouseLeave"
@mouseup="onMouseLeave"
></div>
<div
v-if="!$props.disable && selected && can_resize"
class="resize_div absolute_right_down"
ref="resize_right_down"
@mousedown="onMouseDown"
@mousemove="
(evt) => onResizeMouseMove(evt, flags.down_flag | flags.right_flag)
"
@mouseleave="onMouseLeave"
@mouseup="onMouseLeave"
></div>
</div>
</template>
@ -261,81 +180,6 @@
.top {
z-index: 1;
}
.resize_div {
background: gray;
width: 32px;
height: 32px;
z-index: 999;
}
.absolute_up {
position: absolute;
top: -16px;
left: calc(50% - 16px);
}
.absolute_down {
position: absolute;
bottom: -16px;
left: calc(50% - 16px);
}
div.absolute_up,
div.absolute_down {
cursor: s-resize;
}
.absolute_left {
position: absolute;
top: calc(50% - 16px);
left: -16px;
}
.absolute_right {
position: absolute;
top: calc(50% - 16px);
right: -16px;
}
div.absolute_left,
div.absolute_right {
cursor: w-resize;
}
.absolute_left_up {
position: absolute;
top: -16px;
left: -16px;
}
.absolute_left_down {
position: absolute;
bottom: -16px;
left: -16px;
}
.absolute_right_up {
position: absolute;
top: -16px;
right: -16px;
}
.absolute_right_down {
position: absolute;
bottom: -16px;
right: -16px;
}
div.absolute_left_up,
div.absolute_right_down {
cursor: se-resize;
}
div.absolute_right_up,
div.absolute_left_down {
cursor: ne-resize;
}
</style>
<script lang="ts">
@ -382,8 +226,6 @@ export default defineComponent({
},
},
emits: [
"reset_geometry_offset",
"commit_geometry",
"close_this_window",
"close_other_windows",
"close_all_windows",
@ -423,56 +265,13 @@ export default defineComponent({
return signal_source.value.window_type == "EwindowType::Clock";
});
let selected = ref(false);
let can_move = ref(true);
let can_resize = ref(true);
let move_flag = false;
const onUnSelectAllWindows = () => {
selected.value = false;
};
EventBus.getInstance().on(
EventNamesDefine.UnSelectAllWindows,
onUnSelectAllWindows
);
onUnmounted(() => {
EventBus.getInstance().removeListener(
EventNamesDefine.UnSelectAllWindows,
onUnSelectAllWindows
);
});
let mouse_down_flag = false;
let mouse_last_pos_x = 0;
let mouse_last_pos_y = 0;
const cleanMouseDownFlag = () => {
mouse_down_flag = false;
mouse_last_pos_x = 0;
mouse_last_pos_y = 0;
move_flag = false;
};
reload_signal_source();
watch(
() => props.window,
(a, b) => {
reload_signal_source();
selected.value = false;
cleanMouseDownFlag();
}
);
const flags = new _Flags();
let ctrl_press_flag = false;
return {
signal_source,
selected,
can_move,
can_resize,
flags,
is_audo_player_window,
is_clock_window,
@ -485,112 +284,7 @@ export default defineComponent({
}
if (!props.mouse_area_flag) {
evt.stopPropagation();
if (selected.value != true) {
EventBus.getInstance().emit(EventNamesDefine.UnSelectAllWindows);
selected.value = true;
$store.commit("setSelectedWindow", props.window.uuid);
}
}
},
onMouseDown(evt: MouseEvent) {
ctrl_press_flag = evt.ctrlKey;
if (!evt.ctrlKey) {
evt.stopPropagation();
if (props.disable) {
return;
}
if (selected.value) {
if (evt.button == 0) {
mouse_down_flag = true;
mouse_last_pos_x = evt.x;
mouse_last_pos_y = evt.y;
}
}
}
// console.log("down", ctrl_flag);
},
onMouseMove(evt: MouseEvent) {
if (!props.mouse_area_flag) {
evt.stopPropagation();
if (can_move.value && mouse_down_flag && selected.value) {
emit(
"reset_geometry_offset",
props.window,
evt.x - mouse_last_pos_x,
evt.y - mouse_last_pos_y,
0,
0
);
mouse_last_pos_x = evt.x;
mouse_last_pos_y = evt.y;
move_flag = true;
}
}
},
onResizeMouseDown(evt: MouseEvent) {},
onResizeMouseMove(evt: MouseEvent, dir: number) {
if (can_resize.value && mouse_down_flag && selected.value) {
const h = evt.x - mouse_last_pos_x;
const v = evt.y - mouse_last_pos_y;
let x_offset = 0;
let y_offset = 0;
let width_offset = 0;
let height_offset = 0;
if (dir & flags.up_flag) {
y_offset += v;
height_offset -= v;
}
if (dir & flags.down_flag) {
height_offset += v;
}
if (dir & flags.left_flag) {
x_offset += h;
width_offset -= h;
}
if (dir & flags.right_flag) {
width_offset += h;
}
emit(
"reset_geometry_offset",
props.window,
x_offset,
y_offset,
width_offset,
height_offset
);
mouse_last_pos_x = evt.x;
mouse_last_pos_y = evt.y;
move_flag = true;
}
},
cleanMouseDownFlag,
onMouseLeave(evt: MouseEvent) {
ctrl_press_flag = false;
if (!props.mouse_area_flag) {
if (selected.value && mouse_down_flag) {
if (move_flag) {
emit("commit_geometry", props.window);
}
cleanMouseDownFlag();
}
}
},
onMouseUp(evt: MouseEvent) {
// console.log("up", ctrl_flag);
if (selected.value && mouse_down_flag) {
if (move_flag) {
emit("commit_geometry", props.window);
}
cleanMouseDownFlag();
$store.commit("setSelectedWindow", props.window.uuid);
}
},

View File

@ -60,6 +60,9 @@ export namespace Protocol {
return Commands.PROTOCOL_PREFIX + "ResizeWindow";
}
public static get kSetWindowGeometry() {
return Commands.PROTOCOL_PREFIX + "SetWindowGeometry";
}
public static get kOpenWindow() {
return Commands.PROTOCOL_PREFIX + "OpenWindow";
}
@ -289,6 +292,7 @@ export namespace Protocol {
Commands.kRpcGetApplicationConfig,
Commands.kMoveWindow,
Commands.kResizeWindow,
Commands.kSetWindowGeometry,
Commands.kOpenWindow,
Commands.kCloseWindow,
Commands.kTopWindow,
@ -521,6 +525,29 @@ export namespace Protocol {
}
}
export class SetWindowGeometryRequestEntity extends PacketEntity {
window_id: number = 0;
x: number = 0;
y: number = 0;
width: number = 0;
height: number = 0;
constructor(
window_id: number,
x: number,
y: number,
width: number,
height: number
) {
super();
this.command = Commands.kSetWindowGeometry;
this.window_id = window_id ?? 0;
this.x = x ?? 0;
this.y = y ?? 0;
this.width = width ?? 0;
this.height = height ?? 0;
}
}
export class CloseWindowRequestEntity extends PacketEntity {
window_id: number = 0;
constructor(window_id: number) {

View File

@ -9,51 +9,53 @@
style="background-color: #bce0f0"
>
<div id="windows" style="position: absolute">
<window
@reset_geometry_offset="resetGeometryOffset"
@commit_geometry="commitGeometry"
@close_this_window="closeWindow"
@close_other_windows="closeOtherWindows"
@close_all_windows="closeAllWindows"
@top_window="topWindow"
@lower_window="lowerWindow"
@dblclick="(evt) => windowDBClick(item.window_id)"
@edit_volume="edit_volume"
@mute_unmute="mute_unmute"
@start_polling="start_polling"
@stop_polling="stop_polling"
@polling_setting="polling_setting"
<vue3-resize-drag
:w="item.width * $refs.wall.clientWidth"
:h="item.height * $refs.wall.clientHeight"
:x="
($refs.wall.parentElement?.offsetLeft ?? 0) +
$refs.wall.offsetLeft +
item.x * $refs.wall.clientWidth
"
:y="
($refs.wall.parentElement?.offsetTop ?? 0) +
$refs.wall.offsetTop +
item.y * $refs.wall.clientHeight
"
:zIndex="
$store.state.windows_sort.findIndex((element) => element == item.uuid)
"
:isActive="item.uuid == $store.state.selected_window"
:isGuide="true"
v-for="(item, index) in windows"
:key="index"
:ref="'window_' + item.window_id"
:id="'window_' + item.window_id"
:uuid="item.uuid"
:disable="plan_running"
class="window"
:mouse_area_flag="area_open_window_flag"
:signal_source_table_uuid="item.signal_source_table_uuid"
:window="item"
:style="{
top:
(item.y * $store.state.device_screen_height) / wall_height_scaler +
'px',
left:
(item.x * $store.state.device_screen_width) / wall_width_scaler +
'px',
width:
(item.width * $store.state.device_screen_width) /
wall_width_scaler +
'px',
height:
(item.height * $store.state.device_screen_height) /
wall_height_scaler +
'px',
'z-index': $store.state.windows_sort.findIndex(
(element) => element == item.uuid
),
}"
style="background: red; position: fixed"
@resizeEndHandler="resizeWindow(item.window_id, $event)"
@moveEndHandler="moveWindow(item.window_id, $event)"
>
</window>
<window
@close_this_window="closeWindow"
@close_other_windows="closeOtherWindows"
@close_all_windows="closeAllWindows"
@top_window="topWindow"
@lower_window="lowerWindow"
@dblclick="(evt) => windowDBClick(item.window_id)"
@edit_volume="edit_volume"
@mute_unmute="mute_unmute"
@start_polling="start_polling"
@stop_polling="stop_polling"
@polling_setting="polling_setting"
:ref="'window_' + item.window_id"
:id="'window_' + item.window_id"
:uuid="item.uuid"
:disable="plan_running"
class="window fit"
:mouse_area_flag="area_open_window_flag"
:signal_source_table_uuid="item.signal_source_table_uuid"
:window="item"
>
</window>
</vue3-resize-drag>
</div>
<div ref="wall_grids" @click="onWallGridsClick">
<div
@ -157,6 +159,8 @@ import { NotifyMessage } from "src/common/ClientConnection";
import EditVolumeDialog from "src/components/EditVolumeDialog.vue";
import PollingSettingDialog from "src/components/PollingSettingDialog.vue";
import vue3ResizeDrag from "../third_lib/vue3-resize-drag/components/vue3-resize-drag/index.vue";
class Rect {
start_x = 0;
start_y = 0;
@ -174,7 +178,12 @@ class Rect {
export default defineComponent({
name: "PageWall",
components: { Window, EditVolumeDialog, PollingSettingDialog },
components: {
vue3ResizeDrag,
Window,
EditVolumeDialog,
PollingSettingDialog,
},
setup() {
const $q = useQuasar();
const $store = useStore();
@ -208,17 +217,6 @@ export default defineComponent({
let item_witdh = ref(0);
const item_height = ref(0);
const wall_width_scaler = ref(0);
const wall_height_scaler = ref(0);
const calcWallVWScaler = (wall_width: number, wall_height: number) => {
if (wall.value && wall.value.parentElement) {
wall_height_scaler.value =
$store.state.device_screen_height / wall_height;
wall_width_scaler.value = $store.state.device_screen_width / wall_width;
}
};
const calcWallItemWH = () => {
item_witdh.value =
wall?.value?.parentElement?.offsetWidth ?? 0 / wall_cols.value;
@ -302,14 +300,10 @@ export default defineComponent({
?.openWindow(
new Protocol.OpenWindowRequestEntity(
$store.state.selected_signal_source,
(start_x * wall_width_scaler.value) /
$store.state.device_screen_width,
(start_y * wall_height_scaler.value) /
$store.state.device_screen_height,
(end_x * wall_width_scaler.value) /
$store.state.device_screen_width,
(end_y * wall_height_scaler.value) /
$store.state.device_screen_height
start_x / wall.value.offsetWidth,
start_y / wall.value.offsetHeight,
end_x / wall.value.offsetWidth,
end_y / wall.value.offsetHeight
)
);
}
@ -367,12 +361,14 @@ export default defineComponent({
}
}
if (!flag) {
EventBus.getInstance().emit(EventNamesDefine.UnSelectAllWindows);
$store.commit("setSelectedWindow", "");
}
}
}
);
const __temp__size_a__ = 0.00000001;
EventBus.getInstance().on(
EventNamesDefine.NotifyMessage,
(notify: NotifyMessage) => {
@ -396,18 +392,34 @@ export default defineComponent({
(item) => item.window_id == temp.window_id
);
if (window) {
$store.commit("setWindowPropertys", [
{
window,
property_name: "x",
value: temp.x ?? 0,
},
{
window,
property_name: "y",
value: temp.y ?? 0,
},
]);
// ,
// $store.commit("setWindowPropertys", [
// {
// window,
// property_name: "x",
// value: (temp.x ?? 0) + __temp__size_a__,
// },
// {
// window,
// property_name: "y",
// value: (temp.y ?? 0) + __temp__size_a__,
// },
// ]);
setTimeout(() => {
$store.commit("setWindowPropertys", [
{
window,
property_name: "x",
value: temp.x ?? 0,
},
{
window,
property_name: "y",
value: temp.y ?? 0,
},
]);
}, 0);
}
}
}
@ -420,18 +432,33 @@ export default defineComponent({
(item) => item.window_id == temp.window_id
);
if (window) {
$store.commit("setWindowPropertys", [
{
window,
property_name: "width",
value: temp.width ?? 0,
},
{
window,
property_name: "height",
value: temp.height ?? 0,
},
]);
// $store.commit("setWindowPropertys", [
// {
// window,
// property_name: "width",
// value: (temp.width ?? 0) + __temp__size_a__,
// },
// {
// window,
// property_name: "height",
// value: (temp.width ?? 0) + __temp__size_a__,
// },
// ]);
setTimeout(() => {
$store.commit("setWindowPropertys", [
{
window,
property_name: "width",
value: temp.width ?? 0,
},
{
window,
property_name: "height",
value: temp.height ?? 0,
},
]);
}, 0);
}
}
}
@ -554,13 +581,69 @@ export default defineComponent({
(element: HTMLElement) => {
if (element) {
calcWallItemWH();
calcWallVWScaler(element.offsetWidth, element.offsetHeight);
}
}
);
}
});
const moveWindow = (window_id: number, evt: any) => {
evt.x = evt.x ?? 0;
evt.y = evt.y ?? 0;
if (wall.value) {
const x =
evt.left -
(wall.value.parentElement?.offsetLeft ?? 0) -
wall.value.offsetLeft;
const y =
evt.top -
(wall.value.parentElement?.offsetTop ?? 0) -
wall.value.offsetTop;
GlobalData.getInstance()
.getCurrentClient()
?.moveWindow(
window_id,
x / wall.value.clientWidth,
y / wall.value.clientHeight
);
}
};
const resizeWindow = (window_id: number, evt: any) => {
console.log(evt);
evt.width = evt.width ?? 0;
evt.height = evt.height ?? 0;
evt.left = evt.left ?? 0;
evt.top = evt.top ?? 0;
// moveWindow(window_id, evt);
if (wall.value) {
// GlobalData.getInstance()
// .getCurrentClient()
// ?.resizeWindow(
// window_id,
// evt.width / wall.value.clientWidth,
// evt.height / wall.value.clientHeight
// );
const x =
evt.left -
(wall.value.parentElement?.offsetLeft ?? 0) -
wall.value.offsetLeft;
const y =
evt.top -
(wall.value.parentElement?.offsetTop ?? 0) -
wall.value.offsetTop;
GlobalData.getInstance()
.getCurrentClient()
?.setWindowGeometry(
window_id,
x / wall.value.clientWidth,
y / wall.value.clientHeight,
evt.width / wall.value.clientWidth,
evt.height / wall.value.clientHeight
);
}
};
return {
windows,
wall,
@ -568,8 +651,6 @@ export default defineComponent({
wall_cols,
item_witdh,
item_height,
wall_width_scaler,
wall_height_scaler,
plan_running,
edit_volume_dialog,
polling_setting_dialog,
@ -672,66 +753,10 @@ export default defineComponent({
onWallGridsClick(e: MouseEvent) {
e.stopPropagation();
EventBus.getInstance().emit(EventNamesDefine.UnSelectAllWindows);
$store.commit("setSelectedWindow", "");
},
resetGeometryOffset(
window: any,
offset_x: number,
offset_y: number,
offset_width: number,
offset_height: number
) {
$store.commit("setWindowPropertys", [
{
window,
property_name: "x",
value:
window.x +
(offset_x * wall_width_scaler.value) /
$store.state.device_screen_width,
},
{
window,
property_name: "y",
value:
window.y +
(offset_y * wall_height_scaler.value) /
$store.state.device_screen_height,
},
{
window,
property_name: "width",
value: Math.max(
window.width +
(offset_width * wall_width_scaler.value) /
$store.state.device_screen_width,
32 / $store.state.device_screen_width
),
},
{
window,
property_name: "height",
value: Math.max(
window.height +
(offset_height * wall_height_scaler.value) /
$store.state.device_screen_height,
32 / $store.state.device_screen_height
),
},
]);
},
commitGeometry(window: any) {
const win = window as WindowOpenNotifyEntity;
if (win) {
GlobalData.getInstance()
.getCurrentClient()
?.moveWindow(win.window_id, win.x, win.y);
GlobalData.getInstance()
.getCurrentClient()
?.resizeWindow(win.window_id, win.width, win.height);
}
},
moveWindow,
resizeWindow,
closeAllWindows() {
for (const window of $store.state.windows) {
if (window) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,10 @@
<template>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name:'vue3ResizeDragCanvas'
})
</script>

View File

@ -0,0 +1,19 @@
import { watch } from 'vue';
function active(props: any, emit: Function) {
// 监听是否激活
watch(() => {
return props.isActive
},
(n) => {
if(n){
emit('activated',{
})
}else{
emit('deactivated',{
})
}
})
}
export default active

View File

@ -0,0 +1,66 @@
import { ref, reactive } from "vue";
import { styleIf } from "../../../types/style";
// 外层元素点击拖拽
const itemDrag = (
ev: any,
emit: Function,
props: any,
style: any,
moveing: any
): void => {
let press_x = ev.clientX;
let press_y = ev.clientY;
let target: any = ev.target || ev.srcElement;
ev.stopPropagation();
ev.preventDefault();
emit("downHandler", {
// 点击元素事件回调
el: target,
e: ev,
});
if (!props.isDraggable) return;
//鼠标按下并移动的事件
document.onmousemove = (e: any) => {
if (e.movementX || e.movementY) {
moveing.value = true;
let offset_left = press_x - e.clientX;
let offset_top = press_y - e.clientY;
press_x = e.clientX;
press_y = e.clientY;
//绑定元素位置到positionX和positionY上面
//移动当前元素
style.top -= offset_top;
style.left -= offset_left;
emit("moveHandler", {
// 移动事件回调
el: target,
e,
top: style.top,
left: style.left,
});
}
};
document.onmouseup = (e: any) => {
moveing.value = false;
if (style.left == style.back_left && style.top == style.back_top) {
style.left = style.back_left;
style.top = style.back_top;
} else {
emit("moveEndHandler", {
// 抬起事件回调
el: target,
e,
top: style.top,
left: style.left,
});
}
document.onmousemove = null;
document.onmouseup = null;
};
};
export default itemDrag;

View File

@ -0,0 +1,106 @@
const props = {
x: {
type: Number,
default: () => {
return 0;
},
},
y: {
type: Number,
default: () => {
return 0;
},
},
zIndex: {
type: Number,
default: () => {
return 0;
},
},
w: {
type: Number,
default: () => {
return 100;
},
},
h: {
type: Number,
default: () => {
return 100;
},
},
isActive: {
type: Boolean,
default: () => {
return false;
},
},
isDraggable: {
type: Boolean,
default: () => {
return true;
},
},
isResizable: {
type: Boolean,
default: () => {
return true;
},
},
isGuide: {
type: Boolean,
default: () => {
return false;
},
},
guideStyle: {
type: Object,
default: () => {
return {};
},
},
resizeIconSize: {
type: Number,
default: () => {
return 8;
},
},
isRotate: {
type: Boolean,
default: () => {
return false;
},
},
rotate: {
type: Number,
default: () => {
return 0;
},
},
limitTop: {
type: Number,
default: () => {
return -1;
},
},
limitLeft: {
type: Number,
default: () => {
return -1;
},
},
limitBottom: {
type: Number,
default: () => {
return -1;
},
},
limitRight: {
type: Number,
default: () => {
return -1;
},
},
};
export default props;

View File

@ -0,0 +1,146 @@
import { computed, reactive, ref } from "vue";
import { styleIf } from "../../../types/style";
function render(props: any) {
// 外层元素定位style
const style = reactive<styleIf>({
top: props.y,
left: props.x,
right: 0,
bottom: 0,
zIndex: props.zIndex,
width: props.w,
height: props.h,
rotate: props.rotate,
back_height: 0,
back_left: 0,
back_top: 0,
back_width: 0,
});
const moveing = ref(false);
// 计算属性 计算元素位置以及宽高
const styleHandler = computed(() => {
return {
top: style.top + "px",
left: style.left + "px",
right: style.right + "px",
bottom: style.bottom + "px",
zIndex: style.zIndex,
width: style.width + "px",
height: style.height + "px",
transform: `rotate(${style.rotate}deg)`,
};
});
const dragElResizeIcon = computed(() => {
return [
{
class: "drag-lt",
style: {
top: -(props.resizeIconSize / 2) + "px",
left: -(props.resizeIconSize / 2) + "px",
width: props.resizeIconSize + "px",
height: props.resizeIconSize + "px",
},
},
{
class: "drag-ct",
style: {
top: -(props.resizeIconSize / 2) + "px",
left: <number>style.width / 2 - props.resizeIconSize / 2 + "px",
width: props.resizeIconSize + "px",
height: props.resizeIconSize + "px",
},
},
{
class: "drag-rt",
style: {
top: -(props.resizeIconSize / 2) + "px",
right: -(props.resizeIconSize / 2) + "px",
width: props.resizeIconSize + "px",
height: props.resizeIconSize + "px",
},
},
{
class: "drag-rc",
style: {
top: <number>style.height / 2 - props.resizeIconSize / 2 + "px",
right: -(props.resizeIconSize / 2) + "px",
width: props.resizeIconSize + "px",
height: props.resizeIconSize + "px",
},
},
{
class: "drag-rb",
style: {
bottom: -(props.resizeIconSize / 2) + "px",
right: -(props.resizeIconSize / 2) + "px",
width: props.resizeIconSize + "px",
height: props.resizeIconSize + "px",
},
},
{
class: "drag-bc",
style: {
bottom: -(props.resizeIconSize / 2) + "px",
left: <number>style.width / 2 - props.resizeIconSize / 2 + "px",
width: props.resizeIconSize + "px",
height: props.resizeIconSize + "px",
},
},
{
class: "drag-lb",
style: {
bottom: -(props.resizeIconSize / 2) + "px",
left: -(props.resizeIconSize / 2) + "px",
width: props.resizeIconSize + "px",
height: props.resizeIconSize + "px",
},
},
{
class: "drag-lc",
style: {
top: <number>style.height / 2 - props.resizeIconSize / 2 + "px",
left: -(props.resizeIconSize / 2) + "px",
width: props.resizeIconSize + "px",
height: props.resizeIconSize + "px",
},
},
];
});
const guideBaseStyle = computed(() => {
return [
{
top: 0,
borderTop: "1px dashed #58B0E9",
width: "100vw",
left: `calc(-50vw + ${<number>style.width / 2}px)`,
},
{
left: 0,
borderLeft: "1px dashed #58B0E9",
height: "100vh",
top: `calc(-50vh + ${<number>style.height / 2}px)`,
},
{
right: 0,
borderRight: "1px dashed #58B0E9",
height: "100vh",
top: `calc(-50vh + ${<number>style.height / 2}px)`,
},
{
bottom: 0,
borderBottom: "1px dashed #58B0E9",
width: "100vw",
left: `calc(-50vw + ${<number>style.width / 2}px)`,
},
];
});
return {
style,
moveing,
styleHandler,
dragElResizeIcon,
guideBaseStyle,
};
}
export default render;

View File

@ -0,0 +1,114 @@
// 元素缩放
const itemResize = (
ev: any,
cls: string,
index: number,
emit: Function,
style: any
) => {
let move_flag = false;
let target: any = ev.target.offsetParent || ev.srcElement.parentNode;
ev.stopPropagation();
ev.preventDefault();
let w: number = 0;
let h: number = 0;
let top: number = 0;
let left: number = 0;
// 鼠标拖拽改变元素大小
document.onmousemove = (e) => {
if (e.movementX || e.movementY) {
move_flag = true;
switch (cls) {
case "drag-ct": // top center
h = target.offsetHeight + target.offsetTop - e.clientY;
w = target.offsetWidth;
top = e.clientY;
left = target.offsetLeft;
break;
case "drag-lc": // left center
h = target.offsetHeight;
w = target.offsetWidth + target.offsetLeft - e.clientX;
top = target.offsetTop;
left = e.clientX;
break;
case "drag-bc": // bottom center
h = e.clientY - target.offsetTop;
w = target.offsetWidth;
top = target.offsetTop;
left = target.offsetLeft;
break;
case "drag-rc": // right center
h = target.offsetHeight;
w = e.clientX - target.offsetLeft;
top = target.offsetTop;
left = target.offsetLeft;
break;
case "drag-lt": // left top
h = target.offsetHeight + target.offsetTop - e.clientY;
w = target.offsetWidth + target.offsetLeft - e.clientX;
top = e.clientY;
left = e.clientX;
break;
case "drag-rt": // right top
h = target.offsetHeight + target.offsetTop - e.clientY;
w = e.clientX - target.offsetLeft;
top = e.clientY;
left = target.offsetLeft;
break;
case "drag-rb": // right bottom
h = e.clientY - target.offsetTop;
w = e.clientX - target.offsetLeft;
top = target.offsetTop;
left = target.offsetLeft;
break;
case "drag-lb": // left bottom
h = e.clientY - target.offsetTop;
w = target.offsetWidth + target.offsetLeft - e.clientX;
top = target.offsetTop;
left = e.clientX;
break;
default:
w = target.offsetWidth;
h = target.offsetHeight;
top = target.offsetTop;
left = target.offsetLeft;
break;
}
emit("resizeHandler", {
el: target,
e,
w,
h,
top,
left,
});
style.width = w;
style.height = h;
style.top = top;
style.left = left;
}
};
document.onmouseup = (e: any) => {
if (!move_flag) {
style.left = style.back_left;
style.top = style.back_top;
} else {
console.log(style);
emit("resizeEndHandler", {
// 抬起事件回调
el: target,
e: ev,
top: style.top,
left: style.left,
width: style.width,
height: style.height,
});
}
document.onmousemove = null;
document.onmouseup = null;
};
};
export default itemResize;

View File

@ -0,0 +1,44 @@
// 元素旋转
const itemRotate = (ev: any,emit: Function,style:any) => {
let target:any = ev.target.offsetParent || ev.srcElement.parentNode
ev.stopPropagation()
ev.preventDefault()
let cx:number = target.offsetWidth / 2
let cy:number = target.offsetHeight / 2
let offsetX:number = target.offsetLeft
let offsetY:number = target.offsetTop
document.onmousemove = (e:any) => {
let mouseX:number = e.pageX - offsetX;//计算出鼠标相对于画布顶点的位置,无pageX时用clientY + body.scrollTop - body.clientTop代替,可视区域y+body滚动条所走的距离-body的border-top,不用offsetX等属性的原因在于鼠标会移出画布
let mouseY:number = e.pageY - offsetY;
let ox:number = mouseX - cx;//cx,cy为圆心
let oy:number = mouseY - cy;
let to:number = Math.abs(ox / oy);
let angle:number = Math.atan(to) / (2 * Math.PI) * 360;//鼠标相对于旋转中心的角度
if (ox < 0 && oy < 0)//相对在左上角第四象限js中坐标系是从左上角开始的这里的象限是正常坐标系
{
angle = -angle;
} else if (ox < 0 && oy > 0)//左下角,3象限
{
angle = -(180 - angle)
} else if (ox > 0 && oy < 0)//右上角1象限
{
angle = angle;
} else if (ox > 0 && oy > 0)//右下角2象限
{
angle = 180 - angle;
}
style.rotate = angle
emit('rotateHandler', {
el: target,
e,
rotate: angle
})
}
document.onmouseup = () => {
document.onmousemove = null
document.onmouseup = null
}
}
export default itemRotate

View File

@ -0,0 +1,41 @@
import { watch } from 'vue'
// 外层元素点击拖拽
const watchProps = (props: any, style: any): void => {
watch(() => {
return props.x
},
(n) => {
style.left = n
})
watch(() => {
return props.y
},
(n) => {
style.top = n
})
watch(() => {
return props.zIndex
},
(n) => {
style.zIndex = n
})
watch(() => {
return props.w
},
(n) => {
style.width = n
})
watch(() => {
return props.h
},
(n) => {
style.height = n
})
watch(() => {
return props.rotate
},
(n) => {
style.rotate = n
})
}
export default watchProps

View File

@ -0,0 +1,145 @@
<template>
<div class="vue3-resize-drag" :style="styleHandler" @mousedown="itemDown">
<slot></slot>
<div v-if="isRotate" class="rotate-icon" @mousedown="itemRotate"></div>
<!-- 组件移动辅助线 -->
<div
v-if="isGuide"
v-for="i in [1, 2, 3, 4]"
:key="i"
:style="moveing ? { ...guideBaseStyle[i - 1], ...guideStyle } : {}"
class="guide"
></div>
<!-- 组件四周缩放按钮 -->
<div
v-if="isResizable && isActive"
v-for="(el, index) in dragElResizeIcon"
:key="el.class"
class="dragElResizeIcon"
:class="el.class"
@mousedown="itemResize($event, el.class, index)"
:style="el.style"
></div>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, reactive, ref, watch } from "vue";
import { styleIf } from "../../types/style";
import renderFn from "./func/renderData";
import props from "./func/props";
import watchProps from "./func/watchProps";
import active from "./func/active";
import itemDrag from "./func/drag";
import itemRotateFn from "./func/rotate";
import itemResizeFn from "./func/resize";
export default defineComponent({
name: "vue3ResizeDrag",
props,
setup(props, { emit }) {
// ----------------------------------------------------------------------------------------------
let {
style, // z-index
moveing, // 线
styleHandler, // style
dragElResizeIcon, //
guideBaseStyle, // 线
} = renderFn(props); // data
// -----------------------------------------------------------------------------------------------
watchProps(props, style);
const itemResize = (ev: any, cls: string, index: number) => {
//
style.back_top = style.top;
style.back_height = style.height;
style.back_left = style.left;
style.back_width = style.width;
itemResizeFn(ev, cls, index, emit, style);
};
const itemRotate = (ev: any) => {
//
itemRotateFn(ev, emit, style);
};
const itemDown = (ev: any) => {
if (!props.isActive) {
return;
}
//
if (ev.ctrlKey) {
return;
}
style.back_top = style.top;
style.back_height = style.height;
style.back_left = style.left;
style.back_width = style.width;
itemDrag(ev, emit, props, style, moveing);
};
active(props, emit); //
return {
style,
guideBaseStyle,
dragElResizeIcon,
styleHandler,
itemDown,
itemResize,
itemRotate,
moveing,
};
},
});
</script>
<style scoped>
.vue3-resize-drag {
position: absolute;
box-sizing: border-box;
border: 1px dashed #ccc;
transition: width height 1s;
transform-origin: center center;
}
/* 辅助线 */
.guide {
position: absolute;
}
/* 旋转图标 */
.rotate-icon {
width: 16px;
height: 16px;
position: absolute;
top: -20px;
left: calc(50% - 8px);
background: url("../../assets/rotate.png") no-repeat;
background-size: 100%;
}
/* 缩放角标 */
.dragElResizeIcon {
position: absolute;
background: #fff;
border: 1px solid #ccc;
}
.drag-ct {
cursor: s-resize;
}
.drag-lt {
cursor: se-resize;
}
.drag-rt {
cursor: ne-resize;
}
.drag-rc {
cursor: w-resize;
}
.drag-rb {
cursor: se-resize;
}
.drag-bc {
cursor: s-resize;
}
.drag-lb {
cursor: ne-resize;
}
.drag-lc {
cursor: w-resize;
}
</style>

View File

@ -0,0 +1,14 @@
export interface styleIf {
left: number | string;
top: number | string;
bottom: number | string;
right: number | string;
zIndex: number;
width: number | string;
height: number | string;
rotate: number;
back_left: number | string;
back_top: number | string;
back_width: number | string;
back_height: number | string;
}

758
yarn.lock

File diff suppressed because it is too large Load Diff