416 lines
8.8 KiB
Vue
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>
|