@@ -40,6 +40,10 @@
< view class = "section" >
< view class = "section-header" >
< text class = "section-title" > 选择底图 < / text >
< view class = "more-btn" @tap ="openMorePopup" >
< text > 查看更多 < / text >
< text class = "arrow" > › < / text >
< / view >
< / view >
< scroll-view scroll -x class = "avatar-scroll" show -scrollbar = " false " >
< view class = "avatar-list" >
@@ -110,12 +114,42 @@
<!-- Login Popup -- >
< LoginPopup ref = "loginPopupRef" @logind ="handleLogind" / >
<!-- More Avatar Popup -- >
< uni-popup ref = "morePopup" type = "bottom" background -color = " # fff " >
< view class = "popup-content" >
< view class = "popup-header" >
< text class = "popup-title" > 选择头像 < / text >
< view class = "close-btn" @tap ="closeMorePopup" > ✕ < / view >
< / view >
< scroll -view
scroll -y
class = "popup-scroll"
@scrolltolower ="loadMoreAvatars"
>
< view class = "popup-grid" >
< view
v-for = "(item, i) in moreAvatars"
:key = "i"
class = "popup-item"
@tap ="selectMoreAvatar(item)"
>
< image :src = "item" class = "popup-img" mode = "aspectFill" / >
< / view >
< / view >
< view v-if = "loading" class="loading-text" > 加载中... < / view >
< view v-if = "!hasMore && moreAvatars.length > 0" class="no-more-text" >
没有更多了
< / view >
< / scroll -view >
< / view >
< / uni-popup >
< / view >
< / template >
< script setup >
import { ref , computed } from "vue" ;
import { onShareAppMessage } from "@dcloudio/uni-app" ;
import { onShareAppMessage , onLoad , onReachBottom } from "@dcloudio/uni-app" ;
import { getBavBarHeight , getDeviceInfo } from "@/utils/system" ;
import { useUserStore } from "@/stores/user" ;
import {
@@ -123,34 +157,167 @@ import {
getShareReward ,
abilityCheck ,
} from "@/api/system.js" ;
import { avatarDownloadRecord } from "@/api/avatar.js" ;
import {
avatarDownloadRecord ,
getAvatarSystemList ,
getAvatarFrameList ,
getAvatarDecorList ,
} from "@/api/avatar.js" ;
const userStore = useUserStore ( ) ;
const loginPopupRef = ref ( null ) ;
const isLoggedIn = computed ( ( ) => ! ! userStore . userInfo . nickName ) ;
const systemAvatars = [
"https://file.lihailezzc.com/20260109082842_666_1.jpg" ,
"https://file.lihailezzc.com/20260108222141_644_1.jpg" ,
"https://file.lihailezzc.com/9a929a32-439f-453b-b603-fda7b04cbe08.png" ,
] ;
const frames = [
"https://file.lihailezzc.com/6.png" ,
"https://file.lihailezzc.com/7.png" ,
"https://file.lihailezzc.com/yunshi.png" ,
"https://file.lihailezzc.com/x_CURXRzG4wHF2dp_zu_r-removebg-preview.png" ,
] ;
const decors = [
"https://file.lihailezzc.com/6.png" ,
"https://file.lihailezzc.com/7.png" ,
] ;
const systemAvatars = ref ( [ ] ) ;
const frames = ref ( [ ] ) ;
const decors = ref ( [ ] ) ;
const currentAvatar = ref ( systemAvatars [ 0 ] ) ;
// Pagination states
const framePage = ref ( 1 ) ;
const frameHasNext = ref ( true ) ;
const frameLoading = ref ( false ) ;
const decorPage = ref ( 1 ) ;
const decorHasNext = ref ( true ) ;
const decorLoading = ref ( false ) ;
const currentAvatar = ref ( "" ) ;
const selectedFrame = ref ( "" ) ;
const selectedDecor = ref ( "" ) ;
const activeTab = ref ( "frame" ) ;
// More Popup logic
const morePopup = ref ( null ) ;
const moreAvatars = ref ( [ ] ) ;
const page = ref ( 1 ) ;
const hasMore = ref ( true ) ;
const loading = ref ( false ) ;
const loadFrames = async ( ) => {
if ( frameLoading . value || ! frameHasNext . value ) return ;
frameLoading . value = true ;
try {
const res = await getAvatarFrameList ( framePage . value ) ;
const list = res ? . list || [ ] ;
if ( list . length > 0 ) {
frames . value . push ( ... list . map ( ( item ) => item . imageUrl ) ) ;
framePage . value ++ ;
}
if ( typeof res . hasNext !== "undefined" ) {
frameHasNext . value = res . hasNext ;
} else {
if ( list . length < 10 ) frameHasNext . value = false ;
}
} catch ( e ) {
console . error ( e ) ;
} finally {
frameLoading . value = false ;
}
} ;
const loadDecors = async ( ) => {
if ( decorLoading . value || ! decorHasNext . value ) return ;
decorLoading . value = true ;
try {
const res = await getAvatarDecorList ( decorPage . value ) ;
const list = res ? . list || [ ] ;
if ( list . length > 0 ) {
decors . value . push ( ... list . map ( ( item ) => item . imageUrl ) ) ;
decorPage . value ++ ;
}
if ( typeof res . hasNext !== "undefined" ) {
decorHasNext . value = res . hasNext ;
} else {
if ( list . length < 10 ) decorHasNext . value = false ;
}
} catch ( e ) {
console . error ( e ) ;
} finally {
decorLoading . value = false ;
}
} ;
const initSystemAvatars = async ( ) => {
try {
const res = await getAvatarSystemList ( 1 ) ;
const list = res ? . list || [ ] ;
if ( list . length > 0 ) {
// 取前3个展示在首页
systemAvatars . value = list . slice ( 0 , 3 ) . map ( ( item ) => item . imageUrl ) ;
// 默认选中第一个
if ( systemAvatars . value . length > 0 ) {
currentAvatar . value = systemAvatars . value [ 0 ] ;
}
}
} catch ( e ) {
console . error ( "Failed to load system avatars:" , e ) ;
}
} ;
onLoad ( ( ) => {
initSystemAvatars ( ) ;
loadFrames ( ) ;
loadDecors ( ) ;
} ) ;
onReachBottom ( ( ) => {
if ( activeTab . value === "frame" ) {
loadFrames ( ) ;
} else if ( activeTab . value === "decor" ) {
loadDecors ( ) ;
}
} ) ;
const openMorePopup = ( ) => {
morePopup . value . open ( ) ;
if ( moreAvatars . value . length === 0 ) {
// 重新加载第一页,因为 systemAvatars 只取了前3个,
// 这里我们简单处理:重新请求第一页作为更多列表的开始
// 或者你可以复用 systemAvatars 的数据,然后 page 从 2 开始请求
// 为了逻辑简单和数据一致性,这里选择重新请求第一页填充更多列表
loadMoreAvatars ( ) ;
}
} ;
const closeMorePopup = ( ) => {
morePopup . value . close ( ) ;
} ;
const loadMoreAvatars = async ( ) => {
if ( loading . value || ! hasMore . value ) return ;
loading . value = true ;
try {
const res = await getAvatarSystemList ( page . value ) ;
const list = res ? . list || [ ] ;
if ( list . length > 0 ) {
const newAvatars = list . map ( ( item ) => item . imageUrl ) ;
moreAvatars . value . push ( ... newAvatars ) ;
page . value ++ ;
}
// 根据接口返回的 hasNext 字段判断是否还有更多数据
// 如果接口没有返回 hasNext, 则降级使用列表长度判断( 假设每页10条)
if ( typeof res . hasNext !== "undefined" ) {
hasMore . value = res . hasNext ;
} else {
if ( list . length < 10 ) {
hasMore . value = false ;
}
}
} catch ( e ) {
console . error ( e ) ;
} finally {
loading . value = false ;
}
} ;
const selectMoreAvatar = ( url ) => {
currentAvatar . value = url ;
closeMorePopup ( ) ;
} ;
const toggleFrame = ( frame ) => {
if ( selectedFrame . value === frame ) {
selectedFrame . value = "" ;
@@ -598,4 +765,77 @@ const loadImage = (url) => {
left : - 9999 px ;
top : - 9999 px ;
}
/* More Popup Styles */
. more - btn {
margin - left : auto ;
font - size : 24 rpx ;
color : # 999 ;
display : flex ;
align - items : center ;
}
. arrow {
font - size : 32 rpx ;
margin - left : 4 rpx ;
line - height : 1 ;
position : relative ;
top : - 2 rpx ;
}
. popup - content {
background : # fff ;
border - radius : 24 rpx 24 rpx 0 0 ;
height : 60 vh ;
display : flex ;
flex - direction : column ;
overflow : hidden ;
}
. popup - header {
padding : 32 rpx ;
display : flex ;
align - items : center ;
justify - content : center ;
position : relative ;
border - bottom : 2 rpx solid # f5f5f5 ;
}
. popup - title {
font - size : 32 rpx ;
font - weight : 600 ;
}
. close - btn {
position : absolute ;
right : 32 rpx ;
top : 50 % ;
transform : translateY ( - 50 % ) ;
font - size : 32 rpx ;
color : # 999 ;
padding : 10 rpx ;
}
. popup - scroll {
flex : 1 ;
height : 0 ;
}
. popup - grid {
display : grid ;
grid - template - columns : repeat ( 3 , 1 fr ) ;
gap : 20 rpx ;
padding : 24 rpx ;
}
. popup - item {
aspect - ratio : 1 ;
border - radius : 16 rpx ;
overflow : hidden ;
background : # f5f5f5 ;
}
. popup - img {
width : 100 % ;
height : 100 % ;
}
. loading - text ,
. no - more - text {
text - align : center ;
padding : 24 rpx ;
color : # 999 ;
font - size : 24 rpx ;
}
< / style >