|
@@ -0,0 +1,319 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div v-if="showToggle" class="vconsole-toggle">
|
|
|
|
|
+ <!-- 悬浮按钮 -->
|
|
|
|
|
+ <button
|
|
|
|
|
+ class="toggle-btn"
|
|
|
|
|
+ @click="handleToggle"
|
|
|
|
|
+ @touchstart="handleTouchStart"
|
|
|
|
|
+ @touchmove="handleTouchMove"
|
|
|
|
|
+ @touchend="handleTouchEnd"
|
|
|
|
|
+ :style="buttonStyle"
|
|
|
|
|
+ >
|
|
|
|
|
+ <svg
|
|
|
|
|
+ v-if="!isDragging"
|
|
|
|
|
+ class="icon"
|
|
|
|
|
+ viewBox="0 0 24 24"
|
|
|
|
|
+ fill="none"
|
|
|
|
|
+ xmlns="http://www.w3.org/2000/svg"
|
|
|
|
|
+ >
|
|
|
|
|
+ <path
|
|
|
|
|
+ d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"
|
|
|
|
|
+ fill="currentColor"
|
|
|
|
|
+ />
|
|
|
|
|
+ </svg>
|
|
|
|
|
+ <span v-else class="drag-icon">✋</span>
|
|
|
|
|
+ </button>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 快捷菜单 -->
|
|
|
|
|
+ <transition name="menu">
|
|
|
|
|
+ <div v-if="showMenu" class="quick-menu">
|
|
|
|
|
+ <button @click="toggleVConsole" class="menu-item">
|
|
|
|
|
+ {{ isVConsoleVisible ? '隐藏' : '显示' }} vConsole
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button @click="clearConsole" class="menu-item">清空日志</button>
|
|
|
|
|
+ <button @click="togglePersist" class="menu-item">
|
|
|
|
|
+ {{ isPersistent ? '关闭' : '开启' }}持久化
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button @click="showSystemInfo" class="menu-item">系统信息</button>
|
|
|
|
|
+ <button @click="closeMenu" class="menu-item close">关闭</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </transition>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { ref, onMounted, onUnmounted } from 'vue';
|
|
|
|
|
+import {
|
|
|
|
|
+ toggleVConsole as toggle,
|
|
|
|
|
+ enableVConsole,
|
|
|
|
|
+ disableVConsole
|
|
|
|
|
+} from '@/utils/vconsole';
|
|
|
|
|
+
|
|
|
|
|
+// 是否显示切换按钮
|
|
|
|
|
+const showToggle = ref(false);
|
|
|
|
|
+const showMenu = ref(false);
|
|
|
|
|
+const isVConsoleVisible = ref(false);
|
|
|
|
|
+const isPersistent = ref(false);
|
|
|
|
|
+const isDragging = ref(false);
|
|
|
|
|
+
|
|
|
|
|
+// 按钮位置
|
|
|
|
|
+const buttonPosition = ref({ x: 10, y: 200 });
|
|
|
|
|
+const buttonStyle = ref({
|
|
|
|
|
+ right: '10px',
|
|
|
|
|
+ top: '200px',
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 触摸相关
|
|
|
|
|
+let touchStartX = 0;
|
|
|
|
|
+let touchStartY = 0;
|
|
|
|
|
+let buttonStartX = 0;
|
|
|
|
|
+let buttonStartY = 0;
|
|
|
|
|
+let longPressTimer: any = null;
|
|
|
|
|
+
|
|
|
|
|
+// 检查是否应该显示切换按钮
|
|
|
|
|
+const checkShouldShow = () => {
|
|
|
|
|
+ // 开发环境显示
|
|
|
|
|
+ if (import.meta.env.DEV) {
|
|
|
|
|
+ showToggle.value = true;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // URL参数控制
|
|
|
|
|
+ const urlParams = new URLSearchParams(window.location.search);
|
|
|
|
|
+ if (urlParams.get('debug') === 'true' || urlParams.get('vconsole') === 'true') {
|
|
|
|
|
+ showToggle.value = true;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // localStorage 控制
|
|
|
|
|
+ if (localStorage.getItem('vconsole_enabled') === 'true') {
|
|
|
|
|
+ showToggle.value = true;
|
|
|
|
|
+ isPersistent.value = true;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 切换 vConsole 显示
|
|
|
|
|
+const toggleVConsole = () => {
|
|
|
|
|
+ toggle();
|
|
|
|
|
+ isVConsoleVisible.value = !isVConsoleVisible.value;
|
|
|
|
|
+ closeMenu();
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 清空控制台
|
|
|
|
|
+const clearConsole = () => {
|
|
|
|
|
+ console.clear();
|
|
|
|
|
+ console.log('[vConsole] 日志已清空');
|
|
|
|
|
+ closeMenu();
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 切换持久化
|
|
|
|
|
+const togglePersist = () => {
|
|
|
|
|
+ if (isPersistent.value) {
|
|
|
|
|
+ disableVConsole();
|
|
|
|
|
+ isPersistent.value = false;
|
|
|
|
|
+ console.log('[vConsole] 持久化已关闭');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ enableVConsole();
|
|
|
|
|
+ isPersistent.value = true;
|
|
|
|
|
+ console.log('[vConsole] 持久化已开启');
|
|
|
|
|
+ }
|
|
|
|
|
+ closeMenu();
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 显示系统信息
|
|
|
|
|
+const showSystemInfo = () => {
|
|
|
|
|
+ const info = {
|
|
|
|
|
+ '用户代理': navigator.userAgent,
|
|
|
|
|
+ '平台': navigator.platform,
|
|
|
|
|
+ '语言': navigator.language,
|
|
|
|
|
+ '屏幕尺寸': `${screen.width}x${screen.height}`,
|
|
|
|
|
+ '视口尺寸': `${window.innerWidth}x${window.innerHeight}`,
|
|
|
|
|
+ '像素比': window.devicePixelRatio,
|
|
|
|
|
+ '在线状态': navigator.onLine ? '在线' : '离线',
|
|
|
|
|
+ '网络类型': (navigator as any).connection?.effectiveType || '未知',
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ console.group('📱 系统信息');
|
|
|
|
|
+ Object.entries(info).forEach(([key, value]) => {
|
|
|
|
|
+ console.log(`${key}:`, value);
|
|
|
|
|
+ });
|
|
|
|
|
+ console.groupEnd();
|
|
|
|
|
+
|
|
|
|
|
+ closeMenu();
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 处理按钮点击
|
|
|
|
|
+const handleToggle = () => {
|
|
|
|
|
+ if (!isDragging.value) {
|
|
|
|
|
+ showMenu.value = !showMenu.value;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 关闭菜单
|
|
|
|
|
+const closeMenu = () => {
|
|
|
|
|
+ showMenu.value = false;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 触摸开始
|
|
|
|
|
+const handleTouchStart = (e: TouchEvent) => {
|
|
|
|
|
+ const touch = e.touches[0];
|
|
|
|
|
+ touchStartX = touch.clientX;
|
|
|
|
|
+ touchStartY = touch.clientY;
|
|
|
|
|
+
|
|
|
|
|
+ const rect = (e.target as HTMLElement).getBoundingClientRect();
|
|
|
|
|
+ buttonStartX = window.innerWidth - rect.right;
|
|
|
|
|
+ buttonStartY = rect.top;
|
|
|
|
|
+
|
|
|
|
|
+ // 长按开始拖动
|
|
|
|
|
+ longPressTimer = setTimeout(() => {
|
|
|
|
|
+ isDragging.value = true;
|
|
|
|
|
+ closeMenu();
|
|
|
|
|
+ }, 500);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 触摸移动
|
|
|
|
|
+const handleTouchMove = (e: TouchEvent) => {
|
|
|
|
|
+ if (!isDragging.value) return;
|
|
|
|
|
+
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ const touch = e.touches[0];
|
|
|
|
|
+ const deltaX = touchStartX - touch.clientX;
|
|
|
|
|
+ const deltaY = touch.clientY - touchStartY;
|
|
|
|
|
+
|
|
|
|
|
+ const newRight = Math.max(10, Math.min(window.innerWidth - 60, buttonStartX + deltaX));
|
|
|
|
|
+ const newTop = Math.max(50, Math.min(window.innerHeight - 60, buttonStartY + deltaY));
|
|
|
|
|
+
|
|
|
|
|
+ buttonStyle.value = {
|
|
|
|
|
+ right: `${newRight}px`,
|
|
|
|
|
+ top: `${newTop}px`,
|
|
|
|
|
+ };
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 触摸结束
|
|
|
|
|
+const handleTouchEnd = () => {
|
|
|
|
|
+ clearTimeout(longPressTimer);
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ isDragging.value = false;
|
|
|
|
|
+ }, 100);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 键盘快捷键
|
|
|
|
|
+const handleKeyboard = (e: KeyboardEvent) => {
|
|
|
|
|
+ // Ctrl + Shift + D 切换 vConsole
|
|
|
|
|
+ if (e.ctrlKey && e.shiftKey && e.key === 'D') {
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ toggleVConsole();
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ checkShouldShow();
|
|
|
|
|
+ document.addEventListener('keydown', handleKeyboard);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+onUnmounted(() => {
|
|
|
|
|
+ document.removeEventListener('keydown', handleKeyboard);
|
|
|
|
|
+ clearTimeout(longPressTimer);
|
|
|
|
|
+});
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.vconsole-toggle {
|
|
|
|
|
+ position: fixed;
|
|
|
|
|
+ z-index: 10000;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.toggle-btn {
|
|
|
|
|
+ position: fixed;
|
|
|
|
|
+ width: 50px;
|
|
|
|
|
+ height: 50px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background: rgba(67, 207, 124, 0.9);
|
|
|
|
|
+ border: 2px solid rgba(255, 255, 255, 0.3);
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.3s ease;
|
|
|
|
|
+ -webkit-tap-highlight-color: transparent;
|
|
|
|
|
+ user-select: none;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.toggle-btn:active {
|
|
|
|
|
+ transform: scale(0.95);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.toggle-btn .icon {
|
|
|
|
|
+ width: 24px;
|
|
|
|
|
+ height: 24px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.toggle-btn .drag-icon {
|
|
|
|
|
+ font-size: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.quick-menu {
|
|
|
|
|
+ position: fixed;
|
|
|
|
|
+ right: 70px;
|
|
|
|
|
+ background: rgba(0, 0, 0, 0.9);
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ padding: 8px 0;
|
|
|
|
|
+ min-width: 150px;
|
|
|
|
|
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
|
|
|
|
+ backdrop-filter: blur(10px);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.menu-item {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 10px 16px;
|
|
|
|
|
+ text-align: left;
|
|
|
|
|
+ background: none;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: background 0.2s;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.menu-item:hover {
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.1);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.menu-item.close {
|
|
|
|
|
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
|
|
+ margin-top: 8px;
|
|
|
|
|
+ padding-top: 12px;
|
|
|
|
|
+ color: #ff6b6b;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 过渡动画 */
|
|
|
|
|
+.menu-enter-active,
|
|
|
|
|
+.menu-leave-active {
|
|
|
|
|
+ transition: all 0.3s ease;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.menu-enter-from,
|
|
|
|
|
+.menu-leave-to {
|
|
|
|
|
+ opacity: 0;
|
|
|
|
|
+ transform: translateX(20px);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 移动端适配 */
|
|
|
|
|
+@media (max-width: 768px) {
|
|
|
|
|
+ .toggle-btn {
|
|
|
|
|
+ width: 45px;
|
|
|
|
|
+ height: 45px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .quick-menu {
|
|
|
|
|
+ right: 10px;
|
|
|
|
|
+ top: 60px;
|
|
|
|
|
+ left: auto;
|
|
|
|
|
+ min-width: 140px;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|