OPC-Starter 学习教程

OPC-Starter 学习教程

目录

  1. 快速入门
  2. 项目结构详解
  3. 核心功能开发
  4. Agent Studio (A2UI)
  5. 数据服务层
  6. 测试与质量保证
  7. AI 辅助开发最佳实践
  8. 部署与上线

一、快速入门

1.1 环境准备

系统要求: - Node.js 18+ - npm 9+ 或 pnpm 8+ - Git

推荐工具: - Cursor - AI 代码编辑器(推荐) - Qoder - AI 编程助手 - VS Code - 传统编辑器

1.2 克隆项目

# 克隆仓库
git clone https://github.com/alibaba/opc-starter.git
cd opc-starter

# 安装依赖
npm install

1.3 启动开发服务器

方式一:MSW Mock 模式(推荐新手)

无需配置真实后端,使用 Mock 数据:

# 使用测试环境配置启动
npm run dev:test

访问 http://localhost:5173

测试账号: - 邮箱:test@example.com - 密码:888888

方式二:真实 Supabase 模式

  1. 创建 Supabase 项目:https://supabase.com
  2. 配置环境变量:
# 复制环境变量模板
cp app/.env.example app/.env.local

编辑 app/.env.local

VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key
  1. 运行数据库 Schema(在 Supabase SQL Editor 中执行 app/supabase/setup.sql

  2. 启动应用:

npm run dev

1.4 项目验证

# 类型检查
npm run type-check

# 单元测试
npm test

# 构建
npm run build

# 完整质量检查
npm run ai:check

二、项目结构详解

2.1 目录结构

app/
├── src/
│   ├── auth/                 # 认证模块
│   │   ├── AuthProvider.tsx  # 认证上下文
│   │   ├── LoginForm.tsx     # 登录表单
│   │   └── ProtectedRoute.tsx # 路由守卫
│   │
│   ├── components/           # 组件目录
│   │   ├── agent/            # Agent Studio
│   │   ├── business/         # 业务组件
│   │   ├── layout/           # 布局组件
│   │   ├── organization/     # 组织架构
│   │   └── ui/               # 基础 UI
│   │
│   ├── hooks/                # 自定义 Hooks
│   ├── lib/                  # 库封装
│   │   ├── agent/            # Agent 核心
│   │   ├── reactive/         # 响应式数据
│   │   └── supabase/         # Supabase 客户端
│   │
│   ├── pages/                # 页面组件
│   ├── services/             # 服务层
│   │   └── data/             # DataService
│   │
│   ├── stores/               # Zustand 状态
│   ├── types/                # TypeScript 类型
│   └── utils/                # 工具函数
│
├── supabase/
│   ├── functions/            # Edge Functions
│   └── setup.sql             # 数据库 Schema
│
├── cypress/                  # E2E 测试
└── .env.test                 # 测试环境变量

2.2 核心入口文件

main.tsx - 应用入口:

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
import './index.css'

// MSW Mock 初始化(开发/测试环境)
if (import.meta.env.VITE_ENABLE_MSW === 'true') {
  import('./mocks/browser').then(({ worker }) => {
    worker.start()
  })
}

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

App.tsx - 根组件:

import { AuthProvider } from '@/auth/AuthProvider'
import { Router } from '@/config/routes'

function App() {
  return (
    <AuthProvider>
      <Router />
    </AuthProvider>
  )
}

export default App

2.3 路由配置

config/routes.tsx

import { createBrowserRouter } from 'react-router-dom'
import { ProtectedRoute } from '@/auth/ProtectedRoute'
import { Layout } from '@/components/layout/Layout'
import { Dashboard } from '@/pages/Dashboard'
import { Login } from '@/pages/Login'

export const router = createBrowserRouter([
  {
    path: '/login',
    element: <Login />,
  },
  {
    path: '/',
    element: (
      <ProtectedRoute>
        <Layout />
      </ProtectedRoute>
    ),
    children: [
      { index: true, element: <Dashboard /> },
      { path: 'settings', element: <Settings /> },
      // 添加更多路由...
    ],
  },
])

三、核心功能开发

3.1 认证系统

登录流程

// pages/Login.tsx
import { useAuth } from '@/auth/AuthProvider'
import { LoginForm } from '@/auth/LoginForm'

export function Login() {
  const { signIn } = useAuth()

  const handleSubmit = async (email: string, password: string) => {
    const { error } = await signIn(email, password)
    if (error) {
      // 错误处理
    }
  }

  return <LoginForm onSubmit={handleSubmit} />
}

AuthProvider 实现

// auth/AuthProvider.tsx
import { createContext, useContext, useEffect, useState } from 'react'
import { supabase } from '@/lib/supabase'

interface AuthContextType {
  user: User | null
  signIn: (email: string, password: string) => Promise<void>
  signOut: () => Promise<void>
}

const AuthContext = createContext<AuthContextType | null>(null)

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null)

  useEffect(() => {
    // 监听认证状态变化
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      (_, session) => {
        setUser(session?.user ?? null)
      }
    )

    return () => subscription.unsubscribe()
  }, [])

  const signIn = async (email: string, password: string) => {
    const { error } = await supabase.auth.signInWithPassword({
      email,
      password,
    })
    if (error) throw error
  }

  const signOut = async () => {
    await supabase.auth.signOut()
    setUser(null)
  }

  return (
    <AuthContext.Provider value={{ user, signIn, signOut }}>
      {children}
    </AuthContext.Provider>
  )
}

