初始化参股

This commit is contained in:
2026-01-27 18:06:04 +08:00
commit 2774a539bf
254 changed files with 33255 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
import { IsNotEmpty, IsString, IsOptional, IsEnum, IsNumber, IsArray, Min } from 'class-validator';
import { Type } from 'class-transformer';
export class CreateServiceDto {
@IsNotEmpty({ message: '服务名称不能为空' })
@IsString()
name: string;
@IsNotEmpty({ message: '服务描述不能为空' })
@IsString()
description: string;
@IsEnum(['fabric', 'leather', 'cleaning', 'repair', 'custom'])
type: string;
@IsNotEmpty({ message: '基础价格不能为空' })
@Type(() => Number)
@IsNumber()
@Min(0)
basePrice: number;
@IsOptional()
@IsArray()
images?: string[];
@IsOptional()
@IsArray()
features?: string[];
@IsOptional()
@Type(() => Number)
@IsNumber()
@Min(1)
estimatedDays?: number;
@IsOptional()
@Type(() => Number)
@IsNumber()
sortOrder?: number;
}
export class UpdateServiceDto {
@IsOptional()
@IsString()
name?: string;
@IsOptional()
@IsString()
description?: string;
@IsOptional()
@IsEnum(['fabric', 'leather', 'cleaning', 'repair', 'custom'])
type?: string;
@IsOptional()
@Type(() => Number)
@IsNumber()
@Min(0)
basePrice?: number;
@IsOptional()
@IsArray()
images?: string[];
@IsOptional()
@IsArray()
features?: string[];
@IsOptional()
@Type(() => Number)
@IsNumber()
@Min(1)
estimatedDays?: number;
@IsOptional()
@IsEnum(['active', 'inactive'])
status?: string;
@IsOptional()
@Type(() => Number)
@IsNumber()
sortOrder?: number;
}
export class QueryServiceDto {
@IsOptional()
@IsEnum(['fabric', 'leather', 'cleaning', 'repair', 'custom'])
type?: string;
@IsOptional()
@IsEnum(['active', 'inactive'])
status?: string = 'active';
}

View File

@@ -0,0 +1,98 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, Query, UseGuards } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiQuery } from '@nestjs/swagger';
import { ServiceService } from './service.service';
import { CreateServiceDto, UpdateServiceDto, QueryServiceDto } from './dto/service.dto';
import { Roles } from '../auth/guards/roles.decorator';
import { Public } from '../auth/guards/public.decorator';
@ApiTags('服务管理')
@Controller('services')
export class ServiceController {
constructor(private readonly serviceService: ServiceService) {}
@ApiBearerAuth()
@Post()
@Roles('admin')
@ApiOperation({ summary: '创建服务(管理员)' })
@ApiResponse({ status: 201, description: '创建成功' })
@ApiResponse({ status: 409, description: '服务类型已存在' })
create(@Body() createServiceDto: CreateServiceDto) {
return this.serviceService.create(createServiceDto);
}
@Public()
@Get()
@ApiOperation({ summary: '获取服务列表' })
@ApiQuery({ name: 'type', required: false, enum: ['fabric', 'leather', 'cleaning', 'repair', 'custom'] })
@ApiQuery({ name: 'status', required: false, enum: ['active', 'inactive'], description: '默认为active' })
@ApiResponse({ status: 200, description: '获取成功' })
findAll(@Query() query: QueryServiceDto) {
return this.serviceService.findAll(query);
}
@Public()
@Get('active')
@ApiOperation({ summary: '获取所有有效服务' })
@ApiResponse({ status: 200, description: '获取成功' })
getActiveServices() {
return this.serviceService.getActiveServices();
}
@Public()
@Get(':id')
@ApiOperation({ summary: '根据ID获取服务详情' })
@ApiResponse({ status: 200, description: '获取成功' })
@ApiResponse({ status: 404, description: '服务不存在' })
findOne(@Param('id') id: string) {
return this.serviceService.findOne(+id);
}
@Public()
@Get('type/:type')
@ApiOperation({ summary: '根据类型获取服务' })
@ApiResponse({ status: 200, description: '获取成功' })
@ApiResponse({ status: 404, description: '服务类型不存在' })
findByType(@Param('type') type: string) {
return this.serviceService.findByType(type);
}
@ApiBearerAuth()
@Patch(':id')
@Roles('admin')
@ApiOperation({ summary: '更新服务(管理员)' })
@ApiResponse({ status: 200, description: '更新成功' })
@ApiResponse({ status: 404, description: '服务不存在' })
@ApiResponse({ status: 409, description: '服务类型已存在' })
update(@Param('id') id: string, @Body() updateServiceDto: UpdateServiceDto) {
return this.serviceService.update(+id, updateServiceDto);
}
@ApiBearerAuth()
@Delete(':id')
@Roles('admin')
@ApiOperation({ summary: '删除服务(管理员)' })
@ApiResponse({ status: 200, description: '删除成功' })
@ApiResponse({ status: 404, description: '服务不存在' })
remove(@Param('id') id: string) {
return this.serviceService.remove(+id);
}
@ApiBearerAuth()
@Patch(':id/toggle-status')
@Roles('admin')
@ApiOperation({ summary: '切换服务状态(管理员)' })
@ApiResponse({ status: 200, description: '状态切换成功' })
@ApiResponse({ status: 404, description: '服务不存在' })
toggleStatus(@Param('id') id: string) {
return this.serviceService.toggleStatus(+id);
}
@ApiBearerAuth()
@Patch('sort-order')
@Roles('admin')
@ApiOperation({ summary: '更新服务排序(管理员)' })
@ApiResponse({ status: 200, description: '排序更新成功' })
updateSortOrder(@Body() serviceOrders: { id: number; sortOrder: number }[]) {
return this.serviceService.updateSortOrder(serviceOrders);
}
}

