TypeScript 综合实战项目
目录
项目一:Todo 应用
项目概述
一个功能完整的 Todo 应用,包含任务的增删改查、分类、优先级、截止日期等功能。
类型定义
// 枚举定义
enum Priority {
Low = 'low',
Medium = 'medium',
High = 'high',
Urgent = 'urgent'
}
enum Status {
Pending = 'pending',
InProgress = 'in-progress',
Completed = 'completed',
Cancelled = 'cancelled'
}
// 基础接口
interface Todo {
id: string;
title: string;
description?: string;
priority: Priority;
status: Status;
category?: string;
tags: string[];
dueDate?: Date;
createdAt: Date;
updatedAt: Date;
completedAt?: Date;
}
interface Category {
id: string;
name: string;
color: string;
icon?: string;
}
interface TodoFilter {
status?: Status[];
priority?: Priority[];
category?: string;
tags?: string[];
searchText?: string;
dueDateFrom?: Date;
dueDateTo?: Date;
}
interface TodoStats {
total: number;
pending: number;
inProgress: number;
completed: number;
cancelled: number;
overdue: number;
}
服务层实现
// Todo 服务接口
interface ITodoService {
// CRUD 操作
createTodo(todo: Omit<Todo, 'id' | 'createdAt' | 'updatedAt'>): Todo;
getTodo(id: string): Todo | undefined;
getAllTodos(): Todo[];
updateTodo(id: string, updates: Partial<Todo>): Todo;
deleteTodo(id: string): boolean;
// 查询操作
filterTodos(filter: TodoFilter): Todo[];
searchTodos(keyword: string): Todo[];
getTodosByCategory(categoryId: string): Todo[];
getTodosByStatus(status: Status): Todo[];
getOverdueTodos(): Todo[];
// 统计操作
getStats(): TodoStats;
// 批量操作
bulkUpdateStatus(ids: string[], status: Status): Todo[];
bulkDelete(ids: string[]): boolean;
}
// Todo 服务实现
class TodoService implements ITodoService {
private todos: Map<string, Todo> = new Map();
private categories: Map<string, Category> = new Map();
createTodo(todoData: Omit<Todo, 'id' | 'createdAt' | 'updatedAt'>): Todo {
const todo: Todo = {
...todoData,
id: this.generateId(),
createdAt: new Date(),
updatedAt: new Date()
};
this.todos.set(todo.id, todo);
return todo;
}
getTodo(id: string): Todo | undefined {
return this.todos.get(id);
}
getAllTodos(): Todo[] {
return Array.from(this.todos.values());
}
updateTodo(id: string, updates: Partial<Todo>): Todo {
const todo = this.todos.get(id);
if (!todo) {
throw new Error(`Todo with id ${id} not found`);
}
const updatedTodo: Todo = {
...todo,
...updates,
updatedAt: new Date()
};
// 如果状态变为已完成,设置完成时间
if (updates.status === Status.Completed && todo.status !== Status.Completed) {
updatedTodo.completedAt = new Date();
}
this.todos.set(id, updatedTodo);
return updatedTodo;
}
deleteTodo(id: string): boolean {
return this.todos.delete(id);
}
filterTodos(filter: TodoFilter): Todo[] {
let todos = this.getAllTodos();
if (filter.status) {
todos = todos.filter(todo => filter.status!.includes(todo.status));
}
if (filter.priority) {
todos = todos.filter(todo => filter.priority!.includes(todo.priority));
}
if (filter.category) {
todos = todos.filter(todo => todo.category === filter.category);
}
if (filter.tags && filter.tags.length > 0) {
todos = todos.filter(todo =>
filter.tags!.some(tag => todo.tags.includes(tag))
);
}
if (filter.searchText) {
const searchLower = filter.searchText.toLowerCase();
todos = todos.filter(todo =>
todo.title.toLowerCase().includes(searchLower) ||
(todo.description && todo.description.toLowerCase().includes(searchLower))
);
}
if (filter.dueDateFrom) {
todos = todos.filter(todo =>
todo.dueDate && todo.dueDate >= filter.dueDateFrom!
);
}
if (filter.dueDateTo) {
todos = todos.filter(todo =>
todo.dueDate && todo.dueDate <= filter.dueDateTo!
);
}
return todos;
}
searchTodos(keyword: string): Todo[] {
return this.filterTodos({ searchText: keyword });
}
getTodosByCategory(categoryId: string): Todo[] {
return this.filterTodos({ category: categoryId });
}
getTodosByStatus(status: Status): Todo[] {
return this.filterTodos({ status: [status] });
}
getOverdueTodos(): Todo[] {
const now = new Date();
return this.getAllTodos().filter(todo =>
todo.status !== Status.Completed &&
todo.status !== Status.Cancelled &&
todo.dueDate &&
todo.dueDate < now
);
}
getStats(): TodoStats {
const todos = this.getAllTodos();
const overdueTodos = this.getOverdueTodos();
return {
total: todos.length,
pending: todos.filter(t => t.status === Status.Pending).length,
inProgress: todos.filter(t => t.status === Status.InProgress).length,
completed: todos.filter(t => t.status === Status.Completed).length,
cancelled: todos.filter(t => t.status === Status.Cancelled).length,
overdue: overdueTodos.length
};
}
bulkUpdateStatus(ids: string[], status: Status): Todo[] {
return ids.map(id => this.updateTodo(id, { status }));
}
bulkDelete(ids: string[]): boolean {
let allDeleted = true;
ids.forEach(id => {
if (!this.deleteTodo(id)) {
allDeleted = false;
}
});
return allDeleted;
}
private generateId(): string {
return `todo_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
}
使用示例
// 创建 Todo 服务实例
const todoService = new TodoService();
// 创建 Todo
const todo1 = todoService.createTodo({
title: '学习 TypeScript',
description: '完成 TypeScript 学习资料的阅读',
priority: Priority.High,
status: Status.InProgress,
tags: ['学习', '编程'],
dueDate: new Date('2024-12-31')
});
const todo2 = todoService.createTodo({
title: '完成项目文档',
priority: Priority.Medium,
status: Status.Pending,
tags: ['工作', '文档'],
dueDate: new Date('2024-12-15')
});
// 查询 Todo
const allTodos = todoService.getAllTodos();
console.log('所有任务:', allTodos);
// 过滤 Todo
const highPriorityTodos = todoService.filterTodos({
priority: [Priority.High, Priority.Urgent]
});
console.log('高优先级任务:', highPriorityTodos);
// 搜索 Todo
const searchResults = todoService.searchTodos('TypeScript');
console.log('搜索结果:', searchResults);
// 获取统计信息
const stats = todoService.getStats();
console.log('任务统计:', stats);
// 更新 Todo
todoService.updateTodo(todo1.id, {
status: Status.Completed
});
// 获取逾期任务
const overdueTodos = todoService.getOverdueTodos();
console.log('逾期任务:', overdueTodos);
// 批量更新状态
todoService.bulkUpdateStatus([todo1.id, todo2.id], Status.Completed);
项目二:用户管理系统
项目概述
一个完整的用户管理系统,包含用户认证、角色权限、用户资料管理等功能。
类型定义
// 用户角色和权限
enum UserRole {
SuperAdmin = 'super-admin',
Admin = 'admin',
Manager = 'manager',
User = 'user',
Guest = 'guest'
}
enum Permission {
// 用户管理
UserRead = 'user:read',
UserWrite = 'user:write',
UserDelete = 'user:delete',
// 角色管理
RoleRead = 'role:read',
RoleWrite = 'role:write',
RoleDelete = 'role:delete',
// 系统管理
SystemConfig = 'system:config',
SystemLogs = 'system:logs',
// 内容管理
ContentRead = 'content:read',
ContentWrite = 'content:write',
ContentPublish = 'content:publish',
ContentDelete = 'content:delete'
}
// 用户接口
interface User {
id: number;
username: string;
email: string;
passwordHash: string;
role: UserRole;
profile: UserProfile;
status: UserStatus;
permissions: Permission[];
createdAt: Date;
updatedAt: Date;
lastLoginAt?: Date;
}
interface UserProfile {
firstName: string;
lastName: string;
avatar?: string;
phone?: string;
bio?: string;
address?: Address;
}
interface Address {
street: string;
city: string;
state: string;
zipCode: string;
country: string;
}
enum UserStatus {
Active = 'active',
Inactive = 'inactive',
Suspended = 'suspended',
Deleted = 'deleted'
}
// 认证相关
interface AuthCredentials {
username: string;
password: string;
}
interface AuthToken {
accessToken: string;
refreshToken: string;
expiresIn: number;
tokenType: 'Bearer';
}
interface AuthSession {
userId: number;
token: string;
expiresAt: Date;
createdAt: Date;
}
// 注册数据
interface RegisterData {
username: string;
email: string;
password: string;
firstName: string;
lastName: string;
}
// 用户查询过滤
interface UserFilter {
role?: UserRole[];
status?: UserStatus[];
searchText?: string;
createdFrom?: Date;
createdTo?: Date;
}
认证服务实现
// 认证服务接口
interface IAuthService {
register(data: RegisterData): Promise<User>;
login(credentials: AuthCredentials): Promise<{ user: User; token: AuthToken }>;
logout(userId: number): Promise<void>;
refreshToken(refreshToken: string): Promise<AuthToken>;
verifyToken(token: string): Promise<User | null>;
changePassword(userId: number, oldPassword: string, newPassword: string): Promise<boolean>;
resetPassword(email: string): Promise<boolean>;
}
// 认证服务实现
class AuthService implements IAuthService {
private users: Map<number, User> = new Map();
private sessions: Map<string, AuthSession> = new Map();
private nextUserId = 1;
async register(data: RegisterData): Promise<User> {
// 检查用户名是否已存在
const existingUser = Array.from(this.users.values()).find(
u => u.username === data.username || u.email === data.email
);
if (existingUser) {
throw new Error('用户名或邮箱已存在');
}
// 创建新用户
const user: User = {
id: this.nextUserId++,
username: data.username,
email: data.email,
passwordHash: await this.hashPassword(data.password),
role: UserRole.User,
profile: {
firstName: data.firstName,
lastName: data.lastName
},
status: UserStatus.Active,
permissions: this.getDefaultPermissions(UserRole.User),
createdAt: new Date(),
updatedAt: new Date()
};
this.users.set(user.id, user);
return this.sanitizeUser(user);
}
async login(credentials: AuthCredentials): Promise<{ user: User; token: AuthToken }> {
// 查找用户
const user = Array.from(this.users.values()).find(
u => u.username === credentials.username
);
if (!user) {
throw new Error('用户名或密码错误');
}
// 验证密码
const isValidPassword = await this.verifyPassword(
credentials.password,
user.passwordHash
);
if (!isValidPassword) {
throw new Error('用户名或密码错误');
}
// 检查用户状态
if (user.status !== UserStatus.Active) {
throw new Error('用户账号已被禁用');
}
// 生成令牌
const token = this.generateToken(user);
// 创建会话
const session: AuthSession = {
userId: user.id,
token: token.accessToken,
expiresAt: new Date(Date.now() + token.expiresIn * 1000),
createdAt: new Date()
};
this.sessions.set(token.accessToken, session);
// 更新最后登录时间
user.lastLoginAt = new Date();
user.updatedAt = new Date();
return {
user: this.sanitizeUser(user),
token
};
}
async logout(userId: number): Promise<void> {
// 删除用户的所有会话
for (const [token, session] of this.sessions.entries()) {
if (session.userId === userId) {
this.sessions.delete(token);
}
}
}
async refreshToken(refreshToken: string): Promise<AuthToken> {
// 验证刷新令牌并生成新的访问令牌
const session = this.sessions.get(refreshToken);
if (!session || session.expiresAt < new Date()) {
throw new Error('刷新令牌无效或已过期');
}
const user = this.users.get(session.userId);
if (!user) {
throw new Error('用户不存在');
}
return this.generateToken(user);
}
async verifyToken(token: string): Promise<User | null> {
const session = this.sessions.get(token);
if (!session || session.expiresAt < new Date()) {
return null;
}
const user = this.users.get(session.userId);
return user ? this.sanitizeUser(user) : null;
}
async changePassword(
userId: number,
oldPassword: string,
newPassword: string
): Promise<boolean> {
const user = this.users.get(userId);
if (!user) {
throw new Error('用户不存在');
}
const isValidPassword = await this.verifyPassword(
oldPassword,
user.passwordHash
);
if (!isValidPassword) {
throw new Error('当前密码错误');
}
user.passwordHash = await this.hashPassword(newPassword);
user.updatedAt = new Date();
// 使所有会话失效
await this.logout(userId);
return true;
}
async resetPassword(email: string): Promise<boolean> {
const user = Array.from(this.users.values()).find(
u => u.email === email
);
if (!user) {
// 为了安全,即使用户不存在也返回 true
return true;
}
// 在实际应用中,这里会发送重置密码的邮件
console.log(`发送密码重置邮件到: ${email}`);
return true;
}
private async hashPassword(password: string): Promise<string> {
// 实际应用中应使用 bcrypt 等安全的哈希算法
return `hashed_${password}`;
}
private async verifyPassword(password: string, hash: string): Promise<boolean> {
// 实际应用中应使用 bcrypt 验证
return hash === `hashed_${password}`;
}
private generateToken(user: User): AuthToken {
const accessToken = `access_${user.id}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const refreshToken = `refresh_${user.id}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
return {
accessToken,
refreshToken,
expiresIn: 3600, // 1 hour
tokenType: 'Bearer'
};
}
private getDefaultPermissions(role: UserRole): Permission[] {
const permissionMap: Record<UserRole, Permission[]> = {
[UserRole.SuperAdmin]: Object.values(Permission),
[UserRole.Admin]: [
Permission.UserRead,
Permission.UserWrite,
Permission.RoleRead,
Permission.ContentRead,
Permission.ContentWrite,
Permission.ContentPublish
],
[UserRole.Manager]: [
Permission.UserRead,
Permission.ContentRead,
Permission.ContentWrite,
Permission.ContentPublish
],
[UserRole.User]: [
Permission.ContentRead
],
[UserRole.Guest]: [
Permission.ContentRead
]
};
return permissionMap[role] || [];
}
private sanitizeUser(user: User): User {
// 移除敏感信息
const { passwordHash, ...sanitized } = user;
return sanitized as User;
}
}
用户服务实现
// 用户服务接口
interface IUserService {
getUser(id: number): User | undefined;
getAllUsers(): User[];
updateUser(id: number, updates: Partial<User>): User;
deleteUser(id: number): boolean;
filterUsers(filter: UserFilter): User[];
updateUserRole(id: number, role: UserRole): User;
updateUserStatus(id: number, status: UserStatus): User;
grantPermissions(id: number, permissions: Permission[]): User;
revokePermissions(id: number, permissions: Permission[]): User;
hasPermission(userId: number, permission: Permission): boolean;
}
// 用户服务实现
class UserService implements IUserService {
constructor(private authService: AuthService) {}
getUser(id: number): User | undefined {
// 实现从 authService 获取用户
return undefined;
}
getAllUsers(): User[] {
// 实现获取所有用户
return [];
}
updateUser(id: number, updates: Partial<User>): User {
const user = this.getUser(id);
if (!user) {
throw new Error('用户不存在');
}
// 不允许直接更新敏感字段
const { passwordHash, id: _, ...allowedUpdates } = updates as any;
const updatedUser = {
...user,
...allowedUpdates,
updatedAt: new Date()
};
return updatedUser;
}
deleteUser(id: number): boolean {
const user = this.getUser(id);
if (!user) {
return false;
}
// 软删除:更改用户状态
this.updateUserStatus(id, UserStatus.Deleted);
return true;
}
filterUsers(filter: UserFilter): User[] {
let users = this.getAllUsers();
if (filter.role) {
users = users.filter(u => filter.role!.includes(u.role));
}
if (filter.status) {
users = users.filter(u => filter.status!.includes(u.status));
}
if (filter.searchText) {
const searchLower = filter.searchText.toLowerCase();
users = users.filter(u =>
u.username.toLowerCase().includes(searchLower) ||
u.email.toLowerCase().includes(searchLower) ||
u.profile.firstName.toLowerCase().includes(searchLower) ||
u.profile.lastName.toLowerCase().includes(searchLower)
);
}
return users;
}
updateUserRole(id: number, role: UserRole): User {
return this.updateUser(id, { role });
}
updateUserStatus(id: number, status: UserStatus): User {
return this.updateUser(id, { status });
}
grantPermissions(id: number, permissions: Permission[]): User {
const user = this.getUser(id);
if (!user) {
throw new Error('用户不存在');
}
const newPermissions = Array.from(
new Set([...user.permissions, ...permissions])
);
return this.updateUser(id, { permissions: newPermissions });
}
revokePermissions(id: number, permissions: Permission[]): User {
const user = this.getUser(id);
if (!user) {
throw new Error('用户不存在');
}
const newPermissions = user.permissions.filter(
p => !permissions.includes(p)
);
return this.updateUser(id, { permissions: newPermissions });
}
hasPermission(userId: number, permission: Permission): boolean {
const user = this.getUser(userId);
if (!user || user.status !== UserStatus.Active) {
return false;
}
return user.permissions.includes(permission);
}
}
使用示例
// 创建服务实例
const authService = new AuthService();
const userService = new UserService(authService);
// 用户注册
async function registerExample() {
try {
const newUser = await authService.register({
username: 'zhangsan',
email: 'zhangsan@example.com',
password: 'Password123!',
firstName: '张',
lastName: '三'
});
console.log('注册成功:', newUser);
} catch (error) {
console.error('注册失败:', error);
}
}
// 用户登录
async function loginExample() {
try {
const result = await authService.login({
username: 'zhangsan',
password: 'Password123!'
});
console.log('登录成功:', result.user);
console.log('访问令牌:', result.token.accessToken);
// 保存令牌以供后续请求使用
localStorage.setItem('accessToken', result.token.accessToken);
} catch (error) {
console.error('登录失败:', error);
}
}
// 验证令牌
async function verifyTokenExample() {
const token = localStorage.getItem('accessToken');
if (!token) {
console.log('未登录');
return;
}
const user = await authService.verifyToken(token);
if (user) {
console.log('当前用户:', user);
} else {
console.log('令牌无效');
localStorage.removeItem('accessToken');
}
}
// 权限检查
function checkPermissionExample(userId: number) {
const canWrite = userService.hasPermission(userId, Permission.ContentWrite);
const canDelete = userService.hasPermission(userId, Permission.ContentDelete);
console.log('是否有写权限:', canWrite);
console.log('是否有删除权限:', canDelete);
}
项目三:电商购物车
项目概述
一个功能完整的电商购物车系统,包含商品管理、购物车、订单、支付等功能。
类型定义
// 商品相关
interface Product {
id: string;
name: string;
description: string;
price: number;
originalPrice?: number;
category: Category;
images: string[];
stock: number;
sku: string;
attributes: ProductAttribute[];
rating: number;
reviewCount: number;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
interface Category {
id: string;
name: string;
slug: string;
parent?: Category;
children?: Category[];
}
interface ProductAttribute {
name: string;
value: string;
}
// 购物车相关
interface CartItem {
productId: string;
product: Product;
quantity: number;
selectedAttributes?: ProductAttribute[];
addedAt: Date;
}
interface Cart {
id: string;
userId: string;
items: CartItem[];
subtotal: number;
discount: number;
tax: number;
shipping: number;
total: number;
createdAt: Date;
updatedAt: Date;
}
// 订单相关
enum OrderStatus {
Pending = 'pending',
Confirmed = 'confirmed',
Processing = 'processing',
Shipped = 'shipped',
Delivered = 'delivered',
Cancelled = 'cancelled',
Refunded = 'refunded'
}
enum PaymentStatus {
Pending = 'pending',
Authorized = 'authorized',
Paid = 'paid',
Failed = 'failed',
Refunded = 'refunded'
}
enum PaymentMethod {
CreditCard = 'credit-card',
DebitCard = 'debit-card',
PayPal = 'paypal',
Alipay = 'alipay',
WeChatPay = 'wechat-pay',
BankTransfer = 'bank-transfer'
}
interface Order {
id: string;
orderNumber: string;
userId: string;
items: OrderItem[];
subtotal: number;
discount: number;
tax: number;
shipping: number;
total: number;
status: OrderStatus;
paymentStatus: PaymentStatus;
paymentMethod: PaymentMethod;
shippingAddress: Address;
billingAddress: Address;
notes?: string;
createdAt: Date;
updatedAt: Date;
paidAt?: Date;
shippedAt?: Date;
deliveredAt?: Date;
}
interface OrderItem {
productId: string;
productName: string;
productImage: string;
quantity: number;
price: number;
subtotal: number;
selectedAttributes?: ProductAttribute[];
}
// 优惠券
interface Coupon {
id: string;
code: string;
type: 'percentage' | 'fixed';
value: number;
minPurchase?: number;
maxDiscount?: number;
expiresAt?: Date;
usageLimit?: number;
usageCount: number;
isActive: boolean;
}
购物车服务实现
interface ICartService {
getCart(userId: string): Cart;
addItem(userId: string, productId: string, quantity: number): Cart;
updateItemQuantity(userId: string, productId: string, quantity: number): Cart;
removeItem(userId: string, productId: string): Cart;
clearCart(userId: string): Cart;
applyCoupon(userId: string, couponCode: string): Cart;
removeCoupon(userId: string): Cart;
calculateTotals(cart: Cart): Cart;
}
class CartService implements ICartService {
private carts: Map<string, Cart> = new Map();
private products: Map<string, Product> = new Map();
private coupons: Map<string, Coupon> = new Map();
getCart(userId: string): Cart {
let cart = this.carts.get(userId);
if (!cart) {
cart = {
id: this.generateId(),
userId,
items: [],
subtotal: 0,
discount: 0,
tax: 0,
shipping: 0,
total: 0,
createdAt: new Date(),
updatedAt: new Date()
};
this.carts.set(userId, cart);
}
return cart;
}
addItem(userId: string, productId: string, quantity: number): Cart {
const cart = this.getCart(userId);
const product = this.products.get(productId);
if (!product) {
throw new Error('商品不存在');
}
if (!product.isActive) {
throw new Error('商品已下架');
}
if (product.stock < quantity) {
throw new Error(`库存不足,当前库存:${product.stock}`);
}
// 检查商品是否已在购物车中
const existingItem = cart.items.find(item => item.productId === productId);
if (existingItem) {
// 更新数量
const newQuantity = existingItem.quantity + quantity;
if (product.stock < newQuantity) {
throw new Error(`库存不足,当前库存:${product.stock}`);
}
existingItem.quantity = newQuantity;
} else {
// 添加新商品
const cartItem: CartItem = {
productId,
product,
quantity,
addedAt: new Date()
};
cart.items.push(cartItem);
}
cart.updatedAt = new Date();
return this.calculateTotals(cart);
}
updateItemQuantity(userId: string, productId: string, quantity: number): Cart {
const cart = this.getCart(userId);
const item = cart.items.find(item => item.productId === productId);
if (!item) {
throw new Error('购物车中没有该商品');
}
if (quantity <= 0) {
return this.removeItem(userId, productId);
}
if (item.product.stock < quantity) {
throw new Error(`库存不足,当前库存:${item.product.stock}`);
}
item.quantity = quantity;
cart.updatedAt = new Date();
return this.calculateTotals(cart);
}
removeItem(userId: string, productId: string): Cart {
const cart = this.getCart(userId);
const index = cart.items.findIndex(item => item.productId === productId);
if (index > -1) {
cart.items.splice(index, 1);
cart.updatedAt = new Date();
}
return this.calculateTotals(cart);
}
clearCart(userId: string): Cart {
const cart = this.getCart(userId);
cart.items = [];
cart.updatedAt = new Date();
return this.calculateTotals(cart);
}
applyCoupon(userId: string, couponCode: string): Cart {
const cart = this.getCart(userId);
const coupon = Array.from(this.coupons.values()).find(
c => c.code === couponCode && c.isActive
);
if (!coupon) {
throw new Error('优惠券无效');
}
if (coupon.expiresAt && coupon.expiresAt < new Date()) {
throw new Error('优惠券已过期');
}
if (coupon.usageLimit && coupon.usageCount >= coupon.usageLimit) {
throw new Error('优惠券已达到使用次数上限');
}
if (coupon.minPurchase && cart.subtotal < coupon.minPurchase) {
throw new Error(`最低消费金额为 ${coupon.minPurchase} 元`);
}
// 计算折扣
let discount = 0;
if (coupon.type === 'percentage') {
discount = cart.subtotal * (coupon.value / 100);
} else {
discount = coupon.value;
}
if (coupon.maxDiscount && discount > coupon.maxDiscount) {
discount = coupon.maxDiscount;
}
cart.discount = discount;
cart.updatedAt = new Date();
return this.calculateTotals(cart);
}
removeCoupon(userId: string): Cart {
const cart = this.getCart(userId);
cart.discount = 0;
cart.updatedAt = new Date();
return this.calculateTotals(cart);
}
calculateTotals(cart: Cart): Cart {
// 计算小计
cart.subtotal = cart.items.reduce((sum, item) => {
return sum + (item.product.price * item.quantity);
}, 0);
// 计算税费(假设税率为 10%)
cart.tax = (cart.subtotal - cart.discount) * 0.1;
// 计算运费(假设满 100 免运费)
cart.shipping = cart.subtotal >= 100 ? 0 : 10;
// 计算总价
cart.total = cart.subtotal - cart.discount + cart.tax + cart.shipping;
return cart;
}
private generateId(): string {
return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
}
订单服务实现
interface IOrderService {
createOrder(userId: string, shippingAddress: Address, paymentMethod: PaymentMethod): Order;
getOrder(orderId: string): Order | undefined;
getUserOrders(userId: string): Order[];
updateOrderStatus(orderId: string, status: OrderStatus): Order;
cancelOrder(orderId: string): Order;
processPayment(orderId: string, paymentDetails: any): Order;
refundOrder(orderId: string): Order;
}
class OrderService implements IOrderService {
private orders: Map<string, Order> = new Map();
constructor(private cartService: CartService) {}
createOrder(
userId: string,
shippingAddress: Address,
paymentMethod: PaymentMethod
): Order {
const cart = this.cartService.getCart(userId);
if (cart.items.length === 0) {
throw new Error('购物车为空');
}
// 验证库存
for (const item of cart.items) {
if (item.product.stock < item.quantity) {
throw new Error(`商品 ${item.product.name} 库存不足`);
}
}
// 创建订单项
const orderItems: OrderItem[] = cart.items.map(item => ({
productId: item.productId,
productName: item.product.name,
productImage: item.product.images[0],
quantity: item.quantity,
price: item.product.price,
subtotal: item.product.price * item.quantity,
selectedAttributes: item.selectedAttributes
}));
// 生成订单号
const orderNumber = this.generateOrderNumber();
// 创建订单
const order: Order = {
id: this.generateId(),
orderNumber,
userId,
items: orderItems,
subtotal: cart.subtotal,
discount: cart.discount,
tax: cart.tax,
shipping: cart.shipping,
total: cart.total,
status: OrderStatus.Pending,
paymentStatus: PaymentStatus.Pending,
paymentMethod,
shippingAddress,
billingAddress: shippingAddress, // 简化处理,使用相同地址
createdAt: new Date(),
updatedAt: new Date()
};
this.orders.set(order.id, order);
// 清空购物车
this.cartService.clearCart(userId);
// 更新库存
for (const item of cart.items) {
item.product.stock -= item.quantity;
}
return order;
}
getOrder(orderId: string): Order | undefined {
return this.orders.get(orderId);
}
getUserOrders(userId: string): Order[] {
return Array.from(this.orders.values())
.filter(order => order.userId === userId)
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
}
updateOrderStatus(orderId: string, status: OrderStatus): Order {
const order = this.orders.get(orderId);
if (!order) {
throw new Error('订单不存在');
}
order.status = status;
order.updatedAt = new Date();
// 更新特定状态的时间戳
if (status === OrderStatus.Shipped) {
order.shippedAt = new Date();
} else if (status === OrderStatus.Delivered) {
order.deliveredAt = new Date();
}
return order;
}
cancelOrder(orderId: string): Order {
const order = this.orders.get(orderId);
if (!order) {
throw new Error('订单不存在');
}
// 只有待处理和已确认的订单可以取消
if (![OrderStatus.Pending, OrderStatus.Confirmed].includes(order.status)) {
throw new Error('当前订单状态不允许取消');
}
order.status = OrderStatus.Cancelled;
order.updatedAt = new Date();
// 恢复库存
// 实际应用中需要从产品服务获取产品并更新库存
return order;
}
processPayment(orderId: string, paymentDetails: any): Order {
const order = this.orders.get(orderId);
if (!order) {
throw new Error('订单不存在');
}
if (order.paymentStatus !== PaymentStatus.Pending) {
throw new Error('订单已支付或支付失败');
}
try {
// 实际应用中这里会调用支付网关 API
// 模拟支付成功
order.paymentStatus = PaymentStatus.Paid;
order.paidAt = new Date();
order.status = OrderStatus.Confirmed;
order.updatedAt = new Date();
return order;
} catch (error) {
order.paymentStatus = PaymentStatus.Failed;
order.updatedAt = new Date();
throw new Error('支付失败');
}
}
refundOrder(orderId: string): Order {
const order = this.orders.get(orderId);
if (!order) {
throw new Error('订单不存在');
}
if (order.paymentStatus !== PaymentStatus.Paid) {
throw new Error('订单未支付,无法退款');
}
if (order.status === OrderStatus.Delivered) {
// 可以添加退款申请审核流程
}
order.paymentStatus = PaymentStatus.Refunded;
order.status = OrderStatus.Refunded;
order.updatedAt = new Date();
// 恢复库存
// 实际应用中需要从产品服务获取产品并更新库存
return order;
}
private generateId(): string {
return `order_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
private generateOrderNumber(): string {
const date = new Date();
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const random = Math.random().toString(36).substr(2, 6).toUpperCase();
return `${year}${month}${day}${random}`;
}
}
使用示例
// 创建服务实例
const cartService = new CartService();
const orderService = new OrderService(cartService);
// 添加商品到购物车
const userId = 'user123';
cartService.addItem(userId, 'product1', 2);
cartService.addItem(userId, 'product2', 1);
// 查看购物车
const cart = cartService.getCart(userId);
console.log('购物车:', cart);
// 应用优惠券
cartService.applyCoupon(userId, 'SAVE10');
// 创建订单
const shippingAddress: Address = {
street: '长安街1号',
city: '北京',
state: '北京市',
zipCode: '100000',
country: '中国'
};
const order = orderService.createOrder(
userId,
shippingAddress,
PaymentMethod.Alipay
);
console.log('订单创建成功:', order.orderNumber);
// 处理支付
const paidOrder = orderService.processPayment(order.id, {
// 支付详情
});
console.log('支付成功:', paidOrder);
// 查询用户订单
const userOrders = orderService.getUserOrders(userId);
console.log('用户订单:', userOrders);
总结
通过这些综合实战项目,您可以学习到:
1. Todo 应用
- 基础的 CRUD 操作
- 枚举类型的使用
- 数据过滤和查询
- 统计功能实现
2. 用户管理系统
- 用户认证和授权
- 角色和权限管理
- 会话管理
- 密码加密和验证
3. 电商购物车
- 购物车状态管理
- 订单处理流程
- 支付集成
- 库存管理
关键学习点
- 类型安全:利用 TypeScript 的类型系统确保代码安全
- 接口设计:定义清晰的服务接口
- 错误处理:完善的错误检查和异常处理
- 业务逻辑:复杂业务场景的实现
- 数据验证:输入验证和状态检查
- 代码组织:合理的代码结构和模块划分
实践建议
- 逐步实现:从简单功能开始,逐步增加复杂度
- 测试驱动:为关键功能编写单元测试
- 重构优化:不断改进代码质量
- 实际应用:将这些模式应用到真实项目中
- 扩展功能:根据需求添加更多功能
这些项目涵盖了大多数实际应用中常见的场景,掌握这些模式将帮助您更好地使用 TypeScript 开发复杂的应用程序。