feat:访问他人展览页面修改
This commit is contained in:
parent
94457ffa8a
commit
37a3f292df
Binary file not shown.
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 解锁条件
|
||||
|
||||
@ -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" +
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -602,7 +602,7 @@ defineExpose({
|
||||
position: relative;
|
||||
/* 左侧留出足够空间给钻石图标,防止文字被压在图标正下方 */
|
||||
padding-left: 50rpx;
|
||||
top: -24rpx;
|
||||
/* top: -24rpx; */
|
||||
}
|
||||
|
||||
/* --- 左侧钻石图标 --- */
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 + <style scoped> 在原生 widget 上
|
||||
// 不可靠(data-v 穿透不到 EditText/ UITextField),改用 placeholder-style 更稳。
|
||||
// text-shadow 在 placeholder 内联里同样用 px(避开 rpx + 负偏移在某些 Android WebView
|
||||
// 上的解析问题);其余属性与原 .message-row__input-placeholder 完全一致。
|
||||
const placeholderStyle =
|
||||
'color: rgba(255, 255, 255, 0.9); font-weight: bold; ' +
|
||||
'font-family: "Abhaya Libre ExtraBold", "PingFang SC", sans-serif; ' +
|
||||
'text-shadow: -2rpx 2rpx 8rpx rgba(206, 9, 9, 0.45);';
|
||||
|
||||
function onInput(e) {
|
||||
// uniapp 小程序 / h5: e.detail.value;某些端直接传字符串;两种都兼容
|
||||
const val = typeof e === "string" ? e : e?.detail?.value ?? "";
|
||||
@ -194,6 +203,18 @@ function handleSend() {
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
/* placeholder 样式已迁到 template 的 :placeholder-style 内联字符串,
|
||||
原因见 script 里 placeholderStyle 的注释。
|
||||
原 .message-row__input-placeholder 类已废弃,保留以下注释方便回滚对照。 */
|
||||
/*
|
||||
.message-row__input-placeholder {
|
||||
color: rgba(255, 255, 255, 0.9) !important;
|
||||
font-weight: bold;
|
||||
font-family: "Abhaya Libre ExtraBold", "PingFang SC", sans-serif;
|
||||
text-shadow: -2rpx 2rpx 8rpx rgba(206, 9, 9, 0.45);
|
||||
}
|
||||
*/
|
||||
|
||||
/* 外层 pill */
|
||||
.message-row__pill {
|
||||
position: absolute;
|
||||
@ -239,14 +260,6 @@ function handleSend() {
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.message-row__input-placeholder {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-weight: bold;
|
||||
font-family: "Abhaya Libre ExtraBold", "PingFang SC", sans-serif;
|
||||
text-shadow: -2rpx 2rpx 8rpx rgba(206, 9, 9, 0.45);
|
||||
}
|
||||
|
||||
/* 左侧头像 (突出 pill 左缘) */
|
||||
.message-row__avatar {
|
||||
position: absolute;
|
||||
left: 12rpx; /* 6px */
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
</view>
|
||||
|
||||
<!-- 我的排名条(对应 Figma 110-662) -->
|
||||
<view v-if="myInfo.rank" class="my-rank-card">
|
||||
<view class="my-rank-card">
|
||||
<view class="my-rank-row">
|
||||
<image
|
||||
class="my-avatar"
|
||||
@ -27,7 +27,7 @@
|
||||
@error="handleAvatarError"
|
||||
/>
|
||||
<text class="my-rank-label">当前排名</text>
|
||||
<text class="my-rank-number">{{ myInfo.rank }}</text>
|
||||
<text class="my-rank-number">{{ myInfo.rank || '暂无排名' }}</text>
|
||||
<image
|
||||
class="my-rank-icon"
|
||||
src="/static/rank/lsph.png"
|
||||
@ -74,6 +74,20 @@ const myInfo = ref({
|
||||
gapToPrev: 0,
|
||||
});
|
||||
|
||||
// 兜底:从未登录态 / 本地缓存读取用户头像
|
||||
function getFallbackAvatar() {
|
||||
try {
|
||||
const userStr = uni.getStorageSync("user");
|
||||
if (userStr) {
|
||||
const u = typeof userStr === "string" ? JSON.parse(userStr) : userStr;
|
||||
return u?.avatar_url || u?.avatar || "";
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略解析错误
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// 头像加载失败兜底
|
||||
function handleAvatarError(e) {
|
||||
e.target.src = "/static/avatar/1.jpeg";
|
||||
@ -123,7 +137,12 @@ async function loadRanking() {
|
||||
gapToPrev: calcGapToPrev(my, items),
|
||||
};
|
||||
} else {
|
||||
myInfo.value = { rank: null, avatar: "", gapToPrev: 0 };
|
||||
// 未购买过道具:仍展示卡片,排名显示"暂无排名",距离上一名显示 0
|
||||
myInfo.value = {
|
||||
rank: null,
|
||||
avatar: getFallbackAvatar(),
|
||||
gapToPrev: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
@ -231,6 +250,7 @@ defineExpose({
|
||||
font-family: "yt", "Baloo Bhai", sans-serif;
|
||||
text-shadow: -1px 1px 4px rgba(206, 9, 9, 0.45);
|
||||
margin: 0 8rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.my-rank-icon {
|
||||
|
||||
@ -697,7 +697,7 @@ onUnload(() => {
|
||||
/* background: rgba(255,255,255,0.5);
|
||||
border-radius: 50%; */
|
||||
position: fixed;
|
||||
top: 88rpx;
|
||||
top: 96rpx;
|
||||
left: 32rpx;
|
||||
}
|
||||
|
||||
@ -716,7 +716,7 @@ onUnload(() => {
|
||||
position: fixed;
|
||||
/* 左侧留出足够空间给钻石图标,防止文字被压在图标正下方 */
|
||||
padding-left: 50rpx;
|
||||
top: 96rpx;
|
||||
top: 104rpx;
|
||||
left: 112rpx;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user