feat: 展览选择藏品进行分类功能

This commit is contained in:
zerosaturation 2026-04-20 19:31:42 +08:00
parent 5931040057
commit e5a09194ad
7 changed files with 1178 additions and 253 deletions

View File

@ -148,24 +148,89 @@ func ConvertGetMyAssetsResponse(pbResp *pbAsset.GetMyAssetsResponse) *GetMyAsset
return nil
}
dto := &GetMyAssetsResponseDTO{
Total: pbResp.Total,
Page: pbResp.Page,
PageSize: pbResp.PageSize,
HasMore: pbResp.HasMore,
Items: make([]AssetListItemDTO, 0, len(pbResp.Items)),
}
dto := &GetMyAssetsResponseDTO{}
// 转换列表项
for _, item := range pbResp.Items {
if item != nil {
dto.Items = append(dto.Items, ConvertAssetListItem(item))
if pbResp.Data != nil {
dto.Data = &AssetListDataDTO{
Total: pbResp.Data.Total,
Page: pbResp.Data.Page,
PageSize: pbResp.Data.PageSize,
HasMore: pbResp.Data.HasMore,
Groups: make([]AssetGroupDTO, 0, len(pbResp.Data.Groups)),
}
// 转换分组
for _, group := range pbResp.Data.Groups {
if group != nil {
dto.Data.Groups = append(dto.Data.Groups, ConvertAssetGroup(group))
}
}
}
return dto
}
// ConvertAssetGroup 转换资产分组
func ConvertAssetGroup(pbGroup *pbAsset.AssetGroup) AssetGroupDTO {
dto := AssetGroupDTO{
Type: pbGroup.Type,
Category: pbGroup.Category,
CategoryName: pbGroup.CategoryName,
TotalCount: pbGroup.TotalCount,
HasMore: pbGroup.HasMore,
Grades: make([]GradeSectionDTO, 0, len(pbGroup.Grades)),
Items: make([]AssetItemDTO, 0, len(pbGroup.Items)),
}
// 转换 grades
for _, grade := range pbGroup.Grades {
if grade != nil {
dto.Grades = append(dto.Grades, ConvertGradeSection(grade))
}
}
// 转换 items
for _, item := range pbGroup.Items {
if item != nil {
dto.Items = append(dto.Items, ConvertAssetItem(item))
}
}
return dto
}
// ConvertGradeSection 转换等级分组
func ConvertGradeSection(pbGrade *pbAsset.GradeSection) GradeSectionDTO {
dto := GradeSectionDTO{
Grade: pbGrade.Grade,
TotalCount: pbGrade.TotalCount,
HasMore: pbGrade.HasMore,
Items: make([]AssetItemDTO, 0, len(pbGrade.Items)),
}
for _, item := range pbGrade.Items {
if item != nil {
dto.Items = append(dto.Items, ConvertAssetItem(item))
}
}
return dto
}
// ConvertAssetItem 转换资产项
func ConvertAssetItem(pbItem *pbAsset.AssetItem) AssetItemDTO {
return AssetItemDTO{
AssetID: pbItem.AssetId,
Name: pbItem.Name,
CoverURLSigned: pbItem.CoverUrlSigned,
LikeCount: pbItem.LikeCount,
CreatedAt: pbItem.CreatedAt,
Category: pbItem.Category,
Grade: pbItem.Grade,
DisplayStatus: pbItem.DisplayStatus,
}
}
// ConvertAssetListItem 转换资产列表项
func ConvertAssetListItem(pbItem *pbAsset.AssetListItem) AssetListItemDTO {
dto := AssetListItemDTO{

View File

@ -91,11 +91,47 @@ type OwnerInfoDTO struct {
// GetMyAssetsResponseDTO 获取我的藏品列表响应
type GetMyAssetsResponseDTO struct {
Items []AssetListItemDTO `json:"items"` // 资产列表
Total int64 `json:"total"` // 总数
Page int32 `json:"page"` // 当前页码
PageSize int32 `json:"page_size"` // 每页数量
HasMore bool `json:"has_more"` // 是否有更多
Data *AssetListDataDTO `json:"data"` // 分组后的藏品数据
}
// AssetListDataDTO 藏品列表数据与星册home一致的结构
type AssetListDataDTO struct {
Groups []AssetGroupDTO `json:"groups"` // 分组列表
Total int64 `json:"total"` // 总数
Page int32 `json:"page"` // 当前页码
PageSize int32 `json:"page_size"` // 每页数量
HasMore bool `json:"has_more"` // 是否有更多
}
// AssetGroupDTO 资产分组与星册home一致
type AssetGroupDTO struct {
Type string `json:"type"` // 'regular' / 'collection' / 'activity'
Category string `json:"category"` // 'castlove'(regular) / collection_category / activity_type
CategoryName string `json:"category_name"` // 分组名称
Grades []GradeSectionDTO `json:"grades"` // 仅 regular 时有效
Items []AssetItemDTO `json:"items"` // collection / activity 时有效
TotalCount int32 `json:"total_count"` // 总数
HasMore bool `json:"has_more"` // 是否有更多
}
// GradeSectionDTO 等级分组(仅 regular 类型使用)
type GradeSectionDTO struct {
Grade int32 `json:"grade"` // 等级1/2/3/4/5...
Items []AssetItemDTO `json:"items"` // 藏品列表
TotalCount int32 `json:"total_count"` // 该等级总数
HasMore bool `json:"has_more"` // 是否有更多
}
// AssetItemDTO 资产项与星册home一致
type AssetItemDTO struct {
AssetID int64 `json:"asset_id"` // 资产ID
Name string `json:"name"` // 藏品名称
CoverURLSigned string `json:"cover_url_signed"` // 预签名封面URL
LikeCount int32 `json:"like_count"` // 点赞数
CreatedAt int64 `json:"created_at"` // 创建时间
Category string `json:"category"` // 分类
Grade int32 `json:"grade"` // 等级(仅 regular 时有效)
DisplayStatus int32 `json:"display_status"` // 展示状态0=待展示, 1=已展示
}
// AssetListItemDTO 资产列表项(简化版,用于列表展示)

File diff suppressed because it is too large Load Diff

View File

@ -125,14 +125,50 @@ message GetMyAssetsRequest {
//
message GetMyAssetsResponse {
topfans.common.BaseResponse base = 1;
repeated AssetListItem items = 2; //
int64 total = 3; //
int32 page = 4; //
int32 page_size = 5; //
bool has_more = 6; //
AssetListData data = 2; //
}
//
// home一致的结构
message AssetListData {
repeated AssetGroup groups = 1; //
int64 total = 2; //
int32 page = 3; //
int32 page_size = 4; //
bool has_more = 5; //
}
// home一致
message AssetGroup {
string type = 1; // 'regular' / 'collection' / 'activity'
string category = 2; // 'castlove'(regular) / collection_category / activity_type
string category_name = 3;
repeated GradeSection grades = 4; // regular
repeated AssetItem items = 5; // collection / activity
int32 total_count = 6;
bool has_more = 7;
}
// regular 使
message GradeSection {
int32 grade = 1; // 1/2/3/4/5...
repeated AssetItem items = 2;
int32 total_count = 3;
bool has_more = 4;
}
// home一致
message AssetItem {
int64 asset_id = 1;
string name = 2;
string cover_url_signed = 3; // URL
int32 like_count = 4;
int64 created_at = 5;
string category = 6; // regular: 'castlove' / collection: category / activity: activity_type
int32 grade = 7; // regular 1/2/3... 0
int32 display_status = 8; // 0=, 1=
}
// -
message AssetListItem {
int64 asset_id = 1; // ID
string name = 2; //

View File

@ -129,8 +129,8 @@ func main() {
logger.Logger.Info("User Service RPC client initialized")
// 创建 Service 层实例
assetService := service.NewAssetService(assetRepo, mintOrderRepo, assetLikeRepo, userClient, database.GetDB())
registryRepo := starbookRepo.NewAssetRegistryRepository(database.GetDB())
assetService := service.NewAssetService(assetRepo, mintOrderRepo, assetLikeRepo, userClient, database.GetDB(), registryRepo)
mintService := service.NewMintService(assetRepo, mintOrderRepo, userClient, database.GetDB(), config.GlobalAssetConfig, registryRepo)
assetLikeService := service.NewAssetLikeService(assetRepo, assetLikeRepo, database.GetDB())
rankingService := service.NewRankingService(rankingRepo, userClient)

View File

@ -212,7 +212,7 @@ func (p *AssetProvider) GetMyAssets(ctx context.Context, req *pb.GetMyAssetsRequ
logger.Logger.Debug("GetMyAssets successful",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
zap.Int("count", len(resp.Items)),
zap.Int("count", len(resp.Data.Groups)),
)
return resp, nil

View File

@ -136,18 +136,18 @@
<!-- 藏品选择弹窗 -->
<view v-if="showAssetSelectModal" class="asset-select-modal-mask" @tap="closeAssetSelectModal">
<view
class="asset-select-modal"
@tap.stop
<view
class="asset-select-modal"
@tap.stop
:class="{ 'show': assetSelectModalAnimated }"
>
<!-- 背景图片 -->
<image class="modal-background-image" src="/static/background/starbook.jpg" mode="aspectFill"></image>
<!-- 内容包装器 -->
<view class="modal-content-wrapper">
<!-- 顶部拖动区域 -->
<view
<view
class="modal-top-drag-area"
@touchstart="handleModalTouchStart"
@touchmove="handleModalTouchMove"
@ -155,27 +155,137 @@
>
<!-- 顶部拖动条 -->
<view class="modal-handle"></view>
<!-- 标题 -->
<text class="modal-title">选择要展出的藏品</text>
</view>
<!-- 藏品网格 -->
<view class="modal-nft-grid-container">
<view
v-for="(item, index) in myAssetsList"
:key="item.asset_id || index"
class="modal-nft-grid-item"
<!-- 类型Tab -->
<view class="modal-type-tabs">
<view
class="modal-tab-item"
:class="{ active: modalCurrentType === 'regular' }"
@click="switchModalType('regular')"
>
<NftCard
:cover-image="item.image || '/static/nft/collection.png'"
:width="cardSize"
:height="cardSize"
:custom-style="cardCustomStyle"
@click="handleAssetSelect(item)"
/>
<text>原创</text>
</view>
<view
class="modal-tab-item"
:class="{ active: modalCurrentType === 'collection' }"
@click="switchModalType('collection')"
>
<text>典藏</text>
</view>
<view
class="modal-tab-item"
:class="{ active: modalCurrentType === 'activity' }"
@click="switchModalType('activity')"
>
<text>活动</text>
</view>
</view>
<!-- 加载中 -->
<view v-if="modalLoading" class="modal-loading-container">
<text class="modal-loading-text">加载中...</text>
</view>
<!-- 空状态 -->
<view v-else-if="!modalHasData" class="modal-empty-container">
<text class="modal-empty-text">暂无藏品</text>
</view>
<!-- 藏品列表 - 与星册一致的水平滚动布局 -->
<scroll-view v-else class="modal-nft-scroll-view" scroll-y :show-scrollbar="false">
<view class="modal-nft-list-container">
<!-- 原创藏品 grade 分组 -->
<template v-if="modalCurrentType === 'regular'">
<view v-for="gradeItem in modalRegularGrades" :key="gradeItem.grade" class="modal-grade-section">
<view class="modal-group-header">
<text class="modal-group-title">{{ formatModalGrade(gradeItem.grade) }}</text>
</view>
<scroll-view class="modal-nft-row" scroll-x :show-scrollbar="false" :enable-flex="true">
<view class="modal-nft-row-content">
<view
v-for="item in gradeItem.items"
:key="item.asset_id"
class="modal-nft-grid-item"
@click.stop="handleAssetSelect(item)"
>
<image
class="modal-nft-image"
:src="item.coverUrl || '/static/nft/collection.png'"
mode="aspectFill"
/>
<view class="modal-status-badge" :class="item.display_status === 1 ? 'modal-status-badge-active' : 'modal-status-badge-pending'">
<text class="modal-status-text">{{ item.display_status === 1 ? '已展示' : '待展示' }}</text>
</view>
<view class="modal-nft-info">
<text class="modal-nft-name">{{ item.name }}</text>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<!-- 典藏藏品直接列表 -->
<template v-else-if="modalCurrentType === 'collection'">
<view class="modal-grade-section">
<scroll-view class="modal-nft-row" scroll-x :show-scrollbar="false" :enable-flex="true">
<view class="modal-nft-row-content">
<view
v-for="item in modalCollectionItems"
:key="item.asset_id"
class="modal-nft-grid-item"
@click.stop="handleAssetSelect(item)"
>
<image
class="modal-nft-image"
:src="item.coverUrl || '/static/nft/collection.png'"
mode="aspectFill"
/>
<view class="modal-status-badge" :class="item.display_status === 1 ? 'modal-status-badge-active' : 'modal-status-badge-pending'">
<text class="modal-status-text">{{ item.display_status === 1 ? '已展示' : '待展示' }}</text>
</view>
<view class="modal-nft-info">
<text class="modal-nft-name">{{ item.name }}</text>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<!-- 活动藏品直接列表 -->
<template v-else-if="modalCurrentType === 'activity'">
<view class="modal-grade-section">
<scroll-view class="modal-nft-row" scroll-x :show-scrollbar="false" :enable-flex="true">
<view class="modal-nft-row-content">
<view
v-for="item in modalActivityItems"
:key="item.asset_id"
class="modal-nft-grid-item"
@click.stop="handleAssetSelect(item)"
>
<image
class="modal-nft-image"
:src="item.coverUrl || '/static/nft/collection.png'"
mode="aspectFill"
/>
<view class="modal-status-badge" :class="item.display_status === 1 ? 'modal-status-badge-active' : 'modal-status-badge-pending'">
<text class="modal-status-text">{{ item.display_status === 1 ? '已展示' : '待展示' }}</text>
</view>
<view class="modal-nft-info">
<text class="modal-nft-name">{{ item.name }}</text>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
</view>
</scroll-view>
</view>
</view>
</view>
@ -332,7 +442,54 @@ export default {
const selectedSlotIndex = ref(null);
const showPlaceConfirmModal = ref(false);
const selectedAssetForPlace = ref(null);
const myAssetsList = ref([]);
const myAssetsGroups = ref([]); //
const modalLoading = ref(false); //
const modalCurrentType = ref('regular'); //
//
const modalHasData = computed(() => {
const groups = myAssetsGroups.value;
if (modalCurrentType.value === 'regular') {
const group = groups.find(g => g.type === 'regular');
return group && group.grades && group.grades.some(g => g.items && g.items.length > 0);
} else if (modalCurrentType.value === 'collection') {
const group = groups.find(g => g.type === 'collection');
return group && group.items && group.items.length > 0;
} else if (modalCurrentType.value === 'activity') {
const group = groups.find(g => g.type === 'activity');
return group && group.items && group.items.length > 0;
}
return false;
});
//
const modalRegularGrades = computed(() => {
const group = myAssetsGroups.value.find(g => g.type === 'regular');
return group && group.grades ? group.grades : [];
});
//
const modalCollectionItems = computed(() => {
const group = myAssetsGroups.value.find(g => g.type === 'collection');
return group && group.items ? group.items : [];
});
//
const modalActivityItems = computed(() => {
const group = myAssetsGroups.value.find(g => g.type === 'activity');
return group && group.items ? group.items : [];
});
// grade
const gradeMap = { 1: '一', 2: '二', 3: '三', 4: '四', 5: '五' };
const formatModalGrade = (grade) => {
return `等级${gradeMap[grade] || grade}`;
};
//
const switchModalType = (type) => {
modalCurrentType.value = type;
};
//
const showRemoveConfirmModal = ref(false);
@ -820,28 +977,84 @@ export default {
selectedSlotId.value = slot.slot_id;
selectedSlotIndex.value = slot.slot_index;
//
modalLoading.value = true;
try {
const response = await getMyAssetsApi(1, 20);
if (response.code === 200 && response.data && response.data.items) {
const assetsPromises = response.data.items.map(async item => {
const realCoverUrl = await getAssetCoverRealUrl(item.cover_url);
return {
asset_id: item.asset_id,
name: item.name,
image: realCoverUrl,
cover_url: item.cover_url || '/static/nft/collection.png'
};
});
myAssetsList.value = await Promise.all(assetsPromises);
//
if (response.code === 200 && response.data && response.data.data.groups) {
//
showAssetSelectModal.value = true;
//
setTimeout(() => {
assetSelectModalAnimated.value = true;
}, 50);
//
const allItems = [];
const itemRefs = [];
// items
const processedGroups = [];
for (const group of response.data.data.groups) {
const processedGroup = {
type: group.type,
category: group.category,
category_name: group.category_name,
total_count: group.total_count,
has_more: group.has_more,
grades: [],
items: []
};
// grades
if (group.grades) {
for (const grade of group.grades) {
const processedGrade = {
grade: grade.grade,
total_count: grade.total_count,
has_more: grade.has_more,
items: []
};
for (const item of grade.items || []) {
allItems.push(item);
itemRefs.push({ type: 'grade', parent: processedGrade, grade: item.grade, category: null });
}
processedGroup.grades.push(processedGrade);
}
}
// items
if (group.items) {
for (const item of group.items) {
allItems.push(item);
itemRefs.push({ type: 'item', parent: processedGroup, grade: null, category: item.category });
}
}
processedGroups.push(processedGroup);
}
// URL
const coverUrlPromises = allItems.map(item => getAssetCoverRealUrl(item.cover_url_signed));
const coverUrls = await Promise.all(coverUrlPromises);
// URL
for (let i = 0; i < allItems.length; i++) {
const item = allItems[i];
const ref = itemRefs[i];
const processedItem = {
asset_id: item.asset_id,
name: item.name,
coverUrl: coverUrls[i],
display_status: item.display_status || 0,
like_count: item.like_count,
grade: ref.grade,
category: ref.category
};
ref.parent.items.push(processedItem);
}
myAssetsGroups.value = processedGroups;
}
} catch (error) {
console.error('获取藏品列表失败:', error);
@ -850,6 +1063,8 @@ export default {
icon: 'none',
duration: 2000
});
} finally {
modalLoading.value = false;
}
};
@ -860,7 +1075,7 @@ export default {
//
setTimeout(() => {
showAssetSelectModal.value = false;
myAssetsList.value = [];
myAssetsGroups.value = [];
//
if (isReplaceMode.value) {
isReplaceMode.value = false;
@ -964,7 +1179,7 @@ export default {
assetSelectModalAnimated.value = false;
setTimeout(() => {
showAssetSelectModal.value = false;
myAssetsList.value = [];
myAssetsGroups.value = [];
}, 300);
//
@ -1100,26 +1315,82 @@ export default {
selectedSlotId.value = asset.slot_id;
//
modalLoading.value = true;
try {
const response = await getMyAssetsApi(1, 20);
if (response.code === 200 && response.data && response.data.items) {
const assetsPromises = response.data.items.map(async item => {
const realCoverUrl = await getAssetCoverRealUrl(item.cover_url);
return {
asset_id: item.asset_id,
name: item.name,
image: realCoverUrl,
cover_url: item.cover_url || '/static/nft/collection.png'
};
});
myAssetsList.value = await Promise.all(assetsPromises);
//
if (response.code === 200 && response.data && response.data.data.groups) {
//
showAssetSelectModal.value = true;
//
setTimeout(() => {
assetSelectModalAnimated.value = true;
}, 50);
//
const allItems = [];
const itemRefs = [];
// items
const processedGroups = [];
for (const group of response.data.data.groups) {
const processedGroup = {
type: group.type,
category: group.category,
category_name: group.category_name,
total_count: group.total_count,
has_more: group.has_more,
grades: [],
items: []
};
// grades
if (group.grades) {
for (const grade of group.grades) {
const processedGrade = {
grade: grade.grade,
total_count: grade.total_count,
has_more: grade.has_more,
items: []
};
for (const item of grade.items || []) {
allItems.push(item);
itemRefs.push({ type: 'grade', parent: processedGrade, grade: item.grade, category: null });
}
processedGroup.grades.push(processedGrade);
}
}
// items
if (group.items) {
for (const item of group.items) {
allItems.push(item);
itemRefs.push({ type: 'item', parent: processedGroup, grade: null, category: item.category });
}
}
processedGroups.push(processedGroup);
}
// URL
const coverUrlPromises = allItems.map(item => getAssetCoverRealUrl(item.cover_url_signed));
const coverUrls = await Promise.all(coverUrlPromises);
// URL
for (let i = 0; i < allItems.length; i++) {
const item = allItems[i];
const ref = itemRefs[i];
const processedItem = {
asset_id: item.asset_id,
name: item.name,
coverUrl: coverUrls[i],
display_status: item.display_status || 0,
like_count: item.like_count,
grade: ref.grade,
category: ref.category
};
ref.parent.items.push(processedItem);
}
myAssetsGroups.value = processedGroups;
}
} catch (error) {
console.error('获取藏品列表失败:', error);
@ -1131,6 +1402,8 @@ export default {
//
isReplaceMode.value = false;
selectedAssetForRemove.value = null;
} finally {
modalLoading.value = false;
}
};
@ -1539,28 +1812,198 @@ export default {
flex-shrink: 0;
}
/* 藏品网格容器 */
.modal-nft-grid-container {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
display: grid;
grid-template-columns: repeat(3, 1fr);
column-gap: 15rpx;
row-gap: 10rpx;
align-items: start;
align-content: start;
width: 100%;
max-width: 100%;
/* 弹窗内类型Tab */
.modal-type-tabs {
display: flex;
justify-content: center;
gap: 40rpx;
padding: 20rpx 30rpx;
z-index: 100;
}
.modal-tab-item {
padding: 12rpx 30rpx;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.6);
border-bottom: 4rpx solid transparent;
transition: all 0.3s ease;
}
.modal-tab-item.active {
color: #ffffff;
border-bottom-color: #ffffff;
}
/* 弹窗内加载中 */
.modal-loading-container {
display: flex;
justify-content: center;
align-items: center;
padding-top: 200rpx;
}
.modal-loading-text {
color: rgba(255, 255, 255, 0.6);
font-size: 28rpx;
}
/* 弹窗内空状态 */
.modal-empty-container {
display: flex;
justify-content: center;
align-items: center;
padding-top: 200rpx;
}
.modal-empty-text {
color: rgba(255, 255, 255, 0.6);
font-size: 28rpx;
}
/* 弹窗内可滚动区域 */
.modal-nft-scroll-view {
height: calc(80vh - 200rpx);
}
/* 隐藏滚动条 */
.modal-nft-scroll-view::-webkit-scrollbar {
display: none;
}
.modal-nft-scroll-view {
scrollbar-width: none;
-ms-overflow-style: none;
}
/* 弹窗内藏品列表容器 */
.modal-nft-list-container {
width: 100%;
}
/* 弹窗内藏品分组 */
.modal-nft-group {
margin-bottom: 30rpx;
}
/* 弹窗内等级区块 */
.modal-grade-section {
background: rgba(255, 255, 255, 0.03);
border-radius: 16rpx;
padding: 20rpx;
}
/* 弹窗内分组标题 */
.modal-group-header {
margin-bottom: 16rpx;
padding-bottom: 10rpx;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.1);
}
.modal-group-title {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.8);
}
/* 弹窗内藏品行 - 水平滚动 */
.modal-nft-row {
width: 100%;
height: 288rpx;
white-space: nowrap;
}
/* 弹窗内藏品行内容容器 */
.modal-nft-row-content {
display: inline-block;
white-space: nowrap;
padding-left: 24rpx;
height: 100%;
width: 192rpx;
}
/* 隐藏滚动条 */
.modal-nft-row::-webkit-scrollbar {
display: none;
}
.modal-nft-row {
scrollbar-width: none;
-ms-overflow-style: none;
}
/* 弹窗内藏品网格项 */
.modal-nft-grid-item {
position: relative;
width: 100%;
padding-top: 133.33%;
display: inline-block;
vertical-align: top;
margin-right: 32rpx;
height: 100%;
}
/* 弹窗内NFT图片 */
.modal-nft-image {
width: 192rpx;
height: 224rpx;
border-radius: 16rpx;
background: rgba(255, 255, 255, 0.05);
display: block;
}
/* 弹窗内更多项 */
.modal-nft-grid-item.modal-more-item {
cursor: pointer;
}
/* 弹窗内藏品信息 */
.modal-nft-info {
padding: 12rpx 0;
text-align: center;
}
/* 弹窗内展示状态标签 */
.modal-status-badge {
position: absolute;
top: 8rpx;
right: 8rpx;
border-radius: 8rpx;
padding: 4rpx 8rpx;
z-index: 1;
}
.modal-status-badge-active {
background: linear-gradient(135deg, #FFD700, #FFA500);
box-shadow: 0 0 12rpx rgba(255, 215, 0, 0.6);
}
.modal-status-badge-pending {
background: rgba(0, 0, 0, 0.6);
}
.modal-status-text {
font-size: 18rpx;
color: #fff;
font-weight: bold;
}
.modal-nft-name {
display: block;
font-size: 22rpx;
color: rgba(255, 255, 255, 0.9);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 弹窗内更多覆盖层 */
.modal-more-overlay {
width: 192rpx;
height: 224rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.5);
border-radius: 16rpx;
}
.modal-more-text {
font-size: 26rpx;
color: #ffffff;
}
/* 上架确认弹窗遮罩 */
.place-confirm-modal-mask {
position: fixed;