style(docs): improve typography and layout consistency

- Standardize font sizes and line heights across docs
- Add ChapterIntroduction component for consistent chapter headers
- Fix markdown formatting and whitespace issues
- Improve code block and table styling
- Add font size and line height controls to layout
This commit is contained in:
sanbuphy
2026-01-13 14:42:34 +08:00
parent 7c546e62f8
commit 1d25eb9b9b
20 changed files with 1655 additions and 945 deletions
+7
View File
@@ -79,6 +79,7 @@ stage-{N}/
``` ```
Examples: Examples:
- `stage-1/1.1-introduction-to-ai-ide/index.md` - `stage-1/1.1-introduction-to-ai-ide/index.md`
- `stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md` - `stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md`
@@ -89,18 +90,21 @@ Examples:
The project uses **VitePress 2.0.0-alpha.15** with these key features: The project uses **VitePress 2.0.0-alpha.15** with these key features:
**Configuration** (`docs/.vitepress/config.mjs`): **Configuration** (`docs/.vitepress/config.mjs`):
- **Single Sidebar**: Route-based sidebars configured per path prefix (`/stage-0/`, `/stage-1/`, etc.) - **Single Sidebar**: Route-based sidebars configured per path prefix (`/stage-0/`, `/stage-1/`, etc.)
- **Navigation**: Top nav with links to each stage and appendix - **Navigation**: Top nav with links to each stage and appendix
- **Search**: Local search via `minisearch` (no external API required) - **Search**: Local search via `minisearch` (no external API required)
- **Dark Mode**: Built-in VitePress theme with toggle - **Dark Mode**: Built-in VitePress theme with toggle
**Custom Theme** (`docs/.vitepress/theme/`): **Custom Theme** (`docs/.vitepress/theme/`):
- **Image Viewer**: Viewer.js integration for zoom/rotate/flip on all images - **Image Viewer**: Viewer.js integration for zoom/rotate/flip on all images
- **Typewriter Effect**: TypeIt.js for homepage hero tagline animation - **Typewriter Effect**: TypeIt.js for homepage hero tagline animation
- **Image Optimization**: Automatic image height classes based on aspect ratio - **Image Optimization**: Automatic image height classes based on aspect ratio
- **Custom Layout**: Extends default theme with `Layout.vue` override - **Custom Layout**: Extends default theme with `Layout.vue` override
**Key Theme Behaviors**: **Key Theme Behaviors**:
- Images with aspect ratio > 1.2 get height-limited classes (tall/very-tall/ultra-tall) - Images with aspect ratio > 1.2 get height-limited classes (tall/very-tall/ultra-tall)
- Viewer.js initialized on `.vp-doc` container on each route change - Viewer.js initialized on `.vp-doc` container on each route change
- Typewriter effect only activates on homepage when `frontmatter.hero.tagline` is an array - Typewriter effect only activates on homepage when `frontmatter.hero.tagline` is an array
@@ -116,6 +120,7 @@ The sidebar is defined in `docs/.vitepress/config.mjs`. When adding new chapters
5. Links should not include `index` - use directory path with trailing slash 5. Links should not include `index` - use directory path with trailing slash
Example pattern: Example pattern:
```javascript ```javascript
{ {
text: 'Chapter Title', text: 'Chapter Title',
@@ -134,11 +139,13 @@ Example pattern:
### Deployment ### Deployment
**Vercel** (vercel.json): **Vercel** (vercel.json):
- Build command: `npm run build` - Build command: `npm run build`
- Output directory: `docs/.vitepress/dist` - Output directory: `docs/.vitepress/dist`
- Framework: vitepress - Framework: vitepress
**Preview Production Build**: **Preview Production Build**:
```bash ```bash
npm run build npm run build
npm run preview # Preview built site locally npm run preview # Preview built site locally
+7 -7
View File
@@ -1,4 +1,3 @@
<!-- trigger vercel build --> <!-- trigger vercel build -->
<div align='center'> <div align='center'>
<img src="./assets/head.png" alt="easy-vibe" width="100%"> <img src="./assets/head.png" alt="easy-vibe" width="100%">
@@ -25,6 +24,7 @@
当你尝试用AI写代码,出错不断,时常想放弃,不清楚如何将程序真正上线 😢。 当你尝试用AI写代码,出错不断,时常想放弃,不清楚如何将程序真正上线 😢。
本教程专门设计,从0到1,带你逐步掌握 Vibe Coding 技巧: 本教程专门设计,从0到1,带你逐步掌握 Vibe Coding 技巧:
- 第零阶段:从**小游戏快速入门** 上手 vibe coding 技能 - 第零阶段:从**小游戏快速入门** 上手 vibe coding 技能
- 第一阶段:产品经理视角下的 **vibe coding 技能和业务理解**,实现 Web 应用原型 - 第一阶段:产品经理视角下的 **vibe coding 技能和业务理解**,实现 Web 应用原型
- 第二阶段:学习**前后端开发与 AI 能力**相关的 vibecoding 技巧,完成完整应用 - 第二阶段:学习**前后端开发与 AI 能力**相关的 vibecoding 技巧,完成完整应用
@@ -66,7 +66,7 @@
#### 附录 #### 附录
| 章节 | 关键内容 | 状态 | | 章节 | 关键内容 | 状态 |
| :-------------------------------------------------------------------------- | :---------------------------------------- | :--- | | :-------------------------------------------------------------------------- | :--------------------------------- | :--- |
| [附录A:产品思维补充](docs/stage-1/appendix-a-product-thinking/index.md) | 从零到一做产品需要考虑的思维框架 | ✅ | | [附录A:产品思维补充](docs/stage-1/appendix-a-product-thinking/index.md) | 从零到一做产品需要考虑的思维框架 | ✅ |
| [附录B:常见报错及解决方案](docs/stage-1/appendix-b-common-errors/index.md) | vibe coding 中的常见错误及排查方法 | ✅ | | [附录B:常见报错及解决方案](docs/stage-1/appendix-b-common-errors/index.md) | vibe coding 中的常见错误及排查方法 | ✅ |
@@ -75,7 +75,7 @@
#### 前端部分 #### 前端部分
| 章节 | 关键内容 | 状态 | | 章节 | 关键内容 | 状态 |
| :------------------------------------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------- | :--- | | :------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------- | :--- |
| [前端零:使用 lovart 生产素材](docs/stage-2/frontend/2.0-lovart-assets/) | 学会用 lovart 批量生成人物、场景等视觉素材,为 UI 设计和前端开发提供素材基础 | 🚧 | | [前端零:使用 lovart 生产素材](docs/stage-2/frontend/2.0-lovart-assets/) | 学会用 lovart 批量生成人物、场景等视觉素材,为 UI 设计和前端开发提供素材基础 | 🚧 |
| [前端一:Figma 与 MasterGo 入门](docs/stage-2/frontend/2.1-figma-mastergo/) | 用设计工具梳理信息架构和页面结构,为前端实现打基础 | 🚧 | | [前端一:Figma 与 MasterGo 入门](docs/stage-2/frontend/2.1-figma-mastergo/) | 用设计工具梳理信息架构和页面结构,为前端实现打基础 | 🚧 |
| [前端二:构建第一个现代应用程序-UI 设计](docs/stage-2/frontend/2.2-ui-design/) | 基于设计稿完成组件化界面,实现从设计到代码的第一条链路 | 🚧 | | [前端二:构建第一个现代应用程序-UI 设计](docs/stage-2/frontend/2.2-ui-design/) | 基于设计稿完成组件化界面,实现从设计到代码的第一条链路 | 🚧 |
@@ -85,7 +85,7 @@
#### 后端与全栈部分 #### 后端与全栈部分
| 章节 | 关键内容 | 状态 | | 章节 | 关键内容 | 状态 |
| :------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------ | :--- | | :-------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------ | :--- |
| [后端一:什么是 API](docs/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | 理解 HTTP 接口与请求响应模型,为后端集成与联调做准备 | ✅ | | [后端一:什么是 API](docs/stage-2/backend/2.1-what-is-api/extra2/extra2-what-is-api.md) | 理解 HTTP 接口与请求响应模型,为后端集成与联调做准备 | ✅ |
| [后端二:从数据库到 Supabase](docs/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) | 在 Supabase 上落地数据库和 API,打通数据模型与前端页面 | ✅ | | [后端二:从数据库到 Supabase](docs/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase.md) | 在 Supabase 上落地数据库和 API,打通数据模型与前端页面 | ✅ |
| [后端三:大模型辅助编写接口代码与接口文档](docs/stage-2/backend/2.3-ai-interface-code/) | 用大模型协助生成接口与数据库文档及代码,实现可读可测的后端 | 🚧 | | [后端三:大模型辅助编写接口代码与接口文档](docs/stage-2/backend/2.3-ai-interface-code/) | 用大模型协助生成接口与数据库文档及代码,实现可读可测的后端 | 🚧 |
@@ -99,14 +99,14 @@
#### AI 能力附录 #### AI 能力附录
| 章节 | 关键内容 | 状态 | | 章节 | 关键内容 | 状态 |
| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------- | :--- | | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------- | :--- |
| [AI 一:Dify 入门与知识库集成](docs/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) | 用 Dify Workflow 与基础 RAG 搭建工具类产品,为后续应用升级打样 | ✅ | | [AI 一:Dify 入门与知识库集成](docs/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration.md) | 用 Dify Workflow 与基础 RAG 搭建工具类产品,为后续应用升级打样 | ✅ |
| [AI 二:学会查询 AI 词典与集成多模态 API](docs/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) | 学会查找合适的模型与 API,并把文本、图像等多模态能力接入产品 | 🚧 | | [AI 二:学会查询 AI 词典与集成多模态 API](docs/stage-2/ai-capabilities/2.2-multimodal-api/extra3/extra3-ai-capability-starter-handbook.md) | 学会查找合适的模型与 API,并把文本、图像等多模态能力接入产品 | 🚧 |
### 三、高级开发工程师 ### 三、高级开发工程师
| 章节 | 关键内容 | 状态 | | 章节 | 关键内容 | 状态 |
| :-------------------------------------------------------------------- | :----------------------------------------------------------- | :--- | | :------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- | :--- |
| [高级一:MCP 与 ClaudeCode Skills](docs/stage-3/core-skills/3.1-mcp-claudecode-skills/) | 通过 MCP 与 Skills 扩展 IDE 能力,把外部服务接成工具 | 🚧 | | [高级一:MCP 与 ClaudeCode Skills](docs/stage-3/core-skills/3.1-mcp-claudecode-skills/) | 通过 MCP 与 Skills 扩展 IDE 能力,把外部服务接成工具 | 🚧 |
| [高级二:如何让 Coding Tools 长时间工作](docs/stage-3/core-skills/3.2-long-running-tasks/) | 设计和配置长时间运行的任务,让 Coding Tools 更稳定可靠 | 🚧 | | [高级二:如何让 Coding Tools 长时间工作](docs/stage-3/core-skills/3.2-long-running-tasks/) | 设计和配置长时间运行的任务,让 Coding Tools 更稳定可靠 | 🚧 |
| [高级三:多平台开发:如何构建微信小程序](docs/stage-3/cross-platform/3.3-wechat-miniprogram/) | 了解微信小程序生态,从官方模板到上线完成一个前端小程序 | ✅ | | [高级三:多平台开发:如何构建微信小程序](docs/stage-3/cross-platform/3.3-wechat-miniprogram/) | 了解微信小程序生态,从官方模板到上线完成一个前端小程序 | ✅ |
@@ -118,7 +118,7 @@
#### AI 能力附录 #### AI 能力附录
| 章节 | 关键内容 | 状态 | | 章节 | 关键内容 | 状态 |
| :-------------------------------------------------------------------- | :------------------------------------------------------ | :--- | | :-------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------ | :--- |
| [高级 AI 一:什么是 RAG 以及它如何工作](docs/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) | 系统理解 RAG 原理与常见架构,为复杂应用提供知识检索基础 | ✅ | | [高级 AI 一:什么是 RAG 以及它如何工作](docs/stage-3/ai-advanced/3.a1-rag-introduction/extra5-what-is-rag-and-how-does-it-work-and-future.md) | 系统理解 RAG 原理与常见架构,为复杂应用提供知识检索基础 | ✅ |
| [高级 AI 二:中高级 RAG 与工作流编排:以 LangGraph 为例](docs/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | 使用 LangGraph 等工具设计多步工作流与中高级 RAG 系统 | 🚧 | | [高级 AI 二:中高级 RAG 与工作流编排:以 LangGraph 为例](docs/stage-3/ai-advanced/3.a2-langgraph-advanced-rag/) | 使用 LangGraph 等工具设计多步工作流与中高级 RAG 系统 | 🚧 |
+1 -2
View File
@@ -71,8 +71,7 @@ export default defineConfig({
{ {
text: '附录 B:常见报错及解决方案', text: '附录 B:常见报错及解决方案',
link: '/stage-1/appendix-b-common-errors/' link: '/stage-1/appendix-b-common-errors/'
} },
,
{ {
text: '附录示例:贪吃蛇游戏教程', text: '附录示例:贪吃蛇游戏教程',
link: '/stage-1/appendix-articles/example0-1/vibe-coding-tools-snake-game-tutorial' link: '/stage-1/appendix-articles/example0-1/vibe-coding-tools-snake-game-tutorial'
+239 -3
View File
@@ -2,6 +2,7 @@
import DefaultTheme from 'vitepress/theme' import DefaultTheme from 'vitepress/theme'
import { useData } from 'vitepress' import { useData } from 'vitepress'
import TextType from './components/TextType.vue' import TextType from './components/TextType.vue'
import { onMounted, ref, watch } from 'vue'
const { frontmatter } = useData() const { frontmatter } = useData()
@@ -12,16 +13,251 @@ const homeTaglineTyping = {
postDeletingDelay: 500, postDeletingDelay: 500,
deletingSpeed: 18 deletingSpeed: 18
} }
const FONT_SIZE_STORAGE_KEY = 'ev-doc-font-size'
const LINE_HEIGHT_STORAGE_KEY = 'ev-doc-line-height'
const MIN_FONT_SIZE = 12
const MAX_FONT_SIZE = 18
const DEFAULT_FONT_SIZE = 13
const MIN_LINE_HEIGHT = 1.25
const MAX_LINE_HEIGHT = 1.8
const DEFAULT_LINE_HEIGHT = 1.5
const fontSize = ref(DEFAULT_FONT_SIZE)
const lineHeight = ref(DEFAULT_LINE_HEIGHT)
const isHydrated = ref(false)
const clampFontSize = (value) => {
const numeric = Number(value)
if (!Number.isFinite(numeric)) return DEFAULT_FONT_SIZE
return Math.min(MAX_FONT_SIZE, Math.max(MIN_FONT_SIZE, numeric))
}
const clampLineHeight = (value) => {
const numeric = Number(value)
if (!Number.isFinite(numeric)) return DEFAULT_LINE_HEIGHT
return Math.min(MAX_LINE_HEIGHT, Math.max(MIN_LINE_HEIGHT, numeric))
}
const applyFontSize = (size) => {
if (typeof document === 'undefined') return
document.documentElement.style.setProperty('--ev-doc-font-size', `${size}px`)
}
const applyLineHeight = (value) => {
if (typeof document === 'undefined') return
document.documentElement.style.setProperty('--ev-doc-line-height', String(value))
}
const decreaseFontSize = () => {
fontSize.value = clampFontSize(fontSize.value - 1)
}
const increaseFontSize = () => {
fontSize.value = clampFontSize(fontSize.value + 1)
}
const resetFontSize = () => {
fontSize.value = DEFAULT_FONT_SIZE
}
const resetLineHeight = () => {
lineHeight.value = DEFAULT_LINE_HEIGHT
}
onMounted(() => {
const saved = clampFontSize(localStorage.getItem(FONT_SIZE_STORAGE_KEY))
const savedLineHeight = clampLineHeight(localStorage.getItem(LINE_HEIGHT_STORAGE_KEY))
fontSize.value = saved
lineHeight.value = savedLineHeight
applyFontSize(saved)
applyLineHeight(savedLineHeight)
isHydrated.value = true
})
watch(fontSize, (next) => {
if (!isHydrated.value) return
const normalized = clampFontSize(next)
applyFontSize(normalized)
localStorage.setItem(FONT_SIZE_STORAGE_KEY, String(normalized))
})
watch(lineHeight, (next) => {
if (!isHydrated.value) return
const normalized = clampLineHeight(next)
applyLineHeight(normalized)
localStorage.setItem(LINE_HEIGHT_STORAGE_KEY, String(normalized))
})
</script> </script>
<template> <template>
<DefaultTheme.Layout> <DefaultTheme.Layout>
<template #home-hero-info-after> <template #nav-bar-content-after>
<div v-if="frontmatter.layout === 'home' && frontmatter.hero?.tagline" class="vp-typed-tagline">
<ClientOnly> <ClientOnly>
<TextType :text="frontmatter.hero.tagline" v-bind="homeTaglineTyping" :loop="true" /> <el-popover placement="bottom-end" trigger="click" :width="260">
<template #reference>
<button
class="ev-fontsize-button"
type="button"
aria-label="阅读设置"
style="margin-left: 16px; padding: 0; width: 32px;"
>
<el-icon :size="16"><Setting /></el-icon>
</button>
</template>
<div class="ev-fontsize-panel">
<div class="ev-setting-group">
<div class="ev-setting-header">
<div class="ev-setting-title">字号</div>
<div class="ev-setting-value">{{ fontSize }}px</div>
</div>
<div class="ev-fontsize-actions">
<button
class="ev-fontsize-action"
type="button"
@click="decreaseFontSize"
>
A-
</button>
<button
class="ev-fontsize-action"
type="button"
@click="resetFontSize"
>
默认
</button>
<button
class="ev-fontsize-action"
type="button"
@click="increaseFontSize"
>
A+
</button>
</div>
<el-slider v-model="fontSize" :min="MIN_FONT_SIZE" :max="MAX_FONT_SIZE" :step="1" />
</div>
<div class="ev-setting-group">
<div class="ev-setting-header">
<div class="ev-setting-title">行距</div>
<div class="ev-setting-value">{{ lineHeight.toFixed(2) }}</div>
</div>
<div class="ev-fontsize-actions">
<button class="ev-fontsize-action" type="button" @click="resetLineHeight">
默认
</button>
<button
class="ev-fontsize-action"
type="button"
@click="lineHeight = clampLineHeight(lineHeight - 0.05)"
>
更紧
</button>
<button
class="ev-fontsize-action"
type="button"
@click="lineHeight = clampLineHeight(lineHeight + 0.05)"
>
更松
</button>
</div>
<el-slider
v-model="lineHeight"
:min="MIN_LINE_HEIGHT"
:max="MAX_LINE_HEIGHT"
:step="0.05"
/>
</div>
</div>
</el-popover>
</ClientOnly>
</template>
<template #home-hero-info-after>
<div
v-if="frontmatter.layout === 'home' && frontmatter.hero?.tagline"
class="vp-typed-tagline"
>
<ClientOnly>
<TextType
:text="frontmatter.hero.tagline"
v-bind="homeTaglineTyping"
:loop="true"
/>
</ClientOnly> </ClientOnly>
</div> </div>
</template> </template>
</DefaultTheme.Layout> </DefaultTheme.Layout>
</template> </template>
<style>
.ev-fontsize-button {
display: inline-flex;
align-items: center;
justify-content: center;
height: 32px;
min-width: 32px;
padding: 0 10px;
border: 1px solid var(--vp-c-divider);
border-radius: 999px;
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
font-size: 13px;
font-weight: 600;
line-height: 1;
cursor: pointer;
}
.ev-fontsize-button:hover {
border-color: var(--vp-c-brand);
color: var(--vp-c-brand);
}
.ev-fontsize-panel {
display: grid;
gap: 12px;
}
.ev-setting-group {
display: grid;
gap: 8px;
}
.ev-setting-header {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
}
.ev-setting-title {
font-size: 13px;
font-weight: 600;
color: var(--vp-c-text-1);
}
.ev-setting-value {
font-size: 12px;
color: var(--vp-c-text-2);
}
.ev-fontsize-actions {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.ev-fontsize-action {
height: 32px;
border: 1px solid var(--vp-c-divider);
border-radius: 10px;
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
font-size: 13px;
cursor: pointer;
}
.ev-fontsize-action:hover {
border-color: var(--vp-c-brand);
color: var(--vp-c-brand);
}
</style>
@@ -0,0 +1,266 @@
<script setup>
import { computed } from 'vue'
const props = defineProps({
duration: {
type: String,
default: ''
},
expectedOutput: {
type: String,
default: ''
},
coreOutput: {
type: String,
default: ''
},
assignment: {
type: String,
default: ''
},
tags: {
type: Array,
default: () => []
}
})
const hasMeta = computed(() => props.duration || props.expectedOutput || props.coreOutput || props.assignment)
const hasTags = computed(() => props.tags && props.tags.length > 0)
</script>
<template>
<div class="chapter-introduction">
<!-- Learning Objective -->
<div class="objective-section">
<div class="objective-label">
<span class="icon">🎯</span>
<span class="title">本章学习目标</span>
</div>
<div class="content">
<!-- If tags are provided, show tags list -->
<div v-if="hasTags" class="tags-container">
<span v-for="(tag, index) in tags" :key="index" class="objective-tag">
{{ tag }}
</span>
</div>
<!-- Slot content (full description) always rendered below tags if tags exist, or alone if not -->
<div class="description-text" :class="{ 'has-tags': hasTags }">
<slot></slot>
</div>
</div>
</div>
<!-- Metrics Grid -->
<div v-if="hasMeta" class="metrics-grid">
<!-- Duration Card -->
<div v-if="duration" class="metric-card time-card">
<div class="card-icon"></div>
<div class="card-content">
<div class="card-label">预计耗时</div>
<div class="card-value" v-html="duration"></div>
</div>
</div>
<!-- Output Card -->
<div v-if="expectedOutput || coreOutput" class="metric-card output-card">
<div class="card-icon">📦</div>
<div class="card-content">
<div class="card-label">预期产出</div>
<div class="output-container">
<div v-if="coreOutput" class="core-output">{{ coreOutput }}</div>
<div v-if="expectedOutput" class="output-desc" v-html="expectedOutput"></div>
</div>
</div>
</div>
<!-- Assignment Card -->
<div v-if="assignment" class="metric-card task-card">
<div class="card-icon">📝</div>
<div class="card-content">
<div class="card-label">课后任务</div>
<div class="card-value" v-html="assignment"></div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.chapter-introduction {
margin: 24px 0;
border-radius: 16px;
background-color: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
overflow: hidden;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.04);
}
.objective-section {
padding: 24px 28px;
background: linear-gradient(to right, rgba(var(--vp-c-brand-rgb), 0.05), transparent);
border-bottom: 1px dashed var(--vp-c-divider);
}
.objective-label {
display: flex;
align-items: center;
margin-bottom: 16px;
color: var(--vp-c-brand);
}
.icon {
font-size: 20px;
margin-right: 8px;
}
.title {
font-size: 14px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.content {
font-size: 16px;
line-height: 1.7;
color: var(--vp-c-text-1);
font-weight: 500;
}
/* Tags Styling */
.tags-container {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.description-text {
font-size: 16px;
line-height: 1.7;
color: var(--vp-c-text-1);
}
.description-text.has-tags {
margin-top: 16px;
font-size: 14px;
color: var(--vp-c-text-2);
border-top: 1px solid var(--vp-c-divider);
padding-top: 12px;
}
.objective-tag {
display: inline-flex;
align-items: center;
padding: 6px 14px;
background-color: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-radius: 99px;
font-size: 14px;
font-weight: 600;
color: var(--vp-c-text-1);
transition: all 0.2s;
}
.objective-tag:hover {
border-color: var(--vp-c-brand);
color: var(--vp-c-brand);
background-color: var(--vp-c-bg-soft);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
/* Metrics Grid */
.metrics-grid {
display: flex;
flex-wrap: wrap;
gap: 1px;
background-color: var(--vp-c-divider);
border-top: 1px solid var(--vp-c-divider);
}
.metric-card {
flex: 1 1 240px;
background-color: var(--vp-c-bg-soft);
padding: 20px 24px;
display: flex;
align-items: flex-start;
gap: 16px;
transition: background-color 0.2s;
}
.metric-card:hover {
background-color: var(--vp-c-bg-alt);
}
.card-icon {
font-size: 24px;
line-height: 1;
padding-top: 2px;
}
.card-content {
flex: 1;
display: flex;
flex-direction: column;
}
.card-label {
font-size: 12px;
color: var(--vp-c-text-2);
margin-bottom: 8px;
font-weight: 600;
text-transform: uppercase;
}
.card-value {
font-size: 14px;
line-height: 1.5;
color: var(--vp-c-text-1);
}
.card-value :deep(strong) {
display: inline-block;
color: var(--vp-c-brand-dark);
font-weight: 800;
font-size: 16px;
margin-top: 2px;
}
/* Output Container Styling */
.output-container {
display: flex;
flex-direction: column;
gap: 4px;
}
.core-output {
font-size: 18px;
font-weight: 800;
color: var(--vp-c-brand);
line-height: 1.4;
margin-bottom: 2px;
}
.output-desc {
font-size: 13px;
color: var(--vp-c-text-2);
line-height: 1.4;
}
.output-desc :deep(strong) {
color: var(--vp-c-text-1);
font-weight: 600;
}
/* Mobile adjustments */
@media (max-width: 640px) {
.metric-card {
padding: 16px 20px;
flex-basis: 100%;
}
.objective-section {
padding: 20px;
}
}
</style>
+4 -4
View File
@@ -18,10 +18,10 @@ defineProps({
items: { items: {
type: Array, type: Array,
default: () => [ default: () => [
{ title: "困境与机会", description: "普通人的编程新可能" }, { title: '困境与机会', description: '普通人的编程新可能' },
{ title: "能力初探", description: "60秒极速开发体验" }, { title: '能力初探', description: '60秒极速开发体验' },
{ title: "原生实战", description: "打造AI原生贪吃蛇" }, { title: '原生实战', description: '打造AI原生贪吃蛇' },
{ title: "拓展创造", description: "举一反三做游戏" } { title: '拓展创造', description: '举一反三做游戏' }
] ]
} }
}) })
+53 -16
View File
@@ -1,5 +1,12 @@
<script setup> <script setup>
import { computed, onMounted, onUnmounted, ref, useAttrs, watchEffect } from 'vue' import {
computed,
onMounted,
onUnmounted,
ref,
useAttrs,
watchEffect
} from 'vue'
const props = defineProps({ const props = defineProps({
text: { text: {
@@ -91,7 +98,9 @@ const currentTextIndex = ref(0)
const isVisible = ref(!props.startOnVisible) const isVisible = ref(!props.startOnVisible)
const containerRef = ref(null) const containerRef = ref(null)
const textArray = computed(() => (Array.isArray(props.text) ? props.text : [props.text])) const textArray = computed(() =>
Array.isArray(props.text) ? props.text : [props.text]
)
const cursorStyle = computed(() => ({ const cursorStyle = computed(() => ({
animationDuration: `${props.cursorBlinkDuration}s` animationDuration: `${props.cursorBlinkDuration}s`
@@ -104,8 +113,14 @@ const currentColor = computed(() => {
const getRandomSpeed = () => { const getRandomSpeed = () => {
if (!props.variableSpeed) return props.typingSpeed if (!props.variableSpeed) return props.typingSpeed
const min = typeof props.variableSpeed.min === 'number' ? props.variableSpeed.min : props.typingSpeed const min =
const max = typeof props.variableSpeed.max === 'number' ? props.variableSpeed.max : props.typingSpeed typeof props.variableSpeed.min === 'number'
? props.variableSpeed.min
: props.typingSpeed
const max =
typeof props.variableSpeed.max === 'number'
? props.variableSpeed.max
: props.typingSpeed
if (max <= min) return min if (max <= min) return min
return Math.random() * (max - min) + min return Math.random() * (max - min) + min
} }
@@ -114,7 +129,7 @@ let observer
onMounted(() => { onMounted(() => {
if (!props.startOnVisible || !containerRef.value) return if (!props.startOnVisible || !containerRef.value) return
observer = new IntersectionObserver( observer = new IntersectionObserver(
entries => { (entries) => {
for (const entry of entries) { for (const entry of entries) {
if (entry.isIntersecting) { if (entry.isIntersecting) {
isVisible.value = true isVisible.value = true
@@ -131,7 +146,7 @@ onUnmounted(() => {
if (observer) observer.disconnect() if (observer) observer.disconnect()
}) })
watchEffect(onCleanup => { watchEffect((onCleanup) => {
if (!isVisible.value) return if (!isVisible.value) return
if (!textArray.value.length) { if (!textArray.value.length) {
@@ -140,13 +155,16 @@ watchEffect(onCleanup => {
} }
const currentText = textArray.value[currentTextIndex.value] ?? '' const currentText = textArray.value[currentTextIndex.value] ?? ''
const processedText = props.reverseMode ? String(currentText).split('').reverse().join('') : String(currentText) const processedText = props.reverseMode
? String(currentText).split('').reverse().join('')
: String(currentText)
if (!isClient) { if (!isClient) {
return return
} }
const shouldStopAtEnd = !props.loop && currentTextIndex.value === textArray.value.length - 1 const shouldStopAtEnd =
!props.loop && currentTextIndex.value === textArray.value.length - 1
let timeoutId let timeoutId
@@ -155,11 +173,15 @@ watchEffect(onCleanup => {
if (!displayedText.value) { if (!displayedText.value) {
isDeleting.value = false isDeleting.value = false
if (props.onSentenceComplete) { if (props.onSentenceComplete) {
props.onSentenceComplete(textArray.value[currentTextIndex.value], currentTextIndex.value) props.onSentenceComplete(
textArray.value[currentTextIndex.value],
currentTextIndex.value
)
} }
if (shouldStopAtEnd) return if (shouldStopAtEnd) return
timeoutId = setTimeout(() => { timeoutId = setTimeout(() => {
currentTextIndex.value = (currentTextIndex.value + 1) % textArray.value.length currentTextIndex.value =
(currentTextIndex.value + 1) % textArray.value.length
currentCharIndex.value = 0 currentCharIndex.value = 0
}, props.postDeletingDelay) }, props.postDeletingDelay)
return return
@@ -172,10 +194,13 @@ watchEffect(onCleanup => {
} }
if (currentCharIndex.value < processedText.length) { if (currentCharIndex.value < processedText.length) {
timeoutId = setTimeout(() => { timeoutId = setTimeout(
() => {
displayedText.value += processedText[currentCharIndex.value] displayedText.value += processedText[currentCharIndex.value]
currentCharIndex.value += 1 currentCharIndex.value += 1
}, props.variableSpeed ? getRandomSpeed() : props.typingSpeed) },
props.variableSpeed ? getRandomSpeed() : props.typingSpeed
)
return return
} }
@@ -185,7 +210,11 @@ watchEffect(onCleanup => {
}, props.pauseDuration) }, props.pauseDuration)
} }
if (currentCharIndex.value === 0 && !isDeleting.value && !displayedText.value) { if (
currentCharIndex.value === 0 &&
!isDeleting.value &&
!displayedText.value
) {
timeoutId = setTimeout(schedule, props.initialDelay) timeoutId = setTimeout(schedule, props.initialDelay)
} else { } else {
schedule() schedule()
@@ -197,7 +226,9 @@ watchEffect(onCleanup => {
const shouldHideCursor = computed(() => { const shouldHideCursor = computed(() => {
if (!props.hideCursorWhileTyping) return false if (!props.hideCursorWhileTyping) return false
const currentText = textArray.value[currentTextIndex.value] ?? '' const currentText = textArray.value[currentTextIndex.value] ?? ''
const processedText = props.reverseMode ? String(currentText).split('').reverse().join('') : String(currentText) const processedText = props.reverseMode
? String(currentText).split('').reverse().join('')
: String(currentText)
return currentCharIndex.value < processedText.length || isDeleting.value return currentCharIndex.value < processedText.length || isDeleting.value
}) })
</script> </script>
@@ -209,13 +240,19 @@ const shouldHideCursor = computed(() => {
:class="['text-type', className]" :class="['text-type', className]"
v-bind="attrs" v-bind="attrs"
> >
<span class="text-type__content" :style="{ color: currentColor || 'inherit' }"> <span
class="text-type__content"
:style="{ color: currentColor || 'inherit' }"
>
{{ displayedText }} {{ displayedText }}
</span> </span>
<span <span
v-if="showCursor" v-if="showCursor"
class="text-type__cursor" class="text-type__cursor"
:class="[cursorClassName, shouldHideCursor ? 'text-type__cursor--hidden' : '']" :class="[
cursorClassName,
shouldHideCursor ? 'text-type__cursor--hidden' : ''
]"
:style="cursorStyle" :style="cursorStyle"
> >
{{ cursorCharacter }} {{ cursorCharacter }}
+7 -2
View File
@@ -7,13 +7,17 @@ import TypeIt from 'typeit'
import { onMounted, watch, nextTick } from 'vue' import { onMounted, watch, nextTick } from 'vue'
import { useRoute, useData } from 'vitepress' import { useRoute, useData } from 'vitepress'
import './style.css' import './style.css'
import Layout from './Layout.vue'
import StepBar from './components/StepBar.vue' import StepBar from './components/StepBar.vue'
import ChapterIntroduction from './components/ChapterIntroduction.vue'
export default { export default {
extends: DefaultTheme, extends: DefaultTheme,
Layout,
enhanceApp({ app }) { enhanceApp({ app }) {
app.use(ElementPlus) app.use(ElementPlus)
app.component('StepBar', StepBar) app.component('StepBar', StepBar)
app.component('ChapterIntroduction', ChapterIntroduction)
}, },
setup() { setup() {
const route = useRoute() const route = useRoute()
@@ -85,7 +89,7 @@ export default {
const optimizeImages = () => { const optimizeImages = () => {
const images = document.querySelectorAll('.vp-doc img') const images = document.querySelectorAll('.vp-doc img')
images.forEach(img => { images.forEach((img) => {
if (img.complete) { if (img.complete) {
applyImageStyle(img) applyImageStyle(img)
} else { } else {
@@ -133,7 +137,8 @@ export default {
watch( watch(
() => route.path, () => route.path,
() => nextTick(() => { () =>
nextTick(() => {
initViewer() initViewer()
initTypewriter() initTypewriter()
optimizeImages() optimizeImages()
+64 -2
View File
@@ -2,10 +2,70 @@
/* Easy-Vibe Theme Fix v2025-01-12 */ /* Easy-Vibe Theme Fix v2025-01-12 */
/* 通过变量控制分组底部留白(默认 24px) */ /* 通过变量控制分组底部留白(默认 24px) */
--vp-sidebar-nav-section-gap: 8px; --vp-sidebar-nav-section-gap: 8px;
--ev-doc-font-size: 13px;
--ev-doc-line-height: 1.5;
} }
.vp-doc { .vp-doc {
font-size: 15px; font-size: var(--ev-doc-font-size);
line-height: var(--ev-doc-line-height);
--el-font-size-extra-large: calc(var(--ev-doc-font-size) + 6px);
--el-font-size-large: calc(var(--ev-doc-font-size) + 4px);
--el-font-size-medium: calc(var(--ev-doc-font-size) + 2px);
--el-font-size-base: var(--ev-doc-font-size);
--el-font-size-small: calc(var(--ev-doc-font-size) - 1px);
--el-font-size-extra-small: calc(var(--ev-doc-font-size) - 2px);
--el-font-line-height-primary: var(--ev-doc-line-height);
}
.vp-doc :where(p, ul, ol, table, blockquote, pre, details, figure) {
margin: 10px 0;
}
.vp-doc :where(li) {
margin: 4px 0;
}
.vp-doc :where(ul, ol) {
padding-left: 1.15em;
}
.vp-doc :where(h1, h2, h3, h4, h5, h6) {
line-height: 1.3;
}
.vp-doc :where(h1) {
margin: 22px 0 12px;
}
.vp-doc :where(h2) {
margin: 20px 0 10px;
}
.vp-doc h2 {
margin: 16px 0 8px !important;
padding-top: 10px !important;
border-top: 0 !important;
}
.vp-doc :where(h3) {
margin: 18px 0 8px;
}
.vp-doc :where(h4, h5, h6) {
margin: 16px 0 8px;
}
.vp-doc :where(hr) {
margin: 14px 0;
}
.vp-doc :where(th, td) {
padding: 6px 10px;
}
.vp-doc :where(:not(pre) > code) {
font-size: 0.95em;
} }
/* 生产环境 data-v-* scoped 样式会比 class 选择器更高优先级 /* 生产环境 data-v-* scoped 样式会比 class 选择器更高优先级
@@ -44,7 +104,9 @@
/* 进一步压缩分组标题与第一项之间的间距 */ /* 进一步压缩分组标题与第一项之间的间距 */
:where(html) .VPSidebarItem.level-0 + .VPSidebarItem.level-1, :where(html) .VPSidebarItem.level-0 + .VPSidebarItem.level-1,
:where(html) .VPSidebarItem.level-0[data-v-d81de50c] + .VPSidebarItem.level-1[data-v-d81de50c] { :where(html)
.VPSidebarItem.level-0[data-v-d81de50c]
+ .VPSidebarItem.level-1[data-v-d81de50c] {
margin-top: -2px !important; margin-top: -2px !important;
} }
+4 -4
View File
@@ -23,7 +23,7 @@
### 零、幼儿园 ### 零、幼儿园
| 章节 | 关键内容 | 状态 | | 章节 | 关键内容 | 状态 |
| :----------------------------------------------------------------------------- | :------------------------------------- | :--- | | :------------------------------------------------------------------------------- | :------------------------------------- | :--- |
| [新手入门:学习地图](/stage-0/0.1-learning-map/) | 整体学习路径导览 | ✅ | | [新手入门:学习地图](/stage-0/0.1-learning-map/) | 整体学习路径导览 | ✅ |
| [新手入门:AI 时代,会说话就会编程](/stage-0/0.2-ai-capabilities-through-games/) | 通过贪吃蛇等案例初步感受 AI 编程的能力 | ✅ | | [新手入门:AI 时代,会说话就会编程](/stage-0/0.2-ai-capabilities-through-games/) | 通过贪吃蛇等案例初步感受 AI 编程的能力 | ✅ |
@@ -49,7 +49,7 @@
#### 前端部分 #### 前端部分
| 章节 | 关键内容 | 状态 | | 章节 | 关键内容 | 状态 |
| :------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------- | :--- | | :------------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------- | :--- |
| 前端零:使用 lovart 生产素材 | 学会用 lovart 批量生成人物、场景等视觉素材,为 UI 设计和前端开发提供素材基础 | 🚧 | | 前端零:使用 lovart 生产素材 | 学会用 lovart 批量生成人物、场景等视觉素材,为 UI 设计和前端开发提供素材基础 | 🚧 |
| 前端一:Figma 与 MasterGo 入门 | 用设计工具梳理信息架构和页面结构,为前端实现打基础 | 🚧 | | 前端一:Figma 与 MasterGo 入门 | 用设计工具梳理信息架构和页面结构,为前端实现打基础 | 🚧 |
| 前端二:构建第一个现代应用程序-UI 设计 | 基于设计稿完成组件化界面,实现从设计到代码的第一条链路 | 🚧 | | 前端二:构建第一个现代应用程序-UI 设计 | 基于设计稿完成组件化界面,实现从设计到代码的第一条链路 | 🚧 |
@@ -59,7 +59,7 @@
#### 后端与全栈部分 #### 后端与全栈部分
| 章节 | 关键内容 | 状态 | | 章节 | 关键内容 | 状态 |
| :---------------------------------------------------------------------------------- | :------------------------------------------------------------ | :--- | | :---------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------ | :--- |
| 后端一:什么是 API | 理解 HTTP 接口与请求响应模型,为后端集成与联调做准备 | 🚧 | | 后端一:什么是 API | 理解 HTTP 接口与请求响应模型,为后端集成与联调做准备 | 🚧 |
| [后端二:从数据库到 Supabase](/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase) | 在 Supabase 上落地数据库和 API,打通数据模型与前端页面 | 🚧 | | [后端二:从数据库到 Supabase](/stage-2/backend/2.2-database-supabase/chapter5/chapter5-from-database-to-supabase) | 在 Supabase 上落地数据库和 API,打通数据模型与前端页面 | 🚧 |
| 后端三:大模型辅助编写接口代码与接口文档 | 用大模型协助生成接口与数据库文档及代码,实现可读可测的后端 | 🚧 | | 后端三:大模型辅助编写接口代码与接口文档 | 用大模型协助生成接口与数据库文档及代码,实现可读可测的后端 | 🚧 |
@@ -72,7 +72,7 @@
#### AI 能力附录 #### AI 能力附录
| 章节 | 关键内容 | 状态 | | 章节 | 关键内容 | 状态 |
| :---------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------- | :--- | | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------- | :--- |
| [AI 一:Dify 入门与知识库集成](/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration) | 用 Dify Workflow 与基础 RAG 搭建工具类产品,为后续应用升级打样 | 🚧 | | [AI 一:Dify 入门与知识库集成](/stage-2/ai-capabilities/2.1-dify-knowledge-base/chapter3/chapter3-getting-started-with-dify-and-its-knowledge-base-integration) | 用 Dify Workflow 与基础 RAG 搭建工具类产品,为后续应用升级打样 | 🚧 |
| AI 二:学会查询 AI 词典与集成多模态 API | 学会查找合适的模型与 API,并把文本、图像等多模态能力接入产品 | 🚧 | | AI 二:学会查询 AI 词典与集成多模态 API | 学会查找合适的模型与 API,并把文本、图像等多模态能力接入产品 | 🚧 |
+61 -1
View File
@@ -1,10 +1,70 @@
:root { :root {
/* 调整侧边栏分组之间的间距 */ /* 调整侧边栏分组之间的间距 */
--vp-sidebar-nav-section-gap: 8px; --vp-sidebar-nav-section-gap: 8px;
--ev-doc-font-size: 13px;
--ev-doc-line-height: 1.5;
} }
.vp-doc { .vp-doc {
font-size: 15px; font-size: var(--ev-doc-font-size);
line-height: var(--ev-doc-line-height);
--el-font-size-extra-large: calc(var(--ev-doc-font-size) + 6px);
--el-font-size-large: calc(var(--ev-doc-font-size) + 4px);
--el-font-size-medium: calc(var(--ev-doc-font-size) + 2px);
--el-font-size-base: var(--ev-doc-font-size);
--el-font-size-small: calc(var(--ev-doc-font-size) - 1px);
--el-font-size-extra-small: calc(var(--ev-doc-font-size) - 2px);
--el-font-line-height-primary: var(--ev-doc-line-height);
}
.vp-doc :where(p, ul, ol, table, blockquote, pre, details, figure) {
margin: 10px 0;
}
.vp-doc :where(li) {
margin: 4px 0;
}
.vp-doc :where(ul, ol) {
padding-left: 1.15em;
}
.vp-doc :where(h1, h2, h3, h4, h5, h6) {
line-height: 1.3;
}
.vp-doc :where(h1) {
margin: 22px 0 12px;
}
.vp-doc :where(h2) {
margin: 20px 0 10px;
}
.vp-doc h2 {
margin: 16px 0 8px !important;
padding-top: 10px !important;
border-top: 0 !important;
}
.vp-doc :where(h3) {
margin: 18px 0 8px;
}
.vp-doc :where(h4, h5, h6) {
margin: 16px 0 8px;
}
.vp-doc :where(hr) {
margin: 14px 0;
}
.vp-doc :where(th, td) {
padding: 6px 10px;
}
.vp-doc :where(:not(pre) > code) {
font-size: 0.95em;
} }
/* 减少一级标题(如"前端开发")底部的间距 */ /* 减少一级标题(如"前端开发")底部的间距 */
+10 -2
View File
@@ -163,11 +163,13 @@ AI coding的出现正在改写传统编程学习的规则。你不再需要花
::: details 第一阶段完整课程大纲 (点击收起) ::: details 第一阶段完整课程大纲 (点击收起)
**模块一:AI 时代,会说话就会编程** **模块一:AI 时代,会说话就会编程**
- **1.1** 普通人的困境与机会? - **1.1** 普通人的困境与机会?
- **1.2** AI 能帮你做到什么程度? - **1.2** AI 能帮你做到什么程度?
- **1.3** 动手:你的第一个 AI 原生应用 - **1.3** 动手:你的第一个 AI 原生应用
**模块二:认识 AI IDE 工具** **模块二:认识 AI IDE 工具**
- **2.1** 写代码需要什么环境和工具 - **2.1** 写代码需要什么环境和工具
- **2.2** 什么是 IDE,为什么需要 IDE - **2.2** 什么是 IDE,为什么需要 IDE
- **2.3** AI IDE 和普通 IDE 有什么不同 - **2.3** AI IDE 和普通 IDE 有什么不同
@@ -175,6 +177,7 @@ AI coding的出现正在改写传统编程学习的规则。你不再需要花
- **2.5** 怎么跟 AI 说话才有效 - **2.5** 怎么跟 AI 说话才有效
**模块三:动手做出原型** **模块三:动手做出原型**
- **3.1** 把需求变成代码的过程 - **3.1** 把需求变成代码的过程
- **3.2** 从一个单页面开始 - **3.2** 从一个单页面开始
- **3.3** 遇到报错了怎么办 - **3.3** 遇到报错了怎么办
@@ -182,6 +185,7 @@ AI coding的出现正在改写传统编程学习的规则。你不再需要花
- **3.5** 把原型做得像那么回事 - **3.5** 把原型做得像那么回事
**模块四:给原型加上 AI 能力** **模块四:给原型加上 AI 能力**
- **4.1** 什么是 AI 能力接入(API 调用) - **4.1** 什么是 AI 能力接入(API 调用)
- **4.2** 如何接入文生图能力 - **4.2** 如何接入文生图能力
- **4.3** 如何接入视频生成能力 - **4.3** 如何接入视频生成能力
@@ -189,20 +193,24 @@ AI coding的出现正在改写传统编程学习的规则。你不再需要花
- **4.5** 成本控制和错误处理 - **4.5** 成本控制和错误处理
**模块五:完整项目实战** **模块五:完整项目实战**
- **5.1** 制造模拟数据让原型看起来真实 - **5.1** 制造模拟数据让原型看起来真实
- **5.2** 收集反馈并快速调整 - **5.2** 收集反馈并快速调整
- **5.3** 展示你的成果 - **5.3** 展示你的成果
**大作业** **大作业**
- 做一个完整的 Web 应用原型并展示 - 做一个完整的 Web 应用原型并展示
**附录A:产品思维补充** **附录A:产品思维补充**
- **A.1** 什么是好的产品想法 - **A.1** 什么是好的产品想法
- **A.2** 如何发现用户真正的需求 - **A.2** 如何发现用户真正的需求
- **A.3** 功能优先级怎么排 - **A.3** 功能优先级怎么排
- **A.4** MVP 思维:最小可行产品 - **A.4** MVP 思维:最小可行产品
**附录B:常见报错及解决方案** **附录B:常见报错及解决方案**
- **B.1** 页面显示空白或不加载 - **B.1** 页面显示空白或不加载
- **B.2** 数据保存不成功 - **B.2** 数据保存不成功
- **B.3** 样式显示不正常 - **B.3** 样式显示不正常
@@ -231,7 +239,6 @@ AI coding的出现正在改写传统编程学习的规则。你不再需要花
短期来看,这种训练确实比较折磨人;但从长期来看,它会极大提高你在求职和职业发展中的竞争力:你会更能扛事儿,更能在不确定环境中找到突破口,也更有能力把 AI 变成真正落地的产品,而不是停留在“玩玩 Demo”阶段。 短期来看,这种训练确实比较折磨人;但从长期来看,它会极大提高你在求职和职业发展中的竞争力:你会更能扛事儿,更能在不确定环境中找到突破口,也更有能力把 AI 变成真正落地的产品,而不是停留在“玩玩 Demo”阶段。
# 提问的艺术:AI 时代的必备技能 # 提问的艺术:AI 时代的必备技能
在 AI 时代,提问也属于一种 “基本功”。同一份代码、同一个报错,**你怎么提问,几乎决定了 AI 能给出怎样的答案**:是泛泛而谈,还是一步一步给出可落地的改法。 在 AI 时代,提问也属于一种 “基本功”。同一份代码、同一个报错,**你怎么提问,几乎决定了 AI 能给出怎样的答案**:是泛泛而谈,还是一步一步给出可落地的改法。
@@ -255,7 +262,7 @@ AI coding的出现正在改写传统编程学习的规则。你不再需要花
两种方式都可以,但用途不同: 两种方式都可以,但用途不同:
| 方式 | 适用场景 | 关键要求 | | 方式 | 适用场景 | 关键要求 |
| --- | --- | --- | | ------------ | ----------------------------------------- | ----------------------------------------- |
| **复制粘贴** | 报错堆栈、日志、代码、配置、API 返回 | 尽量完整,不要只截一行关键字 | | **复制粘贴** | 报错堆栈、日志、代码、配置、API 返回 | 尽量完整,不要只截一行关键字 |
| **截图** | UI 布局问题、交互异常、工具界面找不到按钮 | 截全屏 + 标注重点区域,最好配一句文字说明 | | **截图** | UI 布局问题、交互异常、工具界面找不到按钮 | 截全屏 + 标注重点区域,最好配一句文字说明 |
@@ -268,6 +275,7 @@ AI coding的出现正在改写传统编程学习的规则。你不再需要花
如果你不是只要答案,而是要“学会”答案。使用类似下面指令能显著提升解释质量: 如果你不是只要答案,而是要“学会”答案。使用类似下面指令能显著提升解释质量:
::: tip 🧠 学习型提问示例 ::: tip 🧠 学习型提问示例
- “请先用 5 句话讲清楚这个概念,再给几个问题提问我验证我理解对了没。” - “请先用 5 句话讲清楚这个概念,再给几个问题提问我验证我理解对了没。”
- ”请你详细解释一下这个报错信息,我不理解为什么会报错。” - ”请你详细解释一下这个报错信息,我不理解为什么会报错。”
::: :::
@@ -1,6 +1,5 @@
# 初级一:AI 时代,会说话就会编程 # 初级一:AI 时代,会说话就会编程
这是一个**基于项目制学习**的学习教程。我们鼓励你跟随步骤一步步操作,并尝试复现结果。 这是一个**基于项目制学习**的学习教程。我们鼓励你跟随步骤一步步操作,并尝试复现结果。
不要担心犯错或修改内容,我们永远相信你可以做到,请你永远记住: 不要担心犯错或修改内容,我们永远相信你可以做到,请你永远记住:
@@ -12,16 +11,18 @@
</div> </div>
</el-card> </el-card>
## 本章导读 ## 本章导读
::: info 🎯 学习目标 <ChapterIntroduction
在这一节,你会用对话式 AI 做出第一个 AI 原生小游戏——一款会“吃单词、写诗、画画”的贪吃蛇,并借此搞清楚 AI 编程的初步效果。 duration="约 <strong>4 小时</strong>,可分多次完成"
::: :tags="['对话式 AI 编程', 'AI 原生小游戏', '贪吃蛇实战']"
coreOutput="AI 原生贪吃蛇 + 自创小游戏"
expectedOutput="1 个可运行的 AI 原生贪吃蛇 + (可选)1 个你自创的 AI 原生小游戏或 Demo"
>
- 预计时间:约 **4 小时**,可分多次完成 在这一节,你会用对话式 AI 做出第一个 AI 原生小游戏——一款会“吃单词、写诗、画画”的贪吃蛇,并借此搞清楚 AI 编程的初步效果。
- 预期产出:1 个可运行的 AI 原生贪吃蛇 + (可选)1 个你自创的 AI 原生小游戏或 Demo
- Assignment:复现贪吃蛇,并(可选)实现一种你感兴趣的 AI 原生游戏 </ChapterIntroduction>
<div style="margin: 50px 0;"> <div style="margin: 50px 0;">
<ClientOnly> <ClientOnly>
@@ -168,6 +169,7 @@ AI 出现之后,第一次给了普通人一个全新的可能:你不需要会
一个可参考的经验是: 一个可参考的经验是:
::: warning ⚠️ 适用场景指南 ::: warning ⚠️ 适用场景指南
- **原型 / Demo / 内部自用工具**:非常适合先交给 AI 打第一版,再由你迭代细节。 - **原型 / Demo / 内部自用工具**:非常适合先交给 AI 打第一版,再由你迭代细节。
- **面向真实用户的大型产品**:通常需要工程师在架构、抽象、性能和维护上长期投入。 - **面向真实用户的大型产品**:通常需要工程师在架构、抽象、性能和维护上长期投入。
- **强安全 / 强合规系统(如支付、风控、医疗等)**:在当前阶段,不宜“生成完就直接上线”,必须引入严格的审查与测试流程。 - **强安全 / 强合规系统(如支付、风控、医疗等)**:在当前阶段,不宜“生成完就直接上线”,必须引入严格的审查与测试流程。
@@ -2,12 +2,16 @@
## 本章导读 ## 本章导读
::: info 🎯 学习目标 <ChapterIntroduction
本章将围绕本地开发环境展开讲解:你将学会从 z.ai 过渡到本地的开发环境,学会在自己的电脑上搭建完整的开发环境,理解什么是 IDE、什么是 AI IDE,以及如何在日常开发中高效地使用它们。 duration="约 <strong>1 天</strong>,可分多次完成"
::: :tags="['本地开发环境搭建', 'IDE 与 AI IDE', '高效开发技巧']"
coreOutput="1 个自创小游戏"
expectedOutput="使用 Trae 产出"
>
- 预计时间:约 **1 天**,可分多次完成 本章将围绕本地开发环境展开讲解:你将学会从 z.ai 过渡到本地的开发环境,学会在自己的电脑上搭建完整的开发环境,理解什么是 IDE、什么是 AI IDE,以及如何在日常开发中高效地使用它们。
- 预期产出:使用 Trae 产出 **1 个自创小游戏**
</ChapterIntroduction>
<div style="margin: 50px 0;"> <div style="margin: 50px 0;">
<ClientOnly> <ClientOnly>
@@ -548,23 +552,44 @@ AI 很认真地给了你一段代码,你也老老实实地复制进去了,
<div style="font-weight: bold; font-size: 16px;">🚀 挑战任务:打造你的专属游戏</div> <div style="font-weight: bold; font-size: 16px;">🚀 挑战任务:打造你的专属游戏</div>
</template> </template>
你已经用本地 AI IDE 做过一个贪吃蛇。现在请你再挑战一个更复杂一点的小游戏,完整走一遍“描述需求 → 生成项目 → 本地运行 → 调试迭代”的流程。 <p>
你已经用本地 AI IDE 做过一个贪吃蛇。现在请你再挑战一个更复杂一点的小游戏,完整走一遍“描述需求 →
生成项目 → 本地运行 → 调试迭代”的流程。
</p>
1. **选择一个比贪吃蛇更复杂的游戏** <ol>
- 可以是“俄罗斯方块”“打地鼠”“扫雷”“2048””飞机大战“之类 <li>
- 或者你自己想象的一个简单原创游戏 <strong>选择一个比贪吃蛇更复杂的游戏</strong>
2. **必须用本地 AI IDE 来完成整个过程** <ul>
- 新建一个空文件夹,用 AI IDE 打开 <li>可以是“俄罗斯方块”“打地鼠”“扫雷”“2048”“飞机大战”之类</li>
- 在侧边栏聊天里描述清楚你的游戏需求 <li>或者你自己想象的一个简单原创游戏</li>
- 让 AI 负责创建文件、搭建项目结构和实现主要逻辑 </ul>
- 在本地启动开发服务器,确保游戏可以正常运行 </li>
3. **有基本的“可玩性”和反馈** <li>
- 至少包含开始、进行中、结束三种状态 <strong>必须用本地 AI IDE 来完成整个过程</strong>
- 玩家有明确的操作方式(键盘或鼠标) <ul>
- 屏幕上有清晰的得分或进度反馈 <li>新建一个空文件夹,用 AI IDE 打开</li>
4. **至少进行 2 轮以上的迭代** <li>在侧边栏聊天里描述清楚你的游戏需求</li>
- 第一轮让 AI 做出“能玩”的版本 <li>让 AI 负责创建文件、搭建项目结构和实现主要逻辑</li>
- 第二轮以后,逐步提出具体改进(样式、难度、交互优化等) <li>在本地启动开发服务器,确保游戏可以正常运行</li>
</ul>
</li>
<li>
<strong>有基本的“可玩性”和反馈</strong>
<ul>
<li>至少包含开始、进行中、结束三种状态</li>
<li>玩家有明确的操作方式(键盘或鼠标)</li>
<li>屏幕上有清晰的得分或进度反馈</li>
</ul>
</li>
<li>
<strong>至少进行 2 轮以上的迭代</strong>
<ul>
<li>第一轮让 AI 做出“能玩”的版本</li>
<li>第二轮以后,逐步提出具体改进(样式、难度、交互优化等)</li>
</ul>
</li>
</ol>
</el-card> </el-card>
## 附录 ## 附录
@@ -120,7 +120,6 @@ AI Agent 是一种软件系统,它能够感知环境、做出决策,并自
> - 蛇吃掉的不是食物,而是英文单词。 > - 蛇吃掉的不是食物,而是英文单词。
> - 页面侧边栏展示已收集单词及其数量。 > - 页面侧边栏展示已收集单词及其数量。
> - 游戏结束后,已收集的单词仍然保留,并在新一局中延续。 > - 游戏结束后,已收集的单词仍然保留,并在新一局中延续。
>
> - 页面 2:写诗页面(Make Poem) > - 页面 2:写诗页面(Make Poem)
> - 展示与游戏页面相同的单词列表(数据一致)。 > - 展示与游戏页面相同的单词列表(数据一致)。
> - 提供一个按钮,将当前收集的单词发送给后端生成一首诗。 > - 提供一个按钮,将当前收集的单词发送给后端生成一首诗。
@@ -225,7 +225,8 @@ workflow:
prompt_template: prompt_template:
- id: 2686a731-f250-46f0-97ec-033e929160a5 - id: 2686a731-f250-46f0-97ec-033e929160a5
role: system role: system
text: 'You are the computer that answers the user''s question. Always start text:
'You are the computer that answers the user''s question. Always start
with "hello guest" ' with "hello guest" '
selected: false selected: false
title: Guest LLM title: Guest LLM
@@ -416,7 +417,8 @@ workflow:
prompt_template: prompt_template:
- id: 1129723a-50cb-4350-a118-3b9ac6dac523 - id: 1129723a-50cb-4350-a118-3b9ac6dac523
role: system role: system
text: 'You are the computer that answers the admin''s question. Always start text:
'You are the computer that answers the admin''s question. Always start
with "hello admin" . You know AIID(AI innovative Design) is a master program with "hello admin" . You know AIID(AI innovative Design) is a master program
in open FIESTA in Shenzhen. ' in open FIESTA in Shenzhen. '
selected: false selected: false
@@ -315,7 +315,8 @@ workflow:
width: 244 width: 244
zIndex: 1002 zIndex: 1002
- data: - data:
code: "\ndef main(arg1: str, arg2: str) -> dict:\n return {\n \"\ code:
"\ndef main(arg1: str, arg2: str) -> dict:\n return {\n \"\
result\": arg1 +arg2 ,\n }\n" result\": arg1 +arg2 ,\n }\n"
code_language: python3 code_language: python3
desc: '' desc: ''
@@ -124,7 +124,7 @@ user_info = {
我们也可参考某家云厂商的[数据库选型推荐](https://help.aliyun.com/zh/govcloud/getting-started/select-database-services),根据场景可进行不同数据库类型的选择,你可以对比不同云厂商的数据库规格选出最合适的进行使用。 我们也可参考某家云厂商的[数据库选型推荐](https://help.aliyun.com/zh/govcloud/getting-started/select-database-services),根据场景可进行不同数据库类型的选择,你可以对比不同云厂商的数据库规格选出最合适的进行使用。
| 数据库类型 | 数据库名称 | 价格 | 适用场景 | | 数据库类型 | 数据库名称 | 价格 | 适用场景 |
| ------------ | ---------------- | ---- | -------------------------------------------------------------------------------------------------------------------------------- | | ------------ | ---------------- | ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 关系型数据库 | RDS MySQL版 | 低 | 基础版:学习以及小型网站高可用版:一定业务压力的中型数据库场景集群版:业务不允许中断,访问压力较大 | | 关系型数据库 | RDS MySQL版 | 低 | 基础版:学习以及小型网站高可用版:一定业务压力的中型数据库场景集群版:业务不允许中断,访问压力较大 |
| | RDS SQL server版 | 高 | 基础版:测试以及小型商业化网站高可用版:企业级商业化网站集群版:企业业务不允许中断,访问压力较大 | | | RDS SQL server版 | 高 | 基础版:测试以及小型商业化网站高可用版:企业级商业化网站集群版:企业业务不允许中断,访问压力较大 |
| | RDS PostgreSQL版 | 最低 | 基础版:学习以及小型网站高可用版:一定业务压力的中型数据库场景集群版:业务不允许中断,访问压力较大的场景,其性能较一般MySQL高 | | | RDS PostgreSQL版 | 最低 | 基础版:学习以及小型网站高可用版:一定业务压力的中型数据库场景集群版:业务不允许中断,访问压力较大的场景,其性能较一般MySQL高 |
@@ -1,4 +1,5 @@
# 如何构建一个最简单的微信小程序 # 如何构建一个最简单的微信小程序
# 1. 什么是微信小程序和微信小程序开发 # 1. 什么是微信小程序和微信小程序开发
在这篇教程中,我们将完整跑通一条闭环:从脑海中的一个想法,到在微信里可以搜索、可以扫码打开的真实小程序。 在这篇教程中,我们将完整跑通一条闭环:从脑海中的一个想法,到在微信里可以搜索、可以扫码打开的真实小程序。
@@ -211,9 +212,9 @@ AI 完成第一轮开发之后,代码已经落在项目里了,但这时候
AI 生成的版本不一定一开始就完美。有时候你会遇到这些情况: AI 生成的版本不一定一开始就完美。有时候你会遇到这些情况:
* 运行时报错,小程序无法正常打开; - 运行时报错,小程序无法正常打开;
* 功能大致正确,但细节和你想象的不太一样; - 功能大致正确,但细节和你想象的不太一样;
* 界面可以用,但你觉得还可以更好看或更顺手。 - 界面可以用,但你觉得还可以更好看或更顺手。
在这些时候,不需要自己钻进代码里盲改,而是可以把遇到的问题直接用自然语言重新描述给 Trae 中的 AI 助手,例如: 在这些时候,不需要自己钻进代码里盲改,而是可以把遇到的问题直接用自然语言重新描述给 Trae 中的 AI 助手,例如:
@@ -229,10 +230,10 @@ AI 会根据你当前的项目状态和描述,给出修改建议并直接应
经过一轮又一轮的 **自然语言叙述 → AI 修改 → 在微信开发者工具中预览 → 继续对话微调** ,我最终得到的是这样一个成品: 经过一轮又一轮的 **自然语言叙述 → AI 修改 → 在微信开发者工具中预览 → 继续对话微调** ,我最终得到的是这样一个成品:
* 有完整的游戏页面; - 有完整的游戏页面;
* 蛇可以顺畅移动并吃到食物; - 蛇可以顺畅移动并吃到食物;
* 支持摇杆控制; - 支持摇杆控制;
* 在小程序模拟器中可以顺利运行。 - 在小程序模拟器中可以顺利运行。
最终开发成品如下: 最终开发成品如下: