2026-02-01 23:42:12 +08:00
|
|
|
|
<script setup>
|
|
|
|
|
|
import { ref, reactive, computed } from 'vue'
|
|
|
|
|
|
|
|
|
|
|
|
const selectedElement = ref('box') // 'box' or 'text'
|
|
|
|
|
|
|
|
|
|
|
|
const styles = reactive({
|
|
|
|
|
|
box: {
|
|
|
|
|
|
backgroundColor: '#409eff',
|
|
|
|
|
|
padding: '20px',
|
|
|
|
|
|
borderRadius: '8px',
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
|
alignItems: 'center'
|
|
|
|
|
|
},
|
|
|
|
|
|
text: {
|
|
|
|
|
|
color: '#ffffff',
|
|
|
|
|
|
fontSize: '20px',
|
|
|
|
|
|
fontWeight: 'bold'
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const domTree = [
|
|
|
|
|
|
{ tag: 'div', class: 'container', id: 'app' },
|
|
|
|
|
|
{ tag: 'div', class: 'box', id: 'target-box', parent: 'app' },
|
|
|
|
|
|
{ tag: 'span', class: 'text', text: 'Hello DevTools', parent: 'target-box' }
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
const computedStyle = computed(() => {
|
|
|
|
|
|
return styles[selectedElement.value]
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const updateStyle = (prop, value) => {
|
|
|
|
|
|
styles[selectedElement.value][prop] = value
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<el-card
|
|
|
|
|
|
class="elements-demo"
|
|
|
|
|
|
shadow="hover"
|
|
|
|
|
|
>
|
2026-02-01 23:42:12 +08:00
|
|
|
|
<template #header>
|
|
|
|
|
|
<div class="header">
|
|
|
|
|
|
<span class="title">Elements (元素面板)</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="devtools-layout">
|
|
|
|
|
|
<!-- Left: DOM Tree -->
|
|
|
|
|
|
<div class="panel dom-panel">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="panel-header">
|
|
|
|
|
|
DOM Tree
|
|
|
|
|
|
</div>
|
2026-02-01 23:42:12 +08:00
|
|
|
|
<div class="dom-content">
|
|
|
|
|
|
<div class="dom-line">
|
|
|
|
|
|
<span class="tag"><div</span> <span class="attr">id</span>="app" <span class="attr">class</span>="container"<span class="tag">></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="dom-line indent"
|
|
|
|
|
|
:class="{ selected: selectedElement === 'box' }"
|
|
|
|
|
|
@click="selectedElement = 'box'"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="tag"><div</span> <span class="attr">id</span>="target-box" <span class="attr">class</span>="box"<span class="tag">></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="dom-line indent-2"
|
|
|
|
|
|
:class="{ selected: selectedElement === 'text' }"
|
|
|
|
|
|
@click="selectedElement = 'text'"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="tag"><span</span> <span class="attr">class</span>="text"<span class="tag">></span>Hello DevTools<span class="tag"></span></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="dom-line indent">
|
|
|
|
|
|
<span class="tag"></div></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="dom-line">
|
|
|
|
|
|
<span class="tag"></div></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Right: Styles -->
|
|
|
|
|
|
<div class="panel style-panel">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="panel-header">
|
|
|
|
|
|
Styles ({{ selectedElement === 'box' ? '.box' : '.text' }})
|
|
|
|
|
|
</div>
|
2026-02-01 23:42:12 +08:00
|
|
|
|
<div class="style-content">
|
|
|
|
|
|
<div class="css-rule">
|
|
|
|
|
|
<span class="selector">{{ selectedElement === 'box' ? '.box' : '.text' }}</span> {
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div
|
|
|
|
|
|
v-for="(value, prop) in styles[selectedElement]"
|
|
|
|
|
|
:key="prop"
|
|
|
|
|
|
class="css-prop"
|
|
|
|
|
|
>
|
2026-02-01 23:42:12 +08:00
|
|
|
|
<span class="prop-name">{{ prop }}</span>:
|
|
|
|
|
|
<span class="prop-value">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<!-- Simple editable input simulation -->
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model="styles[selectedElement][prop]"
|
|
|
|
|
|
class="style-input"
|
|
|
|
|
|
>
|
2026-02-01 23:42:12 +08:00
|
|
|
|
</span>;
|
|
|
|
|
|
</div>
|
|
|
|
|
|
}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Preview Area -->
|
|
|
|
|
|
<div class="preview-area">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
<div class="preview-label">
|
|
|
|
|
|
页面预览 (Page Preview)
|
|
|
|
|
|
</div>
|
2026-02-01 23:42:12 +08:00
|
|
|
|
<div class="preview-content">
|
|
|
|
|
|
<div :style="styles.box">
|
|
|
|
|
|
<span :style="styles.text">Hello DevTools</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="footer-tip">
|
2026-02-18 17:38:10 +08:00
|
|
|
|
点击左侧 DOM 树中的元素,在右侧 Styles 面板修改样式,下方预览会实时更新。
|
2026-02-01 23:42:12 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.elements-demo {
|
|
|
|
|
|
margin: 20px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.devtools-layout {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
height: 250px;
|
|
|
|
|
|
border: 1px solid #dcdfe6;
|
|
|
|
|
|
font-family: monospace;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.panel {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.dom-panel {
|
|
|
|
|
|
border-right: 1px solid #dcdfe6;
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.style-panel {
|
|
|
|
|
|
background-color: #f9f9f9;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.panel-header {
|
|
|
|
|
|
padding: 5px 10px;
|
|
|
|
|
|
background-color: #f0f2f5;
|
|
|
|
|
|
border-bottom: 1px solid #dcdfe6;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.dom-content, .style-content {
|
|
|
|
|
|
padding: 10px;
|
2026-02-14 20:23:34 +08:00
|
|
|
|
|
2026-02-01 23:42:12 +08:00
|
|
|
|
flex: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.dom-line {
|
|
|
|
|
|
padding: 2px 4px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.dom-line:hover {
|
|
|
|
|
|
background-color: #f0f9eb;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.dom-line.selected {
|
|
|
|
|
|
background-color: #d1e8ff; /* Selection color */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.indent { padding-left: 20px; }
|
|
|
|
|
|
.indent-2 { padding-left: 40px; }
|
|
|
|
|
|
|
|
|
|
|
|
.tag { color: #a626a4; }
|
|
|
|
|
|
.attr { color: #986801; }
|
|
|
|
|
|
|
|
|
|
|
|
.css-rule {
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.selector { color: #d19a66; }
|
|
|
|
|
|
.prop-name { color: #e45649; }
|
|
|
|
|
|
.prop-value { color: #50a14f; }
|
|
|
|
|
|
|
|
|
|
|
|
.style-input {
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
color: inherit;
|
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
|
width: 100px;
|
|
|
|
|
|
border-bottom: 1px dashed #ccc;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.style-input:focus {
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
border-bottom: 1px solid #409eff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.preview-area {
|
|
|
|
|
|
margin-top: 15px;
|
|
|
|
|
|
border: 1px dashed #dcdfe6;
|
|
|
|
|
|
padding: 15px;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.preview-label {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: -10px;
|
|
|
|
|
|
left: 10px;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
padding: 0 5px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.preview-content {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
min-height: 100px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.header {
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.footer-tip {
|
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|