Merge pull request #853 from IGCrystal/master

🎈 perf: 优化了iframe窗口,新增跳转按钮
This commit is contained in:
Soulter
2025-03-17 23:25:26 +08:00
committed by GitHub
@@ -1,5 +1,6 @@
<script setup>
import { ref, shallowRef } from 'vue';
import { ref, shallowRef, onMounted } from 'vue';
import axios from 'axios';
import { useCustomizerStore } from '../../../stores/customizer';
import sidebarItems from './sidebarItem';
import NavItem from './NavItem.vue';
@@ -8,10 +9,51 @@ const customizer = useCustomizerStore();
const sidebarMenu = shallowRef(sidebarItems);
const showIframe = ref(false);
const version = ref("");
const buildVer = ref("");
const hasWebUIUpdate = ref(false);
// 默认桌面端 iframe 样式
const iframeStyle = ref({
position: 'fixed',
bottom: '16px',
right: '16px',
width: '490px',
height: '640px',
minWidth: '300px',
minHeight: '200px',
background: 'white',
resize: 'both',
overflow: 'auto',
zIndex: '10000000',
borderRadius: '12px',
boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
});
// 如果为移动端,则采用百分比尺寸,并设置初始位置
if (window.innerWidth < 768) {
iframeStyle.value = {
position: 'fixed',
top: '10%',
left: '0%',
width: '100%',
height: '50%',
minWidth: '300px',
minHeight: '200px',
background: 'white',
resize: 'both',
overflow: 'auto',
zIndex: '1002',
borderRadius: '12px',
boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
};
// 移动端默认关闭侧边栏
customizer.Sidebar_drawer = false;
}
const dragHeaderStyle = {
width: '100%',
padding: '4px',
padding: '8px',
background: '#f0f0f0',
borderBottom: '1px solid #ccc',
borderTopLeftRadius: '8px',
@@ -26,45 +68,119 @@ function toggleIframe() {
showIframe.value = !showIframe.value;
}
function openIframeLink() {
if (typeof window !== 'undefined') {
window.open("https://astrbot.app", "_blank");
}
}
// 拖拽相关变量与函数
let offsetX = 0;
let offsetY = 0;
let isDragging = false;
function onMouseDown(event) {
// 如果点击的是关闭按钮则不启动拖拽
// 可通过 event.target 判断(这里通过在关闭按钮上添加 .stop 处理)
// 辅助函数:限制数值在一定范围内
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
function startDrag(clientX, clientY) {
isDragging = true;
const dm = document.getElementById('draggable-iframe');
const rect = dm.getBoundingClientRect();
offsetX = event.clientX - rect.left;
offsetY = event.clientY - rect.top;
// 禁用文字选中
offsetX = clientX - rect.left;
offsetY = clientY - rect.top;
document.body.style.userSelect = 'none';
// 绑定全局事件,确保拖拽不中断
// 绑定全局鼠标和触摸事件
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
document.addEventListener('touchmove', onTouchMove, { passive: false });
document.addEventListener('touchend', onTouchEnd);
}
function onMouseDown(event) {
startDrag(event.clientX, event.clientY);
}
function onMouseMove(event) {
if (isDragging) {
const dm = document.getElementById('draggable-iframe');
dm.style.left = (event.clientX - offsetX) + 'px';
dm.style.top = (event.clientY - offsetY) + 'px';
moveAt(event.clientX, event.clientY);
}
}
function onMouseUp() {
endDrag();
}
function onTouchStart(event) {
if (event.touches.length === 1) {
const touch = event.touches[0];
startDrag(touch.clientX, touch.clientY);
}
}
function onTouchMove(event) {
if (isDragging && event.touches.length === 1) {
event.preventDefault();
const touch = event.touches[0];
moveAt(touch.clientX, touch.clientY);
}
}
function onTouchEnd() {
endDrag();
}
function moveAt(clientX, clientY) {
const dm = document.getElementById('draggable-iframe');
const newLeft = clamp(clientX - offsetX, 0, window.innerWidth - dm.offsetWidth);
const newTop = clamp(clientY - offsetY, 0, window.innerHeight - dm.offsetHeight);
// 将拖拽后的位置同步到响应式样式变量中
iframeStyle.value.left = newLeft + 'px';
iframeStyle.value.top = newTop + 'px';
}
function endDrag() {
isDragging = false;
document.body.style.userSelect = ''; // 恢复文字选中
// 移除全局事件监听
document.body.style.userSelect = '';
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
document.removeEventListener('touchmove', onTouchMove);
document.removeEventListener('touchend', onTouchEnd);
}
// 获取版本和更新信息
onMounted(() => {
axios.get('/api/stat/version')
.then((res) => {
version.value = "v" + res.data.data.version;
})
.catch((err) => {
console.log(err);
});
axios.get('/api/update/check?type=dashboard')
.then((res) => {
hasWebUIUpdate.value = res.data.data.has_new_version;
buildVer.value = res.data.data.current_version;
})
.catch((err) => {
console.log(err);
});
});
</script>
<template>
<v-navigation-drawer left v-model="customizer.Sidebar_drawer" elevation="0" rail-width="80" app class="leftSidebar" width="220"
:rail="customizer.mini_sidebar">
<v-navigation-drawer
left
v-model="customizer.Sidebar_drawer"
elevation="0"
rail-width="80"
app
class="leftSidebar"
width="220"
:rail="customizer.mini_sidebar"
>
<v-list class="pa-4 listitem" style="height: auto;">
<template v-for="(item, i) in sidebarMenu" :key="i">
<NavItem :item="item" class="leftPadding" />
@@ -73,7 +189,6 @@ function onMouseUp() {
<div class="text-center">
<v-chip color="inputBorder" size="small"> {{ version }} </v-chip>
</div>
<div style="position: absolute; bottom: 32px; width: 100%; font-size: 13px;" class="text-center">
<v-list-item v-if="!customizer.mini_sidebar" @click="toggleIframe">
<v-btn variant="plain" size="small">
@@ -87,75 +202,47 @@ function onMouseUp() {
<small v-bind="props" v-if="hasWebUIUpdate" style="display: block; margin-top: 4px;">面板有更新</small>
</template>
</v-tooltip>
<small style="display: block; margin-top: 8px;">AGPL-3.0</small>
</div>
</v-navigation-drawer>
<!-- 修改后的拖拽 iframe -->
<div v-if="showIframe"
<!-- 优化后的悬浮 iframe -->
<div
v-if="showIframe"
id="draggable-iframe"
style="position: fixed; bottom: 16px; right: 16px; width: 500px; height: 400px; min-width: 300px; min-height: 200px; border: 1px solid #ccc; background: white; resize: both; overflow: auto; z-index: 10000000; border-radius: 8px;">
<!-- 拖拽头部整个区域均可拖拽内部的关闭按钮阻止事件冒泡 -->
<div :style="dragHeaderStyle" @mousedown="onMouseDown">
:style="iframeStyle"
>
<!-- 拖拽头部支持鼠标和触摸 -->
<div :style="dragHeaderStyle" @mousedown="onMouseDown" @touchstart="onTouchStart">
<div style="display: flex; align-items: center;">
<v-icon icon="mdi-cursor-move" />
<span style="margin-left: 8px;">拖拽</span>
</div>
<!-- 关闭按钮点击时停止事件传播避免触发拖拽 -->
<v-btn
icon
@click.stop="toggleIframe"
@mousedown.stop
style="border: 1px solid #ccc; border-radius: 8px; padding: 4px; width: 32px; height: 32px;"
>
<v-icon icon="mdi-close" />
</v-btn>
<div style="display: flex; gap: 8px;">
<!-- 跳转按钮 -->
<v-btn
icon
@click.stop="openIframeLink"
@mousedown.stop
style="border-radius: 8px; border: 1px solid #ccc;"
>
<v-icon icon="mdi-open-in-new" />
</v-btn>
<!-- 关闭按钮 -->
<v-btn
icon
@click.stop="toggleIframe"
@mousedown.stop
style="border-radius: 8px; border: 1px solid #ccc;"
>
<v-icon icon="mdi-close" />
</v-btn>
</div>
</div>
<!-- iframe 区域 -->
<iframe src="https://astrbot.app" style="width: 100%; height: calc(100% - 32px); border: none; border-bottom-left-radius: 8px; border-bottom-right-radius: 8px;"></iframe>
<iframe
src="https://astrbot.app"
style="width: 100%; height: calc(100% - 56px); border: none; border-bottom-left-radius: 12px; border-bottom-right-radius: 12px;"
></iframe>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'VerticalSidebar',
components: {
NavItem,
},
data: () => ({
version: "",
buildVer: "",
hasWebUIUpdate: false,
}),
mounted() {
this.get_version();
this.check_webui_update();
},
methods: {
get_version() {
axios.get('/api/stat/version')
.then((res) => {
this.version = "v" + res.data.data.version;
})
.catch((err) => {
console.log(err);
});
},
check_webui_update() {
axios.get('/api/update/check?type=dashboard')
.then((res) => {
this.hasWebUIUpdate = res.data.data.has_new_version;
this.buildVer = res.data.data.current_version;
})
.catch((err) => {
console.log(err);
});
}
},
};
</script>