在前端实现中,我使用了以下技术栈和库:
- 主要框架和语言:
- React 18
- TypeScript
- Tailwind CSS(用于样式)
- 图表和UI组件:
- Recharts(用于统计图表)
- shadcn/ui(一些基础UI组件)
- 工具库:
- lodash (工具函数)
- lucide-react (图标库)
让我们基于这些技术栈创建一个更清晰的项目结构:
// package.json
{
"name": "integral-system-frontend",
"version": "1.0.0",
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"recharts": "^2.12.0",
"lucide-react": "^0.263.1",
"lodash": "^4.17.21",
"@radix-ui/react-alert-dialog": "^1.0.5",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"tailwind-merge": "^2.2.0",
"tailwindcss": "^3.4.0",
"typescript": "^5.3.0"
}
}
// src/
// ├── components/
// │ ├── ui/ # shadcn/ui components
// │ ├── layout/ # Layout components
// │ └── integral/ # Integral specific components
// ├── hooks/ # Custom hooks
// ├── services/ # API services
// ├── types/ # TypeScript types
// ├── utils/ # Utility functions
// └── pages/ # Page components
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"lib": ["DOM", "DOM.Iterable", "ES2020"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
},
},
},
plugins: [],
}
// src/components/integral/IntegralCard.tsx
import React from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Coins } from 'lucide-react';
interface IntegralCardProps {
total: number;
nearestExpire?: {
points: number;
date: string;
};
}
export const IntegralCard: React.FC<IntegralCardProps> = ({ total, nearestExpire }) => (
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">可用积分</CardTitle>
<Coins className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{total}</div>
{nearestExpire && (
<p className="text-xs text-muted-foreground">
{nearestExpire.points} 积分将于 {nearestExpire.date} 过期
</p>
)}
</CardContent>
</Card>
);
// src/components/integral/IntegralHistory.tsx
import React from 'react';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { IntegralDetail } from '@/types';
import { formatDate } from '@/utils/date';
interface IntegralHistoryProps {
details: IntegralDetail[];
loading?: boolean;
}
export const IntegralHistory: React.FC<IntegralHistoryProps> = ({
details,
loading = false,
}) => {
if (loading) {
return (
<div className="w-full h-64 flex items-center justify-center">
<span className="loading loading-spinner loading-lg" />
</div>
);
}
return (
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead>时间</TableHead>
<TableHead>类型</TableHead>
<TableHead className="text-right">积分变动</TableHead>
<TableHead>过期时间</TableHead>
<TableHead>说明</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{details.map((detail) => (
<TableRow key={detail.id}>
<TableCell>{formatDate(detail.createTime)}</TableCell>
<TableCell>{getIntegralTypeName(detail.type)}</TableCell>
<TableCell className={`text-right ${
detail.value > 0 ? 'text-green-600' : 'text-red-600'
}`}>
{detail.value > 0 ? '+' : ''}{detail.value}
</TableCell>
<TableCell>{formatDate(detail.expireTime)}</TableCell>
<TableCell>{detail.reason}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
);
};
// src/components/integral/IntegralUseDialog.tsx
import React, { useState } from 'react';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { Input } from '@/components/ui/input';
interface IntegralUseDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onConfirm: (points: number) => void;
maxPoints: number;
}
export const IntegralUseDialog: React.FC<IntegralUseDialogProps> = ({
open,
onOpenChange,
onConfirm,
maxPoints,
}) => {
const [points, setPoints] = useState('');
const [error, setError] = useState('');
const handleConfirm = () => {
const pointsNum = Number(points);
if (!pointsNum || pointsNum <= 0) {
setError('请输入有效的积分数');
return;
}
if (pointsNum > maxPoints) {
setError('积分不足');
return;
}
onConfirm(pointsNum);
setPoints('');
setError('');
};
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>使用积分</AlertDialogTitle>
<AlertDialogDescription>
当前可用积分: {maxPoints}
</AlertDialogDescription>
</AlertDialogHeader>
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Input
type="number"
value={points}
onChange={(e) => {
setPoints(e.target.value);
setError('');
}}
placeholder="请输入使用积分数"
min={1}
max={maxPoints}
/>
{error && (
<p className="text-sm text-red-500">{error}</p>
)}
</div>
</div>
<AlertDialogFooter>
<AlertDialogCancel>取消</AlertDialogCancel>
<AlertDialogAction onClick={handleConfirm}>
确认使用
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};
这些组件使用了 shadcn/ui 的基础组件,并结合了 Tailwind CSS 进行样式设计。主要特点:
- 使用了 TypeScript 进行类型检查
- 采用函数式组件和 React Hooks
- 使用 Tailwind CSS 进行样式管理
- 集成了 shadcn/ui 的组件库
- 使用 Lucide React 图标库
- 响应式设计
要使用这个项目,需要:
- 初始化项目:
npx create-next-app@latest my-integral-system --typescript --tailwind
- 安装依赖:
npm install @radix-ui/react-alert-dialog class-variance-authority clsx
npm install tailwind-merge lucide-react recharts lodash
- 安装和配置 shadcn/ui:
npx shadcn-ui@latest init
- 安装需要的组件:
npx shadcn-ui@latest add card table alert-dialog input button
这样就可以开始使用这些组件了。整个项目采用了现代化的前端技术栈,具有良好的类型支持和组件复用性。需要注意的是使用 Tailwind CSS 时要遵循其预定义的类名,避免使用自定义值。

504

被折叠的 条评论
为什么被折叠?



