From 10b42eca7af2557c65bcf0968feeae1fe624b07b Mon Sep 17 00:00:00 2001 From: tikkhun Date: Mon, 1 Dec 2025 15:07:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(dict):=20=E6=94=B9=E8=BF=9B=E5=AD=97?= =?UTF-8?q?=E5=85=B8=E9=A1=B9=E5=90=8D=E7=A7=B0=E6=A0=BC=E5=BC=8F=E5=8C=96?= =?UTF-8?q?=E4=B8=BAkebab-case=E5=B9=B6=E6=9B=B4=E6=96=B0=E5=94=AF?= =?UTF-8?q?=E4=B8=80=E7=BA=A6=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构字典服务以统一格式化名称,将字典项名称中的空格、下划线和点转换为中划线并转为小写 更新字典项实体,移除name字段的唯一约束,改为组合索引(name, dict) 添加根据字典名称获取字典项的API端点 更新数据迁移和种子数据以匹配新的名称格式 --- src/controller/dict.controller.ts | 18 +- ...7170-update-dict-item-unique-constraint.ts | 46 ++++ src/db/seeds/dict.seeder.ts | 221 ++++++++++-------- src/dto/product.dto.ts | 17 ++ src/entity/dict_item.entity.ts | 4 +- src/entity/product.entity.ts | 2 +- src/service/dict.service.ts | 50 +++- src/service/wp_product.service.ts | 6 + 8 files changed, 256 insertions(+), 108 deletions(-) create mode 100644 src/db/migrations/1764569947170-update-dict-item-unique-constraint.ts diff --git a/src/controller/dict.controller.ts b/src/controller/dict.controller.ts index d80b89f..bb4cc91 100644 --- a/src/controller/dict.controller.ts +++ b/src/controller/dict.controller.ts @@ -131,9 +131,13 @@ export class DictController { * @param dictId 字典ID (可选) */ @Get('/items') - async getDictItems(@Query('dictId') dictId?: number) { + async getDictItems( + @Query('dictId') dictId?: number, + @Query('name') name?: string, + @Query('title') title?: string, + ) { // 调用服务层方法 - return this.dictService.getDictItems(dictId); + return this.dictService.getDictItems({ dictId, name, title }); } /** @@ -168,4 +172,14 @@ export class DictController { // 调用服务层方法 return this.dictService.deleteDictItem(id); } + + /** + * 根据字典名称获取字典项列表 + * @param name 字典名称 + */ + @Get('/items-by-name') + async getDictItemsByDictName(@Query('name') name: string) { + // 调用服务层方法 + return this.dictService.getDictItemsByDictName(name); + } } diff --git a/src/db/migrations/1764569947170-update-dict-item-unique-constraint.ts b/src/db/migrations/1764569947170-update-dict-item-unique-constraint.ts new file mode 100644 index 0000000..e71f676 --- /dev/null +++ b/src/db/migrations/1764569947170-update-dict-item-unique-constraint.ts @@ -0,0 +1,46 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UpdateDictItemUniqueConstraint1764569947170 implements MigrationInterface { + name = 'UpdateDictItemUniqueConstraint1764569947170' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`productId\``); + await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`isPackage\``); + await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`externalOrderId\` varchar(255) NOT NULL`); + await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`externalProductId\` varchar(255) NOT NULL`); + await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`externalVariationId\` varchar(255) NOT NULL`); + await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`subtotal\` decimal(10,2) NULL`); + await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`subtotal_tax\` decimal(10,2) NULL`); + await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`total\` decimal(10,2) NULL`); + await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`total_tax\` decimal(10,2) NULL`); + await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`price\` decimal(10,2) NOT NULL`); + await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`productId\` int NOT NULL`); + await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`isPackage\` tinyint NOT NULL DEFAULT 0`); + await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`siteId\``); + await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`siteId\` varchar(255) NOT NULL`); + await queryRunner.query(`ALTER TABLE \`order_item_original\` CHANGE \`sku\` \`sku\` varchar(255) NULL`); + await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`siteId\``); + await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`siteId\` int NULL`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`siteId\``); + await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`siteId\` varchar(255) NOT NULL`); + await queryRunner.query(`ALTER TABLE \`order_item_original\` CHANGE \`sku\` \`sku\` varchar(255) NOT NULL`); + await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`siteId\``); + await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`siteId\` int NULL`); + await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`isPackage\``); + await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`productId\``); + await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`price\``); + await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`total_tax\``); + await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`total\``); + await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`subtotal_tax\``); + await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`subtotal\``); + await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`externalVariationId\``); + await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`externalProductId\``); + await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`externalOrderId\``); + await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`isPackage\` tinyint NOT NULL DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`productId\` int NOT NULL`); + } + +} diff --git a/src/db/seeds/dict.seeder.ts b/src/db/seeds/dict.seeder.ts index 5a9d0ed..7deb17c 100644 --- a/src/db/seeds/dict.seeder.ts +++ b/src/db/seeds/dict.seeder.ts @@ -1,103 +1,114 @@ -import { Seeder, SeederFactoryManager } from 'typeorm-extension'; +import { Seeder } from 'typeorm-extension'; import { DataSource } from 'typeorm'; import { Dict } from '../../entity/dict.entity'; import { DictItem } from '../../entity/dict_item.entity'; export default class DictSeeder implements Seeder { + /** + * 格式化名称为 kebab-case + * @param name 需要格式化的名称 + * @returns 格式化后的名称 + */ + private formatName(name: string): string { + // 将空格、下划线、点统一转换成中划线,并转为小写 + return String(name).replace(/[_\s.]+/g, '-').toLowerCase(); + } + public async run( dataSource: DataSource, - factoryManager: SeederFactoryManager ): Promise { const dictRepository = dataSource.getRepository(Dict); const dictItemRepository = dataSource.getRepository(DictItem); const flavorsData = [ - { id: 1, title: 'Bellini Mini', name: 'Bellini Mini' }, - { id: 2, title: 'Max Polarmint', name: 'Max Polarmint' }, - { id: 3, title: 'Blueberry', name: 'Blueberry' }, - { id: 4, title: 'Citrus', name: 'Citrus' }, - { id: 5, title: 'Wintergreen', name: 'Wintergreen' }, - { id: 6, title: 'COOL MINT', name: 'COOL MINT' }, - { id: 7, title: 'JUICY PEACH', name: 'JUICY PEACH' }, - { id: 8, title: 'ORANGE', name: 'ORANGE' }, - { id: 9, title: 'PEPPERMINT', name: 'PEPPERMINT' }, - { id: 10, title: 'SPEARMINT', name: 'SPEARMINT' }, - { id: 11, title: 'STRAWBERRY', name: 'STRAWBERRY' }, - { id: 12, title: 'WATERMELON', name: 'WATERMELON' }, - { id: 13, title: 'COFFEE', name: 'COFFEE' }, - { id: 14, title: 'LEMONADE', name: 'LEMONADE' }, - { id: 15, title: 'apple mint', name: 'apple mint' }, - { id: 16, title: 'PEACH', name: 'PEACH' }, - { id: 17, title: 'Mango', name: 'Mango' }, - { id: 18, title: 'ICE WINTERGREEN', name: 'ICE WINTERGREEN' }, - { id: 19, title: 'Pink Lemonade', name: 'Pink Lemonade' }, - { id: 20, title: 'Blackcherry', name: 'Blackcherry' }, - { id: 21, title: 'fresh mint', name: 'fresh mint' }, - { id: 22, title: 'Strawberry Lychee', name: 'Strawberry Lychee' }, - { id: 23, title: 'Passion Fruit', name: 'Passion Fruit' }, - { id: 24, title: 'Banana lce', name: 'Banana lce' }, - { id: 25, title: 'Bubblegum', name: 'Bubblegum' }, - { id: 26, title: 'Mango lce', name: 'Mango lce' }, - { id: 27, title: 'Grape lce', name: 'Grape lce' }, + { title: 'Bellini', name: 'bellini' }, + { title: 'Max Polarmint', name: 'max-polarmint' }, + { title: 'Blueberry', name: 'blueberry' }, + { title: 'Citrus', name: 'citrus' }, + { title: 'Wintergreen', name: 'wintergreen' }, + { title: 'COOL MINT', name: 'cool-mint' }, + { title: 'JUICY PEACH', name: 'juicy-peach' }, + { title: 'ORANGE', name: 'orange' }, + { title: 'PEPPERMINT', name: 'peppermint' }, + { title: 'SPEARMINT', name: 'spearmint' }, + { title: 'STRAWBERRY', name: 'strawberry' }, + { title: 'WATERMELON', name: 'watermelon' }, + { title: 'COFFEE', name: 'coffee' }, + { title: 'LEMONADE', name: 'lemonade' }, + { title: 'apple mint', name: 'apple-mint' }, + { title: 'PEACH', name: 'peach' }, + { title: 'Mango', name: 'mango' }, + { title: 'ICE WINTERGREEN', name: 'ice-wintergreen' }, + { title: 'Pink Lemonade', name: 'pink-lemonade' }, + { title: 'Blackcherry', name: 'blackcherry' }, + { title: 'fresh mint', name: 'fresh-mint' }, + { title: 'Strawberry Lychee', name: 'strawberry-lychee' }, + { title: 'Passion Fruit', name: 'passion-fruit' }, + { title: 'Banana lce', name: 'banana-lce' }, + { title: 'Bubblegum', name: 'bubblegum' }, + { title: 'Mango lce', name: 'mango-lce' }, + { title: 'Grape lce', name: 'grape-lce' }, + { title: 'apple', name: 'apple' }, + { title: 'grape', name: 'grape' }, + { title: 'cherry', name: 'cherry' }, + { title: 'lemon', name: 'lemon' }, + { title: 'razz', name: 'razz' }, + { title: 'pineapple', name: 'pineapple' }, + { title: 'berry', name: 'berry' }, + { title: 'fruit', name: 'fruit' }, + { title: 'mint', name: 'mint' }, + { title: 'menthol', name: 'menthol' }, ]; const brandsData = [ - { id: 1, title: 'Yoone', name: 'YOONE' }, - { id: 2, title: 'White Fox', name: 'WHITE_FOX' }, - { id: 3, title: 'ZYN', name: 'ZYN' }, - { id: 4, title: 'Zonnic', name: 'ZONNIC' }, - { id: 5, title: 'Zolt', name: 'ZOLT' }, - { id: 6, title: 'Velo', name: 'VELO' }, - { id: 7, title: 'Lucy', name: 'LUCY' }, - { id: 8, title: 'EGP', name: 'EGP' }, - { id: 9, title: 'Bridge', name: 'BRIDGE' }, - { id: 10, title: 'ZEX', name: 'ZEX' }, - { id: 11, title: 'Sesh', name: 'Sesh' }, - { id: 12, title: 'Pablo', name: 'Pablo' }, + { title: 'Yoone', name: 'yoone' }, + { title: 'White Fox', name: 'white-fox' }, + { title: 'ZYN', name: 'zyn' }, + { title: 'Zonnic', name: 'zonnic' }, + { title: 'Zolt', name: 'zolt' }, + { title: 'Velo', name: 'velo' }, + { title: 'Lucy', name: 'lucy' }, + { title: 'EGP', name: 'egp' }, + { title: 'Bridge', name: 'bridge' }, + { title: 'ZEX', name: 'zex' }, + { title: 'Sesh', name: 'sesh' }, + { title: 'Pablo', name: 'pablo' }, ]; const strengthsData = [ - { id: 1, title: '3MG', name: '3MG' }, - { id: 2, title: '9MG', name: '9MG' }, - { id: 3, title: '2MG', name: '2MG' }, - { id: 4, title: '4MG', name: '4MG' }, - { id: 5, title: '12MG', name: '12MG' }, - { id: 6, title: '18MG', name: '18MG' }, - { id: 7, title: '6MG', name: '6MG' }, - { id: 8, title: '16.5MG', name: '16.5MG' }, - { id: 9, title: '6.5MG', name: '6.5MG' }, - { id: 10, title: '30MG', name: '30MG' }, + { title: '3MG', name: '3mg' }, + { title: '9MG', name: '9mg' }, + { title: '2MG', name: '2mg' }, + { title: '4MG', name: '4mg' }, + { title: '12MG', name: '12mg' }, + { title: '18MG', name: '18mg' }, + { title: '6MG', name: '6mg' }, + { title: '16.5MG', name: '16-5mg' }, + { title: '6.5MG', name: '6-5mg' }, + { title: '30MG', name: '30mg' }, ]; - // 在插入新数据前,不清空旧数据,改为如果不存在则创建 - // await dictItemRepository.query('DELETE FROM `dict_item`'); - // await dictRepository.query('DELETE FROM `dict`'); - // // 重置自增 ID - // await dictItemRepository.query('ALTER TABLE `dict_item` AUTO_INCREMENT = 1'); - // await dictRepository.query('ALTER TABLE `dict` AUTO_INCREMENT = 1'); + const nonFlavorTokensData = ['slim', 'pouches', 'pouch', 'mini', 'dry'].map(item => ({ title: item, name: item })); // 初始化语言字典 const locales = [ - { name: 'zh-CN', title: '简体中文' }, - { name: 'en-US', title: 'English' }, + { name: 'zh-cn', title: '简体中文' }, + { name: 'en-us', title: 'English' }, ]; for (const locale of locales) { - let dict = await dictRepository.findOne({ where: { name: locale.name } }); - if (!dict) { - dict = await dictRepository.save(locale); - } + await this.createOrFindDict(dictRepository, locale); } // 添加示例翻译条目 - const zhDict = await dictRepository.findOne({ where: { name: 'zh-CN' } }); - const enDict = await dictRepository.findOne({ where: { name: 'en-US' } }); + const zhDict = await dictRepository.findOne({ where: { name: 'zh-cn' } }); + const enDict = await dictRepository.findOne({ where: { name: 'en-us' } }); const translations = [ - { name: 'common.save', zh: '保存', en: 'Save' }, - { name: 'common.cancel', zh: '取消', en: 'Cancel' }, - { name: 'common.success', zh: '操作成功', en: 'Success' }, - { name: 'common.failure', zh: '操作失败', en: 'Failure' }, + { name: 'common-save', zh: '保存', en: 'Save' }, + { name: 'common-cancel', zh: '取消', en: 'Cancel' }, + { name: 'common-success', zh: '操作成功', en: 'Success' }, + { name: 'common-failure', zh: '操作失败', en: 'Failure' }, ]; for (const t of translations) { @@ -114,40 +125,56 @@ export default class DictSeeder implements Seeder { } } - - - const brandDict = await dictRepository.save({ title: '品牌', name: 'brand' }); - const flavorDict = await dictRepository.save({ title: '口味', name: 'flavor' }); - const strengthDict = await dictRepository.save({ title: '强度', name: 'strength' }); + const brandDict = await this.createOrFindDict(dictRepository, { title: '品牌', name: 'brand' }); + const flavorDict = await this.createOrFindDict(dictRepository, { title: '口味', name: 'flavor' }); + const strengthDict = await this.createOrFindDict(dictRepository, { title: '强度', name: 'strength' }); + const nonFlavorTokensDict = await this.createOrFindDict(dictRepository, { title: '非口味关键词', name: 'non-flavor-tokens' }); // 遍历品牌数据 - for (const brand of brandsData) { - // 保存字典项,并关联到品牌字典 - await dictItemRepository.save({ - title: brand.title, - name: brand.name, - dict: brandDict, - }); - } + await this.seedDictItems(dictItemRepository, brandDict, brandsData); // 遍历口味数据 - for (const flavor of flavorsData) { - // 保存字典项,并关联到口味字典 - await dictItemRepository.save({ - title: flavor.title, - name: flavor.name, - dict: flavorDict, - }); - } + await this.seedDictItems(dictItemRepository, flavorDict, flavorsData); // 遍历强度数据 - for (const strength of strengthsData) { - // 保存字典项,并关联到强度字典 - await dictItemRepository.save({ - title: strength.title, - name: strength.name, - dict: strengthDict, - }); + await this.seedDictItems(dictItemRepository, strengthDict, strengthsData); + + // 遍历非口味关键词数据 + await this.seedDictItems(dictItemRepository, nonFlavorTokensDict, nonFlavorTokensData); + } + + /** + * 创建或查找字典 + * @param repo DictRepository + * @param dictInfo 字典信息 + * @returns Dict 实例 + */ + private async createOrFindDict(repo: any, dictInfo: { title: string; name: string }): Promise { + // 格式化 name + const formattedName = this.formatName(dictInfo.name); + let dict = await repo.findOne({ where: { name: formattedName } }); + if (!dict) { + // 如果字典不存在,则使用格式化后的 name 创建新字典 + dict = await repo.save({ title: dictInfo.title, name: formattedName }); + } + return dict; + } + + /** + * 填充字典项 + * @param repo DictItemRepository + * @param dict 字典实例 + * @param items 字典项数组 + */ + private async seedDictItems(repo: any, dict: Dict, items: { title: string; name: string }[]): Promise { + for (const item of items) { + // 格式化 name + const formattedName = this.formatName(item.name); + const existingItem = await repo.findOne({ where: { name: formattedName, dict: { id: dict.id } } }); + if (!existingItem) { + // 如果字典项不存在,则使用格式化后的 name 创建新字典项 + await repo.save({ ...item, name: formattedName, dict }); + } } } } diff --git a/src/dto/product.dto.ts b/src/dto/product.dto.ts index 166ff63..f3f9d5b 100644 --- a/src/dto/product.dto.ts +++ b/src/dto/product.dto.ts @@ -40,6 +40,23 @@ export class CreateProductDTO { @ApiProperty({ description: '商品类型', enum: ['simple', 'bundle'], default: 'simple', required: false }) @Rule(RuleType.string().valid('simple', 'bundle').default('simple')) type?: string; + + // 中文注释:仅当 type 为 'bundle' 时,才需要提供 components + @ApiProperty({ description: '产品组成', type: 'array', required: false }) + @Rule( + RuleType.array() + .items( + RuleType.object({ + sku: RuleType.string().required(), + quantity: RuleType.number().required(), + }) + ) + .when('type', { + is: 'bundle', + then: RuleType.array().required(), + }) + ) + components?: { sku: string; quantity: number }[]; } /** diff --git a/src/entity/dict_item.entity.ts b/src/entity/dict_item.entity.ts index c53b21a..7546bac 100644 --- a/src/entity/dict_item.entity.ts +++ b/src/entity/dict_item.entity.ts @@ -9,6 +9,7 @@ import { Column, CreateDateColumn, Entity, + Index, JoinColumn, ManyToMany, ManyToOne, @@ -17,6 +18,7 @@ import { } from 'typeorm'; @Entity() +@Index(['name', 'dict'], { unique: true }) export class DictItem { // 主键 @PrimaryGeneratedColumn() @@ -29,7 +31,7 @@ export class DictItem { @Column({ comment: '字典项中文名称', nullable: true }) titleCN: string; // 唯一标识 - @Column({ unique: true, comment: '字典唯一标识名称' }) + @Column({ comment: '字典唯一标识名称' }) name: string; // 字典项值 diff --git a/src/entity/product.entity.ts b/src/entity/product.entity.ts index b8fed9c..eaef800 100644 --- a/src/entity/product.entity.ts +++ b/src/entity/product.entity.ts @@ -64,7 +64,7 @@ export class Product { @Column({ default: 0 }) stock: number; - @ManyToMany(() => DictItem, { + @ManyToMany(() => DictItem, dictItem => dictItem.products, { cascade: true, }) @JoinTable() diff --git a/src/service/dict.service.ts b/src/service/dict.service.ts index 43c9177..f18316a 100644 --- a/src/service/dict.service.ts +++ b/src/service/dict.service.ts @@ -16,6 +16,11 @@ export class DictService { @InjectEntityModel(DictItem) dictItemModel: Repository; + // 格式化名称为 kebab-case + private formatName(name: string): string { + return String(name).replace(/[_\s.]+/g, '-').toLowerCase(); + } + // 生成并返回字典的XLSX模板 getDictXLSXTemplate() { // 定义表头 @@ -43,7 +48,7 @@ export class DictService { // 创建要保存的字典实体数组 const dicts = data.map((row: any) => { const dict = new Dict(); - dict.name = row.name; + dict.name = this.formatName(row.name); dict.title = row.title; return dict; }); @@ -76,7 +81,7 @@ export class DictService { const items = data.map((row: any) => { const item = new DictItem(); - item.name = row.name; + item.name = this.formatName(row.name); item.title = row.title; item.titleCN = row.titleCN; // 保存中文名称 item.value = row.value; @@ -106,13 +111,16 @@ export class DictService { // 创建新字典 async createDict(createDictDTO: CreateDictDTO) { const dict = new Dict(); - dict.name = createDictDTO.name; + 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 }); } @@ -127,10 +135,23 @@ export class DictService { } // 获取字典项列表,支持按 dictId 过滤 - async getDictItems(dictId?: number) { - // 如果提供了 dictId,则只返回该字典下的项 + async getDictItems(params: { dictId?: number; name?: string; title?: string; }) { + const { dictId, name, title } = params; + const where: any = {}; + if (dictId) { - return this.dictItemModel.find({ where: { dict: { id: 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(); @@ -143,7 +164,7 @@ export class DictService { throw new Error('指定的字典不存在'); } const item = new DictItem(); - item.name = createDictItemDTO.name; + item.name = this.formatName(createDictItemDTO.name); item.title = createDictItemDTO.title; item.titleCN = createDictItemDTO.titleCN; // 保存中文名称 item.dict = dict; @@ -152,6 +173,9 @@ export class DictService { // 更新字典项 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 }); } @@ -161,4 +185,16 @@ export class DictService { 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 } } }); + } } diff --git a/src/service/wp_product.service.ts b/src/service/wp_product.service.ts index 58cc3af..ab12c00 100644 --- a/src/service/wp_product.service.ts +++ b/src/service/wp_product.service.ts @@ -374,8 +374,12 @@ export class WpProductService { // 数据转换 const items = rawResult.reduce((acc, row) => { + // 在累加器中查找当前产品 let product = acc.find(p => p.id === row.id); + // 如果产品不存在,则创建新产品 if (!product) { + // 从原始产品列表中查找,以获取 'site' 关联数据 + const originalProduct = products.find(p => p.id === row.id); product = { ...Object.keys(row) .filter(key => !key.startsWith('variation_')) @@ -384,6 +388,8 @@ export class WpProductService { return obj; }, {}), variations: [], + // 附加 'site' 对象 + site: originalProduct.site, }; acc.push(product); }