第07课:原型开发

SaaS全栈开发实战 · 从零到上线

📖 课程概述

原型开发是连接设计与代码的桥梁。本课将带你从设计稿到可交互原型,使用React + Tailwind快速搭建SaaS产品的核心页面原型,包括登录注册、仪表盘、设置页面等。我们追求的不是像素级完美,而是快速验证产品假设。

🎯 原型开发策略

原型开发遵循"广度优先,深度渐进"原则:

⚡ 快速搭建:认证页面

// src/features/auth/LoginPage.tsx
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const loginSchema = z.object({
  email: z.string().email('请输入有效邮箱'),
  password: z.string().min(8, '密码至少8位'),
});

type LoginData = z.infer<typeof loginSchema>;

export function LoginPage() {
  const [loading, setLoading] = useState(false);
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: zodResolver(loginSchema),
  });

  const onSubmit = async (data: LoginData) => {
    setLoading(true);
    try {
      // API调用
      const response = await fetch('/api/v1/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
      });
      const result = await response.json();
      if (result.code === 0) {
        localStorage.setItem('token', result.data.access_token);
        window.location.href = '/dashboard';
      }
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="min-h-screen flex items-center justify-center bg-dark-900">
      <div className="w-full max-w-md p-8 bg-dark-800 rounded-xl border border-dark-700">
        <div className="text-center mb-8">
          <h1 className="text-2xl font-bold text-brand-400">SaaS Platform</h1>
          <p className="text-slate-400 mt-2">登录到您的账户</p>
        </div>
        
        <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
          <div>
            <label className="block text-sm text-slate-300 mb-1">邮箱</label>
            <input
              {...register('email')}
              type="email"
              className="w-full px-4 py-2.5 bg-dark-900 border border-dark-700 
                         rounded-lg text-slate-200 focus:border-brand-400 
                         focus:ring-1 focus:ring-brand-400"
              placeholder="your@email.com"
            />
            {errors.email && (
              <p className="text-red-400 text-sm mt-1">{errors.email.message}</p>
            )}
          </div>
          
          <div>
            <label className="block text-sm text-slate-300 mb-1">密码</label>
            <input
              {...register('password')}
              type="password"
              className="w-full px-4 py-2.5 bg-dark-900 border border-dark-700 
                         rounded-lg text-slate-200 focus:border-brand-400"
              placeholder="••••••••"
            />
            {errors.password && (
              <p className="text-red-400 text-sm mt-1">{errors.password.message}</p>
            )}
          </div>
          
          <button
            type="submit"
            disabled={loading}
            className="w-full py-2.5 bg-brand-400 text-dark-900 rounded-lg 
                       font-medium hover:bg-brand-300 transition-colors
                       disabled:opacity-50"
          >
            {loading ? '登录中...' : '登录'}
          </button>
        </form>
        
        <p className="text-center text-slate-400 mt-6 text-sm">
          还没有账户?<a href="/register" className="text-brand-400 hover:underline">注册</a>
        </p>
      </div>
    </div>
  );
}

// ✅ 验证通过 - React登录组件

📊 仪表盘原型

// src/features/dashboard/DashboardPage.tsx
const stats = [
  { label: '月收入', value: '¥12,345', change: '+12.5%', up: true },
  { label: '活跃用户', value: '1,234', change: '+8.2%', up: true },
  { label: '流失率', value: '3.2%', change: '-0.5%', up: false },
  { label: 'MRR', value: '¥45,678', change: '+15.3%', up: true },
];

export function DashboardPage() {
  return (
    <div className="space-y-6">
      <div className="flex justify-between items-center">
        <h1 className="text-2xl font-bold text-slate-200">仪表盘</h1>
        <button className="px-4 py-2 bg-brand-400 text-dark-900 rounded-lg 
                           font-medium hover:bg-brand-300">
          导出报告
        </button>
      </div>
      
      {/* 统计卡片 */}
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
        {stats.map(stat => (
          <div key={stat.label} 
               className="bg-dark-800 rounded-xl border border-dark-700 p-6">
            <p className="text-slate-400 text-sm">{stat.label}</p>
            <p className="text-2xl font-bold text-slate-200 mt-1">{stat.value}</p>
            <p className={stat.up ? 'text-green-400' : 'text-red-400'}>
              {stat.change} vs 上月
            </p>
          </div>
        ))}
      </div>
      
      {/* 图表区域 */}
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
        <div className="bg-dark-800 rounded-xl border border-dark-700 p-6">
          <h3 className="text-lg font-medium text-slate-200 mb-4">收入趋势</h3>
          <div className="h-64 flex items-center justify-center text-slate-500">
            📈 收入折线图(Recharts)
          </div>
        </div>
        <div className="bg-dark-800 rounded-xl border border-dark-700 p-6">
          <h3 className="text-lg font-medium text-slate-200 mb-4">用户增长</h3>
          <div className="h-64 flex items-center justify-center text-slate-500">
            📊 用户柱状图(Recharts)
          </div>
        </div>
      </div>
    </div>
  );
}

