Files
test-repo/docs/.vitepress/theme/components/appendix/ide-intro/AiHelpDemo.vue
T

708 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="ai-help-demo">
<div class="desktop-container">
<!-- 1. VS Code 窗口 (全功能模拟) -->
<div class="window vscode" :class="getWindowClass('vscode')">
<!-- 标题栏 -->
<div class="title-bar">
<div class="controls">
<span class="dot red"></span>
<span class="dot yellow"></span>
<span class="dot green"></span>
</div>
<div class="title-text">App.vue - easy-vibe - Visual Studio Code</div>
</div>
<div class="main-layout">
<!-- 侧边栏 (Activity Bar) -->
<div class="activity-bar">
<div class="icon active">📁</div>
<div class="icon">🔍</div>
<div class="icon">🌿</div>
<div class="icon">🐛</div>
<div class="icon">🧩</div>
</div>
<!-- 资源管理器 (Sidebar) -->
<div class="sidebar">
<div class="sidebar-title">EXPLORER</div>
<div class="file-tree">
<div class="tree-item expanded">
<span class="arrow"></span> src
</div>
<div class="tree-item indent">
<span class="icon">📄</span> main.js
</div>
<div class="tree-item indent active">
<span class="icon">V</span> App.vue
</div>
<div class="tree-item indent">
<span class="icon">🎨</span> style.css
</div>
</div>
</div>
<!-- 编辑器区域 -->
<div class="editor-area">
<div class="tab-bar">
<div class="tab active">
<span class="icon">V</span> App.vue <span class="close">×</span>
</div>
</div>
<div class="code-content">
<div class="line-numbers">
<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"></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">&nbsp;&nbsp;<span class="comment">// 等待数据加载完成</span></div>
<div class="line">&nbsp;&nbsp;<span class="kwd">await</span> <span class="func">fetchData</span>()</div>
<div class="line">&nbsp;&nbsp;</div>
<div class="line" ref="targetCode">
&nbsp;&nbsp;<span class="comment">// 👈 等待 DOM 更新后再渲染图表</span>
</div>
<div class="line" ref="targetCode2">
&nbsp;&nbsp;<span class="kwd">await</span> <span class="func">nextTick</span>()
</div>
<div class="line">&nbsp;&nbsp;</div>
<div class="line">&nbsp;&nbsp;<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">&nbsp;&nbsp;<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="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>
</div>
</div>
<!-- 模拟操作引导 -->
<div class="guide-overlay" v-if="step === 'idle'">
<div class="start-btn" @click="startDemo">
<span>📸 演示遇到代码不懂怎么问 AI</span>
</div>
</div>
</div>
<!-- 2. ChatGPT 窗口 -->
<div class="window chatgpt" :class="getWindowClass('chatgpt')">
<div class="chat-sidebar">
<div class="new-chat">+ New chat</div>
<div class="history-item">Previous 7 Days</div>
<div class="history-item active">Vue nextTick explanation</div>
<div class="history-item">CSS Grid layout</div>
</div>
<div class="chat-main">
<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>
<div class="msg-text">{{ typedText }}</div>
</div>
</div>
<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>
</div>
<div class="msg-bubble ai-bubble">
<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>
</ul>
<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="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>
</div>
<button class="send-btn" :class="{ active: typedText.length > 5 }"></button>
</div>
</div>
</div>
</div>
<!-- 全局重置按钮 -->
<button class="reset-btn" v-if="step === 'finished'" @click="reset">
🔄 重播
</button>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
// 状态机
// idle: 初始状态
// selecting: 正在截图(选框出现)
// captured: 截图完成(闪烁)
// switching: 窗口切换中
// pasting: ChatGPT 界面,显示输入动作
// typing: 正在打字
// sending: 发送中
// responding: AI 回复中
// finished: 结束
const step = ref('idle')
const typedText = ref('')
const isTyping = ref(false)
const stepInt = computed(() => {
const map = {
idle: 0,
selecting: 1,
captured: 2,
switching: 3,
pasting: 4,
typing: 5,
sending: 6,
responding: 7,
finished: 8
}
return map[step.value] || 0
})
const getWindowClass = (winName) => {
if (winName === 'vscode') {
if (stepInt.value < 3) return 'active'
if (stepInt.value === 3) return 'inactive zoom-out'
return 'inactive hidden'
}
if (winName === 'chatgpt') {
if (stepInt.value < 3) return 'inactive hidden'
if (stepInt.value === 3) return 'active zoom-in'
return 'active'
}
}
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 = "帮我看下这张图,左边红框里那一块是干嘛用的?"
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'
}
const reset = () => {
step.value = 'idle'
typedText.value = ''
}
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;
}
.desktop-container {
position: relative;
width: 100%;
height: 400px; /* 增加高度以容纳更多内容 */
background: #333; /* 桌面背景 */
border-radius: 12px;
overflow: hidden;
box-shadow: 0 20px 50px rgba(0,0,0,0.3);
}
/* 通用窗口样式 */
.window {
position: absolute;
top: 5%;
left: 5%;
width: 90%;
height: 90%;
border-radius: 8px;
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);
overflow: hidden;
background: #fff;
}
/* 窗口状态动画 */
.window.active {
opacity: 1;
transform: scale(1);
z-index: 2;
filter: blur(0);
}
.window.inactive {
opacity: 0;
pointer-events: none;
z-index: 1;
}
.window.zoom-out {
transform: scale(0.9);
opacity: 0;
filter: blur(2px);
}
.window.zoom-in {
animation: zoomIn 0.5s forwards;
}
@keyframes zoomIn {
from { transform: scale(1.1); opacity: 0; filter: blur(4px); }
to { transform: scale(1); opacity: 1; filter: blur(0); }
}
/* ================= VS Code 样式 ================= */
.vscode {
background: #1e1e1e;
color: #ccc;
font-family: 'Consolas', 'Monaco', monospace;
}
.title-bar {
height: 30px;
background: #252526;
display: flex;
align-items: center;
padding: 0 10px;
font-size: 12px;
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; }
.main-layout { flex: 1; display: flex; overflow: hidden; }
.activity-bar {
width: 40px;
background: #333;
display: flex;
flex-direction: column;
align-items: center;
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); }
.sidebar {
width: 180px;
background: #252526;
border-right: 1px solid #1e1e1e;
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; }
.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;
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
border-top: 1px solid #007acc;
}
.tab .close { margin-left: 8px; font-size: 14px; }
.code-content {
flex: 1;
display: flex;
padding: 10px 0;
font-size: 13px;
line-height: 20px;
position: relative;
}
.line-numbers {
width: 40px;
text-align: right;
padding-right: 15px;
color: #6e7681;
user-select: none;
}
.code-lines { flex: 1; padding-left: 5px; }
/* 语法高亮 */
.kwd { color: #569cd6; }
.var { color: #9cdcfe; }
.func { color: #dcdcaa; }
.str { color: #ce9178; }
.comment { color: #6a9955; }
/* 截图覆盖层 */
.screenshot-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.3);
z-index: 10;
cursor: crosshair;
}
.selection-box {
position: absolute;
top: 40px; /* 覆盖左侧 Sidebar */
left: 0;
width: 280px;
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); /* 遮罩效果 */
animation: selectAnim 0.5s ease-out;
}
.selection-box.flashed {
animation: flash 0.3s;
background: rgba(255, 255, 255, 0.3);
}
@keyframes selectAnim {
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); }
}
.selection-size {
position: absolute;
bottom: -20px;
right: 0;
background: #ff5f56;
color: white;
font-size: 10px;
padding: 2px 4px;
}
/* UI Snapshot Styling */
.ui-snapshot {
background: #252526;
padding: 8px;
border-radius: 4px;
display: flex;
align-items: center;
gap: 8px;
}
.snapshot-rect {
width: 30px;
height: 20px;
border: 2px solid #ff5f56;
background: rgba(255, 95, 86, 0.2);
}
.snapshot-text {
font-size: 11px;
color: #ccc;
}
.mini-snapshot-ui {
width: 40px;
height: 30px;
background: #252526;
border: 1px solid #565869;
display: flex;
align-items: center;
justify-content: center;
}
.mini-rect {
width: 20px;
height: 14px;
border: 1px solid #ff5f56;
}
/* ================= ChatGPT 样式 ================= */
.chatgpt {
background: #343541;
color: #ececf1;
display: flex;
flex-direction: row;
}
.chat-sidebar {
width: 200px;
background: #202123;
padding: 10px;
display: flex;
flex-direction: column;
gap: 10px;
}
.new-chat {
border: 1px solid #565869;
border-radius: 5px;
padding: 10px;
font-size: 13px;
text-align: left;
cursor: pointer;
}
.history-item {
font-size: 13px;
color: #ececf1;
padding: 8px;
border-radius: 5px;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.history-item.active { background: #343541; }
.chat-main {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
background: #343541;
}
.chat-model-selector {
height: 40px;
display: flex;
align-items: center;
padding: 0 20px;
font-weight: 600;
color: #d2d6db;
border-bottom: 1px solid rgba(255,255,255,0.1);
font-size: 14px;
}
.messages-container {
flex: 1;
overflow-y: auto;
padding: 20px;
display: flex;
flex-direction: column;
gap: 20px;
}
.msg-row {
display: flex;
gap: 15px;
max-width: 80%;
}
.msg-row.user { align-self: flex-end; flex-direction: row-reverse; }
.msg-row.ai { align-self: flex-start; }
.avatar {
width: 30px;
height: 30px;
border-radius: 2px;
display: flex;
align-items: center;
justify-content: center;
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; }
.msg-bubble {
background: transparent;
padding: 0;
font-size: 14px;
line-height: 1.6;
}
.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);
}
.pasted-image {
margin-bottom: 8px;
display: inline-block;
border: 1px solid #565869;
border-radius: 4px;
overflow: hidden;
}
.code-snapshot {
background: #1e1e1e;
padding: 8px;
font-family: monospace;
font-size: 11px;
}
.chat-input-area {
padding: 20px;
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-radius: 12px;
padding: 10px 12px;
display: flex;
align-items: center;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
position: relative;
}
.input-preview {
position: absolute;
bottom: 100%;
left: 0;
margin-bottom: 8px;
background: #40414f;
padding: 4px;
border-radius: 4px;
border: 1px solid #565869;
animation: slideUp 0.3s;
}
.fake-input {
flex: 1;
font-size: 14px;
color: white;
min-height: 24px;
display: flex;
align-items: center;
}
.placeholder { color: #8e8ea0; }
.send-btn {
background: #19c37d;
border: none;
width: 28px;
height: 28px;
border-radius: 4px;
color: white;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.5;
transition: opacity 0.2s;
}
.send-btn.active { opacity: 1; }
/* 引导层 */
.guide-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.4);
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(2px);
z-index: 100;
}
.start-btn {
background: white;
color: #333;
padding: 12px 24px;
border-radius: 30px;
font-weight: bold;
cursor: pointer;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
transition: transform 0.2s;
}
.start-btn:hover { transform: scale(1.05); }
.reset-btn {
position: absolute;
top: 10px;
right: 10px;
z-index: 1000;
background: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
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); }
}
</style>