View File

@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ServiceService } from './service.service';
import { ServiceController } from './service.controller';
import { Service } from '../entities/service.entity';
@Module({
imports: [TypeOrmModule.forFeature([Service])],
controllers: [ServiceController],
providers: [ServiceService],
exports: [ServiceService],
})
export class ServiceModule {}

View File

@@ -0,0 +1,117 @@
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Service } from '../entities/service.entity';
import { CreateServiceDto, UpdateServiceDto, QueryServiceDto } from './dto/service.dto';
@Injectable()
export class ServiceService {
constructor(
@InjectRepository(Service)
private serviceRepository: Repository<Service>,
) {}
async create(createServiceDto: CreateServiceDto): Promise<Service> {
// 检查服务类型是否已存在
const existingService = await this.serviceRepository.findOne({
where: { type: createServiceDto.type }
});
if (existingService) {
throw new ConflictException('该服务类型已存在');
}
const service = this.serviceRepository.create({
...createServiceDto,
status: 'active',
});
return this.serviceRepository.save(service);
}
async findAll(query: QueryServiceDto): Promise<Service[]> {
const { type, status } = query;
const queryBuilder = this.serviceRepository.createQueryBuilder('service');
if (type) {
queryBuilder.andWhere('service.type = :type', { type });
}
if (status) {
queryBuilder.andWhere('service.status = :status', { status });
}
queryBuilder.orderBy('service.sortOrder', 'ASC');
queryBuilder.addOrderBy('service.createdAt', 'DESC');
return queryBuilder.getMany();
}
async findOne(id: number): Promise<Service> {
const service = await this.serviceRepository.findOne({
where: { id }
});
if (!service) {
throw new NotFoundException('服务不存在');
}
return service;
}
async findByType(type: string): Promise<Service> {
const service = await this.serviceRepository.findOne({
where: { type }
});
if (!service) {
throw new NotFoundException('服务类型不存在');
}
return service;
}
async update(id: number, updateServiceDto: UpdateServiceDto): Promise<Service> {
const service = await this.findOne(id);
// 如果要更新服务类型,检查新类型是否已被其他服务使用
if (updateServiceDto.type && updateServiceDto.type !== service.type) {
const existingService = await this.serviceRepository.findOne({
where: { type: updateServiceDto.type }
});
if (existingService) {
throw new ConflictException('该服务类型已存在');
}
}
await this.serviceRepository.update(id, updateServiceDto);
return this.findOne(id);
}
async remove(id: number): Promise<void> {
const service = await this.findOne(id);
await this.serviceRepository.remove(service);
}
async getActiveServices(): Promise<Service[]> {
return this.serviceRepository.find({
where: { status: 'active' },
order: { sortOrder: 'ASC', createdAt: 'DESC' }
});
}
async updateSortOrder(serviceOrders: { id: number; sortOrder: number }[]): Promise<void> {
for (const { id, sortOrder } of serviceOrders) {
await this.serviceRepository.update(id, { sortOrder });
}
}
async toggleStatus(id: number): Promise<Service> {
const service = await this.findOne(id);
const newStatus = service.status === 'active' ? 'inactive' : 'active';
await this.serviceRepository.update(id, { status: newStatus });
return this.findOne(id);
}
}