training.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. <template>
  2. <view class="container">
  3. <!-- 训练概览 -->
  4. <view class="stats-card" v-if="stats">
  5. <view class="stats-row">
  6. <view class="stats-item">
  7. <text class="stats-value">{{ stats.totalGames || 0 }}</text>
  8. <text class="stats-label">总训练次数</text>
  9. </view>
  10. <view class="stats-divider"></view>
  11. <view class="stats-item">
  12. <text class="stats-value">{{ stats.bestScore || 0 }}</text>
  13. <text class="stats-label">最高分</text>
  14. </view>
  15. <view class="stats-divider"></view>
  16. <view class="stats-item">
  17. <text class="stats-value">{{ stats.avgScore ? Math.round(stats.avgScore) : 0 }}</text>
  18. <text class="stats-label">平均分</text>
  19. </view>
  20. <view class="stats-divider"></view>
  21. <view class="stats-item">
  22. <text class="stats-value">{{ stats.totalPoints || 0 }}</text>
  23. <text class="stats-label">累计积分</text>
  24. </view>
  25. </view>
  26. </view>
  27. <view class="stats-card stats-loading" v-else-if="statsLoading">
  28. <text class="stats-loading-text">加载训练数据...</text>
  29. </view>
  30. <!-- 训练维度描述 -->
  31. <view class="dimension-card" v-if="currentDim">
  32. <view class="dimension-icon-wrap" :style="{ background: currentDim.lightColor }">
  33. <text class="dimension-icon">{{ currentDim.icon }}</text>
  34. </view>
  35. <text class="dimension-name">{{ currentDim.name }}</text>
  36. <text class="dimension-desc">{{ currentDim.desc }}</text>
  37. </view>
  38. <!-- 推荐游戏 -->
  39. <view class="section" v-if="currentDim && currentDim.games.length > 0">
  40. <view class="section-header">
  41. <text class="section-title">推荐训练</text>
  42. </view>
  43. <view class="game-list">
  44. <view class="game-card" v-for="game in currentDim.games" :key="game.code" @click="goGame(game)">
  45. <view class="game-icon-wrap" :style="{ background: currentDim.lightColor }">
  46. <text class="game-card-icon">{{ game.icon }}</text>
  47. </view>
  48. <view class="game-info">
  49. <text class="game-title">{{ game.name }}</text>
  50. <text class="game-desc">{{ game.desc }}</text>
  51. </view>
  52. <text class="game-arrow">&#x2192;</text>
  53. </view>
  54. </view>
  55. </view>
  56. <!-- 成长建议 -->
  57. <view class="section" v-if="currentDim">
  58. <view class="section-header">
  59. <text class="section-title">成长建议</text>
  60. </view>
  61. <view class="tip-card">
  62. <view class="tip-left-bar" :style="{ background: currentDim.color }"></view>
  63. <view class="tip-body">
  64. <text class="tip-content">{{ currentDim.tip }}</text>
  65. </view>
  66. </view>
  67. </view>
  68. <!-- 其他维度训练 -->
  69. <view class="section">
  70. <view class="section-header">
  71. <text class="section-title">其他维度</text>
  72. </view>
  73. <view class="other-grid">
  74. <view class="other-item" v-for="dim in otherDimensions" :key="dim.key" @click="switchDimension(dim.key)">
  75. <view class="other-icon-wrap" :style="{ background: dim.lightColor }">
  76. <text class="other-icon">{{ dim.icon }}</text>
  77. </view>
  78. <text class="other-name">{{ dim.name }}</text>
  79. </view>
  80. </view>
  81. </view>
  82. <!-- 底部占位 -->
  83. <view class="bottom-spacer"></view>
  84. </view>
  85. </template>
  86. <script>
  87. import { getGameStats } from '../../utils/api.js'
  88. var DIMENSION_MAP = {
  89. perception: {
  90. key: 'perception',
  91. name: '感知训练',
  92. icon: '\u{1F441}',
  93. color: '#8B5CF6',
  94. lightColor: '#EDE9FE',
  95. desc: '通过图形匹配、颜色分辨等练习提升感知能力',
  96. tip: '感知力是认知的起点。多观察身边事物的细节,锻炼视觉辨识能力,能有效提升感知水平。',
  97. games: [
  98. { code: 'schulte', name: '舒尔特方格', icon: '\u{1F3AF}', desc: '快速定位数字,训练视觉搜索' }
  99. ]
  100. },
  101. focus: {
  102. key: 'focus',
  103. name: '专注训练',
  104. icon: '\u{1F3AF}',
  105. color: '#F97316',
  106. lightColor: '#FFF7ED',
  107. desc: '通过舒尔特方格、数字追踪等练习提升专注力',
  108. tip: '专注力是一切学习的基础。每天坚持10分钟专注训练,持续21天会有显著提升。',
  109. games: [
  110. { code: 'schulte', name: '舒尔特方格', icon: '\u{1F3AF}', desc: '按序点击数字,训练持续专注' }
  111. ]
  112. },
  113. memory: {
  114. key: 'memory',
  115. name: '记忆训练',
  116. icon: '\u{1F9E0}',
  117. color: '#10B981',
  118. lightColor: '#ECFDF5',
  119. desc: '通过数字记忆、序列回忆等练习提升记忆力',
  120. tip: '记忆力可以通过科学训练显著提升。联想记忆法和间隔重复是最有效的两种方法。',
  121. games: [
  122. { code: '1a2b', name: '猜数字', icon: '\u{1F522}', desc: '记忆数字排列,训练工作记忆' }
  123. ]
  124. },
  125. logic: {
  126. key: 'logic',
  127. name: '逻辑训练',
  128. icon: '\u{1F9E9}',
  129. color: '#3B82F6',
  130. lightColor: '#EFF6FF',
  131. desc: '通过推理题、模式识别等练习提升逻辑思维',
  132. tip: '逻辑思维是解决复杂问题的关键。多做推理和归纳练习,能帮助建立清晰的思维框架。',
  133. games: [
  134. { code: 'sudoku', name: '数独', icon: '\u{1F9E9}', desc: '数字逻辑推理,训练演绎思维' },
  135. { code: '1a2b', name: '猜数字', icon: '\u{1F522}', desc: '逻辑排除推理,训练分析能力' }
  136. ]
  137. },
  138. spatial: {
  139. key: 'spatial',
  140. name: '空间训练',
  141. icon: '\u{1F9CA}',
  142. color: '#8D6E63',
  143. lightColor: '#EFEBE9',
  144. desc: '通过空间旋转、立体构建等练习提升空间思维能力',
  145. tip: '空间思维对数学和科学学习至关重要。拼图和积木是提升空间能力的最佳方式。',
  146. games: [
  147. { code: 'sudoku', name: '数独', icon: '\u{1F9E9}', desc: '九宫格空间布局,训练空间推理' }
  148. ]
  149. },
  150. processingSpeed: {
  151. key: 'processingSpeed',
  152. name: '加工速度训练',
  153. icon: '\u26A1',
  154. color: '#FFD700',
  155. lightColor: '#FFFBEB',
  156. desc: '通过快速反应、速度测试等练习提升加工速度',
  157. tip: '加工速度影响学习和反应效率。速算、速读等限时练习是提升加工速度的有效方法。',
  158. games: [
  159. { code: 'schulte', name: '舒尔特方格', icon: '\u{1F3AF}', desc: '限时完成,训练反应速度' },
  160. { code: '1a2b', name: '猜数字', icon: '\u{1F522}', desc: '快速推理,训练信息处理速度' }
  161. ]
  162. }
  163. }
  164. var GAME_PAGES = {
  165. schulte: '/pages/games/schulte',
  166. '1a2b': '/pages/games/1a2b',
  167. sudoku: '/pages/games/sudoku'
  168. }
  169. export default {
  170. components: { },
  171. data: function() {
  172. return {
  173. dimension: null,
  174. memberId: null,
  175. stats: null,
  176. statsLoading: false
  177. }
  178. },
  179. computed: {
  180. currentDim: function() {
  181. var dim = DIMENSION_MAP[this.dimension]
  182. return dim || null
  183. },
  184. currentQuote: function() {
  185. var dim = this.currentDim
  186. return dim ? dim.desc : '知己知彼,百战不殆'
  187. },
  188. otherDimensions: function() {
  189. var self = this
  190. var keys = Object.keys(DIMENSION_MAP)
  191. var result = []
  192. for (var i = 0; i < keys.length; i++) {
  193. if (keys[i] !== self.dimension) {
  194. result.push(DIMENSION_MAP[keys[i]])
  195. }
  196. }
  197. return result
  198. }
  199. },
  200. onLoad: function(options) {
  201. if (options && options.dimension) {
  202. this.dimension = options.dimension
  203. }
  204. if (options && options.memberId) {
  205. this.memberId = options.memberId
  206. } else {
  207. this.memberId = uni.getStorageSync('currentChildId') || null
  208. }
  209. uni.setNavigationBarTitle({
  210. title: this.currentDim ? this.currentDim.name : '能力训练'
  211. })
  212. this.loadStats()
  213. },
  214. methods: {
  215. loadStats: function() {
  216. var self = this
  217. if (!self.memberId) return
  218. self.statsLoading = true
  219. getGameStats({ memberId: self.memberId }).then(function(res) {
  220. if (res && res.data) {
  221. self.stats = res.data
  222. }
  223. }).catch(function() {
  224. // silent — stats not critical
  225. }).finally(function() {
  226. self.statsLoading = false
  227. })
  228. },
  229. goGame: function(game) {
  230. var url = GAME_PAGES[game.code]
  231. if (!url) {
  232. uni.showToast({ title: '游戏暂未开放', icon: 'none' })
  233. return
  234. }
  235. var memberId = this.memberId || ''
  236. url = url + '?memberId=' + memberId + '&dimension=' + (this.dimension || '')
  237. uni.navigateTo({ url: url })
  238. },
  239. switchDimension: function(key) {
  240. this.dimension = key
  241. uni.setNavigationBarTitle({
  242. title: DIMENSION_MAP[key].name
  243. })
  244. }
  245. }
  246. }
  247. </script>
  248. <style scoped>
  249. .container {
  250. min-height: 100vh;
  251. background: #f5f7fa;
  252. padding-bottom: 120rpx;
  253. }
  254. /* ===== 训练概览卡 ===== */
  255. .stats-card {
  256. margin: 30rpx;
  257. background: #fff;
  258. border-radius: 20rpx;
  259. padding: 36rpx 24rpx;
  260. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  261. }
  262. .stats-row {
  263. display: flex;
  264. flex-direction: row;
  265. align-items: center;
  266. justify-content: space-around;
  267. }
  268. .stats-item {
  269. display: flex;
  270. flex-direction: column;
  271. align-items: center;
  272. flex: 1;
  273. }
  274. .stats-value {
  275. font-size: 44rpx;
  276. font-weight: bold;
  277. color: #F97316;
  278. margin-bottom: 8rpx;
  279. }
  280. .stats-label {
  281. font-size: 22rpx;
  282. color: #999;
  283. }
  284. .stats-divider {
  285. width: 1rpx;
  286. height: 60rpx;
  287. background: #eee;
  288. flex-shrink: 0;
  289. }
  290. .stats-loading {
  291. display: flex;
  292. align-items: center;
  293. justify-content: center;
  294. padding: 24rpx;
  295. }
  296. .stats-loading-text {
  297. font-size: 26rpx;
  298. color: #bbb;
  299. }
  300. /* ===== 维度描述卡 ===== */
  301. .dimension-card {
  302. margin: 30rpx;
  303. background: #fff;
  304. border-radius: 20rpx;
  305. padding: 40rpx 32rpx;
  306. display: flex;
  307. flex-direction: column;
  308. align-items: center;
  309. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  310. }
  311. .dimension-icon-wrap {
  312. width: 120rpx;
  313. height: 120rpx;
  314. border-radius: 60rpx;
  315. display: flex;
  316. align-items: center;
  317. justify-content: center;
  318. margin-bottom: 20rpx;
  319. }
  320. .dimension-icon {
  321. font-size: 56rpx;
  322. }
  323. .dimension-name {
  324. font-size: 36rpx;
  325. font-weight: bold;
  326. color: #333;
  327. margin-bottom: 16rpx;
  328. }
  329. .dimension-desc {
  330. font-size: 26rpx;
  331. color: #888;
  332. text-align: center;
  333. line-height: 1.6;
  334. }
  335. /* ===== 通用区段 ===== */
  336. .section {
  337. margin: 20rpx 30rpx;
  338. }
  339. .section-header {
  340. margin-bottom: 20rpx;
  341. }
  342. .section-title {
  343. font-size: 30rpx;
  344. font-weight: bold;
  345. color: #333;
  346. }
  347. /* ===== 推荐游戏列表 ===== */
  348. .game-list {
  349. display: flex;
  350. flex-direction: column;
  351. }
  352. .game-card {
  353. display: flex;
  354. flex-direction: row;
  355. align-items: center;
  356. background: #fff;
  357. border-radius: 20rpx;
  358. padding: 24rpx;
  359. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  360. margin-bottom: 20rpx;
  361. }
  362. .game-card:active {
  363. opacity: 0.7;
  364. }
  365. .game-icon-wrap {
  366. width: 80rpx;
  367. height: 80rpx;
  368. border-radius: 40rpx;
  369. display: flex;
  370. align-items: center;
  371. justify-content: center;
  372. margin-right: 20rpx;
  373. flex-shrink: 0;
  374. }
  375. .game-card-icon {
  376. font-size: 40rpx;
  377. }
  378. .game-info {
  379. flex: 1;
  380. display: flex;
  381. flex-direction: column;
  382. }
  383. .game-title {
  384. font-size: 28rpx;
  385. font-weight: bold;
  386. color: #333;
  387. margin-bottom: 6rpx;
  388. }
  389. .game-desc {
  390. font-size: 22rpx;
  391. color: #999;
  392. }
  393. .game-arrow {
  394. font-size: 28rpx;
  395. color: #ccc;
  396. margin-left: 16rpx;
  397. flex-shrink: 0;
  398. }
  399. /* ===== 成长建议卡 ===== */
  400. .tip-card {
  401. display: flex;
  402. flex-direction: row;
  403. background: #fff;
  404. border-radius: 20rpx;
  405. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  406. overflow: hidden;
  407. }
  408. .tip-left-bar {
  409. width: 6rpx;
  410. flex-shrink: 0;
  411. }
  412. .tip-body {
  413. flex: 1;
  414. padding: 28rpx 24rpx;
  415. }
  416. .tip-content {
  417. font-size: 26rpx;
  418. color: #666;
  419. line-height: 1.7;
  420. }
  421. /* ===== 其他维度网格 ===== */
  422. .other-grid {
  423. display: flex;
  424. flex-direction: row;
  425. flex-wrap: wrap;
  426. margin: 0 -10rpx;
  427. }
  428. .other-item {
  429. width: calc(33.33% - 20rpx);
  430. display: flex;
  431. flex-direction: column;
  432. align-items: center;
  433. background: #fff;
  434. border-radius: 16rpx;
  435. padding: 24rpx 8rpx;
  436. margin: 0 10rpx 20rpx;
  437. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.05);
  438. }
  439. .other-item:active {
  440. opacity: 0.7;
  441. }
  442. .other-icon-wrap {
  443. width: 64rpx;
  444. height: 64rpx;
  445. border-radius: 32rpx;
  446. display: flex;
  447. align-items: center;
  448. justify-content: center;
  449. margin-bottom: 10rpx;
  450. }
  451. .other-icon {
  452. font-size: 32rpx;
  453. }
  454. .other-name {
  455. font-size: 22rpx;
  456. color: #666;
  457. font-weight: 500;
  458. }
  459. /* ===== 底部 ===== */
  460. .bottom-spacer {
  461. height: 120rpx;
  462. }
  463. </style>