export const useAuth = () => {
  const context = useContext(AuthContext)
  if (!context) throw new Error('useAuth must be used within AuthProvider')
  return context
}

3.2 添加新页面

Step 1: 创建页面组件

// pages/Products.tsx
import { useQuery } from '@tanstack/react-query'
import { dataService } from '@/services/data/DataService'

export function Products() {
  const { data: products, isLoading } = useQuery({
    queryKey: ['products'],
    queryFn: () => dataService.getProducts(),
  })

  if (isLoading) return <div>Loading...</div>

  return (
    <div className="p-6">
      <h1 className="text-2xl font-bold mb-4">Products</h1>
      <div className="grid grid-cols-3 gap-4">
        {products?.map((product) => (
          <ProductCard key={product.id} product={product} />
        ))}
      </div>
    </div>
  )
}

Step 2: 添加路由

// config/routes.tsx
import { Products } from '@/pages/Products'

// 在 children 数组中添加
{ path: 'products', element: <Products /> },

Step 3: 添加导航

// components/layout/Sidebar.tsx
const navItems = [
  { path: '/', label: 'Dashboard', icon: Home },
  { path: '/products', label: 'Products', icon: Package },
  // ...
]

3.3 状态管理

使用 Zustand

// stores/productStore.ts
import { create } from 'zustand'

interface ProductState {
  products: Product[]
  selectedProduct: Product | null
  setProducts: (products: Product[]) => void
  selectProduct: (product: Product | null) => void
}

export const useProductStore = create<ProductState>((set) => ({
  products: [],
  selectedProduct: null,
  setProducts: (products) => set({ products }),
  selectProduct: (product) => set({ selectedProduct: product }),
}))

在组件中使用

// components/ProductList.tsx
import { useProductStore } from '@/stores/productStore'

export function ProductList() {
  const { products, selectProduct } = useProductStore()

  return (
    <ul>
      {products.map((product) => (
        <li key={product.id} onClick={() => selectProduct(product)}>
          {product.name}
        </li>
      ))}
    </ul>
  )
}

四、Agent Studio (A2UI)

4.1 A2UI 协议简介

A2UI(Agent-to-User Interface)是一种声明式 UI 协议,让 AI Agent 可以生成动态交互界面。

工作流程

用户输入 → LLM 解析 → A2UI Schema → 动态渲染

4.2 配置 Agent Studio

环境变量

# 使用阿里云通义千问
VITE_DASHSCOPE_API_KEY=your_dashscope_api_key

# 或使用其他 LLM Provider
VITE_OPENAI_API_KEY=your_openai_api_key

4.3 使用 Agent Studio

基本用法

// components/AgentChat.tsx
import { AgentStudio } from '@/components/agent/AgentStudio'

export function AgentChat() {
  const handleResponse = (schema: A2UISchema) => {
    console.log('AI generated UI:', schema)
  }

  return (
    <div className="h-screen">
      <AgentStudio onResponse={handleResponse} />
    </div>
  )
}

A2UI Schema 示例

{
  "type": "container",
  "children": [
    {
      "type": "text",
      "content": "Hello, User!",
      "style": { "fontSize": "24px", "fontWeight": "bold" }
    },
    {
      "type": "button",
      "label": "Click me",
      "onClick": "handleClick"
    },
    {
      "type": "list",
      "items": [
        { "type": "text", "content": "Item 1" },
        { "type": "text", "content": "Item 2" }
      ]
    }
  ]
}

