API/src/service/dict.service.ts

355 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Provide } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository, Like } from 'typeorm';
import { Dict } from '../entity/dict.entity';
import { DictItem } from '../entity/dict_item.entity';
import { CreateDictDTO, UpdateDictDTO } from '../dto/dict.dto';
import { CreateDictItemDTO, UpdateDictItemDTO } from '../dto/dict.dto';
import * as xlsx from 'xlsx';
import * as fs from 'fs';
import { BatchOperationResultDTO } from '../dto/api.dto';
// 定义 Excel 行数据的类型接口
interface ExcelRow {
name: string;
title: string;
titleCN?: string;
value?: string;
image?: string;
shortName?: string;
sort?: number;
}
@Provide()
export class DictService {
@InjectEntityModel(Dict)
dictModel: Repository<Dict>;
@InjectEntityModel(DictItem)
dictItemModel: Repository<DictItem>;
// 格式化名称为 kebab-case
private formatName(name: string): string {
// 只替换空格和下划线
return String(name).replace(/[_\s]+/g, '-').toLowerCase();
}
// 生成并返回字典的XLSX模板
getDictXLSXTemplate() {
// 定义表头
const headers = ['name', 'title'];
// 创建一个新的工作表
const ws = xlsx.utils.aoa_to_sheet([headers]);
// 创建一个新的工作簿
const wb = xlsx.utils.book_new();
// 将工作表添加到工作簿
xlsx.utils.book_append_sheet(wb, ws, 'Dicts');
// 将工作簿写入缓冲区
return xlsx.write(wb, { type: 'buffer', bookType: 'xlsx' });
}
// 从XLSX文件导入字典
async importDictsFromXLSX(bufferOrPath: Buffer | string) {
// 判断传入的是 Buffer 还是文件路径字符串
let buffer: Buffer;
if (typeof bufferOrPath === 'string') {
// 如果是文件路径,读取文件内容
buffer = fs.readFileSync(bufferOrPath);
} else {
// 如果是 Buffer直接使用
buffer = bufferOrPath;
}
// 读取缓冲区中的工作簿
const wb = xlsx.read(buffer, { type: 'buffer' });
// 获取第一个工作表的名称
const wsname = wb.SheetNames[0];
// 获取第一个工作表
const ws = wb.Sheets[wsname];
// 将工作表转换为JSON对象数组xlsx会自动将第一行作为表头
const data = xlsx.utils.sheet_to_json(ws) as { name: string; title: string }[];
// 创建要保存的字典实体数组
const dicts = data.map((row: { name: string; title: string }) => {
const dict = new Dict();
dict.name = this.formatName(row.name);
dict.title = row.title;
return dict;
});
// 保存字典实体数组到数据库
await this.dictModel.save(dicts);
// 返回成功导入的记录数
return { success: true, count: dicts.length };
}
// 生成并返回字典项的XLSX模板
getDictItemXLSXTemplate() {
const headers = ['name', 'title', 'titleCN', 'value', 'sort', 'image', 'shortName'];
const ws = xlsx.utils.aoa_to_sheet([headers]);
const wb = xlsx.utils.book_new();
xlsx.utils.book_append_sheet(wb, ws, 'DictItems');
return xlsx.write(wb, { type: 'buffer', bookType: 'xlsx' });
}
// 从XLSX文件导入字典项
async importDictItemsFromXLSX(bufferOrPath: Buffer | string, dictId: number): Promise<BatchOperationResultDTO> {
if(!dictId){
throw new Error("引入失败, 请输入字典 ID")
}
const dict = await this.dictModel.findOneBy({ id: dictId });
if (!dict) {
throw new Error('指定的字典不存在');
}
// 判断传入的是 Buffer 还是文件路径字符串
let buffer: Buffer;
if (typeof bufferOrPath === 'string') {
// 如果是文件路径,读取文件内容
buffer = fs.readFileSync(bufferOrPath);
} else {
// 如果是 Buffer直接使用
buffer = bufferOrPath;
}
const wb = xlsx.read(buffer, { type: 'buffer' });
const wsname = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
// 使用默认的header解析方式xlsx会自动将第一行作为表头
const data = xlsx.utils.sheet_to_json(ws) as ExcelRow[];
// 使用 upsertDictItem 方法逐个处理,存在则更新,不存在则创建
const createdItems = [];
const updatedItems = [];
const errors = [];
for (const row of data) {
try {
const result = await this.upsertDictItem(dictId, {
name: row.name,
title: row.title,
titleCN: row.titleCN,
value: row.value,
image: row.image,
shortName: row.shortName,
sort: row.sort || 0,
});
if (result.action === 'created') {
createdItems.push(result.item);
} else {
updatedItems.push(result.item);
}
} catch (error) {
// 记录错误信息
errors.push({
identifier: row.name || 'unknown',
error: error instanceof Error ? error.message : String(error)
});
}
}
const processed = createdItems.length + updatedItems.length;
return {
total: data.length,
processed: processed,
updated: updatedItems.length,
created: createdItems.length,
errors: errors
};
}
getDict(where: { name?: string; id?: number; }, relations: string[]) {
if (!where.name && !where.id) {
throw new Error('必须提供 name 或 id');
}
return this.dictModel.findOne({ where, relations });
}
// 获取字典列表,支持按标题搜索
async getDicts(options: { title?: string; name?: string; }) {
const where = {
title: options.title ? Like(`%${options.title}%`) : undefined,
name: options.name ? Like(`%${options.name}%`) : undefined,
}
return this.dictModel.find({ where });
}
// 创建新字典
async createDict(createDictDTO: CreateDictDTO) {
const dict = new Dict();
dict.name = this.formatName(createDictDTO.name);
dict.title = createDictDTO.title;
return this.dictModel.save(dict);
}
// 更新字典
async updateDict(id: number, updateDictDTO: UpdateDictDTO) {
if (updateDictDTO.name) {
updateDictDTO.name = this.formatName(updateDictDTO.name);
}
await this.dictModel.update(id, updateDictDTO);
return this.dictModel.findOneBy({ id });
}
// 删除字典及其所有字典项
async deleteDict(id: number) {
// 首先删除该字典下的所有字典项
await this.dictItemModel.delete({ dict: { id } });
// 然后删除字典本身
const result = await this.dictModel.delete(id);
return result.affected > 0;
}
// 获取字典项列表,支持按 dictId 过滤
async getDictItems(params: { dictId?: number; name?: string; title?: string; }) {
const { dictId, name, title } = params;
const where: any = {};
if (dictId) {
where.dict = { id: dictId };
}
if (name) {
where.name = Like(`%${name}%`);
}
if (title) {
where.title = Like(`%${title}%`);
}
// 如果提供了 dictId,则只返回该字典下的项
if (params.dictId) {
return this.dictItemModel.find({ where });
}
// 否则,返回所有字典项
return this.dictItemModel.find();
}
// 创建新字典项
async createDictItem(createDictItemDTO: CreateDictItemDTO) {
const dict = await this.dictModel.findOneBy({ id: createDictItemDTO.dictId });
if (!dict) {
throw new Error(`创建新字典项,指定的字典ID为${createDictItemDTO.dictId},但不存在`);
}
const item = new DictItem();
item.name = this.formatName(createDictItemDTO.name);
item.title = createDictItemDTO.title;
item.titleCN = createDictItemDTO.titleCN; // 保存中文名称
item.image = createDictItemDTO.image;
item.shortName = createDictItemDTO.shortName;
item.dict = dict;
return this.dictItemModel.save(item);
}
// 更新或创建字典项 (Upsert)
// 如果字典项已存在(根据 name 和 dictId 判断),则更新;否则创建新的
async upsertDictItem(dictId: number, itemData: {
name: string;
title: string;
titleCN?: string;
value?: string;
image?: string;
shortName?: string;
sort?: number;
}) {
// 格式化 name
const formattedName = this.formatName(itemData.name);
// 查找是否已存在该字典项(根据 name 和 dictId)
const existingItem = await this.dictItemModel.findOne({
where: {
name: formattedName,
dict: { id: dictId }
}
});
if (existingItem) {
// 如果存在,则更新
existingItem.title = itemData.title;
existingItem.titleCN = itemData.titleCN;
existingItem.value = itemData.value;
existingItem.image = itemData.image;
existingItem.shortName = itemData.shortName;
existingItem.sort = itemData.sort || 0;
const savedItem = await this.dictItemModel.save(existingItem);
return { item: savedItem, action: 'updated' };
} else {
// 如果不存在,则创建新的
const dict = await this.dictModel.findOneBy({ id: dictId });
if (!dict) {
throw new Error(`指定的字典ID为${dictId},但不存在`);
}
const item = new DictItem();
item.name = formattedName;
item.title = itemData.title;
item.titleCN = itemData.titleCN;
item.value = itemData.value;
item.image = itemData.image;
item.shortName = itemData.shortName;
item.sort = itemData.sort || 0;
item.dict = dict;
const savedItem = await this.dictItemModel.save(item);
return { item: savedItem, action: 'created' };
}
}
// 更新字典项
async updateDictItem(id: number, updateDictItemDTO: UpdateDictItemDTO) {
if (updateDictItemDTO.name) {
updateDictItemDTO.name = this.formatName(updateDictItemDTO.name);
}
await this.dictItemModel.update(id, updateDictItemDTO);
return this.dictItemModel.findOneBy({ id });
}
// 删除字典项
async deleteDictItem(id: number) {
const result = await this.dictItemModel.delete(id);
return result.affected > 0;
}
// 根据字典名称获取字典项列表
async getDictItemsByDictName(dictName: string) {
// 查找字典
const dict = await this.dictModel.findOne({ where: { name: dictName } });
// 如果字典不存在,则返回空数组
if (!dict) {
return [];
}
// 返回该字典下的所有字典项
return this.dictItemModel.find({ where: { dict: { id: dict.id } } });
}
// 导出字典项为 XLSX 文件
async exportDictItemsToXLSX(dictId: number) {
// 查找字典
const dict = await this.dictModel.findOneBy({ id: dictId });
// 如果字典不存在,则抛出错误
if (!dict) {
throw new Error('指定的字典不存在');
}
// 获取该字典下的所有字典项
const items = await this.dictItemModel.find({
where: { dict: { id: dictId } },
order: { sort: 'ASC', id: 'DESC' },
});
// 定义表头
const headers = ['name', 'title', 'titleCN', 'value', 'sort', 'image', 'shortName'];
// 将字典项转换为二维数组
const data = items.map((item) => [
item.name,
item.title,
item.titleCN || '',
item.value || '',
item.sort,
item.image || '',
item.shortName || '',
]);
// 创建工作表
const ws = xlsx.utils.aoa_to_sheet([headers, ...data]);
// 创建工作簿
const wb = xlsx.utils.book_new();
// 将工作表添加到工作簿
xlsx.utils.book_append_sheet(wb, ws, 'DictItems');
// 将工作簿写入缓冲区
return xlsx.write(wb, { type: 'buffer', bookType: 'xlsx' });
}
}