feat: update docs and components, fix DLQ demo bug
This commit is contained in:
@@ -22,7 +22,7 @@
|
||||
<div class="icon">🐛</div>
|
||||
<div class="icon">🧩</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 资源管理器 (Sidebar) -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-title">EXPLORER</div>
|
||||
@@ -54,38 +54,82 @@
|
||||
<div v-for="n in 15" :key="n">{{ n }}</div>
|
||||
</div>
|
||||
<div class="code-lines">
|
||||
<div class="line"><span class="kwd">import</span> { <span class="var">ref</span>, <span class="var">onMounted</span>, <span class="var">nextTick</span> } <span class="kwd">from</span> <span class="str">'vue'</span></div>
|
||||
<div class="line">
|
||||
<span class="kwd">import</span> {
|
||||
<span class="var">ref</span>,
|
||||
<span class="var">onMounted</span>,
|
||||
<span class="var">nextTick</span> }
|
||||
<span class="kwd">from</span> <span class="str">'vue'</span>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="line"><span class="kwd">const</span> <span class="var">chartRef</span> = <span class="func">ref</span>(<span class="kwd">null</span>)</div>
|
||||
<div class="line"><span class="kwd">const</span> <span class="var">data</span> = <span class="func">ref</span>([])</div>
|
||||
<div class="line">
|
||||
<span class="kwd">const</span>
|
||||
<span class="var">chartRef</span> =
|
||||
<span class="func">ref</span>(<span class="kwd">null</span>)
|
||||
</div>
|
||||
<div class="line">
|
||||
<span class="kwd">const</span> <span class="var">data</span> =
|
||||
<span class="func">ref</span>([])
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="line"><span class="kwd">const</span> <span class="func">initChart</span> = <span class="kwd">async</span> () => {</div>
|
||||
<div class="line"> <span class="comment">// 等待数据加载完成</span></div>
|
||||
<div class="line"> <span class="kwd">await</span> <span class="func">fetchData</span>()</div>
|
||||
<div class="line">
|
||||
<span class="kwd">const</span>
|
||||
<span class="func">initChart</span> =
|
||||
<span class="kwd">async</span> () => {
|
||||
</div>
|
||||
<div class="line">
|
||||
<span class="comment">// 等待数据加载完成</span>
|
||||
</div>
|
||||
<div class="line">
|
||||
<span class="kwd">await</span>
|
||||
<span class="func">fetchData</span>()
|
||||
</div>
|
||||
<div class="line"> </div>
|
||||
<div class="line" ref="targetCode">
|
||||
<span class="comment">// 👈 等待 DOM 更新后再渲染图表</span>
|
||||
<span class="comment"
|
||||
>// 👈 等待 DOM 更新后再渲染图表</span
|
||||
>
|
||||
</div>
|
||||
<div class="line" ref="targetCode2">
|
||||
<span class="kwd">await</span> <span class="func">nextTick</span>()
|
||||
<span class="kwd">await</span>
|
||||
<span class="func">nextTick</span>()
|
||||
</div>
|
||||
<div class="line"> </div>
|
||||
<div class="line"> <span class="kwd">const</span> <span class="var">chart</span> = <span class="var">echarts</span>.<span class="func">init</span>(<span class="var">chartRef</span>.<span class="var">value</span>)</div>
|
||||
<div class="line"> <span class="var">chart</span>.<span class="func">setOption</span>({ ... })</div>
|
||||
<div class="line">
|
||||
<span class="kwd">const</span>
|
||||
<span class="var">chart</span> =
|
||||
<span class="var">echarts</span>.<span class="func">init</span
|
||||
>(<span class="var">chartRef</span>.<span class="var"
|
||||
>value</span
|
||||
>)
|
||||
</div>
|
||||
<div class="line">
|
||||
<span class="var">chart</span>.<span class="func"
|
||||
>setOption</span
|
||||
>({ ... })
|
||||
</div>
|
||||
<div class="line">}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 截图选框 (Overlay) - Moved to main-layout level -->
|
||||
<div class="screenshot-overlay" v-if="step === 'selecting' || step === 'captured'">
|
||||
<div class="selection-box" :class="{ flashed: step === 'captured' }">
|
||||
<div
|
||||
class="screenshot-overlay"
|
||||
v-if="step === 'selecting' || step === 'captured'"
|
||||
>
|
||||
<div
|
||||
class="selection-box"
|
||||
:class="{ flashed: step === 'captured' }"
|
||||
>
|
||||
<div class="selection-handle top-left"></div>
|
||||
<div class="selection-handle top-right"></div>
|
||||
<div class="selection-handle bottom-left"></div>
|
||||
<div class="selection-handle bottom-right"></div>
|
||||
<div class="cursor-crosshair" v-if="step === 'selecting'"></div>
|
||||
<div class="selection-size" v-if="step === 'selecting'">220 × 350</div>
|
||||
<div class="selection-size" v-if="step === 'selecting'">
|
||||
220 × 350
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -110,16 +154,16 @@
|
||||
<div class="chat-model-selector">
|
||||
<span>GPT-4o</span> <span class="arrow">▼</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="messages-container" ref="messagesContainer">
|
||||
<div class="msg-row user" v-if="stepInt >= 5">
|
||||
<div class="avatar">U</div>
|
||||
<div class="msg-bubble">
|
||||
<div class="pasted-image" v-if="stepInt >= 5">
|
||||
<div class="ui-snapshot">
|
||||
<div class="snapshot-rect menu-rect"></div>
|
||||
<div class="snapshot-text">Menu Bar.png</div>
|
||||
</div>
|
||||
<div class="ui-snapshot">
|
||||
<div class="snapshot-rect menu-rect"></div>
|
||||
<div class="snapshot-text">Menu Bar.png</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="msg-text">{{ typedText }}</div>
|
||||
</div>
|
||||
@@ -127,39 +171,74 @@
|
||||
|
||||
<div class="msg-row ai" v-if="stepInt >= 7">
|
||||
<div class="avatar gpt">
|
||||
<svg viewBox="0 0 41 41" class="gpt-logo"><path d="M37.532 16.87a9.963 9.963 0 00-.856-8.184c-3.15-5.49-10.25-7.38-15.738-4.23-.718.412-1.35.914-1.896 1.488a9.965 9.965 0 00-7.144-1.156 9.972 9.972 0 00-6.73 4.966c-3.15 5.49-1.26 12.59 4.23 15.738.412.237.854.43 1.306.586a9.963 9.963 0 00.856 8.184c3.15 5.49 10.25 7.38 15.738 4.23.718-.412 1.35-.914 1.896-1.488a9.965 9.965 0 007.144 1.156 9.972 9.972 0 006.73-4.966c3.15-5.49 1.26-12.59-4.23-15.738a9.953 9.953 0 00-1.306-.586zM20.5 29.5a9 9 0 110-18 9 9 0 010 18z" fill="currentColor"></path></svg>
|
||||
<svg viewBox="0 0 41 41" class="gpt-logo">
|
||||
<path
|
||||
d="M37.532 16.87a9.963 9.963 0 00-.856-8.184c-3.15-5.49-10.25-7.38-15.738-4.23-.718.412-1.35.914-1.896 1.488a9.965 9.965 0 00-7.144-1.156 9.972 9.972 0 00-6.73 4.966c-3.15 5.49-1.26 12.59 4.23 15.738.412.237.854.43 1.306.586a9.963 9.963 0 00.856 8.184c3.15 5.49 10.25 7.38 15.738 4.23.718-.412 1.35-.914 1.896-1.488a9.965 9.965 0 007.144 1.156 9.972 9.972 0 006.73-4.966c3.15-5.49 1.26-12.59-4.23-15.738a9.953 9.953 0 00-1.306-.586zM20.5 29.5a9 9 0 110-18 9 9 0 010 18z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="msg-bubble ai-bubble">
|
||||
<p>这是 VS Code 的 <strong>顶部菜单栏 (Menu Bar)</strong>,包含了软件的所有功能入口。</p>
|
||||
<p>
|
||||
这是 VS Code 的
|
||||
<strong>顶部菜单栏 (Menu Bar)</strong
|
||||
>,包含了软件的所有功能入口。
|
||||
</p>
|
||||
<p><strong>常用菜单解释:</strong></p>
|
||||
<ul>
|
||||
<li><strong>File (文件)</strong>:新建、打开、保存文件或项目。</li>
|
||||
<li><strong>Edit (编辑)</strong>:复制粘贴、查找替换、撤销重做。</li>
|
||||
<li><strong>View (视图)</strong>:控制界面显示,比如打开侧边栏、终端等。</li>
|
||||
<li><strong>Terminal (终端)</strong>:打开内置命令行工具。</li>
|
||||
<li>
|
||||
<strong>File (文件)</strong>:新建、打开、保存文件或项目。
|
||||
</li>
|
||||
<li>
|
||||
<strong>Edit (编辑)</strong>:复制粘贴、查找替换、撤销重做。
|
||||
</li>
|
||||
<li>
|
||||
<strong>View (视图)</strong
|
||||
>:控制界面显示,比如打开侧边栏、终端等。
|
||||
</li>
|
||||
<li>
|
||||
<strong>Terminal (终端)</strong>:打开内置命令行工具。
|
||||
</li>
|
||||
</ul>
|
||||
<p>💡 <strong>小技巧</strong>:如果不记得某个功能在哪,可以按 <code>F1</code> 或 <code>Ctrl+Shift+P</code> 打开命令面板直接搜索功能名字!</p>
|
||||
<p>
|
||||
💡 <strong>小技巧</strong>:如果不记得某个功能在哪,可以按
|
||||
<code>F1</code> 或
|
||||
<code>Ctrl+Shift+P</code> 打开命令面板直接搜索功能名字!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-input-area">
|
||||
<div class="input-wrapper">
|
||||
<div class="input-preview" v-if="stepInt === 4 || (stepInt === 5 && isTyping)">
|
||||
<div
|
||||
class="input-preview"
|
||||
v-if="stepInt === 4 || (stepInt === 5 && isTyping)"
|
||||
>
|
||||
<div class="mini-snapshot-ui">
|
||||
<div class="mini-menu-rect"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fake-input">
|
||||
<span v-if="stepInt < 5" class="placeholder">Message ChatGPT...</span>
|
||||
<span v-else class="typing-text">{{ typedText }}<span class="cursor" v-if="isTyping">|</span></span>
|
||||
<span v-if="stepInt < 5" class="placeholder"
|
||||
>Message ChatGPT...</span
|
||||
>
|
||||
<span v-else class="typing-text"
|
||||
>{{ typedText
|
||||
}}<span class="cursor" v-if="isTyping">|</span></span
|
||||
>
|
||||
</div>
|
||||
<button class="send-btn" :class="{ active: typedText.length > 5 }">↑</button>
|
||||
<button
|
||||
class="send-btn"
|
||||
:class="{ active: typedText.length > 5 }"
|
||||
>
|
||||
↑
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 全局重置按钮 -->
|
||||
<button class="reset-btn" v-if="step === 'finished'" @click="reset">
|
||||
🔄 重播
|
||||
@@ -215,41 +294,41 @@ const getWindowClass = (winName) => {
|
||||
|
||||
const startDemo = async () => {
|
||||
step.value = 'selecting'
|
||||
|
||||
|
||||
// 1. 模拟截图过程 (1.5s)
|
||||
await wait(1500)
|
||||
step.value = 'captured'
|
||||
|
||||
|
||||
// 2. 截图闪烁确认 (0.5s)
|
||||
await wait(600)
|
||||
|
||||
|
||||
// 3. 窗口切换 (0.8s)
|
||||
step.value = 'switching'
|
||||
await wait(800)
|
||||
|
||||
|
||||
// 4. ChatGPT 界面准备 (粘贴动作)
|
||||
step.value = 'pasting'
|
||||
await wait(800)
|
||||
|
||||
|
||||
// 5. 打字
|
||||
step.value = 'typing'
|
||||
isTyping.value = true
|
||||
const question = "帮我看下这张图,左边红框里那一块是干嘛用的?"
|
||||
const question = '帮我看下这张图,左边红框里那一块是干嘛用的?'
|
||||
for (let i = 0; i < question.length; i++) {
|
||||
typedText.value += question[i]
|
||||
await wait(50)
|
||||
}
|
||||
isTyping.value = false
|
||||
await wait(300)
|
||||
|
||||
|
||||
// 6. 发送
|
||||
step.value = 'sending'
|
||||
await wait(500)
|
||||
|
||||
|
||||
// 7. AI 回复
|
||||
step.value = 'responding'
|
||||
await wait(2500) // 模拟阅读时间
|
||||
|
||||
|
||||
step.value = 'finished'
|
||||
}
|
||||
|
||||
@@ -258,14 +337,16 @@ const reset = () => {
|
||||
typedText.value = ''
|
||||
}
|
||||
|
||||
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ai-help-demo {
|
||||
margin: 40px 0;
|
||||
perspective: 1000px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
||||
Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
.desktop-container {
|
||||
@@ -275,7 +356,7 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
background: #333; /* 桌面背景 */
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 20px 50px rgba(0,0,0,0.3);
|
||||
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* 通用窗口样式 */
|
||||
@@ -286,7 +367,7 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1);
|
||||
@@ -319,8 +400,16 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
@keyframes zoomIn {
|
||||
from { transform: scale(1.1); opacity: 0; filter: blur(4px); }
|
||||
to { transform: scale(1); opacity: 1; filter: blur(0); }
|
||||
from {
|
||||
transform: scale(1.1);
|
||||
opacity: 0;
|
||||
filter: blur(4px);
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
filter: blur(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ================= VS Code 样式 ================= */
|
||||
@@ -340,13 +429,31 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.controls { display: flex; gap: 6px; margin-right: 16px; }
|
||||
.dot { width: 10px; height: 10px; border-radius: 50%; }
|
||||
.red { background: #ff5f56; }
|
||||
.yellow { background: #ffbd2e; }
|
||||
.green { background: #27c93f; }
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
.dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.red {
|
||||
background: #ff5f56;
|
||||
}
|
||||
.yellow {
|
||||
background: #ffbd2e;
|
||||
}
|
||||
.green {
|
||||
background: #27c93f;
|
||||
}
|
||||
|
||||
.main-layout { flex: 1; display: flex; overflow: hidden; }
|
||||
.main-layout {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.activity-bar {
|
||||
width: 40px;
|
||||
@@ -357,8 +464,16 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
padding-top: 10px;
|
||||
gap: 15px;
|
||||
}
|
||||
.activity-bar .icon { font-size: 18px; opacity: 0.5; filter: grayscale(1); }
|
||||
.activity-bar .icon.active { opacity: 1; border-left: 2px solid white; filter: grayscale(0); }
|
||||
.activity-bar .icon {
|
||||
font-size: 18px;
|
||||
opacity: 0.5;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
.activity-bar .icon.active {
|
||||
opacity: 1;
|
||||
border-left: 2px solid white;
|
||||
filter: grayscale(0);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 180px;
|
||||
@@ -367,14 +482,41 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
font-size: 12px;
|
||||
color: #ccc;
|
||||
}
|
||||
.sidebar-title { padding: 10px; font-weight: bold; font-size: 11px; }
|
||||
.tree-item { padding: 4px 10px; display: flex; align-items: center; gap: 6px; cursor: pointer; }
|
||||
.tree-item.active { background: #37373d; }
|
||||
.tree-item.indent { padding-left: 20px; }
|
||||
.tree-item .arrow { font-size: 10px; color: #999; }
|
||||
.sidebar-title {
|
||||
padding: 10px;
|
||||
font-weight: bold;
|
||||
font-size: 11px;
|
||||
}
|
||||
.tree-item {
|
||||
padding: 4px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tree-item.active {
|
||||
background: #37373d;
|
||||
}
|
||||
.tree-item.indent {
|
||||
padding-left: 20px;
|
||||
}
|
||||
.tree-item .arrow {
|
||||
font-size: 10px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.editor-area { flex: 1; display: flex; flex-direction: column; background: #1e1e1e; position: relative; }
|
||||
.tab-bar { height: 35px; background: #252526; display: flex; }
|
||||
.editor-area {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #1e1e1e;
|
||||
position: relative;
|
||||
}
|
||||
.tab-bar {
|
||||
height: 35px;
|
||||
background: #252526;
|
||||
display: flex;
|
||||
}
|
||||
.tab {
|
||||
background: #1e1e1e;
|
||||
padding: 0 15px;
|
||||
@@ -384,7 +526,10 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
font-size: 12px;
|
||||
border-top: 1px solid #007acc;
|
||||
}
|
||||
.tab .close { margin-left: 8px; font-size: 14px; }
|
||||
.tab .close {
|
||||
margin-left: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.code-content {
|
||||
flex: 1;
|
||||
@@ -401,14 +546,27 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
color: #6e7681;
|
||||
user-select: none;
|
||||
}
|
||||
.code-lines { flex: 1; padding-left: 5px; }
|
||||
.code-lines {
|
||||
flex: 1;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
/* 语法高亮 */
|
||||
.kwd { color: #569cd6; }
|
||||
.var { color: #9cdcfe; }
|
||||
.func { color: #dcdcaa; }
|
||||
.str { color: #ce9178; }
|
||||
.comment { color: #6a9955; }
|
||||
.kwd {
|
||||
color: #569cd6;
|
||||
}
|
||||
.var {
|
||||
color: #9cdcfe;
|
||||
}
|
||||
.func {
|
||||
color: #dcdcaa;
|
||||
}
|
||||
.str {
|
||||
color: #ce9178;
|
||||
}
|
||||
.comment {
|
||||
color: #6a9955;
|
||||
}
|
||||
|
||||
/* 截图覆盖层 */
|
||||
.screenshot-overlay {
|
||||
@@ -417,7 +575,7 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.3);
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
z-index: 10;
|
||||
cursor: crosshair;
|
||||
}
|
||||
@@ -430,7 +588,7 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
height: 320px;
|
||||
border: 3px solid #ff5f56; /* 醒目的红框 */
|
||||
background: rgba(255, 95, 86, 0.1);
|
||||
box-shadow: 0 0 0 9999px rgba(0,0,0,0.5); /* 遮罩效果 */
|
||||
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5); /* 遮罩效果 */
|
||||
animation: selectAnim 0.5s ease-out;
|
||||
}
|
||||
|
||||
@@ -440,13 +598,23 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
@keyframes selectAnim {
|
||||
from { width: 0; height: 0; }
|
||||
to { width: 280px; height: 320px; }
|
||||
from {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
to {
|
||||
width: 280px;
|
||||
height: 320px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes flash {
|
||||
0% { background: rgba(255, 255, 255, 0.8); }
|
||||
100% { background: rgba(255, 255, 255, 0.1); }
|
||||
0% {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
100% {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.selection-size {
|
||||
@@ -528,7 +696,9 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.history-item.active { background: #343541; }
|
||||
.history-item.active {
|
||||
background: #343541;
|
||||
}
|
||||
|
||||
.chat-main {
|
||||
flex: 1;
|
||||
@@ -545,7 +715,7 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
padding: 0 20px;
|
||||
font-weight: 600;
|
||||
color: #d2d6db;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@@ -563,8 +733,13 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
gap: 15px;
|
||||
max-width: 80%;
|
||||
}
|
||||
.msg-row.user { align-self: flex-end; flex-direction: row-reverse; }
|
||||
.msg-row.ai { align-self: flex-start; }
|
||||
.msg-row.user {
|
||||
align-self: flex-end;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.msg-row.ai {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 30px;
|
||||
@@ -576,9 +751,19 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
.user .avatar { background: #5436da; color: white; border-radius: 50%; }
|
||||
.ai .avatar { background: #19c37d; color: white; }
|
||||
.gpt-logo { width: 20px; height: 20px; }
|
||||
.user .avatar {
|
||||
background: #5436da;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.ai .avatar {
|
||||
background: #19c37d;
|
||||
color: white;
|
||||
}
|
||||
.gpt-logo {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.msg-bubble {
|
||||
background: transparent;
|
||||
@@ -586,12 +771,14 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.user .msg-bubble { text-align: right; }
|
||||
.user .msg-bubble {
|
||||
text-align: right;
|
||||
}
|
||||
.ai-bubble {
|
||||
background: #444654;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.pasted-image {
|
||||
@@ -610,17 +797,17 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
|
||||
.chat-input-area {
|
||||
padding: 20px;
|
||||
background-image: linear-gradient(180deg, rgba(53,55,64,0), #353740 50%);
|
||||
background-image: linear-gradient(180deg, rgba(53, 55, 64, 0), #353740 50%);
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
background: #40414f;
|
||||
border: 1px solid rgba(0,0,0,0.1);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 12px;
|
||||
padding: 10px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -644,7 +831,9 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.placeholder { color: #8e8ea0; }
|
||||
.placeholder {
|
||||
color: #8e8ea0;
|
||||
}
|
||||
|
||||
.send-btn {
|
||||
background: #19c37d;
|
||||
@@ -659,7 +848,9 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
opacity: 0.5;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.send-btn.active { opacity: 1; }
|
||||
.send-btn.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 引导层 */
|
||||
.guide-overlay {
|
||||
@@ -668,7 +859,7 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.4);
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -682,10 +873,12 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
border-radius: 30px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.start-btn:hover { transform: scale(1.05); }
|
||||
.start-btn:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
position: absolute;
|
||||
@@ -697,11 +890,17 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -32,7 +32,7 @@ const run = async () => {
|
||||
isRunning.value = true
|
||||
logs.value = []
|
||||
activeStep.value = 'start'
|
||||
|
||||
|
||||
await wait(600)
|
||||
|
||||
if (currentScenario.value === 'editor') {
|
||||
@@ -43,12 +43,12 @@ const run = async () => {
|
||||
// Has extension
|
||||
activeStep.value = 'extension'
|
||||
await wait(800)
|
||||
|
||||
|
||||
if (currentScenario.value === 'extension') {
|
||||
logs.value.push('> python main.py')
|
||||
await wait(600)
|
||||
logs.value.push("Error: command 'python' not found")
|
||||
logs.value.push("系统: 找不到 Python 解释器")
|
||||
logs.value.push('系统: 找不到 Python 解释器')
|
||||
activeStep.value = 'error-env'
|
||||
} else {
|
||||
// Full
|
||||
@@ -63,7 +63,7 @@ const run = async () => {
|
||||
isRunning.value = false
|
||||
}
|
||||
|
||||
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
const setScenario = (key) => {
|
||||
if (isRunning.value) return
|
||||
@@ -77,13 +77,15 @@ const setScenario = (key) => {
|
||||
<div class="arch-demo">
|
||||
<div class="demo-header">
|
||||
<div class="title">🛠️ IDE 核心机制模拟器</div>
|
||||
<div class="subtitle">点击下方标签,体验不同配置下的运行结果,理解为什么缺一不可。</div>
|
||||
<div class="subtitle">
|
||||
点击下方标签,体验不同配置下的运行结果,理解为什么缺一不可。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab Selection -->
|
||||
<div class="tabs">
|
||||
<div
|
||||
v-for="(conf, key) in scenarios"
|
||||
<div
|
||||
v-for="(conf, key) in scenarios"
|
||||
:key="key"
|
||||
class="tab"
|
||||
:class="{ active: currentScenario === key }"
|
||||
@@ -99,16 +101,23 @@ const setScenario = (key) => {
|
||||
</div>
|
||||
|
||||
<div class="diagram-container">
|
||||
|
||||
<!-- Layer 1: VS Code -->
|
||||
<div class="component vscode" :class="{ dim: activeStep === 'env' }">
|
||||
<div class="comp-label">1. 外壳 (VS Code)</div>
|
||||
<div class="editor-window">
|
||||
<div class="file-tab">main.py</div>
|
||||
<div class="code-area">
|
||||
<span style="color: #c586c0">print</span>(<span style="color: #ce9178">"Hello"</span>)
|
||||
<span style="color: #c586c0">print</span>(<span
|
||||
style="color: #ce9178"
|
||||
>"Hello"</span
|
||||
>)
|
||||
</div>
|
||||
<button class="run-btn-small" @click="run" :disabled="isRunning" title="点击运行">
|
||||
<button
|
||||
class="run-btn-small"
|
||||
@click="run"
|
||||
:disabled="isRunning"
|
||||
title="点击运行"
|
||||
>
|
||||
{{ isRunning ? '...' : '▶ 运行' }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -119,12 +128,34 @@ const setScenario = (key) => {
|
||||
|
||||
<!-- Connector 1 -->
|
||||
<div class="connector">
|
||||
<div class="line" :class="{ active: ['extension', 'env', 'result', 'error-env'].includes(activeStep) }"></div>
|
||||
<div class="arrow-tip" :class="{ active: ['extension', 'env', 'result', 'error-env'].includes(activeStep) }">⬇</div>
|
||||
<div
|
||||
class="line"
|
||||
:class="{
|
||||
active: ['extension', 'env', 'result', 'error-env'].includes(
|
||||
activeStep
|
||||
)
|
||||
}"
|
||||
></div>
|
||||
<div
|
||||
class="arrow-tip"
|
||||
:class="{
|
||||
active: ['extension', 'env', 'result', 'error-env'].includes(
|
||||
activeStep
|
||||
)
|
||||
}"
|
||||
>
|
||||
⬇
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Layer 2: Extension -->
|
||||
<div class="component extension" :class="{ missing: currentScenario === 'editor', active: activeStep === 'extension' }">
|
||||
<div
|
||||
class="component extension"
|
||||
:class="{
|
||||
missing: currentScenario === 'editor',
|
||||
active: activeStep === 'extension'
|
||||
}"
|
||||
>
|
||||
<div class="comp-label">2. 中介 (插件)</div>
|
||||
<div class="comp-box">
|
||||
<div v-if="currentScenario === 'editor'" class="missing-content">
|
||||
@@ -133,7 +164,14 @@ const setScenario = (key) => {
|
||||
<div v-else class="active-content">
|
||||
<div class="icon">🧩</div>
|
||||
<div class="text">Python 插件</div>
|
||||
<div class="action" v-if="activeStep === 'extension' || activeStep === 'env' || activeStep === 'error-env'">
|
||||
<div
|
||||
class="action"
|
||||
v-if="
|
||||
activeStep === 'extension' ||
|
||||
activeStep === 'env' ||
|
||||
activeStep === 'error-env'
|
||||
"
|
||||
>
|
||||
生成指令: <code>python main.py</code>
|
||||
</div>
|
||||
</div>
|
||||
@@ -142,12 +180,26 @@ const setScenario = (key) => {
|
||||
|
||||
<!-- Connector 2 -->
|
||||
<div class="connector">
|
||||
<div class="line" :class="{ active: ['env', 'result'].includes(activeStep) }"></div>
|
||||
<div class="arrow-tip" :class="{ active: ['env', 'result'].includes(activeStep) }">⬇</div>
|
||||
<div
|
||||
class="line"
|
||||
:class="{ active: ['env', 'result'].includes(activeStep) }"
|
||||
></div>
|
||||
<div
|
||||
class="arrow-tip"
|
||||
:class="{ active: ['env', 'result'].includes(activeStep) }"
|
||||
>
|
||||
⬇
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Layer 3: Environment -->
|
||||
<div class="component env" :class="{ missing: currentScenario !== 'full', active: activeStep === 'env' }">
|
||||
<div
|
||||
class="component env"
|
||||
:class="{
|
||||
missing: currentScenario !== 'full',
|
||||
active: activeStep === 'env'
|
||||
}"
|
||||
>
|
||||
<div class="comp-label">3. 引擎 (环境)</div>
|
||||
<div class="comp-box">
|
||||
<div v-if="currentScenario !== 'full'" class="missing-content">
|
||||
@@ -168,7 +220,6 @@ const setScenario = (key) => {
|
||||
🚫 找不到程序
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Output Console -->
|
||||
@@ -177,17 +228,30 @@ const setScenario = (key) => {
|
||||
<span class="term-icon">_</span> 终端 (Terminal)
|
||||
</div>
|
||||
<div class="term-body">
|
||||
<div v-for="(l, i) in logs" :key="i" class="log-line" :class="{ error: l.includes('Error') || l.includes('失败') }">
|
||||
<div
|
||||
v-for="(l, i) in logs"
|
||||
:key="i"
|
||||
class="log-line"
|
||||
:class="{ error: l.includes('Error') || l.includes('失败') }"
|
||||
>
|
||||
{{ l }}
|
||||
</div>
|
||||
<div v-if="logs.length === 0" class="placeholder">点击上方“运行”按钮开始...</div>
|
||||
<div v-if="logs.length === 0" class="placeholder">
|
||||
点击上方“运行”按钮开始...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="result-bar" :class="{ success: scenarios[currentScenario].result.includes('成功'), error: !scenarios[currentScenario].result.includes('成功') }" v-if="!isRunning && logs.length > 0">
|
||||
<div
|
||||
class="result-bar"
|
||||
:class="{
|
||||
success: scenarios[currentScenario].result.includes('成功'),
|
||||
error: !scenarios[currentScenario].result.includes('成功')
|
||||
}"
|
||||
v-if="!isRunning && logs.length > 0"
|
||||
>
|
||||
{{ scenarios[currentScenario].result }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -199,7 +263,7 @@ const setScenario = (key) => {
|
||||
background: var(--vp-c-bg-soft);
|
||||
margin: 24px 0;
|
||||
font-family: var(--vp-font-family-base);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
@@ -244,7 +308,7 @@ const setScenario = (key) => {
|
||||
.tab.active {
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-brand);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -297,7 +361,7 @@ const setScenario = (key) => {
|
||||
background: #1e1e1e;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid #333;
|
||||
}
|
||||
.vscode .file-tab {
|
||||
@@ -493,20 +557,36 @@ const setScenario = (key) => {
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(-5px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@keyframes slideIn {
|
||||
from { opacity: 0; transform: translate(-10px, -50%); }
|
||||
to { opacity: 1; transform: translate(0, -50%); }
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate(-10px, -50%);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
}
|
||||
.spin {
|
||||
display: inline-block;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile Responsive */
|
||||
@@ -522,4 +602,4 @@ const setScenario = (key) => {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -103,9 +103,27 @@ const closeTab = (index) => {
|
||||
|
||||
const activeSidebarView = ref('EXPLORER') // 'EXPLORER' | 'EXTENSIONS'
|
||||
const extensions = ref([
|
||||
{ id: 'python', name: 'Python', description: 'IntelliSense, linting, debugging...', installed: false, installing: false },
|
||||
{ id: 'cpp', name: 'C/C++', description: 'C/C++ IntelliSense, debugging...', installed: false, installing: false },
|
||||
{ id: 'vue', name: 'Vue - Official', description: 'Language support for Vue 3', installed: false, installing: false },
|
||||
{
|
||||
id: 'python',
|
||||
name: 'Python',
|
||||
description: 'IntelliSense, linting, debugging...',
|
||||
installed: false,
|
||||
installing: false
|
||||
},
|
||||
{
|
||||
id: 'cpp',
|
||||
name: 'C/C++',
|
||||
description: 'C/C++ IntelliSense, debugging...',
|
||||
installed: false,
|
||||
installing: false
|
||||
},
|
||||
{
|
||||
id: 'vue',
|
||||
name: 'Vue - Official',
|
||||
description: 'Language support for Vue 3',
|
||||
installed: false,
|
||||
installing: false
|
||||
}
|
||||
])
|
||||
|
||||
const searchQuery = ref('')
|
||||
@@ -113,22 +131,23 @@ const searchQuery = ref('')
|
||||
const typeText = async (text, setter) => {
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
setter(text.substring(0, i + 1))
|
||||
await new Promise(r => setTimeout(r, 100)) // typing speed
|
||||
await new Promise((r) => setTimeout(r, 100)) // typing speed
|
||||
}
|
||||
}
|
||||
|
||||
const filteredExtensions = computed(() => {
|
||||
if (!searchQuery.value) return extensions.value
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
return extensions.value.filter(ext =>
|
||||
ext.name.toLowerCase().includes(query) ||
|
||||
ext.description.toLowerCase().includes(query)
|
||||
return extensions.value.filter(
|
||||
(ext) =>
|
||||
ext.name.toLowerCase().includes(query) ||
|
||||
ext.description.toLowerCase().includes(query)
|
||||
)
|
||||
})
|
||||
|
||||
const installExtension = (id) => {
|
||||
const ext = extensions.value.find(e => e.id === id)
|
||||
if(ext && !ext.installed && !ext.installing) {
|
||||
const ext = extensions.value.find((e) => e.id === id)
|
||||
if (ext && !ext.installed && !ext.installing) {
|
||||
ext.installing = true
|
||||
setTimeout(() => {
|
||||
ext.installing = false
|
||||
@@ -302,64 +321,67 @@ const vClickOutside = {
|
||||
}
|
||||
|
||||
const startTour = async () => {
|
||||
if (isAutoPlaying.value) return
|
||||
isAutoPlaying.value = true
|
||||
cursorVisible.value = true
|
||||
if (isAutoPlaying.value) return
|
||||
isAutoPlaying.value = true
|
||||
cursorVisible.value = true
|
||||
|
||||
// Reset UI state to ensure all elements are visible
|
||||
activeFileIndex.value = 0
|
||||
activePanel.value = 'TERMINAL'
|
||||
sidebarVisible.value = false
|
||||
panelVisible.value = true
|
||||
activeMenu.value = null
|
||||
hoverInfo.value = ''
|
||||
searchQuery.value = '' // Reset search
|
||||
activeSidebarView.value = 'EXPLORER' // Reset sidebar view
|
||||
// Reset UI state to ensure all elements are visible
|
||||
activeFileIndex.value = 0
|
||||
activePanel.value = 'TERMINAL'
|
||||
sidebarVisible.value = false
|
||||
panelVisible.value = true
|
||||
activeMenu.value = null
|
||||
hoverInfo.value = ''
|
||||
searchQuery.value = '' // Reset search
|
||||
activeSidebarView.value = 'EXPLORER' // Reset sidebar view
|
||||
|
||||
const container = vscodeMockRef.value
|
||||
if (!container) return
|
||||
const container = vscodeMockRef.value
|
||||
if (!container) return
|
||||
|
||||
const moveCursorTo = (selector, infoText, action = null) => {
|
||||
return new Promise((resolve) => {
|
||||
if (action) action()
|
||||
const moveCursorTo = (selector, infoText, action = null) => {
|
||||
return new Promise((resolve) => {
|
||||
if (action) action()
|
||||
|
||||
// Small delay to allow UI updates (like opening menus)
|
||||
setTimeout(() => {
|
||||
const el = container.querySelector(selector)
|
||||
if (el) {
|
||||
// Recalculate container rect in case of scroll
|
||||
const containerRect = container.getBoundingClientRect()
|
||||
const rect = el.getBoundingClientRect()
|
||||
|
||||
// Calculate relative position
|
||||
cursorX.value = rect.left - containerRect.left + rect.width / 2
|
||||
cursorY.value = rect.top - containerRect.top + rect.height / 2
|
||||
// Small delay to allow UI updates (like opening menus)
|
||||
setTimeout(() => {
|
||||
const el = container.querySelector(selector)
|
||||
if (el) {
|
||||
// Recalculate container rect in case of scroll
|
||||
const containerRect = container.getBoundingClientRect()
|
||||
const rect = el.getBoundingClientRect()
|
||||
|
||||
// Update highlight box
|
||||
// Use box-sizing: border-box in CSS
|
||||
// Match exact size (border will be drawn inside the element area)
|
||||
highlightStyle.value = {
|
||||
top: rect.top - containerRect.top + 'px',
|
||||
left: rect.left - containerRect.left + 'px',
|
||||
width: rect.width + 'px',
|
||||
height: rect.height + 'px'
|
||||
}
|
||||
highlightVisible.value = true
|
||||
// Calculate relative position
|
||||
cursorX.value = rect.left - containerRect.left + rect.width / 2
|
||||
cursorY.value = rect.top - containerRect.top + rect.height / 2
|
||||
|
||||
hoverInfo.value = infoText
|
||||
} else {
|
||||
// Fallback if element not found: just proceed after delay
|
||||
// This prevents the tour from hanging forever
|
||||
console.warn(`Tour element not found: ${selector}`)
|
||||
// Update highlight box
|
||||
// Use box-sizing: border-box in CSS
|
||||
// Match exact size (border will be drawn inside the element area)
|
||||
highlightStyle.value = {
|
||||
top: rect.top - containerRect.top + 'px',
|
||||
left: rect.left - containerRect.left + 'px',
|
||||
width: rect.width + 'px',
|
||||
height: rect.height + 'px'
|
||||
}
|
||||
|
||||
tourTimeout = setTimeout(() => {
|
||||
highlightVisible.value = true
|
||||
|
||||
hoverInfo.value = infoText
|
||||
} else {
|
||||
// Fallback if element not found: just proceed after delay
|
||||
// This prevents the tour from hanging forever
|
||||
console.warn(`Tour element not found: ${selector}`)
|
||||
}
|
||||
|
||||
tourTimeout = setTimeout(
|
||||
() => {
|
||||
highlightVisible.value = false
|
||||
resolve()
|
||||
}, el ? 2500 : 500) // Shorter delay if skipped
|
||||
}, 800) // Increased delay for better stability
|
||||
})
|
||||
}
|
||||
},
|
||||
el ? 2500 : 500
|
||||
) // Shorter delay if skipped
|
||||
}, 800) // Increased delay for better stability
|
||||
})
|
||||
}
|
||||
|
||||
// --- Tour Segments ---
|
||||
|
||||
@@ -369,25 +391,17 @@ const startTour = async () => {
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
// Menus
|
||||
await moveCursorTo(
|
||||
'.menu-bar-container',
|
||||
'菜单栏:所有功能'
|
||||
)
|
||||
await moveCursorTo('.menu-bar-container', '菜单栏:所有功能')
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
// Demonstrate clicking a menu
|
||||
await moveCursorTo(
|
||||
'.menu-item:nth-child(1)',
|
||||
'文件菜单:文件操作',
|
||||
() => toggleMenu('File')
|
||||
await moveCursorTo('.menu-item:nth-child(1)', '文件菜单:文件操作', () =>
|
||||
toggleMenu('File')
|
||||
)
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
// Show a specific item in the dropdown
|
||||
await moveCursorTo(
|
||||
'.dropdown-item:nth-child(1)',
|
||||
'新建文件:创建空文件'
|
||||
)
|
||||
await moveCursorTo('.dropdown-item:nth-child(1)', '新建文件:创建空文件')
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
// Close menu
|
||||
@@ -397,55 +411,36 @@ const startTour = async () => {
|
||||
await moveCursorTo('.nav-arrows', '导航按钮:后退/前进')
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
await moveCursorTo(
|
||||
'.search-box',
|
||||
'命令中心:快速搜索'
|
||||
)
|
||||
await moveCursorTo('.search-box', '命令中心:快速搜索')
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
await moveCursorTo(
|
||||
'.layout-controls',
|
||||
'布局控制:切换视图'
|
||||
)
|
||||
await moveCursorTo('.layout-controls', '布局控制:切换视图')
|
||||
}
|
||||
|
||||
const runActivityBarTour = async () => {
|
||||
// --- 2. Activity Bar (Left) ---
|
||||
await moveCursorTo(
|
||||
'.activity-bar',
|
||||
'活动栏:切换视图'
|
||||
)
|
||||
await moveCursorTo('.activity-bar', '活动栏:切换视图')
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
await moveCursorTo(
|
||||
'.icon[title="Explorer"]',
|
||||
'资源管理器:管理文件',
|
||||
() => { sidebarVisible.value = true }
|
||||
() => {
|
||||
sidebarVisible.value = true
|
||||
}
|
||||
)
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
await moveCursorTo(
|
||||
'.icon[title="Search"]',
|
||||
'全局搜索:查找替换'
|
||||
)
|
||||
await moveCursorTo('.icon[title="Search"]', '全局搜索:查找替换')
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
await moveCursorTo(
|
||||
'.icon[title="Source Control"]',
|
||||
'源代码管理:Git'
|
||||
)
|
||||
await moveCursorTo('.icon[title="Source Control"]', '源代码管理:Git')
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
await moveCursorTo(
|
||||
'.icon[title="Run and Debug"]',
|
||||
'运行和调试:调试代码'
|
||||
)
|
||||
await moveCursorTo('.icon[title="Run and Debug"]', '运行和调试:调试代码')
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
await moveCursorTo(
|
||||
'.icon[title="Extensions"]',
|
||||
'扩展商店:安装插件'
|
||||
)
|
||||
await moveCursorTo('.icon[title="Extensions"]', '扩展商店:安装插件')
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
await moveCursorTo('.icon[title="Accounts"]', '账户:同步设置')
|
||||
@@ -460,7 +455,7 @@ const startTour = async () => {
|
||||
sidebarVisible.value = true
|
||||
await new Promise((r) => setTimeout(r, 300))
|
||||
}
|
||||
|
||||
|
||||
await moveCursorTo('.sidebar', '侧边栏:详细内容')
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
@@ -470,10 +465,7 @@ const startTour = async () => {
|
||||
)
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
await moveCursorTo(
|
||||
'.sidebar-section:nth-child(3)',
|
||||
'项目文件树:项目结构'
|
||||
)
|
||||
await moveCursorTo('.sidebar-section:nth-child(3)', '项目文件树:项目结构')
|
||||
}
|
||||
|
||||
const runEditorTour = async () => {
|
||||
@@ -487,22 +479,13 @@ const startTour = async () => {
|
||||
}
|
||||
|
||||
// --- 4. Editor Area ---
|
||||
await moveCursorTo(
|
||||
'.tabs',
|
||||
'标签页:已打开文件'
|
||||
)
|
||||
await moveCursorTo('.tabs', '标签页:已打开文件')
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
await moveCursorTo(
|
||||
'.breadcrumbs',
|
||||
'路径导航:文件路径'
|
||||
)
|
||||
await moveCursorTo('.breadcrumbs', '路径导航:文件路径')
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
await moveCursorTo(
|
||||
'.code-wrapper',
|
||||
'编辑区:编写代码'
|
||||
)
|
||||
await moveCursorTo('.code-wrapper', '编辑区:编写代码')
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
await moveCursorTo('.minimap', '缩略图:预览代码')
|
||||
@@ -513,33 +496,21 @@ const startTour = async () => {
|
||||
await moveCursorTo('.bottom-panel', '底部面板:集成工具')
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
await moveCursorTo(
|
||||
'.panel-tabs',
|
||||
'面板切换:切换工具'
|
||||
)
|
||||
await moveCursorTo('.panel-tabs', '面板切换:切换工具')
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
await moveCursorTo(
|
||||
'.terminal-content',
|
||||
'终端:运行命令'
|
||||
)
|
||||
await moveCursorTo('.terminal-content', '终端:运行命令')
|
||||
}
|
||||
|
||||
const runStatusTour = async () => {
|
||||
// --- 6. Status Bar ---
|
||||
await moveCursorTo(
|
||||
'.status-bar',
|
||||
'状态栏:全局信息'
|
||||
)
|
||||
await moveCursorTo('.status-bar', '状态栏:全局信息')
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
await moveCursorTo('.status-left', '左侧信息:Git/错误')
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
await moveCursorTo(
|
||||
'.status-right',
|
||||
'右侧信息:环境信息'
|
||||
)
|
||||
await moveCursorTo('.status-right', '右侧信息:环境信息')
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -553,8 +524,8 @@ const startTour = async () => {
|
||||
}
|
||||
|
||||
if (mode === 'all' || mode === 'extensions') {
|
||||
// --- Extensions Tour ---
|
||||
await moveCursorTo(
|
||||
// --- Extensions Tour ---
|
||||
await moveCursorTo(
|
||||
'.icon[title="Extensions"]',
|
||||
'扩展商店:安装插件',
|
||||
() => toggleSidebarView('EXTENSIONS')
|
||||
@@ -565,7 +536,7 @@ const startTour = async () => {
|
||||
'.sidebar-search input',
|
||||
'搜索插件:输入 python',
|
||||
async () => {
|
||||
await typeText('python', (v) => searchQuery.value = v)
|
||||
await typeText('python', (v) => (searchQuery.value = v))
|
||||
}
|
||||
)
|
||||
if (!isAutoPlaying.value) return
|
||||
@@ -576,17 +547,13 @@ const startTour = async () => {
|
||||
() => installExtension('python')
|
||||
)
|
||||
if (!isAutoPlaying.value) return
|
||||
|
||||
|
||||
// Switch back to explorer for next steps if in 'all' mode
|
||||
if (mode === 'all') {
|
||||
await moveCursorTo(
|
||||
'.icon[title="Explorer"]',
|
||||
'返回资源管理器',
|
||||
() => {
|
||||
toggleSidebarView('EXPLORER')
|
||||
searchQuery.value = '' // Clear search when leaving
|
||||
}
|
||||
)
|
||||
await moveCursorTo('.icon[title="Explorer"]', '返回资源管理器', () => {
|
||||
toggleSidebarView('EXPLORER')
|
||||
searchQuery.value = '' // Clear search when leaving
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -725,9 +692,7 @@ onUnmounted(() => {
|
||||
</div>
|
||||
<div
|
||||
class="menu-bar-container"
|
||||
@mouseenter.stop="
|
||||
showInfo('菜单栏:功能入口')
|
||||
"
|
||||
@mouseenter.stop="showInfo('菜单栏:功能入口')"
|
||||
@mouseleave="clearInfo"
|
||||
>
|
||||
<div
|
||||
@@ -832,7 +797,9 @@ onUnmounted(() => {
|
||||
<div class="top-icons">
|
||||
<div
|
||||
class="icon"
|
||||
:class="{ active: activeSidebarView === 'EXPLORER' && sidebarVisible }"
|
||||
:class="{
|
||||
active: activeSidebarView === 'EXPLORER' && sidebarVisible
|
||||
}"
|
||||
title="Explorer"
|
||||
@click="toggleSidebarView('EXPLORER')"
|
||||
@mouseenter.stop="showInfo('资源管理器:文件管理')"
|
||||
@@ -985,7 +952,9 @@ onUnmounted(() => {
|
||||
</div>
|
||||
<div
|
||||
class="icon"
|
||||
:class="{ active: activeSidebarView === 'EXTENSIONS' && sidebarVisible }"
|
||||
:class="{
|
||||
active: activeSidebarView === 'EXTENSIONS' && sidebarVisible
|
||||
}"
|
||||
title="Extensions"
|
||||
@click="toggleSidebarView('EXTENSIONS')"
|
||||
@mouseenter.stop="showInfo('扩展:插件')"
|
||||
@@ -1105,9 +1074,7 @@ onUnmounted(() => {
|
||||
<div
|
||||
class="sidebar"
|
||||
v-show="sidebarVisible"
|
||||
@mouseenter.stop="
|
||||
showInfo('侧边栏:详细内容')
|
||||
"
|
||||
@mouseenter.stop="showInfo('侧边栏:详细内容')"
|
||||
@mouseleave="clearInfo"
|
||||
>
|
||||
<div v-if="activeSidebarView === 'EXPLORER'" class="sidebar-content">
|
||||
@@ -1169,33 +1136,56 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="activeSidebarView === 'EXTENSIONS'" class="sidebar-content">
|
||||
<div class="sidebar-header">
|
||||
|
||||
<div
|
||||
v-else-if="activeSidebarView === 'EXTENSIONS'"
|
||||
class="sidebar-content"
|
||||
>
|
||||
<div class="sidebar-header">
|
||||
<span>EXTENSIONS</span>
|
||||
<span class="sidebar-dots">•••</span>
|
||||
</div>
|
||||
<div class="sidebar-search">
|
||||
<input type="text" placeholder="Search Extensions in Marketplace" :value="searchQuery" readonly />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search Extensions in Marketplace"
|
||||
:value="searchQuery"
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
<div class="sidebar-section expanded">
|
||||
<div class="section-header">▼ POPULAR</div>
|
||||
<div class="extension-list">
|
||||
<div v-for="ext in filteredExtensions" :key="ext.id" class="extension-item">
|
||||
<div
|
||||
v-for="ext in filteredExtensions"
|
||||
:key="ext.id"
|
||||
class="extension-item"
|
||||
>
|
||||
<div class="extension-icon"></div>
|
||||
<div class="extension-info">
|
||||
<div class="extension-name">
|
||||
{{ ext.name }}
|
||||
<span v-if="ext.installed" class="installed-badge">✔</span>
|
||||
<span v-if="ext.installed" class="installed-badge"
|
||||
>✔</span
|
||||
>
|
||||
</div>
|
||||
<div class="extension-desc">{{ ext.description }}</div>
|
||||
<div class="extension-actions">
|
||||
<button
|
||||
class="install-btn"
|
||||
:class="{ installing: ext.installing, installed: ext.installed }"
|
||||
<button
|
||||
class="install-btn"
|
||||
:class="{
|
||||
installing: ext.installing,
|
||||
installed: ext.installed
|
||||
}"
|
||||
@click.stop="installExtension(ext.id)"
|
||||
>
|
||||
{{ ext.installed ? 'Manage' : (ext.installing ? 'Installing' : 'Install') }}
|
||||
{{
|
||||
ext.installed
|
||||
? 'Manage'
|
||||
: ext.installing
|
||||
? 'Installing'
|
||||
: 'Install'
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1436,9 +1426,7 @@ onUnmounted(() => {
|
||||
<!-- Status Bar -->
|
||||
<div
|
||||
class="status-bar"
|
||||
@mouseenter.stop="
|
||||
showInfo('状态栏:环境信息')
|
||||
"
|
||||
@mouseenter.stop="showInfo('状态栏:环境信息')"
|
||||
@mouseleave="clearInfo"
|
||||
>
|
||||
<div class="status-left">
|
||||
@@ -2416,8 +2404,17 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
@keyframes highlightPulse {
|
||||
0% { transform: scale(1); opacity: 0.7; }
|
||||
50% { transform: scale(1.02); opacity: 0.9; }
|
||||
100% { transform: scale(1); opacity: 0.7; }
|
||||
0% {
|
||||
transform: scale(1);
|
||||
opacity: 0.7;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.02);
|
||||
opacity: 0.9;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user