topfans/frontend/components/GuideListModal.vue
2026-04-07 23:08:49 +08:00

416 lines
8.8 KiB
Vue

<template>
<view v-if="visible" class="guide-list-modal">
<view class="modal-mask" @click="handleClose"></view>
<view class="modal-content">
<view class="modal-header">
<view class="modal-title">🧭 新手引导</view>
<view class="modal-close" @click="handleClose">✕</view>
</view>
<view class="modal-body">
<view
v-for="item in guideList"
:key="item.key"
class="guide-item"
:class="{ 'in-progress': item.status === 'in_progress' }"
>
<view class="guide-item-header">
<view class="guide-status-badge" :class="item.status">
{{ item.statusText }}
</view>
<text class="guide-name">{{ item.name }}</text>
</view>
<view class="guide-item-body">
<text class="guide-desc">{{ item.desc }}</text>
</view>
<!-- 进度条(进行中状态显示) -->
<view v-if="item.status === 'in_progress'" class="guide-progress">
<view class="progress-bar">
<view
class="progress-fill"
:style="{ width: item.progress.percentage + '%' }"
></view>
</view>
<text class="progress-text">{{ item.progress.completed }}/{{ item.progress.total }} 步骤</text>
</view>
<view class="guide-item-footer">
<!-- 未开始 -->
<view
v-if="item.buttonType === 'start'"
class="guide-btn start-btn"
@click="handleStartGuide(item.key)"
>
{{ item.buttonText }}
</view>
<!-- 进行中 -->
<view
v-else-if="item.buttonType === 'continue'"
class="guide-btn continue-btn"
@click="handleContinueGuide(item.key)"
>
{{ item.buttonText }}
</view>
<!-- 已完成(可领取) -->
<view
v-else-if="item.buttonType === 'claim'"
class="guide-btn claim-btn"
@click="handleClaimReward(item.key)"
>
{{ item.buttonText }}
</view>
<!-- 已领取 -->
<view
v-else
class="guide-btn disabled-btn"
>
{{ item.buttonText }}
</view>
</view>
</view>
</view>
<view class="modal-footer">
<view class="footer-stats">
已完成 {{ doneCount }}/{{ totalCount }}
</view>
<view v-if="claimableCount > 0" class="footer-claim-tip">
{{ claimableCount }} 个奖励可领取
</view>
</view>
</view>
</view>
</template>
<script>
import {
getGuideStatusList,
getStepProgress,
hasGuideProgress,
getNextIncompleteStep,
getStepPage,
clearSubStepProgress,
claimGuideReward
} from '@/utils/guideConfig'
export default {
name: 'GuideListModal',
props: {
visible: {
type: Boolean,
default: false
}
},
data() {
return {
guideList: []
}
},
computed: {
totalCount() {
return this.guideList.length
},
doneCount() {
return this.guideList.filter(item => item.status === 'completed' || item.status === 'reward_claimed').length
},
claimableCount() {
return this.guideList.filter(item => item.status === 'completed').length
}
},
watch: {
visible(val) {
if (val) {
this.refreshList()
}
}
},
methods: {
refreshList() {
const rawList = getGuideStatusList()
this.guideList = rawList.map(item => {
const progress = getStepProgress(item.key)
const status = this.calculateStatus(item.key, item.done, item.claimed)
return {
...item,
progress,
status,
statusText: this.getStatusText(status, progress),
buttonText: this.getButtonText(status),
buttonType: this.getButtonType(status)
}
})
},
calculateStatus(key, done, claimed) {
if (claimed) return 'reward_claimed'
if (done && !claimed) return 'completed'
if (hasGuideProgress(key)) return 'in_progress'
return 'not_started'
},
getStatusText(status, progress) {
if (status === 'in_progress') {
return `${progress.completed}/${progress.total} 步骤`
}
const map = {
not_started: '未开始',
completed: '已完成',
reward_claimed: '已领取'
}
return map[status] || '未开始'
},
getButtonText(status) {
const map = {
not_started: '开始',
in_progress: '继续',
completed: '领取奖励',
reward_claimed: '已领取'
}
return map[status] || '开始'
},
getButtonType(status) {
const map = {
not_started: 'start',
in_progress: 'continue',
completed: 'claim',
reward_claimed: 'claimed'
}
return map[status] || 'start'
},
handleStartGuide(key) {
console.log('[GuideListModal] handleStartGuide clicked, key:', key)
// 清除进度从头开始
clearSubStepProgress(key)
const targetPage = getStepPage(key, 0)
console.log('[GuideListModal] emitting start-guide:', { key, fromStep: 0, targetPage })
this.$emit('start-guide', { key, fromStep: 0, targetPage })
this.$emit('close')
},
handleContinueGuide(key) {
// 从第一个未完成步骤继续
const resumeStep = getNextIncompleteStep(key)
const targetPage = getStepPage(key, resumeStep)
this.$emit('start-guide', { key, fromStep: resumeStep, targetPage })
this.$emit('close')
},
handleClaimReward(key) {
const reward = claimGuideReward(key)
if (reward) {
uni.showToast({
title: `领取成功!${reward.exp}经验 ${reward.diamond}`,
icon: 'none'
})
this.refreshList()
this.$emit('claim-success', reward)
}
},
handleClose() {
this.$emit('close')
}
}
}
</script>
<style lang="scss" scoped>
.guide-list-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
.modal-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
}
.modal-content {
position: relative;
width: 600rpx;
max-height: 80vh;
background: #fff;
border-radius: 24rpx;
display: flex;
flex-direction: column;
overflow: hidden;
}
.modal-header {
display: flex;
align-items: center;
justify-content: center;
padding: 32rpx;
position: relative;
}
.modal-title {
font-size: 34rpx;
font-weight: bold;
color: #333;
}
.modal-close {
position: absolute;
right: 32rpx;
font-size: 36rpx;
color: #999;
padding: 8rpx;
}
.modal-body {
flex: 1;
overflow-y: auto;
padding: 0 32rpx;
}
.guide-item {
background: #f8f8f8;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 20rpx;
&.in-progress {
border-left: 4rpx solid #4a90e2;
}
}
.guide-item-header {
display: flex;
align-items: center;
margin-bottom: 12rpx;
}
.guide-status-badge {
padding: 4rpx 12rpx;
border-radius: 8rpx;
font-size: 22rpx;
margin-right: 12rpx;
&.not_started {
background: #f0f0f0;
color: #999;
}
&.in_progress {
background: #e6f0ff;
color: #4a90e2;
}
&.completed {
background: #fff3e6;
color: #ff9500;
}
&.reward_claimed {
background: #e8f5e9;
color: #4caf50;
}
}
.guide-name {
font-size: 30rpx;
font-weight: 500;
color: #333;
flex: 1;
}
.guide-item-body {
margin-bottom: 16rpx;
}
.guide-desc {
font-size: 26rpx;
color: #666;
}
// 进度条
.guide-progress {
margin-bottom: 16rpx;
}
.progress-bar {
height: 8rpx;
background: #eee;
border-radius: 4rpx;
overflow: hidden;
margin-bottom: 8rpx;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
border-radius: 4rpx;
transition: width 0.3s ease;
}
.progress-text {
font-size: 22rpx;
color: #999;
}
.guide-item-footer {
display: flex;
justify-content: flex-end;
}
.guide-btn {
padding: 12rpx 32rpx;
color: #fff;
border-radius: 30rpx;
font-size: 26rpx;
}
.start-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.continue-btn {
background: linear-gradient(135deg, #4a90e2 0%, #357abd 100%);
}
.claim-btn {
background: linear-gradient(135deg, #f6d365 0%, #fda085 100%);
}
.disabled-btn {
background: #ddd;
color: #999;
}
.modal-footer {
padding: 24rpx 32rpx;
border-top: 1rpx solid #eee;
}
.footer-stats {
font-size: 28rpx;
color: #333;
text-align: center;
margin-bottom: 8rpx;
}
.footer-claim-tip {
font-size: 26rpx;
color: #ff6b6b;
text-align: center;
}
</style>