初始化参股
This commit is contained in:
93
后端/src/service/dto/service.dto.ts
Normal file
93
后端/src/service/dto/service.dto.ts
Normal 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';
|
||||
}
|
||||
98
后端/src/service/service.controller.ts
Normal file
98
后端/src/service/service.controller.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
13
后端/src/service/service.module.ts
Normal file
13
后端/src/service/service.module.ts
Normal 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 {}
|
||||
117
后端/src/service/service.service.ts
Normal file
117
后端/src/service/service.service.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user