feat: 修改各种小bug
This commit is contained in:
parent
78c70e14df
commit
2450fc1368
@ -27,6 +27,9 @@ type AssetRepository interface {
|
|||||||
// GetDisplayStatusByAssetID 根据asset_id查询展示状态(从asset_registry表)
|
// GetDisplayStatusByAssetID 根据asset_id查询展示状态(从asset_registry表)
|
||||||
GetDisplayStatusByAssetID(assetID int64) (int32, error)
|
GetDisplayStatusByAssetID(assetID int64) (int32, error)
|
||||||
|
|
||||||
|
// GetGradeByAssetID 根据asset_id查询藏品等级(从asset_registry表)
|
||||||
|
GetGradeByAssetID(assetID int64) (int32, error)
|
||||||
|
|
||||||
// GetByOwner 查询用户的资产列表
|
// GetByOwner 查询用户的资产列表
|
||||||
GetByOwner(ownerUID, starID int64, limit, offset int) ([]*models.Asset, error)
|
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
|
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 批量查询资产
|
// GetByIDs 批量查询资产
|
||||||
func (r *assetRepository) GetByIDs(assetIDs []int64) ([]*models.Asset, error) {
|
func (r *assetRepository) GetByIDs(assetIDs []int64) ([]*models.Asset, error) {
|
||||||
if len(assetIDs) == 0 {
|
if len(assetIDs) == 0 {
|
||||||
|
|||||||
@ -42,6 +42,16 @@ func (r *MaterialRepository) FindByHash(hash string, starID int64) (*models.Mate
|
|||||||
return &m, nil
|
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 软删除素材
|
// SoftDelete 软删除素材
|
||||||
func (r *MaterialRepository) SoftDelete(materialID int64) error {
|
func (r *MaterialRepository) SoftDelete(materialID int64) error {
|
||||||
now := time.Now().UnixMilli()
|
now := time.Now().UnixMilli()
|
||||||
|
|||||||
@ -490,6 +490,16 @@ func (s *assetService) GetAsset(req *pb.GetAssetRequest, userID, starID int64) (
|
|||||||
exhibitionExpireAt, _ = s.assetRepo.GetExhibitionExpireTime(asset.ID)
|
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. 构建响应
|
// 7. 构建响应
|
||||||
response := &pb.GetAssetResponse{
|
response := &pb.GetAssetResponse{
|
||||||
Base: &pbCommon.BaseResponse{
|
Base: &pbCommon.BaseResponse{
|
||||||
@ -497,7 +507,7 @@ func (s *assetService) GetAsset(req *pb.GetAssetRequest, userID, starID int64) (
|
|||||||
Message: "",
|
Message: "",
|
||||||
Timestamp: time.Now().UnixMilli(),
|
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",
|
logger.Logger.Debug("Get asset successful",
|
||||||
@ -646,7 +656,7 @@ func ModelToProtoAsset(asset *models.Asset) *pb.AssetListItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ModelToProtoAssetDetail 将数据库模型转换为Proto格式(Asset详情)
|
// 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 {
|
if asset == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -659,7 +669,7 @@ func ModelToProtoAssetDetail(asset *models.Asset, ownerNickname string, isLiked
|
|||||||
CoverUrl: asset.CoverURL,
|
CoverUrl: asset.CoverURL,
|
||||||
MaterialUrl: getStringValue(asset.MaterialURL),
|
MaterialUrl: getStringValue(asset.MaterialURL),
|
||||||
Description: getStringValue(asset.Description),
|
Description: getStringValue(asset.Description),
|
||||||
Grade: getInt32Value(asset.Grade),
|
Grade: grade,
|
||||||
Tags: []string(asset.Tags),
|
Tags: []string(asset.Tags),
|
||||||
Visibility: asset.Visibility,
|
Visibility: asset.Visibility,
|
||||||
Status: asset.Status,
|
Status: asset.Status,
|
||||||
|
|||||||
@ -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) {
|
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 {
|
if err == nil && existing != nil {
|
||||||
return existing, nil
|
return existing, nil
|
||||||
}
|
}
|
||||||
@ -32,6 +33,22 @@ func (s *MaterialService) UploadMaterial(m *models.Material) (*models.Material,
|
|||||||
return nil, err
|
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 {
|
if err := s.materialRepo.Create(m); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -464,7 +464,7 @@ func (s *mintService) CreateMintOrder(req *pb.CreateMintOrderRequest, userID, st
|
|||||||
Timestamp: time.Now().UnixMilli(),
|
Timestamp: time.Now().UnixMilli(),
|
||||||
},
|
},
|
||||||
Order: ModelToProtoMintOrder(mintOrder),
|
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,
|
CostCrystal: capturedCostCrystal,
|
||||||
BalanceAfter: newBalance,
|
BalanceAfter: newBalance,
|
||||||
}
|
}
|
||||||
@ -569,7 +569,7 @@ func (s *mintService) GetMintOrder(orderID string, userID, starID int64) (*pb.Ge
|
|||||||
// 由于是查询自己的订单,is_liked 设为 false(简化处理)
|
// 由于是查询自己的订单,is_liked 设为 false(简化处理)
|
||||||
// 获取 display_status
|
// 获取 display_status
|
||||||
displayStatus, _ := s.assetRepo.GetDisplayStatusByAssetID(asset.ID)
|
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
|
// 如果 cover_url 存在,生成预签名 URL
|
||||||
if assetProto.CoverUrl != "" {
|
if assetProto.CoverUrl != "" {
|
||||||
|
|||||||
@ -14,8 +14,11 @@ DECLARE
|
|||||||
asset_rec record;
|
asset_rec record;
|
||||||
i int := 0;
|
i int := 0;
|
||||||
BEGIN
|
BEGIN
|
||||||
-- 收集star_id=87的所有可用slot_id
|
-- 收集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;
|
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资产创建展示记录
|
-- 为每个mock资产创建展示记录
|
||||||
FOR asset_rec IN SELECT id, owner_uid FROM assets WHERE id >= 10000 ORDER BY id LOOP
|
FOR asset_rec IN SELECT id, owner_uid FROM assets WHERE id >= 10000 ORDER BY id LOOP
|
||||||
|
|||||||
@ -109,7 +109,7 @@ const cardRotateStyle = computed(() => {
|
|||||||
const x = tiltVisualX.value
|
const x = tiltVisualX.value
|
||||||
const rotateY = (x / 120) * 12
|
const rotateY = (x / 120) * 12
|
||||||
return {
|
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 angle = 135 + (x / 120) * 60
|
||||||
const a = Math.max(0, Math.min(0.35, Number(props.shimmerMidOpacity) || 0.1))
|
const a = Math.max(0, Math.min(0.35, Number(props.shimmerMidOpacity) || 0.1))
|
||||||
return {
|
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 baseOpacity = t ? t.opacity : layer.opacity
|
||||||
const x = t ? t.x : 0
|
const x = t ? t.x : 0
|
||||||
// 不在行内样式写 mix-blend-mode:部分小程序 / WebView 对非 normal 支持差,可能引发渲染异常
|
// 不在行内样式写 mix-blend-mode:部分小程序 / WebView 对非 normal 支持差,可能引发渲染异常
|
||||||
// return {
|
return {
|
||||||
// opacity: baseOpacity,
|
opacity: baseOpacity,
|
||||||
// transform: `translate3d(${x}px, 0, 0)`,
|
transform: `translate3d(${x}px, 0, 0)`,
|
||||||
// background: layer.background || 'transparent',
|
background: layer.background || 'transparent',
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDotStyle(dot) {
|
function getDotStyle(dot) {
|
||||||
|
|||||||
@ -27,7 +27,20 @@
|
|||||||
"Push" : {}
|
"Push" : {}
|
||||||
},
|
},
|
||||||
"nativePlugins" : {
|
"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" : {
|
"distribute" : {
|
||||||
@ -51,7 +64,9 @@
|
|||||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
|
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
|
||||||
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
|
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
|
||||||
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
|
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
|
||||||
"<uses-permission android:name=\"android.permission.INTERNET\"/>"
|
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.BODY_SENSORS\"/>",
|
||||||
|
"<uses-feature android:name=\"android.hardware.sensor.gyroscope\"/>"
|
||||||
],
|
],
|
||||||
"abiFilters" : [ "armeabi-v7a", "arm64-v8a" ]
|
"abiFilters" : [ "armeabi-v7a", "arm64-v8a" ]
|
||||||
},
|
},
|
||||||
@ -80,33 +95,33 @@
|
|||||||
},
|
},
|
||||||
"icons" : {
|
"icons" : {
|
||||||
"android" : {
|
"android" : {
|
||||||
"hdpi" : "static/app-icons/72x72.png",
|
"hdpi" : "unpackage/res/icons/72x72.png",
|
||||||
"xhdpi" : "static/app-icons/96x96.png",
|
"xhdpi" : "unpackage/res/icons/96x96.png",
|
||||||
"xxhdpi" : "static/app-icons/144x144.png",
|
"xxhdpi" : "unpackage/res/icons/144x144.png",
|
||||||
"xxxhdpi" : "static/app-icons/192x192.png"
|
"xxxhdpi" : "unpackage/res/icons/192x192.png"
|
||||||
},
|
},
|
||||||
"ios" : {
|
"ios" : {
|
||||||
"appstore" : "static/app-icons/1024x1024.png",
|
"appstore" : "unpackage/res/icons/1024x1024.png",
|
||||||
"ipad" : {
|
"ipad" : {
|
||||||
"app" : "static/app-icons/76x76.png",
|
"app" : "unpackage/res/icons/76x76.png",
|
||||||
"app@2x" : "static/app-icons/152x152.png",
|
"app@2x" : "unpackage/res/icons/152x152.png",
|
||||||
"notification" : "static/app-icons/20x20.png",
|
"notification" : "unpackage/res/icons/20x20.png",
|
||||||
"notification@2x" : "static/app-icons/40x40.png",
|
"notification@2x" : "unpackage/res/icons/40x40.png",
|
||||||
"proapp@2x" : "static/app-icons/167x167.png",
|
"proapp@2x" : "unpackage/res/icons/167x167.png",
|
||||||
"settings" : "static/app-icons/29x29.png",
|
"settings" : "unpackage/res/icons/29x29.png",
|
||||||
"settings@2x" : "static/app-icons/58x58.png",
|
"settings@2x" : "unpackage/res/icons/58x58.png",
|
||||||
"spotlight" : "static/app-icons/40x40.png",
|
"spotlight" : "unpackage/res/icons/40x40.png",
|
||||||
"spotlight@2x" : "static/app-icons/80x80.png"
|
"spotlight@2x" : "unpackage/res/icons/80x80.png"
|
||||||
},
|
},
|
||||||
"iphone" : {
|
"iphone" : {
|
||||||
"app@2x" : "static/app-icons/120x120.png",
|
"app@2x" : "unpackage/res/icons/120x120.png",
|
||||||
"app@3x" : "static/app-icons/180x180.png",
|
"app@3x" : "unpackage/res/icons/180x180.png",
|
||||||
"notification@2x" : "static/app-icons/40x40.png",
|
"notification@2x" : "unpackage/res/icons/40x40.png",
|
||||||
"notification@3x" : "static/app-icons/60x60.png",
|
"notification@3x" : "unpackage/res/icons/60x60.png",
|
||||||
"settings@2x" : "static/app-icons/58x58.png",
|
"settings@2x" : "unpackage/res/icons/58x58.png",
|
||||||
"settings@3x" : "static/app-icons/87x87.png",
|
"settings@3x" : "unpackage/res/icons/87x87.png",
|
||||||
"spotlight@2x" : "static/app-icons/80x80.png",
|
"spotlight@2x" : "unpackage/res/icons/80x80.png",
|
||||||
"spotlight@3x" : "static/app-icons/120x120.png"
|
"spotlight@3x" : "unpackage/res/icons/120x120.png"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,104 +10,10 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 铸爱:选图后确认铸造(光栅 / 镭射) -->
|
|
||||||
<scroll-view v-if="craftConfirmMode" scroll-y class="content-scroll craft-confirm-scroll">
|
|
||||||
<view class="content-wrapper craft-confirm-body">
|
|
||||||
<view class="card-section craft-card-section">
|
|
||||||
<view class="card-wrapper craft-card-wrapper">
|
|
||||||
<image
|
|
||||||
class="card-frame"
|
|
||||||
src="/static/square/gerenzhongxincangpinkuang.png"
|
|
||||||
mode="aspectFit"
|
|
||||||
/>
|
|
||||||
<view v-if="isCraftLenticular" class="craft-lenticular-slot">
|
|
||||||
<LenticularCard
|
|
||||||
class="craft-lenticular-card"
|
|
||||||
:layers="lenticularLayers"
|
|
||||||
:transforms="layerTransforms"
|
|
||||||
:gyro-source="gyroSourceLabel"
|
|
||||||
:skip-built-in-touch="true"
|
|
||||||
tilt-hint-text="晃动查看"
|
|
||||||
:shimmer-mid-opacity="0.16"
|
|
||||||
@simulate="simulate"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
<image
|
|
||||||
v-else
|
|
||||||
class="card-image craft-card-image"
|
|
||||||
:src="craftCoverUrl"
|
|
||||||
mode="aspectFill"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
<view class="card-meta-row">
|
|
||||||
<view class="earnings-area">
|
|
||||||
<image class="crystal-icon" src="/static/icon/crystal.png" mode="aspectFit" />
|
|
||||||
<text class="earnings-text">{{ craftEarningsHint }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="info-row craft-info-row">
|
|
||||||
<view class="info-col">
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="info-label">分类</text>
|
|
||||||
<text class="info-value">{{ craftCategoryLabel }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="info-col">
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="info-label">创作者</text>
|
|
||||||
<text class="info-value">{{ craftCreatorName }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="info-col">
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="info-label">铸爱时间</text>
|
|
||||||
<text class="info-value">{{ craftMintDate }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="chain-section">
|
|
||||||
<image class="chain-logo" src="/static/logo/APPLOGO.png" mode="aspectFit" />
|
|
||||||
<view class="chain-left">
|
|
||||||
<view class="chain-row">
|
|
||||||
<text class="chain-label">数根名称</text>
|
|
||||||
<view class="chain-value-wrap">
|
|
||||||
<text class="chain-value">{{ craftAssetName }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="chain-row">
|
|
||||||
<text class="chain-label">数根发行方</text>
|
|
||||||
<view class="chain-value-wrap">
|
|
||||||
<text class="chain-value">TOPFANS</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="chain-row">
|
|
||||||
<text class="chain-label">区块链编号</text>
|
|
||||||
<view class="chain-value-wrap">
|
|
||||||
<text class="chain-value">{{ craftBlockPlaceholder }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="chain-row">
|
|
||||||
<text class="chain-label">交易哈希</text>
|
|
||||||
<view class="chain-value-wrap">
|
|
||||||
<text class="chain-value chain-hash">{{ craftHashPlaceholder }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="craft-mint-bar">
|
|
||||||
<button class="craft-mint-btn" :disabled="craftMinting" @tap="handleCraftMint">
|
|
||||||
{{ craftMinting ? '提交中…' : '确认铸造' }}
|
|
||||||
</button>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</scroll-view>
|
|
||||||
|
|
||||||
<!-- 加载中 -->
|
<!-- 加载中 -->
|
||||||
<view v-else-if="loading" class="loading-wrapper">
|
<view v-if="loading" class="loading-wrapper">
|
||||||
<!-- 旋转光环 -->
|
<!-- 旋转光环 -->
|
||||||
<view class="loading-ring-outer">
|
<view class="loading-ring-outer">
|
||||||
<view class="loading-ring"></view>
|
<view class="loading-ring"></view>
|
||||||
@ -137,44 +43,28 @@
|
|||||||
<image class="card-frame" src="/static/square/gerenzhongxincangpinkuang.png" mode="aspectFit">
|
<image class="card-frame" src="/static/square/gerenzhongxincangpinkuang.png" mode="aspectFit">
|
||||||
</image>
|
</image>
|
||||||
<view v-if="isLenticularAsset" class="detail-lenticular-slot">
|
<view v-if="isLenticularAsset" class="detail-lenticular-slot">
|
||||||
<LenticularCard
|
<LenticularCard class="detail-lenticular-card" :layers="lenticularLayers"
|
||||||
class="detail-lenticular-card"
|
:transforms="layerTransforms" :gyro-source="gyroSourceLabel" :skip-built-in-touch="true"
|
||||||
:layers="lenticularLayers"
|
tilt-hint-text="倾斜手机查看光栅效果" :shimmer-mid-opacity="0.16" @simulate="simulate" />
|
||||||
:transforms="layerTransforms"
|
|
||||||
:gyro-source="gyroSourceLabel"
|
|
||||||
:skip-built-in-touch="true"
|
|
||||||
tilt-hint-text="倾斜手机查看光栅效果"
|
|
||||||
:shimmer-mid-opacity="0.16"
|
|
||||||
@simulate="simulate"
|
|
||||||
/>
|
|
||||||
</view>
|
</view>
|
||||||
<image v-else class="card-image" :src="coverUrl" mode="aspectFill"></image>
|
<image v-else class="card-image" :src="coverUrl" mode="aspectFill"></image>
|
||||||
|
<image class="card-badge" :src="gradeBadgeUrl" mode="aspectFit"></image>
|
||||||
<!-- 贴纸叠加层 -->
|
<!-- 贴纸叠加层 -->
|
||||||
<image
|
<image v-for="sticker in activeStickers" :key="sticker.id" class="card-sticker"
|
||||||
v-for="sticker in activeStickers"
|
:src="sticker.src" mode="aspectFit" :style="getStickerStyle(sticker)" />
|
||||||
:key="sticker.id"
|
|
||||||
class="card-sticker"
|
|
||||||
:src="sticker.src"
|
|
||||||
mode="aspectFit"
|
|
||||||
:style="getStickerStyle(sticker)"
|
|
||||||
/>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 贴纸合成导出隐藏画布 -->
|
<!-- 贴纸合成导出隐藏画布 -->
|
||||||
<canvas
|
<canvas v-if="activeStickers.length" canvas-id="stickerCompositCanvas" class="export-canvas"
|
||||||
v-if="activeStickers.length"
|
:style="{ position: 'fixed', left: '-9999px', top: '-9999px', width: '450px', height: '600px' }" />
|
||||||
canvas-id="stickerCompositCanvas"
|
|
||||||
class="export-canvas"
|
|
||||||
:style="{ position: 'fixed', left: '-9999px', top: '-9999px', width: '450px', height: '600px' }"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 点赞 + 收益 + 倒计时行 -->
|
<!-- 点赞 + 收益 + 倒计时行 -->
|
||||||
<view class="card-meta-row">
|
<view class="card-meta-row">
|
||||||
<!-- 点赞 -->
|
<!-- 点赞 -->
|
||||||
<view v-if="assetData.display_status === 1" class="like-area" @tap="handleLike">
|
<view class="like-area" @tap="handleLike">
|
||||||
<image :src="isLiked ? '/static/icon/like-after.png' : '/static/icon/like-before.png'"
|
<image :src="isLiked ? '/static/icon/like-after.png' : '/static/icon/like-before.png'"
|
||||||
class="like-icon" mode="aspectFit"></image>
|
class="like-icon" mode="aspectFit"></image>
|
||||||
<text class="like-num">{{ likeCount }}</text>
|
<text class="like-num">{{ likeCount || 0 }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 收益 -->
|
<!-- 收益 -->
|
||||||
@ -204,7 +94,8 @@
|
|||||||
<view class="info-item">
|
<view class="info-item">
|
||||||
<text class="info-label">创作者</text>
|
<text class="info-label">创作者</text>
|
||||||
<view class="info-nickname">
|
<view class="info-nickname">
|
||||||
<image v-if="userAvatarUrl" class="info-avatar" :src="userAvatarUrl" mode="aspectFill"></image>
|
<image v-if="userAvatarUrl" class="info-avatar" :src="userAvatarUrl" mode="aspectFill">
|
||||||
|
</image>
|
||||||
<text class="info-value">{{ assetData.owner_nickname || '未知' }}</text>
|
<text class="info-value">{{ assetData.owner_nickname || '未知' }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -273,7 +164,9 @@
|
|||||||
<text class="chain-value">{{ showBlockNumber ? (assetData.block_number || '未知') :
|
<text class="chain-value">{{ showBlockNumber ? (assetData.block_number || '未知') :
|
||||||
hiddenBlockNumber }}</text>
|
hiddenBlockNumber }}</text>
|
||||||
<view class="toggle-btn" @tap="showBlockNumber = !showBlockNumber">
|
<view class="toggle-btn" @tap="showBlockNumber = !showBlockNumber">
|
||||||
<image class="toggle-icon" :src="showBlockNumber ? '/static/icon/show.png' : '/static/icon/hide.png'" mode="aspectFit"></image>
|
<image class="toggle-icon"
|
||||||
|
:src="showBlockNumber ? '/static/icon/show.png' : '/static/icon/hide.png'"
|
||||||
|
mode="aspectFit"></image>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -283,7 +176,9 @@
|
|||||||
<text class="chain-value chain-hash" @longpress="copyHash">{{ showTxHash ? displayTxHash
|
<text class="chain-value chain-hash" @longpress="copyHash">{{ showTxHash ? displayTxHash
|
||||||
: hiddenTxHash }}</text>
|
: hiddenTxHash }}</text>
|
||||||
<view class="toggle-btn" @tap="showTxHash = !showTxHash">
|
<view class="toggle-btn" @tap="showTxHash = !showTxHash">
|
||||||
<image class="toggle-icon" :src="showTxHash ? '/static/icon/show.png' : '/static/icon/hide.png'" mode="aspectFit"></image>
|
<image class="toggle-icon"
|
||||||
|
:src="showTxHash ? '/static/icon/show.png' : '/static/icon/hide.png'"
|
||||||
|
mode="aspectFit"></image>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -585,6 +480,19 @@ const hiddenBlockNumber = computed(() => {
|
|||||||
return `${str.substring(0, 6)}******`;
|
return `${str.substring(0, 6)}******`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 等级徽章图片
|
||||||
|
const gradeBadgeUrl = computed(() => {
|
||||||
|
const grade = assetData.value.grade;
|
||||||
|
const gradeMap = {
|
||||||
|
1: '/static/starbookcontent/grade/Ndengji.png',
|
||||||
|
2: '/static/starbookcontent/grade/Rdengji.png',
|
||||||
|
3: '/static/starbookcontent/grade/SRdengji.png',
|
||||||
|
4: '/static/starbookcontent/grade/SSRdengji.png',
|
||||||
|
5: '/static/starbookcontent/grade/URengji.png',
|
||||||
|
};
|
||||||
|
return gradeMap[grade] || '/static/starbookcontent/grade/Ndengji.png';
|
||||||
|
});
|
||||||
|
|
||||||
// 复制完整哈希
|
// 复制完整哈希
|
||||||
const copyHash = () => {
|
const copyHash = () => {
|
||||||
const hash = assetData.value.tx_hash;
|
const hash = assetData.value.tx_hash;
|
||||||
@ -1092,6 +1000,16 @@ onUnmounted(() => {
|
|||||||
transform: rotate(-10deg);
|
transform: rotate(-10deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: -8rpx;
|
||||||
|
left: -56rpx;
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
z-index: 4;
|
||||||
|
transform: rotate(-10deg);
|
||||||
|
}
|
||||||
|
|
||||||
.card-frame {
|
.card-frame {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@ -1538,9 +1456,9 @@ onUnmounted(() => {
|
|||||||
margin-bottom: 8rpx;
|
margin-bottom: 8rpx;
|
||||||
/* transform: scale(1.2); */
|
/* transform: scale(1.2); */
|
||||||
}
|
}
|
||||||
|
|
||||||
.visit-text {
|
.visit-text {
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
font-size: 16rpx;
|
font-size: 16rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -70,6 +70,7 @@ import NftCard from '../components/NftCard.vue';
|
|||||||
import LenticularCard from '@/components/lenticular/LenticularCard.vue';
|
import LenticularCard from '@/components/lenticular/LenticularCard.vue';
|
||||||
import { useLenticularCraftTiltPreview } from '@/composables/useLenticularCraftTiltPreview.js';
|
import { useLenticularCraftTiltPreview } from '@/composables/useLenticularCraftTiltPreview.js';
|
||||||
import { buildLenticularLayersTwo } from '@/utils/castloveMintForm.js';
|
import { buildLenticularLayersTwo } from '@/utils/castloveMintForm.js';
|
||||||
|
import { getAssetDetailApi, getAssetMaterialsApi } from '@/utils/api.js';
|
||||||
|
|
||||||
// 藏品数据
|
// 藏品数据
|
||||||
const nftData = ref({
|
const nftData = ref({
|
||||||
@ -103,12 +104,83 @@ const nftCardStyle = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 页面加载时获取数据
|
// 页面加载时获取数据
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
// 从全局存储读取数据
|
// 从全局存储读取数据
|
||||||
try {
|
try {
|
||||||
const tempNftData = uni.getStorageSync('temp_nft_data');
|
const tempNftData = uni.getStorageSync('temp_nft_data');
|
||||||
if (tempNftData) {
|
if (tempNftData) {
|
||||||
const data = JSON.parse(tempNftData);
|
const data = JSON.parse(tempNftData);
|
||||||
|
|
||||||
|
// 优先使用 API 获取最新数据
|
||||||
|
if (data.asset_id) {
|
||||||
|
try {
|
||||||
|
const res = await getAssetDetailApi(data.asset_id);
|
||||||
|
console.log('[success] asset detail API 返回:', res);
|
||||||
|
if (res.code === 200 && res.data?.asset) {
|
||||||
|
const asset = res.data.asset;
|
||||||
|
console.log('[success] asset:', asset);
|
||||||
|
|
||||||
|
// 获取主图和背景图(通过 materials 接口)
|
||||||
|
let imageUrl = asset.image || data.image || '';
|
||||||
|
let bgImageUrl = asset.bg_image || data.bg_image || '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const materialsRes = await getAssetMaterialsApi(data.asset_id);
|
||||||
|
console.log('[success] materials API 返回:', materialsRes);
|
||||||
|
if (materialsRes.code === 200 && materialsRes.data) {
|
||||||
|
const materials = Array.isArray(materialsRes.data) ? materialsRes.data : materialsRes.data.materials || [];
|
||||||
|
console.log('[success] materials 数组:', materials);
|
||||||
|
// 找到 type 为主图和背景图的素材
|
||||||
|
const mainMat = materials.find(m => m.material_type === 'main');
|
||||||
|
const bgMat = materials.find(m => m.material_type === 'bg');
|
||||||
|
console.log('[success] mainMat:', mainMat, 'bgMat:', bgMat);
|
||||||
|
if (mainMat?.material_url_signed) imageUrl = mainMat.material_url_signed;
|
||||||
|
if (bgMat?.material_url_signed) bgImageUrl = bgMat.material_url_signed;
|
||||||
|
console.log('[success] imageUrl:', imageUrl, 'bgImageUrl:', bgImageUrl);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取素材详情失败:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
nftData.value = {
|
||||||
|
image: imageUrl,
|
||||||
|
name: asset.name || data.name || '未命名藏品',
|
||||||
|
event: asset.event || data.event || '',
|
||||||
|
remark: asset.remark || data.remark || '',
|
||||||
|
materialType: asset.material_type || data.materialType || '',
|
||||||
|
is_lenticular: asset.is_lenticular || data.is_lenticular || false,
|
||||||
|
bg_image: bgImageUrl,
|
||||||
|
};
|
||||||
|
console.log('[success] nftData 设置后:', nftData.value);
|
||||||
|
// 如果是光栅卡,构建图层
|
||||||
|
if ((asset.is_lenticular || data.is_lenticular) && bgImageUrl && imageUrl) {
|
||||||
|
isLenticular.value = true
|
||||||
|
lenticularLayers.value = buildLenticularLayersTwo(bgImageUrl, imageUrl)
|
||||||
|
scheduleTiltStart()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// API 返回异常,使用本地数据
|
||||||
|
applyLocalNftData(data);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取藏品详情失败,使用本地数据:', e);
|
||||||
|
applyLocalNftData(data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
applyLocalNftData(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('读取藏品数据失败:', e);
|
||||||
|
uni.showToast({
|
||||||
|
title: '数据加载失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 应用本地存储的数据
|
||||||
|
function applyLocalNftData(data) {
|
||||||
nftData.value = {
|
nftData.value = {
|
||||||
image: data.image || '',
|
image: data.image || '',
|
||||||
name: data.name || '未命名藏品',
|
name: data.name || '未命名藏品',
|
||||||
@ -125,14 +197,6 @@ onMounted(() => {
|
|||||||
scheduleTiltStart()
|
scheduleTiltStart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
console.error('读取藏品数据失败:', e);
|
|
||||||
uni.showToast({
|
|
||||||
title: '数据加载失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnload(() => {
|
onUnload(() => {
|
||||||
stopTiltPreview()
|
stopTiltPreview()
|
||||||
|
|||||||
@ -319,7 +319,7 @@ const handleClaimReward = async (item, _index) => {
|
|||||||
const revenueRecord = records.find(r => r.asset_id === item.id);
|
const revenueRecord = records.find(r => r.asset_id === item.id);
|
||||||
|
|
||||||
if (!revenueRecord) {
|
if (!revenueRecord) {
|
||||||
uni.showToast({ title: '暂无可领取收益', icon: 'none' });
|
uni.showToast({ title: '一分钟延迟领取', icon: 'none' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,7 +330,23 @@ const handleClaimReward = async (item, _index) => {
|
|||||||
|
|
||||||
// 更新全局余额
|
// 更新全局余额
|
||||||
if (claimRes?.data?.total_balance !== undefined) {
|
if (claimRes?.data?.total_balance !== undefined) {
|
||||||
uni.$emit('balanceUpdated', { crystal_balance: claimRes.data.total_balance });
|
const userStr = uni.getStorageSync('user')
|
||||||
|
if (userStr) {
|
||||||
|
// 确保正确解析用户对象
|
||||||
|
let user
|
||||||
|
if (typeof userStr === 'string') {
|
||||||
|
user = JSON.parse(userStr)
|
||||||
|
} else {
|
||||||
|
// 如果已经是对象,创建一个新副本避免引用问题
|
||||||
|
user = { ...userStr }
|
||||||
|
}
|
||||||
|
// 更新余额,确保是数字类型
|
||||||
|
user.crystal_balance = Number(claimRes.data.total_balance) || 0
|
||||||
|
|
||||||
|
// 保存回存储,确保是字符串格式
|
||||||
|
uni.setStorageSync('user', JSON.stringify(user))
|
||||||
|
uni.$emit('balanceUpdated', { crystal_balance: user.crystal_balance })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刷新列表
|
// 刷新列表
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<view v-if="visible" class="modal-wrapper" @touchmove.stop.prevent="handlePreventMove" @click.stop>
|
<view v-if="visible" class="modal-wrapper" @touchmove.stop.prevent="handlePreventMove" @click.stop>
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<view v-if="visible" class="modal-mask" @touchstart.stop="handleMaskTouchStart" @touchmove.stop.prevent="handlePreventMove" @click.stop>
|
<view v-if="visible" class="modal-mask" @touchstart.stop="handleMaskTouchStart"
|
||||||
|
@touchmove.stop.prevent="handlePreventMove" @click.stop>
|
||||||
</view>
|
</view>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
@ -18,7 +19,8 @@
|
|||||||
<!-- 顶部区域:返回按钮和Tab -->
|
<!-- 顶部区域:返回按钮和Tab -->
|
||||||
<view class="top-bar">
|
<view class="top-bar">
|
||||||
<!-- 返回按钮 -->
|
<!-- 返回按钮 -->
|
||||||
<view class="back-button" @touchstart.stop="handleCloseTouchStart" @touchend.stop="handleCloseTouchEnd" @click="handleCloseClick">
|
<view class="back-button" @touchstart.stop="handleCloseTouchStart"
|
||||||
|
@touchend.stop="handleCloseTouchEnd" @click="handleCloseClick">
|
||||||
<image class="back-icon" src="/static/icon/back.png" mode="aspectFit" />
|
<image class="back-icon" src="/static/icon/back.png" mode="aspectFit" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -56,43 +58,38 @@
|
|||||||
<view v-for="task in sortedTasks" :key="task.task_key" class="task-item">
|
<view v-for="task in sortedTasks" :key="task.task_key" class="task-item">
|
||||||
<view class="task-info">
|
<view class="task-info">
|
||||||
<text class="task-name">{{ task.name }}</text>
|
<text class="task-name">{{ task.name }}</text>
|
||||||
<text class="task-progress" v-if="task.current_count !== undefined">({{ task.current_count }}/{{ task.target_count }})</text>
|
<text class="task-progress" v-if="task.current_count !== undefined">({{
|
||||||
|
task.current_count }}/{{ task.target_count }})</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="task-right">
|
<view class="task-right">
|
||||||
<!-- 礼盒图标 - 点击展开/收起 -->
|
<!-- 礼盒图标 - 点击展开/收起 -->
|
||||||
<view class="reward-gift" @click.stop="toggleReward(task.task_key)">
|
<view class="reward-gift" @click.stop="toggleReward(task.task_key)">
|
||||||
<image
|
<image class="gift-icon" :class="{ 'gift-open': expandedTaskKey === task.task_key }"
|
||||||
class="gift-icon"
|
|
||||||
:class="{ 'gift-open': expandedTaskKey === task.task_key }"
|
|
||||||
:src="expandedTaskKey === task.task_key ? '/static/nft/lihe_kaiqi.png' : '/static/nft/lihe.png'"
|
:src="expandedTaskKey === task.task_key ? '/static/nft/lihe_kaiqi.png' : '/static/nft/lihe.png'"
|
||||||
mode="aspectFit"
|
mode="aspectFit"></image>
|
||||||
></image>
|
|
||||||
|
|
||||||
<!-- 奖励详情弹出 -->
|
<!-- 奖励详情弹出 -->
|
||||||
<view v-if="expandedTaskKey === task.task_key" class="reward-popup">
|
<view v-if="expandedTaskKey === task.task_key" class="reward-popup">
|
||||||
<view class="reward-item">
|
<view class="reward-item">
|
||||||
<image class="reward-icon-img" src="/static/icon/crystal.png" mode="aspectFit"></image>
|
<image class="reward-icon-img" src="/static/icon/crystal.png"
|
||||||
|
mode="aspectFit"></image>
|
||||||
<text class="reward-value">{{ task.crystal_reward }}</text>
|
<text class="reward-value">{{ task.crystal_reward }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="reward-item">
|
<!-- <view class="reward-item">
|
||||||
<image class="reward-icon-img" src="/static/nft/jingyanzhi.png" mode="aspectFit"></image>
|
<image class="reward-icon-img" src="/static/nft/jingyanzhi.png" mode="aspectFit"></image>
|
||||||
<text class="reward-value">{{ task.exp_reward }}</text>
|
<text class="reward-value">{{ task.exp_reward }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="reward-item">
|
<view class="reward-item">
|
||||||
<image class="reward-icon-img" src="/static/nft/huoyuezhitubiao.png" mode="aspectFit"></image>
|
<image class="reward-icon-img" src="/static/nft/huoyuezhitubiao.png" mode="aspectFit"></image>
|
||||||
<text class="reward-value">{{ task.activity_reward || 10 }}</text>
|
<text class="reward-value">{{ task.activity_reward || 10 }}</text>
|
||||||
</view>
|
</view> -->
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- 状态按钮/标签 -->
|
<!-- 状态按钮/标签 -->
|
||||||
<button
|
<button class="claim-btn" :class="getStatusClass(task.status)"
|
||||||
class="claim-btn"
|
:loading="claimingTask === task.task_key" :disabled="!task.can_claim"
|
||||||
:class="getStatusClass(task.status)"
|
@click="task.can_claim && handleClaim(task)">
|
||||||
:loading="claimingTask === task.task_key"
|
|
||||||
:disabled="!task.can_claim"
|
|
||||||
@click="task.can_claim && handleClaim(task)"
|
|
||||||
>
|
|
||||||
{{ task.can_claim ? '待领取' : getStatusText(task.status) }}
|
{{ task.can_claim ? '待领取' : getStatusText(task.status) }}
|
||||||
</button>
|
</button>
|
||||||
</view>
|
</view>
|
||||||
@ -104,25 +101,15 @@
|
|||||||
<!-- 进度条 -->
|
<!-- 进度条 -->
|
||||||
<view class="progress-section">
|
<view class="progress-section">
|
||||||
<view class="progress-bar">
|
<view class="progress-bar">
|
||||||
<view
|
<view v-for="(milestone, index) in milestones" :key="index" class="progress-node"
|
||||||
v-for="(milestone, index) in milestones"
|
:class="{ 'active': index < completedCount }">
|
||||||
:key="index"
|
|
||||||
class="progress-node"
|
|
||||||
:class="{ 'active': index < completedCount }"
|
|
||||||
>
|
|
||||||
<!-- 奖励图标 -->
|
<!-- 奖励图标 -->
|
||||||
<image
|
<image class="milestone-icon"
|
||||||
class="milestone-icon"
|
|
||||||
:src="[20, 40, 80].includes(milestone.value) ? '/static/icon/crystal.png' : '/static/nft/lihe.png'"
|
:src="[20, 40, 80].includes(milestone.value) ? '/static/icon/crystal.png' : '/static/nft/lihe.png'"
|
||||||
mode="aspectFit"
|
mode="aspectFit"></image>
|
||||||
></image>
|
|
||||||
<!-- 进度节点 -->
|
<!-- 进度节点 -->
|
||||||
<image
|
<image class="node-circle" :class="{ 'node-inactive': index >= completedCount }"
|
||||||
class="node-circle"
|
src="/static/nft/huoyuezhi_jiedian.png" mode="aspectFit"></image>
|
||||||
:class="{ 'node-inactive': index >= completedCount }"
|
|
||||||
src="/static/nft/huoyuezhi_jiedian.png"
|
|
||||||
mode="aspectFit"
|
|
||||||
></image>
|
|
||||||
<text class="node-label">{{ milestone.value }}</text>
|
<text class="node-label">{{ milestone.value }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -130,13 +117,8 @@
|
|||||||
|
|
||||||
<!-- 一键领取按钮 -->
|
<!-- 一键领取按钮 -->
|
||||||
<view class="claim-all-bar">
|
<view class="claim-all-bar">
|
||||||
<button
|
<button class="claim-all-btn" :class="{ 'disabled': !hasClaimableTasks }"
|
||||||
class="claim-all-btn"
|
:loading="claimingAll" :disabled="!hasClaimableTasks" @click="handleClaimAll">
|
||||||
:class="{ 'disabled': !hasClaimableTasks }"
|
|
||||||
:loading="claimingAll"
|
|
||||||
:disabled="!hasClaimableTasks"
|
|
||||||
@click="handleClaimAll"
|
|
||||||
>
|
|
||||||
一键领取 ({{ claimableCount }})
|
一键领取 ({{ claimableCount }})
|
||||||
</button>
|
</button>
|
||||||
</view>
|
</view>
|
||||||
@ -359,9 +341,24 @@ async function handleClaim(task) {
|
|||||||
tasks.value[index].status = 'claimed'
|
tasks.value[index].status = 'claimed'
|
||||||
tasks.value[index].can_claim = false
|
tasks.value[index].can_claim = false
|
||||||
}
|
}
|
||||||
|
const userStr = uni.getStorageSync('user')
|
||||||
|
if (userStr) {
|
||||||
|
// 确保正确解析用户对象
|
||||||
|
let user
|
||||||
|
if (typeof userStr === 'string') {
|
||||||
|
user = JSON.parse(userStr)
|
||||||
|
} else {
|
||||||
|
// 如果已经是对象,创建一个新副本避免引用问题
|
||||||
|
user = { ...userStr }
|
||||||
|
}
|
||||||
emit('updated')
|
emit('updated')
|
||||||
uni.$emit('balanceUpdated', { crystal_balance: res.data?.crystal_balance, experience: res.data?.experience })
|
// 更新余额,确保是数字类型
|
||||||
|
user.crystal_balance = Number(res.data?.crystal_balance) || 0
|
||||||
|
|
||||||
|
// 保存回存储,确保是字符串格式
|
||||||
|
uni.setStorageSync('user', JSON.stringify(user))
|
||||||
|
uni.$emit('balanceUpdated', { crystal_balance: user.crystal_balance })
|
||||||
|
}
|
||||||
uni.showToast({ title: '领取成功', icon: 'success' })
|
uni.showToast({ title: '领取成功', icon: 'success' })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('handleClaim error:', err)
|
console.error('handleClaim error:', err)
|
||||||
@ -386,7 +383,24 @@ async function handleClaimAll() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
emit('updated')
|
emit('updated')
|
||||||
uni.$emit('balanceUpdated', { crystal_balance: res.data?.crystal_balance, experience: res.data?.experience })
|
const userStr = uni.getStorageSync('user')
|
||||||
|
if (userStr) {
|
||||||
|
// 确保正确解析用户对象
|
||||||
|
let user
|
||||||
|
if (typeof userStr === 'string') {
|
||||||
|
user = JSON.parse(userStr)
|
||||||
|
} else {
|
||||||
|
// 如果已经是对象,创建一个新副本避免引用问题
|
||||||
|
user = { ...userStr }
|
||||||
|
}
|
||||||
|
emit('updated')
|
||||||
|
// 更新余额,确保是数字类型
|
||||||
|
user.crystal_balance = Number(res.data?.crystal_balance) || 0
|
||||||
|
|
||||||
|
// 保存回存储,确保是字符串格式
|
||||||
|
uni.setStorageSync('user', JSON.stringify(user))
|
||||||
|
uni.$emit('balanceUpdated', { crystal_balance: user.crystal_balance })
|
||||||
|
}
|
||||||
uni.showToast({ title: '领取成功', icon: 'success' })
|
uni.showToast({ title: '领取成功', icon: 'success' })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('handleClaimAll error:', err)
|
console.error('handleClaimAll error:', err)
|
||||||
@ -617,7 +631,9 @@ const handleCloseClick = (e) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
to { transform: rotate(360deg); }
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-text,
|
.loading-text,
|
||||||
@ -747,6 +763,7 @@ const handleCloseClick = (e) => {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(-50%) scale(0.8);
|
transform: translateY(-50%) scale(0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateY(-50%) scale(1);
|
transform: translateY(-50%) scale(1);
|
||||||
@ -812,9 +829,11 @@ const handleCloseClick = (e) => {
|
|||||||
border-radius: 25rpx;
|
border-radius: 25rpx;
|
||||||
/* 渐变:左浅橙粉 → 右柔粉红 */
|
/* 渐变:左浅橙粉 → 右柔粉红 */
|
||||||
background: linear-gradient(to bottom right,
|
background: linear-gradient(to bottom right,
|
||||||
#F0E4B1 0%, /* 左:浅橙粉 */
|
#F0E4B1 0%,
|
||||||
|
/* 左:浅橙粉 */
|
||||||
#F08399 50%,
|
#F08399 50%,
|
||||||
#B94E73 100% /* 右:柔粉红 */
|
#B94E73 100%
|
||||||
|
/* 右:柔粉红 */
|
||||||
);
|
);
|
||||||
|
|
||||||
/* 立体感核心:多层阴影 + 内阴影模拟凸起 */
|
/* 立体感核心:多层阴影 + 内阴影模拟凸起 */
|
||||||
@ -824,8 +843,10 @@ const handleCloseClick = (e) => {
|
|||||||
0 2rpx 6rpx rgba(255, 143, 158, 0.15),
|
0 2rpx 6rpx rgba(255, 143, 158, 0.15),
|
||||||
|
|
||||||
/* 内阴影 - 模拟顶部受光 + 底部凹陷 */
|
/* 内阴影 - 模拟顶部受光 + 底部凹陷 */
|
||||||
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4), /* 顶部高光 */
|
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4),
|
||||||
inset 0 -2rpx 4rpx rgba(0, 0, 0, 0.05); /* 底部暗部 */
|
/* 顶部高光 */
|
||||||
|
inset 0 -2rpx 4rpx rgba(0, 0, 0, 0.05);
|
||||||
|
/* 底部暗部 */
|
||||||
border: 2rpx solid rgba(255, 255, 255, 0.5);
|
border: 2rpx solid rgba(255, 255, 255, 0.5);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
|
|||||||
BIN
frontend/static/starbookcontent/grade/Ndengji.png
Normal file
BIN
frontend/static/starbookcontent/grade/Ndengji.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
frontend/static/starbookcontent/grade/Rdengji.png
Normal file
BIN
frontend/static/starbookcontent/grade/Rdengji.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
frontend/static/starbookcontent/grade/SRdengji.png
Normal file
BIN
frontend/static/starbookcontent/grade/SRdengji.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
frontend/static/starbookcontent/grade/SSRdengji.png
Normal file
BIN
frontend/static/starbookcontent/grade/SSRdengji.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
frontend/static/starbookcontent/grade/URengji.png
Normal file
BIN
frontend/static/starbookcontent/grade/URengji.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Loading…
Reference in New Issue
Block a user