界面如下
代码如下
< template>
< view class = " puzzle-container" >
< view class = " puzzle-title" > 任务进度 {{completedCount}}/{{totalPieces}}</ view>
< view class = " puzzle-grid" >
< view
v-for = " (piece, index) in puzzlePieces"
:key = " index"
class = " puzzle-piece"
:class = " {
' unlocked' : piece.unlocked,
' locked' : !piece.unlocked,
' unlock-animation' : animatingIndex === index
}"
@click = " onPieceTap(index)"
>
< image
v-if = " piece.unlocked"
:src = " piece.imageUrl"
mode = " aspectFill"
class = " piece-image"
> </ image>
< image
v-else
src = " @/static/images/form/close-image.png"
class = " lock-icon"
> </ image>
< text class = " piece-number" > {{index + 1}}</ text>
< view class = " particle-container" v-if = " animatingIndex === index" >
< view
class = " particle"
v-for = " particle in particles"
:key = " particle.id"
:style = " {
left: particle.left + ' %' ,
top: particle.top + ' %' ,
backgroundColor: particle.color
}"
> </ view>
</ view>
</ view>
</ view>
< uni-popup ref = " completePopup" type = " center" >
< view class = " celebration-popup" >
< image src = " /static/celebration.png" mode = " widthFix" > </ image>
< text class = " celebration-text" > 恭喜完成所有任务!</ text>
< button @click = " viewCompletePuzzle" > 查看完整拼图</ button>
</ view>
</ uni-popup>
</ view>
</ template>
< script>
export default {
data ( ) {
return {
totalPieces: 20 ,
puzzlePieces: [ ] ,
completedCount: 0 ,
animatingIndex: - 1 ,
particles: [ ] ,
allCompleted: false
}
} ,
created ( ) {
this . initPuzzle ( ) ;
} ,
methods: {
initPuzzle ( ) {
this . puzzlePieces = Array ( this . totalPieces) . fill ( ) . map ( ( _, index ) => ( {
index,
unlocked: false ,
imageUrl: ` ../../static/pt/pt ${ index + 1 } .png `
} ) ) ;
this . unlockPiece ( 0 ) ;
this . unlockPiece ( 1 ) ;
this . unlockPiece ( 2 ) ;
this . unlockPiece ( 3 ) ;
this . unlockPiece ( 4 ) ;
this . completedCount = 5 ;
} ,
unlockPiece ( index ) {
if ( index >= this . totalPieces || this . puzzlePieces[ index] . unlocked) return ;
this . $set ( this . puzzlePieces, index, {
... this . puzzlePieces[ index] ,
unlocked: true
} ) ;
this . animatingIndex = index;
this . completedCount++ ;
this . createParticles ( ) ;
if ( this . completedCount === this . totalPieces) {
this . allCompleted = true ;
setTimeout ( ( ) => {
this . $refs. completePopup. open ( ) ;
} , 1000 ) ;
}
setTimeout ( ( ) => {
this . animatingIndex = - 1 ;
} , 1000 ) ;
} ,
createParticles ( ) {
this . particles = [ ] ;
const particles = [ ] ;
for ( let i = 0 ; i < 12 ; i++ ) {
particles. push ( {
id: i,
left: Math. random ( ) * 60 + 20 ,
top: Math. random ( ) * 60 + 20 ,
color: ` hsl( ${ Math. random ( ) * 360 } , 100%, 50%) ` ,
'--tx' : Math. random ( ) * 100 ,
'--ty' : Math. random ( ) * 100
} ) ;
}
this . particles = particles;
setTimeout ( ( ) => {
this . particles = [ ] ;
} , 1000 ) ;
} ,
onPieceTap ( index ) {
if ( ! this . puzzlePieces[ index] . unlocked) {
uni. showToast ( {
title: ` 完成任务 ${ index + 1 } 后解锁 ` ,
icon: 'none'
} ) ;
}
} ,
completeRandomTask ( ) {
const firstLockedIndex = this . puzzlePieces. findIndex ( p => ! p. unlocked) ;
if ( firstLockedIndex !== - 1 ) {
this . unlockPiece ( firstLockedIndex) ;
} else {
uni. showToast ( {
title: '所有拼图已解锁!' ,
icon: 'success'
} ) ;
}
} ,
viewCompletePuzzle ( ) {
this . $refs. completePopup. close ( ) ;
uni. navigateTo ( {
url: '/pages/puzzle-preview/puzzle-preview'
} ) ;
}
}
}
</ script>
< style lang = " scss" scoped >
.puzzle-container {
padding : 20rpx;
box-sizing : border-box;
}
.puzzle-title {
text-align : center;
font-size : 36rpx;
margin-bottom : 30rpx;
color : #333;
}
.puzzle-grid {
display : grid;
grid-template-columns : repeat ( 5, 1fr) ;
gap : 10rpx;
}
.puzzle-piece {
position : relative;
aspect-ratio : 1;
border-radius : 10rpx;
overflow : hidden;
display : flex;
justify-content : center;
align-items : center;
background-color : #f5f5f5;
&.unlocked {
box-shadow : 0 4rpx 12rpx rgba ( 0, 0, 0, 0.1) ;
}
.piece-image, .lock-icon {
width : 100%;
height : 100%;
}
.lock-icon {
width : 60% !important ;
height : 60% !important ;
opacity : 0.6;
}
.piece-number {
position : absolute;
bottom : 8rpx;
right : 8rpx;
font-size : 24rpx;
color : #999;
background-color : rgba ( 255, 255, 255, 0.7) ;
border-radius : 50%;
width : 36rpx;
height : 36rpx;
display : flex;
justify-content : center;
align-items : center;
}
}
.unlock-animation {
animation : unlockScale 0.6s ease-out;
}
@keyframes unlockScale {
0% { transform : scale ( 0.8) ; opacity : 0; }
50% { transform : scale ( 1.1) ; }
100% { transform : scale ( 1) ; opacity : 1; }
}
.particle-container {
position : absolute;
width : 100%;
height : 100%;
pointer-events : none;
}
.particle {
position : absolute;
width : 10rpx;
height : 10rpx;
border-radius : 50%;
animation : particleFly 1s ease-out forwards;
}
@keyframes particleFly {
to {
transform : translate (
calc ( ( var ( --tx, 0) - 50) * 1rpx) ,
calc ( ( var ( --ty, 0) - 50) * 1rpx)
) ;
opacity : 0;
}
}
.celebration-popup {
background : white;
padding : 40rpx;
border-radius : 20rpx;
display : flex;
flex-direction : column;
align-items : center;
image {
width : 200rpx;
margin-bottom : 20rpx;
}
.celebration-text {
font-size : 36rpx;
margin-bottom : 30rpx;
color : #ff6b81;
font-weight : bold;
}
button {
background : #ff6b81;
color : white;
border : none;
}
}
</ style>