diff --git a/backend/services/galleryService/repository/gallery_repository.go b/backend/services/galleryService/repository/gallery_repository.go index efe8f07..67300c2 100644 --- a/backend/services/galleryService/repository/gallery_repository.go +++ b/backend/services/galleryService/repository/gallery_repository.go @@ -32,6 +32,11 @@ type GalleryRepository interface { // 资产注册表相关 UpdateAssetRegistryDisplayStatus(assetID int64, displayStatus int32) error + // 事务性操作:创建展品并更新展示状态(原子操作) + PlaceExhibitionTx(exhibition *models.Exhibition, displayStatus int32) error + // 事务性操作:删除展品并更新展示状态(原子操作) + RemoveExhibitionTx(exhibitionID int64, assetID int64) error + // ========== 我的作品相关 ========== // GetMyExhibitedAssets 获取我展出的作品列表(只返回展出中且未过期的,含收益) @@ -282,6 +287,59 @@ func (r *galleryRepository) UpdateAssetRegistryDisplayStatus(assetID int64, disp Update("display_status", displayStatus).Error } +// PlaceExhibitionTx 事务性创建展品并更新展示状态(原子操作) +func (r *galleryRepository) PlaceExhibitionTx(exhibition *models.Exhibition, displayStatus int32) error { + now := time.Now().UnixMilli() + exhibition.CreatedAt = now + exhibition.UpdatedAt = now + exhibition.DeletedAt = nil + + return r.db.Transaction(func(tx *gorm.DB) error { + // 1. 物理删除已软删除的旧记录 + if err := tx.Exec(` + DELETE FROM exhibitions + WHERE asset_id = ? AND deleted_at IS NOT NULL + `, exhibition.AssetID).Error; err != nil { + return err + } + // 2. 插入新记录 + if err := tx.Create(exhibition).Error; err != nil { + return err + } + // 3. 更新展示状态(与展出操作在同一事务中) + if err := tx.Model(&models.AssetRegistry{}). + Where("asset_id = ?", exhibition.AssetID). + Update("display_status", displayStatus).Error; err != nil { + return err + } + return nil + }) +} + +// RemoveExhibitionTx 事务性删除展品并更新展示状态(原子操作) +func (r *galleryRepository) RemoveExhibitionTx(exhibitionID int64, assetID int64) error { + now := time.Now().UnixMilli() + + return r.db.Transaction(func(tx *gorm.DB) error { + // 1. 软删除展品记录 + if err := tx.Model(&models.Exhibition{}). + Where("id = ?", exhibitionID). + Updates(map[string]interface{}{ + "deleted_at": now, + "updated_at": now, + }).Error; err != nil { + return err + } + // 2. 更新展示状态为未展示(与删除操作在同一事务中) + if err := tx.Model(&models.AssetRegistry{}). + Where("asset_id = ?", assetID). + Update("display_status", int32(0)).Error; err != nil { + return err + } + return nil + }) +} + // ========== 我的作品相关实现 ========== // GetMyExhibitedAssets 获取我展出的作品列表(只返回展出中且未过期的,含收益) @@ -304,15 +362,16 @@ func (r *galleryRepository) GetMyExhibitedAssets(userID, starID int64, page, pag err = r.db.Model(&models.Exhibition{}). Raw(` SELECT exhibitions.asset_id, a.name, a.cover_url, a.like_count, - exhibitions.start_time as exhibited_at, exhibitions.expire_at, + exhibitions.start_time as exhibited_at, exhibitions.expire_at, bs.slot_index, COALESCE(CAST(SUM(err.crystal_amount) / 10 AS bigint), 0) as earnings FROM exhibitions JOIN assets a ON a.id = exhibitions.asset_id + JOIN booth_slots bs ON bs.slot_id = exhibitions.slot_id LEFT JOIN exhibition_revenue_records err ON err.asset_id = a.id AND err.status = 'claimable' WHERE exhibitions.occupier_uid = ? AND exhibitions.occupier_star_id = ? AND exhibitions.deleted_at IS NULL AND exhibitions.expire_at > ? - GROUP BY exhibitions.asset_id, a.name, a.cover_url, a.like_count, exhibitions.start_time, exhibitions.expire_at - ORDER BY exhibitions.start_time DESC + GROUP BY exhibitions.asset_id, a.name, a.cover_url, a.like_count, exhibitions.start_time, exhibitions.expire_at, bs.slot_index + ORDER BY bs.slot_index ASC LIMIT ? OFFSET ? `, userID, starID, now, pageSize, offset).Scan(&items).Error @@ -343,15 +402,16 @@ func (r *galleryRepository) GetUserExhibitedAssets(userID, starID int64, page, p err = r.db.Model(&models.Exhibition{}). Raw(` SELECT exhibitions.asset_id, a.name, a.cover_url, a.like_count, - exhibitions.start_time as exhibited_at, exhibitions.expire_at, + exhibitions.start_time as exhibited_at, exhibitions.expire_at, bs.slot_index, COALESCE(CAST(SUM(err.crystal_amount) / 10 AS bigint), 0) as earnings FROM exhibitions JOIN assets a ON a.id = exhibitions.asset_id + JOIN booth_slots bs ON bs.slot_id = exhibitions.slot_id LEFT JOIN exhibition_revenue_records err ON err.asset_id = a.id AND err.status = 'claimable' WHERE exhibitions.occupier_uid = ? AND exhibitions.occupier_star_id = ? AND exhibitions.deleted_at IS NULL AND exhibitions.expire_at > ? - GROUP BY exhibitions.asset_id, a.name, a.cover_url, a.like_count, exhibitions.start_time, exhibitions.expire_at - ORDER BY exhibitions.start_time DESC + GROUP BY exhibitions.asset_id, a.name, a.cover_url, a.like_count, exhibitions.start_time, exhibitions.expire_at, bs.slot_index + ORDER BY bs.slot_index ASC LIMIT ? OFFSET ? `, userID, starID, now, pageSize, offset).Scan(&items).Error diff --git a/backend/services/starbookService/service/starbook_service.go b/backend/services/starbookService/service/starbook_service.go index d17ec5c..07d5849 100644 --- a/backend/services/starbookService/service/starbook_service.go +++ b/backend/services/starbookService/service/starbook_service.go @@ -276,6 +276,7 @@ func (s *starbookService) buildAssetItemsFromRegistries(registries []*models.Ass var assetCoverMap map[int64]string // assetID -> coverURL var assetNameMap map[int64]string // assetID -> name var categoryMap map[int64]string // assetID -> category + var assetLikeCountMap map[int64]int32 // assetID -> likeCount switch assetType { case models.AssetTypeRegular: @@ -283,9 +284,11 @@ func (s *starbookService) buildAssetItemsFromRegistries(registries []*models.Ass if err == nil && len(assets) > 0 { assetCoverMap = make(map[int64]string) assetNameMap = make(map[int64]string) + assetLikeCountMap = make(map[int64]int32) for _, asset := range assets { assetCoverMap[asset.ID] = asset.CoverURL assetNameMap[asset.ID] = asset.Name + assetLikeCountMap[asset.ID] = asset.LikeCount } } case models.AssetTypeCollection: @@ -345,6 +348,13 @@ func (s *starbookService) buildAssetItemsFromRegistries(registries []*models.Ass item.Category = cat } + // 从 assets 表获取点赞数(regular 类型) + if assetType == models.AssetTypeRegular { + if likeCount, ok := assetLikeCountMap[reg.AssetID]; ok { + item.LikeCount = likeCount + } + } + items = append(items, item) } diff --git a/frontend/pages/components/RankingListItem.vue b/frontend/pages/components/RankingListItem.vue index 1f5e5a4..07101a0 100644 --- a/frontend/pages/components/RankingListItem.vue +++ b/frontend/pages/components/RankingListItem.vue @@ -55,11 +55,11 @@ > - 拜访小屋 + 点击拜访 @@ -71,8 +71,8 @@ class="menu-item" @tap="handleMenuVisit" > - - 拜访小屋 + + 点击拜访 @@ -207,14 +207,14 @@ const handleArtworkError = (e) => { display: flex; align-items: center; justify-content: space-between; - margin-bottom: 32rpx; + margin-bottom: 24rpx; gap: 20rpx; position:relative; } .rank-decoration { position: absolute; - bottom: -24rpx; + bottom: -8rpx; left: 32rpx; width: 92%; height: 8rpx; @@ -225,14 +225,14 @@ const handleArtworkError = (e) => { /* 排名编号 */ .rank-number { - min-width: 48rpx; + min-width: 64rpx; display: flex; align-items: center; padding: 8rpx; } .rank-text { - font-size: 48rpx; + font-size: 32rpx; font-weight: bold; color: #FFFFFF; text-shadow: @@ -269,8 +269,8 @@ const handleArtworkError = (e) => { /* 作品图片 */ .artwork-image { - width: 96rpx; - height: 120rpx; + width: 80rpx; + height: 104rpx; border-radius: 16rpx; border: 3rpx solid rgba(255, 255, 255, 0.6); /* 优化:2层阴影简化为1层 */ @@ -359,6 +359,7 @@ const handleArtworkError = (e) => { } .popularity-container { + position: relative; display: flex; align-items: center; gap: 20rpx; @@ -373,12 +374,16 @@ const handleArtworkError = (e) => { } .fire-icon { - width: 32rpx; - height: 40rpx; + width: 64rpx; + height: 72rpx; + position: absolute; + left: -32rpx; + top: -32rpx; } .popularity-score { font-size: 24rpx; + padding: 0 16rpx 0 32rpx; color: #FFFFFF; text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.4), @@ -401,7 +406,8 @@ const handleArtworkError = (e) => { .visit-icon { width: 80rpx; height: 80rpx; - transform: scale(1.2); + margin-bottom: 8rpx; + /* transform: scale(1.2); */ } .visit-text{ color: #FFFFFF; diff --git a/frontend/pages/components/RankingModal.vue b/frontend/pages/components/RankingModal.vue index 1e002bb..bfdb79f 100644 --- a/frontend/pages/components/RankingModal.vue +++ b/frontend/pages/components/RankingModal.vue @@ -1295,15 +1295,12 @@ const handleTouchEnd = (e) => { .modal-container { position: absolute; - width: 100%; + width: calc(100% - 16rpx); height: calc(100% - 224rpx); bottom: 32rpx; border-radius: 48rpx; overflow: hidden; z-index: 10; - /* 优化:3层阴影简化为1层,提升渲染性能 */ - box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.35); - border: 2rpx solid rgba(255, 255, 255, 0.2); /* 添加过渡动画,使下拉关闭更流畅 */ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); } @@ -1319,9 +1316,10 @@ const handleTouchEnd = (e) => { left: 0; right: 0; bottom: 0; - width: 100%; - height: 100%; - background-image: url('/static/rank/rank-bg.png'); + /* width: 100%; + height: 100%; */ + background-image: url('/static/rank/paihangbang.png'); + background-size: 100% 100%; background-repeat: no-repeat; background-position: center; z-index: 0; @@ -1356,6 +1354,7 @@ const handleTouchEnd = (e) => { margin-bottom: 40rpx; padding: 0 15rpx; position: relative; + top: 16rpx; } .nav-arrow { @@ -1405,7 +1404,7 @@ const handleTouchEnd = (e) => { .ranking-title-image.loaded { opacity: 1; - transform: scale(2.5); + transform: scale(2.3); } @@ -1625,7 +1624,7 @@ const handleTouchEnd = (e) => { justify-content: space-between; gap: 60rpx; margin-bottom: 84rpx; - padding: 0 15rpx; + padding: 0 48rpx; } /* 空数据提示 */ @@ -1652,7 +1651,7 @@ const handleTouchEnd = (e) => { /* 排名列表区域 */ .ranking-list-section { margin-top: 20rpx; - padding: 0 15rpx; + padding: 0 48rpx; } /* 加载更多容器 */ @@ -1702,9 +1701,9 @@ const handleTouchEnd = (e) => { /* 当前用户栏 - 固定在底部 */ .current-user-bar { position: absolute; - bottom: 148rpx; - left: 20rpx; - right: 20rpx; + bottom: 152rpx; + left: 84rpx; + right: 84rpx; padding: 24rpx; /* 优化:5色渐变简化为2色,提升性能 */ background: linear-gradient(135deg, rgba(255, 107, 157, 0.9) 0%, rgba(255, 177, 153, 0.9) 100%); @@ -1733,11 +1732,12 @@ const handleTouchEnd = (e) => { /* gap: 34rpx; */ position: relative; z-index: 1; + height: 64rpx; } .current-user-avatar { width: 88rpx; - height: 88rpx; + height: 64rpx; border-radius: 50%; border: 4rpx solid rgba(255, 255, 255, 0.9); box-shadow: @@ -1746,7 +1746,7 @@ const handleTouchEnd = (e) => { } .current-user-avatar.no-artwork { - width: 64rpx; + width: 56rpx; border-radius: 0; } @@ -2103,7 +2103,7 @@ const handleTouchEnd = (e) => { .nav-arrow { min-width: 88rpx; - transform: scale(2) + transform: scale(1.7) } .tab-item { diff --git a/frontend/pages/components/StarbookContent.vue b/frontend/pages/components/StarbookContent.vue index b829763..555fa7e 100644 --- a/frontend/pages/components/StarbookContent.vue +++ b/frontend/pages/components/StarbookContent.vue @@ -1,7 +1,7 @@