feat(docs): add new assignment projects and update existing content

This commit is contained in:
sanbuphy
2026-03-26 11:20:31 +08:00
parent f6454b0342
commit 7d6c2cbf9c
24 changed files with 4536 additions and 37 deletions
+8
View File
@@ -0,0 +1,8 @@
{
"mcpServers": {
"autoglm-browser-agent": {
"command": "/Users/sanbu/.agents/skills/autoglm-browser-agent/dist/mcp_server --start_url https://www.bing.com --window_width 1456 --window_height 819 --resize_width 1456 --resize_height 819 --max_steps 100 --log_dir /Users/sanbu/.agents/skills/autoglm-browser-agent/mcp_output --if_subagent"
}
},
"imports": []
}
+1 -1
View File
@@ -1433,7 +1433,7 @@ Sitemap: ${siteUrl}/sitemap.xml
link: '/zh-cn/stage-2/frontend/2.1-figma-mastergo/' link: '/zh-cn/stage-2/frontend/2.1-figma-mastergo/'
}, },
{ {
text: '参考 UI 设计规范与多产品 UI 设计', text: '参考 UI 设计规范设计页面和按钮',
link: '/zh-cn/stage-2/frontend/2.3-multi-product-ui/' link: '/zh-cn/stage-2/frontend/2.3-multi-product-ui/'
}, },
{ {
+276 -3
View File
@@ -4,7 +4,7 @@ import 'element-plus/dist/index.css'
import Viewer from 'viewerjs' import Viewer from 'viewerjs'
import 'viewerjs/dist/viewer.css' import 'viewerjs/dist/viewer.css'
import TypeIt from 'typeit' import TypeIt from 'typeit'
import { onMounted, watch, nextTick } from 'vue' import { onMounted, onBeforeUnmount, 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 Layout from './Layout.vue'
@@ -1712,12 +1712,259 @@ export default {
const route = useRoute() const route = useRoute()
const { frontmatter } = useData() const { frontmatter } = useData()
let viewer = null let viewer = null
let mermaidViewer = null
let mermaidViewerWrapper = null
let mermaidViewerObjectUrl = null
let mermaidApi = null
let themeObserver = null
let currentMermaidTheme = null
const COLLAPSIBLE_CODE_MIN_LINES = 14
// Skip browser-only initialization during SSR // Skip browser-only initialization during SSR
if (import.meta.env.SSR) { if (import.meta.env.SSR) {
return return
} }
const getMermaidTheme = () =>
document.documentElement.classList.contains('dark') ? 'dark' : 'default'
const loadMermaid = async () => {
if (mermaidApi) return mermaidApi
const mermaidModule = await import('mermaid')
mermaidApi = mermaidModule.default ?? mermaidModule
return mermaidApi
}
const renderMermaidDiagrams = async (force = false) => {
const mermaidBlocks = document.querySelectorAll(
'.vp-doc div.language-mermaid, .vp-doc .mermaid-diagram[data-source]'
)
if (!mermaidBlocks.length) return
const mermaid = await loadMermaid()
const nextTheme = getMermaidTheme()
if (force || currentMermaidTheme !== nextTheme) {
mermaid.initialize({
startOnLoad: false,
securityLevel: 'loose',
theme: nextTheme
})
currentMermaidTheme = nextTheme
}
let index = 0
for (const block of mermaidBlocks) {
let source = ''
let container = block
if (block.classList.contains('language-mermaid')) {
source = block.querySelector('code')?.textContent?.trim() ?? ''
if (!source) continue
container = document.createElement('div')
container.className = 'mermaid-diagram'
container.dataset.source = source
block.replaceWith(container)
} else {
source = block.dataset.source ?? ''
if (!source) continue
}
try {
const diagramId = `mermaid-${route.path.replace(/\W+/g, '-')}-${Date.now()}-${index}`
const { svg, bindFunctions } = await mermaid.render(diagramId, source)
container.innerHTML = svg
container.classList.remove('mermaid-diagram-error')
container.setAttribute('role', 'button')
container.setAttribute('tabindex', '0')
container.setAttribute('aria-label', 'Open Mermaid diagram in fullscreen viewer')
container.onclick = (event) => {
if (event.target.closest?.('a')) return
openMermaidViewer(container)
}
container.onkeydown = (event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault()
openMermaidViewer(container)
}
}
bindFunctions?.(container)
} catch (error) {
console.error('Mermaid render failed:', error)
container.innerHTML = ''
container.classList.add('mermaid-diagram-error')
}
index += 1
}
}
const cleanupMermaidViewer = () => {
if (mermaidViewer) {
mermaidViewer.destroy()
mermaidViewer = null
}
if (mermaidViewerWrapper) {
mermaidViewerWrapper.remove()
mermaidViewerWrapper = null
}
if (mermaidViewerObjectUrl) {
URL.revokeObjectURL(mermaidViewerObjectUrl)
mermaidViewerObjectUrl = null
}
document.body.classList.remove('mermaid-viewer-open')
document.body.classList.remove('viewer-ready')
}
const openMermaidViewer = (container) => {
const svg = container.querySelector('svg')
if (!svg) return
cleanupMermaidViewer()
const serializer = new XMLSerializer()
let svgMarkup = serializer.serializeToString(svg)
if (!svgMarkup.includes('xmlns="http://www.w3.org/2000/svg"')) {
svgMarkup = svgMarkup.replace(
'<svg',
'<svg xmlns="http://www.w3.org/2000/svg"'
)
}
const blob = new Blob([svgMarkup], {
type: 'image/svg+xml;charset=utf-8'
})
mermaidViewerObjectUrl = URL.createObjectURL(blob)
mermaidViewerWrapper = document.createElement('div')
mermaidViewerWrapper.className = 'mermaid-viewer-source'
const previewImage = document.createElement('img')
previewImage.src = mermaidViewerObjectUrl
previewImage.alt = 'Mermaid diagram preview'
mermaidViewerWrapper.append(previewImage)
document.body.append(mermaidViewerWrapper)
mermaidViewer = new Viewer(mermaidViewerWrapper, {
button: true,
navbar: false,
title: false,
toolbar: true,
tooltip: true,
movable: true,
zoomable: true,
rotatable: false,
scalable: false,
transition: false,
fullscreen: true,
keyboard: true,
url: 'src',
shown() {
document.body.classList.add('mermaid-viewer-open')
document.body.classList.add('viewer-ready')
},
viewed() {
requestAnimationFrame(() => {
const imageData = mermaidViewer?.imageData
const viewerData = mermaidViewer?.viewerData
if (!imageData || !viewerData) return
const widthScale = (viewerData.width * 0.94) / imageData.width
const heightScale = (viewerData.height * 0.94) / imageData.height
const targetScale = Math.min(widthScale, heightScale)
if (targetScale > 1.02) {
mermaidViewer.zoomTo(imageData.ratio * targetScale, false)
}
})
},
hidden() {
cleanupMermaidViewer()
}
})
mermaidViewer.view(0)
}
const initRenderedMermaidFeatures = async (force = false) => {
await renderMermaidDiagrams(force)
}
const getCodeToggleLabels = () => {
const isChineseRoute =
route.path.startsWith('/zh-cn/') || route.path.startsWith('/zh-tw/')
return isChineseRoute
? {
expand: '展开代码',
collapse: '收起代码'
}
: {
expand: 'Expand code',
collapse: 'Collapse code'
}
}
const getCodeLineCount = (source) => {
const normalized = source.replace(/\s+$/, '')
if (!normalized) return 0
return normalized.split('\n').length
}
const updateCodeToggleButton = (block, button, lineCount) => {
const labels = getCodeToggleLabels()
const isCollapsed = block.classList.contains('is-code-collapsed')
const nextLabel = isCollapsed ? labels.expand : labels.collapse
button.textContent = `${nextLabel} (${lineCount} 行)`
button.setAttribute('aria-expanded', String(!isCollapsed))
button.setAttribute('title', nextLabel)
}
const initCollapsibleCodeBlocks = () => {
const codeBlocks = document.querySelectorAll(
'.vp-doc div[class*="language-"]:not(.language-mermaid)'
)
codeBlocks.forEach((block) => {
const pre = block.querySelector('pre')
const code = pre?.querySelector('code')
if (!pre || !code) return
const lineCount = getCodeLineCount(code.textContent ?? '')
const existingToggle = block.querySelector('.code-collapse-toggle')
if (lineCount < COLLAPSIBLE_CODE_MIN_LINES) {
block.classList.remove('is-collapsible-code', 'is-code-collapsed')
existingToggle?.remove()
return
}
block.classList.add('is-collapsible-code')
let toggle = existingToggle
if (!toggle) {
toggle = document.createElement('button')
toggle.type = 'button'
toggle.className = 'code-collapse-toggle'
toggle.addEventListener('click', () => {
block.classList.toggle('is-code-collapsed')
updateCodeToggleButton(block, toggle, lineCount)
})
block.append(toggle)
}
block.classList.add('is-code-collapsed')
updateCodeToggleButton(block, toggle, lineCount)
})
}
const initViewer = () => { const initViewer = () => {
// 销毁旧实例 // 销毁旧实例
if (viewer) { if (viewer) {
@@ -1827,20 +2074,46 @@ export default {
} }
} }
onMounted(() => { onMounted(async () => {
initViewer() initViewer()
initTypewriter() initTypewriter()
optimizeImages() optimizeImages()
await initRenderedMermaidFeatures(true)
initCollapsibleCodeBlocks()
themeObserver = new MutationObserver(() => {
const nextTheme = getMermaidTheme()
if (nextTheme === currentMermaidTheme) return
nextTick(async () => {
await initRenderedMermaidFeatures(true)
})
})
themeObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class']
})
}) })
watch( watch(
() => route.path, () => route.path,
() => () =>
nextTick(() => { nextTick(async () => {
cleanupMermaidViewer()
initViewer() initViewer()
initTypewriter() initTypewriter()
optimizeImages() optimizeImages()
await initRenderedMermaidFeatures(true)
initCollapsibleCodeBlocks()
}) })
) )
onBeforeUnmount(() => {
cleanupMermaidViewer()
if (themeObserver) {
themeObserver.disconnect()
themeObserver = null
}
})
} }
} }
+132
View File
@@ -460,3 +460,135 @@
white-space: nowrap; white-space: nowrap;
flex-shrink: 0; flex-shrink: 0;
} }
/* Mermaid diagrams */
.vp-doc .mermaid-diagram {
margin: 18px 0 22px;
padding: 0;
overflow-x: auto;
position: relative;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
cursor: zoom-in;
}
.vp-doc .mermaid-diagram svg {
display: block;
max-width: 100%;
height: auto;
margin: 0 auto;
}
.vp-doc .mermaid-diagram-error {
min-height: 80px;
}
.dark .vp-doc .mermaid-diagram {
border: 0;
background: transparent;
}
.vp-doc .mermaid-diagram:focus-visible {
outline: 2px solid var(--vp-c-brand-1);
outline-offset: 3px;
}
.mermaid-viewer-source {
position: fixed;
width: 0;
height: 0;
overflow: hidden;
opacity: 0;
pointer-events: none;
}
body.mermaid-viewer-open .viewer-backdrop {
background: rgba(245, 247, 250, 0.96);
}
body.mermaid-viewer-open .viewer-canvas {
background: rgba(248, 250, 252, 0.98) !important;
}
body.mermaid-viewer-open .viewer-canvas img {
background: transparent;
border-radius: 0;
box-shadow: none;
}
/* Long code blocks */
.vp-doc div[class*='language-'].is-collapsible-code {
position: relative;
}
.vp-doc div[class*='language-'].is-collapsible-code pre {
position: relative;
transition: max-height 0.24s ease;
}
.vp-doc div[class*='language-'].is-collapsible-code.is-code-collapsed pre {
max-height: 320px;
overflow-x: auto;
overflow-y: hidden;
}
.vp-doc
div[class*='language-'].is-collapsible-code.is-code-collapsed
pre::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 88px;
pointer-events: none;
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0),
var(--vp-code-block-bg) 72%
);
}
.vp-doc .code-collapse-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 132px;
margin: 12px auto 2px;
padding: 7px 14px;
border: 1px solid rgba(0, 113, 227, 0.2);
border-radius: 999px;
background: rgba(0, 113, 227, 0.06);
color: var(--vp-c-brand-1);
font-size: 13px;
font-weight: 600;
line-height: 1;
cursor: pointer;
transition:
background-color 0.2s ease,
border-color 0.2s ease,
transform 0.2s ease;
}
.vp-doc .code-collapse-toggle:hover {
border-color: rgba(0, 113, 227, 0.32);
background: rgba(0, 113, 227, 0.1);
transform: translateY(-1px);
}
.vp-doc .code-collapse-toggle:focus-visible {
outline: 2px solid var(--vp-c-brand-1);
outline-offset: 2px;
}
.dark .vp-doc .code-collapse-toggle {
border-color: rgba(96, 165, 250, 0.24);
background: rgba(96, 165, 250, 0.12);
}
.dark .vp-doc .code-collapse-toggle:hover {
border-color: rgba(96, 165, 250, 0.38);
background: rgba(96, 165, 250, 0.18);
}
@@ -1,3 +1,207 @@
# Build Your First Modern App: Full-Stack Application # Build Your First Modern App: Full-Stack Application
> This chapter is currently being written. Stay tuned... ::: tip Goal
Turn a prototype idea into a small but complete web application with a real frontend, backend logic, persistent data, and a public deployment link.
:::
At this point, the goal is no longer just “make a page look right.” The goal is to make the core product loop actually work: a user opens the app, does something meaningful, triggers backend logic, stores or reads data, and gets a result that still exists the next time they come back.
You do not need to build a huge platform. A small product with a clear scope and a fully working flow is a much better submission than a large project full of unfinished features.
This assignment is meant to connect the Stage 2 path:
- [UI Design](../../frontend/2.2-ui-design/)
- [From Design Prototype to Project Code](../../frontend/2.6-design-to-code/)
- [Modern Component Libraries](../../frontend/2.7-modern-component-library/)
- [From Database to Supabase](../../backend/2.2-database-supabase/)
- [Using LLMs to Write API Code](../../backend/2.3-ai-interface-code/)
- [Git and GitHub Workflow](../../backend/2.4-git-workflow/)
- [How to Deploy Web Applications](../../backend/2.5-zeabur-deployment/)
## 1. Assignment Goal
Build a **working, demoable, publicly accessible** full-stack web app.
Your project should have:
1. **A clear user and use case**
- You should be able to explain who the product is for and what problem it solves.
2. **A complete product loop**
- The key action must go beyond frontend mock data and trigger real backend or database behavior.
3. **A real engineering handoff**
- The code should live in a repo, secrets should be handled correctly, and someone else should be able to understand how the project works.
Good project directions include:
- upgrading a Stage 1 prototype into a real application
- extending [Hogwarts Portraits](../../frontend/2.5-hogwarts-portraits/) into a more complete product
- building a small AI utility such as a writing tool, summarizer, or research workspace
- building a lightweight vertical tool such as a CRM, scheduling tool, or learning assistant
## 2. Minimum Requirements
| Area | Minimum Requirement | Notes |
| --- | --- | --- |
| Product scope | One clear core workflow | Example: create and save content, upload and process input, create a character and chat |
| Frontend | At least 2 pages, or 1 complex main page plus 1 supporting page | Desktop-ready is required |
| States | Handle loading, empty, and error states | Do not build happy-path only |
| Data | At least 1 core table, ideally 2 related entities | Example: `users` + `projects`, `characters` + `messages` |
| Backend | At least 3 real API endpoints or equivalent server-side capabilities | Express, Next.js route handlers, or edge functions are all acceptable |
| Persistence | Core user actions must be saved and retrievable | Not just mock data |
| Delivery | A GitHub repo and a public online link | Vercel or Zeabur are recommended |
| Documentation | A readable `README.md` | Include purpose, stack, setup, and env vars |
## 3. Recommended Stack
You can use any reasonable stack, but if you want the safest path, this combination works well:
| Layer | Recommended Option |
| --- | --- |
| Frontend | React / Next.js or Vue |
| Component library | shadcn/ui, Ant Design, HeroUI, or Element Plus |
| Data and auth | Supabase |
| Backend | Next.js route handlers, Express, or Supabase Edge Functions |
| Deployment | Vercel or Zeabur |
For a first full-stack project, “frontend + Supabase + a small amount of server logic” is usually the fastest way to a successful submission.
## 4. Suggested Workflow
### 4.1 Write the core task in one sentence
Before coding, answer:
> What is the single most important thing a user should be able to do in this app?
Examples:
- upload text, generate a summary, and save it to history
- create a character profile and continue chatting with it
- add a customer lead and review its status later
If that sentence is still vague, the scope is probably too wide.
### 4.2 Plan the structure first
Sketch:
- what pages you need
- the most important module on each page
- which actions read data
- which actions write data
This will make AI-assisted implementation much more stable.
### 4.3 Start with mock data
A practical order is:
1. build the frontend structure with mock data
2. connect real backend logic
3. replace mock data with real persistence
This keeps the debugging surface much smaller.
### 4.4 Starter prompt for AI IDEs
```text
Help me build a deployable full-stack web app for [your use case].
Do not generate everything at once. First break the project into:
1. page structure and routes
2. data model design
3. API or server-side capability design
4. frontend component breakdown
5. environment variable and deployment setup
Requirements:
- Use [React/Next.js/Vue + your component library + Supabase/Express]
- Prioritize a working MVP over over-engineering
- Tell me which files to create
- Include loading, empty, error, and validation states
```
## 5. Deliverables
Please submit:
1. **A GitHub repository link**
2. **A public online link**
3. **A `README.md`**
- project name and one-line description
- target users and use case
- tech stack
- main features
- local setup steps
- environment variable notes
4. **A short demo asset**
- ideally a 60 to 90 second video, or a few key screenshots/GIFs
Useful optional additions:
- database schema or ER diagram
- API notes or endpoint list
- design/architecture explanation
- a short iteration log
## 6. Evaluation Checklist
Your project is in good shape if:
- a user can complete a real end-to-end flow
- key actions create visible changes in the system
- data still exists after refresh
- backend failures do not crash the UI
- you can explain the product clearly in about one minute
## 7. Bonus Ideas
You can go further with:
- authentication and multi-user support
- real AI capabilities with streaming or retries
- better UI quality with a modern component library
- monitoring, logging, or analytics
- basic tests
- payment or subscription flows
- mobile adaptation
## 8. Common Mistakes
### 8.1 The scope is too large
If your first version requires auth, admin tools, payments, workflow builders, knowledge bases, and multi-user collaboration all at once, the project is probably too big.
### 8.2 The frontend looks good, but nothing is real
That may still be a useful frontend exercise, but not a strong full-stack assignment. This project should include real persistence and server-side behavior.
### 8.3 Secrets are committed into the repo
API keys and database secrets belong in environment variables, not in frontend source code.
### 8.4 Only testing the happy path
Manually test:
- empty input
- duplicate actions
- network failure
- missing data
- permission errors
That is where the most useful product fixes usually appear.
## 9. Final Advice
Treat this assignment as your first real product delivery exercise, not as a feature-count competition.
What matters most is not how many tools you used, but whether you can:
- define a clear product goal
- use AI tools to move from idea to implementation
- deliver something another person can understand, try, and reproduce
::: tip Next
After this assignment, continue with [Assignment 2: Modern Frontend Component Library + Trae](../2.2-modern-frontend-trae/) to push the interface quality even further.
:::
@@ -1,3 +1,282 @@
# Assignment 2: Modern Frontend Component Library + Trae Practice # Assignment 2: Modern Frontend Component Library + Trae Practice
> This chapter is currently being written. Stay tuned... ::: tip Goal
Use Trae or another AI IDE together with a modern component library to build a frontend that feels like a real product rather than a classroom demo.
:::
The previous assignment focused on full-stack completeness. This one focuses on frontend quality: structure, consistency, interaction, responsiveness, and iteration.
Many first frontend projects can “work” but still feel unfinished:
- the layout is usable, but visually inconsistent
- buttons, forms, drawers, and tables do not feel like one system
- the desktop version is acceptable, but narrow screens fall apart
- AI can generate a first version quickly, but the result still looks generic
This assignment is about pushing past that first draft.
Suggested review chapters:
- [From Design Prototype to Project Code](../../frontend/2.6-design-to-code/)
- [Upgrade Your Interface with Modern Component Libraries](../../frontend/2.7-modern-component-library/)
- [Make Interfaces Beautiful with LLMs and Skills](../../frontend/2.4-llm-skills-beautiful/)
- [Reference UI Design Specifications and Multi-Product UI Design](../../frontend/2.3-multi-product-ui/)
## 1. Assignment Goal
Build a frontend project with:
- a clear use case
- a structured layout
- a consistent design language
- real interaction patterns
- code organized in reusable components
Possible project types:
- a landing page
- a logged-in product workspace
- a dashboard or admin interface
- an AI tool interface
- a multi-module content management page
## 2. Required Tools
You should use:
- **Trae** or another AI coding IDE
- **at least one modern component library**
Examples:
- React: `shadcn/ui`, `Ant Design`, `HeroUI`, `Material UI`
- Vue: `Element Plus`, `Naive UI`, `Ant Design Vue`
The important part is not just naming a library. The library should meaningfully improve the project's structure and polish.
## 3. Minimum Requirements
| Area | Minimum Requirement | Notes |
| --- | --- | --- |
| Page scope | At least 2 pages, or 1 complex main page plus 1 supporting page | Example: dashboard + detail page |
| Component library | Use at least 1 real component library | It should appear in the actual implementation |
| Component breakdown | At least 5 reusable components or clearly separated modules | Navigation, filters, table area, modal, summary cards, etc. |
| Interaction | At least 3 interaction types | Form validation, modal/drawer, tabs, menus, filters, pagination, chart switching, etc. |
| States | Include at least 3 of the following: loading, empty, error, success feedback | Important for product realism |
| Responsiveness | Desktop-ready, with reasonable narrow-screen handling | Full mobile support is a bonus |
| Data | Real APIs or structured mock data | Even mock data should be organized well |
| Delivery | Repo link + public deployment | GitHub + Vercel/Zeabur is recommended |
## 4. Suggested Project Directions
### 4.1 Product landing page
Good for practicing visual hierarchy and presentation.
Suggested sections:
- hero
- feature cards
- workflow
- testimonials
- pricing
- FAQ
- footer
Good library options: `HeroUI`, `Material UI`
### 4.2 AI tool workspace
Good for layout, state design, and interaction.
Suggested sections:
- sidebar
- top toolbar
- input area
- result area
- history panel
- settings modal
Good library options: `shadcn/ui`, `Element Plus`
### 4.3 Dashboard / admin interface
Good for dense information layout and component composition.
Suggested sections:
- summary cards
- charts
- filters
- data table
- details drawer or modal
- pagination and status tags
Good library options: `Ant Design`, `Element Plus`
## 5. Suggested Workflow
### 5.1 Decide three things first
Before opening Trae, write down:
1. what kind of product you are building
2. why you chose this component library
3. which 3 modules matter most on the page
That clarity will make AI-generated output far better.
### 5.2 Generate the first structural version
In the first round, focus on layout, component structure, data shape, and core interactions rather than visual perfection.
Example prompt:
```text
Help me build a frontend project for [product type] using [component library].
For the first version, focus on:
1. page structure
2. component breakdown
3. mock data structure
4. core interactions
5. responsive layout foundations
Please split the code into reasonable page and component files.
```
### 5.3 Use later rounds for polish
Once the first version works, iterate on:
- spacing and layout rhythm
- visual hierarchy
- button priority
- hover/focus/disabled states
- status colors, icons, and tags
- narrow-screen adaptation
This is often where the project begins to feel like a real product.
### 5.4 Add the missing product details
Do not skip:
- loading buttons during submission
- empty-state messaging
- fallback UI for failed requests
- confirmation and feedback for save/delete/export flows
- scroll and overflow behavior in dense layouts
These details often create the biggest quality jump.
## 6. Starter Prompt Template
```text
Help me build a frontend interface for [project name] using [component library].
The style should feel [modern / professional / lightweight / consumer / enterprise].
Target users:
- [who uses this]
Page goal:
- [the most important task on this page]
Layout:
- left side: ...
- top area: ...
- main content: ...
- right side / bottom: ...
Interaction requirements:
- include several of: modal, drawer, filters, form validation, charts, pagination
- include loading, empty, and error states
- desktop-first, with narrow-screen handling
Implementation requirements:
- clear component breakdown
- mock data in separate files or modules
- do not generate one giant page file
- build structure first, then improve visuals iteratively
```
## 7. Deliverables
Please submit:
1. **A GitHub repository link**
2. **A public online link**
3. **A `README.md`**
- what the page or product is
- which component library you used and why
- the main modules included
- how to run it locally
4. **Screenshots or a short demo**
- at minimum, show the main desktop view
- if you handled responsiveness, include a narrow-screen view too
Useful optional additions:
- before/after comparison between first draft and final result
- the prompts that helped you most
- one UI detail you are especially proud of
## 8. Evaluation Checklist
Your project is strong if:
- the first screen clearly communicates what the product is
- the layout feels organized even when the page contains a lot of information
- controls feel consistent across the page
- the code is broken into understandable parts
- interactions feel intentional rather than decorative
- the result feels like a product, not a collage of screenshots
## 9. Bonus Ideas
You can go further with:
- real API integration
- theme tokens or dark/light theme switching
- reusable layout primitives
- charts, skeleton loaders, or richer data states
- accessibility improvements such as focus states and semantic structure
- a short design rationale
## 10. Common Mistakes
### 10.1 Focusing only on “looking better”
Modern frontend quality is not just visuals. It also includes clarity, usability, state handling, and structure.
### 10.2 Copying default component library examples too closely
A component library is a foundation, not the final identity of the product.
### 10.3 Generating everything at once
Complex interfaces are usually better when built in stages:
1. structure
2. modules
3. polish
4. details
## 11. Final Advice
Do not treat AI as a one-shot page generator. Treat it as a collaborative frontend partner.
The most useful pattern is usually:
- you define the direction
- AI creates the first version
- you critique the result
- AI refines it
- you keep making product decisions
That is how you move from “a page that exists” to “a frontend worth shipping.”
::: tip Next
If this assignment goes well, revisit [the full-stack assignment](../2.1-fullstack-app/) and upgrade that product with the frontend quality you built here.
:::
+9 -10
View File
@@ -788,7 +788,7 @@
</url> </url>
<url> <url>
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-0/</loc> <loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-0/</loc>
<lastmod>2026-03-25T17:40:30+08:00</lastmod> <lastmod>2026-03-25T23:02:33+08:00</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>0.9</priority> <priority>0.9</priority>
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-0/"/> <xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-0/"/>
@@ -892,7 +892,7 @@
</url> </url>
<url> <url>
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/appendix-double-diamond/</loc> <loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/appendix-double-diamond/</loc>
<lastmod>2026-03-25T17:40:30+08:00</lastmod> <lastmod>2026-03-25T23:02:33+08:00</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>0.9</priority> <priority>0.9</priority>
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/appendix-double-diamond/"/> <xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/appendix-double-diamond/"/>
@@ -900,7 +900,7 @@
</url> </url>
<url> <url>
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/appendix-idea-sources/</loc> <loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/appendix-idea-sources/</loc>
<lastmod>2026-03-25T17:40:30+08:00</lastmod> <lastmod>2026-03-25T23:02:33+08:00</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>0.9</priority> <priority>0.9</priority>
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/appendix-idea-sources/"/> <xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/appendix-idea-sources/"/>
@@ -916,7 +916,7 @@
</url> </url>
<url> <url>
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/appendix-jobs-to-be-done/</loc> <loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/appendix-jobs-to-be-done/</loc>
<lastmod>2026-03-25T17:40:30+08:00</lastmod> <lastmod>2026-03-25T23:02:33+08:00</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>0.9</priority> <priority>0.9</priority>
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/appendix-jobs-to-be-done/"/> <xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/appendix-jobs-to-be-done/"/>
@@ -924,7 +924,7 @@
</url> </url>
<url> <url>
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/appendix-mom-test/</loc> <loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/appendix-mom-test/</loc>
<lastmod>2026-03-25T17:40:30+08:00</lastmod> <lastmod>2026-03-25T23:02:33+08:00</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>0.9</priority> <priority>0.9</priority>
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/appendix-mom-test/"/> <xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/appendix-mom-test/"/>
@@ -947,12 +947,11 @@
<xhtml:link rel="alternate" hreflang="en" href="https://datawhalechina.github.io/easy-vibe/en/stage-2/assignments/2.1-fullstack-app/"/> <xhtml:link rel="alternate" hreflang="en" href="https://datawhalechina.github.io/easy-vibe/en/stage-2/assignments/2.1-fullstack-app/"/>
</url> </url>
<url> <url>
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/</loc> <loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-2/assignments/在线考试与管理系统/</loc>
<lastmod>2026-03-25T00:28:36+08:00</lastmod> <lastmod>2026-03-26T02:36:21.127Z</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>0.8</priority> <priority>0.8</priority>
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/"/> <xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-2/assignments/在线考试与管理系统/"/>
<xhtml:link rel="alternate" hreflang="en" href="https://datawhalechina.github.io/easy-vibe/en/stage-2/assignments/2.2-modern-frontend-trae/"/>
</url> </url>
<url> <url>
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-2/backend/2.2-database-supabase/</loc> <loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-2/backend/2.2-database-supabase/</loc>
@@ -1252,7 +1251,7 @@
</url> </url>
<url> <url>
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/cross-platform/3.8-pwa-local-app/</loc> <loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/cross-platform/3.8-pwa-local-app/</loc>
<lastmod>2026-03-25T00:28:36+08:00</lastmod> <lastmod>2026-03-25T20:01:19+08:00</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>0.8</priority> <priority>0.8</priority>
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/cross-platform/3.8-pwa-local-app/"/> <xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/cross-platform/3.8-pwa-local-app/"/>
+1 -1
View File
@@ -56,7 +56,7 @@
| 使用 lovart 生产素材 | 学会用 lovart 批量生成人物、场景等视觉素材,为 UI 设计和前端开发提供素材基础 | 🚧 | | 使用 lovart 生产素材 | 学会用 lovart 批量生成人物、场景等视觉素材,为 UI 设计和前端开发提供素材基础 | 🚧 |
| Figma 与 MasterGo 入门 | 用设计工具梳理信息架构和页面结构,为前端实现打基础 | 🚧 | | Figma 与 MasterGo 入门 | 用设计工具梳理信息架构和页面结构,为前端实现打基础 | 🚧 |
| 构建第一个现代应用程序-UI 设计 | 基于设计稿完成组件化界面,实现从设计到代码的第一条链路 | 🚧 | | 构建第一个现代应用程序-UI 设计 | 基于设计稿完成组件化界面,实现从设计到代码的第一条链路 | 🚧 |
| 参考 UI 设计规范与多产品 UI 设计 | 围绕统一主视觉扩展多产品界面,练习系统化设计能力 | 🚧 | | 参考 UI 设计规范设计页面和按钮 | 学习用主流设计规范组织页面结构、按钮层级,并借助 AI 生成设计方案 | 🚧 |
| [一起做霍格沃茨画像](/zh-cn/stage-2/frontend/2.5-hogwarts-portraits/) | 从 0 到 1 做出接入 AI 能力的前端应用,串联设计与开发 | 🚧 | | [一起做霍格沃茨画像](/zh-cn/stage-2/frontend/2.5-hogwarts-portraits/) | 从 0 到 1 做出接入 AI 能力的前端应用,串联设计与开发 | 🚧 |
#### 后端开发部分 #### 后端开发部分
@@ -1,3 +0,0 @@
# 大作业 1:构建第一个现代应用程序 - 全栈应用
> 本章节正在编写中,敬请期待...
@@ -1,3 +0,0 @@
# 大作业 2:现代前端组件库 + Trae 实战
> 本章节正在编写中,敬请期待...
@@ -0,0 +1,417 @@
# 大作业 1:第一个 SaaS 全栈应用——文案生成网站
第一次做全栈网站,最容易卡住的地方往往不是代码,而是**不知道该做什么**。
题目太大,功能太散,做到一半发现根本收不住。
所以这次换个思路:不给开放题,直接给你一个明确的方向——做一件完整但不复杂的事。
::: tip 🎯 这次做什么?
打造一个 **AI 营销文案工作台**。用户登录后填写产品信息,一键生成营销文案,自动保存历史记录。需要更多生成次数?升级套餐即可。管理员可在后台查看所有用户、生成记录和支付情况。
:::
<div style="margin: 32px 0;">
<ClientOnly>
<StepBar :active="0" :items="[
{ title: '定主题', description: '先把网站的页面和功能定下来' },
{ title: '搭前台', description: '首页、登录、工作台先做出来' },
{ title: '接后端', description: '数据库、生成、支付接起来' },
{ title: '做后台与交付', description: '管理台、部署、演示材料补齐' }
]" />
</ClientOnly>
</div>
## 为什么选这个题目?
因为它恰到好处——**涵盖了现代网站该有的所有要素,又不会复杂到失控**。
- **前台有实际用途**:不是摆设,用户真的来解决问题
- **用户系统有登录权限**:区分访客与注册用户
- **核心功能是"生成"**:调用 AI 动态产出内容,而非展示静态页面
- **数据持久化**:生成结果存入数据库,随时可查
- **付费机制**:看起来像正经 SaaS,而非玩具项目
- **管理后台**:体验管理员视角,掌控全局
难度适中:不会简单到只有一个表单,也不会复杂到做一周还找不着北。
## 1. 定主题:先搞清楚要做什么
网站名称:**LaunchKit**
定位:AI 营销文案工作台
目标用户:独立开发者、小商家、内容运营者,以及想快速搞定 Landing Page 的人。
他们来这里不是为了闲聊——就是想**快速拿到能用的营销文案**。
### 核心功能
别想太复杂,核心就一件事:
**用户输入**:产品名、一句话介绍、目标用户、3 个卖点、投放渠道
**系统输出**:主标题、副标题、CTA 按钮文案、3 版短文案、1 版长文案
生成结果自动存入账户,下次登录随时查看。
### 页面规划
按这 6 个页面来做:
| 页面 | 路径 | 说明 |
|------|------|------|
| 首页 | `/` | 清晰传达产品价值,放置注册/登录入口 |
| 登录页 | `/login` | 简洁的登录表单 |
| 注册页 | `/register` | 简洁的注册表单 |
| 工作台 | `/dashboard` | 填写信息、生成文案、查看结果 |
| 套餐页 | `/billing` | 展示免费版与 Pro 版,跳转 Stripe 付款 |
| 管理后台 | `/admin` | 管理员查看用户、生成记录、支付状态 |
### 数据模型
三张核心表足够:
```sql
profiles (
id uuid primary key,
email text,
role text, -- user / admin
plan text, -- free / pro
created_at timestamptz
)
generations (
id uuid primary key,
user_id uuid,
product_name text,
target_channel text,
input_payload jsonb,
result_payload jsonb,
created_at timestamptz
)
subscriptions (
id uuid primary key,
user_id uuid,
stripe_customer_id text,
stripe_subscription_id text,
plan text,
status text,
created_at timestamptz
)
```
到这一步,整个网站的骨架已经清晰。
<div style="margin: 32px 0;">
<ClientOnly>
<StepBar :active="1" :items="[
{ title: '定主题', description: '先把网站的页面和功能定下来' },
{ title: '搭前台', description: '首页、登录、工作台先做出来' },
{ title: '接后端', description: '数据库、生成、支付接起来' },
{ title: '做后台与交付', description: '管理台、部署、演示材料补齐' }
]" />
</ClientOnly>
</div>
## 2. 搭前台:先把页面做出来
这一步先别碰数据库,也别急着接支付——**先把前台骨架搭起来**。
### 技术栈
- **Next.js App Router** —— 现代 React 框架
- **TypeScript** —— 类型安全
- **Tailwind CSS** —— 原子化样式
- **shadcn/ui** —— 精致组件库
- **Supabase** —— 后端服务
- **Stripe** —— 支付处理
这套组合是目前 AI IDE 最擅长的技术栈,也最符合现代 SaaS 的审美。
### 第一步:搭建项目骨架
把下面这段提示词丢给 Trae / Cursor / Claude Code
```text
请帮我创建一个现代 SaaS 网站,名字叫 LaunchKit。
技术栈要求:
- Next.js App Router
- TypeScript
- Tailwind CSS
- shadcn/ui
页面清单:
1. 首页 /
2. 登录页 /login
3. 注册页 /register
4. 用户工作台 /dashboard
5. 套餐页 /billing
6. 管理后台 /admin
请先只做前端结构,不接数据库。
要求:
- 首页要有现代 AI SaaS 落地页的气质
- 登录和注册页保持简洁
- Dashboard 左侧表单,右侧结果展示
- Billing 页面展示 free 和 pro 两个套餐
- Admin 页面先做后台框架:侧边栏、顶部栏、表格区域
- 使用 shadcn/ui 组件
- 页面风格要像真实产品,不要像课堂 demo
```
### 第二步:完善工作台细节
第一版页面完成后,继续补充:
```text
请继续完善 /dashboard 页面。
这是一个 AI 营销文案工作台。
左侧表单字段:
- 产品名
- 一句话介绍
- 目标用户
- 3 个卖点
- 投放渠道(官网、朋友圈、小红书、抖音、邮件)
右侧结果区域预留:
- 主标题
- 副标题
- CTA
- 3 版短文案
- 长文案
先用 mock 数据跑通交互。
要求:
- 点击"生成文案"后有 loading 状态
- 结果区域设计空状态
- 响应式布局,宽屏窄屏都能正常显示
```
### 遇到阻碍?
回头看看这几篇:
- [构建第一个现代应用程序 - UI 设计](../../frontend/2.2-ui-design/)
- [参考 UI 设计规范设计页面和按钮](../../frontend/2.3-multi-product-ui/)
- [用 LLM 和 Skills 让界面变好看](../../frontend/2.4-llm-skills-beautiful/)
- [从设计原型到项目代码](../../frontend/2.6-design-to-code/)
- [使用现代组件库更新你的界面](../../frontend/2.7-modern-component-library/)
<div style="margin: 32px 0;">
<ClientOnly>
<StepBar :active="2" :items="[
{ title: '定主题', description: '先把网站的页面和功能定下来' },
{ title: '搭前台', description: '首页、登录、工作台先做出来' },
{ title: '接后端', description: '数据库、生成、支付接起来' },
{ title: '做后台与交付', description: '管理台、部署、演示材料补齐' }
]" />
</ClientOnly>
</div>
## 3. 接后端:把数据库、生成、支付串起来
这一步才算真正的"全栈"。
### 第三步:接入 Supabase 登录
```text
请把我当成 0 基础,一步一步带我完成 Supabase 登录接入。
需要你帮我完成:
1. 项目接入 Supabase
2. 实现注册、登录、退出功能
3. 登录成功后跳转到 /dashboard
4. 未登录用户访问 /dashboard、/billing、/admin 时自动跳转 /login
5. 创建 profiles 表
6. 用户注册成功后自动在 profiles 表创建记录
7. profiles 表包含 email、role、plan 字段
实现要求:
- 每步都说明在修改哪些文件
- 密钥不要硬编码
- 需要在 Supabase 后台手动操作的地方请明确标注
- 完成后说明如何验证注册和登录
```
### 第四步:接入生成接口和数据库
```text
请把我当成 0 基础,帮我完成网站的核心功能:生成营销文案并保存。
目标效果:
1. 用户在 /dashboard 填写表单,点击"生成文案"
2. 后端接收:产品名、介绍、目标用户、卖点、投放渠道
3. 后端调用模型生成结果
4. 页面展示生成结果
5. 输入和输出都保存到数据库
6. 用户下次进入可查看历史记录
需要你完成:
- 创建生成接口 /api/generate
- 创建 generations 表
- 设计输入和输出字段
- Dashboard 页面读取当前用户的历史记录
用户体验:
- 按钮 loading 状态
- 生成失败时的错误提示
- 无历史记录时的空状态
完成后请说明:
- 前端页面文件位置
- 后端接口文件位置
- 数据写入数据库的逻辑位置
- 如何测试完整生成链路
```
### 第五步:接入 Stripe 付费
```text
请把我当成 0 基础,帮我给 LaunchKit 加上最简可用的 Stripe 付费。
不需要复杂系统,先跑通最基本的付费链路。
需要你完成:
1. /billing 页面展示 free 和 pro 两个套餐
2. 用户点击升级后跳转 Stripe Checkout
3. 支付成功后返回网站
4. 支付结果保存到 subscriptions 表
5. 同步更新 profile.plan 字段
6. free 用户每日限 3 次生成,pro 用户不限
实现原则:
- 先跑通主流程,暂不考虑复杂边界
- 需要在 Stripe 后台配置的地方请写清楚
- 完成后说明如何测试完整支付流程
```
### 第六步:搭建管理后台
```text
请把我当成 0 基础,帮我做一个简洁可用的管理后台。
仅限管理员访问。
需要你完成:
1. 仅 role = admin 的用户可访问 /admin
2. 后台包含 3 个 Tab
- 用户列表
- 生成记录
- 订阅状态
3. 用户列表显示:email、plan、创建时间
4. 生成记录显示:用户、产品名、渠道、创建时间
5. 订阅状态显示:用户、套餐、支付状态
要求:
- 界面简洁清晰
- 使用现有组件库的表格、Tab、Badge
- 完成后说明如何将账号设为 admin
```
### 遇到阻碍?
回头看看这几篇:
- [从数据库到 Supabase](../../backend/2.2-database-supabase/)
- [大模型辅助编写接口代码与接口文档](../../backend/2.3-ai-interface-code/)
- [如何集成 Stripe 等收费系统](../../backend/2.7-stripe-payment/)
<div style="margin: 32px 0;">
<ClientOnly>
<StepBar :active="3" :items="[
{ title: '定主题', description: '先把网站的页面和功能定下来' },
{ title: '搭前台', description: '首页、登录、工作台先做出来' },
{ title: '接后端', description: '数据库、生成、支付接起来' },
{ title: '做后台与交付', description: '管理台、部署、演示材料补齐' }
]" />
</ClientOnly>
</div>
## 4. 做后台与交付:真正上线
网站基本成型,最后三件事:
### 4.1 部署
代码推送到 GitHub,部署到公网。
参考:
- [Git 和 GitHub 工作流](../../backend/2.4-git-workflow/)
- [如何部署 Web 应用](../../backend/2.5-zeabur-deployment/)
### 第七步:部署前检查
```text
请把我当成 0 基础,帮我检查项目是否具备部署条件。
检查重点:
- 环境变量是否完整
- 登录回调地址是否正确
- Stripe 支付回调地址是否正确
- 页面是否缺少 loading、空状态、错误提示
- README 是否包含启动说明和部署说明
需要你:
1. 按优先级列出待修复事项
2. 标注哪些必须先修
3. 说明修复后的部署步骤
```
### 4.2 README
至少包含:
- 项目简介
- 核心页面说明
- 技术栈
- 本地启动步骤
- 环境变量清单
### 4.3 演示材料
至少准备:
- 首页截图
- Dashboard 生成截图
- Billing 页面截图
- Admin 后台截图
- 60 秒左右演示视频
## 5. 最终成果
按这篇指南做完,你拿到的不是"练习页",而是一个**最小但完整的 SaaS 产品**:
- ✅ 现代组件库前端
- ✅ Supabase 数据库与登录
- ✅ 真实 AI 生成功能
- ✅ Stripe 支付系统
- ✅ 管理员后台
- ✅ 可部署上线
这完全够资格作为**第一个全栈作品**。
## 6. 提交前最后检查
<el-card shadow="hover" style="margin: 20px 0; border-radius: 12px;">
<template #header>
<div style="font-weight: bold; font-size: 16px;">提交前最后看一眼</div>
</template>
<ul style="list-style-type: none; padding-left: 0;">
<li><label><input type="checkbox" disabled /> 首页、登录页、Dashboard、Billing、Admin 均已完成</label></li>
<li><label><input type="checkbox" disabled /> 用户可以注册、登录、退出</label></li>
<li><label><input type="checkbox" disabled /> 生成结果真实写入数据库</label></li>
<li><label><input type="checkbox" disabled /> 支付主流程已跑通</label></li>
<li><label><input type="checkbox" disabled /> 管理员可查看用户、生成记录和支付状态</label></li>
<li><label><input type="checkbox" disabled /> 项目已部署到公网</label></li>
</ul>
</el-card>
::: tip 🚀 下一篇
完成这个网站后,继续阅读 [大作业 2:现代前端组件库 + Trae 实战](../2.2-modern-frontend-trae/),把界面质量再提升一层。
:::
@@ -0,0 +1,7 @@
# 自制 Dify 智能体平台
::: warning 🚧 建设中
本文档正在编写中,内容可能会有较大变动。请关注更新。
:::
@@ -0,0 +1,6 @@
# 大作业 2:在线考试与管理系统
自动出题答题、后台记录每次测试、有管理员和学生模式。。。支持正常登录。。
有管理系统、前端正常页面系统。。。。使用现代组件库。
> 本章节正在编写中,敬请期待...
@@ -0,0 +1,7 @@
# 现代网页落地页
::: warning 🚧 建设中
本文档正在编写中,内容可能会有较大变动。请关注更新。
:::
@@ -0,0 +1,7 @@
# 电影推荐网 (Spring Boot)
::: warning 🚧 建设中
本文档正在编写中,内容可能会有较大变动。请关注更新。
:::
@@ -0,0 +1,7 @@
# 简单买菜微服务网站
::: warning 🚧 建设中
本文档正在编写中,内容可能会有较大变动。请关注更新。
:::
@@ -0,0 +1,7 @@
# 交通大数据可视化分析 (Go项目)
::: warning 🚧 建设中
本文档正在编写中,内容可能会有较大变动。请关注更新。
:::
@@ -0,0 +1,7 @@
# 智能旅游规划 Agent 平台
::: warning 🚧 建设中
本文档正在编写中,内容可能会有较大变动。请关注更新。
:::
@@ -1,3 +1,907 @@
# 如何集成 Stripe 等收费系统 # 如何集成 Stripe 等收费系统
> 本章节正在编写中,敬请期待... 当你的产品已经有了页面、登录、数据库和基础后端之后,下一个现实问题就是:**怎么收费**。
很多人第一次接支付,会把注意力全放在"怎么跳转到付款页"上。但真正决定系统是否稳定的,不是按钮,而是整条收费链路:谁决定价格、谁确认支付成功、谁更新数据库、谁回收权限。
这篇文章我帮你拆成两部分:
- **前半部分**只讲最实用的基础接入,目标是让你尽快把 Stripe 接进项目。
- **后半部分**统一放到附录,包含 Webhook 细节、订阅事件、不同国家和地区的支付方案差异。
> 💡 建议先学完这些章节再继续
>
> - [从数据库到 Supabase](../2.2-database-supabase/)
> - [大模型辅助编写接口代码与接口文档](../2.3-ai-interface-code/)
> - [如何部署 Web 应用](../2.5-zeabur-deployment/)
# 你将学到
1. 最小可行的支付系统到底长什么样。
2. 如何用最快的方式把 Stripe 接进你的项目。
3. 如何写提示词,让 AI 直接帮你加支付系统。
4. 如果不是做海外 Stripe 项目,不同地区应该优先考虑什么支付方案。
---
# 第一部分:基础上手
## 1. 先记住 3 个原则
如果你只记住三件事,就记住下面这三条:
1. **价格必须由后端决定**,不能相信前端传来的金额。
2. **真正让权限生效的是 Webhook**,不是 `success` 页面。
3. **你自己的数据库必须保存支付状态**,不能只依赖 Stripe 后台。
这三条是支付系统最核心的边界。只要边界没错,后面换 Stripe、PayPal、支付宝、微信支付,本质上都只是"接口换了,架构不变"。
## 2. 如果不在后端处理,而是前端直接连 Stripe,会怎么样?
这是很多人第一次做支付时最自然的想法:
- 页面上已经有"购买"按钮了
- 那我能不能让前端自己去连 Stripe
- 这样是不是就不用做后端了
如果你只是做一个假的演示页面,这样想当然没问题。
但如果你是真的要收钱,**这条路通常会把事情做坏**。
最常见的问题有这几个:
1. **价格容易被改**
浏览器里的请求,是用户自己电脑上发出去的。别人是可以改请求内容的。
2. **敏感信息容易暴露**
真正重要的密钥、价格逻辑、会员开通逻辑,本来就不该放在前端。
3. **你没法可靠确认"这笔钱到底算不算成功"**
用户跳到成功页,不代表你的数据库已经同步对了。
4. **数据库状态会乱**
用户可能说"我明明已经付钱了",但你自己的系统里根本没记上。
所以更安全的分工应该是:
- 前端负责:展示按钮、发起购买、跳转页面
- 后端负责:决定价格、创建支付会话、接收 Webhook、更新数据库
::: info 这一段你可以直接记成一句话
**前端可以负责跳转,后端必须负责定价和确认。**
只要是真收钱,就不要把"最终价格决定权"和"支付成功后的开通逻辑"放在前端。
:::
## 3. 什么时候适合先用 Stripe
如果你做的是下面这些场景,Stripe 往往是最顺手的起点:
- 面向海外用户的 SaaS
- 订阅制会员产品
- 数字产品、模板、AI 积分包
- 想先快速验证商业化,而不是一开始就处理太多本地支付细节
如果你的主要用户在中国大陆,那通常不会把 Stripe 当第一选择,这个我放到附录里统一讲。
## 4. 最小可行支付链路
先看最小版本。只要这条链路能跑通,你的支付系统就有了骨架。
```mermaid
flowchart LR
user["用户"]
frontend["前端页面"]
backend["你的后端"]
checkout["Stripe Checkout"]
webhook["Stripe Webhook"]
db["Supabase / 业务数据库"]
user -->|"点击购买"| frontend
frontend -->|"请求创建支付会话"| backend
backend -->|"按后端价格创建 Session"| checkout
frontend -->|"跳转到支付页"| checkout
checkout -->|"支付完成后发送事件"| webhook
webhook -->|"校验签名并更新状态"| backend
backend -->|"写入 orders / subscriptions"| db
db -->|"前端刷新后读取最新状态"| frontend
```
把它翻译成人话就是:
1. 用户点按钮。
2. 前端找后端要支付链接。
3. 后端用 Stripe 密钥创建支付会话。
4. 用户去 Stripe 页面付款。
5. Stripe 把"付款真的成功了"这件事通过 Webhook 通知你。
6. 你的后端再去更新数据库。
## 5. 发起付款的标准时序图
如果你习惯看更规范的系统图,可以直接看这张时序图:
```mermaid
sequenceDiagram
autonumber
actor User as 用户
participant Frontend as 前端页面
participant Backend as 后端 API
participant Stripe as Stripe Checkout
User->>Frontend: 点击"升级"或"购买"
Frontend->>Backend: POST /api/billing/create-checkout-session
Note right of Frontend: 前端传 plan / userId / email\n不传最终收费金额
Backend->>Backend: 校验套餐并映射 priceId
Backend->>Stripe: 创建 Checkout Session
Stripe-->>Backend: 返回 session.url
Backend-->>Frontend: 返回支付链接
Frontend-->>User: 跳转到 Stripe 支付页
User->>Stripe: 完成付款
```
## 6. 快速开始
如果你想最快把它接进项目,照着下面这 5 步做就够了。
### 6.1 第一步:在 Stripe 后台创建商品和价格
这一步的目的,不是"先随便配点东西",而是先把 **你到底在卖什么、打算怎么收费** 这件事在 Stripe 里定义清楚。
在 Stripe 的模型里:
- **Product** 表示"你卖的是什么",比如 `Pro 会员`
- **Price** 表示"这个东西卖多少钱、按什么周期卖",比如 `月付 9.9 美元``年付 99 美元`
为什么要先做这一步?
因为后面当你的后端创建 Checkout Session 时,并不是直接传一个金额给 Stripe,而是要传一个已经存在的 `price_id`。Stripe 再根据这个 `price_id` 去生成真正的支付页、金额、币种和订阅周期。
如果你跳过这一步,后面的"创建支付链接"其实就没法做。
::: info 为什么这里要先停一下
很多新手看到 `Product``Price` 这两个词会有点烦,觉得像是在学 Stripe 的内部术语。
但实际上,这一步是在做一件很朴素的事:
- 把"卖什么"定义清楚
- 把"卖多少钱"定义清楚
- 让后端之后能拿一个稳定的 `price_id` 去创建支付链接
只要把这层想明白,后面的 Checkout Session 就不会觉得抽象。
:::
对于一个最小可行的订阅系统,你至少先建这两个层级:
- 一个 `Product`
- 一个或多个 `Price`
你可以直接打开这些页面:
- Stripe Dashboard 登录页:[Dashboard Login](https://dashboard.stripe.com/login)
- Stripe 商品与价格管理文档:[Manage products and prices](https://docs.stripe.com/products-prices/manage-prices)
- Stripe Checkout 快速开始文档:[Build a Stripe-hosted checkout page](https://docs.stripe.com/checkout/quickstart?lang=node)
- Stripe Dashboard 商品页:[Product catalog](https://dashboard.stripe.com/test/products)
推荐你先在 **Test mode(测试模式)** 下操作,不要一开始就在正式环境里建。
一个最常见的最小配置是:
- `Product`: `Pro Plan`
- `Price 1`: `pro_monthly`
- `Price 2`: `pro_yearly`
你在后台操作时,可以按这个顺序理解:
1. 先创建一个商品 `Pro Plan`
2. 再在这个商品下面挂两个价格
3. 月付和年付其实是同一个商品的两种收费方式
完成后,你至少要记下这些信息:
- 月付价格的 `price_id`
- 年付价格的 `price_id`
- 你自己的套餐名,例如 `pro_monthly``pro_yearly`
如果你是第一次进 Stripe 后台,建议你把这一步理解成:
- `Product` 决定支付页里卖的是什么
- `Price` 决定支付页里收多少钱
- 后端之后真正会用到的,主要是 `price_id`
::: info 真正要记下来的值
这一页里最重要的不是商品名称,而是 `price_id`
后面无论是让 AI 帮你接后端,还是你自己排查问题,真正会频繁用到的,通常都是:
- `STRIPE_PRICE_PRO_MONTHLY`
- `STRIPE_PRICE_PRO_YEARLY`
- 它们背后对应的两个 `price_id`
:::
如果你想让 AI 先带你把后台配置做完,可以直接用这个 prompt:
```text
我现在是第一次用 Stripe,你先不要改代码,先带我在 Stripe 后台把最基本的付费配置做好。
请基于这些官方文档给我一步一步的操作说明:
- https://docs.stripe.com/products-prices/manage-prices
- https://docs.stripe.com/checkout/quickstart?lang=node
我的情况是:
- 我想做一个最简单的会员付费
- 只有两个套餐:月付和年付
- 我现在还不懂 Product、Price 这些词
请你:
1. 先用最简单的话告诉我 Product 和 Price 分别是什么。
2. 再按"先打开哪个页面 -> 点哪里 -> 填什么"的顺序教我操作。
3. 最后提醒我,做完以后我需要从后台复制哪些内容给后端使用。
4. 如果我容易走错,请顺便提醒我应该一直在测试模式里操作。
```
### 6.2 第二步:准备环境变量
你通常至少需要准备这些环境变量:
- `STRIPE_SECRET_KEY`
- `STRIPE_WEBHOOK_SECRET`
- `STRIPE_PRICE_PRO_MONTHLY`
- `STRIPE_PRICE_PRO_YEARLY`
- `APP_URL`
- `SUPABASE_URL`
- `SUPABASE_SERVICE_ROLE_KEY`
你可以直接打开这些页面:
- Stripe API Keys 文档:[API keys](https://docs.stripe.com/keys)
- Stripe Dashboard API Keys 页面:[API Keys](https://dashboard.stripe.com/test/apikeys)
- Stripe Webhooks 文档:[Receive Stripe events in your webhook endpoint](https://docs.stripe.com/webhooks)
- Stripe Dashboard Webhooks 页面:[Workbench Webhooks](https://dashboard.stripe.com/test/workbench/webhooks)
> ⚠️ `STRIPE_SECRET_KEY``SUPABASE_SERVICE_ROLE_KEY` 都只能放在后端。
::: info 环境变量这一步的目的
这一步不是为了"先把 `.env` 填满",而是为了把支付系统里最敏感的几样东西放到后端保管:
- Stripe 的后端密钥
- Webhook 验签密钥
- 你自己的价格映射
简单理解:
前端只负责发起购买,真正的秘密和定价逻辑都应该留在服务端。
:::
这一步也可以直接让 AI 帮你整理:
```text
请你先看看我这个项目现在是怎么放环境变量的,然后帮我把 Stripe 需要的环境变量整理出来。
请参考这些文档:
- https://docs.stripe.com/keys
- https://docs.stripe.com/webhooks
我的情况是:
- 我是零基础
- 我分不清哪些变量应该放前端,哪些应该放后端
- 我也不确定当前项目应该改 `.env``.env.local` 还是别的文件
请你:
1. 先搜索当前项目里环境变量通常写在哪。
2. 帮我列出 Stripe 接入最少需要哪些变量。
3. 用最简单的话告诉我每个变量是干什么的。
4. 告诉我每个变量应该去哪一个 Stripe 页面复制。
5. 如果项目里有示例环境变量文件,请直接帮我补上变量名。
```
### 6.3 第三步:后端创建 Checkout Session
这一步你不用自己写接口,直接让 AI 参考官方文档帮你实现。
先把这些文档给它:
- Stripe Checkout 快速开始:[Build a Stripe-hosted checkout page](https://docs.stripe.com/checkout/quickstart?lang=node)
- Checkout Sessions API[Create a Checkout Session](https://docs.stripe.com/api/checkout/sessions/create)
- 订阅说明:[Subscriptions](https://docs.stripe.com/payments/subscriptions)
然后直接贴这个 prompt
```text
请你先看看我当前项目的后端代码是怎么组织的,然后帮我把 Stripe 支付接进去。
请参考这些官方文档:
- https://docs.stripe.com/checkout/quickstart?lang=node
- https://docs.stripe.com/api/checkout/sessions/create
- https://docs.stripe.com/payments/subscriptions
我的目标很简单:
- 用户点购买按钮后,能跳到 Stripe 的付款页面
- 套餐只有月付和年付两种
- 不要让我自己决定代码该放在哪,你先看项目再帮我放到合适的位置
请你:
1. 先搜索项目,弄清楚后端入口文件、路由文件、环境变量写法分别在哪里。
2. 再参考官方文档,帮我把"创建 Stripe 支付链接"这一步接进去。
3. 不要让我自己传金额,价格请用后端环境变量来决定。
4. 做完后告诉我你改了哪些文件。
5. 最后告诉我,我还需要去 Stripe 后台补哪些配置。
```
### 6.4 第四步:前端跳转到支付页
这一步的目标非常简单:让定价页按钮调用你的后端接口,再跳转到 Stripe Checkout。
参考文档:
- Stripe Checkout 集成说明:[Build an integration with Checkout](https://docs.stripe.com/payments/checkout/build-integration)
给 AI 的 prompt
```text
帮我把项目里的"购买"按钮接上 Stripe。
要求:
- 不动现有页面,只改按钮点击后的逻辑
- 点击后调用后端接口获取支付链接,然后跳转到 Stripe
- 如果出错,给用户一个简单提示(比如"支付暂时不可用,请稍后再试")
参考文档:https://docs.stripe.com/payments/checkout/build-integration
```
### 6.5 第五步:Webhook 更新数据库状态
这是最关键的一步。
::: info 为什么这一步最关键
很多人会以为"用户付完款并且跳转到了 success 页面"就算完成了。
不是。
对你的系统来说,真正重要的是:
**Stripe 有没有正式把事件打到你的 Webhook,而你的后端有没有把数据库状态更新成功。**
:::
你也可以让 AI 按 Stripe 官方 Webhook 文档直接实现,不要自己手写。
参考文档:
- Stripe Webhooks[Receive Stripe events in your webhook endpoint](https://docs.stripe.com/webhooks)
- Stripe CLI[Stripe CLI](https://docs.stripe.com/stripe-cli)
- Stripe CLI 用法:[Use the Stripe CLI](https://docs.stripe.com/stripe-cli/use-cli)
给 AI 的 prompt
```text
请继续帮我把 Stripe 的"付款成功后自动生效"这一步接好。
请参考这些官方文档:
- https://docs.stripe.com/webhooks
- https://docs.stripe.com/stripe-cli
- https://docs.stripe.com/stripe-cli/use-cli
我的目标是:
- 用户付完钱后,不只是跳转到成功页面
- 而是真的把我数据库里的会员状态改成已开通
请你:
1. 先搜索当前项目里数据库相关代码和用户状态是怎么存的。
2. 再帮我加 Stripe webhook。
3. 支付成功后,把对应用户改成 active,或者更新成项目里现在已经在用的会员状态字段。
4. 如果项目里已经有订阅表、订单表、用户表,请优先沿用现有结构。
5. 做完后告诉我你改了哪些文件。
6. 顺便告诉我本地怎么测试这一步有没有真的生效。
```
## 7. 让 AI 帮你快速接入的提示词
如果你用的是 Codex、Claude Code、Trae、Cursor 一类工具,可以直接把下面这个提示词贴给它,让它在你的项目里做支付接入。
```text
请你帮我把当前项目接上 Stripe 支付,我希望做一个最简单能跑起来的会员收费功能。
我的要求:
1. 我是零基础,请你先自己看项目,再决定代码应该改哪里。
2. 不要让我自己判断目录结构、路由结构、数据库结构。
3. 我只想先做最简单版本:月付和年付两个套餐。
4. 用户点击购买后,能跳到 Stripe 付款页面。
5. 付款成功后,我数据库里的会员状态能变成已开通。
6. 不要一开始加太多复杂功能,比如优惠券、升级降级、复杂发票。
输出要求:
1. 先给我一个改动计划。
2. 然后直接修改代码。
3. 最后告诉我怎么一步一步本地测试。
4. 如果有哪个步骤还需要我去 Stripe 后台操作,请直接把链接和要点告诉我。
```
如果你希望 AI 更贴近你的项目,还可以在开头补上:
- 你的前端框架
- 你的后端目录结构
- 你的数据库表名
- 你现在的用户系统是 Supabase Auth 还是自建 Auth
## 7.1 本地联调也尽量交给 AI
如果你希望连本地联调都让 AI 帮你串起来,可以直接用下面这段:
```text
请继续帮我把 Stripe 支付真正跑通,我想一步一步照着做,不想自己猜。
请参考官方文档:
- https://docs.stripe.com/webhooks
- https://docs.stripe.com/stripe-cli
- https://docs.stripe.com/stripe-cli/use-cli
我的目标:
1. 告诉我先打开哪些 Stripe 页面。
2. 告诉我如何拿到 STRIPE_WEBHOOK_SECRET。
3. 告诉我如何使用 stripe login 和 stripe listen。
4. 告诉我怎样验证 checkout.session.completed 已经成功打到本地 webhook。
5. 如果当前项目需要先启动前端和后端,也请顺带告诉我具体命令。
6. 不要只讲原理,请按实际操作步骤输出。
7. 如果我某一步做错了,也请告诉我最常见的报错会长什么样。
```
## 8. 最容易踩坑的 4 件事
1. **把 `success` 页面当成支付成功**
真正决定状态的是 Webhook,不是前端跳转。
2. **让前端传金额**
这会带来严重的价格篡改风险。
3. **Webhook 路由被 `express.json()` 提前处理**
Stripe 验签需要原始请求体。
4. **没有做幂等处理**
Webhook 可能重试,如果你每次都重复加会员或积分,就会出事故。
## 9. 一句话选型建议
如果你现在只是想先把收费跑起来:
| 你的主要用户 | 最先尝试的方案 |
| :--- | :--- |
| 海外 SaaS / 国际用户 | Stripe |
| 中国大陆用户 | 支付宝 / 微信支付 |
| 香港或跨境团队 | Stripe + 本地钱包 / FPS 聚合方案 |
后面的具体区别,我统一放到附录。
::: info 最简单的选型思路
不要一开始就想"我要把全球支付方式一次全接完"。
更实际的顺序通常是:
- 先按主要用户所在地区选一条主支付链路
- 先把最小可行支付跑通
- 再根据真实用户来源补第二、第三种支付方式
:::
## 10. 小结
到这里,你已经掌握了最基础但最重要的一条收费链路:
1. 前端发起购买。
2. 后端创建 Checkout Session。
3. 用户在 Stripe 页面支付。
4. Stripe 通过 Webhook 通知后端。
5. 后端更新数据库。
6. 前端刷新后显示新的会员或订单状态。
如果你只想快速把支付接进项目,前面的内容已经够用了。下面的附录你可以在真正遇到问题时再回来看。
---
# 附录
## 附录 A:Stripe 里最常见的几个对象
第一次看 Stripe 文档,最容易被这些对象名绕晕。你其实只需要先理解下面几个:
| 对象 | 作用 | 你可以把它理解成什么 |
| :--- | :--- | :--- |
| `Product` | 描述卖的是什么 | 商品或会员套餐 |
| `Price` | 描述卖多少钱、周期怎么收费 | 月付、年付、买断 |
| `Checkout Session` | Stripe 托管的支付流程 | 付款页 |
| `Subscription` | 周期订阅关系 | 自动续费会员 |
| `Customer` | 付款用户 | Stripe 中的客户档案 |
| `Webhook` | 异步通知 | Stripe 告诉你"这笔款怎么样了" |
## 附录 B:为什么 `success` 页面不等于支付成功
很多人以为"用户付完钱,跳到了 success 页面"就算支付成功了。这是最容易踩的坑。
### 先讲一个真实场景
假设你做了一个会员网站:
1. 用户点击"购买会员"
2. 跳转到 Stripe 付款页面
3. 用户输入信用卡,点击付款
4. 页面跳转到你的 `success.html`
5. 你在 success 页面写代码:"既然到了这页,就给用户开通会员"
**问题在哪?**
用户可能根本没付钱,或者付到一半关页面了,也能直接访问 `success.html`
### 两条完全不同的路径
```mermaid
flowchart TB
pay["用户在 Stripe 完成支付"]
subgraph unreliable["❌ 不可靠路径:只看 success 页面"]
success["浏览器跳到 success 页面"]
fake["前端代码认为已开通"]
risk["风险:关页 / 断网 / 伪造 URL / 根本没付钱"]
success --> fake --> risk
end
subgraph reliable["✅ 可靠路径:以后端 Webhook 为准"]
event["Stripe 服务器发送 Webhook"]
verify["后端校验签名"]
active["数据库正式更新为已付费"]
event --> verify --> active
end
pay --> success
pay --> event
```
**关键区别:**
| | success 页面跳转 | Webhook 通知 |
| :--- | :--- | :--- |
| 谁发起的 | 用户的浏览器 | Stripe 的服务器 |
| 能伪造吗 | 能,直接访问 URL 就行 | 不能,有签名验证 |
| 一定代表付款成功吗 | 不一定 | 一定 |
| 你的系统怎么知道 | 前端代码猜的 | Stripe 正式通知的 |
### 完整流程应该是怎样的
```mermaid
sequenceDiagram
autonumber
actor User as 用户
participant Frontend as 你的网页
participant Stripe as Stripe
participant Webhook as 你的后端接口
participant DB as 数据库
User->>Stripe: 在 Stripe 页面完成付款
Note over Stripe: 钱真的到了 Stripe 账户
Stripe-->>Frontend: 浏览器跳转到 success 页面
Note over Frontend: ⚠️ 这步只是跳转<br/>不代表系统已确认
Stripe->>Webhook: 发送 Webhook 通知<br/>"checkout.session.completed"
Note over Webhook: ✅ 这才是正式通知
Webhook->>Webhook: 校验签名<br/>(确保是 Stripe 发的,不是黑客)
Webhook->>DB: 更新用户状态为"已付费"
DB-->>Webhook: 保存成功
Webhook-->>Stripe: 返回 200 OK
Frontend->>DB: 用户刷新页面,查询状态
DB-->>Frontend: 返回"已付费"
Note over Frontend: 这时候才显示会员功能
```
### 每个环节的卡点
**第 1 步:用户在 Stripe 付款**
这是唯一确定"钱真的付了"的时刻:
- 用户输入信用卡信息,点击确认
- 银行从用户卡里扣款
- Stripe 确认收到这笔钱
**第 2 步:浏览器跳转到 success 页面(问题最大)**
这一步完全不可靠,因为:
- 用户可以直接在浏览器输入 `yoursite.com/success`,根本没付钱也能访问
- 用户付到一半关页面了,但之前复制了 success 链接,之后直接打开
- 网络问题导致跳转失败,但钱已经扣了(用户付了钱却没看到成功页面)
- 用户点返回键,又付了一次钱,但两次都跳转到同一个 success 页面
**第 3 步:Stripe 发送 Webhook**
这是 Stripe 主动通知你的服务器"这笔款到账了":
- 只有 Stripe 的服务器能发起这个请求
- 请求里带有签名,你的后端可以验证是不是真的 Stripe 发的
- 即使 success 页面没打开、用户断网了,Webhook 也会发送
**第 4 步:后端校验签名**
为什么要校验?防止黑客伪造通知。
假设没有校验,黑客可以直接给你的服务器发一个假通知:"用户 A 付了 1000 元"。你的系统就会给黑客开通会员。
校验的过程:
- Stripe 用你们约定的密钥对通知内容生成签名
- 你的后端用同样的密钥验证签名是否匹配
- 匹配 = 100% 是 Stripe 发的,不匹配 = 直接拒绝
**第 5 步:更新数据库**
只有校验通过后,才更新数据库:
- 把用户状态从"待付款"改成"已付费"
- 记录订单号、金额、付款时间
- 开通对应的会员权限
**第 6 步:前端查询状态**
success 页面不要自己判断"到了这页就是成功了"。正确的做法:
- 页面加载时,向后端发送请求:"这个用户付费了吗?"
- 后端查数据库,返回真实状态
- 根据返回结果显示"开通成功"或"等待确认"
### 一个常见的错误做法
```javascript
// 错误:在 success 页面直接开通
// success.html
if (window.location.pathname === '/success') {
// 危险!任何人都能访问 /success
activateMembership();
}
```
```javascript
// 正确:每次刷新都查后端
// success.html
async function checkStatus() {
const response = await fetch('/api/user/status');
const data = await response.json();
if (data.paymentStatus === 'paid') {
showMemberFeatures();
} else {
showPendingMessage();
}
}
```
### 总结一句话
**success 页面只是"浏览器跳转成功"Webhook 才是"Stripe 正式确认收款"。**
你的系统必须以 Webhook 为准,不能相信前端的跳转。
## 附录 C:订阅系统最值得监听的事件
| 事件 | 含义 | 你通常要做什么 |
| :--- | :--- | :--- |
| `checkout.session.completed` | 首次开通成功 | 创建本地订阅记录 |
| `invoice.paid` | 自动续费成功 | 延长有效期 |
| `invoice.payment_failed` | 自动扣费失败 | 标记风险状态并提醒用户 |
| `customer.subscription.deleted` | 订阅取消 | 回收权限或标记到期后失效 |
### 订阅状态图
```mermaid
stateDiagram-v2
[*] --> NotStarted: 用户未购买
NotStarted --> Active: checkout.session.completed
Active --> Active: invoice.paid
Active --> PastDue: invoice.payment_failed
PastDue --> Active: 用户补款成功
Active --> Canceled: customer.subscription.deleted
PastDue --> Canceled: 到期未恢复
Canceled --> [*]
state "未开通" as NotStarted
state "会员有效" as Active
state "扣费失败 / 待恢复" as PastDue
state "已取消 / 到期回收" as Canceled
```
### 续费 / 失败 / 取消时序图
```mermaid
sequenceDiagram
autonumber
participant Stripe as Stripe
participant Webhook as 你的 Webhook 接口
participant DB as 订阅表 / 订单表
participant App as 你的应用
actor User as 用户
rect rgb(235, 248, 255)
Stripe->>Webhook: invoice.paid
Webhook->>DB: 延长 current_period_end
DB-->>Webhook: 更新成功
Webhook-->>Stripe: 200 OK
App-->>User: 继续保持会员有效
end
rect rgb(255, 247, 237)
Stripe->>Webhook: invoice.payment_failed
Webhook->>DB: 标记 past_due
DB-->>Webhook: 更新成功
Webhook-->>Stripe: 200 OK
App-->>User: 提醒更新支付方式
end
rect rgb(254, 242, 242)
Stripe->>Webhook: customer.subscription.deleted
Webhook->>DB: 标记 canceled
DB-->>Webhook: 更新成功
Webhook-->>Stripe: 200 OK
App-->>User: 停止高级权限
end
```
## 附录 D:其他支付方案怎么选
### 1. 中国大陆
主要用户在大陆的话,首选还是 **[支付宝](https://open.alipay.com/)** 和 **[微信支付](https://pay.wechatpay.cn/)**。
**业务模式:**
两者都是"支付网关"模式。你需要:
- 申请商户资质(营业执照、对公账户)
- 用户付的钱直接到你的商户账户
- 你自己负责税务、退款、对账
**技术模式:**
两者都是"后端下单 + 前端调起 + 后端通知"的模型,跟 Stripe 思路一样。
**支付宝接入流程:**
1. 在支付宝开放平台创建应用
2. 配置公私钥和回调地址
3. 后端调用统一下单接口,生成支付链接或二维码
4. 用户扫码或跳转付款
5. 支付宝异步通知你的后端,更新订单状态
**微信支付接入流程:**
- JSAPI 支付:适合公众号、小程序,用户在微信内直接付款
- Native 支付:PC 端生成二维码,用户扫码付款
- H5 支付:手机浏览器内拉起微信 App 付款
流程:后端下单 → 拿到 `prepay_id``code_url` → 前端调起支付 → 后端接收通知确认成功
**参考链接:**
- 支付宝开放平台:https://open.alipay.com/
- 微信支付商户文档:https://pay.wechatpay.cn/doc/v3/merchant/
### 2. 香港
香港市场比较混合,常见组合:
- 银行卡:Visa / Mastercard
- FPS(转数快):香港本地即时转账
- AlipayHK / WeChat Pay HK:香港版支付宝和微信
**推荐组合:**
- 用 **[Stripe](https://stripe.com/hk)** 覆盖国际卡和订阅
- 用 **[Airwallex](https://www.airwallex.com/)** 或 **[Adyen](https://www.adyen.com/)** 补本地钱包和 FPS
### 3. 海外 / 国际 SaaS
#### [Stripe](https://stripe.com/)
**业务模式:** 支付网关
- 你需要自己申请商户资质(部分国家 Stripe 可以帮你搞定)
- 用户付的钱到你的 Stripe 账户,再结算到你的银行账户
- 你自己负责税务申报
**技术模式:**
- API 体验最好,文档清晰
- 支持 Checkout(托管页面)、Elements(自定义表单)、Payment Links(无代码)
- Webhook 通知支付状态
- 支持订阅、发票、多币种
**适合谁:** 海外 SaaS、独立开发者、需要灵活定制的团队
**参考链接:** https://docs.stripe.com/
#### [PayPal](https://www.paypal.com/)
**业务模式:** 支付网关
- 用户付的钱到你的 PayPal 账户,再提现到银行
- 你自己负责税务
**技术模式:**
- 一次性支付:前端放按钮,后端创建/确认订单
- 订阅制:先建 Product 和 Plan,再用 SDK 拉起
- 同样需要后端和 Webhook,不要只看前端回调
**适合谁:** 需要补充渠道的海外业务,用户习惯用 PayPal 付款
**参考链接:** https://developer.paypal.com/docs/
#### [Paddle](https://www.paddle.com/)
**业务模式:** Merchant of Record (MoR)
- Paddle 是"记录商家",法律上由 Paddle 向用户收款
- Paddle 帮你处理全球税务、VAT、退款、合规
- 用户付的钱到 Paddle,Paddle 扣除税费和手续费后结算给你
- 你不需要在每个国家注册公司或处理税务
**技术模式:**
- Paddle.js:前端嵌入托管结账页
- 后端 API:创建 transaction,交给 checkout 处理
- Webhook 同步订阅状态
**适合谁:** 不想处理全球税务的 SaaS 团队,尤其是 B2B SaaS
**参考链接:** https://developer.paddle.com/
#### [Lemon Squeezy](https://www.lemonsqueezy.com/)
**业务模式:** Merchant of Record (MoR)
- 和 Paddle 类似,Lemon Squeezy 是"记录商家"
- 帮你处理全球税务、VAT、合规
- 2024 年被 Stripe 收购,但独立运营
**技术模式:**
- Hosted Checkout:最简单,直接生成付款链接
- Checkout Overlay:浮层嵌入你的页面
- 后端 API:创建 checkout,灵活控制
**适合谁:** 独立开发者、数字产品、软件授权
**参考链接:** https://docs.lemonsqueezy.com/
### 4. 企业级方案
#### [Airwallex(空中云汇)](https://www.airwallex.com/)
**业务模式:** 支付网关 + 全球账户
- 提供全球收款账户(类似虚拟银行账户)
- 支持多币种收款、换汇、付款
- 你自己负责税务
**技术模式:**
- Payment Links:几乎不用代码,生成付款链接
- Hosted Payment Page:托管页面
- Drop-in / Embedded / Native API:深度接入,自定义程度高
- 支持 Alipay HK、FPS、WeChat Pay 等本地支付方式
**适合谁:** 香港团队、跨境业务、需要多币种账户的公司
**参考链接:** https://www.airwallex.com/docs/
#### [Adyen](https://www.adyen.com/)
**业务模式:** 支付网关
- 企业级支付平台,年处理交易额万亿欧元
- 支持线上、线下、移动端全渠道
- 你自己负责税务
**技术模式:**
- Pay by Link:最简单,生成付款链接
- Drop-in / Components:标准线上接入
- 后台可启用 Alipay、Alipay HK、PayMe 等本地支付方式
**适合谁:** 大型企业、需要全渠道支付的公司
**参考链接:** https://docs.adyen.com/
### 5. 方案对比
| 方案 | 业务模式 | 税务处理 | 适合谁 |
| :--- | :--- | :--- | :--- |
| Stripe | 支付网关 | 自己处理 | 海外 SaaS、开发者 |
| PayPal | 支付网关 | 自己处理 | 海外补充渠道 |
| Paddle | MoR | Paddle 代处理 | B2B SaaS、不想管税务 |
| Lemon Squeezy | MoR | LS 代处理 | 独立开发者、数字产品 |
| Adyen | 支付网关 | 自己处理 | 大型企业 |
| Airwallex | 支付网关 + 账户 | 自己处理 | 跨境业务、香港团队 |
| 支付宝/微信 | 支付网关 | 自己处理 | 大陆用户 |
### 6. 按地区选方案
| 你的市场 | 推荐方案 |
| :--- | :--- |
| 中国大陆 | 支付宝 / 微信支付 |
| 香港 | Stripe + Airwallex / Adyen |
| 海外 SaaS | Stripe(自己管税务)或 PaddleMoR 代管) |
| 海外数字产品 | Stripe / Lemon Squeezy / Paddle |
| 多地区企业级 | Adyen / Airwallex / Stripe 组合 |
@@ -1,3 +1,425 @@
# 参考 UI 设计规范与多产品 UI 设计 # 参考 UI 设计规范设计页面和按钮
> 本章节正在编写中,敬请期待... 很多人说"我想让页面更像 Apple 一点""按钮想做得更高级一点",但真正开始做时,往往会卡在一个问题上:
**到底该参考什么?**
盯着截图模仿,学到的只是"像不像"。但打开 Apple、Google、Microsoft、Atlassian 的设计规范,你会发现它们真正厉害的地方不是视觉风格,而是**把设计问题讲清楚**:页面先突出什么、按钮如何分级、操作怎么强调——这些判断标准才是核心。
> 参考设计规范,不是为了做得"像谁",而是学会别人怎么做判断。
:::: info 为什么现在还要学这些
设计规则早已被训练进模型、被设计工具默认吸收,甚至贴几张截图 AI 就能学会。但我们仍然有必要知道这些规则从哪来、为什么这样定。
::::
## 先看几段原文,感受差距
如果你以前觉得“设计规范不就是讲讲风格吗”,先看几条官方原文。
平时我们在团队里经常会这样说:
- 做个下拉框
- 这里放个菜单
- 菜单栏加几个功能
- 这里放两个按钮,一个确认一个取消
听起来没问题,但在大厂规范里,这些词都不是模糊概念,而是被拆得非常细。
| 平时随口说的话 | 官方原文 | 简单说 |
| :--- | :--- | :--- |
| “做个菜单” | Apple: [“A menu reveals its options...”](https://developer.apple.com/design/human-interface-guidelines/menus) | `Menu` 是拿来做操作的 |
| “菜单栏里放功能” | Apple: [“menu bar menus contain all the commands...”](https://developer.apple.com/design/human-interface-guidelines/menus) | 这是应用顶部的命令菜单 |
| “做个下拉框” | Apple: [“A pop-up list lets the user choose one option among several.”](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MenuList/Articles/ManagingPopUpItems.html) | `pop-up` 是从列表里选一个 |
| “也做个下拉框” | Apple: [“A pull-down list is generally used for selecting commands in a specific context.”](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MenuList/Articles/ManagingPopUpItems.html) | `pull-down` 是点开做当前操作 |
| “菜单也能拿来筛选吧” | Fluent: [“If you need to collect information from people, try a select, dropdown, or combobox instead.”](https://fluent2.microsoft.design/components/web/react/core/menu/usage) | `Menu` 不是拿来选值的 |
| “菜单也能当导航吧” | Material: [“Menus should not be used as a primary method for navigation within an app.”](https://m1.material.io/components/menus.html) | `Menu` 不是主导航 |
| “按钮随便写个 OK / Cancel” | Apple: [“Always use Cancel to title a button that cancels the alerts action.”](https://developer.apple.com/design/human-interface-guidelines/alerts) | 按钮文字不能随便写 |
> 表格里的引文都可以直接点击,跳到对应的官方页面。
这就是第一次真正看设计规范时最容易被震到的地方:
> 我们平时以为自己在讨论 UI,实际上很多时候只是在用一堆含糊词交流。
Apple 不会只说“做个菜单”;它会继续区分:
- `menu`
- `menu bar menu`
- `pop-up button`
- `pull-down button`
- `context menu`
Fluent 不会只说“下拉框”;它会继续区分:
- `menu`
- `dropdown`
- `select`
- `combobox`
这就是设计规范的必要性。
它不是为了让页面显得更专业,而是为了让团队在讨论 UI 时,不再每个人脑子里都是不同的东西。
## 你将学到
1. 为什么设计页面和按钮时要先看设计规范
2. Apple、Material、Fluent、Atlassian 这些规范里,哪些内容最值得参考
3. 如何把“页面层级”和“按钮层级”设计清楚
4. 如何让 AI 参考别人的规范来生成页面和按钮
## 1. 设计规范为什么能帮你把页面做清楚
看完上面这些原文,你会发现一个关键点:
**设计规范不是锦上添花,而是在先把词说准。**
很多页面不好看,不是因为配色不够高级,而是因为信息层级混乱。
很多按钮不好用,也不是因为圆角不对,而是因为:
- 主按钮太多,用户不知道该点哪个
- 危险按钮和普通按钮看起来差不多
- 页面里所有按钮都在抢注意力
- 不同页面里的按钮样式和语义不一致
成熟的设计规范,恰好就是在解决这些问题。它们通常会定义:
| 规范内容 | 它解决什么问题 |
| :--- | :--- |
| **页面层级** | 先看哪里、后看哪里,信息怎么组织 |
| **视觉基础** | 颜色、间距、字体、圆角、阴影怎样统一 |
| **按钮层级** | 主按钮、次按钮、文字按钮、危险按钮如何区分 |
| **状态规则** | hover、focus、disabled、loading 怎么表现 |
| **交互语义** | 哪个按钮是“确认”,哪个是“取消”,哪个是“更多操作” |
所以,设计规范真正提供的不是一套“皮肤”,而是一套**判断标准**。
## 2. 参考大厂规范时,重点看什么
### 2.1 参考 Apple:学习“定义得足够细”这件事
Apple 最值得学的,不只是视觉上的克制感,而是它会把概念定义得非常细。
同样是很多团队口中的“菜单”或“下拉框”,Apple 会继续往下拆:
- `menu`:一组命令、选项或状态
- `menu bar menu`:应用级命令集合
- `pop-up button`:选择一个值
- `pull-down button`:在当前上下文里触发命令
- `context menu`:与当前对象或任务相关的常用动作
这套区分非常重要,因为它会直接影响:
- 这个组件是拿来选值,还是拿来做动作
- 它属于页面局部,还是属于应用级
- 它应该长期显示当前选中值,还是只临时展开命令
当你开始按这种粒度思考时,你设计出来的页面就会一下子清楚很多。
### 2.2 参考 Apple:学习页面层级和克制感
Apple Human Interface Guidelines 特别适合学习两件事:
- 页面如何建立清晰层级
- 控件如何在不喧宾夺主的前提下保持明确
Apple 强调 `Hierarchy``Harmony``Consistency`。这意味着页面设计时要回答:
- 当前页面最重要的信息是什么
- 用户的主要任务是什么
- 哪个操作该最显眼,哪个操作应该退后
如果你参考 Apple 来设计页面,可以重点借鉴:
- 首屏信息不要太碎,核心内容先聚焦
- 用留白、字号、分组建立秩序,而不是靠堆很多边框
- 按钮不要全部高强调,只有关键动作才应该最突出
### 2.3 参考 Material:学习清晰的页面结构
Material Design 很适合学习“页面是怎么组织任务流”的。
它的很多组件和布局规范,核心都在帮助你明确:
- 页面是浏览型,还是执行任务型
- 当前页面是让用户阅读、选择,还是提交
- 一个页面里哪些元素应该稳定重复,哪些元素应该响应上下文变化
如果你参考 Material 来设计页面,可以重点借鉴:
- 页面区块清楚,模块职责明确
- 导航、内容区、操作区分工清晰
- 不同按钮样式对应不同操作优先级
### 2.4 参考 Fluent:学习组件边界和按钮层级
Fluent 2 很适合后台、工具型产品和复杂表单系统。它最值得学的地方,是会直接告诉你“不要混用概念”。
例如它明确写到:如果你要“collect information”,就不要继续用 `menu`,而应该考虑 `select``dropdown``combobox`
这句话非常重要,因为它把很多人脑中的“都差不多”打碎了。
Fluent 2 也很重视:
- 操作层级
- 组件语义边界
- 密集信息场景下的清晰度
如果你参考 Fluent 来设计按钮,可以重点借鉴:
- `Primary button` 用来承接当前最重要的动作
- `Secondary button` 用来承接支持性动作
- `Subtle``Transparent` 这类弱强调按钮用于不该抢主流程的操作
- 页面里的按钮数量越多,越要控制视觉优先级
### 2.5 参考 Atlassian:学习系统化地管理页面和按钮
Atlassian Design System 特别适合“一个团队做很多页面”的情况。它强调:
- foundations 是共享基础
- tokens 是统一视觉决策的方法
- components 是被反复复用的交互构件
如果你参考 Atlassian 来做页面和按钮,最有价值的是:
- 把按钮尺寸、颜色、圆角、间距做成统一规则
- 把页面布局的节奏固定下来
- 让不同页面虽然内容不同,但结构语言一致
## 3. 设计页面时,应该参考规范里的哪些点
当你看一个设计系统时,不要先问“这个页面好不好看”,而要先问下面几个问题。
### 3.1 页面第一眼,主次是不是明确
一个页面通常至少要有三层:
- **主信息**:当前页面最重要的内容
- **辅助信息**:帮助理解或补充的内容
- **次级操作**:不应该干扰主任务的动作
如果三层没有拉开,页面就会“都重要”,等于“都不重要”。
### 3.2 页面布局,是不是服务任务而不是堆模块
参考规范时,可以特别注意:
- 标题区有没有明确页面目标
- 主内容区是不是围绕任务组织
- 操作按钮是不是贴近相关内容
- 次要信息有没有被弱化
### 3.3 页面里的操作,是不是有优先级
很多页面一眼看过去有 6 个按钮,结果每个按钮都像 CTA,这是典型的层级失控。
更合理的方式是:
- 一个区域通常只有一个主动作
- 次级动作可以用描边、文字按钮或更弱的样式
- 风险动作不要和主动作长得一样
## 4. 设计按钮时,应该参考规范里的哪些点
按钮是最容易被“随手设计”的部分,但也是最能暴露系统是否成熟的部分。
### 4.1 按钮先分“语义”,再分“样式”
不要先想“蓝色按钮还是黑色按钮”,先想这个按钮是什么角色。
常见按钮角色可以这样分:
| 按钮类型 | 作用 | 常见样式策略 |
| :--- | :--- | :--- |
| **Primary** | 当前区域最关键动作 | 实心、高对比、最显眼 |
| **Secondary** | 支持性动作 | 描边或低一级强调 |
| **Tertiary / Text** | 弱操作 | 文字或低视觉占比 |
| **Destructive** | 删除、停用、清空等风险操作 | 警示色或明确风险样式 |
| **Icon button** | 局部工具操作 | 简洁、靠近上下文 |
### 4.2 一个页面不要有太多 Primary Button
这是很多新手最容易踩的坑。
如果页面上有 4 个主按钮,那么等于没有主按钮。主按钮的意义本来就是“告诉用户现在最应该做什么”。
你可以借鉴很多设计系统的共同做法:
- 一个主要区域通常只保留一个主按钮
- 取消、返回、关闭一般不和确认按钮抢同级
- 更多操作放到次级按钮或菜单中
### 4.3 按钮要能表达状态变化
设计规范通常会对按钮状态写得很清楚:
- 默认态
- 悬停态
- 聚焦态
- 禁用态
- 加载态
- 危险态
这很重要,因为按钮不是一张静态图,而是用户操作过程中最常被触发的控件之一。
### 4.4 按钮文案,也属于设计的一部分
按钮文案不只是“文案问题”,它直接影响用户理解。
例如:
- `保存`
- `保存更改`
- `立即发布`
- `删除项目`
- `移到回收站`
这些文案传达的心理预期完全不同。成熟规范通常会要求按钮标签清楚表达动作,而不是使用含糊词。
## 5. 一个很实用的页面与按钮设计清单
你自己设计页面时,可以先快速过一遍这张清单:
### 页面清单
- 页面标题是否清楚说明当前任务
- 首屏最重要的信息是否一眼可见
- 页面是不是按任务流程组织,而不是按想到什么放什么
- 同一个区域里是否只有一个主要动作
- 次要内容是否被适当弱化
### 按钮清单
- 这个按钮是主动作还是次动作
- 它为什么值得比别的按钮更显眼
- 页面里是不是有太多主按钮
- 危险操作是否被明确标识
- 按钮文案是否足够具体
## 6. 怎样用 AI 参考别人的规范来设计页面
这一节最实用。
很多人让 AI 设计页面时,只会说:
```md
帮我做一个设置页面,要高级一点,参考苹果风格
```
这类提示词太模糊了,AI 最后通常只能模仿“白底、圆角、阴影”。
对新手来说,更实用的方式不是自己总结一大段,而是直接把**规范原文里的关键句**贴给 AI。
这样做有两个好处:
- 你不用自己先“翻译”一遍设计思想
- AI 更容易按官方定义去理解页面和按钮
### 6.1 例子一:让 AI 参考 Apple 设计一个设置页面
先找一句 Apple 原文:
> ["Establish a clear visual hierarchy..."](https://developer.apple.com/design/human-interface-guidelines/)
你可以直接这样贴给 AI
```md
参考 Apple Human Interface Guidelines 里的这句话:
"Establish a clear visual hierarchy..."
帮我设计一个账号安全设置页面。
要求页面层级清楚,重要信息放前面,分组整齐一点。
```
这样写的重点是:不用你自己解释太多,直接把 Apple 的原话贴进去。
### 6.2 例子二:让 AI 参考 Fluent 设计后台页面按钮
先找一句 Fluent 原文:
> ["Only use one primary button in a layout..."](https://fluent2.microsoft.design/components/web/react/core/button/usage)
你可以直接这样贴给 AI
```md
参考 Fluent 2 里的这句话:
"Only use one primary button in a layout..."
帮我设计一个团队管理后台的按钮。
添加成员按钮最明显,导出、筛选、更多操作弱一点,删除按钮单独突出。
```
这一句非常适合新手,因为它直接告诉 AI:一个区域不要放太多主按钮。
### 6.3 例子三:让 AI 同时参考页面规范和按钮规范
你也可以一次贴两句原文,让 AI 同时参考页面和按钮:
> Apple: ["Establish a clear visual hierarchy..."](https://developer.apple.com/design/human-interface-guidelines/)
>
> Fluent: ["Only use one primary button in a layout..."](https://fluent2.microsoft.design/components/web/react/core/button/usage)
然后直接这样写:
```md
参考下面两句设计规范原文:
Apple: "Establish a clear visual hierarchy..."
Fluent: "Only use one primary button in a layout..."
帮我设计一个项目详情页。
页面包含项目介绍、成员、最近活动和设置入口。
页面层级清楚一点,主按钮只保留一个,其他按钮弱一点。
```
这种方式特别适合新手,因为你只要会复制原文,再加两句自己的需求就够了。
## 7. 怎样用 AI 参考按钮规范来直接生成按钮设计
如果你只想先做按钮,也可以直接贴按钮规范原文。
例如 Atlassian 对按钮的定义很短:
> ["A button triggers an event or action."](https://atlassian.design/components/button/)
你可以这样问 AI
```md
参考 Atlassian 的这句话:
"A button triggers an event or action."
帮我设计一套后台页面按钮样式。
我要有主按钮、次按钮、删除按钮,顺便告诉我分别用在什么地方。
```
这类提示词尤其适合新手,基本就是“贴原文 + 说需求”。
## 8. 小结
参考 UI 设计规范设计页面和按钮,最重要的不是“做得像谁”,而是学会下面这几件事:
1. 用层级组织页面,而不是把内容堆上去
2. 用按钮分级表达操作优先级,而不是让所有按钮都一样抢眼
3. 用设计规范里的定义、边界和判断标准指导设计
4. 让 AI 参考别人规范时,参考的是“原则和结构”,而不是只参考皮肤
当你这样使用规范时,你参考到的就不只是一个风格,而是一套成熟的设计思考方式。
---
## 参考资料
以下链接都来自官方设计系统或官方文档:
- Apple Human Interface Guidelines: [Overview](https://developer.apple.com/design/human-interface-guidelines/)
- Apple Human Interface Guidelines: [Menus](https://developer.apple.com/design/human-interface-guidelines/menus)
- Apple Human Interface Guidelines: [Alerts](https://developer.apple.com/design/human-interface-guidelines/alerts)
- Apple Human Interface Guidelines: [Buttons](https://developer.apple.com/design/human-interface-guidelines/buttons)
- Apple Archive: [How Menus Work](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MenuList/Articles/HowMenusWork.html)
- Apple Archive: [Managing Pop-Up Buttons and Pull-Down Lists](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MenuList/Articles/ManagingPopUpItems.html)
- Material Design: [Buttons overview](https://m3.material.io/components/buttons/overview)
- Material Design: [Menus](https://m1.material.io/components/menus.html)
- Microsoft Fluent 2: [Start designing](https://fluent2.microsoft.design/get-started/design)
- Microsoft Fluent 2: [Menu usage](https://fluent2.microsoft.design/components/web/react/core/menu/usage)
- Microsoft Fluent 2: [Button usage](https://fluent2.microsoft.design/components/web/react/core/button/usage)
- Atlassian Design System: [Foundations](https://atlassian.design/foundations/)
- Atlassian Design System: [Button](https://atlassian.design/components/button/)
+2 -2
View File
@@ -26,8 +26,8 @@
/> />
<NavCard <NavCard
href="/zh-cn/stage-2/frontend/2.3-multi-product-ui/" href="/zh-cn/stage-2/frontend/2.3-multi-product-ui/"
title="参考 UI 设计规范与多产品 UI 设计" title="参考 UI 设计规范设计页面和按钮"
description="学习主流 UI 设计规范,提升产品设计的一致性与美感" description="学习主流 UI 设计规范,设计更清晰的页面层级与按钮层级"
/> />
<NavCard <NavCard
href="/zh-cn/stage-2/frontend/2.4-llm-skills-beautiful/" href="/zh-cn/stage-2/frontend/2.4-llm-skills-beautiful/"
@@ -1,3 +1,600 @@
# 多平台开发 - 如何构建微信小程序(包含后端) # 如何构建一个带后端的微信小程序
> 本章节正在编写中,敬请期待... 在上一节里,我们做的是一个“前端就能跑起来”的小程序。但只要你的产品开始接近真实业务,很快就会遇到这样几类需求:
- 用户登录后,需要识别“这是谁”
- 数据不能只存在本地,而要能跨设备同步
- 图片、音频、文档需要上传到云端
- 订单、支付、会员、积分这些逻辑不能放在前端裸奔
- 你希望接入 AI、数据库、管理后台、定时任务、消息通知
这时候,你做的就不再只是一个“小程序页面”,而是一个完整的小程序产品。它需要前端,也需要后端。
截至 **2026 年 3 月 25 日**,如果你的目标是“尽快做出真实可上线的小程序,并且尽量少踩基础设施的坑”,最推荐的路线不是一上来就自己买服务器、配 Nginx、写一堆鉴权中间件,而是:
::: tip 推荐路线
**优先选择:微信小程序 + 微信云开发 / CloudBase**
也就是用:
- 小程序前端负责界面和交互
- 云函数或云托管负责后端逻辑
- 云数据库负责数据存储
- 云存储负责文件
- 安全规则、内容审核、日志等能力作为上线标配
:::
原因很简单:这条路线和微信生态贴得最近,登录态传递、用户身份识别、文件上传、数据库访问、服务端函数调用都更顺手,特别适合新手、独立开发者、MVP、内容产品、工具类产品,以及你现在这种 **“用 AI 快速把产品做出来”** 的场景。
当然,这并不意味着“自建后端”没价值。真正的最佳实践,不是所有项目都用同一种方案,而是 **先选最省心的默认方案,再在必要时升级到更重的架构**
# 1. 什么叫“小程序带后端”
最简单的理解是:
- **前端**:跑在微信里的界面,负责展示页面、接收点击、发起请求
- **后端**:跑在服务器或云端,负责身份校验、数据库读写、支付签名、业务规则、第三方 API 调用
一个成熟的小程序,通常会把职责分成三层:
1. **小程序前端层**
负责页面 UI、表单输入、列表展示、加载状态、错误提示、用户操作。
2. **业务逻辑层**
负责“真正重要”的事情,例如:创建订单、检查权限、扣减库存、生成支付参数、调用大模型、审核内容、写操作日志。
3. **数据与资源层**
负责存储用户数据、文章内容、订单信息、上传文件、图片资源、审核结果等。
这三层不要混在一起。尤其是第二层,绝对不能为了省事直接塞到前端里。
## 1.1 哪些事情必须放到后端
下面这些能力,原则上都应该放在服务端,而不是直接写在小程序前端:
- `AppSecret`、支付密钥、商户私钥、第三方平台密钥
- 登录态换取、用户身份绑定、管理员权限判断
- 支付下单、签名生成、支付回调验签
- 数据库的写权限控制
- 价格、库存、积分、优惠券等业务规则
- 内容审核、风控、限流、反刷
- 定时任务、批处理、异步任务
只要一条逻辑涉及“密钥、权限、金额、不可篡改的业务规则”,就不要把它放在前端。
# 2. 最推荐的架构是什么
对大部分第一次做“小程序 + 后端”的项目,我推荐你用下面这个架构:
```mermaid
flowchart LR
miniapp["微信小程序页面"]
invoke["wx.cloud.callFunction<br/>/ wx.cloud.callContainer"]
backend["云函数 / 云托管服务"]
infra["云数据库 / 云存储 / 第三方 API / 微信支付"]
miniapp --> invoke
invoke --> backend
backend --> infra
```
这条路线背后的核心思路是:
- **前端尽量薄**
只做 UI、参数收集、结果展示,不直接碰密钥和关键业务规则。
- **后端尽量集中**
让鉴权、支付、数据写入、权限控制都从服务端入口走。
- **数据权限默认收紧**
先默认拒绝,再按角色和场景放开。
- **先用托管能力跑通闭环**
先把产品做出来,再考虑是否拆分成更复杂的微服务。
## 2.1 三种可选路线
### 路线 A:云开发(默认推荐)
最适合:
- 新手第一次做带后端的小程序
- 工具类、内容类、社区类、表单类、轻电商类产品
- 想快速做 MVP、验证需求、快速上线
- 希望和微信登录态、云函数、云数据库配合得更顺
典型能力组合:
- `wx.cloud.callFunction`
- 云函数
- 云数据库
- 云存储
- 内容安全审核
- 定时任务
这是我最推荐你先学、先用、先跑通的一条线。
### 路线 B:云托管 / HTTP 服务(推荐给中等复杂度项目)
最适合:
- 你已经有现成的 Node.js / NestJS / Express / Python / Go 服务
- 需要标准 HTTP API、复杂路由、更多中间件
- 需要更灵活的容器化部署
- 需要对接更多第三方服务,或者你未来还想服务 H5、管理后台、App
这条路线依然可以放在微信云开发体系里,但服务形态更像“真正的后端服务”,而不只是函数。
### 路线 C:完全自建后端
最适合:
- 你有成熟的后端团队
- 需要更强的私有化、专有网络、合规隔离
- 已经有统一网关、统一鉴权、统一运维平台
对于本教程面向的大多数读者来说,这通常不是第一步,而是第二阶段甚至第三阶段的事情。
## 2.2 最佳实践结论
如果你现在问我一句最短答案:
::: tip 最短答案
**先用云开发把登录、数据、上传、审核、基础业务跑通;**
**当你需要标准 HTTP 服务、复杂中间件或更强扩展性时,再升级到云托管;**
**只有在明确有组织级后端要求时,才考虑完全自建。**
:::
# 3. 为什么云开发是最好的起步方案
这不是因为“它最炫”,而是因为它在微信生态下的工程摩擦最低。
## 3.1 身份传递更自然
CloudBase 官方文档明确提到,小程序端调用云函数时,SDK 会自动携带当前用户身份,服务端可以结合上下文识别调用者;而在云函数里也可以通过 `getWXContext()` 获取当前调用的小程序用户信息。
这意味着你不用从第一天开始自己折腾一整套 token 分发系统,就能先跑通“谁在调用这个接口”。
## 3.2 数据、文件、函数是同一套体系
如果你的产品里有这些需求:
- 用户上传头像
- 发帖子、评论、收藏
- 生成 AI 内容
- 记录订单或表单
- 后台查日志
那么云数据库、云存储、云函数直接配套,开发路径会非常短。
## 3.3 更适合 AI 协作开发
你用 Trae 或其他 AI 编程工具时,越“标准化”的工程结构,AI 越容易理解和修改。
相比“前端请求一个你自己七拼八凑的服务器”,下面这种结构对 AI 更友好:
```text
miniprogram/
cloudfunctions/
database collections/
cloud storage/
```
因为职责清楚、目录简单、边界明确,AI 更容易帮你一次性生成能运行的版本。
# 4. 一个真正可上线的最小架构
如果你要做一个真实的小程序,我建议最少包含下面这些模块:
```text
小程序前端
├── pages/ 页面
├── components/ 组件
├── services/ 前端调用封装
└── app.js 云环境初始化
云函数 / 云托管
├── auth 登录态和身份补充信息
├── user 用户资料读写
├── content 内容 CRUD
├── order 订单创建与状态流转
├── payment 支付下单与回调处理
└── audit 内容审核、风控、限流
数据层
├── users 用户表
├── posts 内容表
├── orders 订单表
├── files 文件记录
└── audit_logs 审计日志
```
## 4.1 小程序前端只做三件事
前端最好只负责:
1. 收集参数
2. 调服务端接口
3. 展示结果
例如:
- 点击“发布”时,把标题、正文、图片 ID 发给后端
- 点击“支付”时,请后端返回支付参数
- 点击“生成文案”时,请后端去调用大模型
不要让前端直接决定价格、库存、积分、管理员身份。
## 4.2 后端负责“真实业务”
后端应该统一处理:
- 当前用户是谁
- 有没有权限
- 数据是否合法
- 这次写入是否需要事务或幂等
- 是否要记录操作日志
- 是否要调用审核、支付、消息通知
一句话概括:**前端是入口,后端是裁判。**
# 5. 用云开发快速落地的标准步骤
下面给你一条最务实的 SOP。你完全可以照着这条路,用 AI 在几个小时内搭出第一版。
## 5.1 第一步:初始化云环境
如果你是零基础,不要想着“我要怎么写初始化代码”。你现在只需要会对 AI 说一句人话。
```text
请帮我把这个微信小程序项目接上云开发,并直接改好项目文件。改完以后,请用最简单的话告诉我:我下一步去哪里填云环境 ID,以及我怎么判断这一步已经成功。
```
如果 AI 第一次没理解,你就补一句:
```text
我是零基础,请不要讲太多代码,直接帮我改好,并告诉我下一步点哪里。
```
这一步你真正要理解的,不是几行代码,而是三件事:
- 这个项目已经“接上云了”
- 后面的小程序页面可以开始调用云函数
- 你要尽早把开发环境、测试环境、生产环境分开,不要一套环境用到底
你做完这一步后,理想状态应该是:
- 项目还能正常启动
- 控制台没有明显的云开发初始化报错
- 后面可以继续往项目里加云函数和数据库能力
## 5.2 第二步:先写一个最简单的云函数
第二步也一样。你不需要先理解“云函数文件放在哪、怎么 export、怎么返回结果”,你只需要先把最小闭环跑通。
```text
请帮我做一个最简单的“前端调用后端”示例:让小程序页面可以调用一个云函数,并看到返回结果。请你直接修改真实项目文件,改完以后告诉我:我应该点哪里测试,以及成功时会看到什么。
```
如果你想让 AI 更具体一点,可以再补一句:
```text
这个示例可以用一个叫 `getCurrentUser` 的云函数来做,越简单越好。
```
为什么这一步特别重要?
因为它不是在教你背云函数语法,而是在帮你拿到第一个真正的“前端 -> 后端 -> 返回结果”的闭环。一旦这个闭环跑通,后面再加数据库、上传文件、内容审核、支付,都会顺很多。
如果你是第一次做,建议把成功标准定得非常简单:
- 云函数已经创建成功
- 前端能正常发起调用
- 你能在调试结果里看到一份返回数据
做到这三点,这一步就算过关了。
## 5.3 第三步:把数据库写操作收回后端
很多新手一开始会想:“既然能直接访问数据库,我是不是前端直接写就行?”
不建议。
最佳实践是:
- 前端读操作可以根据业务适度开放
- 关键写操作尽量通过云函数或云托管服务统一处理
例如:
- 发布内容
- 删除内容
- 修改价格
- 创建订单
- 发放权益
都应该从服务端入口走。
如果你想让 AI 直接帮你往这个方向改,可以说:
```text
请帮我检查这个小程序项目里哪些数据库写操作不应该放在前端。如果有不合适的地方,请改成通过云函数处理,并用最简单的话告诉我为什么要这样改。
```
## 5.4 第四步:给数据库和函数加安全规则
CloudBase 官方提供了数据库安全规则和云函数安全规则。这一步一定不要省。
新手最容易犯的错误是:为了图省事,把权限直接开成“所有人可读写”。这样虽然调试很爽,但上线后风险极高。
更好的做法是:
- 默认拒绝
- 只允许登录用户读自己的数据
- 管理员写操作单独校验
- 涉及订单、支付、积分的数据只允许服务端改
如果你未来做的是社区、表单、课程、会员、电商,这一步几乎决定了项目能不能安全上线。
如果你不确定怎么开权限,就直接对 AI 说:
```text
请帮我给这个小程序项目补一套最保守的安全规则。默认尽量收紧,只保留最基本的可用权限。改完以后,请告诉我哪些数据只能后端改,哪些数据可以前端读。
```
## 5.5 第五步:文件上传统一走云存储
图片、音频、PDF、头像、海报,尽量都不要传到乱七八糟的外部图床。
更稳妥的方式是:
1. 前端上传到云存储
2. 后端记录文件元数据
3. 业务表只保存文件 ID 或文件 URL
这样后面做权限控制、清理垃圾文件、生成缩略图、审核资源时,结构会更清楚。
如果你已经走到上传这一步,可以直接对 AI 说:
```text
请帮我把这个小程序项目的上传功能接到云存储,不要用外部图床。上传成功后,请顺手把文件信息记录下来,并告诉我后面应该把图片地址存在哪里。
```
# 6. 如果你的项目更复杂,就升级到云托管
当项目开始出现下面这些信号时,就说明你不应该只靠简单云函数了:
- API 路由越来越多
- 需要 Express / NestJS / FastAPI 这类成熟框架
- 需要复杂鉴权、中间件、统一错误处理
- 需要连接更多外部系统
- 需要更稳定的容器级部署
这时比较合理的做法不是“完全推倒重来”,而是升级到 **云托管**
你可以理解为:
- 云函数更像“一个个能力点”
- 云托管更像“一个完整的后端服务”
## 6.1 云托管适合什么样的工程
比如你要做:
- 带管理后台的内容平台
- 有商品、订单、支付、售后的一套业务
- 有 AI 工作流、异步队列、Webhook 回调的系统
- 同时服务小程序、H5、后台管理端的统一 API
那么云托管会更舒服。
## 6.2 一个更接近传统后端的结构
```text
server/
├── src/
│ ├── controllers/
│ ├── services/
│ ├── repositories/
│ ├── middleware/
│ └── app.js
├── Dockerfile
└── package.json
```
此时你的小程序前端可以通过:
- `wx.cloud.callContainer`
- 或者你配置好的 HTTPS API
去请求这个后端服务。
# 7. 支付为什么一定要有后端
这是“带后端小程序”最典型、也最不能偷懒的一件事。
微信支付的正确姿势是:
1. 小程序前端点击“支付”
2. 前端请求你的后端
3. 后端调用微信支付下单接口,拿到预支付信息
4. 后端把支付参数返回给小程序
5. 小程序调用 `wx.requestPayment`
6. 支付结果以服务端回调为准,后端更新订单状态
这里有三个原则:
- **下单在服务端**
- **签名在服务端**
- **订单最终状态以服务端通知为准**
不要用“前端支付成功弹窗出现了”来判断订单成功,那会出大问题。
## 7.1 一个正确的支付职责划分
**前端:**
- 展示商品和价格
- 发起“我要支付”
- 调起 `wx.requestPayment`
- 展示支付中、支付成功、支付失败状态
**后端:**
- 校验商品和价格是否有效
- 创建本地订单
- 调微信支付下单
- 保存交易流水
- 处理回调通知
- 更新订单状态
- 做幂等处理,避免重复发货或重复记账
# 8. 传统自建后端的标准做法
如果你明确知道自己要走“自建服务”的路线,那么小程序和后端之间最常见的流程是:
```text
小程序调用 wx.login
-> 把 code 发给你的后端
-> 后端调用微信官方登录态接口
-> 后端拿到用户标识并建立自己的用户体系
```
再往后,小程序就只跟你的后端 API 交互。
这条路线没有问题,但它比“直接用云开发”多出很多基础设施工作:
- 合法域名配置
- HTTPS
- 登录态管理
- 部署与运维
- 日志和监控
- 安全策略
- 服务器扩缩容
所以我的建议一直是:**除非你已经明确需要这些能力,否则不要在第一版就把自己拖进运维泥潭。**
# 9. 安全是“最佳实现”的一部分
很多人一说“最佳实践”,脑子里只想到技术栈。但真正的小程序后端最佳实践,安全一定是标配。
## 9.1 你至少要做到这些
- 不把任何密钥放进小程序前端
- 订单、积分、价格、库存都由服务端决定
- 数据库默认最小权限
- 所有关键写操作走服务端
- 上传内容做安全审核
- 支付回调做验签和幂等
- 区分开发、测试、生产环境
- 给关键链路加日志和告警
## 9.2 内容型产品一定要加审核
如果你的产品允许用户上传:
- 文本
- 图片
- 音频
- 评论
- 头像
- 社区内容
那就应该把内容安全审核接进后端流程,而不是靠人工祈祷。
一个典型流程是:
```text
用户提交内容
-> 后端写入待审核状态
-> 调用审核能力
-> 审核通过后再公开展示
```
这会比“前端一提交就直接全量公开”安全得多。
# 10. 一个真正适合 0 基础的 Prompt
如果你完全是第一次做,不要发那种很长的任务清单。你可以先从下面这句开始:
```text
请帮我做一个最简单的“微信小程序 + 云开发后端”版本。要求是:我几乎不懂代码,所以请你直接修改真实项目文件,少讲术语,每做完一步都告诉我下一步该点哪里、看到什么才算成功。
```
如果 AI 已经开始干活了,你再一小步一小步加需求,例如:
```text
下一步请帮我加一个最简单的云函数测试页面。
```
```text
下一步请帮我把内容发布改成走云函数,不要前端直接写数据库。
```
```text
下一步请帮我接云存储上传,并告诉我上传成功后我应该在哪个页面看到结果。
```
0 基础最重要的原则不是“一次把 Prompt 写得很完美”,而是:
- 先让 AI 帮你完成一个很小的动作
- 确认成功
- 再继续下一步
你不需要一开始就写出一份架构师级别的长 Prompt。
# 11. 你应该按什么顺序做
如果你是第一次做这一类项目,我建议顺序如下:
1. 先把小程序前端页面搭出来
2. 让 AI 帮你初始化云开发环境
3. 让 AI 帮你生成一个最简单的 `getCurrentUser` 云函数
4. 跑通第一条“前端调用后端”的闭环
5. 加数据库集合和最小安全规则
6. 把关键写操作收回到云函数或云托管
7. 再接上传、审核、支付、AI 这些增强能力
不要一开始就同时搞:
- 登录体系
- 支付体系
- 积分体系
- 会员体系
- 分销体系
- 管理后台
那样非常容易把自己做崩。
# 12. 本节小结
如果把这一节压缩成一句话,那就是:
::: tip 结论
**做带后端的微信小程序时,默认最佳实现是“小程序前端 + 云开发后端”;**
**关键业务逻辑统一收口到服务端;**
**支付、密钥、权限、审核、安全规则一定不要放松。**
:::
你可以先用最小成本做出一版:
- 前端能展示页面
- 云函数能处理业务
- 数据库存数据
- 云存储放文件
- 审核保证内容安全
等业务变复杂,再逐步升级到云托管或更重的自建后端架构。
这才是真正适合独立开发者和 AI 协作开发的“小程序带后端最佳实践”。
# 参考资料
- 微信云开发小程序快速开始:<https://docs.cloudbase.net/quick-start/mini-program/introduce>
- CloudBase 云函数使用指南:<https://docs.cloudbase.net/cloud-function/how-use>
- CloudBase 数据库安全规则:<https://docs.cloudbase.net/database/security-rules>
- CloudBase 云函数安全规则:<https://docs.cloudbase.net/cloud-function/security-rules>
- CloudBase 云托管快速开始:<https://docs.cloudbase.net/run/quick-start/introduce>
- CloudBase HTTP 访问服务:<https://docs.cloudbase.net/hosting/access/service>
- CloudBase 内容安全审核:<https://docs.cloudbase.net/safety-audit/introduce>
- 微信支付小程序调起支付文档:<https://pay.wechatpay.cn/doc/v3/partner/4013070347>
+1228 -7
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -38,6 +38,7 @@
"@element-plus/icons-vue": "^2.3.2", "@element-plus/icons-vue": "^2.3.2",
"claude": "^0.1.1", "claude": "^0.1.1",
"element-plus": "^2.13.1", "element-plus": "^2.13.1",
"mermaid": "^11.13.0",
"typeit": "^8.8.7", "typeit": "^8.8.7",
"viewerjs": "^1.11.7", "viewerjs": "^1.11.7",
"vitepress": "^2.0.0-alpha.16", "vitepress": "^2.0.0-alpha.16",