diff --git a/backend/galleryService b/backend/galleryService deleted file mode 100755 index 48720ab..0000000 Binary files a/backend/galleryService and /dev/null differ diff --git a/backend/gateway/dto/gallery_converter.go b/backend/gateway/dto/gallery_converter.go index 73bdbbb..59bf455 100644 --- a/backend/gateway/dto/gallery_converter.go +++ b/backend/gateway/dto/gallery_converter.go @@ -91,11 +91,13 @@ func ConvertAssetInfo(pbAsset *pbGallery.AssetInfo) *AssetInfoDTO { } return &AssetInfoDTO{ - AssetID: pbAsset.AssetId, - Name: pbAsset.Name, - CoverURL: pbAsset.CoverUrl, - LikeCount: pbAsset.LikeCount, - RemainTime: pbAsset.RemainTime, + AssetID: pbAsset.AssetId, + Name: pbAsset.Name, + CoverURL: pbAsset.CoverUrl, + LikeCount: pbAsset.LikeCount, + RemainTime: pbAsset.RemainTime, + Earnings: pbAsset.Earnings, + HourlyEarnings: pbAsset.HourlyEarnings, } } diff --git a/backend/gateway/dto/gallery_dto.go b/backend/gateway/dto/gallery_dto.go index 0eed245..1db0fbe 100644 --- a/backend/gateway/dto/gallery_dto.go +++ b/backend/gateway/dto/gallery_dto.go @@ -45,11 +45,13 @@ type SlotInfoDTO struct { // AssetInfoDTO 资产信息(展馆展示用) type AssetInfoDTO struct { - AssetID int64 `json:"asset_id"` // 资产ID - Name string `json:"name"` // 资产名称 - CoverURL string `json:"cover_url"` // 封面图URL - LikeCount int32 `json:"like_count"` // 点赞数 - RemainTime int64 `json:"remain_time"` // 剩余时间(秒) + AssetID int64 `json:"asset_id"` // 资产ID + Name string `json:"name"` // 资产名称 + CoverURL string `json:"cover_url"` // 封面图URL + LikeCount int32 `json:"like_count"` // 点赞数 + RemainTime int64 `json:"remain_time"` // 剩余时间(秒) + Earnings int64 `json:"earnings"` // 当前可领取收益(与 ExhibitedAssetItemDTO 命名对齐) + HourlyEarnings float64 `json:"hourly_earnings"` // 每小时收益 } // UnlockConditionDTO 解锁条件 diff --git a/backend/pkg/proto/gallery/gallery.pb.go b/backend/pkg/proto/gallery/gallery.pb.go index 6bb035c..5195690 100644 --- a/backend/pkg/proto/gallery/gallery.pb.go +++ b/backend/pkg/proto/gallery/gallery.pb.go @@ -610,14 +610,16 @@ func (x *SlotInfo) GetOperation() string { } type AssetInfo struct { - state protoimpl.MessageState `protogen:"open.v1"` - AssetId int64 `protobuf:"varint,1,opt,name=asset_id,json=assetId,proto3" json:"asset_id,omitempty"` - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` - CoverUrl string `protobuf:"bytes,3,opt,name=cover_url,json=coverUrl,proto3" json:"cover_url,omitempty"` - LikeCount int32 `protobuf:"varint,4,opt,name=like_count,json=likeCount,proto3" json:"like_count,omitempty"` - RemainTime int64 `protobuf:"varint,5,opt,name=remain_time,json=remainTime,proto3" json:"remain_time,omitempty"` // 剩余时间(秒) - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + AssetId int64 `protobuf:"varint,1,opt,name=asset_id,json=assetId,proto3" json:"asset_id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + CoverUrl string `protobuf:"bytes,3,opt,name=cover_url,json=coverUrl,proto3" json:"cover_url,omitempty"` + LikeCount int32 `protobuf:"varint,4,opt,name=like_count,json=likeCount,proto3" json:"like_count,omitempty"` + RemainTime int64 `protobuf:"varint,5,opt,name=remain_time,json=remainTime,proto3" json:"remain_time,omitempty"` // 剩余时间(秒) + Earnings int64 `protobuf:"varint,6,opt,name=earnings,proto3" json:"earnings,omitempty"` // 当前可领取收益(按 calculateRealtimeEarnings 实时计算) + HourlyEarnings float64 `protobuf:"fixed64,7,opt,name=hourly_earnings,json=hourlyEarnings,proto3" json:"hourly_earnings,omitempty"` // 每小时收益(按 calculateHourlyEarnings 计算) + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AssetInfo) Reset() { @@ -685,6 +687,20 @@ func (x *AssetInfo) GetRemainTime() int64 { return 0 } +func (x *AssetInfo) GetEarnings() int64 { + if x != nil { + return x.Earnings + } + return 0 +} + +func (x *AssetInfo) GetHourlyEarnings() float64 { + if x != nil { + return x.HourlyEarnings + } + return 0 +} + type UnlockCondition struct { state protoimpl.MessageState `protogen:"open.v1"` Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // level, crystal @@ -1810,7 +1826,7 @@ const file_gallery_proto_rawDesc = "" + "visibility\x12\x1f\n" + "\vcan_operate\x18\v \x01(\bR\n" + "canOperate\x12\x1c\n" + - "\toperation\x18\f \x01(\tR\toperation\"\x97\x01\n" + + "\toperation\x18\f \x01(\tR\toperation\"\xdc\x01\n" + "\tAssetInfo\x12\x19\n" + "\basset_id\x18\x01 \x01(\x03R\aassetId\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12\x1b\n" + @@ -1818,7 +1834,9 @@ const file_gallery_proto_rawDesc = "" + "\n" + "like_count\x18\x04 \x01(\x05R\tlikeCount\x12\x1f\n" + "\vremain_time\x18\x05 \x01(\x03R\n" + - "remainTime\";\n" + + "remainTime\x12\x1a\n" + + "\bearnings\x18\x06 \x01(\x03R\bearnings\x12'\n" + + "\x0fhourly_earnings\x18\a \x01(\x01R\x0ehourlyEarnings\";\n" + "\x0fUnlockCondition\x12\x12\n" + "\x04type\x18\x01 \x01(\tR\x04type\x12\x14\n" + "\x05value\x18\x02 \x01(\x05R\x05value\"r\n" + diff --git a/backend/proto/gallery.proto b/backend/proto/gallery.proto index 9818f68..2188d2d 100644 --- a/backend/proto/gallery.proto +++ b/backend/proto/gallery.proto @@ -137,6 +137,8 @@ message AssetInfo { string cover_url = 3; int32 like_count = 4; int64 remain_time = 5; // 剩余时间(秒) + int64 earnings = 6; // 当前可领取收益(按 calculateRealtimeEarnings 实时计算) + double hourly_earnings = 7; // 每小时收益(按 calculateHourlyEarnings 计算) } message UnlockCondition { diff --git a/backend/services/galleryService/repository/gallery_repository.go b/backend/services/galleryService/repository/gallery_repository.go index b274428..febb35c 100644 --- a/backend/services/galleryService/repository/gallery_repository.go +++ b/backend/services/galleryService/repository/gallery_repository.go @@ -510,8 +510,8 @@ func (r *galleryRepository) GetMyExhibitedAssets(userID, starID int64, page, pag // R0 = 5 水晶/小时 now := time.Now().UnixMilli() for _, item := range items { - item.HourlyEarnings = calculateHourlyEarnings(item.LikeCount) - item.Earnings = calculateRealtimeEarnings(item.LikeCount, item.ExhibitedAt, now, item.ExpireAt) + item.HourlyEarnings = CalculateHourlyEarnings(item.LikeCount) + item.Earnings = CalculateRealtimeEarnings(item.LikeCount, item.ExhibitedAt, now, item.ExpireAt) } return items, total, nil @@ -554,8 +554,8 @@ func (r *galleryRepository) GetUserExhibitedAssets(userID, starID int64, page, p // 实时计算每个资产的收益 for _, item := range items { - item.HourlyEarnings = calculateHourlyEarnings(item.LikeCount) - item.Earnings = calculateRealtimeEarnings(item.LikeCount, item.ExhibitedAt, now, item.ExpireAt) + item.HourlyEarnings = CalculateHourlyEarnings(item.LikeCount) + item.Earnings = CalculateRealtimeEarnings(item.LikeCount, item.ExhibitedAt, now, item.ExpireAt) } return items, total, nil @@ -740,10 +740,11 @@ func generateHostProfileID(userID, starID int64) int64 { return userID*1000000 + starID } -// calculateHourlyEarnings 计算每小时收益 +// CalculateHourlyEarnings 计算每小时收益 // 公式:R0 × [100% + Buff(n)] // R0 = 5 水晶/小时,Buff(n) 根据点赞数计算 -func calculateHourlyEarnings(likeCount int32) float64 { +// 导出供 service 层(如 gallery_service)在构造 AssetInfo 时复用,避免公式漂移 +func CalculateHourlyEarnings(likeCount int32) float64 { R0 := float64(5) // 水晶/小时 // 计算Buff @@ -763,11 +764,12 @@ func calculateHourlyEarnings(likeCount int32) float64 { return R0 * (100 + float64(buff)) / 100 } -// calculateRealtimeEarnings 实时计算展示收益 +// CalculateRealtimeEarnings 实时计算展示收益 // 公式:R1 = R0 × T × [100% + Buff(n)] // R0 = 5 水晶/小时,T = 上架时长(小时),Buff(n) 根据点赞数计算 // 注意:使用 min(now, expireAt) 确保过期后收益不再增长 -func calculateRealtimeEarnings(likeCount int32, startTime, now, expireAt int64) int64 { +// 导出供 service 层(如 gallery_service)在构造 AssetInfo 时复用,避免公式漂移 +func CalculateRealtimeEarnings(likeCount int32, startTime, now, expireAt int64) int64 { // 计算有效截止时间(展览结束时间 vs 当前时间,取较小值) endTime := now if expireAt > 0 && expireAt < now { @@ -781,5 +783,5 @@ func calculateRealtimeEarnings(likeCount int32, startTime, now, expireAt int64) } // 总收益 = 每小时收益 × 时长(转int64取整) - return int64(calculateHourlyEarnings(likeCount) * float64(T)) + return int64(CalculateHourlyEarnings(likeCount) * float64(T)) } diff --git a/backend/services/galleryService/service/gallery_service.go b/backend/services/galleryService/service/gallery_service.go index bc41f1e..b57230a 100644 --- a/backend/services/galleryService/service/gallery_service.go +++ b/backend/services/galleryService/service/gallery_service.go @@ -205,6 +205,9 @@ func (s *galleryService) buildSlotInfos(slots []*models.BoothSlot, viewerUID, vi remainTime = 0 } slotInfo.Asset.RemainTime = remainTime + // 计算收益(复用 repository 中的公式,与 GetMyExhibitedAssets 保持一致) + slotInfo.Asset.HourlyEarnings = repository.CalculateHourlyEarnings(assetInfo.LikeCount) + slotInfo.Asset.Earnings = repository.CalculateRealtimeEarnings(assetInfo.LikeCount, exhibition.StartTime, now, exhibition.ExpireAt) } } } diff --git a/frontend/pages/components/Header.vue b/frontend/pages/components/Header.vue index 15dbda7..0da16cf 100644 --- a/frontend/pages/components/Header.vue +++ b/frontend/pages/components/Header.vue @@ -602,7 +602,7 @@ defineExpose({ position: relative; /* 左侧留出足够空间给钻石图标,防止文字被压在图标正下方 */ padding-left: 50rpx; - top: -24rpx; + /* top: -24rpx; */ } /* --- 左侧钻石图标 --- */ diff --git a/frontend/pages/profile/hisWorks.vue b/frontend/pages/profile/hisWorks.vue index 609b213..6dff19b 100644 --- a/frontend/pages/profile/hisWorks.vue +++ b/frontend/pages/profile/hisWorks.vue @@ -557,9 +557,14 @@ const loadExhibitedAssets = async () => { cover_url: slot.asset.cover_url, like_count: slot.asset.like_count, earnings: slot.asset.earnings || 0, + hourly_earnings: slot.asset.hourly_earnings || 0, name: slot.asset.name, slot_index: slot.slot_index ?? 0, is_lenticular: slot.asset.is_lenticular ?? false, + // 修复倒计时永远 00:00:00 的 bug:补上 remainSeconds / expire_at 字段映射 + remainSeconds: slot.asset.remain_time || 0, + expire_at: slot.expire_at, + occupied_at: slot.occupied_at, })); // 把后端返回的相对 cover_url 解析成可访问的 URL(OSS 预签名 / 完整URL / 静态资源) for (const item of mapped) { diff --git a/frontend/pages/support-activity/components/MessageInput.vue b/frontend/pages/support-activity/components/MessageInput.vue index 20947a0..9f13306 100644 --- a/frontend/pages/support-activity/components/MessageInput.vue +++ b/frontend/pages/support-activity/components/MessageInput.vue @@ -18,7 +18,7 @@ class="message-row__input" :value="inputValue" :placeholder="displayText" - placeholder-class="message-row__input-placeholder" + :placeholder-style="placeholderStyle" confirm-type="send" :maxlength="200" @input="onInput" @@ -143,6 +143,15 @@ const displayText = computed(() => { // 2) emoji 切换不会覆盖用户已经输入的内容,只切换 placeholder const inputValue = ref(""); +// placeholder 内联样式:app 端 placeholder-class +