This commit is contained in:
zpc 2026-03-01 14:43:44 +08:00
parent 6d7d4b099c
commit 1bebd937bc
9 changed files with 198 additions and 8 deletions

View File

@ -0,0 +1,112 @@
# Task 6: 商城商品详情页Banner图配置 - 实施完成
## 概述
为商城类型盒子添加独立的商品详情页Banner图字段 `imgurl_banner`,解决当前 `imgurl_detail` 同时用于顶部Banner和底部详情图的问题。
## 字段用途说明
- `imgurl` → 列表页封面图(小图)
- `imgurl_banner` → 详情页顶部Banner图新增
- `imgurl_detail` → 详情页底部详情图
## 已完成的修改
### 1. 数据库 ✅
**文件**: `server/HoneyBox/scripts/add_imgurl_banner.sql`
- 添加 `imgurl_banner` 字段NVARCHAR(500) NULL
- 包含字段存在性检查,避免重复添加
**执行方式**:
```bash
# 在 SQL Server Management Studio 或命令行执行
sqlcmd -S localhost -d honey_box -i server/HoneyBox/scripts/add_imgurl_banner.sql
```
### 2. 后端实体 ✅
**文件**: `server/HoneyBox/src/HoneyBox.Model/Entities/Good.cs`
- 添加 `ImgUrlBanner` 属性string?
### 3. 后端 DTO ✅
**文件**: `server/HoneyBox/src/HoneyBox.Model/Models/Goods/GoodsModels.cs`
- `GoodsInfoDto` 添加 `imgurl_banner` 字段
- 使用 `[JsonPropertyName("imgurl_banner")]` 保持 snake_case 格式
### 4. 后台管理配置 ✅
**文件**: `server/HoneyBox/src/HoneyBox.Admin/admin-web/src/views/business/goods/config/typeFieldConfig.ts`
- `GoodsTypeFieldConfig` 接口添加 `showBannerImage` 字段
- 商城类型type=10配置 `showBannerImage: true`
- 其他类型保持 `showBannerImage: false`
### 5. 后台管理 - 新增对话框 ✅
**文件**: `server/HoneyBox/src/HoneyBox.Admin/admin-web/src/views/business/goods/components/GoodsAddDialog.vue`
- 添加 Banner 图上传组件(仅商城类型显示)
- 添加表单验证规则
- 添加到提交数据中
### 6. 后台管理 - 编辑对话框 ✅
**文件**: `server/HoneyBox/src/HoneyBox.Admin/admin-web/src/views/business/goods/components/GoodsEditDialog.vue`
- 添加 Banner 图上传组件(仅商城类型显示)
- 添加表单验证规则
- 加载和保存 Banner 图数据
### 7. 小程序前端 ✅
**文件**: `honey_box/pages/mall/detail.vue`
- 修改顶部 Banner 图片源:从 `imgurl_detail || imgurl` 改为 `imgurl_banner || imgurl`
- 底部详情图继续使用 `imgurl_detail`
## 数据库迁移 SQL
```sql
-- 为 goods 表添加 imgurl_banner 字段
-- 用于商城详情页顶部 banner 图
USE honey_box;
GO
-- 添加 imgurl_banner 字段
IF NOT EXISTS (
SELECT * FROM sys.columns
WHERE object_id = OBJECT_ID(N'dbo.goods')
AND name = 'imgurl_banner'
)
BEGIN
ALTER TABLE dbo.goods
ADD imgurl_banner NVARCHAR(500) NULL;
PRINT 'Column imgurl_banner added successfully.';
END
ELSE
BEGIN
PRINT 'Column imgurl_banner already exists.';
END
GO
PRINT 'Migration completed.';
GO
```
## 使用说明
### 后台管理操作
1. 进入"商品管理" → "盒子管理"
2. 新增或编辑商城类型type=10的盒子
3. 会看到三个图片上传字段:
- **盒子封面图**:列表页显示的小图
- **盒子详情图**:详情页底部的详情图
- **商品详情页Banner图**:详情页顶部的大图(新增)
4. 上传对应图片后保存
### 前端显示逻辑
- 详情页顶部:优先显示 `imgurl_banner`,如果没有则显示 `imgurl`(封面图)
- 详情页底部:显示 `imgurl_detail`(详情图)
## 注意事项
1. 只有商城类型type=10的盒子会显示 Banner 图上传字段
2. Banner 图字段为可选NULL不影响现有数据
3. 如果不上传 Banner 图,前端会自动使用封面图作为兜底
4. Mapster 会自动映射新字段,无需额外配置
## 测试建议
1. 执行 SQL 脚本添加字段
2. 重启后端服务(如果需要)
3. 在后台管理中编辑一个商城类型盒子,上传 Banner 图
4. 在小程序中查看该商品详情页,确认顶部显示 Banner 图,底部显示详情图

View File

