self-quiz.vue 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921
  1. <template>
  2. <view class="container">
  3. <view class="resume-prompt" v-if="showResume">
  4. <text class="resume-text">检测到上次未完成的测评</text>
  5. <view class="resume-btns">
  6. <view class="btn btn--outline" @tap="resumeSession">继续答题</view>
  7. <view class="btn btn--ghost" @tap="clearSession">重新开始</view>
  8. </view>
  9. </view>
  10. <view class="step-wrap" v-if="!showResume">
  11. <view class="step-row">
  12. <view class="step-item" v-for="i in 4" :key="i" @tap="goStep(i)">
  13. <view class="step-dot" :class="{
  14. 'step-dot--active': step === i,
  15. 'step-dot--done': step > i,
  16. 'step-dot--pending': step < i
  17. }">
  18. <text v-if="step > i" class="step-check">&#10003;</text>
  19. <text v-else class="step-num">{{ i }}</text>
  20. </view>
  21. <text class="step-label" :class="{
  22. 'step-label--active': step >= i,
  23. 'step-label--muted': step < i
  24. }">{{ stepLabels[i - 1] }}</text>
  25. </view>
  26. </view>
  27. </view>
  28. <view class="step-content" v-if="step === 1 && !showResume">
  29. <view class="content-header">
  30. <text class="content-title">选择测评目标</text>
  31. <text class="content-sub">选择你想提升的能力维度</text>
  32. </view>
  33. <view class="goal-grid">
  34. <view class="goal-card" v-for="g in goals" :key="g.id" :class="{ 'goal-card--selected': selectedGoal && selectedGoal.id === g.id }" @tap="selectGoal(g)">
  35. <view class="goal-icon-wrap">
  36. <text class="goal-icon">{{ g.icon }}</text>
  37. </view>
  38. <text class="goal-title">{{ g.title }}</text>
  39. <text class="goal-desc">{{ g.desc }}</text>
  40. </view>
  41. </view>
  42. <view class="action-bar">
  43. <view class="btn btn--primary" :class="{ 'btn--disabled': !selectedGoal }" @tap="goStep2">下一步</view>
  44. </view>
  45. </view>
  46. <view class="step-content" v-if="step === 2 && !showResume">
  47. <view class="content-header">
  48. <text class="content-title">选择题目类型</text>
  49. <text class="content-sub">共5道题,选择适合的答题方式</text>
  50. </view>
  51. <view class="type-list">
  52. <view class="type-card" v-for="t in questionTypes" :key="t.id" :class="{ 'type-card--selected': questionType === t.id }" @tap="selectType(t.id)">
  53. <view class="type-icon-wrap">
  54. <text class="type-icon">{{ t.icon }}</text>
  55. </view>
  56. <view class="type-info">
  57. <text class="type-name">{{ t.name }}</text>
  58. <text class="type-desc">{{ t.desc }}</text>
  59. </view>
  60. <view class="type-radio" :class="{ 'type-radio--checked': questionType === t.id }">
  61. <view v-if="questionType === t.id" class="type-radio-dot"></view>
  62. </view>
  63. </view>
  64. </view>
  65. <view class="action-bar action-bar--double">
  66. <view class="btn btn--outline" @tap="goStep1">上一步</view>
  67. <view class="btn btn--primary" :class="{ 'btn--disabled': !questionType }" @tap="startQuiz">开始答题</view>
  68. </view>
  69. </view>
  70. <view class="step-content" v-if="step === 3 && !showResume">
  71. <view class="progress-header">
  72. <text class="progress-label">第 {{ currentQ + 1 }}/{{ questions.length }} 题</text>
  73. <view class="progress-bar">
  74. <view class="progress-fill" :style="'width:' + progressPct + '%'"></view>
  75. </view>
  76. </view>
  77. <view class="question-card" v-if="currentQuestion">
  78. <view class="q-tag">{{ tagLabel }}</view>
  79. <text class="q-text">{{ currentQuestion.question }}</text>
  80. <view class="q-options" v-if="currentQuestion.isTF">
  81. <view class="q-option q-option--tf" :class="{ 'q-option--selected': currentAnswer === '正确' }" @tap="currentAnswer = '正确'">
  82. <text class="q-option-tf-text">正确</text>
  83. </view>
  84. <view class="q-option q-option--tf" :class="{ 'q-option--selected': currentAnswer === '错误' }" @tap="currentAnswer = '错误'">
  85. <text class="q-option-tf-text">错误</text>
  86. </view>
  87. </view>
  88. <view class="q-options" v-else>
  89. <view class="q-option" v-for="(opt, oi) in currentQuestion.options" :key="oi" :class="{ 'q-option--selected': currentAnswer === opt }" @tap="currentAnswer = opt">
  90. <view class="q-radio" :class="{ 'q-radio--checked': currentAnswer === opt }">
  91. <view v-if="currentAnswer === opt" class="q-radio-dot"></view>
  92. </view>
  93. <text class="q-option-text">{{ opt }}</text>
  94. </view>
  95. </view>
  96. </view>
  97. <view class="action-bar action-bar--double">
  98. <view class="btn btn--outline" :class="{ 'btn--disabled': currentQ === 0 }" @tap="prevQuestion">上一题</view>
  99. <view class="btn btn--primary" :class="{ 'btn--disabled': !currentAnswer }" @tap="nextQuestion">{{ currentQ < questions.length - 1 ? '下一题' : '查看结果' }}</view>
  100. </view>
  101. </view>
  102. <view class="step-content" v-if="step === 4 && !showResume">
  103. <view class="result-header">
  104. <view class="score-circle">
  105. <text class="score-value">{{ score }}/5</text>
  106. </view>
  107. <text class="score-label">{{ resultMessage }}</text>
  108. </view>
  109. <view class="section-title">维度分析</view>
  110. <view class="result-dims">
  111. <view class="dim-bar-item" v-for="(dim, di) in dimBreakdown" :key="di">
  112. <view class="dim-bar-header">
  113. <text class="dim-bar-name">{{ dim.name }}</text>
  114. <text class="dim-bar-count">{{ dim.correct }}/{{ dim.total }}</text>
  115. </view>
  116. <view class="dim-bar-track">
  117. <view class="dim-bar-fill" :style="'width:' + dim.pct + '%'"></view>
  118. </view>
  119. </view>
  120. </view>
  121. <view class="action-bar">
  122. <view class="btn btn--primary" @tap="resetQuiz">再测一次</view>
  123. <view class="btn btn--outline" @tap="goHome">返回首页</view>
  124. </view>
  125. </view>
  126. <view class="bottom-spacer"></view>
  127. </view>
  128. </template>
  129. <script>
  130. var QUESTION_BANK = [
  131. { dimension: 'focus', question: '以下哪个因素最容易影响注意力集中?', options: ['噪音', '温度', '光照', '以上都是'], answer: '以上都是' },
  132. { dimension: 'focus', question: '持续专注工作建议多久休息一次?', options: ['15分钟', '30分钟', '45分钟', '60分钟'], answer: '45分钟' },
  133. { dimension: 'focus', question: '舒尔特方格的训练目标是提升什么?', options: ['记忆力', '专注力', '逻辑思维', '创造力'], answer: '专注力' },
  134. { dimension: 'memory', question: '短期记忆通常能记住几个信息组块?', options: ['3-4个', '5-9个', '10-15个', '20个以上'], answer: '5-9个' },
  135. { dimension: 'memory', question: '以下哪个方法有助于增强记忆?', options: ['死记硬背', '联想记忆', '反复抄写', '一次性学习'], answer: '联想记忆' },
  136. { dimension: 'memory', question: '睡眠对记忆的影响是什么?', options: ['削弱记忆', '巩固记忆', '无影响', '干扰记忆'], answer: '巩固记忆' },
  137. { dimension: 'logic', question: '如果A>B且B>C,那么?', options: ['A=C', 'A>C', 'A<C', '不确定'], answer: 'A>C' },
  138. { dimension: 'logic', question: '1, 1, 2, 3, 5, 8, ? 下一个数是多少?', options: ['10', '11', '12', '13'], answer: '13' },
  139. { dimension: 'logic', question: '以下哪个不是有效推理方式?', options: ['归纳推理', '演绎推理', '循环论证', '类比推理'], answer: '循环论证' },
  140. { dimension: 'perception', question: '人眼能分辨约多少种颜色?', options: ['几百种', '几千种', '几万种', '数百万种'], answer: '数百万种' },
  141. { dimension: 'perception', question: '以下哪种错觉与视觉感知有关?', options: ['缪勒-莱尔错觉', '德布罗意错觉', '薛定谔错觉', '迈克尔逊错觉'], answer: '缪勒-莱尔错觉' },
  142. { dimension: 'perception', question: '感觉适应是指什么?', options: ['敏感度降低', '敏感度升高', '感知消失', '感知增强'], answer: '敏感度降低' },
  143. { dimension: 'spatial', question: '正方体有多少个面?', options: ['4', '6', '8', '12'], answer: '6' },
  144. { dimension: 'spatial', question: '以下哪个是立体几何的基本要素?', options: ['点线面', '正反合', '红黄蓝', '上下左右'], answer: '点线面' },
  145. { dimension: 'spatial', question: '一个立方体展开图有几个正方形?', options: ['4', '6', '8', '12'], answer: '6' },
  146. { dimension: 'speed', question: '反应时实验测量的是什么?', options: ['思考速度', '反应速度', '运动速度', '阅读速度'], answer: '反应速度' },
  147. { dimension: 'speed', question: '以下哪个因素会影响加工速度?', options: ['年龄', '情绪', '疲劳', '以上都是'], answer: '以上都是' },
  148. { dimension: 'speed', question: '信息加工速度最快的年龄段是?', options: ['儿童期', '青春期', '成年早期', '中老年期'], answer: '成年早期' }
  149. ]
  150. export default {
  151. data() {
  152. return {
  153. step: 1,
  154. selectedGoal: null,
  155. questionType: null,
  156. questions: [],
  157. currentQ: 0,
  158. answers: [],
  159. showResume: false,
  160. savedSession: null,
  161. goals: [
  162. { id: 'focus', title: '专注力', desc: '提升注意力集中能力', icon: '集' },
  163. { id: 'memory', title: '记忆力', desc: '增强信息记忆与回忆能力', icon: '忆' },
  164. { id: 'logic', title: '逻辑思维', desc: '锻炼逻辑推理与判断能力', icon: '逻' },
  165. { id: 'perception', title: '感知觉', desc: '提高感官信息处理能力', icon: '感' },
  166. { id: 'spatial', title: '空间思维', desc: '培养空间想象与构建能力', icon: '间' },
  167. { id: 'speed', title: '加工速度', desc: '加快信息处理与反应速度', icon: '速' }
  168. ],
  169. questionTypes: [
  170. { id: 'choice', name: '选择题', desc: '四选一标准选择题', icon: '选' },
  171. { id: 'truefalse', name: '判断题', desc: '判断陈述正误', icon: '判' },
  172. { id: 'mix', name: '混合题型', desc: '选择题与判断题混合', icon: '混' }
  173. ],
  174. stepLabels: ['目标', '题型', '答题', '结果']
  175. }
  176. },
  177. computed: {
  178. currentQuestion: function() {
  179. if (this.questions.length === 0) {
  180. return null
  181. }
  182. return this.questions[this.currentQ]
  183. },
  184. currentAnswer: {
  185. get: function() {
  186. return this.answers[this.currentQ] || null
  187. },
  188. set: function(val) {
  189. this.$set(this.answers, this.currentQ, val)
  190. }
  191. },
  192. progressPct: function() {
  193. if (this.questions.length === 0) {
  194. return 0
  195. }
  196. return ((this.currentQ + 1) / this.questions.length) * 100
  197. },
  198. score: function() {
  199. var correct = 0
  200. for (var i = 0; i < this.questions.length; i++) {
  201. var q = this.questions[i]
  202. var a = this.answers[i]
  203. if (q && a && q.answer === a) {
  204. correct++
  205. }
  206. }
  207. return correct
  208. },
  209. resultMessage: function() {
  210. var s = this.score
  211. if (s >= 5) return '太棒了!满分通过!'
  212. if (s >= 4) return '非常优秀!继续保持!'
  213. if (s >= 3) return '表现不错,还有提升空间!'
  214. return '继续加油,多练习会更好!'
  215. },
  216. tagLabel: function() {
  217. if (!this.currentQuestion) return ''
  218. var dimMap = {
  219. focus: '专注力',
  220. memory: '记忆力',
  221. logic: '逻辑思维',
  222. perception: '感知觉',
  223. spatial: '空间思维',
  224. speed: '加工速度'
  225. }
  226. return dimMap[this.currentQuestion.dimension] || ''
  227. },
  228. dimBreakdown: function() {
  229. var dimNames = {
  230. focus: '专注力',
  231. memory: '记忆力',
  232. logic: '逻辑思维',
  233. perception: '感知觉',
  234. spatial: '空间思维',
  235. speed: '加工速度'
  236. }
  237. var dims = {}
  238. for (var i = 0; i < this.questions.length; i++) {
  239. var q = this.questions[i]
  240. if (!q) continue
  241. if (!dims[q.dimension]) {
  242. dims[q.dimension] = { total: 0, correct: 0 }
  243. }
  244. dims[q.dimension].total++
  245. if (this.answers[i] && q.answer === this.answers[i]) {
  246. dims[q.dimension].correct++
  247. }
  248. }
  249. var result = []
  250. var keys = Object.keys(dims)
  251. for (var k = 0; k < keys.length; k++) {
  252. var key = keys[k]
  253. var item = dims[key]
  254. result.push({
  255. name: dimNames[key] || key,
  256. total: item.total,
  257. correct: item.correct,
  258. pct: item.total > 0 ? Math.round((item.correct / item.total) * 100) : 0
  259. })
  260. }
  261. return result
  262. }
  263. },
  264. onLoad: function() {
  265. this.checkSession()
  266. },
  267. onUnload: function() {
  268. this.saveSession()
  269. },
  270. methods: {
  271. checkSession: function() {
  272. var saved = uni.getStorageSync('selfQuizSession')
  273. if (saved) {
  274. try {
  275. var session = JSON.parse(saved)
  276. if (session && session.step && session.step >= 2 && session.step <= 3) {
  277. this.showResume = true
  278. this.savedSession = session
  279. } else {
  280. uni.removeStorageSync('selfQuizSession')
  281. }
  282. } catch (e) {
  283. uni.removeStorageSync('selfQuizSession')
  284. }
  285. }
  286. },
  287. saveSession: function() {
  288. var session = {
  289. step: this.step,
  290. selectedGoal: this.selectedGoal,
  291. questionType: this.questionType,
  292. questions: this.questions,
  293. currentQ: this.currentQ,
  294. answers: this.answers
  295. }
  296. uni.setStorageSync('selfQuizSession', JSON.stringify(session))
  297. },
  298. resumeSession: function() {
  299. if (!this.savedSession) return
  300. var s = this.savedSession
  301. this.step = s.step
  302. this.selectedGoal = s.selectedGoal
  303. this.questionType = s.questionType
  304. this.questions = s.questions || []
  305. this.currentQ = s.currentQ || 0
  306. this.answers = s.answers || []
  307. this.showResume = false
  308. this.savedSession = null
  309. },
  310. clearSession: function() {
  311. uni.removeStorageSync('selfQuizSession')
  312. this.showResume = false
  313. this.savedSession = null
  314. },
  315. selectGoal: function(g) {
  316. this.selectedGoal = g
  317. },
  318. goStep2: function() {
  319. if (this.selectedGoal) {
  320. this.step = 2
  321. this.saveSession()
  322. }
  323. },
  324. goStep1: function() {
  325. this.step = 1
  326. this.saveSession()
  327. },
  328. selectType: function(id) {
  329. this.questionType = id
  330. },
  331. startQuiz: function() {
  332. if (!this.questionType) return
  333. this.generateQuestions()
  334. this.currentQ = 0
  335. this.answers = []
  336. this.step = 3
  337. this.saveSession()
  338. },
  339. shuffle: function(arr) {
  340. var a = arr.slice()
  341. for (var i = a.length - 1; i > 0; i--) {
  342. var j = Math.floor(Math.random() * (i + 1))
  343. var tmp = a[i]
  344. a[i] = a[j]
  345. a[j] = tmp
  346. }
  347. return a
  348. },
  349. convertToTF: function(q) {
  350. var opts = q.options
  351. var idx = opts.indexOf(q.answer)
  352. var wrongOpts = []
  353. for (var w = 0; w < opts.length; w++) {
  354. if (opts[w] !== q.answer) {
  355. wrongOpts.push(opts[w])
  356. }
  357. }
  358. var isTrue = Math.random() > 0.4
  359. var claimIdx
  360. if (isTrue) {
  361. claimIdx = idx
  362. } else {
  363. var randomWrong = wrongOpts[Math.floor(Math.random() * wrongOpts.length)]
  364. claimIdx = opts.indexOf(randomWrong)
  365. }
  366. return {
  367. dimension: q.dimension,
  368. question: q.question + ' 正确答案是"' + opts[claimIdx] + '"',
  369. options: ['正确', '错误'],
  370. answer: isTrue ? '正确' : '错误',
  371. isTF: true
  372. }
  373. },
  374. generateQuestions: function() {
  375. var goalId = this.selectedGoal && this.selectedGoal.id
  376. if (!goalId) return
  377. var pool = []
  378. for (var p = 0; p < QUESTION_BANK.length; p++) {
  379. if (QUESTION_BANK[p].dimension === goalId) {
  380. pool.push(QUESTION_BANK[p])
  381. }
  382. }
  383. var shuffled = this.shuffle(pool)
  384. var type = this.questionType
  385. if (type === 'truefalse') {
  386. var tfQuestions = []
  387. var count = Math.min(5, shuffled.length)
  388. for (var ti = 0; ti < count; ti++) {
  389. tfQuestions.push(this.convertToTF(shuffled[ti]))
  390. }
  391. this.questions = tfQuestions
  392. } else if (type === 'mix') {
  393. var allDims = ['focus', 'memory', 'logic', 'perception', 'spatial', 'speed']
  394. var otherDims = []
  395. for (var od = 0; od < allDims.length; od++) {
  396. if (allDims[od] !== goalId) {
  397. otherDims.push(allDims[od])
  398. }
  399. }
  400. otherDims = this.shuffle(otherDims)
  401. var selectedDims = [goalId].concat(otherDims.slice(0, 4))
  402. var mixedQuestions = []
  403. for (var di = 0; di < selectedDims.length; di++) {
  404. var dimPool = []
  405. for (var dq = 0; dq < QUESTION_BANK.length; dq++) {
  406. if (QUESTION_BANK[dq].dimension === selectedDims[di]) {
  407. dimPool.push(QUESTION_BANK[dq])
  408. }
  409. }
  410. if (dimPool.length > 0) {
  411. var pick = this.shuffle(dimPool)
  412. mixedQuestions.push(pick[0])
  413. }
  414. }
  415. this.questions = this.shuffle(mixedQuestions).slice(0, 5)
  416. } else {
  417. this.questions = shuffled.slice(0, 5)
  418. }
  419. },
  420. nextQuestion: function() {
  421. if (!this.currentAnswer) return
  422. if (this.currentQ < this.questions.length - 1) {
  423. this.currentQ++
  424. this.saveSession()
  425. } else {
  426. this.step = 4
  427. this.saveSession()
  428. }
  429. },
  430. prevQuestion: function() {
  431. if (this.currentQ > 0) {
  432. this.currentQ--
  433. }
  434. },
  435. resetQuiz: function() {
  436. uni.removeStorageSync('selfQuizSession')
  437. this.step = 1
  438. this.selectedGoal = null
  439. this.questionType = null
  440. this.questions = []
  441. this.currentQ = 0
  442. this.answers = []
  443. },
  444. goHome: function() {
  445. uni.navigateTo({ url: '/pages/wisdom-detail/index' })
  446. },
  447. goStep: function(n) {
  448. if (n === this.step) return
  449. if (n < this.step) {
  450. this.step = n
  451. this.saveSession()
  452. }
  453. }
  454. }
  455. }
  456. </script>
  457. <style scoped>
  458. .container {
  459. min-height: 100vh;
  460. background: #f5f7fa;
  461. padding: 30rpx;
  462. padding-bottom: 120rpx;
  463. box-sizing: border-box;
  464. }
  465. .resume-prompt {
  466. background: #fff;
  467. border-radius: 20rpx;
  468. padding: 50rpx 30rpx;
  469. text-align: center;
  470. margin-top: 160rpx;
  471. box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
  472. }
  473. .resume-text {
  474. font-size: 28rpx;
  475. color: #333;
  476. display: block;
  477. margin-bottom: 30rpx;
  478. }
  479. .resume-btns {
  480. display: flex;
  481. justify-content: center;
  482. gap: 20rpx;
  483. }
  484. .step-wrap {
  485. margin-bottom: 10rpx;
  486. }
  487. .step-row {
  488. display: flex;
  489. justify-content: center;
  490. align-items: flex-start;
  491. padding: 20rpx 0;
  492. }
  493. .step-item {
  494. display: flex;
  495. flex-direction: column;
  496. align-items: center;
  497. margin: 0 24rpx;
  498. }
  499. .step-dot {
  500. width: 48rpx;
  501. height: 48rpx;
  502. border-radius: 50%;
  503. display: flex;
  504. align-items: center;
  505. justify-content: center;
  506. border: 3rpx solid #ddd;
  507. background: #fff;
  508. transition: all 0.3s;
  509. }
  510. .step-dot--active {
  511. background: #FFD700;
  512. border-color: #FFD700;
  513. }
  514. .step-dot--done {
  515. border-color: #FFD700;
  516. background: #FFD700;
  517. }
  518. .step-dot--pending {
  519. border-color: #ddd;
  520. background: #fff;
  521. }
  522. .step-check {
  523. font-size: 22rpx;
  524. color: #fff;
  525. font-weight: bold;
  526. }
  527. .step-num {
  528. font-size: 22rpx;
  529. color: #999;
  530. }
  531. .step-dot--active .step-num {
  532. color: #fff;
  533. }
  534. .step-label {
  535. font-size: 20rpx;
  536. margin-top: 8rpx;
  537. color: #999;
  538. }
  539. .step-label--active {
  540. color: #FFD700;
  541. font-weight: bold;
  542. }
  543. .step-label--muted {
  544. color: #ccc;
  545. }
  546. .step-content {
  547. animation: fadeIn 0.3s ease;
  548. }
  549. @keyframes fadeIn {
  550. from { opacity: 0; transform: translateY(20rpx); }
  551. to { opacity: 1; transform: translateY(0); }
  552. }
  553. .content-header {
  554. text-align: center;
  555. margin-bottom: 30rpx;
  556. }
  557. .content-title {
  558. font-size: 36rpx;
  559. font-weight: bold;
  560. color: #333;
  561. display: block;
  562. }
  563. .content-sub {
  564. font-size: 24rpx;
  565. color: #999;
  566. margin-top: 10rpx;
  567. display: block;
  568. }
  569. .section-title {
  570. font-size: 28rpx;
  571. font-weight: bold;
  572. color: #333;
  573. margin-bottom: 20rpx;
  574. }
  575. .goal-grid {
  576. display: flex;
  577. flex-wrap: wrap;
  578. }
  579. .goal-card {
  580. width: calc(50% - 12rpx);
  581. margin: 6rpx;
  582. background: #fff;
  583. border-radius: 20rpx;
  584. padding: 30rpx 16rpx;
  585. box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
  586. display: flex;
  587. flex-direction: column;
  588. align-items: center;
  589. border: 3rpx solid transparent;
  590. transition: all 0.3s;
  591. box-sizing: border-box;
  592. }
  593. .goal-card--selected {
  594. border-color: #FFD700;
  595. background: #FFF8E1;
  596. }
  597. .goal-icon-wrap {
  598. width: 80rpx;
  599. height: 80rpx;
  600. border-radius: 50%;
  601. background: linear-gradient(135deg, #FFD700, #FFA500);
  602. display: flex;
  603. align-items: center;
  604. justify-content: center;
  605. margin-bottom: 16rpx;
  606. }
  607. .goal-icon {
  608. font-size: 32rpx;
  609. color: #fff;
  610. font-weight: bold;
  611. }
  612. .goal-title {
  613. font-size: 28rpx;
  614. font-weight: bold;
  615. color: #333;
  616. margin-bottom: 6rpx;
  617. }
  618. .goal-desc {
  619. font-size: 22rpx;
  620. color: #999;
  621. text-align: center;
  622. line-height: 1.4;
  623. }
  624. .type-list {
  625. margin-top: 10rpx;
  626. }
  627. .type-card {
  628. display: flex;
  629. align-items: center;
  630. background: #fff;
  631. border-radius: 20rpx;
  632. padding: 28rpx 24rpx;
  633. margin-bottom: 20rpx;
  634. box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
  635. border: 3rpx solid transparent;
  636. transition: all 0.3s;
  637. }
  638. .type-card--selected {
  639. border-color: #FFD700;
  640. background: #FFF8E1;
  641. }
  642. .type-icon-wrap {
  643. width: 72rpx;
  644. height: 72rpx;
  645. border-radius: 50%;
  646. background: linear-gradient(135deg, #FFD700, #FFA500);
  647. display: flex;
  648. align-items: center;
  649. justify-content: center;
  650. margin-right: 20rpx;
  651. flex-shrink: 0;
  652. }
  653. .type-icon {
  654. font-size: 28rpx;
  655. color: #fff;
  656. font-weight: bold;
  657. }
  658. .type-info {
  659. flex: 1;
  660. }
  661. .type-name {
  662. font-size: 28rpx;
  663. font-weight: bold;
  664. color: #333;
  665. display: block;
  666. }
  667. .type-desc {
  668. font-size: 22rpx;
  669. color: #999;
  670. margin-top: 4rpx;
  671. display: block;
  672. }
  673. .type-radio {
  674. width: 36rpx;
  675. height: 36rpx;
  676. border-radius: 50%;
  677. border: 3rpx solid #ddd;
  678. display: flex;
  679. align-items: center;
  680. justify-content: center;
  681. flex-shrink: 0;
  682. margin-left: 16rpx;
  683. }
  684. .type-radio--checked {
  685. border-color: #FFD700;
  686. }
  687. .type-radio-dot {
  688. width: 20rpx;
  689. height: 20rpx;
  690. border-radius: 50%;
  691. background: #FFD700;
  692. }
  693. .progress-header {
  694. margin-bottom: 30rpx;
  695. }
  696. .progress-label {
  697. font-size: 26rpx;
  698. color: #666;
  699. display: block;
  700. margin-bottom: 12rpx;
  701. }
  702. .progress-bar {
  703. height: 8rpx;
  704. background: #eee;
  705. border-radius: 4rpx;
  706. overflow: hidden;
  707. }
  708. .progress-fill {
  709. height: 100%;
  710. background: linear-gradient(135deg, #FFD700, #FFA500);
  711. border-radius: 4rpx;
  712. transition: width 0.3s ease;
  713. }
  714. .question-card {
  715. background: #fff;
  716. border-radius: 20rpx;
  717. padding: 36rpx 28rpx;
  718. box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
  719. margin-bottom: 30rpx;
  720. }
  721. .q-tag {
  722. display: inline-block;
  723. padding: 6rpx 20rpx;
  724. background: #FFF8E1;
  725. color: #B8860B;
  726. font-size: 20rpx;
  727. border-radius: 20rpx;
  728. margin-bottom: 20rpx;
  729. }
  730. .q-text {
  731. font-size: 30rpx;
  732. color: #333;
  733. line-height: 1.6;
  734. display: block;
  735. margin-bottom: 30rpx;
  736. }
  737. .q-options {
  738. display: flex;
  739. flex-direction: column;
  740. gap: 16rpx;
  741. }
  742. .q-option {
  743. display: flex;
  744. align-items: center;
  745. padding: 24rpx 20rpx;
  746. border-radius: 16rpx;
  747. border: 2rpx solid #eee;
  748. transition: all 0.3s;
  749. }
  750. .q-option--selected {
  751. border-color: #FFD700;
  752. background: #FFF8E1;
  753. }
  754. .q-option--tf {
  755. justify-content: center;
  756. padding: 28rpx;
  757. }
  758. .q-option-tf-text {
  759. font-size: 28rpx;
  760. font-weight: bold;
  761. color: #666;
  762. }
  763. .q-option--selected .q-option-tf-text {
  764. color: #B8860B;
  765. }
  766. .q-radio {
  767. width: 32rpx;
  768. height: 32rpx;
  769. border-radius: 50%;
  770. border: 3rpx solid #ddd;
  771. display: flex;
  772. align-items: center;
  773. justify-content: center;
  774. flex-shrink: 0;
  775. margin-right: 16rpx;
  776. }
  777. .q-radio--checked {
  778. border-color: #FFD700;
  779. }
  780. .q-radio-dot {
  781. width: 18rpx;
  782. height: 18rpx;
  783. border-radius: 50%;
  784. background: #FFD700;
  785. }
  786. .q-option-text {
  787. font-size: 26rpx;
  788. color: #333;
  789. flex: 1;
  790. }
  791. .action-bar {
  792. margin-top: 40rpx;
  793. display: flex;
  794. flex-direction: column;
  795. gap: 16rpx;
  796. }
  797. .action-bar--double {
  798. flex-direction: row;
  799. gap: 20rpx;
  800. }
  801. .action-bar--double .btn {
  802. flex: 1;
  803. }
  804. .btn {
  805. padding: 24rpx 0;
  806. border-radius: 16rpx;
  807. text-align: center;
  808. font-size: 28rpx;
  809. font-weight: bold;
  810. transition: all 0.3s;
  811. box-sizing: border-box;
  812. }
  813. .btn--primary {
  814. background: linear-gradient(135deg, #FFD700, #FFA500);
  815. color: #fff;
  816. }
  817. .btn--primary:active {
  818. opacity: 0.9;
  819. transform: scale(0.98);
  820. }
  821. .btn--disabled {
  822. opacity: 0.4;
  823. pointer-events: none;
  824. }
  825. .btn--outline {
  826. border: 3rpx solid #FFD700;
  827. color: #B8860B;
  828. background: #fff;
  829. }
  830. .btn--outline:active {
  831. background: #FFF8E1;
  832. }
  833. .btn--ghost {
  834. color: #999;
  835. background: transparent;
  836. }
  837. .result-header {
  838. display: flex;
  839. flex-direction: column;
  840. align-items: center;
  841. margin: 40rpx 0;
  842. }
  843. .score-circle {
  844. width: 160rpx;
  845. height: 160rpx;
  846. border-radius: 50%;
  847. background: linear-gradient(135deg, #FFD700, #FFA500);
  848. display: flex;
  849. align-items: center;
  850. justify-content: center;
  851. box-shadow: 0 8rpx 30rpx rgba(255,215,0,0.3);
  852. margin-bottom: 20rpx;
  853. }
  854. .score-value {
  855. font-size: 40rpx;
  856. font-weight: bold;
  857. color: #fff;
  858. }
  859. .score-label {
  860. font-size: 28rpx;
  861. color: #666;
  862. display: block;
  863. }
  864. .result-dims {
  865. margin: 20rpx 0 30rpx;
  866. }
  867. .dim-bar-item {
  868. margin-bottom: 24rpx;
  869. }
  870. .dim-bar-header {
  871. display: flex;
  872. justify-content: space-between;
  873. margin-bottom: 8rpx;
  874. }
  875. .dim-bar-name {
  876. font-size: 24rpx;
  877. color: #333;
  878. font-weight: bold;
  879. }
  880. .dim-bar-count {
  881. font-size: 22rpx;
  882. color: #999;
  883. }
  884. .dim-bar-track {
  885. height: 8rpx;
  886. background: #eee;
  887. border-radius: 4rpx;
  888. overflow: hidden;
  889. }
  890. .dim-bar-fill {
  891. height: 100%;
  892. background: linear-gradient(135deg, #FFD700, #FFA500);
  893. border-radius: 4rpx;
  894. transition: width 0.6s ease;
  895. }
  896. .bottom-spacer {
  897. height: 120rpx;
  898. }
  899. </style>