|
@@ -0,0 +1,329 @@
|
|
|
+<template>
|
|
|
+ <view class="guide-manager">
|
|
|
+ <!-- 遮罩层 -->
|
|
|
+ <view class="guide-mask" v-if="currentStage" @click="handleMaskClick">
|
|
|
+ <!-- 高亮区域 -->
|
|
|
+ <view class="highlight-area" :style="highlightStyle"></view>
|
|
|
+
|
|
|
+ <!-- 提示框 -->
|
|
|
+ <view class="guide-tips" :style="tipsStyle">
|
|
|
+ <view class="tips-content">
|
|
|
+ <text class="tips-text">{{ currentStep.tips }}</text>
|
|
|
+ <view class="tips-buttons">
|
|
|
+ <text class="skip-btn" @click.stop="skipStage">跳过</text>
|
|
|
+ <text class="next-btn" @click.stop="nextStep">{{ isLastStep ? '完成' : '下一步' }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <!-- 箭头 -->
|
|
|
+ <view class="arrow" :style="arrowStyle"></view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+export default {
|
|
|
+ name: 'GuideManager',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ // 当前引导阶段
|
|
|
+ currentStage: null,
|
|
|
+ // 当前步骤索引
|
|
|
+ currentStepIndex: 0,
|
|
|
+ // 已完成阶段
|
|
|
+ completedStages: [],
|
|
|
+ // 引导配置
|
|
|
+ guideConfig: {
|
|
|
+ // 主岛引导
|
|
|
+ mainLand: {
|
|
|
+ id: 'mainLand',
|
|
|
+ steps: [
|
|
|
+ {
|
|
|
+ target: '.house-image',
|
|
|
+ tips: '这是你的房屋,点击可以进入查看',
|
|
|
+ position: 'bottom'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ target: '.farm-image',
|
|
|
+ tips: '这是农场,可以种植作物',
|
|
|
+ position: 'bottom'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ target: '.shop-image',
|
|
|
+ tips: '这是商店,可以购买物品',
|
|
|
+ position: 'bottom'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ // 家园引导
|
|
|
+ homeLand: {
|
|
|
+ id: 'homeLand',
|
|
|
+ steps: [
|
|
|
+ {
|
|
|
+ target: '.house-image',
|
|
|
+ tips: '这是你的家,点击可以进入',
|
|
|
+ position: 'bottom'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ target: '.table-image',
|
|
|
+ tips: '这是工作台,可以制作物品',
|
|
|
+ position: 'bottom'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ target: '.taskBoard-image',
|
|
|
+ tips: '这是任务板,可以查看任务',
|
|
|
+ position: 'bottom'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ // 当前步骤
|
|
|
+ currentStep() {
|
|
|
+ if (!this.currentStage) return null
|
|
|
+ return this.currentStage.steps[this.currentStepIndex]
|
|
|
+ },
|
|
|
+ // 是否是最后一步
|
|
|
+ isLastStep() {
|
|
|
+ if (!this.currentStage) return false
|
|
|
+ return this.currentStepIndex === this.currentStage.steps.length - 1
|
|
|
+ },
|
|
|
+ // 高亮区域样式
|
|
|
+ highlightStyle() {
|
|
|
+ if (!this.currentStep) return {}
|
|
|
+ return this.currentStep.rect ? {
|
|
|
+ top: `${this.currentStep.rect.top}px`,
|
|
|
+ left: `${this.currentStep.rect.left}px`,
|
|
|
+ width: `${this.currentStep.rect.width}px`,
|
|
|
+ height: `${this.currentStep.rect.height}px`
|
|
|
+ } : {}
|
|
|
+ },
|
|
|
+ // 提示框样式
|
|
|
+ tipsStyle() {
|
|
|
+ if (!this.currentStep || !this.currentStep.rect) return {}
|
|
|
+
|
|
|
+ const rect = this.currentStep.rect
|
|
|
+ const position = this.currentStep.position
|
|
|
+ const systemInfo = uni.getSystemInfoSync()
|
|
|
+
|
|
|
+ let style = {}
|
|
|
+ switch (position) {
|
|
|
+ case 'top':
|
|
|
+ style = {
|
|
|
+ bottom: `${systemInfo.windowHeight - rect.top + 10}px`,
|
|
|
+ left: `${rect.left + rect.width / 2}px`,
|
|
|
+ transform: 'translateX(-50%)'
|
|
|
+ }
|
|
|
+ break
|
|
|
+ case 'bottom':
|
|
|
+ style = {
|
|
|
+ top: `${rect.bottom + 10}px`,
|
|
|
+ left: `${rect.left + rect.width / 2}px`,
|
|
|
+ transform: 'translateX(-50%)'
|
|
|
+ }
|
|
|
+ break
|
|
|
+ case 'left':
|
|
|
+ style = {
|
|
|
+ top: `${rect.top + rect.height / 2}px`,
|
|
|
+ right: `${systemInfo.windowWidth - rect.left + 10}px`,
|
|
|
+ transform: 'translateY(-50%)'
|
|
|
+ }
|
|
|
+ break
|
|
|
+ case 'right':
|
|
|
+ style = {
|
|
|
+ top: `${rect.top + rect.height / 2}px`,
|
|
|
+ left: `${rect.right + 10}px`,
|
|
|
+ transform: 'translateY(-50%)'
|
|
|
+ }
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ return style
|
|
|
+ },
|
|
|
+ // 箭头样式
|
|
|
+ arrowStyle() {
|
|
|
+ if (!this.currentStep) return {}
|
|
|
+ const position = this.currentStep.position
|
|
|
+
|
|
|
+ let style = {}
|
|
|
+ switch (position) {
|
|
|
+ case 'top':
|
|
|
+ style = {
|
|
|
+ transform: 'rotate(180deg)',
|
|
|
+ bottom: '0'
|
|
|
+ }
|
|
|
+ break
|
|
|
+ case 'bottom':
|
|
|
+ style = {
|
|
|
+ transform: 'rotate(0deg)',
|
|
|
+ top: '0'
|
|
|
+ }
|
|
|
+ break
|
|
|
+ case 'left':
|
|
|
+ style = {
|
|
|
+ transform: 'rotate(90deg)',
|
|
|
+ right: '0'
|
|
|
+ }
|
|
|
+ break
|
|
|
+ case 'right':
|
|
|
+ style = {
|
|
|
+ transform: 'rotate(-90deg)',
|
|
|
+ left: '0'
|
|
|
+ }
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ return style
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ // 开始引导
|
|
|
+ startGuide(stageId) {
|
|
|
+ // 检查是否已完成
|
|
|
+ if (this.completedStages.includes(stageId)) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置当前阶段
|
|
|
+ this.currentStage = this.guideConfig[stageId]
|
|
|
+ this.currentStepIndex = 0
|
|
|
+
|
|
|
+ // 等待DOM更新后更新位置
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.updatePositions()
|
|
|
+ })
|
|
|
+ },
|
|
|
+ // 下一步
|
|
|
+ nextStep() {
|
|
|
+ if (this.isLastStep) {
|
|
|
+ this.completeStage()
|
|
|
+ } else {
|
|
|
+ this.currentStepIndex++
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.updatePositions()
|
|
|
+ })
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 跳过当前阶段
|
|
|
+ skipStage() {
|
|
|
+ this.completeStage()
|
|
|
+ },
|
|
|
+ // 完成当前阶段
|
|
|
+ completeStage() {
|
|
|
+ if (this.currentStage) {
|
|
|
+ // 添加到已完成列表
|
|
|
+ this.completedStages.push(this.currentStage.id)
|
|
|
+ // 保存到本地存储
|
|
|
+ this.saveCompletedStages()
|
|
|
+ // 重置状态
|
|
|
+ this.currentStage = null
|
|
|
+ this.currentStepIndex = 0
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 更新位置
|
|
|
+ updatePositions() {
|
|
|
+ if (!this.currentStep) return
|
|
|
+
|
|
|
+ const query = uni.createSelectorQuery().in(this)
|
|
|
+ query.select(this.currentStep.target).boundingClientRect(data => {
|
|
|
+ if (data) {
|
|
|
+ // 更新当前步骤的位置信息
|
|
|
+ this.$set(this.currentStage.steps[this.currentStepIndex], 'rect', data)
|
|
|
+ }
|
|
|
+ }).exec()
|
|
|
+ },
|
|
|
+ // 保存已完成阶段
|
|
|
+ saveCompletedStages() {
|
|
|
+ uni.setStorageSync('completedGuideStages', this.completedStages)
|
|
|
+ },
|
|
|
+ // 加载已完成阶段
|
|
|
+ loadCompletedStages() {
|
|
|
+ const stages = uni.getStorageSync('completedGuideStages')
|
|
|
+ if (stages) {
|
|
|
+ this.completedStages = stages
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 处理遮罩层点击
|
|
|
+ handleMaskClick() {
|
|
|
+ // 可以在这里添加点击遮罩层的处理逻辑
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ // 加载已完成阶段
|
|
|
+ this.loadCompletedStages()
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.guide-manager {
|
|
|
+ .guide-mask {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background-color: rgba(0, 0, 0, 0.7);
|
|
|
+ z-index: 9999;
|
|
|
+
|
|
|
+ .highlight-area {
|
|
|
+ position: absolute;
|
|
|
+ box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.7);
|
|
|
+ border-radius: 4px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .guide-tips {
|
|
|
+ position: absolute;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 16px;
|
|
|
+ min-width: 200px;
|
|
|
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
|
|
+ transition: all 0.3s ease;
|
|
|
+
|
|
|
+ .tips-content {
|
|
|
+ .tips-text {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #333;
|
|
|
+ line-height: 1.5;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tips-buttons {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ .skip-btn,
|
|
|
+ .next-btn {
|
|
|
+ font-size: 14px;
|
|
|
+ padding: 4px 12px;
|
|
|
+ border-radius: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+
|
|
|
+ .skip-btn {
|
|
|
+ color: #999;
|
|
|
+ }
|
|
|
+
|
|
|
+ .next-btn {
|
|
|
+ color: #fff;
|
|
|
+ background-color: #1890ff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .arrow {
|
|
|
+ position: absolute;
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ border: 8px solid transparent;
|
|
|
+ border-bottom-color: #fff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|