4.4 自定义 A2UI 组件

注册自定义组件

// lib/agent/customComponents.ts
import { registerA2UIComponent } from '@/lib/agent/registry'

// 注册自定义图表组件
registerA2UIComponent('chart', {
  render: (props: ChartProps) => <Chart {...props} />,
  validate: (props) => {
    // 验证 props
    return true
  },
})

在 A2UI Schema 中使用

{
  "type": "chart",
  "chartType": "line",
  "data": [
    { "x": "Jan", "y": 100 },
    { "x": "Feb", "y": 150 }
  ]
}

五、数据服务层

5.1 DataService 架构

┌─────────────────────────────────────────────────────────┐
│                     DataService                          │
│  ┌─────────────────────────────────────────────────┐    │
│  │                 统一 API 接口                     │    │
│  └─────────────────────────────────────────────────┘    │
│                         │                                │
│         ┌───────────────┼───────────────┐               │
│         ▼               ▼               ▼               │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐       │
│  │  IndexedDB  │ │   Sync      │ │  Supabase   │       │
│  │  (本地存储)  │ │  (同步引擎)  │ │  (云端 API)  │       │
│  └─────────────┘ └─────────────┘ └─────────────┘       │
└─────────────────────────────────────────────────────────┘

5.2 使用 DataService

读取数据

import { dataService } from '@/services/data/DataService'

// 获取单个实体
const product = await dataService.getProduct('product-123')

// 获取列表
const products = await dataService.getProducts({ category: 'electronics' })

// 使用 React Query
const { data, isLoading } = useQuery({
  queryKey: ['products', category],
  queryFn: () => dataService.getProducts({ category }),
})

写入数据

// 创建
const newProduct = await dataService.createProduct({
  name: 'New Product',
  price: 99.99,
})

// 更新
await dataService.updateProduct('product-123', {
  price: 89.99,
})

// 删除
await dataService.deleteProduct('product-123')

5.3 实时订阅

订阅数据变化

// hooks/useRealtimeProducts.ts
import { useEffect, useState } from 'react'
import { dataService } from '@/services/data/DataService'

export function useRealtimeProducts() {
  const [products, setProducts] = useState<Product[]>([])

  useEffect(() => {
    // 初始加载
    dataService.getProducts().then(setProducts)

    // 订阅变化
    const subscription = dataService.subscribeToProducts((updated) => {
      setProducts(updated)
    })

    return () => subscription.unsubscribe()
  }, [])

  return products
}

5.4 离线支持

IndexedDB 缓存

// services/data/adapters/IndexedDBAdapter.ts
import { openDB } from 'idb'

const db = await openDB('opc-starter', 1, {
  upgrade(db) {
    db.createObjectStore('products', { keyPath: 'id' })
    db.createObjectStore('orders', { keyPath: 'id' })
  },
})

// 读取
const products = await db.getAll('products')

// 写入
await db.put('products', product)

// 删除
await db.delete('products', productId)

六、测试与质量保证

6.1 单元测试

使用 Vitest

// __tests__/components/ProductCard.test.tsx
import { render, screen } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import { ProductCard } from '@/components/ProductCard'

describe('ProductCard', () => {
  it('renders product name', () => {
    const product = { id: '1', name: 'Test Product', price: 99.99 }
    render(<ProductCard product={product} />)

    expect(screen.getByText('Test Product')).toBeInTheDocument()
  })

  it('formats price correctly', () => {
    const product = { id: '1', name: 'Test', price: 99.99 }
    render(<ProductCard product={product} />)

    expect(screen.getByText('$99.99')).toBeInTheDocument()
  })
})

运行测试

# 运行所有测试
npm test

# 监听模式
npm test -- --watch

# 覆盖率报告
npm test -- --coverage

6.2 E2E 测试

使用 Cypress

// cypress/e2e/login.cy.ts
describe('Login Flow', () => {
  it('should login successfully', () => {
    cy.visit('/login')

    cy.get('[data-testid="email-input"]').type('test@example.com')
    cy.get('[data-testid="password-input"]').type('888888')
    cy.get('[data-testid="login-button"]').click()

    cy.url().should('include', '/')
    cy.contains('Dashboard').should('be.visible')
  })
})

运行 E2E 测试

# 交互模式
npm run test:e2e

# 无头模式(CI)
npm run test:e2e:headless

6.3 质量检查

AI 质量检查命令