@ -7,7 +7,7 @@
<view class="detail-content-wrap">
<!-- 商品主图 -->
<view class="goods-banner">
<image :src="goodsInfo.imgurl_detail || goodsInfo.imgurl" mode="widthFix" class="banner-img"></image>
<image :src="goodsInfo.imgurl_banner || goodsInfo.imgurl" mode="widthFix" class="banner-img"></image>
</view>
<!-- 价格区域 -->

View File

@ -191,6 +191,8 @@
// (, )
downOption: {
auto: false,
bgColor: '#ffffff', //
textColor: '#666666', //
},
// (, )
upOption: {

View File

@ -0,0 +1,30 @@
-- 为 goods 表添加 imgurl_banner 字段
-- 用于商城详情页顶部 banner 图
USE honey_box;
GO
-- 添加 imgurl_banner 字段
IF NOT EXISTS (
SELECT * FROM sys.columns
WHERE object_id = OBJECT_ID(N'dbo.goods')
AND name = 'imgurl_banner'
)
BEGIN
ALTER TABLE dbo.goods
ADD imgurl_banner NVARCHAR(500) NULL;
PRINT 'Column imgurl_banner added successfully.';
END
ELSE
BEGIN
PRINT 'Column imgurl_banner already exists.';
END
GO
-- 可选:将现有的 imgurl 复制到 imgurl_banner 作为初始值
-- UPDATE dbo.goods SET imgurl_banner = imgurl WHERE imgurl_banner IS NULL AND imgurl IS NOT NULL;
-- GO
PRINT 'Migration completed.';
GO

View File

@ -327,7 +327,7 @@
<!-- 图片上传 -->
<el-divider v-if="fieldConfig.showCoverImage || fieldConfig.showDetailImage" content-position="left">图片上传</el-divider>
<el-row :gutter="20" v-if="fieldConfig.showCoverImage || fieldConfig.showDetailImage">
<el-row :gutter="20" v-if="fieldConfig.showCoverImage || fieldConfig.showDetailImage || fieldConfig.showBannerImage">
<el-col :span="12" v-if="fieldConfig.showCoverImage">
<el-form-item label="盒子封面图" prop="imgUrl">
<ImageUpload
@ -347,6 +347,18 @@
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" v-if="fieldConfig.showBannerImage">
<el-col :span="12">
<el-form-item label="商品详情页Banner图" prop="imgUrlBanner">
<ImageUpload
v-model="formData.imgUrlBanner"
placeholder="点击上传Banner图"
tip="支持 jpg、png、gif、webp 格式,最大 10MB"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
@ -433,6 +445,7 @@ const formData = reactive({
unlockAmount: 0,
imgUrl: '',
imgUrlDetail: '',
imgUrlBanner: '',
categoryId: 0,
couponPro: 0,
})
@ -457,6 +470,9 @@ const formRules = computed<FormRules>(() => {
if (fieldConfig.value.showDetailImage) {
rules.imgUrlDetail = [{ required: true, message: '请上传盒子详情图', trigger: 'change' }]
}
if (fieldConfig.value.showBannerImage) {
rules.imgUrlBanner = [{ required: true, message: '请上传商品详情页Banner图', trigger: 'change' }]
}
//
if (fieldConfig.value.showTimeConfig) {
@ -551,6 +567,7 @@ const resetForm = () => {
formData.unlockAmount = 0
formData.imgUrl = ''
formData.imgUrlDetail = ''
formData.imgUrlBanner = ''
formData.categoryId = 0
formData.couponPro = 0
formRef.value?.resetFields()
@ -598,6 +615,7 @@ const handleSubmit = async () => {
type: formData.type,
imgUrl: formData.imgUrl,
imgUrlDetail: formData.imgUrlDetail,
imgUrlBanner: formData.imgUrlBanner || undefined,
stock: formData.stock,
sort: formData.sort,
dailyLimit: formData.dailyLimit,

View File

@ -337,7 +337,7 @@
<!-- 图片上传 -->
<el-divider v-if="fieldConfig.showCoverImage || fieldConfig.showDetailImage" content-position="left">图片上传</el-divider>
<el-row :gutter="20" v-if="fieldConfig.showCoverImage || fieldConfig.showDetailImage">
<el-row :gutter="20" v-if="fieldConfig.showCoverImage || fieldConfig.showDetailImage || fieldConfig.showBannerImage">
<el-col :span="12" v-if="fieldConfig.showCoverImage">
<el-form-item label="盒子封面图" prop="imgUrl">
<ImageUpload
@ -357,6 +357,18 @@
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" v-if="fieldConfig.showBannerImage">
<el-col :span="12">
<el-form-item label="商品详情页Banner图" prop="imgUrlBanner">
<ImageUpload
v-model="formData.imgUrlBanner"
placeholder="点击上传Banner图"
tip="支持 jpg、png、gif、webp 格式,最大 10MB"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
@ -451,6 +463,7 @@ const formData = reactive({
unlockAmount: 0,
imgUrl: '',
imgUrlDetail: '',
imgUrlBanner: '',
categoryId: 0,
couponPro: 0,
})
@ -474,6 +487,9 @@ const formRules = computed<FormRules>(() => {
if (fieldConfig.value.showDetailImage) {
rules.imgUrlDetail = [{ required: true, message: '请上传盒子详情图', trigger: 'change' }]
}
if (fieldConfig.value.showBannerImage) {
rules.imgUrlBanner = [{ required: true, message: '请上传商品详情页Banner图', trigger: 'change' }]
}
//
if (fieldConfig.value.showTimeConfig) {
@ -521,6 +537,7 @@ const loadGoodsDetail = async () => {
formData.lingzhuFan = detail.lingzhuFan
formData.imgUrl = detail.imgUrl
formData.imgUrlDetail = detail.imgUrlDetail
formData.imgUrlBanner = detail.imgUrlBanner || ''
formData.categoryId = detail.categoryId
formData.couponPro = detail.couponPro
formData.unlockAmount = (detail as any).unlockAmount || 0
@ -600,6 +617,7 @@ const handleSubmit = async () => {
type: formData.type,
imgUrl: formData.imgUrl,
imgUrlDetail: formData.imgUrlDetail,
imgUrlBanner: formData.imgUrlBanner || undefined,
stock: formData.stock,
sort: formData.sort,
dailyLimit: formData.dailyLimit,

View File

@ -103,6 +103,7 @@ export interface GoodsTypeFieldConfig {
showUnlockAmount: boolean // 显示解锁金额
showCoverImage: boolean // 显示盒子封面图
showDetailImage: boolean // 显示盒子详情图
showBannerImage: boolean // 显示商品详情页Banner图
showPrice: boolean // 显示盒子价格
showShouZhe: boolean // 显示首抽五折
}
@ -128,6 +129,7 @@ export const defaultFieldConfig: GoodsTypeFieldConfig = {
showUnlockAmount: true,
showCoverImage: true,
showDetailImage: true,
showBannerImage: false,
showPrice: true,
showShouZhe: true,
}
@ -150,7 +152,7 @@ export const GoodsTypeFieldConfigs: Record<number, GoodsTypeFieldConfig> = {
showLianji: false, showTimeConfig: false, showAutoXiajia: false,
showCoupon: false, showIntegral: false, showDescription: false,
showQuanjuXiangou: false, showShowIs: false, showUnlockAmount: true,
showCoverImage: true, showDetailImage: true,
showCoverImage: true, showDetailImage: true, showBannerImage: false,
showPrice: true, showShouZhe: true,
},
// 无限赏:不需要套数、锁箱,需要首抽五折
@ -160,17 +162,17 @@ export const GoodsTypeFieldConfigs: Record<number, GoodsTypeFieldConfig> = {
showLianji: false, showTimeConfig: false, showAutoXiajia: false,
showCoupon: false, showIntegral: false, showDescription: false,
showQuanjuXiangou: false, showShowIs: false, showUnlockAmount: true,
showCoverImage: true, showDetailImage: true,
showCoverImage: true, showDetailImage: true, showBannerImage: false,
showPrice: true, showShouZhe: true,
},
// 商城赏:套数、盒子描述、详情图
// 商城赏:套数、盒子描述、详情图、Banner图
[GoodsType.ShangChengShang]: {
showStock: true, showLock: false, showDailyLimit: false,
showRage: false, showItemCard: false, showLingzhu: false,
showLianji: false, showTimeConfig: false, showAutoXiajia: false,
showCoupon: false, showIntegral: false, showDescription: true,
showQuanjuXiangou: false, showShowIs: false, showUnlockAmount: true,
showCoverImage: true, showDetailImage: true,
showCoverImage: true, showDetailImage: true, showBannerImage: true,
showPrice: true, showShouZhe: false,
},
// 福利屋:时间配置、盒子描述,不需要封面图、详情图、价格、首抽五折
@ -180,7 +182,7 @@ export const GoodsTypeFieldConfigs: Record<number, GoodsTypeFieldConfig> = {
showLianji: false, showTimeConfig: true, showAutoXiajia: false,
showCoupon: false, showIntegral: false, showDescription: true,
showQuanjuXiangou: false, showShowIs: false, showUnlockAmount: false,
showCoverImage: false, showDetailImage: false,
showCoverImage: false, showDetailImage: false, showBannerImage: false,
showPrice: false, showShouZhe: false,
},
}

View File

@ -33,6 +33,11 @@ public partial class Good
/// </summary>
public string ImgUrlDetail { get; set; } = null!;
/// <summary>
/// 商品详情页Banner图片URL商城类型专用
/// </summary>
public string? ImgUrlBanner { get; set; }
/// <summary>
/// 商品价格
/// </summary>

View File

@ -299,6 +299,9 @@ public class GoodsInfoDto
[JsonPropertyName("imgurl_detail")]
public string ImgUrlDetail { get; set; } = string.Empty;
[JsonPropertyName("imgurl_banner")]
public string? ImgUrlBanner { get; set; }
[JsonPropertyName("price")]
public string Price { get; set; } = "0";