2026-01-18 10:24:35 +08:00
< script setup >
import { ref , computed , onMounted , onUnmounted , nextTick , watch } from 'vue'
const activeTab = ref ( 'elements' ) // 默认改为 Elements,匹配截图
const hoverInfo = ref ( '' )
const isDark = ref ( false )
const isAutoPlaying = ref ( false )
const cursorX = ref ( 0 )
const cursorY = ref ( 0 )
const cursorVisible = ref ( false )
const highlightVisible = ref ( false )
const highlightStyle = ref ( { } )
let tourTimeout = null
const demoRef = ref ( null )
// 导览选项
const tourOptions = [
{ value : '' , label : '选择导览场景...' , disabled : true } ,
{ value : 'elements' , label : '1. 元素面板 (Elements)' } ,
{ value : 'console' , label : '2. 控制台 (Console)' } ,
{ value : 'sources' , label : '3. 源代码 (Sources)' } ,
{ value : 'network' , label : '4. 网络 (Network)' } ,
{ value : 'application' , label : '5. 应用 (Application)' }
]
const selectedTour = ref ( '' )
const tabs = [
2026-01-18 12:21:49 +08:00
{
id : 'elements' ,
label : '元素' ,
desc : '查看和修改页面 HTML 结构与 CSS 样式'
} ,
{
id : 'console' ,
label : '控制台' ,
desc : '查看日志、错误信息,执行 JavaScript 代码'
} ,
{
id : 'sources' ,
label : '源代码/来源' ,
desc : '查看源代码,设置断点调试 JavaScript'
} ,
{
id : 'network' ,
label : '网络' ,
desc : '监控网络请求,查看接口数据和加载性能'
} ,
2026-01-18 10:24:35 +08:00
{ id : 'performance' , label : '性能' , desc : '分析页面运行性能' } ,
{ id : 'memory' , label : '内存' , desc : '检测内存泄漏' } ,
2026-01-18 12:21:49 +08:00
{
id : 'application' ,
label : '应用' ,
desc : '查看本地存储(Storage)、Cookies、缓存等'
} ,
2026-01-18 10:24:35 +08:00
{ id : 'security' , label : '隐私与安全' , desc : '查看证书和安全问题' } ,
{ id : 'lighthouse' , label : 'Lighthouse' , desc : '页面质量审计' } ,
{ id : 'recorder' , label : '记录器' , desc : '录制用户操作' }
]
// --- Console Data ---
const consoleSidebarItems = [
{ label : '6 条消息' , icon : 'list' , count : 6 , type : 'all' } ,
{ label : '6 条用户消息' , icon : 'user' , count : 6 , type : 'user' } ,
{ label : '无错误' , icon : 'error' , count : 0 , type : 'error' } ,
{ label : '无警告' , icon : 'warn' , count : 0 , type : 'warn' } ,
{ label : '无信息' , icon : 'info' , count : 0 , type : 'info' } ,
{ label : '6 条详细消息' , icon : 'verbose' , count : 6 , type : 'verbose' }
]
const consoleLogs = ref ( [
{ type : 'log' , msg : '[vite] connecting...' , source : 'client:733' } ,
{ type : 'log' , msg : '[vite] connected.' , source : 'client:827' } ,
2026-01-18 12:21:49 +08:00
{
type : 'log' ,
msg : 'Config Layers for 404.md:\n========================\n1. locale config (root)\n2. .vitepress/config (root)' ,
source : 'shared.js:194'
} ,
{
type : 'log' ,
msg : 'Config Layers for zh-cn/appendix/browser-devtools/index.md:\n=======================================================\n1. locale config (zh-cn)\n2. .vitepress/config (root)' ,
source : 'shared.js:194'
} ,
{
type : 'log' ,
msg : '[vite] hot updated: .vitepress/theme/components/appendix/browser-devtools/BrowserDevToolsDemo.vue' ,
source : 'client:810'
} ,
{
type : 'log' ,
msg : '[vite] hot updated: .vitepress/theme/components/appendix/browser-devtools/BrowserDevToolsDemo.vue?vue&type=style&index=0&scoped=d906459f&lang.css' ,
source : 'client:810'
}
2026-01-18 10:24:35 +08:00
] )
const consoleInput = ref ( '' )
// --- Elements Data (Aligned with screenshot) ---
const domTree = ref ( [
2026-01-18 12:21:49 +08:00
{
tag : 'html' ,
2026-01-18 10:24:35 +08:00
attrs : { class : 'mac' , lang : 'zh-CN' , dir : 'ltr' } ,
expanded : true ,
children : [
2026-01-18 12:21:49 +08:00
{
tag : 'head' ,
2026-01-18 10:24:35 +08:00
expanded : false ,
2026-01-18 12:21:49 +08:00
children : [ { tag : 'title' , text : 'DevTools Demo' } ]
2026-01-18 10:24:35 +08:00
} ,
2026-01-18 12:21:49 +08:00
{
tag : 'body' ,
2026-01-18 10:24:35 +08:00
expanded : true ,
children : [
2026-01-18 12:21:49 +08:00
{
tag : 'div' ,
attrs : { id : 'app' , 'data-v-app' : '' } ,
2026-01-18 10:24:35 +08:00
expanded : true ,
children : [
2026-01-18 12:21:49 +08:00
{ tag : 'div' , text : '' } ,
{
tag : 'script' ,
attrs : {
type : 'module' ,
src : '/easy-vibe/node_modules/vitepress/dist/client/app/index.js'
}
} ,
{
tag : 'div' ,
attrs : { id : 'el-popper-container-3083' } ,
text : ''
}
2026-01-18 10:24:35 +08:00
]
} ,
2026-01-18 12:21:49 +08:00
{
tag : 'div' ,
attrs : { style : 'all: initial' } ,
expanded : false ,
children : [ ]
} ,
{
tag : 'div' ,
attrs : {
id : 'immersive-translate-browser-popup' ,
style : 'all: initial'
} ,
expanded : false ,
children : [ ]
}
2026-01-18 10:24:35 +08:00
]
}
]
}
] )
const cssRules = ref ( [
2026-01-18 12:21:49 +08:00
{
selector : 'body' ,
styles : {
'background-color' : 'rgb(255, 255, 255)' ,
color : '#24292f' ,
margin : '0' ,
'font-family' : '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto'
}
} ,
{ selector : '#app' , styles : { padding : '20px' } } ,
2026-01-18 10:24:35 +08:00
{ selector : '.mac' , styles : { 'font-synthesis' : 'none' } }
] )
// --- Sources Data ---
const fileSystem = ref ( [
{ name : 'index.html' , type : 'file' } ,
2026-01-18 12:21:49 +08:00
{
name : 'src' ,
type : 'folder' ,
expanded : true ,
children : [
2026-01-18 10:24:35 +08:00
{ name : 'main.js' , type : 'file' } ,
{ name : 'App.vue' , type : 'file' } ,
{ name : 'utils.js' , type : 'file' }
2026-01-18 12:21:49 +08:00
]
}
2026-01-18 10:24:35 +08:00
] )
const activeFile = ref ( 'main.js' )
const fileContent = ref ( ` import { createApp } from 'vue'
import App from './App.vue'
console.log('App mounted successfully.')
const app = createApp(App)
app.mount('#app') ` )
// --- Network Data ---
const networkRequests = ref ( [
2026-01-18 12:21:49 +08:00
{
id : 1 ,
name : 'index.html' ,
status : 200 ,
type : 'document' ,
size : '2.4kB' ,
time : '120ms' ,
waterfall : 10 ,
headers : { 'Content-Type' : 'text/html; charset=utf-8' , Server : 'Vite' } ,
requestHeaders : {
'User-Agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)' ,
Accept : 'text/html'
} ,
preview :
'<!DOCTYPE html>\n<html lang="zh-CN">\n<head>...</head>\n<body>...</body>\n</html>'
2026-01-18 10:24:35 +08:00
} ,
2026-01-18 12:21:49 +08:00
{
id : 2 ,
name : 'main.js' ,
status : 200 ,
type : 'script' ,
size : '15.2kB' ,
time : '80ms' ,
waterfall : 40 ,
headers : {
'Content-Type' : 'application/javascript' ,
'Cache-Control' : 'no-cache'
} ,
requestHeaders : {
'User-Agent' : 'Mozilla/5.0 ...' ,
Referer : 'http://localhost:3000/'
} ,
preview :
'import { createApp } from "vue";\nimport App from "./App.vue";\ncreateApp(App).mount("#app");'
2026-01-18 10:24:35 +08:00
} ,
2026-01-18 12:21:49 +08:00
{
id : 3 ,
name : 'style.css' ,
status : 200 ,
type : 'stylesheet' ,
size : '4.1kB' ,
time : '45ms' ,
waterfall : 50 ,
2026-01-18 10:24:35 +08:00
headers : { 'Content-Type' : 'text/css' } ,
2026-01-18 12:21:49 +08:00
requestHeaders : {
'User-Agent' : 'Mozilla/5.0 ...' ,
Referer : 'http://localhost:3000/'
} ,
preview :
'body { margin: 0; font-family: sans-serif; }\n#app { padding: 20px; }'
2026-01-18 10:24:35 +08:00
} ,
2026-01-18 12:21:49 +08:00
{
id : 4 ,
name : 'api/user' ,
status : 200 ,
type : 'fetch' ,
size : '500B' ,
time : '200ms' ,
waterfall : 120 ,
2026-01-18 10:24:35 +08:00
headers : { 'Content-Type' : 'application/json' } ,
2026-01-18 12:21:49 +08:00
requestHeaders : {
Authorization : 'Bearer eyJhbGci...' ,
Accept : 'application/json' ,
'Content-Type' : 'application/json'
} ,
preview :
'{\n "id": 1001,\n "username": "developer",\n "role": "admin",\n "permissions": ["read", "write"]\n}'
2026-01-18 10:24:35 +08:00
} ,
2026-01-18 12:21:49 +08:00
{
id : 5 ,
name : 'logo.png' ,
status : 304 ,
type : 'png' ,
size : '12kB' ,
time : '20ms' ,
waterfall : 60 ,
2026-01-18 10:24:35 +08:00
headers : { 'Content-Type' : 'image/png' } ,
2026-01-18 12:21:49 +08:00
requestHeaders : {
'User-Agent' : 'Mozilla/5.0 ...' ,
Accept : 'image/webp,image/apng'
} ,
2026-01-18 10:24:35 +08:00
preview : '[Binary Data - Image]'
}
] )
const selectedRequest = ref ( null )
const activeDetailTab = ref ( 'headers' )
const selectRequest = ( req ) => {
if ( selectedRequest . value && selectedRequest . value . id === req . id ) {
selectedRequest . value = null // Toggle off
} else {
selectedRequest . value = req
activeDetailTab . value = 'headers' // Reset to default tab
}
}
// --- Application Data ---
const localStorageData = ref ( [
{ key : 'theme' , value : 'light' } ,
{ key : 'user_token' , value : 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' } ,
{ key : 'sidebar_collapsed' , value : 'false' }
] )
// --- Actions ---
const showInfo = ( text ) => {
if ( isAutoPlaying . value ) return
hoverInfo . value = text
}
const clearInfo = ( ) => {
if ( isAutoPlaying . value ) return
hoverInfo . value = ''
}
const runConsoleCommand = ( ) => {
if ( ! consoleInput . value . trim ( ) ) return
consoleLogs . value . push ( { type : 'command' , msg : consoleInput . value } )
try {
const val = consoleInput . value
if ( val === '1+1' ) consoleLogs . value . push ( { type : 'result' , msg : '2' } )
2026-01-18 12:21:49 +08:00
else if ( val . includes ( 'alert' ) )
consoleLogs . value . push ( { type : 'result' , msg : 'undefined' } )
else
consoleLogs . value . push ( {
type : 'error' ,
msg : 'ReferenceError: Command not found in mock'
} )
2026-01-18 10:24:35 +08:00
} catch ( e ) {
consoleLogs . value . push ( { type : 'error' , msg : e . message } )
}
consoleInput . value = ''
nextTick ( ( ) => {
const output = demoRef . value ? . querySelector ( '.console-output' )
if ( output ) output . scrollTop = output . scrollHeight
} )
}
// --- Auto Tour Logic ---
const handleTourSelect = async ( ) => {
if ( ! selectedTour . value ) return
const target = selectedTour . value
2026-01-18 12:21:49 +08:00
2026-01-18 10:24:35 +08:00
// 如果已经在播放,先停止
if ( isAutoPlaying . value ) {
stopTour ( )
2026-01-18 12:21:49 +08:00
await new Promise ( ( r ) => setTimeout ( r , 100 ) )
2026-01-18 10:24:35 +08:00
}
2026-01-18 12:21:49 +08:00
2026-01-18 10:24:35 +08:00
// 切换到目标 Tab
activeTab . value = target
2026-01-18 12:21:49 +08:00
2026-01-18 10:24:35 +08:00
// 启动导览
startTour ( target )
}
const moveCursorTo = ( selector , infoText , waitTime = 2000 ) => {
return new Promise ( ( resolve ) => {
const container = demoRef . value
if ( ! container ) return resolve ( )
// Find element
const el = container . querySelector ( selector )
if ( el ) {
const containerRect = container . getBoundingClientRect ( )
const rect = el . getBoundingClientRect ( )
2026-01-18 12:21:49 +08:00
2026-01-18 10:24:35 +08:00
// Calculate center
const targetX = rect . left - containerRect . left + rect . width / 2
const targetY = rect . top - containerRect . top + rect . height / 2
2026-01-18 12:21:49 +08:00
2026-01-18 10:24:35 +08:00
// Move cursor
cursorX . value = targetX
cursorY . value = targetY
cursorVisible . value = true
2026-01-18 12:21:49 +08:00
2026-01-18 10:24:35 +08:00
// Show highlight after a slight delay to simulate travel time
setTimeout ( ( ) => {
if ( ! isAutoPlaying . value ) return resolve ( )
2026-01-18 12:21:49 +08:00
2026-01-18 10:24:35 +08:00
highlightStyle . value = {
2026-01-18 12:21:49 +08:00
top : rect . top - containerRect . top + 'px' ,
left : rect . left - containerRect . left + 'px' ,
2026-01-18 10:24:35 +08:00
width : rect . width + 'px' ,
height : rect . height + 'px'
}
highlightVisible . value = true
hoverInfo . value = infoText
2026-01-18 12:21:49 +08:00
2026-01-18 10:24:35 +08:00
// Wait and then clear
tourTimeout = setTimeout ( ( ) => {
highlightVisible . value = false
resolve ( )
} , waitTime )
} , 500 ) // faster movement
} else {
console . warn ( 'Selector not found:' , selector )
resolve ( ) // Skip if not found
}
} )
}
const stopTour = ( ) => {
isAutoPlaying . value = false
cursorVisible . value = false
highlightVisible . value = false
hoverInfo . value = ''
selectedTour . value = ''
if ( tourTimeout ) clearTimeout ( tourTimeout )
}
const startTour = async ( targetTab ) => {
if ( isAutoPlaying . value ) return
isAutoPlaying . value = true
cursorVisible . value = true
hoverInfo . value = ''
2026-01-18 12:21:49 +08:00
2026-01-18 10:24:35 +08:00
try {
// Dispatch based on target tab
if ( targetTab === 'console' ) await runConsoleTour ( )
else if ( targetTab === 'elements' ) await runElementsTour ( )
else if ( targetTab === 'sources' ) await runSourcesTour ( )
else if ( targetTab === 'network' ) await runNetworkTour ( )
else if ( targetTab === 'application' ) await runApplicationTour ( )
2026-01-18 12:21:49 +08:00
2026-01-18 10:24:35 +08:00
stopTour ( )
} catch ( e ) {
console . error ( e )
stopTour ( )
}
}
const runConsoleTour = async ( ) => {
2026-01-18 12:21:49 +08:00
await moveCursorTo (
'.tab[data-id="console"]' ,
'控制台 (Console):查看日志、交互式运行代码'
)
2026-01-18 10:24:35 +08:00
if ( ! isAutoPlaying . value ) return
2026-01-18 12:21:49 +08:00
await moveCursorTo (
'.console-toolbar' ,
'工具栏:可清空日志、设置 Log 级别、过滤内容'
)
2026-01-18 10:24:35 +08:00
if ( ! isAutoPlaying . value ) return
2026-01-18 12:21:49 +08:00
await moveCursorTo (
'.console-sidebar' ,
'侧边栏:按类型聚合消息 (Errors, Warnings)'
)
2026-01-18 10:24:35 +08:00
if ( ! isAutoPlaying . value ) return
2026-01-18 12:21:49 +08:00
await moveCursorTo (
'.log-line:nth-child(1)' ,
'日志流:显示代码输出,点击右侧链接可跳转源码'
)
2026-01-18 10:24:35 +08:00
if ( ! isAutoPlaying . value ) return
2026-01-18 12:21:49 +08:00
await moveCursorTo (
'.bottom-drawer-header' ,
'抽屉 (Drawer):查看搜索结果、Issues 等辅助信息'
)
2026-01-18 10:24:35 +08:00
if ( ! isAutoPlaying . value ) return
2026-01-18 12:21:49 +08:00
await moveCursorTo (
'.console-input-area' ,
'即时执行:在这里输入 JS 表达式并回车运行'
)
2026-01-18 10:24:35 +08:00
}
const runElementsTour = async ( ) => {
2026-01-18 12:21:49 +08:00
await moveCursorTo (
'.tab[data-id="elements"]' ,
'元素面板 (Elements):实时查看和修改 DOM/CSS'
)
2026-01-18 10:24:35 +08:00
if ( ! isAutoPlaying . value ) return
2026-01-18 12:21:49 +08:00
await moveCursorTo (
'.dom-tree-panel' ,
'DOM 树:页面的 HTML 结构,可折叠/展开/拖拽'
)
2026-01-18 10:24:35 +08:00
if ( ! isAutoPlaying . value ) return
2026-01-18 12:21:49 +08:00
await moveCursorTo (
'.dom-node[data-tag="div"]' ,
'选中元素:点击元素以在右侧查看其样式'
)
2026-01-18 10:24:35 +08:00
if ( ! isAutoPlaying . value ) return
2026-01-18 12:21:49 +08:00
await moveCursorTo (
'.styles-panel' ,
'样式面板 (Styles):查看计算后的样式和 CSS 规则'
)
2026-01-18 10:24:35 +08:00
if ( ! isAutoPlaying . value ) return
2026-01-18 12:21:49 +08:00
await moveCursorTo (
'.style-rule:first-child' ,
'CSS 规则:可直接修改属性值,实时预览效果'
)
2026-01-18 10:24:35 +08:00
}
const runSourcesTour = async ( ) => {
2026-01-18 12:21:49 +08:00
await moveCursorTo (
'.tab[data-id="sources"]' ,
'源代码 (Sources):文件浏览与断点调试'
)
2026-01-18 10:24:35 +08:00
if ( ! isAutoPlaying . value ) return
await moveCursorTo ( '.file-navigator' , '文件系统:查看加载的所有资源文件' )
if ( ! isAutoPlaying . value ) return
await moveCursorTo ( '.code-editor' , '编辑器:查看源码,点击行号设置断点' )
if ( ! isAutoPlaying . value ) return
2026-01-18 12:21:49 +08:00
await moveCursorTo (
'.debugger-sidebar' ,
'调试器:查看变量 (Watch)、调用栈 (Call Stack)'
)
2026-01-18 10:24:35 +08:00
}
const runNetworkTour = async ( ) => {
await moveCursorTo ( '.tab[data-id="network"]' , '网络 (Network):抓包分析' )
if ( ! isAutoPlaying . value ) return
2026-01-18 12:21:49 +08:00
await moveCursorTo (
'.network-toolbar' ,
'过滤器:按类型筛选请求 (XHR/Fetch, CSS, JS)'
)
2026-01-18 10:24:35 +08:00
if ( ! isAutoPlaying . value ) return
2026-01-18 12:21:49 +08:00
await moveCursorTo (
'.network-grid-header' ,
'请求列表:查看状态码、类型、大小、耗时'
)
2026-01-18 10:24:35 +08:00
if ( ! isAutoPlaying . value ) return
2026-01-18 12:21:49 +08:00
2026-01-18 10:24:35 +08:00
// Simulate clicking the API request
await moveCursorTo ( '.network-row:nth-child(4)' , '点击请求行查看详情' )
if ( ! isAutoPlaying . value ) return
2026-01-18 12:21:49 +08:00
2026-01-18 10:24:35 +08:00
// Trigger selection
selectedRequest . value = networkRequests . value [ 3 ] // api/user
2026-01-18 12:21:49 +08:00
await moveCursorTo (
'.detail-header' ,
'详情面板:查看 Headers, Preview, Response'
)
if ( ! isAutoPlaying . value ) return
// 1. Headers Tab
activeDetailTab . value = 'headers'
await moveCursorTo (
'.detail-title:nth-child(1)' ,
'Headers: 查看请求/响应头信息'
)
if ( ! isAutoPlaying . value ) return
await moveCursorTo (
'.detail-section:nth-child(1)' ,
'General:查看 URL、Method (GET/POST) 和状态码 (200)'
)
if ( ! isAutoPlaying . value ) return
await moveCursorTo (
'.detail-section:nth-child(2)' ,
'Response Headers:服务器返回的头信息 (Content-Type)'
)
if ( ! isAutoPlaying . value ) return
await moveCursorTo (
'.detail-section:nth-child(3)' ,
'Request Headers:浏览器发送的头信息 (User-Agent, Cookies)'
)
if ( ! isAutoPlaying . value ) return
// 2. Preview Tab
await moveCursorTo (
'.detail-title:nth-child(2)' ,
'Preview: 格式化预览接口返回的数据'
)
if ( ! isAutoPlaying . value ) return
activeDetailTab . value = 'preview'
await moveCursorTo ( '.preview-content' , 'Preview Content: 查看 JSON 结构' )
if ( ! isAutoPlaying . value ) return
// 3. Response Tab
await moveCursorTo ( '.detail-title:nth-child(3)' , 'Response: 查看原始响应数据' )
if ( ! isAutoPlaying . value ) return
activeDetailTab . value = 'response'
await moveCursorTo ( '.preview-content' , 'Response Body: 原始文本内容' )
if ( ! isAutoPlaying . value ) return
await moveCursorTo (
'.waterfall-cell' ,
'瀑布流 (Waterfall):请求生命周期耗时分析'
)
2026-01-18 10:24:35 +08:00
}
const runApplicationTour = async ( ) => {
2026-01-18 12:21:49 +08:00
await moveCursorTo (
'.tab[data-id="application"]' ,
'应用 (Application):存储与缓存管理'
)
2026-01-18 10:24:35 +08:00
if ( ! isAutoPlaying . value ) return
2026-01-18 12:21:49 +08:00
await moveCursorTo (
'.storage-sidebar' ,
'存储类型:Local Storage, Cookies, IndexedDB'
)
2026-01-18 10:24:35 +08:00
if ( ! isAutoPlaying . value ) return
2026-01-18 12:21:49 +08:00
await moveCursorTo (
'.storage-content' ,
'数据视图:查看 Key-Value 数据,支持增删改查'
)
2026-01-18 10:24:35 +08:00
}
onUnmounted ( ( ) => {
if ( tourTimeout ) clearTimeout ( tourTimeout )
} )
< / script >
< template >
2026-01-18 12:21:49 +08:00
< div
2026-02-18 17:38:10 +08:00
ref = "demoRef"
2026-01-18 12:21:49 +08:00
class = "browser-devtools-demo"
: class = "{ 'dark-mode': isDark }"
>
2026-01-18 10:24:35 +08:00
<!-- Top Controls ( Custom for Demo ) -- >
< div class = "demo-controls" >
2026-02-18 17:38:10 +08:00
< div class = "control-label" >
Chrome DevTools 模拟器
< / div >
2026-01-18 10:24:35 +08:00
< div class = "control-actions" >
2026-01-18 12:21:49 +08:00
< select
v-model = "selectedTour"
class = "tour-select"
:disabled = "isAutoPlaying"
2026-02-18 17:38:10 +08:00
@change ="handleTourSelect"
2026-01-18 12:21:49 +08:00
>
< option
v-for = "opt in tourOptions"
:key = "opt.value"
:value = "opt.value"
:disabled = "opt.disabled"
>
2026-01-18 10:24:35 +08:00
{ { opt . label } }
< / option >
< / select >
2026-02-18 17:38:10 +08:00
< button
v-if = "isAutoPlaying"
class = "stop-btn"
@click ="stopTour"
>
2026-01-18 12:21:49 +08:00
停止演示
< / button >
2026-01-18 10:24:35 +08:00
< / div >
< / div >
<!-- Virtual Cursor & Highlight -- >
2026-01-18 12:21:49 +08:00
< div
v-if = "cursorVisible"
2026-02-18 17:38:10 +08:00
class = "virtual-cursor"
2026-01-18 12:21:49 +08:00
: style = "{ transform: `translate(${cursorX}px, ${cursorY}px)` }"
>
< svg
width = "24"
height = "24"
viewBox = "0 0 24 24"
fill = "none"
style = "filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2))"
>
< path
d = "M5.65376 12.3673H5.46026L5.31717 12.4976L0.500002 16.8829L0.500002 1.19823L11.4818 12.3673H5.65376Z"
fill = "#000"
stroke = "white"
stroke -width = " 1.5 "
/ >
2026-01-18 10:24:35 +08:00
< / svg >
< / div >
2026-01-18 12:21:49 +08:00
< div
v-if = "highlightVisible"
2026-02-18 17:38:10 +08:00
class = "highlight-box"
2026-01-18 12:21:49 +08:00
:style = "highlightStyle"
2026-02-18 17:38:10 +08:00
/ >
2026-01-18 10:24:35 +08:00
<!-- Main UI Container -- >
< div class = "devtools-container" >
<!-- Header -- >
< div class = "devtools-header" >
< div class = "header-left" >
2026-01-18 12:21:49 +08:00
< div
class = "icon-btn element-picker"
title = "选择页面中的元素以进行检查"
>
2026-02-18 17:38:10 +08:00
< svg
width = "16"
height = "16"
viewBox = "0 0 24 24"
fill = "#6e6e6e"
>
2026-01-18 12:21:49 +08:00
< path
d = "M4 4h9v2H4V4zm0 4h5v2H4V8zm0 4h5v2H4v-2zm12-5l-4 4h3v4h2v-4h3l-4-4z"
/ >
< / svg >
2026-01-18 10:24:35 +08:00
< / div >
2026-02-18 17:38:10 +08:00
< div
class = "icon-btn device-toggle"
title = "切换设备工具栏"
>
< svg
width = "16"
height = "16"
viewBox = "0 0 24 24"
fill = "#6e6e6e"
>
2026-01-18 12:21:49 +08:00
< path
d = "M17 1.01L7 1c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM17 19H7V5h10v14z"
/ >
< / svg >
2026-01-18 10:24:35 +08:00
< / div >
2026-02-18 17:38:10 +08:00
< div class = "separator" / >
2026-01-18 10:24:35 +08:00
< div class = "tabs" >
2026-01-18 12:21:49 +08:00
< div
v-for = "tab in tabs"
:key = "tab.id"
class = "tab"
: class = "{ active: activeTab === tab.id }"
:data-id = "tab.id"
@click ="activeTab = tab.id"
@mouseenter ="showInfo(tab.desc)"
@mouseleave ="clearInfo"
>
2026-01-18 10:24:35 +08:00
{ { tab . label } }
< / div >
< / div >
< / div >
< div class = "header-right" >
2026-02-18 17:38:10 +08:00
< div
class = "icon-btn settings"
title = "设置"
>
⚙ ️
< / div >
< div
class = "icon-btn close"
title = "关闭"
>
×
< / div >
2026-01-18 10:24:35 +08:00
< / div >
< / div >
<!-- Body Area -- >
< div class = "devtools-body" >
<!-- 1. Console Panel -- >
2026-02-18 17:38:10 +08:00
< div
v-if = "activeTab === 'console'"
class = "panel console-panel-layout"
>
< div
class = "console-toolbar"
@mouseenter ="showInfo('控制台工具栏')"
>
< div
class = "icon-btn clear"
title = "清除控制台"
>
🚫
< / div >
< div class = "separator" / >
< div class = "dropdown-trigger" >
top ▼
< / div >
< div
class = "icon-btn eye"
title = "创建实时表达式"
>
👁 ️
< / div >
2026-01-18 12:21:49 +08:00
< div class = "filter-box" >
2026-02-18 17:38:10 +08:00
< span class = "filter-icon" > 🔍 < / span > < input placeholder = "过滤" >
< / div >
< div class = "dropdown-trigger" >
默认级别 ▼
2026-01-18 12:21:49 +08:00
< / div >
2026-01-18 10:24:35 +08:00
< / div >
< div class = "console-main-area" >
< div class = "console-sidebar" >
2026-01-18 12:21:49 +08:00
< div
v-for = "(item, idx) in consoleSidebarItems"
:key = "idx"
class = "sidebar-item"
: class = "{ active: item.type === 'all' }"
>
< span class = "item-icon" > { {
item . icon === 'error'
? '❌'
: item . icon === 'warn'
? '⚠️'
: item . icon === 'info'
? 'ℹ ️'
: item . icon === 'verbose'
? '💬'
: item . icon === 'user'
? '👤'
: '📋'
} } < / span >
< span class = "item-label" > { { item . label } } < / span >
< / div >
2026-01-18 10:24:35 +08:00
< / div >
< div class = "console-content-wrapper" >
2026-01-18 12:21:49 +08:00
< div class = "console-output" >
< div
v-for = "(log, idx) in consoleLogs"
:key = "idx"
class = "log-line"
:class = "log.type"
>
< div class = "log-gutter" >
2026-02-18 17:38:10 +08:00
< span
v-if = "log.type === 'error'"
class = "icon error"
> ❌ < / span >
< span
v-else-if = "log.type === 'warn'"
class = "icon warn"
> ⚠ ️ < / span >
< span
v-else-if = "log.type === 'command'"
class = "icon"
> & gt ; < / span >
< span
v-else
class = "icon"
> & lt ; < / span >
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< div class = "log-content" >
< pre > { { log . msg } } < / pre >
< / div >
< div class = "log-source" >
< span class = "source" > { { log . source } } < / span >
< / div >
< / div >
<!-- Input area inside scrollable area for Chrome feel -- >
< div class = "console-input-area" >
< span class = "prompt" > & gt ; < / span >
< input
v-model = "consoleInput"
class = "input-field"
placeholder = ""
2026-02-18 17:38:10 +08:00
@keyup.enter ="runConsoleCommand"
>
2026-01-18 12:21:49 +08:00
< / div >
< / div >
2026-01-18 10:24:35 +08:00
< / div >
< / div >
<!-- Bottom Drawer -- >
< div class = "bottom-drawer" >
< div class = "bottom-drawer-header" >
2026-01-18 12:21:49 +08:00
< div
class = "icon-btn more"
style = "padding: 0 4px; margin-right: 4px"
>
⋮
< / div >
2026-02-18 17:38:10 +08:00
< div class = "drawer-tab" >
控制台
< / div >
< div class = "drawer-tab" >
AI 辅助
< / div >
< div class = "drawer-tab" >
新变化
< / div >
< div class = "drawer-tab" >
问题
< / div >
2026-01-18 12:21:49 +08:00
< div class = "drawer-tab active" >
搜索 < span class = "close-icon" > × < / span >
< / div >
2026-01-18 10:24:35 +08:00
< / div >
< div class = "drawer-content" >
2026-01-18 12:21:49 +08:00
< div class = "search-panel" >
< div class = "search-bar" >
< span class = "prompt" > 🔍 < / span >
< input
placeholder = "A terminal is just a grid of same-sized cells..."
class = "search-input"
2026-02-18 17:38:10 +08:00
>
< div class = "search-actions" >
Aa ab . *
< / div >
2026-01-18 12:21:49 +08:00
< / div >
< div class = "search-results" >
< div class = "no-results" >
2026-02-18 17:38:10 +08:00
< div class = "no-results-title" >
未找到匹配项
< / div >
2026-01-18 12:21:49 +08:00
< div class = "no-results-desc" >
没有与您的搜索查询相符的结果
< / div >
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< / div >
< / div >
2026-01-18 10:24:35 +08:00
< / div >
< / div >
< / div >
<!-- 2. Elements Panel -- >
2026-02-18 17:38:10 +08:00
< div
v-else-if = "activeTab === 'elements'"
class = "panel elements-panel"
>
2026-01-18 10:24:35 +08:00
< div class = "dom-tree-panel" >
< div class = "dom-tree-content" >
2026-02-18 17:38:10 +08:00
< div
class = "dom-node"
data -tag = " html "
>
2026-01-18 12:21:49 +08:00
< div class = "line-content" >
< span class = "arrow expanded" > ▼ < / span >
< span class = "tag-name" > html < / span >
2026-02-18 17:38:10 +08:00
< span class = "attr-name" > class < / span > = < span class = "attr-val" > "mac" < / span >
< span class = "attr-name" > lang < / span > = < span class = "attr-val" > "zh-CN" < / span >
< span class = "attr-name" > dir < / span > = < span class = "attr-val" > "ltr" < / span >
< span class = "attr-name" > style < / span > = < span class = "attr-val" > "--ev-doc-font-size: 14px..." < / span >
2026-01-18 12:21:49 +08:00
< / div >
< div class = "children" >
2026-02-18 17:38:10 +08:00
< div
class = "dom-node"
data -tag = " head "
>
2026-01-18 12:21:49 +08:00
< div class = "line-content" >
< span class = "arrow" > ▶ < / span >
< span class = "tag-name" > head < / span >
< span class = "dots" > ... < / span >
< / div >
< / div >
2026-02-18 17:38:10 +08:00
< div
class = "dom-node"
data -tag = " body "
>
2026-01-18 12:21:49 +08:00
< div class = "line-content" >
< span class = "arrow expanded" > ▼ < / span >
< span class = "tag-name" > body < / span >
< span class = "node-trail" > == $0 < / span >
< / div >
< div class = "children" >
2026-02-18 17:38:10 +08:00
< div
class = "dom-node selected"
data -tag = " div "
>
2026-01-18 12:21:49 +08:00
< div class = "line-content" >
< span class = "arrow expanded" > ▼ < / span >
< span class = "tag-name" > div < / span >
< span class = "attr-name" > id < / span > = < span
class = "attr-val"
2026-02-18 17:38:10 +08:00
> "app" < / span >
2026-01-18 12:21:49 +08:00
< span class = "attr-name" > data - v - app < / span >
< / div >
< div class = "children" >
< div class = "dom-node" >
< div class = "line-content" >
2026-02-18 17:38:10 +08:00
< span class = "indent" / > < span class = "tag-name" > div < / span > < span class = "dots" > ... < / span > < span class = "tag-name" > / div < / span >
2026-01-18 12:21:49 +08:00
< / div >
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< div class = "dom-node" >
< div class = "line-content" >
2026-02-18 17:38:10 +08:00
< span class = "indent" / > < span class = "tag-name" > script < / span >
2026-01-18 12:21:49 +08:00
< span class = "attr-name" > type < / span > = < span
class = "attr-val"
2026-02-18 17:38:10 +08:00
> "module" < / span >
2026-01-18 12:21:49 +08:00
< span class = "attr-name" > src < / span > = < span
class = "attr-val"
2026-02-18 17:38:10 +08:00
> "/easy-vibe/..." < / span > < span class = "tag-name" > / script < / span >
2026-01-18 12:21:49 +08:00
< / div >
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< div class = "dom-node" >
< div class = "line-content" >
2026-02-18 17:38:10 +08:00
< span class = "indent" / > < span class = "tag-name" > div < / span >
2026-01-18 12:21:49 +08:00
< span class = "attr-name" > id < / span > = < span
class = "attr-val"
2026-02-18 17:38:10 +08:00
> "el-popper-container-3083" < / span > < span class = "tag-name" > & gt ; < / span > < span class = "dots" > ... < / span > < span class = "tag-name" > / div < / span >
2026-01-18 12:21:49 +08:00
< / div >
< / div >
< / div >
< div class = "line-content" >
< span class = "tag-name" > / div < / span >
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< / div >
< div class = "dom-node" >
< div class = "line-content" >
< span class = "arrow" > ▶ < / span >
< span class = "tag-name" > div < / span >
< span class = "attr-name" > style < / span > = < span
class = "attr-val"
2026-02-18 17:38:10 +08:00
> "all: initial;" < / span > < span class = "tag-name" > & gt ; < / span > < span class = "dots" > ... < / span > < span class = "tag-name" > / div < / span >
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< / div >
< div class = "dom-node" >
< div class = "line-content" >
< span class = "arrow" > ▶ < / span >
< span class = "tag-name" > div < / span >
< span class = "attr-name" > id < / span > = < span
class = "attr-val"
2026-02-18 17:38:10 +08:00
> "immersive-translate-browser-popup" < / span >
2026-01-18 12:21:49 +08:00
< span class = "attr-name" > style < / span > = < span
class = "attr-val"
2026-02-18 17:38:10 +08:00
> "all: initial;" < / span > < span class = "tag-name" > & gt ; < / span > < span class = "dots" > ... < / span > < span class = "tag-name" > / div < / span >
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< / div >
< / div >
< div class = "line-content" >
< span class = "tag-name" > / body < / span >
< / div >
< / div >
< / div >
< div class = "line-content" >
< span class = "tag-name" > / html < / span >
< / div >
< / div >
2026-01-18 10:24:35 +08:00
< / div >
2026-02-18 17:38:10 +08:00
< div class = "breadcrumbs" >
html . mac > body > div # app
< / div >
2026-01-18 10:24:35 +08:00
<!-- Bottom Drawer ( Shared ) -- >
2026-02-18 17:38:10 +08:00
< div
class = "bottom-drawer"
style = "border-top: 1px solid #ccc"
>
2026-01-18 12:21:49 +08:00
< div class = "bottom-drawer-header" >
< div
class = "icon-btn more"
style = "padding: 0 4px; margin-right: 4px"
>
⋮
< / div >
2026-02-18 17:38:10 +08:00
< div class = "drawer-tab" >
控制台
< / div >
< div class = "drawer-tab" >
AI 辅助
< / div >
< div class = "drawer-tab" >
新变化
< / div >
< div class = "drawer-tab" >
问题
< / div >
2026-01-18 12:21:49 +08:00
< div class = "drawer-tab active" >
搜索 < span class = "close-icon" > × < / span >
< / div >
< / div >
< div class = "drawer-content" >
< div class = "search-panel" >
< div class = "search-bar" >
< span class = "prompt" > 🔍 < / span >
< input
placeholder = "A terminal is just a grid of same-sized cells..."
class = "search-input"
2026-02-18 17:38:10 +08:00
>
< div class = "search-actions" >
Aa ab . *
< / div >
2026-01-18 12:21:49 +08:00
< / div >
< div class = "search-results" >
< div class = "no-results" >
2026-02-18 17:38:10 +08:00
< div class = "no-results-title" >
未找到匹配项
< / div >
2026-01-18 12:21:49 +08:00
< div class = "no-results-desc" >
没有与您的搜索查询相符的结果
< / div >
< / div >
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< / div >
< / div >
2026-01-18 10:24:35 +08:00
< / div >
< / div >
< div class = "styles-panel" >
< div class = "styles-tabs" >
2026-02-18 17:38:10 +08:00
< div class = "style-tab active" >
样式
< / div >
< div class = "style-tab" >
计算样式
< / div >
< div class = "style-tab" >
布局
< / div >
< div class = "style-tab" >
事件监听器
< / div >
< div class = "style-tab" >
»
< / div >
2026-01-18 10:24:35 +08:00
< / div >
< div class = "styles-content" >
2026-01-18 12:21:49 +08:00
<!-- Box Model Mock -- >
< div class = "box-model-container" >
< div class = "box-margin" >
2026-02-18 17:38:10 +08:00
< div class = "label" >
margin
< / div >
< div class = "val-top" >
-
< / div >
< div class = "val-left" >
-
< / div >
< div class = "val-right" >
-
< / div >
< div class = "val-bottom" >
-
< / div >
2026-01-18 12:21:49 +08:00
< div class = "box-border" >
2026-02-18 17:38:10 +08:00
< div class = "label" >
border
< / div >
< div class = "val-top" >
-
< / div >
< div class = "val-left" >
-
< / div >
< div class = "val-right" >
-
< / div >
< div class = "val-bottom" >
-
< / div >
2026-01-18 12:21:49 +08:00
< div class = "box-padding" >
2026-02-18 17:38:10 +08:00
< div class = "label" >
padding
< / div >
< div class = "val-top" >
-
< / div >
< div class = "val-left" >
-
< / div >
< div class = "val-right" >
-
< / div >
< div class = "val-bottom" >
-
< / div >
2026-01-18 12:21:49 +08:00
< div class = "box-content" >
2026-02-18 17:38:10 +08:00
< div class = "val-content" >
1600 x 3461
< / div >
2026-01-18 12:21:49 +08:00
< / div >
< / div >
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< / div >
< / div >
< div class = "filter-bar" >
2026-02-18 17:38:10 +08:00
< input placeholder = "过滤" >
2026-01-18 12:21:49 +08:00
< span class = "filter-opt" > : hov < / span >
< span class = "filter-opt" > . cls < / span >
< span class = "filter-opt" > + < / span >
< / div >
< div
v-for = "(rule, idx) in cssRules"
:key = "idx"
2026-02-18 17:38:10 +08:00
class = "style-rule"
2026-01-18 12:21:49 +08:00
>
2026-02-18 17:38:10 +08:00
< div class = "selector" >
{ { rule . selector } } {
< / div >
2026-01-18 12:21:49 +08:00
< div
v-for = "(val, key) in rule.styles"
:key = "key"
2026-02-18 17:38:10 +08:00
class = "property"
2026-01-18 12:21:49 +08:00
>
2026-02-18 17:38:10 +08:00
< span class = "prop-name" > { { key } } < / span > : < span class = "prop-val" > { { val } } < / span > ;
< / div >
< div class = "selector" >
}
2026-01-18 12:21:49 +08:00
< / div >
< / div >
2026-01-18 10:24:35 +08:00
< / div >
< / div >
< / div >
<!-- 3. Sources Panel -- >
2026-02-18 17:38:10 +08:00
< div
v-else-if = "activeTab === 'sources'"
class = "panel sources-panel"
>
2026-01-18 12:21:49 +08:00
< div class = "file-navigator" >
< div class = "nav-header" >
< span class = "nav-tab active" > Page < / span >
< span class = "nav-tab" > Filesystem < / span >
< / div >
< div class = "file-tree" >
< div class = "file-item file" >
< span class = "icon" > 📄 < / span > index . html
< / div >
< div class = "file-item folder expanded" >
< span class = "folder-icon" > ▼ < / span >
< span class = "icon" > 📁 < / span > src
< div class = "folder-children" >
< div class = "file-item file active" >
< span class = "icon" > 📄 < / span > main . js
< / div >
< div class = "file-item file" >
< span class = "icon" > 📄 < / span > App . vue
< / div >
< / div >
< / div >
< / div >
< / div >
< div class = "code-editor" >
< div class = "editor-tabs" >
< div class = "editor-tab active" >
< span class = "icon" > 📄 < / span > main . js
< span class = "close" > × < / span >
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< / div >
< div class = "editor-content" >
< div class = "line-numbers" >
2026-02-18 17:38:10 +08:00
1 < br > 2 < br > 3 < br > 4 < br > 5 < br > 6
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< div class = "code-text" >
< pre > { { fileContent } } < / pre >
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< / div >
< / div >
< div class = "debugger-sidebar" >
< div class = "debug-section" >
< div class = "section-title" >
< span class = "arrow" > ▼ < / span > Watch
2026-01-18 10:24:35 +08:00
< / div >
2026-02-18 17:38:10 +08:00
< div class = "section-content empty" >
No watch expressions
< / div >
2026-01-18 12:21:49 +08:00
< / div >
< div class = "debug-section" >
< div class = "section-title" >
< span class = "arrow" > ▼ < / span > Breakpoints
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< div class = "section-content" >
2026-02-18 17:38:10 +08:00
< label > < input
type = "checkbox"
checked
> main . js : 12 < / label >
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< / div >
< div class = "debug-section" >
< div class = "section-title" >
< span class = "arrow" > ▼ < / span > Scope
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< / div >
< / div >
2026-01-18 10:24:35 +08:00
< / div >
<!-- 4. Network Panel -- >
2026-02-18 17:38:10 +08:00
< div
v-else-if = "activeTab === 'network'"
class = "panel network-panel"
>
2026-01-18 12:21:49 +08:00
< div class = "network-toolbar" >
2026-02-18 17:38:10 +08:00
< div class = "record-icon" >
🔴
< / div >
< div class = "separator" / >
2026-01-18 12:21:49 +08:00
< span class = "filter-btn active" > All < / span >
< span class = "filter-btn" > Fetch / XHR < / span >
< span class = "filter-btn" > JS < / span >
< span class = "filter-btn" > CSS < / span >
< span class = "filter-btn" > Img < / span >
< / div >
< div class = "network-split-view" >
< div class = "network-grid" >
< div class = "network-grid-header" >
2026-02-18 17:38:10 +08:00
< div class = "col name" >
Name
< / div >
< div class = "col status" >
Status
< / div >
< div class = "col type" >
Type
< / div >
< div class = "col size" >
Size
< / div >
< div class = "col time" >
Time
< / div >
< div class = "col waterfall" >
Waterfall
< / div >
2026-01-18 12:21:49 +08:00
< / div >
< div class = "network-rows" >
< div
v-for = "(req, idx) in networkRequests"
:key = "idx"
2026-02-18 17:38:10 +08:00
class = "network-row"
2026-01-18 12:21:49 +08:00
: class = "{
selected: selectedRequest && selectedRequest.id === req.id
}"
@click ="selectRequest(req)"
>
2026-02-18 17:38:10 +08:00
< div class = "col name" >
{ { req . name } }
< / div >
< div class = "col status" >
{ { req . status } }
< / div >
< div class = "col type" >
{ { req . type } }
< / div >
< div class = "col size" >
{ { req . size } }
< / div >
< div class = "col time" >
{ { req . time } }
< / div >
2026-01-18 12:21:49 +08:00
< div class = "col waterfall" >
< div
class = "waterfall-bar"
: style = "{
width: req.waterfall + 'px',
left: idx * 10 + 'px'
}"
2026-02-18 17:38:10 +08:00
/ >
2026-01-18 12:21:49 +08:00
< / div >
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< / div >
< / div >
<!-- Network Detail Panel ( Right Side ) -- >
2026-02-18 17:38:10 +08:00
< div
v-if = "selectedRequest"
class = "network-detail"
>
2026-01-18 12:21:49 +08:00
< div class = "detail-header" >
< span
class = "detail-title"
: class = "{ active: activeDetailTab === 'headers' }"
@click ="activeDetailTab = 'headers'"
2026-02-18 17:38:10 +08:00
> Headers < / span >
2026-01-18 12:21:49 +08:00
< span
class = "detail-title"
: class = "{ active: activeDetailTab === 'preview' }"
@click ="activeDetailTab = 'preview'"
2026-02-18 17:38:10 +08:00
> Preview < / span >
2026-01-18 12:21:49 +08:00
< span
class = "detail-title"
: class = "{ active: activeDetailTab === 'response' }"
@click ="activeDetailTab = 'response'"
2026-02-18 17:38:10 +08:00
> Response < / span >
< span
class = "close-detail"
@click ="selectedRequest = null"
> × < / span >
2026-01-18 12:21:49 +08:00
< / div >
< div class = "detail-content" >
< div v-if = "activeDetailTab === 'headers'" >
< div class = "detail-section" >
2026-02-18 17:38:10 +08:00
< div class = "section-label" >
General
< / div >
2026-01-18 12:21:49 +08:00
< div class = "detail-row" >
< span class = "key" > Request URL : < / span >
2026-02-18 17:38:10 +08:00
< span class = "val" > http : //localhost:3000/{{ selectedRequest.name }}</span>
2026-01-18 12:21:49 +08:00
< / div >
< div class = "detail-row" >
< span class = "key" > Request Method : < / span >
< span class = "val" > GET < / span >
< / div >
< div class = "detail-row" >
< span class = "key" > Status Code : < / span >
2026-02-18 17:38:10 +08:00
< span class = "val status-code" > { { selectedRequest . status } } OK < / span >
2026-01-18 12:21:49 +08:00
< / div >
< / div >
< div class = "detail-section" >
2026-02-18 17:38:10 +08:00
< div class = "section-label" >
Response Headers
< / div >
2026-01-18 12:21:49 +08:00
< div
v-for = "(val, key) in selectedRequest.headers"
:key = "key"
2026-02-18 17:38:10 +08:00
class = "detail-row"
2026-01-18 12:21:49 +08:00
>
< span class = "key" > { { key } } : < / span >
< span class = "val" > { { val } } < / span >
< / div >
< / div >
< div
v-if = "selectedRequest.requestHeaders"
2026-02-18 17:38:10 +08:00
class = "detail-section"
2026-01-18 12:21:49 +08:00
>
2026-02-18 17:38:10 +08:00
< div class = "section-label" >
Request Headers
< / div >
2026-01-18 12:21:49 +08:00
< div
v-for = "(val, key) in selectedRequest.requestHeaders"
:key = "key"
2026-02-18 17:38:10 +08:00
class = "detail-row"
2026-01-18 12:21:49 +08:00
>
< span class = "key" > { { key } } : < / span >
< span class = "val" > { { val } } < / span >
< / div >
< / div >
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< div v-if = "activeDetailTab === 'preview'" >
< div class = "detail-section" >
2026-02-18 17:38:10 +08:00
< div class = "section-label" >
Preview
< / div >
2026-01-18 12:21:49 +08:00
< div class = "preview-content" >
{ { selectedRequest . preview } }
< / div >
< / div >
< / div >
2026-01-18 10:24:35 +08:00
2026-01-18 12:21:49 +08:00
< div v-if = "activeDetailTab === 'response'" >
< div class = "detail-section" >
2026-02-18 17:38:10 +08:00
< div class = "section-label" >
Response
< / div >
2026-01-18 12:21:49 +08:00
< div class = "preview-content" >
{ { selectedRequest . preview } }
< / div >
< / div >
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< / div >
< / div >
< / div >
2026-01-18 10:24:35 +08:00
< / div >
<!-- 5. Application Panel -- >
2026-01-18 12:21:49 +08:00
< div
v-else-if = "activeTab === 'application'"
class = "panel application-panel"
>
< div class = "storage-sidebar" >
< div class = "sidebar-section" >
2026-02-18 17:38:10 +08:00
< div class = "section-title" >
Application
< / div >
< div class = "section-item" >
Manifest
< / div >
< div class = "section-item" >
Service Workers
< / div >
2026-01-18 12:21:49 +08:00
< / div >
< div class = "sidebar-section" >
2026-02-18 17:38:10 +08:00
< div class = "section-title" >
Storage
< / div >
2026-01-18 12:21:49 +08:00
< div class = "section-item active" >
< span class = "arrow" > ▼ < / span > Local Storage
< / div >
2026-02-18 17:38:10 +08:00
< div class = "section-item indent" >
http : //localhost
< / div >
2026-01-18 12:21:49 +08:00
< div class = "section-item" >
< span class = "arrow" > ▶ < / span > Session Storage
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< div class = "section-item" >
< span class = "arrow" > ▶ < / span > Cookies
< / div >
< / div >
< / div >
< div class = "storage-content" >
< div class = "storage-table" >
< div class = "table-header" >
2026-02-18 17:38:10 +08:00
< div class = "col key" >
Key
< / div >
< div class = "col value" >
Value
< / div >
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< div
v-for = "(item, idx) in localStorageData"
:key = "idx"
2026-02-18 17:38:10 +08:00
class = "table-row"
2026-01-18 12:21:49 +08:00
>
2026-02-18 17:38:10 +08:00
< div class = "col key" >
{ { item . key } }
< / div >
< div class = "col value" >
{ { item . value } }
< / div >
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< / div >
< / div >
2026-01-18 10:24:35 +08:00
< / div >
< / div >
<!-- Info Bar Overlay -- >
2026-02-18 17:38:10 +08:00
< div
v-if = "hoverInfo"
class = "info-bar"
>
2026-01-18 12:21:49 +08:00
< span class = "info-icon" > 💡 < / span > { { hoverInfo } }
2026-01-18 10:24:35 +08:00
< / div >
< / div >
< / div >
< / template >
< style scoped >
/* Reset & Base - COMPACT MODE */
. browser - devtools - demo {
border : 1 px solid # d0d7de ;
border - radius : 6 px ;
background - color : # ffffff ;
color : # 202124 ;
font - family : 'Segoe UI' , '.SFNSDisplay' , 'Roboto' , sans - serif ;
font - size : 11 px ; /* Smaller font */
overflow : hidden ;
display : flex ;
flex - direction : column ;
height : 400 px ; /* Reduced height */
position : relative ;
2026-01-18 12:21:49 +08:00
box - shadow : 0 4 px 12 px rgba ( 0 , 0 , 0 , 0.08 ) ;
2026-01-18 10:24:35 +08:00
user - select : none ;
}
/* Demo Controls (Top Bar) */
. demo - controls {
padding : 6 px 12 px ;
background : # f6f8fa ;
border - bottom : 1 px solid # d0d7de ;
display : flex ;
justify - content : space - between ;
align - items : center ;
z - index : 10 ;
height : 32 px ;
}
. control - label {
font - weight : 600 ;
color : # 24292 f ;
font - size : 12 px ;
}
2026-01-18 12:21:49 +08:00
. control - actions {
display : flex ;
gap : 8 px ;
}
2026-01-18 10:24:35 +08:00
. tour - select {
padding : 2 px 6 px ;
border : 1 px solid # d0d7de ;
border - radius : 4 px ;
font - size : 11 px ;
color : # 24292 f ;
min - width : 180 px ;
cursor : pointer ;
}
. stop - btn {
background : # cf222e ;
color : white ;
border : none ;
padding : 2 px 8 px ;
border - radius : 4 px ;
cursor : pointer ;
font - weight : 500 ;
font - size : 11 px ;
}
/* DevTools Container */
. devtools - container {
flex : 1 ;
display : flex ;
flex - direction : column ;
overflow : hidden ;
}
/* Header & Tabs */
. devtools - header {
background - color : # f3f3f3 ;
border - bottom : 1 px solid # ccc ;
display : flex ;
justify - content : space - between ;
height : 24 px ; /* Reduced header height */
padding : 0 4 px ;
}
2026-01-18 12:21:49 +08:00
. header - left ,
. header - right {
display : flex ;
align - items : center ;
height : 100 % ;
}
. icon - btn {
padding : 0 6 px ;
cursor : pointer ;
color : # 6 e6e6e ;
2026-01-18 10:24:35 +08:00
height : 100 % ;
display : flex ;
align - items : center ;
justify - content : center ;
}
2026-01-18 12:21:49 +08:00
. icon - btn : hover {
color : # 202124 ;
background - color : # eaeaea ;
}
. separator {
width : 1 px ;
height : 14 px ;
background - color : # ccc ;
margin : 0 6 px ;
}
. tabs {
display : flex ;
height : 100 % ;
overflow - x : auto ;
}
. tab {
padding : 0 8 px ;
cursor : pointer ;
display : flex ;
align - items : center ;
color : # 5 f6368 ;
border - bottom : 2 px solid transparent ;
2026-01-18 10:24:35 +08:00
height : 100 % ;
font - size : 11 px ;
white - space : nowrap ;
}
2026-01-18 12:21:49 +08:00
. tab : hover {
background - color : # e8eaed ;
color : # 202124 ;
}
. tab . active {
color : # 1 a73e8 ;
border - bottom : 2 px solid # 1 a73e8 ;
font - weight : 500 ;
}
2026-01-18 10:24:35 +08:00
/* Body Layout */
2026-01-18 12:21:49 +08:00
. devtools - body {
flex : 1 ;
display : flex ;
overflow : hidden ;
background - color : # fff ;
position : relative ;
}
. panel {
flex : 1 ;
display : flex ;
overflow : hidden ;
width : 100 % ;
}
2026-01-18 10:24:35 +08:00
/* --- 1. Console Panel --- */
2026-01-18 12:21:49 +08:00
. console - panel - layout {
flex - direction : column ;
}
. console - toolbar {
2026-01-18 10:24:35 +08:00
height : 24 px ; /* Reduced */
2026-01-18 12:21:49 +08:00
border - bottom : 1 px solid # e0e0e0 ;
display : flex ;
align - items : center ;
padding : 0 4 px ;
background : # f1f3f4 ;
}
. filter - box {
display : flex ;
align - items : center ;
border : 1 px solid # ccc ;
background : # fff ;
border - radius : 2 px ;
padding : 0 4 px ;
margin : 0 8 px ;
flex : 1 ;
2026-01-18 10:24:35 +08:00
max - width : 300 px ;
height : 18 px ;
}
2026-01-18 12:21:49 +08:00
. filter - box input {
border : none ;
outline : none ;
width : 100 % ;
font - size : 11 px ;
}
. dropdown - trigger {
font - size : 11 px ;
color : # 5 f6368 ;
padding : 0 6 px ;
cursor : pointer ;
}
. console - main - area {
flex : 1 ;
display : flex ;
overflow : hidden ;
}
. console - sidebar {
width : 160 px ;
border - right : 1 px solid # e0e0e0 ;
background : # f3f3f3 ;
padding - top : 2 px ;
}
. sidebar - item {
display : flex ;
align - items : center ;
padding : 1 px 8 px ;
cursor : pointer ;
color : # 5 f6368 ;
height : 20 px ;
}
. sidebar - item : hover {
background : # e8eaed ;
}
. sidebar - item . active {
background : # d2e3fc ;
color : # 1 a73e8 ;
}
. item - icon {
margin - right : 6 px ;
width : 14 px ;
text - align : center ;
}
. console - content - wrapper {
flex : 1 ;
display : flex ;
flex - direction : column ;
background : # fff ;
}
. console - output {
flex : 1 ;
2026-02-14 20:23:34 +08:00
2026-01-18 12:21:49 +08:00
font - family : Consolas , 'Lucida Console' , monospace ;
font - size : 11 px ;
}
. log - line {
border - bottom : 1 px solid # f0f0f0 ;
display : flex ;
padding : 2 px 0 ;
min - height : 18 px ;
}
. log - line . error {
background : # fef0f0 ;
border - bottom - color : # ffd6d6 ;
color : # d93025 ;
}
. log - line . warn {
background : # fff8e1 ;
border - bottom - color : # ffeba0 ;
color : # 5 f4b0e ;
}
. log - gutter {
width : 20 px ;
text - align : center ;
flex - shrink : 0 ;
padding - top : 1 px ;
}
. log - content {
flex : 1 ;
white - space : pre - wrap ;
padding - right : 4 px ;
line - height : 1.3 ;
}
. log - source {
margin - left : 10 px ;
margin - right : 10 px ;
text - align : right ;
flex - shrink : 0 ;
color : # 80868 b ;
text - decoration : underline ;
cursor : pointer ;
}
. console - input - area {
display : flex ;
align - items : center ;
border - top : 1 px solid # e0e0e0 ;
padding : 2 px 4 px ;
min - height : 22 px ;
}
. console - input - area . prompt {
color : # 1 a73e8 ;
margin - right : 6 px ;
font - weight : bold ;
}
. input - field {
border : none ;
outline : none ;
flex : 1 ;
font - family : Consolas , monospace ;
font - size : 11 px ;
}
2026-01-18 10:24:35 +08:00
/* Bottom Drawer */
. bottom - drawer {
height : 120 px ;
border - top : 1 px solid # ccc ;
display : flex ;
flex - direction : column ;
background : # fff ;
}
. bottom - drawer - header {
height : 24 px ;
background : # f3f3f3 ;
border - bottom : 1 px solid # ccc ;
display : flex ;
align - items : center ;
}
. drawer - tab {
padding : 0 12 px ;
height : 100 % ;
display : flex ;
align - items : center ;
cursor : pointer ;
color : # 5 f6368 ;
border - right : 1 px solid transparent ;
font - size : 11 px ;
}
. drawer - tab . active {
background : # fff ;
color : # 202124 ;
border - right : 1 px solid # ccc ;
}
2026-01-18 12:21:49 +08:00
. close - icon {
margin - left : 6 px ;
font - size : 12 px ;
2026-01-18 10:24:35 +08:00
}
2026-01-18 12:21:49 +08:00
. drawer - content {
flex : 1 ;
overflow : hidden ;
2026-01-18 10:24:35 +08:00
}
2026-01-18 12:21:49 +08:00
. search - panel {
display : flex ;
flex - direction : column ;
height : 100 % ;
}
. search - bar {
padding : 4 px ;
border - bottom : 1 px solid # e0e0e0 ;
display : flex ;
align - items : center ;
}
. search - input {
flex : 1 ;
border : none ;
outline : none ;
font - size : 11 px ;
}
. search - actions {
color : # 5 f6368 ;
cursor : pointer ;
}
. search - results {
flex : 1 ;
display : flex ;
align - items : center ;
justify - content : center ;
background : # fff ;
}
. no - results {
text - align : center ;
color : # 5 f6368 ;
}
. no - results - title {
font - weight : bold ;
font - size : 12 px ;
margin - bottom : 4 px ;
}
. no - results - desc {
font - size : 11 px ;
}
/* --- 2. Elements Panel --- */
. elements - panel {
display : flex ;
flex - direction : row ;
}
. dom - tree - panel {
flex : 2 ;
border - right : 1 px solid # d0d7de ;
display : flex ;
flex - direction : column ;
overflow : auto ;
padding : 4 px 0 ;
font - family : Consolas , Menlo , monospace ;
font - size : 12 px ;
background : # fff ;
}
. dom - node {
padding - left : 14 px ;
line - height : 18 px ;
cursor : default ;
white - space : nowrap ;
}
. dom - node . selected {
background - color : # cfe8fc ;
}
. line - content {
display : flex ;
align - items : center ;
}
. node - trail {
color : # 5 f6368 ;
margin - left : 6 px ;
}
. arrow {
color : # 5 f6368 ;
font - size : 10 px ;
display : inline - block ;
width : 14 px ;
margin - left : - 14 px ;
text - align : center ;
}
. tag - name {
color : # a90d91 ;
}
. attr - name {
color : # 994500 ;
margin - left : 4 px ;
}
. attr - val {
color : # 1 a1aa6 ;
}
. dots {
background : # eee ;
border - radius : 2 px ;
padding : 0 2 px ;
color : # 777 ;
font - size : 10 px ;
margin : 0 2 px ;
}
. indent {
display : inline - block ;
width : 14 px ;
}
. breadcrumbs {
border - top : 1 px solid # ccc ;
padding : 2 px 8 px ;
font - size : 11 px ;
color : # 5 f6368 ;
background : # fff ;
border - bottom : 1 px solid # eee ;
}
. styles - panel {
flex : 1 ;
display : flex ;
flex - direction : column ;
background : # fff ;
border - left : 1 px solid # d0d7de ;
min - width : 260 px ;
}
. styles - tabs {
display : flex ;
background : # f1f3f4 ;
border - bottom : 1 px solid # ccc ;
height : 26 px ;
}
. style - tab {
padding : 0 10 px ;
display : flex ;
align - items : center ;
color : # 5 f6368 ;
cursor : pointer ;
border - bottom : 2 px solid transparent ;
font - size : 11 px ;
}
. style - tab : hover {
background - color : # e8eaed ;
color : # 202124 ;
}
. style - tab . active {
color : # 1 a73e8 ;
border - bottom : 2 px solid # 1 a73e8 ;
font - weight : 500 ;
}
. styles - content {
padding : 0 ;
overflow : auto ;
background : # fff ;
flex : 1 ;
}
/* Refined Box Model */
. box - model - container {
padding : 16 px ;
display : flex ;
justify - content : center ;
background - color : # f8f9fa ;
border - bottom : 1 px solid # e0e0e0 ;
margin - bottom : 4 px ;
}
. box - margin {
background - color : rgba ( 249 , 204 , 157 , 0.4 ) ;
border : 1 px dashed # caaa87 ;
padding : 16 px ; /* Increased padding for values */
2026-01-18 10:24:35 +08:00
position : relative ;
font - size : 9 px ;
color : # 222 ;
}
2026-01-18 12:21:49 +08:00
. box - border {
background - color : rgba ( 255 , 221 , 150 , 0.4 ) ;
border : 1 px solid # dac689 ;
padding : 16 px ;
2026-01-18 10:24:35 +08:00
position : relative ;
}
2026-01-18 12:21:49 +08:00
. box - padding {
background - color : rgba ( 195 , 223 , 183 , 0.4 ) ;
border : 1 px dashed # 9 bc89b ;
padding : 16 px ;
2026-01-18 10:24:35 +08:00
position : relative ;
}
2026-01-18 12:21:49 +08:00
. box - content {
background - color : rgba ( 174 , 213 , 243 , 0.4 ) ;
border : 1 px solid # 7 eb0d8 ;
padding : 4 px 8 px ;
2026-01-18 10:24:35 +08:00
min - width : 60 px ;
text - align : center ;
}
2026-01-18 12:21:49 +08:00
. label {
position : absolute ;
top : 2 px ;
left : 4 px ;
font - size : 8 px ;
color : # 555 ;
pointer - events : none ;
}
2026-01-18 10:24:35 +08:00
/* Positioning values */
2026-01-18 12:21:49 +08:00
. val - top {
position : absolute ;
top : 2 px ;
left : 0 ;
right : 0 ;
text - align : center ;
}
. val - bottom {
position : absolute ;
bottom : 2 px ;
left : 0 ;
right : 0 ;
text - align : center ;
}
. val - left {
position : absolute ;
top : 0 ;
bottom : 0 ;
left : 2 px ;
display : flex ;
align - items : center ;
}
. val - right {
position : absolute ;
top : 0 ;
bottom : 0 ;
right : 2 px ;
display : flex ;
align - items : center ;
}
2026-01-18 10:24:35 +08:00
2026-01-18 12:21:49 +08:00
. filter - bar {
display : flex ;
border : 1 px solid # ccc ;
border - radius : 2 px ;
padding : 2 px 4 px ;
margin : 8 px ;
background : # fff ;
align - items : center ;
}
. filter - bar input {
border : none ;
outline : none ;
flex : 1 ;
font - size : 11 px ;
}
. filter - opt {
padding : 0 4 px ;
color : # 5 f6368 ;
cursor : pointer ;
font - weight : bold ;
margin - left : 4 px ;
}
2026-01-18 10:24:35 +08:00
2026-01-18 12:21:49 +08:00
. style - rule {
margin - bottom : 8 px ;
font - family : Consolas , Menlo , monospace ;
font - size : 11 px ;
border - bottom : 1 px solid # eee ;
padding : 4 px 8 px 8 px 8 px ;
}
. selector {
color : # a90d91 ;
font - weight : bold ;
}
. property {
padding - left : 14 px ;
line - height : 1.4 ;
}
. prop - name {
color : # 994500 ;
}
. prop - val {
color : # 222 ;
}
2026-01-18 10:24:35 +08:00
/* --- 3. Sources Panel --- */
2026-01-18 12:21:49 +08:00
. sources - panel {
display : flex ;
}
. file - navigator {
width : 180 px ;
border - right : 1 px solid # ccc ;
background : # fff ;
display : flex ;
flex - direction : column ;
}
. nav - header {
background : # f3f3f3 ;
border - bottom : 1 px solid # ccc ;
display : flex ;
height : 24 px ;
}
. nav - tab {
padding : 0 8 px ;
cursor : pointer ;
color : # 5 f6368 ;
font - size : 11 px ;
display : flex ;
align - items : center ;
}
. nav - tab . active {
background : # fff ;
color : # 202124 ;
border - right : 1 px solid # ccc ;
}
. file - tree {
padding : 4 px ;
overflow : auto ;
font - family : 'Segoe UI' , sans - serif ;
font - size : 11 px ;
}
. file - item {
padding : 1 px 4 px ;
cursor : pointer ;
display : flex ;
align - items : center ;
white - space : nowrap ;
}
. file - item : hover {
background : # f3f3f3 ;
}
. file - item . active {
background : # cfe8fc ;
}
. file - item . icon {
margin - right : 6 px ;
opacity : 0.7 ;
font - size : 12 px ;
}
. folder - children {
padding - left : 16 px ;
}
. code - editor {
flex : 1 ;
display : flex ;
flex - direction : column ;
background : # fff ;
}
. editor - tabs {
display : flex ;
background : # f3f3f3 ;
border - bottom : 1 px solid # ccc ;
height : 24 px ;
}
. editor - tab {
padding : 0 8 px ;
background : # fff ;
border - right : 1 px solid # ccc ;
display : flex ;
align - items : center ;
font - size : 11 px ;
color : # 333 ;
}
. editor - content {
flex : 1 ;
display : flex ;
font - family : Consolas , monospace ;
font - size : 11 px ;
overflow : auto ;
}
. line - numbers {
width : 30 px ;
background : # f3f3f3 ;
border - right : 1 px solid # ddd ;
text - align : right ;
padding : 4 px ;
color : # 999 ;
line - height : 1.5 ;
}
. code - text {
flex : 1 ;
padding : 4 px ;
line - height : 1.5 ;
color : # 222 ;
}
. debugger - sidebar {
width : 200 px ;
border - left : 1 px solid # ccc ;
background : # f3f3f3 ;
display : flex ;
flex - direction : column ;
}
. debug - section {
border - bottom : 1 px solid # ccc ;
}
. section - title {
padding : 2 px 8 px ;
background : # e0e0e0 ;
font - weight : 700 ;
font - size : 11 px ;
color : # 333 ;
cursor : pointer ;
}
. section - content {
padding : 2 px 8 px ;
background : # fff ;
}
2026-01-18 10:24:35 +08:00
/* --- 4. Network Panel --- */
2026-01-18 12:21:49 +08:00
. network - panel {
display : flex ;
flex - direction : column ;
}
. network - toolbar {
height : 24 px ;
background : # f3f3f3 ;
border - bottom : 1 px solid # ccc ;
display : flex ;
align - items : center ;
padding : 0 8 px ;
gap : 8 px ;
}
. record - icon {
color : # d93025 ;
font - size : 10 px ;
cursor : pointer ;
}
. filter - btn {
cursor : pointer ;
padding : 1 px 6 px ;
border - radius : 2 px ;
color : # 5 f6368 ;
}
. filter - btn : hover {
background : # e0e0e0 ;
color : # 202124 ;
}
. filter - btn . active {
background : # cdcdcd ;
font - weight : 600 ;
color : # 202124 ;
}
. network - split - view {
flex : 1 ;
display : flex ;
overflow : hidden ;
}
. network - grid {
flex : 1 ;
display : flex ;
flex - direction : column ;
font - size : 11 px ;
overflow : hidden ;
}
. network - grid - header {
display : flex ;
background : # f8f9fa ;
border - bottom : 1 px solid # ccc ;
padding : 1 px 0 ;
font - weight : bold ;
color : # 333 ;
}
. network - rows {
flex : 1 ;
overflow : auto ;
background : # fff ;
}
. network - row {
display : flex ;
border - bottom : 1 px solid # f0f0f0 ;
padding : 1 px 0 ;
cursor : default ;
}
. network - row : nth - child ( even ) {
background : # f9f9f9 ;
}
. network - row : hover {
background : # e8f0fe ;
}
. network - row . selected {
background : # cfe8fc ;
}
. col {
padding : 1 px 6 px ;
overflow : hidden ;
text - overflow : ellipsis ;
white - space : nowrap ;
border - right : 1 px solid # f0f0f0 ;
display : flex ;
align - items : center ;
}
. col . name {
width : 140 px ;
}
. col . status {
width : 40 px ;
color : # 5 f6368 ;
}
. col . type {
width : 60 px ;
color : # 5 f6368 ;
}
. col . size {
width : 50 px ;
color : # 5 f6368 ;
}
. col . time {
width : 50 px ;
color : # 5 f6368 ;
}
. col . waterfall {
flex : 1 ;
position : relative ;
}
. waterfall - bar {
height : 6 px ;
background : # 8 ab4f8 ;
position : absolute ;
top : 50 % ;
transform : translateY ( - 50 % ) ;
border - radius : 2 px ;
border : 1 px solid # 4285 f4 ;
}
2026-01-18 10:24:35 +08:00
/* Network Detail Panel */
2026-01-18 12:21:49 +08:00
. network - detail {
width : 300 px ;
border - left : 1 px solid # ccc ;
background : # fff ;
display : flex ;
flex - direction : column ;
2026-01-18 10:24:35 +08:00
font - size : 11 px ;
}
2026-01-18 12:21:49 +08:00
. detail - header {
height : 24 px ;
background : # f3f3f3 ;
border - bottom : 1 px solid # ccc ;
display : flex ;
align - items : center ;
2026-01-18 10:24:35 +08:00
padding : 0 8 px ;
}
2026-01-18 12:21:49 +08:00
. detail - title {
margin - right : 12 px ;
color : # 5 f6368 ;
font - weight : bold ;
cursor : pointer ;
2026-01-18 10:24:35 +08:00
border - bottom : 2 px solid transparent ;
line - height : 22 px ;
}
2026-01-18 12:21:49 +08:00
. detail - title : hover {
color : # 333 ;
}
. detail - title . active {
color : # 1 a73e8 ;
border - bottom : 2 px solid # 1 a73e8 ;
}
. close - detail {
margin - left : auto ;
cursor : pointer ;
font - size : 14 px ;
color : # 5 f6368 ;
}
. detail - content {
flex : 1 ;
overflow : auto ;
padding : 8 px ;
}
. detail - section {
margin - bottom : 12 px ;
}
. section - label {
font - weight : bold ;
margin - bottom : 4 px ;
color : # 333 ;
}
. detail - row {
display : flex ;
margin - bottom : 2 px ;
line - height : 1.4 ;
word - break : break - all ;
}
. detail - row . key {
color : # 5 f6368 ;
margin - right : 6 px ;
flex - shrink : 0 ;
min - width : 80 px ;
}
. detail - row . val {
color : # 222 ;
}
. status - code {
color : # 1 a73e8 ;
font - weight : bold ;
}
. preview - content {
font - family : Consolas , monospace ;
background : # f8f9fa ;
padding : 6 px ;
border - radius : 2 px ;
2026-01-18 10:24:35 +08:00
border : 1 px solid # eee ;
white - space : pre - wrap ;
color : # 24292 e ;
}
/* --- 5. Application Panel --- */
2026-01-18 12:21:49 +08:00
. application - panel {
display : flex ;
}
. storage - sidebar {
width : 180 px ;
border - right : 1 px solid # ccc ;
background : # fff ;
padding : 0 ;
overflow : auto ;
}
. sidebar - section {
margin - bottom : 8 px ;
}
. section - title {
font - weight : bold ;
color : # 5 f6368 ;
padding : 2 px 8 px ;
}
. section - item {
padding : 1 px 8 px ;
cursor : pointer ;
display : flex ;
align - items : center ;
color : # 333 ;
}
. section - item : hover {
background : # f3f3f3 ;
}
. section - item . active {
background : # cfe8fc ;
}
. section - item . indent {
padding - left : 20 px ;
}
. section - item . arrow {
margin - right : 4 px ;
width : 10 px ;
}
. storage - content {
flex : 1 ;
background : # fff ;
overflow : auto ;
display : flex ;
flex - direction : column ;
}
. storage - table {
width : 100 % ;
font - size : 11 px ;
border - collapse : collapse ;
}
. table - header {
display : flex ;
background : # f3f3f3 ;
border - bottom : 1 px solid # ccc ;
font - weight : bold ;
}
. table - row {
display : flex ;
border - bottom : 1 px solid # eee ;
}
. table - row : nth - child ( even ) {
background : # f9f9f9 ;
}
. table - row : hover {
background : # eef ;
}
. storage - table . col {
padding : 2 px 8 px ;
border - right : 1 px solid # eee ;
}
. storage - table . col . key {
width : 150 px ;
font - weight : 600 ;
}
. storage - table . col . value {
flex : 1 ;
font - family : Consolas , monospace ;
}
2026-01-18 10:24:35 +08:00
/* Overlays */
. info - bar {
background - color : # 323232 ;
color : white ;
padding : 6 px 12 px ;
font - size : 12 px ;
display : flex ;
align - items : center ;
gap : 8 px ;
position : absolute ;
bottom : 16 px ;
left : 50 % ;
transform : translateX ( - 50 % ) ;
border - radius : 24 px ;
2026-01-18 12:21:49 +08:00
box - shadow : 0 4 px 12 px rgba ( 0 , 0 , 0 , 0.3 ) ;
2026-01-18 10:24:35 +08:00
z - index : 9999 ;
white - space : nowrap ;
pointer - events : none ;
}
2026-01-18 12:21:49 +08:00
. info - icon {
font - size : 14 px ;
}
2026-01-18 10:24:35 +08:00
. virtual - cursor {
position : absolute ;
2026-01-18 12:21:49 +08:00
top : 0 ;
left : 0 ;
2026-01-18 10:24:35 +08:00
z - index : 10000 ;
pointer - events : none ;
transition : transform 0.6 s cubic - bezier ( 0.25 , 1 , 0.5 , 1 ) ;
margin - top : - 5 px ;
margin - left : - 3 px ;
}
. highlight - box {
position : absolute ;
border : 2 px solid # 1 a73e8 ;
background - color : rgba ( 26 , 115 , 232 , 0.15 ) ;
pointer - events : none ;
z - index : 9998 ;
box - sizing : border - box ;
transition : all 0.3 s ease ;
border - radius : 2 px ;
}
< / style >