292 lines
9.0 KiB
TypeScript
292 lines
9.0 KiB
TypeScript
import { Injectable, NotFoundException, Logger } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { Repository, FindOptionsWhere, In } from 'typeorm';
|
|
import { Word, WordType, WordStatus } from './word.entity';
|
|
import { WordImage } from './word-image.entity';
|
|
import { Sentence } from './sentence.entity';
|
|
import { WordSentence } from './word-sentence.entity';
|
|
import { CreateWordDto } from './dto/create-word.dto';
|
|
import { UpdateWordDto } from './dto/update-word.dto';
|
|
import { QueryWordsDto } from './dto/query-words.dto';
|
|
import { CreateSentenceDto } from './dto/create-sentence.dto';
|
|
import { PaginatedWordData } from './dto/paginated-response.dto';
|
|
|
|
@Injectable()
|
|
export class WordsService {
|
|
private readonly logger = new Logger(WordsService.name);
|
|
|
|
constructor(
|
|
@InjectRepository(Word)
|
|
private readonly wordRepository: Repository<Word>,
|
|
@InjectRepository(WordImage)
|
|
private readonly wordImageRepository: Repository<WordImage>,
|
|
@InjectRepository(Sentence)
|
|
private readonly sentenceRepository: Repository<Sentence>,
|
|
@InjectRepository(WordSentence)
|
|
private readonly wordSentenceRepository: Repository<WordSentence>,
|
|
) {}
|
|
|
|
/**
|
|
* 创建汉字
|
|
*/
|
|
async create(createWordDto: CreateWordDto): Promise<Word> {
|
|
const word = this.wordRepository.create(createWordDto);
|
|
return await this.wordRepository.save(word);
|
|
}
|
|
|
|
/**
|
|
* 查询汉字(支持多种查询条件和分页)
|
|
*/
|
|
async findAllPaginated(queryDto: QueryWordsDto): Promise<PaginatedWordData> {
|
|
const where: FindOptionsWhere<Word> = {};
|
|
|
|
// 只查询汉字
|
|
where.type = WordType.CHINESE_CHAR;
|
|
|
|
// 搜索条件
|
|
if (queryDto.search) {
|
|
// 使用 Like 进行模糊搜索
|
|
const queryBuilder = this.wordRepository.createQueryBuilder('word');
|
|
queryBuilder.where('word.type = :type', { type: WordType.CHINESE_CHAR });
|
|
queryBuilder.andWhere('word.content LIKE :search', {
|
|
search: `%${queryDto.search}%`,
|
|
});
|
|
|
|
if (queryDto.grade) {
|
|
queryBuilder.andWhere('word.grade = :grade', { grade: queryDto.grade });
|
|
}
|
|
|
|
// 排序:按年级升序(低年级在前),年级相同时按创建时间
|
|
queryBuilder.orderBy('word.grade', 'ASC', 'NULLS LAST');
|
|
queryBuilder.addOrderBy('word.createdAt', 'ASC');
|
|
queryBuilder.addOrderBy('word.id', 'ASC');
|
|
|
|
// 分页参数
|
|
const page = queryDto.page || 1;
|
|
const limit = queryDto.limit || 9;
|
|
const skip = (page - 1) * limit;
|
|
|
|
// 加载关联数据
|
|
queryBuilder.leftJoinAndSelect('word.images', 'images');
|
|
queryBuilder.leftJoinAndSelect('word.wordSentences', 'wordSentences');
|
|
queryBuilder.leftJoinAndSelect('wordSentences.sentence', 'sentence');
|
|
|
|
// 分页
|
|
queryBuilder.skip(skip).take(limit);
|
|
|
|
const [list, total] = await queryBuilder.getManyAndCount();
|
|
|
|
// 计算总页数
|
|
const total_page = Math.ceil(total / limit);
|
|
|
|
return {
|
|
list,
|
|
pagination: {
|
|
total,
|
|
total_page,
|
|
page_size: limit,
|
|
current_page: page,
|
|
},
|
|
};
|
|
}
|
|
|
|
// 年级筛选
|
|
if (queryDto.grade) {
|
|
where.grade = queryDto.grade;
|
|
}
|
|
|
|
// 分页参数
|
|
const page = queryDto.page || 1;
|
|
const limit = queryDto.limit || 9;
|
|
const skip = (page - 1) * limit;
|
|
|
|
// 查询总数
|
|
const total = await this.wordRepository.count({ where });
|
|
|
|
// 查询分页数据
|
|
const list = await this.wordRepository.find({
|
|
where,
|
|
relations: ['images', 'wordSentences', 'wordSentences.sentence'],
|
|
order: {
|
|
grade: 'ASC',
|
|
createdAt: 'ASC',
|
|
id: 'ASC',
|
|
},
|
|
skip,
|
|
take: limit,
|
|
});
|
|
|
|
// 计算总页数
|
|
const total_page = Math.ceil(total / limit);
|
|
|
|
return {
|
|
list,
|
|
pagination: {
|
|
total,
|
|
total_page,
|
|
page_size: limit,
|
|
current_page: page,
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 根据 ID 查询单个汉字
|
|
*/
|
|
async findOneById(id: number): Promise<Word> {
|
|
const word = await this.wordRepository.findOne({
|
|
where: { id },
|
|
relations: ['images', 'wordSentences', 'wordSentences.sentence'],
|
|
order: {
|
|
images: { sortOrder: 'ASC' },
|
|
wordSentences: { sortOrder: 'ASC' },
|
|
},
|
|
});
|
|
|
|
if (!word) {
|
|
throw new NotFoundException(`未找到ID为 ${id} 的汉字`);
|
|
}
|
|
|
|
return word;
|
|
}
|
|
|
|
/**
|
|
* 更新汉字信息
|
|
*/
|
|
async update(id: number, updateWordDto: UpdateWordDto): Promise<Word> {
|
|
const word = await this.findOneById(id);
|
|
|
|
// 更新基础信息
|
|
Object.assign(word, updateWordDto);
|
|
|
|
// 更新关联句子
|
|
if (updateWordDto.sentenceIds) {
|
|
// 删除现有关联
|
|
await this.wordSentenceRepository.delete({ wordId: id });
|
|
|
|
// 创建新关联
|
|
for (let i = 0; i < updateWordDto.sentenceIds.length; i++) {
|
|
const sentenceId = updateWordDto.sentenceIds[i];
|
|
const wordSentence = this.wordSentenceRepository.create({
|
|
wordId: id,
|
|
sentenceId: sentenceId,
|
|
sortOrder: i,
|
|
});
|
|
await this.wordSentenceRepository.save(wordSentence);
|
|
}
|
|
}
|
|
|
|
return await this.wordRepository.save(word);
|
|
}
|
|
|
|
/**
|
|
* 删除汉字
|
|
*/
|
|
async remove(id: number): Promise<void> {
|
|
const word = await this.findOneById(id);
|
|
await this.wordRepository.remove(word);
|
|
}
|
|
|
|
// 图片相关方法
|
|
/**
|
|
* 获取汉字图片列表
|
|
*/
|
|
async getWordImages(wordId: number): Promise<WordImage[]> {
|
|
return await this.wordImageRepository.find({
|
|
where: { wordId: wordId },
|
|
order: { sortOrder: 'ASC' },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 更新图片排序
|
|
*/
|
|
async updateWordImagesSort(wordId: number, imageIds: number[]): Promise<WordImage[]> {
|
|
for (let i = 0; i < imageIds.length; i++) {
|
|
await this.wordImageRepository.update(
|
|
{ id: imageIds[i], wordId: wordId },
|
|
{ sortOrder: i },
|
|
);
|
|
}
|
|
return await this.getWordImages(wordId);
|
|
}
|
|
|
|
/**
|
|
* 删除汉字图片
|
|
*/
|
|
async deleteWordImage(wordId: number, imageId: number): Promise<void> {
|
|
const image = await this.wordImageRepository.findOne({
|
|
where: { id: imageId, wordId: wordId },
|
|
});
|
|
|
|
if (!image) {
|
|
throw new NotFoundException('图片不存在');
|
|
}
|
|
|
|
await this.wordImageRepository.remove(image);
|
|
}
|
|
|
|
// 句子相关方法
|
|
/**
|
|
* 获取汉字句子列表
|
|
*/
|
|
async getWordSentences(wordId: number): Promise<Sentence[]> {
|
|
const wordSentences = await this.wordSentenceRepository.find({
|
|
where: { wordId: wordId },
|
|
relations: ['sentence'],
|
|
order: { sortOrder: 'ASC' },
|
|
});
|
|
|
|
return wordSentences.map((ws) => ws.sentence);
|
|
}
|
|
|
|
/**
|
|
* 添加句子到汉字
|
|
*/
|
|
async addSentenceToWord(wordId: number, createSentenceDto: CreateSentenceDto): Promise<Sentence> {
|
|
// 创建句子
|
|
const sentence = this.sentenceRepository.create(createSentenceDto);
|
|
const savedSentence = await this.sentenceRepository.save(sentence);
|
|
|
|
// 创建关联
|
|
const maxSort = await this.wordSentenceRepository
|
|
.createQueryBuilder('ws')
|
|
.where('ws.wordId = :wordId', { wordId })
|
|
.select('MAX(ws.sortOrder)', 'max')
|
|
.getRawOne();
|
|
|
|
const wordSentence = this.wordSentenceRepository.create({
|
|
wordId: wordId,
|
|
sentenceId: savedSentence.id,
|
|
sortOrder: (maxSort?.max || 0) + 1,
|
|
});
|
|
|
|
await this.wordSentenceRepository.save(wordSentence);
|
|
return savedSentence;
|
|
}
|
|
|
|
/**
|
|
* 从汉字中删除句子
|
|
*/
|
|
async removeSentenceFromWord(wordId: number, sentenceId: number): Promise<void> {
|
|
const wordSentence = await this.wordSentenceRepository.findOne({
|
|
where: { wordId: wordId, sentenceId: sentenceId },
|
|
});
|
|
|
|
if (!wordSentence) {
|
|
throw new NotFoundException('句子关联不存在');
|
|
}
|
|
|
|
await this.wordSentenceRepository.remove(wordSentence);
|
|
}
|
|
|
|
// 词语相关方法(后续实现)
|
|
/**
|
|
* 获取关联词语
|
|
*/
|
|
async getRelatedWords(wordId: number): Promise<Word[]> {
|
|
// TODO: 实现词语关联查询
|
|
return [];
|
|
}
|
|
}
|