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; @InjectEntityModel(DictItem) dictItemModel: Repository; // 格式化名称为 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 { 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' }); } }