商品模板功能上传

This commit is contained in:
2026-07-06 15:29:27 +08:00
parent 80d967f2cf
commit f46904b47c
19 changed files with 1148 additions and 8 deletions
+25
View File
@@ -90,6 +90,11 @@ enum ProductStatus {
OFF_SALE
}
enum DetailTemplateStatus {
ACTIVE
DISABLED
}
enum PromoCodeStatus {
ACTIVE
DISABLED
@@ -333,6 +338,26 @@ model CommonProductItem {
@@map("common_product_item")
}
model CommonProductDetailTemplate {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
code String @unique @db.VarChar(32)
name String @db.VarChar(64)
description String? @db.VarChar(256)
aromaType AromaType? @map("aroma_type")
storyTitle String? @map("story_title") @db.VarChar(128)
storyText String? @map("story_text") @db.Text
features Json?
detailImageUrls Json? @map("detail_image_urls")
suggestedDetailImageCount Int @default(1) @map("suggested_detail_image_count")
sortOrder Int @default(0) @map("sort_order")
status DetailTemplateStatus @default(ACTIVE)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
@@index([status, sortOrder])
@@map("common_product_detail_template")
}
model CommonStoreCategory {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
code String @unique @db.VarChar(32)
+21
View File
@@ -1,4 +1,5 @@
import { PrismaClient, ResourceBizType, ResourceMediaType, ResourceOwnerType } from '@prisma/client';
import { DEFAULT_PRODUCT_DETAIL_TEMPLATES } from './seeds/product-detail-templates.default';
const prisma = new PrismaClient();
@@ -46,6 +47,7 @@ async function main() {
await prisma.commonCity.deleteMany();
await prisma.partner.deleteMany();
await prisma.commonProductItem.deleteMany();
await prisma.commonProductDetailTemplate.deleteMany();
await prisma.commonStoreCategory.deleteMany();
await prisma.commonPromoCode.deleteMany();
await prisma.commonResource.deleteMany();
@@ -89,6 +91,24 @@ async function main() {
prisma.commonStoreCategory.create({ data: { code: 'LOCAL', name: '地方菜', sort: 2 } }),
]);
for (const tpl of DEFAULT_PRODUCT_DETAIL_TEMPLATES) {
await prisma.commonProductDetailTemplate.create({
data: {
code: tpl.code,
name: tpl.name,
description: tpl.description,
aromaType: tpl.aromaType as 'QINGXIANG' | 'JIANGXIANG' | 'NONGXIANG' | null,
storyTitle: tpl.storyTitle,
storyText: tpl.storyText,
features: tpl.features,
detailImageUrls: [],
suggestedDetailImageCount: tpl.suggestedDetailImageCount,
sortOrder: tpl.sortOrder,
status: 'ACTIVE',
},
});
}
const productDefs = [
{ skuCode: 'QX-001', name: '杜康·白水古酿 500ml', subtitle: '清香型 52度 礼盒装', price: 599, sortOrder: 1, img: 'https://picsum.photos/seed/dukang1/400/400' },
{ skuCode: 'QX-002', name: '杜康·年份陈酿(十年)', subtitle: '清香型 42度 纯粮酿造', price: 880, sortOrder: 2, img: 'https://picsum.photos/seed/dukang2/400/400' },
@@ -227,6 +247,7 @@ async function main() {
console.log('Seed complete:', {
city: city.name,
detailTemplates: DEFAULT_PRODUCT_DETAIL_TEMPLATES.length,
products: products.length,
stores: storeDefs.length,
testPhones: {
@@ -0,0 +1,89 @@
/** 商品详情模板默认种子(与 admin-web 历史静态模板一致) */
export const DEFAULT_PRODUCT_DETAIL_TEMPLATES = [
{
code: 'dukang-classic',
name: '杜康经典',
description: '品牌故事 + 双卖点,适合主力 SKU',
aromaType: null as string | null,
storyTitle: '千年杜康 · 唯有此处',
storyText:
'选自白水杜康核心产区,取山泉之灵气,集五谷之精华。古法酿造工艺,历经九九八十一道工序,方得这一口醇厚绵甜。',
features: [
{ icon: 'water_drop', title: '泉水酿造', desc: '甘冽清甜 灵动自然' },
{ icon: 'grain', title: '精选五谷', desc: '传统比例 匠心发酵' },
],
suggestedDetailImageCount: 2,
sortOrder: 1,
},
{
code: 'qingxiang',
name: '清香型',
description: '突出清香甘冽、入口绵柔',
aromaType: 'QINGXIANG',
storyTitle: '清香传世 · 入口甘冽',
storyText:
'以优质高粱、大麦、豌豆为原料,地缸固态发酵,酒体清澈透明,清香纯正,甘冽爽口,余味悠长。',
features: [
{ icon: 'air', title: '清香纯正', desc: '窖香优雅 入口绵甜' },
{ icon: 'local_drink', title: '纯粮固态', desc: '地缸发酵 传统工艺' },
],
suggestedDetailImageCount: 2,
sortOrder: 2,
},
{
code: 'jiangxiang',
name: '酱香型',
description: '突出酱香突出、回味悠长',
aromaType: 'JIANGXIANG',
storyTitle: '酱香典范 · 岁月醇香',
storyText:
'遵循端午制曲、重阳下沙,九次蒸煮八次发酵,长期窖藏陈化,酱香突出,幽雅细腻,空杯留香持久。',
features: [
{ icon: 'schedule', title: '陈年窖藏', desc: '时光淬炼 醇厚丰满' },
{ icon: 'spa', title: '酱香工艺', desc: '九蒸八酵 匠心酿造' },
],
suggestedDetailImageCount: 3,
sortOrder: 3,
},
{
code: 'nongxiang',
name: '浓香型',
description: '突出窖香浓郁、绵甜爽净',
aromaType: 'NONGXIANG',
storyTitle: '浓香典范 · 窖香天成',
storyText:
'泥窖固态发酵,续糟配料,蒸馏摘酒,分级贮存。酒体无色透明,窖香浓郁,绵甜爽净,尾净余香长。',
features: [
{ icon: 'foundation', title: '百年窖池', desc: '微生物群落 窖香天成' },
{ icon: 'water_drop', title: '绵甜爽净', desc: '入口醇和 回味悠长' },
],
suggestedDetailImageCount: 2,
sortOrder: 4,
},
{
code: 'benefit-focus',
name: '好客权益',
description: '强调购酒享权益、到店核销',
aromaType: null,
storyTitle: '买杜康美酒 · 享全城好客礼遇',
storyText:
'购酒即享本城专属好客权益,为您安排一场地道饭局。权益金可直接用于本地签约门店消费,到店享用,醇香美酒配佳肴。',
features: [
{ icon: 'confirmation_number', title: '购酒发券', desc: '支付成功即享权益额度' },
{ icon: 'store', title: '全城好店', desc: '本地签约门店随心核销' },
],
suggestedDetailImageCount: 1,
sortOrder: 5,
},
{
code: 'minimal',
name: '简约留白',
description: '仅故事正文,无卖点模块',
aromaType: null,
storyTitle: '商品详情',
storyText: '请在上方上传详情长图,或在此补充商品说明文字。',
features: [] as Array<{ icon: string; title: string; desc: string }>,
suggestedDetailImageCount: 1,
sortOrder: 6,
},
] as const;
@@ -0,0 +1,46 @@
import { PrismaClient } from '@prisma/client';
import { DEFAULT_PRODUCT_DETAIL_TEMPLATES } from './seeds/product-detail-templates.default';
const prisma = new PrismaClient();
async function main() {
for (const tpl of DEFAULT_PRODUCT_DETAIL_TEMPLATES) {
await prisma.commonProductDetailTemplate.upsert({
where: { code: tpl.code },
create: {
code: tpl.code,
name: tpl.name,
description: tpl.description,
aromaType: tpl.aromaType as 'QINGXIANG' | 'JIANGXIANG' | 'NONGXIANG' | null,
storyTitle: tpl.storyTitle,
storyText: tpl.storyText,
features: tpl.features,
detailImageUrls: [],
suggestedDetailImageCount: tpl.suggestedDetailImageCount,
sortOrder: tpl.sortOrder,
status: 'ACTIVE',
},
update: {
name: tpl.name,
description: tpl.description,
aromaType: tpl.aromaType as 'QINGXIANG' | 'JIANGXIANG' | 'NONGXIANG' | null,
storyTitle: tpl.storyTitle,
storyText: tpl.storyText,
features: tpl.features,
detailImageUrls: [],
suggestedDetailImageCount: tpl.suggestedDetailImageCount,
sortOrder: tpl.sortOrder,
},
});
}
console.log(`Upserted ${DEFAULT_PRODUCT_DETAIL_TEMPLATES.length} product detail templates`);
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});