1a2b.vue 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. <template>
  2. <view class="game-container">
  3. <view class="game-header">
  4. <text class="game-title">猜数字</text>
  5. <view class="game-stats">
  6. <text>剩余次数: {{ remainingGuesses }}</text>
  7. </view>
  8. </view>
  9. <view class="instructions" v-if="!isPlaying">
  10. <text class="info-text">电脑会生成一个4位不重复数字</text>
  11. <text class="info-text">A = 数字和位置都正确</text>
  12. <text class="info-text">B = 数字正确但位置错误</text>
  13. <text class="info-text">请在{{ maxGuesses }}次内猜出正确答案</text>
  14. </view>
  15. <view class="input-area" v-if="!isPlaying">
  16. <input
  17. class="guess-input"
  18. type="number"
  19. v-model="userGuess"
  20. maxlength="4"
  21. placeholder="输入4位不重复数字"
  22. />
  23. <button class="start-btn" @click="startGame">开始游戏</button>
  24. </view>
  25. <view class="history" v-if="history.length > 0">
  26. <text class="history-title">猜测历史:</text>
  27. <view class="history-item" v-for="(item, index) in history" :key="index">
  28. <text>{{ index + 1 }}. {{ item.guess }} - {{ item.result }}</text>
  29. </view>
  30. </view>
  31. <view class="input-area" v-if="isPlaying && !gameOver">
  32. <input
  33. class="guess-input"
  34. type="number"
  35. v-model="userGuess"
  36. maxlength="4"
  37. placeholder="输入猜测"
  38. />
  39. <button class="guess-btn" @click="makeGuess">猜测</button>
  40. </view>
  41. <view class="result-modal" v-if="gameOver">
  42. <view class="result-content">
  43. <text class="result-title">{{ isWin ? '猜对了!' : '游戏结束' }}</text>
  44. <text class="result-info">答案是: {{ answer }}</text>
  45. <text class="result-score">获得积分: {{ score }}</text>
  46. <button class="restart-btn" @click="resetGame">再玩一次</button>
  47. </view>
  48. </view>
  49. </view>
  50. </template>
  51. <script>
  52. import { completeMiniGame, getChildren } from '@/utils/api.js'
  53. export default {
  54. data() {
  55. return {
  56. answer: '',
  57. userGuess: '',
  58. isPlaying: false,
  59. gameOver: false,
  60. isWin: false,
  61. maxGuesses: 10,
  62. remainingGuesses: 10,
  63. history: [],
  64. score: 0,
  65. time: 0,
  66. timer: null,
  67. childId: null,
  68. taskId: null,
  69. pointsEarned: 0,
  70. newBalance: 0
  71. }
  72. },
  73. onLoad(options) {
  74. if (options.taskId) {
  75. this.taskId = parseInt(options.taskId)
  76. }
  77. if (options.childId) {
  78. this.childId = parseInt(options.childId)
  79. }
  80. this.loadChildId()
  81. },
  82. onUnload() {
  83. if (this.timer) clearInterval(this.timer)
  84. },
  85. methods: {
  86. async loadChildId() {
  87. try {
  88. if (!this.childId) {
  89. const res = await getChildren()
  90. if (res.data && res.data.length > 0) {
  91. this.childId = res.data[0].id
  92. }
  93. }
  94. } catch (e) {
  95. console.error('获取孩子ID失败', e)
  96. }
  97. },
  98. generateAnswer() {
  99. let digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
  100. let result = ''
  101. for (let i = 0; i < 4; i++) {
  102. const idx = Math.floor(Math.random() * digits.length)
  103. result += digits[idx]
  104. digits.splice(idx, 1)
  105. }
  106. return result
  107. },
  108. startGame() {
  109. if (this.userGuess.length !== 4 || !this.isUnique(this.userGuess)) {
  110. uni.showToast({ title: '请输入4位不重��数字', icon: 'none' })
  111. return
  112. }
  113. this.answer = this.generateAnswer()
  114. this.isPlaying = true
  115. this.gameOver = false
  116. this.remainingGuesses = this.maxGuesses
  117. this.history = []
  118. this.score = 0
  119. this.timer = setInterval(() => { this.time++ }, 1000)
  120. },
  121. isUnique(str) {
  122. return new Set(str).size === str.length
  123. },
  124. checkGuess(guess) {
  125. let a = 0, b = 0
  126. for (let i = 0; i < 4; i++) {
  127. if (guess[i] === this.answer[i]) {
  128. a++
  129. } else if (this.answer.includes(guess[i])) {
  130. b++
  131. }
  132. }
  133. return `${a}A${b}B`
  134. },
  135. makeGuess() {
  136. if (this.userGuess.length !== 4 || !this.isUnique(this.userGuess)) {
  137. uni.showToast({ title: '请输入4位不重复数字', icon: 'none' })
  138. return
  139. }
  140. const result = this.checkGuess(this.userGuess)
  141. this.history.push({ guess: this.userGuess, result })
  142. this.remainingGuesses--
  143. if (result === '4A0B') {
  144. this.score = Math.max(100 - (this.maxGuesses - this.remainingGuesses) * 8, 60)
  145. this.gameOver = true
  146. this.isWin = true
  147. clearInterval(this.timer)
  148. this.submitScore()
  149. } else if (this.remainingGuesses === 0) {
  150. this.gameOver = true
  151. this.isWin = false
  152. this.score = 30
  153. this.submitScore()
  154. }
  155. this.userGuess = ''
  156. },
  157. async submitScore() {
  158. if (!this.childId) return
  159. try {
  160. if (this.taskId) {
  161. const token = uni.getStorageSync('token')
  162. await uni.request({
  163. url: `http://localhost:8080/api/tasks/${this.taskId}/complete-minigame`,
  164. method: 'POST',
  165. header: {
  166. 'Authorization': token ? `Bearer ${token}` : '',
  167. 'Content-Type': 'application/json'
  168. },
  169. data: {
  170. childId: this.childId,
  171. completionTime: this.time,
  172. score: this.score
  173. }
  174. })
  175. }
  176. const res = await completeMiniGame(this.childId, '1a2b', this.time, this.score)
  177. if (res.code === 200) {
  178. uni.showToast({ title: `获得${res.data.pointsEarned}积分!`, icon: 'success' })
  179. }
  180. } catch (e) {
  181. console.error('提交成绩失败', e)
  182. }
  183. },
  184. resetGame() {
  185. this.isPlaying = false
  186. this.gameOver = false
  187. this.userGuess = ''
  188. this.history = []
  189. this.time = 0
  190. if (this.timer) clearInterval(this.timer)
  191. }
  192. }
  193. }
  194. </script>
  195. <style scoped>
  196. .game-container {
  197. min-height: 100vh;
  198. background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
  199. padding: 30rpx;
  200. }
  201. .game-header {
  202. text-align: center;
  203. margin-bottom: 40rpx;
  204. }
  205. .game-title {
  206. font-size: 48rpx;
  207. color: #fff;
  208. display: block;
  209. margin-bottom: 20rpx;
  210. }
  211. .game-stats {
  212. color: #888;
  213. font-size: 28rpx;
  214. }
  215. .instructions {
  216. background: rgba(255,255,255,0.1);
  217. padding: 30rpx;
  218. border-radius: 16rpx;
  219. margin-bottom: 30rpx;
  220. }
  221. .info-text {
  222. color: #aaa;
  223. font-size: 28rpx;
  224. display: block;
  225. margin-bottom: 10rpx;
  226. }
  227. .input-area {
  228. margin: 30rpx 0;
  229. }
  230. .guess-input {
  231. background: rgba(255,255,255,0.1);
  232. border: 1rpx solid rgba(255,255,255,0.2);
  233. border-radius: 12rpx;
  234. padding: 20rpx;
  235. color: #fff;
  236. font-size: 36rpx;
  237. text-align: center;
  238. margin-bottom: 20rpx;
  239. }
  240. .start-btn, .guess-btn {
  241. background: #4ECDC4;
  242. color: #1a1a2e;
  243. border-radius: 12rpx;
  244. padding: 20rpx;
  245. }
  246. .history {
  247. background: rgba(255,255,255,0.05);
  248. padding: 20rpx;
  249. border-radius: 12rpx;
  250. margin: 30rpx 0;
  251. }
  252. .history-title {
  253. color: #888;
  254. font-size: 28rpx;
  255. display: block;
  256. margin-bottom: 16rpx;
  257. }
  258. .history-item {
  259. color: #ccc;
  260. font-size: 28rpx;
  261. padding: 10rpx 0;
  262. border-bottom: 1rpx solid rgba(255,255,255,0.1);
  263. }
  264. .result-modal {
  265. position: fixed;
  266. top: 0;
  267. left: 0;
  268. right: 0;
  269. bottom: 0;
  270. background: rgba(0,0,0,0.8);
  271. display: flex;
  272. align-items: center;
  273. justify-content: center;
  274. }
  275. .result-content {
  276. background: #2a2a4a;
  277. padding: 60rpx;
  278. border-radius: 20rpx;
  279. text-align: center;
  280. }
  281. .result-title {
  282. font-size: 48rpx;
  283. color: #FFE66D;
  284. display: block;
  285. margin-bottom: 30rpx;
  286. }
  287. .result-info, .result-score {
  288. color: #fff;
  289. font-size: 32rpx;
  290. display: block;
  291. margin-bottom: 20rpx;
  292. }
  293. .restart-btn {
  294. background: #4ECDC4;
  295. color: #1a1a2e;
  296. padding: 20rpx 60rpx;
  297. margin-top: 30rpx;
  298. }
  299. </style>