cognitive-report.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011
  1. <template>
  2. <view class="container">
  3. <!-- ===== 报告卡片 ===== -->
  4. <view class="report-card" v-if="report">
  5. <view class="report-header">
  6. <text class="report-title">认知测评报告</text>
  7. <text class="report-date">测评日期: {{ report.assessmentDate || '-' }}</text>
  8. </view>
  9. <!-- 综合认知评分 -->
  10. <view class="overall-score-wrap">
  11. <text class="overall-label">综合认知评分</text>
  12. <view class="overall-row">
  13. <text class="overall-value">{{ report.overallScore || 0 }}</text>
  14. </view>
  15. </view>
  16. <!-- 常模参考线说明 -->
  17. <view class="norm-legend" v-if="norms">
  18. <view class="norm-legend-item"><view class="norm-dot norm-dot-p50"></view>同龄中位</view>
  19. <view class="norm-legend-item"><view class="norm-dot norm-dot-child"></view>当前得分</view>
  20. </view>
  21. <!-- 六维条形图 -->
  22. <view class="dimension-list">
  23. <view class="dimension-item" v-for="dim in dimensions" :key="dim.key">
  24. <view class="dimension-header">
  25. <view class="dimension-label-row">
  26. <text class="dimension-icon">{{ dim.icon }}</text>
  27. <text class="dimension-name">{{ dim.label }}</text>
  28. </view>
  29. <view class="dimension-score-wrap">
  30. <text class="dimension-score">{{ report[dim.key] || 0 }}</text>
  31. <text class="norm-level-tag" v-if="normLevel(dim.key)" :class="dim.normClass">{{ normLevel(dim.key) }}</text>
  32. </view>
  33. </view>
  34. <view class="dimension-bar">
  35. <view class="dimension-bar-fill" :style="{ width: (report[dim.key] || 0) + '%' }"></view>
  36. <view class="norm-marker" :style="{ left: normP50(dim.key) + '%' }">
  37. <view class="norm-marker-line"></view>
  38. </view>
  39. </view>
  40. <text class="dimension-desc">{{ dim.desc }}</text>
  41. </view>
  42. </view>
  43. <!-- 分析报告 -->
  44. <view class="report-analysis" v-if="report.analysisReport">
  45. <view class="analysis-header-row">
  46. <text class="analysis-icon">&#x1F4DD;</text>
  47. <text class="analysis-title">分析报告</text>
  48. </view>
  49. <text class="analysis-content">{{ report.analysisReport }}</text>
  50. </view>
  51. <!-- 成长建议 -->
  52. <view class="report-suggestions" v-if="report.growthSuggestions">
  53. <view class="analysis-header-row">
  54. <text class="analysis-icon">&#x1F331;</text>
  55. <text class="analysis-title">成长建议</text>
  56. </view>
  57. <text class="suggestion-content">{{ report.growthSuggestions }}</text>
  58. </view>
  59. <!-- 底部操作 -->
  60. <view class="action-row">
  61. <view class="action-btn secondary" @click="goAssessment">
  62. <text class="action-btn-text">预约测评</text>
  63. </view>
  64. <view class="action-btn primary" @click="goTraining">
  65. <text class="action-btn-text">认知训练</text>
  66. </view>
  67. </view>
  68. </view>
  69. <!-- ===== 家庭行动建议 ===== -->
  70. <view class="action-plan-section" v-if="recommendations && recommendations.length > 0">
  71. <view class="action-plan-header">
  72. <text class="action-plan-icon">&#x1F4CB;</text>
  73. <text class="action-plan-title">家庭行动建议</text>
  74. </view>
  75. <text class="action-plan-desc">基于本次测评结果,为你推荐以下训练方向</text>
  76. <!-- 综合评分 -->
  77. <view class="overall-suggestion" v-if="overall">
  78. <text class="overall-suggestion-text">{{ overall.suggestion }}</text>
  79. </view>
  80. <!-- 各维度推荐 -->
  81. <view class="rec-item" v-for="rec in recommendations" :key="rec.dimensionKey"
  82. :class="'rec-level-' + (rec.priority || 'normal')">
  83. <view class="rec-header">
  84. <view class="rec-dim-row">
  85. <text class="rec-icon">{{ rec.icon }}</text>
  86. <text class="rec-label">{{ rec.label }}</text>
  87. <text class="rec-score">{{ rec.score }}分</text>
  88. </view>
  89. <text class="rec-level-tag" :class="'tag-' + (rec.priority || 'normal')">{{ rec.level }}</text>
  90. </view>
  91. <text class="rec-suggestion">{{ rec.suggestion }}</text>
  92. <view class="rec-actions" v-if="rec.tasks && rec.tasks.length > 0">
  93. <view class="rec-tag-btn add-task-btn" @click="addTask(rec)">
  94. <text class="rec-tag-btn-icon">&#x2795;</text>
  95. <text class="rec-tag-btn-text">添加到今日任务</text>
  96. </view>
  97. <view class="rec-tag-btn game-btn" v-if="rec.games && rec.games.length > 0"
  98. @click="goGame(rec.games[0].code)">
  99. <text class="rec-tag-btn-text">去训练</text>
  100. </view>
  101. <view class="rec-tag-btn article-btn" v-if="rec.articles && rec.articles.length > 0"
  102. @click="goArticle(rec.articles[0].id)">
  103. <text class="rec-tag-btn-text">读文章</text>
  104. </view>
  105. </view>
  106. </view>
  107. </view>
  108. <!-- ===== 历史趋势 ===== -->
  109. <view class="trend-section" v-if="trendData && trendData.length > 1">
  110. <view class="trend-header">
  111. <text class="trend-icon">&#x1F4C8;</text>
  112. <text class="trend-title">历史趋势</text>
  113. </view>
  114. <view class="trend-overview-row">
  115. <view class="trend-stat" v-for="stat in trendStats" :key="stat.key">
  116. <text class="trend-stat-label">{{ stat.label }}</text>
  117. <text class="trend-stat-value" :class="'ts-' + stat.direction">{{ stat.display }}</text>
  118. <text class="trend-stat-sub" v-if="stat.pct != 0">{{ stat.pct > 0 ? '↑' : '↓' }}{{ Math.abs(stat.pct) }}%</text>
  119. </view>
  120. </view>
  121. <!-- 最近两次对比 -->
  122. <view class="trend-compare" v-if="trendData.length >= 2">
  123. <view class="tc-header">
  124. <text class="tc-title">前后两次对比</text>
  125. </view>
  126. <view class="tc-dim-row tc-header-row">
  127. <text class="tc-dim-name">维度</text>
  128. <text class="tc-old">上次</text>
  129. <text class="tc-new">本次</text>
  130. <text class="tc-change">变化</text>
  131. </view>
  132. <view class="tc-dim-row" v-for="dim in dimensions" :key="dim.key">
  133. <text class="tc-dim-name">{{ dim.label }}</text>
  134. <text class="tc-old">{{ prevScore(dim.key) }}</text>
  135. <text class="tc-new" :class="scoreUp(dim.key) ? 'sc-up' : (scoreDown(dim.key) ? 'sc-down' : '')">{{ latestScore(dim.key) }}</text>
  136. <text class="tc-change">
  137. <text v-if="scoreUp(dim.key)" class="change-up">&#x25B2; +{{ scoreDiff(dim.key) }}</text>
  138. <text v-else-if="scoreDown(dim.key)" class="change-down">&#x25BC; {{ scoreDiff(dim.key) }}</text>
  139. <text v-else class="change-flat">—</text>
  140. </text>
  141. </view>
  142. </view>
  143. </view>
  144. <!-- 加载中 -->
  145. <view class="loading-state" v-if="loading">
  146. <text class="loading-text">加载中...</text>
  147. </view>
  148. <!-- 无数据 -->
  149. <view class="empty-state" v-if="!report && !loading">
  150. <text class="empty-icon">&#x1F9E0;</text>
  151. <text class="empty-title">暂无认知测评数据</text>
  152. <text class="empty-desc">完成 DAN 认知评估后,可在此查看详细的六维认知分析报告和家庭训练建议</text>
  153. <view class="empty-btn" @click="goAssessment">预约测评</view>
  154. </view>
  155. </view>
  156. </template>
  157. <script>
  158. import { getAssessmentLatestResult, getCognitiveRecommendations, createTaskFromRecommendation, getCognitiveNorms, getCognitiveTrend } from '../../utils/api.js'
  159. export default {
  160. components: { },
  161. data: function() {
  162. return {
  163. memberId: null,
  164. report: null,
  165. recommendations: null,
  166. overall: null,
  167. norms: null,
  168. trendData: null,
  169. loading: true,
  170. addingTask: false,
  171. dimensions: [
  172. { key: 'perceptionScore', label: '感知能力', icon: '\u{1F441}', desc: '感官信息处理能力', normClass: '' },
  173. { key: 'focusScore', label: '专注力', icon: '\u{1F3AF}', desc: '持续集中和抗干扰能力', normClass: '' },
  174. { key: 'memoryScore', label: '记忆力', icon: '\u{1F9E0}', desc: '信息编码和回忆能力', normClass: '' },
  175. { key: 'logicScore', label: '逻辑思维', icon: '\u{1F9E9}', desc: '推理和问题解决能力', normClass: '' },
  176. { key: 'spatialScore', label: '空间思维', icon: '\u{1F9CA}', desc: '空间想象和方位判断', normClass: '' },
  177. { key: 'processingSpeedScore', label: '加工速度', icon: '\u26A1', desc: '信息处理和反应速度', normClass: '' }
  178. ]
  179. }
  180. },
  181. computed: {
  182. trendStats: function() {
  183. if (!this.trendData || this.trendData.length < 2) return []
  184. var latest = this.trendData[0]
  185. var prev = this.trendData[this.trendData.length - 1]
  186. var diff = (latest.overallScore || 0) - (prev.overallScore || 0)
  187. var pct = prev.overallScore ? Math.round(diff / prev.overallScore * 100) : 0
  188. return [
  189. { key: 'count', label: '测评次数', display: this.trendData.length + '次', direction: 'neutral', pct: 0 },
  190. { key: 'trend', label: '综合趋势', display: diff > 0 ? '上升' : (diff < 0 ? '下降' : '持平'), direction: diff > 0 ? 'up' : (diff < 0 ? 'down' : 'flat'), pct: pct },
  191. { key: 'latest', label: '最新综合', display: latest.overallScore || 0 + '分', direction: 'neutral', pct: 0 }
  192. ]
  193. }
  194. },
  195. onLoad: function(options) {
  196. if (options && options.memberId) {
  197. this.memberId = options.memberId
  198. } else {
  199. this.memberId = uni.getStorageSync('currentChildId') || null
  200. }
  201. this.loadData()
  202. },
  203. methods: {
  204. loadData: function() {
  205. var self = this
  206. var memberId = self.memberId
  207. if (!memberId) {
  208. self.loading = false
  209. return
  210. }
  211. self.loading = true
  212. // 并行加载报告和建议
  213. getAssessmentLatestResult(memberId).then(function(res) {
  214. if (res.code === 200 && res.data) {
  215. self.report = res.data
  216. }
  217. }).catch(function() {})
  218. getCognitiveRecommendations(memberId).then(function(res) {
  219. if (res.code === 200 && res.data) {
  220. self.recommendations = res.data.recommendations || []
  221. self.overall = res.data.overall || null
  222. }
  223. }).catch(function() {})
  224. getCognitiveNorms(memberId).then(function(res) {
  225. if (res.code === 200 && res.data) {
  226. self.norms = res.data
  227. }
  228. }).catch(function() {})
  229. getCognitiveTrend(memberId, 5).then(function(res) {
  230. if (res.code === 200 && res.data && res.data.length > 0) {
  231. self.trendData = res.data
  232. }
  233. }).catch(function() {})
  234. // 都完成后关闭加载
  235. setTimeout(function() {
  236. self.loading = false
  237. }, 800)
  238. },
  239. addTask: function(rec) {
  240. if (this.addingTask) return
  241. this.addingTask = true
  242. var self = this
  243. createTaskFromRecommendation(self.memberId, rec.dimensionKey, 0).then(function(res) {
  244. self.addingTask = false
  245. if (res.code === 200 && res.data) {
  246. uni.showToast({ title: '已添加到今日任务', icon: 'success' })
  247. } else {
  248. uni.showToast({ title: res.message || '添加失败', icon: 'none' })
  249. }
  250. }).catch(function() {
  251. self.addingTask = false
  252. uni.showToast({ title: '网络异常,请重试', icon: 'none' })
  253. })
  254. },
  255. goGame: function(code) {
  256. var url = ''
  257. if (code === 'schulte') {
  258. url = '/pages/games/schulte'
  259. } else if (code === '1a2b') {
  260. url = '/pages/games/1a2b'
  261. } else if (code === 'sudoku') {
  262. url = '/pages/games/sudoku'
  263. }
  264. if (url) {
  265. uni.navigateTo({ url: url })
  266. }
  267. },
  268. goArticle: function(articleId) {
  269. uni.navigateTo({ url: '/pages/article-center/article-detail?id=' + articleId })
  270. },
  271. goAssessment: function() {
  272. uni.navigateTo({ url: '/pages/assessment/apply' })
  273. },
  274. goTraining: function() {
  275. uni.navigateTo({ url: '/pages/wisdom-detail/training-hub?memberId=' + (this.memberId || '') })
  276. },
  277. normLevel: function(dimKey) {
  278. var dim = this.dimensions.find(function(d) { return d.key === dimKey })
  279. if (!this.norms || !dim) return ''
  280. var normKey = dimKey.replace('Score', '') + 'Norm'
  281. var info = this.norms[normKey]
  282. var level = info && info.level ? info.level : ''
  283. var map = {
  284. '较弱': 'nl-weak',
  285. '偏弱': 'nl-below-average',
  286. '中等': 'nl-average',
  287. '良好': 'nl-good',
  288. '优秀': 'nl-excellent'
  289. }
  290. dim.normClass = level ? (map[level] || '') : ''
  291. return level
  292. },
  293. normP50: function(dimKey) {
  294. if (!this.norms) return 50
  295. var normKey = dimKey.replace('Score', '') + 'Norm'
  296. var info = this.norms[normKey]
  297. return info && info.p50 ? info.p50 : 50
  298. },
  299. prevScore: function(dimKey) {
  300. return this.trendData && this.trendData.length > 1 ? this.trendData[1][dimKey] || 0 : 0
  301. },
  302. latestScore: function(dimKey) {
  303. return this.trendData && this.trendData.length > 0 ? this.trendData[0][dimKey] || 0 : 0
  304. },
  305. scoreDiff: function(dimKey) {
  306. return this.latestScore(dimKey) - this.prevScore(dimKey)
  307. },
  308. scoreUp: function(dimKey) {
  309. return this.scoreDiff(dimKey) > 0
  310. },
  311. scoreDown: function(dimKey) {
  312. return this.scoreDiff(dimKey) < 0
  313. }
  314. }
  315. }
  316. </script>
  317. <style scoped>
  318. .container {
  319. min-height: 100vh;
  320. background: #f5f7fa;
  321. padding-bottom: 120rpx;
  322. }
  323. /* ===== 报告卡 ===== */
  324. .report-card {
  325. margin: 20rpx 30rpx;
  326. background: #fff;
  327. border-radius: 24rpx;
  328. padding: 40rpx;
  329. box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
  330. }
  331. .report-header {
  332. display: flex;
  333. flex-direction: row;
  334. justify-content: space-between;
  335. align-items: center;
  336. margin-bottom: 30rpx;
  337. }
  338. .report-title {
  339. font-size: 36rpx;
  340. font-weight: bold;
  341. color: #333;
  342. }
  343. .report-date {
  344. font-size: 24rpx;
  345. color: #999;
  346. }
  347. /* ===== 综合评分 ===== */
  348. .overall-score-wrap {
  349. text-align: center;
  350. padding: 40rpx 0;
  351. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  352. border-radius: 20rpx;
  353. margin-bottom: 40rpx;
  354. color: #fff;
  355. }
  356. .overall-label {
  357. font-size: 28rpx;
  358. opacity: 0.9;
  359. display: block;
  360. margin-bottom: 16rpx;
  361. }
  362. .overall-row {
  363. display: flex;
  364. flex-direction: row;
  365. align-items: center;
  366. justify-content: center;
  367. }
  368. .overall-value {
  369. font-size: 80rpx;
  370. font-weight: bold;
  371. margin-right: 20rpx;
  372. }
  373. /* ===== 六维条形图 ===== */
  374. .dimension-list {
  375. margin-bottom: 30rpx;
  376. }
  377. .dimension-item {
  378. margin-bottom: 28rpx;
  379. }
  380. .dimension-header {
  381. display: flex;
  382. flex-direction: row;
  383. justify-content: space-between;
  384. align-items: center;
  385. margin-bottom: 10rpx;
  386. }
  387. .dimension-label-row {
  388. display: flex;
  389. flex-direction: row;
  390. align-items: center;
  391. }
  392. .dimension-icon {
  393. font-size: 28rpx;
  394. margin-right: 10rpx;
  395. }
  396. .dimension-name {
  397. font-size: 28rpx;
  398. color: #333;
  399. font-weight: 500;
  400. }
  401. .dimension-score {
  402. font-size: 32rpx;
  403. font-weight: bold;
  404. color: #667eea;
  405. }
  406. .dimension-desc {
  407. font-size: 22rpx;
  408. color: #999;
  409. line-height: 1.4;
  410. }
  411. /* ===== 常模参考线 ===== */
  412. .norm-legend {
  413. display: flex;
  414. flex-direction: row;
  415. align-items: center;
  416. gap: 24rpx;
  417. margin-bottom: 20rpx;
  418. padding: 12rpx 16rpx;
  419. background: #f8f9ff;
  420. border-radius: 12rpx;
  421. }
  422. .norm-legend-item {
  423. display: flex;
  424. flex-direction: row;
  425. align-items: center;
  426. font-size: 22rpx;
  427. color: #888;
  428. }
  429. .norm-dot {
  430. width: 16rpx;
  431. height: 16rpx;
  432. border-radius: 50%;
  433. margin-right: 8rpx;
  434. }
  435. .norm-dot-p50 {
  436. background: #FF9500;
  437. }
  438. .norm-dot-child {
  439. background: #667eea;
  440. }
  441. .dimension-score-wrap {
  442. display: flex;
  443. flex-direction: row;
  444. align-items: center;
  445. gap: 10rpx;
  446. }
  447. .norm-level-tag {
  448. font-size: 20rpx;
  449. padding: 2rpx 12rpx;
  450. border-radius: 16rpx;
  451. font-weight: 500;
  452. }
  453. .nl-weak, .nl-below-average {
  454. background: #FEF2F2;
  455. color: #EF4444;
  456. }
  457. .nl-average {
  458. background: #FFFBEB;
  459. color: #F59E0B;
  460. }
  461. .nl-good, .nl-excellent {
  462. background: #F0FDF4;
  463. color: #22C55E;
  464. }
  465. .dimension-bar {
  466. height: 16rpx;
  467. background: #f0f0f0;
  468. border-radius: 8rpx;
  469. margin-bottom: 8rpx;
  470. position: relative;
  471. }
  472. .dimension-bar-fill {
  473. height: 100%;
  474. background: linear-gradient(90deg, #667eea, #764ba2);
  475. border-radius: 8rpx;
  476. transition: width 0.5s;
  477. }
  478. .norm-marker {
  479. position: absolute;
  480. top: -4rpx;
  481. width: 2rpx;
  482. height: 24rpx;
  483. z-index: 2;
  484. transform: translateX(-50%);
  485. }
  486. .norm-marker-line {
  487. width: 4rpx;
  488. height: 24rpx;
  489. background: #FF9500;
  490. border-radius: 2rpx;
  491. }
  492. /* ===== 分析/建议 ===== */
  493. .report-analysis, .report-suggestions {
  494. margin-top: 30rpx;
  495. padding: 28rpx;
  496. border-radius: 16rpx;
  497. }
  498. .report-analysis {
  499. background: #f9f9ff;
  500. }
  501. .report-suggestions {
  502. background: #f0fdf4;
  503. }
  504. .analysis-header-row {
  505. display: flex;
  506. flex-direction: row;
  507. align-items: center;
  508. margin-bottom: 16rpx;
  509. }
  510. .analysis-icon {
  511. font-size: 30rpx;
  512. margin-right: 10rpx;
  513. }
  514. .analysis-title {
  515. font-size: 30rpx;
  516. font-weight: bold;
  517. color: #333;
  518. }
  519. .analysis-content, .suggestion-content {
  520. font-size: 26rpx;
  521. color: #666;
  522. line-height: 1.7;
  523. display: block;
  524. }
  525. /* ===== 底部操作 ===== */
  526. .action-row {
  527. display: flex;
  528. flex-direction: row;
  529. justify-content: space-between;
  530. margin-top: 40rpx;
  531. }
  532. .action-btn {
  533. flex: 1;
  534. display: flex;
  535. align-items: center;
  536. justify-content: center;
  537. padding: 20rpx 0;
  538. border-radius: 40rpx;
  539. }
  540. .action-btn.primary {
  541. background: linear-gradient(135deg, #667eea, #764ba2);
  542. margin-left: 16rpx;
  543. box-shadow: 0 4rpx 16rpx rgba(102,126,234,0.3);
  544. }
  545. .action-btn.secondary {
  546. background: #fff;
  547. border: 2rpx solid #ddd;
  548. margin-right: 16rpx;
  549. }
  550. .action-btn-text {
  551. font-size: 28rpx;
  552. font-weight: 500;
  553. color: #fff;
  554. }
  555. .action-btn.secondary .action-btn-text {
  556. color: #666;
  557. }
  558. /* ===== 家庭行动建议 ===== */
  559. .action-plan-section {
  560. margin: 30rpx 30rpx 40rpx;
  561. }
  562. .action-plan-header {
  563. display: flex;
  564. flex-direction: row;
  565. align-items: center;
  566. margin-bottom: 10rpx;
  567. }
  568. .action-plan-icon {
  569. font-size: 36rpx;
  570. margin-right: 10rpx;
  571. }
  572. .action-plan-title {
  573. font-size: 34rpx;
  574. font-weight: bold;
  575. color: #333;
  576. }
  577. .action-plan-desc {
  578. font-size: 26rpx;
  579. color: #888;
  580. margin-bottom: 20rpx;
  581. display: block;
  582. }
  583. .overall-suggestion {
  584. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  585. border-radius: 16rpx;
  586. padding: 24rpx 28rpx;
  587. margin-bottom: 24rpx;
  588. }
  589. .overall-suggestion-text {
  590. font-size: 26rpx;
  591. color: #fff;
  592. line-height: 1.6;
  593. display: block;
  594. }
  595. /* 各维度推荐卡片 */
  596. .rec-item {
  597. background: #fff;
  598. border-radius: 20rpx;
  599. padding: 28rpx;
  600. margin-bottom: 20rpx;
  601. box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.05);
  602. border-left: 8rpx solid #667eea;
  603. }
  604. .rec-item.rec-level-high {
  605. border-left-color: #EF4444;
  606. }
  607. .rec-item.rec-level-normal {
  608. border-left-color: #F59E0B;
  609. }
  610. .rec-item.rec-level-low {
  611. border-left-color: #22C55E;
  612. }
  613. .rec-header {
  614. display: flex;
  615. flex-direction: row;
  616. justify-content: space-between;
  617. align-items: center;
  618. margin-bottom: 12rpx;
  619. }
  620. .rec-dim-row {
  621. display: flex;
  622. flex-direction: row;
  623. align-items: center;
  624. }
  625. .rec-icon {
  626. font-size: 28rpx;
  627. margin-right: 8rpx;
  628. }
  629. .rec-label {
  630. font-size: 28rpx;
  631. font-weight: bold;
  632. color: #333;
  633. margin-right: 12rpx;
  634. }
  635. .rec-score {
  636. font-size: 24rpx;
  637. color: #888;
  638. }
  639. .rec-level-tag {
  640. font-size: 22rpx;
  641. padding: 4rpx 16rpx;
  642. border-radius: 20rpx;
  643. font-weight: 500;
  644. }
  645. .tag-high {
  646. background: #FEF2F2;
  647. color: #EF4444;
  648. }
  649. .tag-normal {
  650. background: #FFFBEB;
  651. color: #F59E0B;
  652. }
  653. .tag-low {
  654. background: #F0FDF4;
  655. color: #22C55E;
  656. }
  657. .rec-suggestion {
  658. font-size: 26rpx;
  659. color: #555;
  660. line-height: 1.6;
  661. display: block;
  662. margin-bottom: 16rpx;
  663. }
  664. .rec-actions {
  665. display: flex;
  666. flex-direction: row;
  667. flex-wrap: wrap;
  668. gap: 12rpx;
  669. }
  670. .rec-tag-btn {
  671. display: flex;
  672. flex-direction: row;
  673. align-items: center;
  674. padding: 12rpx 24rpx;
  675. border-radius: 32rpx;
  676. font-size: 24rpx;
  677. }
  678. .rec-tag-btn:active {
  679. opacity: 0.7;
  680. }
  681. .add-task-btn {
  682. background: #EEF2FF;
  683. color: #667eea;
  684. }
  685. .game-btn {
  686. background: #FEF3C7;
  687. color: #D97706;
  688. }
  689. .article-btn {
  690. background: #F0FDF4;
  691. color: #16A34A;
  692. }
  693. .rec-tag-btn-icon {
  694. font-size: 22rpx;
  695. margin-right: 6rpx;
  696. }
  697. .rec-tag-btn-text {
  698. font-size: 24rpx;
  699. font-weight: 500;
  700. }
  701. /* ===== 加载中 ===== */
  702. .loading-state {
  703. display: flex;
  704. align-items: center;
  705. justify-content: center;
  706. padding: 120rpx 0;
  707. }
  708. .loading-text {
  709. font-size: 28rpx;
  710. color: #999;
  711. }
  712. /* ===== 空状态 ===== */
  713. .empty-state {
  714. display: flex;
  715. flex-direction: column;
  716. align-items: center;
  717. padding: 120rpx 40rpx 80rpx;
  718. }
  719. .empty-icon {
  720. font-size: 100rpx;
  721. margin-bottom: 20rpx;
  722. }
  723. .empty-title {
  724. font-size: 32rpx;
  725. color: #333;
  726. font-weight: bold;
  727. margin-bottom: 16rpx;
  728. }
  729. .empty-desc {
  730. font-size: 26rpx;
  731. color: #999;
  732. text-align: center;
  733. line-height: 1.6;
  734. margin-bottom: 40rpx;
  735. }
  736. .empty-btn {
  737. background: linear-gradient(135deg, #667eea, #764ba2);
  738. color: #fff;
  739. font-size: 28rpx;
  740. font-weight: bold;
  741. padding: 16rpx 64rpx;
  742. border-radius: 40rpx;
  743. box-shadow: 0 4rpx 16rpx rgba(102,126,234,0.3);
  744. }
  745. /* ===== 历史趋势 ===== */
  746. .trend-section {
  747. margin: 30rpx 30rpx 40rpx;
  748. }
  749. .trend-header {
  750. display: flex;
  751. flex-direction: row;
  752. align-items: center;
  753. margin-bottom: 16rpx;
  754. }
  755. .trend-icon {
  756. font-size: 36rpx;
  757. margin-right: 10rpx;
  758. }
  759. .trend-title {
  760. font-size: 34rpx;
  761. font-weight: bold;
  762. color: #333;
  763. }
  764. .trend-overview-row {
  765. display: flex;
  766. flex-direction: row;
  767. gap: 16rpx;
  768. margin-bottom: 20rpx;
  769. }
  770. .trend-stat {
  771. flex: 1;
  772. background: #fff;
  773. border-radius: 16rpx;
  774. padding: 20rpx;
  775. text-align: center;
  776. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.05);
  777. }
  778. .trend-stat-label {
  779. font-size: 24rpx;
  780. color: #888;
  781. display: block;
  782. margin-bottom: 8rpx;
  783. }
  784. .trend-stat-value {
  785. font-size: 32rpx;
  786. font-weight: bold;
  787. display: block;
  788. }
  789. .ts-up {
  790. color: #22C55E;
  791. }
  792. .ts-down {
  793. color: #EF4444;
  794. }
  795. .ts-flat {
  796. color: #F59E0B;
  797. }
  798. .ts-neutral {
  799. color: #333;
  800. }
  801. .trend-stat-sub {
  802. font-size: 22rpx;
  803. margin-top: 4rpx;
  804. display: block;
  805. }
  806. .ts-up + .trend-stat-sub {
  807. color: #22C55E;
  808. }
  809. .ts-down + .trend-stat-sub {
  810. color: #EF4444;
  811. }
  812. .trend-compare {
  813. background: #fff;
  814. border-radius: 20rpx;
  815. padding: 24rpx;
  816. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.05);
  817. }
  818. .tc-header {
  819. margin-bottom: 16rpx;
  820. padding-bottom: 12rpx;
  821. border-bottom: 2rpx solid #f0f0f0;
  822. }
  823. .tc-title {
  824. font-size: 28rpx;
  825. font-weight: bold;
  826. color: #333;
  827. }
  828. .tc-dim-row {
  829. display: flex;
  830. flex-direction: row;
  831. align-items: center;
  832. padding: 12rpx 0;
  833. border-bottom: 1rpx solid #f5f5f5;
  834. }
  835. .tc-header-row {
  836. padding-bottom: 10rpx;
  837. border-bottom: 2rpx solid #e8e8e8;
  838. }
  839. .tc-dim-row:last-child {
  840. border-bottom: none;
  841. }
  842. .tc-dim-name {
  843. flex: 2;
  844. font-size: 26rpx;
  845. color: #333;
  846. }
  847. .tc-old {
  848. flex: 1;
  849. font-size: 24rpx;
  850. color: #999;
  851. text-align: center;
  852. }
  853. .tc-new {
  854. flex: 1;
  855. font-size: 26rpx;
  856. font-weight: bold;
  857. text-align: center;
  858. color: #333;
  859. }
  860. .sc-up {
  861. color: #22C55E;
  862. }
  863. .sc-down {
  864. color: #EF4444;
  865. }
  866. .tc-change {
  867. flex: 1.5;
  868. text-align: right;
  869. font-size: 24rpx;
  870. }
  871. .change-up {
  872. color: #22C55E;
  873. }
  874. .change-down {
  875. color: #EF4444;
  876. }
  877. .change-flat {
  878. color: #999;
  879. }
  880. </style>