diff --git a/backend/services/assetService/repository/asset_repository.go b/backend/services/assetService/repository/asset_repository.go index 2ced4f6..2e027c4 100644 --- a/backend/services/assetService/repository/asset_repository.go +++ b/backend/services/assetService/repository/asset_repository.go @@ -27,6 +27,9 @@ type AssetRepository interface { // GetDisplayStatusByAssetID 根据asset_id查询展示状态(从asset_registry表) GetDisplayStatusByAssetID(assetID int64) (int32, error) + // GetGradeByAssetID 根据asset_id查询藏品等级(从asset_registry表) + GetGradeByAssetID(assetID int64) (int32, error) + // GetByOwner 查询用户的资产列表 GetByOwner(ownerUID, starID int64, limit, offset int) ([]*models.Asset, error) @@ -127,6 +130,27 @@ func (r *assetRepository) GetDisplayStatusByAssetID(assetID int64) (int32, error return registry.DisplayStatus, nil } +// GetGradeByAssetID 根据asset_id查询藏品等级(从asset_registry表) +func (r *assetRepository) GetGradeByAssetID(assetID int64) (int32, error) { + if assetID <= 0 { + return 0, errors.New("asset_id must be greater than 0") + } + + var registry models.AssetRegistry + // 直接使用表名查询 public.asset_registry + if err := r.db.Table("public.asset_registry").Where("asset_id = ?", assetID).First(®istry).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return 0, nil // 没找到返回0,不报错 + } + return 0, err + } + + if registry.Grade != nil { + return *registry.Grade, nil + } + return 0, nil +} + // GetByIDs 批量查询资产 func (r *assetRepository) GetByIDs(assetIDs []int64) ([]*models.Asset, error) { if len(assetIDs) == 0 { diff --git a/backend/services/assetService/repository/material_repository.go b/backend/services/assetService/repository/material_repository.go index 967639d..c2048ae 100644 --- a/backend/services/assetService/repository/material_repository.go +++ b/backend/services/assetService/repository/material_repository.go @@ -42,6 +42,16 @@ func (r *MaterialRepository) FindByHash(hash string, starID int64) (*models.Mate return &m, nil } +// FindByHashAndOssKey 根据 hash 和 oss_key 查找(双重去重) +func (r *MaterialRepository) FindByHashAndOssKey(hash string, ossKey string, starID int64) (*models.Material, error) { + var m models.Material + err := r.db.Where("hash = ? AND oss_key = ? AND star_id = ? AND deleted_at IS NULL", hash, ossKey, starID).First(&m).Error + if err != nil { + return nil, err + } + return &m, nil +} + // SoftDelete 软删除素材 func (r *MaterialRepository) SoftDelete(materialID int64) error { now := time.Now().UnixMilli() diff --git a/backend/services/assetService/service/asset_service.go b/backend/services/assetService/service/asset_service.go index ca57cc7..f6ab3c0 100644 --- a/backend/services/assetService/service/asset_service.go +++ b/backend/services/assetService/service/asset_service.go @@ -490,6 +490,16 @@ func (s *assetService) GetAsset(req *pb.GetAssetRequest, userID, starID int64) ( exhibitionExpireAt, _ = s.assetRepo.GetExhibitionExpireTime(asset.ID) } + // 6.5 从 asset_registry 表获取 grade + grade, err := s.assetRepo.GetGradeByAssetID(asset.ID) + if err != nil { + logger.Logger.Warn("Failed to get grade, will return 0", + zap.Int64("asset_id", asset.ID), + zap.Error(err), + ) + grade = 0 + } + // 7. 构建响应 response := &pb.GetAssetResponse{ Base: &pbCommon.BaseResponse{ @@ -497,7 +507,7 @@ func (s *assetService) GetAsset(req *pb.GetAssetRequest, userID, starID int64) ( Message: "", Timestamp: time.Now().UnixMilli(), }, - Asset: ModelToProtoAssetDetail(asset, ownerNickname, isLiked, displayStatus, earnings, exhibitionExpireAt), + Asset: ModelToProtoAssetDetail(asset, ownerNickname, isLiked, displayStatus, earnings, exhibitionExpireAt, grade), } logger.Logger.Debug("Get asset successful", @@ -646,7 +656,7 @@ func ModelToProtoAsset(asset *models.Asset) *pb.AssetListItem { } // ModelToProtoAssetDetail 将数据库模型转换为Proto格式(Asset详情) -func ModelToProtoAssetDetail(asset *models.Asset, ownerNickname string, isLiked bool, displayStatus int32, earnings int64, exhibitionExpireAt int64) *pb.Asset { +func ModelToProtoAssetDetail(asset *models.Asset, ownerNickname string, isLiked bool, displayStatus int32, earnings int64, exhibitionExpireAt int64, grade int32) *pb.Asset { if asset == nil { return nil } @@ -659,7 +669,7 @@ func ModelToProtoAssetDetail(asset *models.Asset, ownerNickname string, isLiked CoverUrl: asset.CoverURL, MaterialUrl: getStringValue(asset.MaterialURL), Description: getStringValue(asset.Description), - Grade: getInt32Value(asset.Grade), + Grade: grade, Tags: []string(asset.Tags), Visibility: asset.Visibility, Status: asset.Status, diff --git a/backend/services/assetService/service/material_service.go b/backend/services/assetService/service/material_service.go index 9abc833..db4d3b3 100644 --- a/backend/services/assetService/service/material_service.go +++ b/backend/services/assetService/service/material_service.go @@ -22,9 +22,10 @@ func NewMaterialService(materialRepo *repository.MaterialRepository, relationRep } } -// UploadMaterial 上传素材(含 hash 去重) +// UploadMaterial 上传素材(含 hash + oss_key 双重去重) func (s *MaterialService) UploadMaterial(m *models.Material) (*models.Material, error) { - existing, err := s.materialRepo.FindByHash(m.Hash, m.StarID) + // 先按 hash + oss_key 双重判断,避免同一张图被错误复用 + existing, err := s.materialRepo.FindByHashAndOssKey(m.Hash, m.OssKey, m.StarID) if err == nil && existing != nil { return existing, nil } @@ -32,6 +33,22 @@ func (s *MaterialService) UploadMaterial(m *models.Material) (*models.Material, return nil, err } + // 如果没找到,再按单纯 hash 查找(兼容旧数据) + existing, err = s.materialRepo.FindByHash(m.Hash, m.StarID) + if err == nil && existing != nil { + // 找到旧记录但 oss_key 不同,说明是不同文件但 hash 巧合相同,需要创建新记录 + if existing.OssKey != m.OssKey { + if err := s.materialRepo.Create(m); err != nil { + return nil, err + } + return m, nil + } + return existing, nil + } + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, err + } + if err := s.materialRepo.Create(m); err != nil { return nil, err } diff --git a/backend/services/assetService/service/mint_service.go b/backend/services/assetService/service/mint_service.go index 4fa94ed..fe85dc4 100644 --- a/backend/services/assetService/service/mint_service.go +++ b/backend/services/assetService/service/mint_service.go @@ -464,7 +464,7 @@ func (s *mintService) CreateMintOrder(req *pb.CreateMintOrderRequest, userID, st Timestamp: time.Now().UnixMilli(), }, Order: ModelToProtoMintOrder(mintOrder), - Asset: ModelToProtoAssetDetail(asset, ownerNickname, false, 0, 0, 0), // 新创建的资产,is_liked 为 false,display_status 默认为 0,earnings 和 exhibitionExpireAt 为 0 + Asset: ModelToProtoAssetDetail(asset, ownerNickname, false, 0, 0, 0, getInt32Value(asset.Grade)), // 新创建的资产,is_liked 为 false,display_status 默认为 0,earnings 和 exhibitionExpireAt 为 0,grade 从 asset.Grade 获取 CostCrystal: capturedCostCrystal, BalanceAfter: newBalance, } @@ -569,7 +569,7 @@ func (s *mintService) GetMintOrder(orderID string, userID, starID int64) (*pb.Ge // 由于是查询自己的订单,is_liked 设为 false(简化处理) // 获取 display_status displayStatus, _ := s.assetRepo.GetDisplayStatusByAssetID(asset.ID) - assetProto = ModelToProtoAssetDetail(asset, ownerNickname, false, displayStatus, 0, 0) // 新创建的资产,earnings 和 exhibitionExpireAt 为 0 + assetProto = ModelToProtoAssetDetail(asset, ownerNickname, false, displayStatus, 0, 0, getInt32Value(asset.Grade)) // 新创建的资产,earnings 和 exhibitionExpireAt 为 0 // 如果 cover_url 存在,生成预签名 URL if assetProto.CoverUrl != "" { diff --git a/docker/sql/migrations/V8__exhibition_for_mock_assets.sql b/docker/sql/migrations/V8__exhibition_for_mock_assets.sql index 9563859..fafa623 100644 --- a/docker/sql/migrations/V8__exhibition_for_mock_assets.sql +++ b/docker/sql/migrations/V8__exhibition_for_mock_assets.sql @@ -14,8 +14,11 @@ DECLARE asset_rec record; i int := 0; BEGIN - -- 收集star_id=87的所有可用slot_id - SELECT array_agg(slot_id ORDER BY slot_id) INTO slot_ids FROM booth_slots WHERE star_id = 87; + -- 收集star_id=87的所有可用slot_id(排除已被用户1占用的) + SELECT array_agg(slot_id ORDER BY slot_id) INTO slot_ids + FROM booth_slots + WHERE star_id = 87 + AND slot_id NOT IN (SELECT slot_id FROM exhibitions WHERE occupier_uid = 1 AND deleted_at IS NULL); -- 为每个mock资产创建展示记录 FOR asset_rec IN SELECT id, owner_uid FROM assets WHERE id >= 10000 ORDER BY id LOOP diff --git a/frontend/components/lenticular/LenticularCard.vue b/frontend/components/lenticular/LenticularCard.vue index aa87bdb..3ee0251 100644 --- a/frontend/components/lenticular/LenticularCard.vue +++ b/frontend/components/lenticular/LenticularCard.vue @@ -109,7 +109,7 @@ const cardRotateStyle = computed(() => { const x = tiltVisualX.value const rotateY = (x / 120) * 12 return { - // transform: `perspective(1000px) rotateY(${rotateY}deg) rotateX(0deg)`, + transform: `perspective(1000px) rotateY(${rotateY}deg) rotateX(0deg)`, } }) @@ -118,7 +118,7 @@ const shimmerStyle = computed(() => { const angle = 135 + (x / 120) * 60 const a = Math.max(0, Math.min(0.35, Number(props.shimmerMidOpacity) || 0.1)) return { - // background: `linear-gradient(${angle}deg, transparent 30%, rgba(221,183,255,${a}) 50%, transparent 70%)`, + background: `linear-gradient(${angle}deg, transparent 30%, rgba(221,183,255,${a}) 50%, transparent 70%)`, } }) @@ -127,11 +127,11 @@ function getLayerStyle(layer) { const baseOpacity = t ? t.opacity : layer.opacity const x = t ? t.x : 0 // 不在行内样式写 mix-blend-mode:部分小程序 / WebView 对非 normal 支持差,可能引发渲染异常 - // return { - // opacity: baseOpacity, - // transform: `translate3d(${x}px, 0, 0)`, - // background: layer.background || 'transparent', - // } + return { + opacity: baseOpacity, + transform: `translate3d(${x}px, 0, 0)`, + background: layer.background || 'transparent', + } } function getDotStyle(dot) { diff --git a/frontend/manifest.json b/frontend/manifest.json index e09d268..dae300b 100644 --- a/frontend/manifest.json +++ b/frontend/manifest.json @@ -27,7 +27,20 @@ "Push" : {} }, "nativePlugins" : { - "imengyu-UniAndroidGyro" : {} + "imengyu-UniAndroidGyro" : { + "__plugin_info__" : { + "name" : "imengyu-UniAndroidGyro", + "description" : "APP端陀螺仪数据采集", + "platforms" : "Android,iOS", + "url" : "", + "android_package_name" : "", + "ios_bundle_id" : "", + "isCloud" : false, + "bought" : -1, + "pid" : "", + "parameters" : {} + } + } }, /* 应用发布信息 */ "distribute" : { @@ -51,7 +64,9 @@ "", "", "", - "" + "", + "", + "" ], "abiFilters" : [ "armeabi-v7a", "arm64-v8a" ] }, @@ -80,33 +95,33 @@ }, "icons" : { "android" : { - "hdpi" : "static/app-icons/72x72.png", - "xhdpi" : "static/app-icons/96x96.png", - "xxhdpi" : "static/app-icons/144x144.png", - "xxxhdpi" : "static/app-icons/192x192.png" + "hdpi" : "unpackage/res/icons/72x72.png", + "xhdpi" : "unpackage/res/icons/96x96.png", + "xxhdpi" : "unpackage/res/icons/144x144.png", + "xxxhdpi" : "unpackage/res/icons/192x192.png" }, "ios" : { - "appstore" : "static/app-icons/1024x1024.png", + "appstore" : "unpackage/res/icons/1024x1024.png", "ipad" : { - "app" : "static/app-icons/76x76.png", - "app@2x" : "static/app-icons/152x152.png", - "notification" : "static/app-icons/20x20.png", - "notification@2x" : "static/app-icons/40x40.png", - "proapp@2x" : "static/app-icons/167x167.png", - "settings" : "static/app-icons/29x29.png", - "settings@2x" : "static/app-icons/58x58.png", - "spotlight" : "static/app-icons/40x40.png", - "spotlight@2x" : "static/app-icons/80x80.png" + "app" : "unpackage/res/icons/76x76.png", + "app@2x" : "unpackage/res/icons/152x152.png", + "notification" : "unpackage/res/icons/20x20.png", + "notification@2x" : "unpackage/res/icons/40x40.png", + "proapp@2x" : "unpackage/res/icons/167x167.png", + "settings" : "unpackage/res/icons/29x29.png", + "settings@2x" : "unpackage/res/icons/58x58.png", + "spotlight" : "unpackage/res/icons/40x40.png", + "spotlight@2x" : "unpackage/res/icons/80x80.png" }, "iphone" : { - "app@2x" : "static/app-icons/120x120.png", - "app@3x" : "static/app-icons/180x180.png", - "notification@2x" : "static/app-icons/40x40.png", - "notification@3x" : "static/app-icons/60x60.png", - "settings@2x" : "static/app-icons/58x58.png", - "settings@3x" : "static/app-icons/87x87.png", - "spotlight@2x" : "static/app-icons/80x80.png", - "spotlight@3x" : "static/app-icons/120x120.png" + "app@2x" : "unpackage/res/icons/120x120.png", + "app@3x" : "unpackage/res/icons/180x180.png", + "notification@2x" : "unpackage/res/icons/40x40.png", + "notification@3x" : "unpackage/res/icons/60x60.png", + "settings@2x" : "unpackage/res/icons/58x58.png", + "settings@3x" : "unpackage/res/icons/87x87.png", + "spotlight@2x" : "unpackage/res/icons/80x80.png", + "spotlight@3x" : "unpackage/res/icons/120x120.png" } } } diff --git a/frontend/pages/asset-detail/asset-detail.vue b/frontend/pages/asset-detail/asset-detail.vue index 6a28381..4528f29 100644 --- a/frontend/pages/asset-detail/asset-detail.vue +++ b/frontend/pages/asset-detail/asset-detail.vue @@ -10,104 +10,10 @@ - - - - - - - - - - - - - - - {{ craftEarningsHint }} - - - - - - - 分类 - {{ craftCategoryLabel }} - - - - - 创作者 - {{ craftCreatorName }} - - - - - 铸爱时间 - {{ craftMintDate }} - - - - - - - - - 数根名称 - - {{ craftAssetName }} - - - - 数根发行方 - - TOPFANS - - - - 区块链编号 - - {{ craftBlockPlaceholder }} - - - - 交易哈希 - - {{ craftHashPlaceholder }} - - - - - - - - - - - + @@ -137,44 +43,28 @@ - + + - + - + -