feat:访问他人展览页面修改
This commit is contained in:
parent
94457ffa8a
commit
37a3f292df
Binary file not shown.
@ -96,6 +96,8 @@ func ConvertAssetInfo(pbAsset *pbGallery.AssetInfo) *AssetInfoDTO {
|
|||||||
CoverURL: pbAsset.CoverUrl,
|
CoverURL: pbAsset.CoverUrl,
|
||||||
LikeCount: pbAsset.LikeCount,
|
LikeCount: pbAsset.LikeCount,
|
||||||
RemainTime: pbAsset.RemainTime,
|
RemainTime: pbAsset.RemainTime,
|
||||||
|
Earnings: pbAsset.Earnings,
|
||||||
|
HourlyEarnings: pbAsset.HourlyEarnings,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -50,6 +50,8 @@ type AssetInfoDTO struct {
|
|||||||
CoverURL string `json:"cover_url"` // 封面图URL
|
CoverURL string `json:"cover_url"` // 封面图URL
|
||||||
LikeCount int32 `json:"like_count"` // 点赞数
|
LikeCount int32 `json:"like_count"` // 点赞数
|
||||||
RemainTime int64 `json:"remain_time"` // 剩余时间(秒)
|
RemainTime int64 `json:"remain_time"` // 剩余时间(秒)
|
||||||
|
Earnings int64 `json:"earnings"` // 当前可领取收益(与 ExhibitedAssetItemDTO 命名对齐)
|
||||||
|
HourlyEarnings float64 `json:"hourly_earnings"` // 每小时收益
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnlockConditionDTO 解锁条件
|
// UnlockConditionDTO 解锁条件
|
||||||
|
|||||||
@ -616,6 +616,8 @@ type AssetInfo struct {
|
|||||||
CoverUrl string `protobuf:"bytes,3,opt,name=cover_url,json=coverUrl,proto3" json:"cover_url,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"`
|
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"` // 剩余时间(秒)
|
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
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@ -685,6 +687,20 @@ func (x *AssetInfo) GetRemainTime() int64 {
|
|||||||
return 0
|
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 {
|
type UnlockCondition struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // level, crystal
|
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" +
|
"visibility\x12\x1f\n" +
|
||||||
"\vcan_operate\x18\v \x01(\bR\n" +
|
"\vcan_operate\x18\v \x01(\bR\n" +
|
||||||
"canOperate\x12\x1c\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" +
|
"\tAssetInfo\x12\x19\n" +
|
||||||
"\basset_id\x18\x01 \x01(\x03R\aassetId\x12\x12\n" +
|
"\basset_id\x18\x01 \x01(\x03R\aassetId\x12\x12\n" +
|
||||||
"\x04name\x18\x02 \x01(\tR\x04name\x12\x1b\n" +
|
"\x04name\x18\x02 \x01(\tR\x04name\x12\x1b\n" +
|
||||||
@ -1818,7 +1834,9 @@ const file_gallery_proto_rawDesc = "" +
|
|||||||
"\n" +
|
"\n" +
|
||||||
"like_count\x18\x04 \x01(\x05R\tlikeCount\x12\x1f\n" +
|
"like_count\x18\x04 \x01(\x05R\tlikeCount\x12\x1f\n" +
|
||||||
"\vremain_time\x18\x05 \x01(\x03R\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" +
|
"\x0fUnlockCondition\x12\x12\n" +
|
||||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x14\n" +
|
"\x04type\x18\x01 \x01(\tR\x04type\x12\x14\n" +
|
||||||
"\x05value\x18\x02 \x01(\x05R\x05value\"r\n" +
|
"\x05value\x18\x02 \x01(\x05R\x05value\"r\n" +
|
||||||
|
|||||||
@ -137,6 +137,8 @@ message AssetInfo {
|
|||||||
string cover_url = 3;
|
string cover_url = 3;
|
||||||
int32 like_count = 4;
|
int32 like_count = 4;
|
||||||
int64 remain_time = 5; // 剩余时间(秒)
|
int64 remain_time = 5; // 剩余时间(秒)
|
||||||
|
int64 earnings = 6; // 当前可领取收益(按 calculateRealtimeEarnings 实时计算)
|
||||||
|
double hourly_earnings = 7; // 每小时收益(按 calculateHourlyEarnings 计算)
|
||||||
}
|
}
|
||||||
|
|
||||||
message UnlockCondition {
|
message UnlockCondition {
|
||||||
|
|||||||
@ -510,8 +510,8 @@ func (r *galleryRepository) GetMyExhibitedAssets(userID, starID int64, page, pag
|
|||||||
// R0 = 5 水晶/小时
|
// R0 = 5 水晶/小时
|
||||||
now := time.Now().UnixMilli()
|
now := time.Now().UnixMilli()
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
item.HourlyEarnings = calculateHourlyEarnings(item.LikeCount)
|
item.HourlyEarnings = CalculateHourlyEarnings(item.LikeCount)
|
||||||
item.Earnings = calculateRealtimeEarnings(item.LikeCount, item.ExhibitedAt, now, item.ExpireAt)
|
item.Earnings = CalculateRealtimeEarnings(item.LikeCount, item.ExhibitedAt, now, item.ExpireAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
return items, total, nil
|
return items, total, nil
|
||||||
@ -554,8 +554,8 @@ func (r *galleryRepository) GetUserExhibitedAssets(userID, starID int64, page, p
|
|||||||
|
|
||||||
// 实时计算每个资产的收益
|
// 实时计算每个资产的收益
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
item.HourlyEarnings = calculateHourlyEarnings(item.LikeCount)
|
item.HourlyEarnings = CalculateHourlyEarnings(item.LikeCount)
|
||||||
item.Earnings = calculateRealtimeEarnings(item.LikeCount, item.ExhibitedAt, now, item.ExpireAt)
|
item.Earnings = CalculateRealtimeEarnings(item.LikeCount, item.ExhibitedAt, now, item.ExpireAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
return items, total, nil
|
return items, total, nil
|
||||||
@ -740,10 +740,11 @@ func generateHostProfileID(userID, starID int64) int64 {
|
|||||||
return userID*1000000 + starID
|
return userID*1000000 + starID
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculateHourlyEarnings 计算每小时收益
|
// CalculateHourlyEarnings 计算每小时收益
|
||||||
// 公式:R0 × [100% + Buff(n)]
|
// 公式:R0 × [100% + Buff(n)]
|
||||||
// R0 = 5 水晶/小时,Buff(n) 根据点赞数计算
|
// R0 = 5 水晶/小时,Buff(n) 根据点赞数计算
|
||||||
func calculateHourlyEarnings(likeCount int32) float64 {
|
// 导出供 service 层(如 gallery_service)在构造 AssetInfo 时复用,避免公式漂移
|
||||||
|
func CalculateHourlyEarnings(likeCount int32) float64 {
|
||||||
R0 := float64(5) // 水晶/小时
|
R0 := float64(5) // 水晶/小时
|
||||||
|
|
||||||
// 计算Buff
|
// 计算Buff
|
||||||
@ -763,11 +764,12 @@ func calculateHourlyEarnings(likeCount int32) float64 {
|
|||||||
return R0 * (100 + float64(buff)) / 100
|
return R0 * (100 + float64(buff)) / 100
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculateRealtimeEarnings 实时计算展示收益
|
// CalculateRealtimeEarnings 实时计算展示收益
|
||||||
// 公式:R1 = R0 × T × [100% + Buff(n)]
|
// 公式:R1 = R0 × T × [100% + Buff(n)]
|
||||||
// R0 = 5 水晶/小时,T = 上架时长(小时),Buff(n) 根据点赞数计算
|
// R0 = 5 水晶/小时,T = 上架时长(小时),Buff(n) 根据点赞数计算
|
||||||
// 注意:使用 min(now, expireAt) 确保过期后收益不再增长
|
// 注意:使用 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 当前时间,取较小值)
|
// 计算有效截止时间(展览结束时间 vs 当前时间,取较小值)
|
||||||
endTime := now
|
endTime := now
|
||||||
if expireAt > 0 && expireAt < now {
|
if expireAt > 0 && expireAt < now {
|
||||||
@ -781,5 +783,5 @@ func calculateRealtimeEarnings(likeCount int32, startTime, now, expireAt int64)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 总收益 = 每小时收益 × 时长(转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
|
remainTime = 0
|
||||||
}
|
}
|
||||||
slotInfo.Asset.RemainTime = remainTime
|
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;
|
position: relative;
|
||||||
/* 左侧留出足够空间给钻石图标,防止文字被压在图标正下方 */
|
/* 左侧留出足够空间给钻石图标,防止文字被压在图标正下方 */
|
||||||
padding-left: 50rpx;
|
padding-left: 50rpx;
|
||||||
top: -24rpx;
|
/* top: -24rpx; */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- 左侧钻石图标 --- */
|
/* --- 左侧钻石图标 --- */
|
||||||
|
|||||||
@ -557,9 +557,14 @@ const loadExhibitedAssets = async () => {
|
|||||||
cover_url: slot.asset.cover_url,
|
cover_url: slot.asset.cover_url,
|
||||||
like_count: slot.asset.like_count,
|
like_count: slot.asset.like_count,
|
||||||
earnings: slot.asset.earnings || 0,
|
earnings: slot.asset.earnings || 0,
|
||||||
|
hourly_earnings: slot.asset.hourly_earnings || 0,
|
||||||
name: slot.asset.name,
|
name: slot.asset.name,
|
||||||
slot_index: slot.slot_index ?? 0,
|
slot_index: slot.slot_index ?? 0,
|
||||||
is_lenticular: slot.asset.is_lenticular ?? false,
|
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 / 静态资源)
|
// 把后端返回的相对 cover_url 解析成可访问的 URL(OSS 预签名 / 完整URL / 静态资源)
|
||||||
for (const item of mapped) {
|
for (const item of mapped) {
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
class="message-row__input"
|
class="message-row__input"
|
||||||
:value="inputValue"
|
:value="inputValue"
|
||||||
:placeholder="displayText"
|
:placeholder="displayText"
|
||||||
placeholder-class="message-row__input-placeholder"
|
:placeholder-style="placeholderStyle"
|
||||||
confirm-type="send"
|
confirm-type="send"
|
||||||
:maxlength="200"
|
:maxlength="200"
|
||||||
@input="onInput"
|
@input="onInput"
|
||||||
@ -143,6 +143,15 @@ const displayText = computed(() => {
|
|||||||
// 2) emoji 切换不会覆盖用户已经输入的内容,只切换 placeholder
|
// 2) emoji 切换不会覆盖用户已经输入的内容,只切换 placeholder
|
||||||
const inputValue = ref("");
|
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) {
|
function onInput(e) {
|
||||||
// uniapp 小程序 / h5: e.detail.value;某些端直接传字符串;两种都兼容
|
// uniapp 小程序 / h5: e.detail.value;某些端直接传字符串;两种都兼容
|
||||||
const val = typeof e === "string" ? e : e?.detail?.value ?? "";
|
const val = typeof e === "string" ? e : e?.detail?.value ?? "";
|
||||||
@ -194,6 +203,18 @@ function handleSend() {
|
|||||||
z-index: 50;
|
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 */
|
/* 外层 pill */
|
||||||
.message-row__pill {
|
.message-row__pill {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -239,14 +260,6 @@ function handleSend() {
|
|||||||
transform-origin: center;
|
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 {
|
.message-row__avatar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 12rpx; /* 6px */
|
left: 12rpx; /* 6px */
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 我的排名条(对应 Figma 110-662) -->
|
<!-- 我的排名条(对应 Figma 110-662) -->
|
||||||
<view v-if="myInfo.rank" class="my-rank-card">
|
<view class="my-rank-card">
|
||||||
<view class="my-rank-row">
|
<view class="my-rank-row">
|
||||||
<image
|
<image
|
||||||
class="my-avatar"
|
class="my-avatar"
|
||||||
@ -27,7 +27,7 @@
|
|||||||
@error="handleAvatarError"
|
@error="handleAvatarError"
|
||||||
/>
|
/>
|
||||||
<text class="my-rank-label">当前排名</text>
|
<text class="my-rank-label">当前排名</text>
|
||||||
<text class="my-rank-number">{{ myInfo.rank }}</text>
|
<text class="my-rank-number">{{ myInfo.rank || '暂无排名' }}</text>
|
||||||
<image
|
<image
|
||||||
class="my-rank-icon"
|
class="my-rank-icon"
|
||||||
src="/static/rank/lsph.png"
|
src="/static/rank/lsph.png"
|
||||||
@ -74,6 +74,20 @@ const myInfo = ref({
|
|||||||
gapToPrev: 0,
|
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) {
|
function handleAvatarError(e) {
|
||||||
e.target.src = "/static/avatar/1.jpeg";
|
e.target.src = "/static/avatar/1.jpeg";
|
||||||
@ -123,7 +137,12 @@ async function loadRanking() {
|
|||||||
gapToPrev: calcGapToPrev(my, items),
|
gapToPrev: calcGapToPrev(my, items),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
myInfo.value = { rank: null, avatar: "", gapToPrev: 0 };
|
// 未购买过道具:仍展示卡片,排名显示"暂无排名",距离上一名显示 0
|
||||||
|
myInfo.value = {
|
||||||
|
rank: null,
|
||||||
|
avatar: getFallbackAvatar(),
|
||||||
|
gapToPrev: 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -231,6 +250,7 @@ defineExpose({
|
|||||||
font-family: "yt", "Baloo Bhai", sans-serif;
|
font-family: "yt", "Baloo Bhai", sans-serif;
|
||||||
text-shadow: -1px 1px 4px rgba(206, 9, 9, 0.45);
|
text-shadow: -1px 1px 4px rgba(206, 9, 9, 0.45);
|
||||||
margin: 0 8rpx;
|
margin: 0 8rpx;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.my-rank-icon {
|
.my-rank-icon {
|
||||||
|
|||||||
@ -697,7 +697,7 @@ onUnload(() => {
|
|||||||
/* background: rgba(255,255,255,0.5);
|
/* background: rgba(255,255,255,0.5);
|
||||||
border-radius: 50%; */
|
border-radius: 50%; */
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 88rpx;
|
top: 96rpx;
|
||||||
left: 32rpx;
|
left: 32rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -716,7 +716,7 @@ onUnload(() => {
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
/* 左侧留出足够空间给钻石图标,防止文字被压在图标正下方 */
|
/* 左侧留出足够空间给钻石图标,防止文字被压在图标正下方 */
|
||||||
padding-left: 50rpx;
|
padding-left: 50rpx;
|
||||||
top: 96rpx;
|
top: 104rpx;
|
||||||
left: 112rpx;
|
left: 112rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user