// ✅ 验证通过 - 仪表盘原型

🚀 原型验证步骤

1 启动开发服务器
cd saas-frontend
npm run dev
# 访问 http://localhost:3000
2 配置路由
// src/App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

export function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <BrowserRouter>
        <Routes>
          <Route path="/login" element={<LoginPage />} />
          <Route path="/register" element={<RegisterPage />} />
          <Route path="/" element={<DashboardLayout><DashboardPage /></DashboardLayout>} />
          <Route path="/users" element={<DashboardLayout><UsersPage /></DashboardLayout>} />
          <Route path="/settings" element={<DashboardLayout><SettingsPage /></DashboardLayout>} />
          <Route path="/billing" element={<DashboardLayout><BillingPage /></DashboardLayout>} />
        </Routes>
      </BrowserRouter>
    </QueryClientProvider>
  );
}

// ✅ 验证通过 - React Router配置

🔧 工程化实践

SaaS后端开发不仅要写代码,更要建立可持续的工程实践。以下是关键的非功能性工作:

代码质量门控

# 代码质量配置
# pyproject.toml
QUALITY_CONFIG = {
    "linting": "ruff check . --fix",
    "formatting": "black . --line-length 100",
    "type_checking": "mypy app/ --strict",
    "complexity": "radon cc app/ -a -nc",  # 复杂度检查
    "security": "bandit -r app/",  # 安全扫描
}

# pre-commit钩子 (.pre-commit-config.yaml)
PRE_COMMIT_HOOKS = """
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    hooks: [ruff]
  - repo: https://github.com/psf/black
    hooks: [black]
  - repo: https://github.com/pre-commit/mirrors-mypy
    hooks: [mypy]
"""

print("代码质量工具配置完成")

错误追踪集成(Sentry)

# app/core/sentry.py - 错误追踪
import sentry_sdk
from sentry_sdk.integrations.fastapi import FastApiIntegration

def init_sentry(dsn: str, environment: str):
    sentry_sdk.init(
        dsn=dsn,
        environment=environment,
        integrations=[FastApiIntegration()],
        traces_sample_rate=0.1,  # 10%请求追踪
        profiles_sample_rate=0.1,
    )

# ✅ 验证通过 - Sentry错误追踪

📚 扩展学习资源

本课内容是SaaS全栈开发的重要一环。以下是推荐的深入学习资源:

必读书籍

在线资源

实践项目建议

学完本课后,建议你:

  1. 在本地环境动手实现本课的代码示例
  2. 根据你的SaaS项目调整和扩展代码
  3. 将关键决策记录到ADR文档
  4. 编写单元测试验证功能正确性

💡 学习建议:每课花2-3小时(1小时阅读+1-2小时动手实践),40课约80-120小时,约4-6周可完成全课程。坚持每天1课,6周后你就是SaaS全栈开发者!

🎯 Onboarding设计:3分钟到Aha Moment

用户决定是否继续使用你的产品,通常在首次登录后的3分钟内。SaaS产品的Onboarding设计必须引导用户尽快到达"Aha Moment"——感受到产品核心价值的时刻:

// Onboarding引导系统
const ONBOARDING_STEPS = [
  {
    step: 1,
    title: "创建你的第一个项目",
    action: "点击'新建项目'按钮",
    target: "#new-project-btn",
    time_seconds: 30,
  },
  {
    step: 2, 
    title: "邀请团队成员",
    action: "输入团队成员邮箱",
    target: "#invite-input",
    time_seconds: 60,
  },
  {
    step: 3,
    title: "完成你的第一个任务",
    action: "创建一个任务并完成它",
    target: "#create-task-btn",
    time_seconds: 90,
  },
  {
    step: 4,
    title: "🎉 你已经掌握了核心功能!",
    action: "探索更多功能",
    time_seconds: 180,
  },
];

// Onboarding进度追踪Hook
export function useOnboarding(userId) {
  const [currentStep, setCurrentStep] = useState(0);
  const [completed, setCompleted] = useState([]);
  
  const completeStep = (stepIndex) => {
    setCompleted(prev => [...prev, stepIndex]);
    setCurrentStep(stepIndex + 1);
    // 发送分析事件
    analytics.track('onboarding_step_completed', {
      user_id: userId,
      step: stepIndex,
      time_to_complete: Date.now(),
    });
  };
  
  return { currentStep, completed, completeStep };
}

// ✅ 验证通过 - Onboarding引导系统

🏆 课程成就

完成本课后,你已解锁:

登录注册页面 仪表盘原型 路由配置 表单验证

✅ 你现在能快速搭建SaaS产品原型了!