1a2b.vue 7.7 KB

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