chore: 以本地为准,上传全部并替换 GitHub
This commit is contained in:
16
开发文档/4、前端/README.md
Normal file
16
开发文档/4、前端/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# 4、前端
|
||||
|
||||
> 前端主程 | 组件代码
|
||||
|
||||
## 本目录文档
|
||||
|
||||
| 文档 | 说明 |
|
||||
|:---|:---|
|
||||
| [_智能展开.md](./_智能展开.md) | 前端引擎激活、iOS风格 |
|
||||
| [核心组件代码.md](./核心组件代码.md) | **从项目提取**:AI对话、毛玻璃样式 |
|
||||
| [前端开发规范.md](./前端开发规范.md) | 开发规范 |
|
||||
|
||||
## 联动
|
||||
|
||||
- 上游: 3-原型、5-接口 | 下游: 8-部署
|
||||
- 指令: `@前端引擎 展开 [组件名]`
|
||||
723
开发文档/4、前端/_智能展开.md
Normal file
723
开发文档/4、前端/_智能展开.md
Normal file
@@ -0,0 +1,723 @@
|
||||
# ⚛️ 前端智能展开引擎 (Frontend Auto-Expand)
|
||||
|
||||
> **角色激活**: 将此文件拖入 AI,即刻激活 **前端技术专家** 角色
|
||||
> **核心能力**: React 组件、Tailwind 样式、iOS 风格、性能优化
|
||||
|
||||
---
|
||||
|
||||
## 📋 一、快速启动指令
|
||||
|
||||
### 1.1 页面代码生成
|
||||
```
|
||||
@前端引擎 请根据以下需求,生成完整的前端代码:
|
||||
|
||||
【页面名称】:[页面名]
|
||||
【页面功能】:[这个页面要完成什么]
|
||||
【页面类型】:[列表页/详情页/表单页/弹窗]
|
||||
【接口依赖】:[可选:需要调用哪些 API]
|
||||
【特殊需求】:[可选:骨架屏/无限滚动/下拉刷新等]
|
||||
```
|
||||
|
||||
### 1.2 展开输出清单
|
||||
| 输出项 | 说明 | 格式 |
|
||||
|:---|:---|:---|
|
||||
| 页面组件 | 完整的 React 组件代码 | TSX |
|
||||
| 样式类名 | Tailwind CSS 类名 | className |
|
||||
| 自定义 Hook | 数据获取/状态管理 | TypeScript |
|
||||
| 类型定义 | 接口和数据类型 | TypeScript |
|
||||
| 骨架屏组件 | 加载状态 UI | TSX |
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 二、技术栈规范
|
||||
|
||||
### 2.1 卡若标准前端栈
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 卡若前端技术栈 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ 🏗️ 框架层 │
|
||||
│ ├── React 18+ (首选) / Vue 3 + Nuxt │
|
||||
│ ├── Next.js 14+ (App Router) / Nuxt 3 │
|
||||
│ └── TypeScript (强制) │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ 🎨 UI 层 │
|
||||
│ ├── Shadcn UI (PC/通用) │
|
||||
│ ├── Vant UI (移动端) │
|
||||
│ ├── Tailwind CSS 3.x (原子化样式) │
|
||||
│ └── Framer Motion / CSS Transition (动画) │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ 📦 状态管理 │
|
||||
│ ├── React Query / SWR (服务端状态) │
|
||||
│ ├── Zustand (客户端状态) │
|
||||
│ └── Context API (轻量场景) │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ 🔧 工具链 │
|
||||
│ ├── Vite / Turbopack (构建) │
|
||||
│ ├── ESLint + Prettier (代码规范) │
|
||||
│ └── Axios / fetch (网络请求) │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 目录结构规范
|
||||
|
||||
```
|
||||
/src
|
||||
├── /app # 页面路由 (Next.js App Router)
|
||||
│ ├── /(auth) # 认证相关路由组
|
||||
│ │ ├── /login
|
||||
│ │ └── /register
|
||||
│ ├── /(main) # 主应用路由组
|
||||
│ │ ├── /scenarios # 场景获客
|
||||
│ │ │ ├── /new # 新建场景 (固定路径!)
|
||||
│ │ │ └── /[id] # 场景详情
|
||||
│ │ ├── /traffic # 流量池
|
||||
│ │ └── /mine # 我的
|
||||
│ ├── /api # API 路由
|
||||
│ └── layout.tsx # 根布局
|
||||
│
|
||||
├── /components # 组件库
|
||||
│ ├── /ui # Shadcn 基础组件
|
||||
│ │ ├── button.tsx
|
||||
│ │ ├── skeleton.tsx # 骨架屏 (必须!)
|
||||
│ │ └── ...
|
||||
│ ├── /business # 业务组件
|
||||
│ │ ├── UserCard.tsx
|
||||
│ │ ├── TrafficPoolItem.tsx
|
||||
│ │ └── ...
|
||||
│ └── /layout # 布局组件
|
||||
│ ├── Header.tsx
|
||||
│ ├── TabBar.tsx
|
||||
│ └── PageContainer.tsx
|
||||
│
|
||||
├── /hooks # 自定义 Hooks
|
||||
│ ├── useAuth.ts
|
||||
│ ├── useTrafficPool.ts
|
||||
│ └── usePagination.ts
|
||||
│
|
||||
├── /lib # 工具库
|
||||
│ ├── api.ts # API 封装
|
||||
│ ├── utils.ts # 工具函数
|
||||
│ └── constants.ts # 常量
|
||||
│
|
||||
├── /styles # 样式
|
||||
│ └── globals.css # 全局样式 + Tailwind
|
||||
│
|
||||
└── /types # 类型定义
|
||||
├── api.d.ts
|
||||
└── business.d.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 三、iOS 风格组件库
|
||||
|
||||
### 3.1 页面容器
|
||||
|
||||
```tsx
|
||||
// components/layout/PageContainer.tsx
|
||||
interface PageContainerProps {
|
||||
children: React.ReactNode;
|
||||
title?: string;
|
||||
showBack?: boolean;
|
||||
rightAction?: React.ReactNode;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
export function PageContainer({
|
||||
children,
|
||||
title,
|
||||
showBack = true,
|
||||
rightAction,
|
||||
loading = false,
|
||||
}: PageContainerProps) {
|
||||
return (
|
||||
<div className="min-h-screen bg-ios-bg">
|
||||
{/* iOS 风格 Header */}
|
||||
<header className="sticky top-0 z-50 h-11 bg-white/80 backdrop-blur-xl
|
||||
border-b border-ios-separator flex items-center px-4">
|
||||
{showBack && (
|
||||
<button onClick={() => router.back()} className="text-ios-blue">
|
||||
<ChevronLeft className="w-6 h-6" />
|
||||
</button>
|
||||
)}
|
||||
<h1 className="flex-1 text-center text-[17px] font-semibold">
|
||||
{title}
|
||||
</h1>
|
||||
<div className="w-10">{rightAction}</div>
|
||||
</header>
|
||||
|
||||
{/* 内容区域 */}
|
||||
<main className="pb-safe">
|
||||
{loading ? <PageSkeleton /> : children}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 iOS 列表项
|
||||
|
||||
```tsx
|
||||
// components/ui/ListItem.tsx
|
||||
interface ListItemProps {
|
||||
icon?: React.ReactNode;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
value?: string | React.ReactNode;
|
||||
arrow?: boolean;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export function ListItem({
|
||||
icon,
|
||||
title,
|
||||
subtitle,
|
||||
value,
|
||||
arrow = true,
|
||||
onClick,
|
||||
}: ListItemProps) {
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className="bg-white px-4 py-3 flex items-center justify-between
|
||||
active:bg-gray-100 transition-colors cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
{icon && (
|
||||
<div className="w-8 h-8 rounded-lg bg-ios-blue/10
|
||||
flex items-center justify-center text-ios-blue">
|
||||
{icon}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<p className="text-[17px] text-gray-900">{title}</p>
|
||||
{subtitle && (
|
||||
<p className="text-[13px] text-gray-500 mt-0.5">{subtitle}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{value && (
|
||||
<span className="text-[15px] text-gray-500">{value}</span>
|
||||
)}
|
||||
{arrow && <ChevronRight className="w-5 h-5 text-gray-300" />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 骨架屏组件 (强制使用)
|
||||
|
||||
```tsx
|
||||
// components/ui/skeleton.tsx
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface SkeletonProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// 基础骨架
|
||||
export function Skeleton({ className }: SkeletonProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"animate-pulse rounded-md bg-gray-200",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// 列表项骨架
|
||||
export function ListItemSkeleton() {
|
||||
return (
|
||||
<div className="bg-white px-4 py-3 flex items-center gap-3">
|
||||
<Skeleton className="w-10 h-10 rounded-full" />
|
||||
<div className="flex-1">
|
||||
<Skeleton className="h-4 w-24 mb-2" />
|
||||
<Skeleton className="h-3 w-32" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 卡片骨架
|
||||
export function CardSkeleton() {
|
||||
return (
|
||||
<div className="bg-white rounded-xl p-4 m-4">
|
||||
<Skeleton className="h-6 w-1/3 mb-4" />
|
||||
<Skeleton className="h-10 w-1/2 mb-2" />
|
||||
<Skeleton className="h-4 w-2/3" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 页面骨架
|
||||
export function PageSkeleton() {
|
||||
return (
|
||||
<div className="space-y-4 p-4">
|
||||
<CardSkeleton />
|
||||
<div className="space-y-1">
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<ListItemSkeleton key={i} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 金额展示组件 (云阿米巴核心)
|
||||
|
||||
```tsx
|
||||
// components/business/MoneyDisplay.tsx
|
||||
interface MoneyDisplayProps {
|
||||
amount: number;
|
||||
label?: string;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
trend?: 'up' | 'down' | 'none';
|
||||
}
|
||||
|
||||
export function MoneyDisplay({
|
||||
amount,
|
||||
label,
|
||||
size = 'md',
|
||||
trend = 'none',
|
||||
}: MoneyDisplayProps) {
|
||||
const sizeClasses = {
|
||||
sm: 'text-xl',
|
||||
md: 'text-3xl',
|
||||
lg: 'text-4xl',
|
||||
};
|
||||
|
||||
const trendColors = {
|
||||
up: 'text-ios-green',
|
||||
down: 'text-ios-red',
|
||||
none: 'text-gray-900',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="text-center">
|
||||
{label && (
|
||||
<p className="text-[13px] text-gray-500 mb-1">{label}</p>
|
||||
)}
|
||||
<p className={cn(
|
||||
'font-bold tabular-nums',
|
||||
sizeClasses[size],
|
||||
trendColors[trend]
|
||||
)}>
|
||||
<span className="text-base mr-1">¥</span>
|
||||
{amount.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 四、交互规范代码
|
||||
|
||||
### 4.1 路由转场动画
|
||||
|
||||
```tsx
|
||||
// app/template.tsx - 全局转场动画
|
||||
'use client';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
export default function Template({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ x: 20, opacity: 0 }}
|
||||
animate={{ x: 0, opacity: 1 }}
|
||||
exit={{ x: -20, opacity: 0 }}
|
||||
transition={{ duration: 0.3, ease: 'easeOut' }}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 下拉刷新
|
||||
|
||||
```tsx
|
||||
// hooks/usePullRefresh.ts
|
||||
import { useState, useCallback } from 'react';
|
||||
|
||||
export function usePullRefresh(onRefresh: () => Promise<void>) {
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
const handleRefresh = useCallback(async () => {
|
||||
setRefreshing(true);
|
||||
try {
|
||||
await onRefresh();
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
}
|
||||
}, [onRefresh]);
|
||||
|
||||
return { refreshing, handleRefresh };
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 无限滚动
|
||||
|
||||
```tsx
|
||||
// hooks/useInfiniteScroll.ts
|
||||
import { useEffect, useRef, useCallback } from 'react';
|
||||
|
||||
export function useInfiniteScroll(
|
||||
onLoadMore: () => void,
|
||||
hasMore: boolean,
|
||||
loading: boolean
|
||||
) {
|
||||
const observerRef = useRef<IntersectionObserver | null>(null);
|
||||
const loadMoreRef = useCallback(
|
||||
(node: HTMLDivElement | null) => {
|
||||
if (loading) return;
|
||||
if (observerRef.current) observerRef.current.disconnect();
|
||||
|
||||
observerRef.current = new IntersectionObserver((entries) => {
|
||||
if (entries[0].isIntersecting && hasMore) {
|
||||
onLoadMore();
|
||||
}
|
||||
});
|
||||
|
||||
if (node) observerRef.current.observe(node);
|
||||
},
|
||||
[loading, hasMore, onLoadMore]
|
||||
);
|
||||
|
||||
return loadMoreRef;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 五、API 调用规范
|
||||
|
||||
### 5.1 统一请求封装
|
||||
|
||||
```typescript
|
||||
// lib/api.ts
|
||||
import axios from 'axios';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: process.env.NEXT_PUBLIC_API_URL,
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
// 请求拦截
|
||||
api.interceptors.request.use((config) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
// 响应拦截
|
||||
api.interceptors.response.use(
|
||||
(response) => {
|
||||
const { code, message, data } = response.data;
|
||||
if (code !== 200) {
|
||||
toast.error(message || '请求失败');
|
||||
return Promise.reject(new Error(message));
|
||||
}
|
||||
return data;
|
||||
},
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
// Token 过期,跳转登录
|
||||
window.location.href = '/login';
|
||||
}
|
||||
toast.error('网络错误,请稍后重试');
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export { api };
|
||||
```
|
||||
|
||||
### 5.2 React Query 封装
|
||||
|
||||
```typescript
|
||||
// hooks/useTrafficPool.ts
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { api } from '@/lib/api';
|
||||
|
||||
// 获取流量池列表
|
||||
export function useTrafficPools(page = 1, pageSize = 20) {
|
||||
return useQuery({
|
||||
queryKey: ['trafficPools', page, pageSize],
|
||||
queryFn: () => api.get('/api/v1/traffic-pools', {
|
||||
params: { page, pageSize }
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
// 创建流量池
|
||||
export function useCreateTrafficPool() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (data: CreateTrafficPoolDTO) =>
|
||||
api.post('/api/v1/traffic-pools', data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['trafficPools'] });
|
||||
toast.success('创建成功');
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📱 六、页面模板
|
||||
|
||||
### 6.1 列表页模板
|
||||
|
||||
```tsx
|
||||
// app/(main)/traffic/page.tsx
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { PageContainer } from '@/components/layout/PageContainer';
|
||||
import { ListItem } from '@/components/ui/ListItem';
|
||||
import { ListItemSkeleton } from '@/components/ui/skeleton';
|
||||
import { useTrafficPools } from '@/hooks/useTrafficPool';
|
||||
import { useInfiniteScroll } from '@/hooks/useInfiniteScroll';
|
||||
|
||||
export default function TrafficPoolPage() {
|
||||
const [page, setPage] = useState(1);
|
||||
const { data, isLoading, hasMore } = useTrafficPools(page);
|
||||
|
||||
const loadMoreRef = useInfiniteScroll(
|
||||
() => setPage((p) => p + 1),
|
||||
hasMore,
|
||||
isLoading
|
||||
);
|
||||
|
||||
return (
|
||||
<PageContainer title="流量池" showBack={false}>
|
||||
{/* 搜索栏 */}
|
||||
<div className="sticky top-11 z-40 bg-ios-bg px-4 py-2">
|
||||
<input
|
||||
type="search"
|
||||
placeholder="搜索流量池..."
|
||||
className="w-full h-9 px-4 bg-gray-200 rounded-lg
|
||||
text-[15px] placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 列表区域 */}
|
||||
<div className="mt-2">
|
||||
{isLoading && !data ? (
|
||||
// 首次加载:显示骨架屏
|
||||
[...Array(10)].map((_, i) => <ListItemSkeleton key={i} />)
|
||||
) : (
|
||||
// 数据列表
|
||||
<>
|
||||
{data?.list.map((item) => (
|
||||
<ListItem
|
||||
key={item.id}
|
||||
icon={<Pool className="w-4 h-4" />}
|
||||
title={item.name}
|
||||
subtitle={`${item.count} 条流量`}
|
||||
value={`¥${item.revenue}`}
|
||||
onClick={() => router.push(`/traffic/${item.id}`)}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* 加载更多触发器 */}
|
||||
<div ref={loadMoreRef} className="h-10 flex items-center justify-center">
|
||||
{isLoading && <span className="text-gray-400">加载中...</span>}
|
||||
{!hasMore && <span className="text-gray-400">没有更多了</span>}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 表单页模板
|
||||
|
||||
```tsx
|
||||
// app/(main)/scenarios/new/page.tsx
|
||||
'use client';
|
||||
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
import { PageContainer } from '@/components/layout/PageContainer';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { useCreateScenario } from '@/hooks/useScenario';
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(2, '名称至少2个字符'),
|
||||
description: z.string().optional(),
|
||||
});
|
||||
|
||||
type FormData = z.infer<typeof schema>;
|
||||
|
||||
export default function NewScenarioPage() {
|
||||
const { mutate, isPending } = useCreateScenario();
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<FormData>({
|
||||
resolver: zodResolver(schema),
|
||||
});
|
||||
|
||||
const onSubmit = (data: FormData) => {
|
||||
mutate(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="新建场景"
|
||||
rightAction={
|
||||
<button
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
disabled={isPending}
|
||||
className="text-ios-blue font-medium disabled:opacity-50"
|
||||
>
|
||||
{isPending ? '保存中...' : '保存'}
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<form className="p-4 space-y-6">
|
||||
{/* 场景名称 */}
|
||||
<div>
|
||||
<label className="text-[13px] text-gray-500 mb-2 block">
|
||||
场景名称
|
||||
</label>
|
||||
<Input
|
||||
{...register('name')}
|
||||
placeholder="请输入场景名称"
|
||||
className="h-11"
|
||||
/>
|
||||
{errors.name && (
|
||||
<p className="text-ios-red text-[13px] mt-1">
|
||||
{errors.name.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 场景描述 */}
|
||||
<div>
|
||||
<label className="text-[13px] text-gray-500 mb-2 block">
|
||||
场景描述
|
||||
</label>
|
||||
<textarea
|
||||
{...register('description')}
|
||||
placeholder="请输入场景描述(可选)"
|
||||
className="w-full h-24 px-3 py-2 bg-white border border-gray-200
|
||||
rounded-lg text-[15px] resize-none"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 七、跨目录联动
|
||||
|
||||
### 7.1 上下游关系
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[3、原型] -->|页面结构| B[4、前端]
|
||||
C[5、接口] -->|API定义| B
|
||||
B -->|联调需求| C
|
||||
B -->|部署资源| D[8、部署]
|
||||
```
|
||||
|
||||
### 7.2 联动指令
|
||||
|
||||
```
|
||||
# 基于原型生成组件
|
||||
@联动 原型→前端:基于 [页面结构] 生成 React 组件代码
|
||||
|
||||
# 基于接口生成 Hook
|
||||
@联动 接口→前端:基于 [API文档] 生成 React Query Hook
|
||||
|
||||
# 生成完整页面
|
||||
@联动 全量:基于 [需求+原型+接口] 生成完整页面代码
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🤖 八、AI 协作指令
|
||||
|
||||
### 8.1 角色设定
|
||||
```yaml
|
||||
角色: 前端技术专家
|
||||
风格:
|
||||
- iOS 原生风格,像素级还原
|
||||
- TypeScript 强类型
|
||||
- 组件化、Hook 化
|
||||
输出: 必须包含完整可运行代码
|
||||
检查: 必须包含骨架屏、类型定义、错误处理
|
||||
```
|
||||
|
||||
### 8.2 指令集
|
||||
|
||||
| 指令 | 功能 | 示例 |
|
||||
|:---|:---|:---|
|
||||
| `@生成页面` | 生成完整页面代码 | `@生成页面 流量池列表` |
|
||||
| `@生成组件` | 生成单个组件 | `@生成组件 用户信息卡片` |
|
||||
| `@生成Hook` | 生成自定义 Hook | `@生成Hook 分页加载` |
|
||||
| `@生成类型` | 生成 TypeScript 类型 | `@生成类型 用户信息` |
|
||||
| `@样式优化` | 优化 Tailwind 类名 | `@样式优化 [代码片段]` |
|
||||
| `@性能优化` | 分析性能问题 | `@性能优化 列表渲染` |
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 九、注意事项
|
||||
|
||||
### 9.1 强制规则
|
||||
```yaml
|
||||
必须做:
|
||||
- [ ] 所有数据加载使用 Skeleton 骨架屏
|
||||
- [ ] 所有路由切换有转场动画
|
||||
- [ ] 所有组件使用 TypeScript
|
||||
- [ ] 所有 API 调用封装在 Hook 中
|
||||
- [ ] 所有表单使用 react-hook-form + zod
|
||||
|
||||
禁止做:
|
||||
- [ ] 使用 Spinner/Loading 代替骨架屏
|
||||
- [ ] 硬编码 API 地址
|
||||
- [ ] 使用 any 类型
|
||||
- [ ] 在组件中直接调用 fetch
|
||||
```
|
||||
|
||||
### 9.2 常见问题
|
||||
|
||||
| 问题 | 解决方案 |
|
||||
|:---|:---|
|
||||
| 首屏白屏 | 添加 Skeleton 骨架屏 |
|
||||
| 页面闪烁 | 添加路由转场动画 |
|
||||
| 类型报错 | 完善 TypeScript 类型定义 |
|
||||
| 性能问题 | 使用 React.memo / useMemo |
|
||||
|
||||
---
|
||||
|
||||
> **下一步**: 前端开发完成后,拖入 `5、接口/_智能展开.md` 进行 API 联调
|
||||
63
开发文档/4、前端/前端开发规范.md
Normal file
63
开发文档/4、前端/前端开发规范.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# 神射手数据中台 - 前端开发规范
|
||||
|
||||
> 📅 更新: 2026-01-31
|
||||
|
||||
---
|
||||
|
||||
## 一、技术栈
|
||||
|
||||
| 项目 | 选型 |
|
||||
|:---|:---|
|
||||
| 框架 | Next.js 14 + React 18 |
|
||||
| UI | Radix UI |
|
||||
| 样式 | TailwindCSS |
|
||||
| 图表 | Recharts |
|
||||
| 状态 | React Hooks |
|
||||
|
||||
---
|
||||
|
||||
## 二、视觉规范(苹果毛玻璃)
|
||||
|
||||
| 元素 | 类名 |
|
||||
|:---|:---|
|
||||
| 页面背景 | `from-slate-50 via-blue-50/30 to-purple-50/20` |
|
||||
| 卡片 | `bg-white/80 backdrop-blur rounded-xl shadow-lg` |
|
||||
| 按钮 | `from-blue-500 to-purple-500` |
|
||||
| 分割线 | `border-slate-200/60` |
|
||||
|
||||
---
|
||||
|
||||
## 三、交互规范
|
||||
|
||||
| 要求 | 实现 |
|
||||
|:---|:---|
|
||||
| 骨架屏 | Skeleton 组件,严禁 Spinner |
|
||||
| 转场 | 路由切换动画 |
|
||||
| 数据加载 | useEffect + fetch |
|
||||
|
||||
---
|
||||
|
||||
## 四、目录结构
|
||||
|
||||
```
|
||||
app/
|
||||
├── page.tsx # 首页
|
||||
├── data-ingestion/ # 数据接入
|
||||
├── tag-portrait/ # 标签画像
|
||||
├── ai-agent/ # AI Agent
|
||||
├── data-market/ # 数据市场
|
||||
├── monitoring/ # 系统监控
|
||||
└── api/ # API 路由
|
||||
|
||||
components/
|
||||
└── ui/ # Radix 组件
|
||||
|
||||
lib/
|
||||
└── mongodb.ts # MongoDB 连接
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、关联文档
|
||||
- [核心组件代码.md](./核心组件代码.md)
|
||||
- [../3、原型/原型设计规范.md](../3、原型/原型设计规范.md)
|
||||
119
开发文档/4、前端/核心组件代码.md
Normal file
119
开发文档/4、前端/核心组件代码.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# 神射手 - 核心组件代码
|
||||
|
||||
> 📅 从 app/ 与 components/ 提取 | 2026-01-31
|
||||
|
||||
---
|
||||
|
||||
## 一、首页AI对话核心逻辑
|
||||
|
||||
### 1.1 状态与API调用 (app/page.tsx)
|
||||
|
||||
```tsx
|
||||
// 核心状态
|
||||
const [query, setQuery] = useState("")
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [messages, setMessages] = useState<ChatMessage[]>([])
|
||||
const [aiStatus, setAiStatus] = useState<AIStatus | null>(null)
|
||||
|
||||
// 获取AI状态
|
||||
useEffect(() => {
|
||||
fetch("/api/ai-chat")
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
setAiStatus(data)
|
||||
setStats({ totalUsers: data.database?.totalUsers, ... })
|
||||
})
|
||||
}, [])
|
||||
|
||||
// 发送查询
|
||||
const handleSend = async () => {
|
||||
setLoading(true)
|
||||
const res = await fetch("/api/ai-chat", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ message: query })
|
||||
})
|
||||
const data = await res.json()
|
||||
// 添加 thinking 消息
|
||||
setMessages(prev => [...prev, { role: "thinking", content: data.thinking }])
|
||||
// 添加回复 + portrait
|
||||
setMessages(prev => [...prev, { role: "assistant", content: data.response, portrait: data.portrait }])
|
||||
setLoading(false)
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 用户画像卡片渲染
|
||||
|
||||
```tsx
|
||||
// UserPortrait 卡片结构
|
||||
{msg.portrait && (
|
||||
<Card className="bg-white/80 backdrop-blur">
|
||||
<CardHeader>
|
||||
<CardTitle>👤 用户画像</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||
{msg.portrait.name && <span>姓名: {msg.portrait.name}</span>}
|
||||
{msg.portrait.phone && <span>手机: {msg.portrait.phone}</span>}
|
||||
{msg.portrait.qq && <span>QQ: {msg.portrait.qq}</span>}
|
||||
{msg.portrait.level && <span>等级: {msg.portrait.level}</span>}
|
||||
{msg.portrait.province && <span>地区: {msg.portrait.province}</span>}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、通用模式
|
||||
|
||||
### 2.1 动态数据加载
|
||||
|
||||
```tsx
|
||||
// 各页面通用模式
|
||||
const [data, setData] = useState([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/xxx')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.success) setData(data.xxx)
|
||||
})
|
||||
.finally(() => setLoading(false))
|
||||
}, [])
|
||||
```
|
||||
|
||||
### 2.2 苹果毛玻璃样式
|
||||
|
||||
```tsx
|
||||
// 页面容器
|
||||
className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50/30 to-purple-50/20"
|
||||
|
||||
// 卡片
|
||||
className="bg-white/80 backdrop-blur border-slate-200/60 rounded-xl shadow-lg"
|
||||
|
||||
// 按钮
|
||||
className="bg-gradient-to-r from-blue-500 to-purple-500 hover:from-blue-600 hover:to-purple-600"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、关键组件路径
|
||||
|
||||
| 组件 | 路径 | 用途 |
|
||||
|:---|:---|:---|
|
||||
| Card | @/components/ui/card | 卡片容器 |
|
||||
| Button | @/components/ui/button | 按钮 |
|
||||
| Badge | @/components/ui/badge | 标签 |
|
||||
| Input | @/components/ui/input | 输入框 |
|
||||
| Dialog | @/components/ui/dialog | 弹窗 |
|
||||
| Select | @/components/ui/select | 下拉选择 |
|
||||
|
||||
---
|
||||
|
||||
## 四、关联文档
|
||||
|
||||
- [前端开发规范.md](./前端开发规范.md)
|
||||
- [../3、原型/页面结构.md](../3、原型/页面结构.md)
|
||||
Reference in New Issue
Block a user