feat(dict): 改进字典项名称格式化为kebab-case并更新唯一约束

重构字典服务以统一格式化名称,将字典项名称中的空格、下划线和点转换为中划线并转为小写
更新字典项实体,移除name字段的唯一约束,改为组合索引(name, dict)
添加根据字典名称获取字典项的API端点
更新数据迁移和种子数据以匹配新的名称格式
This commit is contained in:
tikkhun 2025-12-01 15:07:06 +08:00
parent e4fc195b8d
commit 10b42eca7a
8 changed files with 256 additions and 108 deletions

View File

@ -131,9 +131,13 @@ export class DictController {
* @param dictId ID () * @param dictId ID ()
*/ */
@Get('/items') @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); return this.dictService.deleteDictItem(id);
} }
/**
*
* @param name
*/
@Get('/items-by-name')
async getDictItemsByDictName(@Query('name') name: string) {
// 调用服务层方法
return this.dictService.getDictItemsByDictName(name);
}
} }

View File

@ -0,0 +1,46 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class UpdateDictItemUniqueConstraint1764569947170 implements MigrationInterface {
name = 'UpdateDictItemUniqueConstraint1764569947170'
public async up(queryRunner: QueryRunner): Promise<void> {
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<void> {
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`);
}
}

View File

@ -1,103 +1,114 @@
import { Seeder, SeederFactoryManager } from 'typeorm-extension'; import { Seeder } from 'typeorm-extension';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import { Dict } from '../../entity/dict.entity'; import { Dict } from '../../entity/dict.entity';
import { DictItem } from '../../entity/dict_item.entity'; import { DictItem } from '../../entity/dict_item.entity';
export default class DictSeeder implements Seeder { 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( public async run(
dataSource: DataSource, dataSource: DataSource,
factoryManager: SeederFactoryManager
): Promise<any> { ): Promise<any> {
const dictRepository = dataSource.getRepository(Dict); const dictRepository = dataSource.getRepository(Dict);
const dictItemRepository = dataSource.getRepository(DictItem); const dictItemRepository = dataSource.getRepository(DictItem);
const flavorsData = [ const flavorsData = [
{ id: 1, title: 'Bellini Mini', name: 'Bellini Mini' }, { title: 'Bellini', name: 'bellini' },
{ id: 2, title: 'Max Polarmint', name: 'Max Polarmint' }, { title: 'Max Polarmint', name: 'max-polarmint' },
{ id: 3, title: 'Blueberry', name: 'Blueberry' }, { title: 'Blueberry', name: 'blueberry' },
{ id: 4, title: 'Citrus', name: 'Citrus' }, { title: 'Citrus', name: 'citrus' },
{ id: 5, title: 'Wintergreen', name: 'Wintergreen' }, { title: 'Wintergreen', name: 'wintergreen' },
{ id: 6, title: 'COOL MINT', name: 'COOL MINT' }, { title: 'COOL MINT', name: 'cool-mint' },
{ id: 7, title: 'JUICY PEACH', name: 'JUICY PEACH' }, { title: 'JUICY PEACH', name: 'juicy-peach' },
{ id: 8, title: 'ORANGE', name: 'ORANGE' }, { title: 'ORANGE', name: 'orange' },
{ id: 9, title: 'PEPPERMINT', name: 'PEPPERMINT' }, { title: 'PEPPERMINT', name: 'peppermint' },
{ id: 10, title: 'SPEARMINT', name: 'SPEARMINT' }, { title: 'SPEARMINT', name: 'spearmint' },
{ id: 11, title: 'STRAWBERRY', name: 'STRAWBERRY' }, { title: 'STRAWBERRY', name: 'strawberry' },
{ id: 12, title: 'WATERMELON', name: 'WATERMELON' }, { title: 'WATERMELON', name: 'watermelon' },
{ id: 13, title: 'COFFEE', name: 'COFFEE' }, { title: 'COFFEE', name: 'coffee' },
{ id: 14, title: 'LEMONADE', name: 'LEMONADE' }, { title: 'LEMONADE', name: 'lemonade' },
{ id: 15, title: 'apple mint', name: 'apple mint' }, { title: 'apple mint', name: 'apple-mint' },
{ id: 16, title: 'PEACH', name: 'PEACH' }, { title: 'PEACH', name: 'peach' },
{ id: 17, title: 'Mango', name: 'Mango' }, { title: 'Mango', name: 'mango' },
{ id: 18, title: 'ICE WINTERGREEN', name: 'ICE WINTERGREEN' }, { title: 'ICE WINTERGREEN', name: 'ice-wintergreen' },
{ id: 19, title: 'Pink Lemonade', name: 'Pink Lemonade' }, { title: 'Pink Lemonade', name: 'pink-lemonade' },
{ id: 20, title: 'Blackcherry', name: 'Blackcherry' }, { title: 'Blackcherry', name: 'blackcherry' },
{ id: 21, title: 'fresh mint', name: 'fresh mint' }, { title: 'fresh mint', name: 'fresh-mint' },
{ id: 22, title: 'Strawberry Lychee', name: 'Strawberry Lychee' }, { title: 'Strawberry Lychee', name: 'strawberry-lychee' },
{ id: 23, title: 'Passion Fruit', name: 'Passion Fruit' }, { title: 'Passion Fruit', name: 'passion-fruit' },
{ id: 24, title: 'Banana lce', name: 'Banana lce' }, { title: 'Banana lce', name: 'banana-lce' },
{ id: 25, title: 'Bubblegum', name: 'Bubblegum' }, { title: 'Bubblegum', name: 'bubblegum' },
{ id: 26, title: 'Mango lce', name: 'Mango lce' }, { title: 'Mango lce', name: 'mango-lce' },
{ id: 27, title: 'Grape lce', name: 'Grape 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 = [ const brandsData = [
{ id: 1, title: 'Yoone', name: 'YOONE' }, { title: 'Yoone', name: 'yoone' },
{ id: 2, title: 'White Fox', name: 'WHITE_FOX' }, { title: 'White Fox', name: 'white-fox' },
{ id: 3, title: 'ZYN', name: 'ZYN' }, { title: 'ZYN', name: 'zyn' },
{ id: 4, title: 'Zonnic', name: 'ZONNIC' }, { title: 'Zonnic', name: 'zonnic' },
{ id: 5, title: 'Zolt', name: 'ZOLT' }, { title: 'Zolt', name: 'zolt' },
{ id: 6, title: 'Velo', name: 'VELO' }, { title: 'Velo', name: 'velo' },
{ id: 7, title: 'Lucy', name: 'LUCY' }, { title: 'Lucy', name: 'lucy' },
{ id: 8, title: 'EGP', name: 'EGP' }, { title: 'EGP', name: 'egp' },
{ id: 9, title: 'Bridge', name: 'BRIDGE' }, { title: 'Bridge', name: 'bridge' },
{ id: 10, title: 'ZEX', name: 'ZEX' }, { title: 'ZEX', name: 'zex' },
{ id: 11, title: 'Sesh', name: 'Sesh' }, { title: 'Sesh', name: 'sesh' },
{ id: 12, title: 'Pablo', name: 'Pablo' }, { title: 'Pablo', name: 'pablo' },
]; ];
const strengthsData = [ const strengthsData = [
{ id: 1, title: '3MG', name: '3MG' }, { title: '3MG', name: '3mg' },
{ id: 2, title: '9MG', name: '9MG' }, { title: '9MG', name: '9mg' },
{ id: 3, title: '2MG', name: '2MG' }, { title: '2MG', name: '2mg' },
{ id: 4, title: '4MG', name: '4MG' }, { title: '4MG', name: '4mg' },
{ id: 5, title: '12MG', name: '12MG' }, { title: '12MG', name: '12mg' },
{ id: 6, title: '18MG', name: '18MG' }, { title: '18MG', name: '18mg' },
{ id: 7, title: '6MG', name: '6MG' }, { title: '6MG', name: '6mg' },
{ id: 8, title: '16.5MG', name: '16.5MG' }, { title: '16.5MG', name: '16-5mg' },
{ id: 9, title: '6.5MG', name: '6.5MG' }, { title: '6.5MG', name: '6-5mg' },
{ id: 10, title: '30MG', name: '30MG' }, { title: '30MG', name: '30mg' },
]; ];
// 在插入新数据前,不清空旧数据,改为如果不存在则创建 const nonFlavorTokensData = ['slim', 'pouches', 'pouch', 'mini', 'dry'].map(item => ({ title: item, name: item }));
// 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 locales = [ const locales = [
{ name: 'zh-CN', title: '简体中文' }, { name: 'zh-cn', title: '简体中文' },
{ name: 'en-US', title: 'English' }, { name: 'en-us', title: 'English' },
]; ];
for (const locale of locales) { for (const locale of locales) {
let dict = await dictRepository.findOne({ where: { name: locale.name } }); await this.createOrFindDict(dictRepository, locale);
if (!dict) {
dict = await dictRepository.save(locale);
}
} }
// 添加示例翻译条目 // 添加示例翻译条目
const zhDict = await dictRepository.findOne({ where: { name: 'zh-CN' } }); const zhDict = await dictRepository.findOne({ where: { name: 'zh-cn' } });
const enDict = await dictRepository.findOne({ where: { name: 'en-US' } }); const enDict = await dictRepository.findOne({ where: { name: 'en-us' } });
const translations = [ const translations = [
{ name: 'common.save', zh: '保存', en: 'Save' }, { name: 'common-save', zh: '保存', en: 'Save' },
{ name: 'common.cancel', zh: '取消', en: 'Cancel' }, { name: 'common-cancel', zh: '取消', en: 'Cancel' },
{ name: 'common.success', zh: '操作成功', en: 'Success' }, { name: 'common-success', zh: '操作成功', en: 'Success' },
{ name: 'common.failure', zh: '操作失败', en: 'Failure' }, { name: 'common-failure', zh: '操作失败', en: 'Failure' },
]; ];
for (const t of translations) { for (const t of translations) {
@ -114,40 +125,56 @@ export default class DictSeeder implements Seeder {
} }
} }
const brandDict = await this.createOrFindDict(dictRepository, { title: '品牌', name: 'brand' });
const flavorDict = await this.createOrFindDict(dictRepository, { title: '口味', name: 'flavor' });
const brandDict = await dictRepository.save({ title: '品牌', name: 'brand' }); const strengthDict = await this.createOrFindDict(dictRepository, { title: '强度', name: 'strength' });
const flavorDict = await dictRepository.save({ title: '口味', name: 'flavor' }); const nonFlavorTokensDict = await this.createOrFindDict(dictRepository, { title: '非口味关键词', name: 'non-flavor-tokens' });
const strengthDict = await dictRepository.save({ title: '强度', name: 'strength' });
// 遍历品牌数据 // 遍历品牌数据
for (const brand of brandsData) { await this.seedDictItems(dictItemRepository, brandDict, brandsData);
// 保存字典项,并关联到品牌字典
await dictItemRepository.save({
title: brand.title,
name: brand.name,
dict: brandDict,
});
}
// 遍历口味数据 // 遍历口味数据
for (const flavor of flavorsData) { await this.seedDictItems(dictItemRepository, flavorDict, flavorsData);
// 保存字典项,并关联到口味字典
await dictItemRepository.save({
title: flavor.title,
name: flavor.name,
dict: flavorDict,
});
}
// 遍历强度数据 // 遍历强度数据
for (const strength of strengthsData) { await this.seedDictItems(dictItemRepository, strengthDict, strengthsData);
// 保存字典项,并关联到强度字典
await dictItemRepository.save({ // 遍历非口味关键词数据
title: strength.title, await this.seedDictItems(dictItemRepository, nonFlavorTokensDict, nonFlavorTokensData);
name: strength.name, }
dict: strengthDict,
}); /**
*
* @param repo DictRepository
* @param dictInfo
* @returns Dict
*/
private async createOrFindDict(repo: any, dictInfo: { title: string; name: string }): Promise<Dict> {
// 格式化 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<void> {
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 });
}
} }
} }
} }

View File

@ -40,6 +40,23 @@ export class CreateProductDTO {
@ApiProperty({ description: '商品类型', enum: ['simple', 'bundle'], default: 'simple', required: false }) @ApiProperty({ description: '商品类型', enum: ['simple', 'bundle'], default: 'simple', required: false })
@Rule(RuleType.string().valid('simple', 'bundle').default('simple')) @Rule(RuleType.string().valid('simple', 'bundle').default('simple'))
type?: string; 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 }[];
} }
/** /**

View File

@ -9,6 +9,7 @@ import {
Column, Column,
CreateDateColumn, CreateDateColumn,
Entity, Entity,
Index,
JoinColumn, JoinColumn,
ManyToMany, ManyToMany,
ManyToOne, ManyToOne,
@ -17,6 +18,7 @@ import {
} from 'typeorm'; } from 'typeorm';
@Entity() @Entity()
@Index(['name', 'dict'], { unique: true })
export class DictItem { export class DictItem {
// 主键 // 主键
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
@ -29,7 +31,7 @@ export class DictItem {
@Column({ comment: '字典项中文名称', nullable: true }) @Column({ comment: '字典项中文名称', nullable: true })
titleCN: string; titleCN: string;
// 唯一标识 // 唯一标识
@Column({ unique: true, comment: '字典唯一标识名称' }) @Column({ comment: '字典唯一标识名称' })
name: string; name: string;
// 字典项值 // 字典项值

View File

@ -64,7 +64,7 @@ export class Product {
@Column({ default: 0 }) @Column({ default: 0 })
stock: number; stock: number;
@ManyToMany(() => DictItem, { @ManyToMany(() => DictItem, dictItem => dictItem.products, {
cascade: true, cascade: true,
}) })
@JoinTable() @JoinTable()

View File

@ -16,6 +16,11 @@ export class DictService {
@InjectEntityModel(DictItem) @InjectEntityModel(DictItem)
dictItemModel: Repository<DictItem>; dictItemModel: Repository<DictItem>;
// 格式化名称为 kebab-case
private formatName(name: string): string {
return String(name).replace(/[_\s.]+/g, '-').toLowerCase();
}
// 生成并返回字典的XLSX模板 // 生成并返回字典的XLSX模板
getDictXLSXTemplate() { getDictXLSXTemplate() {
// 定义表头 // 定义表头
@ -43,7 +48,7 @@ export class DictService {
// 创建要保存的字典实体数组 // 创建要保存的字典实体数组
const dicts = data.map((row: any) => { const dicts = data.map((row: any) => {
const dict = new Dict(); const dict = new Dict();
dict.name = row.name; dict.name = this.formatName(row.name);
dict.title = row.title; dict.title = row.title;
return dict; return dict;
}); });
@ -76,7 +81,7 @@ export class DictService {
const items = data.map((row: any) => { const items = data.map((row: any) => {
const item = new DictItem(); const item = new DictItem();
item.name = row.name; item.name = this.formatName(row.name);
item.title = row.title; item.title = row.title;
item.titleCN = row.titleCN; // 保存中文名称 item.titleCN = row.titleCN; // 保存中文名称
item.value = row.value; item.value = row.value;
@ -106,13 +111,16 @@ export class DictService {
// 创建新字典 // 创建新字典
async createDict(createDictDTO: CreateDictDTO) { async createDict(createDictDTO: CreateDictDTO) {
const dict = new Dict(); const dict = new Dict();
dict.name = createDictDTO.name; dict.name = this.formatName(createDictDTO.name);
dict.title = createDictDTO.title; dict.title = createDictDTO.title;
return this.dictModel.save(dict); return this.dictModel.save(dict);
} }
// 更新字典 // 更新字典
async updateDict(id: number, updateDictDTO: UpdateDictDTO) { async updateDict(id: number, updateDictDTO: UpdateDictDTO) {
if (updateDictDTO.name) {
updateDictDTO.name = this.formatName(updateDictDTO.name);
}
await this.dictModel.update(id, updateDictDTO); await this.dictModel.update(id, updateDictDTO);
return this.dictModel.findOneBy({ id }); return this.dictModel.findOneBy({ id });
} }
@ -127,10 +135,23 @@ export class DictService {
} }
// 获取字典项列表,支持按 dictId 过滤 // 获取字典项列表,支持按 dictId 过滤
async getDictItems(dictId?: number) { async getDictItems(params: { dictId?: number; name?: string; title?: string; }) {
// 如果提供了 dictId则只返回该字典下的项 const { dictId, name, title } = params;
const where: any = {};
if (dictId) { 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(); return this.dictItemModel.find();
@ -143,7 +164,7 @@ export class DictService {
throw new Error('指定的字典不存在'); throw new Error('指定的字典不存在');
} }
const item = new DictItem(); const item = new DictItem();
item.name = createDictItemDTO.name; item.name = this.formatName(createDictItemDTO.name);
item.title = createDictItemDTO.title; item.title = createDictItemDTO.title;
item.titleCN = createDictItemDTO.titleCN; // 保存中文名称 item.titleCN = createDictItemDTO.titleCN; // 保存中文名称
item.dict = dict; item.dict = dict;
@ -152,6 +173,9 @@ export class DictService {
// 更新字典项 // 更新字典项
async updateDictItem(id: number, updateDictItemDTO: UpdateDictItemDTO) { async updateDictItem(id: number, updateDictItemDTO: UpdateDictItemDTO) {
if (updateDictItemDTO.name) {
updateDictItemDTO.name = this.formatName(updateDictItemDTO.name);
}
await this.dictItemModel.update(id, updateDictItemDTO); await this.dictItemModel.update(id, updateDictItemDTO);
return this.dictItemModel.findOneBy({ id }); return this.dictItemModel.findOneBy({ id });
} }
@ -161,4 +185,16 @@ export class DictService {
const result = await this.dictItemModel.delete(id); const result = await this.dictItemModel.delete(id);
return result.affected > 0; 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 } } });
}
} }

View File

@ -374,8 +374,12 @@ export class WpProductService {
// 数据转换 // 数据转换
const items = rawResult.reduce((acc, row) => { const items = rawResult.reduce((acc, row) => {
// 在累加器中查找当前产品
let product = acc.find(p => p.id === row.id); let product = acc.find(p => p.id === row.id);
// 如果产品不存在,则创建新产品
if (!product) { if (!product) {
// 从原始产品列表中查找,以获取 'site' 关联数据
const originalProduct = products.find(p => p.id === row.id);
product = { product = {
...Object.keys(row) ...Object.keys(row)
.filter(key => !key.startsWith('variation_')) .filter(key => !key.startsWith('variation_'))
@ -384,6 +388,8 @@ export class WpProductService {
return obj; return obj;
}, {}), }, {}),
variations: [], variations: [],
// 附加 'site' 对象
site: originalProduct.site,
}; };
acc.push(product); acc.push(product);
} }