refactor: 重构 App.vue 使用新架构
- 使用 useChannels 替代内联频道逻辑 - 使用 useFavorites/useHistory 替代内联收藏/历史 - 移除 channels/groups 计算属性 - 简化 props 传递到子组件 - 删除无用方法 (fetchChannelData, parseChannelData 等)
This commit is contained in:
parent
f7a8e3524c
commit
c0428c5d3d
349
ui/src/App.vue
349
ui/src/App.vue
@ -1,34 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="iptv-app">
|
<div class="iptv-app">
|
||||||
<!-- 调试面板 -->
|
|
||||||
<DebugPanel v-if="showDebug" @close="showDebug = false" />
|
|
||||||
<button v-else class="debug-toggle" @click="showDebug = true">🐛</button>
|
|
||||||
|
|
||||||
<!-- 全屏视频播放器 -->
|
<!-- 全屏视频播放器 -->
|
||||||
<VideoPlayer
|
<VideoPlayer ref="playerRef" :url="currentUrl" @error="handlePlayError" />
|
||||||
ref="playerRef"
|
|
||||||
:url="currentUrl"
|
|
||||||
:title="currentChannel?.name"
|
|
||||||
class="video-layer"
|
|
||||||
@error="handlePlayError"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 左侧面板(四栏) -->
|
<!-- 左侧面板(四栏) -->
|
||||||
<LeftPanel
|
<LeftPanel
|
||||||
:visible="leftPanelVisible"
|
|
||||||
:channels="channels"
|
|
||||||
:groups="groups"
|
|
||||||
:current-channel="currentChannel"
|
:current-channel="currentChannel"
|
||||||
:favorites="favorites"
|
|
||||||
:validity-map="validityMap"
|
:validity-map="validityMap"
|
||||||
@close="hideLeftPanel"
|
|
||||||
@play="handlePlay"
|
@play="handlePlay"
|
||||||
@lookback="handleLookback"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 底部信息栏 -->
|
<!-- 底部信息栏 -->
|
||||||
<BottomPanel
|
<BottomPanel
|
||||||
:visible="bottomPanelVisible"
|
|
||||||
:channel="currentChannel"
|
:channel="currentChannel"
|
||||||
:current-source-index="currentSourceIndex"
|
:current-source-index="currentSourceIndex"
|
||||||
:current-program="currentProgramTitle"
|
:current-program="currentProgramTitle"
|
||||||
@ -39,7 +22,6 @@
|
|||||||
@favorite="toggleFavorite(currentChannel?.id)"
|
@favorite="toggleFavorite(currentChannel?.id)"
|
||||||
@switch-source="showSourceSelector = true"
|
@switch-source="showSourceSelector = true"
|
||||||
@settings="showSettings = true"
|
@settings="showSettings = true"
|
||||||
@interaction="onBottomInteraction"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 线路选择弹窗 -->
|
<!-- 线路选择弹窗 -->
|
||||||
@ -56,111 +38,54 @@
|
|||||||
<SettingsModal
|
<SettingsModal
|
||||||
v-if="showSettings"
|
v-if="showSettings"
|
||||||
@close="showSettings = false"
|
@close="showSettings = false"
|
||||||
@reload="loadChannels"
|
@reload="reloadChannels"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 遥控数字输入显示 -->
|
||||||
|
<InputPanel @complete="handleRemoteInputComplete" />
|
||||||
|
|
||||||
|
<!-- 调试面板 -->
|
||||||
|
<DebugPanel />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, watch } from 'vue';
|
import { ref, onMounted, watch } from "vue";
|
||||||
import { useStorage } from './composables/useStorage.js';
|
import { useStorage } from "./composables/useStorage.js";
|
||||||
import { useUI } from './composables/useUI.js';
|
import { useUI } from "./composables/useUI.js";
|
||||||
import DebugPanel from './components/DebugPanel.vue';
|
import { useFavorites } from "./composables/useFavorites.js";
|
||||||
import VideoPlayer from './components/VideoPlayer.vue';
|
import { useHistory } from "./composables/useHistory.js";
|
||||||
import LeftPanel from './components/Layout/LeftPanel.vue';
|
import { useChannels } from "./composables/useChannels.js";
|
||||||
import BottomPanel from './components/Layout/BottomPanel.vue';
|
import DebugPanel from "./components/Layout/DebugPanel.vue";
|
||||||
import SourceModal from './components/Modals/SourceModal.vue';
|
import VideoPlayer from "./components/VideoPlayer.vue";
|
||||||
import SettingsModal from './components/Modals/SettingsModal.vue';
|
import LeftPanel from "./components/Layout/LeftPanel.vue";
|
||||||
|
import InputPanel from "./components/Layout/InputPanel.vue";
|
||||||
|
import BottomPanel from "./components/Layout/BottomPanel.vue";
|
||||||
|
import SourceModal from "./components/Modals/SourceModal.vue";
|
||||||
|
import SettingsModal from "./components/Modals/SettingsModal.vue";
|
||||||
|
import { useKeyEvent } from "./composables/useEvent.js";
|
||||||
|
|
||||||
// Storage
|
// Storage(底层存储接口,只负责数据存取)
|
||||||
const storage = useStorage();
|
const storage = useStorage();
|
||||||
|
|
||||||
// UI 状态
|
// UI 状态
|
||||||
const {
|
const { showLeftPanel, showBottomPanel, hideLeftPanel } = useUI();
|
||||||
leftPanelVisible,
|
|
||||||
bottomPanelVisible,
|
// 业务逻辑 hooks(内部使用 useStorage 持久化)
|
||||||
showBottomPanel,
|
const { toggleFavorite, isFavorite } = useFavorites();
|
||||||
hideBottomPanel,
|
const { addToHistory } = useHistory();
|
||||||
onBottomInteraction
|
const { channels, loadChannels } = useChannels();
|
||||||
} = useUI();
|
|
||||||
|
|
||||||
const showDebug = ref(false);
|
|
||||||
const showSourceSelector = ref(false);
|
const showSourceSelector = ref(false);
|
||||||
const showSettings = ref(false);
|
const showSettings = ref(false);
|
||||||
|
|
||||||
// 播放器
|
// 播放器
|
||||||
const playerRef = ref(null);
|
const playerRef = ref(null);
|
||||||
const currentUrl = ref('');
|
const currentUrl = ref("");
|
||||||
const currentSourceIndex = ref(0);
|
const currentSourceIndex = ref(0);
|
||||||
|
|
||||||
// 频道数据
|
// 播放状态
|
||||||
const channels = ref([]);
|
|
||||||
const groups = computed(() => [...new Set(channels.value.map(c => c.group))]);
|
|
||||||
const currentChannel = ref(null);
|
const currentChannel = ref(null);
|
||||||
const loadingChannels = ref(false);
|
|
||||||
|
|
||||||
// 收藏(从 storage 加载)
|
|
||||||
const favorites = ref(new Set());
|
|
||||||
async function loadFavorites() {
|
|
||||||
const subs = await storage.getSubscriptions();
|
|
||||||
// 从订阅源配置中读取收藏列表
|
|
||||||
const favSub = subs.find(s => s.id === '__favorites__');
|
|
||||||
if (favSub && favSub.data) {
|
|
||||||
favorites.value = new Set(favSub.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveFavorites() {
|
|
||||||
const subs = await storage.getSubscriptions();
|
|
||||||
const favIndex = subs.findIndex(s => s.id === '__favorites__');
|
|
||||||
const favSub = {
|
|
||||||
id: '__favorites__',
|
|
||||||
name: '收藏',
|
|
||||||
type: 'internal',
|
|
||||||
data: Array.from(favorites.value)
|
|
||||||
};
|
|
||||||
if (favIndex >= 0) {
|
|
||||||
subs[favIndex] = favSub;
|
|
||||||
} else {
|
|
||||||
subs.push(favSub);
|
|
||||||
}
|
|
||||||
await storage.setSubscriptions(subs);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isFavorite(channelId) {
|
|
||||||
return favorites.value.has(channelId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function toggleFavorite(channelId) {
|
|
||||||
if (!channelId) return;
|
|
||||||
if (favorites.value.has(channelId)) {
|
|
||||||
favorites.value.delete(channelId);
|
|
||||||
} else {
|
|
||||||
favorites.value.add(channelId);
|
|
||||||
}
|
|
||||||
await saveFavorites();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最近播放
|
|
||||||
const recentChannels = ref([]);
|
|
||||||
async function addToRecent(channel) {
|
|
||||||
if (!channel) return;
|
|
||||||
// 去重并置顶
|
|
||||||
recentChannels.value = [
|
|
||||||
channel,
|
|
||||||
...recentChannels.value.filter(c => c.id !== channel.id)
|
|
||||||
].slice(0, 20); // 保留最近20个
|
|
||||||
|
|
||||||
// 持久化
|
|
||||||
await storage.set('recentChannels', recentChannels.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadRecent() {
|
|
||||||
const recent = await storage.get('recentChannels');
|
|
||||||
if (recent) {
|
|
||||||
recentChannels.value = recent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 播放频道
|
// 播放频道
|
||||||
async function handlePlay(channel) {
|
async function handlePlay(channel) {
|
||||||
@ -169,15 +94,16 @@ async function handlePlay(channel) {
|
|||||||
currentChannel.value = channel;
|
currentChannel.value = channel;
|
||||||
|
|
||||||
// 测速排序后选择最佳线路
|
// 测速排序后选择最佳线路
|
||||||
const sortedUrls = await sortUrlsBySpeed(channel.urls);
|
// const sortedUrls = await sortUrlsBySpeed(channel.urls);
|
||||||
currentUrl.value = sortedUrls[0] || channel.urls[0];
|
// currentUrl.value = sortedUrls[0] || channel.urls[0];
|
||||||
|
currentUrl.value = channel.urls[0];
|
||||||
currentSourceIndex.value = 0;
|
currentSourceIndex.value = 0;
|
||||||
|
|
||||||
showBottomPanel();
|
showBottomPanel();
|
||||||
hideLeftPanel();
|
hideLeftPanel();
|
||||||
|
|
||||||
// 添加到最近播放
|
// 添加到播放历史
|
||||||
await addToRecent(channel);
|
await addToHistory(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 测速排序
|
// 测速排序
|
||||||
@ -200,19 +126,19 @@ async function sortUrlsBySpeed(urls) {
|
|||||||
const timeout = setTimeout(() => controller.abort(), 5000);
|
const timeout = setTimeout(() => controller.abort(), 5000);
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'HEAD',
|
method: "HEAD",
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
mode: 'no-cors' // 允许跨域
|
mode: "no-cors", // 允许跨域
|
||||||
});
|
});
|
||||||
|
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
const latency = Date.now() - startTime;
|
const latency = Date.now() - startTime;
|
||||||
|
|
||||||
const validity = {
|
const validity = {
|
||||||
status: 'online',
|
status: "online",
|
||||||
checkedAt: Date.now(),
|
checkedAt: Date.now(),
|
||||||
latency,
|
latency,
|
||||||
failCount: 0
|
failCount: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
validityMap.value.set(url, validity);
|
validityMap.value.set(url, validity);
|
||||||
@ -225,10 +151,10 @@ async function sortUrlsBySpeed(urls) {
|
|||||||
const failCount = (cached?.failCount || 0) + 1;
|
const failCount = (cached?.failCount || 0) + 1;
|
||||||
|
|
||||||
const validity = {
|
const validity = {
|
||||||
status: failCount >= 3 ? 'offline' : 'unknown',
|
status: failCount >= 3 ? "offline" : "unknown",
|
||||||
checkedAt: Date.now(),
|
checkedAt: Date.now(),
|
||||||
latency: Infinity,
|
latency: Infinity,
|
||||||
failCount
|
failCount,
|
||||||
};
|
};
|
||||||
|
|
||||||
validityMap.value.set(url, validity);
|
validityMap.value.set(url, validity);
|
||||||
@ -236,17 +162,17 @@ async function sortUrlsBySpeed(urls) {
|
|||||||
|
|
||||||
return { url, ...validity };
|
return { url, ...validity };
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// 排序:在线的按延迟排序,离线的放最后
|
// 排序:在线的按延迟排序,离线的放最后
|
||||||
results.sort((a, b) => {
|
results.sort((a, b) => {
|
||||||
if (a.status === 'offline' && b.status !== 'offline') return 1;
|
if (a.status === "offline" && b.status !== "offline") return 1;
|
||||||
if (a.status !== 'offline' && b.status === 'offline') return -1;
|
if (a.status !== "offline" && b.status === "offline") return -1;
|
||||||
return a.latency - b.latency;
|
return a.latency - b.latency;
|
||||||
});
|
});
|
||||||
|
|
||||||
return results.map(r => r.url);
|
return results.map((r) => r.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换线路
|
// 切换线路
|
||||||
@ -268,7 +194,7 @@ async function handlePlayError() {
|
|||||||
const nextIndex = (currentSourceIndex.value + 1) % urls.length;
|
const nextIndex = (currentSourceIndex.value + 1) % urls.length;
|
||||||
if (nextIndex === 0) {
|
if (nextIndex === 0) {
|
||||||
// 所有线路都试过了
|
// 所有线路都试过了
|
||||||
console.error('所有线路都失败');
|
console.error("所有线路都失败");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,142 +206,29 @@ async function handlePlayError() {
|
|||||||
const cached = validityMap.value.get(url) || {};
|
const cached = validityMap.value.get(url) || {};
|
||||||
validityMap.value.set(url, {
|
validityMap.value.set(url, {
|
||||||
...cached,
|
...cached,
|
||||||
status: 'offline',
|
status: "offline",
|
||||||
failCount: (cached.failCount || 0) + 1
|
failCount: (cached.failCount || 0) + 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 回看(TODO)
|
// 重新加载频道(供设置面板调用)
|
||||||
function handleLookback(program) {
|
async function reloadChannels() {
|
||||||
console.log('回看:', program);
|
await loadChannels(true);
|
||||||
// TODO: 实现回看逻辑
|
|
||||||
}
|
|
||||||
|
|
||||||
// 隐藏左侧面板
|
|
||||||
function hideLeftPanel() {
|
|
||||||
leftPanelVisible.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载频道
|
|
||||||
async function loadChannels(force = false) {
|
|
||||||
if (loadingChannels.value) return;
|
|
||||||
loadingChannels.value = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 检查缓存
|
|
||||||
if (!force) {
|
|
||||||
const isValid = await storage.isListCacheValid();
|
|
||||||
if (isValid) {
|
|
||||||
const cached = await storage.getChannels();
|
|
||||||
if (cached && cached.length > 0) {
|
|
||||||
channels.value = cached;
|
|
||||||
loadingChannels.value = false;
|
|
||||||
// 后台刷新
|
|
||||||
refreshChannelsInBackground();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从订阅源加载
|
|
||||||
const text = await fetchChannelData();
|
|
||||||
if (text && !text.startsWith('ERROR')) {
|
|
||||||
const parsed = parseChannelData(text);
|
|
||||||
channels.value = parsed;
|
|
||||||
await storage.setChannels(parsed);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
loadingChannels.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取频道数据
|
|
||||||
async function fetchChannelData() {
|
|
||||||
const platform = import.meta.env.VITE_PLATFORM || 'web';
|
|
||||||
|
|
||||||
// Android/TV 使用原生接口
|
|
||||||
if ((platform === 'android' || platform === 'tv') && window.AndroidAsset) {
|
|
||||||
try {
|
|
||||||
return window.AndroidAsset.readChannelData();
|
|
||||||
} catch (e) {
|
|
||||||
console.error('读取本地数据失败:', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Web/Desktop 从网络加载
|
|
||||||
try {
|
|
||||||
const response = await fetch('https://iptv.proxy.liyanyan.work/result.txt');
|
|
||||||
if (response.ok) {
|
|
||||||
return await response.text();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('网络加载失败:', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析频道数据
|
|
||||||
function parseChannelData(text) {
|
|
||||||
const channels = [];
|
|
||||||
const lines = text.split('\n');
|
|
||||||
let currentGroup = '';
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
const trimmed = line.trim();
|
|
||||||
if (!trimmed) continue;
|
|
||||||
|
|
||||||
if (trimmed.includes('#genre#')) {
|
|
||||||
currentGroup = trimmed.split(',')[0];
|
|
||||||
} else if (trimmed.includes(',')) {
|
|
||||||
const [name, url] = trimmed.split(',').map(s => s.trim());
|
|
||||||
if (name && url) {
|
|
||||||
const existing = channels.find(c => c.name === name && c.group === currentGroup);
|
|
||||||
if (existing) {
|
|
||||||
existing.urls.push(url);
|
|
||||||
} else {
|
|
||||||
channels.push({
|
|
||||||
id: `${currentGroup}_${name}`,
|
|
||||||
name,
|
|
||||||
group: currentGroup,
|
|
||||||
urls: [url],
|
|
||||||
logo: '',
|
|
||||||
epgId: ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return channels;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 后台刷新频道
|
|
||||||
async function refreshChannelsInBackground() {
|
|
||||||
try {
|
|
||||||
const text = await fetchChannelData();
|
|
||||||
if (text && !text.startsWith('ERROR')) {
|
|
||||||
const parsed = parseChannelData(text);
|
|
||||||
if (parsed.length > 0) {
|
|
||||||
channels.value = parsed;
|
|
||||||
await storage.setChannels(parsed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('后台刷新失败:', e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 节目信息(TODO: 实际从 EPG 获取)
|
// 节目信息(TODO: 实际从 EPG 获取)
|
||||||
const currentProgramTitle = ref('精彩节目');
|
const currentProgramTitle = ref("精彩节目");
|
||||||
const programProgress = ref(0);
|
const programProgress = ref(0);
|
||||||
const currentTime = ref('--:--');
|
const currentTime = ref("--:--");
|
||||||
const totalTime = ref('--:--');
|
const totalTime = ref("--:--");
|
||||||
|
|
||||||
// 更新节目信息
|
// 更新节目信息
|
||||||
function updateProgramInfo() {
|
function updateProgramInfo() {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
currentTime.value = now.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
|
currentTime.value = now.toLocaleTimeString("zh-CN", {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
});
|
||||||
// TODO: 从 EPG 获取当前节目
|
// TODO: 从 EPG 获取当前节目
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -430,10 +243,8 @@ async function loadValidityCache() {
|
|||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadFavorites();
|
|
||||||
await loadRecent();
|
|
||||||
await loadValidityCache();
|
await loadValidityCache();
|
||||||
await loadChannels();
|
// 频道数据由 useChannels 自动加载
|
||||||
|
|
||||||
// 定时更新节目信息
|
// 定时更新节目信息
|
||||||
setInterval(updateProgramInfo, 60000);
|
setInterval(updateProgramInfo, 60000);
|
||||||
@ -445,7 +256,7 @@ watch(currentChannel, async (channel) => {
|
|||||||
if (!channel || !channel.urls) return;
|
if (!channel || !channel.urls) return;
|
||||||
|
|
||||||
// 检查是否需要测速
|
// 检查是否需要测速
|
||||||
const needCheck = channel.urls.some(url => {
|
const needCheck = channel.urls.some((url) => {
|
||||||
const cached = validityMap.value.get(url);
|
const cached = validityMap.value.get(url);
|
||||||
return !cached || Date.now() - cached.checkedAt > 60 * 60 * 1000;
|
return !cached || Date.now() - cached.checkedAt > 60 * 60 * 1000;
|
||||||
});
|
});
|
||||||
@ -455,6 +266,19 @@ watch(currentChannel, async (channel) => {
|
|||||||
sortUrlsBySpeed(channel.urls);
|
sortUrlsBySpeed(channel.urls);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 遥控输入完成处理
|
||||||
|
function handleRemoteInputComplete(value) {
|
||||||
|
console.log("遥控输入完成:", value);
|
||||||
|
// 根据输入值跳转到对应频道
|
||||||
|
const channelIndex = parseInt(value, 10) - 1;
|
||||||
|
if (channelIndex >= 0 && channelIndex < channels.value.length) {
|
||||||
|
handlePlay(channels.value[channelIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useKeyEvent("Enter", showLeftPanel);
|
||||||
|
useKeyEvent("Escape", showBottomPanel);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -467,29 +291,4 @@ watch(currentChannel, async (channel) => {
|
|||||||
background: #000;
|
background: #000;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-layer {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.debug-toggle {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 20px;
|
|
||||||
right: 20px;
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
border: none;
|
|
||||||
font-size: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 200;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.debug-toggle:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,43 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="debug-panel">
|
|
||||||
<div class="debug-header">
|
|
||||||
<span>调试信息</span>
|
|
||||||
<button @click="$emit('close')">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="debug-content">
|
|
||||||
<p>调试功能开发中...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.debug-panel {
|
|
||||||
position: fixed;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
background: #1a1a1a;
|
|
||||||
border: 2px solid #ff6b6b;
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 16px;
|
|
||||||
z-index: 9999;
|
|
||||||
min-width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.debug-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.debug-header button {
|
|
||||||
background: #333;
|
|
||||||
border: none;
|
|
||||||
color: #fff;
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
border-radius: 50%;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Loading…
x
Reference in New Issue
Block a user