# 核心检查(lint + type-check + test + build)
npm run ai:check

# 完整质量检查脚本
./scripts/quality_check.sh

质量检查清单

  • [ ] ESLint 无错误
  • [ ] TypeScript 类型检查通过
  • [ ] 单元测试全部通过
  • [ ] E2E 测试全部通过
  • [ ] 生产构建成功
  • [ ] 无 console 警告

七、AI 辅助开发最佳实践

7.1 使用 AGENTS.md

AGENTS.md 是给 AI 编码工具的项目指南:

# AGENTS.md

## 项目概述
OPC-Starter 是一个 AI-Friendly React Boilerplate...

## 代码风格
- 使用函数组件和 Hooks
- 遵循 TypeScript 严格模式
- 组件命名:PascalCase
- 函数命名:camelCase

## 目录约定
- components/ - 可复用组件
- pages/ - 页面组件
- hooks/ - 自定义 Hooks
- stores/ - Zustand 状态

## AI 迭代地图
| 想做什么 | 从哪里开始 | 推荐验证 |
|---------|-----------|---------|
| 新增页面 | routes.tsx | npm run type-check |
| 改数据访问 | DataService.ts | npm test |
...

7.2 Cursor 使用技巧

1. 使用 @ 符号引用文件

@components/ProductCard.tsx 请帮我添加一个价格显示功能

2. 使用 Codebase Indexing

# indexing 整个项目
/Codebase: Re-index

3. 使用 BMAD 工作流

/analyst 分析这个需求
/pm 生成 PRD
/dev 实现功能

7.3 AI 友好的代码结构

原则

  1. 单一职责:每个文件只做一件事
  2. 清晰命名:文件名和函数名要有描述性
  3. 类型完整:不要使用 any
  4. 注释适度:复杂逻辑添加注释

示例

// ❌ 不好:文件太大,职责不清
// components/Product.tsx (500 行)

// ✅ 好:职责清晰
// components/ProductCard.tsx (50 行)
// components/ProductList.tsx (80 行)
// components/ProductDetail.tsx (100 行)
// hooks/useProduct.ts (40 行)
// types/product.ts (20 行)

7.4 常见 AI 辅助任务

添加新功能

请帮我添加一个订单管理页面:
1. 显示订单列表
2. 支持搜索和筛选
3. 点击订单可查看详情

参考 @components/ProductList.tsx 的结构

修复 Bug

@components/Checkout.tsx 中的支付按钮点击后没有响应
请帮我定位问题并修复

重构代码

@services/data/DataService.ts 这个文件太大了
请帮我拆分成多个文件,保持功能不变

八、部署与上线

8.1 构建生产版本

# 构建
npm run build

# 预览构建结果
npm run preview

8.2 部署到 Vercel

方式一:通过 Vercel CLI

# 安装 Vercel CLI
npm i -g vercel

# 部署
vercel

方式二:通过 GitHub 集成

  1. 将代码推送到 GitHub
  2. 在 Vercel 中导入项目
  3. 配置环境变量
  4. 自动部署

8.3 部署到 Netlify

# 安装 Netlify CLI
npm i -g netlify-cli

# 构建
npm run build

# 部署
netlify deploy --prod --dir=dist

8.4 环境变量配置

生产环境变量

# Supabase
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-production-anon-key

# LLM Provider(可选)
VITE_DASHSCOPE_API_KEY=your_dashscope_api_key

# 功能开关
VITE_ENABLE_MSW=false

8.5 Supabase 生产配置

安全设置

  1. 在 Supabase Dashboard 中配置 Row Level Security (RLS)
  2. 设置 API 速率限制
  3. 配置 CORS 允许的域名

数据库优化

-- 添加索引
CREATE INDEX idx_products_category ON products(category);
CREATE INDEX idx_orders_user_id ON orders(user_id);

-- 配置 RLS 策略
ALTER TABLE products ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Users can view their own orders"
ON orders FOR SELECT
USING (user_id = auth.uid());

附录:常用命令速查表

命令 说明
npm run dev 启动开发服务器(真实后端)
npm run dev:test 启动开发服务器(Mock 模式)
npm test 运行单元测试
npm run test:e2e 运行 E2E 测试
npm run type-check TypeScript 类型检查
npm run lint ESLint 检查
npm run build 构建生产版本
npm run ai:check 完整质量检查
./scripts/quality_check.sh 完整质量检查(含 E2E)

教程生成时间:2026-03-21