GuideManager.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. <template>
  2. <view class="guide-manager">
  3. <!-- 遮罩层 -->
  4. <view class="guide-mask" v-if="currentStage" @click="handleMaskClick">
  5. <!-- 高亮区域 -->
  6. <view class="highlight-area" :style="highlightStyle"></view>
  7. <!-- 提示框 -->
  8. <view class="guide-tips" :style="tipsStyle">
  9. <view class="tips-content">
  10. <text class="tips-text">{{ currentStep.tips }}</text>
  11. <view class="tips-buttons">
  12. <text class="skip-btn" @click.stop="skipStage">跳过</text>
  13. <text class="next-btn" @click.stop="nextStep">{{ isLastStep ? '完成' : '下一步' }}</text>
  14. </view>
  15. </view>
  16. <!-- 箭头 -->
  17. <view class="arrow" :style="arrowStyle"></view>
  18. </view>
  19. </view>
  20. </view>
  21. </template>
  22. <script>
  23. export default {
  24. name: 'GuideManager',
  25. data() {
  26. return {
  27. // 当前引导阶段
  28. currentStage: null,
  29. // 当前步骤索引
  30. currentStepIndex: 0,
  31. // 已完成阶段
  32. completedStages: [],
  33. // 已完成的主线
  34. // completedMainLines: [],
  35. // 引导配置
  36. guideConfig: {
  37. mainLines: [
  38. {
  39. name: "new immigrant",
  40. stage: "homeLand",
  41. steps: [
  42. {
  43. arrow: null,
  44. talkData: [
  45. {
  46. characterImage: '/static/island/npc.png',
  47. characterName: '罗宾',
  48. text: '你好!欢迎来到萌创星球,你的移民计划正式完成。',
  49. position: 'left',
  50. isMirror: true
  51. }
  52. ]
  53. },
  54. {
  55. arrow: {x: 350, y: 460, r: 0},
  56. talkData: [
  57. {
  58. characterImage: '/static/island/npc.png',
  59. characterName: '罗宾',
  60. text: '这里是工具台 ,它可以制作各实用和非常棒的工具和装饰物,千万不要错过哟。',
  61. position: 'left',
  62. isMirror: true
  63. },
  64. ]
  65. }
  66. ]
  67. },
  68. {
  69. name: "new craftsman",
  70. stage: "homeLand_table",
  71. steps: [
  72. {
  73. arrow: null,
  74. talkData: [
  75. {
  76. characterImage: '/static/island/npc.png',
  77. characterName: '罗宾',
  78. text: '果然很顺利的打开了工作台呢!',
  79. position: 'left',
  80. isMirror: true
  81. },
  82. {
  83. characterImage: '/static/island/building/1.png',
  84. characterName: '我',
  85. text: '好像可以有好多工具,但是好像没有材料呢~',
  86. position: 'right',
  87. isMirror: false
  88. },
  89. {
  90. characterImage: '/static/island/npc.png',
  91. characterName: '罗宾',
  92. text: '材料确实不太容易获取呢,可以去看看公告栏。',
  93. position: 'left',
  94. isMirror: true
  95. }
  96. ]
  97. },
  98. {
  99. arrow: {x: 885, y: 535, r: 270},
  100. talkData: [
  101. {
  102. characterImage: '/static/island/npc.png',
  103. characterName: '罗宾',
  104. text: '看到小箭头指示的地方了么...那是任务看板,可以获取一些稀有道具和铃钱哦。',
  105. position: 'left',
  106. isMirror: true
  107. }
  108. ]
  109. }
  110. ]
  111. },
  112. {
  113. name: "oh, taskBoard",
  114. stage: "homeLand_TaskBoard",
  115. steps: [
  116. {
  117. arrow: null,
  118. talkData: [
  119. {
  120. characterImage: '/static/island/npc.png',
  121. characterName: '罗宾',
  122. text: '这里就是任务面板呦....',
  123. position: 'left',
  124. isMirror: true
  125. },
  126. {
  127. characterImage: '/static/island/npc.png',
  128. characterName: '罗宾',
  129. text: '顺便说一下,你的移民计划正式开启目前您需要缴纳移民费用为388888元。也在任务面板发布了呦~',
  130. position: 'left',
  131. isMirror: true
  132. },
  133. {
  134. characterImage: '/static/island/building/1.png',
  135. characterName: '我',
  136. text: '什么!移民费用388888铃钱,这也太贵了吧...',
  137. position: 'right',
  138. isMirror: false
  139. },
  140. {
  141. characterImage: '/static/island/npc.png',
  142. characterName: '罗宾',
  143. text: '移民本就是一件不容易的事呢...',
  144. position: 'left',
  145. isMirror: true
  146. },
  147. {
  148. characterImage: '/static/island/npc.png',
  149. characterName: '罗宾',
  150. text: '对了,听说近期市政厅那边的花田正在招募园丁,报酬丰厚,',
  151. position: 'left',
  152. isMirror: true
  153. },
  154. {
  155. characterImage: '/static/island/npc.png',
  156. characterName: '罗宾',
  157. text: '相信以你的能力完全是没问题的,右侧过桥后就到了。',
  158. position: 'left',
  159. isMirror: true
  160. }
  161. ]
  162. }
  163. ]
  164. },
  165. {
  166. name: "welcome to mainLand~",
  167. stage: "mainLand",
  168. steps: [
  169. {
  170. arrow: null,
  171. talkData: [
  172. {
  173. characterImage: '/static/island/npc.png',
  174. characterName: '罗宾',
  175. text: '正式欢迎您来到我们的市政厅所在的主岛....',
  176. position: 'left',
  177. isMirror: true
  178. },
  179. {
  180. characterImage: '/static/island/npc.png',
  181. characterName: '罗宾',
  182. text: '因为前几天发生了台风,所以还有两条路被倒下的树给拦住了。',
  183. position: 'left',
  184. isMirror: true
  185. },
  186. {
  187. characterImage: '/static/island/building/1.png',
  188. characterName: '我',
  189. text: 'ah...原来如此,你刚才说的花田有没有被拦住?',
  190. position: 'right',
  191. isMirror: false
  192. }
  193. ]
  194. },
  195. {
  196. arrow: {x: 785, y: 535, r: 270},
  197. talkData: [
  198. {
  199. characterImage: '/static/island/npc.png',
  200. characterName: '罗宾',
  201. text: '哦~瞧我这记性,花田在市政厅的旁边,就在那儿。走,我会告诉你怎么种花。',
  202. position: 'left',
  203. isMirror: true
  204. }
  205. ]
  206. }
  207. ]
  208. },
  209. {
  210. name: "flowerFarm",
  211. stage: "mainLand_farm",
  212. steps: [
  213. {
  214. arrow: null,
  215. talkData: [
  216. {
  217. characterImage: '/static/island/npc.png',
  218. characterName: '罗宾',
  219. text: '快来吧,这里就是花田了。',
  220. position: 'left',
  221. isMirror: true
  222. }
  223. ]
  224. }
  225. ]
  226. }
  227. ]
  228. },
  229. // 当前主线ID
  230. currentMainLineId: 0,
  231. // 当前主线
  232. currentMainLine: null,
  233. // 对话数据
  234. currentTalkData: [],
  235. // 当前对话索引
  236. currentTalkIndex: 0
  237. }
  238. },
  239. computed: {
  240. // 当前步骤
  241. currentStep() {
  242. if (!this.currentStage) return null
  243. return this.currentStage.steps[this.currentStepIndex]
  244. },
  245. // 是否是最后一步
  246. isLastStep() {
  247. if (!this.currentStage) return false
  248. return this.currentStepIndex === this.currentStage.steps.length - 1
  249. },
  250. // 高亮区域样式
  251. highlightStyle() {
  252. if (!this.currentStep) return {}
  253. return this.currentStep.rect ? {
  254. top: `${this.currentStep.rect.top}px`,
  255. left: `${this.currentStep.rect.left}px`,
  256. width: `${this.currentStep.rect.width}px`,
  257. height: `${this.currentStep.rect.height}px`
  258. } : {}
  259. },
  260. // 提示框样式
  261. tipsStyle() {
  262. if (!this.currentStep || !this.currentStep.rect) return {}
  263. const rect = this.currentStep.rect
  264. const position = this.currentStep.position
  265. const systemInfo = uni.getSystemInfoSync()
  266. let style = {}
  267. switch (position) {
  268. case 'top':
  269. style = {
  270. bottom: `${systemInfo.windowHeight - rect.top + 10}px`,
  271. left: `${rect.left + rect.width / 2}px`,
  272. transform: 'translateX(-50%)'
  273. }
  274. break
  275. case 'bottom':
  276. style = {
  277. top: `${rect.bottom + 10}px`,
  278. left: `${rect.left + rect.width / 2}px`,
  279. transform: 'translateX(-50%)'
  280. }
  281. break
  282. case 'left':
  283. style = {
  284. top: `${rect.top + rect.height / 2}px`,
  285. right: `${systemInfo.windowWidth - rect.left + 10}px`,
  286. transform: 'translateY(-50%)'
  287. }
  288. break
  289. case 'right':
  290. style = {
  291. top: `${rect.top + rect.height / 2}px`,
  292. left: `${rect.right + 10}px`,
  293. transform: 'translateY(-50%)'
  294. }
  295. break
  296. }
  297. return style
  298. },
  299. // 箭头样式
  300. arrowStyle() {
  301. if (!this.currentStep) return {}
  302. const position = this.currentStep.position
  303. let style = {}
  304. switch (position) {
  305. case 'top':
  306. style = {
  307. transform: 'rotate(180deg)',
  308. bottom: '0'
  309. }
  310. break
  311. case 'bottom':
  312. style = {
  313. transform: 'rotate(0deg)',
  314. top: '0'
  315. }
  316. break
  317. case 'left':
  318. style = {
  319. transform: 'rotate(90deg)',
  320. right: '0'
  321. }
  322. break
  323. case 'right':
  324. style = {
  325. transform: 'rotate(-90deg)',
  326. left: '0'
  327. }
  328. break
  329. }
  330. return style
  331. }
  332. },
  333. methods: {
  334. // 开始引导
  335. startGuide(stage) {
  336. // 获取当前主线
  337. const mainLine = this.guideConfig.mainLines[this.currentMainLineId];
  338. // 检查主线是否存在且场景是否匹配
  339. if (!mainLine || mainLine.stage !== stage) {
  340. return false;
  341. }
  342. // 检查主线是否已完成
  343. // if (this.completedMainLines.includes(this.currentMainLineId)) {
  344. // return false;
  345. // }
  346. // 设置当前主线
  347. this.currentMainLine = mainLine;
  348. this.currentStepIndex = 0;
  349. this.currentTalkIndex = 0;
  350. // 初始化对话数据
  351. this.initTalkData();
  352. return true;
  353. },
  354. // 设置当前主线ID
  355. setMainLineId(id) {
  356. if (id >= 0 && id < this.guideConfig.mainLines.length) {
  357. this.currentMainLineId = id;
  358. // 保存到本地存储
  359. this.saveCurrentMainLineId();
  360. }
  361. },
  362. // 保存当前主线ID
  363. saveCurrentMainLineId() {
  364. uni.setStorageSync('currentGuideMainLineId', this.currentMainLineId);
  365. },
  366. // 加载当前主线ID
  367. loadCurrentMainLineId() {
  368. const id = uni.getStorageSync('currentGuideMainLineId');
  369. if (id !== '') {
  370. this.currentMainLineId = id;
  371. }
  372. },
  373. // 初始化对话数据
  374. initTalkData() {
  375. if (this.currentMainLine && this.currentMainLine.steps[this.currentStepIndex]) {
  376. this.currentTalkData = this.currentMainLine.steps[this.currentStepIndex].talkData;
  377. }
  378. },
  379. // 下一步
  380. nextStep() {
  381. console.log("-------- next step", {
  382. currentMainLine: this.currentMainLine,
  383. currentStepIndex: this.currentStepIndex,
  384. currentTalkIndex: this.currentTalkIndex,
  385. currentTalkData: this.currentTalkData
  386. });
  387. if (!this.currentMainLine) return;
  388. // 如果当前步骤是最后一步
  389. if (this.currentStepIndex === this.currentMainLine.steps.length - 1) {
  390. this.completeMainLine();
  391. } else {
  392. // 进入下一步
  393. this.currentStepIndex++;
  394. this.currentTalkIndex = 0;
  395. this.initTalkData();
  396. }
  397. console.log("-------- next step2", {
  398. currentMainLine: this.currentMainLine,
  399. currentStepIndex: this.currentStepIndex,
  400. currentTalkIndex: this.currentTalkIndex,
  401. currentTalkData: this.currentTalkData
  402. });
  403. },
  404. // 完成主线
  405. completeMainLine() {
  406. if (this.currentMainLine) {
  407. // 添加到已完成列表
  408. // this.completedMainLines.push(this.currentMainLineId);
  409. // 保存到本地存储
  410. // this.saveCompletedMainLines();
  411. // 重置状态
  412. this.currentMainLine = null;
  413. this.currentStepIndex = 0;
  414. this.currentTalkIndex = 0;
  415. this.currentTalkData = [];
  416. // 增加主线ID
  417. this.currentMainLineId++;
  418. this.saveCurrentMainLineId();
  419. }
  420. },
  421. // 保存已完成主线
  422. // saveCompletedMainLines() {
  423. // uni.setStorageSync('completedGuideMainLines', this.completedMainLines);
  424. // },
  425. // 加载已完成主线
  426. // loadCompletedMainLines() {
  427. // const mainLines = uni.getStorageSync('completedGuideMainLines');
  428. // if (mainLines) {
  429. // this.completedMainLines = mainLines;
  430. // }
  431. // },
  432. // 获取当前箭头位置
  433. getCurrentArrowPosition() {
  434. if (!this.currentMainLine || !this.currentMainLine.steps[this.currentStepIndex]) {
  435. return null;
  436. }
  437. return this.currentMainLine.steps[this.currentStepIndex].arrow;
  438. },
  439. // 获取当前对话数据
  440. getCurrentTalkData() {
  441. if (!this.currentTalkData.length) return null;
  442. return this.currentTalkData;
  443. },
  444. // 跳过当前阶段
  445. skipStage() {
  446. this.completeStage()
  447. },
  448. // 完成当前阶段
  449. completeStage() {
  450. if (this.currentStage) {
  451. // 添加到已完成列表
  452. this.completedStages.push(this.currentStage.id)
  453. // 如果当前阶段有主线ID,检查该主线的所有阶段是否都已完成
  454. if (this.currentStage.mainLineId) {
  455. const mainLineId = this.currentStage.mainLineId
  456. const mainLineStages = Object.values(this.guideConfig).filter(
  457. stage => stage.mainLineId === mainLineId
  458. )
  459. // 检查该主线的所有阶段是否都已完成
  460. const isMainLineCompleted = mainLineStages.every(
  461. stage => this.completedStages.includes(stage.id)
  462. )
  463. // 如果主线完成,添加到已完成主线列表
  464. // if (isMainLineCompleted && !this.completedMainLines.includes(mainLineId)) {
  465. // this.completedMainLines.push(mainLineId)
  466. // }
  467. }
  468. // 保存到本地存储
  469. this.saveCompletedStages()
  470. // this.saveCompletedMainLines()
  471. // 重置状态
  472. this.currentStage = null
  473. this.currentStepIndex = 0
  474. }
  475. },
  476. // 更新位置
  477. updatePositions() {
  478. if (!this.currentStep) return
  479. const query = uni.createSelectorQuery().in(this)
  480. query.select(this.currentStep.target).boundingClientRect(data => {
  481. if (data) {
  482. // 更新当前步骤的位置信息
  483. this.$set(this.currentStage.steps[this.currentStepIndex], 'rect', data)
  484. }
  485. }).exec()
  486. },
  487. // 保存已完成阶段
  488. saveCompletedStages() {
  489. uni.setStorageSync('completedGuideStages', this.completedStages)
  490. },
  491. // 加载已完成阶段
  492. loadCompletedStages() {
  493. const stages = uni.getStorageSync('completedGuideStages')
  494. if (stages) {
  495. this.completedStages = stages
  496. }
  497. },
  498. // 处理遮罩层点击
  499. handleMaskClick() {
  500. // 可以在这里添加点击遮罩层的处理逻辑
  501. }
  502. },
  503. mounted() {
  504. // 加载已完成主线和当前主线ID
  505. // this.loadCompletedMainLines();
  506. this.loadCurrentMainLineId();
  507. //测试重置主线
  508. // this.currentMainLineId=0;
  509. }
  510. }
  511. </script>
  512. <style lang="scss" scoped>
  513. .guide-manager {
  514. .guide-mask {
  515. position: fixed;
  516. top: 0;
  517. left: 0;
  518. right: 0;
  519. bottom: 0;
  520. background-color: rgba(0, 0, 0, 0.7);
  521. z-index: 9999;
  522. .highlight-area {
  523. position: absolute;
  524. box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.7);
  525. border-radius: 4px;
  526. transition: all 0.3s ease;
  527. }
  528. .guide-tips {
  529. position: absolute;
  530. background: #fff;
  531. border-radius: 8px;
  532. padding: 16px;
  533. min-width: 200px;
  534. box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
  535. transition: all 0.3s ease;
  536. .tips-content {
  537. .tips-text {
  538. font-size: 14px;
  539. color: #333;
  540. line-height: 1.5;
  541. margin-bottom: 12px;
  542. }
  543. .tips-buttons {
  544. display: flex;
  545. justify-content: space-between;
  546. align-items: center;
  547. .skip-btn,
  548. .next-btn {
  549. font-size: 14px;
  550. padding: 4px 12px;
  551. border-radius: 4px;
  552. cursor: pointer;
  553. }
  554. .skip-btn {
  555. color: #999;
  556. }
  557. .next-btn {
  558. color: #fff;
  559. background-color: #1890ff;
  560. }
  561. }
  562. }
  563. .arrow {
  564. position: absolute;
  565. width: 0;
  566. height: 0;
  567. border: 8px solid transparent;
  568. border-bottom-color: #fff;
  569. }
  570. }
  571. }
  572. }
  573. </style>