feat(docs): add new assignment projects and update existing content
This commit is contained in:
@@ -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": []
|
||||
}
|
||||
@@ -1433,7 +1433,7 @@ Sitemap: ${siteUrl}/sitemap.xml
|
||||
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/'
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'element-plus/dist/index.css'
|
||||
import Viewer from 'viewerjs'
|
||||
import 'viewerjs/dist/viewer.css'
|
||||
import TypeIt from 'typeit'
|
||||
import { onMounted, watch, nextTick } from 'vue'
|
||||
import { onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
|
||||
import { useRoute, useData } from 'vitepress'
|
||||
import './style.css'
|
||||
import Layout from './Layout.vue'
|
||||
@@ -1712,12 +1712,259 @@ export default {
|
||||
const route = useRoute()
|
||||
const { frontmatter } = useData()
|
||||
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
|
||||
if (import.meta.env.SSR) {
|
||||
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 = () => {
|
||||
// 销毁旧实例
|
||||
if (viewer) {
|
||||
@@ -1827,20 +2074,46 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
initViewer()
|
||||
initTypewriter()
|
||||
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(
|
||||
() => route.path,
|
||||
() =>
|
||||
nextTick(() => {
|
||||
nextTick(async () => {
|
||||
cleanupMermaidViewer()
|
||||
initViewer()
|
||||
initTypewriter()
|
||||
optimizeImages()
|
||||
await initRenderedMermaidFeatures(true)
|
||||
initCollapsibleCodeBlocks()
|
||||
})
|
||||
)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
cleanupMermaidViewer()
|
||||
if (themeObserver) {
|
||||
themeObserver.disconnect()
|
||||
themeObserver = null
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,3 +460,135 @@
|
||||
white-space: nowrap;
|
||||
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
|
||||
|
||||
> 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
|
||||
|
||||
> 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
@@ -788,7 +788,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<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>
|
||||
<priority>0.9</priority>
|
||||
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-0/"/>
|
||||
@@ -892,7 +892,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<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>
|
||||
<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/"/>
|
||||
@@ -900,7 +900,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<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>
|
||||
<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/"/>
|
||||
@@ -916,7 +916,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<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>
|
||||
<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/"/>
|
||||
@@ -924,7 +924,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<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>
|
||||
<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/"/>
|
||||
@@ -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/"/>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/</loc>
|
||||
<lastmod>2026-03-25T00:28:36+08:00</lastmod>
|
||||
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-2/assignments/在线考试与管理系统/</loc>
|
||||
<lastmod>2026-03-26T02:36:21.127Z</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<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="en" href="https://datawhalechina.github.io/easy-vibe/en/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/在线考试与管理系统/"/>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-2/backend/2.2-database-supabase/</loc>
|
||||
@@ -1252,7 +1251,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<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>
|
||||
<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/"/>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
| 使用 lovart 生产素材 | 学会用 lovart 批量生成人物、场景等视觉素材,为 UI 设计和前端开发提供素材基础 | 🚧 |
|
||||
| Figma 与 MasterGo 入门 | 用设计工具梳理信息架构和页面结构,为前端实现打基础 | 🚧 |
|
||||
| 构建第一个现代应用程序-UI 设计 | 基于设计稿完成组件化界面,实现从设计到代码的第一条链路 | 🚧 |
|
||||
| 参考 UI 设计规范与多产品 UI 设计 | 围绕统一主视觉扩展多产品界面,练习系统化设计能力 | 🚧 |
|
||||
| 参考 UI 设计规范设计页面和按钮 | 学习用主流设计规范组织页面结构、按钮层级,并借助 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 接进项目。
|
||||
- **后半部分**统一放到附录,包含 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(自己管税务)或 Paddle(MoR 代管) |
|
||||
| 海外数字产品 | 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 alert’s 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/)
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
/>
|
||||
<NavCard
|
||||
href="/zh-cn/stage-2/frontend/2.3-multi-product-ui/"
|
||||
title="参考 UI 设计规范与多产品 UI 设计"
|
||||
description="学习主流 UI 设计规范,提升产品设计的一致性与美感"
|
||||
title="参考 UI 设计规范设计页面和按钮"
|
||||
description="学习主流 UI 设计规范,设计更清晰的页面层级与按钮层级"
|
||||
/>
|
||||
<NavCard
|
||||
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>
|
||||
|
||||
Generated
+1228
-7
File diff suppressed because it is too large
Load Diff
@@ -38,6 +38,7 @@
|
||||
"@element-plus/icons-vue": "^2.3.2",
|
||||
"claude": "^0.1.1",
|
||||
"element-plus": "^2.13.1",
|
||||
"mermaid": "^11.13.0",
|
||||
"typeit": "^8.8.7",
|
||||
"viewerjs": "^1.11.7",
|
||||
"vitepress": "^2.0.0-alpha.16",
|
||||
|
||||
Reference in New Issue
Block a user